Files
gen2d/docs/api.md
T
wonder cbcda82515 docs: 适配单机场景,简化存储与认证方案
- 数据库:MySQL → SQLite,去掉连接池配置和 golang-migrate
- 文件存储:OSS 对象存储 → 本地文件系统,统一 Gin 静态文件服务
- ID 生成:Sonyflake → 前缀+随机字符串
- 认证:Bearer Token → httpOnly Cookie,WebSocket 同步改为 Cookie 认证
- 质检重试:PromptBuilder 重试时注入 RejectReason 改进提示词
- 合并 multi-agent-pipeline.md 到 backend.md,消除文档重复
- 移除缓存管理 API(暴露内部实现细节)
2026-05-24 10:56:40 +08:00

504 lines
12 KiB
Markdown
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": {} }
```
## 认证
除健康检查和注册登录外,所有接口需通过 httpOnly Cookie 携带 JWT。登录/注册成功后,服务端通过 `Set-Cookie` 响应头写入 Token,后续请求自动携带。
Cookie 属性:
- `Name`: `token`
- `HttpOnly`: true
- `SameSite`: Lax
- `Path`: `/`
- `MaxAge`: 由 `GEN2D_JWT_EXPIRE` 控制(默认 7200 秒)
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` | 健康检查 |
## 用户认证
| 状态 | 方法 | 路径 | 说明 |
|------|------|------|------|
| [ ] | POST | `/api/v1/auth/register` | 用户注册 |
| [ ] | POST | `/api/v1/auth/login` | 用户登录 |
| [ ] | GET | `/api/v1/auth/me` | 获取当前用户信息 |
| [ ] | PUT | `/api/v1/auth/password` | 修改密码 |
### POST /api/v1/auth/register
```json
{
"username": "player1",
"email": "player1@example.com",
"password": "s3cretP@ss"
}
```
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `username` | string | 是 | 3-64 字符,字母数字下划线 |
| `email` | string | 是 | 邮箱地址 |
| `password` | string | 是 | 8-128 字符 |
响应(Token 通过 `Set-Cookie` 响应头写入,不在 body 中返回):
```json
{
"code": 0,
"message": "ok",
"data": {
"id": "user_a1B2c3D4",
"username": "player1",
"email": "player1@example.com"
}
}
```
错误响应:
| 场景 | code | message |
|------|------|---------|
| 用户名格式不合法(非 3-64 字符或含特殊字符) | 400 | 用户名格式不合法 |
| 密码长度不足 | 400 | 密码长度不能少于 8 位 |
| 邮箱格式不合法 | 400 | 邮箱格式不正确 |
| 用户名已存在 | 409 | 用户名已被注册 |
| 邮箱已存在 | 409 | 邮箱已被注册 |
### POST /api/v1/auth/login
```json
{
"username": "player1",
"password": "s3cretP@ss"
}
```
支持 `username` 或 `email` 登录:
```json
{
"email": "player1@example.com",
"password": "s3cretP@ss"
}
```
响应(Token 通过 `Set-Cookie` 响应头写入,不在 body 中返回):
```json
{
"code": 0,
"message": "ok",
"data": {
"id": "user_a1B2c3D4",
"username": "player1",
"email": "player1@example.com"
}
}
```
### 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 | 原密码不正确 |
## 工程管理
需认证。以下接口均需通过 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。
| 状态 | 方法 | 路径 | 说明 |
|------|------|------|------|
| [ ] | POST | `/api/v1/generate` | 提交生成任务,返回 taskId |
| [ ] | GET | `/api/v1/tasks/:taskId` | 查询任务状态与进度 |
| [ ] | GET | `/api/v1/tasks/:taskId/assets` | 获取生成结果(素材列表 + 元数据) |
| [ ] | WS | `/api/v1/tasks/:taskId/ws` | WebSocket 实时进度推送 |
### 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 | 是 | 用户原始文本,后端 PromptBuilder 负责三段式重写 |
| `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": [
{
"id": "asset_001",
"url": "users/user_a1B2c3D4/projects/proj_abc123/tasks/task_xyz789/output/spritesheet.png",
"format": "png",
"width": 256,
"height": 64,
"metadata": {
"frameWidth": 64,
"frameHeight": 64,
"frameCount": 4,
"directions": 1
}
}
]
}
}
```
### WebSocket 消息格式
连接路径:`ws://host/api/v1/tasks/:taskId/ws`(通过 httpOnly Cookie 自动携带认证信息)
```typescript
interface PipelineProgress {
stage: 'prompt_builder' | '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_builder","status":"running","progress":0}
{"stage":"prompt_builder","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_builder","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":[...]}}
```