f4c1a10403
- 修复 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 超时控制说明
9.7 KiB
9.7 KiB
数据存储
选型
| 用途 | 方案 | 说明 |
|---|---|---|
| 持久化存储 | MySQL 8.0+ | 工程、任务、素材元数据、风格配置全部落库 |
| 文件存储 | OSS 对象存储 | 生成的图片与 spritesheet 文件上传至 S3 兼容存储桶,通过 CDN URL 访问 |
| 认证 | JWT | 简单注册登录,Bearer Token 鉴权 |
| 缓存 / 去重 | MySQL + 应用层内存 | 请求去重通过数据库唯一约束 + 应用层 sync.Map 实现短期去重窗口,不引入 Redis |
不用 Redis 的理由:gen2d 是单实例部署的工具型应用,并发量有限,任务状态轮询即可满足实时性需求。MySQL 完全能覆盖缓存、去重、队列语义,引入 Redis 增加运维复杂度但收益不大。
表结构
user — 用户
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 — 工程
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 — 工程风格
工程级键值对,保证同一工程下所有素材风格一致。每个工程恰好一行。
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 — 生成任务
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 — 生成素材
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
对应 后端工程 中的关键实体定义,所有一对多关系通过外键 + 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 的并发请求,通过应用层去重避免重复生成:
- 请求到达时,计算
hash(prompt + assetType + params)作为去重键。 - 应用内存(
sync.Map)中查找该键:- 若存在且任务仍在运行中,直接返回已有 taskId。
- 若不存在,写入内存并提交任务。
- 任务完成或失败后,从内存中清除(可设置 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 管理 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) |
任务下素材列表查询 |