Bläddra i källkod

✨ feat(goods): add random goods list API

- 创建随机商品列表路由(/random),支持按分类筛选
- 实现随机商品查询功能,包括数量限制和关联数据查询
- 重构商品路由结构,采用OpenAPIHono聚合基础CRUD和扩展路由
- 添加随机商品查询参数和响应数据验证Schema
yourname 3 månader sedan
förälder
incheckning
ddb600c0e4

+ 9 - 1
src/server/api/goods/index.ts

@@ -1,9 +1,12 @@
 import { createCrudRoutes } from '@/server/utils/generic-crud.routes';
+import { OpenAPIHono } from '@hono/zod-openapi';
 import { Goods } from '@/server/modules/goods/goods.entity';
 import { GoodsSchema, CreateGoodsDto, UpdateGoodsDto } from '@/server/modules/goods/goods.schema';
 import { authMiddleware } from '@/server/middleware/auth.middleware';
 import { File } from '@/server/modules/files/file.entity';
+import randomRoute from './random';
 
+// 创建基础CRUD路由
 const goodsRoutes = createCrudRoutes({
   entity: Goods,
   createSchema: CreateGoodsDto,
@@ -26,4 +29,9 @@ const goodsRoutes = createCrudRoutes({
   }
 });
 
-export default goodsRoutes;
+// 聚合基础CRUD路由和扩展路由
+const app = new OpenAPIHono()
+  .route('/random', randomRoute)  // 随机商品列表路由
+  .route('/', goodsRoutes)          // 基础CRUD路由
+
+export default app;

+ 113 - 0
src/server/api/goods/random.ts

@@ -0,0 +1,113 @@
+import { createRoute, OpenAPIHono } from '@hono/zod-openapi';
+import { z } from '@hono/zod-openapi';
+import { GoodsSchema } from '@/server/modules/goods/goods.schema';
+import { ErrorSchema } from '@/server/utils/errorHandler';
+import { AppDataSource } from '@/server/data-source';
+import { Goods } from '@/server/modules/goods/goods.entity';
+import { AuthContext } from '@/server/types/context';
+import { authMiddleware } from '@/server/middleware/auth.middleware';
+import { RandomGoodsQuerySchema, RandomGoodsResponseSchema } from '@/server/modules/goods/schemas/random.schema';
+import { parseWithAwait } from '@/server/utils/parseWithAwait';
+
+// 定义随机商品列表路由
+const routeDef = createRoute({
+  method: 'get',
+  path: '/',
+  middleware: [authMiddleware],
+  request: {
+    query: RandomGoodsQuerySchema
+  },
+  responses: {
+    200: {
+      description: '成功获取随机商品列表',
+      content: {
+        'application/json': {
+          schema: RandomGoodsResponseSchema
+        }
+      }
+    },
+    400: {
+      description: '请求参数错误',
+      content: {
+        'application/json': {
+          schema: ErrorSchema
+        }
+      }
+    },
+    500: {
+      description: '服务器错误',
+      content: {
+        'application/json': {
+          schema: ErrorSchema
+        }
+      }
+    }
+  }
+});
+
+// 路由实现
+const app = new OpenAPIHono<AuthContext>().openapi(routeDef, async (c) => {
+  try {
+    const query = c.req.valid('query');
+    const { limit, categoryId, includeImages } = query;
+
+    // 创建查询构建器
+    const queryBuilder = AppDataSource.getRepository(Goods)
+      .createQueryBuilder('goods')
+      .where('goods.state = :state', { state: 1 }) // 只获取可用的商品
+      .orderBy('RAND()') // 使用随机排序
+      .limit(limit);
+
+    // 如果指定了分类ID,添加分类过滤
+    if (categoryId) {
+      queryBuilder.andWhere(
+        'goods.category_id1 = :categoryId OR goods.category_id2 = :categoryId OR goods.category_id3 = :categoryId',
+        { categoryId }
+      );
+    }
+
+    // 如果需要包含关联数据
+    if (includeImages) {
+      queryBuilder
+        .leftJoinAndSelect('goods.imageFile', 'imageFile')
+        .leftJoinAndSelect('imageFile.uploadUser', 'imageUploadUser')
+        .leftJoinAndSelect('goods.category1', 'category1')
+        .leftJoinAndSelect('goods.category2', 'category2')
+        .leftJoinAndSelect('goods.category3', 'category3')
+        .leftJoinAndSelect('goods.supplier', 'supplier');
+    }
+
+    // 获取随机商品
+    const goods = await queryBuilder.getMany();
+
+    // 获取总数(用于分页参考)
+    const totalQuery = AppDataSource.getRepository(Goods)
+      .createQueryBuilder('goods')
+      .where('goods.state = :state', { state: 1 });
+
+    if (categoryId) {
+      totalQuery.andWhere(
+        'goods.category_id1 = :categoryId OR goods.category_id2 = :categoryId OR goods.category_id3 = :categoryId',
+        { categoryId }
+      );
+    }
+
+    const total = await totalQuery.getCount();
+
+    // 使用 parseWithAwait 确保数据格式正确
+    const validatedGoods = await parseWithAwait(z.array(GoodsSchema), goods);
+
+    return c.json({
+      data: validatedGoods,
+      total
+    }, 200);
+  } catch (error) {
+    console.error('获取随机商品列表失败:', error);
+    return c.json({
+      code: 500,
+      message: error instanceof Error ? error.message : '获取随机商品列表失败'
+    }, 500);
+  }
+});
+
+export default app;

+ 59 - 0
src/server/modules/goods/schemas/random.schema.ts

@@ -0,0 +1,59 @@
+import { z } from '@hono/zod-openapi';
+import { GoodsSchema } from '@/server/modules/goods/goods.schema';
+
+// 随机商品列表查询参数Schema
+export const RandomGoodsQuerySchema = z.object({
+  limit: z.coerce.number().int().positive().min(1).max(50).default(10).openapi({
+    description: '返回商品数量限制',
+    example: 10
+  }),
+  categoryId: z.coerce.number().int().positive().optional().openapi({
+    description: '指定商品分类ID',
+    example: 1
+  }),
+  includeImages: z.coerce.boolean().default(true).openapi({
+    description: '是否包含商品图片',
+    example: true
+  })
+});
+
+// 随机商品列表响应Schema
+export const RandomGoodsResponseSchema = z.object({
+  data: z.array(GoodsSchema).openapi({
+    description: '随机商品列表',
+    example: [{
+      id: 1,
+      name: 'iPhone 15',
+      price: 5999.99,
+      costPrice: 4999.99,
+      salesNum: 100,
+      clickNum: 1000,
+      categoryId1: 1,
+      categoryId2: 2,
+      categoryId3: 3,
+      goodsType: 1,
+      supplierId: 1,
+      imageFileId: 1,
+      detail: null,
+      instructions: '高品质智能手机',
+      sort: 0,
+      state: 1,
+      stock: 100,
+      spuId: 0,
+      spuName: null,
+      lowestBuy: 1,
+      createdAt: new Date('2024-01-01T12:00:00Z'),
+      updatedAt: new Date('2024-01-01T12:00:00Z'),
+      createdBy: 1,
+      updatedBy: 1
+    }]
+  }),
+  total: z.number().int().nonnegative().openapi({
+    description: '符合条件的商品总数',
+    example: 100
+  })
+});
+
+// 类型定义
+export type RandomGoodsQuery = z.infer<typeof RandomGoodsQuerySchema>;
+export type RandomGoodsResponse = z.infer<typeof RandomGoodsResponseSchema>;