前端的请求内容
Content-Type: multipart/form-data;
form-data; name="filedata"; filename="具体的文件名称"
后端定义接口
@ApiOperation("文件上传接口")
@RequestMapping(value = "/upload/coursefile", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public UploadFileResultDto upload(@RequestPart("filedata") MultipartFile multipartFile){
Long companyId=2L;
try {
//创建临时文件
File tempFile = tempFile = File.createTempFile("minio", "temp");
//上传的文件拷贝到临时文件
multipartFile.transferTo(tempFile);
UploadFileParamsDto uploadFileParamsDto = new UploadFileParamsDto();
uploadFileParamsDto.setFilename(multipartFile.getOriginalFilename());
uploadFileParamsDto.setFileSize(multipartFile.getSize());
uploadFileParamsDto.setFileType("001001");
String absolutePath = tempFile.getAbsolutePath();
UploadFileResultDto uploadFileResultDto = mediaFileService.uploadFile(companyId, uploadFileParamsDto, absolutePath);
return uploadFileResultDto;
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
使用minio来完成业务逻辑
minio介绍:
分布式文件系统minio-****博客
获取文件默认目录名
//获取文件默认目录名
private String getDefaultFolderPath(){
//根据时间来生产存储目录
LocalDate now = LocalDate.now();
String defaultFolderPath = now.toString().replaceAll("-", "/")+"/";
return defaultFolderPath;
}
获取文件md5值
//获取文件md5值
private String getFileMd5(File file){
try {
FileInputStream fileInputStream = new FileInputStream(file);
String md5 = DigestUtils.md5Hex(fileInputStream);
return md5;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
根据扩展名来获取文件类型
private String getMimeType(String extension){
if(extension==null)
extension = "";
//根据扩展名取出mimeType
ContentInfo extensionMatch = ContentInfoUtil.findExtensionMatch(extension);
//通用mimeType,字节流
String mimeType = MediaType.APPLICATION_OCTET_STREAM_VALUE;
if(extensionMatch!=null){
mimeType = extensionMatch.getMimeType();
}
return mimeType;
}
上传到数据库
/**
* @description 将文件信息添加到文件表
* @param companyId 机构id
* @param fileMd5 文件md5值
* @param uploadFileParamsDto 上传文件的信息
* @param bucket 桶
* @param objectName 对象名称
* @return com.xuecheng.media.model.po.MediaFiles
* @author Mr.M
* @date 2022/10/12 21:22
*/
@Transactional
public MediaFiles addMediaFilesToDb(Long companyId,String fileMd5,QueryMediaParamsDto uploadFileParamsDto,String bucket,String objectName){
//从数据库查询文件
MediaFiles mediaFiles = mediaFilesMapper.selectById(fileMd5);
if (mediaFiles == null) {
mediaFiles = new MediaFiles();
//拷贝基本信息
BeanUtils.copyProperties(uploadFileParamsDto, mediaFiles);
mediaFiles.setId(fileMd5);
mediaFiles.setFileId(fileMd5);
mediaFiles.setCompanyId(companyId);
mediaFiles.setUrl("/" + bucket + "/" + objectName);
mediaFiles.setBucket(bucket);
mediaFiles.setFilePath(objectName);
mediaFiles.setCreateDate(LocalDateTime.now());
mediaFiles.setAuditStatus("002003");
mediaFiles.setStatus("1");
//保存文件信息到文件表
int insert = mediaFilesMapper.insert(mediaFiles);
if (insert < 0) {
log.error("保存文件信息到数据库失败,{}",mediaFiles.toString());
XueChengPlusException.cast("保存文件信息失败");
}
log.debug("保存文件信息到数据库成功,{}",mediaFiles.toString());
}
return mediaFiles;
}
service:
@Autowired
MediaFilesMapper mediaFilesMapper;
//普通文件桶
@Value("${minio.bucket.files}")
String bucket_Files;
@Autowired
MinioClient minioClient;
/**
* 普通文件上传
* @param companyId 机构id
* @param queryMediaParamsDto 上传文件信息
* @param localFilePath 文件磁盘路径
* @return
*/
@Override
public UploadFileResultDto uploadFile(Long companyId, QueryMediaParamsDto queryMediaParamsDto, String localFilePath) {
//根据文件路径获取文件对象
File file = new File(localFilePath);
if (file==null){
XueChengPlusException.cast("文件不存在");
}
//获取文件名称
String filename = queryMediaParamsDto.getFilename();
//获取文件拓展名
String extension = filename.substring(filename.lastIndexOf("."));
//获取默认存储目录
String defaultFolderPath = getDefaultFolderPath();
//获取文件的md5值
String fileMd5 = getFileMd5(file);
//获取文件类型
String mimeType = getMimeType(extension);
//存储到minio中的对象名(带目录)
String objectName = defaultFolderPath+fileMd5+extension;
//上传到minio
try {
UploadObjectArgs uploadObjectArgs = UploadObjectArgs.builder()
.bucket(bucket_Files)
.object(objectName)//添加子目录
.filename(localFilePath)
.contentType(mimeType)//默认根据扩展名确定文件内容类型,也可以指定
.build();
minioClient.uploadObject(uploadObjectArgs);
log.debug("上传文件到minio成功,bucket:{},objectName:{}",bucket_Files,objectName);
} catch (Exception e) {
e.printStackTrace();
log.error("上传文件到minio出错,bucket:{},objectName:{},错误原因:{}",bucket_Files,objectName,e.getMessage(),e);
XueChengPlusException.cast("上传文件到文件系统失败");
}
//将信息存储到数据库中
MediaFiles mediaFiles = addMediaFilesToDb(companyId, fileMd5, queryMediaParamsDto, bucket_Files, objectName);
UploadFileResultDto uploadFileResultDto = new UploadFileResultDto();
BeanUtils.copyProperties(mediaFiles,uploadFileResultDto);
return uploadFileResultDto;
}
事务控制优化
在测试中我发现addMediaFilesToDb上加的事务控制失效了:
当一个非事务方法调用同一个类中的事务方法时,事务无法按预期控制的原因通常与事务管理器的代理机制有关。在Spring框架中,事务管理通常是通过AOP(面向切面编程)实现的,这意味着事务是通过动态代理来控制的。
具体来说,Spring使用@TransactionInterceptor
来拦截对事务方法的调用,并在方法执行前后添加事务管理的逻辑。但是,这种代理通常只会在外部类(或Spring容器外部)对目标方法进行调用时生效。如果同一个类中的非事务方法直接调用事务方法,那么代理机制不会被触发,因为内部调用会绕过Spring容器注入的代理对象,直接调用目标对象的实例方法。
总结:当使用代理对象时@TransactionInterceptor才会进行事务
解决方法:
将事务方法移动到一个不同的Spring Bean中,并确保从原始Bean中通过Spring容器注入该Bean的引用。这样,当原始Bean中的非事务方法调用该Bean的事务方法时,Spring的代理机制将被触发,从而应用事务管理。
将bean注入进来,保证事务方法由代理对象来执行
@Autowired
MediaFileService currentProxy;
把addMediaFilesToDb提到接口中
public MediaFiles addMediaFilesToDb
(Long companyId, String fileMd5, UploadFileParamsDto uploadFileParamsDto, String bucket, String objectName);
修改serviceimpl
MediaFiles mediaFiles =
currentProxy.addMediaFilesToDb(companyId, fileMd5, uploadFileParamsDto, bucket_files, objectName);