diff --git a/common/src/main/java/com/sdm/common/entity/req/data/ChunkUploadMinioFileReq.java b/common/src/main/java/com/sdm/common/entity/req/data/ChunkUploadMinioFileReq.java index 5a9e9862..3c9d7fd5 100644 --- a/common/src/main/java/com/sdm/common/entity/req/data/ChunkUploadMinioFileReq.java +++ b/common/src/main/java/com/sdm/common/entity/req/data/ChunkUploadMinioFileReq.java @@ -6,6 +6,15 @@ import org.springframework.web.multipart.MultipartFile; @Data public class ChunkUploadMinioFileReq { + // 接口5.1 前端生成的,用于发起统一一次审批流的凭证 + private String uploadTaskId; + + // 接口5.1 返回的这个文件对应的业务数据主键id + private Long businessId; + + // 接口5.1 返回的 objectKey + private String objectKey; + // 原始文件的名称 private String sourceFileName; @@ -21,7 +30,8 @@ public class ChunkUploadMinioFileReq { // 分块文件传输对象 private MultipartFile file; - // 第一片请求不传,后面的请求必传,第一次请求成功后后端会返回,本次文件的父目录 - private String fileDirPath; + // 单一文件维度。第一片请求不传,后面的请求必传,第一次请求成功后后端会返回,本次文件的父目录 + private String fileTempPath; + } diff --git a/common/src/main/java/com/sdm/common/entity/resp/data/ChunkUploadMinioFileResp.java b/common/src/main/java/com/sdm/common/entity/resp/data/ChunkUploadMinioFileResp.java index 31657407..e39856da 100644 --- a/common/src/main/java/com/sdm/common/entity/resp/data/ChunkUploadMinioFileResp.java +++ b/common/src/main/java/com/sdm/common/entity/resp/data/ChunkUploadMinioFileResp.java @@ -5,16 +5,19 @@ import lombok.Data; @Data public class ChunkUploadMinioFileResp { - // 临时文件的父级的父级目录,第一次请求后返回,后面每次同一分片任务请求都必须携带 - private String fileDirPath; + // 本次分片上传的结果 true 成功,false 失败 + private Boolean result; - // 最后合并文件的绝对路径 - private String filePath=""; + // 文件关联的业务数据Id + private Long businessId; - // 分片异常msg,正常无数据 - private String errMsg=""; + // 分片上传的任务Id,用于回调处理的审批流创建 + private String uploadTaskId; - // 最后合并文件的桶名称 - private String bucketName; + // 分片文件的临时目录,第一次请求后,每次都会返回 + private String fileTempPath; + + // 失败的原因 + private String errMsg; } diff --git a/data/src/main/java/com/sdm/data/service/impl/MinioFileIDataFileServiceImpl.java b/data/src/main/java/com/sdm/data/service/impl/MinioFileIDataFileServiceImpl.java index 7914b4c8..852b12b7 100644 --- a/data/src/main/java/com/sdm/data/service/impl/MinioFileIDataFileServiceImpl.java +++ b/data/src/main/java/com/sdm/data/service/impl/MinioFileIDataFileServiceImpl.java @@ -96,10 +96,6 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService { @Value("${fileSystem.chunkBucket:spdm}") private String chunkBucket; - // 路径待确定 待配置及初始化 - @Value("${fileSystem.chunkBasePath:/chunkBase}") - private String chunkBasePath; - @Autowired private IFileMetadataInfoService fileMetadataInfoService; @@ -223,58 +219,52 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService { public SdmResponse chunkUploadToMinio(ChunkUploadMinioFileReq req) { ChunkUploadMinioFileResp resp = new ChunkUploadMinioFileResp(); // 基础路径配置 - Long tenantId = ThreadLocalContext.getTenantId(); -// Long tenantId = 123456l; -// Long userId = 9999l; - Long userId= ThreadLocalContext.getUserId(); // -2. 参数校验 try { - validateReq(req,tenantId,userId); + validateReq(req); } catch (Exception e) { CoreLogger.error("validateReq error:{}", e.getMessage()); - return buildFailedResponse(resp,e.getMessage(),"",chunkBucket); + return buildFailedResponse(resp,e.getMessage(),req); } // -1.确定文件夹 String timestamp = String.valueOf(System.currentTimeMillis()); // 合并目录 - String filePath = org.apache.commons.lang3.StringUtils.isNotBlank(req.getFileDirPath())? - req.getFileDirPath():chunkBasePath + "/" + tenantId + "/" + userId +"/" + timestamp + "/"; + String filePath = getMinioFolderPath(req.getObjectKey()); // 0. 一个文件直接传 if(Objects.equals(req.getChunkTotal(),NumberConstants.ONE)&& Objects.equals(req.getChunk(),NumberConstants.ONE)){ String finalFileName = filePath + req.getSourceFileName(); Boolean b = minioService.chunkUpload(chunkBucket, req.getFile(), finalFileName); if(!b){ - return buildFailedResponse(resp,"单一文件上传失败",filePath,chunkBucket); + return buildFailedResponse(resp,"单一文件上传失败",req); } - return buildSuccessResponse(resp,finalFileName,filePath,chunkBucket); + return buildSuccessResponse(resp,req,""); } - - String tempDirPath = filePath +"temp/"; + // 碎片目录 + String tempDirPath = org.apache.commons.lang3.StringUtils.isBlank(req.getFileTempPath())? + filePath +"temp/"+timestamp+"/":req.getFileTempPath(); // 1. 保存当前分片到临时目录 1 2 3 4 ....temp String chunkFileName =tempDirPath+req.getChunk()+ PermConstants.CHUNK_TEMPFILE_SUFFIX; // 片文件上传到minio Boolean b = minioService.chunkUpload(chunkBucket, req.getFile(), chunkFileName); if(!b){ - return buildFailedResponse(resp,"chunkUpload第"+req.getChunk()+"次失败",filePath,chunkBucket); + deleteTempFileAfterFailed(tempDirPath,chunkBucket); + return buildFailedResponse(resp,"chunkUpload第"+req.getChunk()+"次失败",req); } // 2. 判断分片是否已全部上传完毕 if (req.getChunk() < req.getChunkTotal()) { - return buildSuccessResponse(resp,"",filePath,chunkBucket); + return buildSuccessResponse(resp,req,tempDirPath); } // 3. 全部分片已经上传 => 自动合并 String finalFileName = filePath + req.getSourceFileName(); Boolean merge = minioService.merge(chunkBucket, tempDirPath, chunkBucket, finalFileName); if(!merge){ - return buildFailedResponse(resp,req.getSourceFileName()+"合并分片失败",filePath,chunkBucket); + deleteTempFileAfterFailed(tempDirPath,chunkBucket); + return buildFailedResponse(resp,req.getSourceFileName()+"合并分片失败",req); } // 4. 合并完成后删除临时目录 - String finalTempDirPath=tempDirPath; - String finalChunkBucket=chunkBucket; - CompletableFuture.runAsync(() -> { - minioService.deleteDirectoryRecursively2(finalTempDirPath, finalChunkBucket); - }, nonSensitiveTaskPool); - return buildSuccessResponse(resp,finalFileName,filePath,chunkBucket); + deleteTempFileAfterFailed(tempDirPath,chunkBucket); + return buildSuccessResponse(resp,req,tempDirPath); } @@ -2148,10 +2138,8 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService { /** * 参数校验 */ - private void validateReq(ChunkUploadMinioFileReq req, Long tenantId, Long userId) { + private void validateReq(ChunkUploadMinioFileReq req) { // 基础参数校验 - Assert.notNull(tenantId, "租户ID不能为空"); - Assert.notNull(userId, "用户ID不能为空"); Assert.hasText(req.getSourceFileName(), "原始文件名称不能为空"); Assert.notNull(req.getChunk(), "分片编号不能为空"); Assert.notNull(req.getChunkTotal(), "分片总数不能为空"); @@ -2161,28 +2149,60 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService { Assert.isTrue(req.getChunk() >= 1 && req.getChunk() <= req.getChunkTotal(), "分片编号非法:当前" + req.getChunk() + ",总分片" + req.getChunkTotal()); Assert.isTrue(req.getChunkTotal() >= 1, "分片总数必须大于等于1"); + Assert.hasText(req.getObjectKey(), "文件存储位置不能为空"); + Assert.hasText(req.getUploadTaskId(), "文件上传任务Id不能为空"); + Assert.notNull(req.getBusinessId(), "文件绑定业务Id不能为空"); if(req.getChunk() > NumberConstants.ONE) { - Assert.hasText(req.getFileDirPath(), "分片父级目录不允许为空"); + Assert.hasText(req.getFileTempPath(), "分片临时目录不允许为空"); } } - // 包含业务数据的响应体 - private SdmResponse buildSuccessResponse(ChunkUploadMinioFileResp resp, String finalFileName, String fileDirPath,String chunkBucket) { - resp.setBucketName(chunkBucket); - resp.setFilePath(finalFileName); - resp.setFileDirPath(fileDirPath); - // 成功时,错误信息通常为空 - resp.setErrMsg(""); - return SdmResponse.success(resp); - } + // 包含业务数据的响应体 + private SdmResponse buildSuccessResponse(ChunkUploadMinioFileResp resp, ChunkUploadMinioFileReq req,String fileTempPath) { + resp.setResult(true); + resp.setBusinessId(req.getBusinessId()); + resp.setUploadTaskId(req.getUploadTaskId()); + resp.setFileTempPath(fileTempPath); + // 成功时,错误信息通常为空 + resp.setErrMsg(""); + return SdmResponse.success(resp); + } - // 构建一个失败的响应对象 - private SdmResponse buildFailedResponse(ChunkUploadMinioFileResp resp, String errMsg, String fileDirPath,String chunkBucket) { - resp.setBucketName(chunkBucket); - resp.setFileDirPath(fileDirPath); + // 构建一个失败的响应对象 + private SdmResponse buildFailedResponse(ChunkUploadMinioFileResp resp, String errMsg,ChunkUploadMinioFileReq req) { + resp.setResult(false); + resp.setBusinessId(req.getBusinessId()); + resp.setUploadTaskId(req.getUploadTaskId()); // 如果 errMsg 为 null,设置为空字符串 "" resp.setErrMsg(errMsg != null ? errMsg : ""); return SdmResponse.failed(resp); } + /** + * 从文件路径字符串中获取文件夹路径(仅适用于 '/' 作为分隔符的情况) + * @param filePath 完整文件路径(如:knowledge/20251111-yy/仿真地图 (46)_V1.xls) + * @return 文件夹路径(如:knowledge/20251111-yy/),若输入为空或无分隔符则返回原路径 + */ + private static String getMinioFolderPath(String filePath) { + // 1. 校验输入合法性 + if (filePath == null || filePath.trim().isEmpty()) { + return filePath; + } + // 2. 查找最后一个 '/' 分隔符的位置 + int lastSeparatorIndex = filePath.lastIndexOf("/"); + // 3. 若未找到分隔符(说明是单纯文件名,无路径),返回原字符串 + if (lastSeparatorIndex == -1) { + return filePath; + } + // 4. 截取从开头到最后一个分隔符(包含分隔符)的子字符串 + return filePath.substring(0, lastSeparatorIndex + 1); + } + + // 分片上传,异常的时候删除临时目录,假如后面断点续传,某些场景的删除要最后确定失败再删除 + private void deleteTempFileAfterFailed(String tempFilePath,String finalChunkBucket) { + CompletableFuture.runAsync(() -> { + minioService.deleteDirectoryRecursively2(tempFilePath, finalChunkBucket); + }, nonSensitiveTaskPool); + } + } \ No newline at end of file