数据库设计
设计哲学
单文件 SQLite + 启动时迁移 + 行级事务 —— 简单到极致,可靠到极致。
概览
数据库共 13 张主表,按职责分为 5 个域:
| 职责域 | 表数量 | 主要表 |
|---|---|---|
| 🔐 认证与权限 | 2 | users, sessions |
| 💰 配额与计量 | 2 | quotas, usage_logs |
| ⚙️ 供应商配置 | 2 | api_providers, model_configs |
| 🎟️ 激活码体系 | 2 | activation_keys, activation_key_redemptions |
| 🧠 GraphRAG | 2 | kg_entities, kg_relations |
| 📜 请求审计 | 1 | request_logs |
| 🔧 系统 | 2 | migrations, kv_store |
ER 图

数据库整体按 5 个职责域 组织(认证与权限 / 配额计量 / 供应商配置 / 激活码体系 / GraphRAG 与审计),各表通过外键 + 唯一约束建立强一致性。
表结构详细
1. users(用户)
sql
CREATE TABLE users (
id TEXT PRIMARY KEY,
username TEXT UNIQUE NOT NULL,
password_hash TEXT NOT NULL, -- bcrypt
role TEXT NOT NULL DEFAULT 'user', -- 'admin' / 'user'
status TEXT NOT NULL DEFAULT 'active', -- 'active' / 'disabled'
created_at TEXT NOT NULL DEFAULT (datetime('now')),
last_login_at TEXT
);
CREATE INDEX idx_users_username ON users(username);2. sessions(会话)
sql
CREATE TABLE sessions (
id TEXT PRIMARY KEY,
token_hash TEXT UNIQUE NOT NULL, -- SHA-256,明文 token 仅存 Cookie
user_id TEXT NOT NULL,
expires_at TEXT NOT NULL,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
last_seen_at TEXT,
user_agent TEXT,
ip TEXT,
FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE
);
CREATE INDEX idx_sessions_token ON sessions(token_hash);
CREATE INDEX idx_sessions_user ON sessions(user_id, expires_at);3. quotas(配额)
sql
CREATE TABLE quotas (
user_id TEXT PRIMARY KEY,
image_total INTEGER NOT NULL DEFAULT 0,
image_used INTEGER NOT NULL DEFAULT 0,
copy_total INTEGER NOT NULL DEFAULT 0,
copy_used INTEGER NOT NULL DEFAULT 0,
video_total INTEGER NOT NULL DEFAULT 0,
video_used INTEGER NOT NULL DEFAULT 0,
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
CHECK(image_used >= 0 AND image_used <= image_total),
CHECK(copy_used >= 0 AND copy_used <= copy_total),
CHECK(video_used >= 0 AND video_used <= video_total),
FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE
);4. usage_logs(使用日志)
sql
CREATE TABLE usage_logs (
id TEXT PRIMARY KEY, -- ticket UUID
user_id TEXT NOT NULL,
quota_type TEXT NOT NULL, -- 'image' / 'copy' / 'video'
amount INTEGER NOT NULL DEFAULT 1,
status TEXT NOT NULL, -- 'pending' / 'success' / 'failed'
model TEXT,
provider TEXT,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
completed_at TEXT,
FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE
);
CREATE INDEX idx_usage_user ON usage_logs(user_id, created_at);
CREATE INDEX idx_usage_status ON usage_logs(status, created_at);关键索引
idx_usage_status (status, created_at) 是 reaper goroutine 高频扫描的核心索引:
sql
SELECT * FROM usage_logs
WHERE status = 'pending'
AND created_at < datetime('now', '-30 seconds');5. api_providers(图片直连供应商)
sql
CREATE TABLE api_providers (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
type TEXT NOT NULL, -- 'gemini' / 'openai'
base_url TEXT NOT NULL,
api_key TEXT NOT NULL, -- 生产环境建议加密
priority INTEGER NOT NULL DEFAULT 1,
weight INTEGER NOT NULL DEFAULT 100,
model_aliases TEXT, -- JSON
status TEXT NOT NULL DEFAULT 'active',
created_at TEXT NOT NULL DEFAULT (datetime('now')),
updated_at TEXT
);
CREATE INDEX idx_providers_active ON api_providers(status, priority, weight);6. model_configs(后端代理模型配置)
sql
CREATE TABLE model_configs (
id TEXT PRIMARY KEY, -- 'copy' / 'video' / 'product_analysis'
provider_id TEXT NOT NULL,
model TEXT NOT NULL,
config TEXT NOT NULL, -- JSON: max_tokens, temperature, ...
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
FOREIGN KEY(provider_id) REFERENCES api_providers(id)
);7. activation_keys(激活码)
sql
CREATE TABLE activation_keys (
id TEXT PRIMARY KEY,
code TEXT UNIQUE NOT NULL, -- HYZC-2026-IMG50-CPY30-VID10-A8D2
image_quota INTEGER NOT NULL DEFAULT 0,
copy_quota INTEGER NOT NULL DEFAULT 0,
video_quota INTEGER NOT NULL DEFAULT 0,
max_uses INTEGER NOT NULL DEFAULT 1,
used_count INTEGER NOT NULL DEFAULT 0,
expires_at TEXT, -- NULL = 永不过期
status TEXT NOT NULL DEFAULT 'active',
created_by TEXT NOT NULL,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
CHECK(used_count <= max_uses),
FOREIGN KEY(created_by) REFERENCES users(id)
);
CREATE INDEX idx_keys_code ON activation_keys(code);8. activation_key_redemptions(兑换记录)
sql
CREATE TABLE activation_key_redemptions (
id TEXT PRIMARY KEY,
key_id TEXT NOT NULL,
user_id TEXT NOT NULL,
redeemed_at TEXT NOT NULL DEFAULT (datetime('now')),
UNIQUE(key_id, user_id), -- 防重复兑换
FOREIGN KEY(key_id) REFERENCES activation_keys(id),
FOREIGN KEY(user_id) REFERENCES users(id)
);9. kg_entities(知识图谱实体)
sql
CREATE TABLE kg_entities (
id TEXT PRIMARY KEY, -- ent_xxx
product_id TEXT NOT NULL, -- 关联商品
type TEXT NOT NULL, -- Material / Spec / UseCase / ...
name TEXT NOT NULL,
attributes TEXT, -- JSON 扩展属性
embedding BLOB, -- 1024 维浮点向量
created_at TEXT NOT NULL DEFAULT (datetime('now'))
);
CREATE INDEX idx_kg_entities_product ON kg_entities(product_id);
CREATE INDEX idx_kg_entities_type ON kg_entities(type);10. kg_relations(语义关系)
sql
CREATE TABLE kg_relations (
id TEXT PRIMARY KEY,
source_id TEXT NOT NULL,
target_id TEXT NOT NULL,
rel_type TEXT NOT NULL, -- HAS_SPEC / MADE_OF / SUITABLE_FOR / HIGHLIGHTS / COMPLIES_WITH
weight REAL NOT NULL DEFAULT 1.0,
evidence TEXT, -- 抽取证据片段
FOREIGN KEY(source_id) REFERENCES kg_entities(id),
FOREIGN KEY(target_id) REFERENCES kg_entities(id)
);
CREATE INDEX idx_kg_rel_source ON kg_relations(source_id, rel_type);
CREATE INDEX idx_kg_rel_target ON kg_relations(target_id, rel_type);11. request_logs(请求日志)
sql
CREATE TABLE request_logs (
id TEXT PRIMARY KEY,
user_id TEXT,
username TEXT,
route TEXT NOT NULL,
method TEXT NOT NULL,
model TEXT,
provider TEXT,
status_code INTEGER,
latency_ms INTEGER,
ip TEXT,
user_agent TEXT,
error TEXT,
created_at TEXT NOT NULL DEFAULT (datetime('now'))
);
CREATE INDEX idx_logs_user ON request_logs(user_id, created_at);
CREATE INDEX idx_logs_route ON request_logs(route, created_at);
CREATE INDEX idx_logs_error ON request_logs(status_code, created_at) WHERE status_code >= 400;12. migrations(迁移记录)
sql
CREATE TABLE migrations (
id TEXT PRIMARY KEY, -- 0001_init_users.sql
applied_at TEXT NOT NULL DEFAULT (datetime('now'))
);13. kv_store(系统配置)
sql
CREATE TABLE kv_store (
key TEXT PRIMARY KEY,
value TEXT NOT NULL,
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
);关键设计决策
决策 1:会话表只存 token_hash
安全性
- 明文 token 仅存浏览器 HttpOnly Cookie
- 服务端只存 SHA-256 hash
- 即使数据库泄露,攻击者也无法获取有效 Session
决策 2:配额表与日志表分离
| 表 | 职责 | 写入频率 |
|---|---|---|
quotas | 当前剩余 | 每次扣减 / 充值 |
usage_logs | 历史明细 | 每次操作 + 状态变更 |
好处:
- 配额查询(高频)只读 1 行
- 日志可单独归档 / 清理
- 失败回滚不影响日志完整性
决策 3:api_providers 与 model_configs 分离
| 表 | 适用 | 调用方式 |
|---|---|---|
api_providers | 图片生成 | 浏览器直连 |
model_configs | 文案 / 视频 / 选品 | 后端代理 |
解耦了通用图片接口和特定厂商接口的差异。
决策 4:GraphRAG 用关系表 + 向量索引
不引入 Neo4j / Pinecone 等额外依赖:
sql
-- 子图召回 SQL(仅需 SQLite 即可)
SELECT t.*
FROM kg_relations r
JOIN kg_entities t ON t.id = r.target_id
WHERE r.source_id = :product_id
AND r.rel_type IN ('HAS_SPEC', 'HIGHLIGHTS', 'COMPLIES_WITH')
ORDER BY r.weight DESC;向量相似度计算在 Go 层做:
go
func cosineSimilarity(a, b []float32) float32 {
// SQLite BLOB → []float32 → 余弦相似度
}SQLite WAL 模式
sql
PRAGMA journal_mode=WAL; -- Write-Ahead Logging
PRAGMA synchronous=NORMAL; -- 性能与可靠性平衡
PRAGMA busy_timeout=5000; -- 5s 锁等待启用后读写并发良好,单机 100+ 并发无压力。
数据规模估算
| 表 | 单 SKU 增量 | 1000 SKU 后规模 |
|---|---|---|
| users | 0(人工创建) | 10-100 行 |
| usage_logs | 11 行(一键全案) | 11 万行 |
| kg_entities | ≈ 20 行 | 2 万行 |
| kg_relations | ≈ 18 行 | 1.8 万行 |
| request_logs | ≈ 15 行 | 1.5 万行 |
| 总规模 | - | < 100MB SQLite 文件 |
备份与恢复
bash
# 备份(单文件 cp)
cp backend/data/app.db backup/app-$(date +%Y%m%d).db
# 在线热备份(SQLite 内置)
sqlite3 backend/data/app.db ".backup backup/app.db"
# 恢复
cp backup/app-20260501.db backend/data/app.db
# 完整性检查
sqlite3 backend/data/app.db "PRAGMA integrity_check;"下一步
- 🔌 API 接口 —— 完整 API 列表与示例
- 🧠 GraphRAG 存储 —— 知识图谱存储深入
- 🛡️ 管理后台 —— 表的实际使用场景