知识库历史文件

This commit is contained in:
2025-12-20 17:12:42 +08:00
parent dfd9bb8b95
commit 62850717eb
59 changed files with 2351 additions and 276 deletions

View File

@@ -1,5 +1,8 @@
package org.xyzh.file.controller;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
@@ -94,9 +97,10 @@ public class FileController {
ResultDomain<TbSysFileDTO> fileInfo = fileService.getFileById(fileId);
String filename = fileInfo.getData() != null ? fileInfo.getData().getName() : "download";
String encodedFilename = URLEncoder.encode(filename, StandardCharsets.UTF_8).replace("+", "%20");
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + filename + "\"")
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + encodedFilename + "\"; filename*=UTF-8''" + encodedFilename)
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.body(result.getData());
}

View File

@@ -9,6 +9,8 @@ import org.springframework.util.DigestUtils;
import org.springframework.web.multipart.MultipartFile;
import org.xyzh.api.file.dto.TbSysFileDTO;
import org.xyzh.api.file.service.FileService;
import org.xyzh.common.auth.utils.LoginUtil;
import org.xyzh.common.core.domain.LoginDomain;
import org.xyzh.common.core.domain.ResultDomain;
import org.xyzh.file.config.MinioConfig;
import org.xyzh.file.mapper.FileMapper;
@@ -53,65 +55,7 @@ public class FileServiceImpl implements FileService {
if (file == null || file.isEmpty()) {
return ResultDomain.failure("文件不能为空");
}
// 生成文件信息
String originalFilename = file.getOriginalFilename();
String extension = getFileExtension(originalFilename);
String contentType = file.getContentType();
long size = file.getSize();
// 生成唯一的对象名称
String objectName = generateObjectName(originalFilename, module);
// 计算文件MD5
String md5Hash = calculateMD5(file.getBytes());
// 上传到MinIO
String bucketName = minioConfig.getBucketName();
boolean uploadSuccess = minioUtil.uploadFile(
bucketName,
objectName,
file.getInputStream(),
size,
contentType
);
if (!uploadSuccess) {
return ResultDomain.failure("文件上传到MinIO失败");
}
// 保存到数据库
TbSysFileDTO fileDTO = new TbSysFileDTO();
String fileId = UUID.randomUUID().toString();
fileDTO.setOptsn(UUID.randomUUID().toString());
fileDTO.setFileId(fileId);
fileDTO.setName(originalFilename);
fileDTO.setPath(objectName);
fileDTO.setSize(size);
fileDTO.setType(extension);
fileDTO.setStorageType("MINIO");
fileDTO.setMimeType(contentType);
// URL 设为 NULL前端通过后端接口 /api/file/download/{fileId} 下载
fileDTO.setUrl(null);
fileDTO.setStatus("NORMAL");
fileDTO.setModule(module);
fileDTO.setBusinessId(businessId);
fileDTO.setObjectName(objectName);
fileDTO.setBucketName(bucketName);
fileDTO.setMd5Hash(md5Hash);
fileDTO.setExtension(extension);
fileDTO.setCreateTime(new java.util.Date());
int result = fileMapper.insertFile(fileDTO);
if (result <= 0) {
// 如果数据库保存失败删除MinIO中的文件
minioUtil.deleteFile(bucketName, objectName);
return ResultDomain.failure("文件信息保存失败");
}
logger.info("文件上传成功: {}, 大小: {} bytes", originalFilename, size);
return ResultDomain.success("文件上传成功", fileDTO);
return uploadFileBytesWithUser(file.getBytes(), file.getOriginalFilename(), file.getContentType(), module, businessId, getCurrentUserId());
} catch (Exception e) {
logger.error("文件上传失败", e);
return ResultDomain.failure("文件上传失败: " + e.getMessage());
@@ -283,16 +227,6 @@ public class FileServiceImpl implements FileService {
}
}
/**
* @description 上传新版本文件用于文件更新fileRootId保持一致version递增
* @param file 文件对象
* @param module 所属模块
* @param businessId 业务ID
* @param fileRootId 文件根ID多版本一致
* @return ResultDomain<TbSysFileDTO> 上传结果,包含新版本文件信息
* @author yslg
* @since 2025-12-18
*/
@Override
public ResultDomain<TbSysFileDTO> uploadFileVersion(MultipartFile file, String module, String businessId, String fileRootId) {
try {
@@ -302,96 +236,105 @@ public class FileServiceImpl implements FileService {
if (fileRootId == null || fileRootId.isEmpty()) {
return ResultDomain.failure("文件根ID不能为空");
}
return uploadFileBytesVersionWithUser(file.getBytes(), file.getOriginalFilename(), file.getContentType(), module, businessId, fileRootId, getCurrentUserId());
} catch (Exception e) {
logger.error("新版本文件上传失败", e);
return ResultDomain.failure("文件上传失败: " + e.getMessage());
}
}
@Override
public ResultDomain<TbSysFileDTO> uploadFileBytes(byte[] fileBytes, String fileName, String contentType, String module, String businessId) {
return uploadFileBytesWithUser(fileBytes, fileName, contentType, module, businessId, getCurrentUserId());
}
@Override
public ResultDomain<TbSysFileDTO> uploadFileBytesWithUser(byte[] fileBytes, String fileName, String contentType, String module, String businessId, String uploaderUserId) {
try {
if (fileBytes == null || fileBytes.length == 0) {
return ResultDomain.failure("文件不能为空");
}
String extension = getFileExtension(fileName);
long size = fileBytes.length;
String objectName = generateObjectName(fileName, module);
String md5Hash = calculateMD5(fileBytes);
String bucketName = minioConfig.getBucketName();
java.io.ByteArrayInputStream inputStream = new java.io.ByteArrayInputStream(fileBytes);
boolean uploadSuccess = minioUtil.uploadFile(bucketName, objectName, inputStream, size, contentType);
if (!uploadSuccess) {
return ResultDomain.failure("文件上传到MinIO失败");
}
TbSysFileDTO fileDTO = new TbSysFileDTO();
String fileId = UUID.randomUUID().toString().replace("-", "");
fileDTO.setOptsn(UUID.randomUUID().toString());
fileDTO.setFileId(fileId);
fileDTO.setName(fileName);
fileDTO.setPath(objectName);
fileDTO.setUrl(null);
fileDTO.setSize(size);
fileDTO.setMimeType(contentType);
fileDTO.setExtension(extension);
fileDTO.setMd5Hash(md5Hash);
fileDTO.setModule(module);
fileDTO.setBusinessId(businessId);
fileDTO.setStorageType("MINIO");
fileDTO.setObjectName(objectName);
fileDTO.setBucketName(bucketName);
fileDTO.setVersion(1);
fileDTO.setFileRootId(fileId);
fileDTO.setCreator(uploaderUserId);
fileDTO.setUploader(uploaderUserId);
fileDTO.setCreateTime(new java.util.Date());
int result = fileMapper.insertFile(fileDTO);
if (result <= 0) {
minioUtil.deleteFile(bucketName, objectName);
return ResultDomain.failure("文件信息保存失败");
}
logger.info("字节数组文件上传成功: {}, uploader: {}", fileName, uploaderUserId);
return ResultDomain.success("文件上传成功", fileDTO);
} catch (Exception e) {
logger.error("字节数组文件上传失败", e);
return ResultDomain.failure("文件上传失败: " + e.getMessage());
}
}
@Override
public ResultDomain<TbSysFileDTO> uploadFileBytesVersion(byte[] fileBytes, String fileName, String contentType, String module, String businessId, String fileRootId) {
return uploadFileBytesVersionWithUser(fileBytes, fileName, contentType, module, businessId, fileRootId, getCurrentUserId());
}
@Override
public ResultDomain<TbSysFileDTO> uploadFileBytesVersionWithUser(byte[] fileBytes, String fileName, String contentType, String module, String businessId, String fileRootId, String uploaderUserId) {
try {
if (fileBytes == null || fileBytes.length == 0) {
return ResultDomain.failure("文件不能为空");
}
if (fileRootId == null || fileRootId.isEmpty()) {
return ResultDomain.failure("文件根ID不能为空");
}
// 1. 获取当前最大版本号
Integer maxVersion = fileMapper.selectMaxVersionByFileRootId(fileRootId);
int newVersion = (maxVersion != null ? maxVersion : 0) + 1;
// 2. 生成文件信息
String originalFilename = file.getOriginalFilename();
String extension = getFileExtension(originalFilename);
String contentType = file.getContentType();
long size = file.getSize();
// 3. 生成唯一的对象名称
String objectName = generateObjectName(originalFilename, module);
// 4. 计算文件MD5
String md5Hash = calculateMD5(file.getBytes());
// 5. 上传到MinIO
String bucketName = minioConfig.getBucketName();
boolean uploadSuccess = minioUtil.uploadFile(
bucketName,
objectName,
file.getInputStream(),
size,
contentType
);
if (!uploadSuccess) {
return ResultDomain.failure("文件上传到MinIO失败");
}
// 6. 构建文件访问URL
String fileUrl = minioConfig.buildFileUrl(objectName);
// 7. 保存到数据库(新版本记录)
TbSysFileDTO fileDTO = new TbSysFileDTO();
fileDTO.setOptsn(UUID.randomUUID().toString());
fileDTO.setFileId(UUID.randomUUID().toString());
fileDTO.setFileRootId(fileRootId);
fileDTO.setVersion(newVersion);
fileDTO.setName(originalFilename);
fileDTO.setPath(objectName);
fileDTO.setSize(size);
fileDTO.setType(extension);
fileDTO.setStorageType("MINIO");
fileDTO.setMimeType(contentType);
fileDTO.setUrl(fileUrl);
fileDTO.setStatus("NORMAL");
fileDTO.setModule(module);
fileDTO.setBusinessId(businessId);
fileDTO.setObjectName(objectName);
fileDTO.setBucketName(bucketName);
fileDTO.setMd5Hash(md5Hash);
fileDTO.setExtension(extension);
fileDTO.setCreateTime(new java.util.Date());
int result = fileMapper.insertFile(fileDTO);
if (result <= 0) {
// 如果数据库保存失败删除MinIO中的文件
minioUtil.deleteFile(bucketName, objectName);
return ResultDomain.failure("文件信息保存失败");
}
logger.info("新版本文件上传成功: {}, version: {}, fileRootId: {}", originalFilename, newVersion, fileRootId);
return ResultDomain.success("文件上传成功", fileDTO);
} catch (Exception e) {
logger.error("新版本文件上传失败", e);
return ResultDomain.failure("文件上传失败: " + e.getMessage());
}
}
@Override
public ResultDomain<TbSysFileDTO> uploadFileBytes(byte[] fileBytes, String fileName, String contentType, String module, String businessId) {
try {
if (fileBytes == null || fileBytes.length == 0) {
return ResultDomain.failure("文件不能为空");
}
// 生成文件信息
String extension = getFileExtension(fileName);
long size = fileBytes.length;
// 生成唯一的对象名称
// 3. 生成唯一的对象名称
String objectName = generateObjectName(fileName, module);
// 计算文件MD5
// 4. 计算文件MD5
String md5Hash = calculateMD5(fileBytes);
// 上传到MinIO
// 5. 上传到MinIO
String bucketName = minioConfig.getBucketName();
java.io.ByteArrayInputStream inputStream = new java.io.ByteArrayInputStream(fileBytes);
boolean uploadSuccess = minioUtil.uploadFile(
@@ -405,41 +348,45 @@ public class FileServiceImpl implements FileService {
if (!uploadSuccess) {
return ResultDomain.failure("文件上传到MinIO失败");
}
// 6. 构建文件访问URL
String fileUrl = minioConfig.buildFileUrl(objectName);
// 创建文件DTO
// 7. 保存到数据库(新版本记录)
TbSysFileDTO fileDTO = new TbSysFileDTO();
String fileId = UUID.randomUUID().toString().replace("-", "");
fileDTO.setOptsn(UUID.randomUUID().toString());
fileDTO.setFileId(fileId);
fileDTO.setFileId(UUID.randomUUID().toString());
fileDTO.setFileRootId(fileRootId);
fileDTO.setVersion(newVersion);
fileDTO.setName(fileName);
fileDTO.setPath(objectName);
// URL 设为 NULL前端通过后端接口 /api/file/download/{fileId} 下载
fileDTO.setUrl(null);
fileDTO.setSize(size);
fileDTO.setType(extension);
fileDTO.setStorageType("MINIO");
fileDTO.setMimeType(contentType);
fileDTO.setExtension(extension);
fileDTO.setMd5Hash(md5Hash);
fileDTO.setUrl(fileUrl);
fileDTO.setStatus("NORMAL");
fileDTO.setModule(module);
fileDTO.setBusinessId(businessId);
fileDTO.setStorageType("MINIO");
fileDTO.setObjectName(objectName);
fileDTO.setBucketName(bucketName);
fileDTO.setVersion(1);
fileDTO.setFileRootId(fileId);
fileDTO.setMd5Hash(md5Hash);
fileDTO.setExtension(extension);
fileDTO.setCreator(uploaderUserId);
fileDTO.setUploader(uploaderUserId);
fileDTO.setCreateTime(new java.util.Date());
// 保存到数据库
int result = fileMapper.insertFile(fileDTO);
if (result <= 0) {
minioUtil.deleteFile(bucketName, objectName);
return ResultDomain.failure("文件信息保存失败");
}
logger.info("字节数组文件上传成功: {}", fileName);
logger.info("新版本文件上传成功(bytes): {}, version: {}, fileRootId: {}, uploader: {}", fileName, newVersion, fileRootId, uploaderUserId);
return ResultDomain.success("文件上传成功", fileDTO);
} catch (Exception e) {
logger.error("字节数组文件上传失败", e);
logger.error("新版本文件上传失败(bytes)", e);
return ResultDomain.failure("文件上传失败: " + e.getMessage());
}
}
@@ -471,4 +418,19 @@ public class FileServiceImpl implements FileService {
private String calculateMD5(byte[] data) {
return DigestUtils.md5DigestAsHex(data);
}
/**
* 获取当前登录用户ID支持未登录场景
*/
private String getCurrentUserId() {
try {
LoginDomain loginDomain = LoginUtil.getCurrentLogin();
if (loginDomain != null && loginDomain.getUser() != null) {
return loginDomain.getUser().getUserId();
}
} catch (Exception e) {
logger.debug("获取当前登录用户失败: {}", e.getMessage());
}
return null;
}
}

View File

@@ -62,6 +62,8 @@
<if test="bucketName != null">, bucket_name</if>
<if test="md5Hash != null">, md5_hash</if>
<if test="extension != null">, extension</if>
<if test="fileRootId != null">, file_root_id</if>
<if test="version != null">, version</if>
) VALUES (
<!-- 必填字段值 -->
#{optsn}, #{fileId}, #{name}, #{path}, #{size}
@@ -86,6 +88,8 @@
<if test="bucketName != null">, #{bucketName}</if>
<if test="md5Hash != null">, #{md5Hash}</if>
<if test="extension != null">, #{extension}</if>
<if test="fileRootId != null">, #{fileRootId}</if>
<if test="version != null">, #{version}</if>
)
</insert>