从前端到后端全面解析文件上传
1.前端准备(vue+element-ui)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>index</title>
<link rel="stylesheet" href="./element-ui/lib/theme-chalk/index.css">
<link rel="stylesheet" href="css/index.css">
<script src="js/vue.js"></script>
<script src="js/axios-0.18.0.js"></script>
<script src="./element-ui/lib/index.js"></script>
<style>
.avatar-uploader .el-upload {
border: 1px dashed #d9d9d9;
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
}
.avatar-uploader .el-upload:hover {
border-color: #409EFF;
}
.avatar-uploader-icon {
font-size: 28px;
color: #8c939d;
width: 178px;
height: 178px;
line-height: 178px;
text-align: center;
}
.avatar {
width: 178px;
height: 178px;
display: block;
}
</style>
</head>
<style>
</style>
<body>
<div id="app">
<el-upload
class="avatar-uploader"
action="http://localhost:8088/members/upload"
:show-file-list="false"
:on-success="handleAvatarSuccess"
:before-upload="beforeAvatarUpload">
<img v-if="imageUrl" :src="imageUrl" class="avatar">
<i v-else class="el-icon-plus avatar-uploader-icon"></i>
<p>{{name}}</p>
</el-upload>
</div>
<script>
new Vue({
el: "#app",
data() {
return {
imageUrl: '',
name:'',
url:'',
};
},
methods: {
handleAvatarSuccess(res, file) {
this.imageUrl = URL.createObjectURL(file.raw);
this.name=file.response.name
this.url=file.response.url
console.log(file)
},
beforeAvatarUpload(file) {
const isLt2M = file.size / 1024 / 1024 < 10;
if (!isLt2M) {
this.$message.error('上传头像图片大小不能超过 10MB!');
}
return isLt2M;
}
}
})
</script>
</body>
</html>
2.后端准备(SpringBoot+minio+mysql)
2.1解决跨域
package com.data211.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
/**
* @author rjj
* @date 2023/2/3 - 15:15
*/
@Configuration
public class GlobalCorsConfig {
/**
* 允许跨域调用的过滤器
*/
@Bean
public CorsFilter corsFilter() {
CorsConfiguration config = new CorsConfiguration();
//允许白名单域名进行跨域调用(设置http://localhost:8080/ 表示指定请求源允许跨域)
config.addAllowedOriginPattern("*");
//允许跨越发送cookie
config.setAllowCredentials(true);
//放行全部原始头信息
config.addAllowedHeader("*");
//允许所有请求方法跨域调用
config.addAllowedMethod("*");
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
//指定拦截路径
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}
}
2.2配置minio与mysql
pom依赖
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>8.5.1</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.1</version>
</dependency>
<!--第三方工具jar包-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.7.17</version>
</dependency>
配置文件配置
server:
port: 8088
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url:
username: root
password:
mybatis-plus:
mapper-locations: classpath:mapper/*.xml
# 配置别名
type-aliases-package: com.data211.pojo
global-config:
db-config:
# 主键自增长
id-type: auto
# 表名前缀
table-prefix: data211_
# 逻辑删除
logic-delete-value: 1
logic-not-delete-value: 0
# 控制台输出操作数据库日志
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
minio:
endpoint:
accessKey:
secretKey:
bucket:
files:
配置minio客户端
package com.data211.config;
import io.minio.MinioClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
@author rjj
@date 2023/2/18 - 17:37
*/
@Configuration
public class MinioConfig {
@Value("${minio.endpoint}")
private String endpoint;
@Value("${minio.accessKey}")
private String accessKey;
@Value("${minio.secretKey}")
private String secretKey;
@Bean
public MinioClient minioClient() {
MinioClient minioClient =
MinioClient.builder()
.endpoint(endpoint)
.credentials(accessKey, secretKey)
.build();
return minioClient;
}
}
2.3controller层
@RequestMapping(value = "/upload")
public UploadFileResultDto upload(@RequestPart("file") MultipartFile file) throws IOException {
return membersService.upload(file);
};
2.4service层
package com.data211.service.impl;
import cn.hutool.crypto.digest.DigestUtil;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.data211.dao.MembersDao;
import com.data211.dto.UploadFileResultDto;
import com.data211.pojo.MediaFiles;
import com.data211.pojo.Members;
import com.data211.service.IMembersService;
import com.data211.utils.BaseContext;
import io.minio.MinioClient;
import io.minio.PutObjectArgs;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
@Service
public class MembersServiceImpl extends ServiceImpl<MembersDao, Members> implements IMembersService {
@Resource
private MinioClient minioClient;
//普通文件桶
@Value("${minio.bucket.files}")
private String bucket_Files;
@Resource
private MembersServiceImpl membersService;
@Resource
private MediaFilesServiceImpl mediaFilesService;
@Override
public UploadFileResultDto upload(MultipartFile file) throws IOException {
String fileMd5 = DigestUtil.md5Hex(file.getBytes());
String folder = getFileFolder(new Date(), true, true, true);
String filename = fileMd5+file.getName().substring(file.getName().lastIndexOf("."));
MediaFiles mediaFiles = null;
try {
//TODO 上传到minio
addMediaFilesToMinIO(file, bucket_Files, folder+filename);
//TODO 上传到数据库 (用Spring控制的代理对象实现事务控制生效)
mediaFiles = membersService.addMediaFilesToDb(BaseContext.getUserId(),filename,folder+filename);
UploadFileResultDto uploadFileParamsDto = new UploadFileResultDto();
BeanUtils.copyProperties(mediaFiles,uploadFileParamsDto);
return uploadFileParamsDto;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
@Override
@Transactional
public MediaFiles addMediaFilesToDb(String userId, String filename,String url) {
MediaFiles mediaFiles = new MediaFiles(userId, filename, url);
mediaFilesService.save(mediaFiles);
return mediaFiles;
}
public void addMediaFilesToMinIO(MultipartFile file, String bucket, String objectName) throws IOException {
// 将文件字节输入到内存流中
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(file.getBytes());
//获取文件类型
String contentType = file.getContentType();
try {
PutObjectArgs putObjectArgs =
PutObjectArgs.builder().bucket(bucket).object(objectName)
//-1 表示文件分片按 5M(不小于 5M,不大于 5T),分片数量最大10000
.stream(byteArrayInputStream, byteArrayInputStream.available(), -1)
.contentType(contentType)
.build();
minioClient.putObject(putObjectArgs);
} catch (Exception e) {
e.printStackTrace();
}
}
private String getFileFolder(Date date, boolean year, boolean month, boolean day) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
//获取当前日期字符串
String dateString = sdf.format(new Date());
//取出年、月、日
String[] dateStringArray = dateString.split("-");
StringBuffer folderString = new StringBuffer();
if (year) {
folderString.append(dateStringArray[0]);
folderString.append("/");
}
if (month) {
folderString.append(dateStringArray[1]);
folderString.append("/");
}
if (day) {
folderString.append(dateStringArray[2]);
folderString.append("/");
}
return folderString.toString();
}
}