阿里云OSS签名直传实现(前端、后端踩坑)

阿里云OSS签名直传实现(前端、后端踩坑),第1张

阿里云OSS签名直传实现(前端、后端踩坑)

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录
  • 前言
  • 一、OSS签名直传是什么?
      • 1.概念
      • 2.优点
  • 二、后端代码
      • 1.请求OSS签名
  • 三.前端代码
      • 碰到的问题如下:
          •    1.前端采用ImageCropper组件直接上传的时候,不知道将OSS所需参数加到何处?
          •    2.当参数问题解决后,发现上传失败,具体报错为
          •    3.文件可以顺利上传到OSS中,但是vue-admin-template却提示上传失败
  • 总结


前言

练手的项目中使用到了OSS的签名直传,将该过程进行记录,以便后续使用。前端采用的是vue-admin-template框架的上传组件,后端采用java完成


一、OSS签名直传是什么? 1.概念

Web端常见的上传方法是用户在浏览器或App端上传文件到应用服务器,应用服务器再把文件上传到OSS。这种方式需通过应用服务器中转,传输效率明显低于数据直传至OSS的方式。数据直传至OSS是利用OSS的PostObject接口,使用表单上传方式上传文件至OSS


流程解析:

  • 前端向后端请求OSS签名
  • 前端得到签名之后,直接上传文件到OSS中
  • 上传文件成功之后采用回调机制保存图片地址到数据库中
2.优点

  2.1 签名直传无需将Accesskey暴露在前端页面,相比Javascript客户端签名直传具有更高的安全性
  2.2 签名直传无需将资源传到应用服务器再转发到OSS中,而是直接上传至OSS中,节省了带宽

二、后端代码 1.请求OSS签名

后端的代码较为简单,只需返回直传所需的一些验证参数即可,具体参数可参照官方文档:阿里云OSS直传.


Controller层:

@Api("oss管理接口")
@RestController
@RequestMapping("/ossService")
public class OssController
{
    private final OssService ossService;

    public OssController(OssService ossService) {
        this.ossService = ossService;
    }

    @ApiOperation(value = "获取oss签名")
    @GetMapping("/getOssSign")
    public R getOssSign(){
        Map signMap = ossService.getOssSign();
        if(signMap == null || signMap.size() == 0){
            return R.error().msg("未获取到签名,请重试");
        }
        return R.ok().data(signMap);
    }
}

Service层:

@Service
public class OssServiceImpl implements OssService
{
    @Override
    public Map getOssSign() {
        String accessId = ConstantPropertiesUtils.KEY_ID; // 请填写您的AccessKeyId。
        String accessKey = ConstantPropertiesUtils.KEY_SECRET; // 请填写您的AccessKeySecret。
        String endpoint = ConstantPropertiesUtils.END_POINT; // 请填写您的 endpoint。
        String bucket = ConstantPropertiesUtils.BUCKET; // 请填写您的 bucketname 。
        String host = "https://" + bucket + "." + endpoint; // host的格式为 bucketname.endpoint
        String dir = new DateTime().toString("yyyy/MM/dd"); // 用户上传文件时指定的前缀。
        Map respMap = null;

        // 创建OSSClient实例。
        OSS ossClient = new OSSClientBuilder().build(endpoint, accessId, accessKey);
        try {
            long expireTime = 30;
            long expireEndTime = System.currentTimeMillis() + expireTime * 1000;
            Date expiration = new Date(expireEndTime);
            // PostObject请求最大可支持的文件大小为5 GB,即CONTENT_LENGTH_RANGE为5*1024*1024*1024。
            PolicyConditions policyConds = new PolicyConditions();
            policyConds.addConditionItem(PolicyConditions.COND_CONTENT_LENGTH_RANGE, 0, 1048576000);
            policyConds.addConditionItem(MatchMode.StartWith, PolicyConditions.COND_KEY, dir);

            String postPolicy = ossClient.generatePostPolicy(expiration, policyConds);
            byte[] binaryData = postPolicy.getBytes("utf-8");
            String encodedPolicy = BinaryUtil.tobase64String(binaryData);
            String postSignature = ossClient.calculatePostSignature(postPolicy);
            respMap = new linkedHashMap<>();
            //这些参数名必须要这样写,与官方文档一一对应
            respMap.put("OSSAccessKeyId", accessId);
            respMap.put("policy", encodedPolicy);
            respMap.put("Signature", postSignature);
            respMap.put("dir", dir);
            respMap.put("host", host);
            respMap.put("expire", String.valueOf(expireEndTime / 1000));
            //让服务端返回200,不然,默认会返回204
            //respMap.put("success_action_status", 200);
        }
        catch (Exception e) {
            // Assert.fail(e.getMessage());
            System.out.println(e.getMessage());
        }
        finally {
            ossClient.shutdown();
        }
        return respMap;
    }
}
三.前端代码

前端使用的是vue-admin-template框架,其中文件上传用的是ImageCropper和Hamburger组件,由于对前端代码不熟,因此碰到了一些问题(有坑,需要自己改部分源码)


前端html代码:
      
        
        
        更换头像
        
        
        
      

其中有2个主要的函数,其作用分别为:

  • beforeUpload:用于上传OSS前到后端获取签名
  • uploadSuccess:上传OSS成功后拼接url

前端js代码

   //获取oss的token和签名
    beforeUpload() {
      this.imagecropperShow = true;
      //获取后台签名
      oss.getOssSign().then((response) => {
        console.log(response);
        this.ossObj = response.data;
        this.ossObj.key = response.data.dir + "/" + this.randomString(10);
        console.log(this.ossObj.key);
      });
    },
    //上传成功的回调
    uploadSuccess(data) {
      this.imagecropperShow = false;
      //上传之后拼接url,这样后续提交表单数据时便可保存url到数据库中
      let url = this.ossObj.host + "/" + this.ossObj.key;
      this.teacher.avatar = url;
      this.imagecropperKey = this.imagecropperKey + 1;
    },
    //用于随机生成文件名
    randomString(len) {
      len = len || 32;
      var chars = "ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678";
      var maxPos = chars.length;
      var pwd = "";
      for (var i = 0; i < len; i++) {
        pwd += chars.charAt(Math.floor(Math.random() * maxPos));
      }
      return pwd;
    },
碰到的问题如下:    1.前端采用ImageCropper组件直接上传的时候,不知道将OSS所需参数加到何处?

     解决:查看ImageCropper的源码发现,所有的其余参数都应该加到:parms中,这样会自动上传

   2.当参数问题解决后,发现上传失败,具体报错为
The bucket POST must contain the specified ‘key‘. If it is specified, please check the order

      报错原因:阿里云OSS规定文件上传时,必须携带key参数,且key参数必须在上传的file之前,如果key在file后或者缺少key参数,便会报错。而ImageCropper源码在填充参数时是先将file放在第一位,后面的参数依次遍历添加,因此出现了key在file后面的情况,导致报错

      //  807行
      fmData.append(
        field,
        data2blob(createImgUrl, mime),
        field + "." + imgFormat
      );
      //添加其他参数
      if (typeof params === "object" && params) {
        Object.keys(params).forEach((k) => {
          fmData.append(k, params[k]);
        });
      }

     解决:将代码顺序进行修改,这样key就会在file之前,报错解决

      if (typeof params === "object" && params) {
        Object.keys(params).forEach((k) => {
          fmData.append(k, params[k]);
        });
      }
      fmData.append(
        field,
        data2blob(createImgUrl, mime),
        field + "." + imgFormat
      );
   3.文件可以顺利上传到OSS中,但是vue-admin-template却提示上传失败

      报错原因:vue-admin-template封装了axios请求,加入了拦截器,因此它对于是否上传成功的判断是,如果后端返回的data.code===20000则成功,否则失败。但是OSS是外部服务器,不会出现20000的响应,因此应当根据响应状态码进行判断

     解决:在封装axios请求的js代码中新增判断条件(修改response拦截器即可),不仅code为20000时可以成功,并且响应码为204时也可以成功

utils/request.js:

import axios from 'axios'
import { Message, MessageBox } from 'element-ui'
import store from '../store'
import { getToken } from '@/utils/auth'

// 创建axios实例
const service = axios.create({
  baseURL: process.env.base_API, // api 的 base_url
  timeout: 5000 // 请求超时时间
})

// request拦截器
service.interceptors.request.use(
  config => {
    if (store.getters.token) {
      config.headers['X-Token'] = getToken() // 让每个请求携带自定义token 请根据实际情况自行修改
    }
    return config
  },
  error => {
    // Do something with request error
    console.log(error) // for debug
    Promise.reject(error)
  }
)

// response 拦截器
service.interceptors.response.use(
  response => {
    
    const res = response.data
    if (response.status === 204 || res.code === 20000) {
      return response.data
    } else {
      Message({
        message: res.message,
        type: 'error',
        duration: 5 * 1000
      })

      // 50008:非法的token; 50012:其他客户端登录了;  50014:Token 过期了;
      if (res.code === 50008 || res.code === 50012 || res.code === 50014) {
        MessageBox./confirm/i(
          '你已被登出,可以取消继续留在该页面,或者重新登录',
          '确定登出',
          {
            /confirm/iButtonText: '重新登录',
            cancelButtonText: '取消',
            type: 'warning'
          }
        ).then(() => {
          store.dispatch('FedLogOut').then(() => {
            location.reload() // 为了重新实例化vue-router对象 避免bug
          })
        })
      }
      return Promise.reject('error')
    }
  },
  error => {
    console.log('err' + error) // for debug
    Message({
      message: error.message,
      type: 'error',
      duration: 5 * 1000
    })
    return Promise.reject(error)
  }
)

export default service

总结

阿里云OSS签名直传是如今比较流行的文件上传方式,本文介绍了签名直传的实现,后端代码实现较为简单,主要出的坑都在前端(主要是前端框架的使用问题),应当加强前端代码能力
TO DO
签名直传还有回调功能,该回调功能应当写在后端代码中,但实现起来与签名直传并无太大区别,加上回调函数即可,具体可参照服务端签名直传并设置上传回调

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/5563347.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-14
下一篇 2022-12-14

发表评论

登录后才能评论

评论列表(0条)

保存