# 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 的具体发行版和部署方式。 - 完整日志、指标和链路追踪方案。 这些选项应在业务流程、数据量和实际故障模式出现后再定型。