generic-crud.routes.ts 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349
  1. import { createRoute, OpenAPIHono } from '@hono/zod-openapi';
  2. import { z } from '@hono/zod-openapi';
  3. import { GenericCrudService, CrudOptions } from './generic-crud.service';
  4. import { ErrorSchema } from './errorHandler';
  5. import { AuthContext } from '../types/context';
  6. import { ObjectLiteral } from 'typeorm';
  7. import { AppDataSource } from '../data-source';
  8. export function createCrudRoutes<
  9. T extends ObjectLiteral,
  10. CreateSchema extends z.ZodSchema = z.ZodSchema,
  11. UpdateSchema extends z.ZodSchema = z.ZodSchema,
  12. GetSchema extends z.ZodSchema = z.ZodSchema,
  13. ListSchema extends z.ZodSchema = z.ZodSchema
  14. >(options: CrudOptions<T, CreateSchema, UpdateSchema, GetSchema, ListSchema>) {
  15. const { entity, createSchema, updateSchema, getSchema, listSchema, searchFields, relations, middleware = [], userTracking, relationFields } = options;
  16. // 创建CRUD服务实例
  17. // 抽象类不能直接实例化,需要创建具体实现类
  18. class ConcreteCrudService extends GenericCrudService<T> {
  19. constructor() {
  20. super(AppDataSource, entity, { userTracking, relationFields });
  21. }
  22. }
  23. const crudService = new ConcreteCrudService();
  24. // 创建路由实例
  25. const app = new OpenAPIHono<AuthContext>();
  26. // 分页查询路由
  27. const listRoute = createRoute({
  28. method: 'get',
  29. path: '/',
  30. middleware,
  31. request: {
  32. query: z.object({
  33. page: z.coerce.number().int().positive().default(1).openapi({
  34. example: 1,
  35. description: '页码,从1开始'
  36. }),
  37. pageSize: z.coerce.number().int().positive().default(10).openapi({
  38. example: 10,
  39. description: '每页数量'
  40. }),
  41. keyword: z.string().optional().openapi({
  42. example: '搜索关键词',
  43. description: '搜索关键词'
  44. }),
  45. sortBy: z.string().optional().openapi({
  46. example: 'createdAt',
  47. description: '排序字段'
  48. }),
  49. sortOrder: z.enum(['ASC', 'DESC']).optional().default('DESC').openapi({
  50. example: 'DESC',
  51. description: '排序方向'
  52. }),
  53. // 增强的筛选参数
  54. filters: z.string().optional().openapi({
  55. example: '{"status": 1, "createdAt": {"gte": "2024-01-01", "lte": "2024-12-31"}}',
  56. description: '筛选条件(JSON字符串),支持精确匹配、范围查询、IN查询等'
  57. })
  58. })
  59. },
  60. responses: {
  61. 200: {
  62. description: '成功获取列表',
  63. content: {
  64. 'application/json': {
  65. schema: z.object({
  66. data: z.array(listSchema),
  67. pagination: z.object({
  68. total: z.number().openapi({ example: 100, description: '总记录数' }),
  69. current: z.number().openapi({ example: 1, description: '当前页码' }),
  70. pageSize: z.number().openapi({ example: 10, description: '每页数量' })
  71. })
  72. })
  73. }
  74. }
  75. },
  76. 400: {
  77. description: '参数错误',
  78. content: { 'application/json': { schema: ErrorSchema } }
  79. },
  80. 500: {
  81. description: '服务器错误',
  82. content: { 'application/json': { schema: ErrorSchema } }
  83. }
  84. }
  85. });
  86. // 创建资源路由
  87. const createRouteDef = createRoute({
  88. method: 'post',
  89. path: '/',
  90. middleware,
  91. request: {
  92. body: {
  93. content: {
  94. 'application/json': { schema: createSchema }
  95. }
  96. }
  97. },
  98. responses: {
  99. 201: {
  100. description: '创建成功',
  101. content: { 'application/json': { schema: getSchema } }
  102. },
  103. 400: {
  104. description: '输入数据无效',
  105. content: { 'application/json': { schema: ErrorSchema } }
  106. },
  107. 500: {
  108. description: '服务器错误',
  109. content: { 'application/json': { schema: ErrorSchema } }
  110. }
  111. }
  112. });
  113. // 获取单个资源路由
  114. const getRouteDef = createRoute({
  115. method: 'get',
  116. path: '/{id}',
  117. middleware,
  118. request: {
  119. params: z.object({
  120. id: z.coerce.number().openapi({
  121. param: { name: 'id', in: 'path' },
  122. example: 1,
  123. description: '资源ID'
  124. })
  125. })
  126. },
  127. responses: {
  128. 200: {
  129. description: '成功获取详情',
  130. content: { 'application/json': { schema: getSchema } }
  131. },
  132. 400: {
  133. description: '资源不存在',
  134. content: { 'application/json': { schema: ErrorSchema } }
  135. },
  136. 404: {
  137. description: '参数验证失败',
  138. content: { 'application/json': { schema: ErrorSchema } }
  139. },
  140. 500: {
  141. description: '服务器错误',
  142. content: { 'application/json': { schema: ErrorSchema } }
  143. }
  144. }
  145. });
  146. // 更新资源路由
  147. const updateRouteDef = createRoute({
  148. method: 'put',
  149. path: '/{id}',
  150. middleware,
  151. request: {
  152. params: z.object({
  153. id: z.coerce.number().openapi({
  154. param: { name: 'id', in: 'path' },
  155. example: 1,
  156. description: '资源ID'
  157. })
  158. }),
  159. body: {
  160. content: {
  161. 'application/json': { schema: updateSchema }
  162. }
  163. }
  164. },
  165. responses: {
  166. 200: {
  167. description: '更新成功',
  168. content: { 'application/json': { schema: getSchema } }
  169. },
  170. 400: {
  171. description: '无效输入',
  172. content: { 'application/json': { schema: ErrorSchema } }
  173. },
  174. 404: {
  175. description: '资源不存在',
  176. content: { 'application/json': { schema: ErrorSchema } }
  177. },
  178. 500: {
  179. description: '服务器错误',
  180. content: { 'application/json': { schema: ErrorSchema } }
  181. }
  182. }
  183. });
  184. // 删除资源路由
  185. const deleteRouteDef = createRoute({
  186. method: 'delete',
  187. path: '/{id}',
  188. middleware,
  189. request: {
  190. params: z.object({
  191. id: z.coerce.number().openapi({
  192. param: { name: 'id', in: 'path' },
  193. example: 1,
  194. description: '资源ID'
  195. })
  196. })
  197. },
  198. responses: {
  199. 204: { description: '删除成功' },
  200. 404: {
  201. description: '资源不存在',
  202. content: { 'application/json': { schema: ErrorSchema } }
  203. },
  204. 500: {
  205. description: '服务器错误',
  206. content: { 'application/json': { schema: ErrorSchema } }
  207. }
  208. }
  209. });
  210. // 注册路由处理函数
  211. const routes = app
  212. .openapi(listRoute, async (c) => {
  213. try {
  214. const query = c.req.valid('query') as any;
  215. const { page, pageSize, keyword, sortBy, sortOrder, filters } = query;
  216. // 构建排序对象
  217. const order: any = {};
  218. if (sortBy) {
  219. order[sortBy] = sortOrder || 'DESC';
  220. } else {
  221. order['id'] = 'DESC';
  222. }
  223. // 解析筛选条件
  224. let parsedFilters: any = undefined;
  225. if (filters) {
  226. try {
  227. parsedFilters = JSON.parse(filters);
  228. } catch (e) {
  229. return c.json({ code: 400, message: '筛选条件格式错误' }, 400);
  230. }
  231. }
  232. const [data, total] = await crudService.getList(
  233. page,
  234. pageSize,
  235. keyword,
  236. searchFields,
  237. undefined,
  238. relations || [],
  239. order,
  240. parsedFilters
  241. );
  242. return c.json({
  243. data: z.array(listSchema).parse(data),
  244. pagination: { total, current: page, pageSize }
  245. }, 200);
  246. } catch (error) {
  247. if (error instanceof z.ZodError) {
  248. return c.json({ code: 400, message: '参数验证失败', errors: error.errors }, 400);
  249. }
  250. return c.json({
  251. code: 500,
  252. message: error instanceof Error ? error.message : '获取列表失败'
  253. }, 500);
  254. }
  255. })
  256. .openapi(createRouteDef, async (c: any) => {
  257. try {
  258. const data = c.req.valid('json');
  259. const user = c.get('user');
  260. const result = await crudService.create(data, user?.id);
  261. return c.json(result, 201);
  262. } catch (error) {
  263. if (error instanceof z.ZodError) {
  264. return c.json({ code: 400, message: '参数验证失败', errors: error.errors }, 400);
  265. }
  266. return c.json({
  267. code: 500,
  268. message: error instanceof Error ? error.message : '创建资源失败'
  269. }, 500);
  270. }
  271. })
  272. .openapi(getRouteDef, async (c: any) => {
  273. try {
  274. const { id } = c.req.valid('param');
  275. const result = await crudService.getById(id, relations || []);
  276. if (!result) {
  277. return c.json({ code: 404, message: '资源不存在' }, 404);
  278. }
  279. return c.json(getSchema.parse(result), 200);
  280. } catch (error) {
  281. if (error instanceof z.ZodError) {
  282. return c.json({ code: 400, message: '参数验证失败', errors: error.errors }, 400);
  283. }
  284. return c.json({
  285. code: 500,
  286. message: error instanceof Error ? error.message : '获取资源失败'
  287. }, 500);
  288. }
  289. })
  290. .openapi(updateRouteDef, async (c: any) => {
  291. try {
  292. const { id } = c.req.valid('param');
  293. const data = c.req.valid('json');
  294. const user = c.get('user');
  295. const result = await crudService.update(id, data, user?.id);
  296. if (!result) {
  297. return c.json({ code: 404, message: '资源不存在' }, 404);
  298. }
  299. return c.json(result, 200);
  300. } catch (error) {
  301. if (error instanceof z.ZodError) {
  302. return c.json({ code: 400, message: '参数验证失败', errors: error.errors }, 400);
  303. }
  304. return c.json({
  305. code: 500,
  306. message: error instanceof Error ? error.message : '更新资源失败'
  307. }, 500);
  308. }
  309. })
  310. .openapi(deleteRouteDef, async (c: any) => {
  311. try {
  312. const { id } = c.req.valid('param');
  313. const success = await crudService.delete(id);
  314. if (!success) {
  315. return c.json({ code: 404, message: '资源不存在' }, 404);
  316. }
  317. return c.body(null, 204);
  318. } catch (error) {
  319. if (error instanceof z.ZodError) {
  320. return c.json({ code: 400, message: '参数验证失败', errors: error.errors }, 400);
  321. }
  322. return c.json({
  323. code: 500,
  324. message: error instanceof Error ? error.message : '删除资源失败'
  325. }, 500);
  326. }
  327. });
  328. return routes;
  329. }