## 项目初始化：Recall Platform Monorepo 脚手架搭建

### 背景

请先阅读项目根目录下的 docs/architecture.md 文件，这是经过完整评审的技术架构设计方案，包含了所有的设计决策、方案对比和取舍依据。本次任务是基于该架构方案搭建项目脚手架。

### 项目概述

这是一个齿科 SaaS 智能客户召回服务的 Monorepo 项目，采用 Turborepo 管理。包含一个 NestJS 后端服务和一个 Next.js 前端应用，供多个宿主系统（如 Friday SaaS、5i5y）通过 iframe 嵌入或 API 调用的方式接入。

### 技术栈要求（全部使用最新稳定版）

**Monorepo 基建：**
- Turborepo
- pnpm workspace
- TypeScript（严格模式，全项目统一 tsconfig）
- ESLint + Prettier（统一代码规范）
- Docker + docker-compose（本地开发 + 生产部署）

**前端 apps/recall-web：**
- Next.js（最新版，App Router）
- React（最新版）
- TypeScript
- Tailwind CSS（最新版）
- shadcn/ui（最新版，按需引入基础组件：Button、Input、Table、Dialog、Badge、Card、Tabs）
- Lucide React（图标）
- 状态管理：Zustand
- HTTP 请求：fetch 封装（带 Authorization Bearer token 拦截器）
- 不使用 SSR，纯客户端渲染（这是供 iframe 加载的独立应用）

**后端 apps/recall-service：**
- NestJS（最新版）
- TypeScript
- Prisma ORM（PostgreSQL）
- @nestjs/jwt（JWT 签发与验签）
- @nestjs/passport + passport-jwt（API 鉴权 Guard）
- @nestjs/schedule（定时任务，用于同步引擎）
- @nestjs/swagger（生成 OpenAPI spec）
- @scalar/nestjs-api-reference（替代 Swagger UI，渲染现代化 API 文档，支持多语言代码示例和在线调试）
- @nestjs/config（环境变量管理）
- ioredis（Redis 连接，用于一次性 code 缓存、token 缓存）
- class-validator + class-transformer（DTO 校验）
- helmet（安全头）
- SSE 支持（用于话术生成的流式输出）
- Winston 或 NestJS 内置 Logger（日志）

**共享包 packages/：**
- packages/shared-types：共享 TypeScript 类型定义（API 请求响应结构、数据契约、枚举常量）
- packages/shared-utils：共享工具函数（日期处理、加密解密等）

### 项目结构
recall-platform/
├── apps/
│   ├── recall-service/          # NestJS 后端
│   │   ├── src/
│   │   │   ├── modules/
│   │   │   │   ├── auth/        # 鉴权模块（Token Exchange 换票、一次性 code 兑换、JWT Guard）
│   │   │   │   ├── sync/        # 同步引擎模块（定时增量拉取宿主数据、服务账号登录、字段映射 adapter）
│   │   │   │   ├── recall/      # 召回引擎模块（召回池计算、规则引擎）
│   │   │   │   ├── lead/        # 线索管理模块（分配/回收/状态流转/生命周期）
│   │   │   │   ├── ai/          # AI 话术生成模块（对接 AI Gateway、SSE 流式输出）
│   │   │   │   └── tenant/      # 租户配置管理模块（platform 管理、同步配置、规则配置、路由模板）
│   │   │   ├── common/
│   │   │   │   ├── guards/      # 全局 Guard（JWT 鉴权、权限校验）
│   │   │   │   ├── interceptors/# 全局拦截器（platform_id + clinic_id 多租户过滤）
│   │   │   │   ├── filters/     # 全局异常过滤器
│   │   │   │   └── decorators/  # 自定义装饰器（@CurrentUser、@RequirePermission）
│   │   │   ├── prisma/          # Prisma 相关（service、module）
│   │   │   ├── app.module.ts
│   │   │   └── main.ts
│   │   ├── prisma/
│   │   │   └── schema.prisma    # 数据库 Schema
│   │   ├── Dockerfile
│   │   ├── .env.example
│   │   └── package.json
│   │
│   └── recall-web/              # Next.js 前端
│       ├── src/
│       │   ├── app/             # App Router 页面
│       │   ├── components/      # 业务组件
│       │   ├── lib/             # 工具库（API client、token 管理）
│       │   ├── hooks/           # 自定义 Hooks
│       │   └── stores/          # Zustand stores
│       ├── Dockerfile
│       └── package.json
│
├── packages/
│   ├── shared-types/
│   │   ├── src/
│   │   │   ├── api/             # API 请求响应类型
│   │   │   ├── models/          # 数据模型类型（Patient、Diagnosis、Lead、Task 等）
│   │   │   ├── enums/           # 枚举（LeadStatus、RecallRole、Permission 等）
│   │   │   └── index.ts
│   │   ├── tsconfig.json
│   │   └── package.json
│   │
│   └── shared-utils/
│       ├── src/
│       │   └── index.ts
│       ├── tsconfig.json
│       └── package.json
│
├── docs/
│   └── architecture.md          # 技术架构设计方案（评审稿）
│
├── docker-compose.yml           # 本地开发环境
├── docker-compose.prod.yml      # 生产部署
├── pnpm-workspace.yaml
├── turbo.json
├── tsconfig.base.json           # 基础 TypeScript 配置
├── .gitignore
├── .npmrc
└── package.json

### Prisma Schema 要点

数据库为 PostgreSQL，所有业务表必须包含三级隔离字段：
- platform_id（宿主平台标识，如 friday / 5i5y）
- tenant_id（宿主平台内的租户标识，即宿主自身的客户/组织）
- clinic_id（租户下的具体诊所标识）

注意命名区分：这里的 tenant_id 是宿主系统自身的租户概念（如 Friday 的某个牙科集团客户），platform_id 才是召回服务层面的"谁接入了我"。三个字段建立联合索引，查询层强制追加三重过滤条件。


需要的核心表：
- Platform（平台/租户注册信息、appId、appSecret 哈希、同步配置 JSON、规则配置 JSON、路由模板 JSON）
- Patient（冗余存储的患者档案）
- Diagnosis（冗余存储的诊断记录）
- Treatment（冗余存储的治疗记录）
- Appointment（冗余存储的预约记录）
- RecallPool（召回池，引擎计算结果）
- Lead（线索，含状态、分配人、优先级分数）
- Task（回访任务记录、话术快照）
- SyncLog（同步引擎执行日志）
- AiUsageLog（AI 调用记录、Token 用量）

所有表包含 created_at、updated_at 字段。
业务表包含 platform_id、tenant_id、clinic_id 字段并建立联合索引。

### docker-compose.yml 要求

本地开发环境包含四个服务：
- recall-service（NestJS，端口 3001）
- recall-web（Next.js，端口 3000）
- postgres（PostgreSQL 16，端口 5432，持久化 volume）
- redis（Redis 7 Alpine，端口 6379）

### 具体实现要求

1. **Auth 模块骨架：**
   - POST /recall/v1/auth/token（换票接口，接收 appId + appSecret + user 信息，签发 JWT，同时生成一次性 code 存入 Redis 60 秒过期，一并返回 recall_token 和 code）
   - POST /recall/v1/auth/exchange-code（code 兑换接口，验证 code 后返回 recall_token，code 一次即作废）
   - JWT Guard 实现（从 Bearer token 中解析 platform_id、tenant_id、clinic_ids、user_id、permissions）
   - 权限装饰器 @RequirePermission('lead:view_own')
   - recall_token payload 结构：{ sub（userId）, platformId, clinicIds, recallRole, permissions[], iat, exp }

2. **多租户拦截器骨架：**
   - 全局 Interceptor，从当前用户的 JWT 中提取 platform_id、tenant_id 和 clinic_ids
   - 自动注入到所有数据库查询条件中（通过 Prisma middleware 或手动注入）

3. **前端 Token 管理：**
   - recall-web 启动时从 URL 参数获取 code
   - 调用 /auth/exchange-code 换取 recall_token
   - token 存储在内存中（Zustand store），绝不使用 Cookie 或 localStorage
   - 封装 API client，所有请求自动带 Authorization: Bearer 头
   - token 临近过期时触发刷新流程

4. **API 文档：**
   - 使用 @nestjs/swagger 生成 OpenAPI spec
   - 使用 @scalar/nestjs-api-reference 渲染文档界面
   - 访问 /api/docs 展示现代化接口文档（含 curl/JS/Python 等多语言代码示例和在线调试能力）

5. **turbo.json 配置：**
   - build、dev、lint、type-check 的 pipeline 配置
   - 正确的依赖关系（apps 依赖 packages）
   - 缓存配置

6. **环境变量模板（.env.example）：**
   - DATABASE_URL
   - REDIS_URL
   - JWT_SECRET
   - JWT_REFRESH_SECRET
   - JWT_EXPIRES_IN
   - AI_GATEWAY_URL

### 不需要实现的（后续迭代）
- 同步引擎的具体拉取逻辑（只建模块骨架和定时任务 placeholder）
- 召回引擎的具体计算逻辑（只建模块骨架）
- AI 话术生成的具体对接（只建 SSE endpoint 骨架）
- 前端页面的具体业务 UI（只建基础布局和路由骨架）
- Webhook 事件推送
- npm 前端组件包

### 完成后验证
1. pnpm install 无报错
2. docker compose up 能启动所有服务
3. pnpm dev 能并行启动前后端
4. 访问 http://localhost:3101/api/docs 能看到 Scalar API 文档界面
5. 访问 http://localhost:3100 能看到前端页面骨架
6. Prisma migrate 能成功创建所有表
7. TypeScript 类型检查全项目无报错
8. POST /recall/v1/auth/token 能成功换票并返回 recall_token + code
9. POST /recall/v1/auth/exchange-code 能成功用 code 换回 recall_token
10. 带 recall_token 访问受保护接口能通过鉴权