Files
gen2d/docs/database.md
T
wonder f4c1a10403 docs: 交叉检查修复文档疏漏与不一致
- 修复 api.md 修改密码接口混入注册场景的 409 错误
- 补充 api.md WebSocket 示例缺失的 quality_supervisor running 消息
- 补充 api.md 注册接口格式校验错误响应、缓存接口详细说明
- 补充 frontend.md Task 接口缺失的 error 字段
- 补充 frontend.md 错误处理章节(API 错误、WebSocket 断连、加载状态)
- frontend.md 风格键表改为引用 style-keys.md,消除重复
- async-tasks.md 去重/文件存储改为引用 database.md,消除重复
- backend.md ER 图改为引用 database.md,消除重复
- 补充 backend.md 目录结构中缺失的 auth/project handler 和 user model
- 补充 backend.md 缓存层设计说明
- 补充 database.md GEN2D_SERVER_PORT 环境变量和迁移文件归属说明
- 补充 multi-agent-pipeline.md 到 backend.md 和 async-tasks.md 的交叉引用
- 补充 async-tasks.md 超时控制说明
2026-05-24 10:47:00 +08:00

243 lines
9.7 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.
# 数据存储
## 选型
| 用途 | 方案 | 说明 |
|------|------|------|
| 持久化存储 | MySQL 8.0+ | 工程、任务、素材元数据、风格配置全部落库 |
| 文件存储 | OSS 对象存储 | 生成的图片与 spritesheet 文件上传至 S3 兼容存储桶,通过 CDN URL 访问 |
| 认证 | JWT | 简单注册登录,Bearer Token 鉴权 |
| 缓存 / 去重 | MySQL + 应用层内存 | 请求去重通过数据库唯一约束 + 应用层 sync.Map 实现短期去重窗口,不引入 Redis |
**不用 Redis 的理由**:gen2d 是单实例部署的工具型应用,并发量有限,任务状态轮询即可满足实时性需求。MySQL 完全能覆盖缓存、去重、队列语义,引入 Redis 增加运维复杂度但收益不大。
---
## 表结构
### user — 用户
```sql
CREATE TABLE `user` (
`id` CHAR(20) NOT NULL, -- 用户 ID,如 user_a1B2c3
`username` VARCHAR(64) NOT NULL, -- 登录用户名
`email` VARCHAR(128) NOT NULL, -- 邮箱
`password_hash` VARCHAR(128) NOT NULL, -- bcrypt 哈希
`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_username` (`username`),
UNIQUE KEY `uk_email` (`email`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
```
### project — 工程
```sql
CREATE TABLE `project` (
`id` CHAR(20) NOT NULL, -- 项目 ID,如 proj_abc123
`user_id` CHAR(20) NOT NULL, -- 所属用户
`name` VARCHAR(128) NOT NULL, -- 工程名称
`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `idx_user` (`user_id`, `created_at`),
CONSTRAINT `fk_project_user` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
```
### project_style — 工程风格
工程级键值对,保证同一工程下所有素材风格一致。每个工程恰好一行。
```sql
CREATE TABLE `project_style` (
`id` CHAR(20) NOT NULL,
`project_id` CHAR(20) NOT NULL,
`kv_pairs` JSON NOT NULL, -- {"artStyle":"pixel","palette":"warm",...}
`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_project` (`project_id`),
CONSTRAINT `fk_style_project` FOREIGN KEY (`project_id`) REFERENCES `project` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
```
### task — 生成任务
```sql
CREATE TABLE `task` (
`id` CHAR(20) NOT NULL, -- 任务 ID,如 task_xyz789
`project_id` CHAR(20) NOT NULL,
`prompt` TEXT NOT NULL, -- 用户原始文本
`asset_type` VARCHAR(32) NOT NULL, -- sprite / background / ui / animation
`task_style` JSON NULL, -- 任务级风格覆盖,可为 NULL
`params` JSON NOT NULL DEFAULT '{}', -- {"resolution":64,"frames":{...},"format":"spritesheet"}
`status` VARCHAR(16) NOT NULL DEFAULT 'pending', -- pending / running / completed / failed
`stage` VARCHAR(32) NULL, -- 当前管线阶段:prompt_builder / asset_generator / quality_supervisor / format_adapter
`progress` TINYINT UNSIGNED NOT NULL DEFAULT 0, -- 0-100
`retry_count` TINYINT UNSIGNED NOT NULL DEFAULT 0,
`error` TEXT NULL, -- 失败原因
`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `idx_project` (`project_id`, `created_at`),
KEY `idx_status` (`status`),
CONSTRAINT `fk_task_project` FOREIGN KEY (`project_id`) REFERENCES `project` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
```
### asset — 生成素材
```sql
CREATE TABLE `asset` (
`id` CHAR(20) NOT NULL, -- 素材 ID,如 asset_001
`task_id` CHAR(20) NOT NULL,
`url` VARCHAR(512) NOT NULL, -- OSS 完整 URL 或相对路径
`format` VARCHAR(16) NOT NULL DEFAULT 'png', -- png / json
`width` INT NOT NULL DEFAULT 0,
`height` INT NOT NULL DEFAULT 0,
`metadata` JSON NULL, -- {"frameWidth":64,"frameHeight":64,"frameCount":4,"directions":1}
`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `idx_task` (`task_id`),
CONSTRAINT `fk_asset_task` FOREIGN KEY (`task_id`) REFERENCES `task` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
```
---
## ER 关系
```
user 1 ── N project
project 1 ── 1 project_style
project 1 ── N task
task 1 ── N asset
```
对应 [后端工程](backend.md) 中的关键实体定义,所有一对多关系通过外键 + CASCADE 删除维护。
---
## ID 生成
使用 **Sonyflake**(或类似分布式 ID 方案)生成 20 字符的字符串 ID,格式为 `{前缀}_{base62}`:
| 实体 | 前缀 | 示例 |
|------|------|------|
| user | `user` | `user_a1B2c3D4` |
| project | `proj` | `proj_3kF9a2Bc` |
| project_style | `sty` | `sty_7xM2pQ1d` |
| task | `task` | `task_9bN4vR8e` |
| asset | `asset` | `asset_2wK6tY5f` |
---
## 文件存储
使用 S3 兼容对象存储(如阿里云 OSS、MinIO),用户生成的素材持久化保存,便于重复利用。
### OSS Key 结构
```
{bucket}/
└── users/
└── {userId}/
└── projects/
└── {projectId}/
└── tasks/
└── {taskId}/
├── raw/ # AssetGenerator 输出的原始图片
│ ├── 0.png
│ ├── 1.png
│ └── ...
├── output/ # FormatAdapter 输出的最终素材
│ ├── spritesheet.png
│ └── metadata.json
└── pipeline.log # 管线执行日志
```
### 访问方式
- **开发环境**:Gin 静态文件服务,路由 `/files/*` 映射到本地 `data/` 目录,无需 OSS。
- **生产环境**:文件上传至 OSS 存储桶,`asset.url` 存储完整 CDN URL,前端直接访问。
### asset 表的 url 字段
- 开发环境:相对路径,如 `projects/proj_abc/tasks/task_xyz/output/spritesheet.png`,前端通过 `/files/{url}` 拼接。
- 生产环境:完整 URL,如 `https://cdn.example.com/users/user_xxx/projects/proj_abc/tasks/task_xyz/output/spritesheet.png`。
---
## 去重策略
相同 prompt + assetType + params 的并发请求,通过应用层去重避免重复生成:
1. 请求到达时,计算 `hash(prompt + assetType + params)` 作为去重键。
2. 应用内存(`sync.Map`)中查找该键:
- 若存在且任务仍在运行中,直接返回已有 taskId。
- 若不存在,写入内存并提交任务。
3. 任务完成或失败后,从内存中清除(可设置 TTL 过期兜底)。
不依赖数据库唯一约束做去重,因为相同输入在不同时间点应该允许重新生成。
---
## 配置
数据库连接信息通过环境变量注入:
| 环境变量 | 默认值 | 说明 |
|----------|--------|------|
| `GEN2D_SERVER_PORT` | `8080` | HTTP 服务监听端口 |
| `GEN2D_DB_DSN` | `root:@tcp(127.0.0.1:3306)/gen2d?charset=utf8mb4&parseTime=True&loc=Local` | MySQL DSN |
| `GEN2D_DB_MAX_OPEN` | `25` | 最大连接数 |
| `GEN2D_DB_MAX_IDLE` | `5` | 最大空闲连接数 |
| `GEN2D_DB_MAX_LIFETIME` | `300` | 连接最大存活时间(秒) |
| `GEN2D_JWT_SECRET` | — | JWT 签名密钥(必填) |
| `GEN2D_JWT_EXPIRE` | `7200` | Token 过期时间(秒) |
| `GEN2D_OSS_ENDPOINT` | — | OSS Endpoint(生产环境必填) |
| `GEN2D_OSS_BUCKET` | — | 存储桶名称 |
| `GEN2D_OSS_ACCESS_KEY` | — | Access Key ID |
| `GEN2D_OSS_SECRET_KEY` | — | Access Key Secret |
| `GEN2D_OSS_CDN_DOMAIN` | — | CDN 域名(可选,用于拼接公开访问 URL) |
在 `config.go` 中扩展字段即可,无需额外依赖。
---
## 迁移
使用 [golang-migrate](https://github.com/golang-migrate/migrate) 管理 schema 版本:
```
backend/internal/migrations/
├── 000001_init_user.up.sql
├── 000001_init_user.down.sql
├── 000002_init_project.up.sql
├── 000002_init_project.down.sql
├── 000003_init_task.up.sql
├── 000003_init_task.down.sql
├── 000004_init_asset.up.sql
└── 000004_init_asset.down.sql
```
启动时自动执行 `migrate.Up()`,保证 schema 与代码版本一致。
> 注:`project_style` 表的创建包含在 `000002_init_project.up.sql` 中,与 `project` 表同批迁移。
---
## 索引说明
| 表 | 索引 | 用途 |
|----|------|------|
| `user` | `uk_username (username)` | 用户名唯一约束 |
| `user` | `uk_email (email)` | 邮箱唯一约束 |
| `project` | `idx_user (user_id, created_at)` | 用户下工程列表分页查询 |
| `project_style` | `uk_project (project_id)` | 一个工程一个风格,唯一约束 |
| `task` | `idx_project (project_id, created_at)` | 工程下任务列表分页查询 |
| `task` | `idx_status (status)` | 按状态筛选任务(如查找所有 pending 任务) |
| `asset` | `idx_task (task_id)` | 任务下素材列表查询 |