Files
gen2d/docs/database.md
T
wonder 5100a52bca fix: 七牛云私有 bucket 下载 URL 签名支持
对私有读 bucket,通过 HMAC-SHA1 签名生成带 e(过期时间)和 token(下载凭证)
参数的临时下载 URL,前端获取的素材 URL 和下载接口均返回签名 URL。

新增配置项 GEN2D_QINIU_URL_EXPIRE,默认 3600 秒。
2026-05-25 19:05:55 +08:00

228 lines
8.0 KiB
Markdown
Executable File
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 文件上传到七牛云 Bucket,通过 CDN URL 访问 |
| 认证 | 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 编码,兼顾可读性与唯一性。
---
## 文件存储
生成的素材文件上传到七牛云对象存储,通过 CDN URL 访问。
### 存储结构
对象 Key 命名规则(与原有本地路径保持一致):
```
users/{userId}/projects/{projectId}/tasks/{taskId}/output/{filename}
```
示例:
```
users/user_a1B2c3/projects/proj_abc/tasks/task_xyz/output/spritesheet.png
```
### 访问方式
素材上传后 asset 表存储对象 Key 和基础 CDN URL。由于 bucket 为私有读,前端通过 API 获取的 `url` 字段为带签名参数的临时下载链接(有效期可配置,默认 3600 秒)。
格式:`https://cdn.example.com/<key>?e=<deadline>&token=<downloadToken>`
下载接口 `GET /api/v1/assets/download?key=...` 返回 302 重定向到带签名的临时 URL。
### asset 表的 url 字段
存储七牛云基础 CDN URL(不含签名),如:
`https://cdn.example.com/users/user_a1B2c3/projects/proj_abc/tasks/task_xyz/output/spritesheet.png`
注意:该字段仅在数据库中使用,API 返回给前端的 URL 会根据对象 Key 实时生成带签名的临时下载链接。
---
## 去重策略
相同 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_JWT_SECRET` | — | JWT 签名密钥(必填) |
| `GEN2D_JWT_EXPIRE` | `7200` | Token 过期时间(秒) |
| `GEN2D_QINIU_ACCESS_KEY` | — | 七牛云 AccessKey |
| `GEN2D_QINIU_SECRET_KEY` | — | 七牛云 SecretKey |
| `GEN2D_QINIU_BUCKET` | — | 七牛云 Bucket 名称 |
| `GEN2D_QINIU_CDN_HOST` | — | CDN 域名(如 `https://cdn.example.com`) |
| `GEN2D_QINIU_USE_HTTPS` | `true` | 是否使用 HTTPS |
| `GEN2D_QINIU_URL_EXPIRE` | `3600` | 私有下载签名 URL 有效期(秒) |
在 `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)` | 任务下素材列表查询 |