美文网首页
OSS前端直传+后端签名

OSS前端直传+后端签名

作者: 你苍白了我的等待 | 来源:发表于2023-03-21 13:53 被阅读0次

    OSS前端直传+后端签名

    一、服务端签名后前端直传

    首先安装阿里云SDK Aliyun.OSS.SDK.NetCore

            public static string accessKeyId = "你的accessKeyId";
            public static string accessKeySecret = "你的accessKeySecret";
            public static string bucketName = "你的桶名称";
            public static string endpoint = "oss-cn-beijing.aliyuncs.com";
            public static int expireTime = 30;
            public Dictionary<string, string> GetPolicy(string fileName)
            {
                var dir = DateTime.Now.ToString("yyyyMMdd") + "/";
                // 构造OssClient实例。 endpoint 格式:https://oss-cn-beijing.aliyuncs.com
                var ossClient = new OssClient("https://" + endpoint, accessKeyId, accessKeySecret);
                var config = new PolicyConditions();
                config.AddConditionItem(PolicyConditions.CondContentLengthRange, 1, 1024L * 1024 * 1024 * 5);// 文件大小范围:单位byte
                config.AddConditionItem(MatchMode.StartWith, PolicyConditions.CondKey, dir);
                var expire = DateTimeOffset.Now.AddMinutes(30);// 过期时间
                // 生成 Policy,并进行 Base64 编码
                var policy = ossClient.GeneratePostPolicy(expire.LocalDateTime, config);
                var policyBase64 = Convert.ToBase64String(Encoding.UTF8.GetBytes(policy));
    
                // 计算签名
                var hmac = new HMACSHA1(Encoding.UTF8.GetBytes(accessKeySecret));
                var bytes = hmac.ComputeHash(Encoding.UTF8.GetBytes(policyBase64));
                var sign = Convert.ToBase64String(bytes);
                // 将签名和回调的内容,返回给前端
                var host = $"https://{bucketName}.{endpoint}";
                var key = $"{dir}{Guid.NewGuid()}/{fileName}";
                var fullUrl = $"https://{bucketName}.{endpoint}/{key}";
                var rt = new Dictionary<string, string>
                {
                    { "OSSAccessKeyId",accessKeyId},
                    { "Host",host },
                    { "key",key},
                    { "policy",policyBase64},
                    { "Signature",sign},
                    { "success_action_status","200"},
                    { "fullUrl",fullUrl },
                    {"expire",expire.ToString() }
                };
    
                return rt;
            }
    

    前端首先访问后端获取签名,获取签名后使用FromData的形式上传文件

    async startUpload() {
          // 获取后端签名和上传地址
          const res = await axios.get("http://localhost:5152/api/OSS/GetPolicy", {
            params: {
              name: this.file.name
            }
          });
          var formData = new FormData();
          formData.append("name", this.file.name);
          formData.append("OSSAccessKeyId", res.data.OSSAccessKeyId);
          formData.append("key", res.data.key);
          formData.append("policy", res.data.policy);
          formData.append("signature", res.data.Signature);
          formData.append("success_action_status", res.data.success_action_status);
          formData.append("file", this.file);
          axios
            .post(res.data.Host, formData, {
              headers: {
                "Content-Type": "multipart/form-data"
              },
              withCredentials: false
            })
            .then(res => {
              console.log(res);
            });
        }
    

    二、服务端STS签名前端分片上传+断点续传

    当文件过大时,考虑使用分片上传和断点续传的方式来上传文件到oss,这时我们就不能直接使用accesskeyId和accessKeySecret的方式来在前端上传,以免暴露我们的密钥,当然也不能直接使用第一种的方式进行签名(或许可以,没有找到示例,也没有研究出来),所以我们采用STStoken的方式签名,然后在前端使用阿里云提供的SDK进行文件上传。

    断点续传的思路是在每个分片上传的时候存储当前文件的上传进度,如果中间因为各种原因无法继续上传时,当用户重新上传同一个文件的时候,获取文件的上传进度,继续上传没有上传完的部分,而不是重新上传整个文件。为了确保断点续传前后上传的是同一个文件,我们使用md5作为存储进度的key值,如果是同一个文件,则续传,如果不是同一个文件,则从0开始上传。

    首先登录阿里云开通sts账户和权限。

    安装 aliyun-net-sdk-core和aliyun-net-sdk-sts sdk

    public Dictionary<string, string> GetSTSToken()
            {
                //此处使用sts账户的id和secret
                var AccessKeyID = "***";
                var AccessKeySecret = "***";
                string bucketName = "***";
                // ststoken
                IClientProfile profile = DefaultProfile.GetProfile("oss-cn-beijing", AccessKeyID, AccessKeySecret);
                DefaultAcsClient client = new DefaultAcsClient(profile);
                var request = new AssumeRoleRequest();
                request.RoleArn = "***";
                request.RoleSessionName = "xxx";//这里的名字随便写
                request.DurationSeconds = 3600;//过期时间
                var response = client.GetAcsResponse(request);
    
                var result = new Dictionary<string, string>
                {
                    {"AccessKeyId", response.Credentials.AccessKeyId},
                    {"AccessKeySecret",response.Credentials.AccessKeySecret },
                    {"SecurityToken",response.Credentials.SecurityToken },
                    {"Expiration",response.Credentials.Expiration },
                    {"BucketName",bucketName }
                };
    
                return result;
            }
    

    签名完成后,安装阿里云oss sdk

    npm install ali-oss;
    npm install spark-md5;
    
    <template>
      <div class="hello">
        <div>
          <input type="file" @change="fileChange" />
          <div>{{ progress }}</div>
        </div>
      </div>
    </template>
    #自行导入包,自行定义变量
    async fileChange(e) {
          this.file = e.target.files[0];
          this.uploadFile(this.file);
        },
    async uploadFile(file) {
          const objectKey = "xxx" + "/file/" + this.file.name;
          // 初始化 OSS 客户端 SDK
          await this.initOSSClient();
          this.resumeUpload(objectKey, file);
        }
    # 首先初始化oss 对象
        async initOSSClient() {
          const res = await axios.get("http://localhost:5152/api/OSS/GetSTSToken");
          console.log(res);
          const {
            AccessKeyId,
            AccessKeySecret,
            SecurityToken,
            BucketName
          } = res.data;
          this.bucketName = BucketName;
    
          this.client = new OSS({
            region: "oss-cn-beijing",
            accessKeyId: AccessKeyId,
            accessKeySecret: AccessKeySecret,
            stsToken: SecurityToken,
            bucket: BucketName
          });
        },
        # 断点上传
        async resumeUpload(objectKey, file) {
          //使用SparkMd5计算文件的md5值
          let md5 =await this.calculateFileMD5(file);
    
          let checkpoint = JSON.parse(
            window.localStorage.getItem("checkpoint_" + md5)
          );
          var _this = this;
          // 重试五次。
          for (let i = 0; i < 5; i++) {
            try {
              const result = await this.client.multipartUpload(objectKey, file, {
                checkpoint,
                async progress(percentage, cpt) {
                  checkpoint = cpt;
                  _this.progress = parseInt(percentage * 100);
                  // 将 checkpoint 保存到浏览器localstorage 中。
                  window.localStorage.setItem(
                    "checkpoint_" + md5,
                    JSON.stringify(checkpoint)
                  );
                }
              });
              // 删除本地保存的 checkpoint,如果此处不删除的话,上传成功后,用户无法再次上传同名文件
              window.localStorage.removeItem("checkpoint_" + md5);
    
              break; // 跳出当前循环。
            } catch (e) {
              console.log(e);
            }
          }
        },
        // 使用sparkMD5 计算文件md5
         calculateFileMD5(file, chunkSize = 2097152) {
          // chunkSize为分块大小,默认为2MB
          return new Promise((resolve, reject) => {
            const fileReader = new FileReader();
            let currentPosition = 0;
            const spark = new SparkMD5.ArrayBuffer();
    
            fileReader.onerror = function() {
              reject("文件读取失败!");
            };
    
            fileReader.onload = function() {
              spark.append(fileReader.result); // 将读取到的数据添加到MD5计算器中
    
              currentPosition += chunkSize;
              if (currentPosition < file.size) {
                // 文件还没读完,继续读取下一块
                loadNext();
              } else {
                // 文件读取完毕,计算MD5值并返回结果
                const hash = spark.end();
                resolve(hash);
              }
            };
    
            function loadNext() {
              const blob = file.slice(currentPosition, currentPosition + chunkSize);
              fileReader.readAsArrayBuffer(blob);
            }
    
            // 开始读取第一块
            loadNext();
          });
        }
    

    如果想自己控制上传的各步骤可以使用initiateMultipartUpload uploadPart completeMultipartUpload 等方法自行实现各步骤,大致思路就是先initiateMultipartUpload初始化一个分片上传,返回uploadid,然后将文件按一定的大小分片,之后循环上传每个分片,完成分片之后调用completeMultipartUpload方法合并文件,这种方式比较复杂。

    相关文章

      网友评论

          本文标题:OSS前端直传+后端签名

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