Files
dsh_shenxian/dsh-server-docs/03-数据库/DB-01-接入指南.md
T
admin e6207aa691
build / build-and-scan (push) Waiting to run
chore(仓库对齐): 文档库结构治理 + IM/插件线落地
文档库:目录改为编号制(01-规范/02-架构设计/03-数据库/04-调整方案/
05-交接单/06-ops/07-scripts/08-skills/09-archive),顶层散文件归入 01-规范/;
INDEX.md 与 docs-manifest.json 重刷(档案 146 篇);旧目录名引用全量对齐。

IM 线:src/im/**(SDK / hub / store / presence / ws / gateway-token)、
src/web/routes/im.ts、src/db/plugin-data/**、src/supervisor/plugin-assembly.ts
及对应 test/**。

插件线:poc/{im-agent-bridge,im-connection-gateway,im-conversation-tabs,
business-plugins-im,carbon-mcp-probe}、src/web/routes/{sessions,overlay-device}.ts、
src/net/relay/{device-grant,instance-credential}.ts。

仓库卫生:清出 40 个历史误入库 / 已改名文件(34 个交接单归档 + 6 个旧结构,
本地均有副本);dsh-server-docs/.gitignore 补 tmp/;交接单不入库(政策)。
2026-09-24 07:25:16 +08:00

9.2 KiB
Raw Blame History

数据库专区 · 01 接入指南(各种情况怎么接)

状态:✅ 已落文档库(2026-09-20)| 专区:03-数据库/

立稿:2026-09-20 | 起草会话 IM插件数据面-专门文档> 单一来源:代码 src/db/{adapter,connection,schema,repo,sqlite,pg}.ts;档案 142 §3.6;04-调整方案/140(配置外置)。

〇、本文怎么用

先看你要存的是哪一类数据,再照着对应那节做。顺序不可颠倒:先判落点,再谈建表。

# 情形 落点 能不能建表 访问入口
1 平台内核数据(账号 / 实例 / 主机 / 租约) 控制面库 dshs ✅ 内核自己建 src/db/**
2 插件业务数据(跟房间走 / 跟用户走) 该插件自己的库 dshs_pl_<pluginId> ✅ 声明式声明,内核翻译建表(含归属列) 内核数据 API
3 用户的业务数据(索引 / 导出产物 / 设置) 库(插件维度进插件库、平台维度进控制面库) ✅ 由内核建 内核数据 API
4 本机运维数据(健康 / 临时态) Worker 本地库 ✅ Worker 本地 平台内部接口
5 大对象(图片 / 视频 / Excel / 代码包) 对象存储桶 ❌ 不进 DB files 面(sha256 + bucket_key)
6 实例本地(只剩三类:文件内容 / 缓存 / 临时) 实例 home ✅ 只放可重建的东西 实例本地
7 缓存 / 队列 / 后处理任务 预留端口 端口先立、实现后置 im.cache / im.queue

判据一句话:跟平台全局走 ⇒ 控制面库 dshs;跟某个插件走 ⇒ 该插件的库 dshs_pl_<pluginId>(库内按归属列切分用户 / 房间);是文件内容 ⇒ 桶;本机运维 ⇒ Worker 本地库。 ⛔ 同一份数据不双写(双写 = 脑裂)。


情形 1 · 平台内核数据

  • 谁做:平台自己(不是插件)。改 src/db/schema.ts 的 MIGRATIONS。
  • 硬纪律:每条迁移必须同时给 sqlite 与 pg 两份 SQL(双后端可切是本平台的既有承诺);只增不减(加表 / 加列带默认值 / 加索引)。
  • 命名:内核表用保留名、不带前缀(users / sessions / workspaces / folder_plugins / dsh_instances / audit_log / domains / credential_vault / business_plugins / dsh_hosts / email_codes / overlay_devices / schema_migrations)。
  • 验收:新库跑到最新 version 且重复执行版本不变(幂等);npm test 的 test/db.test.mjs 绿。

情形 2 · 插件业务数据(问得最多的那种)

  • 判据:一个插件一个库 —— dshs_pl_<pluginId>,建在同一个 PostgreSQL 13.23 实例上;库内按归属分两类(scope: user 跟用户走 / scope: room 跟房间走),靠归属列区分。
  • 建表:插件不写 SQL —— 用声明式 schema(中性类型 text/integer/bigint/real/boolean/json/timestamp/uuid),由内核翻译成 sqlite + pg 两套 DDL;内核自动补库名、表前缀 p_<pluginId>_、归属列(user_id / room_id)、审计列。
  • 迁移:只许加表 / 加列(带默认值)/ 加索引;在启用插件时执行;任一步失败 ⇒ 拒绝启用(不做半迁移)。
  • 访问:im.data.table(name).insert/update/delete/find/count + 游标分页;库路由由内核按插件身份决定(插件看不到库名);归属判定与房间 ACL 由内核强制注入;同库内要原子 ⇒ 走内核事务口;写与"刚写就读"走主库。
  • 禁止:直连 DB / 原生 SQL / 访问别的插件库 / 自组跨库事务 / 绕过归属判定 / 以实例本地库当权威。
  • 验收:插件包内 CREATE TABLE、better-sqlite3、pg 零命中;scope: user 读他人行 = 0 行;非成员读 room-scoped 行 = 0 行。
  • 📄 完整契约 = 本专区 DB-03-插件数据面规范.md。

情形 3 · 用户自己的数据(2026-09-22 改:入库;实例本地只留缓存 / 临时)

  • 🔴 用户数据一律入库(这是"能备份、能迁移"的前提):
    • 插件里的用户数据(scope: user)⇒ 该插件的库 dshs_pl_<pluginId>,带 user_id;
    • 平台维度的用户数据(会话 / 设置 / 工作区文件索引)⇒ 控制面库 dshs,带归属列。
  • 实例本地(<userRoot>/)此后只剩三类:① 文件内容(当前形态;目标形态改为落对象存储、本地只留按需缓存)② 缓存(可重建)③ 临时文件。 判据:本地丢了不影响正确性,只影响延迟 —— 凡是"本地丢了就丢数据"的东西,一律不属于实例本地。
  • ⛔ 不得以实例本地库 / 本地文件作为数据权威(旧形态「插件在实例里自建 SQLite 装业务数据」已作废;存量按架构定稿 S2 步骤导入插件库)。
  • 仍生效的写入门禁:平台侧写实例 home 必须走 UserFs —— 直接 fs 写 = 静默空操作(用户卷在 worker 上时尤其如此)。
  • 🔴 平台侧不得以 cp / mv / 直接 fs 写用户 home 下的任意文件(2026-09-21 立,仍有效):这三条路径都不是合规写入通路,⛔ 一律不认。
    • 理由(实测证据):UserFs 的 home 面只有 3 个白名单裸文件名 —— settings.yaml / .credentials.yaml / .overlay-device.json(src/fs/user-fs.ts:126,⛔ 不收路径,所以 home/.dsh/<plugin>.db 这类根本进不了白名单);且 writeHomeFile 签名是 text: string = 文本语义(src/fs/user-fs.ts:113),upload(src/fs/user-fs.ts:83)是 workspace 相对路径 ⇒ 只能写 ws/。
    • ⇒ 结论(2026-09-22 收窄):平台侧对实例 home 下的二进制 / 任意文件没有合规写入通路。⚠️ 但这不再是"插件自有 DB 该落哪"的答案 —— 插件数据的正解是入库(§情形 2 的插件库);home 只放缓存 / 临时,对 home 内的文件平台侧只能只读核对,⛔ 不能代写。
  • 实例内插件(2026-09-22 改):数据入库 —— 插件数据进 dshs_pl_<pluginId>(scope: user 的行带 user_id),平台维度的用户数据进控制面库;⛔ 不得再把实例本地 SQLite / 本地文件当数据权威(可以做缓存,丢了可重建)。存量(如 MCN)按架构定稿 S2 步骤导入。
  • 坑:⛔ 用 homedir() 拼路径必错(实例内 HOME=<userRoot>/ws);正解 = process.env.DSH_HOME ? join(DSH_HOME,'.dsh') : join(homedir(),'.dsh')。

情形 4 · Worker 本地运维数据

  • 落点:Worker 本机库(该机器的健康 / 临时态 / 运维缓存)。
  • 判据:跟机器走、可以重建 ⇒ 放这里,别放平台库(否则每台机器都要往 Manager 写,形成热点)。
  • ⛔ 不放归属 / 租约类数据(那类只有 Manager 能写)。

情形 5 · 大对象

  • 落点:S3 兼容桶(自建 MinIO 或云 OSS/COS 兼容端点)⇒ 不锁厂商;端点与密钥走 config/platform.env(⛔ 不入库、默认值中性)。
  • 分工:桶存内容、DB 存元数据(files 表:sha256 / bucket_key / size / mime / visibility)。⛔ 大对象不进 DB(bytea 会把库与备份一起拖垮)。
  • 上传:预签名直传桶(平台不经中转);下载:短 TTL 预签名(≤5 min),需审计时走平台代理。
  • 生命周期:同 sha256 只存一份 + 桶 lifecycle;删除 = 先软删元数据,桶内回收交 lifecycle(⛔ 不做同步删,不可逆)。
  • 权限:复用房间 ACL,⛔ 不新起一套。

情形 6 · 缓存 / 队列 / 后处理

  • 队列先用 PG:SELECT … FOR UPDATE SKIP LOCKED + LISTEN/NOTIFY ⇒ 零新增进程、零新增内存。
  • Redis 后置:仅在「跨机扇出 / presence 广播」实测成热点后引入;硬前提 = 单实例 ≤128–256 MiB(实例硬顶 1024 MiB / 宿主 1870 MiB)。
  • "预留"的正解:先把端口立起来(im.cache / im.queue),本期实现 = 内存 Map / PG 表 ⇒ 将来换实现不改调用点。
  • ⛔ 插件不得自行引入 Redis / MQ 进程。

主从与扩展(四个情形共用)

项 结论
选型 保留 PostgreSQL 13.23(现有实例 + db/ 双后端可切)> MySQL(无收益、要重迁移)> SQLite(⛔ 不支持主从,只留单机/测试)
主从 流复制 + 只读副本
读写分离 ⚠️ 按数据分类:元信息读可走副本;消息/"刚写就读"必须走主(否则破坏读己所写)
扩展顺序 先索引与游标分页 → 再只读副本 → 再分区/分片。⚠️ 原「不要提前分库」已作废(2026-09-22 拍板按插件分库)

验收速查(任何接入都跑这三条)

  1. 归属对:表名能一眼判断属于谁(无前缀 = 内核;p_<pluginId>_ = 该插件)。
  2. 双后端:新结构在 sqlite 与 pg 上都建得起来。
  3. 不越权:非成员 / 跨前缀 / 跨插件 一律拿不到数据(不是"拿得到但看不见")。