mirror of
				https://github.com/continew-org/continew-starter.git
				synced 2025-10-31 08:58:37 +08:00 
			
		
		
		
	perf(storage): 优化文件类型构建逻辑和默认存储平台延迟加载
- 优化 FileWrapper 类,增加从请求中获取文件名和内容类型的逻辑 - 改进 UploadPretreatment 类,延迟获取默认存储平台 Co-authored-by: QAQ_Z<958142070@qq.com> # message auto-generated for no-merge-commit merge: merge storage into dev 优化文件类型构建逻辑和默认存储平台延迟加载 Created-by: QAQ_Z Commit-by: QAQ_Z Merged-by: Charles_7c Description: <!-- 非常感谢您的 PR!在提交之前,请务必确保您 PR 的代码经过了完整测试,并且通过了代码规范检查。 --> <!-- 在 [] 中输入 x 来勾选) --> ## PR 类型 <!-- 您的 PR 引入了哪种类型的变更? --> <!-- 只支持选择一种类型,如果有多种类型,可以在更新日志中增加 “类型” 列。 --> - [x] 新 feature - [x] Bug 修复 - [ ] 功能增强 - [ ] 文档变更 - [ ] 代码样式变更 - [ ] 重构 - [ ] 性能改进 - [ ] 单元测试 - [ ] CI/CD - [ ] 其他 ## PR 目的 <!-- 描述一下您的 PR 解决了什么问题。如果可以,请链接到相关 issues。 --> 优化文件类型构建逻辑和默认存储平台延迟加载 ## 解决方案 <!-- 详细描述您是如何解决的问题 --> ## PR 测试 <!-- 如果可以,请为您的 PR 添加或更新单元测试。 --> <!-- 请描述一下您是如何测试 PR 的。例如:创建/更新单元测试或添加相关的截图。 --> ## Changelog | 模块 | Changelog | Related issues | |-----|-----------| -------------- | | continew-starter-storage | 优化文件类型构建逻辑和默认存储平台延迟加载 | | <!-- 如果有多种类型的变更,可以在变更日志表中增加 “类型” 列,该列的值与上方 “PR 类型” 相同。 --> <!-- Related issues 格式为 Closes #<issue号>,或者 Fixes #<issue号>,或者 Resolves #<issue号>。 --> ## 其他信息 <!-- 请描述一下还有哪些注意事项。例如:如果引入了一个不向下兼容的变更,请描述其影响。 --> ## 提交前确认 - [x] PR 代码经过了完整测试,并且通过了代码规范检查 - [ ] 已经完整填写 Changelog,并链接到了相关 issues - [x] PR 代码将要提交到 dev 分支 See merge request: continew/continew-starter!5
This commit is contained in:
		| @@ -16,11 +16,11 @@ | ||||
|  | ||||
| package top.continew.starter.storage.core; | ||||
|  | ||||
| import cn.hutool.core.util.StrUtil; | ||||
| import org.springframework.web.multipart.MultipartFile; | ||||
| import top.continew.starter.storage.domain.model.context.UploadContext; | ||||
| import top.continew.starter.storage.domain.model.req.ThumbnailSize; | ||||
| import top.continew.starter.storage.domain.model.resp.FileInfo; | ||||
| import top.continew.starter.storage.processor.preprocess.*; | ||||
| import top.continew.starter.storage.processor.progress.UploadProgressListener; | ||||
| import top.continew.starter.storage.service.FileProcessor; | ||||
|  | ||||
| @@ -45,7 +45,6 @@ public class UploadPretreatment { | ||||
|         this.storageService = storageService; | ||||
|         this.context = new UploadContext(); | ||||
|         this.context.setFile(file); | ||||
|         this.context.setPlatform(storageService.getDefaultPlatform()); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
| @@ -146,6 +145,8 @@ public class UploadPretreatment { | ||||
|      * @return {@link FileInfo } | ||||
|      */ | ||||
|     public FileInfo upload() { | ||||
|  | ||||
|         // 添加文件处理器 | ||||
|         for (FileProcessor processor : processors) { | ||||
|             storageService.addProcessor(processor); | ||||
|         } | ||||
| @@ -155,6 +156,11 @@ public class UploadPretreatment { | ||||
|             storageService.onProgress(progressListener); | ||||
|         } | ||||
|  | ||||
|         // 如果没有设置平台,则获取默认平台 (延迟获取默认存储平台) | ||||
|         if (StrUtil.isBlank(context.getPlatform())) { | ||||
|             context.setPlatform(storageService.getDefaultPlatform()); | ||||
|         } | ||||
|  | ||||
|         // 执行上传 | ||||
|         return storageService.upload(context); | ||||
|     } | ||||
|   | ||||
| @@ -17,13 +17,21 @@ | ||||
| package top.continew.starter.storage.domain.file; | ||||
|  | ||||
| import cn.hutool.json.JSONUtil; | ||||
| import jakarta.servlet.http.HttpServletRequest; | ||||
| import jakarta.servlet.http.Part; | ||||
| import org.slf4j.Logger; | ||||
| import org.slf4j.LoggerFactory; | ||||
| import org.springframework.http.MediaType; | ||||
| import org.springframework.web.context.request.RequestAttributes; | ||||
| import org.springframework.web.context.request.RequestContextHolder; | ||||
| import org.springframework.web.context.request.ServletRequestAttributes; | ||||
| import org.springframework.web.multipart.MultipartFile; | ||||
| import top.continew.starter.storage.common.exception.StorageException; | ||||
|  | ||||
| import java.io.IOException; | ||||
| import java.io.InputStream; | ||||
| import java.nio.charset.StandardCharsets; | ||||
| import java.util.Collection; | ||||
|  | ||||
| /** | ||||
|  * 文件包装器,用于统一处理不同类型的输入 | ||||
| @@ -33,6 +41,8 @@ import java.nio.charset.StandardCharsets; | ||||
|  */ | ||||
| public class FileWrapper { | ||||
|  | ||||
|     private static final Logger log = LoggerFactory.getLogger(FileWrapper.class); | ||||
|  | ||||
|     private MultipartFile multipartFile; | ||||
|     private byte[] bytes; | ||||
|     private InputStream inputStream; | ||||
| @@ -57,27 +67,51 @@ public class FileWrapper { | ||||
|  | ||||
|     /** | ||||
|      * 从 byte[] 创建 | ||||
|      * | ||||
|      * @param bytes       字节 | ||||
|      * @param filename    文件名 | ||||
|      * @param contentType 内容类型 | ||||
|      * @return {@link FileWrapper } | ||||
|      */ | ||||
|     public static FileWrapper of(byte[] bytes, String filename, String contentType) { | ||||
|         if (filename == null || filename.trim().isEmpty()) { | ||||
|             throw new StorageException("文件名不能为空"); | ||||
|         } | ||||
|         if (contentType == null || contentType.trim().isEmpty()) { | ||||
|             throw new StorageException("文件类型不能为空"); | ||||
|         } | ||||
|  | ||||
|         FileWrapper wrapper = new FileWrapper(); | ||||
|         FileWrapper wrapper = createBaseWrapper(filename, contentType); | ||||
|         wrapper.bytes = bytes; | ||||
|         wrapper.originalFilename = filename; | ||||
|         wrapper.contentType = contentType; | ||||
|         wrapper.size = bytes.length; | ||||
|         wrapper.size = bytes != null ? bytes.length : 0; | ||||
|         return wrapper; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 从 InputStream 创建 | ||||
|      * | ||||
|      * @param inputStream 输入流 | ||||
|      * @param filename    文件名 | ||||
|      * @param contentType 内容类型 | ||||
|      * @return {@link FileWrapper } | ||||
|      */ | ||||
|     public static FileWrapper of(InputStream inputStream, String filename, String contentType) { | ||||
|         FileWrapper wrapper = createBaseWrapper(filename, contentType); | ||||
|         wrapper.inputStream = inputStream; | ||||
|         wrapper.size = -1; | ||||
|         return wrapper; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 创建基本包装器 | ||||
|      * | ||||
|      * @param filename    文件名 | ||||
|      * @param contentType 内容类型 | ||||
|      * @return {@link FileWrapper } | ||||
|      */ | ||||
|     private static FileWrapper createBaseWrapper(String filename, String contentType) { | ||||
|         // 如果没有提供,尝试从请求中获取 | ||||
|         if (filename == null) { | ||||
|             filename = tryGetFilenameFromRequest(); | ||||
|         } | ||||
|         if (contentType == null) { | ||||
|             contentType = tryGetContentTypeFromRequest(); | ||||
|         } | ||||
|  | ||||
|         // 最终校验 | ||||
|         if (filename == null || filename.trim().isEmpty()) { | ||||
|             throw new StorageException("文件名不能为空"); | ||||
|         } | ||||
| @@ -86,13 +120,70 @@ public class FileWrapper { | ||||
|         } | ||||
|  | ||||
|         FileWrapper wrapper = new FileWrapper(); | ||||
|         wrapper.inputStream = inputStream; | ||||
|         wrapper.originalFilename = filename; | ||||
|         wrapper.contentType = contentType; | ||||
|         wrapper.size = -1; | ||||
|         return wrapper; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 尝试从当前 HTTP 请求中获取文件名 | ||||
|      */ | ||||
|     private static String tryGetFilenameFromRequest() { | ||||
|         try { | ||||
|             RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes(); | ||||
|             if (requestAttributes instanceof ServletRequestAttributes) { | ||||
|                 HttpServletRequest request = ((ServletRequestAttributes)requestAttributes).getRequest(); | ||||
|  | ||||
|                 // 检查是否是 multipart 请求 | ||||
|                 String requestContentType = request.getContentType(); | ||||
|                 if (requestContentType != null && requestContentType.toLowerCase().startsWith("multipart/")) { | ||||
|                     Collection<Part> parts = request.getParts(); | ||||
|  | ||||
|                     for (Part part : parts) { | ||||
|                         String submittedFilename = part.getSubmittedFileName(); | ||||
|                         if (submittedFilename != null && !submittedFilename.trim().isEmpty()) { | ||||
|                             return submittedFilename; | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } catch (Exception e) { | ||||
|             log.debug("从请求中获取文件名时发生异常: {}", e.getMessage()); | ||||
|         } | ||||
|         return null; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 尝试从当前 HTTP 请求中获取 ContentType | ||||
|      */ | ||||
|     private static String tryGetContentTypeFromRequest() { | ||||
|         try { | ||||
|             RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes(); | ||||
|             if (requestAttributes instanceof ServletRequestAttributes) { | ||||
|                 HttpServletRequest request = ((ServletRequestAttributes)requestAttributes).getRequest(); | ||||
|  | ||||
|                 // 检查是否是 multipart 请求 | ||||
|                 String requestContentType = request.getContentType(); | ||||
|                 if (requestContentType != null && requestContentType.toLowerCase().startsWith("multipart/")) { | ||||
|                     Collection<Part> parts = request.getParts(); | ||||
|  | ||||
|                     for (Part part : parts) { | ||||
|                         // 只处理文件部分 | ||||
|                         if (part.getSubmittedFileName() != null) { | ||||
|                             String partContentType = part.getContentType(); | ||||
|                             if (partContentType != null && !partContentType.trim().isEmpty()) { | ||||
|                                 return partContentType; | ||||
|                             } | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } catch (Exception e) { | ||||
|             log.debug("从请求中获取 ContentType 时发生异常: {}", e.getMessage()); | ||||
|         } | ||||
|         return null; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 从 Object 创建(智能识别) | ||||
|      */ | ||||
| @@ -115,17 +206,11 @@ public class FileWrapper { | ||||
|  | ||||
|         // 如果是 byte[] | ||||
|         if (obj instanceof byte[]) { | ||||
|             if (filename == null || contentType == null) { | ||||
|                 throw new StorageException("byte[] 类型必须指定文件名和文件类型"); | ||||
|             } | ||||
|             return of((byte[])obj, filename, contentType); | ||||
|         } | ||||
|  | ||||
|         // 如果是 InputStream | ||||
|         if (obj instanceof InputStream) { | ||||
|             if (filename == null || contentType == null) { | ||||
|                 throw new StorageException("InputStream 类型必须指定文件名和文件类型"); | ||||
|             } | ||||
|             return of((InputStream)obj, filename, contentType); | ||||
|         } | ||||
|  | ||||
|   | ||||
| @@ -93,8 +93,8 @@ public class StorageStrategyRouter implements ApplicationListener<ApplicationEve | ||||
|      */ | ||||
|     private StorageStrategy getOriginalStrategy(String platform) { | ||||
|         return Optional.ofNullable(dynamicStrategies.get(platform)) | ||||
|                 .or(() -> Optional.ofNullable(configStrategies.get(platform))) | ||||
|                 .orElseThrow(() -> new StorageException(String.format("不支持的存储平台: %s", platform))); | ||||
|             .or(() -> Optional.ofNullable(configStrategies.get(platform))) | ||||
|             .orElseThrow(() -> new StorageException(String.format("不支持的存储平台: %s", platform))); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 QAQ_Z
					QAQ_Z