feat: batch update - gift card breakdown spec, backend APIs, miniprogram pages, ETL finance recharge, docs & migrations
This commit is contained in:
1
.kiro/specs/rns1-infra-contract-rewrite/.config.kiro
Normal file
1
.kiro/specs/rns1-infra-contract-rewrite/.config.kiro
Normal file
@@ -0,0 +1 @@
|
||||
{"specId": "13cfd0bc-b6d6-408e-b943-aa11fb515478", "workflowType": "requirements-first", "specType": "feature"}
|
||||
552
.kiro/specs/rns1-infra-contract-rewrite/design.md
Normal file
552
.kiro/specs/rns1-infra-contract-rewrite/design.md
Normal file
@@ -0,0 +1,552 @@
|
||||
# 技术设计文档 — RNS1.0:基础设施与契约重写
|
||||
|
||||
## 概述
|
||||
|
||||
RNS1.0 是 NS1 小程序后端 API 补全项目的基础设施层,阻塞所有后续子 spec(RNS1.1-1.4)。本设计覆盖 6 个任务:
|
||||
|
||||
1. **全局响应包装中间件**(T0-1):ASGI 中间件 + FastAPI 异常处理器,统一 `{ code: 0, data }` 格式
|
||||
2. **Pydantic camelCase 统一**(T0-2):基类 `CamelModel` 配置 `alias_generator=to_camel`
|
||||
3. **路由路径修正**(T0-3):`/cancel-abandon` → `/restore`
|
||||
4. **前端 request() 解包**(T0-4):`.data` 自动提取 + 错误码处理
|
||||
5. **API 契约完全重写**(T0-5):8 个接口的响应定义重写为独立文档
|
||||
6. **前端跨页面参数修复**(T0-6):8 个页面间的参数传递统一用唯一 ID
|
||||
|
||||
### 设计原则
|
||||
|
||||
- **最小侵入**:全局中间件对现有 16 个端点透明生效,无需逐个修改路由函数
|
||||
- **契约驱动**:API 契约文档是后续子 spec 实现的唯一基准,前后端共同遵守
|
||||
- **DWD-DOC 强制规则**:所有金额字段使用 `items_sum` 口径,助教费用使用 `assistant_pd_money` + `assistant_cx_money` 拆分
|
||||
|
||||
## 架构
|
||||
|
||||
### 整体架构
|
||||
|
||||
RNS1.0 的改动集中在 3 个层面:
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
subgraph "微信小程序 (apps/miniprogram/)"
|
||||
A[services/api.ts] --> B[utils/request.ts]
|
||||
C[各页面 .ts] --> A
|
||||
end
|
||||
|
||||
subgraph "FastAPI 后端 (apps/backend/app/)"
|
||||
D[ResponseWrapperMiddleware<br/>ASGI 中间件] --> E[ExceptionHandler<br/>FastAPI 异常处理器]
|
||||
E --> F[routers/xcx_*.py<br/>路由层]
|
||||
F --> G[services/*.py<br/>业务逻辑层]
|
||||
G --> H[database.py<br/>数据库连接]
|
||||
end
|
||||
|
||||
subgraph "数据库"
|
||||
I[(zqyy_app<br/>业务库)]
|
||||
J[(etl_feiqiu<br/>ETL 库 via FDW)]
|
||||
end
|
||||
|
||||
B -->|HTTP JSON| D
|
||||
H --> I
|
||||
H --> J
|
||||
|
||||
style D fill:#f9f,stroke:#333
|
||||
style E fill:#f9f,stroke:#333
|
||||
style B fill:#bbf,stroke:#333
|
||||
```
|
||||
|
||||
### 请求-响应流程
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant MP as 小程序 request()
|
||||
participant MW as ResponseWrapperMiddleware
|
||||
participant EH as ExceptionHandler
|
||||
participant R as Router 端点
|
||||
participant S as Service 层
|
||||
|
||||
MP->>MW: HTTP Request
|
||||
MW->>R: 透传请求
|
||||
R->>S: 调用业务逻辑
|
||||
|
||||
alt 成功
|
||||
S-->>R: 返回数据
|
||||
R-->>MW: JSONResponse(data)
|
||||
MW-->>MP: { code: 0, data: ... }
|
||||
end
|
||||
|
||||
alt HTTPException
|
||||
S-->>EH: raise HTTPException(status, detail)
|
||||
EH-->>MW: JSONResponse({ code, message })
|
||||
MW-->>MP: { code: status_code, message: detail }
|
||||
end
|
||||
|
||||
alt 未捕获异常
|
||||
S-->>EH: raise Exception
|
||||
EH-->>MW: JSONResponse({ code: 500, message })
|
||||
Note over EH: 完整堆栈写入服务端日志
|
||||
MW-->>MP: { code: 500, message: "Internal Server Error" }
|
||||
end
|
||||
|
||||
alt SSE 流式
|
||||
S-->>R: StreamingResponse(text/event-stream)
|
||||
R-->>MW: StreamingResponse
|
||||
MW-->>MP: 直接透传,不包装
|
||||
end
|
||||
```
|
||||
|
||||
|
||||
## 组件与接口
|
||||
|
||||
### 组件 1:ResponseWrapperMiddleware(ASGI 中间件)
|
||||
|
||||
**位置**:`apps/backend/app/middleware/response_wrapper.py`
|
||||
|
||||
**职责**:拦截所有 HTTP 响应,对 JSON 成功响应自动包装为 `{ code: 0, data }` 格式。
|
||||
|
||||
**选型理由**:选择 ASGI 中间件而非 FastAPI 中间件(`BaseHTTPMiddleware`),原因:
|
||||
- `BaseHTTPMiddleware` 会将 `StreamingResponse` 缓冲为完整响应体,破坏 SSE 流式传输
|
||||
- ASGI 中间件可在 `http.response.start` 阶段检查 `content-type`,对 SSE 直接透传
|
||||
- 性能更优,无额外的请求体/响应体缓冲开销
|
||||
|
||||
**接口定义**:
|
||||
|
||||
```python
|
||||
class ResponseWrapperMiddleware:
|
||||
"""ASGI 中间件:全局响应包装。"""
|
||||
|
||||
def __init__(self, app: ASGIApp):
|
||||
self.app = app
|
||||
|
||||
async def __call__(self, scope: Scope, receive: Receive, send: Send):
|
||||
# 仅处理 HTTP 请求
|
||||
if scope["type"] != "http":
|
||||
await self.app(scope, receive, send)
|
||||
return
|
||||
|
||||
# 拦截响应头,检查 content-type
|
||||
# - text/event-stream → 透传
|
||||
# - 非 application/json → 透传
|
||||
# - application/json + 2xx → 包装为 { code: 0, data: <原始body> }
|
||||
# - application/json + 非 2xx → 透传(已由 ExceptionHandler 格式化)
|
||||
...
|
||||
```
|
||||
|
||||
**跳过条件**:
|
||||
1. `content-type` 为 `text/event-stream`(SSE 端点)
|
||||
2. `content-type` 不包含 `application/json`(文件下载等)
|
||||
3. HTTP 状态码非 2xx(错误响应已由 ExceptionHandler 格式化)
|
||||
|
||||
### 组件 2:ExceptionHandler(FastAPI 异常处理器)
|
||||
|
||||
**位置**:`apps/backend/app/middleware/response_wrapper.py`(与中间件同文件)
|
||||
|
||||
**职责**:捕获 `HTTPException` 和未处理异常,统一格式化为 `{ code, message }`。
|
||||
|
||||
**接口定义**:
|
||||
|
||||
```python
|
||||
async def http_exception_handler(request: Request, exc: HTTPException) -> JSONResponse:
|
||||
"""HTTPException → { code: <status_code>, message: <detail> }"""
|
||||
return JSONResponse(
|
||||
status_code=exc.status_code,
|
||||
content={"code": exc.status_code, "message": exc.detail},
|
||||
)
|
||||
|
||||
async def unhandled_exception_handler(request: Request, exc: Exception) -> JSONResponse:
|
||||
"""未捕获异常 → { code: 500, message: "Internal Server Error" }
|
||||
完整堆栈写入服务端日志。"""
|
||||
logger.exception("未捕获异常: %s", exc)
|
||||
return JSONResponse(
|
||||
status_code=500,
|
||||
content={"code": 500, "message": "Internal Server Error"},
|
||||
)
|
||||
```
|
||||
|
||||
**注册方式**(`main.py`):
|
||||
|
||||
```python
|
||||
from app.middleware.response_wrapper import (
|
||||
ResponseWrapperMiddleware,
|
||||
http_exception_handler,
|
||||
unhandled_exception_handler,
|
||||
)
|
||||
|
||||
# 异常处理器(在路由注册之后)
|
||||
app.add_exception_handler(HTTPException, http_exception_handler)
|
||||
app.add_exception_handler(Exception, unhandled_exception_handler)
|
||||
|
||||
# ASGI 中间件(在 CORS 之后添加,注意顺序:后添加的先执行)
|
||||
app.add_middleware(ResponseWrapperMiddleware)
|
||||
```
|
||||
|
||||
### 组件 3:CamelModel(Pydantic 基类)
|
||||
|
||||
**位置**:`apps/backend/app/schemas/base.py`
|
||||
|
||||
**职责**:所有 Pydantic 响应 schema 的基类,统一配置 camelCase 输出。
|
||||
|
||||
**接口定义**:
|
||||
|
||||
```python
|
||||
from pydantic import BaseModel, ConfigDict
|
||||
from pydantic.alias_generators import to_camel
|
||||
|
||||
class CamelModel(BaseModel):
|
||||
"""所有小程序 API 响应 schema 的基类。
|
||||
|
||||
- alias_generator=to_camel:JSON 输出字段名自动转 camelCase
|
||||
- populate_by_name=True:同时接受 snake_case 和 camelCase 输入
|
||||
- from_attributes=True:支持从 ORM 对象/dict 构造
|
||||
"""
|
||||
model_config = ConfigDict(
|
||||
alias_generator=to_camel,
|
||||
populate_by_name=True,
|
||||
from_attributes=True,
|
||||
)
|
||||
```
|
||||
|
||||
**迁移策略**:
|
||||
- 所有现有 schema(`xcx_tasks.py`、`xcx_auth.py`、`xcx_notes.py`)的 `BaseModel` 替换为 `CamelModel`
|
||||
- 路由函数中 `response_model` 不变,Pydantic 自动使用 alias 序列化
|
||||
- 新增 schema 统一继承 `CamelModel`
|
||||
|
||||
### 组件 4:前端 request() 解包
|
||||
|
||||
**位置**:`apps/miniprogram/miniprogram/utils/request.ts`
|
||||
|
||||
**职责**:从全局包装中自动提取 `.data` 字段。
|
||||
|
||||
**改动逻辑**:
|
||||
|
||||
```typescript
|
||||
// request() 函数返回值处理(伪代码)
|
||||
const res = await wx.request({ ... })
|
||||
|
||||
// 检查是否为标准包装格式
|
||||
if (res.data && typeof res.data === 'object' && 'code' in res.data) {
|
||||
if (res.data.code === 0) {
|
||||
return res.data.data // 成功:返回业务数据
|
||||
} else {
|
||||
throw { code: res.data.code, message: res.data.message } // 错误:抛出
|
||||
}
|
||||
}
|
||||
// 非标准格式(SSE 等):直接返回
|
||||
return res.data
|
||||
```
|
||||
|
||||
### 组件 5:路由路径修正
|
||||
|
||||
**位置**:`apps/backend/app/routers/xcx_tasks.py`
|
||||
|
||||
**改动**:
|
||||
|
||||
```python
|
||||
# 修改前
|
||||
@router.post("/{task_id}/cancel-abandon")
|
||||
async def cancel_abandon(task_id: int, ...):
|
||||
|
||||
# 修改后
|
||||
@router.post("/{task_id}/restore")
|
||||
async def restore_task(task_id: int, ...):
|
||||
```
|
||||
|
||||
- 函数名从 `cancel_abandon` 改为 `restore_task`(语义更清晰)
|
||||
- 业务逻辑不变,仍调用 `task_manager.cancel_abandon()`
|
||||
- 不保留旧路径兼容映射
|
||||
|
||||
### 组件 6:API 契约文档
|
||||
|
||||
**位置**:`docs/miniprogram-dev/API-contract.md`(原地重写)
|
||||
|
||||
**组织结构**:按接口分节,每个接口包含:
|
||||
- 端点路径 + HTTP 方法
|
||||
- 请求参数(Query / Path / Body)
|
||||
- 响应结构(TypeScript 类型定义 + 字段说明表)
|
||||
- 数据源标注(FDW 表名或业务表名)
|
||||
- DWD-DOC 强制规则标注(涉及金额/会员字段时)
|
||||
|
||||
重写范围:BOARD-1/2/3、CUST-1、COACH-1、PERF-1、TASK-1 performance、CHAT-1/2(共 8 个接口)。
|
||||
|
||||
### 组件 7:前端跨页面参数修复
|
||||
|
||||
**涉及文件**:
|
||||
|
||||
| 页面文件 | 修改内容 |
|
||||
|---------|---------|
|
||||
| `pages/task-detail/task-detail.ts` | 跳转 chat/customer-service-records 时传 `customerId` 而非 `detail.id` |
|
||||
| `pages/customer-detail/customer-detail.ts` | 跳转 chat/customer-service-records 时传 `customerId`;`loadDetail()` 从 `onLoad(options)` 获取 ID |
|
||||
| `pages/coach-detail/coach-detail.ts` | 任务项跳转 customer-detail 时传 `id={customerId}` 而非 `name` |
|
||||
| `pages/performance/performance.ts` | 跳转 task-detail 时传 `id={taskId}` 而非 `customerName` |
|
||||
| `pages/chat/chat.ts` | 支持 `customerId`/`historyId`/`coachId` 三种入口参数路由 |
|
||||
| `app.ts` | 登录后将 `role`/`storeName`/`coachLevel`/`avatar` 存入 `globalData.authUser` |
|
||||
|
||||
|
||||
## 数据模型
|
||||
|
||||
### 全局响应包装格式
|
||||
|
||||
```typescript
|
||||
// 成功响应
|
||||
interface SuccessResponse<T> {
|
||||
code: 0
|
||||
data: T
|
||||
}
|
||||
|
||||
// 错误响应
|
||||
interface ErrorResponse {
|
||||
code: number // HTTP 状态码(400/401/403/404/500 等)
|
||||
message: string // 错误描述
|
||||
}
|
||||
```
|
||||
|
||||
### CamelModel 基类影响的现有 Schema
|
||||
|
||||
以下现有 schema 需从 `BaseModel` 迁移到 `CamelModel`:
|
||||
|
||||
| Schema 文件 | 类名 | 影响字段(snake_case → camelCase) |
|
||||
|------------|------|----------------------------------|
|
||||
| `schemas/xcx_tasks.py` | `TaskListItem` | `task_type` → `taskType`、`priority_score` → `priorityScore`、`is_pinned` → `isPinned`、`expires_at` → `expiresAt`、`created_at` → `createdAt`、`member_id` → `memberId`、`member_name` → `memberName`、`member_phone` → `memberPhone`、`rs_score` → `rsScore`、`heart_icon` → `heartIcon`、`abandon_reason` → `abandonReason` |
|
||||
| `schemas/xcx_tasks.py` | `AbandonRequest` | `reason`(单字段,无变化) |
|
||||
| `schemas/xcx_auth.py` | 所有 Auth schema | 需逐个检查字段名 |
|
||||
| `schemas/xcx_notes.py` | 所有 Notes schema | 需逐个检查字段名 |
|
||||
|
||||
### 契约重写涉及的新增数据结构(概要)
|
||||
|
||||
以下为 T0-5 契约重写中定义的核心数据结构,完整定义在 API 契约文档中:
|
||||
|
||||
#### BOARD-3 财务看板(6 板块嵌套结构)
|
||||
|
||||
```typescript
|
||||
interface BoardFinanceResponse {
|
||||
overview: OverviewSection // 经营一览:8 指标 + 8 环比
|
||||
recharge: RechargeSection | null // 预收资产:储值卡 + 赠送卡矩阵(area≠all 时为 null)
|
||||
revenue: RevenueSection // 应计收入:结构表 + 明细
|
||||
cashflow: CashflowSection // 现金流入:消费收款 + 充值收款
|
||||
expense: ExpenseSection // 现金流出:4 子分组
|
||||
coachAnalysis: CoachAnalysisSection // 助教分析:基础课 + 激励课
|
||||
}
|
||||
|
||||
// 环比字段通用模式
|
||||
interface CompareField {
|
||||
value: number
|
||||
compare: string // 如 "+12.5%"
|
||||
isDown: boolean
|
||||
isFlat: boolean
|
||||
}
|
||||
```
|
||||
|
||||
#### BOARD-1 助教看板(基础字段 + 4 维度专属字段)
|
||||
|
||||
```typescript
|
||||
interface CoachBoardItem {
|
||||
// 基础字段(所有维度共享)
|
||||
id: string
|
||||
name: string
|
||||
initial: string
|
||||
avatarGradient: string
|
||||
level: string
|
||||
skills: Array<{ text: string; cls: string }>
|
||||
topCustomers: string[]
|
||||
|
||||
// perf 维度专属
|
||||
perfHours?: number
|
||||
perfHoursBefore?: number
|
||||
perfGap?: string
|
||||
perfReached?: boolean
|
||||
|
||||
// salary 维度专属
|
||||
salary?: number
|
||||
salaryPerfHours?: number
|
||||
salaryPerfBefore?: number
|
||||
|
||||
// sv 维度专属
|
||||
svAmount?: number
|
||||
svCustomerCount?: number
|
||||
svConsume?: number
|
||||
|
||||
// task 维度专属
|
||||
taskRecall?: number
|
||||
taskCallback?: number
|
||||
}
|
||||
```
|
||||
|
||||
#### BOARD-2 客户看板(基础字段 + 8 维度专属字段)
|
||||
|
||||
```typescript
|
||||
interface CustomerBoardItem {
|
||||
// 基础字段
|
||||
id: string
|
||||
name: string
|
||||
initial: string
|
||||
avatarCls: string
|
||||
assistants: Array<{
|
||||
name: string; cls: string; heartScore: number
|
||||
badge?: string; badgeCls?: string
|
||||
}>
|
||||
|
||||
// 各维度专属字段按 dimension 参数动态返回
|
||||
// recall / potential / balance / recharge / recent / spend60 / freq60 / loyal
|
||||
[key: string]: any
|
||||
}
|
||||
```
|
||||
|
||||
#### PERF-1 绩效概览(DateGroup 分组结构)
|
||||
|
||||
```typescript
|
||||
interface DateGroup {
|
||||
date: string // 日期标签,如 "3月15日 周六"
|
||||
totalHours: number // 当日总工时
|
||||
totalIncome: number // 当日总收入
|
||||
records: Array<{
|
||||
customerName: string
|
||||
timeRange: string
|
||||
hours: number
|
||||
courseType: string
|
||||
courseTypeClass: string // basic / vip / tip
|
||||
location: string
|
||||
income: number
|
||||
}>
|
||||
}
|
||||
```
|
||||
|
||||
### DWD-DOC 强制规则在数据模型中的体现
|
||||
|
||||
| 规则 | 影响范围 | 实施方式 |
|
||||
|------|---------|---------|
|
||||
| `items_sum` 口径 | BOARD-3 所有金额、CUST-1 消费金额、PERF-1 收入 | 契约文档标注,后端 SQL 使用 `items_sum` 字段 |
|
||||
| 助教费用拆分 | BOARD-3 助教分析、CUST-1 消费记录 coaches | 使用 `assistant_pd_money` + `assistant_cx_money` |
|
||||
| 会员信息 JOIN | CUST-1、BOARD-2、TASK-1 | 通过 `member_id` JOIN `dim_member`,禁用 `member_phone` |
|
||||
|
||||
|
||||
## 正确性属性
|
||||
|
||||
*属性(Property)是一个在系统所有合法执行路径上都应成立的特征或行为——本质上是对"系统应该做什么"的形式化陈述。属性是人类可读规格与机器可验证正确性保证之间的桥梁。*
|
||||
|
||||
RNS1.0 的核心可测试逻辑集中在全局响应包装中间件(T0-1)、camelCase 转换(T0-2)和前端解包(T0-4)。API 契约重写(T0-5)和前端参数修复(T0-6)主要是文档和具体代码修改,通过示例测试覆盖。
|
||||
|
||||
### Property 1: 响应包装-解包 Round Trip
|
||||
|
||||
*For any* 合法的 JSON 可序列化 Python 对象 `data`,经 `ResponseWrapperMiddleware` 包装为 `{ "code": 0, "data": data }` 后,再经前端 `request()` 的 `.data` 解包,应该得到与原始 `data` 结构等价的对象。
|
||||
|
||||
**Validates: Requirements 1.1, 4.1**
|
||||
|
||||
### Property 2: 异常响应保持 code 和 message
|
||||
|
||||
*For any* HTTP 状态码 `status`(400-599 范围)和任意非空字符串 `detail`,当路由抛出 `HTTPException(status_code=status, detail=detail)` 时,`ExceptionHandler` 的输出 JSON 应满足 `output.code == status` 且 `output.message == detail`。对于未捕获异常(edge case),输出应固定为 `code=500, message="Internal Server Error"`。
|
||||
|
||||
**Validates: Requirements 1.2, 1.3**
|
||||
|
||||
### Property 3: 非 JSON 响应透传
|
||||
|
||||
*For any* HTTP 响应,若其 `content-type` 不包含 `application/json`(包括 `text/event-stream`、`application/octet-stream`、`text/html` 等),`ResponseWrapperMiddleware` 应不修改响应体,输出与输入完全相同。
|
||||
|
||||
**Validates: Requirements 1.5, 1.6**
|
||||
|
||||
### Property 4: camelCase 转换 Round Trip
|
||||
|
||||
*For any* 继承 `CamelModel` 的 Pydantic schema 实例,将其序列化为 JSON(使用 `model_dump(by_alias=True)`)得到 camelCase 字段名的 dict,再用该 dict 反序列化回同一 schema 类(通过 `populate_by_name=True`),应得到与原始实例等价的对象。
|
||||
|
||||
**Validates: Requirements 2.2, 2.4**
|
||||
|
||||
### Property 5: 错误码解包抛出
|
||||
|
||||
*For any* 响应对象 `{ code: n, message: m }`,其中 `n` 为非零整数,前端 `request()` 解包时应抛出包含 `code=n` 和 `message=m` 的错误对象,不返回任何业务数据。
|
||||
|
||||
**Validates: Requirements 4.2**
|
||||
|
||||
### Property 6: 非标准格式响应透传
|
||||
|
||||
*For any* 响应对象,若其不包含 `code` 字段(即非全局包装格式),前端 `request()` 应直接返回原始响应体,不做任何解包或错误处理。
|
||||
|
||||
**Validates: Requirements 4.3**
|
||||
|
||||
|
||||
## 错误处理
|
||||
|
||||
### 后端错误处理层次
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
A[路由层抛出 HTTPException] --> B[http_exception_handler]
|
||||
C[Service 层未捕获异常] --> D[unhandled_exception_handler]
|
||||
B --> E["{ code: status_code, message: detail }"]
|
||||
D --> F["{ code: 500, message: 'Internal Server Error' }"]
|
||||
D --> G[logger.exception 写入完整堆栈]
|
||||
```
|
||||
|
||||
| 错误类型 | 处理方式 | 响应格式 | 日志 |
|
||||
|---------|---------|---------|------|
|
||||
| `HTTPException(400, "参数错误")` | `http_exception_handler` | `{ code: 400, message: "参数错误" }` | 无(业务预期错误) |
|
||||
| `HTTPException(401, "未认证")` | `http_exception_handler` | `{ code: 401, message: "未认证" }` | 无 |
|
||||
| `HTTPException(403, "权限不足")` | `http_exception_handler` | `{ code: 403, message: "权限不足" }` | WARNING(已有,permission.py) |
|
||||
| `HTTPException(404, "资源不存在")` | `http_exception_handler` | `{ code: 404, message: "资源不存在" }` | 无 |
|
||||
| `psycopg2.OperationalError` | `unhandled_exception_handler` | `{ code: 500, message: "Internal Server Error" }` | ERROR + 完整堆栈 |
|
||||
| `ValueError` / `TypeError` 等 | `unhandled_exception_handler` | `{ code: 500, message: "Internal Server Error" }` | ERROR + 完整堆栈 |
|
||||
|
||||
### 前端错误处理
|
||||
|
||||
| 场景 | request() 行为 | 调用方处理 |
|
||||
|------|---------------|-----------|
|
||||
| `code: 0` | 返回 `data` 字段 | 正常渲染 |
|
||||
| `code: 401` | 抛出 `{ code: 401, message }` | 跳转登录页 |
|
||||
| `code: 403` | 抛出 `{ code: 403, message }` | 显示"权限不足"提示 |
|
||||
| `code: 400/404/500` | 抛出 `{ code, message }` | 显示错误提示 toast |
|
||||
| 无 `code` 字段 | 直接返回原始响应 | SSE 等特殊场景处理 |
|
||||
| 网络超时/断连 | wx.request 自身 fail 回调 | 显示网络错误提示 |
|
||||
|
||||
### 中间件错误处理
|
||||
|
||||
`ResponseWrapperMiddleware` 自身的异常处理:
|
||||
- 如果中间件在包装过程中出错(如 JSON 解析失败),应透传原始响应,不阻塞请求
|
||||
- 中间件不应吞掉任何异常,仅做格式转换
|
||||
|
||||
## 测试策略
|
||||
|
||||
### 双轨测试方法
|
||||
|
||||
RNS1.0 采用属性测试(Property-Based Testing)+ 单元测试(Unit Testing)双轨并行:
|
||||
|
||||
- **属性测试**:验证全局响应包装、camelCase 转换、前端解包等通用规则在所有输入上的正确性
|
||||
- **单元测试**:验证具体的路由路径修改、参数传递修复、边界条件等
|
||||
|
||||
### 属性测试配置
|
||||
|
||||
- **测试库**:[Hypothesis](https://hypothesis.readthedocs.io/)(Python,已在项目中使用,见 `.hypothesis/` 目录)
|
||||
- **测试位置**:`tests/` 目录(Monorepo 级属性测试)
|
||||
- **最小迭代次数**:每个属性测试 100 次(`@settings(max_examples=100)`)
|
||||
- **标签格式**:每个测试函数的 docstring 中标注 `Feature: rns1-infra-contract-rewrite, Property {N}: {property_text}`
|
||||
|
||||
### 属性测试清单
|
||||
|
||||
| Property | 测试函数 | 生成器 | 验证逻辑 |
|
||||
|----------|---------|--------|---------|
|
||||
| P1: 响应包装-解包 Round Trip | `test_response_wrap_unwrap_roundtrip` | `st.from_type(dict\|list\|str\|int\|float\|bool\|None)` 生成随机 JSON 可序列化数据 | 包装后 JSON 包含 `code=0` 和 `data`;解包 `data` 等于原始输入 |
|
||||
| P2: 异常响应保持 code 和 message | `test_exception_handler_preserves_code_message` | `st.integers(min_value=400, max_value=599)` × `st.text(min_size=1)` | 输出 JSON 的 `code` 等于输入状态码,`message` 等于输入 detail |
|
||||
| P3: 非 JSON 响应透传 | `test_non_json_response_passthrough` | `st.sampled_from(["text/event-stream", "application/octet-stream", "text/html", ...])` × `st.binary()` | 中间件输出的响应体与输入完全相同 |
|
||||
| P4: camelCase 转换 Round Trip | `test_camel_case_roundtrip` | `st.fixed_dictionaries` 生成随机 snake_case 字段名和值 | `model_dump(by_alias=True)` → `Model(**camel_dict)` → 等于原始实例 |
|
||||
| P5: 错误码解包抛出 | `test_error_code_unpacker_throws` | `st.integers().filter(lambda x: x != 0)` × `st.text()` | 解包函数对 `{ code: n, message: m }` 抛出异常,异常包含 code 和 message |
|
||||
| P6: 非标准格式透传 | `test_non_standard_response_passthrough` | `st.dictionaries(st.text(), st.text()).filter(lambda d: "code" not in d)` | 解包函数直接返回原始 dict |
|
||||
|
||||
### 单元测试清单
|
||||
|
||||
| 测试目标 | 测试文件 | 关键用例 |
|
||||
|---------|---------|---------|
|
||||
| 路由路径修正 | `tests/unit/test_xcx_tasks_route.py` | `/restore` 返回 200;`/cancel-abandon` 返回 404 |
|
||||
| CamelModel 基类 | `tests/unit/test_camel_model.py` | `TaskListItem` 序列化输出 camelCase;反序列化接受两种格式 |
|
||||
| 中间件跳过逻辑 | `tests/unit/test_response_wrapper.py` | SSE 端点不包装;health 端点包装;非 2xx 不二次包装 |
|
||||
| 前端参数传递 | 手动联调验证 | 8 个跳转场景逐一验证 URL 参数正确 |
|
||||
|
||||
### 测试执行命令
|
||||
|
||||
```bash
|
||||
# 属性测试(Hypothesis)
|
||||
cd C:\NeoZQYY && pytest tests/ -v -k "rns1"
|
||||
|
||||
# 单元测试
|
||||
cd apps/backend && pytest tests/unit/ -v -k "response_wrapper or camel_model or xcx_tasks_route"
|
||||
```
|
||||
|
||||
### 契约文档验证
|
||||
|
||||
API 契约重写(T0-5)的验证方式:
|
||||
- 人工 review:对照前端 mock 数据结构,逐字段比对
|
||||
- 后续子 spec 实现时,后端响应必须通过 Pydantic schema 验证(schema 从契约定义生成)
|
||||
- 前端联调时,逐页面验证数据渲染正确
|
||||
|
||||
239
.kiro/specs/rns1-infra-contract-rewrite/requirements.md
Normal file
239
.kiro/specs/rns1-infra-contract-rewrite/requirements.md
Normal file
@@ -0,0 +1,239 @@
|
||||
# 需求文档 — RNS1.0:基础设施与契约重写
|
||||
|
||||
## 简介
|
||||
|
||||
RNS1.0 是 NS1 小程序后端 API 补全项目的第一个子 spec,负责建立全局基础设施(响应包装、camelCase 转换)、修正路由路径、适配前端解包逻辑、完全重写 API 契约响应定义、以及修复前端跨页面参数传递问题。本 spec 阻塞所有后续子 spec(RNS1.1-1.4),必须最先完成。
|
||||
|
||||
### 来源文档
|
||||
|
||||
- `docs/prd/Neo_Specs/RNS1-split-plan.md` — 拆分计划主文档
|
||||
- `docs/prd/Neo_Specs/NS1-xcx-backend-api.md` — NS1 原始 spec(含八½前置审查决策 R1-R8)
|
||||
- `docs/prd/Neo_Specs/storyboard-walkthrough-assistant-view.md` — 助教视角走查报告(51 Gap)
|
||||
- `docs/prd/Neo_Specs/miniprogram-storyboard-walkthrough-gaps.md` — 管理层视角走查报告(31 Gap)
|
||||
|
||||
## 术语表
|
||||
|
||||
- **Response_Wrapper**:全局响应包装中间件,将 FastAPI 路由返回值统一封装为 `{ code: 0, data: ... }` 格式
|
||||
- **Exception_Handler**:全局异常处理器,将 HTTPException 和未捕获异常统一封装为 `{ code: <status_code>, message: <detail> }` 格式
|
||||
- **CamelCase_Converter**:Pydantic schema 的 `alias_generator=to_camel` 配置,使 JSON 响应字段名从 snake_case 转为 camelCase
|
||||
- **API_Contract**:`docs/miniprogram-dev/API-contract.md`,定义前后端接口的请求/响应格式基准文档
|
||||
- **Frontend_Unpacker**:前端 `services/api.ts` 中 `request()` 工具函数的 `.data` 解包逻辑,从全局包装中提取业务数据
|
||||
- **Backend**:FastAPI 后端应用,位于 `apps/backend/`
|
||||
- **Miniprogram**:微信小程序前端应用,位于 `apps/miniprogram/`
|
||||
- **FDW**:PostgreSQL Foreign Data Wrapper,用于从业务库 `zqyy_app` 访问 ETL 库 `etl_feiqiu` 的数据
|
||||
- **DateGroup**:按日期分组的数据结构,包含日期标签、当日汇总、记录列表
|
||||
- **DWD-DOC**:`docs/reports/DWD-DOC/` 标杆文档,金额口径和字段语义的权威参考
|
||||
- **items_sum**:DWD-DOC 强制使用的消费金额口径,= `table_charge_money + goods_money + assistant_pd_money + assistant_cx_money + electricity_money`
|
||||
- **环比**:月环比,当期值与上一个相同时间周期的对比百分比
|
||||
|
||||
## 需求
|
||||
|
||||
### 需求 1:全局响应包装中间件(T0-1)
|
||||
|
||||
**用户故事:** 作为后端开发者,我希望所有 API 响应自动包装为统一格式,以便前端可以用一致的方式解析成功和错误响应。
|
||||
|
||||
#### 验收标准
|
||||
|
||||
1. THE Response_Wrapper SHALL 将所有成功响应(HTTP 2xx)封装为 `{ "code": 0, "data": <原始响应体> }` 格式
|
||||
2. THE Exception_Handler SHALL 将 HTTPException 封装为 `{ "code": <HTTP状态码>, "message": <错误详情> }` 格式
|
||||
3. THE Exception_Handler SHALL 将未捕获的服务端异常封装为 `{ "code": 500, "message": "Internal Server Error" }` 格式,同时将完整异常堆栈写入服务端日志
|
||||
4. WHEN 已有接口(Auth、Tasks、Notes、AI Chat 共 16 个端点)返回响应时,THE Response_Wrapper SHALL 对这些接口同样生效,保持向后兼容
|
||||
5. WHEN 响应内容类型为 `text/event-stream`(SSE 流式端点)时,THE Response_Wrapper SHALL 跳过包装,直接透传原始响应
|
||||
6. WHEN 响应内容类型为非 JSON 格式(如文件下载)时,THE Response_Wrapper SHALL 跳过包装,直接透传原始响应
|
||||
|
||||
|
||||
### 需求 2:Pydantic Schema 统一 camelCase 输出(T0-2)
|
||||
|
||||
**用户故事:** 作为前端开发者,我希望后端 API 响应的 JSON 字段名统一为 camelCase 格式,以便前端无需手动转换 snake_case 字段。
|
||||
|
||||
#### 验收标准
|
||||
|
||||
1. THE CamelCase_Converter SHALL 为所有 Pydantic 响应 schema 配置 `alias_generator=to_camel` 和 `populate_by_name=True`
|
||||
2. WHEN Backend 返回 JSON 响应时,THE CamelCase_Converter SHALL 将所有字段名从 snake_case 转换为 camelCase(例如 `user_id` → `userId`,`store_name` → `storeName`)
|
||||
3. THE CamelCase_Converter SHALL 同时应用于所有现有 schema(Auth、Tasks、Notes、AI Chat 模块)和所有新增 schema
|
||||
4. WHEN Backend 接收请求体时,THE CamelCase_Converter SHALL 同时接受 camelCase 和 snake_case 格式的字段名(通过 `populate_by_name=True` 实现)
|
||||
|
||||
### 需求 3:后端路由路径修正(T0-3)
|
||||
|
||||
**用户故事:** 作为前端开发者,我希望后端任务恢复端点的路径与 API 契约和前端调用一致,以便联调时不会因路径不匹配而失败。
|
||||
|
||||
#### 验收标准
|
||||
|
||||
1. THE Backend SHALL 将 `POST /api/xcx/tasks/{taskId}/cancel-abandon` 端点的路径修改为 `POST /api/xcx/tasks/{taskId}/restore`
|
||||
2. WHEN 前端调用 `POST /api/xcx/tasks/{taskId}/restore` 时,THE Backend SHALL 执行与原 `/cancel-abandon` 端点相同的业务逻辑(将任务状态从 `abandoned` 恢复为 `pending`)
|
||||
3. THE Backend SHALL 移除原 `/cancel-abandon` 路径,不保留旧路径的兼容映射
|
||||
|
||||
### 需求 4:前端请求工具函数适配全局包装(T0-4)
|
||||
|
||||
**用户故事:** 作为前端开发者,我希望 `request()` 工具函数自动从全局包装中提取业务数据,以便各页面调用 API 后无需手动解包。
|
||||
|
||||
#### 验收标准
|
||||
|
||||
1. THE Frontend_Unpacker SHALL 在 `services/api.ts` 的 `request()` 函数中,对成功响应自动提取 `.data` 字段并返回给调用方
|
||||
2. WHEN 响应的 `code` 字段不为 0 时,THE Frontend_Unpacker SHALL 抛出包含 `code` 和 `message` 的错误对象,供调用方的 catch 块处理
|
||||
3. WHEN 响应不包含 `code` 字段(非标准格式,如 SSE 流式响应)时,THE Frontend_Unpacker SHALL 直接返回原始响应体,不做解包处理
|
||||
4. THE Frontend_Unpacker SHALL 确保所有现有 API 调用(Auth、Tasks、Notes、AI Chat)在解包后行为不变
|
||||
|
||||
|
||||
### 需求 5:API 契约完全重写(T0-5)
|
||||
|
||||
**用户故事:** 作为前后端开发者,我希望 API 契约文档准确反映前端实际需要的响应结构,以便后续子 spec(RNS1.1-1.4)的后端实现有明确的、与前端一致的接口定义作为基准。
|
||||
|
||||
#### 5.1 BOARD-3 财务看板契约重写
|
||||
|
||||
##### 验收标准
|
||||
|
||||
1. THE API_Contract SHALL 将 BOARD-3 响应从扁平 `metrics` 数组重写为 6 个独立板块的嵌套结构:`overview`(经营一览)、`recharge`(预收资产)、`revenue`(应计收入确认)、`cashflow`(现金流入)、`expense`(现金流出)、`coachAnalysis`(助教分析)
|
||||
2. THE API_Contract SHALL 为 `overview` 板块定义 8 个指标字段(`occurrence`/`discount`/`discountRate`/`confirmedRevenue`/`cashIn`/`cashOut`/`cashBalance`/`balanceRate`),每个指标附带对应的环比字段(`xxxCompare: string`)和方向标记(`isDown: boolean`、`isFlat: boolean`)
|
||||
3. THE API_Contract SHALL 为 `recharge` 板块定义储值卡 5 个指标(`actualIncome`/`firstCharge`/`renewCharge`/`consumed`/`cardBalance`)及各自环比字段,以及赠送卡 3×4 矩阵(行:新增/消费/余额,列:合计/酒水卡/台费卡/抵用券),每个单元格含值和环比字段,以及全类别会员卡余额合计(`allCardBalance`)及环比
|
||||
4. THE API_Contract SHALL 为 `revenue` 板块定义收入结构表(`structureRows`:9 行含子行标记 `isSub`,每行含 `name`/`desc`/`amount`/`discount`/`booked`/`bookedCompare`)、正价明细(`priceItems`:4 项)、优惠明细(`discountItems`:4 项)、渠道明细(`channelItems`:3 项),以及确认收入合计及环比
|
||||
5. THE API_Contract SHALL 为 `cashflow` 板块定义消费收款(`consumeItems`:3 项)、充值收款(`rechargeItems`:1 项)、合计及环比,每项含 `name`/`desc`/`value`/`compare`/`isDown`
|
||||
6. THE API_Contract SHALL 为 `expense` 板块定义 4 个子分组:经营支出(`operationItems`:3 项)、固定支出(`fixedItems`:4 项)、助教分成(`coachItems`:4 项)、平台服务费(`platformItems`:3 项),以及合计及环比
|
||||
7. THE API_Contract SHALL 为 `coachAnalysis` 板块定义基础课(`basic`)和激励课(`incentive`)两个子表,每个子表含汇总行(`totalPay`/`totalShare`/`avgHourly` 及各自环比)和按等级分行的数组(`rows`:初级/中级/高级/星级,每行含 `level`/`pay`/`payCompare`/`share`/`shareCompare`/`hourly`/`hourlyCompare` 及 `payDown`/`shareDown`/`hourlyFlat` 布尔标记)
|
||||
8. THE API_Contract SHALL 为 BOARD-3 定义请求参数:`time`(8 种时间范围枚举:`month`/`lastMonth`/`week`/`lastWeek`/`quarter3`/`quarter`/`lastQuarter`/`half6`)、`area`(7 种区域枚举:`all`/`hall`/`hallA`/`hallB`/`hallC`/`mahjong`/`teamBuilding`)、`compare`(`0`/`1`,控制是否返回环比数据)
|
||||
9. WHEN `area` 不为 `all` 时,THE API_Contract SHALL 标注 `recharge`(预收资产)板块不返回数据(储值卡数据不按区域拆分)
|
||||
10. THE API_Contract SHALL 标注所有金额字段使用 `items_sum` 口径(DWD-DOC 强制规则 1),助教费用使用 `assistant_pd_money` + `assistant_cx_money` 拆分(DWD-DOC 强制规则 2)
|
||||
|
||||
#### 5.2 BOARD-1 助教看板契约重写
|
||||
|
||||
##### 验收标准
|
||||
|
||||
1. THE API_Contract SHALL 将 BOARD-1 响应从 10 个通用字段扩展为包含基础字段和 4 维度专属字段的结构
|
||||
2. THE API_Contract SHALL 为每个助教 item 定义基础字段:`id`/`name`/`initial`/`avatarGradient`/`level`/`skills`/`topCustomers`,其中 `skills` 类型为 `Array<{ text: string, cls: string }>`(含 emoji 和样式类),`topCustomers` 类型为 `string[]`(含 P6 AC3 四级 emoji 前缀,如 `'💖 王先生'`/`'🧡 李女士'`/`'💛 张先生'`/`'💙 赵女士'`)
|
||||
3. THE API_Contract SHALL 为 `perf` 维度定义专属字段:`perfHours`(当期定档工时)、`perfHoursBefore`(上期定档工时,可选)、`perfGap`(距升档差距描述,可选)、`perfReached`(是否已达标)
|
||||
4. THE API_Contract SHALL 为 `salary` 维度定义专属字段:`salary`(工资总额)、`salaryPerfHours`(定档工时)、`salaryPerfBefore`(上期定档工时,可选)
|
||||
5. THE API_Contract SHALL 为 `sv` 维度定义专属字段:`svAmount`(客源储值总额)、`svCustomerCount`(储值客户数)、`svConsume`(储值消耗额)
|
||||
6. THE API_Contract SHALL 为 `task` 维度定义专属字段:`taskRecall`(召回任务完成数)、`taskCallback`(回访任务完成数)
|
||||
7. THE API_Contract SHALL 为 BOARD-1 定义请求参数:`sort`(6 种排序枚举:`perf_desc`/`perf_asc`/`salary_desc`/`salary_asc`/`sv_desc`/`task_desc`)、`skill`(5 种技能枚举:`all`/`chinese`/`snooker`/`mahjong`/`karaoke`)、`time`(6 种时间范围枚举:`month`/`quarter`/`last_month`/`last_3m`/`last_quarter`/`last_6m`)
|
||||
8. THE API_Contract SHALL 标注交叉约束:`time=last_6m` 与 `sort=sv_desc` 不兼容,后端收到此组合时返回 HTTP 400
|
||||
|
||||
#### 5.3 BOARD-2 客户看板契约重写
|
||||
|
||||
##### 验收标准
|
||||
|
||||
1. THE API_Contract SHALL 将 BOARD-2 响应从 8 个通用字段扩展为包含基础字段和 8 维度专属字段的结构
|
||||
2. THE API_Contract SHALL 为每个客户 item 定义基础字段:`id`/`name`/`initial`/`avatarCls`/`assistants`,其中 `assistants` 类型为 `Array<{ name: string, cls: string, heartScore: number, badge?: string, badgeCls?: string }>`,`heartScore` 范围 0-10,前端通过 heart-icon 组件按 P6 AC3 四级映射渲染(💖>8.5 / 🧡>7 / 💛>5 / 💙≤5)
|
||||
3. THE API_Contract SHALL 为 `recall`(最应召回)维度定义专属字段:`idealDays`/`elapsedDays`/`overdueDays`/`visits30d`/`balance`/`recallIndex`
|
||||
4. THE API_Contract SHALL 为 `potential`(最大消费潜力)维度定义专属字段:`potentialTags`(`Array<{ text: string, theme: string }>`)/`spend30d`/`avgVisits`/`avgSpend`
|
||||
5. THE API_Contract SHALL 为 `balance`(最高余额)维度定义专属字段:`balance`/`lastVisit`/`monthlyConsume`/`availableMonths`
|
||||
6. THE API_Contract SHALL 为 `recharge`(最近充值)维度定义专属字段:`lastRecharge`/`rechargeAmount`/`recharges60d`/`currentBalance`
|
||||
7. THE API_Contract SHALL 为 `recent`(最近到店)维度定义专属字段:`daysAgo`/`visitFreq`/`idealDays`/`visits30d`/`avgSpend`
|
||||
8. THE API_Contract SHALL 为 `spend60`(最高消费近60天)维度定义专属字段:`spend60d`/`visits60d`/`highSpendTag`/`avgSpend`
|
||||
9. THE API_Contract SHALL 为 `freq60`(最频繁近60天)维度定义专属字段:`visits60d`/`avgInterval`/`weeklyVisits`(`Array<{ val: number, pct: number }>`,8 周柱状图数据)/`spend60d`
|
||||
10. THE API_Contract SHALL 为 `loyal`(最专一近60天)维度定义专属字段:`intimacy`/`topCoachName`/`topCoachHeart`/`topCoachScore`/`coachName`/`coachRatio`/`coachDetails`(`Array<{ name, cls, heartScore, badge?, avgDuration, serviceCount, coachSpend, relationIdx }>`)
|
||||
11. THE API_Contract SHALL 为 BOARD-2 定义请求参数:`dimension`(8 种维度枚举)、`project`(5 种项目枚举:`all`/`chinese`/`snooker`/`mahjong`/`karaoke`)、`page`(页码,默认 1)、`pageSize`(每页条数,默认 20)
|
||||
|
||||
|
||||
#### 5.4 CUST-1 客户详情契约重写
|
||||
|
||||
##### 验收标准
|
||||
|
||||
1. THE API_Contract SHALL 为 CUST-1 响应补充客户 Banner 字段:`balance`(余额)、`consumption60d`(近60天消费)、`idealInterval`(理想到店间隔)、`daysSinceVisit`(距上次到店天数)
|
||||
2. THE API_Contract SHALL 为 CUST-1 响应补充 `aiInsight` 模块:`summary`(AI 分析摘要,string)和 `strategies`(策略建议列表,`Array<{ color: string, text: string }>`),数据来源标注为 `biz.ai_cache`(`cache_type=app4_analysis`)
|
||||
3. THE API_Contract SHALL 为 CUST-1 响应补充 `coachTasks` 模块(关联助教任务列表):每个助教含 `name`/`level`/`levelColor`/`taskType`/`taskColor`/`bgClass`/`status`/`lastService`/`metrics`(`Array<{ label, value, color? }>`,含近60天次数/总时长/次均时长)
|
||||
4. THE API_Contract SHALL 为 CUST-1 响应补充 `favoriteCoaches` 模块(最亲密助教):每位助教含 `emoji`/`name`/`relationIndex`/`indexColor`/`bgClass`/`stats`(`Array<{ label, value, color? }>`,含基础课时/激励课时/上课次数/充值金额)
|
||||
5. THE API_Contract SHALL 将 CUST-1 消费记录从扁平结构重写为嵌套结构:每条记录含 `id`/`type`(`table`/`shop`/`recharge` 枚举)/`date`/`tableName`/`startTime`/`endTime`/`duration`/`tableFee`/`tableOrigPrice`/`coaches`(`Array<{ name, level, levelColor, courseType, hours, perfHours?, fee }>`)/`foodAmount`/`foodOrigPrice`/`totalAmount`/`totalOrigPrice`/`payMethod`/`rechargeAmount`
|
||||
6. THE API_Contract SHALL 为 CUST-1 响应补充 `notes` 模块(备注列表):每条备注含 `id`/`tagLabel`/`createdAt`/`content`
|
||||
7. THE API_Contract SHALL 标注会员信息获取规则:通过 `member_id` JOIN `dim_member` 获取手机号和昵称(DWD-DOC 强制规则 DQ-6),禁止直接使用 `settlement_head.member_phone`
|
||||
|
||||
#### 5.5 COACH-1 助教详情契约重写
|
||||
|
||||
##### 验收标准
|
||||
|
||||
1. THE API_Contract SHALL 为 COACH-1 响应补充基本信息字段:`workYears`(工龄)、`customerCount`(客户数)、`hireDate`(入职日期)
|
||||
2. THE API_Contract SHALL 为 COACH-1 响应补充 `performance` 模块(6 个绩效指标):`monthlyHours`/`monthlySalary`/`customerBalance`/`tasksCompleted`/`perfCurrent`/`perfTarget`
|
||||
3. THE API_Contract SHALL 为 COACH-1 响应补充 `income` 模块(收入明细):`thisMonth` 和 `lastMonth` 各含 4 项收入分类(基础课时费/激励课时费/充值提成/酒水提成),每项含 `label`/`amount`/`color`
|
||||
4. THE API_Contract SHALL 为 COACH-1 响应补充 `tierNodes` 字段(档位节点数组,如 `[0, 100, 130, 160, 190, 220]`),供前端绩效进度条组件使用
|
||||
5. THE API_Contract SHALL 为 COACH-1 响应补充 `historyMonths` 模块(历史月份统计,最近 5+ 个月):每月含 `month`(标签)/`estimated`(是否预估)/`customers`/`hours`/`salary`/`callbackDone`/`recallDone`
|
||||
6. THE API_Contract SHALL 扩展 COACH-1 的 `topCustomers` 字段结构,从 5 个字段扩展为 10 个字段:补充 `initial`/`avatarGradient`/`heartEmoji`(P6 AC3 四级映射:💖/🧡/💛/💙)/`relationScore`(关系指数,0-10)/`scoreColor`/`balance`
|
||||
7. THE API_Contract SHALL 为 COACH-1 响应补充近期服务明细中的 `perfHours`(折算工时)和 `customerId` 字段
|
||||
8. THE API_Contract SHALL 为 COACH-1 的任务分组(`visibleTasks`/`hiddenTasks`/`abandonedTasks`)补充字段:TaskItem 补充 `noteCount`/`pinned`/`notes`(`Array<{ pinned?, text, date }>`),AbandonedTask 补充 `reason`
|
||||
9. THE API_Contract SHALL 为 COACH-1 响应补充 `notes` 模块(备注列表):每条备注含 `id`/`content`/`timestamp`/`aiScore`(AI 应用 6 评分,1-10,展示用)/`customerName`/`tagLabel`/`createdAt`
|
||||
|
||||
#### 5.6 PERF-1 绩效概览契约重写
|
||||
|
||||
##### 验收标准
|
||||
|
||||
1. THE API_Contract SHALL 将 PERF-1 的 `thisMonthRecords` 从扁平数组重写为按日期分组的 DateGroup 结构:每组含 `date`(日期标签)/`totalHours`(当日总工时)/`totalIncome`(当日总收入)/`records`(记录列表),每条记录含 `customerName`/`timeRange`/`hours`/`courseType`/`courseTypeClass`/`location`/`income`
|
||||
2. THE API_Contract SHALL 为 PERF-1 响应补充收入档位数据:`currentTier`(当前档,含 `basicRate`/`incentiveRate`)、`nextTier`(下一档,含 `basicRate`/`incentiveRate`)、`upgradeHoursNeeded`(距升档所需工时)、`upgradeBonus`(升档奖金)
|
||||
3. THE API_Contract SHALL 为 PERF-1 响应补充 `lastMonthIncome`(上月收入)字段
|
||||
4. THE API_Contract SHALL 为 PERF-1 的 `incomeItems` 每项补充 `desc` 字段(费率×工时的拆分描述,如 `"80元/h × 75h"`)
|
||||
5. THE API_Contract SHALL 为 PERF-1 的 `newCustomers` 补充 `lastService`(最后服务日期)和 `count`(服务次数)字段;为 `regularCustomers` 补充 `hours`(总工时)和 `income`(总收入)字段
|
||||
|
||||
#### 5.7 TASK-1 绩效概览字段契约重写
|
||||
|
||||
##### 验收标准
|
||||
|
||||
1. THE API_Contract SHALL 将 TASK-1 响应中的 `performance` 从 4 个字段(`totalHours`/`totalIncome`/`totalCustomers`/`monthLabel`)扩展为 15+ 个字段,补充:`tierNodes`(档位节点数组)/`basicHours`(基础课时)/`bonusHours`(激励课时)/`currentTier`(当前档位索引)/`nextTierHours`(下一档位工时阈值)/`tierCompleted`(是否已达标)/`bonusMoney`(升档奖金)/`incomeTrend`(收入趋势,如 `"↓368"`)/`incomeTrendDir`(`up`/`down`)/`prevMonth`(上月标签)
|
||||
2. THE API_Contract SHALL 为 TASK-1 的任务 item 补充可选扩展字段:`lastVisitDays`(距上次到店天数)、`balance`(客户余额)、`aiSuggestion`(AI 建议摘要)
|
||||
|
||||
#### 5.8 CHAT-1/2 对话模块契约重写
|
||||
|
||||
##### 验收标准
|
||||
|
||||
1. THE API_Contract SHALL 统一 CHAT-2 消息的时间字段名为 `createdAt`(替代前端使用的 `timestamp` 和契约定义的 `created_at`,遵循 camelCase 规范)
|
||||
2. THE API_Contract SHALL 为 CHAT-1 历史列表的每条记录补充 `title`(对话标题)字段
|
||||
3. THE API_Contract SHALL 为 CHAT-2 消息补充 `referenceCard` 可选字段:含 `type`(`customer`/`record` 枚举)/`title`/`summary`/`data`(`Record<string, string>` 键值对)
|
||||
4. THE API_Contract SHALL 补充 SSE 流式端点定义:`POST /api/xcx/chat/stream`,请求体含 `chatId`/`content`,响应为 `text/event-stream`
|
||||
5. THE API_Contract SHALL 标注 CHAT 消息查询端点支持 `customerId` 查询参数:后端根据 `customerId` 自动查找或创建对话,返回对应的 `chatId` 和消息列表
|
||||
|
||||
|
||||
### 需求 6:前端跨页面参数修复(T0-6)
|
||||
|
||||
**用户故事:** 作为助教或管理层用户,我希望在小程序中从一个页面跳转到另一个页面时,目标页面能正确加载对应的数据,而不会因为参数传递错误导致页面空白或加载错误数据。
|
||||
|
||||
#### 6.1 task-detail 页面跳转修复
|
||||
|
||||
##### 验收标准
|
||||
|
||||
1. WHEN 用户从 task-detail 页面点击"问问助手"跳转到 chat 页面时,THE Miniprogram SHALL 传递 `customerId={detail.customerId}` 参数(而非当前错误传递的 `detail.id` 即 taskId)
|
||||
2. WHEN 用户从 task-detail 页面点击"查看全部服务记录"跳转到 customer-service-records 页面时,THE Miniprogram SHALL 传递 `customerId={detail.customerId}` 参数(而非当前错误传递的 `detail.id` 即 taskId)
|
||||
3. IF TASK-2 响应中不包含 `customerId` 字段,THEN THE Miniprogram SHALL 无法完成上述跳转修复,因此本需求依赖 API 契约中 TASK-2 响应包含 `customerId` 字段的定义
|
||||
|
||||
#### 6.2 customer-detail 页面跳转修复
|
||||
|
||||
##### 验收标准
|
||||
|
||||
1. WHEN 用户从 customer-detail 页面点击"查看服务记录"跳转到 customer-service-records 页面时,THE Miniprogram SHALL 传递 `customerId={detail.id}` 参数(当前未传任何参数)
|
||||
2. WHEN 用户从 customer-detail 页面点击"问问助手"跳转到 chat 页面时,THE Miniprogram SHALL 传递 `customerId={detail.id}` 参数(当前未传任何参数)
|
||||
3. THE Miniprogram SHALL 修复 customer-detail 页面的 `loadDetail()` 函数,从 `onLoad(options)` 的 `options.id` 获取客户 ID,替代当前通过 `__route__` 解析的错误方式
|
||||
|
||||
#### 6.3 coach-detail 页面跳转修复
|
||||
|
||||
##### 验收标准
|
||||
|
||||
1. WHEN 用户从 coach-detail 页面点击任务项跳转到 customer-detail 页面时,THE Miniprogram SHALL 传递 `id={customerId}` 参数(而非当前错误传递的 `name={customerName}`),此修复依赖 COACH-1 响应中 TaskItem 包含 `customerId` 字段
|
||||
|
||||
#### 6.4 performance 页面跳转修复
|
||||
|
||||
##### 验收标准
|
||||
|
||||
1. WHEN 用户从 performance 页面点击客户卡片或服务记录跳转到 task-detail 页面时,THE Miniprogram SHALL 传递 `id={taskId}` 参数(而非当前错误传递的 `customerName={name}`),此修复依赖 PERF-1 响应中记录包含 `taskId` 字段
|
||||
|
||||
#### 6.5 chat 页面多入口参数路由
|
||||
|
||||
##### 验收标准
|
||||
|
||||
1. WHEN chat 页面从 task-detail 或 customer-detail 跳转进入(携带 `customerId` 参数)时,THE Miniprogram SHALL 使用 `customerId` 查询参数调用 CHAT 消息端点,由后端自动查找或创建对话
|
||||
2. WHEN chat 页面从 chat-history 跳转进入(携带 `historyId` 参数)时,THE Miniprogram SHALL 使用 `historyId` 作为 `chatId` 直接加载历史消息
|
||||
3. WHEN chat 页面从 coach-detail 跳转进入(携带 `coachId` 参数)时,THE Miniprogram SHALL 使用 `coachId` 作为上下文参数传递给 CHAT 端点
|
||||
|
||||
#### 6.6 globalData.authUser 字段扩展
|
||||
|
||||
##### 验收标准
|
||||
|
||||
1. THE Miniprogram SHALL 在登录成功后(`/api/xcx/me` 响应)将 `role`、`storeName`、`coachLevel`、`avatar` 字段存入 `globalData.authUser`,供 task-list Banner、performance Banner、performance-records Banner 等多个页面使用
|
||||
2. WHEN `globalData.authUser` 已包含上述字段时,THE Miniprogram SHALL 不再需要各页面单独请求 `/me` 接口获取这些信息
|
||||
|
||||
### 需求 7:契约文档一致性与完整性
|
||||
|
||||
**用户故事:** 作为后续子 spec(RNS1.1-1.4)的开发者,我希望重写后的 API 契约文档内部一致、无歧义,以便直接作为后端实现的唯一基准。
|
||||
|
||||
#### 验收标准
|
||||
|
||||
1. THE API_Contract SHALL 确保所有接口的响应字段名统一使用 camelCase 格式(与需求 2 的 CamelCase_Converter 输出一致)
|
||||
2. THE API_Contract SHALL 确保所有涉及金额的字段标注使用 `items_sum` 口径,禁止使用 `consume_money`(DWD-DOC 强制规则 1)
|
||||
3. THE API_Contract SHALL 确保所有涉及助教费用的字段标注使用 `assistant_pd_money`(陪打)+ `assistant_cx_money`(超休)拆分,禁止使用 `service_fee`(DWD-DOC 强制规则 2)
|
||||
4. THE API_Contract SHALL 确保所有涉及会员信息的字段标注通过 `member_id` JOIN `dim_member` 获取,禁止直接使用 `member_phone`/`member_name`(DWD-DOC 强制规则 DQ-6/DQ-7)
|
||||
5. THE API_Contract SHALL 为每个接口标注数据源(FDW 表名或业务表名),供后续子 spec 实现时参考
|
||||
6. THE API_Contract SHALL 确保重写后的 8 个接口响应定义(BOARD-1/2/3、CUST-1、COACH-1、PERF-1、TASK-1 performance、CHAT-1/2)与前端内联 mock 数据结构完全对齐,无字段遗漏或类型不匹配
|
||||
251
.kiro/specs/rns1-infra-contract-rewrite/tasks.md
Normal file
251
.kiro/specs/rns1-infra-contract-rewrite/tasks.md
Normal file
@@ -0,0 +1,251 @@
|
||||
# 实现计划:RNS1.0 基础设施与契约重写
|
||||
|
||||
## 概述
|
||||
|
||||
按依赖关系排序实现 6 个主任务:T0-1(全局中间件)和 T0-2(CamelModel)为基础设施层,必须先行;T0-3(路由修正)和 T0-4(前端解包)为适配层;T0-5(契约重写)为文档层;T0-6(参数修复)为前端修复层。属性测试使用 Hypothesis,单元测试使用 pytest。
|
||||
|
||||
## 任务
|
||||
|
||||
- [x] 1. 全局响应包装中间件 + 异常处理器(T0-1)
|
||||
- [x] 1.1 实现 ResponseWrapperMiddleware ASGI 中间件
|
||||
- 创建 `apps/backend/app/middleware/response_wrapper.py`
|
||||
- 实现 ASGI 中间件类,拦截 `http.response.start` 和 `http.response.body`
|
||||
- JSON 成功响应(2xx + application/json)包装为 `{ "code": 0, "data": <原始body> }`
|
||||
- 跳过条件:`text/event-stream`(SSE)、非 `application/json`、非 2xx 状态码
|
||||
- _需求: 1.1, 1.5, 1.6_
|
||||
|
||||
- [x] 1.2 实现异常处理器函数
|
||||
- 在同一文件中实现 `http_exception_handler` 和 `unhandled_exception_handler`
|
||||
- HTTPException → `{ "code": <status_code>, "message": <detail> }`
|
||||
- 未捕获异常 → `{ "code": 500, "message": "Internal Server Error" }` + `logger.exception` 写入完整堆栈
|
||||
- _需求: 1.2, 1.3_
|
||||
|
||||
- [x] 1.3 在 main.py 中注册中间件和异常处理器
|
||||
- `app.add_exception_handler(HTTPException, http_exception_handler)`
|
||||
- `app.add_exception_handler(Exception, unhandled_exception_handler)`
|
||||
- `app.add_middleware(ResponseWrapperMiddleware)`(在 CORS 之后添加)
|
||||
- 验证现有 16 个端点(Auth/Tasks/Notes/AI Chat)在包装后行为正常
|
||||
- _需求: 1.4_
|
||||
|
||||
- [x] 1.4 编写属性测试:响应包装-解包 Round Trip
|
||||
- **Property 1: 响应包装-解包 Round Trip**
|
||||
- 测试文件:`tests/test_rns1_properties.py`
|
||||
- 生成器:`st.from_type(dict|list|str|int|float|bool|None)` 生成随机 JSON 可序列化数据
|
||||
- 验证:包装后 JSON 包含 `code=0` 和 `data`;解包 `data` 等于原始输入
|
||||
- **验证需求: 1.1, 4.1**
|
||||
|
||||
- [x] 1.5 编写属性测试:异常响应保持 code 和 message
|
||||
- **Property 2: 异常响应保持 code 和 message**
|
||||
- 测试文件:`tests/test_rns1_properties.py`
|
||||
- 生成器:`st.integers(min_value=400, max_value=599)` × `st.text(min_size=1)`
|
||||
- 验证:输出 JSON 的 `code` 等于输入状态码,`message` 等于输入 detail
|
||||
- **验证需求: 1.2, 1.3**
|
||||
|
||||
- [x] 1.6 编写属性测试:非 JSON 响应透传
|
||||
- **Property 3: 非 JSON 响应透传**
|
||||
- 测试文件:`tests/test_rns1_properties.py`
|
||||
- 生成器:`st.sampled_from(["text/event-stream", "application/octet-stream", "text/html"])` × `st.binary()`
|
||||
- 验证:中间件输出的响应体与输入完全相同
|
||||
- **验证需求: 1.5, 1.6**
|
||||
|
||||
- [x] 1.7 编写单元测试:中间件跳过逻辑
|
||||
- 测试文件:`apps/backend/tests/unit/test_response_wrapper.py`
|
||||
- 用例:SSE 端点不包装;health 端点包装;非 2xx 不二次包装;中间件自身异常时透传原始响应
|
||||
- _需求: 1.5, 1.6_
|
||||
|
||||
- [x] 2. Pydantic Schema 统一 camelCase(T0-2)
|
||||
- [x] 2.1 创建 CamelModel 基类
|
||||
- 创建 `apps/backend/app/schemas/base.py`
|
||||
- 实现 `CamelModel(BaseModel)` 配置 `alias_generator=to_camel`、`populate_by_name=True`、`from_attributes=True`
|
||||
- _需求: 2.1_
|
||||
|
||||
- [x] 2.2 迁移现有 schema 到 CamelModel
|
||||
- `schemas/xcx_tasks.py`:`TaskListItem`、`AbandonRequest` 等所有类的 `BaseModel` → `CamelModel`
|
||||
- `schemas/xcx_auth.py`:所有 Auth schema 迁移
|
||||
- `schemas/xcx_notes.py`:所有 Notes schema 迁移
|
||||
- 确认路由函数 `response_model` 无需修改(Pydantic 自动使用 alias 序列化)
|
||||
- _需求: 2.2, 2.3_
|
||||
|
||||
- [x] 2.3 编写属性测试:camelCase 转换 Round Trip
|
||||
- **Property 4: camelCase 转换 Round Trip**
|
||||
- 测试文件:`tests/test_rns1_properties.py`
|
||||
- 生成器:`st.fixed_dictionaries` 生成随机 snake_case 字段名和值
|
||||
- 验证:`model_dump(by_alias=True)` → `Model(**camel_dict)` → 等于原始实例
|
||||
- **验证需求: 2.2, 2.4**
|
||||
|
||||
- [x] 2.4 编写单元测试:CamelModel 基类
|
||||
- 测试文件:`apps/backend/tests/unit/test_camel_model.py`
|
||||
- 用例:`TaskListItem` 序列化输出 camelCase 字段名;反序列化同时接受 snake_case 和 camelCase
|
||||
- _需求: 2.2, 2.4_
|
||||
|
||||
- [x] 3. 检查点 — 基础设施层验证
|
||||
- 确保所有测试通过,ask the user if questions arise.
|
||||
- 验证现有 16 个端点在中间件 + CamelModel 迁移后行为正常
|
||||
- 确认 JSON 响应格式为 `{ code: 0, data: { camelCaseFields... } }`
|
||||
|
||||
- [x] 4. 后端路由路径修正(T0-3)
|
||||
- [x] 4.1 修改路由路径和函数名
|
||||
- 文件:`apps/backend/app/routers/xcx_tasks.py`
|
||||
- `@router.post("/{task_id}/cancel-abandon")` → `@router.post("/{task_id}/restore")`
|
||||
- 函数名 `cancel_abandon` → `restore_task`(业务逻辑不变)
|
||||
- 移除旧路径,不保留兼容映射
|
||||
- _需求: 3.1, 3.2, 3.3_
|
||||
|
||||
- [x] 4.2 编写单元测试:路由路径修正
|
||||
- 测试文件:`apps/backend/tests/unit/test_xcx_tasks_route.py`
|
||||
- 用例:`POST /restore` 返回 200;`POST /cancel-abandon` 返回 404/405
|
||||
- _需求: 3.1, 3.3_
|
||||
|
||||
- [x] 5. 前端 request() 解包适配(T0-4)
|
||||
- [x] 5.1 修改 request() 函数添加 .data 解包逻辑
|
||||
- 文件:`apps/miniprogram/miniprogram/utils/request.ts`(或 `services/api.ts`,视实际位置)
|
||||
- `code === 0` → 返回 `res.data.data`(业务数据)
|
||||
- `code !== 0` → 抛出 `{ code, message }` 错误对象
|
||||
- 无 `code` 字段 → 直接返回原始响应体(SSE 等非标准格式)
|
||||
- 确保所有现有 API 调用(Auth/Tasks/Notes/AI Chat)解包后行为不变
|
||||
- _需求: 4.1, 4.2, 4.3, 4.4_
|
||||
|
||||
- [x] 5.2 编写属性测试:错误码解包抛出
|
||||
- **Property 5: 错误码解包抛出**
|
||||
- 测试文件:`tests/test_rns1_properties.py`
|
||||
- 生成器:`st.integers().filter(lambda x: x != 0)` × `st.text()`
|
||||
- 验证:解包函数对 `{ code: n, message: m }` 抛出异常,异常包含 code 和 message
|
||||
- **验证需求: 4.2**
|
||||
|
||||
- [x] 5.3 编写属性测试:非标准格式响应透传
|
||||
- **Property 6: 非标准格式响应透传**
|
||||
- 测试文件:`tests/test_rns1_properties.py`
|
||||
- 生成器:`st.dictionaries(st.text(), st.text()).filter(lambda d: "code" not in d)`
|
||||
- 验证:解包函数直接返回原始 dict
|
||||
- **验证需求: 4.3**
|
||||
|
||||
- [x] 6. 检查点 — 适配层验证
|
||||
- 确保所有测试通过,ask the user if questions arise.
|
||||
- 验证后端路由 `/restore` 可正常调用
|
||||
- 验证前端 request() 解包逻辑对成功/错误/非标准格式三种场景均正确
|
||||
|
||||
- [x] 7. API 契约完全重写(T0-5)
|
||||
- [x] 7.1 重写 BOARD-3 财务看板契约
|
||||
- 文件:`docs/miniprogram-dev/API-contract.md`
|
||||
- 定义 6 板块嵌套结构:overview / recharge / revenue / cashflow / expense / coachAnalysis
|
||||
- 定义请求参数枚举:time(8 种)、area(7 种)、compare(0/1)
|
||||
- 标注 `area≠all` 时 recharge 不返回;标注 `items_sum` 口径和助教费用拆分规则
|
||||
- 每个指标附带环比字段(`xxxCompare` + `isDown` + `isFlat`)
|
||||
- _需求: 5.1.1 ~ 5.1.10, 7.1 ~ 7.5_
|
||||
|
||||
- [x] 7.2 重写 BOARD-1 助教看板契约
|
||||
- 定义基础字段 + 4 维度专属字段(perf/salary/sv/task)
|
||||
- `skills` 类型为 `Array<{ text, cls }>`,`topCustomers` 类型为 `string[]`
|
||||
- 定义请求参数枚举:sort(6 种)、skill(5 种)、time(6 种)
|
||||
- 标注交叉约束:`time=last_6m` 与 `sort=sv_desc` 不兼容 → HTTP 400
|
||||
- _需求: 5.2.1 ~ 5.2.8, 7.1, 7.6_
|
||||
|
||||
- [x] 7.3 重写 BOARD-2 客户看板契约
|
||||
- 定义基础字段 + 8 维度专属字段(recall/potential/balance/recharge/recent/spend60/freq60/loyal)
|
||||
- `assistants` 含 heartScore/badge 等字段
|
||||
- 定义请求参数:dimension(8 种)、project(5 种)、page/pageSize(分页)
|
||||
- _需求: 5.3.1 ~ 5.3.11, 7.1_
|
||||
|
||||
- [x] 7.4 重写 CUST-1 客户详情契约
|
||||
- 补充 Banner 字段:balance/consumption60d/idealInterval/daysSinceVisit
|
||||
- 补充 aiInsight / coachTasks / favoriteCoaches / notes 模块
|
||||
- 消费记录重写为嵌套结构(含 coaches 子数组、tableFee/foodAmount 分项)
|
||||
- 标注会员信息 JOIN 规则(DQ-6)和 `items_sum` 口径
|
||||
- _需求: 5.4.1 ~ 5.4.7, 7.1 ~ 7.4_
|
||||
|
||||
- [x] 7.5 重写 COACH-1 助教详情契约
|
||||
- 补充 performance(6 指标)/ income(本月/上月各 4 项)/ tierNodes / historyMonths
|
||||
- 扩展 topCustomers(heartEmoji/score/balance)
|
||||
- 补充近期服务明细 perfHours/customerId
|
||||
- 任务分组补充 noteCount/pinned/notes/reason
|
||||
- 补充 notes 模块
|
||||
- _需求: 5.5.1 ~ 5.5.9, 7.1 ~ 7.4_
|
||||
|
||||
- [x] 7.6 重写 PERF-1 绩效概览契约
|
||||
- thisMonthRecords 改为 DateGroup 分组结构
|
||||
- 补充收入档位数据:currentTier/nextTier/upgradeHoursNeeded/upgradeBonus
|
||||
- 补充 lastMonthIncome、incomeItems.desc
|
||||
- 补充 newCustomers 和 regularCustomers 扩展字段
|
||||
- _需求: 5.6.1 ~ 5.6.5, 7.1_
|
||||
|
||||
- [x] 7.7 重写 TASK-1 绩效概览字段契约
|
||||
- performance 从 4 字段扩展为 15+ 字段
|
||||
- 补充 tierNodes/basicHours/bonusHours/currentTier/nextTierHours/tierCompleted/bonusMoney/incomeTrend/incomeTrendDir/prevMonth
|
||||
- 任务 item 补充 lastVisitDays/balance/aiSuggestion
|
||||
- _需求: 5.7.1, 5.7.2_
|
||||
|
||||
- [x] 7.8 重写 CHAT-1/2 对话模块契约
|
||||
- 统一时间字段名为 `createdAt`
|
||||
- CHAT-1 补充 `title` 字段
|
||||
- CHAT-2 补充 `referenceCard` 可选字段
|
||||
- 补充 SSE 流式端点定义:`POST /api/xcx/chat/stream`
|
||||
- 标注 `customerId` 查询参数支持
|
||||
- _需求: 5.8.1 ~ 5.8.5_
|
||||
|
||||
- [x] 7.9 契约一致性审查
|
||||
- 确保所有字段名统一 camelCase
|
||||
- 确保所有金额字段标注 `items_sum` 口径
|
||||
- 确保所有助教费用标注 `assistant_pd_money` + `assistant_cx_money` 拆分
|
||||
- 确保所有会员信息标注 `member_id` JOIN `dim_member` 规则
|
||||
- 每个接口标注数据源(FDW 表名或业务表名)
|
||||
- 对照前端 mock 数据结构逐字段比对,确保无遗漏或类型不匹配
|
||||
- _需求: 7.1 ~ 7.6_
|
||||
|
||||
- [x] 8. 检查点 — 契约文档验证
|
||||
- 确保所有测试通过,ask the user if questions arise.
|
||||
- 确认 8 个接口契约定义完整、字段名 camelCase、金额口径标注正确
|
||||
- 确认契约文档可作为后续子 spec(RNS1.1-1.4)的唯一实现基准
|
||||
|
||||
- [x] 9. 前端跨页面参数修复(T0-6)
|
||||
- [x] 9.1 修复 task-detail 页面跳转参数
|
||||
- 文件:`apps/miniprogram/miniprogram/pages/task-detail/task-detail.ts`
|
||||
- 跳转 chat 时传 `customerId={detail.customerId}`(而非 `detail.id`)
|
||||
- 跳转 customer-service-records 时传 `customerId={detail.customerId}`(而非 `detail.id`)
|
||||
- _需求: 6.1.1, 6.1.2_
|
||||
|
||||
- [x] 9.2 修复 customer-detail 页面跳转参数和 ID 获取方式
|
||||
- 文件:`apps/miniprogram/miniprogram/pages/customer-detail/customer-detail.ts`
|
||||
- 跳转 customer-service-records 时传 `customerId={detail.id}`
|
||||
- 跳转 chat 时传 `customerId={detail.id}`
|
||||
- `loadDetail()` 从 `onLoad(options)` 的 `options.id` 获取客户 ID,替代 `__route__` 解析
|
||||
- _需求: 6.2.1, 6.2.2, 6.2.3_
|
||||
|
||||
- [x] 9.3 修复 coach-detail 页面跳转参数
|
||||
- 文件:`apps/miniprogram/miniprogram/pages/coach-detail/coach-detail.ts`
|
||||
- 任务项跳转 customer-detail 时传 `id={customerId}`(而非 `name={customerName}`)
|
||||
- 依赖 COACH-1 响应中 TaskItem 包含 `customerId` 字段
|
||||
- _需求: 6.3.1_
|
||||
|
||||
- [x] 9.4 修复 performance 页面跳转参数
|
||||
- 文件:`apps/miniprogram/miniprogram/pages/performance/performance.ts`
|
||||
- 跳转 task-detail 时传 `id={taskId}`(而非 `customerName={name}`)
|
||||
- 依赖 PERF-1 响应中记录包含 `taskId` 字段
|
||||
- _需求: 6.4.1_
|
||||
|
||||
- [x] 9.5 实现 chat 页面多入口参数路由
|
||||
- 文件:`apps/miniprogram/miniprogram/pages/chat/chat.ts`
|
||||
- 支持 `customerId` 入口:使用 `customerId` 查询参数调用 CHAT 端点
|
||||
- 支持 `historyId` 入口:使用 `historyId` 作为 `chatId` 加载历史消息
|
||||
- 支持 `coachId` 入口:使用 `coachId` 作为上下文参数
|
||||
- _需求: 6.5.1, 6.5.2, 6.5.3_
|
||||
|
||||
- [x] 9.6 扩展 globalData.authUser 字段
|
||||
- 文件:`apps/miniprogram/miniprogram/app.ts`
|
||||
- 登录成功后将 `role`/`storeName`/`coachLevel`/`avatar` 存入 `globalData.authUser`
|
||||
- 各页面 Banner 直接从 `globalData.authUser` 读取,不再单独请求 `/me`
|
||||
- _需求: 6.6.1, 6.6.2_
|
||||
|
||||
- [x] 10. 最终检查点 — 全量验证
|
||||
- 确保所有测试通过,ask the user if questions arise.
|
||||
- 验证后端:中间件包装 + CamelModel + 路由修正 + 异常处理器全部生效
|
||||
- 验证前端:request() 解包 + 8 个跳转场景参数正确 + globalData 字段完整
|
||||
- 验证契约:8 个接口定义完整,可作为 RNS1.1-1.4 实现基准
|
||||
|
||||
## 备注
|
||||
|
||||
- 标记 `*` 的子任务为可选测试任务,可跳过以加速 MVP
|
||||
- 每个任务引用具体需求编号,确保可追溯
|
||||
- 属性测试使用 Hypothesis(`tests/test_rns1_properties.py`),单元测试使用 pytest(`apps/backend/tests/unit/`)
|
||||
- 检查点确保增量验证,避免问题累积
|
||||
- T0-5 契约重写为文档任务,产出物为 `docs/miniprogram-dev/API-contract.md`
|
||||
- T0-6 前端参数修复中部分跳转依赖后续子 spec 的 API 响应字段(如 COACH-1 的 `customerId`、PERF-1 的 `taskId`),当前先修改跳转代码,待后端实现后联调验证
|
||||
Reference in New Issue
Block a user