手把手全程带你使用springboot2.6.7+vue2前后端分离腾讯云对象存储COS上传文件

时间:2022.6.27往后的版本可能需要更改依赖

创建腾讯云COS对象存储存储桶

  • https://console.cloud.tencent.com/cos

在这里插入图片描述

定义自己的存储桶
做个人博客最好需要开放共有读写,不然上传的图片无法显示

在这里插入图片描述


后端

添加pom依赖

<!--腾讯云COS-->
<dependency>
   <groupId>com.qcloud</groupId>
   <artifactId>cos_api</artifactId>
   <version>5.6.89<ersion>
</dependency>

 <!--hutool-->
 <dependency>
     <groupId>cn.hutool</groupId>
     <artifactId>hutool-all</artifactId>
     <version>5.5.9<ersion>
 </dependency>

获取腾讯云的密钥→https://console.cloud.tencent.com/cam/capi
COS地域→https://cloud.tencent.com/document/product/436/6224

在application.yml中添加动态配置

spring:
  # 配置文件大小限制
  servlet:
    multipart:
      max-file-size: 10MB
      max-request-size: 20MB

cos:
  secretId: 你的SecretId
  secretKey: 你的SecretKey
  regionName: 你的COS地域
  bucketName: 你的命名空间名称

以下为方法示例

实现方法在完成后返回文件的url,并将文件信息存储至数据库
未做文件类型限制

SQL

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

DROP TABLE IF EXISTS `file`;
CREATE TABLE `file`  (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'id',
  `name` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL COMMENT '文件名称',
  `type` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL COMMENT '文件类型',
  `size` bigint(255) NULL DEFAULT NULL COMMENT '文件大小',
  `url` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL COMMENT '文件链接',
  `status` int(1) NULL DEFAULT 0 COMMENT '启用状态',
  `del_flag` int(1) NULL DEFAULT 0 COMMENT '删除标记',
  `time` datetime NULL DEFAULT NULL COMMENT '创建时间',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 112 CHARACTER SET = utf8 COLLATE = utf8_bin COMMENT = '文件表' ROW_FORMAT = Dynamic;

SET FOREIGN_KEY_CHECKS = 1;

Entity

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;

import java.io.Serializable;
import java.util.Date;

@Data
@NoArgsConstructor
@AllArgsConstructor
@Accessors(chain = true)
@TableName("sys_file")
@ApiModel(value = "File对象", description = "文件表")
public class File implements Serializable {

    @TableId(value = "id", type = IdType.AUTO)
    private Integer id;

    @TableField("name")
    private String name;

    @TableField("type")
    private String type;

    @TableField("size")
    private Long size;

    @TableField("url")
    private String url;

    @TableField("status")
    private Integer status;

    @TableField("del_flag")
    private Integer delFlag;

    @TableField("time")
    private Date time;
}

Mapper

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.star.domain.entity.File;

public interface FileMapper extends BaseMapper<File> {

}

Controller

import com.star.domain.entity.File;
import com.star.service.IFileService;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;

@RestController
@RequestMapping("/file")
public class FileController {

    @Resource
    private IFileService fileService;

    @PostMapping("/admin/upload")
    public String upload(MultipartFile file) {
        return fileService.upload(file);
    }
}

Service

import com.baomidou.mybatisplus.extension.service.IService;
import com.star.domain.entity.File;
import org.springframework.web.multipart.MultipartFile;

public interface IFileService extends IService<File> {

    String upload(MultipartFile file);
}

ServiceImpl

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.FileUtil;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.qcloud.cos.COSClient;
import com.qcloud.cos.ClientConfig;
import com.qcloud.cos.auth.BasicCOSCredentials;
import com.qcloud.cos.auth.COSCredentials;
import com.qcloud.cos.model.PutObjectRequest;
import com.qcloud.cos.model.PutObjectResult;
import com.qcloud.cos.region.Region;
import com.star.domain.Constants;
import com.star.domain.entity.File;
import com.star.exception.ServiceException;
import com.star.mapper.FileMapper;
import com.star.service.IFileService;
import com.star.utils.PathUtils;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;

@Service
public class FileServiceImpl extends ServiceImpl<FileMapper, FileCOS> implements IFileService {

    @Resource
    private FileMapper fileMapper;
    @Value("${cos.secretId}")
    private String secretId;
    @Value("${cos.secretKey}")
    private String secretKey;
    @Value("${cos.bucketName}")
    private String bucketName;
    @Value("${cos.regionName}")
    private String regionName;

    @Override
    public String upload(MultipartFile file) {
        // 判断文件类型
        // 获取原始文件名
        String originalFilename = file.getOriginalFilename();
        String type = FileUtil.extName(originalFilename);
        long size = file.getSize();
        // 判断原始文件名
        if (originalFilename == null) {
            throw new ServiceException(Constants.CODE_500, "系统错误");
        }
        // 判断通过上传COS
        String filePath = PathUtils.generateFilePath(originalFilename);
        String url = uploadCOS(file, filePath);
        // 上传到腾讯云cos对象存储的同时将文件信息存储至数据库
        FileCOS saveFile = new FileCOS();
        saveFile.setName(originalFilename);
        saveFile.setType(type);
        saveFile.setSize(size / 1024);
        saveFile.setUrl(url);
        saveFile.setTime(DateUtil.parse(DateUtil.now()));
        fileMapper.insert(saveFile);
        return url;
    }

    public String uploadCOS(MultipartFile file, String filePath) {
        String key = filePath;
        try {
            // 默认不指定key的情况下,以文件内容的hash值作为文件名
            PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, key, file.getInputStream(), null);
            // 使用cosClient调用第三方接口
            COSCredentials cred = new BasicCOSCredentials(secretId, secretKey);
            // clientConfig 中包含了设置 region, https(默认 http), 超时, 代理等 set 方法, 使用可参见源码或者常见问题 Java SDK 部分。
            Region region = new Region(regionName);
            ClientConfig clientConfig = new ClientConfig(region);
            // 生成 cos 客户端。
            COSClient cosClient = new COSClient(cred, clientConfig);
            PutObjectResult putObjectResult = cosClient.putObject(putObjectRequest);
        } catch (Exception e) {
            e.printStackTrace();
        }
        //拼接返回路径
        return "https://" + bucketName + ".cos." + regionName + ".myqcloud.com/" + key;
    }
}

ServiceException

import lombok.Getter;

/**
 * 自定义异常
 */
@Getter
public class ServiceException extends RuntimeException {

    private String code;

    public ServiceException(String code, String msg) {
        super(msg);
        this.code = code;
    }
}

Constants

public interface Constants {

    String CODE_200 = "200"; // 成功
    String CODE_401 = "401"; // 权限不足
    String CODE_400 = "400"; // 参数错误
    String CODE_500 = "500"; // 失败
    String CODE_600 = "600"; // 其他业务异常
    
}

前端

  • 为前端将文件以formData格式存储,并通过val.raw的方式将数据传给formData对象,axios发送post请求到后端
  • 由于action属性为上传的地址里用#号忽略了,需要axios发送至后端的请求有一套完整的上传方法利用前端formData对像传递的文件进行文件的上传

按钮

<el-button type="primary" @click="handleFileUpload" class="ml-5">上传文件<i class="el-icon-top"></i></el-button>

上传对话框

<el-dialog title="上传" :visible.sync="dialogOfUpload" width="35%" style="text-align: center;">
  <el-upload class="upload-demo" action="#" drag multiple :headers="headers" :auto-upload="false"
             :file-list="fileList" :on-change="handleChange">
    <i class="el-icon-upload"></i>
    <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
    <div class="el-upload__tip" slot="tip">上传文件</div>
  </el-upload>
  <div slot="footer" class="dialog-footer">
    <el-button @click="dialogOfUpload = false">取 消</el-button>
    <el-button type="primary" @click="confirmUpload()">上 传</el-button>
  </div>
</el-dialog>

相关方法

handleFileUpload(){
  this.dialogOfUpload = true
},
handleChange(file, fileList) { //文件数量改变
  this.fileList = fileList;
},
confirmUpload() { //确认上传
  var param = new FormData();
  this.fileList.forEach(
      (val, index) => {
        param.append("file", val.raw);
      }
  );
  this.request.post("/file/upload", param).then(responce => {});
  this.$message({
    message: "上传成功!",
    duration: 1000
  });
  this.dialogOfUpload = false
  this.load()
},

完整示例 File.vue

<template>
  <div>
    <el-button type="primary" @click="handleFileUpload" class="ml-5">上传文件<i class="el-icon-top"></i></el-button>
    
    <!-- 上传对话框 -->
    <el-dialog title="上传" :visible.sync="dialogFormVisible" width="35%" style="text-align: center;">
      <el-upload class="upload-demo" action="#" drag multiple :headers="headers" :auto-upload="false"
                 :file-list="fileList" :on-change="handleChange">
        <i class="el-icon-upload"></i>
        <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
        <div class="el-upload__tip" slot="tip">上传文件</div>
      </el-upload>
      <div slot="footer" class="dialog-footer">
        <el-button @click="dialogFormVisible= false">取 消</el-button>
        <el-button type="primary" @click="confirmUpload()">上 传</el-button>
      </div>
    </el-dialog>
  </div>
</template>

<script>
import {serverIp} from "../../public/config";

export default {
  name: "File",
  data() {
    return {
      dialogFormVisible: false,
    }
  },
  methods: {
    handleFileUpload() {
      this.dialogOfUpload = true
    },
    handleChange(file, fileList) { //文件数量改变
      this.fileList = fileList;
    },
    confirmUpload() { //确认上传
      var param = new FormData();
      this.fileList.forEach(
          (val, index) => {
            param.append("file", val.raw);
          }
      );
      this.request.post("/file/admin/upload", param).then(responce => {
      });
      this.$message({
        message: "上传成功!",
        duration: 1000
      });
      this.dialogOfUpload = false
      location.reload()
    },
  }
}
</script>

config.js

export const serverIp = 'localhost'

request.js(封装axios请求和数据加载时动画)

import axios from 'axios'
import ElementUI from "element-ui";
import {serverIp} from "../../public/config";
import {hideLoading, showLoading} from '../../public/loading'

const request = axios.create({
    baseURL: `http://${serverIp}:你的后端端口`,
    // ↓ 比如 ↓
    // baseURL: `http://${serverIp}:8888`,
    timeout: 5000
})

// request 拦截器
// 可以自请求发送前对请求做一些处理
// 比如统一加token,对请求参数统一加密
request.interceptors.request.use(config => {
    showLoading()//显示加载中
    config.headers['Content-Type'] = 'application/json;charset=utf-8';
    let user = localStorage.getItem("user") ? JSON.parse(localStorage.getItem("user")) : null
    if (user) {
        config.headers['token'] = user.token;  // 设置请求头
    }
    return config
}, error => {
    return Promise.reject(error)
});

// response 拦截器
// 可以在接口响应后统一处理结果
request.interceptors.response.use(
    response => {
        hideLoading()//关闭加载
        let res = response.data;
        // 如果是返回的文件
        if (response.config.responseType === 'blob') {
            return res
        }
        // 兼容服务端返回的字符串数据
        if (typeof res === 'string') {
            res = res ? JSON.parse(res) : res
        }
        // 当权限验证不通过的时候给出提示
        if (res.code === '401') {
            ElementUI.Message({
                message: res.msg,
                type: 'error'
            });
        }
        return res;
    },
    error => {
        hideLoading()//关闭加载
        console.log('err' + error) // for debug
        return Promise.reject(error)
    }
)
export default request

gloable.css

*{
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}
html,body{
    height: 100vh;
}
.mt-10 {
    margin-top: 10px;
}
.mt-20 {
    margin-top: 20px;
}
.mt-50 {
    margin-top: 50px;
}
.mt-150 {
    margin-top: 150px;
}
.ml-5 {
    margin-left: 5px;
}
.mr-5 {
    margin-right: 5px;
}
.pd-10 {
    padding: 10px 0;
}
a {
    text-decoration: none;
    color: #666;
}

上传对话框

  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值