fix: 完善工作流公共接口上传与错误契约
- 区分 HTTP 请求标识与内部上传标识,补齐 Redis 旧记录兼容和关联日志 - 保持归一化 MIME 一致,并隔离工作流鉴权错误契约对其他公共接口的影响 - 收口 Multipart 操作日志与对象存储故障分类,归档范围:S05
This commit is contained in:
@@ -122,21 +122,23 @@ public class XFIleStorageServiceImpl implements FileStorageService {
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取上传文件的 Content-Type,并为文本文件补充 UTF-8 编码。
|
||||
* 获取上传文件的 Content-Type,并在客户端未声明时为文本文件补充 UTF-8 编码。
|
||||
*
|
||||
* @param file 上传文件
|
||||
* @return 文件媒体类型
|
||||
*/
|
||||
public static String getFileContentType(MultipartFile file) {
|
||||
String originalFilename = file.getOriginalFilename();
|
||||
String contentType = null;
|
||||
if (originalFilename != null && originalFilename.toLowerCase().endsWith(".txt")) {
|
||||
contentType = "text/plain; charset=utf-8";
|
||||
} else {
|
||||
// 其他类型文件可以按需设置
|
||||
contentType = file.getContentType();
|
||||
String contentType = file.getContentType();
|
||||
if (StringUtils.hasText(contentType)) {
|
||||
return contentType;
|
||||
}
|
||||
return contentType;
|
||||
if (StringUtils.endsWithIgnoreCase(
|
||||
originalFilename,
|
||||
".txt")) {
|
||||
return "text/plain; charset=utf-8";
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -109,6 +109,53 @@ public class XFIleStorageServiceImplTest {
|
||||
assertTrue(platform.exists);
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证可恢复上传保留上游已经归一化的 MIME,不再按 txt 扩展名二次覆盖。
|
||||
*
|
||||
* @throws Exception 注入测试替身失败
|
||||
*/
|
||||
@Test
|
||||
public void recoverableSavePreservesNormalizedTextContentType()
|
||||
throws Exception {
|
||||
RecoverablePlatform platform = new RecoverablePlatform(
|
||||
"minio-main",
|
||||
"attachment",
|
||||
"https://files/");
|
||||
RecoverableStorageService delegate =
|
||||
new RecoverableStorageService(platform);
|
||||
XFIleStorageServiceImpl service = createService(delegate);
|
||||
FileStorageWriteHandle handle = service.prepareRecoverableWrite(
|
||||
"workflow-api-upload/upload-1",
|
||||
"content.txt");
|
||||
|
||||
service.saveRecoverable(
|
||||
new BytesMultipartFile(
|
||||
"content".getBytes(
|
||||
java.nio.charset.StandardCharsets.UTF_8),
|
||||
"content.txt",
|
||||
"application/octet-stream"),
|
||||
handle);
|
||||
|
||||
assertEquals(
|
||||
"application/octet-stream",
|
||||
delegate.uploadContentType);
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证客户端未声明 MIME 时继续为 txt 文件补充 UTF-8 文本类型。
|
||||
*/
|
||||
@Test
|
||||
public void textFileWithoutContentTypeUsesUtf8Fallback() {
|
||||
BytesMultipartFile file = new BytesMultipartFile(
|
||||
new byte[]{1},
|
||||
"content.TXT",
|
||||
null);
|
||||
|
||||
assertEquals(
|
||||
"text/plain; charset=utf-8",
|
||||
XFIleStorageServiceImpl.getFileContentType(file));
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证 MinIO 可恢复读取使用已配置客户端和句柄中的精确对象键,不请求公开 URL。
|
||||
*
|
||||
@@ -398,6 +445,8 @@ public class XFIleStorageServiceImplTest {
|
||||
private String uploadPath;
|
||||
/** 上传文件名。 */
|
||||
private String uploadFilename;
|
||||
/** 上传媒体类型。 */
|
||||
private String uploadContentType;
|
||||
/** recorder 删除调用次数。 */
|
||||
private int recorderDeleteCalls;
|
||||
/** recorder 删除是否抛出异常。 */
|
||||
@@ -536,6 +585,7 @@ public class XFIleStorageServiceImplTest {
|
||||
*/
|
||||
@Override
|
||||
public org.dromara.x.file.storage.core.upload.UploadPretreatment setContentType(String contentType) {
|
||||
delegate.uploadContentType = contentType;
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -562,20 +612,42 @@ public class XFIleStorageServiceImplTest {
|
||||
private static final class BytesMultipartFile implements MultipartFile {
|
||||
/** 文件内容。 */
|
||||
private final byte[] bytes;
|
||||
/** 文件名。 */
|
||||
private final String filename;
|
||||
/** 文件媒体类型。 */
|
||||
private final String contentType;
|
||||
|
||||
/**
|
||||
* 创建上传文件替身。
|
||||
*
|
||||
* @param bytes 文件内容
|
||||
*/
|
||||
private BytesMultipartFile(byte[] bytes) { this.bytes = bytes.clone(); }
|
||||
private BytesMultipartFile(byte[] bytes) {
|
||||
this(bytes, "content.bin", "application/octet-stream");
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建指定文件名和媒体类型的上传文件替身。
|
||||
*
|
||||
* @param bytes 文件内容
|
||||
* @param filename 文件名
|
||||
* @param contentType 文件媒体类型
|
||||
*/
|
||||
private BytesMultipartFile(
|
||||
byte[] bytes,
|
||||
String filename,
|
||||
String contentType) {
|
||||
this.bytes = bytes.clone();
|
||||
this.filename = filename;
|
||||
this.contentType = contentType;
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
@Override public String getName() { return "file"; }
|
||||
/** {@inheritDoc} */
|
||||
@Override public String getOriginalFilename() { return "content.bin"; }
|
||||
@Override public String getOriginalFilename() { return filename; }
|
||||
/** {@inheritDoc} */
|
||||
@Override public String getContentType() { return "application/octet-stream"; }
|
||||
@Override public String getContentType() { return contentType; }
|
||||
/** {@inheritDoc} */
|
||||
@Override public boolean isEmpty() { return bytes.length == 0; }
|
||||
/** {@inheritDoc} */
|
||||
|
||||
Reference in New Issue
Block a user