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:
QAQ_Z
2025-09-11 09:06:31 +08:00
committed by Charles_7c
parent 56d93fc80d
commit 7ead337165
3 changed files with 114 additions and 23 deletions

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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)));
}
/**