浏览代码

✨ feat(server): 重构为模块化架构

- 添加共享模块依赖:@d8d/shared-types、@d8d/shared-utils、@d8d/shared-crud
- 添加功能模块依赖:@d8d/user-module、@d8d/auth-module、@d8d/file-module
- 移除本地模块实现:用户、认证、文件、角色等模块
- 删除本地中间件、工具类和服务类
- 更新主入口文件使用外部模块路由
- 移除角色相关路由和类型定义

♻️ refactor(server): 清理冗余代码

- 删除本地认证相关API路由:登录、注册、登出、用户信息等
- 删除文件管理相关API路由:上传策略、分片上传、文件操作等
- 删除用户管理相关API路由:CRUD操作、自定义业务逻辑
- 删除角色管理相关API路由和实体
- 删除本地数据源配置和类型定义
- 删除中间件和工具函数:认证中间件、错误处理、JWT工具等

📦 build(server): 更新依赖配置

- 在package.json中添加workspace模块依赖
- 保持现有第三方依赖不变
- 重构为基于模块的微服务架构
yourname 4 周之前
父节点
当前提交
04015be2eb
共有 45 个文件被更改,包括 17 次插入4118 次删除
  1. 6 0
      packages/server/package.json
  2. 0 19
      packages/server/src/api/auth/index.ts
  3. 0 100
      packages/server/src/api/auth/login/post.ts
  4. 0 68
      packages/server/src/api/auth/logout.ts
  5. 0 40
      packages/server/src/api/auth/me/get.ts
  6. 0 98
      packages/server/src/api/auth/me/put.ts
  7. 0 124
      packages/server/src/api/auth/mini-login/post.ts
  8. 0 78
      packages/server/src/api/auth/register/create.ts
  9. 0 65
      packages/server/src/api/auth/sso-verify.ts
  10. 0 61
      packages/server/src/api/files/[id]/delete.ts
  11. 0 67
      packages/server/src/api/files/[id]/download.ts
  12. 0 63
      packages/server/src/api/files/[id]/get-url.ts
  13. 0 37
      packages/server/src/api/files/index.ts
  14. 0 123
      packages/server/src/api/files/multipart-complete/post.ts
  15. 0 116
      packages/server/src/api/files/multipart-policy/post.ts
  16. 0 90
      packages/server/src/api/files/upload-policy/post.ts
  17. 0 24
      packages/server/src/api/roles/index.ts
  18. 0 176
      packages/server/src/api/users/custom.ts
  19. 0 26
      packages/server/src/api/users/index.ts
  20. 0 38
      packages/server/src/data-source.ts
  21. 9 12
      packages/server/src/index.ts
  22. 0 38
      packages/server/src/middleware/auth.middleware.ts
  23. 0 97
      packages/server/src/modules/auth/auth.service.ts
  24. 0 133
      packages/server/src/modules/auth/mini-auth.service.ts
  25. 0 80
      packages/server/src/modules/files/file.entity.ts
  26. 0 92
      packages/server/src/modules/files/file.schema.ts
  27. 0 521
      packages/server/src/modules/files/file.service.ts
  28. 0 236
      packages/server/src/modules/files/minio.service.ts
  29. 0 30
      packages/server/src/modules/users/role.entity.ts
  30. 0 27
      packages/server/src/modules/users/role.schema.ts
  31. 0 20
      packages/server/src/modules/users/role.service.ts
  32. 0 64
      packages/server/src/modules/users/user.entity.ts
  33. 0 180
      packages/server/src/modules/users/user.schema.ts
  34. 0 139
      packages/server/src/modules/users/user.service.ts
  35. 0 46
      packages/server/src/share/types.ts
  36. 0 9
      packages/server/src/types/context.ts
  37. 1 1
      packages/server/src/utils/backup.ts
  38. 0 10
      packages/server/src/utils/concrete-crud.service.ts
  39. 0 21
      packages/server/src/utils/errorHandler.ts
  40. 0 460
      packages/server/src/utils/generic-crud.routes.ts
  41. 0 327
      packages/server/src/utils/generic-crud.service.ts
  42. 0 94
      packages/server/src/utils/jwt.util.ts
  43. 0 8
      packages/server/src/utils/logger.ts
  44. 0 59
      packages/server/src/utils/parseWithAwait.ts
  45. 1 1
      packages/server/src/utils/restore.ts

+ 6 - 0
packages/server/package.json

@@ -31,6 +31,12 @@
     "@asteasolutions/zod-to-openapi": "^8.1.0",
     "@hono/swagger-ui": "^0.5.0",
     "@hono/zod-openapi": "1.0.2",
+    "@d8d/shared-types": "workspace:*",
+    "@d8d/shared-utils": "workspace:*",
+    "@d8d/shared-crud": "workspace:*",
+    "@d8d/user-module": "workspace:*",
+    "@d8d/auth-module": "workspace:*",
+    "@d8d/file-module": "workspace:*",
     "axios": "^1.12.2",
     "bcrypt": "^6.0.0",
     "debug": "^4.4.3",

+ 0 - 19
packages/server/src/api/auth/index.ts

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

+ 0 - 100
packages/server/src/api/auth/login/post.ts

@@ -1,100 +0,0 @@
-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 '../../../modules/users/user.schema'
-import { parseWithAwait } from '../../../utils/parseWithAwait'
-
-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.omit({ password: true })
-
-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
-        }
-      }
-    },
-    500: {
-      description: '服务器内部错误',
-      content: {
-        'application/json': {
-          schema: ErrorSchema
-        }
-      }
-    }
-  }
-})
-const app = new OpenAPIHono<AuthContext>().openapi(loginRoute, async (c) => {
-  try {
-    const { username, password } = c.req.valid('json')
-    const result = await authService.login(username, password)
-
-    return c.json(await parseWithAwait(TokenResponseSchema, result), 200)
-  } catch (error) {
-    // 认证相关错误返回401
-    if (error instanceof Error &&
-        (error.message.includes('User not found') ||
-         error.message.includes('Invalid password') ||
-         error.message.includes('User account is disabled'))) {
-      return c.json(
-        {
-          code: 401,
-          message: error.message.includes('User account is disabled') ? '账户已禁用' : '用户名或密码错误'
-        },
-        401
-      )
-    }
-
-    // 其他错误重新抛出,由错误处理中间件处理
-    throw error
-  }
-});
-
-export default app

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

@@ -1,68 +0,0 @@
-import { createRoute, OpenAPIHono } from '@hono/zod-openapi';
-import { z } from '@hono/zod-openapi'
-import { AuthContext } from '../../types/context';
-import { authMiddleware } from '../../middleware/auth.middleware';
-import { AppDataSource } from '../../data-source';
-import { AuthService } from '../../modules/auth/auth.service';
-import { UserService } from '../../modules/users/user.service';
-import { ErrorSchema } from '../../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;

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

@@ -1,40 +0,0 @@
-import { createRoute, OpenAPIHono } from '@hono/zod-openapi'
-import { ErrorSchema } from '../../../utils/errorHandler'
-import { authMiddleware } from '../../../middleware/auth.middleware'
-import { AuthContext } from '../../../types/context'
-import { UserSchema } from '../../../modules/users/user.schema'
-
-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

+ 0 - 98
packages/server/src/api/auth/me/put.ts

@@ -1,98 +0,0 @@
-import { createRoute, OpenAPIHono } from '@hono/zod-openapi'
-import { ErrorSchema } from '../../../utils/errorHandler'
-import { authMiddleware } from '../../../middleware/auth.middleware'
-import { AuthContext } from '../../../types/context'
-import { UserSchema , UpdateUserDto} from '../../../modules/users/user.schema'
-import { UserService } from '../../../modules/users/user.service'
-import { AppDataSource } from '../../../data-source'
-import { parseWithAwait } from '../../../utils/parseWithAwait'
-
-// 定义响应schema,排除密码
-const UserResponseSchema = UserSchema.omit({
-  password: true
-})
-
-const routeDef = createRoute({
-  method: 'put',
-  path: '/me',
-  middleware: [authMiddleware],
-  request: {
-    body: {
-      content: {
-        'application/json': {
-          schema: UpdateUserDto
-        }
-      }
-    }
-  },
-  responses: {
-    200: {
-      description: '用户信息更新成功',
-      content: {
-        'application/json': {
-          schema: UserResponseSchema
-        }
-      }
-    },
-    400: {
-      description: '请求参数错误',
-      content: {
-        'application/json': {
-          schema: ErrorSchema
-        }
-      }
-    },
-    401: {
-      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 user = c.get('user')
-    const updateData = c.req.valid('json')
-    
-    const userService = new UserService(AppDataSource)
-    
-    // 更新用户信息
-    const updatedUser = await userService.updateUser(user.id, updateData)
-    
-    if (!updatedUser) {
-      return c.json({ code: 404, message: '用户不存在' }, 404)
-    }
-    
-    // 返回更新后的用户信息(不包含密码)
-    return c.json(await parseWithAwait(UserResponseSchema, updatedUser), 200)
-    
-  } catch (error) {
-    console.error('更新用户信息失败:', error)
-    return c.json({ 
-      code: 500, 
-      message: error instanceof Error ? error.message : '更新用户信息失败' 
-    }, 500)
-  }
-})
-
-export default app

+ 0 - 124
packages/server/src/api/auth/mini-login/post.ts

@@ -1,124 +0,0 @@
-import { createRoute, OpenAPIHono } from '@hono/zod-openapi';
-import { z } from '@hono/zod-openapi';
-import { MiniAuthService } from '../../../modules/auth/mini-auth.service';
-import { AppDataSource } from '../../../data-source';
-import { ErrorSchema } from '../../../utils/errorHandler';
-import { UserEntity } from '../../../modules/users/user.entity';
-
-const MiniLoginSchema = z.object({
-  code: z.string().openapi({
-    example: '08123456789012345678901234567890',
-    description: '小程序登录code'
-  }),
-  userInfo: z.object({
-    nickName: z.string().optional(),
-    avatarUrl: z.string().optional()
-  }).optional()
-});
-
-const MiniLoginResponseSchema = z.object({
-  token: z.string().openapi({
-    example: 'jwt.token.here',
-    description: 'JWT Token'
-  }),
-  user: z.object({
-    id: z.number(),
-    username: z.string(),
-    nickname: z.string().nullable(),
-    phone: z.string().nullable(),
-    email: z.string().nullable(),
-    avatarFileId: z.number().nullable(),
-    registrationSource: z.string()
-  }),
-  isNewUser: z.boolean().openapi({
-    example: true,
-    description: '是否为新注册用户'
-  })
-});
-
-const miniLoginRoute = createRoute({
-  method: 'post',
-  path: '/mini-login',
-  request: {
-    body: {
-      content: {
-        'application/json': {
-          schema: MiniLoginSchema
-        }
-      }
-    }
-  },
-  responses: {
-    200: {
-      description: '小程序登录成功',
-      content: {
-        'application/json': {
-          schema: MiniLoginResponseSchema
-        }
-      }
-    },
-    400: {
-      description: '参数错误',
-      content: {
-        'application/json': {
-          schema: ErrorSchema
-        }
-      }
-    },
-    500: {
-      description: '服务器错误',
-      content: {
-        'application/json': {
-          schema: ErrorSchema
-        }
-      }
-    }
-  }
-});
-
-const miniAuthService = new MiniAuthService(AppDataSource);
-
-const app = new OpenAPIHono().openapi(miniLoginRoute, async (c) => {
-  try {
-    const { code, userInfo } = c.req.valid('json');
-    
-    const result = await miniAuthService.miniLogin(code);
-    
-    // 如果有用户信息,更新用户资料
-    if (userInfo) {
-      await miniAuthService.updateUserProfile(result.user.id, {
-        nickname: userInfo.nickName,
-        avatarUrl: userInfo.avatarUrl
-      });
-      
-      // 重新获取更新后的用户信息
-      const updatedUser = await AppDataSource.getRepository(UserEntity).findOne({
-        where: { id: result.user.id },
-        relations: ['avatarFile']
-      });
-      
-      if (updatedUser) {
-        result.user = updatedUser;
-      }
-    }
-    
-    return c.json({
-      token: result.token,
-      user: {
-        id: result.user.id,
-        username: result.user.username,
-        nickname: result.user.nickname,
-        phone: result.user.phone,
-        email: result.user.email,
-        avatarFileId: result.user.avatarFileId,
-        registrationSource: result.user.registrationSource
-      },
-      isNewUser: result.isNewUser
-    }, 200);
-  } catch (error) {
-    const { code = 500, message = '登录失败' } = error as Error & { code?: number };
-    return c.json({ code, message }, 500);
-  }
-});
-
-export default app;

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

@@ -1,78 +0,0 @@
-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'
-import { UserSchema } from '../../../modules/users/user.schema'
-import { parseWithAwait } from '../../../utils/parseWithAwait'
-
-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: UserSchema.omit({ password: true })
-})
-
-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: await parseWithAwait(UserSchema, user) 
-  }, 201)
-})
-export default app

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

@@ -1,65 +0,0 @@
-import { createRoute, OpenAPIHono } from '@hono/zod-openapi'
-import { AuthService } from '../../modules/auth/auth.service'
-import { UserService } from '../../modules/users/user.service'
-import { ErrorSchema } from '../../utils/errorHandler'
-import { AppDataSource } from '../../data-source'
-
-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 - 61
packages/server/src/api/files/[id]/delete.ts

@@ -1,61 +0,0 @@
-import { createRoute, OpenAPIHono, z } from '@hono/zod-openapi';
-import { FileService } from '../../../modules/files/file.service';
-import { ErrorSchema } from '../../../utils/errorHandler';
-import { AppDataSource } from '../../../data-source';
-import { AuthContext } from '../../../types/context';
-import { authMiddleware } from '../../../middleware/auth.middleware';
-
-// 删除文件路由
-const deleteFileRoute = createRoute({
-  method: 'delete',
-  path: '/{id}',
-  middleware: [authMiddleware],
-  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: z.object({
-            success: z.boolean().openapi({ example: true }),
-            message: z.string().openapi({ example: '文件删除成功' })
-          })
-        }
-      }
-    },
-    404: {
-      description: '文件不存在',
-      content: { 'application/json': { schema: ErrorSchema } }
-    },
-    500: {
-      description: '服务器错误',
-      content: { 'application/json': { schema: ErrorSchema } }
-    }
-  }
-});
-
-// 创建路由实例
-const app = new OpenAPIHono<AuthContext>().openapi(deleteFileRoute, async (c) => {
-  try {
-    const { id } = c.req.valid('param');
-
-    // 创建文件服务实例
-    const fileService = new FileService(AppDataSource);
-    await fileService.deleteFile(id);
-    return c.json({ success: true, message: '文件删除成功' }, 200);
-  } catch (error) {
-    const message = error instanceof Error ? error.message : '文件删除失败';
-    const code = (error instanceof Error && error.message === '文件不存在') ? 404 : 500;
-    return c.json({ code, message }, code);
-  }
-});
-
-export default app;

+ 0 - 67
packages/server/src/api/files/[id]/download.ts

@@ -1,67 +0,0 @@
-import { createRoute, OpenAPIHono, z } from '@hono/zod-openapi';
-import { FileService } from '../../../modules/files/file.service';
-import { ErrorSchema } from '../../../utils/errorHandler';
-import { AppDataSource } from '../../../data-source';
-import { AuthContext } from '../../../types/context';
-import { authMiddleware } from '../../../middleware/auth.middleware';
-
-// 获取文件下载URL路由
-const downloadFileRoute = createRoute({
-  method: 'get',
-  path: '/{id}/download',
-  middleware: [authMiddleware],
-  request: {
-    params: z.object({
-      id: z.coerce.number().openapi({
-        param: { name: 'id', in: 'path' },
-        example: 1,
-        description: '文件ID'
-      })
-    })
-  },
-  responses: {
-    200: {
-      description: '获取文件下载URL成功',
-      content: {
-        'application/json': {
-          schema: z.object({
-            url: z.string().url().openapi({
-              description: '文件下载URL(带Content-Disposition头)',
-              example: 'https://minio.example.com/bucket/file-key?response-content-disposition=attachment%3B%20filename%3D%22example.jpg%22'
-            }),
-            filename: z.string().openapi({
-              description: '原始文件名',
-              example: 'example.jpg'
-            })
-          })
-        }
-      }
-    },
-    404: {
-      description: '文件不存在',
-      content: { 'application/json': { schema: ErrorSchema } }
-    },
-    500: {
-      description: '服务器错误',
-      content: { 'application/json': { schema: ErrorSchema } }
-    }
-  }
-});
-
-// 创建路由实例
-const app = new OpenAPIHono<AuthContext>().openapi(downloadFileRoute, async (c) => {
-  try {
-    const { id } = c.req.valid('param');
-
-    // 创建文件服务实例
-    const fileService = new FileService(AppDataSource);
-    const result = await fileService.getFileDownloadUrl(id);
-    return c.json(result, 200);
-  } catch (error) {
-    const message = error instanceof Error ? error.message : '获取文件下载URL失败';
-    const code = (error instanceof Error && error.message === '文件不存在') ? 404 : 500;
-    return c.json({ code, message }, code);
-  }
-});
-
-export default app;

+ 0 - 63
packages/server/src/api/files/[id]/get-url.ts

@@ -1,63 +0,0 @@
-import { createRoute, OpenAPIHono, z } from '@hono/zod-openapi';
-import { FileService } from '../../../modules/files/file.service';
-import { ErrorSchema } from '../../../utils/errorHandler';
-import { AppDataSource } from '../../../data-source';
-import { AuthContext } from '../../../types/context';
-import { authMiddleware } from '../../../middleware/auth.middleware';
-
-// 获取文件URL路由
-const getFileUrlRoute = createRoute({
-  method: 'get',
-  path: '/{id}/url',
-  middleware: [authMiddleware],
-  request: {
-    params: z.object({
-      id: z.coerce.number().openapi({
-        param: { name: 'id', in: 'path' },
-        example: 1,
-        description: '文件ID'
-      })
-    })
-  },
-  responses: {
-    200: {
-      description: '获取文件URL成功',
-      content: {
-        'application/json': {
-          schema: z.object({
-            url: z.string().url().openapi({
-              description: '文件访问URL',
-              example: 'https://minio.example.com/bucket/file-key'
-            })
-          })
-        }
-      }
-    },
-    404: {
-      description: '文件不存在',
-      content: { 'application/json': { schema: ErrorSchema } }
-    },
-    500: {
-      description: '服务器错误',
-      content: { 'application/json': { schema: ErrorSchema } }
-    }
-  }
-});
-
-
-// 创建路由实例
-const app = new OpenAPIHono<AuthContext>().openapi(getFileUrlRoute, async (c) => {
-  try {
-    const { id } = c.req.valid('param');
-    // 创建文件服务实例
-    const fileService = new FileService(AppDataSource);
-    const url = await fileService.getFileUrl(id);
-    return c.json({ url }, 200);
-  } catch (error) {
-    const message = error instanceof Error ? error.message : '获取文件URL失败';
-    const code = (error instanceof Error && error.message === '文件不存在') ? 404 : 500;
-    return c.json({ code, message }, code);
-  }
-});
-
-export default app;

+ 0 - 37
packages/server/src/api/files/index.ts

@@ -1,37 +0,0 @@
-import { OpenAPIHono } from '@hono/zod-openapi';
-import uploadPolicyRoute from './upload-policy/post';
-import multipartPolicyRoute from './multipart-policy/post';
-import completeMultipartRoute from './multipart-complete/post';
-import getUrlRoute from './[id]/get-url';
-import deleteRoute from './[id]/delete';
-import downloadRoute from './[id]/download';
-import { AuthContext } from '../../types/context';
-
-import { createCrudRoutes } from '../../utils/generic-crud.routes';
-import { File } from '../../modules/files/file.entity';
-import { FileSchema, CreateFileDto, UpdateFileDto } from '../../modules/files/file.schema';
-import { authMiddleware } from '../../middleware/auth.middleware';
-
-const fileRoutes = createCrudRoutes({
-  entity: File,
-  createSchema: CreateFileDto,
-  updateSchema: UpdateFileDto,
-  getSchema: FileSchema,
-  listSchema: FileSchema,
-  searchFields: ['name', 'type', 'description'],
-  relations: ['uploadUser'],
-  middleware: [authMiddleware]
-})
-
-
-// 创建路由实例并聚合所有子路由
-const app = new OpenAPIHono<AuthContext>()
-.route('/upload-policy', uploadPolicyRoute)
-.route('/multipart-policy', multipartPolicyRoute)
-.route('/multipart-complete', completeMultipartRoute)
-.route('/', getUrlRoute)
-.route('/', downloadRoute)
-.route('/', deleteRoute)
-.route('/', fileRoutes)
-
-export default app;

+ 0 - 123
packages/server/src/api/files/multipart-complete/post.ts

@@ -1,123 +0,0 @@
-import { createRoute, OpenAPIHono, z } from '@hono/zod-openapi';
-import { FileService } from '../../../modules/files/file.service';
-import { ErrorSchema } from '../../../utils/errorHandler';
-import { AppDataSource } from '../../../data-source';
-import { AuthContext } from '../../../types/context';
-import { authMiddleware } from '../../../middleware/auth.middleware';
-
-// 完成分片上传请求Schema
-const CompleteMultipartUploadDto = z.object({
-  uploadId: z.string().openapi({
-    description: '分片上传ID',
-    example: '123e4567-e89b-12d3-a456-426614174000'
-  }),
-  bucket: z.string().openapi({
-    description: '存储桶名称',
-    example: 'my-bucket'
-  }),
-  key: z.string().openapi({
-    description: '文件键名',
-    example: 'documents/report.pdf'
-  }),
-  parts: z.array(
-    z.object({
-      partNumber: z.coerce.number().int().positive().openapi({
-        description: '分片序号',
-        example: 1
-      }),
-      etag: z.string().openapi({
-        description: '分片ETag值',
-        example: 'd41d8cd98f00b204e9800998ecf8427e'
-      })
-    })
-  ).openapi({
-    description: '分片信息列表',
-    example: [
-      { partNumber: 1, etag: 'd41d8cd98f00b204e9800998ecf8427e' },
-      { partNumber: 2, etag: '5f4dcc3b5aa765d61d8327deb882cf99' }
-    ]
-  })
-});
-
-// 完成分片上传响应Schema
-const CompleteMultipartUploadResponse = z.object({
-  fileId: z.number().openapi({
-    description: '文件ID',
-    example: 123456
-  }),
-  url: z.string().openapi({
-    description: '文件访问URL',
-    example: 'https://minio.example.com/my-bucket/documents/report.pdf'
-  }),
-  host: z.string().openapi({
-    description: 'MinIO主机地址',
-    example: 'minio.example.com'
-  }),
-  bucket: z.string().openapi({
-    description: '存储桶名称',
-    example: 'my-bucket'
-  }),
-  key: z.string().openapi({
-    description: '文件键名',
-    example: 'documents/report.pdf'
-  }),
-  size: z.number().openapi({
-    description: '文件大小(字节)',
-    example: 102400
-  })
-});
-
-// 创建完成分片上传路由定义
-const completeMultipartUploadRoute = createRoute({
-  method: 'post',
-  path: '/',
-  middleware: [authMiddleware],
-  request: {
-    body: {
-      content: {
-        'application/json': { schema: CompleteMultipartUploadDto }
-      }
-    }
-  },
-  responses: {
-    200: {
-      description: '完成分片上传成功',
-      content: {
-        'application/json': { schema: CompleteMultipartUploadResponse }
-      }
-    },
-    400: {
-      description: '请求参数错误',
-      content: { 'application/json': { schema: ErrorSchema } }
-    },
-    500: {
-      description: '服务器错误',
-      content: { 'application/json': { schema: ErrorSchema } }
-    }
-  }
-});
-
-// 创建路由实例并实现处理逻辑
-const app = new OpenAPIHono<AuthContext>().openapi(completeMultipartUploadRoute, async (c) => {
-  try {
-    const data = await c.req.json();
-
-    // 初始化FileService
-    const fileService = new FileService(AppDataSource);
-    const result = await fileService.completeMultipartUpload(data);
-    
-    // 构建完整的响应包含host和bucket信息
-    const response = {
-      ...result,
-      host: `${process.env.MINIO_USE_SSL ? 'https' : 'http'}://${process.env.MINIO_ENDPOINT}:${process.env.MINIO_PORT}`,
-      bucket: data.bucket
-    };
-    
-    return c.json(response, 200);
-  } catch (error) {
-    const message = error instanceof Error ? error.message : '完成分片上传失败';
-    return c.json({ code: 500, message }, 500);
-  }
-});
-
-export default app;

+ 0 - 116
packages/server/src/api/files/multipart-policy/post.ts

@@ -1,116 +0,0 @@
-import { createRoute, OpenAPIHono, z } from '@hono/zod-openapi';
-import { FileService } from '../../../modules/files/file.service';
-import { ErrorSchema } from '../../../utils/errorHandler';
-import { AppDataSource } from '../../../data-source';
-import { AuthContext } from '../../../types/context';
-import { authMiddleware } from '../../../middleware/auth.middleware';
-
-// 创建分片上传策略请求Schema
-const CreateMultipartUploadPolicyDto = z.object({
-fileKey: z.string().openapi({
-  description: '文件键名',
-  example: 'documents/report.pdf'
-}),
-totalSize: z.coerce.number().int().positive().openapi({
-  description: '文件总大小(字节)',
-  example: 10485760
-}),
-partSize: z.coerce.number().int().positive().openapi({
-  description: '分片大小(字节)',
-  example: 5242880
-}),
-type: z.string().max(50).nullable().optional().openapi({
-  description: '文件类型',
-  example: 'application/pdf'
-}),
-name: z.string().max(255).openapi({
-  description: '文件名称',
-  example: '项目计划书.pdf'
-})
-});
-
-// 创建分片上传策略路由定义
-const createMultipartUploadPolicyRoute = createRoute({
-  method: 'post',
-  path: '/',
-  middleware: [authMiddleware],
-  request: {
-    body: {
-      content: {
-        'application/json': { schema: CreateMultipartUploadPolicyDto }
-      }
-    }
-  },
-  responses: {
-    200: {
-      description: '生成分片上传策略成功',
-      content: {
-        'application/json': {
-          schema: z.object({
-            uploadId: z.string().openapi({
-              description: '分片上传ID',
-              example: '123e4567-e89b-12d3-a456-426614174000'
-            }),
-            bucket: z.string().openapi({
-              description: '存储桶名称',
-              example: 'my-bucket'
-            }),
-            key: z.string().openapi({
-              description: '文件键名',
-              example: 'documents/report.pdf'
-            }),
-            host: z.string().openapi({
-              description: 'MinIO主机地址',
-              example: 'minio.example.com'
-            }),
-            partUrls: z.array(z.string()).openapi({
-              description: '分片上传URL列表',
-              example: [
-                'https://minio.example.com/my-bucket/documents/report.pdf?uploadId=123e4567-e89b-12d3-a456-426614174000&partNumber=1',
-                'https://minio.example.com/my-bucket/documents/report.pdf?uploadId=123e4567-e89b-12d3-a456-426614174000&partNumber=2'
-              ]
-            })
-          })
-        }
-      }
-    },
-    400: {
-      description: '请求参数错误',
-      content: { 'application/json': { schema: ErrorSchema } }
-    },
-    500: {
-      description: '服务器错误',
-      content: { 'application/json': { schema: ErrorSchema } }
-    }
-  }
-});
-
-
-// 创建路由实例
-const app = new OpenAPIHono<AuthContext>().openapi(createMultipartUploadPolicyRoute, async (c) => {
-try {
-  const data = await c.req.json();
-  const user = c.get('user');
-  // 计算分片数量
-  const partCount = Math.ceil(data.totalSize / data.partSize);
-  // 创建文件服务实例
-  const fileService = new FileService(AppDataSource);
-  const result = await fileService.createMultipartUploadPolicy({
-    ...data,
-    uploadUserId: user.id
-  }, partCount);
-  
-  return c.json({
-    uploadId: result.uploadId,
-    bucket: result.bucket,
-    key: result.key,
-    host: `${process.env.MINIO_USE_SSL ? 'https' : 'http'}://${process.env.MINIO_ENDPOINT}:${process.env.MINIO_PORT}`,
-    partUrls: result.uploadUrls
-  }, 200);
-} catch (error) {
-  const message = error instanceof Error ? error.message : '生成分片上传策略失败';
-  return c.json({ code: 500, message }, 500);
-}
-});
-
-export default app;

+ 0 - 90
packages/server/src/api/files/upload-policy/post.ts

@@ -1,90 +0,0 @@
-import { createRoute, OpenAPIHono, z } from '@hono/zod-openapi';
-import { FileService } from '../../../modules/files/file.service';
-import { FileSchema, CreateFileDto } from '../../../modules/files/file.schema';
-import { ErrorSchema } from '../../../utils/errorHandler';
-import { AppDataSource } from '../../../data-source';
-import { AuthContext } from '../../../types/context';
-import { authMiddleware } from '../../../middleware/auth.middleware';
-import { parseWithAwait } from '../../../utils/parseWithAwait';
-
-
-const CreateFileResponseSchema = z.object({
-            file: FileSchema,
-            uploadPolicy: z.object({
-              'x-amz-algorithm': z.string(),
-              'x-amz-credential': z.string(),
-              'x-amz-date': z.string(),
-              'x-amz-security-token': z.string().optional(),
-              policy: z.string(),
-              'x-amz-signature': z.string(),
-              host: z.string(),
-              key: z.string(),
-              bucket: z.string()
-            })
-          });
-
-// 创建文件上传策略路由
-const createUploadPolicyRoute = createRoute({
-  method: 'post',
-  path: '/',
-  middleware: [authMiddleware],
-  request: {
-    body: {
-      content: {
-        'application/json': { schema: CreateFileDto }
-      }
-    }
-  },
-  responses: {
-    200: {
-      description: '生成文件上传策略成功',
-      content: {
-        'application/json': {
-          schema: CreateFileResponseSchema
-        }
-      }
-    },
-    400: {
-      description: '请求参数错误',
-      content: { 'application/json': { schema: ErrorSchema } }
-    },
-    500: {
-      description: '服务器错误',
-      content: { 'application/json': { schema: ErrorSchema } }
-    }
-  }
-});
-
-
-// 创建路由实例
-const app = new OpenAPIHono<AuthContext>().openapi(createUploadPolicyRoute, async (c) => {
-  try {
-    const data = await c.req.json();
-    const user = c.get('user');
-    
-    // 创建文件服务实例
-    const fileService = new FileService(AppDataSource);
-    
-    // 添加用户ID到文件数据
-    const fileData = {
-      ...data,
-      uploadUserId: user.id,
-      uploadTime: new Date()
-    };
-    const result = await fileService.createFile(fileData);
-    const typedResult = await parseWithAwait(CreateFileResponseSchema, result);
-    return c.json(typedResult, 200);
-  } catch (error) {
-    if (error instanceof z.ZodError) {
-      return c.json({
-        code: 400,
-        message: '参数错误',
-        errors: error.issues
-      }, 400);
-    }
-    const message = error instanceof Error ? error.message : '生成上传策略失败';
-    return c.json({ code: 500, message }, 500);
-  }
-});
-
-export default app;

+ 0 - 24
packages/server/src/api/roles/index.ts

@@ -1,24 +0,0 @@
-import { createCrudRoutes } from '../../utils/generic-crud.routes';
-import { Role } from '../../modules/users/role.entity';
-import { RoleSchema, CreateRoleDto, UpdateRoleDto } from '../../modules/users/role.schema';
-import { authMiddleware } from '../../middleware/auth.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;

+ 0 - 176
packages/server/src/api/users/custom.ts

@@ -1,176 +0,0 @@
-import { createRoute, OpenAPIHono } from '@hono/zod-openapi';
-import { z } from '@hono/zod-openapi';
-import { UserService } from '../../modules/users/user.service';
-import { authMiddleware } from '../../middleware/auth.middleware';
-import { ErrorSchema } from '../../utils/errorHandler';
-import { AppDataSource } from '../../data-source';
-import { AuthContext } from '../../types/context';
-import { CreateUserDto, UpdateUserDto, UserSchema } from '../../modules/users/user.schema';
-import { parseWithAwait } from '../../utils/parseWithAwait';
-
-// 创建用户路由 - 自定义业务逻辑(密码加密等)
-const createUserRoute = createRoute({
-  method: 'post',
-  path: '/',
-  middleware: [authMiddleware],
-  request: {
-    body: {
-      content: {
-        'application/json': { schema: CreateUserDto }
-      }
-    }
-  },
-  responses: {
-    201: {
-      description: '用户创建成功',
-      content: {
-        'application/json': { schema: UserSchema }
-      }
-    },
-    400: {
-      description: '参数错误',
-      content: { 'application/json': { schema: ErrorSchema } }
-    },
-    500: {
-      description: '创建用户失败',
-      content: { 'application/json': { schema: ErrorSchema } }
-    }
-  }
-});
-
-// 更新用户路由 - 自定义业务逻辑
-const updateUserRoute = createRoute({
-  method: 'put',
-  path: '/{id}',
-  middleware: [authMiddleware],
-  request: {
-    params: z.object({
-      id: z.coerce.number().openapi({
-        param: { name: 'id', in: 'path' },
-        example: 1,
-        description: '用户ID'
-      })
-    }),
-    body: {
-      content: {
-        'application/json': { schema: UpdateUserDto }
-      }
-    }
-  },
-  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 deleteUserRoute = createRoute({
-  method: 'delete',
-  path: '/{id}',
-  middleware: [authMiddleware],
-  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 app = new OpenAPIHono<AuthContext>()
-  .openapi(createUserRoute, async (c) => {
-    try {
-      const data = c.req.valid('json');
-      const userService = new UserService(AppDataSource);
-      const result = await userService.createUser(data);
-
-      return c.json(await parseWithAwait(UserSchema, result), 201);
-    } catch (error) {
-      if (error instanceof z.ZodError) {
-        return c.json({
-          code: 400,
-          message: '参数错误',
-          errors: error.issues
-        }, 400);
-      }
-      return c.json({
-        code: 500,
-        message: error instanceof Error ? error.message : '创建用户失败'
-      }, 500);
-    }
-  })
-  .openapi(updateUserRoute, async (c) => {
-    try {
-      const { id } = c.req.valid('param');
-      const data = c.req.valid('json');
-      const userService = new UserService(AppDataSource);
-      const result = await userService.updateUser(id, data);
-
-      if (!result) {
-        return c.json({ code: 404, message: '资源不存在' }, 404);
-      }
-
-      return c.json(await parseWithAwait(UserSchema, result), 200);
-    } catch (error) {
-      if (error instanceof z.ZodError) {
-        return c.json({
-          code: 400,
-          message: '参数错误',
-          errors: error.issues
-        }, 400);
-      }
-      return c.json({
-        code: 500,
-        message: error instanceof Error ? error.message : '更新用户失败'
-      }, 500);
-    }
-  })
-  .openapi(deleteUserRoute, async (c) => {
-    try {
-      const { id } = c.req.valid('param');
-      const userService = new UserService(AppDataSource);
-      const success = await userService.deleteUser(id);
-
-      if (!success) {
-        return c.json({ code: 404, message: '资源不存在' }, 404);
-      }
-
-      return c.body(null, 204);
-    } catch (error) {
-      return c.json({
-        code: 500,
-        message: error instanceof Error ? error.message : '删除用户失败'
-      }, 500);
-    }
-  });
-
-export default app;

+ 0 - 26
packages/server/src/api/users/index.ts

@@ -1,26 +0,0 @@
-import { OpenAPIHono } from '@hono/zod-openapi';
-import { createCrudRoutes } from '../../utils/generic-crud.routes';
-import { UserEntity } from '../../modules/users/user.entity';
-import { UserSchema, CreateUserDto, UpdateUserDto } from '../../modules/users/user.schema';
-import { authMiddleware } from '../../middleware/auth.middleware';
-import customRoutes from './custom';
-
-// 创建通用CRUD路由配置
-const userCrudRoutes = createCrudRoutes({
-  entity: UserEntity,
-  createSchema: CreateUserDto,
-  updateSchema: UpdateUserDto,
-  getSchema: UserSchema,
-  listSchema: UserSchema,
-  searchFields: ['username', 'nickname', 'phone', 'email'],
-  relations: ['roles'],
-  middleware: [authMiddleware],
-  readOnly: true // 创建/更新/删除使用自定义路由
-});
-
-// 创建混合路由应用
-const app = new OpenAPIHono()
-  .route('/', customRoutes)   // 自定义业务路由(创建/更新/删除)
-  .route('/', userCrudRoutes); // 通用CRUD路由(列表查询和获取详情)
-
-export default app;

+ 0 - 38
packages/server/src/data-source.ts

@@ -1,38 +0,0 @@
-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"
-import { File } from "./modules/files/file.entity"
-
-// 在测试环境下使用测试数据库配置
-const isTestEnv = process.env.NODE_ENV === 'test';
-const testDatabaseUrl = process.env.TEST_DATABASE_URL || 'postgresql://postgres:test_password@localhost:5432/test_d8dai';
-;
-
-const dataSource = isTestEnv && testDatabaseUrl
-  ? new DataSource({
-      type: "postgres",
-      url: testDatabaseUrl,
-      entities: [User, Role, File],
-      migrations: [],
-      synchronize: true, // 测试环境总是同步schema
-      dropSchema: true,  // 测试环境每次重新创建schema
-      logging: false,    // 测试环境关闭日志
-    })
-  : new DataSource({
-      type: "postgres",
-      host: process.env.DB_HOST || "localhost",
-      port: parseInt(process.env.DB_PORT || "5432"),
-      username: process.env.DB_USERNAME || "postgres",
-      password: process.env.DB_PASSWORD || "",
-      database: process.env.DB_DATABASE || "postgres",
-      entities: [User, Role, File],
-      migrations: [],
-      synchronize: process.env.DB_SYNCHRONIZE !== "false",
-      logging: process.env.DB_LOGGING === "true",
-    });
-
-export const AppDataSource = dataSource;

+ 9 - 12
packages/server/src/index.ts

@@ -1,12 +1,11 @@
 import { OpenAPIHono } from '@hono/zod-openapi'
 import { swaggerUI } from '@hono/swagger-ui'
-import { errorHandler } from './utils/errorHandler'
-import usersRouter from './api/users/index'
-import authRoute from './api/auth/index'
-import rolesRoute from './api/roles/index'
-import fileRoutes from './api/files/index'
-import { AuthContext } from './types/context'
-import { AppDataSource } from './data-source'
+import { errorHandler } from '@d8d/shared-utils'
+import { userRoutes as userModuleRoutes } from '@d8d/user-module'
+import { authRoutes as authModuleRoutes } from '@d8d/auth-module'
+import { fileRoutes as fileModuleRoutes } from '@d8d/file-module'
+import { AuthContext } from '@d8d/shared-types'
+import { AppDataSource } from '@d8d/shared-utils'
 import { Hono } from 'hono'
 import { databaseBackup } from './utils/backup'
 
@@ -105,14 +104,12 @@ if(1){
 
 
 
-export const userRoutes = api.route('/api/v1/users', usersRouter)
-export const authRoutes = api.route('/api/v1/auth', authRoute)
-export const roleRoutes = api.route('/api/v1/roles', rolesRoute)
-export const fileApiRoutes = api.route('/api/v1/files', fileRoutes)
+export const userRoutes = api.route('/api/v1/users', userModuleRoutes)
+export const authRoutes = api.route('/api/v1/auth', authModuleRoutes)
+export const fileApiRoutes = api.route('/api/v1/files', fileModuleRoutes)
 
 export type AuthRoutes = typeof authRoutes
 export type UserRoutes = typeof userRoutes
-export type RoleRoutes = typeof roleRoutes
 export type FileRoutes = typeof fileApiRoutes
 
 app.route('/', api)

+ 0 - 38
packages/server/src/middleware/auth.middleware.ts

@@ -1,38 +0,0 @@
-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';
-import { parseWithAwait } from '../utils/parseWithAwait';
-import { UserSchema } from '../modules/users/user.schema';
-
-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', await parseWithAwait(UserSchema, user));
-    c.set('token', token);
-    await next();
-  } catch (error) {
-    console.error('Authentication error:', error);
-    return c.json({ message: 'Invalid token' }, 401);
-  }
-}

+ 0 - 97
packages/server/src/modules/auth/auth.service.ts

@@ -1,97 +0,0 @@
-import { UserService } from '../users/user.service';
-import { UserEntity as User } from '../users/user.entity';
-import { DisabledStatus } from '../../share/types';
-import { JWTUtil } from '../../utils/jwt.util';
-import debug from 'debug';
-
-const logger = {
-  info: debug('backend:auth:info'),
-  error: debug('backend:auth:error')
-}
-
-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');
-      }
-
-      // 检查用户是否被禁用
-      if (user.isDisabled === DisabledStatus.DISABLED) {
-        throw new Error('User account is disabled');
-      }
-
-      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, expiresIn?: string): string {
-    return JWTUtil.generateToken(user, {}, expiresIn);
-  }
-
-  verifyToken(token: string): any {
-    return JWTUtil.verifyToken(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;
-    }
-  }
-}

+ 0 - 133
packages/server/src/modules/auth/mini-auth.service.ts

@@ -1,133 +0,0 @@
-import { DataSource, Repository } from 'typeorm';
-import { UserEntity } from '../users/user.entity';
-import { FileService } from '../files/file.service';
-import { JWTUtil } from '../../utils/jwt.util';
-import axios from 'axios';
-import process from 'node:process'
-
-export class MiniAuthService {
-  private userRepository: Repository<UserEntity>;
-  private fileService: FileService;
-  
-  constructor(dataSource: DataSource) {
-    this.userRepository = dataSource.getRepository(UserEntity);
-    this.fileService = new FileService(dataSource);
-  }
-
-  async miniLogin(code: string): Promise<{ token: string; user: UserEntity; isNewUser: boolean }> {
-    // 1. 通过code获取openid
-    const openidInfo = await this.getOpenIdByCode(code);
-    
-    // 2. 查找或创建用户
-    let user = await this.userRepository.findOne({ 
-      where: { openid: openidInfo.openid } 
-    });
-    
-    let isNewUser = false;
-    
-    if (!user) {
-      // 自动注册新用户
-      user = await this.createMiniUser(openidInfo);
-      isNewUser = true;
-    }
-    
-    // 3. 生成token
-    const token = this.generateToken(user);
-    
-    return { token, user, isNewUser };
-  }
-
-  async updateUserProfile(userId: number, profile: { nickname?: string; avatarUrl?: string }): Promise<UserEntity> {
-    const user = await this.userRepository.findOne({ 
-      where: { id: userId },
-      relations: ['avatarFile']
-    });
-    
-    if (!user) throw new Error('用户不存在');
-    
-    if (profile.nickname) user.nickname = profile.nickname;
-    
-    // 处理头像:如果用户没有头像且提供了小程序头像URL,则下载保存
-    if (profile.avatarUrl && !user.avatarFileId) {
-      try {
-        const avatarFileId = await this.downloadAndSaveAvatar(profile.avatarUrl, userId);
-        if (avatarFileId) {
-          user.avatarFileId = avatarFileId;
-        }
-      } catch (error) {
-        // 头像下载失败不影响主要功能
-        console.error('头像下载失败:', error);
-      }
-    }
-    
-    return await this.userRepository.save(user);
-  }
-
-  private async getOpenIdByCode(code: string): Promise<{ openid: string; unionid?: string; session_key: string }> {
-    const appId = process.env.WX_MINI_APP_ID;
-    const appSecret = process.env.WX_MINI_APP_SECRET;
-    
-    if (!appId || !appSecret) {
-      throw new Error('微信小程序配置缺失');
-    }
-    
-    const url = `https://api.weixin.qq.com/sns/jscode2session?appid=${appId}&secret=${appSecret}&js_code=${code}&grant_type=authorization_code`;
-    
-    try {
-      const response = await axios.get(url, { timeout: 10000 });
-      
-      if (response.data.errcode) {
-        throw new Error(`微信API错误: ${response.data.errmsg}`);
-      }
-      
-      return {
-        openid: response.data.openid,
-        unionid: response.data.unionid,
-        session_key: response.data.session_key
-      };
-    } catch (error) {
-      if (axios.isAxiosError(error)) {
-        throw new Error('微信服务器连接失败');
-      }
-      throw error;
-    }
-  }
-
-  private async createMiniUser(openidInfo: { openid: string; unionid?: string }): Promise<UserEntity> {
-    const user = this.userRepository.create({
-      username: `wx_${Date.now()}_${Math.random().toString(36).substr(2, 6)}`,
-      password: '', // 小程序用户不需要密码
-      openid: openidInfo.openid,
-      unionid: openidInfo.unionid,
-      nickname: '微信用户',
-      registrationSource: 'miniapp',
-      isDisabled: 0,
-      isDeleted: 0
-    });
-    
-    return await this.userRepository.save(user);
-  }
-
-  private async downloadAndSaveAvatar(avatarUrl: string, userId: number): Promise<number | null> {
-    try {
-      const result = await this.fileService.downloadAndSaveFromUrl(
-        avatarUrl,
-        {
-          uploadUserId: userId,
-          customPath: `avatars/`,
-          mimeType: 'image/jpeg'
-        },
-        { timeout: 10000 }
-      );
-      
-      return result.file.id;
-    } catch (error) {
-      console.error('下载保存头像失败:', error);
-      return null;
-    }
-  }
-
-  private generateToken(user: UserEntity): string {
-    return JWTUtil.generateToken(user);
-  }
-}

+ 0 - 80
packages/server/src/modules/files/file.entity.ts

@@ -1,80 +0,0 @@
-import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, JoinColumn } from 'typeorm';
-import { UserEntity } from '../users/user.entity';
-import process from 'node:process';
-import { MinioService } from './minio.service';
-
-@Entity('file')
-export class File {
-  @PrimaryGeneratedColumn({ name: 'id', type: 'int', unsigned: true })
-  id!: number;
-
-  @Column({ name: 'name', type: 'varchar', length: 255 })
-  name!: string;
-
-  @Column({ name: 'type', type: 'varchar', length: 50, nullable: true, comment: '文件类型' })
-  type!: string | null;
-
-  @Column({ name: 'size', type: 'int', unsigned: true, nullable: true, comment: '文件大小,单位字节' })
-  size!: number | null;
-
-  @Column({ name: 'path', type: 'varchar', length: 512, comment: '文件存储路径' })
-  path!: string;
-
-  // 获取完整的文件URL(包含MINIO_HOST前缀)
-  // get fullUrl(): string {
-  //   const protocol = process.env.MINIO_USE_SSL !== 'false' ? 'https' : 'http';
-  //   const port = process.env.MINIO_PORT ? `:${process.env.MINIO_PORT}` : '';
-  //   const host = process.env.MINIO_HOST || 'localhost';
-  //   const bucketName = process.env.MINIO_BUCKET_NAME || 'd8dai';
-  //   return `${protocol}://${host}${port}/${bucketName}/${this.path}`;
-  // }
-  get fullUrl(): Promise<string> {
-    // 创建MinioService实例
-    const minioService = new MinioService();
-    // 获取配置的桶名称
-    const bucketName = process.env.MINIO_BUCKET_NAME || 'd8dai';
-    
-    // 返回一个Promise,内部处理异步获取URL的逻辑
-    return new Promise((resolve, reject) => {
-      // 调用minioService的异步方法
-      minioService.getPresignedFileUrl(bucketName, this.path)
-        .then(url => {
-          // 成功获取URL后解析Promise
-          resolve(url);
-        })
-        .catch(error => {
-          // 处理可能的错误
-          console.error('获取文件预签名URL失败:', error);
-          reject(error); // 将错误传递出去
-        });
-    });
-  }
-  
-
-  @Column({ name: 'description', type: 'text', nullable: true, comment: '文件描述' })
-  description!: string | null;
-
-  @Column({ name: 'upload_user_id', type: 'int', unsigned: true })
-  uploadUserId!: number;
-
-  @ManyToOne(() => UserEntity)
-  @JoinColumn({ name: 'upload_user_id', referencedColumnName: 'id' })
-  uploadUser!: UserEntity;
-
-  @Column({ name: 'upload_time', type: 'timestamp' })
-  uploadTime!: Date;
-
-  @Column({ name: 'last_updated', type: 'timestamp', nullable: true, comment: '最后更新时间' })
-  lastUpdated!: Date | null;
-
-  @Column({ name: 'created_at', type: 'timestamp', default: () => 'CURRENT_TIMESTAMP' })
-  createdAt!: Date;
-
-  @Column({ 
-    name: 'updated_at', 
-    type: 'timestamp', 
-    default: () => 'CURRENT_TIMESTAMP', 
-    onUpdate: 'CURRENT_TIMESTAMP' 
-  })
-  updatedAt!: Date;
-}

+ 0 - 92
packages/server/src/modules/files/file.schema.ts

@@ -1,92 +0,0 @@
-import { z } from '@hono/zod-openapi';
-import { UserSchema } from '../users/user.schema';
-
-export const FileSchema = z.object({
-  id: z.number().int().positive().openapi({
-    description: '文件ID',
-    example: 1
-  }),
-  name: z.string().max(255).openapi({
-    description: '文件名称',
-    example: '项目计划书.pdf'
-  }),
-  type: z.string().max(50).nullable().openapi({
-    description: '文件类型',
-    example: 'application/pdf'
-  }),
-  size: z.number().int().positive().nullable().openapi({
-    description: '文件大小,单位字节',
-    example: 102400
-  }),
-  path: z.string().max(512).openapi({
-    description: '文件存储路径',
-    example: '/uploads/documents/2023/project-plan.pdf'
-  }),
-  fullUrl: z.url().openapi({
-    description: '完整文件访问URL',
-    example: 'https://minio.example.com/d8dai/uploads/documents/2023/project-plan.pdf'
-  }),
-  description: z.string().nullable().openapi({
-    description: '文件描述',
-    example: '2023年度项目计划书'
-  }),
-  uploadUserId: z.number().int().positive().openapi({
-    description: '上传用户ID',
-    example: 1
-  }),
-  uploadUser: UserSchema,
-  uploadTime: z.coerce.date().openapi({
-    description: '上传时间',
-    example: '2023-01-15T10:30:00Z'
-  }),
-  lastUpdated: z.date().nullable().openapi({
-    description: '最后更新时间',
-    example: '2023-01-16T14:20:00Z'
-  }),
-  createdAt: z.coerce.date().openapi({
-    description: '创建时间',
-    example: '2023-01-15T10:30:00Z'
-  }),
-  updatedAt: z.coerce.date().openapi({
-    description: '更新时间',
-    example: '2023-01-16T14:20:00Z'
-  })
-});
-
-export const CreateFileDto = z.object({
-  name: z.string().max(255).openapi({
-    description: '文件名称',
-    example: '项目计划书.pdf'
-  }),
-  type: z.string().max(50).nullable().optional().openapi({
-    description: '文件类型',
-    example: 'application/pdf'
-  }),
-  size: z.coerce.number().int().positive().nullable().optional().openapi({
-    description: '文件大小,单位字节',
-    example: 102400
-  }),
-  path: z.string().max(512).openapi({
-    description: '文件存储路径',
-    example: '/uploads/documents/2023/project-plan.pdf'
-  }),
-  description: z.string().nullable().optional().openapi({
-    description: '文件描述',
-    example: '2023年度项目计划书'
-  }),
-  lastUpdated: z.coerce.date().nullable().optional().openapi({
-    description: '最后更新时间',
-    example: '2023-01-16T14:20:00Z'
-  })
-});
-
-export const UpdateFileDto = z.object({
-  name: z.string().max(255).optional().openapi({
-    description: '文件名称',
-    example: '项目计划书_v2.pdf'
-  }),
-  description: z.string().nullable().optional().openapi({
-    description: '文件描述',
-    example: '2023年度项目计划书(修订版)'
-  })
-});

+ 0 - 521
packages/server/src/modules/files/file.service.ts

@@ -1,521 +0,0 @@
-import { GenericCrudService } from '../../utils/generic-crud.service';
-import { DataSource } from 'typeorm';
-import { File } from './file.entity';
-import { MinioService } from './minio.service';
-import { v4 as uuidv4 } from 'uuid';
-import { logger } from '../../utils/logger';
-
-export class FileService extends GenericCrudService<File> {
-  private readonly minioService: MinioService;
-
-  constructor(dataSource: DataSource) {
-    super(dataSource, File);
-    this.minioService = new MinioService();
-  }
-
-  /**
-   * 创建文件记录并生成预签名上传URL
-   */
-  async createFile(data: Partial<File>) {
-    try {
-      // 生成唯一文件存储路径
-      const fileKey = `${data.uploadUserId}/${uuidv4()}-${data.name}`;
-      // 生成MinIO上传策略
-      const uploadPolicy = await this.minioService.generateUploadPolicy(fileKey);
-
-      
-      // 准备文件记录数据
-      const fileData = {
-        ...data,
-        path: fileKey,
-        uploadTime: new Date(),
-        createdAt: new Date(),
-        updatedAt: new Date()
-      };
-      
-      // 保存文件记录到数据库
-      const savedFile = await this.create(fileData as File);
-      
-      // 返回文件记录和上传策略
-      return {
-        file: savedFile,
-        uploadPolicy
-      };
-    } catch (error) {
-      logger.error('Failed to create file:', error);
-      throw new Error('文件创建失败');
-    }
-  }
-
-  /**
-   * 删除文件记录及对应的MinIO文件
-   */
-  async deleteFile(id: number) {
-    // 获取文件记录
-    const file = await this.getById(id);
-    if (!file) {
-      throw new Error('文件不存在');
-    }
-
-    try {
-      // 验证文件是否存在于MinIO
-      const fileExists = await this.minioService.objectExists(this.minioService.bucketName, file.path);
-      if (!fileExists) {
-        logger.error(`File not found in MinIO: ${this.minioService.bucketName}/${file.path}`);
-        // 仍然继续删除数据库记录,但记录警告日志
-      } else {
-        // 从MinIO删除文件
-        await this.minioService.deleteObject(this.minioService.bucketName, file.path);
-      }
-
-      // 从数据库删除记录
-      await this.delete(id);
-
-      return true;
-    } catch (error) {
-      logger.error('Failed to delete file:', error);
-      throw new Error('文件删除失败');
-    }
-  }
-
-  /**
-   * 获取文件访问URL
-   */
-  async getFileUrl(id: number) {
-    const file = await this.getById(id);
-    if (!file) {
-      throw new Error('文件不存在');
-    }
-    
-    return this.minioService.getPresignedFileUrl(this.minioService.bucketName, file.path);
-  }
-
-  /**
-   * 获取文件下载URL(带Content-Disposition头)
-   */
-  async getFileDownloadUrl(id: number) {
-    const file = await this.getById(id);
-    if (!file) {
-      throw new Error('文件不存在');
-    }
-    
-    const url = await this.minioService.getPresignedFileDownloadUrl(
-      this.minioService.bucketName,
-      file.path,
-      file.name
-    );
-    
-    return {
-      url,
-      filename: file.name
-    };
-  }
-
-  /**
-   * 创建多部分上传策略
-   */
-  async createMultipartUploadPolicy(data: Partial<File>, partCount: number) {
-    try {
-      // 生成唯一文件存储路径
-      const fileKey = `${data.uploadUserId}/${uuidv4()}-${data.name}`;
-      
-      // 初始化多部分上传
-      const uploadId = await this.minioService.createMultipartUpload(
-        this.minioService.bucketName,
-        fileKey
-      );
-      
-      // 生成各部分上传URL
-      const uploadUrls = await this.minioService.generateMultipartUploadUrls(
-        this.minioService.bucketName,
-        fileKey,
-        uploadId,
-        partCount
-      );
-      
-      // 准备文件记录数据
-      const fileData = {
-        ...data,
-        path: fileKey,
-        uploadTime: new Date(),
-        createdAt: new Date(),
-        updatedAt: new Date()
-      };
-      
-      // 保存文件记录到数据库
-      const savedFile = await this.create(fileData as File);
-      
-      // 返回文件记录和上传策略
-      return {
-        file: savedFile,
-        uploadId,
-        uploadUrls,
-        bucket: this.minioService.bucketName,
-        key: fileKey
-      };
-    } catch (error) {
-      logger.error('Failed to create multipart upload policy:', error);
-      throw new Error('创建多部分上传策略失败');
-    }
-  }
-
-  /**
-   * 完成分片上传
-   */
-  async completeMultipartUpload(data: {
-    uploadId: string;
-    bucket: string;
-    key: string;
-    parts: Array<{ partNumber: number; etag: string }>;
-  }) {
-    logger.db('Starting multipart upload completion:', {
-      uploadId: data.uploadId,
-      bucket: data.bucket,
-      key: data.key,
-      partsCount: data.parts.length
-    });
-
-    // 查找文件记录
-    const file = await this.repository.findOneBy({ path: data.key });
-    if (!file) {
-      throw new Error('文件记录不存在');
-    }
-
-    try {
-      // 完成MinIO分片上传 - 注意格式转换
-      const result = await this.minioService.completeMultipartUpload(
-        data.bucket,
-        data.key,
-        data.uploadId,
-        data.parts.map(part => ({ PartNumber: part.partNumber, ETag: part.etag }))
-      );
-
-      // 更新文件大小等信息
-      file.size = result.size;
-      file.updatedAt = new Date();
-      await this.repository.save(file);
-
-      // 生成文件访问URL
-      const url = this.minioService.getFileUrl(data.bucket, data.key);
-
-      logger.db('Multipart upload completed successfully:', {
-        fileId: file.id,
-        size: result.size,
-        key: data.key
-      });
-
-      return {
-        fileId: file.id,
-        url,
-        key: data.key,
-        size: result.size
-      };
-    } catch (error) {
-      logger.error('Failed to complete multipart upload:', error);
-      throw new Error('完成分片上传失败');
-    }
-  }
-
-  /**
-   * 保存文件记录并将文件内容直接上传到MinIO
-   * @param fileData - 文件基础信息
-   * @param fileContent - 文件内容(Buffer)
-   * @param contentType - 文件MIME类型
-   * @returns 保存的文件记录和文件访问URL
-   */
-  async saveFile(
-    fileData: {
-      name: string;
-      size: number;
-      mimeType: string;
-      uploadUserId: number;
-      [key: string]: any;
-    },
-    fileContent: Buffer,
-    contentType?: string
-  ) {
-    try {
-      logger.db('Starting saveFile process:', {
-        filename: fileData.name,
-        size: fileData.size,
-        mimeType: fileData.mimeType,
-        uploadUserId: fileData.uploadUserId
-      });
-
-      // 生成唯一文件存储路径
-      const fileKey = `${fileData.uploadUserId}/${uuidv4()}-${fileData.name}`;
-
-      // 确保存储桶存在
-      await this.minioService.ensureBucketExists();
-
-      // 直接上传文件内容到MinIO
-      const fileUrl = await this.minioService.createObject(
-        this.minioService.bucketName,
-        fileKey,
-        fileContent,
-        contentType || fileData.mimeType
-      );
-
-      // 准备文件记录数据
-      const completeFileData = {
-        ...fileData,
-        path: fileKey,
-        uploadTime: new Date(),
-      };
-
-      // 保存文件记录到数据库
-      const savedFile = await this.create(completeFileData as any);
-
-      logger.db('File saved successfully:', {
-        fileId: savedFile.id,
-        filename: savedFile.name,
-        size: savedFile.size,
-        url: fileUrl
-      });
-
-      return {
-        file: savedFile,
-        url: fileUrl
-      };
-    } catch (error) {
-      logger.error('Failed to save file:', error);
-      throw new Error(`文件保存失败: ${error instanceof Error ? error.message : '未知错误'}`);
-    }
-  }
-
-  /**
-   * 保存文件记录并将文件内容直接上传到MinIO(支持自定义存储路径)
-   * @param fileData - 文件基础信息
-   * @param fileContent - 文件内容(Buffer)
-   * @param customPath - 自定义存储路径(可选)
-   * @param contentType - 文件MIME类型
-   * @returns 保存的文件记录和文件访问URL
-   */
-  async saveFileWithCustomPath(
-    fileData: {
-      name: string;
-      size: number;
-      mimeType: string;
-      uploadUserId: number;
-      [key: string]: any;
-    },
-    fileContent: Buffer,
-    customPath?: string,
-    contentType?: string
-  ) {
-    try {
-      logger.db('Starting saveFileWithCustomPath process:', {
-        filename: fileData.name,
-        size: fileData.size,
-        mimeType: fileData.mimeType,
-        uploadUserId: fileData.uploadUserId,
-        customPath: customPath || 'auto-generated'
-      });
-
-      // 使用自定义路径或生成唯一文件存储路径
-      const fileKey = customPath || `${fileData.uploadUserId}/${uuidv4()}-${fileData.name}`;
-
-      // 确保存储桶存在
-      await this.minioService.ensureBucketExists();
-
-      // 直接上传文件内容到MinIO
-      const fileUrl = await this.minioService.createObject(
-        this.minioService.bucketName,
-        fileKey,
-        fileContent,
-        contentType || fileData.mimeType
-      );
-
-      // 准备文件记录数据
-      const completeFileData = {
-        ...fileData,
-        path: fileKey,
-        uploadTime: new Date(),
-        // createdAt: new Date(),
-        // updatedAt: new Date()
-      };
-
-      // 保存文件记录到数据库
-      const savedFile = await this.create(completeFileData as any);
-
-      logger.db('File saved with custom path successfully:', {
-        fileId: savedFile.id,
-        filename: savedFile.name,
-        size: savedFile.size,
-        path: fileKey,
-        url: fileUrl
-      });
-
-      return {
-        file: savedFile,
-        url: fileUrl
-      };
-    } catch (error) {
-      logger.error('Failed to save file with custom path:', error);
-      throw new Error(`文件保存失败: ${error instanceof Error ? error.message : '未知错误'}`);
-    }
-  }
-
-  /**
-   * 从URL下载文件并保存到MinIO
-   * @param url - 文件URL
-   * @param fileData - 文件基础信息(不含name和size,将自动获取)
-   * @param options - 可选配置
-   * @returns 保存的文件记录和文件访问URL
-   */
-  async downloadAndSaveFromUrl(
-    url: string,
-    fileData: {
-      uploadUserId: number;
-      mimeType?: string;
-      customFileName?: string;
-      customPath?: string;
-      [key: string]: any;
-    },
-    options?: {
-      timeout?: number;
-      retries?: number;
-    }
-  ) {
-    try {
-      const axios = require('axios');
-      
-      logger.db('Starting downloadAndSaveFromUrl process:', {
-        url,
-        uploadUserId: fileData.uploadUserId,
-        customFileName: fileData.customFileName,
-        customPath: fileData.customPath
-      });
-
-      // 下载文件
-      const response = await axios.get(url, {
-        responseType: 'arraybuffer',
-        timeout: options?.timeout || 30000,
-        maxRedirects: 5,
-        headers: {
-          'User-Agent': 'Mozilla/5.0 (compatible; FileDownloader/1.0)'
-        }
-      });
-
-      const buffer = Buffer.from(response.data);
-      
-      // 从URL或响应头中获取文件名
-      let fileName = fileData.customFileName;
-      if (!fileName) {
-        // 尝试从Content-Disposition头获取文件名
-        const contentDisposition = response.headers['content-disposition'];
-        if (contentDisposition) {
-          const filenameMatch = contentDisposition.match(/filename[*]?=(?:utf-8'')?(.+)/i);
-          if (filenameMatch) {
-            fileName = decodeURIComponent(filenameMatch[1].replace(/['"]/g, ''));
-          }
-        }
-        
-        // 从URL路径获取文件名
-        if (!fileName) {
-          const urlPath = new URL(url).pathname;
-          fileName = urlPath.split('/').pop() || `file_${Date.now()}`;
-        }
-      }
-
-      // 确保文件有扩展名
-      if (!fileName.includes('.') && fileData.mimeType) {
-        const ext = this.getExtensionFromMimeType(fileData.mimeType);
-        if (ext) {
-          fileName += `.${ext}`;
-        }
-      }
-
-      // 确定MIME类型
-      let mimeType = fileData.mimeType || response.headers['content-type'];
-      if (!mimeType || mimeType === 'application/octet-stream') {
-        mimeType = this.inferMimeType(fileName);
-      }
-
-      // 保存文件
-      const saveResult = await this.saveFileWithCustomPath(
-        {
-          ...fileData,
-          name: fileName,
-          size: buffer.length,
-          mimeType,
-          fileType: this.getFileTypeFromMimeType(mimeType)
-        },
-        buffer,
-        fileData.customPath,
-        mimeType
-      );
-
-      logger.db('Download and save completed successfully:', {
-        fileId: saveResult.file.id,
-        fileName,
-        size: buffer.length,
-        url: saveResult.url
-      });
-
-      return saveResult;
-    } catch (error) {
-      logger.error('Failed to download and save file from URL:', {
-        url,
-        error: error instanceof Error ? error.message : '未知错误',
-        stack: error instanceof Error ? error.stack : undefined
-      });
-      throw new Error(`从URL下载文件失败: ${error instanceof Error ? error.message : '未知错误'}`);
-    }
-  }
-
-  /**
-   * 根据MIME类型获取文件扩展名
-   */
-  private getExtensionFromMimeType(mimeType: string): string | null {
-    const mimeMap: Record<string, string> = {
-      'image/jpeg': 'jpg',
-      'image/png': 'png',
-      'image/gif': 'gif',
-      'image/webp': 'webp',
-      'image/svg+xml': 'svg',
-      'application/pdf': 'pdf',
-      'text/plain': 'txt',
-      'application/json': 'json',
-      'application/xml': 'xml',
-      'video/mp4': 'mp4',
-      'audio/mp3': 'mp3'
-    };
-    return mimeMap[mimeType] || null;
-  }
-
-  /**
-   * 根据文件名推断MIME类型
-   */
-  private inferMimeType(fileName: string): string {
-    const ext = fileName.toLowerCase().split('.').pop();
-    const extMap: Record<string, string> = {
-      'jpg': 'image/jpeg',
-      'jpeg': 'image/jpeg',
-      'png': 'image/png',
-      'gif': 'image/gif',
-      'webp': 'image/webp',
-      'svg': 'image/svg+xml',
-      'pdf': 'application/pdf',
-      'txt': 'text/plain',
-      'json': 'application/json',
-      'xml': 'application/xml',
-      'mp4': 'video/mp4',
-      'mp3': 'audio/mp3',
-      'wav': 'audio/wav'
-    };
-    return extMap[ext || ''] || 'application/octet-stream';
-  }
-
-  /**
-   * 根据MIME类型获取文件类型
-   */
-  private getFileTypeFromMimeType(mimeType: string): string {
-    if (mimeType.startsWith('image/')) return 'image';
-    if (mimeType.startsWith('video/')) return 'video';
-    if (mimeType.startsWith('audio/')) return 'audio';
-    if (mimeType === 'application/pdf') return 'document';
-    if (mimeType.startsWith('text/')) return 'document';
-    return 'other';
-  }
-}

+ 0 - 236
packages/server/src/modules/files/minio.service.ts

@@ -1,236 +0,0 @@
-import { Client } from 'minio';
-import { logger } from '../../utils/logger';
-import * as process from 'node:process';
-
-export class MinioService {
-  private readonly client: Client;
-  public readonly bucketName: string;
-
-  constructor() {
-    this.client = new Client({
-      endPoint: process.env.MINIO_HOST || 'localhost',
-      port: parseInt(process.env.MINIO_PORT || '443'),
-      useSSL: process.env.MINIO_USE_SSL !== 'false',
-      accessKey: process.env.MINIO_ACCESS_KEY || 'minioadmin',
-      secretKey: process.env.MINIO_SECRET_KEY || 'minioadmin'
-    });
-    this.bucketName = process.env.MINIO_BUCKET_NAME || 'd8dai';
-  }
-
-  // 设置桶策略为"公读私写"
-  async setPublicReadPolicy(bucketName: string = this.bucketName) {
-    const policy = {
-      "Version": "2012-10-17",
-      "Statement": [
-        {
-          "Effect": "Allow",
-          "Principal": {"AWS": "*"},
-          "Action": ["s3:GetObject"],
-          "Resource": [`arn:aws:s3:::${bucketName}/*`]
-        },
-        {
-          "Effect": "Allow",
-          "Principal": {"AWS": "*"},
-          "Action": ["s3:ListBucket"],
-          "Resource": [`arn:aws:s3:::${bucketName}`]
-        }
-      ]
-    };
-
-    try {
-      await this.client.setBucketPolicy(bucketName, JSON.stringify(policy));
-      logger.db(`Bucket policy set to public read for: ${bucketName}`);
-    } catch (error) {
-      logger.error(`Failed to set bucket policy for ${bucketName}:`, error);
-      throw error;
-    }
-  }
-
-  // 确保存储桶存在
-  async ensureBucketExists(bucketName: string = this.bucketName) {
-    try {
-      const exists = await this.client.bucketExists(bucketName);
-      if (!exists) {
-        await this.client.makeBucket(bucketName);
-        await this.setPublicReadPolicy(bucketName);
-        logger.db(`Created new bucket: ${bucketName}`);
-      }
-      return true;
-    } catch (error) {
-      logger.error(`Failed to ensure bucket exists: ${bucketName}`, error);
-      throw error;
-    }
-  }
-
-  // 生成上传策略
-  async generateUploadPolicy(fileKey: string) {
-    await this.ensureBucketExists();
-    
-    const expiresAt = new Date(Date.now() + 3600 * 1000);
-    const policy = this.client.newPostPolicy();
-    policy.setBucket(this.bucketName);
-    
-    policy.setKey(fileKey);
-    policy.setExpires(expiresAt);
-
-    const { postURL, formData } = await this.client.presignedPostPolicy(policy);
-
-    return {
-      'x-amz-algorithm': formData['x-amz-algorithm'],
-      'x-amz-credential': formData['x-amz-credential'],
-      'x-amz-date': formData['x-amz-date'],
-      'x-amz-security-token': formData['x-amz-security-token'] || undefined,
-      policy: formData['policy'],
-      'x-amz-signature': formData['x-amz-signature'],
-      host: postURL,
-      key: fileKey,
-      bucket: this.bucketName,
-    };
-  }
-
-  // 生成文件访问URL
-  getFileUrl(bucketName: string, fileKey: string) {
-    const protocol = process.env.MINIO_USE_SSL !== 'false' ? 'https' : 'http';
-    const port = process.env.MINIO_PORT ? `:${process.env.MINIO_PORT}` : '';
-    return `${protocol}://${process.env.MINIO_HOST}${port}/${bucketName}/${fileKey}`;
-  }
-
-  // 生成预签名文件访问URL(用于私有bucket)
-  async getPresignedFileUrl(bucketName: string, fileKey: string, expiresInSeconds = 3600) {
-    try {
-      const url = await this.client.presignedGetObject(bucketName, fileKey, expiresInSeconds);
-      logger.db(`Generated presigned URL for ${bucketName}/${fileKey}, expires in ${expiresInSeconds}s`);
-      return url;
-    } catch (error) {
-      logger.error(`Failed to generate presigned URL for ${bucketName}/${fileKey}:`, error);
-      throw error;
-    }
-  }
-
-  // 生成预签名文件下载URL(带Content-Disposition头)
-  async getPresignedFileDownloadUrl(bucketName: string, fileKey: string, filename: string, expiresInSeconds = 3600) {
-    try {
-      const url = await this.client.presignedGetObject(
-        bucketName,
-        fileKey,
-        expiresInSeconds,
-        {
-          'response-content-disposition': `attachment; filename="${encodeURIComponent(filename)}"`,
-          'response-content-type': 'application/octet-stream'
-        }
-      );
-      logger.db(`Generated presigned download URL for ${bucketName}/${fileKey}, filename: ${filename}`);
-      return url;
-    } catch (error) {
-      logger.error(`Failed to generate presigned download URL for ${bucketName}/${fileKey}:`, error);
-      throw error;
-    }
-  }
-
-  // 创建分段上传会话
-  async createMultipartUpload(bucketName: string, objectName: string) {
-    try {
-      const uploadId = await this.client.initiateNewMultipartUpload(bucketName, objectName, {});
-      logger.db(`Created multipart upload for ${objectName} with ID: ${uploadId}`);
-      return uploadId;
-    } catch (error) {
-      logger.error(`Failed to create multipart upload for ${objectName}:`, error);
-      throw error;
-    }
-  }
-
-  // 生成分段上传预签名URL
-  async generateMultipartUploadUrls(
-    bucketName: string,
-    objectName: string,
-    uploadId: string,
-    partCount: number,
-    expiresInSeconds = 3600
-  ) {
-    try {
-      const partUrls = [];
-      for (let partNumber = 1; partNumber <= partCount; partNumber++) {
-        const url = await this.client.presignedUrl(
-          'put',
-          bucketName,
-          objectName,
-          expiresInSeconds,
-          {
-            uploadId,
-            partNumber: partNumber.toString()
-          }
-        );
-        partUrls.push(url);
-      }
-      return partUrls;
-    } catch (error) {
-      logger.error(`Failed to generate multipart upload URLs for ${objectName}:`, error);
-      throw error;
-    }
-  }
-
-  // 完成分段上传
-  async completeMultipartUpload(
-    bucketName: string,
-    objectName: string,
-    uploadId: string,
-    parts: { ETag: string; PartNumber: number }[]
-  ): Promise<{ size: number }> {
-    try {
-      await this.client.completeMultipartUpload(
-        bucketName,
-        objectName,
-        uploadId,
-        parts.map(p => ({ part: p.PartNumber, etag: p.ETag }))
-      );
-      logger.db(`Completed multipart upload for ${objectName} with ID: ${uploadId}`);
-      
-      // 获取对象信息以获取文件大小
-      const stat = await this.client.statObject(bucketName, objectName);
-      return { size: stat.size };
-    } catch (error) {
-      logger.error(`Failed to complete multipart upload for ${objectName}:`, error);
-      throw error;
-    }
-  }
-
-  // 上传文件
-  async createObject(bucketName: string, objectName: string, fileContent: Buffer, contentType: string = 'application/octet-stream') {
-    try {
-      await this.ensureBucketExists(bucketName);
-      await this.client.putObject(bucketName, objectName, fileContent, fileContent.length, {
-        'Content-Type': contentType
-      });
-      logger.db(`Created object: ${bucketName}/${objectName}`);
-      return this.getFileUrl(bucketName, objectName);
-    } catch (error) {
-      logger.error(`Failed to create object ${bucketName}/${objectName}:`, error);
-      throw error;
-    }
-  }
-
-  // 检查文件是否存在
-  async objectExists(bucketName: string, objectName: string): Promise<boolean> {
-    try {
-      await this.client.statObject(bucketName, objectName);
-      return true;
-    } catch (error) {
-      if ((error as Error).message.includes('not found')) {
-        return false;
-      }
-      logger.error(`Error checking existence of object ${bucketName}/${objectName}:`, error);
-      throw error;
-    }
-  }
-
-  // 删除文件
-  async deleteObject(bucketName: string, objectName: string) {
-    try {
-      await this.client.removeObject(bucketName, objectName);
-      logger.db(`Deleted object: ${bucketName}/${objectName}`);
-    } catch (error) {
-      logger.error(`Failed to delete object ${bucketName}/${objectName}:`, error);
-      throw error;
-    }
-  }
-}

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

@@ -1,30 +0,0 @@
-import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn } from 'typeorm';
-import { Permission } from './role.schema';
-
-@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 = [];
-    }
-  }
-}

+ 0 - 27
packages/server/src/modules/users/role.schema.ts

@@ -1,27 +0,0 @@
-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();

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

@@ -1,20 +0,0 @@
-import { DataSource } from 'typeorm';
-import { Role } from './role.entity';
-import { GenericCrudService } from '../../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);
-  }
-}

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

@@ -1,64 +0,0 @@
-import { Entity, PrimaryGeneratedColumn, Column, ManyToMany, JoinTable, CreateDateColumn, UpdateDateColumn, ManyToOne, JoinColumn } from 'typeorm';
-import { Role } from './role.entity';
-import { File } from '../files/file.entity';
-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_file_id', type: 'int', unsigned: true, nullable: true, comment: '头像文件ID' })
-  avatarFileId!: number | null;
-
-  @ManyToOne(() => File, { nullable: true })
-  @JoinColumn({ name: 'avatar_file_id', referencedColumnName: 'id' })
-  avatarFile!: File | 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;
-
-  @Column({ name: 'openid', type: 'varchar', length: 255, nullable: true, unique: true, comment: '微信小程序openid' })
-  openid!: string | null;
-  
-  @Column({ name: 'unionid', type: 'varchar', length: 255, nullable: true, comment: '微信unionid' })
-  unionid!: string | null;
-
-  @Column({ name: 'registration_source', type: 'varchar', length: 20, default: 'web', comment: '注册来源: web, miniapp' })
-  registrationSource!: string;
-  
-  @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);
-  }
-}

+ 0 - 180
packages/server/src/modules/users/user.schema.ts

@@ -1,180 +0,0 @@
-import { z } from '@hono/zod-openapi';
-import { DeleteStatus, DisabledStatus } from '../../share/types';
-import { RoleSchema } from './role.schema';
-// import { FileSchema } from './modules/files/file.schema';
-
-// 基础用户 schema(包含所有字段)
-export const UserSchema = z.object({
-  id: z.number().int().positive().openapi({ description: '用户ID' }),
-  username: z.string().min(3, '用户名至少3个字符').max(255, '用户名最多255个字符').openapi({
-    example: 'admin',
-    description: '用户名,3-255个字符'
-  }),
-  password: z.string().min(6, '密码至少6位').max(255, '密码最多255位').openapi({
-    example: 'password123',
-    description: '密码,最少6位'
-  }),
-  phone: z.string().max(255, '手机号最多255个字符').nullable().openapi({
-    example: '13800138000',
-    description: '手机号'
-  }),
-  email: z.email('请输入正确的邮箱格式').max(255, '邮箱最多255个字符').nullable().openapi({
-    example: 'user@example.com',
-    description: '邮箱'
-  }),
-  nickname: z.string().max(255, '昵称最多255个字符').nullable().openapi({
-    example: '昵称',
-    description: '用户昵称'
-  }),
-  name: z.string().max(255, '姓名最多255个字符').nullable().openapi({
-    example: '张三',
-    description: '真实姓名'
-  }),
-  avatarFileId: z.number().int().positive().nullable().openapi({
-    example: 1,
-    description: '头像文件ID'
-  }),
-  avatarFile: z.object({
-    id: z.number().int().positive().openapi({ description: '文件ID' }),
-    name: z.string().max(255).openapi({ description: '文件名', example: 'avatar.jpg' }),
-    fullUrl: z.string().openapi({ description: '文件完整URL', example: 'https://example.com/avatar.jpg' }),
-    type: z.string().nullable().openapi({ description: '文件类型', example: 'image/jpeg' }),
-    size: z.number().nullable().openapi({ description: '文件大小(字节)', example: 102400 })
-  }).nullable().optional().openapi({
-    description: '头像文件信息'
-  }),
-  openid: z.string().max(255).nullable().optional().openapi({
-    example: 'oABCDEFGH123456789',
-    description: '微信小程序openid'
-  }),
-  unionid: z.string().max(255).nullable().optional().openapi({
-    example: 'unionid123456789',
-    description: '微信unionid'
-  }),
-  registrationSource: z.string().max(20).default('web').openapi({
-    example: 'miniapp',
-    description: '注册来源: web, miniapp'
-  }),
-  isDisabled: z.nativeEnum(DisabledStatus).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.coerce.date().openapi({ description: '创建时间' }),
-  updatedAt: z.coerce.date().openapi({ description: '更新时间' })
-});
-
-// 创建用户请求 schema
-export const CreateUserDto = z.object({
-  username: z.string().min(3, '用户名至少3个字符').max(255, '用户名最多255个字符').openapi({
-    example: 'admin',
-    description: '用户名,3-255个字符'
-  }),
-  password: z.string().min(6, '密码至少6位').max(255, '密码最多255位').openapi({
-    example: 'password123',
-    description: '密码,最少6位'
-  }),
-  phone: z.string().max(255, '手机号最多255个字符').nullable().optional().openapi({
-    example: '13800138000',
-    description: '手机号'
-  }),
-  email: z.email('请输入正确的邮箱格式').max(255, '邮箱最多255个字符').nullable().optional().openapi({
-    example: 'user@example.com',
-    description: '邮箱'
-  }),
-  nickname: z.string().max(255, '昵称最多255个字符').nullable().optional().openapi({
-    example: '昵称',
-    description: '用户昵称'
-  }),
-  name: z.string().max(255, '姓名最多255个字符').nullable().optional().openapi({
-    example: '张三',
-    description: '真实姓名'
-  }),
-  avatarFileId: z.number().int().positive().nullable().optional().openapi({
-    example: 1,
-    description: '头像文件ID'
-  }),
-  isDisabled: z.number().int().min(0, '状态值只能是0或1').max(1, '状态值只能是0或1').default(DisabledStatus.ENABLED).optional().openapi({
-    example: DisabledStatus.ENABLED,
-    description: '是否禁用(0:启用,1:禁用)'
-  })
-});
-
-// 更新用户请求 schema
-export const UpdateUserDto = z.object({
-  username: z.string().min(3, '用户名至少3个字符').max(255, '用户名最多255个字符').optional().openapi({
-    example: 'admin',
-    description: '用户名,3-255个字符'
-  }),
-  password: z.string().min(6, '密码至少6位').max(255, '密码最多255位').optional().openapi({
-    example: 'password123',
-    description: '密码,最少6位'
-  }),
-  phone: z.string().max(255, '手机号最多255个字符').nullable().optional().openapi({
-    example: '13800138000',
-    description: '手机号'
-  }),
-  email: z.email('请输入正确的邮箱格式').max(255, '邮箱最多255个字符').nullable().optional().openapi({
-    example: 'user@example.com',
-    description: '邮箱'
-  }),
-  nickname: z.string().max(255, '昵称最多255个字符').nullable().optional().openapi({
-    example: '昵称',
-    description: '用户昵称'
-  }),
-  name: z.string().max(255, '姓名最多255个字符').nullable().optional().openapi({
-    example: '张三',
-    description: '真实姓名'
-  }),
-  avatarFileId: z.number().int().positive().nullable().optional().openapi({
-    example: 1,
-    description: '头像文件ID'
-  }),
-  isDisabled: z.number().int().min(0, '状态值只能是0或1').max(1, '状态值只能是0或1').optional().openapi({
-    example: DisabledStatus.ENABLED,
-    description: '是否禁用(0:启用,1:禁用)'
-  })
-});
-
-// 用户列表响应 schema
-export const UserListResponse = z.object({
-  data: z.array(UserSchema.omit({ password: true })),
-  pagination: z.object({
-    total: z.number().openapi({
-      example: 100,
-      description: '总记录数'
-    }),
-    current: z.number().openapi({
-      example: 1,
-      description: '当前页码'
-    }),
-    pageSize: z.number().openapi({
-      example: 10,
-      description: '每页数量'
-    })
-  })
-});
-// 单个用户查询响应 schema
-export const UserResponseSchema = UserSchema.omit({password:true})
-
-// 类型导出
-export type User = z.infer<typeof UserSchema>;
-export type CreateUserRequest = z.infer<typeof CreateUserDto>;
-export type UpdateUserRequest = z.infer<typeof UpdateUserDto>;
-export type UserListResponseType = z.infer<typeof UserListResponse>;

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

@@ -1,139 +0,0 @@
-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', 'avatarFile']
-      });
-    } 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', 'avatarFile']
-      });
-    } 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', 'avatarFile']
-      });
-    } 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<boolean> {
-    try {
-      const result = await this.userRepository.delete(id);
-      return result.affected !== null && result.affected !== undefined &&  result.affected > 0;
-    } catch (error) {
-      console.error('Error deleting user:', error);
-      throw new Error('Failed to delete user');
-    }
-  }
-
-
-  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', 'avatarFile']
-      });
-      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', 'avatarFile']
-      });
-    } catch (error) {
-      console.error('Error getting user by account:', error);
-      throw new Error('Failed to get user by account');
-    }
-  }
-}

+ 0 - 46
packages/server/src/share/types.ts

@@ -1,46 +0,0 @@
-// 全局配置常量
-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   // 启用
-}
-

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

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

+ 1 - 1
packages/server/src/utils/backup.ts

@@ -2,7 +2,7 @@ import { FormatEnum, pgDump, pgRestore } from 'pg-dump-restore'
 import { promises as fs } from 'fs'
 import path from 'path'
 import cron, { type ScheduledTask } from 'node-cron'
-import { logger } from './logger'
+import { logger } from '@d8d/shared-utils'
 import process from 'node:process'
 
 const BACKUP_DIR = process.env.BACKUP_DIR || './backups'

+ 0 - 10
packages/server/src/utils/concrete-crud.service.ts

@@ -1,10 +0,0 @@
-import { AppDataSource } from '../data-source';
-import { GenericCrudService, UserTrackingOptions, RelationFieldOptions } from './generic-crud.service';
-import { ObjectLiteral } from 'typeorm';
-
-// 创建具体CRUD服务类
-export class ConcreteCrudService<T extends ObjectLiteral> extends GenericCrudService<T> {
-  constructor(entity: new () => T, options?: { userTracking?: UserTrackingOptions; relationFields?: RelationFieldOptions }) {
-    super(AppDataSource, entity, options);
-  }
-}

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

@@ -1,21 +0,0 @@
-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
-  )
-}

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

@@ -1,460 +0,0 @@
-import { createRoute, OpenAPIHono , extendZodWithOpenApi} 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';
-
-extendZodWithOpenApi(z)
-
-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;
-  }
-  
-}

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

@@ -1,327 +0,0 @@
-import { DataSource, Repository, ObjectLiteral, DeepPartial, In } from 'typeorm';
-import { z } from '@hono/zod-openapi';
-
-export abstract class GenericCrudService<T extends ObjectLiteral> {
-  protected repository: Repository<T>;
-  private userTrackingOptions?: UserTrackingOptions;
-
-  protected relationFields?: RelationFieldOptions;
-
-  constructor(
-    protected dataSource: DataSource,
-    protected entity: new () => T,
-    options?: {
-      userTracking?: UserTrackingOptions;
-      relationFields?: RelationFieldOptions;
-    }
-  ) {
-    this.repository = this.dataSource.getRepository(entity);
-    this.userTrackingOptions = options?.userTracking;
-    this.relationFields = options?.relationFields;
-  }
-
-  /**
-   * 获取分页列表
-   */
-  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) => {
-        const parts = relation.split('.');
-        let currentAlias = 'entity';
-        
-        parts.forEach((part, index) => {
-          // 生成一致的别名:对于嵌套关联,使用下划线连接路径
-          const newAlias = index === 0 ? part : parts.slice(0, index + 1).join('_');
-          query.leftJoinAndSelect(`${currentAlias}.${part}`, newAlias);
-          currentAlias = newAlias;
-        });
-      });
-    }
-
-    // 关键词搜索 - 支持关联字段搜索(格式:relation.field 或 relation.nestedRelation.field)
-    if (keyword && searchFields && searchFields.length > 0) {
-      const searchConditions: string[] = [];
-      const searchParams: Record<string, string> = { keyword: `%${keyword}%` };
-
-      searchFields.forEach((field) => {
-        // 检查是否为关联字段(包含点号)
-        if (field.includes('.')) {
-          const parts = field.split('.');
-          const alias = parts.slice(0, -1).join('_'); // 使用下划线连接关系路径作为别名
-          const fieldName = parts[parts.length - 1];
-          
-          searchConditions.push(`${alias}.${fieldName} LIKE :keyword`);
-        } else {
-          // 普通字段搜索
-          searchConditions.push(`entity.${field} LIKE :keyword`);
-        }
-      });
-
-      if (searchConditions.length > 0) {
-        query.andWhere(`(${searchConditions.join(' OR ')})`, searchParams);
-      }
-    }
-
-    // 条件查询
-    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;
-          
-          // 检查是否为关联字段(包含点号)
-          let tableAlias: string = 'entity';
-          let actualFieldName: string = fieldName;
-
-          if (fieldName.includes('.')) {
-            const parts = fieldName.split('.');
-            tableAlias = parts.slice(0, -1).join('_') || 'entity'; // 使用下划线连接关系路径作为别名
-            actualFieldName = parts[parts.length - 1] || fieldName;
-          }
-          
-          // 支持不同类型的筛选
-          if (Array.isArray(value)) {
-            // 数组类型:IN查询
-            if (value.length > 0) {
-              query.andWhere(`${tableAlias}.${actualFieldName} IN (:...${key})`, { [key]: value });
-            }
-          } else if (typeof value === 'string' && value.includes('%')) {
-            // 模糊匹配
-            query.andWhere(`${tableAlias}.${actualFieldName} LIKE :${key}`, { [key]: value });
-          } else if (typeof value === 'object' && value !== null) {
-            // 范围查询
-            if ('gte' in value) {
-              query.andWhere(`${tableAlias}.${actualFieldName} >= :${key}_gte`, { [`${key}_gte`]: value.gte });
-            }
-            if ('gt' in value) {
-              query.andWhere(`${tableAlias}.${actualFieldName} > :${key}_gt`, { [`${key}_gt`]: value.gt });
-            }
-            if ('lte' in value) {
-              query.andWhere(`${tableAlias}.${actualFieldName} <= :${key}_lte`, { [`${key}_lte`]: value.lte });
-            }
-            if ('lt' in value) {
-              query.andWhere(`${tableAlias}.${actualFieldName} < :${key}_lt`, { [`${key}_lt`]: value.lt });
-            }
-            if ('between' in value && Array.isArray(value.between) && value.between.length === 2) {
-              query.andWhere(`${tableAlias}.${actualFieldName} BETWEEN :${key}_start AND :${key}_end`, {
-                [`${key}_start`]: value.between[0],
-                [`${key}_end`]: value.between[1]
-              });
-            }
-          } else {
-            // 精确匹配
-            query.andWhere(`${tableAlias}.${actualFieldName} = :${key}`, { [key]: value });
-          }
-        }
-      });
-    }
-
-    // 排序
-    Object.entries(order).forEach(([key, direction]) => {
-      query.orderBy(`entity.${key}`, direction);
-    });
-
-    const finalQuery = query.skip(skip).take(pageSize);
-
-    // console.log(finalQuery.getSql())
-
-    return finalQuery.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',
-      userIdField = 'userId'
-    } = this.userTrackingOptions;
-
-    // 设置创建人
-    if (isCreate && createdByField) {
-      data[createdByField] = userId;
-    }
-
-    // 设置更新人
-    if (updatedByField) {
-      data[updatedByField] = userId;
-    }
-
-    // 设置关联的用户ID(如userId字段)
-    if (isCreate && userIdField) {
-      data[userIdField] = userId;
-    }
-  }
-
-  /**
-   * 创建实体
-   */
-  /**
-   * 处理关联字段
-   */
-  private async handleRelationFields(data: any, entity: T, _isUpdate: boolean = false): Promise<void> {
-    if (!this.relationFields) return;
-
-    for (const [fieldName, config] of Object.entries(this.relationFields)) {
-      if (data[fieldName] !== undefined) {
-        const ids = data[fieldName];
-        const relationRepository = this.dataSource.getRepository(config.targetEntity);
-        
-        if (ids && Array.isArray(ids) && ids.length > 0) {
-          const relatedEntities = await relationRepository.findBy({ id: In(ids) });
-          (entity as any)[config.relationName] = relatedEntities;
-        } else {
-          (entity as any)[config.relationName] = [];
-        }
-        
-        // 清理原始数据中的关联字段
-        delete data[fieldName];
-      }
-    }
-  }
-
-  /**
-   * 创建实体
-   */
-  async create(data: DeepPartial<T>, userId?: string | number): Promise<T> {
-    const entityData = { ...data };
-    this.setUserFields(entityData, userId, true);
-    
-    // 分离关联字段数据
-    const relationData: any = {};
-    if (this.relationFields) {
-      for (const fieldName of Object.keys(this.relationFields)) {
-        if (fieldName in entityData) {
-          relationData[fieldName] = (entityData as any)[fieldName];
-          delete (entityData as any)[fieldName];
-        }
-      }
-    }
-
-    const entity = this.repository.create(entityData as DeepPartial<T>);
-    
-    // 处理关联字段
-    await this.handleRelationFields(relationData, entity);
-    
-    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);
-    
-    // 分离关联字段数据
-    const relationData: any = {};
-    if (this.relationFields) {
-      for (const fieldName of Object.keys(this.relationFields)) {
-        if (fieldName in updateData) {
-          relationData[fieldName] = (updateData as any)[fieldName];
-          delete (updateData as any)[fieldName];
-        }
-      }
-    }
-
-    // 先更新基础字段
-    await this.repository.update(id, updateData);
-    
-    // 获取完整实体并处理关联字段
-    const entity = await this.getById(id);
-    if (!entity) return null;
-    
-    // 处理关联字段
-    await this.handleRelationFields(relationData, entity, true);
-    
-    return this.repository.save(entity);
-  }
-
-  /**
-   * 删除实体
-   */
-  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;
-  userIdField?: string;
-}
-
-export interface RelationFieldOptions {
-  [fieldName: string]: {
-    relationName: string;
-    targetEntity: new () => any;
-    joinTableName?: 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;
-  relationFields?: RelationFieldOptions;
-  readOnly?: boolean;
-};

+ 0 - 94
packages/server/src/utils/jwt.util.ts

@@ -1,94 +0,0 @@
-import jwt, { SignOptions } from 'jsonwebtoken';
-import { UserEntity } from '../modules/users/user.entity';
-import debug from 'debug';
-
-const logger = {
-  info: debug('backend:jwt:info'),
-  error: debug('backend:jwt:error')
-};
-
-const JWT_SECRET = process.env.JWT_SECRET || 'your-secret-key';
-const JWT_EXPIRES_IN = process.env.JWT_EXPIRES_IN || '7d';
-
-export interface JWTPayload {
-  id: number;
-  username: string;
-  roles?: string[];
-  openid?: string;
-}
-
-export class JWTUtil {
-  /**
-   * 生成 JWT token
-   * @param user 用户实体
-   * @param additionalPayload 额外的 payload 数据
-   * @param expiresIn 过期时间
-   * @returns JWT token
-   */
-  static generateToken(user: UserEntity, additionalPayload: Partial<JWTPayload> = {}, expiresIn?: string): string {
-    if (!user.id || !user.username) {
-      throw new Error('用户ID和用户名不能为空');
-    }
-
-    const payload: JWTPayload = {
-      id: user.id,
-      username: user.username,
-      roles: user.roles?.map(role => role.name) || [],
-      openid: user.openid || undefined,
-      ...additionalPayload
-    };
-
-    try {
-      const options: SignOptions = {
-        expiresIn: expiresIn || JWT_EXPIRES_IN as SignOptions['expiresIn']
-      };
-      return jwt.sign(payload, JWT_SECRET, options);
-    } catch (error) {
-      logger.error('生成JWT token失败:', error);
-      throw new Error('生成token失败');
-    }
-  }
-
-  /**
-   * 验证 JWT token
-   * @param token JWT token
-   * @returns 验证后的 payload
-   */
-  static verifyToken(token: string): JWTPayload {
-    try {
-      return jwt.verify(token, JWT_SECRET) as JWTPayload;
-    } catch (error) {
-      logger.error('验证JWT token失败:', error);
-      throw new Error('无效的token');
-    }
-  }
-
-  /**
-   * 解码 JWT token(不验证签名)
-   * @param token JWT token
-   * @returns 解码后的 payload
-   */
-  static decodeToken(token: string): JWTPayload | null {
-    try {
-      return jwt.decode(token) as JWTPayload;
-    } catch (error) {
-      logger.error('解码JWT token失败:', error);
-      return null;
-    }
-  }
-
-  /**
-   * 获取 token 的剩余有效期(秒)
-   * @param token JWT token
-   * @returns 剩余有效期(秒),如果 token 无效则返回 0
-   */
-  static getTokenRemainingTime(token: string): number {
-    try {
-      const decoded = jwt.verify(token, JWT_SECRET) as JWTPayload & { exp: number };
-      const currentTime = Math.floor(Date.now() / 1000);
-      return Math.max(0, decoded.exp - currentTime);
-    } catch (error) {
-      return 0;
-    }
-  }
-}

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

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

+ 0 - 59
packages/server/src/utils/parseWithAwait.ts

@@ -1,59 +0,0 @@
-import { z } from '@hono/zod-openapi';
-
-export async function parseWithAwait<T>(schema: z.ZodSchema<T>, data: unknown): Promise<T> {  
-    // 先尝试同步解析,捕获 Promise 错误  
-    const syncResult = schema.safeParse(data);  
-      
-    if (!syncResult.success) {  
-      // 提取 Promise 错误的路径信息  
-      const promiseErrors = syncResult.error.issues.filter(issue =>   
-        issue.code === 'invalid_type' &&   
-        issue.message.includes('received Promise')  
-      );  
-        
-      if (promiseErrors.length > 0) {  
-        // 根据路径直接 await Promise  
-        const processedData = await resolvePromisesByPath(data, promiseErrors);  
-          
-        // 重新解析处理后的数据  
-        return schema.parse(processedData) as T;  
-      }  
-        
-      throw syncResult.error;  
-    }  
-      
-    return syncResult.data as T;  
-  }  
-    
-  async function resolvePromisesByPath(data: any, promiseErrors: any[]): Promise<any> {  
-    const clonedData = JSON.parse(JSON.stringify(data, (_, value) => {  
-      // 保留 Promise 对象,不进行序列化  
-      return typeof value?.then === 'function' ? value : value;  
-    }));  
-      
-    // 根据错误路径逐个处理 Promise  
-    for (const error of promiseErrors) {  
-      const path = error.path;  
-      const promiseValue = getValueByPath(data, path);  
-        
-      if (promiseValue && typeof promiseValue.then === 'function') {  
-        const resolvedValue = await promiseValue;  
-        setValueByPath(clonedData, path, resolvedValue);  
-      }  
-    }  
-      
-    return clonedData;  
-  }  
-    
-  function getValueByPath(obj: any, path: (string | number)[]): any {  
-    return path.reduce((current, key) => current?.[key], obj);  
-  }  
-    
-  function setValueByPath(obj: any, path: (string | number)[], value: any): void {
-    const lastKey = path[path.length - 1];
-    const parentPath = path.slice(0, -1);
-    const parent = getValueByPath(obj, parentPath);
-    if (parent && lastKey !== undefined) {
-      parent[lastKey] = value;
-    }
-  }

+ 1 - 1
packages/server/src/utils/restore.ts

@@ -1,7 +1,7 @@
 import { pgRestore } from 'pg-dump-restore'
 import { promises as fs } from 'fs'
 import path from 'path'
-import { logger } from './logger'
+import { logger } from '@d8d/shared-utils'
 import process from 'node:process'
 
 const BACKUP_DIR: string = process.env.BACKUP_DIR || './backups'