Skip to content

idmap 数据库 ​

目标 ​

新的 idmap 设计把「外部身份」和「下游看到的 ID」分开:

  • QQ 平台的 OpenID、可反查到的 rUIN,以及其他外部平台 ID 都是外部身份。
  • Gensokyo 内部维护稳定的 vUIN,默认下游 OneBot 字段仍使用 vUIN。
  • 多个 OpenID 可以绑定到同一个 vUIN,多个 rUIN 也可以绑定到同一个 vUIN。
  • 反查不再返回某个 vUIN 的全部身份,只按当前消息上下文或最近一次上下文选择本次要使用的身份。

这解决了旧版 idmap.db 的核心缺陷:旧库是 OpenID -> 数字 ID 与 row-数字 ID -> OpenID 的单值双向表,无法表达「同一个真实用户在多个 AppID / 多个平台下的多个身份」。

数据库文件 ​

文件桶用途
openid-map.dbmetaschema version、迁移标记、vUIN 计数器
openid-map.dbidentity_to_vuin外部身份 -> vUIN
openid-map.dbvuin_to_identityvUIN -> 外部身份集合
openid-map.dblast_seenvUIN 在不同场景下最近一次使用的外部身份
openid-map.dbconfig运行时配置
openid-map.dbUserInfo用户信息缓存
msgid-map.dbmsg_to_virtual真实 message_id -> 虚拟 message_id
msgid-map.dbvirtual_to_msg虚拟 message_id -> 真实 message_id
msgid-map.dbexpires_atmessage_id 过期时间
idmap.db(旧)ids/config/UserInfo/cache旧版单库,只作为一次性迁移源

idmap_pro 在新结构中不再有意义:MultiMap 身份图已经原生支持「多个真实身份绑定到同一个 vUIN」。配置项会被移除,旧的 Pro API 仅作为兼容 wrapper 保留。

实际运行:当前使用的数据库文件为 idmap-identity.db(桶: ids/config/UserInfo)和 idmap-msg.db(桶: cache)。上表描述的 openid-map.db / msgid-map.db 多桶架构是 v2 重构方案,可通过 go build -tags map_idmap 启用。两者的对外行为完全一致。

身份键格式 ​

所有写入数据库的身份都必须带类型,不能只靠长度猜测:

类型规范化键
QQ OpenIDopenid:QQ:<appid>:<openid>
QQ rUINruin:QQ:<index>:<qq>
OAuth/GitHub rUINruin:OAuth_Github:<index>:<id>
旧版或未分类原始值raw:<value>

QQ OpenID 的身份键只包含 QQ、AppID 和 OpenID 本体,不再按群聊、私聊或群成员事件拆分。QQ 平台下同一个 QQ 号在同一个 AppID 内应复用同一个 OpenID,因此群聊、C2C、好友事件、群成员事件都会落到同一个 vUIN。

对外传递 rUIN 时使用:

text
rUIN-<PLATFORM_NAME>-<ID_CNT>-<ID>
  • PLATFORM_NAME 示例:QQ、OAuth_Github
  • ID_CNT 从 0 开始递增
  • ID 对下游输出时使用 Base32;接收客户端输入时允许 raw 或 Base32
  • rUIN-QQ-0-2870338968 与 rUIN-QQ-0-GI4DOMBTGM4DSNRY 等价

下游 ID 来源 ​

默认配置继续向下游发送内部 vUIN:

yaml
op_userid_type: vuin

可选值:

值含义
vuin默认值,user_id/group_id 使用内部 vUIN
raw发送当前消息的原始平台 ID
ruin优先发送当前上下文中的 rUIN;没有 rUIN 时回退 vUIN

接收客户端操作时:

  • vUIN 和 OpenID 都是默认行为,不需要额外前缀。
  • rUIN 使用 rUIN-<PLATFORM>-<index>-<id>。
  • 当客户端传入纯数字时,默认按 vUIN 处理;如需阻止推测,使用 op_userid_type 指定策略。

写入路径 ​

收到腾讯侧事件时:

  1. 将 OpenID 规范化为 openid:QQ:<appid>:<openid>。
  2. 如果事件或历史信息能提供 rUIN,则规范化为 ruin:QQ:<index>:<qq>。
  3. 查询 identity_to_vuin:
    • 任一身份已存在,复用对应 vUIN。
    • 都不存在,分配新的 vUIN。
  4. 将本次消息实际出现的身份写入 vuin_to_identity。
  5. 写入 last_seen,记录当前消息上下文用于后续反查。

绑定操作不再移动 row,也不覆盖旧反向键,而是把一个身份追加到目标 vUIN:

text
BindIdentityToVuin(openid:QQ:<appid>:<openid>, 10001)
BindIdentityToVuin(ruin:QQ:0:2870338968, 10001)

反查路径 ​

当下游用 vUIN 发起操作时:

  1. 优先使用请求中携带的显式 OpenID 或 rUIN。
  2. 其次读取当前消息上下文中的 last_seen。
  3. 再从 vuin_to_identity 中选择一个可用于目标 API 的身份。

如果一个 vUIN 下有多个 OpenID/rUIN,反查只附加本次发送需要的身份,不全量附加所有身份。

旧库迁移 ​

启动时如果检测到旧版单库 idmap.db,会阻塞执行一次性转换:

  1. 打开 openid-map.db 与 msgid-map.db。
  2. 读取旧 ids 桶,保留既有 OpenID/raw -> vUIN 关系。
  3. 读取旧 row-<vUIN> 反向键,补齐缺失的身份关系。
  4. 识别旧 idmap_pro 复合键,拆成普通身份映射保存。
  5. 复制 config 与 UserInfo。
  6. 不全量迁移旧 cache 桶;仅同步 message_id 计数器以避免虚拟 message_id 撞号。
  7. 校验完成后在 openid-map.db/meta 写入迁移标记。

迁移完成后热路径只访问新库,不再惰性复制旧库内容。旧 idmap.db 不会被自动删除,确认稳定后可手动备份或删除。

2026-08-01 修复: StartMigration 已改为阻塞式迁移(此前为 go backgroundMigration() 非阻塞)。迁移完成才返回,确保 main.go 调用点之后才连 WS / 启动 HTTP,避免迁移期间 storeIdentity 双写与 backgroundMigration 迁入并发产生 2 row → 1 OpenID 重复映射。同时 writeBatchToNewDB 追加按 value 委重(逆向条目 uin:row-* 迁入前查正向 uin:<OpenID> 是否已存在,若已有则跳过)。

强制解绑工具 ​

迁移异常或历史数据残留导致 2 个虚拟 ID 指向同一 OpenID 时,可用强制解绑工具按 OpenID 直接清理全部映射:

工具调用方式行为
ForceUnbindID(id)Go 内部入参支持OpenID 字符串或虚拟 ID两种形式。OpenID 时直接删正向 uin:<OpenID> + 扫删所有指向同一 OpenID 的逆向条目 uin:row-*(新库+旧库);虚拟 ID 时先通过逆向条目 uin:row-<N> 反查 OpenID(新库+旧库回退),再按 OpenID 清理。返回清理条数
getid type=18HTTP APIGET /getid?type=18&id=<OpenID或虚拟ID>,返回 {"status":"success","unbound_count":N}
UpdateVirtualValue(old, 0)getid type=5newRowValue=0 解绑时已同步彻底清理:删正向 + 扫删所有指向同一 OpenID 的重复逆向条目(2026-08-01 修复,此前只删单条逆向不删正向,导致解绑失效)

解绑后该 OpenID 彻底无映射,下次 storeIdentity 会重新分配唯一虚拟 ID。

2026-08-01 修复: ForceUnbindID 原仅接受 OpenID 入参,但用户实际常传虚拟 ID(row 值),导致查正向 uin:<虚拟ID> 找不到直接返回,unboundCount=0(表现为 getid type=18 返回 false)。现已支持双形式入参:纯数字视为虚拟 ID,先反查 OpenID 再清理;非纯数字视为 OpenID 直接清理。同时去掉“正向条目存在检查”的提前返回,即便正向已被其他路径删除,也要扫删残留逆向条目,确保彻底清理。

批量导出接口 ​

迁移调试或数据核查时,可一次性导出所有身份映射:

工具调用方式行为
ListAllIdentities()Go 内部返回 *IdentitySnapshot,包含所有 OpenID <-> 虚拟 ID 映射及用户名缓存
getid type=19HTTP APIGET /getid?type=19,返回所有身份映射的 JSON 快照

调用示例:

bash
# 本地访问(无 lotus_password 时)
curl http://127.0.0.1:15630/getid?type=19

# 带认证(有 lotus_password 时,需计算 HMAC 签名)
curl "http://<server>:<port>/getid?type=19&timestamp=<ts>&token=<hmac>"

响应示例:

json
{
  "mappings": [
    {"real_id": "ABC123DEF456...", "virtual_id": 10001, "username": "张三"},
    {"real_id": "789GHI012JKL...", "virtual_id": 10002, "username": "李四"},
    {"real_id": "MNO345PQR678...", "virtual_id": 10003, "username": ""}
  ],
  "count": 3,
  "timestamp": 1723190400
}

字段说明:

  • real_id:QQ 官方 OpenID(已去除内部 UIN 前缀)
  • virtual_id:Gensokyo 分配的虚拟数字 ID(下游 OneBot 使用)
  • username:用户名缓存(入站时自动缓存,10 分钟 TTL,可能为空)
  • count:映射总数
  • timestamp:快照生成时间(Unix 时间戳,秒)

快照机制:

  • 接口使用 bbolt MVCC 事务,在请求到达时生成一致性快照
  • 即使遍历期间有其他消息触发新的映射写入,也不影响本次导出结果
  • timestamp 字段可用于判断数据新鲜度

注意事项:

  • type=19 不走 lotus/gRPC 远程调用,始终查询本地 identityDB
  • 受 IDMapAuthMiddleware 保护:有 lotus_password 时需 HMAC 认证,无密码时仅允许本地访问
  • 数据量较大时响应可能较慢,建议仅在调试时使用

msgid-map ​

msgid-map.db 专门保存 message_id 映射,默认 TTL 为 1 小时:

yaml
msgid_ttl_seconds: 3600

每次写入:

  • msg_to_virtual[real_message_id] = virtual_message_id
  • virtual_to_msg[virtual_message_id] = real_message_id
  • expires_at[virtual_message_id] = now + ttl

后台清理每分钟扫描 expires_at,删除过期的三条记录。旧库 cache 不再全量迁移,避免把大量历史临时数据搬进新库。

运行日志 ​

新 idmap 热路径默认输出状态日志,便于把平台原始事件和内部映射结果对齐:

  • [idmap] identity create/hit:OpenID/rUIN/raw 身份新建或命中,并显示对应 vUIN。
  • [idmap] bind moved/skipped/failed:手动或自动绑定时移动身份集合的结果。
  • [idmap] msg_id create/hit/lookup:真实 message_id 与虚拟 message_id 的写入、刷新 TTL、反查结果。
  • [message] group/c2c/friend/group_member ... mapped:Processor 层事件最终映射出的 vUIN 与虚拟 message_id。

兼容性 ​

保留旧公开函数名,内部路由到新结构:

旧函数新行为
StoreIDv2(id)规范化身份并返回 vUIN
RetrieveRowByIDv2(vuin)按上下文解析 vUIN 对应的当前可用外部身份
UpdateVirtualValuev2(old, new)将 old 对应身份绑定到 new vUIN
StoreCachev2(id)写入 msgid-map.db,默认 1h TTL
RetrieveRowByCachev2(id)从 msgid-map.db 读取真实 message_id
StoreIDv2Pro / RetrieveRowByIDv2Pro兼容 wrapper,不再依赖 idmap_pro 配置

故障恢复 ​

故障影响恢复
msgid-map.db 损坏message_id 回执可能丢失停止程序,删除 msgid-map.db 后重启
openid-map.db 损坏且旧 idmap.db 仍在身份映射暂不可用停止程序,删除 openid-map.db 后重启重新迁移
openid-map.db 损坏且旧库已删除身份映射丢失从备份恢复
迁移校验失败新库不写迁移完成标记保留旧库,修复后重启重新迁移

建议定期备份 openid-map.db。msgid-map.db 是临时缓存,不需要长期备份。

Gensokyo-NewQQ 配套 Web 文档站