金山云KS3 浏览器上传文件示例之Form表单

298 阅读2分钟

说明

因为IE低版本(比如IE8,IE9)对html5支持的不完善,为了在这些浏览器里面实现文件直传的功能,KS3提供PostObject接口,通过一个multipart/form-data 的格式,将文件上传到KS3服务器。采用Postobject方式提交数据,建议将生成签名的方法放在应用服务器上,前端传参获取签名。

本文重点介绍:Form表单上传

两种实现方式

方式一:后端计算签名(以nodejs为例)

1、后端服务代码

const ak = '***' // 替换成自己的ak
const sk = '***'// 替换成自己的sk
const endpoint = '***'

app.all('/post-policy', (req, res, next) => {
    var query = req.query;
    var now = Math.round(Date.now() / 1000);
    var exp = now + 900;// 过期时间,可自行设置
    var p = {
      'expiration': new Date(exp * 1000).toISOString(),
      // conditions配置,可根据需求更改,具体参考文档https://docs.ksyun.com/documents/948
      'conditions': [
          ['eq', '$key', query.key],
          ["eq", "$bucket", query.bucketName]
      ]
    }

    // 上传成功后的回调地址
    if(query.redirect) {
      p.conditions.push(['eq', '$redirect', query.redirect])
    }

    var policy = JSON.stringify(p);

    // 借助ks3 node sdk 生成signature
    const signature = KS3.auth.getFormSignature(sk, policy)

    res.json({
      type: 'success',
      result: {
        url: `http://${query.bucketName}.${endpoint}`, // ks3服务地址
        policyObj: JSON.parse(policy),// 为方便前端调试
        policy: Buffer.from(policy).toString('base64'),
        Ak: ak, 
        Signature: signature,
        redirect: query.redirect
        // securityToken: securityToken, // 如果使用临时密钥,则要返回
      }
    });
})
2、前端代码

```
 <html>
  <form id="form" target="submitTarget" action="" method="post" enctype="multipart/form-data" accept="*/*">
    <input id="fileSelector" name="file" type="file">
    <input id="submitBtn" type="button" value="提交">
  </form>
  <iframe id="submitTarget" name="submitTarget" style="display: none;" frameborder="0"></iframe>

  <div id="msg"></div>
  
  <script>
      (function () {
       var fileSelector = document.getElementById('fileSelector');
    var form = document.getElementById('form');
    var parseObj = function (obj) {
        let str = Object.keys(obj).map(key => `${key}=${encodeURIComponent(obj[key])}`).join('&')
        return str
    }
    
    // 获取签名
    var getPostPolicyCredentials = function (opt, callback) {
       // 替换成自己的服务地址
        var url = 'http://127.0.0.1:3000/post-policy?' + parseObj(opt);
        // fetch为自己对xmlhttpRequest的封装 可自行实现
        fetch('get', url).then(data => {
            callback('', data)
        }).catch(err => {
            callback('获取签名失败')
        })
    };

    // 监听上传完成
    var Key;
    var submitTarget = document.getElementById('submitTarget');
    var showMessage = function (err, data) {
        console.log(err || data);
        document.getElementById('msg').innerText = err ? err : ('上传成功,ETag=' + data.ETag);
    };
    submitTarget.onload = function () {
        var search;
        try {
            if (submitTarget.contentWindow.location.href) {
                showMessage('文件 ' + Key + ' 上传成功');
            }
        } catch (e) {
            console.error('search: ', e)
            showMessage('文件 ' + Key + ' 上传失败');
        }
    };

    var setFormField = function (key, value) {
        var el = document.getElementById(key);
        if (!el) {
            el = document.createElement('input');
            el.hidden = true;
            el.id = key;
            el.name = key;
            form.insertBefore(el, fileSelector);
        }
        el.setAttribute('value', value); // 需要保证 file 在表单最后
        el.value = value;
    };

    // 发起上传
    document.getElementById('submitBtn').onclick = function (e) {
        var filePath = document.getElementById('fileSelector').value;
        if (!filePath) {
            document.getElementById('msg').innerText = '未选择上传文件';
            return;
        }
        Key = 'post-' + filePath.match(/[\\\/]?([^\\\/]+)$/)[1]; // 这里指定上传目录和文件名

        // 替换成自己的回调地址
        var callbackUrl = location.origin + '/demo/empty.html'
        
        // 获取签名保护字段
        getPostPolicyCredentials({
            key: Key,
            redirect: callbackUrl
        }, function (error, data) {
            const res = JSON.parse(data)
            const credentials = res.result
            console.log('---> credentials: ',credentials)

            // 添加form节点
            setFormField('key', Key);

            // 使用 policy 签名保护格式
            credentials.securityToken && setFormField('X-Ksc-Security-Token', credentials.securityToken);
            setFormField('KSSAccessKeyId', credentials.Ak);
            setFormField('Signature', credentials.Signature);
            setFormField('policy', credentials.policy);
            setFormField('redirect', credentials.redirect);

            form.action = credentials.url;
            // 提交表单
            form.submit();

        });
    };
      
      })()
  
  </script>
 </html>

```

注:上述代码若想成功运行,自行实现getPostPolicyCredentials方法,替换ak、sk、endpoint、callbackUrl

3、运行结果

image.png

方式二:前端计算签名

依赖库:前端计算签名需要借助第三方库:

github.com/sytelus/Cry… 另外,还需base64将中文utf8编码具体的实现类,此处提供一份网上找到的源码仅供参考gitee.com/studyNeo/co…

示例代码:

<!doctype html>
<html lang="en">
<body>
<style>
    #form {
        display: inline-block;
        margin-top: 10px;
        border: 1px solid blue;
        padding: 10px;
    }
</style> 

<form id="form" target="submitTarget" action="" method="post" enctype="multipart/form-data" accept="*/*">
    <input id="fileSelector" name="file" type="file">
    <input id="submitBtn" type="button" value="提交">
</form>
<iframe id="submitTarget" name="submitTarget" style="display: none;" frameborder="0"></iframe>

<div id="msg"></div>
<script type="text/javascript" src="../lib/crypto1/crypto/crypto.js"></script>
<script type="text/javascript" src="../lib/crypto1/hmac/hmac.js"></script>
<script type="text/javascript" src="../lib/crypto1/sha1/sha1.js"></script>
<script type="text/javascript" src="../lib/base64.js"></script>
<script>
    (function () {
        // 请求用到的参数
        var ak = '' || '<your ak>'
        var sk = '' || '<your sk>'
        var bucketName = '<your bucket>'
        var endpoint = '<your endpoint>' || 'ks3-cn-beijing.ksyuncs.com'
        var Key = "post-403.png"; // 自行修改为上传的Key

        // 回调地址 为了更新页面 需自行修改
        var callbackUrl = location.origin + '/demo/empty.html'
        

        var fileSelector = document.getElementById('fileSelector');
        var form = document.getElementById('form');

        var getSignatureParams = function () {
            var now = Math.floor(Date.now() / 1000)
            var exp = now + 900
            console.log('now: ', now);
            var policyText = {
                "expiration": (new Date(exp * 1000)).toISOString(), //设置该Policy的失效时间,超过这个失效时间之后,就没有办法通过这个policy上传文件了
                "conditions": [
                    ["eq","$key",Key],
                    ["eq", "$bucket", bucketName],
                    ["eq", "$redirect", callbackUrl]
                ]
            };

            // 有关签名请详见文档https://docs.ksyun.com/documents/948
            console.log('policyText: ', policyText);
            var policyBase64 = Base64.encode(JSON.stringify(policyText))
            console.log('policyBase64: ', policyBase64);
            var bytes = Crypto.HMAC(Crypto.SHA1, policyBase64, sk, { asBytes: true }) ;
            var signature = Crypto.util.bytesToBase64(bytes);
            console.log('signature: ', signature);
            return {
                policyBase64,
                signature,
            }
        }
        
        // 监听上传完成
        var submitTarget = document.getElementById('submitTarget');
        var showMessage = function (err, data) {
            console.log(err || data);
            document.getElementById('msg').innerText = err ? err : ('上传成功,ETag=' + data.ETag);
        };
        submitTarget.onload = function () {
            var search;
            try {
                if (submitTarget.contentWindow.location.href) {
                    showMessage('文件 ' + Key + ' 上传成功');
                }
            } catch (e) {
                console.error('search: ', e)
                showMessage('文件 ' + Key + ' 上传失败');
            }
        };

        var setFormField = function (key, value) {
            var el = document.getElementById(key);
            if (!el) {
                el = document.createElement('input');
                el.hidden = true;
                el.id = key;
                el.name = key;
                form.insertBefore(el, fileSelector);
            }
            el.setAttribute('value', value); // 需要保证 file 在表单最后
            el.value = value;
        };

        // 发起上传
        document.getElementById('submitBtn').onclick = function (e) {

            if (ak == '<your ak>' || sk == '<your sk>' || bucketName == '<your bucket>') {
                alert('请修改配置参数')
                return
            }

            var filePath = document.getElementById('fileSelector').value;
            if (!filePath) {
                document.getElementById('msg').innerText = '未选择上传文件';
                return;
            }
            Key = 'post-' + filePath.match(/[\\\/]?([^\\\/]+)$/)[1]; // 这里指定上传目录和文件名

            const param = getSignatureParams()

            setFormField('key', Key);
            setFormField('KSSAccessKeyId', ak);
            setFormField('Signature', param.signature);
            setFormField('policy', param.policyBase64);
            setFormField('redirect', callbackUrl);

            form.action = 'http://' + bucketName + '.' + endpoint;
            // 提交表单
            form.submit();
        };
    })();
</script>

</body>
</html>

运行结果:

image.png

注意事项

  1. 因方式二ak、sk会以明文的形式存在代码中,存在一定的安全隐患,推荐使用方式一。
  2. 获取signature时参与计算的policy字段与表单字段除指定外需要保持一致,不可遗漏,否则会报403,具体说明详见docs.ksyun.com/documents/9…