| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458 |
- 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<T, CreateSchema, UpdateSchema, GetSchema, ListSchema>) {
- const { entity, createSchema, updateSchema, getSchema, listSchema, searchFields, relations, middleware = [], userTracking, relationFields, readOnly = false } = options;
-
- // 创建路由实例
- const app = new OpenAPIHono<AuthContext>();
-
- // 分页查询路由
- const listRoute = createRoute({
- method: 'get',
- path: '/',
- middleware,
- request: {
- query: z.object({
- page: z.coerce.number<number>().int().positive().default(1).openapi({
- example: 1,
- description: '页码,从1开始'
- }),
- pageSize: z.coerce.number<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<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<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<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;
- }
-
- }
|