import { createRoute, OpenAPIHono } from '@hono/zod-openapi'; import { z } from '@hono/zod-openapi'; import { CrudOptions } from './generic-crud.service'; import { ErrorSchema } from './errorHandler'; import { AuthContext } from '../types/context'; import { ObjectLiteral } from 'typeorm'; import { parseWithAwait } from './parseWithAwait'; import { ConcreteCrudService } from './concrete-crud.service'; export function createCrudRoutes< T extends ObjectLiteral, CreateSchema extends z.ZodSchema = z.ZodSchema, UpdateSchema extends z.ZodSchema = z.ZodSchema, GetSchema extends z.ZodSchema = z.ZodSchema, ListSchema extends z.ZodSchema = z.ZodSchema >(options: CrudOptions) { const { entity, createSchema, updateSchema, getSchema, listSchema, searchFields, relations, middleware = [], userTracking, relationFields, readOnly = false } = options; // 创建路由实例 const app = new OpenAPIHono(); // 分页查询路由 const listRoute = createRoute({ method: 'get', path: '/', middleware, request: { query: z.object({ page: z.coerce.number().int().positive().default(1).openapi({ example: 1, description: '页码,从1开始' }), pageSize: z.coerce.number().int().positive().default(10).openapi({ example: 10, description: '每页数量' }), keyword: z.string().optional().openapi({ example: '搜索关键词', description: '搜索关键词' }), sortBy: z.string().optional().openapi({ example: 'createdAt', description: '排序字段' }), sortOrder: z.enum(['ASC', 'DESC']).optional().default('DESC').openapi({ example: 'DESC', description: '排序方向' }), // 增强的筛选参数 filters: z.string().optional().openapi({ example: '{"status": 1, "createdAt": {"gte": "2024-01-01", "lte": "2024-12-31"}}', description: '筛选条件(JSON字符串),支持精确匹配、范围查询、IN查询等' }) }) }, responses: { 200: { description: '成功获取列表', content: { 'application/json': { schema: z.object({ data: z.array(listSchema), pagination: z.object({ total: z.number().openapi({ example: 100, description: '总记录数' }), current: z.number().openapi({ example: 1, description: '当前页码' }), pageSize: z.number().openapi({ example: 10, description: '每页数量' }) }) }) } } }, 400: { description: '参数错误', content: { 'application/json': { schema: ErrorSchema } } }, 500: { description: '服务器错误', content: { 'application/json': { schema: ErrorSchema } } } } }); // 创建资源路由 const createRouteDef = createRoute({ method: 'post', path: '/', middleware, request: { body: { content: { 'application/json': { schema: createSchema } } } }, responses: { 201: { description: '创建成功', content: { 'application/json': { schema: getSchema } } }, 400: { description: '输入数据无效', content: { 'application/json': { schema: ErrorSchema } } }, 500: { description: '服务器错误', content: { 'application/json': { schema: ErrorSchema } } } } }); // 获取单个资源路由 const getRouteDef = createRoute({ method: 'get', path: '/{id}', middleware, request: { params: z.object({ id: z.coerce.number().openapi({ param: { name: 'id', in: 'path' }, example: 1, description: '资源ID' }) }) }, responses: { 200: { description: '成功获取详情', content: { 'application/json': { schema: getSchema } } }, 400: { description: '资源不存在', content: { 'application/json': { schema: ErrorSchema } } }, 404: { description: '参数验证失败', content: { 'application/json': { schema: ErrorSchema } } }, 500: { description: '服务器错误', content: { 'application/json': { schema: ErrorSchema } } } } }); // 更新资源路由 const updateRouteDef = createRoute({ method: 'put', path: '/{id}', middleware, request: { params: z.object({ id: z.coerce.number().openapi({ param: { name: 'id', in: 'path' }, example: 1, description: '资源ID' }) }), body: { content: { 'application/json': { schema: updateSchema } } } }, responses: { 200: { description: '更新成功', content: { 'application/json': { schema: getSchema } } }, 400: { description: '无效输入', content: { 'application/json': { schema: ErrorSchema } } }, 404: { description: '资源不存在', content: { 'application/json': { schema: ErrorSchema } } }, 500: { description: '服务器错误', content: { 'application/json': { schema: ErrorSchema } } } } }); // 删除资源路由 const deleteRouteDef = createRoute({ method: 'delete', path: '/{id}', middleware, request: { params: z.object({ id: z.coerce.number().openapi({ param: { name: 'id', in: 'path' }, example: 1, description: '资源ID' }) }) }, responses: { 204: { description: '删除成功' }, 404: { description: '资源不存在', content: { 'application/json': { schema: ErrorSchema } } }, 500: { description: '服务器错误', content: { 'application/json': { schema: ErrorSchema } } } } }); // 注册路由处理函数 // 只读模式下只注册 GET 路由 if (!readOnly) { // 完整 CRUD 路由 const routes = app .openapi(listRoute, async (c) => { try { const query = c.req.valid('query') as any; const { page, pageSize, keyword, sortBy, sortOrder, filters } = query; // 构建排序对象 const order: any = {}; if (sortBy) { order[sortBy] = sortOrder || 'DESC'; } else { order['id'] = 'DESC'; } // 解析筛选条件 let parsedFilters: any = undefined; if (filters) { try { parsedFilters = JSON.parse(filters); } catch (e) { return c.json({ code: 400, message: '筛选条件格式错误' }, 400); } } const crudService = new ConcreteCrudService(entity, { userTracking: userTracking, relationFields: relationFields }); const [data, total] = await crudService.getList( page, pageSize, keyword, searchFields, undefined, relations || [], order, parsedFilters ); return c.json({ // data: z.array(listSchema).parse(data), data: await parseWithAwait(z.array(listSchema), data), pagination: { total, current: page, pageSize } }, 200); } catch (error) { if (error instanceof z.ZodError) { return c.json({ code: 400, message: '参数验证失败', errors: JSON.parse(error.message) }, 400); } return c.json({ code: 500, message: error instanceof Error ? error.message : '获取列表失败' }, 500); } }) // @ts-ignore .openapi(createRouteDef, async (c: any) => { try { const data = c.req.valid('json'); const user = c.get('user'); const crudService = new ConcreteCrudService(entity, { userTracking: userTracking, relationFields: relationFields }); const result = await crudService.create(data, user?.id); return c.json(result, 201); } catch (error) { if (error instanceof z.ZodError) { return c.json({ code: 400, message: '参数验证失败', errors: JSON.parse(error.message) }, 400); } return c.json({ code: 500, message: error instanceof Error ? error.message : '创建资源失败' }, 500); } }) // @ts-ignore .openapi(getRouteDef, async (c: any) => { try { const { id } = c.req.valid('param'); const crudService = new ConcreteCrudService(entity, { userTracking: userTracking, relationFields: relationFields }); const result = await crudService.getById(id, relations || []); if (!result) { return c.json({ code: 404, message: '资源不存在' }, 404); } // return c.json(await getSchema.parseAsync(result), 200); return c.json(await parseWithAwait(getSchema, result), 200); } catch (error) { if (error instanceof z.ZodError) { return c.json({ code: 400, message: '参数验证失败', errors: JSON.parse(error.message) }, 400); } return c.json({ code: 500, message: error instanceof Error ? error.message : '获取资源失败' }, 500); } }) // @ts-ignore .openapi(updateRouteDef, async (c: any) => { try { const { id } = c.req.valid('param'); const data = c.req.valid('json'); const user = c.get('user'); const crudService = new ConcreteCrudService(entity, { userTracking: userTracking, relationFields: relationFields }); const result = await crudService.update(id, data, user?.id); if (!result) { return c.json({ code: 404, message: '资源不存在' }, 404); } return c.json(result, 200); } catch (error) { if (error instanceof z.ZodError) { return c.json({ code: 400, message: '参数验证失败', errors: JSON.parse(error.message) }, 400); } return c.json({ code: 500, message: error instanceof Error ? error.message : '更新资源失败' }, 500); } }) .openapi(deleteRouteDef, async (c: any) => { try { const { id } = c.req.valid('param'); const crudService = new ConcreteCrudService(entity, { userTracking: userTracking, relationFields: relationFields }); const success = await crudService.delete(id); if (!success) { return c.json({ code: 404, message: '资源不存在' }, 404); } return c.body(null, 204); } catch (error) { if (error instanceof z.ZodError) { return c.json({ code: 400, message: '参数验证失败', errors: JSON.parse(error.message) }, 400); } return c.json({ code: 500, message: error instanceof Error ? error.message : '删除资源失败' }, 500); } }); return routes; } else { // 只读模式,只注册 GET 路由 const routes = app .openapi(listRoute, async (c) => { try { const query = c.req.valid('query') as any; const { page, pageSize, keyword, sortBy, sortOrder, filters } = query; // 构建排序对象 const order: any = {}; if (sortBy) { order[sortBy] = sortOrder || 'DESC'; } else { order['id'] = 'DESC'; } // 解析筛选条件 let parsedFilters: any = undefined; if (filters) { try { parsedFilters = JSON.parse(filters); } catch (e) { return c.json({ code: 400, message: '筛选条件格式错误' }, 400); } } const crudService = new ConcreteCrudService(entity, { userTracking: userTracking, relationFields: relationFields }); const [data, total] = await crudService.getList( page, pageSize, keyword, searchFields, undefined, relations || [], order, parsedFilters ); return c.json({ data: await parseWithAwait(z.array(listSchema), data), pagination: { total, current: page, pageSize } }, 200); } catch (error) { if (error instanceof z.ZodError) { return c.json({ code: 400, message: '参数验证失败', errors: JSON.parse(error.message) }, 400); } return c.json({ code: 500, message: error instanceof Error ? error.message : '获取列表失败' }, 500); } }) // @ts-ignore .openapi(getRouteDef, async (c: any) => { try { const { id } = c.req.valid('param'); const crudService = new ConcreteCrudService(entity, { userTracking: userTracking, relationFields: relationFields }); const result = await crudService.getById(id, relations || []); if (!result) { return c.json({ code: 404, message: '资源不存在' }, 404); } return c.json(await parseWithAwait(getSchema, result), 200); } catch (error) { if (error instanceof z.ZodError) { return c.json({ code: 400, message: '参数验证失败', errors: JSON.parse(error.message) }, 400); } return c.json({ code: 500, message: error instanceof Error ? error.message : '获取资源失败' }, 500); } }); return routes; } }