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()) {