Files
chat-one-service/docs/project-solution.md
alboped bc13417efd feat: Prisma 用户落库、迁移与启动环境加载
- Prisma 7 + adapter-pg;prisma.config 与 users 初始迁移\n- AppModule 挂载 PrismaModule;PrismaService 仅依赖 DATABASE_URL\n- main 入口 dotenv/config,避免 Prisma 早于 Config 读 env\n- 短信登录 upsert User;默认昵称 Chat+手机号后四位\n- README / project-solution:目录、迁移规范、用户 avatar_url 说明\n- 依赖:dotenv、@prisma/adapter-pg、pg

Made-with: Cursor
2026-04-22 01:21:11 +08:00

586 lines
20 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# ChatOne Service 项目方案NestJS
本文给出一个主流且简洁、可直接落地的服务端方案,满足以下目标:
- 基于 NestJS 构建;
- 同时提供客户端接口与后台管理接口;
- 支持短信登录、邮箱登录;
- 支持千问、火山引擎、DeepSeek 等多平台 AI Chat 流式输出;
- 对外提供统一调用接口(自动路由)和指定平台调用接口;
- 提供用户管理、平台管理、用量统计。
## 目录
- [1. 技术选型(主流且简洁)](#1-技术选型主流且简洁)
- [2. 项目结构(推荐:按用户端/管理端强隔离)](#2-项目结构推荐按用户端管理端强隔离)
- [3. 模块职责](#3-模块职责)
- [4. API 设计(示例)](#4-api-设计示例)
- [5. 自动路由与统一格式方案](#5-自动路由与统一格式方案)
- [6. 依赖清单(核心)](#6-依赖清单核心)
- [7. 配置文件示例](#7-配置文件示例)
- [8. 数据库设计PostgreSQL](#8-数据库设计postgresql)
- [9. 缓存方案Redis](#9-缓存方案redis)
- [10. 登录与鉴权方案](#10-登录与鉴权方案)
- [11. 拆分演进路线(低成本)](#11-拆分演进路线低成本)
- [12. 最小可用开发流程MVP](#12-最小可用开发流程mvp)
- [13. 非功能建议(上线前)](#13-非功能建议上线前)
- [14. 一句话总结](#14-一句话总结)
---
## 1. 技术选型(主流且简洁)
- **运行时**Node.js 20 LTS
- **框架**NestJS 10Fastify 适配器,性能更优)
- **ORM**Prisma类型安全、迁移友好
- **数据库**PostgreSQL 15
- **缓存/队列**Redis 7缓存、限流、验证码、会话黑名单
- **鉴权**JWTAccess + 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 }``user``users` 表对齐,建议含 `id``phone``nickname``avatarUrl` 等;未落库前可为占位结构)
- `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
# SMSSpug 推送助手https://push.spug.cc
SPUG_PUSH_SMS_TEMPLATE_ID=你的消息模板编号
# SPUG_PUSH_BASE_URL=https://push.spug.cc
# SPUG_SMS_NAME=模板要求的 name 变量(可选)
# SMS_MOCK=true
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 <xxx@qq.com>
# 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)
- `avatar_url` (varchar, nullable) — 头像地址HTTPS 对象存储或 CDN URL为空时客户端展示默认头像
- `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 短期2hRefreshToken 长期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 路由与统一流式协议。