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

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 11:30:22 +08:00

278 lines
11 KiB
Markdown
Raw Permalink 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.
# 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 确认后方可修改。