Skip to content

数据模型

主节点用一个关系数据库持有全部状态。本页是全表清单与关系总览。建表 SQL 内嵌在 internal/store,启动时按方言自动迁移。

ER 总览

mermaid
erDiagram
    admins ||--o{ admin_sessions : "拥有"
    accounts ||--o{ account_sessions : "拥有"
    accounts ||--o| saves : "一份云存档"
    devices ||--o{ bans : "可被封禁"
    mirror_groups ||--o{ mirrors : "包含"

    admins {
        text id PK
        text username UK
        text email UK
        text password_hash
        text role
    }
    accounts {
        text id PK
        text username UK
        text email UK
        text password_hash
        text status
        int  login_count
    }
    account_sessions {
        text token PK
        text account_id FK
        int  expires_at
        int  last_seen_at
    }
    client_sessions {
        text access_token PK
        text device_id
        text signature
        text channel
        int  expires_at
    }
    saves {
        text account_id PK
        json data
        int  size_bytes
    }
    devices {
        text device_id PK
        text signature
        text client_version
    }
    bans {
        text id PK
        text device_id
        int  expire_time
        bool active
    }

全表清单

按职责分组。config 是一张 KV 表,承载大量运行配置(下文单列)。

身份

主键关键字段说明
adminsidusername/email(唯一)、password_hashrole后台管理员。role: super_admin/admin/readonly
accountsid(acc-xxxx)username/email(唯一)、password_hashstatuslogin_count玩家账号。status: active/disabled

会话(三套)

主键绑定TTL
admin_sessionstokenadmin_id(级联删)7 天
account_sessionstokenaccount_id(级联删)30 天,滑动续期
client_sessionsaccess_tokendevice_id + signature + channel7 天

account_sessions 还存 device_name/os/ip/region,供用户中心展示登录设备。client_sessions 不绑账号(握手在登录之前)。

设备与封禁

主键关键字段说明
devicesdevice_idsignatureclient_versionfirst_seen/last_seen设备指纹,握手时 upsert(保留旧非空值)
bansiddevice_idexpire_time(NULL=永久)、activeauto封禁记录,按 device_id 查活跃封禁

玩家数据

主键关键字段说明
savesaccount_iddata(JSON)、size_bytesupdated_at云存档,一账号一行,级联随账号删除

验证

主键关键字段说明
email_codesid(自增)emailcodepurposeexpires_atconsumed邮箱验证码。purpose: register/change_email/reset
cap_challengestokenc/d/sexpires_atsolvedPoW 挑战
cap_tokenstokenexpires_atused验证码兑换后的一次性令牌

配置与资源

主键说明
configkeyKV 配置表(JSON 值),见下文
mirror_groupsid(自增)镜像线路组,name + sort_order
mirrorsid(自增)镜像,group_idkind(http/s3)、url、可选 bucket/region/内联 files
hot_bundleskind(js/scenario)热更新包,versionsha256download_urlsize
offline_packageid(恒为 1)离线整包单例,download_urlpackage_versionsha256size

运维

主键说明
audit_logid审计日志,tsactortypetargetdetails(JSON)。按 ts/type/actor 建索引
secondary_nodesid已废弃(旧心跳式副节点发现遗留;新架构改用面板注册表 + 签名目录)。仍建表但已无代码读写,新装可忽略

config KV 表

很多运行配置不单独建表,而是以 JSON 存进 config。键即配置域:

key内容谁读
server服务器状态、维护文案、预计恢复时间/client/initserver 对象
versions版本白名单、更新 URL、latest_version、伪装字段、APK 哈希/client/initclient/spoof
features在线/离线下载开关、停用文案/client/initfeatures
services验证码 URL、代理后端、游戏服 host/client/initservices
offline_pack离线包最低版本门槛/client/initoffline_pack
captchaPoW 开关与难度验证码服务
tasks各定时任务周期调度器
auto_package离线包自动打包策略调度器
resource_token_secret自动生成的 HMAC 密钥(hex)资源 token 签名

configConfigGet/ConfigSet/ConfigEnsure 读写,值是任意 JSON,结构由各业务的 Go struct 定义。这让"新增一个配置项"只需加 struct 字段,不用改 schema。

时间戳约定

  • 数据库内统一存 Unix 毫秒(BIGINT/INTEGER),nowMs() 生成。
  • 下发给客户端时,部分字段转成 Unix 秒(如 server.end_time、封禁 expire_time)—— 因为客户端 Java 那边按秒解析。换算在 handler 层做。

这个"库存毫秒、协议出秒"的边界很容易踩错,改相关字段时留意。

JSON 列的方言差异

saves.dataconfig.valueaudit_log.detailsmirrors.filessecondary_nodes.files 等是 JSON:

数据库列类型
PostgreSQLJSONB
MySQLJSON
SQLiteTEXT

Go 侧统一用 json.RawMessage 读写,不依赖数据库的 JSON 函数(只把它当文本存),所以三方言行为一致。

级联与清理

  • accounts → 级联删其 account_sessionssaves(外键 ON DELETE CASCADE)。
  • admins → 级联删其 admin_sessions
  • 过期会话、过期封禁、过期验证码由调度器定期清理,不靠外键。

完整的存储层设计(方言抽象、UPSERT 生成、迁移机制)见 存储层与多方言抽象

文档正文以 CC BY-NC-SA 4.0 授权 · 代码部分以 GPLv3 开源 · 本项目仅作学习研究使用,与版权方无任何关联