From f5e9029c464e33d35888ca590f7216c560272f77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=96=87=E7=9D=BF=20=E5=BC=A0?= Date: Sat, 10 Oct 2026 16:18:02 +0800 Subject: [PATCH] =?UTF-8?q?docs:=20=E4=BF=AE=E6=AD=A3=E3=80=8C=E5=AF=BC?= =?UTF-8?q?=E5=85=A5=E5=BF=85=E9=A1=BB=E7=94=A8=E7=8B=AC=E7=AB=8B=E5=89=8D?= =?UTF-8?q?=E7=BC=80=E3=80=8D=E7=9A=84=E9=94=99=E8=AF=AF=E8=AE=BA=E6=96=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 实测 gin v1.9.1 允许静态段与 :id 在同一层级共存:探针测了 POST/GET 的单级与 两级共 5 种组合,把 /materials/import 放进现成的 materials 组全都不会 panic。 路由本身保持 /material-import 不变——独立成前缀是命名与分组上的选择(导入是 含模板下载与上传入库的一条链路,也避免 /materials/import 被读成物料 id), 不是 gin 的路由限制。计划文档同步更正。 顺带把计划 Task 6 Step 7 的验证方式从 go build 改成跑路由测试:go build 只做 编译、不执行 RegisterRoutes,注册期的 panic 它兜不住。 --- .../plans/2026-10-10-material-bulk-import.md | 16 +++++++++------- internal/handler/api.go | 6 ++++-- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/docs/superpowers/plans/2026-10-10-material-bulk-import.md b/docs/superpowers/plans/2026-10-10-material-bulk-import.md index 9f646d25..e0b56bcf 100644 --- a/docs/superpowers/plans/2026-10-10-material-bulk-import.md +++ b/docs/superpowers/plans/2026-10-10-material-bulk-import.md @@ -56,7 +56,7 @@ | GET | `/api/material-import/template` | 下载 xlsx 模板,返回文件流 | | POST | `/api/material-import` | `multipart/form-data`,字段名 `file`,上传即入库 | -**路由必须挂在独立前缀 `/material-import`**:`materials` 组下已有 `GET /:id`,gin 的路由树不允许同一段既有静态前缀又有 `:id` 通配,把 `/materials/import/...` 加进该组会直接 panic。 +**路由挂在独立前缀 `/material-import`**:这**不是** gin 的限制——实测 gin v1.9.1 允许静态段与 `:id` 在同一层级共存,把 `/materials/import/...` 加进 `materials` 组并不会 panic。独立成前缀是因为「导入」是含模板下载与上传入库的一条完整链路,两个端点归在一个前缀下,也避免 `/materials/import` 被读成某个物料的 id。 成功响应 `201 Created`: @@ -1952,11 +1952,13 @@ func (h *Handler) ImportMaterials(c *gin.Context) { materialImportSvc: service.NewMaterialImportService(), ``` -3. 在 `materials` 路由组之后加(**必须是独立前缀**): +3. 在 `materials` 路由组之后加(独立前缀,但**不是** gin 的路由限制,理由见「设计约定 3」): ```go - // 物料批量导入单独挂前缀:materials 组已有 GET /:id, - // gin 的路由树不允许同段既有静态前缀又有通配参数。 + // 物料批量导入单独挂前缀。这不是 gin 的限制——实测 v1.9.1 允许静态段与 + // :id 同层共存,把它放进上面的 materials 组并不会冲突;独立成前缀是因为 + // 「导入」是含模板下载与上传入库的一条完整链路,两个端点归在一处, + // 也避免 /materials/import 被读成某个物料的 id。 materialImport := api.Group("/material-import") materialImport.Use(middleware.AuthMiddleware(), middleware.OrganizationMiddleware()) { @@ -1970,10 +1972,10 @@ func (h *Handler) ImportMaterials(c *gin.Context) { Run: `go test ./internal/handler/ -v -run 'ErrorWithData|Routes'` Expected: PASS -- [ ] **Step 7: 起服务验证路由不 panic** +- [ ] **Step 7: 验证路由注册不 panic** -Run: `go build -o server.exe ./cmd/server && echo BUILD_OK` -Expected: `BUILD_OK`(gin 路由冲突会在注册时 panic,构建通过后由下一步的路由测试兜住) +Run: `go test ./internal/handler/ -run TestPhaseOneAndLegacyRoutesAreRegistered -v` +Expected: PASS(路由注册发生在 `NewHandler().RegisterRoutes` 内,这条用例会真正走完注册,真有冲突会在注册时 panic。注意 `go build` 只做编译、不注册路由,兜不住这类问题) - [ ] **Step 8: Commit** diff --git a/internal/handler/api.go b/internal/handler/api.go index 502ec1aa..ac16f83d 100644 --- a/internal/handler/api.go +++ b/internal/handler/api.go @@ -95,8 +95,10 @@ func (h *Handler) RegisterRoutes(r *gin.Engine) { materials.POST("/:id/restore", h.RestoreMaterial) } - // 物料批量导入单独挂前缀:materials 组已有 GET /:id, - // gin 的路由树不允许同段既有静态前缀又有通配参数。 + // 物料批量导入单独挂前缀。这不是 gin 的限制——实测 v1.9.1 允许静态段与 + // :id 同层共存,把它放进上面的 materials 组并不会冲突;独立成前缀是因为 + // 「导入」是含模板下载与上传入库的一条完整链路,两个端点归在一处, + // 也避免 /materials/import 被读成某个物料的 id。 materialImport := api.Group("/material-import") materialImport.Use(middleware.AuthMiddleware(), middleware.OrganizationMiddleware()) {