From 62372abeb6e87a4fc90eb679caac5b0e7221b130 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=96=87=E7=9D=BF=20=E5=BC=A0?= Date: Mon, 12 Oct 2026 16:51:58 +0800 Subject: [PATCH] =?UTF-8?q?fix(material-import):=20=E5=AF=BC=E5=85=A5?= =?UTF-8?q?=E4=B8=8A=E9=99=90=E4=B8=8B=E8=B0=83=E4=B8=BA=201MB/10000=20?= =?UTF-8?q?=E8=A1=8C=EF=BC=8C=E5=90=8C=E6=AD=A5=E6=8F=90=E7=A4=BA=E3=80=81?= =?UTF-8?q?=E6=96=87=E6=A1=A3=E4=B8=8E=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/api.md | 6 +++--- docs/回归测试用例.md | 4 ++-- frontend/src/views/Materials.test.js | 2 +- frontend/src/views/Materials.vue | 4 ++-- internal/handler/material_import.go | 6 +++--- internal/handler/material_import_test.go | 4 ++-- internal/service/material_import_excel.go | 6 +++--- internal/service/material_import_excel_test.go | 2 +- 8 files changed, 17 insertions(+), 17 deletions(-) diff --git a/docs/api.md b/docs/api.md index cde262b1..325f35ac 100644 --- a/docs/api.md +++ b/docs/api.md @@ -416,9 +416,9 @@ 上传填好的 xlsx,**校验通过即在同一个请求内入库**——没有「预览确认」环节,也没有暂存任务 ID。 -**请求:** `multipart/form-data`,文件字段名 `file`。仅支持 `.xlsx`,单文件不超过 5 MB,数据区不超过 20,000 行(不含表头)。 +**请求:** `multipart/form-data`,文件字段名 `file`。仅支持 `.xlsx`,单文件不超过 1 MB,数据区不超过 10,000 行(不含表头)。 -服务端在 multipart 解析前限制请求体为 5 MiB + 64 KiB(表单开销),文件本身仍限制为 5 MiB;超大请求在读到上限时提前拒绝。Excel 使用逐行迭代读取,发现第 20,001 条非空数据时立即停止,表头和空行不计数;较大的工作表 XML 使用临时文件,避免将整张表的所有行缓存到内存。 +服务端在 multipart 解析前限制请求体为 1 MiB + 64 KiB(表单开销),文件本身仍限制为 1 MiB;超大请求在读到上限时提前拒绝。Excel 使用逐行迭代读取,发现第 10,001 条非空数据时立即停止,表头和空行不计数;较大的工作表 XML 使用临时文件,避免将整张表的所有行缓存到内存。 **逐行校验规则:** @@ -463,7 +463,7 @@ **状态码:** - `201 Created` — 全部行通过并入库 -- `400 Bad Request` — 存在校验未通过的行(整批不入库);模板不合法(「模板格式不正确,请重新下载最新模板」)、行数超限(「导入行数超过 20000 行上限」)、未选文件(「请选择要导入的 xlsx 文件」)、文件超过 5 MB(「文件超过 5MB 上限」) +- `400 Bad Request` — 存在校验未通过的行(整批不入库);模板不合法(「模板格式不正确,请重新下载最新模板」)、行数超限(「导入行数超过 10000 行上限」)、未选文件(「请选择要导入的 xlsx 文件」)、文件超过 1 MB(「文件超过 1MB 上限」) - `500 Internal Server Error` — 入库失败,事务已回滚,整批未落库 **行为说明:** diff --git a/docs/回归测试用例.md b/docs/回归测试用例.md index 87afb0ac..f46f030a 100644 --- a/docs/回归测试用例.md +++ b/docs/回归测试用例.md @@ -262,9 +262,9 @@ **测试内容:** 验证无效文件拦截和大小、行数限制。 -**操作步骤:** 分别尝试上传 `.xls`、`.csv`、超过 5 MiB(5×1024×1024 字节)的文件、损坏的 `.xlsx`、缺少「物料数据」工作表或必填表头的文件,并尝试取消文件选择。再分别上传 20000 行和 20001 行合法非空数据的 `.xlsx`,两份文件均不超过 5 MiB,记录耗时和结果。 +**操作步骤:** 分别尝试上传 `.xls`、`.csv`、超过 1 MiB(1024×1024 字节)的文件、损坏的 `.xlsx`、缺少「物料数据」工作表或必填表头的文件,并尝试取消文件选择。再分别上传 10000 行和 10001 行合法非空数据的 `.xlsx`,两份文件均不超过 1 MiB,记录耗时和结果。 -**判断指标:** 不支持的格式、超限大小和错误模板不能新增物料,并有可理解的提示;取消选择无新增。20000 行在允许范围内,应完整导入;20001 行被整批拒绝,表头和空行不计入数量。若出现超时,记录实际提示并刷新查询是否已导入,确认结果后再决定是否重传,避免重复新增。 +**判断指标:** 不支持的格式、超限大小和错误模板不能新增物料,并有可理解的提示;取消选择无新增。10000 行在允许范围内,应完整导入;10001 行被整批拒绝,表头和空行不计入数量。若出现超时,记录实际提示并刷新查询是否已导入,确认结果后再决定是否重传,避免重复新增。 ### TC-26 重复数据与重复上传 diff --git a/frontend/src/views/Materials.test.js b/frontend/src/views/Materials.test.js index 91a3ceba..1fa5195c 100644 --- a/frontend/src/views/Materials.test.js +++ b/frontend/src/views/Materials.test.js @@ -682,7 +682,7 @@ describe('批量导入', () => { test.each([ [400, '模板格式不正确,请重新下载最新模板', '模板格式不正确,请重新下载最新模板'], - [400, '文件超过 5MB 上限', '文件超过 5MB 上限'], + [400, '文件超过 1MB 上限', '文件超过 1MB 上限'], [500, '物料编码已存在', '物料编码已存在'], [500, 'Internal Server Error', '批量导入失败'], [500, undefined, '批量导入失败'] diff --git a/frontend/src/views/Materials.vue b/frontend/src/views/Materials.vue index 56b47b81..3454f990 100644 --- a/frontend/src/views/Materials.vue +++ b/frontend/src/views/Materials.vue @@ -289,8 +289,8 @@ async function handleImportFile(event) { ElMessage.warning('只支持 .xlsx 文件') return } - if (file.size > 5 * 1024 * 1024) { - ElMessage.warning('文件不能超过 5MB') + if (file.size > 1 * 1024 * 1024) { + ElMessage.warning('文件不能超过 1MB') return } diff --git a/internal/handler/material_import.go b/internal/handler/material_import.go index a42b5119..513f4997 100644 --- a/internal/handler/material_import.go +++ b/internal/handler/material_import.go @@ -36,7 +36,7 @@ func (h *Handler) ImportMaterials(c *gin.Context) { // 在 multipart 解析前限制读取;文件限额之外仅给表单头部预留少量空间。 const maxRequestBytes = service.MaterialImportMaxBytes + (64 << 10) if c.Request.ContentLength > maxRequestBytes { - response.BadRequest(c, "文件超过 5MB 上限或表单数据过大") + response.BadRequest(c, "文件超过 1MB 上限或表单数据过大") return } c.Request.Body = http.MaxBytesReader(c.Writer, c.Request.Body, maxRequestBytes) @@ -51,14 +51,14 @@ func (h *Handler) ImportMaterials(c *gin.Context) { if err != nil { var sizeErr *http.MaxBytesError if errors.As(err, &sizeErr) { - response.BadRequest(c, "文件超过 5MB 上限或表单数据过大") + response.BadRequest(c, "文件超过 1MB 上限或表单数据过大") return } response.BadRequest(c, "请选择要导入的 xlsx 文件") return } if file.Size > service.MaterialImportMaxBytes { - response.BadRequest(c, "文件超过 5MB 上限") + response.BadRequest(c, "文件超过 1MB 上限") return } diff --git a/internal/handler/material_import_test.go b/internal/handler/material_import_test.go index a67f81e8..4fff0801 100644 --- a/internal/handler/material_import_test.go +++ b/internal/handler/material_import_test.go @@ -53,7 +53,7 @@ func TestMaterialImportRejectsLargeBodyBeforeReadingItAll(t *testing.T) { c.Request.ContentLength = contentLength c.Request.Header.Set("Content-Type", writer.FormDataContentType()) (&Handler{}).ImportMaterials(c) - if recorder.Code != http.StatusBadRequest || !strings.Contains(recorder.Body.String(), "5MB") { + if recorder.Code != http.StatusBadRequest || !strings.Contains(recorder.Body.String(), "1MB") { t.Fatalf("status=%d, body=%s", recorder.Code, recorder.Body.String()) } if body.read > service.MaterialImportMaxBytes+(64<<10)+1 { @@ -87,7 +87,7 @@ func TestMaterialImportFileSizeBoundaryAllowsMultipartOverhead(t *testing.T) { (&Handler{materialImportSvc: service.NewMaterialImportService()}).ImportMaterials(c) want := "模板格式不正确" if size > service.MaterialImportMaxBytes { - want = "文件超过 5MB 上限" + want = "文件超过 1MB 上限" } if recorder.Code != http.StatusBadRequest || !strings.Contains(recorder.Body.String(), want) { t.Fatalf("status=%d, body=%s, want %s", recorder.Code, recorder.Body.String(), want) diff --git a/internal/service/material_import_excel.go b/internal/service/material_import_excel.go index c4864438..9da01da4 100644 --- a/internal/service/material_import_excel.go +++ b/internal/service/material_import_excel.go @@ -16,9 +16,9 @@ const ( // MaterialImportSheetGuide 是填写说明的表名。 MaterialImportSheetGuide = "填写说明" // MaterialImportMaxRows 是数据区最大行数(不含表头)。 - MaterialImportMaxRows = 20000 + MaterialImportMaxRows = 10000 // MaterialImportMaxBytes 是上传文件大小上限。 - MaterialImportMaxBytes = 5 << 20 + MaterialImportMaxBytes = 1 << 20 ) var ( @@ -61,7 +61,7 @@ func BuildMaterialImportTemplate() ([]byte, error) { 14: "2. 在「物料数据」从第 2 行开始填写;第 1 行表头及工作表名称请保留。", 15: "3. 保存为 .xlsx 文件,回到「物料」页面点击「批量导入」并选择文件。", 16: "4. 有错误时查看行号和原因,可导出错误明细;修正原 .xlsx 后重新上传。", - 19: "文件限制:仅支持 .xlsx,大小不超过 5MB;非空数据最多 20000 行,表头和全空行不计数。", + 19: "文件限制:仅支持 .xlsx,大小不超过 1MB;非空数据最多 10000 行,表头和全空行不计数。", 20: "整批校验:任意一行校验失败,本次所有物料均不导入;请全部修正后重传。", 21: "编码与库存:物料编码由系统生成,无需填写;仅新增物料,不增加库存,也不更新已有物料。", 22: "重复导入:同名同规格不会自动去重;成功后重复上传同一文件,会再次新增物料。", diff --git a/internal/service/material_import_excel_test.go b/internal/service/material_import_excel_test.go index 54342e95..44b191e9 100644 --- a/internal/service/material_import_excel_test.go +++ b/internal/service/material_import_excel_test.go @@ -190,7 +190,7 @@ func TestBuildMaterialImportTemplateRoundTrips(t *testing.T) { for _, row := range guide { guideText.WriteString(strings.Join(row, " ")) } - for _, rule := range []string{"编码 名称", "5MB", "20000", "整批", "重复", "库存", "第 2 行"} { + for _, rule := range []string{"编码 名称", "1MB", "10000", "整批", "重复", "库存", "第 2 行"} { if !strings.Contains(guideText.String(), rule) { t.Fatalf("填写说明缺少规则:%s", rule) }