2
0
Просмотр исходного кода

✨ feat(api): 添加完整的用户管理API系统

- 集成TypeORM数据库连接和实体定义
- 添加用户、角色、认证相关API路由
- 实现JWT认证和权限控制中间件
- 提供CRUD通用工具类和服务
- 配置Swagger文档和错误处理
- 支持分页、搜索和筛选功能
yourname 7 месяцев назад
Родитель
Сommit
2ecb84da91

+ 4 - 1
package.json

@@ -16,6 +16,7 @@
     "@heroicons/react": "^2.2.0",
     "@hono/node-server": "^1.17.1",
     "@hono/react-renderer": "^1.0.1",
+    "@hono/swagger-ui": "^0.5.2",
     "@hono/zod-openapi": "^1.0.2",
     "@tanstack/react-query": "^5.83.0",
     "antd": "^5.26.6",
@@ -29,7 +30,9 @@
     "react-hook-form": "^7.61.1",
     "react-router": "^7.7.0",
     "react-router-dom": "^7.7.0",
-    "sirv": "^3.0.1"
+    "reflect-metadata": "^0.2.2",
+    "sirv": "^3.0.1",
+    "typeorm": "^0.3.25"
   },
   "devDependencies": {
     "@tailwindcss/vite": "^4.1.11",

Разница между файлами не показана из-за своего большого размера
+ 499 - 0
pnpm-lock.yaml


+ 58 - 7
src/server/api.ts

@@ -1,10 +1,61 @@
-import { OpenAPIHono } from '@hono/zod-openapi';
-import helloRoute from './api/hello/index';
+import { OpenAPIHono } from '@hono/zod-openapi'
+import { errorHandler } from './utils/errorHandler'
+import usersRouter from './api/users/index'
+import authRoute from './api/auth/index'
+import rolesRoute from './api/roles/index'
+import { AuthContext } from './types/context'
+import { AppDataSource } from './data-source'
 
-// 创建主API路由器
-const api = new OpenAPIHono();
+const api = new OpenAPIHono<AuthContext>()
 
-// 注册hello路由
-api.route('/api/v1/hello', helloRoute);
+api.onError(errorHandler)
 
-export default api;
+// Rate limiting
+api.use('/api/v1/*', async (c, next) => {
+  const ip = c.req.header('x-forwarded-for') || c.req.header('cf-connecting-ip')
+  // 实现速率限制逻辑
+  await next()
+})
+
+// 数据库初始化中间件
+api.use('/api/v1/*', async (c, next) => {
+  if(!AppDataSource.isInitialized) {
+    await AppDataSource.initialize();
+  }
+  await next();
+})
+
+// 注册Bearer认证方案
+api.openAPIRegistry.registerComponent('securitySchemes','bearerAuth',{
+  type:'http',
+  scheme:'bearer',
+  bearerFormat:'JWT',
+  description:'使用JWT进行认证'
+})
+
+// OpenAPI documentation endpoint
+if(!import.meta.env.PROD){
+  api.doc31('/doc', {
+    openapi: '3.1.0',
+    info: {
+      title: 'API Documentation',
+      version: '1.0.0'
+    },
+    security: [{
+      bearerAuth: []
+    }]
+    // servers: [{ url: '/api/v1' }]
+  })
+}
+
+
+
+const userRoutes = api.route('/api/v1/users', usersRouter)
+const authRoutes = api.route('/api/v1/auth', authRoute)
+const roleRoutes = api.route('/api/v1/roles', rolesRoute)
+
+export type AuthRoutes = typeof authRoutes
+export type UserRoutes = typeof userRoutes
+export type RoleRoutes = typeof roleRoutes
+
+export default api

+ 15 - 0
src/server/api/auth/index.ts

@@ -0,0 +1,15 @@
+import { OpenAPIHono } from '@hono/zod-openapi';
+import loginRoute from './login/password';
+import logoutRoute from './logout';
+import meRoute from './me/get';
+import registerRoute from './register/create';
+import ssoVerify from './sso-verify';
+
+const app = new OpenAPIHono()
+  .route('/', loginRoute)
+  .route('/', logoutRoute)
+  .route('/', meRoute)
+  .route('/', registerRoute)
+  .route('/', ssoVerify);
+
+export default app;

+ 71 - 0
src/server/api/auth/login/password.ts

@@ -0,0 +1,71 @@
+import { createRoute, OpenAPIHono } from '@hono/zod-openapi'
+import { AuthService } from '../../../modules/auth/auth.service'
+import { UserService } from '../../../modules/users/user.service'
+import { z } from '@hono/zod-openapi'
+import { ErrorSchema } from '../../../utils/errorHandler'
+import { AppDataSource } from '../../../data-source'
+import { AuthContext } from '../../../types/context'
+import { UserSchema } from '@/server/modules/users/user.entity'
+
+const userService = new UserService(AppDataSource)
+const authService = new AuthService(userService)
+
+const LoginSchema = z.object({
+  username: z.string().min(3).openapi({
+    example: 'admin',
+    description: '用户名'
+  }),
+  password: z.string().min(6).openapi({
+    example: 'admin123',
+    description: '密码'
+  })
+})
+
+const UserResponseSchema = UserSchema
+
+const TokenResponseSchema = z.object({
+  token: z.string().openapi({
+    example: 'jwt.token.here',
+    description: 'JWT Token'
+  }),
+  user: UserResponseSchema
+})
+
+const loginRoute = createRoute({
+  method: 'post',
+  path: '/login',
+  request: {
+    body: {
+      content: {
+        'application/json': {
+          schema: LoginSchema
+        }
+      }
+    }
+  },
+  responses: {
+    200: {
+      description: '登录成功',
+      content: {
+        'application/json': {
+          schema: TokenResponseSchema
+        }
+      }
+    },
+    401: {
+      description: '用户名或密码错误',
+      content: {
+        'application/json': {
+          schema: ErrorSchema
+        }
+      }
+    }
+  }
+})
+const app = new OpenAPIHono<AuthContext>().openapi(loginRoute, async (c) => {
+  const { username, password } = c.req.valid('json')
+  const result = await authService.login(username, password)
+  return c.json(result, 200)
+});
+
+export default app

+ 68 - 0
src/server/api/auth/logout.ts

@@ -0,0 +1,68 @@
+import { createRoute, OpenAPIHono } from '@hono/zod-openapi';
+import { z } from '@hono/zod-openapi'
+import { AuthContext } from '@/server/types/context';
+import { authMiddleware } from '@/server/middleware/auth.middleware';
+import { AppDataSource } from '@/server/data-source';
+import { AuthService } from '@/server/modules/auth/auth.service';
+import { UserService } from '@/server/modules/users/user.service';
+import { ErrorSchema } from '@/server/utils/errorHandler';
+
+// 初始化服务
+const userService = new UserService(AppDataSource);
+const authService = new AuthService(userService);
+
+const SuccessSchema = z.object({
+  message: z.string().openapi({ example: '登出成功' })
+})
+
+// 定义路由
+const routeDef = createRoute({
+  method: 'post',
+  path: '/logout',
+  security: [{ Bearer: [] }],
+  middleware: [authMiddleware],
+  responses: {
+    200: {
+      description: '登出成功',
+      content: {
+        'application/json': {
+          schema: SuccessSchema
+        }
+      }
+    },
+    401: {
+      description: '未授权',
+      content: {
+        'application/json': {
+          schema: ErrorSchema
+        }
+      }
+    },
+    500: {
+      description: '服务器错误',
+      content: {
+        'application/json': {
+          schema: ErrorSchema
+        }
+      }
+    }
+  }
+});
+
+const app = new OpenAPIHono<AuthContext>().openapi(routeDef, async (c) => {
+  try {
+    const token = c.get('token');
+    const decoded = authService.verifyToken(token);
+    if (!decoded) {
+      return c.json({ code: 401, message: '未授权' }, 401);
+    }
+
+    await authService.logout(token);
+    return c.json({ message: '登出成功' }, 200);
+  } catch (error) {
+    console.error('登出失败:', error);
+    return c.json({ code: 500, message: '登出失败' }, 500);
+  }
+});
+
+export default app;

+ 40 - 0
src/server/api/auth/me/get.ts

@@ -0,0 +1,40 @@
+import { createRoute, OpenAPIHono } from '@hono/zod-openapi'
+import { ErrorSchema } from '@/server/utils/errorHandler'
+import { authMiddleware } from '@/server/middleware/auth.middleware'
+import { AuthContext } from '@/server/types/context'
+import { UserSchema } from '../../../modules/users/user.entity'
+
+const UserResponseSchema = UserSchema.omit({
+  password: true
+});
+
+const routeDef = createRoute({
+  method: 'get',
+  path: '/me',
+  middleware: authMiddleware,
+  responses: {
+    200: {
+      description: '获取当前用户信息成功',
+      content: {
+        'application/json': {
+          schema: UserResponseSchema
+        }
+      }
+    },
+    401: {
+      description: '未授权',
+      content: {
+        'application/json': {
+          schema: ErrorSchema
+        }
+      }
+    }
+  }
+})
+
+const app = new OpenAPIHono<AuthContext>().openapi(routeDef, (c) => {
+  const user = c.get('user')
+  return c.json(user, 200)
+})
+
+export default app

+ 76 - 0
src/server/api/auth/register/create.ts

@@ -0,0 +1,76 @@
+import { createRoute, OpenAPIHono } from '@hono/zod-openapi'
+import { AuthService } from '../../../modules/auth/auth.service'
+import { UserService } from '../../../modules/users/user.service'
+import { z } from '@hono/zod-openapi'
+import { AppDataSource } from '../../../data-source'
+import { ErrorSchema } from '../../../utils/errorHandler'
+import { AuthContext } from '../../../types/context'
+
+const RegisterSchema = z.object({
+  username: z.string().min(3).openapi({
+    example: 'john_doe',
+    description: '用户名'
+  }),
+  password: z.string().min(6).openapi({
+    example: 'password123',
+    description: '密码'
+  }),
+  email: z.string().email().openapi({
+    example: 'john@example.com',
+    description: '邮箱'
+  }).optional()
+})
+
+const TokenResponseSchema = z.object({
+  token: z.string().openapi({
+    example: 'jwt.token.here',
+    description: 'JWT Token'
+  }),
+  user: z.object({
+    id: z.number(),
+    username: z.string()
+  })
+})
+
+const userService = new UserService(AppDataSource)
+const authService = new AuthService(userService)
+
+const registerRoute = createRoute({
+  method: 'post',
+  path: '/register',
+  request: {
+    body: {
+      content: {
+        'application/json': {
+          schema: RegisterSchema
+        }
+      }
+    }
+  },
+  responses: {
+    201: {
+      description: '注册成功',
+      content: {
+        'application/json': {
+          schema: TokenResponseSchema
+        }
+      }
+    },
+    400: {
+      description: '用户名已存在',
+      content: {
+        'application/json': {
+          schema: ErrorSchema
+        }
+      }
+    }
+  }
+})
+
+const app = new OpenAPIHono<AuthContext>().openapi(registerRoute, async (c) => {
+  const { username, password, email } = c.req.valid('json')
+  const user = await userService.createUser({ username, password, email })
+  const token = authService.generateToken(user)
+  return c.json({ token, user }, 201)
+})
+export default app

+ 66 - 0
src/server/api/auth/sso-verify.ts

@@ -0,0 +1,66 @@
+import { createRoute, OpenAPIHono } from '@hono/zod-openapi'
+import { AuthService } from '@/server/modules/auth/auth.service'
+import { UserService } from '@/server/modules/users/user.service'
+import { ErrorSchema } from '@/server/utils/errorHandler'
+import { AppDataSource } from '@/server/data-source'
+import { AuthContext } from '@/server/types/context'
+
+const userService = new UserService(AppDataSource)
+const authService = new AuthService(userService)
+
+const routeDef = createRoute({
+  method: 'get',
+  path: '/sso-verify',
+  responses: {
+    200: {
+      description: 'SSO验证成功',
+      headers: {
+        'X-Username': {
+          schema: { type: 'string' },
+          description: '格式化后的用户名'
+        }
+      }
+    },
+    401: {
+      description: '未授权或令牌无效',
+      content: {
+        'application/json': {
+          schema: ErrorSchema
+        }
+      }
+    },
+    500: {
+      description: '服务器错误',
+      content: {
+        'application/json': {
+          schema: ErrorSchema
+        }
+      }
+    }
+  }
+})
+
+const app = new OpenAPIHono().openapi(routeDef, async (c) => {
+  try {
+    const token = c.req.header('Authorization')?.replace('Bearer ', '')
+    
+    if (!token) {
+      return c.json({ code: 401, message: '未提供授权令牌' }, 401)
+    }
+
+    try {
+      const userData = await authService.verifyToken(token)
+      if (!userData) {
+        return c.json({ code: 401, message: '无效令牌' }, 401)
+      }
+
+      return c.text('OK', 200)
+    } catch (tokenError) {
+      return c.json({ code: 401, message: '令牌验证失败' }, 401)
+    }
+  } catch (error) {
+    return c.json({ code: 500, message: 'SSO验证失败' }, 500)
+  }
+})
+
+export default app

+ 0 - 32
src/server/api/hello/index.ts

@@ -1,32 +0,0 @@
-import { createRoute, OpenAPIHono, z} from '@hono/zod-openapi';
-
-// 响应Schema
-const HelloResponse = z.object({
-  message: z.string().openapi({
-    description: '返回的问候消息',
-    example: 'hello'
-  })
-});
-
-// 路由定义
-const routeDef = createRoute({
-  method: 'get',
-  path: '/',
-  responses: {
-    200: {
-      description: '成功返回问候消息',
-      content: {
-        'application/json': {
-          schema: HelloResponse
-        }
-      }
-    }
-  }
-});
-
-// 路由实现
-const app = new OpenAPIHono().openapi(routeDef, async (c) => {
-  return c.json({ message: 'hello' }, 200);
-});
-
-export default app;

+ 25 - 0
src/server/api/roles/index.ts

@@ -0,0 +1,25 @@
+import { createCrudRoutes } from '@/server/utils/generic-crud.routes';
+import { Role } from '@/server/modules/users/role.entity';
+import { RoleSchema, CreateRoleDto, UpdateRoleDto } from '@/server/modules/users/role.entity';
+import { authMiddleware } from '@/server/middleware/auth.middleware';
+import { checkPermission, permissionMiddleware } from '@/server/middleware/permission.middleware';
+import { OpenAPIHono } from '@hono/zod-openapi';
+
+// 创建角色CRUD路由
+const roleRoutes = createCrudRoutes({
+  entity: Role,
+  createSchema: CreateRoleDto,
+  updateSchema: UpdateRoleDto,
+  getSchema: RoleSchema,
+  listSchema: RoleSchema,
+  searchFields: ['name', 'description'],
+  middleware: [
+    authMiddleware, 
+    // permissionMiddleware(checkPermission(['role:manage']))
+]
+})
+const app = new OpenAPIHono()
+  .route('/', roleRoutes)
+// .route('/', customRoute)
+
+export default app;

+ 54 - 0
src/server/api/users/[id]/delete.ts

@@ -0,0 +1,54 @@
+import { createRoute, OpenAPIHono } from '@hono/zod-openapi';
+import { UserService } from '@/server/modules/users/user.service';
+import { z } from '@hono/zod-openapi';
+import { authMiddleware } from '@/server/middleware/auth.middleware';
+import { ErrorSchema } from '@/server/utils/errorHandler';
+import { AppDataSource } from '@/server/data-source';
+import { AuthContext } from '@/server/types/context';
+
+const userService = new UserService(AppDataSource);
+
+const DeleteParams = z.object({
+  id: z.coerce.number().openapi({
+    param: { name: 'id', in: 'path' },
+    example: 1,
+    description: '用户ID'
+  })
+});
+
+const routeDef = createRoute({
+  method: 'delete',
+  path: '/{id}',
+  middleware: [authMiddleware],
+  request: {
+    params: DeleteParams
+  },
+  responses: {
+    204: { 
+      description: '用户删除成功' 
+    },
+    404: {
+      description: '用户不存在',
+      content: { 'application/json': { schema: ErrorSchema } }
+    },
+    500: {
+      description: '服务器错误',
+      content: { 'application/json': { schema: ErrorSchema } }
+    }
+  }
+});
+
+const app = new OpenAPIHono<AuthContext>().openapi(routeDef, async (c) => {
+  try {
+    const { id } = c.req.valid('param');
+    await userService.deleteUser(id);
+    return c.body(null, 204);
+  } catch (error) {
+    return c.json({ 
+      code: 500, 
+      message: error instanceof Error ? error.message : '删除用户失败' 
+    }, 500);
+  }
+});
+
+export default app;

+ 59 - 0
src/server/api/users/[id]/get.ts

@@ -0,0 +1,59 @@
+import { createRoute, OpenAPIHono } from '@hono/zod-openapi';
+import { UserService } from '@/server/modules/users/user.service';
+import { z } from '@hono/zod-openapi';
+import { authMiddleware } from '@/server/middleware/auth.middleware';
+import { ErrorSchema } from '@/server/utils/errorHandler';
+import { AppDataSource } from '@/server/data-source';
+import { AuthContext } from '@/server/types/context';
+import { UserSchema } from '@/server/modules/users/user.entity';
+
+const userService = new UserService(AppDataSource);
+
+const GetParams = z.object({
+  id: z.coerce.number().openapi({
+    param: { name: 'id', in: 'path' },
+    example: 1,
+    description: '用户ID'
+  })
+});
+
+const routeDef = createRoute({
+  method: 'get',
+  path: '/{id}',
+  middleware: [authMiddleware],
+  request: {
+    params: GetParams
+  },
+  responses: {
+    200: {
+      description: '成功获取用户详情',
+      content: { 'application/json': { schema: UserSchema } }
+    },
+    404: {
+      description: '用户不存在',
+      content: { 'application/json': { schema: ErrorSchema } }
+    },
+    500: {
+      description: '服务器错误',
+      content: { 'application/json': { schema: ErrorSchema } }
+    }
+  }
+});
+
+const app = new OpenAPIHono<AuthContext>().openapi(routeDef, async (c) => {
+  try {
+    const { id } = c.req.valid('param');
+    const user = await userService.getUserById(id);
+    if (!user) {
+      return c.json({ code: 404, message: '用户不存在' }, 404);
+    }
+    return c.json(user, 200);
+  } catch (error) {
+    return c.json({ 
+      code: 500, 
+      message: error instanceof Error ? error.message : '获取用户详情失败' 
+    }, 500);
+  }
+});
+
+export default app;

+ 77 - 0
src/server/api/users/[id]/put.ts

@@ -0,0 +1,77 @@
+import { createRoute, OpenAPIHono } from '@hono/zod-openapi';
+import { UserService } from '@/server/modules/users/user.service';
+import { z } from '@hono/zod-openapi';
+import { authMiddleware } from '@/server/middleware/auth.middleware';
+import { ErrorSchema } from '@/server/utils/errorHandler';
+import { AppDataSource } from '@/server/data-source';
+import { AuthContext } from '@/server/types/context';
+import { UserSchema } from '@/server/modules/users/user.entity';
+
+const userService = new UserService(AppDataSource);
+
+const UpdateParams = z.object({
+  id: z.coerce.number().openapi({
+    param: { name: 'id', in: 'path' },
+    example: 1,
+    description: '用户ID'
+  })
+});
+
+const UpdateUserSchema = UserSchema.omit({ 
+  id: true,
+  createdAt: true,
+  updatedAt: true 
+}).partial();
+
+const routeDef = createRoute({
+  method: 'put',
+  path: '/{id}',
+  middleware: [authMiddleware],
+  request: {
+    params: UpdateParams,
+    body: {
+      content: {
+        'application/json': {
+          schema: UpdateUserSchema
+        }
+      }
+    }
+  },
+  responses: {
+    200: {
+      description: '用户更新成功',
+      content: { 'application/json': { schema: UserSchema } }
+    },
+    400: {
+      description: '无效输入',
+      content: { 'application/json': { schema: ErrorSchema } }
+    },
+    404: {
+      description: '用户不存在',
+      content: { 'application/json': { schema: ErrorSchema } }
+    },
+    500: {
+      description: '服务器错误',
+      content: { 'application/json': { schema: ErrorSchema } }
+    }
+  }
+});
+
+const app = new OpenAPIHono<AuthContext>().openapi(routeDef, async (c) => {
+  try {
+    const { id } = c.req.valid('param');
+    const data = c.req.valid('json');
+    const user = await userService.updateUser(id, data);
+    if (!user) {
+      return c.json({ code: 404, message: '用户不存在' }, 404);
+    }
+    return c.json(user, 200);
+  } catch (error) {
+    return c.json({ 
+      code: 500, 
+      message: error instanceof Error ? error.message : '更新用户失败' 
+    }, 500);
+  }
+});
+
+export default app;

+ 108 - 0
src/server/api/users/get.ts

@@ -0,0 +1,108 @@
+import { createRoute, OpenAPIHono } from '@hono/zod-openapi';
+import { UserService } from '../../modules/users/user.service';
+import { z } from '@hono/zod-openapi';
+import { authMiddleware } from '../../middleware/auth.middleware';
+import { ErrorSchema } from '../../utils/errorHandler';
+import { AppDataSource } from '../../data-source';
+import { AuthContext } from '../../types/context';
+import { UserSchema } from '../../modules/users/user.entity';
+
+const userService = new UserService(AppDataSource);
+
+const PaginationQuery = 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: 'admin',
+    description: '搜索关键词(用户名/昵称/手机号)'
+  })
+});
+
+const UserListResponse = z.object({
+  data: z.array(UserSchema),
+  pagination: z.object({
+    total: z.number().openapi({
+      example: 100,
+      description: '总记录数'
+    }),
+    current: z.number().openapi({
+      example: 1,
+      description: '当前页码'
+    }),
+    pageSize: z.number().openapi({
+      example: 10,
+      description: '每页数量'
+    })
+  })
+});
+
+const listUsersRoute = createRoute({
+  method: 'get',
+  path: '/',
+  middleware: [authMiddleware],
+  request: {
+    query: PaginationQuery
+  },
+  responses: {
+    200: {
+      description: '成功获取用户列表',
+      content: {
+        'application/json': {
+          schema: UserListResponse
+        }
+      }
+    },
+    400: {
+      description: '参数错误',
+      content: {
+        'application/json': {
+          schema: ErrorSchema
+        }
+      }
+    },
+    500: {
+      description: '获取用户列表失败',
+      content: {
+        'application/json': {
+          schema: ErrorSchema
+        }
+      }
+    }
+  }
+});
+
+const app = new OpenAPIHono<AuthContext>().openapi(listUsersRoute, async (c) => {
+  try {
+    const { page, pageSize, keyword } = c.req.valid('query');
+    const [users, total] = await userService.getUsersWithPagination({
+      page,
+      pageSize,
+      keyword
+    });
+    
+    return c.json({
+      data: users,
+      pagination: {
+        total,
+        current: page,
+        pageSize
+      }
+    }, 200);
+  } catch (error) {
+    if (error instanceof z.ZodError) {
+      return c.json({ code: 400, message: '参数错误' }, 400);
+    }
+    return c.json({ 
+      code: 500, 
+      message: error instanceof Error ? error.message : '获取用户列表失败' 
+    }, 500);
+  }
+});
+
+export default app;

+ 15 - 0
src/server/api/users/index.ts

@@ -0,0 +1,15 @@
+import { OpenAPIHono } from '@hono/zod-openapi';
+import listUsersRoute from './get';
+import createUserRoute from './post';
+import getUserByIdRoute from './[id]/get';
+import updateUserRoute from './[id]/put';
+import deleteUserRoute from './[id]/delete';
+
+const app = new OpenAPIHono()
+  .route('/', listUsersRoute)
+  .route('/', createUserRoute)
+  .route('/', getUserByIdRoute)
+  .route('/', updateUserRoute)
+  .route('/', deleteUserRoute);
+
+export default app;

+ 75 - 0
src/server/api/users/post.ts

@@ -0,0 +1,75 @@
+import { createRoute, OpenAPIHono } from '@hono/zod-openapi';
+import { UserService } from '../../modules/users/user.service';
+import { z } from '@hono/zod-openapi';
+import { authMiddleware } from '../../middleware/auth.middleware';
+import { ErrorSchema } from '../../utils/errorHandler';
+import { AppDataSource } from '../../data-source';
+import { AuthContext } from '../../types/context';
+import { UserSchema } from '@/server/modules/users/user.entity';
+
+const userService = new UserService(AppDataSource);
+
+
+const CreateUserSchema = UserSchema.omit({
+  id: true,
+  createdAt: true,
+  updatedAt: true,
+}).extend({
+  phone: UserSchema.shape.phone.optional(),
+  email: UserSchema.shape.email.optional(),
+  nickname: UserSchema.shape.nickname.optional(),
+  name: UserSchema.shape.name.optional(),
+  avatar: UserSchema.shape.avatar.optional(),
+})
+
+const createUserRoute = createRoute({
+  method: 'post',
+  path: '/',
+  middleware: authMiddleware,
+  request: {
+    body: {
+      content: {
+        'application/json': {
+          schema: CreateUserSchema
+        }
+      }
+    }
+  },
+  responses: {
+    201: {
+      description: '创建成功',
+      content: {
+        'application/json': {
+          schema: UserSchema
+        }
+      }
+    },
+    400: {
+      description: '输入数据无效',
+      content: {
+        'application/json': {
+          schema: ErrorSchema
+        }
+      }
+    },
+    500: {
+      description: '服务器错误',
+      content: {
+        'application/json': {
+          schema: ErrorSchema
+        }
+      }
+    }
+  }
+});
+
+const app = new OpenAPIHono<AuthContext>().openapi(createUserRoute, async (c) => {
+  try {
+    const data = c.req.valid('json');
+    const user = await userService.createUser(data);
+    return c.json(user, 201);
+  } catch (error) {
+    return c.json({ code: 500, message: '服务器错误' }, 500);
+  }
+});
+export default app;

+ 22 - 0
src/server/data-source.ts

@@ -0,0 +1,22 @@
+import "reflect-metadata"
+import { DataSource } from "typeorm"
+import process from 'node:process'
+
+// 实体类导入
+import { UserEntity as User } from "./modules/users/user.entity"
+import { Role } from "./modules/users/role.entity"
+
+export const AppDataSource = new DataSource({
+  type: "mysql",
+  host: process.env.DB_HOST || "localhost",
+  port: parseInt(process.env.DB_PORT || "3306"),
+  username: process.env.DB_USERNAME || "root",
+  password: process.env.DB_PASSWORD || "",
+  database: process.env.DB_DATABASE || "d8dai",
+  entities: [
+    User, Role, 
+  ],
+  migrations: [],
+  synchronize: process.env.DB_SYNCHRONIZE !== "false",
+  logging: process.env.DB_LOGGING === "true",
+});

+ 36 - 0
src/server/middleware/auth.middleware.ts

@@ -0,0 +1,36 @@
+import { Context, Next } from 'hono';
+import { AuthService } from '../modules/auth/auth.service';
+import { UserService } from '../modules/users/user.service';
+import { AppDataSource } from '../data-source';
+import { AuthContext } from '../types/context';
+
+export async function authMiddleware(c: Context<AuthContext>, next: Next) {
+  try {
+    const authHeader = c.req.header('Authorization');
+    if (!authHeader) {
+      return c.json({ message: 'Authorization header missing' }, 401);
+    }
+
+    const token = authHeader.split(' ')[1];
+    if (!token) {
+      return c.json({ message: 'Token missing' }, 401);
+    }
+
+    const userService = new UserService(AppDataSource);
+    const authService = new AuthService(userService);
+    const decoded = authService.verifyToken(token);
+    
+    const user = await userService.getUserById(decoded.id);
+    
+    if (!user) {
+      return c.json({ message: 'User not found' }, 401);
+    }
+
+    c.set('user', user);
+    c.set('token', token);
+    await next();
+  } catch (error) {
+    console.error('Authentication error:', error);
+    return c.json({ message: 'Invalid token' }, 401);
+  }
+}

+ 39 - 0
src/server/middleware/permission.middleware.ts

@@ -0,0 +1,39 @@
+import { Context, Next } from 'hono';
+import { UserEntity as User } from '../modules/users/user.entity';
+
+type PermissionCheck = (user: User) => boolean | Promise<boolean>;
+
+export function checkPermission(requiredRoles: string[]): PermissionCheck {
+  return (user: User) => {
+    if (!user.roles) return false;
+    return user.roles.some(role => requiredRoles.includes(role.name));
+  };
+}
+
+export function permissionMiddleware(check: PermissionCheck) {
+  return async (c: Context, next: Next) => {
+    try {
+      const user = c.get('user') as User | undefined;
+      if (!user) {
+        return c.json({ message: 'Unauthorized' }, 401);
+      }
+
+      const hasPermission = await check(user);
+      if (!hasPermission) {
+        return c.json({ message: 'Forbidden' }, 403);
+      }
+
+      await next();
+    } catch (error) {
+      console.error('Permission check error:', error);
+      return c.json({ message: 'Internal server error' }, 500);
+    }
+  };
+}
+
+// 示例用法:
+// app.get('/admin', 
+//   authMiddleware,
+//   permissionMiddleware(checkPermission(['admin'])),
+//   (c) => {...}
+// )

+ 104 - 0
src/server/modules/auth/auth.service.ts

@@ -0,0 +1,104 @@
+import jwt from 'jsonwebtoken';
+import { UserService } from '../users/user.service';
+import { UserEntity as User } from '../users/user.entity';
+import { DisabledStatus } from '@/share/types';
+import debug from 'debug';
+
+const logger = {
+  info: debug('backend:auth:info'),
+  error: debug('backend:auth:error')
+}
+
+const JWT_SECRET = 'your-secret-key'; // 生产环境应使用环境变量
+const JWT_EXPIRES_IN = '7d'; // 7天有效期
+const ADMIN_USERNAME = 'admin';
+const ADMIN_PASSWORD = 'admin123';
+
+export class AuthService {
+  private userService: UserService;
+
+  constructor(userService: UserService) {
+    this.userService = userService;
+  }
+
+  async ensureAdminExists(): Promise<User> {
+    try {
+      let admin = await this.userService.getUserByUsername(ADMIN_USERNAME);
+      if (!admin) {
+        logger.info('Admin user not found, creating default admin account');
+        admin = await this.userService.createUser({
+          username: ADMIN_USERNAME,
+          password: ADMIN_PASSWORD,
+          nickname: '系统管理员',
+          isDisabled: DisabledStatus.ENABLED
+        });
+        logger.info('Default admin account created successfully');
+      }
+      return admin;
+    } catch (error) {
+      logger.error('Failed to ensure admin account exists:', error);
+      throw error;
+    }
+  }
+
+  async login(username: string, password: string): Promise<{ token: string; user: User }> {
+    try {
+      // 确保admin用户存在
+      if (username === ADMIN_USERNAME) {
+        await this.ensureAdminExists();
+      }
+      
+      const user = await this.userService.getUserByUsername(username);
+      if (!user) {
+        throw new Error('User not found');
+      }
+
+      const isPasswordValid = await this.userService.verifyPassword(user, password);
+      if (!isPasswordValid) {
+        throw new Error('Invalid password');
+      }
+
+      const token = this.generateToken(user);
+      return { token, user };
+    } catch (error) {
+      logger.error('Login error:', error);
+      throw error;
+    }
+  }
+
+  generateToken(user: User): string {
+    const payload = {
+      id: user.id,
+      username: user.username,
+      roles: user.roles?.map(role => role.name) || []
+    };
+    return jwt.sign(payload, JWT_SECRET, { expiresIn: JWT_EXPIRES_IN });
+  }
+
+  verifyToken(token: string): any {
+    try {
+      return jwt.verify(token, JWT_SECRET);
+    } catch (error) {
+      console.error('Token verification failed:', error);
+      throw new Error('Invalid token');
+    }
+  }
+
+  async logout(token: string): Promise<void> {
+    try {
+      // 验证token有效性
+      const decoded = this.verifyToken(token);
+      if (!decoded) {
+        throw new Error('Invalid token');
+      }
+      
+      // 实际项目中这里可以添加token黑名单逻辑
+      // 或者调用Redis等缓存服务使token失效
+      
+      return Promise.resolve();
+    } catch (error) {
+      console.error('Logout failed:', error);
+      throw error;
+    }
+  }
+}

+ 56 - 0
src/server/modules/users/role.entity.ts

@@ -0,0 +1,56 @@
+import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn } from 'typeorm';
+import { z } from '@hono/zod-openapi';
+
+export type Permission = string;
+
+export const RoleSchema = z.object({
+  id: z.number().int().positive().openapi({
+    description: '角色ID',
+    example: 1
+  }),
+  name: z.string().max(50).openapi({
+    description: '角色名称,唯一标识',
+    example: 'admin'
+  }),
+  description: z.string().max(500).nullable().openapi({
+    description: '角色描述',
+    example: '系统管理员角色'
+  }),
+  permissions: z.array(z.string()).min(1).openapi({
+    description: '角色权限列表',
+    example: ['user:create', 'user:delete']
+  }),
+  createdAt: z.date().openapi({ description: '创建时间' }),
+  updatedAt: z.date().openapi({ description: '更新时间' })
+});
+
+export const CreateRoleDto = RoleSchema.omit({ id: true , createdAt: true, updatedAt: true });
+export const UpdateRoleDto = RoleSchema.partial();
+
+@Entity({ name: 'role' })
+export class Role {
+  @PrimaryGeneratedColumn()
+  id!: number;
+
+  @Column({ type: 'varchar', length: 50, unique: true })
+  name!: string;
+
+  @Column({ type: 'text', nullable: true })
+  description!: string | null;
+
+  @Column({ type: 'simple-array', nullable: false })
+  permissions: Permission[] = [];
+
+  @CreateDateColumn({ name: 'created_at', type: 'timestamp' })
+  createdAt!: Date;
+
+  @UpdateDateColumn({ name: 'updated_at', type: 'timestamp' })
+  updatedAt!: Date;
+
+  constructor(partial?: Partial<Role>) {
+    Object.assign(this, partial);
+    if (!this.permissions) {
+      this.permissions = [];
+    }
+  }
+}

+ 20 - 0
src/server/modules/users/role.service.ts

@@ -0,0 +1,20 @@
+import { DataSource, Repository } from 'typeorm';
+import { Role } from './role.entity';
+import { GenericCrudService } from '@/server/utils/generic-crud.service';
+
+export class RoleService extends GenericCrudService<Role> {
+  constructor(dataSource: DataSource) {
+    super(dataSource, Role);
+  }
+  
+  // 可以添加角色特有的业务逻辑方法
+  async getRoleByName(name: string): Promise<Role | null> {
+    return this.repository.findOneBy({ name });
+  }
+  
+  async checkPermission(roleId: number, permission: string): Promise<boolean> {
+    const role = await this.getById(roleId);
+    if (!role) return false;
+    return role.permissions.includes(permission);
+  }
+}

+ 99 - 0
src/server/modules/users/user.entity.ts

@@ -0,0 +1,99 @@
+import { Entity, PrimaryGeneratedColumn, Column, ManyToMany, JoinTable, CreateDateColumn, UpdateDateColumn, OneToMany } from 'typeorm';
+import { Role, RoleSchema } from './role.entity';
+import { z } from '@hono/zod-openapi';
+import { DeleteStatus, DisabledStatus } from '@/share/types';
+
+@Entity({ name: 'users' })
+export class UserEntity {
+  @PrimaryGeneratedColumn({ unsigned: true, comment: '用户ID' })
+  id!: number;
+
+  @Column({ name: 'username', type: 'varchar', length: 255, unique: true, comment: '用户名' })
+  username!: string;
+
+  @Column({ name: 'password', type: 'varchar', length: 255, comment: '密码' })
+  password!: string;
+
+  @Column({ name: 'phone', type: 'varchar', length: 255, nullable: true, comment: '手机号' })
+  phone!: string | null;
+
+  @Column({ name: 'email', type: 'varchar', length: 255, nullable: true, comment: '邮箱' })
+  email!: string | null;
+
+  @Column({ name: 'nickname', type: 'varchar', length: 255, nullable: true, comment: '昵称' })
+  nickname!: string | null;
+
+  @Column({ name: 'name', type: 'varchar', length: 255, nullable: true, comment: '真实姓名' })
+  name!: string | null;
+
+  @Column({ name: 'avatar', type: 'varchar', length: 255, nullable: true, comment: '头像' })
+  avatar!: string | null;
+
+  @Column({ name: 'is_disabled', type: 'int', default: DisabledStatus.ENABLED, comment: '是否禁用(0:启用,1:禁用)' })
+  isDisabled!: DisabledStatus;
+
+  @Column({ name: 'is_deleted', type: 'int', default: DeleteStatus.NOT_DELETED, comment: '是否删除(0:未删除,1:已删除)' })
+  isDeleted!: DeleteStatus;
+
+  @ManyToMany(() => Role)
+  @JoinTable()
+  roles!: Role[];
+
+  @CreateDateColumn({ name: 'created_at', type: 'timestamp' })
+  createdAt!: Date;
+
+  @UpdateDateColumn({ name: 'updated_at', type: 'timestamp' })
+  updatedAt!: Date;
+
+  constructor(partial?: Partial<UserEntity>) {
+    Object.assign(this, partial);
+  }
+}
+
+export const UserSchema = z.object({
+  id: z.number().int().positive().openapi({ description: '用户ID' }),
+  username: z.string().min(3).max(255).openapi({
+    example: 'admin',
+    description: '用户名,3-255个字符'
+  }),
+  password: z.string().min(6).max(255).openapi({
+    example: 'password123',
+    description: '密码,最少6位'
+  }),
+  phone: z.string().max(255).nullable().openapi({
+    example: '13800138000',
+    description: '手机号'
+  }),
+  email: z.string().email().max(255).nullable().openapi({
+    example: 'user@example.com',
+    description: '邮箱'
+  }),
+  nickname: z.string().max(255).nullable().openapi({
+    example: '昵称',
+    description: '用户昵称'
+  }),
+  name: z.string().max(255).nullable().openapi({
+    example: '张三',
+    description: '真实姓名'
+  }),
+  avatar: z.string().max(255).nullable().openapi({
+    example: 'https://example.com/avatar.jpg',
+    description: '用户头像'
+  }),
+  isDisabled: z.number().int().min(0).max(1).default(DisabledStatus.ENABLED).openapi({
+    example: DisabledStatus.ENABLED,
+    description: '是否禁用(0:启用,1:禁用)'
+  }),
+  isDeleted: z.number().int().min(0).max(1).default(DeleteStatus.NOT_DELETED).openapi({
+    example: DeleteStatus.NOT_DELETED,
+    description: '是否删除(0:未删除,1:已删除)'
+  }),
+  roles: z.array(RoleSchema).optional().openapi({
+    example: [
+      { id: 1, name: 'admin',description:'管理员', permissions: ['user:create'] ,createdAt: new Date(), updatedAt: new Date() }
+    ],
+    description: '用户角色列表'
+  }),
+  createdAt: z.date().openapi({ description: '创建时间' }),
+  updatedAt: z.date().openapi({ description: '更新时间' })
+});

+ 166 - 0
src/server/modules/users/user.service.ts

@@ -0,0 +1,166 @@
+import { DataSource } from 'typeorm';
+import { UserEntity as User } from './user.entity';
+import * as bcrypt from 'bcrypt';
+import { Repository } from 'typeorm';
+import { Role } from './role.entity';
+
+const SALT_ROUNDS = 10;
+
+export class UserService {
+  private userRepository: Repository<User>;
+  private roleRepository: Repository<Role>;
+  private readonly dataSource: DataSource;
+
+  constructor(dataSource: DataSource) {
+    this.dataSource = dataSource;
+    this.userRepository = this.dataSource.getRepository(User);
+    this.roleRepository = this.dataSource.getRepository(Role);
+  }
+
+  async createUser(userData: Partial<User>): Promise<User> {
+    try {
+      if (userData.password) {
+        userData.password = await bcrypt.hash(userData.password, SALT_ROUNDS);
+      }
+      const user = this.userRepository.create(userData);
+      return await this.userRepository.save(user);
+    } catch (error) {
+      console.error('Error creating user:', error);
+      throw new Error(`Failed to create user: ${error instanceof Error ? error.message : String(error)}`)
+    }
+  }
+
+  async getUserById(id: number): Promise<User | null> {
+    try {
+      return await this.userRepository.findOne({ 
+        where: { id },
+        relations: ['roles']
+      });
+    } catch (error) {
+      console.error('Error getting user:', error);
+      throw new Error('Failed to get user');
+    }
+  }
+
+  async getUserByUsername(username: string): Promise<User | null> {
+    try {
+      return await this.userRepository.findOne({
+        where: { username },
+        relations: ['roles']
+      });
+    } catch (error) {
+      console.error('Error getting user:', error);
+      throw new Error('Failed to get user');
+    }
+  }
+
+  async getUserByPhone(phone: string): Promise<User | null> {
+    try {
+      return await this.userRepository.findOne({
+        where: { phone: phone },
+        relations: ['roles']
+      });
+    } catch (error) {
+      console.error('Error getting user by phone:', error);
+      throw new Error('Failed to get user by phone');
+    }
+  }
+
+  async updateUser(id: number, updateData: Partial<User>): Promise<User | null> {
+    try {
+      if (updateData.password) {
+        updateData.password = await bcrypt.hash(updateData.password, SALT_ROUNDS);
+      }
+      await this.userRepository.update(id, updateData);
+      return this.getUserById(id);
+    } catch (error) {
+      console.error('Error updating user:', error);
+      throw new Error('Failed to update user');
+    }
+  }
+
+  async deleteUser(id: number): Promise<void> {
+    try {
+      await this.userRepository.delete(id);
+    } catch (error) {
+      console.error('Error deleting user:', error);
+      throw new Error('Failed to delete user');
+    }
+  }
+
+  async getUsersWithPagination(params: {
+    page: number;
+    pageSize: number;
+    keyword?: string;
+  }): Promise<[User[], number]> {
+    try {
+      const { page, pageSize, keyword } = params;
+      const skip = (page - 1) * pageSize;
+      
+      const queryBuilder = this.userRepository
+        .createQueryBuilder('user')
+        .leftJoinAndSelect('user.roles', 'roles')
+        .skip(skip)
+        .take(pageSize);
+
+      if (keyword) {
+        queryBuilder.where(
+          'user.username LIKE :keyword OR user.nickname LIKE :keyword OR user.phone LIKE :keyword',
+          { keyword: `%${keyword}%` }
+        );
+      }
+
+      return await queryBuilder.getManyAndCount();
+    } catch (error) {
+      console.error('Error getting users with pagination:', error);
+      throw new Error('Failed to get users');
+    }
+  }
+
+  async verifyPassword(user: User, password: string): Promise<boolean> {
+    return password === user.password || bcrypt.compare(password, user.password)
+  }
+
+  async assignRoles(userId: number, roleIds: number[]): Promise<User | null> {
+    try {
+      const user = await this.getUserById(userId);
+      if (!user) return null;
+
+      const roles = await this.roleRepository.findByIds(roleIds);
+      user.roles = roles;
+      return await this.userRepository.save(user);
+    } catch (error) {
+      console.error('Error assigning roles:', error);
+      throw new Error('Failed to assign roles');
+    }
+  }
+
+  async getUsers(): Promise<User[]> {
+    try {
+      const users = await this.userRepository.find({
+        relations: ['roles']
+      });
+      return users;
+    } catch (error) {
+      console.error('Error getting users:', error);
+      throw new Error(`Failed to get users: ${error instanceof Error ? error.message : String(error)}`)
+    }
+  }
+
+
+  getUserRepository(): Repository<User> {
+    return this.userRepository;
+  }
+
+  async getUserByAccount(account: string): Promise<User | null> {
+    try {
+      return await this.userRepository.findOne({
+        where: [{ username: account }, { email: account }],
+        relations: ['roles']
+      });
+    } catch (error) {
+      console.error('Error getting user by account:', error);
+      throw new Error('Failed to get user by account');
+    }
+  }
+}

+ 9 - 0
src/server/types/context.ts

@@ -0,0 +1,9 @@
+import { UserEntity } from "../modules/users/user.entity";
+
+// 扩展Context类型
+export type Variables = {
+  user: UserEntity;
+  token: string;
+}
+
+export type AuthContext = { Variables: Variables }

+ 21 - 0
src/server/utils/errorHandler.ts

@@ -0,0 +1,21 @@
+import { Context } from 'hono'
+import { z } from '@hono/zod-openapi'
+
+export const ErrorSchema = z.object({
+  code: z.number().openapi({
+    example: 400,
+  }),
+  message: z.string().openapi({
+    example: 'Bad Request',
+  }),
+})
+
+export const errorHandler = async (err: Error, c: Context) => {
+  return c.json(
+    { 
+      code: 500,
+      message: err.message || 'Internal Server Error'
+    },
+    500
+  )
+}

+ 349 - 0
src/server/utils/generic-crud.routes.ts

@@ -0,0 +1,349 @@
+import { createRoute, OpenAPIHono } from '@hono/zod-openapi';
+import { z } from '@hono/zod-openapi';
+import { GenericCrudService, CrudOptions } from './generic-crud.service';
+import { ErrorSchema } from './errorHandler';
+import { AuthContext } from '../types/context';
+import { ObjectLiteral } from 'typeorm';
+import { AppDataSource } from '../data-source';
+
+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 } = options;
+  
+  // 创建CRUD服务实例
+  // 抽象类不能直接实例化,需要创建具体实现类
+  class ConcreteCrudService extends GenericCrudService<T> {
+    constructor() {
+      super(AppDataSource, entity, { userTracking });
+    }
+  }
+  const crudService = new ConcreteCrudService();
+  
+  // 创建路由实例
+  const app = new OpenAPIHono<AuthContext>();
+  
+  // 分页查询路由
+  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 } }
+      }
+    }
+  });
+  
+  // 注册路由处理函数
+  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 [data, total] = await crudService.getList(
+          page,
+          pageSize,
+          keyword,
+          searchFields,
+          undefined,
+          relations || [],
+          order,
+          parsedFilters
+        );
+        
+        return c.json({
+          data,
+          pagination: { total, current: page, pageSize }
+        }, 200);
+      } catch (error) {
+        if (error instanceof z.ZodError) {
+          return c.json({ code: 400, message: '参数验证失败', errors: error.errors }, 400);
+        }
+        return c.json({
+          code: 500,
+          message: error instanceof Error ? error.message : '获取列表失败'
+        }, 500);
+      }
+    })
+    .openapi(createRouteDef, async (c: any) => {
+      try {
+        const data = c.req.valid('json');
+        const user = c.get('user');
+        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: error.errors }, 400);
+        }
+        return c.json({
+          code: 500,
+          message: error instanceof Error ? error.message : '创建资源失败'
+        }, 500);
+      }
+    })
+    .openapi(getRouteDef, async (c: any) => {
+      try {
+        const { id } = c.req.valid('param');
+        const result = await crudService.getById(id, relations || []);
+        
+        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: error.errors }, 400);
+        }
+        return c.json({
+          code: 500,
+          message: error instanceof Error ? error.message : '获取资源失败'
+        }, 500);
+      }
+    })
+    .openapi(updateRouteDef, async (c: any) => {
+      try {
+        const { id } = c.req.valid('param');
+        const data = c.req.valid('json');
+        const user = c.get('user');
+        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: error.errors }, 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 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: error.errors }, 400);
+        }
+        return c.json({
+          code: 500,
+          message: error instanceof Error ? error.message : '删除资源失败'
+        }, 500);
+      }
+    });
+  
+  return routes;
+}

+ 204 - 0
src/server/utils/generic-crud.service.ts

@@ -0,0 +1,204 @@
+import { DataSource, Repository, ObjectLiteral, DeepPartial } from 'typeorm';
+import { z } from '@hono/zod-openapi';
+
+export abstract class GenericCrudService<T extends ObjectLiteral> {
+  protected repository: Repository<T>;
+  private userTrackingOptions?: UserTrackingOptions;
+
+  constructor(
+    protected dataSource: DataSource,
+    protected entity: new () => T,
+    options?: {
+      userTracking?: UserTrackingOptions;
+    }
+  ) {
+    this.repository = this.dataSource.getRepository(entity);
+    this.userTrackingOptions = options?.userTracking;
+  }
+
+  /**
+   * 获取分页列表
+   */
+  async getList(
+    page: number = 1,
+    pageSize: number = 10,
+    keyword?: string,
+    searchFields?: string[],
+    where?: Partial<T>,
+    relations: string[] = [],
+    order: { [P in keyof T]?: 'ASC' | 'DESC' } = {},
+    filters?: {
+      [key: string]: any;
+    }
+  ): Promise<[T[], number]> {
+    const skip = (page - 1) * pageSize;
+    const query = this.repository.createQueryBuilder('entity');
+
+    // 添加关联关系(支持嵌套关联,如 ['contract.client'])
+    if (relations.length > 0) {
+      relations.forEach((relation, relationIndex) => {
+        const parts = relation.split('.');
+        let currentAlias = 'entity';
+        
+        parts.forEach((part, index) => {
+          const newAlias = index === 0 ? part : `${currentAlias}_${relationIndex}`;
+          query.leftJoinAndSelect(`${currentAlias}.${part}`, newAlias);
+          currentAlias = newAlias;
+        });
+      });
+    }
+
+    // 关键词搜索
+    if (keyword && searchFields && searchFields.length > 0) {
+      query.andWhere(searchFields.map(field => `entity.${field} LIKE :keyword`).join(' OR '), {
+        keyword: `%${keyword}%`
+      });
+    }
+
+    // 条件查询
+    if (where) {
+      Object.entries(where).forEach(([key, value]) => {
+        if (value !== undefined && value !== null) {
+          query.andWhere(`entity.${key} = :${key}`, { [key]: value });
+        }
+      });
+    }
+
+    // 扩展筛选条件
+    if (filters) {
+      Object.entries(filters).forEach(([key, value]) => {
+        if (value !== undefined && value !== null && value !== '') {
+          const fieldName = key.startsWith('_') ? key.substring(1) : key;
+          
+          // 支持不同类型的筛选
+          if (Array.isArray(value)) {
+            // 数组类型:IN查询
+            if (value.length > 0) {
+              query.andWhere(`entity.${fieldName} IN (:...${key})`, { [key]: value });
+            }
+          } else if (typeof value === 'string' && value.includes('%')) {
+            // 模糊匹配
+            query.andWhere(`entity.${fieldName} LIKE :${key}`, { [key]: value });
+          } else if (typeof value === 'object' && value !== null) {
+            // 范围查询
+            if ('gte' in value) {
+              query.andWhere(`entity.${fieldName} >= :${key}_gte`, { [`${key}_gte`]: value.gte });
+            }
+            if ('gt' in value) {
+              query.andWhere(`entity.${fieldName} > :${key}_gt`, { [`${key}_gt`]: value.gt });
+            }
+            if ('lte' in value) {
+              query.andWhere(`entity.${fieldName} <= :${key}_lte`, { [`${key}_lte`]: value.lte });
+            }
+            if ('lt' in value) {
+              query.andWhere(`entity.${fieldName} < :${key}_lt`, { [`${key}_lt`]: value.lt });
+            }
+            if ('between' in value && Array.isArray(value.between) && value.between.length === 2) {
+              query.andWhere(`entity.${fieldName} BETWEEN :${key}_start AND :${key}_end`, {
+                [`${key}_start`]: value.between[0],
+                [`${key}_end`]: value.between[1]
+              });
+            }
+          } else {
+            // 精确匹配
+            query.andWhere(`entity.${fieldName} = :${key}`, { [key]: value });
+          }
+        }
+      });
+    }
+
+    // 排序
+    Object.entries(order).forEach(([key, direction]) => {
+      query.orderBy(`entity.${key}`, direction);
+    });
+
+    return query.skip(skip).take(pageSize).getManyAndCount();
+  }
+
+  /**
+   * 根据ID获取单个实体
+   */
+  async getById(id: number, relations: string[] = []): Promise<T | null> {
+    return this.repository.findOne({
+      where: { id } as any,
+      relations
+    });
+  }
+
+  /**
+   * 设置用户跟踪字段
+   */
+  private setUserFields(data: any, userId?: string | number, isCreate: boolean = true): void {
+    if (!this.userTrackingOptions || !userId) {
+      return;
+    }
+
+    const { createdByField = 'createdBy', updatedByField = 'updatedBy' } = this.userTrackingOptions;
+
+    if (isCreate && createdByField) {
+      data[createdByField] = userId;
+    }
+
+    if (updatedByField) {
+      data[updatedByField] = userId;
+    }
+  }
+
+  /**
+   * 创建实体
+   */
+  async create(data: DeepPartial<T>, userId?: string | number): Promise<T> {
+    const entityData = { ...data };
+    this.setUserFields(entityData, userId, true);
+    const entity = this.repository.create(entityData as DeepPartial<T>);
+    return this.repository.save(entity);
+  }
+
+  /**
+   * 更新实体
+   */
+  async update(id: number, data: Partial<T>, userId?: string | number): Promise<T | null> {
+    const updateData = { ...data };
+    this.setUserFields(updateData, userId, false);
+    await this.repository.update(id, updateData);
+    return this.getById(id);
+  }
+
+  /**
+   * 删除实体
+   */
+  async delete(id: number): Promise<boolean> {
+    const result = await this.repository.delete(id);
+    return result.affected === 1;
+  }
+
+  /**
+   * 高级查询方法
+   */
+  createQueryBuilder(alias: string = 'entity') {
+    return this.repository.createQueryBuilder(alias);
+  }
+}
+
+export interface UserTrackingOptions {
+  createdByField?: string;
+  updatedByField?: string;
+}
+
+export type CrudOptions<
+  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
+> = {
+  entity: new () => T;
+  createSchema: CreateSchema;
+  updateSchema: UpdateSchema;
+  getSchema: GetSchema;
+  listSchema: ListSchema;
+  searchFields?: string[];
+  relations?: string[];
+  middleware?: any[];
+  userTracking?: UserTrackingOptions;
+};

+ 8 - 0
src/server/utils/logger.ts

@@ -0,0 +1,8 @@
+import debug from 'debug';
+
+export const logger = {
+  error: debug('backend:error'),
+  api: debug('backend:api'),
+  db: debug('backend:db'),
+  middleware: debug('backend:middleware'),
+};

+ 46 - 0
src/share/types.ts

@@ -0,0 +1,46 @@
+// 全局配置常量
+export interface GlobalConfig {
+  OSS_BASE_URL: string;
+  APP_NAME: string;
+}
+
+// 认证上下文类型
+export interface AuthContextType<T> {
+  user: T | null;
+  token: string | null;
+  login: (username: string, password: string, latitude?: number, longitude?: number) => Promise<void>;
+  logout: () => Promise<void>;
+  isAuthenticated: boolean;
+  isLoading: boolean;
+}
+
+// 启用/禁用状态枚举
+export enum EnableStatus {
+  DISABLED = 0, // 禁用
+  ENABLED = 1   // 启用
+}
+
+// 启用/禁用状态中文映射
+export const EnableStatusNameMap: Record<EnableStatus, string> = {
+  [EnableStatus.DISABLED]: '禁用',
+  [EnableStatus.ENABLED]: '启用'
+};
+
+// 删除状态枚举
+export enum DeleteStatus {
+  NOT_DELETED = 0, // 未删除
+  DELETED = 1      // 已删除
+}
+
+// 删除状态中文映射
+export const DeleteStatusNameMap: Record<DeleteStatus, string> = {
+  [DeleteStatus.NOT_DELETED]: '未删除',
+  [DeleteStatus.DELETED]: '已删除'
+};
+
+// 启用/禁用状态枚举
+export enum DisabledStatus {
+  DISABLED = 1, // 禁用
+  ENABLED = 0   // 启用
+}
+

Некоторые файлы не были показаны из-за большого количества измененных файлов