From 100d744c25e1fda505b980d924b9ffd99224a1ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=88=E5=AD=90=E9=BB=98?= <925456043@qq.com> Date: Thu, 30 Jul 2026 17:39:13 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=B0=86=E5=B7=A5=E4=BD=9C=E6=B5=81?= =?UTF-8?q?=E4=B8=8E=20FAQ=20=E4=B8=8A=E4=BC=A0=E4=B8=8A=E9=99=90=E6=89=A9?= =?UTF-8?q?=E8=87=B3=2020MB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 同步管理端、用户中心与后端工作流文件校验 - 扩展 FAQ 普通及分享上传限制并补充边界测试 --- .../controller/ai/FaqItemController.java | 4 +-- .../ai/ShareKnowledgeController.java | 4 +-- .../WorkflowRunningParameterResolver.java | 4 +-- .../ai/node/DocNodeFileContentExtractor.java | 4 +-- .../WorkflowRunningParameterResolverTest.java | 35 +++++++++++++++++++ .../node/DocNodeFileContentExtractorTest.java | 32 +++++++++++++++++ .../langs/en-US/documentCollection.json | 2 +- .../langs/zh-CN/documentCollection.json | 2 +- .../ai/documentCollection/FaqEditDialog.vue | 2 +- .../__tests__/workflowFileValue.test.ts | 16 +++++++-- .../workflow/components/workflowFileValue.ts | 2 +- .../workflow/components/workflowFileValue.ts | 2 +- 12 files changed, 93 insertions(+), 16 deletions(-) diff --git a/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/FaqItemController.java b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/FaqItemController.java index 70f18757..dbcdf93a 100644 --- a/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/FaqItemController.java +++ b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/FaqItemController.java @@ -47,7 +47,7 @@ import java.util.Set; @UsePermission(moduleName = "/api/v1/documentCollection") public class FaqItemController extends BaseCurdController { - 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 ALLOWED_IMAGE_TYPES = new HashSet<>(Arrays.asList( "image/jpeg", "image/png", @@ -215,7 +215,7 @@ public class FaqItemController extends BaseCurdController MAX_IMAGE_SIZE_BYTES) { - throw new BusinessException("图片大小不能超过5MB"); + throw new BusinessException("图片大小不能超过20MB"); } if (!isAllowedImageType(file)) { throw new BusinessException("仅支持 JPG/PNG/WEBP/GIF 图片"); diff --git a/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/ShareKnowledgeController.java b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/ShareKnowledgeController.java index 9718b6e5..47155c46 100644 --- a/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/ShareKnowledgeController.java +++ b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/ShareKnowledgeController.java @@ -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 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 图片"); diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/easyagentsflow/service/WorkflowRunningParameterResolver.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/easyagentsflow/service/WorkflowRunningParameterResolver.java index 2fae99c7..7e580edb 100644 --- a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/easyagentsflow/service/WorkflowRunningParameterResolver.java +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/easyagentsflow/service/WorkflowRunningParameterResolver.java @@ -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; diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/node/DocNodeFileContentExtractor.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/node/DocNodeFileContentExtractor.java index 53f0b9f4..adfb31d7 100644 --- a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/node/DocNodeFileContentExtractor.java +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/node/DocNodeFileContentExtractor.java @@ -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; diff --git a/easyflow-modules/easyflow-module-ai/src/test/java/tech/easyflow/ai/easyagentsflow/service/WorkflowRunningParameterResolverTest.java b/easyflow-modules/easyflow-module-ai/src/test/java/tech/easyflow/ai/easyagentsflow/service/WorkflowRunningParameterResolverTest.java index 9e4dee1d..77ca2826 100644 --- a/easyflow-modules/easyflow-module-ai/src/test/java/tech/easyflow/ai/easyagentsflow/service/WorkflowRunningParameterResolverTest.java +++ b/easyflow-modules/easyflow-module-ai/src/test/java/tech/easyflow/ai/easyagentsflow/service/WorkflowRunningParameterResolverTest.java @@ -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 variables = new LinkedHashMap<>(); + variables.put("attachments", fileValue( + "accepted.pdf", + "/files/accepted.pdf", + 20L * 1024L * 1024L + )); + + Map 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() diff --git a/easyflow-modules/easyflow-module-ai/src/test/java/tech/easyflow/ai/node/DocNodeFileContentExtractorTest.java b/easyflow-modules/easyflow-module-ai/src/test/java/tech/easyflow/ai/node/DocNodeFileContentExtractorTest.java index 3ee937a1..743237e8 100644 --- a/easyflow-modules/easyflow-module-ai/src/test/java/tech/easyflow/ai/node/DocNodeFileContentExtractorTest.java +++ b/easyflow-modules/easyflow-module-ai/src/test/java/tech/easyflow/ai/node/DocNodeFileContentExtractorTest.java @@ -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 accepted = buildFileValue( + "accepted.pdf", + "/files/accepted.pdf", + "application/pdf" + ); + accepted.put("size", 20L * 1024L * 1024L); + Assert.assertEquals(1, extractor.toDocumentSourceRefs(accepted).size()); + + Map 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 buildFileValue(String fileName, String filePath, String contentType) { Map value = new HashMap(); value.put("fileName", fileName); diff --git a/easyflow-ui-admin/app/src/locales/langs/en-US/documentCollection.json b/easyflow-ui-admin/app/src/locales/langs/en-US/documentCollection.json index 49c11f22..7199b9d5 100644 --- a/easyflow-ui-admin/app/src/locales/langs/en-US/documentCollection.json +++ b/easyflow-ui-admin/app/src/locales/langs/en-US/documentCollection.json @@ -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", diff --git a/easyflow-ui-admin/app/src/locales/langs/zh-CN/documentCollection.json b/easyflow-ui-admin/app/src/locales/langs/zh-CN/documentCollection.json index 213f25cc..52ea76ed 100644 --- a/easyflow-ui-admin/app/src/locales/langs/zh-CN/documentCollection.json +++ b/easyflow-ui-admin/app/src/locales/langs/zh-CN/documentCollection.json @@ -179,7 +179,7 @@ "answerRequired": "答案不能为空", "collectionRequired": "知识库ID不能为空", "imageTypeInvalid": "仅支持 JPG/PNG/WEBP/GIF 图片", - "imageSizeExceeded": "图片大小不能超过5MB", + "imageSizeExceeded": "图片大小不能超过20MB", "imageUploadFailed": "图片上传失败", "import": { "title": "导入FAQ", diff --git a/easyflow-ui-admin/app/src/views/ai/documentCollection/FaqEditDialog.vue b/easyflow-ui-admin/app/src/views/ai/documentCollection/FaqEditDialog.vue index f6649143..9eadd751 100644 --- a/easyflow-ui-admin/app/src/views/ai/documentCollection/FaqEditDialog.vue +++ b/easyflow-ui-admin/app/src/views/ai/documentCollection/FaqEditDialog.vue @@ -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', diff --git a/easyflow-ui-admin/app/src/views/ai/workflow/components/__tests__/workflowFileValue.test.ts b/easyflow-ui-admin/app/src/views/ai/workflow/components/__tests__/workflowFileValue.test.ts index 32c15b09..c6e8ad46 100644 --- a/easyflow-ui-admin/app/src/views/ai/workflow/components/__tests__/workflowFileValue.test.ts +++ b/easyflow-ui-admin/app/src/views/ai/workflow/components/__tests__/workflowFileValue.test.ts @@ -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('上传前校验总大小限制', () => { diff --git a/easyflow-ui-admin/app/src/views/ai/workflow/components/workflowFileValue.ts b/easyflow-ui-admin/app/src/views/ai/workflow/components/workflowFileValue.ts index 81465393..b3f5c05b 100644 --- a/easyflow-ui-admin/app/src/views/ai/workflow/components/workflowFileValue.ts +++ b/easyflow-ui-admin/app/src/views/ai/workflow/components/workflowFileValue.ts @@ -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; diff --git a/easyflow-ui-usercenter/app/src/views/ai/workflow/components/workflowFileValue.ts b/easyflow-ui-usercenter/app/src/views/ai/workflow/components/workflowFileValue.ts index 81465393..b3f5c05b 100644 --- a/easyflow-ui-usercenter/app/src/views/ai/workflow/components/workflowFileValue.ts +++ b/easyflow-ui-usercenter/app/src/views/ai/workflow/components/workflowFileValue.ts @@ -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;