# ChatOne Service 项目方案(NestJS) 本文给出一个主流且简洁、可直接落地的服务端方案,满足以下目标: - 基于 NestJS 构建; - 同时提供客户端接口与后台管理接口; - 支持短信登录、邮箱登录; - 支持千问、火山引擎、DeepSeek 等多平台 AI Chat 流式输出; - 对外提供统一调用接口(自动路由)和指定平台调用接口; - 提供用户管理、平台管理、用量统计。 --- ## 1. 技术选型(主流且简洁) - **运行时**:Node.js 20 LTS - **框架**:NestJS 10(Fastify 适配器,性能更优) - **ORM**:Prisma(类型安全、迁移友好) - **数据库**:PostgreSQL 15 - **缓存/队列**:Redis 7(缓存、限流、验证码、会话黑名单) - **鉴权**:JWT(Access + Refresh)+ RBAC - **文档**:Swagger(`@nestjs/swagger`) - **校验**:`class-validator` + `class-transformer` - **日志**:Pino(`nestjs-pino`) - **任务调度**:`@nestjs/schedule`(统计聚合作业) - **流式输出**:SSE(标准 EventSource,前端接入简单) --- ## 2. 项目结构(推荐:按用户端/管理端强隔离) ```text chat-one-service/ ├─ src/ │ ├─ main.ts # 单体双端统一启动入口(MVP) │ ├─ app.module.ts # 根模块:聚合 client/admin/shared │ ├─ common/ │ │ ├─ constants/ # 全局常量(错误码前缀、header key 等) │ │ ├─ decorators/ # 自定义装饰器(当前用户、角色、公开路由) │ │ ├─ filters/ # 全局异常过滤(统一错误响应格式) │ │ ├─ guards/ # 通用 guard(按需被 client/admin 复用) │ │ ├─ interceptors/ # 请求日志、traceId、响应耗时统计 │ │ ├─ pipes/ # DTO 校验与类型转换 │ │ └─ utils/ # 通用工具函数(时间、脱敏、签名等) │ ├─ config/ │ │ ├─ configuration.ts # 读取并组织配置(按模块导出) │ │ ├─ validation.ts # 启动前环境变量校验(fail fast) │ │ └─ swagger.ts # Swagger 初始化与分组(client/admin) │ ├─ apps/ │ │ ├─ client-app/ # 用户端边界:面向 C 端业务能力 │ │ │ ├─ client-app.module.ts # 用户端聚合模块(auth/chat/sessions/profile) │ │ │ ├─ auth/ # 用户端认证:短信验证码、登录、refresh │ │ │ ├─ chat/ │ │ │ │ ├─ chat.module.ts # chat 子模块聚合入口 │ │ │ │ ├─ controllers/ # 对外接口层(统一/指定平台 SSE 入口) │ │ │ │ ├─ application/ # 用例编排层(配额检查、路由、日志) │ │ │ │ ├─ domain/ # 领域规则层(会话/消息/策略) │ │ │ │ ├─ infrastructure/ # 基础设施层(Prisma/Redis/SSE/事件) │ │ │ │ ├─ dto/ # 请求/响应 DTO(仅用户端可见字段) │ │ │ │ └─ constants/ # chat 常量(事件类型、错误码、模型别名) │ │ │ ├─ sessions/ # 会话与历史查询(可逐步并入 chat) │ │ │ └─ profile/ # 用户资料、偏好设置、个人配置 │ │ ├─ admin-app/ # 管理端边界:面向运营/管理能力 │ │ │ ├─ admin-app.module.ts # 管理端聚合模块(auth/users/platforms/stats) │ │ │ ├─ auth/ # 管理端认证:邮箱登录、RBAC、审计 │ │ │ ├─ users/ # 用户管理:查询、禁用、角色与额度治理 │ │ │ ├─ platforms/ # 平台管理:密钥、权重、模型映射、健康检查 │ │ │ ├─ stats/ # 统计报表:请求量、token、成本、错误率 │ │ │ └─ audits/ # 审计日志:关键管理操作留痕 │ │ └─ shared-domain/ # 双端共享能力(无业务端特有语义) │ │ ├─ ai-gateway/ │ │ │ ├─ providers/ # 第三方平台适配实现(qwen/volc/deepseek) │ │ │ │ ├─ provider.interface.ts# provider 抽象协议,统一调用方式 │ │ │ │ ├─ qwen.provider.ts │ │ │ │ ├─ volc.provider.ts │ │ │ │ └─ deepseek.provider.ts │ │ │ ├─ router/ │ │ │ │ └─ provider-router.service.ts # 自动路由与降级策略 │ │ │ └─ formatter/ │ │ │ └─ stream-normalizer.service.ts # 统一 SSE chunk 输出协议 │ │ ├─ identity/ # 身份能力:JWT 签发、策略、guard、token 工具 │ │ ├─ sms/ # 短信能力:验证码发送、频控、模板封装 │ │ ├─ mail/ # 邮件能力:登录通知、告警通知、模板发送 │ │ └─ stats-core/ # 统计核心:聚合计算、指标定义、通用查询 │ ├─ prisma/ │ │ ├─ prisma.module.ts # Prisma 注入模块 │ │ └─ prisma.service.ts # PrismaClient 生命周期与扩展 │ └─ types/ # 全局类型声明(枚举、共享接口) ├─ prisma/ │ ├─ schema.prisma # 数据模型定义 │ └─ migrations/ # 数据库迁移历史 ├─ docs/ │ └─ project-solution.md # 架构与实现方案文档 ├─ .env.example # 环境变量示例 ├─ package.json # 依赖与脚本 └─ tsconfig.json # TypeScript 编译配置 ``` --- ## 3. 模块职责 ### 3.1 用户端(`client-app`) - **client-auth** - 仅负责短信验证码发送/校验、客户端 token 签发与刷新 - **chat** - 提供统一 chat 接口与指定平台 chat 接口 - 对外只暴露用户侧可见字段,不暴露平台敏感信息 - 按 `controller -> application -> domain -> infrastructure` 分层,便于平滑拆分 - **sessions/profile(可选)** - 会话记录、消息历史、个人资料 #### chat 子目录建议(简化版) - **controllers** - 仅处理 HTTP/SSE 协议、参数校验、响应头,不写业务逻辑 - **application** - 编排用例流程(鉴权通过后,做配额检查、路由 provider、写 usage) - **domain** - 放会话、消息、配额策略等核心业务规则与仓储抽象接口 - **infrastructure** - 对接 Prisma、Redis、SSE writer、消息队列等外部依赖实现 - **dto** - 负责 API 入参与出参定义,避免直接暴露内部模型 - **constants** - 统一维护 chat 错误码、模型别名、事件类型常量 建议保持这个粒度即可,后续按开发规模再向下细分,不必一开始拆到文件级别。 ### 3.2 管理端(`admin-app`) - **admin-auth** - 仅负责邮箱密码登录(可选二次验证码) - **users** - 用户管理、封禁/解禁、角色治理 - **platforms** - AI 平台开关、密钥、权重、模型映射、健康检查 - **stats/audits** - 用量统计、成本分析、审计日志 ### 3.3 共享域(`shared-domain`) - **ai-gateway** - 第三方平台适配、自动路由、流式格式统一 - **identity** - JWT strategy、通用 guard、token 工具 - **sms/mail/stats-core** - 可复用基础设施能力,避免双端重复实现 > 设计原则:控制器和应用服务严格按端隔离,后续拆分时优先迁移 `client-app` 或 `admin-app` 整个目录,shared 部分按需复制或抽公共包。 --- ## 4. API 设计(示例) 统一前缀建议: - 客户端:`/api/client/v1` - 管理端:`/api/admin/v1` 为避免后期冲突,建议从现在开始执行以下规范: - DTO 分离:`client-app/dto` 与 `admin-app/dto` 禁止互相引用; - Guard 分离:`ClientJwtGuard` 与 `AdminJwtGuard` 不混用; - Swagger 分离:客户端文档与管理端文档分组输出(如 `/docs-client`、`/docs-admin`); - 错误码分离:客户端错误码与管理端错误码使用不同前缀(如 `C_` / `A_`)。 ### 4.1 客户端认证接口(短信) - `POST /api/client/v1/auth/sms/send` - 入参:`{ phone, scene }` - 出参:`{ requestId, expireIn }` - `POST /api/client/v1/auth/sms/login` - 入参:`{ phone, code }` - 出参:`{ accessToken, refreshToken, user }` - `POST /api/client/v1/auth/refresh` - 入参:`{ refreshToken }` - 出参:`{ accessToken, refreshToken }` ### 4.2 客户端 AI Chat 接口(流式) - `POST /api/client/v1/chat/completions/stream` - 说明:统一接口,后台自动路由平台 - 入参示例: ```json { "model": "gpt-4o-mini-like", "messages": [ { "role": "system", "content": "你是助手" }, { "role": "user", "content": "介绍一下NestJS" } ], "temperature": 0.7, "platform": "auto" } ``` - 返回:`text/event-stream`(SSE) - `POST /api/client/v1/chat/completions/stream/:platform` - 说明:指定平台(`qwen | volc | deepseek`) - 其余参数同上 ### 4.3 建议统一 SSE 事件格式 ```text event: meta data: {"requestId":"xxx","platform":"qwen","model":"qwen-turbo"} event: delta data: {"content":"你好"} event: usage data: {"promptTokens":120,"completionTokens":80,"totalTokens":200} event: done data: {"finishReason":"stop"} event: error data: {"code":"PLATFORM_TIMEOUT","message":"upstream timeout"} ``` ### 4.4 管理端认证接口(邮箱) - `POST /api/admin/v1/auth/login` - 入参:`{ email, password }` - 出参:`{ accessToken, refreshToken, admin }` - `POST /api/admin/v1/auth/refresh` - `POST /api/admin/v1/auth/logout` ### 4.5 管理端用户管理 - `GET /api/admin/v1/users` - `GET /api/admin/v1/users/:id` - `PATCH /api/admin/v1/users/:id/status`(启用/禁用) - `PATCH /api/admin/v1/users/:id/role` ### 4.6 管理端平台管理 - `GET /api/admin/v1/platforms` - `POST /api/admin/v1/platforms` - `PATCH /api/admin/v1/platforms/:id` - `PATCH /api/admin/v1/platforms/:id/health-check` ### 4.7 管理端统计 - `GET /api/admin/v1/stats/overview?startDate=&endDate=` - `GET /api/admin/v1/stats/platforms` - `GET /api/admin/v1/stats/users/top` --- ## 5. 自动路由与统一格式方案 ### 5.1 自动路由策略(简版) 按优先级执行: 1. 过滤禁用平台; 2. 过滤健康检查失败平台; 3. 根据模型支持能力过滤; 4. 根据权重 + 当前错误率 + 当前限流剩余综合打分; 5. 选择得分最高平台; 6. 失败时自动降级重试下一个平台(最多 1~2 次)。 ### 5.2 统一消息格式(内部 DTO) - `ChatMessageDto`: `{ role, content, name?, toolCall? }` - `ChatRequestDto`: `{ model, messages, temperature, topP, stream, userId }` - `ChatChunkDto`: `{ type: 'meta'|'delta'|'usage'|'done'|'error', payload }` 第三方差异全部在 provider 内部适配,Controller 对外始终输出统一 SSE 事件。 --- ## 6. 依赖清单(核心) ```bash # Nest 基础 npm i @nestjs/common @nestjs/core @nestjs/platform-fastify @nestjs/config @nestjs/jwt @nestjs/passport @nestjs/swagger npm i class-validator class-transformer passport passport-jwt # 数据库/缓存 npm i @prisma/client ioredis npm i -D prisma # 日志/安全/限流 npm i nestjs-pino pino-http helmet @nestjs/throttler # 任务/工具 npm i @nestjs/schedule dayjs # 第三方请求 npm i undici # 开发依赖 npm i -D typescript ts-node tsx @types/node eslint prettier ``` 可选增强: - `argon2`:密码哈希 - `zod`:配置或响应结构额外校验 - `@opentelemetry/api`:链路追踪 --- ## 7. 配置文件示例 ### 7.1 `.env.example` ```env # App NODE_ENV=development PORT=3000 APP_NAME=chat-one-service APP_BASE_URL=http://localhost:3000 # JWT JWT_ACCESS_SECRET=replace_me_access JWT_REFRESH_SECRET=replace_me_refresh JWT_ACCESS_EXPIRES_IN=2h JWT_REFRESH_EXPIRES_IN=30d # PostgreSQL DATABASE_URL=postgresql://postgres:postgres@127.0.0.1:5432/chat_one?schema=public # Redis REDIS_HOST=127.0.0.1 REDIS_PORT=6379 REDIS_PASSWORD= REDIS_DB=0 # SMS SMS_PROVIDER=aliyun SMS_ACCESS_KEY_ID=your_key SMS_ACCESS_KEY_SECRET=your_secret SMS_SIGN_NAME=ChatOne SMS_TEMPLATE_CODE_LOGIN=SMS_123456789 SMS_CODE_TTL_SECONDS=300 # Mail (admin login / notice) MAIL_HOST=smtp.qq.com MAIL_PORT=465 MAIL_SECURE=true MAIL_USER=xxx@qq.com MAIL_PASS=app_password MAIL_FROM=ChatOne # AI Providers QWEN_API_KEY=your_qwen_key QWEN_BASE_URL=https://dashscope.aliyuncs.com/compatible-mode/v1 VOLC_API_KEY=your_volc_key VOLC_BASE_URL=https://ark.cn-beijing.volces.com/api/v3 DEEPSEEK_API_KEY=your_deepseek_key DEEPSEEK_BASE_URL=https://api.deepseek.com/v1 # Route Strategy AI_ROUTE_RETRY_TIMES=1 AI_ROUTE_TIMEOUT_MS=45000 # Split-ready(拆分预留) CLIENT_PORT=3000 ADMIN_PORT=3001 CLIENT_JWT_ACCESS_SECRET=replace_me_client_access ADMIN_JWT_ACCESS_SECRET=replace_me_admin_access REDIS_KEY_PREFIX_CLIENT=chatone:client REDIS_KEY_PREFIX_ADMIN=chatone:admin ``` ### 7.2 `src/config/validation.ts`(建议) 使用 `class-validator` 或 `zod` 校验环境变量,应用启动即失败(fail fast),避免线上缺配置。 --- ## 8. 数据库设计(PostgreSQL) 以下为简化且够用的核心表: ### 8.1 用户与管理员 - `users` - `id` (bigserial pk) - `phone` (varchar unique) - `nickname` (varchar) - `status` (smallint, 1正常 0禁用) - `created_at`, `updated_at` - `admins` - `id` - `email` (varchar unique) - `password_hash` (varchar) - `role` (varchar: super_admin/admin/ops) - `status` - `last_login_at` - `created_at`, `updated_at` ### 8.2 平台配置与模型映射 - `ai_platforms` - `id` - `code` (varchar unique: qwen/volc/deepseek) - `name` - `enabled` (bool) - `weight` (int default 100) - `priority` (int default 100) - `base_url` - `api_key_encrypted` - `timeout_ms` - `rpm_limit` - `health_status` (varchar: healthy/unhealthy/unknown) - `created_at`, `updated_at` - `ai_platform_models` - `id` - `platform_id` (fk) - `biz_model` (varchar) # 业务统一模型名 - `provider_model` (varchar) # 平台真实模型名 - `enabled` (bool) - unique(platform_id, biz_model) ### 8.3 会话与消息(可选落库) - `chat_sessions` - `id` - `user_id` (fk) - `title` - `created_at`, `updated_at` - `chat_messages` - `id` - `session_id` (fk) - `role` (user/assistant/system) - `content` (text) - `token_count` (int) - `provider` (varchar) - `created_at` ### 8.4 请求审计与统计 - `ai_request_logs` - `id` - `request_id` (varchar unique) - `user_id` (fk nullable) - `platform_code` - `biz_model` - `provider_model` - `status` (success/fail) - `error_code` - `latency_ms` - `prompt_tokens` - `completion_tokens` - `total_tokens` - `estimated_cost` - `created_at` - `usage_daily_stats` - `id` - `stat_date` (date) - `dimension` (platform/user/total) - `dimension_key` (varchar) - `request_count` - `success_count` - `fail_count` - `total_tokens` - `estimated_cost` - unique(stat_date, dimension, dimension_key) --- ## 9. 缓存方案(Redis) 建议键设计: - 短信验证码:`sms:code:{phone}:{scene}`(TTL 5 分钟) - 短信发送频控:`sms:send:limit:{phone}`(TTL 60 秒) - 刷新令牌黑名单:`auth:rt:blacklist:{jti}`(TTL 到 token 过期) - 用户会话缓存:`auth:user:{userId}`(TTL 30 分钟,可选) - 平台健康状态:`ai:platform:health:{code}`(TTL 30 秒) - 平台动态权重:`ai:platform:weight:{code}`(TTL 60 秒) - 统计临时聚合:`stats:daily:{date}:{dimension}`(TTL 1 天) 为便于拆分,建议从第一天就加命名空间前缀: - 用户端:`chatone:client:*` - 管理端:`chatone:admin:*` - 共享:`chatone:shared:*` 同时启用 `@nestjs/throttler` 做接口限流: - 客户端 chat 接口:按用户 + IP 双维度限流; - 登录接口:按手机号/邮箱严格限流,防刷。 --- ## 10. 登录与鉴权方案 ### 10.1 客户端(短信登录) 1. 用户请求发送验证码; 2. 服务端生成验证码写入 Redis(哈希存储,避免明文); 3. 用户提交验证码登录,校验通过后签发 JWT; 4. AccessToken 短期(2h),RefreshToken 长期(30d); 5. 刷新时校验 RefreshToken 的 `jti` 与黑名单状态; 6. 注销时将 RefreshToken `jti` 拉黑至过期。 ### 10.2 管理端(邮箱登录) 1. 邮箱 + 密码登录(密码 `argon2` 哈希); 2. 可选开启邮箱二次验证码; 3. JWT + RBAC(`super_admin` / `admin` / `ops`); 4. 管理端接口统一加 `JwtAuthGuard + RolesGuard`; 5. 关键操作(平台密钥修改、用户封禁)记录审计日志。 ### 10.3 Token 建议 - AccessToken:只放必要字段(`sub`, `role`, `type`) - RefreshToken:包含 `jti`,便于撤销 - 密钥轮转:支持双密钥平滑切换(`kid`) - 传输:优先 `Authorization: Bearer`;管理端也可改 HttpOnly Cookie - 强隔离建议:客户端与管理端使用不同 `issuer`、`audience`、`secret`,token 绝不互认 --- ## 11. 拆分演进路线(低成本) ### 阶段 A:单体双端(当前推荐) - 一个 Nest 进程,两个 AppModule(`client-app` / `admin-app`); - 共用数据库与 Redis,但 key 前缀、token 体系已隔离; - 发布快,开发成本最低。 ### 阶段 B:双进程同仓 - 启动两个入口:`main.client.ts`、`main.admin.ts`; - 用户端和管理端可独立扩容、独立发布; - 共享逻辑仍来自 `shared-domain`。 ### 阶段 C:双服务拆仓(可选) - `client-service`:保留短信、chat、会话; - `admin-service`:保留邮箱登录、平台管理、统计审计; - `shared-domain` 抽为内部 npm 包或 Git 子模块。 --- ## 12. 最小可用开发流程(MVP) 建议按下面顺序实现,2~3 周可交付可用版本: 1. 初始化 NestJS + Prisma + PostgreSQL + Redis; 2. 完成客户端短信登录与 JWT 刷新; 3. 完成 Qwen 单平台流式 chat; 4. 抽象 provider 接口,接入 Volc、DeepSeek; 5. 实现统一流式格式 + 自动路由; 6. 完成管理端邮箱登录; 7. 完成平台管理、用户管理; 8. 完成请求日志与基础统计报表; 9. 完成 Swagger、限流、异常处理与日志。 --- ## 13. 非功能建议(上线前) - **安全**:API Key 加密存储;生产环境开启 Helmet/CORS 白名单; - **稳定性**:上游超时、熔断、重试与降级; - **可观测性**:请求链路 ID、结构化日志、错误告警; - **成本控制**:平台权重 + 单用户日额度 + 模型白名单; - **测试**:核心流程至少有 e2e(登录、chat、管理端鉴权)。 --- ## 14. 一句话总结 该方案以 NestJS + Prisma + PostgreSQL + Redis 为核心,采用“**用户端/管理端强隔离 + shared-domain 复用**”组织代码,在保持开发效率的同时确保后续可平滑拆分为两个独立服务,并持续支持多平台 AI 路由与统一流式协议。