美文网首页
uniapp阿里云STS上传文件

uniapp阿里云STS上传文件

作者: Lengff | 来源:发表于2021-08-20 16:55 被阅读0次

    uniapp阿里云STS上传文件

    需求

    使用uniapp开发的时候就会遇到有上传图片视频之类的需求,但是我们实际的服务器带宽是有限的,而且如果是先上传到服务器在从服务器上传到OSS也是没有太大意义,所以我们就可以直接从APP里面将文件上传到OSS服务器,然后保存上传后的文件地址即可。

    用户上传文件 ---> OSS服务器 ---> [得到文件的URL] ---> 后台服务器记录文件信息
    

    问题所在

    1. 我们使用uniapp作为APP开发工具,阿里云OSS提供的一些SDK不兼

    一开始直接用官方提供node.js的SDK (ali-oss) 处理文件的时候是没有任何问题的,可以直接上传文件,且配置比较简单,只需要后台返回一些配置信息即可,但是后面我打包APP的时候发现出现错误,页面打不开;看日志也只能看到一些粗略的东西,大概意思就是里面使用了dom对象所以没办法初始化。如果是不需要打包app的话是可以直接使用的,可以参考这个文档 上传本地文件

    2. 后端需要单独开发一个给前端调用OSS接口获取token的接口

    意思就是我们不能直接把我们的accessKeyId 和 accessKeySecret 返回给前台调用,这样不安全。为此OSS也提供了临时访问凭证,配置起来比较麻烦,大家可以参考这个文档 使用STS临时访问凭证访问OSS;跟着文档一步一步来还是可以搞定的。

    可能遇到的问题

    1. OSS老是提示跨域

    这里就正常参照文档提示的跨域配置即可, 参考官方提供的文档 设置跨域规则后调用OSS时仍然报“No 'Access-Control-Allow-Origin'”的错误 , 重点来了一定一定使用的上传的地址是你的Bucket域名 而非 Endpoint 这里我是被坑的最惨的时候了,老是提示跨域一直搞一直错。

    2. uniapp的代码问题

    就是导入库存在的各种问题,这里目前我遇到的就是使用js-base64 这个库的时候一直都是不行的,我也一直都没有搞定,找了别人的一段代码搞定的,不用 js-base64

    代码部分

    这里就只提供uniapp相关的前端代码,后端代码其实比较简答,主要就是各种阿里云上的配置比较麻烦,有需要可以照着文档一步一步来。

    1. 需要引用的依赖

    npm install js-base64

    具体使用参考 https://www.npmjs.com/package/js-base64

    npm install crypto-js

    这两个都是用来做加密的,js-base64可以用或者不用

    2. 前端代码

    上传文件入口程序

    <template>
      <view>
        <button type="default" @click="selectImages">这是一个大大的按钮</button>
      </view>
    </template>
    <script>
    
    import UploadOss from 'utils/UploadOss'
    export default {
      methods: {
        /**
         * 选择上传图片
         */
        selectImages() {
          uni.chooseImage({
            //默认9
            count: 1,
            //可以指定是原图还是压缩图,默认二者都有
            sizeType: ['original', 'compressed'],
            //从相册选择
            sourceType: ['album'],
            success: function (res) {
              console.log(res);
              UploadOss.uploadFile(res).then(res => {
                console.log('上传文件的结果为:', res)
              })
            }
          });
        }
      }
    }
    </script>
    
    
    

    base64.js

    这里主要是因为我使用 js-base64 库会出问题, 如果你的不会则不需要此段代码,可以直接使用js-base64的库

    // 私钥
    const _keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
    // 加密
    const encode = function (input) {
        var output = "";
        var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
        var i = 0;
        input = _utf8_encode(input);
        while (i < input.length) {
            chr1 = input.charCodeAt(i++);
            chr2 = input.charCodeAt(i++);
            chr3 = input.charCodeAt(i++);
            enc1 = chr1 >> 2;
            enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
            enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
            enc4 = chr3 & 63;
            if (isNaN(chr2)) {
                enc3 = enc4 = 64;
            } else if (isNaN(chr3)) {
                enc4 = 64;
            }
            output = output +
                _keyStr.charAt(enc1) + _keyStr.charAt(enc2) +
                _keyStr.charAt(enc3) + _keyStr.charAt(enc4);
        }
        return output;
    }
    // 解密
    const decode = (input) => {
        var output = "";
        var chr1, chr2, chr3;
        var enc1, enc2, enc3, enc4;
        var i = 0;
        if (input == undefined || input == null) {
    
        } else {
            input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");
            while (i < input.length) {
                enc1 = _keyStr.indexOf(input.charAt(i++));
                enc2 = _keyStr.indexOf(input.charAt(i++));
                enc3 = _keyStr.indexOf(input.charAt(i++));
                enc4 = _keyStr.indexOf(input.charAt(i++));
                chr1 = (enc1 << 2) | (enc2 >> 4);
                chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
                chr3 = ((enc3 & 3) << 6) | enc4;
                output = output + String.fromCharCode(chr1);
                if (enc3 != 64) {
                    output = output + String.fromCharCode(chr2);
                }
                if (enc4 != 64) {
                    output = output + String.fromCharCode(chr3);
                }
            }
            output = _utf8_decode(output);
            return output;
        }
    
    }
    // private method for UTF-8 encoding
    const _utf8_encode = (string) => {
        string = string.replace(/\r\n/g, "\n");
        var utftext = "";
        for (var n = 0; n < string.length; n++) {
            var c = string.charCodeAt(n);
            if (c < 128) {
                utftext += String.fromCharCode(c);
            } else if ((c > 127) && (c < 2048)) {
                utftext += String.fromCharCode((c >> 6) | 192);
                utftext += String.fromCharCode((c & 63) | 128);
            } else {
                utftext += String.fromCharCode((c >> 12) | 224);
                utftext += String.fromCharCode(((c >> 6) & 63) | 128);
                utftext += String.fromCharCode((c & 63) | 128);
            }
        }
        return utftext;
    }
    // private method for UTF-8 decoding
    const _utf8_decode = (utftext) => {
        var string = "";
        var i = 0;
        var c = c1 = c2 = 0;
        var c1 = 0;
        var c2 = 0;
        var c3 = 0;
        while (i < utftext.length) {
            c = utftext.charCodeAt(i);
            if (c < 128) {
                string += String.fromCharCode(c);
                i++;
            } else if ((c > 191) && (c < 224)) {
                c2 = utftext.charCodeAt(i + 1);
                string += String.fromCharCode(((c & 31) << 6) | (c2 & 63));
                i += 2;
            } else {
                c2 = utftext.charCodeAt(i + 1);
                c3 = utftext.charCodeAt(i + 2);
                string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
                i += 3;
            }
        }
        return string;
    }
    
    module.exports = {
        decode: decode,
        encode: encode
    };
    
    

    上传文件JS文件

    具体业务逻辑JS中有描述,这里不做赘述

    const crypto = require('crypto-js')
    // 这里主要是因为我使用 js-base64 库会出问题, 如果你的不会则不需要此段代码,可以直接使用js-base64的库
    // const base64 = require('js-base64')
    const base64 = require('./base64s')
    // 获取STS参数信息的接口地址
    const tokenurl = 'https://baicu.com/gettoken';
    // 本地缓存数据的key
    const cacheKey = "xxxxtoken";
    // 本地数据有效期
    const validtimes = 1000 * 100;
    
    /**
     * 上传文件
     * @param fileobj
     * @returns {Promise<unknown>}
     */
    export function uploadFile(fileobj) {
        const filename = fileobj.tempFiles[0].name;
        const filepath = fileobj.tempFilePaths[0];
        return new Promise((resolve, reject) => {
            getFormData(filename).then(data => {
                uni.uploadFile({
                    //后台返回的阿里云存储的上传地址
                    url: data.host,
                    filePath: filepath,
                    fileType: "image",
                    name: 'file',
                    formData: data.formdata,
                    success: res => {
                        uni.hideLoading();
                        uni.showToast({title: '上传成功', icon: 'success', duration: 2000});
                        console.log(data.host + data.formdata.key);
                        resolve(data.host + data.formdata.key);
                    },
                    fail: err => {
                        reject(err)
                        uni.hideLoading();
                        uni.showModal({
                            content: err.errMsg,
                            showCancel: false
                        });
                    }
                });
            })
        })
    }
    
    /**
     * 获取上传文件需要的formdata数据
     */
    const getFormData = (filename) => {
        // 先从本地获取token
        return new Promise((resolve => {
            getToken().then(res => {
                // 获取到的token为
                console.log('获取到的token为', res)
                const ossAccessKeyId = res.accessKeyId;
                const securityToken = res.stsToken;
                // 这里遇到过坑, 上传文件的路径一定是bucket域名
                const aliyunServerURL = 'https://' + res.bucket + '.' + res.endpoint;
                const policy = getPolicyBase64(5, 150)
                const signature = computeSignature(res.accessKeySecret, policy);
                const aliyunFileKey = getOssPath(filename)
    
                const formdata = {
                    //文件名
                    key: aliyunFileKey,
                    //后台获取超时时间
                    policy: policy,
                    //后台获取临时ID
                    OSSAccessKeyId: ossAccessKeyId,
                    // 如果是STS的话这里的参数必须要
                    'x-oss-security-token': securityToken,
                    //让服务端返回200,不然,默认会返回204
                    success_action_status: '200',
                    //后台获取签名
                    signature: signature
                };
                let data = {
                    host: aliyunServerURL,
                    formdata: formdata
                }
                resolve(data);
            })
        }))
    }
    
    /**
     * 获取token信息的方法
     */
    const getToken = () => {
        return new Promise((resolve, reject) => {
            // 先从缓存中获取数据
            const tempData = uni.getStorageSync(cacheKey);
            // 是否有效
            let isvalid = false;
            if (tempData && tempData !== '') {
                // 数据存在,判断数据是否过期
                const tokenObj = JSON.parse(tempData);
                // 判断是否过期 (createtime:是后端指点的创建时间字段)
                var diff = new Date().getTime() - Number.parseFloat(tokenObj.createtime);
                // 判断数据是否过期
                isvalid = diff < validtimes;
            }
    
            if (isvalid) {
                // 数据有效且未过期则使用缓存中的数据
                resolve(JSON.parse(tempData));
            } else {
                // 无有效数据或数据已过期则从新获取数据
                getToken4Api(cacheKey).then(res => {
                    resolve(res);
                })
            }
        })
    }
    
    
    /**
     * 从网关获取数据
     * @param cacheKey
     */
    const getToken4Api = (cacheKey) => {
        return new Promise((resolve, reject) => {
            uni.request({
                url: tokenurl,
                success: (res) => {
                    if (res.data.status === 200) {
                        // 将数据存到本地存储, 由于token信息是有有效时间的,
                        uni.setStorageSync(cacheKey, JSON.stringify(res.data.data))
                        resolve(res.data.data)
                    }
                },
                fail: (err => {
                    reject(err);
                })
            })
        })
    }
    
    /**
     * 设置OSS的存储路径
     * @param filename 文件名称
     * @returns {string}
     */
    const getOssPath = (filename) => {
        const current_timestamp = new Date().getTime()
        const upload_name = crypto.MD5(current_timestamp + Math.random() * 10000) + filename;
        const current_day = dateformat('YYYYmmdd', new Date())
        return 'files/' + current_day + '/' + upload_name
    }
    
    /**
     * 日期格式化方法
     * @param fmt ex: yyyy-MM-dd HH:mm:ss
     * @param date new Date()
     * @returns {*}
     */
    const dateformat = (fmt, date) => {
        let ret;
        const opt = {
            "Y+": date.getFullYear().toString(), // 年
            "m+": (date.getMonth() + 1).toString(), // 月
            "d+": date.getDate().toString(), // 日
            "H+": date.getHours().toString(), // 时
            "M+": date.getMinutes().toString(), // 分
            "S+": date.getSeconds().toString() // 秒
            // 有其他格式化字符需求可以继续添加,必须转化成字符串
        };
        for (let k in opt) {
            ret = new RegExp("(" + k + ")").exec(fmt);
            if (ret) {
                fmt = fmt.replace(ret[1], (ret[1].length === 1) ? (opt[k]) : (opt[k].padStart(ret[1].length, "0")))
            }
        }
        return fmt;
    }
    
    /**
     * 安全策略需要转成base64
     * @param timeout 超时时间
     * @param maxsize 文件大小限制
     * @returns {*}
     */
    const getPolicyBase64 = function (timeout, maxsize) {
        const date = new Date()
        // 限制参数的生效时间(单位:小时)。
        const timeOut = timeout || 1
        // 限制上传文件大小(单位:MB)。 1MB = 1048576B
        const maxSize = maxsize || 10
        date.setHours(date.getHours() + timeOut)
        const policyText = {
            expiration: date.toISOString(),
            conditions: [
                ['content-length-range', 0, maxSize * 1024 * 1024]
            ]
        }
        // policy必须为base64的string。
        return base64.encode(JSON.stringify(policyText))
    }
    
    /**
     * 计算签名
     * @param accessKeySecret
     * @param canonicalString
     * @returns {string}
     */
    const computeSignature = function (accessKeySecret, canonicalString) {
        return crypto.enc.Base64.stringify(
            crypto.HmacSHA1(canonicalString, accessKeySecret)
        )
    }
    
    
    module.exports = {
        uploadFile: uploadFile
    };
    
    
    

    尾巴

    uniapp上传文件到oss真的是搞死我了,一开始使用ali-oss我感觉so easy,但是打包才发现特别坑,查阅了网上很多的教程,大家描述的都挺模糊或者和我搞得模式不太一样,所以特意记录一下这个问题,希望能帮助有着和我一样问题的人。

    鸣谢

    小程序文件OSS直传阿里云

    阿里云对象上传服务器签名

    相关文章

      网友评论

          本文标题:uniapp阿里云STS上传文件

          本文链接:https://www.haomeiwen.com/subject/jobtiltx.html