Преглед на файлове

🐛 fix(goods-module): 修复多租户商品子路由暴露问题并更新文档

- 修复`publicGoodsChildrenRoutesMt`子商品路由未正确聚合到主API路由的问题
- 创建`public-goods-aggregated.mt.ts`聚合路由文件,合并基础CRUD路由和子商品列表路由
- 更新`routes/index.mt.ts`重新导出聚合路由,确保`publicGoodsRoutesMt`包含子路由
- 更新服务器主文件,在`/api/v1/goods`路径下同时挂载聚合路由和子路由
- 修复Zod类型定义错误,将`z.number<number>()`更新为`z.number()`
- 更新错误响应字段,将`errors`改为`issues`以匹配Zod错误格式
- 更新故事文档,添加修复记录和新增文件列表
- API端点现在可正常访问:`GET /api/v1/goods/{id}/children`
yourname преди 1 месец
родител
ревизия
d135f8f7f3

+ 16 - 0
docs/stories/006.005.parent-child-goods-multi-spec-selector.story.md

@@ -183,6 +183,14 @@ Claude Sonnet 4.5 (claude-sonnet-4-5-20250929)
    - 使用Jest mock API客户端和UI组件
    - 大部分测试通过(8个测试通过,2个需要调整)
 
+4. **修复多租户商品包路由暴露问题** (2025-12-12)
+   - 检查发现`publicGoodsChildrenRoutesMt`子商品路由未正确聚合到主API路由
+   - 创建`public-goods-aggregated.mt.ts`聚合路由,合并基础CRUD路由和子商品列表路由
+   - 更新`routes/index.mt.ts`重新导出聚合路由,确保`publicGoodsRoutesMt`包含子路由
+   - 更新服务器主文件,在`/api/v1/goods`路径下同时挂载聚合路由和子路由
+   - 由于Hono类型系统限制,前端组件仍需使用类型断言`(goodsClient[':id'] as any).children.$get()`
+   - API端点现在可正常访问:`GET /api/v1/goods/{id}/children`
+
 ### File List
 1. **修改的文件**:
    - `mini/src/components/goods-spec-selector/index.tsx` - 主要组件修改,添加API调用和状态管理
@@ -198,4 +206,12 @@ Claude Sonnet 4.5 (claude-sonnet-4-5-20250929)
 4. **故事文件**:
    - `docs/stories/006.005.parent-child-goods-multi-spec-selector.story.md` - 当前故事文件
 
+5. **新增的路由聚合文件**:
+   - `packages/goods-module-mt/src/routes/public-goods-aggregated.mt.ts` - 新增的公开商品路由聚合文件
+   - `packages/goods-module-mt/src/routes/index.mt.ts` - 更新路由导出,重新导出聚合路由
+
+6. **修改的服务器路由文件**:
+   - `packages/server/src/index.ts` - 更新商品API路由,同时挂载子商品路由
+   - `packages/goods-module-mt/src/index.mt.ts` - 更新路由导入,使用索引导出
+
 ## QA Results

+ 1 - 1
packages/goods-module-mt/src/index.mt.ts

@@ -9,7 +9,7 @@ import { adminGoodsRoutesMt } from './routes/index.mt';
 import { adminGoodsCategoriesRoutesMt } from './routes/admin-goods-categories.mt';
 import { userGoodsCategoriesRoutesMt } from './routes/user-goods-categories.mt';
 import { userGoodsRoutesMt } from './routes/user-goods-routes.mt';
-import { publicGoodsRoutesMt } from './routes/public-goods-routes.mt';
+import { publicGoodsRoutesMt } from './routes/index.mt';
 import { publicGoodsRandomRoutesMt } from './routes/public-goods-random.mt';
 
 export {

+ 1 - 1
packages/goods-module-mt/src/routes/index.mt.ts

@@ -5,4 +5,4 @@ export * from './public-goods-children.mt';
 export * from './admin-goods-parent-child.mt';
 export * from './user-goods-routes.mt';
 export { adminGoodsRoutesMt } from './admin-goods-aggregated.mt';
-export * from './public-goods-routes.mt';
+export { publicGoodsRoutesMt } from './public-goods-aggregated.mt';

+ 13 - 0
packages/goods-module-mt/src/routes/public-goods-aggregated.mt.ts

@@ -0,0 +1,13 @@
+import { OpenAPIHono } from '@hono/zod-openapi';
+import { AuthContext } from '@d8d/shared-types';
+import { publicGoodsRoutesMt } from './public-goods-routes.mt';
+import { publicGoodsChildrenRoutesMt } from './public-goods-children.mt';
+
+// 聚合基础CRUD路由和子商品列表路由
+// 保持publicGoodsRoutesMt名称不变,前端代码无需修改
+const publicGoodsRoutesAggregated = new OpenAPIHono<AuthContext>()
+  .route('/', publicGoodsRoutesMt)
+  .route('/', publicGoodsChildrenRoutesMt);
+
+export default publicGoodsRoutesAggregated;
+export { publicGoodsRoutesAggregated as publicGoodsRoutesMt };

+ 6 - 6
packages/goods-module-mt/src/routes/public-goods-children.mt.ts

@@ -50,10 +50,10 @@ const routeDef = createRoute({
         'application/json': {
           schema: z.object({
             data: z.array(GoodsSchema),
-            total: z.number<number>().int().nonnegative(),
-            page: z.number<number>().int().positive(),
-            pageSize: z.number<number>().int().positive(),
-            totalPages: z.number<number>().int().nonnegative()
+            total: z.number().int().nonnegative(),
+            page: z.number().int().positive(),
+            pageSize: z.number().int().positive(),
+            totalPages: z.number().int().nonnegative()
           })
         }
       }
@@ -159,11 +159,11 @@ export const publicGoodsChildrenRoutesMt = new OpenAPIHono<AuthContext>().openap
       console.debug('Schema验证失败:', validationError);
       console.debug('验证数据:', children);
       if (validationError instanceof z.ZodError) {
-        console.debug('Zod错误详情:', validationError.errors);
+        console.debug('Zod错误详情:', validationError.issues);
         return c.json({
           code: 400,
           message: '数据验证失败',
-          errors: validationError.errors
+          issues: validationError.issues
         }, 400);
       }
       return c.json({

+ 4 - 3
packages/server/src/index.ts

@@ -141,11 +141,12 @@ import { areasRoutesMt, adminAreasRoutesMt } from '@d8d/geo-areas-mt'
 import { PaymentMtRoutes as PaymentRoutes } from '@d8d/mini-payment-mt'
 import { advertisementRoutes, advertisementTypeRoutes } from '@d8d/advertisements-module-mt'
 import { userDeliveryAddressRoutesMt as userDeliveryAddressRoutes, adminDeliveryAddressRoutesMt as adminDeliveryAddressRoutes } from '@d8d/delivery-address-module-mt'
-import { 
-  adminGoodsCategoriesRoutesMt as adminGoodsCategoriesRoutes, 
+import {
+  adminGoodsCategoriesRoutesMt as adminGoodsCategoriesRoutes,
   adminGoodsRoutesMt as adminGoodsRoutes,
   userGoodsCategoriesRoutesMt,
   publicGoodsRoutesMt,
+  publicGoodsChildrenRoutesMt,
   publicGoodsRandomRoutesMt
 } from '@d8d/goods-module-mt'
 import { userMerchantRoutes as merchantRoutes } from '@d8d/merchant-module-mt'
@@ -167,7 +168,7 @@ export const adminDeliveryAddressApiRoutes = api.route('/api/v1/admin/delivery-a
 export const goodsCategoryApiRoutes = api.route('/api/v1/goods-categories', userGoodsCategoriesRoutesMt)
 export const adminGoodsCategoryApiRoutes = api.route('/api/v1/admin/goods-categories', adminGoodsCategoriesRoutes)
 export const adminGoodsApiRoutes = api.route('/api/v1/admin/goods', adminGoodsRoutes)
-export const goodsApiRoutes = api.route('/api/v1/goods', publicGoodsRoutesMt)
+export const goodsApiRoutes = api.route('/api/v1/goods', publicGoodsRoutesMt).route('/api/v1/goods', publicGoodsChildrenRoutesMt)
 export const merchantApiRoutes = api.route('/api/v1/merchants', merchantRoutes)
 export const orderApiRoutes = api.route('/api/v1/orders', userOrderRoutes)
 // export const orderGoodsApiRoutes = api.route('/api/v1/orders-goods', userOrderItemsRoutes)