Skip to content

数据库设计

设计哲学

单文件 SQLite + 启动时迁移 + 行级事务 —— 简单到极致,可靠到极致。

概览

数据库共 13 张主表,按职责分为 5 个域

职责域表数量主要表
🔐 认证与权限2users, sessions
💰 配额与计量2quotas, usage_logs
⚙️ 供应商配置2api_providers, model_configs
🎟️ 激活码体系2activation_keys, activation_key_redemptions
🧠 GraphRAG2kg_entities, kg_relations
📜 请求审计1request_logs
🔧 系统2migrations, kv_store

ER 图

海域智舱数据库 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 后规模
users0(人工创建)10-100 行
usage_logs11 行(一键全案)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;"

下一步

基于 MIT 协议开源 · 中国大学生计算机设计大赛软件应用与开发类作品