知识库历史文件
This commit is contained in:
@@ -243,13 +243,13 @@ public class KnowledgeController {
|
||||
|
||||
/**
|
||||
* 获取文件历史版本,获取fileRootId下所有version
|
||||
* @param fileRootId 文件id
|
||||
* @param fileRootId 文件根ID
|
||||
* @author yslg
|
||||
* @since 2025-12-18
|
||||
*/
|
||||
@PreAuthorize("hasAuthority('ai:knowledge:file:view')")
|
||||
@GetMapping("/file/{fileId}/history")
|
||||
public ResultDomain<TbKnowledgeFile> getFileHistory(@PathVariable("fileId") @NotBlank String fileRootId) {
|
||||
@GetMapping("/file/{fileRootId}/history")
|
||||
public ResultDomain<KnowledgeFileVO> getFileHistory(@PathVariable("fileRootId") @NotBlank String fileRootId) {
|
||||
logger.info("获取文件历史: fileRootId={}", fileRootId);
|
||||
return knowledgeService.getKnowledgeFileHistory(fileRootId);
|
||||
}
|
||||
|
||||
@@ -76,4 +76,14 @@ public interface TbKnowledgeFileMapper {
|
||||
@Param("knowledgeId") String knowledgeId,
|
||||
@Param("fileRootId") String fileRootId
|
||||
);
|
||||
|
||||
/**
|
||||
* 根据文件根ID查询最大版本的文件
|
||||
*/
|
||||
TbKnowledgeFile selectLatestVersionFile(@Param("fileRootId") String fileRootId);
|
||||
|
||||
/**
|
||||
* 根据文件根ID查询所有版本(包含文件详细信息)
|
||||
*/
|
||||
List<KnowledgeFileVO> selectFileVersionsWithDetail(@Param("fileRootId") String fileRootId);
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@ public class KnowledgeServiceImpl implements KnowledgeService {
|
||||
@Autowired
|
||||
private DifyApiClient difyApiClient;
|
||||
|
||||
@DubboReference(version = "1.0.0", group = "file", timeout = 30000)
|
||||
@DubboReference(version = "1.0.0", group = "file", timeout = 30000, retries = 0)
|
||||
private FileService fileService;
|
||||
|
||||
@Autowired
|
||||
@@ -615,14 +615,22 @@ public class KnowledgeServiceImpl implements KnowledgeService {
|
||||
return ResultDomain.failure("知识库未关联Dify");
|
||||
}
|
||||
|
||||
// 3. 获取旧版本
|
||||
List<TbKnowledgeFile> oldVersions = knowledgeFileMapper.selectFileVersions(fileRootId);
|
||||
if (oldVersions == null || oldVersions.isEmpty()) {
|
||||
// 3. 获取最大版本的旧文件
|
||||
TbKnowledgeFile latestOldFile = knowledgeFileMapper.selectLatestVersionFile(fileRootId);
|
||||
if (latestOldFile == null) {
|
||||
return ResultDomain.failure("原文件不存在");
|
||||
}
|
||||
|
||||
// 4. 上传新版本到minio
|
||||
ResultDomain<TbSysFileDTO> fileResult = fileService.uploadFileVersion(file, "knowledge", knowledgeId, fileRootId);
|
||||
// 4. 上传新版本到minio(使用字节数组避免 Dubbo 序列化问题)
|
||||
byte[] fileBytes;
|
||||
try {
|
||||
fileBytes = file.getBytes();
|
||||
} catch (java.io.IOException e) {
|
||||
logger.error("读取文件字节失败", e);
|
||||
return ResultDomain.failure("读取文件字节失败: " + e.getMessage());
|
||||
}
|
||||
ResultDomain<TbSysFileDTO> fileResult = fileService.uploadFileBytesVersion(
|
||||
fileBytes, file.getOriginalFilename(), file.getContentType(), "knowledge", knowledgeId, fileRootId);
|
||||
if (!fileResult.getSuccess() || fileResult.getData() == null) {
|
||||
return ResultDomain.failure("上传新版本文件失败: " + fileResult.getMessage());
|
||||
}
|
||||
@@ -631,11 +639,9 @@ public class KnowledgeServiceImpl implements KnowledgeService {
|
||||
int newVersion = sysFile.getVersion();
|
||||
logger.info("上传新版本到minio成功: fileId={}, version={}", newFileId, newVersion);
|
||||
|
||||
// 5. 删除Dify旧文档
|
||||
for (TbKnowledgeFile oldFile : oldVersions) {
|
||||
if (StringUtils.hasText(oldFile.getDifyFileId())) {
|
||||
aiFileUploadService.deleteFileFromDify(knowledge.getDifyDatasetId(), oldFile.getDifyFileId());
|
||||
}
|
||||
// 5. 删除Dify最大版本的旧文档
|
||||
if (StringUtils.hasText(latestOldFile.getDifyFileId())) {
|
||||
aiFileUploadService.deleteFileFromDify(knowledge.getDifyDatasetId(), latestOldFile.getDifyFileId());
|
||||
}
|
||||
|
||||
// 6. 上传新文件到Dify
|
||||
@@ -734,17 +740,19 @@ public class KnowledgeServiceImpl implements KnowledgeService {
|
||||
/**
|
||||
* @description 获取文件历史版本
|
||||
* @param fileRootId 文件根ID
|
||||
* @return ResultDomain<TbKnowledgeFile> 文件历史版本列表
|
||||
* @return ResultDomain<KnowledgeFileVO> 文件历史版本列表(dataList)
|
||||
* @author yslg
|
||||
* @since 2025-12-18
|
||||
*/
|
||||
@Override
|
||||
public ResultDomain<TbKnowledgeFile> getKnowledgeFileHistory(String fileRootId) {
|
||||
public ResultDomain<KnowledgeFileVO> getKnowledgeFileHistory(String fileRootId) {
|
||||
if (!StringUtils.hasText(fileRootId)) {
|
||||
return ResultDomain.failure("文件根ID不能为空");
|
||||
}
|
||||
|
||||
List<TbKnowledgeFile> versions = knowledgeFileMapper.selectFileVersions(fileRootId);
|
||||
return ResultDomain.success("查询成功", versions);
|
||||
List<KnowledgeFileVO> versions = knowledgeFileMapper.selectFileVersionsWithDetail(fileRootId);
|
||||
ResultDomain<KnowledgeFileVO> result = ResultDomain.success("查询成功");
|
||||
result.setDataList(versions);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
<result column="file_url" property="fileUrl" jdbcType="VARCHAR"/>
|
||||
<result column="file_extension" property="fileExtension" jdbcType="VARCHAR"/>
|
||||
<result column="file_md5_hash" property="fileMd5Hash" jdbcType="VARCHAR"/>
|
||||
<result column="uploader_name" property="uploaderName" jdbcType="VARCHAR"/>
|
||||
</resultMap>
|
||||
|
||||
<sql id="Base_Column_List">
|
||||
@@ -83,9 +84,17 @@
|
||||
f.mime_type as file_mime_type,
|
||||
f.url as file_url,
|
||||
f.extension as file_extension,
|
||||
f.md5_hash as file_md5_hash
|
||||
f.md5_hash as file_md5_hash,
|
||||
ui.username as uploader_name
|
||||
FROM ai.tb_knowledge_file kf
|
||||
INNER JOIN (
|
||||
SELECT file_root_id, MAX(version) as max_version
|
||||
FROM ai.tb_knowledge_file
|
||||
WHERE knowledge_id = #{knowledgeId} AND deleted = false
|
||||
GROUP BY file_root_id
|
||||
) latest ON kf.file_root_id = latest.file_root_id AND kf.version = latest.max_version
|
||||
LEFT JOIN file.tb_sys_file f ON kf.file_id = f.file_id AND f.deleted = false
|
||||
LEFT JOIN sys.tb_sys_user_info ui ON f.uploader = ui.user_id AND ui.deleted = false
|
||||
WHERE kf.knowledge_id = #{knowledgeId} AND kf.deleted = false
|
||||
ORDER BY kf.create_time DESC
|
||||
</select>
|
||||
@@ -107,16 +116,24 @@
|
||||
f.mime_type as file_mime_type,
|
||||
f.url as file_url,
|
||||
f.extension as file_extension,
|
||||
f.md5_hash as file_md5_hash
|
||||
f.md5_hash as file_md5_hash,
|
||||
ui.username as uploader_name
|
||||
FROM ai.tb_knowledge_file kf
|
||||
INNER JOIN (
|
||||
SELECT file_root_id, MAX(version) as max_version
|
||||
FROM ai.tb_knowledge_file
|
||||
WHERE knowledge_id = #{knowledgeId} AND deleted = false
|
||||
GROUP BY file_root_id
|
||||
) latest ON kf.file_root_id = latest.file_root_id AND kf.version = latest.max_version
|
||||
LEFT JOIN file.tb_sys_file f ON kf.file_id = f.file_id AND f.deleted = false
|
||||
LEFT JOIN sys.tb_sys_user_info ui ON f.uploader = ui.user_id AND ui.deleted = false
|
||||
WHERE kf.knowledge_id = #{knowledgeId} AND kf.deleted = false
|
||||
ORDER BY kf.create_time DESC
|
||||
LIMIT #{pageParam.pageSize} OFFSET #{pageParam.offset}
|
||||
</select>
|
||||
|
||||
<select id="countFiles" resultType="long">
|
||||
SELECT COUNT(*)
|
||||
SELECT COUNT(DISTINCT file_root_id)
|
||||
FROM ai.tb_knowledge_file
|
||||
WHERE knowledge_id = #{knowledgeId} AND deleted = false
|
||||
</select>
|
||||
@@ -133,4 +150,31 @@
|
||||
FROM ai.tb_knowledge_file
|
||||
WHERE knowledge_id = #{knowledgeId} AND file_root_id = #{fileRootId}
|
||||
</select>
|
||||
|
||||
<select id="selectLatestVersionFile" resultMap="BaseResultMap">
|
||||
SELECT <include refid="Base_Column_List"/>
|
||||
FROM ai.tb_knowledge_file
|
||||
WHERE file_root_id = #{fileRootId} AND deleted = false
|
||||
ORDER BY version DESC
|
||||
LIMIT 1
|
||||
</select>
|
||||
|
||||
<select id="selectFileVersionsWithDetail" resultMap="KnowledgeFileVOResultMap">
|
||||
SELECT
|
||||
kf.optsn, kf.knowledge_id, kf.file_root_id, kf.file_id, kf.dify_file_id, kf.version,
|
||||
kf.create_time, kf.update_time, kf.delete_time, kf.deleted,
|
||||
f.name as file_name,
|
||||
f.path as file_path,
|
||||
f.size as file_size,
|
||||
f.mime_type as file_mime_type,
|
||||
f.url as file_url,
|
||||
f.extension as file_extension,
|
||||
f.md5_hash as file_md5_hash,
|
||||
ui.username as uploader_name
|
||||
FROM ai.tb_knowledge_file kf
|
||||
LEFT JOIN file.tb_sys_file f ON kf.file_id = f.file_id AND f.deleted = false
|
||||
LEFT JOIN sys.tb_sys_user_info ui ON f.uploader = ui.user_id AND ui.deleted = false
|
||||
WHERE kf.file_root_id = #{fileRootId} AND kf.deleted = false
|
||||
ORDER BY kf.version DESC
|
||||
</select>
|
||||
</mapper>
|
||||
|
||||
@@ -162,10 +162,10 @@ public interface KnowledgeService {
|
||||
/**
|
||||
* @description 获取文件历史版本
|
||||
* @param fileRootId 文件根ID
|
||||
* @return ResultDomain<TbKnowledgeFile> 文件历史版本列表
|
||||
* @return ResultDomain<KnowledgeFileVO> 文件历史版本列表(dataList)
|
||||
* @author yslg
|
||||
* @since 2025-12-18
|
||||
*/
|
||||
ResultDomain<TbKnowledgeFile> getKnowledgeFileHistory(String fileRootId);
|
||||
ResultDomain<KnowledgeFileVO> getKnowledgeFileHistory(String fileRootId);
|
||||
|
||||
}
|
||||
|
||||
@@ -57,4 +57,7 @@ public class KnowledgeFileVO extends BaseVO {
|
||||
|
||||
@Schema(description = "文件MD5值")
|
||||
private String fileMd5Hash;
|
||||
|
||||
@Schema(description = "上传人员名称")
|
||||
private String uploaderName;
|
||||
}
|
||||
|
||||
@@ -108,4 +108,47 @@ public interface FileService {
|
||||
*/
|
||||
ResultDomain<TbSysFileDTO> uploadFileBytes(byte[] fileBytes, String fileName, String contentType, String module, String businessId);
|
||||
|
||||
/**
|
||||
* @description 通过字节数组上传文件(支持直接指定上传者,用于未登录用户场景如AIChat)
|
||||
* @param fileBytes 文件字节数组
|
||||
* @param fileName 文件名
|
||||
* @param contentType 文件类型
|
||||
* @param module 所属模块
|
||||
* @param businessId 业务ID
|
||||
* @param uploaderUserId 上传者用户ID(可为null)
|
||||
* @return ResultDomain<TbSysFileDTO> 上传结果
|
||||
* @author yslg
|
||||
* @since 2025-12-20
|
||||
*/
|
||||
ResultDomain<TbSysFileDTO> uploadFileBytesWithUser(byte[] fileBytes, String fileName, String contentType, String module, String businessId, String uploaderUserId);
|
||||
|
||||
/**
|
||||
* @description 通过字节数组上传新版本文件(用于跨模块 Dubbo 调用的文件版本更新)
|
||||
* @param fileBytes 文件字节数组
|
||||
* @param fileName 文件名
|
||||
* @param contentType 文件类型
|
||||
* @param module 所属模块
|
||||
* @param businessId 业务ID
|
||||
* @param fileRootId 文件根ID(多版本一致)
|
||||
* @return ResultDomain<TbSysFileDTO> 上传结果,包含新版本文件信息
|
||||
* @author yslg
|
||||
* @since 2025-12-20
|
||||
*/
|
||||
ResultDomain<TbSysFileDTO> uploadFileBytesVersion(byte[] fileBytes, String fileName, String contentType, String module, String businessId, String fileRootId);
|
||||
|
||||
/**
|
||||
* @description 通过字节数组上传新版本文件(支持直接指定上传者)
|
||||
* @param fileBytes 文件字节数组
|
||||
* @param fileName 文件名
|
||||
* @param contentType 文件类型
|
||||
* @param module 所属模块
|
||||
* @param businessId 业务ID
|
||||
* @param fileRootId 文件根ID(多版本一致)
|
||||
* @param uploaderUserId 上传者用户ID(可为null)
|
||||
* @return ResultDomain<TbSysFileDTO> 上传结果,包含新版本文件信息
|
||||
* @author yslg
|
||||
* @since 2025-12-20
|
||||
*/
|
||||
ResultDomain<TbSysFileDTO> uploadFileBytesVersionWithUser(byte[] fileBytes, String fileName, String contentType, String module, String businessId, String fileRootId, String uploaderUserId);
|
||||
|
||||
}
|
||||
|
||||
@@ -34,10 +34,10 @@ public class AuthController {
|
||||
@Autowired
|
||||
private AuthService authService;
|
||||
|
||||
@DubboReference(version = "1.0.0", group = "system", timeout = 5000, check = false)
|
||||
@DubboReference(version = "1.0.0", group = "system", timeout = 5000, check = false, retries = 0)
|
||||
private SysUserService userService;
|
||||
|
||||
@DubboReference(version = "1.0.0", group = "message", timeout = 5000, check = false)
|
||||
@DubboReference(version = "1.0.0", group = "message", timeout = 5000, check = false, retries = 0)
|
||||
private MessageService messageService;
|
||||
|
||||
@Autowired
|
||||
|
||||
@@ -59,10 +59,10 @@ public class AuthServiceImpl implements AuthService{
|
||||
private RedisService redisService;
|
||||
|
||||
|
||||
@DubboReference(version = "1.0.0", group = "system", timeout = 5000, check = false)
|
||||
@DubboReference(version = "1.0.0", group = "system", timeout = 5000, check = false, retries = 0)
|
||||
private SysUserService userService;
|
||||
|
||||
@DubboReference(version = "1.0.0", group = "system", timeout = 5000, check = false)
|
||||
@DubboReference(version = "1.0.0", group = "system", timeout = 5000, check = false, retries = 0)
|
||||
private ModulePermissionService modulePermissionService;
|
||||
|
||||
@Autowired
|
||||
|
||||
@@ -84,17 +84,28 @@ public class LoginUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* 从请求头获取Token
|
||||
* 从请求头或Dubbo RpcContext获取Token
|
||||
*/
|
||||
public static String getToken() {
|
||||
// 1. 优先从HTTP请求头获取(正常Web请求)
|
||||
HttpServletRequest request = getRequest();
|
||||
if (request == null) {
|
||||
return null;
|
||||
if (request != null) {
|
||||
String authHeader = request.getHeader(AUTHORIZATION_HEADER);
|
||||
if (StringUtils.hasText(authHeader) && authHeader.startsWith(BEARER_PREFIX)) {
|
||||
return authHeader.substring(BEARER_PREFIX.length());
|
||||
}
|
||||
}
|
||||
|
||||
String authHeader = request.getHeader(AUTHORIZATION_HEADER);
|
||||
if (StringUtils.hasText(authHeader) && authHeader.startsWith(BEARER_PREFIX)) {
|
||||
return authHeader.substring(BEARER_PREFIX.length());
|
||||
// 2. 从Dubbo Provider ThreadLocal获取(跨服务调用)
|
||||
try {
|
||||
Class<?> filterClass = Class.forName("org.xyzh.common.auth.filter.DubboProviderContextFilter");
|
||||
ThreadLocal<String> tokenHolder = (ThreadLocal<String>) filterClass.getField("TOKEN_HOLDER").get(null);
|
||||
String token = tokenHolder.get();
|
||||
if (StringUtils.hasText(token)) {
|
||||
return token;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// Dubbo Filter不存在或未加载,忽略
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
@@ -2,6 +2,10 @@ package org.xyzh.common.vo;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import org.xyzh.common.dto.OrderField;
|
||||
|
||||
import com.alibaba.fastjson2.annotation.JSONField;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
|
||||
@@ -54,5 +58,17 @@ public class BaseVO implements Serializable {
|
||||
|
||||
@Schema(description = "是否已删除", defaultValue = "false")
|
||||
private Boolean deleted = false;
|
||||
|
||||
@Schema(description = "数量限制")
|
||||
private Integer limit;
|
||||
|
||||
@Schema(description = "开始时间")
|
||||
private Date startTime;
|
||||
|
||||
@Schema(description = "结束时间")
|
||||
private Date endTime;
|
||||
|
||||
@Schema(description = "排序字段")
|
||||
private List<OrderField> orderFields;
|
||||
}
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ public class WeChatKefuInit {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(WeChatKefuInit.class);
|
||||
|
||||
@DubboReference(version = "1.0.0", group = "system", check = false)
|
||||
@DubboReference(version = "1.0.0", group = "system", check = false, retries = 0)
|
||||
private SysConfigService sysConfigService;
|
||||
|
||||
private static WeChatKefuConfig weChatConfig;
|
||||
|
||||
@@ -29,7 +29,7 @@ public class KefuAccessTokenManager {
|
||||
|
||||
private final RestTemplate restTemplate = new RestTemplate();
|
||||
|
||||
@DubboReference(version = "1.0.0", group = "system", check = false)
|
||||
@DubboReference(version = "1.0.0", group = "system", check = false, retries = 0)
|
||||
private SysConfigService sysConfigService;
|
||||
|
||||
private String corpId;
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ public class DynamicConfigLoader implements ApplicationRunner {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(DynamicConfigLoader.class);
|
||||
|
||||
@DubboReference(version = "1.0.0", group = "system", timeout = 5000, check = false)
|
||||
@DubboReference(version = "1.0.0", group = "system", timeout = 5000, check = false, retries = 0)
|
||||
private SysConfigService sysConfigService;
|
||||
|
||||
@Autowired(required = false)
|
||||
|
||||
@@ -30,10 +30,10 @@ public class KnowledgeInit {
|
||||
private static final String CATEGORY_INTERNAL = "internal";
|
||||
private static final String CATEGORY_EXTERNAL = "external";
|
||||
|
||||
@DubboReference(version = "1.0.0", group = "ai", timeout = 30000)
|
||||
@DubboReference(version = "1.0.0", group = "ai", timeout = 30000, retries = 0)
|
||||
private KnowledgeService knowledgeService;
|
||||
|
||||
@DubboReference(version = "1.0.0", group = "system", timeout = 30000)
|
||||
@DubboReference(version = "1.0.0", group = "system", timeout = 30000, retries = 0)
|
||||
private SysConfigService sysConfigService;
|
||||
|
||||
@Bean
|
||||
|
||||
@@ -33,7 +33,7 @@ public class WorkcaseChatServiceImpl implements WorkcaseChatService{
|
||||
private static final String CHAT_COUNT_KEY_PREFIX = "workcase:chat:count:";
|
||||
private static final int TRANSFER_HUMAN_THRESHOLD = 3;
|
||||
|
||||
@DubboReference(version = "1.0.0", group = "ai", check = false)
|
||||
@DubboReference(version = "1.0.0", group = "ai", check = false, retries = 0)
|
||||
private AgentChatService agentChatService;
|
||||
|
||||
@Autowired
|
||||
|
||||
Reference in New Issue
Block a user