Files
gen2d/docs/api.md
T
wonder 2f8e7ead17 fix: 修复前端无法加载七牛云图片的问题
1. 修复 buildURL 中 url.PathEscape 对路径分隔符 / 的错误编码(/→%2F),
   改为逐段编码后拼接
2. AssetResponse 新增 key 字段,返回七牛云对象存储 Key
3. 前端改用 /api/v1/assets/download 接口加载图片,通过后端 302 重定向
   访问 CDN,避免浏览器直接访问 CDN 可能出现的跨域或编码问题
4. 更新 api.md 文档,补充 key 字段说明和 download 接口公开属性
2026-05-25 15:25:40 +08:00

565 lines
14 KiB
Markdown
Executable File
Raw 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.
# gen2d API 设计
所有接口统一前缀 `/api/v1/`,统一响应格式:
```json
{ "code": 0, "message": "ok", "data": {} }
```
## 认证
除健康检查和注册登录外,所有接口需认证。登录成功后,Token 在响应体 `data.token` 字段中返回,客户端需自行存储(如 localStorage)并通过 `Authorization: Bearer <token>` 请求头携带。
Token 过期或无效时返回:
```json
{ "code": 401, "message": "未登录或 Token 已过期", "data": null }
```
权限不足时返回:
```json
{ "code": 403, "message": "无权访问该资源", "data": null }
```
## 错误码
| code | 含义 | 场景 |
|------|------|------|
| 0 | 成功 | — |
| 400 | 请求参数错误 | 缺少必填字段、格式不合法 |
| 401 | 未认证 | Token 缺失、过期、无效 |
| 403 | 无权访问 | 访问不属于自己的资源 |
| 404 | 资源不存在 | 工程/任务/素材 ID 不存在 |
| 409 | 冲突 | 用户名或邮箱已注册 |
| 429 | 请求过于频繁 | 触发速率限制 |
| 500 | 服务器内部错误 | 未预期异常 |
## 状态说明
- [x] 已完成
- [ ] 规划中
---
## 基础设施
| 状态 | 方法 | 路径 | 说明 |
|------|------|------|------|
| [x] | GET | `/api/v1/health` | 健康检查 |
## 用户认证
> **注意:** 后端代码实际注册路径为 `/auth/*`(非 `/api/v1/auth/*`),前端 Vite 代理及 nginx 均已配置 `/auth` 转发。
| 状态 | 方法 | 路径 | 说明 |
|------|------|------|------|
| [x] | POST | `/auth/register` | 用户注册 |
| [x] | POST | `/auth/login` | 用户登录 |
| [ ] | GET | `/api/v1/auth/me` | 获取当前用户信息 |
| [ ] | PUT | `/api/v1/auth/password` | 修改密码 |
### POST /auth/register
```json
{
"username": "player1",
"email": "player1@example.com",
"password": "s3cretP@ss"
}
```
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `username` | string | 是 | 3-32 字符 |
| `email` | string | 否 | 邮箱地址 |
| `password` | string | 是 | 6-64 字符 |
响应(HTTP 状态码 201):
```json
{
"code": 0,
"message": "ok",
"data": {
"id": 1,
"username": "player1"
}
}
```
错误响应:
| 场景 | code | message |
|------|------|---------|
| 用户名格式不合法(非 3-32 字符) | 400 | 参数错误 |
| 密码长度不足 | 400 | 参数错误 |
| 邮箱格式不合法 | 400 | 参数错误 |
| 用户名已存在 | 409 | 用户名已被注册 |
### POST /auth/login
```json
{
"username": "player1",
"password": "s3cretP@ss"
}
```
响应(Token 在 body 中返回):
```json
{
"code": 0,
"message": "ok",
"data": {
"token": "eyJhbGciOiJIUzI1NiIs...",
"expiresIn": 7200,
"user": {
"id": 1,
"username": "player1",
"email": "player1@example.com",
"createdAt": "2026-05-24T10:00:00Z",
"updatedAt": "2026-05-24T10:00:00Z"
}
}
}
```
### GET /api/v1/auth/me
需认证。响应:
```json
{
"code": 0,
"message": "ok",
"data": {
"id": "user_a1B2c3D4",
"username": "player1",
"email": "player1@example.com",
"createdAt": "2026-05-24T10:00:00Z"
}
}
```
### PUT /api/v1/auth/password
需认证。
```json
{
"oldPassword": "s3cretP@ss",
"newPassword": "n3wS3cretP@ss"
}
```
错误响应:
| 场景 | code | message |
|------|------|---------|
| 原密码错误 | 400 | 原密码不正确 |
## 提示词优化
将用户标签和原始描述送入 LLM,生成规范化三段式提示词。前端组件收集用户标签和原始提示词后,调用本接口获取优化结果,用于预览或直接发起素材生成。
| 状态 | 方法 | 路径 | 说明 |
|------|------|------|------|
| [x] | POST | `/api/v1/prompt/optimize` | 提示词优化 |
### POST /api/v1/prompt/optimize
```json
{
"tags": ["像素", "战士", "黑暗"],
"assetType": "sprite",
"prompt": "我要一个拿着大剑的战士角色,背景是黑暗地牢",
"userNote": "需要武器发光特效"
}
```
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `tags` | string[] | 是 | 用户选择的风格标签,至少 1 个 |
| `assetType` | string | 是 | 素材类型:`sprite` / `background` / `ui` / `animation` |
| `prompt` | string | 否 | 用户输入的原始提示词描述 |
| `userNote` | string | 否 | 额外补充说明 |
响应:
```json
{
"code": 0,
"message": "ok",
"data": {
"prompt": "【主题】一个融合像素、战士元素的游戏角色精灵图...\n【风格】色彩鲜明;细节丰富...\n【技术】输出格式: spritesheet...",
"rawText": "【主题】一个融合像素..."
}
}
```
**链路:**
```
POST /api/v1/prompt/optimize
→ handler.PromptOptimize 解析请求
→ service.RunPromptAgent 执行 Eino Chain
→ formatMetaPrompt: 构建元提示词(角色设定 + 标签 + 原始描述 + 素材类型)
→ llmRefine: 调用 OpenAI 兼容 Chat Completions API
└── 无 API key → 回退模板生成
→ 返回 PromptAgentOutput { prompt, rawText }
```
## 工程管理
需认证。以下接口均需通过 Cookie 携带 Token,工程归属于当前登录用户。
| 状态 | 方法 | 路径 | 说明 |
|------|------|------|------|
| [ ] | GET | `/api/v1/projects` | 获取当前用户的工程列表 |
| [ ] | POST | `/api/v1/projects` | 创建工程 |
| [ ] | GET | `/api/v1/projects/:projectId` | 获取工程信息 |
| [ ] | DELETE | `/api/v1/projects/:projectId` | 删除工程(级联删除任务和素材) |
| [ ] | GET | `/api/v1/projects/:projectId/tasks` | 获取工程下的任务列表 |
### GET /api/v1/projects
获取当前用户的工程列表。
| 参数 | 类型 | 说明 |
|------|------|------|
| `page` | int | 页码,默认 1 |
| `pageSize` | int | 每页条数,默认 20 |
响应:
```json
{
"code": 0,
"message": "ok",
"data": {
"total": 3,
"projects": [
{
"id": "proj_abc123",
"name": "我的像素游戏",
"createdAt": "2026-05-24T10:00:00Z"
}
]
}
}
```
### POST /api/v1/projects
```json
{
"name": "我的像素游戏"
}
```
响应:
```json
{
"code": 0,
"message": "ok",
"data": {
"id": "proj_abc123",
"name": "我的像素游戏",
"style": { "kvPairs": {} },
"createdAt": "2026-05-24T10:00:00Z"
}
}
```
### GET /api/v1/projects/:projectId
响应:
```json
{
"code": 0,
"message": "ok",
"data": {
"id": "proj_abc123",
"name": "我的像素游戏",
"createdAt": "2026-05-24T10:00:00Z",
"updatedAt": "2026-05-24T10:00:00Z"
}
}
```
错误响应:
| 场景 | code | message |
|------|------|---------|
| projectId 不存在 | 404 | 工程不存在 |
| 工程不属于当前用户 | 403 | 无权访问该工程 |
### DELETE /api/v1/projects/:projectId
级联删除工程下的所有任务、素材及七牛云对象。
响应:
```json
{
"code": 0,
"message": "ok",
"data": null
}
```
### GET /api/v1/projects/:projectId/tasks
| 参数 | 类型 | 说明 |
|------|------|------|
| `page` | int | 页码,默认 1 |
| `pageSize` | int | 每页条数,默认 20 |
响应:
```json
{
"code": 0,
"message": "ok",
"data": {
"total": 42,
"tasks": [
{
"id": "task_xyz789",
"prompt": "一个拿剑的小人",
"assetType": "sprite",
"status": "completed",
"createdAt": "2026-05-24T10:05:00Z"
}
]
}
}
```
## 工程风格
需认证。风格归属于工程,仅工程所属用户可操作。
| 状态 | 方法 | 路径 | 说明 |
|------|------|------|------|
| [ ] | GET | `/api/v1/projects/:projectId/style` | 获取工程风格 |
| [ ] | PUT | `/api/v1/projects/:projectId/style` | 更新工程风格 |
请求体示例 (PUT /api/v1/projects/:projectId/style):
```json
{
"kvPairs": {
"artStyle": "pixel",
"palette": "warm",
"lineWeight": "thin",
"lighting": "bright"
}
}
```
## 素材生成
需认证。任务归属于当前用户的工程,跨用户访问返回 403。
| 状态 | 方法 | 路径 | 说明 |
|------|------|------|------|
| [x] | POST | `/api/v1/generate` | 提交生成任务,返回 taskId |
| [x] | GET | `/api/v1/tasks/:taskId` | 查询任务状态与进度 |
| [x] | GET | `/api/v1/tasks/:taskId/assets` | 获取生成结果(素材列表 + 元数据) |
| [ ] | WS | `/api/v1/tasks/:taskId/ws` | WebSocket 实时进度推送 |
| [x] | GET | `/api/v1/assets/download` | 下载素材(重定向到 CDN) |
### POST /api/v1/generate
请求体对应后端 `PipelineInput`:
```json
{
"projectId": "proj_abc123",
"prompt": "一个拿剑的小人",
"assetType": "sprite",
"taskStyle": {
"scene": "dungeon",
"mood": "dark"
},
"params": {
"resolution": 64,
"frames": { "directions": 8, "framesPerDirection": 4 },
"format": "spritesheet"
}
}
```
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `projectId` | string | 是 | 工程 ID,用于获取工程风格 |
| `prompt` | string | 是 | 用户原始文本,后端 PromptOptimizer/PromptAgent 负责提示词优化 |
| `assetType` | string | 是 | 素材类型:`sprite` / `background` / `ui` / `animation` |
| `taskStyle` | object | 否 | 任务级别风格覆盖(同名键覆盖工程风格) |
| `params.resolution` | int | 否 | 分辨率,默认 64 |
| `params.frames` | object | 否 | 帧参数(仅 sprite/animation) |
| `params.frames.directions` | int | 否 | 方向数,默认 4 |
| `params.frames.framesPerDirection` | int | 否 | 每方向帧数,默认 4 |
| `params.format` | string | 否 | 输出格式:`spritesheet` / `individual`,默认 `spritesheet` |
响应:
```json
{
"code": 0,
"message": "ok",
"data": {
"taskId": "task_xyz789",
"status": "pending"
}
}
```
去重:相同 `prompt + assetType + params` 的并发请求返回已有 taskId,不重复创建任务。
错误响应:
| 场景 | code | message |
|------|------|---------|
| projectId 不存在或不属于当前用户 | 403 | 无权访问该工程 |
| prompt 为空 | 400 | prompt 不能为空 |
### GET /api/v1/tasks/:taskId
响应:
```json
{
"code": 0,
"message": "ok",
"data": {
"taskId": "task_xyz789",
"projectId": "proj_abc123",
"prompt": "一个拿剑的小人",
"assetType": "sprite",
"taskStyle": null,
"params": { "resolution": 64, "format": "spritesheet" },
"status": "running",
"stage": "asset_generator",
"progress": 45,
"retryCount": 0,
"error": null,
"createdAt": "2026-05-24T10:05:00Z",
"updatedAt": "2026-05-24T10:05:30Z"
}
}
```
| 字段 | 类型 | 说明 |
|------|------|------|
| `status` | string | 任务状态:`pending` / `running` / `completed` / `failed` |
| `stage` | string | 当前管线阶段,`running` 时有值 |
| `progress` | int | 0-100,整体进度 |
| `retryCount` | int | 质检重试次数 |
| `error` | string | 失败原因,仅 `failed` 时有值 |
状态机与管线阶段详见 [异步任务](async-tasks.md)。
错误响应:
| 场景 | code | message |
|------|------|---------|
| taskId 不存在 | 404 | 任务不存在 |
| 任务不属于当前用户 | 403 | 无权访问该任务 |
### GET /api/v1/tasks/:taskId/assets
任务状态为 `completed` 时返回素材列表,其他状态返回空数组。
```json
{
"code": 0,
"message": "ok",
"data": {
"assets": [
{
"key": "generation/proj_abc123/task_xyz789/0.png",
"url": "https://cdn.example.com/generation/proj_abc123/task_xyz789/0.png",
"format": "png",
"width": 256,
"height": 64,
"metadata": {
"frameWidth": 64,
"frameHeight": 64,
"frameCount": 4,
"directions": 1
}
}
]
}
}
```
| 字段 | 类型 | 说明 |
|------|------|------|
| `key` | string | 七牛云对象存储 Key |
| `url` | string | CDN 直接访问 URL |
| `format` | string | 图片格式(png 等) |
### GET /api/v1/assets/download
公开接口(无需认证)。返回 302 重定向到七牛云 CDN URL。前端可通过此接口加载素材图片,作为 CDN 直接访问的替代方案。
| 参数 | 类型 | 说明 |
|------|------|------|
| `key` | string | 对象存储 Key,查询参数 |
响应:
- HTTP 302,`Location` 头指向 CDN URL
- 400:缺少 key 参数
- 500:生成下载链接失败
### WebSocket 消息格式
连接路径:`ws://host/api/v1/tasks/:taskId/ws`(通过 httpOnly Cookie 自动携带认证信息)
```typescript
interface PipelineProgress {
stage: 'prompt_optimizer' | 'asset_generator' | 'quality_supervisor' | 'format_adapter';
status: 'running' | 'completed' | 'failed';
progress: number; // 0-100
message?: string; // 阶段描述
retryCount?: number; // 质检重试次数(仅 quality_supervisor 阶段)
rejectReason?: string; // 质检不通过原因(仅 quality_supervisor fail 时)
result?: { // 仅在 format_adapter completed 时返回
assets: Asset[];
};
error?: string; // 仅在 failed 时返回
}
```
消息示例(正常流程):
```json
{"stage":"prompt_optimizer","status":"running","progress":0}
{"stage":"prompt_optimizer","status":"completed","progress":100}
{"stage":"asset_generator","status":"running","progress":0}
{"stage":"asset_generator","status":"completed","progress":100}
{"stage":"quality_supervisor","status":"running","progress":0}
{"stage":"quality_supervisor","status":"completed","progress":100}
{"stage":"format_adapter","status":"running","progress":0}
{"stage":"format_adapter","status":"completed","progress":100,"result":{"assets":[...]}}
```
消息示例(质检重试):
```json
{"stage":"quality_supervisor","status":"running","progress":0}
{"stage":"quality_supervisor","status":"failed","progress":100,"retryCount":1,"rejectReason":"风格不一致"}
{"stage":"prompt_optimizer","status":"running","progress":0}
{"stage":"asset_generator","status":"running","progress":0}
{"stage":"quality_supervisor","status":"completed","progress":100}
{"stage":"format_adapter","status":"completed","progress":100,"result":{"assets":[...]}}
```