2
0
Эх сурвалжийг харах

📝 docs: add development and error handling documentation

- 创建开发环境配置指南,包含系统要求、快速开始、数据库设置等内容
- 添加错误处理规范文档,定义错误响应格式、错误代码和实现指南

✅ test: add testing configuration and scripts

- 添加Jest配置文件jest.config.js
- 添加测试环境设置文件src/test/setup.ts
- 添加测试相关npm脚本(test, test:coverage等)

🔧 chore: add database and code quality scripts

- 添加数据库迁移、种子和重置脚本(db:migrate, db:seed等)
- 添加代码检查和类型检查脚本(lint, typecheck等)
- 更新package.json依赖项,添加测试相关包
yourname 2 сар өмнө
parent
commit
da7958ef11
5 өөрчлөгдсөн 890 нэмэгдсэн , 1 устгасан
  1. 241 0
      docs/development.md
  2. 485 0
      docs/error-handling.md
  3. 100 0
      jest.config.js
  4. 19 1
      package.json
  5. 45 0
      src/test/setup.ts

+ 241 - 0
docs/development.md

@@ -0,0 +1,241 @@
+# D8D Starter 开发环境配置指南
+
+## 版本信息
+| 版本 | 日期 | 描述 | 作者 |
+|------|------|------|------|
+| 1.0 | 2025-09-15 | 初始开发指南 | Sarah (PO) |
+
+## 1. 环境要求
+
+### 系统要求
+- **操作系统**: macOS 10.15+, Windows 10+, Linux Ubuntu 18.04+
+- **Node.js**: 20.18.3 或更高版本
+- **npm**: 10.8.2 或更高版本
+- **Docker**: 24.0+ (用于本地开发环境)
+- **Docker Compose**: 2.20+
+
+### 推荐开发工具
+- **代码编辑器**: VS Code (推荐) 或 WebStorm
+- **浏览器**: Chrome 120+, Firefox 115+, Safari 16+
+- **终端**: iTerm2 (macOS), Windows Terminal, GNOME Terminal
+
+## 2. 快速开始
+
+### 2.1 克隆项目
+```bash
+git clone <repository-url>
+cd d8d-starter
+```
+
+### 2.2 安装依赖
+```bash
+# 安装根目录依赖
+npm install
+
+# 安装客户端依赖
+cd src/client
+npm install
+
+# 安装服务器依赖
+cd ../server
+npm install
+
+# 返回根目录
+cd ../..
+```
+
+### 2.3 环境变量配置
+
+创建环境配置文件:
+```bash
+# 复制示例文件
+cp .env.example .env
+```
+
+编辑 `.env` 文件:
+```env
+# 数据库配置
+DATABASE_URL=mysql://root:password@localhost:3306/d8dai
+DATABASE_HOST=localhost
+DATABASE_PORT=3306
+DATABASE_NAME=d8dai
+DATABASE_USER=root
+DATABASE_PASSWORD=password
+
+# 应用配置
+NODE_ENV=development
+PORT=3000
+JWT_SECRET=your-super-secret-jwt-key-change-in-production
+JWT_EXPIRES_IN=7d
+
+# 前端配置
+VITE_API_BASE_URL=http://localhost:3000
+VITE_APP_NAME=D8D Starter
+
+# 文件存储 (MinIO)
+OSS_BASE_URL=https://oss.d8d.fun
+OSS_ACCESS_KEY=your-access-key
+OSS_SECRET_KEY=your-secret-key
+OSS_BUCKET_NAME=d8dai
+
+# Redis配置
+REDIS_HOST=localhost
+REDIS_PORT=6379
+REDIS_PASSWORD=
+```
+
+### 2.4 启动开发环境
+
+#### 选项A: 使用 Docker Compose (推荐)
+```bash
+# 启动所有服务 (数据库 + Redis + 应用)
+docker-compose up -d
+
+# 查看日志
+docker-compose logs -f
+
+# 停止服务
+docker-compose down
+```
+
+#### 选项B: 手动启动
+```bash
+# 启动后端服务器 (端口3000)
+npm run dev:server
+
+# 启动前端开发服务器 (端口5173)
+npm run dev:client
+
+# 或者同时启动前后端
+npm run dev
+```
+
+## 3. 数据库设置
+
+### 3.1 使用 Docker 数据库
+```bash
+# 启动 MySQL 和 Redis
+docker-compose up mysql redis -d
+
+# 运行数据库迁移
+npm run db:migrate
+
+# 运行数据种子
+npm run db:seed
+```
+
+### 3.2 手动数据库配置
+1. 安装 MySQL 8.0+ 和 Redis 7+
+2. 创建数据库: `CREATE DATABASE d8dai;`
+3. 配置连接信息在 `.env` 文件中
+
+## 4. 开发工作流
+
+### 4.1 代码结构
+```
+src/
+├── client/          # React前端代码
+│   ├── admin/       # 管理后台
+│   ├── home/        # 用户前台
+│   ├── components/  # 共享组件
+│   └── lib/         # 工具库
+├── server/          # Node.js后端
+│   ├── api/         # API路由
+│   ├── modules/     # 业务模块
+│   └── utils/       # 工具函数
+└── shared/          # 前后端共享代码
+```
+
+### 4.2 常用开发命令
+```bash
+# 开发命令
+npm run dev          # 启动完整开发环境
+npm run dev:client   # 仅启动前端
+npm run dev:server   # 仅启动后端
+
+# 构建命令
+npm run build        # 生产构建
+npm run build:client # 仅构建前端
+npm run build:server # 仅构建后端
+
+# 数据库命令
+npm run db:migrate   # 运行数据库迁移
+npm run db:seed      # 填充种子数据
+npm run db:reset     # 重置数据库
+
+# 代码质量
+npm run lint         # 代码检查
+npm run lint:fix     # 自动修复
+npm run typecheck    # 类型检查
+```
+
+### 4.3 热重载和调试
+- 前端: Vite 热重载,修改后自动刷新
+- 后端: Nodemon 自动重启,支持调试
+- VS Code 调试配置已包含在 `.vscode/launch.json`
+
+## 5. 测试环境配置
+
+### 5.1 测试数据库设置
+```bash
+# 创建测试数据库
+echo "CREATE DATABASE d8dai_test;" | mysql -u root -p
+
+# 或者使用Docker测试数据库
+docker-compose -f docker-compose.test.yml up -d
+```
+
+### 5.2 测试环境变量
+创建 `.env.test` 文件:
+```env
+NODE_ENV=test
+DATABASE_URL=mysql://root:password@localhost:3306/d8dai_test
+JWT_SECRET=test-jwt-secret
+```
+
+## 6. 故障排除
+
+### 常见问题
+
+**端口冲突**:
+```bash
+# 查找占用端口的进程
+lsof -i :3000  # 后端端口
+lsof -i :5173  # 前端端口
+lsof -i :3306  # 数据库端口
+
+# 终止进程
+kill -9 <PID>
+```
+
+**依赖安装失败**:
+```bash
+# 清除缓存并重新安装
+rm -rf node_modules package-lock.json
+npm cache clean --force
+npm install
+```
+
+**数据库连接问题**:
+- 确保 MySQL 服务正在运行
+- 检查 `.env` 文件中的数据库配置
+- 验证网络连接和防火墙设置
+
+### 获取帮助
+- 查看详细日志: `docker-compose logs [service]`
+- 检查服务状态: `docker-compose ps`
+- 查阅架构文档: `docs/architecture.md`
+
+## 7. 下一步
+
+1. ✅ 完成环境设置
+2. 🚀 运行 `npm run dev` 启动开发服务器
+3. 📖 阅读 `docs/prd.md` 了解产品需求
+4. 🏗️ 查看 `docs/architecture.md` 了解系统架构
+5. 🧪 运行 `npm test` 执行测试
+
+---
+
+**最后更新**: 2025-09-15
+**维护者**: 开发团队
+**文档状态**: 正式版

+ 485 - 0
docs/error-handling.md

@@ -0,0 +1,485 @@
+# D8D Starter 错误处理规范
+
+## 版本信息
+| 版本 | 日期 | 描述 | 作者 |
+|------|------|------|------|
+| 1.0 | 2025-09-15 | 初始错误处理规范 | Sarah (PO) |
+
+## 1. 错误处理原则
+
+### 1.1 核心原则
+- **一致性**: 所有错误响应格式统一
+- **安全性**: 不泄露敏感信息到客户端
+- **可操作性**: 错误信息应指导用户或开发者
+- **可追溯性**: 错误应包含足够上下文用于调试
+
+### 1.2 错误分类
+
+#### 按严重程度
+- 🔴 **致命错误**: 系统无法继续运行
+- 🟡 **业务错误**: 用户操作失败,可恢复
+- 🔵 **客户端错误**: 用户输入或配置问题
+- ⚪ **信息性错误**: 警告或提示信息
+
+#### 按来源
+- **验证错误**: 输入数据验证失败
+- **业务逻辑错误**: 业务规则违反
+- **系统错误**: 基础设施或第三方服务故障
+- **网络错误**: 连接超时或中断
+
+## 2. 错误响应格式
+
+### 2.1 标准错误响应
+```typescript
+// 成功响应
+{
+  "success": true,
+  "data": { /* 业务数据 */ },
+  "message": "操作成功"
+}
+
+// 错误响应
+{
+  "success": false,
+  "error": {
+    "code": "VALIDATION_ERROR",
+    "message": "输入数据验证失败",
+    "details": [
+      {
+        "field": "email",
+        "message": "邮箱格式不正确"
+      }
+    ],
+    "timestamp": "2025-09-15T10:30:00Z",
+    "requestId": "req_1234567890"
+  }
+}
+```
+
+### 2.2 HTTP状态码映射
+
+| 错误类型 | HTTP状态码 | 错误代码 | 描述 |
+|----------|------------|----------|------|
+| 验证错误 | 400 | `VALIDATION_ERROR` | 输入数据验证失败 |
+| 未授权 | 401 | `UNAUTHORIZED` | 需要认证 |
+| 权限不足 | 403 | `FORBIDDEN` | 权限不足 |
+| 资源不存在 | 404 | `NOT_FOUND` | 资源未找到 |
+| 业务错误 | 409 | `BUSINESS_ERROR` | 业务规则冲突 |
+| 系统错误 | 500 | `INTERNAL_ERROR` | 服务器内部错误 |
+| 服务不可用 | 503 | `SERVICE_UNAVAILABLE` | 服务暂时不可用 |
+
+## 3. 错误代码规范
+
+### 3.1 错误代码命名约定
+- 使用 `SCREAMING_SNAKE_CASE`
+- 前缀表示错误类别
+- 后缀表示具体错误
+
+### 3.2 标准错误代码
+
+#### 认证错误 (AUTH_*)
+```typescript
+export const AUTH_ERRORS = {
+  UNAUTHORIZED: 'AUTH_UNAUTHORIZED',
+  INVALID_CREDENTIALS: 'AUTH_INVALID_CREDENTIALS',
+  TOKEN_EXPIRED: 'AUTH_TOKEN_EXPIRED',
+  TOKEN_INVALID: 'AUTH_TOKEN_INVALID',
+  PERMISSION_DENIED: 'AUTH_PERMISSION_DENIED'
+} as const;
+```
+
+#### 验证错误 (VALIDATION_*)
+```typescript
+export const VALIDATION_ERRORS = {
+  REQUIRED_FIELD: 'VALIDATION_REQUIRED_FIELD',
+  INVALID_EMAIL: 'VALIDATION_INVALID_EMAIL',
+  INVALID_PASSWORD: 'VALIDATION_INVALID_PASSWORD',
+  UNIQUE_CONSTRAINT: 'VALIDATION_UNIQUE_CONSTRAINT',
+  OUT_OF_RANGE: 'VALIDATION_OUT_OF_RANGE'
+} as const;
+```
+
+#### 业务错误 (BUSINESS_*)
+```typescript
+export const BUSINESS_ERRORS = {
+  USER_NOT_FOUND: 'BUSINESS_USER_NOT_FOUND',
+  INSUFFICIENT_BALANCE: 'BUSINESS_INSUFFICIENT_BALANCE',
+  OPERATION_NOT_ALLOWED: 'BUSINESS_OPERATION_NOT_ALLOWED',
+  RESOURCE_CONFLICT: 'BUSINESS_RESOURCE_CONFLICT'
+} as const;
+```
+
+#### 系统错误 (SYSTEM_*)
+```typescript
+export const SYSTEM_ERRORS = {
+  DATABASE_ERROR: 'SYSTEM_DATABASE_ERROR',
+  NETWORK_ERROR: 'SYSTEM_NETWORK_ERROR',
+  EXTERNAL_SERVICE_ERROR: 'SYSTEM_EXTERNAL_SERVICE_ERROR',
+  CONFIGURATION_ERROR: 'SYSTEM_CONFIGURATION_ERROR'
+} as const;
+```
+
+## 4. 实现指南
+
+### 4.1 后端错误处理
+
+#### 错误类定义
+```typescript
+// src/shared/errors/AppError.ts
+export class AppError extends Error {
+  public readonly code: string;
+  public readonly statusCode: number;
+  public readonly details?: any;
+  public readonly timestamp: Date;
+
+  constructor(
+    code: string,
+    message: string,
+    statusCode: number = 500,
+    details?: any
+  ) {
+    super(message);
+    this.code = code;
+    this.statusCode = statusCode;
+    this.details = details;
+    this.timestamp = new Date();
+
+    // 保持正确的堆栈跟踪
+    Error.captureStackTrace(this, this.constructor);
+  }
+
+  toJSON() {
+    return {
+      code: this.code,
+      message: this.message,
+      details: this.details,
+      timestamp: this.timestamp.toISOString()
+    };
+  }
+}
+
+// 特定错误类
+export class ValidationError extends AppError {
+  constructor(message: string, details?: any) {
+    super('VALIDATION_ERROR', message, 400, details);
+  }
+}
+
+export class NotFoundError extends AppError {
+  constructor(resource: string, id?: string | number) {
+    const message = id
+      ? `${resource} with ID ${id} not found`
+      : `${resource} not found`;
+    super('NOT_FOUND', message, 404);
+  }
+}
+```
+
+#### 错误处理中间件
+```typescript
+// src/server/middleware/errorHandler.ts
+import { Context } from 'hono';
+import { AppError } from '../../shared/errors/AppError';
+
+export async function errorHandler(err: Error, c: Context) {
+  // 记录错误
+  console.error('Error:', {
+    message: err.message,
+    stack: err.stack,
+    url: c.req.url,
+    method: c.req.method,
+    timestamp: new Date().toISOString()
+  });
+
+  // AppError 实例
+  if (err instanceof AppError) {
+    return c.json(
+      {
+        success: false,
+        error: {
+          code: err.code,
+          message: err.message,
+          details: err.details,
+          timestamp: err.timestamp.toISOString(),
+          requestId: c.get('requestId')
+        }
+      },
+      err.statusCode
+    );
+  }
+
+  // 未知错误(生产环境隐藏细节)
+  const isProduction = process.env.NODE_ENV === 'production';
+
+  return c.json(
+    {
+      success: false,
+      error: {
+        code: 'INTERNAL_ERROR',
+        message: isProduction ? 'Internal server error' : err.message,
+        timestamp: new Date().toISOString(),
+        requestId: c.get('requestId')
+      }
+    },
+    500
+  );
+}
+```
+
+#### 使用示例
+```typescript
+// 在服务中使用
+export class UserService {
+  async getUserById(id: number) {
+    const user = await User.findOne({ where: { id } });
+
+    if (!user) {
+      throw new NotFoundError('User', id);
+    }
+
+    return user;
+  }
+
+  async createUser(data: CreateUserDto) {
+    // 验证输入
+    const errors = validateUserData(data);
+    if (errors.length > 0) {
+      throw new ValidationError('Invalid user data', errors);
+    }
+
+    // 检查唯一性
+    const existingUser = await User.findOne({
+      where: [{ email: data.email }, { username: data.username }]
+    });
+
+    if (existingUser) {
+      throw new BusinessError('USER_ALREADY_EXISTS', 'User with this email or username already exists', 409);
+    }
+
+    // 创建用户
+    try {
+      return await User.create(data).save();
+    } catch (error) {
+      throw new DatabaseError('Failed to create user', error);
+    }
+  }
+}
+```
+
+### 4.2 前端错误处理
+
+#### 错误处理钩子
+```typescript
+// src/client/hooks/useErrorHandler.ts
+import { toast } from 'sonner';
+
+export function useErrorHandler() {
+  const handleError = (error: unknown) => {
+    console.error('Application error:', error);
+
+    if (error instanceof Error) {
+      // 显示用户友好的错误消息
+      toast.error(getUserFriendlyMessage(error));
+    } else {
+      toast.error('发生未知错误,请重试');
+    }
+  };
+
+  const getUserFriendlyMessage = (error: Error): string => {
+    const message = error.message.toLowerCase();
+
+    if (message.includes('network')) {
+      return '网络连接失败,请检查网络设置';
+    }
+
+    if (message.includes('validation')) {
+      return '输入数据验证失败,请检查表单';
+    }
+
+    if (message.includes('permission') || message.includes('auth')) {
+      return '权限不足,请重新登录';
+    }
+
+    return error.message || '操作失败,请重试';
+  };
+
+  return { handleError, getUserFriendlyMessage };
+}
+```
+
+#### API客户端错误处理
+```typescript
+// src/client/api.ts
+import { toast } from 'sonner';
+
+const axiosFetch = async (url: RequestInfo | URL, init?: RequestInit) => {
+  try {
+    const response = await axios.request({
+      url: url.toString(),
+      method: init?.method || 'GET',
+      headers: init?.headers,
+      data: init?.body,
+    });
+
+    // 处理错误响应
+    if (response.status >= 400) {
+      const errorData = response.data?.error || {};
+      throw new ApiError(
+        errorData.message || '请求失败',
+        response.status,
+        errorData.code,
+        errorData.details
+      );
+    }
+
+    return createResponse(response);
+  } catch (error) {
+    if (axios.isAxiosError(error)) {
+      const errorData = error.response?.data?.error || {};
+      throw new ApiError(
+        errorData.message || error.message,
+        error.response?.status || 500,
+        errorData.code,
+        errorData.details
+      );
+    }
+    throw error;
+  }
+};
+
+class ApiError extends Error {
+  constructor(
+    message: string,
+    public status: number,
+    public code?: string,
+    public details?: any
+  ) {
+    super(message);
+    this.name = 'ApiError';
+  }
+}
+```
+
+## 5. 日志和监控
+
+### 5.1 错误日志格式
+```typescript
+// 错误日志示例
+{
+  "level": "error",
+  "timestamp": "2025-09-15T10:30:00.000Z",
+  "message": "User creation failed",
+  "code": "VALIDATION_ERROR",
+  "stack": "Error: Invalid email format...",
+  "context": {
+    "userId": 123,
+    "email": "invalid-email",
+    "requestId": "req_1234567890",
+    "url": "/api/v1/users",
+    "method": "POST"
+  }
+}
+```
+
+### 5.2 监控配置
+```typescript
+// 错误监控集成
+import * as Sentry from '@sentry/node';
+import * as Tracing from '@sentry/tracing';
+
+Sentry.init({
+  dsn: process.env.SENTRY_DSN,
+  environment: process.env.NODE_ENV,
+  tracesSampleRate: 1.0,
+});
+
+// 在错误处理中间件中
+if (process.env.NODE_ENV === 'production') {
+  Sentry.captureException(err, {
+    extra: {
+      url: c.req.url,
+      method: c.req.method,
+      requestId: c.get('requestId')
+    }
+  });
+}
+```
+
+## 6. 测试错误场景
+
+### 6.1 错误测试示例
+```typescript
+// 测试验证错误
+describe('UserService - Error Handling', () => {
+  it('should throw ValidationError for invalid email', async () => {
+    const invalidData = { email: 'invalid', password: '123' };
+
+    await expect(userService.createUser(invalidData))
+      .rejects
+      .toThrow(ValidationError);
+
+    await expect(userService.createUser(invalidData))
+      .rejects
+      .toMatchObject({
+        code: 'VALIDATION_ERROR',
+        statusCode: 400
+      });
+  });
+
+  it('should throw NotFoundError for non-existent user', async () => {
+    await expect(userService.getUserById(999))
+      .rejects
+      .toThrow(NotFoundError);
+  });
+});
+
+// 测试错误响应格式
+describe('Error Handler Middleware', () => {
+  it('should return formatted error response', async () => {
+    const server = createTestServer();
+
+    const response = await server.inject({
+      method: 'GET',
+      url: '/api/v1/users/999'
+    });
+
+    expect(response.statusCode).toBe(404);
+    expect(response.json()).toEqual({
+      success: false,
+      error: {
+        code: 'NOT_FOUND',
+        message: 'User with ID 999 not found',
+        timestamp: expect.any(String),
+        requestId: expect.any(String)
+      }
+    });
+  });
+});
+```
+
+## 7. 最佳实践清单
+
+### 7.1 错误处理检查清单
+- [ ] 所有错误都有明确的错误代码
+- [ ] 错误消息对用户友好且可操作
+- [ ] 生产环境不泄露敏感信息
+- [ ] 错误包含足够的调试上下文
+- [ ] 错误响应格式统一
+- [ ] 适当的HTTP状态码
+- [ ] 错误日志记录完整
+- [ ] 前端错误处理用户友好
+- [ ] 测试覆盖错误场景
+
+### 7.2 安全注意事项
+- ❌ 不要返回堆栈跟踪到客户端
+- ❌ 不要泄露数据库错误细节
+- ❌ 不要暴露敏感配置信息
+- ✅ 使用通用的错误消息
+- ✅ 记录详细错误到服务器日志
+- ✅ 使用请求ID关联错误
+
+---
+
+**最后更新**: 2025-09-15
+**维护者**: 开发团队
+**文档状态**: 正式版

+ 100 - 0
jest.config.js

@@ -0,0 +1,100 @@
+/** @type {import('jest').Config} */
+const config = {
+  preset: 'ts-jest',
+  testEnvironment: 'node',
+
+  // 测试文件匹配模式
+  testMatch: [
+    '**/__tests__/**/*.test.[jt]s?(x)',
+    '**/?(*.)+(spec|test).[jt]s?(x)'
+  ],
+
+  // 模块名称映射
+  moduleNameMapping: {
+    '^@/(.*)$': '<rootDir>/src/$1',
+    '^@/client/(.*)$': '<rootDir>/src/client/$1',
+    '^@/server/(.*)$': '<rootDir>/src/server/$1',
+    '^@/shared/(.*)$': '<rootDir>/src/shared/$1',
+    '^@/test/(.*)$': '<rootDir>/test/$1'
+  },
+
+  // 覆盖率配置
+  collectCoverageFrom: [
+    'src/**/*.{js,jsx,ts,tsx}',
+    '!src/**/*.d.ts',
+    '!src/client/api.ts',
+    '!src/**/__tests__/**',
+    '!src/**/__mocks__/**',
+    '!src/**/index.ts',
+    '!src/**/types.ts'
+  ],
+
+  coverageDirectory: 'coverage',
+  coverageReporters: ['text', 'lcov', 'html'],
+  coverageThreshold: {
+    global: {
+      branches: 70,
+      functions: 70,
+      lines: 70,
+      statements: 70
+    }
+  },
+
+  // 测试超时
+  testTimeout: 10000,
+
+  // 测试环境设置
+  setupFilesAfterEnv: ['<rootDir>/src/test/setup.ts'],
+
+  // 变换配置
+  transform: {
+    '^.+\.[tj]sx?$': ['ts-jest', {
+      tsconfig: 'tsconfig.json',
+      useESM: true,
+    }],
+  },
+
+  // 模块文件扩展名
+  moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
+
+  // 测试路径忽略模式
+  testPathIgnorePatterns: [
+    '/node_modules/',
+    '/dist/',
+    '/build/',
+    '/coverage/'
+  ],
+
+  // 监听模式配置
+  watchPathIgnorePatterns: [
+    '/node_modules/',
+    '/dist/',
+    '/build/',
+    '/coverage/'
+  ],
+
+  // 测试运行器显示配置
+  verbose: true,
+  notify: false,
+
+  // CI环境配置
+  maxWorkers: process.env.CI ? 2 : '50%',
+
+  // 全局变量
+  globals: {
+    'ts-jest': {
+      isolatedModules: true
+    }
+  }
+};
+
+// 前端测试环境特殊配置
+if (process.env.TEST_ENV === 'ui') {
+  config.testEnvironment = 'jsdom';
+  config.setupFilesAfterEnv = [
+    '<rootDir>/src/test/setup.ts',
+    '@testing-library/jest-dom'
+  ];
+}
+
+module.exports = config;

+ 19 - 1
package.json

@@ -8,7 +8,18 @@
     "build": "npm run build:client && npm run build:server",
     "build:client": "vite build --outDir dist/client --manifest",
     "build:server": "vite build --ssr src/server/index.tsx --outDir dist/server",
-    "start": "PORT=8080 cross-env NODE_ENV=production node server"
+    "start": "PORT=8080 cross-env NODE_ENV=production node server",
+    "test": "jest",
+    "test:coverage": "jest --coverage",
+    "test:watch": "jest --watch",
+    "test:ui": "jest src/client/__tests__",
+    "test:api": "jest src/server/__tests__",
+   "db:migrate": "tsx scripts/migrate.ts",
+    "db:seed": "tsx scripts/seed.ts",
+    "db:reset": "tsx scripts/reset-db.ts",
+    "lint": "eslint . --ext .ts,.tsx",
+    "lint:fix": "eslint . --ext .ts,.tsx --fix",
+    "typecheck": "tsc --noEmit"
   },
   "dependencies": {
     "@ant-design/icons": "^6.0.0",
@@ -81,14 +92,21 @@
   },
   "devDependencies": {
     "@tailwindcss/vite": "^4.1.11",
+    "@testing-library/jest-dom": "^6.6.3",
+    "@testing-library/react": "^16.3.2",
+    "@testing-library/user-event": "^14.6.2",
     "@types/bcrypt": "^6.0.0",
     "@types/debug": "^4.1.12",
+    "@types/jest": "^29.5.14",
     "@types/jsonwebtoken": "^9.0.10",
     "@types/node": "^24.0.10",
     "@types/react": "^19.1.8",
     "@types/react-dom": "^19.1.6",
     "@vitejs/plugin-react-swc": "^3.10.2",
     "cross-env": "^7.0.3",
+    "jest": "^29.7.0",
+    "jest-environment-jsdom": "^29.7.0",
+    "ts-jest": "^29.2.5",
     "tailwindcss": "^4.1.11",
     "tsx": "^4.20.3",
     "typescript": "~5.8.3",

+ 45 - 0
src/test/setup.ts

@@ -0,0 +1,45 @@
+// 测试环境全局设置
+import { jest } from '@jest/globals';
+
+// 全局测试超时设置
+jest.setTimeout(10000);
+
+// 全局测试前置处理
+beforeAll(() => {
+  // 设置测试环境变量
+  process.env.NODE_ENV = 'test';
+
+  // 抑制控制台输出(测试中)
+  jest.spyOn(console, 'log').mockImplementation(() => {});
+  jest.spyOn(console, 'error').mockImplementation(() => {});
+  jest.spyOn(console, 'warn').mockImplementation(() => {});
+  jest.spyOn(console, 'info').mockImplementation(() => {});
+});
+
+// 全局测试后置清理
+afterAll(() => {
+  // 恢复控制台输出
+  jest.restoreAllMocks();
+});
+
+// 每个测试后的清理
+afterEach(() => {
+  jest.clearAllMocks();
+});
+
+// 全局测试工具函数
+globalThis.createTestContext = () => ({
+  timestamp: new Date().toISOString(),
+  requestId: `test_${Math.random().toString(36).substr(2, 9)}`
+});
+
+// 类型声明
+declare global {
+  // eslint-disable-next-line no-var
+  var createTestContext: () => {
+    timestamp: string;
+    requestId: string;
+  };
+}
+
+export {};