Files
gen2d/docs/database.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

232 lines
7.9 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.
# 数据存储
## 选型
| 用途 | 方案 | 说明 |
|------|------|------|
| 持久化存储 | SQLite 3 | 工程、任务、素材元数据、风格配置全部落库,单文件部署 |
| 文件存储 | 本地文件系统 | 生成的图片与 spritesheet 文件保存到本地目录,通过 Gin 静态文件服务访问 |
| 认证 | JWT + Cookie | 简单注册登录,httpOnly Cookie 鉴权 |
| 缓存 / 去重 | 应用层内存 | 请求去重通过应用层 `sync.Map` 实现短期去重窗口,无需外部依赖 |
**选用 SQLite 的理由**:gen2d 是单实例部署的工具型应用,SQLite 零配置、单文件、性能足够,无需额外数据库服务。
---
## 表结构
### user — 用户
```sql
CREATE TABLE user (
id TEXT NOT NULL PRIMARY KEY, -- 用户 ID,如 user_a1B2c3
username TEXT NOT NULL,
email TEXT NOT NULL,
password_hash TEXT NOT NULL, -- bcrypt 哈希
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE UNIQUE INDEX uk_username ON user (username);
CREATE UNIQUE INDEX uk_email ON user (email);
```
### project — 工程
```sql
CREATE TABLE project (
id TEXT NOT NULL PRIMARY KEY, -- 项目 ID,如 proj_abc123
user_id TEXT NOT NULL,
name TEXT NOT NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES user (id) ON DELETE CASCADE
);
CREATE INDEX idx_project_user ON project (user_id, created_at);
```
### project_style — 工程风格
工程级键值对,保证同一工程下所有素材风格一致。每个工程恰好一行。
```sql
CREATE TABLE project_style (
id TEXT NOT NULL PRIMARY KEY,
project_id TEXT NOT NULL,
kv_pairs TEXT NOT NULL, -- JSON: {"artStyle":"pixel","palette":"warm",...}
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (project_id) REFERENCES project (id) ON DELETE CASCADE
);
CREATE UNIQUE INDEX uk_style_project ON project_style (project_id);
```
### task — 生成任务
```sql
CREATE TABLE task (
id TEXT NOT NULL PRIMARY KEY, -- 任务 ID,如 task_xyz789
project_id TEXT NOT NULL,
prompt TEXT NOT NULL, -- 用户原始文本
asset_type TEXT NOT NULL, -- sprite / background / ui / animation
task_style TEXT, -- JSON: 任务级风格覆盖,可为 NULL
params TEXT NOT NULL DEFAULT '{}', -- JSON: {"resolution":64,"frames":{...},"format":"spritesheet"}
status TEXT NOT NULL DEFAULT 'pending', -- pending / running / completed / failed
stage TEXT, -- 当前管线阶段
progress INTEGER NOT NULL DEFAULT 0, -- 0-100
retry_count INTEGER NOT NULL DEFAULT 0,
error TEXT, -- 失败原因
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (project_id) REFERENCES project (id) ON DELETE CASCADE
);
CREATE INDEX idx_task_project ON task (project_id, created_at);
CREATE INDEX idx_task_status ON task (status);
```
### asset — 生成素材
```sql
CREATE TABLE asset (
id TEXT NOT NULL PRIMARY KEY, -- 素材 ID,如 asset_001
task_id TEXT NOT NULL,
url TEXT NOT NULL, -- 相对路径,如 tasks/task_xyz/output/spritesheet.png
format TEXT NOT NULL DEFAULT 'png', -- png / json
width INTEGER NOT NULL DEFAULT 0,
height INTEGER NOT NULL DEFAULT 0,
metadata TEXT, -- JSON: {"frameWidth":64,"frameHeight":64,"frameCount":4,"directions":1}
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (task_id) REFERENCES task (id) ON DELETE CASCADE
);
CREATE INDEX idx_asset_task ON asset (task_id);
```
---
## ER 关系
```
user 1 ── N project
project 1 ── 1 project_style
project 1 ── N task
task 1 ── N asset
```
对应 [后端工程](backend.md) 中的关键实体定义,所有一对多关系通过外键 + CASCADE 删除维护。
---
## ID 生成
使用前缀 + 随机字符串生成 ID,格式为 `{prefix}_{random}`:
| 实体 | 前缀 | 示例 |
|------|------|------|
| user | `user` | `user_a1B2c3` |
| project | `proj` | `proj_3kF9a2` |
| project_style | `sty` | `sty_7xM2pQ` |
| task | `task` | `task_9bN4vR` |
| asset | `asset` | `asset_2wK6tY` |
随机部分使用 `crypto/rand` 生成 6 字节 base62 编码,兼顾可读性与唯一性。
---
## 文件存储
生成的素材文件保存到本地文件系统,通过 Gin 静态文件服务访问。
### 目录结构
```
{dataDir}/
└── users/
└── {userId}/
└── projects/
└── {projectId}/
└── tasks/
└── {taskId}/
├── raw/ # AssetGenerator 输出的原始图片
│ ├── 0.png
│ ├── 1.png
│ └── ...
├── output/ # FormatAdapter 输出的最终素材
│ ├── spritesheet.png
│ └── metadata.json
└── pipeline.log # 管线执行日志
```
`dataDir` 通过环境变量 `GEN2D_DATA_DIR` 配置,默认 `./data`。
### 访问方式
Gin 静态文件服务,路由 `/files/*` 映射到 `{dataDir}/` 目录:
```go
router.Static("/files", dataDir)
```
### asset 表的 url 字段
存储相对路径,前端通过 `/files/{url}` 拼接访问:
- 如 `users/user_a1B2c3/projects/proj_abc/tasks/task_xyz/output/spritesheet.png`
- 前端拼接为 `/files/users/user_a1B2c3/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_PATH` | `./data/gen2d.db` | SQLite 数据库文件路径 |
| `GEN2D_DATA_DIR` | `./data` | 本地文件存储根目录 |
| `GEN2D_JWT_SECRET` | — | JWT 签名密钥(必填) |
| `GEN2D_JWT_EXPIRE` | `7200` | Token 过期时间(秒) |
在 `config.go` 中扩展字段即可,无需额外依赖。
---
## 迁移
应用启动时自动执行建表 SQL,保证 schema 与代码版本一致:
```go
// 启动时执行
db.Exec(createUserTableSQL)
db.Exec(createProjectTableSQL)
db.Exec(createTaskTableSQL)
db.Exec(createAssetTableSQL)
```
使用 `CREATE TABLE IF NOT EXISTS` 保证幂等性。
---
## 索引说明
| 表 | 索引 | 用途 |
|----|------|------|
| `user` | `uk_username (username)` | 用户名唯一约束 |
| `user` | `uk_email (email)` | 邮箱唯一约束 |
| `project` | `idx_project_user (user_id, created_at)` | 用户下工程列表分页查询 |
| `project_style` | `uk_style_project (project_id)` | 一个工程一个风格,唯一约束 |
| `task` | `idx_task_project (project_id, created_at)` | 工程下任务列表分页查询 |
| `task` | `idx_task_status (status)` | 按状态筛选任务 |
| `asset` | `idx_asset_task (task_id)` | 任务下素材列表查询 |