From a9bd7726ff494debfcf4d8066ff86122e03ee633 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E5=90=B4=E6=B3=BD=E5=A8=81?= <958142070@qq.com>
Date: Mon, 2 Mar 2026 17:08:39 +0800
Subject: [PATCH 01/12] =?UTF-8?q?refactor(storage):=20=E9=87=8D=E6=9E=84?=
=?UTF-8?q?=E6=96=87=E4=BB=B6=E5=AD=98=E5=82=A8=E7=9B=B8=E5=85=B3=E9=80=BB?=
=?UTF-8?q?=E8=BE=91=E5=92=8C=E5=A4=9A=E5=A4=84=E6=8E=A5=E5=8F=A3=E8=B0=83?=
=?UTF-8?q?=E7=94=A8?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 替换 BaseController 中的工具类调用,改用 SpringUtils 处理请求路径匹配
- 调整 CommonController 和 FileController 返回 FileInfo 字段映射,兼容 thumbnail 字段更名
- 移除 ContiNewAdminApplication 中无用的 @EnableFileStorage 注解及相关配置
- 优化 FileDO 实体类构造和转换逻辑,增强路径和文件名规范处理
- 精简 FileNameGenerator,改善文件名解析与拼接的安全性和格式一致性
- 重构 FileRecorderImpl,新增分片上传会话及文件分片持久化接口,实现分片上传数据管理
- FileRecycleServiceImpl 增加对文件路径归一化处理,支持回收站文件操作路径规范
- FileServiceImpl 上传逻辑增强,新增 SHA256 计算,调整路径处理及缩略图 URL 兼容
- 移除各处对旧文件存储核心类 org.dromara.x.file.storage 的依赖,改用 top.continew.starter.storage 新存储模型
- MultipartUploadServiceImpl 重构,去除旧分片 DAO,改用统一 FileStorageService 实现分片上传核心功能
- 优化多处接口参数及返回值结构,统一元信息和路径编码处理,提升系统存储层一致性和稳定性
---
continew-common/pom.xml | 17 +-
.../base/controller/BaseController.java | 4 +-
.../admin/ContiNewAdminApplication.java | 4 -
.../system/config/file/FileRecorderImpl.java | 173 ++++++++++++--
.../system/controller/CommonController.java | 6 +-
.../system/controller/FileController.java | 8 +-
.../impl/RedisMultipartUploadDaoDaoImpl.java | 4 +-
.../admin/system/model/entity/FileDO.java | 77 ++++---
.../admin/system/service/FileService.java | 4 +-
.../service/impl/FileRecycleServiceImpl.java | 44 ++--
.../system/service/impl/FileServiceImpl.java | 101 +++++----
.../impl/MultipartUploadServiceImpl.java | 213 +++++++-----------
.../service/impl/StorageServiceImpl.java | 57 ++---
.../system/service/impl/UserServiceImpl.java | 4 +-
.../admin/system/util/FileNameGenerator.java | 37 +--
pom.xml | 2 +-
16 files changed, 435 insertions(+), 320 deletions(-)
diff --git a/continew-common/pom.xml b/continew-common/pom.xml
index d6564727..fd705068 100644
--- a/continew-common/pom.xml
+++ b/continew-common/pom.xml
@@ -26,17 +26,6 @@
cosid-spring-redis
-
-
- org.dromara.x-file-storage
- x-file-storage-spring
-
-
-
- com.amazonaws
- aws-java-sdk-s3
-
-
software.amazon.awssdk
@@ -79,6 +68,12 @@
continew-starter-validation
+
+
+ top.continew.starter
+ continew-starter-storage
+
+
top.continew.starter
diff --git a/continew-common/src/main/java/top/continew/admin/common/base/controller/BaseController.java b/continew-common/src/main/java/top/continew/admin/common/base/controller/BaseController.java
index 175cbadf..682c0ee9 100644
--- a/continew-common/src/main/java/top/continew/admin/common/base/controller/BaseController.java
+++ b/continew-common/src/main/java/top/continew/admin/common/base/controller/BaseController.java
@@ -27,7 +27,7 @@ import top.continew.admin.common.base.service.BaseService;
import top.continew.admin.common.config.crud.CrudApiPermissionPrefixCache;
import top.continew.starter.auth.satoken.autoconfigure.SaTokenExtensionProperties;
import top.continew.starter.core.util.ServletUtils;
-import top.continew.starter.core.util.SpringWebUtils;
+import top.continew.starter.core.util.SpringUtils;
import top.continew.starter.extension.crud.annotation.CrudApi;
import top.continew.starter.extension.crud.controller.AbstractCrudController;
import top.continew.starter.extension.crud.enums.Api;
@@ -69,7 +69,7 @@ public class BaseController, L, D, Q, C> exten
SaTokenExtensionProperties saTokenExtensionProperties = SpringUtil.getBean(SaTokenExtensionProperties.class);
if (saTokenExtensionProperties.isEnabled()) {
String[] excludePatterns = saTokenExtensionProperties.getSecurity().getExcludes();
- if (SpringWebUtils.isMatch(ServletUtils.getRequestPath(), excludePatterns)) {
+ if (SpringUtils.isMatch(ServletUtils.getRequest().getServletPath(), excludePatterns)) {
return;
}
}
diff --git a/continew-server/src/main/java/top/continew/admin/ContiNewAdminApplication.java b/continew-server/src/main/java/top/continew/admin/ContiNewAdminApplication.java
index 81117345..a92e0b2b 100644
--- a/continew-server/src/main/java/top/continew/admin/ContiNewAdminApplication.java
+++ b/continew-server/src/main/java/top/continew/admin/ContiNewAdminApplication.java
@@ -25,7 +25,6 @@ import com.alicp.jetcache.anno.config.EnableMethodCache;
import io.swagger.v3.oas.annotations.Hidden;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
-import org.dromara.x.file.storage.spring.EnableFileStorage;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
@@ -51,7 +50,6 @@ import top.nextdoc4j.core.configuration.NextDoc4jProperties;
@Slf4j
@EnableCrudApi
@EnableGlobalResponse
-@EnableFileStorage
@EnableMethodCache(basePackages = "top.continew.admin")
@EnableFeignClients
@RestController
@@ -63,8 +61,6 @@ public class ContiNewAdminApplication implements ApplicationRunner {
private final ServerProperties serverProperties;
public static void main(String[] args) {
- // 禁用 AWS SDK for Java 1.x 弃用提示(1.x 由 x-file-storage 等依赖引入,计划后续迁移至 2.x)
- System.setProperty("aws.java.v1.disableDeprecationAnnouncement", "true");
SpringApplication application = new SpringApplication(ContiNewAdminApplication.class);
application.setDefaultProperties(MapUtil.of("continew-starter.version", ContiNewStarterVersion.getVersion()));
application.run(args);
diff --git a/continew-system/src/main/java/top/continew/admin/system/config/file/FileRecorderImpl.java b/continew-system/src/main/java/top/continew/admin/system/config/file/FileRecorderImpl.java
index 4da0c27b..f80a7f57 100644
--- a/continew-system/src/main/java/top/continew/admin/system/config/file/FileRecorderImpl.java
+++ b/continew-system/src/main/java/top/continew/admin/system/config/file/FileRecorderImpl.java
@@ -17,16 +17,14 @@
package top.continew.admin.system.config.file;
import cn.hutool.core.collection.CollUtil;
-import cn.hutool.core.util.ClassUtil;
+import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.URLUtil;
import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
-import org.dromara.x.file.storage.core.FileInfo;
-import org.dromara.x.file.storage.core.recorder.FileRecorder;
-import org.dromara.x.file.storage.core.upload.FilePartInfo;
import org.springframework.stereotype.Component;
+import top.continew.admin.system.dao.MultipartUploadDao;
import top.continew.admin.system.mapper.FileMapper;
import top.continew.admin.system.mapper.StorageMapper;
import top.continew.admin.system.model.entity.FileDO;
@@ -34,6 +32,10 @@ import top.continew.admin.system.model.entity.StorageDO;
import top.continew.starter.core.constant.StringConstants;
import top.continew.starter.core.util.CollUtils;
import top.continew.starter.core.util.URLUtils;
+import top.continew.starter.storage.domain.model.resp.FileInfo;
+import top.continew.starter.storage.domain.model.resp.FilePartInfo;
+import top.continew.starter.storage.domain.model.resp.MultipartInitResp;
+import top.continew.starter.storage.service.FileRecorder;
import java.util.List;
import java.util.Map;
@@ -53,25 +55,30 @@ public class FileRecorderImpl implements FileRecorder {
private final FileMapper fileMapper;
private final StorageMapper storageMapper;
+ private final MultipartUploadDao multipartUploadDao;
@Override
public boolean save(FileInfo fileInfo) {
- if (fileInfo.getAttr() == null) {
+ // 分片初始化阶段不落库,只记录分片会话和分片信息
+ if ("UPLOADING".equalsIgnoreCase(fileInfo.getMetadata() != null
+ ? fileInfo.getMetadata().get("status")
+ : null)) {
+ return true;
+ }
+ StorageDO storage = this.getStorageByPlatform(fileInfo.getPlatform());
+ if (storage == null) {
return true;
}
- // 保存文件信息
FileDO file = new FileDO(fileInfo);
- StorageDO storage = (StorageDO)fileInfo.getAttr().get(ClassUtil.getClassName(StorageDO.class, false));
file.setStorageId(storage.getId());
fileMapper.insert(file);
- // 方便文件上传完成后获取文件信息
- fileInfo.setId(String.valueOf(file.getId()));
+ fileInfo.setFileId(String.valueOf(file.getId()));
if (!URLUtils.isHttpUrl(fileInfo.getUrl())) {
String prefix = storage.getUrlPrefix();
- String url = URLUtil.normalize(prefix + fileInfo.getUrl(), false, true);
+ String url = URLUtil.normalize(prefix + fileInfo.getPath(), false, true);
fileInfo.setUrl(url);
- if (StrUtil.isNotBlank(fileInfo.getThUrl())) {
- fileInfo.setThUrl(URLUtil.normalize(prefix + fileInfo.getThUrl(), false, true));
+ if (StrUtil.isNotBlank(fileInfo.getThumbnailPath()) && !URLUtils.isHttpUrl(fileInfo.getThumbnailPath())) {
+ fileInfo.setThumbnailPath(URLUtil.normalize(prefix + fileInfo.getThumbnailPath(), false, true));
}
}
return true;
@@ -88,8 +95,16 @@ public class FileRecorderImpl implements FileRecorder {
}
@Override
- public boolean delete(String url) {
- FileDO file = this.getFileByUrl(url);
+ public boolean delete(String platform, String path) {
+ StorageDO storage = this.getStorageByPlatform(platform);
+ if (storage == null) {
+ return true;
+ }
+ String normalizedPath = StrUtil.prependIfMissing(path, StringConstants.SLASH);
+ FileDO file = fileMapper.lambdaQuery()
+ .eq(FileDO::getStorageId, storage.getId())
+ .eq(FileDO::getPath, normalizedPath)
+ .one();
if (file == null) {
return true;
}
@@ -97,18 +112,129 @@ public class FileRecorderImpl implements FileRecorder {
}
@Override
- public void update(FileInfo fileInfo) {
- /* 不使用分片功能则无需重写 */
+ public boolean update(FileInfo fileInfo) {
+ StorageDO storage = this.getStorageByPlatform(fileInfo.getPlatform());
+ if (storage == null) {
+ return false;
+ }
+ FileDO file = new FileDO(fileInfo);
+ file.setStorageId(storage.getId());
+ FileDO existFile = fileMapper.lambdaQuery()
+ .eq(FileDO::getStorageId, storage.getId())
+ .eq(FileDO::getPath, file.getPath())
+ .one();
+ if (existFile == null) {
+ fileMapper.insert(file);
+ fileInfo.setFileId(String.valueOf(file.getId()));
+ return true;
+ } else {
+ file.setId(existFile.getId());
+ boolean updated = fileMapper.updateById(file) > 0;
+ fileInfo.setFileId(String.valueOf(existFile.getId()));
+ return updated;
+ }
}
@Override
public void saveFilePart(FilePartInfo filePartInfo) {
- /* 不使用分片功能则无需重写 */
+ top.continew.admin.system.model.resp.file.FilePartInfo target = new top.continew.admin.system.model.resp.file.FilePartInfo();
+ target.setFileId(filePartInfo.getFileId());
+ target.setPartNumber(filePartInfo.getPartNumber());
+ target.setPartSize(filePartInfo.getPartSize());
+ target.setPartMd5(filePartInfo.getPartMd5());
+ target.setPartETag(filePartInfo.getPartETag());
+ target.setUploadId(filePartInfo.getUploadId());
+ target.setUploadTime(filePartInfo.getUploadTime());
+ target.setStatus(filePartInfo.getStatus());
+ target.setBucket(filePartInfo.getBucket());
+ target.setPath(filePartInfo.getPath());
+ multipartUploadDao.setFilePart(filePartInfo.getUploadId(), target);
+ }
+
+ @Override
+ public List getFileParts(String fileId) {
+ return multipartUploadDao.getFileParts(fileId).stream().map(filePartInfo -> {
+ FilePartInfo target = new FilePartInfo();
+ target.setFileId(filePartInfo.getFileId());
+ target.setPartNumber(filePartInfo.getPartNumber());
+ target.setPartSize(filePartInfo.getPartSize());
+ target.setPartMd5(filePartInfo.getPartMd5());
+ target.setPartETag(filePartInfo.getPartETag());
+ target.setUploadId(filePartInfo.getUploadId());
+ target.setUploadTime(filePartInfo.getUploadTime());
+ target.setStatus(filePartInfo.getStatus());
+ target.setBucket(filePartInfo.getBucket());
+ target.setPath(filePartInfo.getPath());
+ return target;
+ }).toList();
+ }
+
+ @Override
+ public void deleteFileParts(String fileId) {
+ multipartUploadDao.deleteFileParts(fileId);
+ }
+
+ @Override
+ public String getUploadIdByMd5(String md5) {
+ return multipartUploadDao.getUploadIdByMd5(md5);
}
@Override
- public void deleteFilePartByUploadId(String s) {
- /* 不使用分片功能则无需重写 */
+ public void setMd5Mapping(String md5, String uploadId) {
+ multipartUploadDao.setMd5Mapping(md5, uploadId);
+ }
+
+ @Override
+ public void deleteMd5Mapping(String md5) {
+ multipartUploadDao.deleteMd5Mapping(md5);
+ }
+
+ @Override
+ public void saveMultipartSession(String uploadId, MultipartInitResp initResp, Map metadata) {
+ top.continew.admin.system.model.resp.file.MultipartUploadInitResp target = new top.continew.admin.system.model.resp.file.MultipartUploadInitResp();
+ target.setFileId(initResp.getFileId());
+ target.setUploadId(initResp.getUploadId());
+ target.setBucket(initResp.getBucket());
+ target.setPlatform(initResp.getPlatform());
+ target.setFileName(initResp.getFileName());
+ target.setFileMd5(initResp.getFileMd5());
+ target.setFileSize(ObjectUtil.defaultIfNull(initResp.getFileSize(), 0L));
+ target.setExtension(initResp.getExtension());
+ target.setContentType(initResp.getContentType());
+ target.setParentPath(initResp.getParentPath());
+ target.setPath(initResp.getPath());
+ target.setPartSize(initResp.getPartSize());
+ target.setUploadedPartNumbers(initResp.getUploadedPartNumbers());
+ multipartUploadDao.setMultipartUpload(uploadId, target, metadata);
+ }
+
+ @Override
+ public MultipartInitResp getMultipartSession(String uploadId) {
+ top.continew.admin.system.model.resp.file.MultipartUploadInitResp source = multipartUploadDao
+ .getMultipartUpload(uploadId);
+ if (source == null) {
+ return null;
+ }
+ MultipartInitResp target = new MultipartInitResp();
+ target.setFileId(source.getFileId());
+ target.setUploadId(source.getUploadId());
+ target.setBucket(source.getBucket());
+ target.setPlatform(source.getPlatform());
+ target.setFileName(source.getFileName());
+ target.setFileMd5(source.getFileMd5());
+ target.setFileSize(source.getFileSize());
+ target.setExtension(source.getExtension());
+ target.setContentType(source.getContentType());
+ target.setParentPath(source.getParentPath());
+ target.setPath(source.getPath());
+ target.setPartSize(source.getPartSize());
+ target.setUploadedPartNumbers(source.getUploadedPartNumbers());
+ return target;
+ }
+
+ @Override
+ public void deleteMultipartSession(String uploadId) {
+ multipartUploadDao.deleteMultipartUpload(uploadId);
}
/**
@@ -144,4 +270,11 @@ public class FileRecorderImpl implements FileRecorder {
return urlPrefix.equals(URLUtil.normalize(storage.getUrlPrefix() + file.getParentPath(), false, true));
}).findFirst().orElse(null);
}
-}
\ No newline at end of file
+
+ private StorageDO getStorageByPlatform(String platform) {
+ if (StrUtil.isBlank(platform)) {
+ return null;
+ }
+ return storageMapper.lambdaQuery().eq(StorageDO::getCode, platform).one();
+ }
+}
diff --git a/continew-system/src/main/java/top/continew/admin/system/controller/CommonController.java b/continew-system/src/main/java/top/continew/admin/system/controller/CommonController.java
index 398869b5..3ab9f683 100644
--- a/continew-system/src/main/java/top/continew/admin/system/controller/CommonController.java
+++ b/continew-system/src/main/java/top/continew/admin/system/controller/CommonController.java
@@ -25,7 +25,6 @@ import io.swagger.v3.oas.annotations.enums.ParameterIn;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;
-import org.dromara.x.file.storage.core.FileInfo;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
@@ -39,6 +38,7 @@ import top.continew.starter.extension.crud.model.resp.LabelValueResp;
import top.continew.starter.extension.tenant.annotation.TenantIgnore;
import top.continew.starter.extension.tenant.context.TenantContextHolder;
import top.continew.starter.log.annotation.Log;
+import top.continew.starter.storage.domain.model.resp.FileInfo;
import java.io.IOException;
import java.util.List;
@@ -69,9 +69,9 @@ public class CommonController {
ValidationUtils.throwIf(file::isEmpty, "文件不能为空");
FileInfo fileInfo = fileService.upload(file, parentPath);
return FileUploadResp.builder()
- .id(fileInfo.getId())
+ .id(fileInfo.getFileId())
.url(fileInfo.getUrl())
- .thUrl(fileInfo.getThUrl())
+ .thUrl(fileInfo.getThumbnailPath())
.metadata(fileInfo.getMetadata())
.build();
}
diff --git a/continew-system/src/main/java/top/continew/admin/system/controller/FileController.java b/continew-system/src/main/java/top/continew/admin/system/controller/FileController.java
index 132bffa3..91fa6ca0 100644
--- a/continew-system/src/main/java/top/continew/admin/system/controller/FileController.java
+++ b/continew-system/src/main/java/top/continew/admin/system/controller/FileController.java
@@ -24,7 +24,6 @@ import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;
-import org.dromara.x.file.storage.core.FileInfo;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
@@ -41,6 +40,7 @@ import top.continew.starter.extension.crud.annotation.CrudRequestMapping;
import top.continew.starter.extension.crud.enums.Api;
import top.continew.starter.extension.crud.model.resp.IdResp;
import top.continew.starter.log.annotation.Log;
+import top.continew.starter.storage.domain.model.resp.FileInfo;
import java.io.IOException;
@@ -77,9 +77,9 @@ public class FileController extends BaseController metadataMap = fileInfo.getMetadata();
+ this.sha256 = metadataMap != null
+ ? StrUtil.blankToDefault(metadataMap.get("sha256"), metadataMap.get("etag"))
+ : null;
+ this.metadata = JSONUtil.toJsonStr(metadataMap);
+ String thumbnailPath = fileInfo.getThumbnailPath();
+ if (StrUtil.isNotBlank(thumbnailPath)) {
+ this.thumbnailName = StrUtil.subAfter(thumbnailPath, StringConstants.SLASH, true);
+ }
+ this.thumbnailSize = fileInfo.getThumbnailSize();
+ this.thumbnailMetadata = null;
+ this.setCreateTime(fileInfo.getUploadTime());
}
/**
@@ -156,27 +169,27 @@ public class FileDO extends BaseDO {
public FileInfo toFileInfo(StorageDO storage) {
FileInfo fileInfo = new FileInfo();
fileInfo.setPlatform(storage.getCode());
- fileInfo.setFilename(this.name);
- fileInfo.setOriginalFilename(this.originalName);
- // 暂不使用,所以保持空
- fileInfo.setBasePath(StringConstants.EMPTY);
+ fileInfo.setBucket(storage.getBucketName());
+ fileInfo.setFileId(this.getId() == null ? null : String.valueOf(this.getId()));
+ fileInfo.setName(this.name);
+ fileInfo.setOriginalFileName(this.originalName);
fileInfo.setSize(this.size);
- fileInfo.setPath(StringConstants.SLASH.equals(this.parentPath)
- ? StringConstants.EMPTY
- : StrUtil.appendIfMissing(StrUtil
- .removePrefix(this.parentPath, StringConstants.SLASH), StringConstants.SLASH));
- fileInfo.setExt(this.extension);
+ String normalizedPath = StrUtil.removePrefix(this.path, StringConstants.SLASH);
+ fileInfo.setPath(normalizedPath);
+ fileInfo.setFullPath(normalizedPath);
fileInfo.setContentType(this.contentType);
if (StrUtil.isNotBlank(this.metadata)) {
fileInfo.setMetadata(JSONUtil.toBean(this.metadata, Map.class));
}
- fileInfo.setUrl(StrUtil.removePrefix(this.path, StringConstants.SLASH));
+ fileInfo.setUrl(URLUtil.normalize(storage.getUrlPrefix() + normalizedPath, false, true));
// 缩略图信息
- fileInfo.setThFilename(this.thumbnailName);
- fileInfo.setThSize(this.thumbnailSize);
- fileInfo.setThUrl(fileInfo.getPath() + fileInfo.getThFilename());
- if (StrUtil.isNotBlank(this.thumbnailMetadata)) {
- fileInfo.setThMetadata(JSONUtil.toBean(this.thumbnailMetadata, Map.class));
+ if (StrUtil.isNotBlank(this.thumbnailName)) {
+ String normalizedParentPath = StringConstants.SLASH.equals(this.parentPath)
+ ? StringConstants.EMPTY
+ : StrUtil.appendIfMissing(StrUtil
+ .removePrefix(this.parentPath, StringConstants.SLASH), StringConstants.SLASH);
+ fileInfo.setThumbnailPath(normalizedParentPath + this.thumbnailName);
+ fileInfo.setThumbnailSize(this.thumbnailSize);
}
return fileInfo;
}
diff --git a/continew-system/src/main/java/top/continew/admin/system/service/FileService.java b/continew-system/src/main/java/top/continew/admin/system/service/FileService.java
index ff1bc1eb..56afce14 100644
--- a/continew-system/src/main/java/top/continew/admin/system/service/FileService.java
+++ b/continew-system/src/main/java/top/continew/admin/system/service/FileService.java
@@ -17,7 +17,6 @@
package top.continew.admin.system.service;
import cn.hutool.core.util.StrUtil;
-import org.dromara.x.file.storage.core.FileInfo;
import org.springframework.web.multipart.MultipartFile;
import top.continew.admin.common.base.service.BaseService;
import top.continew.admin.system.model.entity.FileDO;
@@ -28,6 +27,7 @@ import top.continew.admin.system.model.resp.file.FileResp;
import top.continew.admin.system.model.resp.file.FileStatisticsResp;
import top.continew.starter.core.constant.StringConstants;
import top.continew.starter.data.service.IService;
+import top.continew.starter.storage.domain.model.resp.FileInfo;
import java.io.File;
import java.io.IOException;
@@ -175,4 +175,4 @@ public interface FileService extends BaseService> entry : fileListGroup.entrySet()) {
StorageDO storage = storageGroup.get(entry.getKey());
- // 清空回收站
- FileInfo fileInfo = new FileInfo();
- fileInfo.setPlatform(storage.getCode());
- fileInfo.setBasePath(StringConstants.EMPTY);
- fileInfo.setPath(storage.getRecycleBinPath());
- fileStorageService.delete(fileInfo);
+ List deletePaths = entry.getValue()
+ .stream()
+ .filter(file -> !FileTypeEnum.DIR.equals(file.getType()))
+ .map(file -> normalizeStoragePath(storage.getRecycleBinPath() + normalizeStoragePath(file
+ .getPath())))
+ .toList();
+ if (CollUtil.isNotEmpty(deletePaths)) {
+ fileStorageService.batchDelete(storage.getCode(), storage.getBucketName(), deletePaths);
+ }
}
} finally {
InterceptorIgnoreHelper.clearIgnoreStrategy();
}
}
+ private String normalizeStoragePath(String path) {
+ return StrUtil.removePrefix(path.replace("\\", StringConstants.SLASH)
+ .replaceAll("/+", StringConstants.SLASH), StringConstants.SLASH);
+ }
+
/**
* 根据 ID 查询
*
diff --git a/continew-system/src/main/java/top/continew/admin/system/service/impl/FileServiceImpl.java b/continew-system/src/main/java/top/continew/admin/system/service/impl/FileServiceImpl.java
index e1504007..953e52f7 100644
--- a/continew-system/src/main/java/top/continew/admin/system/service/impl/FileServiceImpl.java
+++ b/continew-system/src/main/java/top/continew/admin/system/service/impl/FileServiceImpl.java
@@ -18,16 +18,12 @@ package top.continew.admin.system.service.impl;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.io.file.FileNameUtil;
-import cn.hutool.core.util.ClassUtil;
+import cn.hutool.crypto.digest.DigestUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.URLUtil;
import jakarta.annotation.Resource;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
-import org.dromara.x.file.storage.core.FileInfo;
-import org.dromara.x.file.storage.core.FileStorageService;
-import org.dromara.x.file.storage.core.ProgressListener;
-import org.dromara.x.file.storage.core.upload.UploadPretreatment;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -52,8 +48,13 @@ import top.continew.starter.core.util.CollUtils;
import top.continew.starter.core.util.StrUtils;
import top.continew.starter.core.util.validation.CheckUtils;
import top.continew.starter.core.util.validation.ValidationUtils;
+import top.continew.starter.storage.core.FileStorageService;
+import top.continew.starter.storage.core.UploadPretreatment;
+import top.continew.starter.storage.domain.model.resp.FileInfo;
import java.io.File;
+import java.io.IOException;
+import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
@@ -235,38 +236,28 @@ public class FileServiceImpl extends BaseServiceImpl img.size(100, 100));
+ uploadPretreatment.thumbnail(100, 100);
}
- uploadPretreatment.setProgressMonitor(new ProgressListener() {
- @Override
- public void start() {
- log.info("开始上传文件: {}", uniqueFileName);
- }
-
- @Override
- public void progress(long progressSize, Long allSize) {
- log.info("文件 [{}] 已上传 [{}],总大小 [{}]", uniqueFileName, progressSize, allSize);
- }
-
- @Override
- public void finish() {
- log.info("文件 [{}] 上传完成", uniqueFileName);
- }
- });
+ uploadPretreatment.onProgress((progressSize, allSize, percentage) -> log
+ .info("文件 [{}] 已上传 [{}],总大小 [{}],进度 [{}%]", uniqueFileName, progressSize, allSize, percentage));
// 上传
- return uploadPretreatment.upload();
+ log.info("开始上传文件: {}", uniqueFileName);
+ FileInfo fileInfo = uploadPretreatment.upload();
+ log.info("文件 [{}] 上传完成", uniqueFileName);
+ return this.postProcessUploadResult(fileInfo, storage);
}
/**
@@ -288,7 +279,7 @@ public class FileServiceImpl extends BaseServiceImpl
- * 1.如果 path 为 {@code /},则设置为空
+ * 1.如果 path 为 {@code /},则保持为 {@code /}(避免触发自动日期目录)
* 2.如果 path 不以 {@code /} 结尾,则添加后缀 {@code /}
* 3.如果 path 以 {@code /} 开头,则移除前缀 {@code /}
* 示例:yyyy/MM/dd/
@@ -299,7 +290,8 @@ public class FileServiceImpl extends BaseServiceImpl());
+ }
+ fileInfo.setUrl(URLUtil.normalize(storage.getUrlPrefix() + normalizeStoragePath(fileInfo
+ .getPath()), false, true));
+ if (StrUtil.isNotBlank(fileInfo.getThumbnailPath()) && !StrUtil.startWithAny(fileInfo
+ .getThumbnailPath(), "http://", "https://")) {
+ fileInfo.setThumbnailPath(URLUtil.normalize(storage.getUrlPrefix() + normalizeStoragePath(fileInfo
+ .getThumbnailPath()), false, true));
+ }
+ return fileInfo;
+ }
+
+ private String normalizeStoragePath(String path) {
+ return StrUtil.removePrefix(path.replace("\\", StringConstants.SLASH)
+ .replaceAll("/+", StringConstants.SLASH), StringConstants.SLASH);
+ }
+}
diff --git a/continew-system/src/main/java/top/continew/admin/system/service/impl/MultipartUploadServiceImpl.java b/continew-system/src/main/java/top/continew/admin/system/service/impl/MultipartUploadServiceImpl.java
index ef6bb67a..473edfe0 100644
--- a/continew-system/src/main/java/top/continew/admin/system/service/impl/MultipartUploadServiceImpl.java
+++ b/continew-system/src/main/java/top/continew/admin/system/service/impl/MultipartUploadServiceImpl.java
@@ -21,16 +21,10 @@ import cn.hutool.core.util.StrUtil;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
-import top.continew.admin.system.constant.MultipartUploadConstants;
-import top.continew.admin.system.dao.MultipartUploadDao;
import top.continew.admin.system.enums.FileTypeEnum;
-import top.continew.admin.system.factory.StorageHandlerFactory;
-import top.continew.admin.system.handler.StorageHandler;
-import top.continew.admin.system.handler.impl.LocalStorageHandler;
import top.continew.admin.system.model.entity.FileDO;
import top.continew.admin.system.model.entity.StorageDO;
import top.continew.admin.system.model.req.MultipartUploadInitReq;
-import top.continew.admin.system.model.resp.file.FilePartInfo;
import top.continew.admin.system.model.resp.file.MultipartUploadInitResp;
import top.continew.admin.system.model.resp.file.MultipartUploadResp;
import top.continew.admin.system.mapper.FileMapper;
@@ -39,12 +33,12 @@ import top.continew.admin.system.service.MultipartUploadService;
import top.continew.admin.system.service.StorageService;
import top.continew.admin.system.util.FileNameGenerator;
import top.continew.starter.core.exception.BaseException;
+import top.continew.starter.core.constant.StringConstants;
+import top.continew.starter.storage.core.FileStorageService;
+import top.continew.starter.storage.domain.model.resp.FileInfo;
+import top.continew.starter.storage.domain.model.resp.MultipartInitResp;
-import java.time.LocalDateTime;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.stream.Collectors;
+import java.io.IOException;
/**
* 分片上传业务实现
@@ -57,39 +51,13 @@ import java.util.stream.Collectors;
public class MultipartUploadServiceImpl implements MultipartUploadService {
private final StorageService storageService;
-
- private final StorageHandlerFactory storageHandlerFactory;
-
- private final MultipartUploadDao multipartUploadDao;
-
+ private final FileStorageService fileStorageService;
private final FileService fileService;
-
private final FileMapper fileMapper;
@Override
public MultipartUploadInitResp initMultipartUpload(MultipartUploadInitReq multiPartUploadInitReq) {
- // 后续可以增加storageCode参数 指定某个存储平台 当前设计是默认存储平台
StorageDO storageDO = storageService.getByCode(null);
- // 根据文件Md5查询当前存储平台是否初始化过分片
- String uploadId = multipartUploadDao.getUploadIdByMd5(multiPartUploadInitReq.getFileMd5());
- if (StrUtil.isNotBlank(uploadId)) {
- MultipartUploadInitResp multipartUpload = multipartUploadDao.getMultipartUpload(uploadId);
- //对比存储平台和分片大小是否一致 一致则返回结果
- if (multipartUpload != null && multipartUpload.getPartSize()
- .equals(MultipartUploadConstants.MULTIPART_UPLOAD_PART_SIZE) && multipartUpload.getPlatform()
- .equals(storageDO.getCode())) {
- // 获取已上传分片信息
- List fileParts = multipartUploadDao.getFileParts(uploadId);
- Set partNumbers = fileParts.stream()
- .map(FilePartInfo::getPartNumber)
- .collect(Collectors.toSet());
- multipartUpload.setUploadedPartNumbers(partNumbers);
- return multipartUpload;
- }
- //todo else 待定 更换存储平台 或分片大小有变更 是否需要删除原先分片
-
- }
-
// 检测文件名是否已存在(同一目录下文件名不能重复)
String originalFileName = multiPartUploadInitReq.getFileName();
String parentPath = multiPartUploadInitReq.getParentPath();
@@ -104,124 +72,95 @@ public class MultipartUploadServiceImpl implements MultipartUploadService {
}
// 生成唯一文件名(处理重名情况)
- String uniqueFileName = FileNameGenerator.generateUniqueName(originalFileName, parentPath, storageDO.getId(), fileMapper);
+ String uniqueFileName = FileNameGenerator.generateUniqueName(originalFileName, parentPath, storageDO
+ .getId(), fileMapper);
multiPartUploadInitReq.setFileName(uniqueFileName);
-
- StorageHandler storageHandler = storageHandlerFactory.createHandler(storageDO.getType());
- //文件元信息
- Map metaData = multiPartUploadInitReq.getMetaData();
- MultipartUploadInitResp multipartUploadInitResp = storageHandler
- .initMultipartUpload(storageDO, multiPartUploadInitReq);
- // 缓存文件信息,md5和uploadId映射
- multipartUploadDao.setMultipartUpload(multipartUploadInitResp.getUploadId(), multipartUploadInitResp, metaData);
- multipartUploadDao.setMd5Mapping(multiPartUploadInitReq.getFileMd5(), multipartUploadInitResp.getUploadId());
- return multipartUploadInitResp;
+ fileService.createParentDir(StrUtil.blankToDefault(parentPath, StringConstants.SLASH), storageDO);
+
+ top.continew.starter.storage.domain.model.req.MultipartUploadInitReq storageReq = new top.continew.starter.storage.domain.model.req.MultipartUploadInitReq();
+ storageReq.setPlatform(storageDO.getCode());
+ storageReq.setBucket(storageDO.getBucketName());
+ storageReq.setFileName(uniqueFileName);
+ storageReq.setFileSize(multiPartUploadInitReq.getFileSize());
+ storageReq.setFileMd5(multiPartUploadInitReq.getFileMd5());
+ storageReq.setContentType(multiPartUploadInitReq.getContentType());
+ storageReq.setParentPath(StrUtil.blankToDefault(parentPath, StringConstants.SLASH));
+ storageReq.setMetadata(multiPartUploadInitReq.getMetaData());
+ MultipartInitResp initResp = fileStorageService.initMultipartUpload(storageReq);
+ return this.toAdminInitResp(initResp);
}
@Override
public MultipartUploadResp uploadPart(MultipartFile file, String uploadId, Integer partNumber, String path) {
StorageDO storageDO = storageService.getByCode(null);
- StorageHandler storageHandler = storageHandlerFactory.createHandler(storageDO.getType());
- MultipartUploadResp resp = storageHandler.uploadPart(storageDO, path, uploadId, partNumber, file);
- FilePartInfo partInfo = new FilePartInfo();
- partInfo.setUploadId(uploadId);
- partInfo.setBucket(storageDO.getBucketName());
- partInfo.setPath(path);
- partInfo.setPartNumber(partNumber);
- partInfo.setPartETag(resp.getPartETag());
- partInfo.setPartSize(resp.getPartSize());
- partInfo.setStatus("SUCCESS");
- partInfo.setUploadTime(LocalDateTime.now());
- multipartUploadDao.setFilePart(uploadId, partInfo);
- return resp;
+ try {
+ top.continew.starter.storage.domain.model.resp.MultipartUploadResp resp = fileStorageService
+ .uploadPart(storageDO.getCode(), storageDO
+ .getBucketName(), normalizeStoragePath(path), uploadId, partNumber, file.getInputStream());
+ return this.toAdminUploadResp(resp);
+ } catch (IOException e) {
+ throw new BaseException("上传分片失败: " + e.getMessage(), e);
+ }
}
@Override
public FileDO completeMultipartUpload(String uploadId) {
- StorageDO storageDO = storageService.getByCode(null);
- // 从 FileRecorder 获取所有分片信息
- List recordedParts = multipartUploadDao.getFileParts(uploadId);
- MultipartUploadInitResp initResp = multipartUploadDao.getMultipartUpload(uploadId);
- // 转换为 MultipartUploadResp
- List parts = recordedParts.stream().map(partInfo -> {
- MultipartUploadResp resp = new MultipartUploadResp();
- resp.setPartNumber(partInfo.getPartNumber());
- resp.setPartETag(partInfo.getPartETag());
- resp.setPartSize(partInfo.getPartSize());
- resp.setSuccess("SUCCESS".equals(partInfo.getStatus()));
- return resp;
- }).collect(Collectors.toList());
-
- // 如果没有记录,使用客户端传入的分片信息
- if (parts.isEmpty()) {
- throw new BaseException("没有找到任何分片信息");
+ MultipartInitResp session = fileStorageService.getMultipartSession(uploadId);
+ if (session == null) {
+ throw new BaseException("无效的 uploadId: " + uploadId);
}
-
- // 验证分片完整性
- validatePartsCompleteness(parts);
-
- // 获取策略,判断是否需要验证
- boolean needVerify = true;
- StorageHandler storageHandler = storageHandlerFactory.createHandler(storageDO.getType());
- if (storageHandler instanceof LocalStorageHandler) {
- needVerify = false;
+ FileInfo fileInfo = fileStorageService.completeMultipartUpload(uploadId, null);
+ StorageDO storageDO = storageService.getByCode(session.getPlatform());
+ FileDO file = fileMapper.lambdaQuery()
+ .eq(FileDO::getStorageId, storageDO.getId())
+ .eq(FileDO::getPath, StringConstants.SLASH + normalizeStoragePath(session.getPath()))
+ .one();
+ if (file != null) {
+ return file;
}
-
- // 完成上传
- storageHandler.completeMultipartUpload(storageDO, parts, initResp.getPath(), uploadId, needVerify);
- // 文件名已在初始化阶段处理为唯一文件名
- String uniqueFileName = initResp.getFileName().replaceFirst("^[/\\\\]+", "");
- FileDO file = new FileDO();
- file.setName(uniqueFileName);
- file.setOriginalName(uniqueFileName);
- file.setPath(initResp.getPath());
- file.setParentPath(initResp.getParentPath());
- file.setSize(initResp.getFileSize());
- file.setSha256(initResp.getFileMd5());
- file.setExtension(initResp.getExtension());
- file.setContentType(initResp.getContentType());
- file.setType(FileTypeEnum.getByExtension(FileUtil.extName(uniqueFileName)));
- file.setStorageId(storageDO.getId());
- fileService.save(file);
- multipartUploadDao.deleteMultipartUpload(uploadId);
- return file;
+ // 兼容兜底:记录器未完成落库时补录
+ FileDO fallback = new FileDO(fileInfo);
+ fallback.setStorageId(storageDO.getId());
+ fallback.setType(FileTypeEnum.getByExtension(FileUtil.extName(fallback.getName())));
+ fileService.save(fallback);
+ return fallback;
}
@Override
public void cancelMultipartUpload(String uploadId) {
- StorageDO storageDO = storageService.getByCode(null);
- multipartUploadDao.deleteMultipartUploadAll(uploadId);
- StorageHandler storageHandler = storageHandlerFactory.createHandler(storageDO.getType());
- storageHandler.cleanPart(storageDO, uploadId);
+ fileStorageService.abortMultipartUpload(uploadId);
}
- /**
- * 验证分片完整性
- *
- * @param parts 分片信息
- */
- private void validatePartsCompleteness(List parts) {
- if (parts.isEmpty()) {
- throw new BaseException("没有找到任何分片信息");
- }
-
- // 检查分片编号连续性
- List partNumbers = parts.stream().map(MultipartUploadResp::getPartNumber).sorted().toList();
-
- for (int i = 0; i < partNumbers.size(); i++) {
- if (partNumbers.get(i) != i + 1) {
- throw new BaseException("分片编号不连续,缺失分片: " + (i + 1));
- }
- }
+ private MultipartUploadInitResp toAdminInitResp(MultipartInitResp source) {
+ MultipartUploadInitResp target = new MultipartUploadInitResp();
+ target.setFileId(source.getFileId());
+ target.setUploadId(source.getUploadId());
+ target.setBucket(source.getBucket());
+ target.setPlatform(source.getPlatform());
+ target.setFileName(source.getFileName());
+ target.setFileMd5(source.getFileMd5());
+ target.setFileSize(source.getFileSize());
+ target.setExtension(source.getExtension());
+ target.setContentType(source.getContentType());
+ target.setParentPath(source.getParentPath());
+ target.setPath(StringConstants.SLASH + normalizeStoragePath(source.getPath()));
+ target.setPartSize(source.getPartSize());
+ target.setUploadedPartNumbers(source.getUploadedPartNumbers());
+ return target;
+ }
- // 检查是否所有分片都成功
- List failedParts = parts.stream()
- .filter(part -> !part.isSuccess())
- .map(MultipartUploadResp::getPartNumber)
- .toList();
+ private MultipartUploadResp toAdminUploadResp(top.continew.starter.storage.domain.model.resp.MultipartUploadResp source) {
+ MultipartUploadResp target = new MultipartUploadResp();
+ target.setPartNumber(source.getPartNumber());
+ target.setPartETag(source.getPartETag());
+ target.setPartSize(source.getPartSize());
+ target.setSuccess(source.isSuccess());
+ target.setErrorMessage(source.getErrorMessage());
+ return target;
+ }
- if (!failedParts.isEmpty()) {
- throw new BaseException("存在失败的分片: " + failedParts);
- }
+ private String normalizeStoragePath(String path) {
+ return StrUtil.removePrefix(path.replace("\\", StringConstants.SLASH)
+ .replaceAll("/+", StringConstants.SLASH), StringConstants.SLASH);
}
}
diff --git a/continew-system/src/main/java/top/continew/admin/system/service/impl/StorageServiceImpl.java b/continew-system/src/main/java/top/continew/admin/system/service/impl/StorageServiceImpl.java
index d900bfea..1367d59e 100644
--- a/continew-system/src/main/java/top/continew/admin/system/service/impl/StorageServiceImpl.java
+++ b/continew-system/src/main/java/top/continew/admin/system/service/impl/StorageServiceImpl.java
@@ -17,15 +17,9 @@
package top.continew.admin.system.service.impl;
import cn.hutool.core.bean.BeanUtil;
-import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.StrUtil;
-import cn.hutool.core.util.URLUtil;
import jakarta.annotation.Resource;
import lombok.RequiredArgsConstructor;
-import org.dromara.x.file.storage.core.FileStorageProperties;
-import org.dromara.x.file.storage.core.FileStorageService;
-import org.dromara.x.file.storage.core.FileStorageServiceBuilder;
-import org.dromara.x.file.storage.core.platform.FileStorage;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import top.continew.admin.common.base.service.BaseServiceImpl;
@@ -41,13 +35,15 @@ import top.continew.admin.system.model.resp.StorageResp;
import top.continew.admin.system.service.FileService;
import top.continew.admin.system.service.StorageService;
import top.continew.starter.core.util.ExceptionUtils;
-import top.continew.starter.core.util.SpringWebUtils;
import top.continew.starter.core.util.validation.CheckUtils;
import top.continew.starter.core.util.validation.ValidationUtils;
+import top.continew.starter.storage.autoconfigure.properties.LocalStorageConfig;
+import top.continew.starter.storage.autoconfigure.properties.OssStorageConfig;
+import top.continew.starter.storage.core.FileStorageService;
+import top.continew.starter.storage.strategy.impl.LocalStorageStrategy;
+import top.continew.starter.storage.strategy.impl.OssStorageStrategy;
-import java.util.Collections;
import java.util.List;
-import java.util.concurrent.CopyOnWriteArrayList;
/**
* 存储业务实现
@@ -159,6 +155,7 @@ public class StorageServiceImpl extends BaseServiceImpl fileStorageList = fileStorageService.getFileStorageList();
+ if (fileStorageService.exists(storage.getCode()) && fileStorageService.isDynamic(storage.getCode())) {
+ fileStorageService.unload(storage.getCode());
+ }
switch (storage.getType()) {
case LOCAL -> {
- FileStorageProperties.LocalPlusConfig config = new FileStorageProperties.LocalPlusConfig();
+ LocalStorageConfig config = new LocalStorageConfig();
+ config.setEnabled(true);
config.setPlatform(storage.getCode());
- config.setStoragePath(storage.getBucketName());
- fileStorageList.addAll(FileStorageServiceBuilder.buildLocalPlusFileStorage(Collections
- .singletonList(config)));
- // 注册资源映射
- SpringWebUtils.registerResourceHandler(MapUtil.of(URLUtil.url(storage.getDomain()).getPath(), storage
- .getBucketName()));
+ config.setBucketName(storage.getBucketName());
+ config.setEndpoint(storage.getDomain());
+ fileStorageService.register(new LocalStorageStrategy(config));
}
case OSS -> {
- FileStorageProperties.AmazonS3Config config = new FileStorageProperties.AmazonS3Config();
+ OssStorageConfig config = new OssStorageConfig();
+ config.setEnabled(true);
config.setPlatform(storage.getCode());
config.setAccessKey(storage.getAccessKey());
config.setSecretKey(storage.getSecretKey());
- config.setEndPoint(storage.getEndpoint());
+ config.setEndpoint(storage.getEndpoint());
config.setBucketName(storage.getBucketName());
- fileStorageList.addAll(FileStorageServiceBuilder.buildAmazonS3FileStorage(Collections
- .singletonList(config), null));
+ config.setDomain(storage.getDomain());
+ fileStorageService.register(new OssStorageStrategy(config));
}
default -> throw new IllegalArgumentException("不支持的存储类型:%s".formatted(storage.getType()));
}
+ if (Boolean.TRUE.equals(storage.getIsDefault())) {
+ fileStorageService.defaultStorage(storage.getCode());
+ }
}
@Override
public void unload(StorageDO storage) {
- FileStorage fileStorage = fileStorageService.getFileStorage(storage.getCode());
- if (fileStorage == null) {
+ if (!fileStorageService.exists(storage.getCode()) || !fileStorageService.isDynamic(storage.getCode())) {
return;
}
- CopyOnWriteArrayList fileStorageList = fileStorageService.getFileStorageList();
- fileStorageList.remove(fileStorage);
- fileStorage.close();
- // 本地存储引擎需要移除资源映射
- if (StorageTypeEnum.LOCAL.equals(storage.getType())) {
- SpringWebUtils.deRegisterResourceHandler(MapUtil.of(URLUtil.url(storage.getDomain()).getPath(), storage
- .getBucketName()));
- }
+ fileStorageService.unload(storage.getCode());
}
/**
@@ -253,4 +246,4 @@ public class StorageServiceImpl extends BaseServiceImpl
* 当目标目录存在同名文件时,自动添加序号后缀:
*
- * - file.txt → file(1).txt → file(2).txt → ...
- * - 无扩展名:README → README(1) → README(2) → ...
- * - 隐藏文件:.gitignore → .gitignore(1) → .gitignore(2) → ...
+ * - file.txt → file(1).txt → file(2).txt → ...
+ * - 无扩展名:README → README(1) → README(2) → ...
+ * - 隐藏文件:.gitignore → .gitignore(1) → .gitignore(2) → ...
*
*
*
@@ -90,7 +90,9 @@ public class FileNameGenerator {
// 安全限制,防止无限循环
if (counter > 9999) {
log.warn("文件名重命名超过最大限制,使用当前时间戳: {}", fileName);
- return baseName + "_" + System.currentTimeMillis() + (StrUtil.isNotBlank(extension) ? "." + extension : "");
+ return baseName + "_" + System.currentTimeMillis() + (StrUtil.isNotBlank(extension)
+ ? "." + extension
+ : "");
}
}
}
@@ -102,10 +104,10 @@ public class FileNameGenerator {
* 示例:
*
*
- * - "document.pdf" → ["document", "pdf"]
- * - "README" → ["README", ""]
- * - ".gitignore" → [".gitignore", ""]
- * - "archive.tar.gz" → ["archive.tar", "gz"]
+ * - "document.pdf" → ["document", "pdf"]
+ * - "README" → ["README", ""]
+ * - ".gitignore" → [".gitignore", ""]
+ * - "archive.tar.gz" → ["archive.tar", "gz"]
*
*
* @param fileName 文件名
@@ -113,7 +115,7 @@ public class FileNameGenerator {
*/
public static String[] parseFileName(String fileName) {
if (StrUtil.isBlank(fileName)) {
- return new String[]{"", ""};
+ return new String[] {"", ""};
}
// 处理隐藏文件(以.开头)
@@ -122,7 +124,7 @@ public class FileNameGenerator {
// 处理空文件名(如只有"."的情况)
if (nameWithoutDot.isEmpty()) {
- return new String[]{fileName, ""};
+ return new String[] {fileName, ""};
}
// 查找最后一个点号位置
@@ -130,18 +132,20 @@ public class FileNameGenerator {
// 点号不存在或在开头(如 ".bashrc"),视为无扩展名
if (lastDotIndex <= 0) {
- return new String[]{fileName, ""};
+ return new String[] {fileName, ""};
}
- String baseName = isHidden ? "." + nameWithoutDot.substring(0, lastDotIndex) : nameWithoutDot.substring(0, lastDotIndex);
+ String baseName = isHidden
+ ? "." + nameWithoutDot.substring(0, lastDotIndex)
+ : nameWithoutDot.substring(0, lastDotIndex);
String extension = nameWithoutDot.substring(lastDotIndex + 1);
// 扩展名不应包含路径分隔符(安全检查)
if (extension.contains("/") || extension.contains("\\")) {
- return new String[]{fileName, ""};
+ return new String[] {fileName, ""};
}
- return new String[]{baseName, extension};
+ return new String[] {baseName, extension};
}
/**
@@ -194,7 +198,10 @@ public class FileNameGenerator {
* @param fileMapper 文件Mapper
* @return 文件名列表
*/
- private static List selectNamesByParentPath(String parentPath, Long storageId, String namePrefix, FileMapper fileMapper) {
+ private static List selectNamesByParentPath(String parentPath,
+ Long storageId,
+ String namePrefix,
+ FileMapper fileMapper) {
var wrapper = fileMapper.lambdaQuery()
.eq(FileDO::getParentPath, parentPath)
.eq(FileDO::getStorageId, storageId)
diff --git a/pom.xml b/pom.xml
index 6e721cac..8c4df8c8 100644
--- a/pom.xml
+++ b/pom.xml
@@ -13,7 +13,7 @@
top.continew.starter
continew-starter
- 2.15.0
+ 2.16.0-SNAPSHOT
top.continew.admin
--
Gitee
From 96bf23c9b50bba1f043a2640b1aa51a4d6a7d90c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E5=90=B4=E6=B3=BD=E5=A8=81?= <958142070@qq.com>
Date: Mon, 2 Mar 2026 17:21:39 +0800
Subject: [PATCH 02/12] =?UTF-8?q?fix(file):=20=E4=BF=AE=E6=AD=A3=E7=BC=A9?=
=?UTF-8?q?=E7=95=A5=E5=9B=BE=E8=B7=AF=E5=BE=84=E5=A4=84=E7=90=86=E9=80=BB?=
=?UTF-8?q?=E8=BE=91?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 替换缩略图路径中的反斜杠为正斜杠
- 只有非http/https的路径才移除开头的斜杠
- 使用FileNameUtil获取缩略图名称代替字符串截取
- 优化缩略图名称的提取逻辑,提高兼容性与准确性
---
.../java/top/continew/admin/system/model/entity/FileDO.java | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/continew-system/src/main/java/top/continew/admin/system/model/entity/FileDO.java b/continew-system/src/main/java/top/continew/admin/system/model/entity/FileDO.java
index 3d650c8d..86786d89 100644
--- a/continew-system/src/main/java/top/continew/admin/system/model/entity/FileDO.java
+++ b/continew-system/src/main/java/top/continew/admin/system/model/entity/FileDO.java
@@ -153,7 +153,11 @@ public class FileDO extends BaseDO {
this.metadata = JSONUtil.toJsonStr(metadataMap);
String thumbnailPath = fileInfo.getThumbnailPath();
if (StrUtil.isNotBlank(thumbnailPath)) {
- this.thumbnailName = StrUtil.subAfter(thumbnailPath, StringConstants.SLASH, true);
+ String normalizedThumbnailPath = thumbnailPath.replace("\\", StringConstants.SLASH);
+ if (!normalizedThumbnailPath.startsWith("http://") && !normalizedThumbnailPath.startsWith("https://")) {
+ normalizedThumbnailPath = StrUtil.removePrefix(normalizedThumbnailPath, StringConstants.SLASH);
+ }
+ this.thumbnailName = FileNameUtil.getName(normalizedThumbnailPath);
}
this.thumbnailSize = fileInfo.getThumbnailSize();
this.thumbnailMetadata = null;
--
Gitee
From 83c51326813c09543d90c83619dc49f49c052d81 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E5=90=B4=E6=B3=BD=E5=A8=81?= <958142070@qq.com>
Date: Mon, 2 Mar 2026 17:32:42 +0800
Subject: [PATCH 03/12] =?UTF-8?q?refactor:=20=E5=B0=86MultipartUpload?=
=?UTF-8?q?=E7=BC=93=E5=AD=98=E5=AE=9E=E7=8E=B0=E5=88=87=E6=8D=A2=E4=B8=BA?=
=?UTF-8?q?Redis=E5=AE=9E=E7=8E=B0=E5=B9=B6=E5=88=A0=E9=99=A4=E6=9C=AC?=
=?UTF-8?q?=E5=9C=B0=E5=8F=8ARedis=20DAO?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 移除本地存储处理器及相关MultipartUpload本地和Redis DAO实现
- FileRecorderImpl中分片信息缓存逻辑切换为Redis操作,使用RedisUtils读写分片数据
- 将MD5到uploadId映射、分片上传信息及元数据缓存均迁移至Redis,并设置缓存过期时间
- 优化分片列表获取,读取Redis Hash并反序列化,保证分片按编号排序返回
- 删除分片及MD5映射时,改用Redis删除操作,增加异常捕获及日志
- 删除MultipartSession时同时删除相关缓存及MD5映射,避免遗留脏数据
- 移除对MultipartUploadDao接口的依赖,相关实现统一由缓存层管理
- 精简及优化部分日志输出,增强错误信息追踪能力
---
.../system/config/file/FileRecorderImpl.java | 143 ++++++--
.../admin/system/dao/MultipartUploadDao.java | 124 -------
.../impl/RedisMultipartUploadDaoDaoImpl.java | 260 --------------
.../admin/system/factory/S3ClientFactory.java | 61 ----
.../system/factory/StorageHandlerFactory.java | 73 ----
.../admin/system/handler/StorageHandler.java | 81 -----
.../handler/impl/LocalStorageHandler.java | 246 -------------
.../system/handler/impl/S3StorageHandler.java | 326 ------------------
8 files changed, 117 insertions(+), 1197 deletions(-)
delete mode 100644 continew-system/src/main/java/top/continew/admin/system/dao/MultipartUploadDao.java
delete mode 100644 continew-system/src/main/java/top/continew/admin/system/dao/impl/RedisMultipartUploadDaoDaoImpl.java
delete mode 100644 continew-system/src/main/java/top/continew/admin/system/factory/S3ClientFactory.java
delete mode 100644 continew-system/src/main/java/top/continew/admin/system/factory/StorageHandlerFactory.java
delete mode 100644 continew-system/src/main/java/top/continew/admin/system/handler/StorageHandler.java
delete mode 100644 continew-system/src/main/java/top/continew/admin/system/handler/impl/LocalStorageHandler.java
delete mode 100644 continew-system/src/main/java/top/continew/admin/system/handler/impl/S3StorageHandler.java
diff --git a/continew-system/src/main/java/top/continew/admin/system/config/file/FileRecorderImpl.java b/continew-system/src/main/java/top/continew/admin/system/config/file/FileRecorderImpl.java
index f80a7f57..350dbe09 100644
--- a/continew-system/src/main/java/top/continew/admin/system/config/file/FileRecorderImpl.java
+++ b/continew-system/src/main/java/top/continew/admin/system/config/file/FileRecorderImpl.java
@@ -20,15 +20,17 @@ import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.URLUtil;
+import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
-import top.continew.admin.system.dao.MultipartUploadDao;
+import top.continew.admin.system.constant.MultipartUploadConstants;
import top.continew.admin.system.mapper.FileMapper;
import top.continew.admin.system.mapper.StorageMapper;
import top.continew.admin.system.model.entity.FileDO;
import top.continew.admin.system.model.entity.StorageDO;
+import top.continew.starter.cache.redisson.util.RedisUtils;
import top.continew.starter.core.constant.StringConstants;
import top.continew.starter.core.util.CollUtils;
import top.continew.starter.core.util.URLUtils;
@@ -37,6 +39,9 @@ import top.continew.starter.storage.domain.model.resp.FilePartInfo;
import top.continew.starter.storage.domain.model.resp.MultipartInitResp;
import top.continew.starter.storage.service.FileRecorder;
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
@@ -55,7 +60,6 @@ public class FileRecorderImpl implements FileRecorder {
private final FileMapper fileMapper;
private final StorageMapper storageMapper;
- private final MultipartUploadDao multipartUploadDao;
@Override
public boolean save(FileInfo fileInfo) {
@@ -137,7 +141,7 @@ public class FileRecorderImpl implements FileRecorder {
@Override
public void saveFilePart(FilePartInfo filePartInfo) {
- top.continew.admin.system.model.resp.file.FilePartInfo target = new top.continew.admin.system.model.resp.file.FilePartInfo();
+ FilePartInfo target = new FilePartInfo();
target.setFileId(filePartInfo.getFileId());
target.setPartNumber(filePartInfo.getPartNumber());
target.setPartSize(filePartInfo.getPartSize());
@@ -148,45 +152,94 @@ public class FileRecorderImpl implements FileRecorder {
target.setStatus(filePartInfo.getStatus());
target.setBucket(filePartInfo.getBucket());
target.setPath(filePartInfo.getPath());
- multipartUploadDao.setFilePart(filePartInfo.getUploadId(), target);
+ String key = MultipartUploadConstants.MULTIPART_PARTS_PREFIX + filePartInfo.getUploadId();
+ String partKey = target.getPartNumber().toString();
+ try {
+ RedisUtils.hSet(key, partKey, JSONUtil.toJsonStr(target));
+ RedisUtils.expire(key, Duration.ofHours(MultipartUploadConstants.DEFAULT_EXPIRE_HOURS));
+ log.debug("缓存分片信息: uploadId={}, partNumber={}", filePartInfo.getUploadId(), partKey);
+ } catch (Exception e) {
+ log.error("缓存分片信息失败: uploadId={}, partNumber={}", filePartInfo.getUploadId(), partKey, e);
+ throw new RuntimeException("缓存分片信息失败", e);
+ }
}
@Override
public List getFileParts(String fileId) {
- return multipartUploadDao.getFileParts(fileId).stream().map(filePartInfo -> {
- FilePartInfo target = new FilePartInfo();
- target.setFileId(filePartInfo.getFileId());
- target.setPartNumber(filePartInfo.getPartNumber());
- target.setPartSize(filePartInfo.getPartSize());
- target.setPartMd5(filePartInfo.getPartMd5());
- target.setPartETag(filePartInfo.getPartETag());
- target.setUploadId(filePartInfo.getUploadId());
- target.setUploadTime(filePartInfo.getUploadTime());
- target.setStatus(filePartInfo.getStatus());
- target.setBucket(filePartInfo.getBucket());
- target.setPath(filePartInfo.getPath());
- return target;
- }).toList();
+ String key = MultipartUploadConstants.MULTIPART_PARTS_PREFIX + fileId;
+ try {
+ Map entries = RedisUtils.hGetAll(key);
+ if (CollUtil.isEmpty(entries)) {
+ return new ArrayList<>();
+ }
+ return entries.values()
+ .stream()
+ .map(value -> JSONUtil.toBean(value
+ .toString(), top.continew.admin.system.model.resp.file.FilePartInfo.class))
+ .sorted(Comparator.comparing(top.continew.admin.system.model.resp.file.FilePartInfo::getPartNumber))
+ .map(filePartInfo -> {
+ FilePartInfo target = new FilePartInfo();
+ target.setFileId(filePartInfo.getFileId());
+ target.setPartNumber(filePartInfo.getPartNumber());
+ target.setPartSize(filePartInfo.getPartSize());
+ target.setPartMd5(filePartInfo.getPartMd5());
+ target.setPartETag(filePartInfo.getPartETag());
+ target.setUploadId(filePartInfo.getUploadId());
+ target.setUploadTime(filePartInfo.getUploadTime());
+ target.setStatus(filePartInfo.getStatus());
+ target.setBucket(filePartInfo.getBucket());
+ target.setPath(filePartInfo.getPath());
+ return target;
+ })
+ .toList();
+ } catch (Exception e) {
+ log.error("获取分片列表失败: uploadId={}", fileId, e);
+ return new ArrayList<>();
+ }
}
@Override
public void deleteFileParts(String fileId) {
- multipartUploadDao.deleteFileParts(fileId);
+ try {
+ RedisUtils.delete(MultipartUploadConstants.MULTIPART_PARTS_PREFIX + fileId);
+ log.debug("删除所有分片信息: uploadId={}", fileId);
+ } catch (Exception e) {
+ log.error("删除所有分片信息失败: uploadId={}", fileId, e);
+ }
}
@Override
public String getUploadIdByMd5(String md5) {
- return multipartUploadDao.getUploadIdByMd5(md5);
+ String md5Key = MultipartUploadConstants.MD5_TO_UPLOAD_ID_PREFIX + md5;
+ try {
+ return RedisUtils.hGet(md5Key, "uploadId");
+ } catch (Exception e) {
+ log.error("根据MD5获取uploadId失败: md5={}", md5, e);
+ return null;
+ }
}
@Override
public void setMd5Mapping(String md5, String uploadId) {
- multipartUploadDao.setMd5Mapping(md5, uploadId);
+ String md5Key = MultipartUploadConstants.MD5_TO_UPLOAD_ID_PREFIX + md5;
+ try {
+ RedisUtils.hSet(md5Key, "uploadId", uploadId);
+ RedisUtils.expire(md5Key, Duration.ofHours(MultipartUploadConstants.DEFAULT_EXPIRE_HOURS));
+ log.debug("缓存MD5映射: md5={}, uploadId={}", md5, uploadId);
+ } catch (Exception e) {
+ log.error("缓存MD5映射失败: md5={}, uploadId={}", md5, uploadId, e);
+ throw new RuntimeException("缓存MD5映射失败", e);
+ }
}
@Override
public void deleteMd5Mapping(String md5) {
- multipartUploadDao.deleteMd5Mapping(md5);
+ try {
+ RedisUtils.delete(MultipartUploadConstants.MD5_TO_UPLOAD_ID_PREFIX + md5);
+ log.debug("删除MD5映射: md5={}", md5);
+ } catch (Exception e) {
+ log.error("删除MD5映射失败: md5={}", md5, e);
+ }
}
@Override
@@ -205,13 +258,36 @@ public class FileRecorderImpl implements FileRecorder {
target.setPath(initResp.getPath());
target.setPartSize(initResp.getPartSize());
target.setUploadedPartNumbers(initResp.getUploadedPartNumbers());
- multipartUploadDao.setMultipartUpload(uploadId, target, metadata);
+ String key = MultipartUploadConstants.MULTIPART_UPLOAD_PREFIX + uploadId;
+ String metadataKey = MultipartUploadConstants.MULTIPART_METADATA_PREFIX + uploadId;
+ try {
+ RedisUtils.set(key, JSONUtil.toJsonStr(target), Duration
+ .ofHours(MultipartUploadConstants.DEFAULT_EXPIRE_HOURS));
+ if (metadata != null && !metadata.isEmpty()) {
+ for (Map.Entry entry : metadata.entrySet()) {
+ RedisUtils.hSet(metadataKey, entry.getKey(), entry.getValue());
+ }
+ RedisUtils.expire(metadataKey, Duration.ofHours(MultipartUploadConstants.DEFAULT_EXPIRE_HOURS));
+ }
+ log.debug("缓存分片上传信息: uploadId={}", uploadId);
+ } catch (Exception e) {
+ log.error("缓存分片上传信息失败: uploadId={}", uploadId, e);
+ throw new RuntimeException("缓存分片上传信息失败", e);
+ }
}
@Override
public MultipartInitResp getMultipartSession(String uploadId) {
- top.continew.admin.system.model.resp.file.MultipartUploadInitResp source = multipartUploadDao
- .getMultipartUpload(uploadId);
+ top.continew.admin.system.model.resp.file.MultipartUploadInitResp source = null;
+ try {
+ Object value = RedisUtils.get(MultipartUploadConstants.MULTIPART_UPLOAD_PREFIX + uploadId);
+ if (value != null) {
+ source = JSONUtil.toBean(value
+ .toString(), top.continew.admin.system.model.resp.file.MultipartUploadInitResp.class);
+ }
+ } catch (Exception e) {
+ log.error("获取分片上传信息失败: uploadId={}", uploadId, e);
+ }
if (source == null) {
return null;
}
@@ -234,7 +310,22 @@ public class FileRecorderImpl implements FileRecorder {
@Override
public void deleteMultipartSession(String uploadId) {
- multipartUploadDao.deleteMultipartUpload(uploadId);
+ try {
+ String key = MultipartUploadConstants.MULTIPART_UPLOAD_PREFIX + uploadId;
+ String metadataKey = MultipartUploadConstants.MULTIPART_METADATA_PREFIX + uploadId;
+ String expireKey = MultipartUploadConstants.MULTIPART_EXPIRE_PREFIX + uploadId;
+ MultipartInitResp initResp = getMultipartSession(uploadId);
+ String fileMd5 = initResp != null ? initResp.getFileMd5() : null;
+ if (StrUtil.isNotBlank(fileMd5)) {
+ deleteMd5Mapping(fileMd5);
+ }
+ RedisUtils.delete(key);
+ RedisUtils.delete(metadataKey);
+ RedisUtils.delete(expireKey);
+ log.debug("删除分片上传信息: uploadId={}", uploadId);
+ } catch (Exception e) {
+ log.error("删除分片上传信息失败: uploadId={}", uploadId, e);
+ }
}
/**
diff --git a/continew-system/src/main/java/top/continew/admin/system/dao/MultipartUploadDao.java b/continew-system/src/main/java/top/continew/admin/system/dao/MultipartUploadDao.java
deleted file mode 100644
index 9fe20289..00000000
--- a/continew-system/src/main/java/top/continew/admin/system/dao/MultipartUploadDao.java
+++ /dev/null
@@ -1,124 +0,0 @@
-/*
- * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package top.continew.admin.system.dao;
-
-import top.continew.admin.system.model.resp.file.FilePartInfo;
-import top.continew.admin.system.model.resp.file.MultipartUploadInitResp;
-
-import java.util.List;
-import java.util.Map;
-
-/**
- * 分片上传持久化接口
- *
- * 纯粹的缓存操作,不包含业务逻辑:
- * 1. MD5到uploadId的映射管理
- * 2. 分片信息缓存
- * 3. 上传状态缓存
- *
- *
- * @author KAI
- * @since 2.14.0
- */
-public interface MultipartUploadDao {
-
- /**
- * 根据MD5获取uploadId
- *
- * @param md5 文件MD5值
- * @return uploadId,如果不存在则返回null
- */
- String getUploadIdByMd5(String md5);
-
- /**
- * 缓存MD5到uploadId的映射
- *
- * @param md5 文件MD5值
- * @param uploadId 上传ID
- */
- void setMd5Mapping(String md5, String uploadId);
-
- /**
- * 删除MD5映射
- *
- * @param md5 文件MD5值
- */
- void deleteMd5Mapping(String md5);
-
- /**
- * 设置缓存分片上传信息
- *
- * @param uploadId 上传ID
- * @param initResp 初始化响应
- * @param metadata 元数据
- */
- void setMultipartUpload(String uploadId, MultipartUploadInitResp initResp, Map metadata);
-
- /**
- * 获取分片上传信息
- *
- * @param uploadId 上传ID
- * @return 分片上传信息,如果不存在则返回null
- */
- MultipartUploadInitResp getMultipartUpload(String uploadId);
-
- /**
- * 删除分片上传信息
- *
- * @param uploadId 上传ID
- */
- void deleteMultipartUpload(String uploadId);
-
- void deleteMultipartUploadAll(String uploadId);
-
- /**
- * 设置缓存分片信息
- *
- * @param uploadId 上传ID
- * @param filePartInfo 分片信息
- */
- void setFilePart(String uploadId, FilePartInfo filePartInfo);
-
- /**
- * 获取所有分片信息
- *
- * @param uploadId 上传ID
- * @return 分片信息列表
- */
- List getFileParts(String uploadId);
-
- /**
- * 删除所有分片信息
- *
- * @param uploadId 上传ID
- */
- void deleteFileParts(String uploadId);
-
- /**
- * 检查分片是否存在
- *
- * @param uploadId 上传ID
- * @param partNumber 分片编号
- * @return 是否存在
- */
- boolean existsFilePart(String uploadId, int partNumber);
-
- /**
- * 清理过期的缓存数据
- */
- void cleanupExpiredData();
-}
diff --git a/continew-system/src/main/java/top/continew/admin/system/dao/impl/RedisMultipartUploadDaoDaoImpl.java b/continew-system/src/main/java/top/continew/admin/system/dao/impl/RedisMultipartUploadDaoDaoImpl.java
deleted file mode 100644
index 577c1326..00000000
--- a/continew-system/src/main/java/top/continew/admin/system/dao/impl/RedisMultipartUploadDaoDaoImpl.java
+++ /dev/null
@@ -1,260 +0,0 @@
-/*
- * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package top.continew.admin.system.dao.impl;
-
-import cn.hutool.core.util.StrUtil;
-import cn.hutool.json.JSONUtil;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.stereotype.Repository;
-import top.continew.admin.system.constant.MultipartUploadConstants;
-import top.continew.admin.system.dao.MultipartUploadDao;
-import top.continew.admin.system.model.resp.file.FilePartInfo;
-import top.continew.admin.system.model.resp.file.MultipartUploadInitResp;
-import top.continew.starter.cache.redisson.util.RedisUtils;
-
-import java.time.Duration;
-import java.time.LocalDateTime;
-import java.time.format.DateTimeFormatter;
-import java.util.*;
-import java.util.stream.Collectors;
-
-/**
- * Redis分片上传缓存实现
- *
- * 核心功能:
- * 1. MD5到uploadId的映射管理
- * 2. 分片信息缓存
- * 3. 上传状态缓存
- *
- *
- * @author KAI
- * @since 2025/7/30 17:40
- */
-@Slf4j
-@Repository
-public class RedisMultipartUploadDaoDaoImpl implements MultipartUploadDao {
-
- @Override
- public String getUploadIdByMd5(String md5) {
- String md5Key = MultipartUploadConstants.MD5_TO_UPLOAD_ID_PREFIX + md5;
- try {
- return RedisUtils.hGet(md5Key, "uploadId");
- } catch (Exception e) {
- log.error("根据MD5获取uploadId失败: md5={}", md5, e);
- return null;
- }
- }
-
- @Override
- public void setMd5Mapping(String md5, String uploadId) {
- String md5Key = MultipartUploadConstants.MD5_TO_UPLOAD_ID_PREFIX + md5;
- try {
- RedisUtils.hSet(md5Key, "uploadId", uploadId);
- RedisUtils.expire(md5Key, Duration.ofHours(MultipartUploadConstants.DEFAULT_EXPIRE_HOURS));
- log.debug("缓存MD5映射: md5={}, uploadId={}", md5, uploadId);
- } catch (Exception e) {
- log.error("缓存MD5映射失败: md5={}, uploadId={}", md5, uploadId, e);
- throw new RuntimeException("缓存MD5映射失败", e);
- }
- }
-
- @Override
- public void deleteMd5Mapping(String md5) {
- String md5Key = MultipartUploadConstants.MD5_TO_UPLOAD_ID_PREFIX + md5;
- try {
- RedisUtils.delete(md5Key);
- log.debug("删除MD5映射: md5={}", md5);
- } catch (Exception e) {
- log.error("删除MD5映射失败: md5={}", md5, e);
- }
- }
-
- private String getMd5Mapping(String uploadId) {
- List