将 Clover 从上层产品包旧仓库中独立出来,建立专属版本控制。 当前状态=纵切片端到端已打通(登录→选品→出文出图→审核→下载包), M1文案质量去套路化已验收。此提交作为后续按核销清单逐条修复的基线。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
11 KiB
Clover API 契约(OpenAPI 初稿 · Lead 冻结用)
用途:开工前由 Lead/集成 冻结此契约,前后端按此并行开工,杜绝"前端假设字段、联调爆炸"。 铁律:所有
type: object不许留空——冻结前嵌套对象一级子字段必须定义(官网V1坑4)。 来源:承接架构方案v0.3(golden 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 ticket(JWT认证→返回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. 关键 DTO(type: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
请求体:
{ "username": "string", "password": "string" }
返回 data:
{
"token": "eyJ...",
"user": {
"id": 1, "username": "string", "email": "string",
"current_workspace_id": 1, "role": "admin|supervisor|operator"
}
}
GET /api/v1/auth/me 返回 data
{
"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 单项
{ "id": 1, "provider": "openai", "key_last4": "ab12", "created_at": "2026-01-01T00:00:00Z" }
注:不返回余额/用量(CLAUDE.md前端红线)。
POST /api/v1/api-keys 请求体
{ "provider": "openai", "api_key": "sk-..." }
注:url 字段不接收,后端固定走自家站 endpoint(架构方案§二决策)。
POST /api/v1/products 请求体
{
"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 请求体
{
"screenshot_url": "string",
"highlights": "string",
"link_url": "string|null"
}
GET /api/v1/banned-words 返回 items 单项
{
"id": 1, "word": "美白", "level": "auto_fix|soft_warn|hard_block",
"replacement": "提亮|null", "updatable": true, "workspace_id": 1
}
POST/PUT /api/v1/banned-words 请求体
{ "word": "祛斑", "level": "hard_block", "replacement": null, "updatable": true }
GET /api/v1/tasks 返回 items 单项
{
"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 单项
{
"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
{
"recent_preference": "最近偏好摘要:偏好痛点切入角度(已选3次)",
"reject_reasons": ["标题太硬广", "图换了包装"],
"injected_count": 3
}
POST /api/v1/tasks/{id}/package 请求体
{ "note_ids": [1, 2, 3] }
返回 data:{ "delivery_package_id": 1, "status": "pending" }
GET /api/v1/delivery-packages/{id}/download 返回 data
{ "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 确认后方可修改。