时间:2022.6.27往后的版本可能需要更改依赖
前后端分离腾讯云对象存储COS上传文件
创建腾讯云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;
}