修复GET请求查询参数验证装饰器缺失问题,添加正确的class-validator装饰器 在控制器中实现查询参数类型转换,确保数字参数正确处理 更新技术文档,添加DTO验证装饰器编写规范和GET请求参数处理指南
12 KiB
12 KiB
技术栈与开发环境
核心技术栈
后端框架
- NestJS 11.x: TypeScript 原生支持的企业级 Node.js 框架
- TypeScript 5.7+: 类型安全的 JavaScript 超集
- Express.js: 底层 HTTP 服务器框架(NestJS 内置)
数据库与 ORM
- MySQL 8.0: 主数据库,支持 JSON 字段和全文索引
- Sequelize 6.x: ORM 框架,支持 TypeScript 和迁移
- Sequelize-typescript: TypeScript 类型定义和装饰器支持
认证与安全
- JWT (jsonwebtoken): 无状态身份认证
- Apple Sign-In: iOS 生态登录集成
- crypto-js: 客户端/服务端加密工具
- AES-256-GCM: 敏感数据加密标准
AI 与机器学习
- OpenAI SDK: AI 模型调用统一接口
- 通义千问 (阿里云): 主要 AI 模型服务
- qwen-vl-max: 视觉识别专用模型
- qwen-flash: 快速对话模型
文件存储与云服务
- 腾讯云 COS: 对象存储服务
- qcloud-cos-sts: 临时访问凭证管理
- APNs (Apple Push Notification): iOS 推送服务
- @parse/node-apn: APNs 服务端 SDK
开发工具与环境
包管理与构建
- yarn: 包管理器(支持工作空间)
- npm: 备用包管理器
- SWC: 快速 TypeScript 编译器
- ts-node: 开发时 TypeScript 执行
代码质量与规范
- ESLint 9.x: 代码质量检查
- Prettier: 代码格式化
- TypeScript: 静态类型检查
- Husky: Git hooks 管理(未配置但推荐)
测试框架
- Jest 29.x: 单元测试和集成测试
- Supertest: HTTP 接口测试
- ts-jest: TypeScript 测试支持
部署与运维
- PM2: Node.js 进程管理器
- Docker: 容器化部署(未完全实现)
- Nginx: 反向代理和负载均衡
- Winston: 结构化日志记录
项目配置文件
核心配置
package.json: 项目依赖和脚本定义tsconfig.json: TypeScript 编译配置nest-cli.json: NestJS CLI 配置ecosystem.config.js: PM2 集群配置
环境配置
.env: 开发环境变量(不提交到版本控制).env.glm.example: 环境变量模板eslint.config.mjs: ESLint 配置
部署配置
deploy.sh: 完整部署脚本deploy-optimized.sh: 优化部署脚本start.sh: 服务启动脚本
开发环境设置
本地开发要求
- Node.js: >= 18.0.0
- MySQL: >= 8.0
- yarn: 最新稳定版
- Git: 版本控制
开发命令
# 安装依赖
yarn install
# 开发模式启动
yarn start:dev
# 构建项目
yarn build
# 生产模式启动
yarn start:prod
# 运行测试
yarn test
# 代码检查
yarn lint
# 代码格式化
yarn format
PM2 管理命令
# 启动开发环境
yarn pm2:start:dev
# 启动生产环境
yarn pm2:start
# 查看状态
yarn pm2:status
# 查看日志
yarn pm2:logs
# 重启服务
yarn pm2:restart
# 停止服务
yarn pm2:stop
环境变量配置
必需的环境变量
# 数据库配置
DB_HOST=localhost
DB_PORT=3306
DB_USERNAME=your_username
DB_PASSWORD=your_password
DB_DATABASE=pilates_db
# JWT 配置
JWT_SECRET=your_jwt_secret_key
JWT_EXPIRES_IN=7d
REFRESH_TOKEN_SECRET=your_refresh_token_secret
REFRESH_TOKEN_EXPIRES_IN=30d
# Apple 认证配置
APPLE_BUNDLE_ID=com.yourcompany.pilates
APPLE_KEY_ID=your_apple_key_id
APPLE_ISSUER_ID=your_apple_issuer_id
APPLE_PRIVATE_KEY_PATH=path/to/private/key.p8
APPLE_APP_SHARED_SECRET=your_app_shared_secret
# AI 服务配置
DASHSCOPE_API_KEY=your_dashscope_api_key
DASHSCOPE_BASE_URL=https://dashscope.aliyuncs.com/compatible-mode/v1
DASHSCOPE_MODEL=qwen-flash
DASHSCOPE_VISION_MODEL=qwen-vl-max
# 加密配置
ENCRYPTION_KEY=your-32-character-secret-key-here
# 腾讯云 COS 配置
COS_SECRET_ID=your_cos_secret_id
COS_SECRET_KEY=your_cos_secret_key
COS_REGION=your_cos_region
COS_BUCKET=your_cos_bucket
# RevenueCat 配置
REVENUECAT_PUBLIC_API_KEY=your_revenuecat_public_key
REVENUECAT_SECRET_API_KEY=your_revenuecat_secret_key
# 服务配置
PORT=3002
NODE_ENV=development
数据库架构
字符集和排序规则
- 字符集:
utf8mb4(支持完整 Unicode,包括 emoji) - 排序规则:
utf8mb4_unicode_ci(不区分大小写) - 时区: 统一使用 UTC 时间
连接配置
- 连接池: 最大连接数 10,最小连接数 0
- 超时设置: 查询超时 30 秒,连接超时 10 秒
- 自动重连: 启用连接失败自动重连
迁移策略
- 自动同步: 开发环境使用
synchronize: true - 生产环境: 使用 SQL 脚本手动执行迁移
- 版本控制: SQL 脚本存储在
sql-scripts/目录
API 设计规范
RESTful 设计原则
- 资源导向: 使用名词表示资源
- HTTP 方法: GET/POST/PUT/DELETE 对应 CRUD 操作
- 状态码: 标准 HTTP 状态码 + 业务错误码
- 统一响应: 使用
ApiResponseDto包装所有响应
接口版本控制
- 路径版本:
/api/v1/users(推荐) - 查询参数:
?version=1(备选) - Header 版本:
Accept: application/vnd.api+json;version=1(备选)
请求响应格式
// 成功响应
{
"code": 0,
"message": "操作成功",
"data": { ... }
}
// 错误响应
{
"code": 1,
"message": "错误描述",
"data": null
}
日志管理
日志级别
- ERROR: 错误信息,需要立即关注
- WARN: 警告信息,可能的问题
- INFO: 一般信息,重要业务操作
- DEBUG: 调试信息,详细的执行流程
日志配置
- Winston: 结构化日志记录
- 日志轮转: 按日期和大小自动轮转
- 多输出: 同时输出到文件和控制台
- 格式化: JSON 格式便于分析
日志文件
logs/error.log: 错误日志logs/output.log: 一般输出日志logs/combined.log: 合并日志
安全最佳实践
数据加密
- 传输加密: 强制 HTTPS/TLS 1.3
- 存储加密: 敏感字段 AES-256-GCM 加密
- 密钥管理: 环境变量 + 定期轮换
- 哈希算法: bcrypt 处理密码(如需要)
输入验证
- class-validator: DTO 数据验证
- class-transformer: 数据转换和清理
- SQL 注入防护: Sequelize ORM 自动防护
- XSS 防护: 输入清理和输出编码
DTO 验证装饰器编写规范
基本原则:
- 所有 DTO 类必须同时包含
@ApiProperty和class-validator装饰器 @ApiProperty用于 Swagger 文档生成,class-validator用于数据验证- 缺少验证装饰器会导致参数校验失败,使 API 端点无法正常工作
必需要导入的验证装饰器:
import { IsOptional, IsDateString, IsNumber, IsString, IsNotEmpty, IsEnum, MaxLength, Min, Max } from 'class-validator';
常用字段验证规则:
- 分页参数:在 GET 请求中使用
@IsOptional()+@IsString(),在控制器中转换为数字;在 POST/PUT 请求中使用@IsOptional()+@IsNumber() - 日期参数:
startDate和endDate使用@IsOptional()+@IsDateString() - 字符串参数:使用
@IsOptional()+@IsString(),必要时添加@MaxLength() - 枚举参数:使用
@IsOptional()+@IsEnum(EnumType) - 必填字段:使用
@IsNotEmpty()而不是只使用@IsString()
GET 请求参数特殊处理
重要说明:GET 请求的查询参数(Query Parameters)在 HTTP 协议中都是字符串类型,即使看起来是数字(如 ?page=1&limit=20)。因此需要特殊处理:
GET 请求 DTO 定义:
export class GetRecordsQueryDto {
@ApiProperty({ description: '页码', example: 1, required: false })
@IsOptional()
@IsString() // 注意:GET 请求中使用 @IsString() 而不是 @IsNumber()
page?: string;
@ApiProperty({ description: '每页数量', example: 20, required: false })
@IsOptional()
@IsString() // 注意:GET 请求中使用 @IsString() 而不是 @IsNumber()
limit?: string;
}
控制器中的类型转换:
async getRecords(
@Query() query: GetRecordsQueryDto,
@CurrentUser() user: AccessTokenPayload,
): Promise<RecordsResponseDto> {
// 转换查询参数中的字符串为数字
const convertedQuery = {
page: query.page ? parseInt(query.page, 10) : undefined,
limit: query.limit ? parseInt(query.limit, 10) : undefined,
// 其他字符串参数保持不变
startDate: query.startDate,
endDate: query.endDate,
status: query.status,
};
const result = await this.service.getRecords(user.sub, convertedQuery);
return result;
}
POST/PUT 请求 DTO 定义(对比):
export class CreateRecordDto {
@ApiProperty({ description: '数量', example: 10 })
@IsNumber() // POST/PUT 请求中可以直接使用 @IsNumber()
quantity: number;
}
正确示例(GET 请求):
export class GetRecordsQueryDto {
@ApiProperty({ description: '页码', example: 1, required: false })
@IsOptional()
@IsString() // GET 请求中使用 @IsString()
page?: string;
@ApiProperty({ description: '每页数量', example: 20, required: false })
@IsOptional()
@IsString() // GET 请求中使用 @IsString()
limit?: string;
@ApiProperty({ description: '开始日期', example: '2023-01-01', required: false })
@IsOptional()
@IsDateString()
startDate?: string;
@ApiProperty({ description: '状态', example: 'active', required: false })
@IsOptional()
@IsString()
status?: string;
}
正确示例(POST/PUT 请求):
export class CreateRecordDto {
@ApiProperty({ description: '数量', example: 10 })
@IsNumber() // POST/PUT 请求中可以直接使用 @IsNumber()
quantity: number;
@ApiProperty({ description: '名称', example: '测试' })
@IsString()
@IsNotEmpty()
name: string;
}
常见错误:
- ❌ 只有
@ApiProperty而缺少class-validator装饰器 - ❌ GET 请求中的数字参数使用
@IsNumber()而不是@IsString() - ❌ 使用
@IsString()但没有@IsOptional()处理可选参数 - ❌ 日期字段没有使用
@IsDateString()验证 - ❌ GET 请求中忘记在控制器进行类型转换,导致服务层接收到字符串而不是数字
- ❌ 类型转换时没有进行空值检查,可能导致
parseInt(undefined)返回NaN
访问控制
- JWT 认证: 无状态 Token 认证
- 权限守卫: 基于角色的访问控制
- 速率限制: 防止 API 滥用和攻击
- CORS 配置: 跨域请求安全控制
性能优化
数据库优化
- 索引策略: 为常用查询字段添加索引
- 查询优化: 避免 N+1 查询问题
- 连接池: 合理配置数据库连接池
- 分页查询: 大数据集分页处理
缓存策略
- 内存缓存: 热点数据内存缓存
- 查询缓存: 数据库查询结果缓存
- CDN 缓存: 静态资源 CDN 分发
- 浏览器缓存: 合理设置 Cache-Control
代码优化
- 异步处理: 使用 async/await 处理异步操作
- 批量操作: 减少数据库往返次数
- 流式处理: 大数据量流式处理
- 懒加载: 按需加载模块和数据
监控与调试
应用监控
- PM2 监控: 进程状态和资源使用
- 健康检查: 应用健康状态接口
- 性能指标: 响应时间和吞吐量
- 错误追踪: 异常自动收集和报告
调试工具
- Source Map: 生产环境调试支持
- 日志分析: 结构化日志查询和分析
- API 文档: Swagger 自动生成文档
- 数据库工具: MySQL Workbench/Sequel Pro
部署架构
服务器环境
- 操作系统: Ubuntu 20.04 LTS
- Node.js: 18.x LTS 版本
- 数据库: MySQL 8.0
- Web 服务器: Nginx 1.18+
部署流程
- 代码构建: 本地或服务器端 TypeScript 编译
- 依赖安装: 生产依赖安装和锁定
- 数据库迁移: SQL 脚本执行和数据迁移
- 服务启动: PM2 集群模式启动应用
- 健康检查: 验证服务正常运行
容器化部署(未来)
- Docker: 应用容器化
- Docker Compose: 多服务编排
- Kubernetes: 容器编排管理
- CI/CD: 自动化构建和部署流水线