Files
NewPlatform/docs_old/architecture/technology-stack-details.md
T
2026-08-23 00:16:15 +08:00

425 lines
10 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.
# AIwelink 技术栈选型细节
> 本文是 [技术栈选型](./technology-stack.md) 的细节说明,记录当前边界、约束、实现规则和演进条件。
## 1. Web 与 API 的同域名反向代理
### 1.1 对外访问模型
第一阶段使用同一个公网域名:
```text
https://aiwelink.com/ -> Next.js
https://aiwelink.com/api/v1/... -> REST API
```
入口反向代理负责路由:
```text
Client
│
▼
Nginx
├── /api/v1/* -> api:3000
└── /* -> web:3000
```
NestJS 设置全局 API 前缀 `/api/v1`。反向代理必须统一是否保留路径前缀,避免产生 `/api/v1/api/v1/...` 的重复路径。
### 1.2 同域名的优点
- 浏览器不需要跨域访问 API。
- HttpOnly Cookie 的会话处理更直接。
- 分享页、公开赛事页和登录后工作台使用同一域名。
- 本地开发、测试和生产环境的请求路径一致。
- 将来即使迁移到 `api.aiwelink.com`,外部资源路径仍可以保持不变。
### 1.3 注意事项
- API 和 Web 要在入口层设置不同的超时、缓存和安全策略。
- API 不应由 Next.js 页面逻辑重复实现。
- Next.js 服务端调用 API 时优先使用 Docker 内部网络,而不是绕回公网域名。
- 如果未来提供 App、第三方赛事系统或开放平台,再单独设计 CORS、Token 和 API 子域名策略。
- 如果未来使用 SSE,反向代理要关闭响应缓冲并设置长连接超时。
## 2. NestJS + Fastify Adapter
### 2.1 选择原因
NestJS 提供模块、依赖注入、Guard、拦截器、异常过滤器和 OpenAPI 集成;Fastify Adapter 提供轻量 HTTP 层。该组合适合授权、项目、邀请、积分、订单等规则较多的业务。
### 2.2 代码约束
- 统一使用 Fastify 兼容的 NestJS 插件。
- 不在 Controller 中编写数据库查询和复杂业务规则。
- 通过 Guard 和服务端资源归属检查完成权限控制。
- 统一错误格式和 `requestId`。
- 统一使用 DTO 校验输入,不直接把数据库实体作为 API 响应。
- API 使用 `/api/v1` 版本前缀,JSON 字段使用 `camelCase`,URL 使用小写短横线。
### 2.3 推荐的模块布局
```text
src/
modules/
identity/
users/
profiles/
consents/
projects/
opportunities/
invitations/
points/
commerce/
recommendations/
operations/
common/
guards/
filters/
interceptors/
decorators/
infrastructure/
database/
storage/
cache/
```
## 3. RESTful API 约束
### 3.1 资源设计
使用名词和复数:
```text
GET /api/v1/projects
GET /api/v1/projects/{projectId}
POST /api/v1/projects
PATCH /api/v1/projects/{projectId}
DELETE /api/v1/projects/{projectId}
```
嵌套资源表示明确的从属关系:
```text
GET /api/v1/projects/{projectId}/members
POST /api/v1/projects/{projectId}/applications
```
不要使用 `getProjects`、`createProject`、`generateReport` 这类动词路径。生成报告等动作应建模为创建报告资源或创建任务资源。
### 3.2 状态码
- `200`:读取或更新成功。
- `201`:创建资源成功。
- `202`:异步任务已接受。
- `204`:成功但无响应体。
- `400`:请求格式错误。
- `401`:未登录或会话无效。
- `403`:无权限。
- `404`:资源不存在。
- `409`:资源状态冲突。
- `422`:字段校验失败。
- `429`:请求过于频繁。
- `500`:服务端异常。
### 3.3 响应和错误
成功响应统一包含 `data`,列表响应在 `meta` 中返回游标信息。错误建议采用 `application/problem+json`,至少包含 `type`、`title`、`status`、`code`、`detail` 和 `requestId`。
### 3.4 幂等和安全
以下写操作需要支持 `Idempotency-Key`:
- 订单创建。
- 积分发放。
- 邀请接受。
- 付费权益创建。
- 可能重复提交的报告任务。
权限检查必须在 API 服务端完成,不能依赖前端隐藏按钮。
## 4. Identity 模块细节
### 4.1 责任边界
Identity 模块只负责身份和会话,不负责个人能力画像、项目经历或心理测评结论。
建议的核心表:
```text
users
user_identities
sessions
email_verifications
login_events
```
### 4.2 登录方式
- 微信扫码或授权登录作为主入口。
- 邮箱用于绑定、验证、通知和账号恢复。
- 登录方式与用户主体分离,允许同一用户绑定多个身份。
- 会话使用 HttpOnly、Secure Cookie。
- 服务端保存会话状态和撤销记录。
- 长期 Token 不存储在 `localStorage`。
### 4.3 后续可扩展
未来可增加手机号、学校身份验证或第三方身份,但不改变 `users` 作为统一用户主体的模型。
## 5. Drizzle 与原生 SQL
### 5.1 默认规则
| 场景 | 方式 |
| --- | --- |
| 普通增删改查 | Drizzle |
| 普通事务 | Drizzle transaction |
| JSONB 复杂筛选 | Drizzle + SQL |
| pgvector 相似度 | SQL Repository |
| 推荐排序 | SQL Repository |
| 全文搜索 | SQL Repository |
| 报表和聚合 | SQL Repository |
原生 SQL 必须参数化,禁止字符串拼接用户输入。
### 5.2 访问层边界
```text
Controller
↓
Application Service
↓
Repository Interface
↓
Drizzle Repository / SQL Repository
↓
PostgreSQL
```
Controller 不直接访问数据库,也不直接依赖 Drizzle 实现。
### 5.3 迁移
- 数据库结构必须版本化迁移。
- 普通表结构可由 Drizzle Schema 和迁移工具维护。
- pgvector、特殊索引、函数、分区等能力可以使用手写迁移 SQL。
- 生产环境禁止手工修改表结构而不留下迁移记录。
## 6. PostgreSQL、JSONB 和 pgvector
### 6.1 PostgreSQL 作为主数据源
PostgreSQL 保存所有需要事务一致性的业务数据:用户、身份、授权、项目、成员、申请、积分、订单、报告元数据和推荐记录。
Redis 不保存这些数据的最终状态。
### 6.2 JSONB 使用边界
适合 JSONB:
- 灵活的用户偏好。
- Agent 输出的结构化报告内容。
- 版本化的推荐特征快照。
- 运营可配置字段。
不适合全部放入 JSONB:
- 用户身份。
- 权限和授权。
- 订单金额和状态。
- 积分流水。
- 项目成员关系。
这些核心关系仍应使用结构化字段和约束。
### 6.3 pgvector 使用边界
pgvector 用于项目、课程、学科和公开内容的语义召回。向量记录至少保存:
- 来源资源 ID。
- Embedding 模型名称。
- 模型版本。
- 向量维度。
- 生成时间。
- 来源内容版本。
原始测评答案和敏感心理数据不应默认向量化,也不应直接作为不可解释的决策依据。
## 7. 分层存储与分表策略
### 7.1 逻辑分层
建议至少划分以下数据层:
```text
身份层:users、user_identities、sessions
资料层:profile_basics、profile_preferences、public_profiles
认知层:assessment_sessions、assessment_answers、insight_snapshots
证据层:insight_evidence、project_reviews、behavior_events
授权层:consent_grants、consent_audits
项目层:opportunities、projects、project_members、applications
增长层:referral_events、point_ledger
审计层:audit_logs
```
### 7.2 分表原则
现在就按业务边界拆分表,便于权限、索引和生命周期管理。不要按“感觉可能很大”把一个简单实体过度拆成多个表。
大型追加型表应优先考虑:
- 合理索引。
- 时间范围查询。
- 冷数据归档。
- 单独的查询 Repository。
- 后续按时间做 PostgreSQL 分区。
暂不做跨库分片和手工水平分片。
## 8. Redis 使用边界
Redis 用于:
- 缓存热点数据。
- 请求限流。
- 短期验证码和临时状态。
- 队列基础设施。
- 幂等键状态。
- 短期排行榜。
Redis 不用于保存:
- 最终积分余额。
- 订单状态。
- 授权记录。
- 用户画像原始数据。
- 项目成员关系。
## 9. 阿里云 OSS 文件流转
推荐流程:
```text
API 创建临时凭证
↓
浏览器直传 OSS
↓
上传完成通知 API
↓
PostgreSQL 写入文件元数据
↓
按需执行解析、审核或索引
```
数据库只保存:
```text
fileId
ownerId
objectKey
mimeType
size
hash
visibility
createdAt
```
私有文件只能通过短期签名 URL 访问。上传必须限制类型、大小和归属,不能仅依赖文件扩展名。
## 10. ECS + Docker Compose
### 10.1 第一阶段服务
```text
nginx
web
api
worker(具体 Agent 方案待定)
postgres
redis
```
应用容器需要配置健康检查和自动重启。镜像推送到镜像仓库,生产使用固定版本标签,不直接使用 `latest`。
### 10.2 对 Kubernetes 的兼容性
现在就遵守:
- 应用服务无状态。
- 配置通过环境变量或 Secret 注入。
- OSS 保存文件,不使用容器本地磁盘。
- 数据库迁移独立执行。
- Web、API、Worker 独立容器。
- 服务之间通过内部 DNS 或配置发现。
这些约束可以降低未来迁移 K8S 的改造量。
### 10.3 数据服务部署建议
如果 PostgreSQL 和 Redis 也运行在 ECS 上,应与应用容器至少分开主机或独立数据盘,并配置:
- 自动备份。
- 备份保留周期。
- 恢复演练。
- 磁盘空间监控。
- 数据库升级和回滚方案。
## 11. Uptime Kuma 与测试
### 11.1 Uptime Kuma 的职责
Uptime Kuma 负责基础可用性:
- 首页 HTTP 监控。
- API 存活和就绪监控。
- TCP/端口监控。
- HTTPS 证书到期提醒。
建议提供:
```text
GET /api/v1/health/live
GET /api/v1/health/ready
```
`live` 只判断进程是否存活;`ready` 检查 PostgreSQL、Redis 和必要配置是否可用。
### 11.2 测试不由 Uptime Kuma 替代
Uptime Kuma 不执行单元测试、接口测试或浏览器流程测试。最低测试基线建议为:
- TypeScript 类型检查。
- Lint。
- API 单元和集成测试。
- 关键流程端到端测试。
- 发布前数据库迁移检查。
具体测试框架可以在开始实现前再确认,但必须覆盖:
```text
微信登录
→ 邮箱绑定
→ 创建个人资料
→ 创建项目
→ 发送邀请
→ 授权共享
→ 项目申请
→ 积分流水
```
## 12. 暂不定型事项
以下内容当前不做最终技术承诺:
- Agent 使用的模型供应商和模型路由。
- Agent 对话编排框架。
- 长报告是否使用队列以及具体队列库。
- 推荐系统是否拆成独立服务。
- 是否引入专用搜索引擎。
- Kubernetes 的具体发行版和部署方式。
- 完整日志、指标和链路追踪方案。
这些选项应在业务流程、数据量和实际故障模式出现后再定型。