发布 v1.10 #5

Merged
czm merged 147 commits from develop into main 2026-08-20 11:36:27 +08:00
12 changed files with 93 additions and 16 deletions
Showing only changes of commit 100d744c25 - Show all commits

View File

@@ -47,7 +47,7 @@ import java.util.Set;
@UsePermission(moduleName = "/api/v1/documentCollection")
public class FaqItemController extends BaseCurdController<FaqItemService, FaqItem> {
private static final long MAX_IMAGE_SIZE_BYTES = 5L * 1024L * 1024L;
private static final long MAX_IMAGE_SIZE_BYTES = 20L * 1024L * 1024L;
private static final Set<String> ALLOWED_IMAGE_TYPES = new HashSet<>(Arrays.asList(
"image/jpeg",
"image/png",
@@ -215,7 +215,7 @@ public class FaqItemController extends BaseCurdController<FaqItemService, FaqIte
throw new BusinessException("图片不能为空");
}
if (file.getSize() > MAX_IMAGE_SIZE_BYTES) {
throw new BusinessException("图片大小不能超过5MB");
throw new BusinessException("图片大小不能超过20MB");
}
if (!isAllowedImageType(file)) {
throw new BusinessException("仅支持 JPG/PNG/WEBP/GIF 图片");

View File

@@ -75,7 +75,7 @@ import java.util.Set;
@RequestMapping("/api/v1/share/knowledge")
public class ShareKnowledgeController {
private static final long MAX_IMAGE_SIZE_BYTES = 5L * 1024L * 1024L;
private static final long MAX_IMAGE_SIZE_BYTES = 20L * 1024L * 1024L;
private static final Set<String> ALLOWED_IMAGE_TYPES = new HashSet<>(Arrays.asList(
"image/jpeg",
"image/png",
@@ -785,7 +785,7 @@ public class ShareKnowledgeController {
throw new BusinessException("图片不能为空");
}
if (file.getSize() > MAX_IMAGE_SIZE_BYTES) {
throw new BusinessException("图片大小不能超过5MB");
throw new BusinessException("图片大小不能超过20MB");
}
if (!isAllowedImageType(file)) {
throw new BusinessException("仅支持 JPG/PNG/WEBP/GIF 图片");

View File

@@ -35,7 +35,7 @@ public class WorkflowRunningParameterResolver {
private static final String DEFAULT_START_FORM_DESCRIPTION = "请先补充必要信息,再开始执行工作流。";
private static final String DEFAULT_START_FORM_SUBMIT_TEXT = "开始";
private static final int FILE_MAX_COUNT = 10;
private static final long FILE_MAX_SINGLE_SIZE = 5L * 1024 * 1024;
private static final long FILE_MAX_SINGLE_SIZE = 20L * 1024 * 1024;
private static final long FILE_MAX_TOTAL_SIZE = 50L * 1024 * 1024;
@Resource
@@ -363,7 +363,7 @@ public class WorkflowRunningParameterResolver {
}
Long size = parseLong(fileMap.get("size"));
if (size != null && size > FILE_MAX_SINGLE_SIZE) {
throw new BusinessException("文件参数 " + parameterName + " 中单个文件不能超过 5MB");
throw new BusinessException("文件参数 " + parameterName + " 中单个文件不能超过 20MB");
}
if (size != null && size > 0) {
totalSize += size;

View File

@@ -34,7 +34,7 @@ import java.util.Set;
@Component
public class DocNodeFileContentExtractor {
private static final int FILE_MAX_COUNT = 10;
private static final long FILE_MAX_SINGLE_SIZE = 5L * 1024 * 1024;
private static final long FILE_MAX_SINGLE_SIZE = 20L * 1024 * 1024;
private static final long FILE_MAX_TOTAL_SIZE = 50L * 1024 * 1024;
private final DocumentParseBridgeService documentParseBridgeService;
@@ -145,7 +145,7 @@ public class DocNodeFileContentExtractor {
}
Long size = sourceRef.getSize();
if (size != null && size > FILE_MAX_SINGLE_SIZE) {
throw new BusinessException("单个文件不能超过 5MB: " + sourceRef.getFileName());
throw new BusinessException("单个文件不能超过 20MB: " + sourceRef.getFileName());
}
if (size != null && size > 0) {
totalSize += size;

View File

@@ -8,6 +8,7 @@ import org.junit.Test;
import tech.easyflow.ai.entity.Workflow;
import tech.easyflow.ai.node.SearchDatasetNodeParser;
import tech.easyflow.ai.node.WorkflowNodeParser;
import tech.easyflow.common.web.exceptions.BusinessException;
import java.lang.reflect.Field;
import java.math.BigInteger;
@@ -178,6 +179,40 @@ public class WorkflowRunningParameterResolverTest {
Assert.assertEquals(2, ((List<?>) attachments).size());
}
/**
* 文件参数应允许 20MB 边界值,并拒绝超过边界的文件。
*
* @throws Exception 反射注入失败
*/
@Test
public void testNormalizeRuntimeVariablesShouldEnforceTwentyMbSingleFileLimit() throws Exception {
WorkflowRunningParameterResolver resolver = newResolver();
Map<String, Object> variables = new LinkedHashMap<>();
variables.put("attachments", fileValue(
"accepted.pdf",
"/files/accepted.pdf",
20L * 1024L * 1024L
));
Map<String, Object> normalized = resolver.normalizeRuntimeVariables(
workflowContentWithStartParameters(),
variables
);
Assert.assertEquals(1, ((List<?>) normalized.get("attachments")).size());
variables.put("attachments", fileValue(
"oversized.pdf",
"/files/oversized.pdf",
20L * 1024L * 1024L + 1L
));
try {
resolver.normalizeRuntimeVariables(workflowContentWithStartParameters(), variables);
Assert.fail("expected BusinessException");
} catch (BusinessException exception) {
Assert.assertEquals("文件参数 attachments 中单个文件不能超过 20MB", exception.getMessage());
}
}
private static WorkflowRunningParameterResolver newResolver() throws Exception {
WorkflowRunningParameterResolver resolver = new WorkflowRunningParameterResolver();
ChainParser parser = ChainParser.builder()

View File

@@ -263,6 +263,38 @@ public class DocNodeFileContentExtractorTest {
}
}
/**
* 验证文档节点允许 20MB 边界值,并拒绝超过边界的文件。
*/
@Test
public void shouldEnforceTwentyMbSingleFileLimit() {
DocNodeFileContentExtractor extractor = new DocNodeFileContentExtractor(
new RecordingDocumentParseBridgeService(),
new FakeFileStorageService(),
new FakeReaderManager("plain text")
);
Map<String, Object> accepted = buildFileValue(
"accepted.pdf",
"/files/accepted.pdf",
"application/pdf"
);
accepted.put("size", 20L * 1024L * 1024L);
Assert.assertEquals(1, extractor.toDocumentSourceRefs(accepted).size());
Map<String, Object> oversized = buildFileValue(
"oversized.pdf",
"/files/oversized.pdf",
"application/pdf"
);
oversized.put("size", 20L * 1024L * 1024L + 1L);
try {
extractor.toDocumentSourceRefs(oversized);
Assert.fail("expected BusinessException");
} catch (BusinessException exception) {
Assert.assertEquals("单个文件不能超过 20MB: oversized.pdf", exception.getMessage());
}
}
private Map<String, Object> buildFileValue(String fileName, String filePath, String contentType) {
Map<String, Object> value = new HashMap<String, Object>();
value.put("fileName", fileName);

View File

@@ -179,7 +179,7 @@
"answerRequired": "Answer is required",
"collectionRequired": "Collection id is required",
"imageTypeInvalid": "Only JPG/PNG/WEBP/GIF images are supported",
"imageSizeExceeded": "Image size must be less than 5MB",
"imageSizeExceeded": "Image size must be less than 20MB",
"imageUploadFailed": "Image upload failed",
"import": {
"title": "Import FAQ",

View File

@@ -179,7 +179,7 @@
"answerRequired": "答案不能为空",
"collectionRequired": "知识库ID不能为空",
"imageTypeInvalid": "仅支持 JPG/PNG/WEBP/GIF 图片",
"imageSizeExceeded": "图片大小不能超过5MB",
"imageSizeExceeded": "图片大小不能超过20MB",
"imageUploadFailed": "图片上传失败",
"import": {
"title": "导入FAQ",

View File

@@ -45,7 +45,7 @@ const props = defineProps({
});
const emit = defineEmits(['submit', 'update:modelValue']);
const MAX_IMAGE_SIZE = 5 * 1024 * 1024;
const MAX_IMAGE_SIZE = 20 * 1024 * 1024;
const ALLOWED_IMAGE_TYPES = new Set([
'image/gif',
'image/jpeg',

View File

@@ -127,11 +127,21 @@ describe('workflowFileValue', () => {
});
it('上传前校验单文件大小限制', () => {
const oversizedFile = new File([new Uint8Array(6 * 1024 * 1024)], 'large.pdf');
const acceptedFile = new File(
[new Uint8Array(20 * 1024 * 1024)],
'accepted.pdf',
);
const oversizedFile = new File(
[new Uint8Array(20 * 1024 * 1024 + 1)],
'large.pdf',
);
expect(() =>
validateWorkflowFileSelection([], [oversizedFile]),
).toThrow('单个文件不能超过 5.0 MB');
validateWorkflowFileSelection([], [acceptedFile]),
).not.toThrow();
expect(() => validateWorkflowFileSelection([], [oversizedFile])).toThrow(
'单个文件不能超过 20.0 MB',
);
});
it('上传前校验总大小限制', () => {

View File

@@ -21,7 +21,7 @@ export interface WorkflowResourceLike {
export const WORKFLOW_FILE_LIMITS = {
maxCount: 10,
maxSingleSize: 5 * 1024 * 1024,
maxSingleSize: 20 * 1024 * 1024,
maxTotalSize: 50 * 1024 * 1024,
} as const;

View File

@@ -21,7 +21,7 @@ export interface WorkflowResourceLike {
export const WORKFLOW_FILE_LIMITS = {
maxCount: 10,
maxSingleSize: 5 * 1024 * 1024,
maxSingleSize: 20 * 1024 * 1024,
maxTotalSize: 50 * 1024 * 1024,
} as const;