# 数据存储 ## 选型 | 用途 | 方案 | 说明 | |------|------|------| | 持久化存储 | 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_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 与代码版本一致。 --- ## 索引说明 | 表 | 索引 | 用途 | |----|------|------| | `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)` | 任务下素材列表查询 |