From 8bd56a6001a208390cc2d3f31e0f47786b26b8d4 Mon Sep 17 00:00:00 2001 From: meowrain Date: Sat, 10 Jan 2026 19:38:30 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E6=96=87=E4=BB=B6?= =?UTF-8?q?=E5=93=88=E5=B8=8C=E6=A3=80=E6=9F=A5=E5=8A=9F=E8=83=BD=EF=BC=8C?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E7=A7=92=E4=BC=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 HashCheckRespDTO 用于哈希检查响应 - 文件上传接口支持可选的 hash 参数,用于秒传 - 新增 /check 接口用于检查文件哈希是否存在 - 简化上传逻辑,移除同步/异步哈希计算配置 Co-Authored-By: Claude --- .../controller/AttachmentController.java | 12 ++- .../dto/resp/HashCheckRespDTO.java | 16 ++++ .../service/AttachmentService.java | 11 ++- .../service/impl/AttachmentServiceImpl.java | 76 +++++++++---------- 4 files changed, 71 insertions(+), 44 deletions(-) create mode 100644 aioj-backend-file-service/src/main/java/cn/meowrain/aioj/backend/fileservice/dto/resp/HashCheckRespDTO.java diff --git a/aioj-backend-file-service/src/main/java/cn/meowrain/aioj/backend/fileservice/controller/AttachmentController.java b/aioj-backend-file-service/src/main/java/cn/meowrain/aioj/backend/fileservice/controller/AttachmentController.java index a6ee585..3f85504 100644 --- a/aioj-backend-file-service/src/main/java/cn/meowrain/aioj/backend/fileservice/controller/AttachmentController.java +++ b/aioj-backend-file-service/src/main/java/cn/meowrain/aioj/backend/fileservice/controller/AttachmentController.java @@ -1,6 +1,7 @@ package cn.meowrain.aioj.backend.fileservice.controller; import cn.meowrain.aioj.backend.fileservice.dao.entity.AttachmentDO; +import cn.meowrain.aioj.backend.fileservice.dto.resp.HashCheckRespDTO; import cn.meowrain.aioj.backend.fileservice.service.AttachmentService; import cn.meowrain.aioj.backend.framework.core.web.Result; import cn.meowrain.aioj.backend.framework.core.web.Results; @@ -29,8 +30,15 @@ public class AttachmentController { private final AttachmentService attachmentService; @Operation(summary = "通用文件上传组件") @PostMapping("/upload") - public Result uploadFile(@RequestParam("file")MultipartFile file) { - return Results.success(attachmentService.upload(file)); + public Result uploadFile(@RequestParam("file") MultipartFile file, + @RequestParam(value = "hash", required = false) String hash) { + return Results.success(attachmentService.upload(file, hash)); + } + + @Operation(summary = "哈希是否存在") + @GetMapping("/check") + public Result checkHash(@RequestParam("hash") String hash) { + return Results.success(attachmentService.checkHash(hash)); } /** diff --git a/aioj-backend-file-service/src/main/java/cn/meowrain/aioj/backend/fileservice/dto/resp/HashCheckRespDTO.java b/aioj-backend-file-service/src/main/java/cn/meowrain/aioj/backend/fileservice/dto/resp/HashCheckRespDTO.java new file mode 100644 index 0000000..f8caa76 --- /dev/null +++ b/aioj-backend-file-service/src/main/java/cn/meowrain/aioj/backend/fileservice/dto/resp/HashCheckRespDTO.java @@ -0,0 +1,16 @@ +package cn.meowrain.aioj.backend.fileservice.dto.resp; + +import lombok.Data; + +@Data +public class HashCheckRespDTO { + Boolean exists; + Long fileId; + String url; + + @Data + static class FileInfo { + Long size; + String mime; + } +} diff --git a/aioj-backend-file-service/src/main/java/cn/meowrain/aioj/backend/fileservice/service/AttachmentService.java b/aioj-backend-file-service/src/main/java/cn/meowrain/aioj/backend/fileservice/service/AttachmentService.java index e279df8..5c12664 100644 --- a/aioj-backend-file-service/src/main/java/cn/meowrain/aioj/backend/fileservice/service/AttachmentService.java +++ b/aioj-backend-file-service/src/main/java/cn/meowrain/aioj/backend/fileservice/service/AttachmentService.java @@ -1,6 +1,7 @@ package cn.meowrain.aioj.backend.fileservice.service; import cn.meowrain.aioj.backend.fileservice.dao.entity.AttachmentDO; +import cn.meowrain.aioj.backend.fileservice.dto.resp.HashCheckRespDTO; import com.baomidou.mybatisplus.extension.service.IService; import org.springframework.web.multipart.MultipartFile; @@ -15,5 +16,13 @@ import org.springframework.web.multipart.MultipartFile; public interface AttachmentService extends IService { - AttachmentDO upload(MultipartFile file); + AttachmentDO upload(MultipartFile file, String hash); + + /** + * 检查文件哈希是否存在(用于秒传) + * + * @param hash 文件哈希值 + * @return 哈希检查结果 + */ + HashCheckRespDTO checkHash(String hash); } diff --git a/aioj-backend-file-service/src/main/java/cn/meowrain/aioj/backend/fileservice/service/impl/AttachmentServiceImpl.java b/aioj-backend-file-service/src/main/java/cn/meowrain/aioj/backend/fileservice/service/impl/AttachmentServiceImpl.java index 37e86a1..a82b38e 100644 --- a/aioj-backend-file-service/src/main/java/cn/meowrain/aioj/backend/fileservice/service/impl/AttachmentServiceImpl.java +++ b/aioj-backend-file-service/src/main/java/cn/meowrain/aioj/backend/fileservice/service/impl/AttachmentServiceImpl.java @@ -3,18 +3,16 @@ package cn.meowrain.aioj.backend.fileservice.service.impl; import cn.hutool.core.io.FileUtil; import cn.hutool.core.util.IdUtil; import cn.hutool.core.util.StrUtil; -import cn.hutool.crypto.digest.DigestUtil; import cn.meowrain.aioj.backend.fileservice.dao.entity.AttachmentDO; import cn.meowrain.aioj.backend.fileservice.dao.mapper.AttachmentMapper; +import cn.meowrain.aioj.backend.fileservice.dto.resp.HashCheckRespDTO; import cn.meowrain.aioj.backend.fileservice.service.AttachmentService; -import cn.meowrain.aioj.backend.fileservice.service.HashCalculationService; import cn.meowrain.aioj.backend.fileservice.storage.FileStorageStrategy; import cn.meowrain.aioj.backend.framework.core.enums.DelStatusEnum; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; @@ -35,58 +33,30 @@ import java.time.LocalDateTime; public class AttachmentServiceImpl extends ServiceImpl implements AttachmentService { private final FileStorageStrategy fileStorageStrategy; - private final HashCalculationService hashCalculationService; - - /** - * 是否启用文件去重 - */ - @Value("${file-config.deduplication-enabled:true}") - private boolean deduplicationEnabled; - - /** - * 同步计算哈希的文件大小阈值(字节) - * 超过此大小异步计算,默认 10MB - */ - @Value("${file-config.sync-hash-threshold:10485760}") - private long syncHashThreshold; @Override - public AttachmentDO upload(MultipartFile file) { + public AttachmentDO upload(MultipartFile file, String hash) { try { String originalFilename = file.getOriginalFilename(); String fileExtension = FileUtil.extName(originalFilename); - long fileSize = file.getSize(); - - // 判断文件大小,选择处理方式 - boolean isSmallFile = fileSize <= syncHashThreshold; - - if (deduplicationEnabled && isSmallFile) { - // ========== 小文件:同步计算哈希,立即去重 ========== - String fileHash = DigestUtil.sha256Hex(file.getInputStream()); + // 如果传入了hash,先检查是否已存在(秒传) + if (StrUtil.isNotBlank(hash)) { AttachmentDO existingFile = getOne(new LambdaQueryWrapper() - .eq(AttachmentDO::getFileHash, fileHash) + .eq(AttachmentDO::getFileHash, hash) .eq(AttachmentDO::getIsDeleted, DelStatusEnum.STATUS_NORMAL.code()) .last("LIMIT 1")); if (existingFile != null) { - log.info("小文件已存在,返回已有文件: fileName={}, fileHash={}, existingId={}", - originalFilename, fileHash, existingFile.getId()); + log.info("文件已存在,返回已有文件: fileName={}, hash={}, existingId={}", + originalFilename, hash, existingFile.getId()); return existingFile; } - - // 文件不存在,上传并保存哈希 - return uploadAndSave(file, originalFilename, fileExtension, fileHash); - } else { - // ========== 大文件:直接上传,异步计算哈希 ========== - AttachmentDO attachmentDO = uploadAndSave(file, originalFilename, fileExtension, null); - - // 异步计算哈希(只计算,不删除文件) - hashCalculationService.calculateHashAsync(attachmentDO.getId(), attachmentDO.getStoragePath(), attachmentDO.getStorageType()); - - return attachmentDO; } + // 文件不存在,上传并保存 + return uploadAndSave(file, originalFilename, fileExtension, hash); + } catch (IOException e) { log.error("文件上传失败: {}", file.getOriginalFilename(), e); throw new RuntimeException("文件上传失败: " + e.getMessage(), e); @@ -102,7 +72,7 @@ public class AttachmentServiceImpl extends ServiceImpl() + .eq(AttachmentDO::getFileHash, hash) + .eq(AttachmentDO::getIsDeleted, DelStatusEnum.STATUS_NORMAL.code()) + .last("LIMIT 1")); + + if (existingFile != null) { + respDTO.setExists(true); + respDTO.setFileId(existingFile.getId()); + respDTO.setUrl(fileStorageStrategy.getFileUrl(existingFile.getStoragePath())); + log.info("文件哈希已存在,支持秒传: hash={}, fileId={}", hash, existingFile.getId()); + } + + return respDTO; + } }