2026-05-23 18:49:50 +08:00
|
|
|
|
# 数据存储
|
|
|
|
|
|
|
2026-05-24 10:38:31 +08:00
|
|
|
|
## 选型
|
|
|
|
|
|
|
|
|
|
|
|
| 用途 | 方案 | 说明 |
|
|
|
|
|
|
|------|------|------|
|
2026-05-24 10:56:40 +08:00
|
|
|
|
| 持久化存储 | SQLite 3 | 工程、任务、素材元数据、风格配置全部落库,单文件部署 |
|
2026-05-25 12:57:41 +08:00
|
|
|
|
| 文件存储 | 七牛云对象存储 | 生成的图片与 spritesheet 文件上传到七牛云 Bucket,通过 CDN URL 访问 |
|
2026-05-24 10:56:40 +08:00
|
|
|
|
| 认证 | JWT + Cookie | 简单注册登录,httpOnly Cookie 鉴权 |
|
|
|
|
|
|
| 缓存 / 去重 | 应用层内存 | 请求去重通过应用层 `sync.Map` 实现短期去重窗口,无需外部依赖 |
|
2026-05-24 10:38:31 +08:00
|
|
|
|
|
2026-05-24 10:56:40 +08:00
|
|
|
|
**选用 SQLite 的理由**:gen2d 是单实例部署的工具型应用,SQLite 零配置、单文件、性能足够,无需额外数据库服务。
|
2026-05-24 10:38:31 +08:00
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 表结构
|
|
|
|
|
|
|
|
|
|
|
|
### user — 用户
|
|
|
|
|
|
|
|
|
|
|
|
```sql
|
2026-05-24 10:56:40 +08:00
|
|
|
|
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);
|
2026-05-24 10:38:31 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### project — 工程
|
|
|
|
|
|
|
|
|
|
|
|
```sql
|
2026-05-24 10:56:40 +08:00
|
|
|
|
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);
|
2026-05-24 10:38:31 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### project_style — 工程风格
|
|
|
|
|
|
|
|
|
|
|
|
工程级键值对,保证同一工程下所有素材风格一致。每个工程恰好一行。
|
|
|
|
|
|
|
|
|
|
|
|
```sql
|
2026-05-24 10:56:40 +08:00
|
|
|
|
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);
|
2026-05-24 10:38:31 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### task — 生成任务
|
|
|
|
|
|
|
|
|
|
|
|
```sql
|
2026-05-24 10:56:40 +08:00
|
|
|
|
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);
|
2026-05-24 10:38:31 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### asset — 生成素材
|
|
|
|
|
|
|
|
|
|
|
|
```sql
|
2026-05-24 10:56:40 +08:00
|
|
|
|
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);
|
2026-05-24 10:38:31 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## ER 关系
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
user 1 ── N project
|
|
|
|
|
|
project 1 ── 1 project_style
|
|
|
|
|
|
project 1 ── N task
|
|
|
|
|
|
task 1 ── N asset
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
对应 [后端工程](backend.md) 中的关键实体定义,所有一对多关系通过外键 + CASCADE 删除维护。
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## ID 生成
|
|
|
|
|
|
|
2026-05-24 10:56:40 +08:00
|
|
|
|
使用前缀 + 随机字符串生成 ID,格式为 `{prefix}_{random}`:
|
2026-05-24 10:38:31 +08:00
|
|
|
|
|
|
|
|
|
|
| 实体 | 前缀 | 示例 |
|
|
|
|
|
|
|------|------|------|
|
2026-05-24 10:56:40 +08:00
|
|
|
|
| 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 编码,兼顾可读性与唯一性。
|
2026-05-24 10:38:31 +08:00
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 文件存储
|
|
|
|
|
|
|
2026-05-25 12:57:41 +08:00
|
|
|
|
生成的素材文件上传到七牛云对象存储,通过 CDN URL 访问。
|
2026-05-24 10:38:31 +08:00
|
|
|
|
|
2026-05-25 12:57:41 +08:00
|
|
|
|
### 存储结构
|
|
|
|
|
|
|
|
|
|
|
|
对象 Key 命名规则(与原有本地路径保持一致):
|
2026-05-24 10:38:31 +08:00
|
|
|
|
|
|
|
|
|
|
```
|
2026-05-25 12:57:41 +08:00
|
|
|
|
users/{userId}/projects/{projectId}/tasks/{taskId}/output/{filename}
|
2026-05-24 10:38:31 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
2026-05-25 12:57:41 +08:00
|
|
|
|
示例:
|
|
|
|
|
|
```
|
|
|
|
|
|
users/user_a1B2c3/projects/proj_abc/tasks/task_xyz/output/spritesheet.png
|
|
|
|
|
|
```
|
2026-05-24 10:56:40 +08:00
|
|
|
|
|
2026-05-24 10:38:31 +08:00
|
|
|
|
### 访问方式
|
|
|
|
|
|
|
2026-05-25 12:57:41 +08:00
|
|
|
|
素材上传后返回 CDN URL,前端直接通过该 URL 访问图片。
|
2026-05-24 10:56:40 +08:00
|
|
|
|
|
2026-05-25 12:57:41 +08:00
|
|
|
|
下载接口 `GET /api/v1/assets/download?key=...` 返回 302 重定向到 CDN URL。
|
2026-05-24 10:38:31 +08:00
|
|
|
|
|
|
|
|
|
|
### asset 表的 url 字段
|
|
|
|
|
|
|
2026-05-25 12:57:41 +08:00
|
|
|
|
存储七牛云 CDN 完整 URL,如:
|
|
|
|
|
|
`https://cdn.example.com/users/user_a1B2c3/projects/proj_abc/tasks/task_xyz/output/spritesheet.png`
|
2026-05-24 10:56:40 +08:00
|
|
|
|
|
2026-05-25 12:57:41 +08:00
|
|
|
|
前端直接使用该 URL 加载图片,无需拼接路径。
|
2026-05-24 10:38:31 +08:00
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 去重策略
|
|
|
|
|
|
|
|
|
|
|
|
相同 prompt + assetType + params 的并发请求,通过应用层去重避免重复生成:
|
|
|
|
|
|
|
|
|
|
|
|
1. 请求到达时,计算 `hash(prompt + assetType + params)` 作为去重键。
|
|
|
|
|
|
2. 应用内存(`sync.Map`)中查找该键:
|
|
|
|
|
|
- 若存在且任务仍在运行中,直接返回已有 taskId。
|
|
|
|
|
|
- 若不存在,写入内存并提交任务。
|
|
|
|
|
|
3. 任务完成或失败后,从内存中清除(可设置 TTL 过期兜底)。
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 配置
|
|
|
|
|
|
|
|
|
|
|
|
数据库连接信息通过环境变量注入:
|
|
|
|
|
|
|
|
|
|
|
|
| 环境变量 | 默认值 | 说明 |
|
|
|
|
|
|
|----------|--------|------|
|
2026-05-24 10:47:00 +08:00
|
|
|
|
| `GEN2D_SERVER_PORT` | `8080` | HTTP 服务监听端口 |
|
2026-05-24 10:56:40 +08:00
|
|
|
|
| `GEN2D_DB_PATH` | `./data/gen2d.db` | SQLite 数据库文件路径 |
|
2026-05-24 10:38:31 +08:00
|
|
|
|
| `GEN2D_JWT_SECRET` | — | JWT 签名密钥(必填) |
|
|
|
|
|
|
| `GEN2D_JWT_EXPIRE` | `7200` | Token 过期时间(秒) |
|
2026-05-25 12:57:41 +08:00
|
|
|
|
| `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 |
|
2026-05-24 10:38:31 +08:00
|
|
|
|
|
|
|
|
|
|
在 `config.go` 中扩展字段即可,无需额外依赖。
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 迁移
|
|
|
|
|
|
|
2026-05-24 10:56:40 +08:00
|
|
|
|
应用启动时自动执行建表 SQL,保证 schema 与代码版本一致:
|
2026-05-24 10:38:31 +08:00
|
|
|
|
|
2026-05-24 10:56:40 +08:00
|
|
|
|
```go
|
|
|
|
|
|
// 启动时执行
|
|
|
|
|
|
db.Exec(createUserTableSQL)
|
|
|
|
|
|
db.Exec(createProjectTableSQL)
|
|
|
|
|
|
db.Exec(createTaskTableSQL)
|
|
|
|
|
|
db.Exec(createAssetTableSQL)
|
2026-05-24 10:38:31 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
2026-05-24 10:56:40 +08:00
|
|
|
|
使用 `CREATE TABLE IF NOT EXISTS` 保证幂等性。
|
2026-05-24 10:47:00 +08:00
|
|
|
|
|
2026-05-24 10:38:31 +08:00
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 索引说明
|
|
|
|
|
|
|
|
|
|
|
|
| 表 | 索引 | 用途 |
|
|
|
|
|
|
|----|------|------|
|
|
|
|
|
|
| `user` | `uk_username (username)` | 用户名唯一约束 |
|
|
|
|
|
|
| `user` | `uk_email (email)` | 邮箱唯一约束 |
|
2026-05-24 10:56:40 +08:00
|
|
|
|
| `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)` | 任务下素材列表查询 |
|