• 完整记录一下Web前端直传阿里OSS大文件+采用后端临时授权传stsToken的方式


    前言(可不看)

    最近一段时间领导让我跟踪研究一下云服务系统的文件上传功能。问题的背景是,①文件一旦超过100M以后上传耗时就变得很长;②超过500M以后出错的几率大大增加,用户体验极其不友好。
    已知旧版本在这一块使用的ota上传功能采用的是阿里的upload.js + plupload插件实现的。
    本篇优化使用STS,web端从后台获取临时授权token,调有用aliyun-oss-sdk api来实现。

    <dependency>
        <groupId>com.aliyungroupId>
        <artifactId>aliyun-java-sdk-stsartifactId>
        <version>2.1.6version>
    dependency>
    <dependency>
        <groupId>com.aliyungroupId>
        <artifactId>aliyun-java-sdk-coreartifactId>
        <version>3.2.10version>
    dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    1. 简介

    • 什么是oss ?
      阿里云对象存储服务(Object Storage Service,简称OSS),是阿里云对外提供的海量、安全、低成本、高可靠的云存储服务。我们在上传文件这一功能方面,总不可避免会遇到文件太大上传太慢乃至失败问题,oss就是阿里对该问题提供的解决方案。

    • 更详细介绍
      参考阿里云链接:oss产品简介

    2. 必要了解项

    2.1 资源术语

    中文英文说明
    存储空间bucket存储空间是您用于存储对象(Object)的容器,所有的对象都必须隶属于某个存储空间。对应到阿里云上就是你的bucket桶空间,用来存放文件等资源。
    对象/文件Object对象是 OSS 存储数据的基本单元,也被称为OSS的文件。对象由元信息(Object Meta)、用户数据(Data)和文件名(Key)组成。对象由存储空间内部唯一的Key来标识。
    地域Region地域表示 OSS 的数据中心所在物理位置。您可以根据费用、请求来源等综合选择数据存储的地域。详情请查看OSS已经开通的Region。
    访问域名EndpointEndpoint 表示OSS对外服务的访问域名。OSS以HTTP RESTful API的形式对外提供服务,当访问不同地域的时候,需要不同的域名。通过内网和外网访问同一个地域所需要的域名也是不同的。具体的内容请参见各个Region对应的Endpoint
    访问密钥AccessKeyAccessKey,简称 AK,指的是访问身份验证中用到的AccessKeyId 和AccessKeySecret。OSS通过使用AccessKeyId 和AccessKeySecret对称加密的方法来验证某个请求的发送者身份。AccessKeyId用于标识用户,AccessKeySecret是用户用于加密签名字符串和OSS用来验证签名字符串的密钥,其中AccessKeySecret 必须保密

    (PS: 我们本篇介绍的上传方式就要用到上述术语)

    2.2 常用SDK

    有很多,比如 Java、Node.js、Browser.js、等等。
    重点介绍一下与本篇相关的sdk。

    • Browser.js
    1. 使用前提:存储空间已配置跨域共享(CORS)规则(后面会详细给出)。
    2. 需要后端读取配置文件然后生成临时STSToken 返回给前端,前端才可以继续调用接口实现上传。

    在这里插入图片描述

    3. 准备工作

    3.1 创建bucket

    (我是优化的,所以我司服务器已有一堆bucket,这里贴出来方法)
    登录云账户,搜索框输入 对象存储OSS跳转进入页面,点击右侧,创建bucket。
    在这里插入图片描述

    3.2 设置跨域规则

    点击新建/已有的 bucket名称进入,设置跨域规则。
    在这里插入图片描述
    这里,照着操作就OK:
    在这里插入图片描述

    3.3 创建RAM子账户及配置权限

    !!! AccessKey Secret 只在第一次创建时可见!记不住丢了后面可别找我要哦!!!

    进入RAM访问控制——>用户——>创建用户。勾选 api调用,名称随意(假设命名为STS)。
    在这里插入图片描述
    创建好后,要给它分配权限:
    在这里插入图片描述
    点击名称进入详情——>查看权限管理——>新增授权,我们给它加一个支持调用阿里STS服务 AssumeRole接口的权限,这样后台才可以凭借该用户身份分配STS token给前端。
    在这里插入图片描述
    点击认证管理,我们要牢牢记住该用户的 AccessKeyId 和 AccessKey Secret ,尤其secret,只在创建用户时可见,之后忘了是无法找到的。

    3.4 创建RAM角色

    我们还需要在RAM控制页面 新建一个角色并分配权限。
    作用:使得前端 有权限调用aliyun-oss-sdk进行文件上传(写入桶空间)。
    点击创建角色,这里我命名为:AliyunRAM-OSSRole,你们随意。

    在这里插入图片描述
    在这里插入图片描述
    角色即我们上面的角色名。创建完成。
    在这里插入图片描述
    然后点击名称进入,我们还得分配权限,什么权限呢?
    在这里插入图片描述
    当然是以上两个针对OSS管理/访问的系统策略咯~

    注:还可以自定义权限策略并分配给该角色,我偷懒了,没有,在这里贴出来别人的一份自定义策略,只涉及上传相关的权限,可用性未知。

    {
      "Version": "1",
      "Statement": [
        {
          "Effect": "Allow",
          "Action": [
            "oss:PutObject",
            "oss:InitiateMultipartUpload",
            "oss:UploadPart",
            "oss:UploadPartCopy",
            "oss:CompleteMultipartUpload",
            "oss:AbortMultipartUpload",
            "oss:ListMultipartUploads",
            "oss:ListParts"
          ],
          "Resource": [
            "acs:oss:*:*:mudontire-test",
            "acs:oss:*:*:mudontire-test/*"
          ]
        }
      ]
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    哈哈,到这里我们的一堆配置就OK了,接下来就是我们喜闻乐见的代码环节了!

    4. 后端配置及提供token操作

    4.1 配置文件

    在你的config.properties(名字按你的来)中,配置如下:

    oss.access_id=xxx
    oss.access_secret=xxx
    oss.bucket_name=xxx
    oss.region=xxx
    
    • 1
    • 2
    • 3
    • 4

    你看这里就用到专业术语了吧,不会去复习~

    4.2 OssGetToken

    解释:
    ① MDMProperies 是我的配置类,读取配置信息。
    ② 关于 roleArn,你在上面创建的角色基本信息里就可以看到。
    在这里插入图片描述
    ③关于roleSessionName ,好像可以随意命名,记不清了,你这里跟我一样得了。(貌似为空不行)
    ④ 临时token有效时间 900s <= token <= 3600s
    ⑤ 注意 AssumeRoleRequest,jar版本不同,调用也不同。我这里是
    在这里插入图片描述
    就是我前言放的配置!

    @Component
    public class OssGetStsToken {
        private static final Logger logger = LoggerFactory.getLogger(OssGetStsToken.class);
    
        private static String accessKeyId = MDMProperies.ossBigAccessId;
        private static String accessKeySecret = MDMProperies.ossBigAccessSecret;
        private static final String roleArn = "acs:ram::xxx/aliyunram-ossrole";
        private static final String roleSessionName = "alice";
        private static final String bucketName = MDMProperies.ossBigBucketName;
        private static final String region = MDMProperies.ossBigRegion;
    
        /**
         *  临时授权有效时间900-3600s,token失效时间, 单位秒
         */
        private static final Long durationSeconds = 900L;
    //    private static final String ENDPOINT = MDMProperies.ossEndpoint;
        private static final String ENDPOINT = "sts.aliyuncs.com";
    
        /**
         * 获取STStoken接口
         */
        public static StsTokenVO getStsToken() {
            StsTokenVO tokenVO = new StsTokenVO();
            try {
                // 添加endpoint(直接使用STS endpoint,前两个参数留空,无需添加region ID)
                DefaultProfile.addEndpoint("", "", "Sts", ENDPOINT);
                // 进行角色授权 构造default profile(参数留空,无需添加region ID)
                IClientProfile profile = DefaultProfile.getProfile("", accessKeyId, accessKeySecret);
                // 用profile构造client
                DefaultAcsClient client = new DefaultAcsClient(profile);
                final AssumeRoleRequest request = new AssumeRoleRequest();
                request.setMethod(MethodType.POST);
                request.setRoleArn(roleArn);  // role-Arn
                request.setRoleSessionName(roleSessionName);
                 request.setDurationSeconds(durationSeconds);  // 3600s
                // 针对该临时权限可以根据该属性赋予规则,格式为json,没有特殊要求,默认为空
                // request.setPolicy(policy); // Optional
                final AssumeRoleResponse response = client.getAcsResponse(request);
                AssumeRoleResponse.Credentials credentials = response.getCredentials();
                tokenVO.setAccessKeyId(credentials.getAccessKeyId());
                tokenVO.setAccessKeySecret(credentials.getAccessKeySecret());
                tokenVO.setSecurityToken(credentials.getSecurityToken());
                tokenVO.setBucketName(bucketName);
                tokenVO.setRegion(region);
                tokenVO.setExpiration(durationSeconds);
                logger.info("tokenVO——> :"+tokenVO);
                return tokenVO;
            } catch (ClientException e) {
                logger.error("获取阿里云STS临时授权权限失败,错误信息:"+e);
                throw new RuntimeException("获取阿里云STS临时授权权限失败,错误信息:" +e);
            }
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54

    StsTokenVO:

    @Data
    public class StsTokenVO implements Serializable {
        /**
         * 访问密钥标识
         */
        private String accessKeyId;
        /**
         * 访问密钥
         */
        private String accessKeySecret;
        /**
         * 安全令牌
         */
        private String securityToken;
        /**
         * oss-bucket  桶名称
         */
        private String bucketName;
        /**
         * token过期时间
         */
        private Long expiration;
        /**
         * 桶所在的区域
         */
        private String region;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    然后剩余的 service啦、控制层接口之类的这里就省略了。因项目而异。

    5. 前端(html)

    后端传给前端的就是 上面的StsTokenVO的json数据。

    那么我们来看前端:

    <div class="row">
                <div id="up_wrap" style="margin-left: 420px;width: 800px;"></div>
    
                <div class="col-sm-offset-5 col-sm-10">
                    <button type="button" class="btn btn-sm btn-warning" id="pause"><i class="fa fa-close"> </i> <label class="language_label">Pause</label></button>&nbsp;
                    <button type="button" class="btn btn-sm btn-success" id="resume"><i class="fa fa-cloud-upload"> </i> <label class="language_label">Resume upload</label></button>&nbsp;
                    <button type="button" class="btn btn-sm btn-primary" id="submit"><i class="fa fa-check"></i><label class="language_label">Save</label></button>&nbsp;
                    <button type="button" class="btn btn-sm btn-danger" id="closeSubmit" onclick="closeItem()"><i class="fa fa-reply-all"></i><label class="language_label">Close</label> </button>
                </div>
            </div>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    样式如下(关闭按钮按照你们自己的逻辑来):
    在这里插入图片描述
    哦对了,还有个文件上传选择器:

    <div id="firmware" class="input-group">
        <input type="file" class="form-control" id="fileName" multiple="true">
    </div>
    
    • 1
    • 2
    • 3

    关于这个type=file 原生的太丑的问题,我另开一篇介绍美化。

    Script引入:

    <script type="text/javascript"
            src="https://gosspublic.alicdn.com/aliyun-oss-sdk-6.16.0.min.js"></script>
    <script type="text/javascript">
    
    • 1
    • 2
    • 3

    剩余代码:

    	var prefix = ctx + "biz/firmware_ota";
        var getStsTokenURL = ctx + 'biz/sts_token!token.action';
        var bucket = ""; // bucket 桶名称 (初始化client时获取)
        var region = '';  // oss bucket所在地域名称
        let ossClient = null;  // 定义 oss客户端实例变量
        let credentials = null;  // 变量接收stsToken
        let tempCheckPoint = null; //数组,记录了已经完成上传的分片及其对应的etag
        const checkpoints = {};
        // 定义中断点
        //let abortCheckPoint;
        // 获取上传DOM。
        const submit = document.getElementById("submit");
        // 获取中断dom
        const pause = document.getElementById("pause");
        // 获取续传dom
        const resume = document.getElementById("resume");
    
    // 获取STS Token
        function getCredential() {
            return fetch(getStsTokenURL)
                .then(res => {
                    return res.json()
                })
                .then(res => {
    
                    console.log(JSON.stringify(res));  // 转为json字符串
                    credentials = res.value;
                    console.log("credentials:"+credentials);
                })
                .catch(err => {
                    console.error(err);
                });
        }
    
    // 客户端初始化
    async function initOSSClient() {
        const { accessKeyId, accessKeySecret, securityToken, bucketName } = credentials;
        bucket = credentials["bucketName"];
        region = credentials["region"];
        //console.log("bucket = "+bucket);
        ossClient = new OSS({
            accessKeyId: accessKeyId,
            accessKeySecret: accessKeySecret,
            stsToken: securityToken,
            //secure:true,
            bucket: bucketName,
            region
        });
    }
    
    const headers = {
            // 指定该Object被下载时的网页缓存行为。
            "Cache-Control": "no-cache",
            // 指定该Object被下载时的名称。
            //"Content-Disposition": "example.txt",
            // 指定该Object被下载时的内容编码格式。
            "Content-Encoding": "utf-8",
            // 指定过期时间,单位为毫秒。
            //Expires: "1000",
            "Access-Control-Allow-Origin": "*",
            // 指定Object的存储类型。
            //"x-oss-storage-class": "Standard",
            // 指定Object标签,可同时设置多个标签。
            "x-oss-tagging": "Tag1=1&Tag2=2",
            // 指定初始化分片上传时是否覆盖同名Object。此处设置为true,表示禁止覆盖同名Object;存在相同会报错
            "x-oss-forbid-overwrite": "true",
            "Content-Type": 'application/x-www-form-urlencoded'
        };
    
    const options = {
        // 获取分片上传进度、断点和返回值。
        progress: (p, cpt, res) => {
            tempCheckPoint = cpt;
            console.log(p);
            //console.log(tempCheckPoint);  // 测试输出part和etag
        },
        // 设置并发上传的分片数量。
        parallel: 4,
        // 设置分片大小。默认值为1 MB,最小值为100 KB。
        partSize: 1024 * 1024 * 1,  // 设为1MB
        headers,
        // 自定义元数据,通过HeadObject接口可以获取Object的元数据。
        //meta: { year: 2020, people: "test" },
        mime: "text/plain",
        timeout: 120000  // 设置超时时间
    };
    
    // 普通上传
        async function commonUpload(file) {
            if (!ossClient) {
                await initOSSClient();
            }
            const fileName = file.name;
            $("#fileLength").val(file.size);
            return ossClient.put(fileName, file, {headers: { 'x-oss-forbid-overwrite': true }}).then(result => {
                console.log(`Common upload ${file.name} succeeded, result === `, result)
                const url = `https://${bucket}.${region}.aliyuncs.com/${fileName}`;
                // 获取下载文件的url
                $("#firmwareUrl").val(url);
                $.operate.saveTab(prefix + "!doAddInfo.action", $('#form-info-add').serialize());  // 保存上传记录到后台数据库
                console.log("save success...");
            }).catch(err => {
                console.log(`Common upload ${file.name} failed === `, err);
            });
        }
    
    // 分片上传
        let retryCount = 0;
        let retryCountMax = 3;
        const uploadFile = function uploadFile(client) {
            if (!ossClient || Object.keys(ossClient).length === 0) {
                ossClient = client;
            }
        }
    
    async function multipartUpload(file) {
            if (!ossClient) {
                await initOSSClient();
            }
            const fileName = file.name;
            $("#fileLength").val(file.size);
            return ossClient.multipartUpload(fileName, file, {
                parallel: options.parallel,
                partSize: options.partSize,
                progress: onMultipartUploadProgress,
                headers,
                timeout: 120000  // 设置超时时间2min
            }).then( result => {
                console.log("upload success: ", result);
                const url = `https://${bucket}.${region}.aliyuncs.com/${fileName}`;
                console.log(`Multipart upload file ${file.name} success, url = `, url);
                $("#firmwareUrl").val(url);  // 赋值url
                $.operate.saveTab(prefix + "!doAddInfo.action", $('#form-info-add').serialize());  // 保存上传记录到后台数据库,我自己的需求。这一步看你们需求,只上传就没必要
                console.log("save success...");
                ossClient = null;
            }).catch(err => {
                if (ossClient && ossClient.isCancel()) {
                    console.log("stop-upload!");
                } else {
                    console.log(checkpoints);
                    console.log(`Multipart upload ${file.name} failed === `, err);
                    // retry 重试机制 3次
                    if (retryCount < retryCountMax) {
                        retryCount++;
                        console.error("retryCount: " + retryCount);
                        uploadFile('');
                        
                    }
                }
            })
        }
    
    	var oldDate = null;
        // 分片上传进度改变回调
    	async function onMultipartUploadProgress(progress, checkpoint) {
    	    //console.log(`${checkpoint.file.name} 上传进度 ${progress}`);
    	    checkpoints[checkpoint.uploadId] = checkpoint;
    	
    	    let ps = parseInt((progress.toFixed(2)) * 100);
    	    //console.log("上传进度:", ps + '%');
    	    //console.log("cpt:", checkpoint);
    	    let html = '';
    	    html = '
    + ps + '%">' + ps + '%
    '
    ; $("#up_wrap").html(html); // 判断STS Token是否将要过期,过期则重新获取 const { expiration } = credentials; //console.log("token 过期时间:"+expiration); //console.log("oldDate:"+oldDate); if (oldDate === null) { oldDate = new Date().getTime() + (expiration * 1000); // localStorage.setItem('tokenTime', oldDate); //console.log("tokenTime:"+oldDate); } let tokenTime = localStorage.getItem("tokenTime"); //console.log("localStorage tokenTime:"+tokenTime); if (tokenTime !== null){ const tempTime = 60 * 1000; if ((tokenTime - new Date().getTime()) <= tempTime) { // 距离token过期小于1min时暂停上传重新获取token后再续传 console.log(`STS token will expire in ${tempTime/(60*1000)} minutes,uploading will pause and resume after getting new STS token`); if (ossClient) { ossClient.cancel(); } await getCredential(); await resumeMultipartUpload(); } } // 断点续传 async function resumeMultipartUpload() { Object.values(checkpoints).forEach((checkpoint) => { const { uploadId, file, name } = checkpoint; console.log("uploadId:"+uploadId); console.log("file:"+file); console.log("name:"+name); ossClient.multipartUpload(uploadId, file, { parallel: options.parallel, partSize: options.partSize, progress: onMultipartUploadProgress, checkpoint }).then(result => { console.log('before delete checkpoints === ', checkpoints); delete checkpoints[checkpoint.uploadId]; console.log('after delete checkpoints === ', checkpoints); const url = `https://${bucket}.${region}.aliyuncs.com/${name}`; console.log(`Resume multipart upload ${file.name} succeeded, url === `, url) $("#firmwareUrl").val(url); $.operate.saveTab(prefix + "!doAddInfo.action", $('#form-info-add').serialize()); // 保存上传记录到后台数据库 console.log("save success..."); }).catch(err => { console.log('Resume multipart upload failed === ', err); }); }); } submit.addEventListener("click", async () => { try { const file = document.getElementById("fileName").files[0]; //console.log("data=" +data); //采用时间戳重命名 var last=file.name.substr(file.name.lastIndexOf("."),file.name.length) var fileName=Date.parse(new Date()) + last; console.log("file name:"+file.name); console.log("submit filename="+fileName); console.log("file Size: "+file.size); console.log("file Type: "+file.type); // 获取STS Token await getCredential(); await initOSSClient(); // 如果文件大小小于分片大小,使用普通上传,否则使用分片上传 if (file.size < options.partSize) { console.log("普通上传..."); await commonUpload(file); } else { console.log("大于100M大文件分片上传..."); await multipartUpload(file); } } catch (err) { console.log(err); } }); pause.addEventListener("click", () => { // 暂停上传 if (ossClient) ossClient.cancel(); console.log("暂停上传......"); }) // 监听续传按钮,单击“恢复上传”后继续上传 resume.addEventListener("click", async () => { console.log("断点续传中......"); await resumeMultipartUpload(); })
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
    • 225
    • 226
    • 227
    • 228
    • 229
    • 230
    • 231
    • 232
    • 233
    • 234
    • 235
    • 236
    • 237
    • 238
    • 239
    • 240
    • 241
    • 242
    • 243
    • 244
    • 245
    • 246
    • 247
    • 248
    • 249
    • 250
    • 251
    • 252
    • 253
    • 254

    6. 总结后记

    1. 强烈建议不要直接将 AccessKey 密钥信息暴露在前端,安全性问题,老老实实使用后端提供STS Token的方法来保证。
    2. 关于Java SDK,新建ossClient有多种方式,使用OSS域名、使用自定义域名、专有云或专有域环境、使用IP和使用STS。
    3. 关于Browser.js,使用它的前提限制必须是 STS,涉及到RAM子账户和角色。即,如果你想用阿里云主账户的密钥,抱歉,不行。
    4. 有关实践过程遇到的问题,可以参考我这篇记录:错误记录
    5. 更新1条,web删除上传记录,同步删除存储空间的文件 这个操作,采用的是后端new ossClient删除的,经测试,初始化client参数获取云账户 主账户的accesskeyId、accessKeySecret就可以同步删除(这里不是必须RAM子账户的密钥)
    6. 更新1条,分片上传规定分片大小 100kb ~ 5GB,分片数量 <=10000,最大上传文件<= 48.8TB。自己测试当上传文件超出 最大分片数量1W时,并没有报错抛出异常,而是会自动重新设置分片大小来保证在1W分片数量内,使得成功上传。例子如下:
      在这里插入图片描述

    2022.11.10 再次更新1条:

    1. 最近有地方遇到网络特别差的情况下,上传文件用时超过了我在后台获取stsToken设置的有效期15min。然而按照我上述给出的代码,即:
    if ((tokenTime - new Date().getTime()) <= tempTime) {  // 距离token过期小于1min时暂停上传重新获取token后再续传
        console.log(`STS token will expire in ${tempTime/(60*1000)} minutes,uploading will pause and resume after getting new STS token`);
        if (ossClient) {
            ossClient.cancel();
        }
        await getCredential();
        await resumeMultipartUpload();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    异步获取后台新的stsToken失败,出现错误,调试web发现并没有获取到新的token。修改无果,最终决定改掉这个逻辑,发现ali-oss有更简便的方式来实现自动重新获取stsToken 。(PS:我上面的方式也是参考网上资料的,是通过获取后台token携带的有效期在web端判断1min内过期然后人为再次调用获取token的getCredential方法。但是实际上行不通,应该是异步方法并没有真正的异步执行吧,然后因为是在进度改变回调方法里,感觉不伦不类的。(doge))

    !!!那么重点来了,以我上述程序为基础,修改部分如下:
    ① 去掉onMultipartUploadProgress方法里的判断过期重获token这一段代码:

    // 判断STS Token是否将要过期,过期则重新获取
    	    const { expiration } = credentials;
    	    //console.log("token 过期时间:"+expiration);
    	    //console.log("oldDate:"+oldDate);
    	    if (oldDate === null) {
    	        oldDate = new Date().getTime() + (expiration * 1000);  //
    	        localStorage.setItem('tokenTime', oldDate);
    	        //console.log("tokenTime:"+oldDate);
    	    }
    	    let tokenTime = localStorage.getItem("tokenTime");
    	    //console.log("localStorage tokenTime:"+tokenTime);
    	    if (tokenTime !== null){
    	        const tempTime = 60 * 1000;
    	        if ((tokenTime - new Date().getTime()) <= tempTime) {  // 距离token过期小于1min时暂停上传重新获取token后再续传
    	            console.log(`STS token will expire in ${tempTime/(60*1000)} minutes,uploading will pause and resume after getting new STS token`);
    	            if (ossClient) {
    	                ossClient.cancel();
    	            }
    	            await getCredential();
    	            await resumeMultipartUpload();
    	        }
    	    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    ② 修改初始化 oss客户端的方法:

    // 初始化oss客户端增加token过期重新获取
    async function initOSSClient() {
        const { accessKeyId, accessKeySecret, securityToken, bucketName } = credentials;
        bucket = credentials["bucketName"];
        region = credentials["region"];
        //console.log("bucket = "+bucket);
        ossClient = new OSS({
            accessKeyId: accessKeyId,
            accessKeySecret: accessKeySecret,
            stsToken: securityToken,
            secure:true,
            bucket: bucketName,
            region,
            refreshSTSToken: async () => {
                await getCredential();
                //console.log("credentials = "+JSON.stringify(credentials))
                return {
                    accessKeyId: credentials["accessKeyId"],
                    accessKeySecret: credentials["accessKeySecret"],
                    stsToken: credentials["securityToken"],
                }
            },
            refreshSTSTokenInterval: 900 * 1000   // 单位ms,这里设置15min自动获取新的token
        });
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    这两个参数是sdk提供的,所以根本不需要我们自己主动重新获取,只要设置好refreshSTSTokenInterval,然后就不用管了。这才是简便易用的打开方式!已经过实测,超过15min会获取新的token,各位实测的时候取消console注释,上传过程中断网,超过有效期再联网恢复上传,web控制台会有输出console内容。文件顺利续传成功。

    1. 好奇如何可以超过最长1小时有效期,提了个工单得到的答复如下,记录一下:

    DurationSeconds 默认最大是 3600s 也就是一小时,如果要超过一小时,需要设置 CreateRole或UpdateRole 接口中MaxSessionDuration 的时间

    ===================================================================================

    参考文章:

    1. ali-oss简单上传和分片上传
    2. 阿里云oss 前端通过stsToken,普通上传、分片上传、断点续传(单文件和批量上传)
    3. ali-oss官方文档
    4. 大文件上传阿里oss
    5. 前端oss上传vue完整版
    6. 阿里云oss文件分片断点续传
    7. 阿里云oss前后端实现
  • 相关阅读:
    【智能优化算法】基于移动阻尼波算法求解单目标优化问题附matlab代码
    linux中sshd是什么(ssh服务无法启动解决办法)
    契约测试理论篇
    go-micro教程 — 第一章 快速入门
    在.NET程序中整合微软的Playwright,使用 Playwright 的最佳实践和技巧
    C#【必备技能篇】使用GDI绘制进度条的代码实现
    NRF52840 SOC 在空气净化市场应用的发展趋势
    2.5 OJ 网站的使用与作业全解
    Apache IoTDB v1.2.0/v1.2.1 发布|增加流处理框架、动态模板等新功能
    springboot+redis+阿里云短信实现手机号登录
  • 原文地址:https://blog.csdn.net/qq_36256590/article/details/125913198