|
@@ -309,7 +309,76 @@ export * from './channel-crud.routes';
|
|
|
|
|
|
|
|
**重要**: 自定义路由(非CRUD路由)在返回响应前**必须使用 `parseWithAwait`** 验证和转换数据。
|
|
**重要**: 自定义路由(非CRUD路由)在返回响应前**必须使用 `parseWithAwait`** 验证和转换数据。
|
|
|
|
|
|
|
|
-**实际实现**:
|
|
|
|
|
|
|
+#### 使用 createRoute 定义路由
|
|
|
|
|
+
|
|
|
|
|
+**完整示例**:
|
|
|
|
|
+```typescript
|
|
|
|
|
+import { createRoute, OpenAPIHono } from '@hono/zod-openapi';
|
|
|
|
|
+import { z } from '@hono/zod-openapi';
|
|
|
|
|
+import { parseWithAwait, createZodErrorResponse } from '@d8d/shared-utils';
|
|
|
|
|
+import { ErrorSchema, ZodErrorSchema } from '@d8d/shared-utils/schema/error';
|
|
|
|
|
+import { ChannelSchema, CreateChannelSchema } from '../schemas/channel.schema';
|
|
|
|
|
+
|
|
|
|
|
+// 定义路由(包含请求和响应Schema)
|
|
|
|
|
+const createChannelRoute = createRoute({
|
|
|
|
|
+ method: 'post',
|
|
|
|
|
+ path: '/create',
|
|
|
|
|
+ middleware: [authMiddleware],
|
|
|
|
|
+ request: {
|
|
|
|
|
+ body: {
|
|
|
|
|
+ content: {
|
|
|
|
|
+ 'application/json': { schema: CreateChannelSchema }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ responses: {
|
|
|
|
|
+ 200: {
|
|
|
|
|
+ description: '创建成功',
|
|
|
|
|
+ content: {
|
|
|
|
|
+ 'application/json': { schema: ChannelSchema }
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ // ✅ 400使用 ZodErrorSchema(因为使用 createZodErrorResponse)
|
|
|
|
|
+ 400: {
|
|
|
|
|
+ description: '参数错误',
|
|
|
|
|
+ content: { 'application/json': { schema: ZodErrorSchema } }
|
|
|
|
|
+ },
|
|
|
|
|
+ // ✅ 其他错误使用 ErrorSchema
|
|
|
|
|
+ 401: {
|
|
|
|
|
+ description: '认证失败',
|
|
|
|
|
+ content: { 'application/json': { schema: ErrorSchema } }
|
|
|
|
|
+ },
|
|
|
|
|
+ 500: {
|
|
|
|
|
+ description: '服务器错误',
|
|
|
|
|
+ content: { 'application/json': { schema: ErrorSchema } }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+});
|
|
|
|
|
+
|
|
|
|
|
+// 实现路由处理函数
|
|
|
|
|
+channelCustomRoutes.openapi(createChannelRoute, async (c) => {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const data = c.req.valid('json');
|
|
|
|
|
+ const channelService = new ChannelService(AppDataSource);
|
|
|
|
|
+ const result = await channelService.create(data);
|
|
|
|
|
+
|
|
|
|
|
+ // ✅ 必须:使用 parseWithAwait 验证和转换响应数据
|
|
|
|
|
+ const validatedResult = await parseWithAwait(ChannelSchema, result);
|
|
|
|
|
+ return c.json(validatedResult, 200);
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ if (error instanceof z.ZodError) {
|
|
|
|
|
+ // ✅ 推荐:使用 createZodErrorResponse 处理Zod验证错误
|
|
|
|
|
+ return c.json(createZodErrorResponse(error), 400);
|
|
|
|
|
+ }
|
|
|
|
|
+ return c.json({ code: 500, message: error.message }, 500);
|
|
|
|
|
+ }
|
|
|
|
|
+});
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+#### 不使用 createRoute 的简化写法
|
|
|
|
|
+
|
|
|
|
|
+如果路由不需要完整的 OpenAPI 文档定义,可以使用简化写法:
|
|
|
|
|
+
|
|
|
```typescript
|
|
```typescript
|
|
|
import { parseWithAwait, createZodErrorResponse } from '@d8d/shared-utils';
|
|
import { parseWithAwait, createZodErrorResponse } from '@d8d/shared-utils';
|
|
|
import { z } from '@hono/zod-openapi';
|
|
import { z } from '@hono/zod-openapi';
|
|
@@ -335,7 +404,8 @@ channelCustomRoutes.get('/statistics/:id', async (c) => {
|
|
|
});
|
|
});
|
|
|
```
|
|
```
|
|
|
|
|
|
|
|
-**数组响应处理**:
|
|
|
|
|
|
|
+#### 数组响应处理
|
|
|
|
|
+
|
|
|
```typescript
|
|
```typescript
|
|
|
// 处理数组数据
|
|
// 处理数组数据
|
|
|
const validatedData = await Promise.all(
|
|
const validatedData = await Promise.all(
|
|
@@ -344,10 +414,16 @@ const validatedData = await Promise.all(
|
|
|
return c.json({ data: validatedData, total: result.total }, 200);
|
|
return c.json({ data: validatedData, total: result.total }, 200);
|
|
|
```
|
|
```
|
|
|
|
|
|
|
|
-**关键要点**:
|
|
|
|
|
-- **必须使用 `parseWithAwait`**: 所有自定义路由返回前必须使用 `parseWithAwait` 验证数据
|
|
|
|
|
|
|
+#### 关键要点
|
|
|
|
|
+
|
|
|
|
|
+**响应Schema定义**:
|
|
|
|
|
+- **400响应使用 `ZodErrorSchema`**: 因为使用 `createZodErrorResponse` 返回详细的验证错误
|
|
|
|
|
+- **其他错误使用 `ErrorSchema`**: 401, 404, 500 等使用简单的错误格式
|
|
|
|
|
+
|
|
|
|
|
+**代码实现**:
|
|
|
|
|
+- **必须使用 `parseWithAwait`**: 所有自定义路由返回前必须验证数据
|
|
|
- **捕获 ZodError**: 在catch块中处理 `z.ZodError` 异常
|
|
- **捕获 ZodError**: 在catch块中处理 `z.ZodError` 异常
|
|
|
-- **使用 `createZodErrorResponse`**: 提供统一的错误响应格式
|
|
|
|
|
|
|
+- **使用 `createZodErrorResponse`**: 提供统一的Zod错误响应格式
|
|
|
- **数组使用 `Promise.all`**: 批量验证数组数据
|
|
- **数组使用 `Promise.all`**: 批量验证数组数据
|
|
|
|
|
|
|
|
### 4.4 关键要点
|
|
### 4.4 关键要点
|
|
@@ -357,6 +433,8 @@ return c.json({ data: validatedData, total: result.total }, 200);
|
|
|
- **路由聚合**: 分别定义自定义路由和CRUD路由,然后聚合
|
|
- **路由聚合**: 分别定义自定义路由和CRUD路由,然后聚合
|
|
|
- **不设置 `basePath`**: 在聚合路由时处理路径
|
|
- **不设置 `basePath`**: 在聚合路由时处理路径
|
|
|
- **自定义路由必须使用 `parseWithAwait`**: 验证响应数据符合Schema定义
|
|
- **自定义路由必须使用 `parseWithAwait`**: 验证响应数据符合Schema定义
|
|
|
|
|
+- **400响应使用 `ZodErrorSchema`**: 因为使用 `createZodErrorResponse` 返回详细验证错误
|
|
|
|
|
+- **其他错误使用 `ErrorSchema`**: 401, 404, 500 等使用简单错误格式
|
|
|
|
|
|
|
|
## 5. Schema规范
|
|
## 5. Schema规范
|
|
|
|
|
|
|
@@ -741,6 +819,7 @@ override async create(data: Partial<Channel>, userId?: string | number): Promise
|
|
|
- [ ] 路由使用 `OpenAPIHono` 和 `AuthContext`
|
|
- [ ] 路由使用 `OpenAPIHono` 和 `AuthContext`
|
|
|
- [ ] 自定义路由使用 `parseWithAwait` 验证响应数据
|
|
- [ ] 自定义路由使用 `parseWithAwait` 验证响应数据
|
|
|
- [ ] 自定义路由使用 `createZodErrorResponse` 处理Zod错误
|
|
- [ ] 自定义路由使用 `createZodErrorResponse` 处理Zod错误
|
|
|
|
|
+- [ ] 路由400响应使用 `ZodErrorSchema`,其他错误使用 `ErrorSchema`
|
|
|
- [ ] 测试数据工厂使用时间戳保证唯一性
|
|
- [ ] 测试数据工厂使用时间戳保证唯一性
|
|
|
- [ ] vitest.config.ts 设置 `fileParallelism: false`
|
|
- [ ] vitest.config.ts 设置 `fileParallelism: false`
|
|
|
- [ ] 类型检查通过
|
|
- [ ] 类型检查通过
|