Files
gen2d/docs/api.md
T

574 lines
14 KiB
Markdown
Raw Normal View History

# gen2d API 设计
2026-05-23 12:19:53 +08:00
所有接口统一前缀 `/api/v1/`,统一响应格式:
2026-05-23 12:19:53 +08:00
```json
{ "code": 0, "message": "ok", "data": {} }
2026-05-23 12:19:53 +08:00
```
## 认证
除健康检查和注册登录外,所有接口需认证。登录成功后,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 | 服务器内部错误 | 未预期异常 |
## 状态说明
2026-05-23 12:19:53 +08:00
- [x] 已完成
- [ ] 规划中
2026-05-23 12:19:53 +08:00
---
## 基础设施
2026-05-23 12:19:53 +08:00
| 状态 | 方法 | 路径 | 说明 |
|------|------|------|------|
| [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` | 获取当前用户的工程列表(前端已对接 mock) |
| [ ] | POST | `/api/v1/projects` | 创建工程(前端已对接 mock) |
| [ ] | GET | `/api/v1/projects/:projectId` | 获取工程信息 |
| [ ] | PUT | `/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",
"taskCount": 5,
"lastActivityAt": "2026-05-24T10:05:00Z"
}
]
}
}
```
### POST /api/v1/projects
2026-05-23 12:19:53 +08:00
```json
{
"name": "我的像素游戏",
"style": {
"artStyle": "pixel",
"palette": "warm"
}
}
```
响应:
```json
{
"code": 0,
"message": "ok",
"data": {
"id": "proj_abc123",
"name": "我的像素游戏",
"style": { "kvPairs": { "artStyle": "pixel", "palette": "warm" } },
"createdAt": "2026-05-24T10:00:00Z",
"taskCount": 0,
"lastActivityAt": "2026-05-24T10:00:00Z"
}
2026-05-23 12:19:53 +08:00
}
```
### 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"
}
]
}
}
```
2026-05-23 12:19:53 +08:00
## 工程风格
2026-05-23 12:19:53 +08:00
需认证。风格归属于工程,仅工程所属用户可操作。
| 状态 | 方法 | 路径 | 说明 |
|------|------|------|------|
| [ ] | GET | `/api/v1/projects/:projectId/style` | 获取工程风格 |
| [ ] | PUT | `/api/v1/projects/:projectId/style` | 更新工程风格 |
2026-05-23 12:19:53 +08:00
请求体示例 (PUT /api/v1/projects/:projectId/style):
2026-05-23 12:19:53 +08:00
```json
{
"kvPairs": {
"artStyle": "pixel",
"palette": "warm",
"lineWeight": "thin",
"lighting": "bright"
2026-05-23 12:19:53 +08:00
}
}
```
## 素材生成
需认证。任务归属于当前用户的工程,跨用户访问返回 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":[...]}}
```