baseline: Clover 独立仓库首次基线提交

将 Clover 从上层产品包旧仓库中独立出来,建立专属版本控制。
当前状态=纵切片端到端已打通(登录→选品→出文出图→审核→下载包),
M1文案质量去套路化已验收。此提交作为后续按核销清单逐条修复的基线。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
yangqianqian
2026-06-16 11:30:22 +08:00
commit 6a2632da70
253 changed files with 27467 additions and 0 deletions

277
启动包/API契约.md Normal file
View File

@@ -0,0 +1,277 @@
# Clover API 契约OpenAPI 初稿 · Lead 冻结用)
> 用途:开工前由 Lead/集成 冻结此契约,前后端按此并行开工,杜绝"前端假设字段、联调爆炸"。
> 铁律:所有 `type: object` 不许留空——冻结前嵌套对象一级子字段必须定义官网V1坑4
> 来源承接架构方案v0.3golden standard端点 + 16表字段不重复定义冲突以架构方案为准。
> 范围:一期 1A 生产链 + 1B 飞轮。二期端点(注册/矩阵号/管理后台)标 [二期] 不冻结。
## 0. 全局约定
### 认证
- 除登录/注册外,所有请求头带 `Authorization: Bearer <JWT>`
- JWT 载荷:`user_id` / `current_workspace_id` / `role`HS256 签名,每请求验签)。
- 写操作 + 切 workspace 必须查 workspace_members 校验权限(读操作可信 JWT 加速)。
### 统一响应包络
```
成功:{ "code": 0, "data": {...} }
失败:{ "code": <错误码>, "message": "用户可读信息" }
```
HTTP 状态码与业务 code 分离。
### 错误码规范7 类)
| code | 含义 | HTTP |
|------|------|------|
| 0 | 成功 | 200 |
| 40001 | 参数校验失败 | 400 |
| 40101 | 未认证/JWT失效 | 401 |
| 40301 | 无权限/越权访问 workspace | 403 |
| 40401 | 资源不存在 | 404 |
| 40901 | 状态机非法流转 | 409 |
| 42201 | 业务校验失败(如未配key/违禁词硬拦截) | 422 |
| 50001 | 服务端错误 | 500 |
| 50002 | AI/token站调用失败(归因到个人key) | 502 |
### 分页 & 限流
- 列表统一 `?page=1&page_size=20`,返回 `{items, total, page, page_size}`
- 写操作与 AI 触发接口限流(防滥用),超限返 429。
## 1. 端点清单(一期 1A+1B
### 认证 & 账号
| 方法 | 路径 | 说明 | 角色 |
|------|------|------|------|
| POST | /api/v1/auth/login | 登录,返 JWT | 全部 |
| GET | /api/v1/auth/me | 当前用户+workspace+role | 全部 |
| POST | /api/v1/workspaces/switch | 切换当前 workspace查membership | 全部 |
### API Key只录不显余额CLAUDE.md红线
| 方法 | 路径 | 说明 |
|------|------|------|
| GET | /api/v1/api-keys | 列表(只显 provider + 后4位**不显余额/用量** |
| POST | /api/v1/api-keys | 录入Fernet加密存url固定自家站不接收url字段 |
| DELETE | /api/v1/api-keys/{id} | 删除 |
### 品牌库(管理员)
| 方法 | 路径 | 说明 |
|------|------|------|
| GET/POST | /api/v1/products | 产品档案 CRUD含文案角度+可调prompt+source |
| GET/PUT/DELETE | /api/v1/products/{id} | 单产品 |
| GET/POST | /api/v1/products/{id}/benchmarks | 标杆笔记(截图+手填亮点为主通道) |
| GET/POST/PUT/DELETE | /api/v1/banned-words | 违禁词库level三级workspace可覆盖 |
### 生产链(熟手)
| 方法 | 路径 | 说明 |
|------|------|------|
| POST | /api/v1/tasks | 发起任务(选产品+标杆+今天主题+数量校验有无key→只推task_id入队 |
| GET | /api/v1/tasks | 任务列表(按状态筛) |
| GET | /api/v1/tasks/{id} | 任务详情(含候选+状态机当前态) |
| POST | /api/v1/tasks/{id}/sse-ticket | 换取一次性 SSE ticketJWT认证→返回60s短票FE立即建EventSource |
| GET | /api/v1/tasks/{id}/stream | **SSE** 实时进度见§2用 ?ticket= 认证,禁止 ?token= |
| POST | /api/v1/tasks/{id}/text-candidates/select | 选文案飞轮入口1+3 |
| POST | /api/v1/tasks/{id}/image-candidates/select | 选图飞轮入口2+3 |
| POST | /api/v1/tasks/{id}/import-text | 轨B导入外部文案直接进候选池 |
| POST | /api/v1/tasks/{id}/regenerate | 重新生成(飞轮-1 |
| POST | /api/v1/tasks/{id}/submit-review | 提交审核状态→pending_review |
| GET | /api/v1/tasks/{id}/preference/context | 取本任务飞轮偏好上下文(最近选中样本+最近3条打回原因供 prompt 组装;前端仅用于显示"本次已注入"摘要 |
> 🔴 飞轮**只有读端点**:偏好信号(选文案/选图/通过/打回/重生成)由上方 select / approve / reject / regenerate 接口**内部**写入 `preference_events`**不暴露独立的 POST /preference/event 给前端**(避免被当成"训练按钮",且防伪造信号)。
### 审核(组长)
| 方法 | 路径 | 说明 |
|------|------|------|
| GET | /api/v1/review/queue | 待审队列 |
| POST | /api/v1/review/{task_id}/approve | 通过(飞轮+5最重状态→approved |
| POST | /api/v1/review/{task_id}/reject | 打回(写原因,-3回pending_selection原因下次注入prompt |
### 交付包
| 方法 | 路径 | 说明 |
|------|------|------|
| POST | /api/v1/tasks/{id}/package | 生成达人素材交付包(去水印+打包) |
| GET | /api/v1/delivery-packages/{id}/download | 下载status: pending/ready/downloaded |
### [二期] 不冻结
注册(手机验证码)、matrix_accounts矩阵号、管理后台用量统计、preference_profile物化。
## 2. SSE 事件契约(异步生图电影 + 飞轮注入提示)
`GET /api/v1/tasks/{id}/stream` 流式推送,前端据此渲染"逐帧电影"。
断线重连时后端**补发历史事件**(修 banana 漏洞),前端按 event_seq 去重。
> **🔴 SSE 认证安全红线2026-06-08 倩倩姐拍板)**EventSource 不支持自定义 header
> 故 FE 建连前先用 JWT 调 `POST /sse-ticket` 换一张一次性短票TTL 60s仅对该 task 有效),
> URL 只带 `?ticket=`。ticket 用后即焚Redis getdel就算被日志记下也立即失效。
> **禁止 `?token=` 直传 JWT 进 URL。**
| event 类型 | data 字段(一级子字段必须全定义) | 前端用途 |
|-----------|----------------------------------|---------|
| task_started | `{task_id, total_text, total_image}` | 显示 0/N 进度 |
| analyze_done | `{features:[8特征]}` | 标杆分析完成 |
| text_progress | `{done, total}` | 文案 done/total |
| text_candidate | `{candidate_id, angle_label, content, score}` | 单条文案冒出来 |
| image_progress | `{done, total}` | 图 done/total |
| image_candidate | `{candidate_id, strategy(A/B/C), url, role}` | 单张图冒出来(谁好谁先冒) |
| flywheel_injected | `{recent_preference, reject_reasons:[最近3条]}` | UI显"本次已注入"飞轮基石E第4点 |
| batch_failed | `{batch, reason, retryable:bool}` | 单批失败可重试,不阻塞其他 |
| task_done | `{task_id, status}` | 全部完成,可离开 |
| error | `{code, message}` | 对接§0错误码 |
| heartbeat | `{ts}` | 保活 |
## 3. 关键 DTOtype:object 不许留空)
### 发起任务 POST /api/v1/tasks 请求体
```
{
"product_id": 12,
"benchmark_ids": [3, 5],
"theme": "今天主题:夏日清爽控油", // 前端交互设计的"今天主题"字段
"text_count": 5, // 用户设定,不写死(基石A)
"image_count": 3,
"track": "ai" // ai=轨A生成 / import=轨B导入
}
```
### text_candidate 文案候选(含五维分,挑文案屏用)
```
{
"candidate_id": 101, "angle_label": "痛点切入", "content": "...",
"source": "ai", // ai / import
"score": { "title":24, "emotion":23, "selling":25, "keyword":18, "compliance":5, "total":95 },
"banned_word_status": "pass" // pass / auto_fixed / soft_warn / hard_block
}
```
> 五维标题25/情绪25/买点25/关键词20/合规5过线90承接产品包评分引擎
### 打回 POST /api/v1/review/{task_id}/reject 请求体
```
{ "reason": "标题太硬广,不像素人分享" } // 原文存 preference_events.reason不做AI归纳
```
### 选择类(飞轮信号)统一体
```
{ "candidate_id": 101 } // 后端据登录态拿 user/workspace/product写 preference_events
```
> 凡涉及 workspace_id / product_id 的写入,后端从 JWT + 任务上下文自动带,**前端不传**(防越权)。
## 4. 补全 DTO冻结补全官网V1坑4
### POST /api/v1/auth/login
请求体:
```json
{ "username": "string", "password": "string" }
```
返回 data
```json
{
"token": "eyJ...",
"user": {
"id": 1, "username": "string", "email": "string",
"current_workspace_id": 1, "role": "admin|supervisor|operator"
}
}
```
### GET /api/v1/auth/me 返回 data
```json
{
"id": 1, "username": "string", "email": "string",
"current_workspace_id": 1, "role": "admin|supervisor|operator",
"workspace": { "id": 1, "name": "string", "slug": "string" }
}
```
### POST /api/v1/workspaces/switch
请求体:`{ "workspace_id": 2 }`
返回 data`{ "current_workspace_id": 2, "role": "string", "token": "eyJ..." }`
### GET /api/v1/api-keys 返回 items 单项
```json
{ "id": 1, "provider": "openai", "key_last4": "ab12", "created_at": "2026-01-01T00:00:00Z" }
```
注:不返回余额/用量CLAUDE.md前端红线
### POST /api/v1/api-keys 请求体
```json
{ "provider": "openai", "api_key": "sk-..." }
```
url 字段不接收,后端固定走自家站 endpoint架构方案§二决策
### POST /api/v1/products 请求体
```json
{
"name": "素颜霜", "category": "string", "source": "preset|custom",
"selling_points": ["控油", "补水"],
"style_tone": "素人分享风",
"text_angles": ["痛点切入", "成分党", "使用场景"],
"custom_prompt": "string|null",
"banned_word_ids": [1, 2]
}
```
### POST /api/v1/products/{id}/benchmarks 请求体
```json
{
"screenshot_url": "string",
"highlights": "string",
"link_url": "string|null"
}
```
### GET /api/v1/banned-words 返回 items 单项
```json
{
"id": 1, "word": "美白", "level": "auto_fix|soft_warn|hard_block",
"replacement": "提亮|null", "updatable": true, "workspace_id": 1
}
```
### POST/PUT /api/v1/banned-words 请求体
```json
{ "word": "祛斑", "level": "hard_block", "replacement": null, "updatable": true }
```
### GET /api/v1/tasks 返回 items 单项
```json
{
"id": 1, "product_id": 1, "theme": "string", "status": "pending|generating|pending_selection|pending_review|approved|rejected|archived",
"created_at": "2026-01-01T00:00:00Z", "text_count": 5, "image_count": 3
}
```
### GET /api/v1/review/queue 返回 items 单项
```json
{
"task_id": 1, "product_name": "string", "theme": "string",
"submitted_at": "2026-01-01T00:00:00Z", "operator_name": "string",
"selected_text": { "candidate_id": 101, "angle_label": "string", "content": "string" },
"selected_image": { "candidate_id": 201, "strategy": "A|B|C", "url": "string" }
}
```
### GET /api/v1/tasks/{id}/preference/context 返回 data
```json
{
"recent_preference": "最近偏好摘要偏好痛点切入角度已选3次",
"reject_reasons": ["标题太硬广", "图换了包装"],
"injected_count": 3
}
```
### POST /api/v1/tasks/{id}/package 请求体
```json
{ "note_ids": [1, 2, 3] }
```
返回 data`{ "delivery_package_id": 1, "status": "pending" }`
### GET /api/v1/delivery-packages/{id}/download 返回 data
```json
{ "id": 1, "status": "pending|ready|downloaded", "download_url": "string|null", "expires_at": "string|null" }
```
### image_candidate 的 role 字段说明
`role` = storyboard角色枚举`hook`(钩子) / `pain`(痛点) / `proof`(证明) / `quality`(质感) / `credit`(背书) / `convert`(转化)。单张图时 role=`main`
---
> **冻结戳记**Lead 冻结,日期 2026-06-08。BE/FE/AIE 按此并行开工,契约变更需所有 agent 确认后方可修改。