springboot + vue 整合oss实现文件上传功能

365 阅读2分钟

开通配置阿里云oss

  1. 购买阿里云oss服务

  2. 创建一个Bucket image.png image.png image.png

  3. 增加子账号并赋予权限

    image.png image.png image.png image.png 记录下面信息,后续需要使用 image.png 给新创建的账号添加权限 image.png image.png

  4. 配置oss权限 image.png

  5. 设置oss跨域 image.png

后端代码编写

  1. 添加aliyun-sdk-oss等相关依赖
    <dependencies>
        <!--springboot-web-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--阿里云oss-->
        <dependency>
            <groupId>com.aliyun.oss</groupId>
            <artifactId>aliyun-sdk-oss</artifactId>
        </dependency>
        <!--hutool-->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
        </dependency>
    </dependencies>
    
  2. 配置文件
    aliyun:
      oss:
        endpoint: oss-cn-shanghai.aliyuncs.com # oss对外服务的访问域名
        accessKeyId: LTAI5tN2xxxxxxxxx # 访问身份验证中用到用户标识
        accessKeySecret: FqRbVFpKxxxxxxxxxxxx # 用户用于加密签名字符串和oss用来验证签名字符串的密钥
        bucketName: polars-dev # oss的存储空间
        policy:
          expire: 300 # 签名有效期(S)
        maxSize: 10 # 上传文件大小(M)
        callback: http://liboshuai.com:28080/polaris-pms-service/aliyun/oss/callback # 文件上传成功后的回调地址
        dir:
          prefix: polaris/images/ # 上传文件夹路径前缀
    
  3. oss配置类
    package com.liboshuai.polaris.common.file.aliyun.config;
    
    import com.aliyun.oss.OSSClient;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    /**
     * @Author: liboshuai
     * @Date: 2023-02-13 17:29
     * @Description:
     */
    @Configuration
    public class AliyunOssConfig {
        @Value("${aliyun.oss.endpoint}")
        private String ALIYUN_OSS_ENDPOINT;
        @Value("${aliyun.oss.accessKeyId}")
        private String ALIYUN_OSS_ACCESSKEYID;
        @Value("${aliyun.oss.accessKeySecret}")
        private String ALIYUN_OSS_ACCESSKEYSECRET;
        @Bean
        public OSSClient ossClient(){
            return new OSSClient(ALIYUN_OSS_ENDPOINT,ALIYUN_OSS_ACCESSKEYID,ALIYUN_OSS_ACCESSKEYSECRET);
        }
    }
    
  4. oss上传结果vo对象
    package com.liboshuai.polaris.common.file.aliyun.vo;
    
    import io.swagger.annotations.ApiModelProperty;
    import lombok.Data;
    
    /**
     * @Author: liboshuai
     * @Date: 2023-02-13 17:30
     * @Description: 阿里云文件上传结果
     */
    @Data
    public class AliyunOssPolicyResult {
        @ApiModelProperty("访问身份验证中用到用户标识")
        private String accessKeyId;
        @ApiModelProperty("用户表单上传的策略,经过base64编码过的字符串")
        private String policy;
        @ApiModelProperty("对policy签名后的字符串")
        private String signature;
        @ApiModelProperty("上传文件夹路径前缀")
        private String dir;
        @ApiModelProperty("oss对外服务的访问域名")
        private String host;
        @ApiModelProperty("上传成功后的回调设置")
        private String callback;
    }
    
  5. oss回调接口入参vo对象
    package com.liboshuai.polaris.common.file.aliyun.vo;
    
    import io.swagger.annotations.ApiModelProperty;
    import lombok.Data;
    
    /**
     * @Author: liboshuai
     * @Date: 2023-02-13 17:33
     * @Description: 阿里云文件上传回调入参
     */
    @Data
    public class AliyunOssCallbackParam {
        @ApiModelProperty("请求的回调地址")
        private String callbackUrl;
        @ApiModelProperty("回调是传入request中的参数")
        private String callbackBody;
        @ApiModelProperty("回调时传入参数的格式,比如表单提交形式")
        private String callbackBodyType;
    }
    
  6. oss回调接口结果vo对象
    package com.liboshuai.polaris.common.file.aliyun.vo;
    
    import io.swagger.annotations.ApiModelProperty;
    import lombok.Data;
    
    /**
     * @Author: liboshuai
     * @Date: 2023-02-13 17:44
     * @Description:
     */
    @Data
    public class AliyunOssCallbackResult {
        @ApiModelProperty("文件名称")
        private String filename;
        @ApiModelProperty("文件大小")
        private String size;
        @ApiModelProperty("文件的mimeType")
        private String mimeType;
        @ApiModelProperty("图片文件的宽")
        private String width;
        @ApiModelProperty("图片文件的高")
        private String height;
    }
    
  7. oss文件上传service
    package com.liboshuai.polaris.common.file.aliyun.service;
    
    import com.liboshuai.polaris.common.file.aliyun.vo.AliyunOssCallbackResult;
    import com.liboshuai.polaris.common.file.aliyun.vo.AliyunOssPolicyResult;
    
    import javax.servlet.http.HttpServletRequest;
    
    /**
     * @Author: liboshuai
     * @Date: 2023-02-13 18:27
     * @Description:
     */
    public interface OssService {
        /**
         * oss上传策略生成
         */
        AliyunOssPolicyResult policy();
        /**
         * oss上传成功回调
         */
        AliyunOssCallbackResult callback(HttpServletRequest request);
    }
    
    package com.liboshuai.polaris.common.file.aliyun.service.impl;
    
    import cn.hutool.json.JSONUtil;
    import com.aliyun.oss.OSSClient;
    import com.aliyun.oss.common.utils.BinaryUtil;
    import com.aliyun.oss.model.MatchMode;
    import com.aliyun.oss.model.PolicyConditions;
    import com.liboshuai.polaris.common.file.aliyun.service.OssService;
    import com.liboshuai.polaris.common.file.aliyun.vo.AliyunOssCallbackParam;
    import com.liboshuai.polaris.common.file.aliyun.vo.AliyunOssCallbackResult;
    import com.liboshuai.polaris.common.file.aliyun.vo.AliyunOssPolicyResult;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.stereotype.Service;
    
    import javax.servlet.http.HttpServletRequest;
    import java.text.SimpleDateFormat;
    import java.util.Date;
    
    /**
     * @Author: liboshuai
     * @Date: 2023-02-13 17:36
     * @Description:
     */
    @Slf4j
    @Service
    public class AliyunOssServiceImpl implements OssService {
        @Value("${aliyun.oss.policy.expire}")
        private int ALIYUN_OSS_EXPIRE;
        @Value("${aliyun.oss.maxSize}")
        private int ALIYUN_OSS_MAX_SIZE;
        @Value("${aliyun.oss.callback}")
        private String ALIYUN_OSS_CALLBACK;
        @Value("${aliyun.oss.bucketName}")
        private String ALIYUN_OSS_BUCKET_NAME;
        @Value("${aliyun.oss.endpoint}")
        private String ALIYUN_OSS_ENDPOINT;
        @Value("${aliyun.oss.dir.prefix}")
        private String ALIYUN_OSS_DIR_PREFIX;
    
        @Autowired
        private OSSClient ossClient;
    
        /**
         * 签名生成
         */
        @Override
        public AliyunOssPolicyResult policy() {
            AliyunOssPolicyResult result = new AliyunOssPolicyResult();
            // 存储目录
            SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
            String dir = ALIYUN_OSS_DIR_PREFIX+sdf.format(new Date());
            // 签名有效期
            long expireEndTime = System.currentTimeMillis() + ALIYUN_OSS_EXPIRE * 1000;
            Date expiration = new Date(expireEndTime);
            // 文件大小
            long maxSize = ALIYUN_OSS_MAX_SIZE * 1024 * 1024;
            // 回调
            AliyunOssCallbackParam callback = new AliyunOssCallbackParam();
            callback.setCallbackUrl(ALIYUN_OSS_CALLBACK);
            callback.setCallbackBody("filename=${object}&size=${size}&mimeType=${mimeType}&height=${imageInfo.height}&width=${imageInfo.width}");
            callback.setCallbackBodyType("application/x-www-form-urlencoded");
            // 提交节点
            String action = "http://" + ALIYUN_OSS_BUCKET_NAME + "." + ALIYUN_OSS_ENDPOINT;
            try {
                PolicyConditions policyConds = new PolicyConditions();
                policyConds.addConditionItem(PolicyConditions.COND_CONTENT_LENGTH_RANGE, 0, maxSize);
                policyConds.addConditionItem(MatchMode.StartWith, PolicyConditions.COND_KEY, dir);
                String postPolicy = ossClient.generatePostPolicy(expiration, policyConds);
                byte[] binaryData = postPolicy.getBytes("utf-8");
                String policy = BinaryUtil.toBase64String(binaryData);
                String signature = ossClient.calculatePostSignature(postPolicy);
                String callbackData = BinaryUtil.toBase64String(JSONUtil.parse(callback).toString().getBytes("utf-8"));
                // 返回结果
                result.setAccessKeyId(ossClient.getCredentialsProvider().getCredentials().getAccessKeyId());
                result.setPolicy(policy);
                result.setSignature(signature);
                result.setDir(dir);
                result.setCallback(callbackData);
                result.setHost(action);
            } catch (Exception e) {
                log.error("签名生成失败", e);
            }
            return result;
        }
    
        @Override
        public AliyunOssCallbackResult callback(HttpServletRequest request) {
            AliyunOssCallbackResult result= new AliyunOssCallbackResult();
            String filename = request.getParameter("filename");
            filename = "https://".concat(ALIYUN_OSS_BUCKET_NAME).concat(".").concat(ALIYUN_OSS_ENDPOINT).concat("/").concat(filename);
            log.info("----ossCallBack-fileName: {}-------", filename);
            result.setFilename(filename);
            result.setSize(request.getParameter("size"));
            result.setMimeType(request.getParameter("mimeType"));
            result.setWidth(request.getParameter("width"));
            result.setHeight(request.getParameter("height"));
            return result;
        }
    }
    
  8. oss文件上传controller接口
    package com.liboshuai.polaris.pms.service.controller;
    
    import com.liboshuai.polaris.common.domain.ResponseResult;
    import com.liboshuai.polaris.common.file.aliyun.service.OssService;
    import com.liboshuai.polaris.common.file.aliyun.vo.AliyunOssCallbackResult;
    import com.liboshuai.polaris.common.file.aliyun.vo.AliyunOssPolicyResult;
    import io.swagger.annotations.Api;
    import io.swagger.annotations.ApiOperation;
    import lombok.NoArgsConstructor;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestMethod;
    import org.springframework.web.bind.annotation.ResponseBody;
    
    import javax.servlet.http.HttpServletRequest;
    
    /**
     * @Author: liboshuai
     * @Date: 2023-02-13 18:03
     * @Description:
     */
    @Slf4j
    @Controller
    @Api(tags = "OssController", description = "Oss管理")
    @NoArgsConstructor
    @RequestMapping("/aliyun/oss")
    public class OssController {
    
        private OssService ossService;
    
        @Autowired
        public OssController(OssService ossService) {
            this.ossService = ossService;
        }
    
        @ApiOperation(value = "oss上传签名生成")
        @RequestMapping(value = "/policy", method = RequestMethod.GET)
        @ResponseBody
        public ResponseResult<AliyunOssPolicyResult> policy() {
            AliyunOssPolicyResult result = ossService.policy();
            return ResponseResult.success(result);
        }
    
        @ApiOperation(value = "oss上传成功回调")
        @RequestMapping(value = "/callback", method = RequestMethod.POST)
        @ResponseBody
        public ResponseResult<AliyunOssCallbackResult> callback(HttpServletRequest request) {
            log.info("--------进入oss回调接口--------");
            AliyunOssCallbackResult ossCallbackResult = ossService.callback(request);
            return ResponseResult.success(ossCallbackResult);
        }
    
    }
    

前端代码

<template> 
  <div>
    <el-upload
      :action="useOss?ossUploadUrl:minioUploadUrl"
      :data="useOss?dataObj:null"
      list-type="picture"
      :multiple="false" :show-file-list="showFileList"
      :file-list="fileList"
      :before-upload="beforeUpload"
      :on-remove="handleRemove"
      :on-success="handleUploadSuccess"
      :on-preview="handlePreview">
      <el-button size="small" type="primary">点击上传</el-button>
      <div slot="tip" class="el-upload__tip">只能上传jpg/png文件,且不超过10MB</div>
    </el-upload>
    <el-dialog :visible.sync="dialogVisible">
      <img width="100%" :src="fileList[0].url" alt="">
    </el-dialog>
  </div>
</template>
<script>
  import {policy} from '@/api/oss'

  export default {
    name: 'singleUpload',
    props: {
      value: String
    },
    computed: {
      imageUrl() {
        return this.value;
      },
      imageName() {
        if (this.value != null && this.value !== '') {
          return this.value.substr(this.value.lastIndexOf("/") + 1);
        } else {
          return null;
        }
      },
      fileList() {
        return [{
          name: this.imageName,
          url: this.imageUrl
        }]
      },
      showFileList: {
        get: function () {
          return this.value !== null && this.value !== ''&& this.value!==undefined;
        },
        set: function (newValue) {
        }
      }
    },
    data() {
      return {
        dataObj: {
          policy: '',
          signature: '',
          key: '',
          ossaccessKeyId: '',
          dir: '',
          callback:'',
          host: ''
        },
        dialogVisible: false,
        useOss:true, //使用oss->true;使用MinIO->false
        ossUploadUrl:'https://polars-dev.oss-cn-shanghai.aliyuncs.com',
        minioUploadUrl:'http://localhost:8080/minio/upload',
      };
    },
    methods: {
      emitInput(val) {
        this.$emit('input', val)
      },
      handleRemove(file, fileList) {
        this.emitInput('');
      },
      handlePreview(file) {
        this.dialogVisible = true;
      },
      beforeUpload(file) {
        let _self = this;
        if(!this.useOss){
          //不使用oss不需要获取策略
          return true;
        }
        return new Promise((resolve, reject) => {
          policy().then(response => {
            _self.dataObj.policy = response.data.policy;
            _self.dataObj.signature = response.data.signature;
            _self.dataObj.ossaccessKeyId = response.data.accessKeyId;
            _self.dataObj.key = response.data.dir + '/${filename}';
            _self.dataObj.dir = response.data.dir;
            _self.dataObj.host = response.data.host;
            _self.dataObj.callback = response.data.callback;
            resolve(true)
          }).catch(err => {
            console.log(err)
            reject(false)
          })
        })
      },
      handleUploadSuccess(res, file) {
        this.showFileList = true;
        this.fileList.pop();
        let url = this.dataObj.host + '/' + this.dataObj.dir + '/' + file.name;
        if(!this.useOss){
          //不使用oss直接获取图片路径
          url = res.data.url;
        }
        this.fileList.push({name: file.name, url: url});
        this.emitInput(this.fileList[0].url);
      }
    }
  }
</script>
<style>

</style>

功能演示

image.png

image.png

登录oss控制台,可以查看到我们刚刚上传的图片 image.png

END

参考文章:www.macrozheng.com/mall/archit…