Files
ai_site/宇恒-松离线数据同步使用文档.md
whm cb56e6847e feat: add Z12/Z13 bind APIs, stock import, and sync docs
Enable auto default sync channels on agent activate, bind-code/phone confirm flows, publish ALTER, and align admin/yuheng docs with the production bind path.
2026-08-05 11:47:20 +08:00

20 KiB
Raw Blame History

宇恒 × 智建 · 松离线数据同步使用文档

面向:宇恒一号客户端对接同学
平台侧仓库智建ai建站
依据:松离线-dbsync方案-最终版.md(冻结)、联调后修改意见-宇恒松离线.mdZ12/Z13 产品路径)
本地测试基址示例:http://127.0.0.1:8180;生产示例:https://aisite.yuxindazhineng.com
生产·宇信达联调登录:手机号 13531041944(公司侧专用;勿用超管号 13531041945 / 演示号 13800000001


1. 你要做什么(一句话)

未开通用户零改动。仅当显式 YXD_SYNC_MODE=local_dbsync(或库级选「同步」)且走白名单/Binding 时:业务写进本机正式库 BUUID→ 写 outbox → 本机 agent 调智建 push API → 落到线上库 A。agent 停了也不能挡保存

业务保存 ──► 本机 B最终 UUID立刻可见
              │
              ▼
           outbox积压可接受
              │
              ▼ 本机 agent可选装
         智建 POST .../push  ──► 线上 A

生产开通推荐Z12/Z13:不要引导用户手抄 channel_id
后台启用智能体 / 发绑定码 / 同号确认 → 换票带 sync_bound + 落点 → 本机库选「同步」即可用。
手建通道 + YXD_SYNC_CHANNEL_ID 仅作运维高级 / 联调过渡


2. 硬约束(违反即不合入)

# 要求
H1 未开通:保存 / 自增表 / 插件与现网 行为 diff = 0
H2 禁止自动升为 local_dbsync(仅显式配置或用户库级选「同步」)
H3 agent 停运 / 推送失败 → 保存仍成功;心跳只驱动 UI
H4 默认建表仍自增;仅「同步表」用 UUID TEXT PK
H5 online_primary 客户禁止静默关双写
H6 未开通用户:零同步文案(无强制状态条、无「已自动合并」)

开通判定(写路径):

mode == local_dbsync  AND  (表白名单缓存命中 或 Binding 整库策略)
→ 走松离线旁路
否则 → 原写路径

同表互斥:某表不可同时 HTTP 双写 + dbsync 白名单。


3. 模式三分(环境变量)

YXD_SYNC_MODE 含义 说明
local_only 仅本地 默认之一
online_primary HTTP 双写 / 离线 pending 旧客保留;仅配了 YXD_ONLINE_API_BASE 且未写 MODE 时可兼容升为此模式
local_dbsync 松离线 + 白名单 + agent 必须显式写出,永不因「配了线上地址」自动升

建议本地测试:

# 默认无感:不要设 local_dbsync或显式
YXD_SYNC_MODE=local_only

开通增值(过渡 / 运维仍可用 env生产优先换票落点

YXD_SYNC_MODE=local_dbsync
YXD_SYNC_AGENT=1
YXD_ONLINE_API_BASE=http://127.0.0.1:8180
# 过渡:可手填;生产应优先用换票/agents/me 的 channel_id
YXD_SYNC_CHANNEL_ID=<可选过渡缓存>
YXD_SYNC_ACCESS_TOKEN=<Bearer管理员/智能体「数据同步」,或登录用户 JWT自助须先 Binding>
# 可选
YXD_SYNC_WHITELIST_TTL_SEC=600
YXD_SYNC_AGENT_INTERVAL_SEC=5
YXD_SYNC_WHITELIST_CACHE=cache/db_sync/whitelist.json
# 同号绑定联调(生产宇信达)
YXD_SYNC_LOGIN_PHONE=13531041944

调试兜底表白名单(通道拉取失败前):

YXD_SYNC_WHITELIST=orders,order_items
# 或
YXD_SYNC_DBSYNC_TABLES=orders,order_items

3.1 生产绑定与落点Z12 / Z13 · 必读)

产品约定

  1. 一个登录账号 / 一个智能体 ↔ 本公司同步落点;个人库默认隔离,禁止 A 数据进 B 库。
  2. 禁止把「手抄通道 ID / 先去控制台新建通道」当作普通用户开通主路径。
  3. 公司默认同步通道由智建在启用智能体时自动创建(is_system_default);控制台「数据同步」留给运维改 DSN。
  4. 库选「同步」→ 自动 Binding + drain宇恒 Z13e全程零手填 DSN/通道。

换票带回绑定Z12a

POST /api/v1/auth/tokenclient_credentials或登录响应可含

字段 说明
channel_id 已绑则非空
online_db_id 已绑则非空
database_name 可读落库名
sync_bound true = 已绑落点;false = 须走绑定流程

通道解析顺序建议:env 过渡缓存 → 换票/agents/me → Binding → 租户仅 1 条通道自动选用。

智能体自查Z12b

GET /api/v1/agents/me
Authorization: Bearer <智能体 Token>

返回 channel_id / online_db_id / database_name / sync_bound / status(无需「管理智能体」权限)。

绑定码Z13a/b

管理员(需「数据同步」):

POST /api/v1/admin/bind-codes
Authorization: Bearer <管理员 JWT>
Content-Type: application/json

{ "max_uses": 1, "expires_hours": 168, "note": "开通单" }
GET /api/v1/admin/bind-codes
DELETE /api/v1/admin/bind-codes/{code}

终端兑换(公开):

POST /api/v1/auth/bind-code/redeem
Content-Type: application/json

{ "code": "A1B2C3D4", "host_key": "<本机稳定宿主机 key>", "name": "可选显示名" }

成功 → 智能体挂到该公司 + 默认同步落点,sync_bound=true

同号探测 / 确认Z13c / Z13c-1 · 硬约束)

禁止静默绑定。有手机号时必须先 lookup命中后弹窗确认再 confirm。

POST /api/v1/auth/bind/phone-lookup
Content-Type: application/json

{ "phone": "13531041944" }

响应要点:existstenant_namemasked_nameneed_confirmmessage执行绑定)。

POST /api/v1/auth/bind/phone-confirm
Content-Type: application/json

{
  "phone": "13531041944",
  "host_key": "<本机 host_key>",
  "confirm": true,
  "local_database_id": "可选,本机库 ID"
}
场景 行为
同号命中公司成员 必须询问「已有账号是否绑定」;确认才 phone-confirm
用户取消 不绑定;可改走绑定码 / 换号
无此成员 提示用绑定码;用超管号 13531041945
生产联调样例 智建与宇恒均为 13531041944

未绑定时客户端流程Z13d · 宇恒待接)

换票 / agents/me → sync_bound=false
  ├─ 有手机号 → phone-lookup
  │     ├─ need_confirm → 【弹窗】确认?→ phone-confirm / 取消
  │     └─ 未命中 → 绑定码或换号表单
  └─ 无手机号 → 绑定码表单
绑定成功 → 库选「同步」→ Binding + drain零通道配置

4. 智建侧前置(公司管理员)

生产推荐Z12/Z13

  1. 公司级配置默认同步 DSNDBSync.DefaultRemoteDSN,生产 Postgres
  2. 启用智能体(勿要求用户先「新建通道」)→ 平台自动建默认同步通道并写回智能体落点。
  3. (可选)生成绑定码发给终端;或引导用户用公司成员手机号做同号确认(须弹窗)。
  4. 运维需要时再在「数据同步」改 remote DSN / 查看通道 ID可复制

运维高级 / 联调过渡(手建通道)

  1. 登录智建控制台 → 数据同步 → 新建通道。
  2. 生产remote.driver=postgresDSN 例:postgres://user:pass@host:5432/db?sslmode=disable(须可达)。
  3. 表白名单可空(整库 Binding 策略);若填表须 UUID TEXT/UUID PK + 外键闭包
  4. 方向推荐 本地 → 线上;冲突策略推荐 源端覆盖lww_source
  5. 保存通过校验后记下 通道 ID(过渡写入 YXD_SYNC_CHANNEL_ID)。
  6. 二选一鉴权
    • 管理路径:发带「数据同步」的智能体 Token / 管理员 JWT
    • 用户自助:终端用登录用户 JWT先登记 Bindingpush 带本人 online_db_id

单服务器:一条默认同步通道即可(自动或手建)。
多服务器:每台线上 Postgres 各建一条通道;按本机库选择对应 channel_id(建议登记 Binding

Binding多库 / 用户自助时建议登记;登录 JWT 即可,不必管理员权):

POST /api/v1/admin/sync/bindings
Authorization: Bearer <token>
Content-Type: application/json

{
  "local_database_id": "用户本机库 ID",
  "online_db_id": "线上库 ID",
  "channel_id": "<通道ID>",
  "database_name": "可选,本机库可读名(控制台优先展示)",
  "display_name": "可选,线上库可读名(控制台优先展示)",
  "note": "可选"
}

不传可读名时控制台仍显示 id宇恒有命名库名时建议一并写入便于 SyncPage「本地库名 ↔ 线上库名」。

GET /api/v1/admin/sync/bindings?local_database_id=...

5. 宇恒侧实现清单

5.1 写网关旁路M1可无 agent

insert / update / delete / 写 SQL

  1. 读模式与白名单。
  2. 未命中:原路径,响应形状与现网一致(可多字段,不可少成功语义)。
  3. 命中
    • insert缺 id 或非法 id → 自动补小写带连字符 UUID
    • 写入本机正式库 B
    • append outbox失败只打日志不挡保存
    • sync_statuspending / local_only_table 等仅开通用户可见。

文案:需 agent 才上云(不得暗示已上云)。

5.2 Outbox

建议路径(等价即可):

cache/db_sync/outbox/<local_database_id>/pending.jsonl

每条建议字段:

字段 说明
id outbox 记录 UUID
seq 单调版本(如 time.time_ns()),作 push 的 version
op insert / update / update_by_id / delete
table_name 表名
row_pk 行主键 UUID
payload data / update_data / where_*
status pending

规则:按 seq 保序消费;失败标记 error 并停该库后续,恢复后重试;禁止失败后换新 UUID。

5.3 本机 AgentM2

启用:YXD_SYNC_AGENT=1mode=local_dbsync。默认可不启。

循环建议:

  1. TTL拉白名单 → 写本地缓存。
  2. list_pending → 逐条 push → 成功 mark_done / 失败 mark_error 并 break。
  3. 写心跳文件(供 /sync/status 展示 agent.running / last_beat)。

拉白名单

GET /api/v1/agent/sync/channels/{channel_id}/whitelist
Authorization: Bearer <token>

响应要点:tablespk_columnsconflict_policy

推单条

POST /api/v1/agent/sync/channels/{channel_id}/push
Authorization: Bearer <token>
Content-Type: application/json

{
  "table": "orders",
  "op": "insert",
  "row_pk": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee",
  "row": {
    "id": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee",
    "title": "demo"
  },
  "version": 1710000000000000000,
  "client_outbox_id": "outbox-record-uuid",
  "online_db_id": "用户自助必填(本人 Binding管理员/智能体可选"
}
op 平台行为
insert / update / update_by_id upsert 到 remote A
delete 按 pk 删 A

幂等:同 version 已落地 → 跳过成功(skipped=true)。
LWW目标更新 → 按通道策略;源端覆盖会记超管审计(公司管理员不可见)。
重复 push双 drain正确性由 version 保证;客户端可用 drain 锁减流量,非平台必做。
remote 暂不可达:503 + retryable:true

下行(仅线上 / 灌库 / 补齐)

POST /api/v1/agent/sync/channels/{channel_id}/bootstrap
Authorization: Bearer <token>
Content-Type: application/json

{
  "table": "orders",
  "after_pk": "",
  "limit": 200,
  "online_db_id": "用户自助必填"
}

POST .../pullmode 取:

mode 作用
bootstrap 分页全量(循环至 has_more=false
pks 只取 A 主键,本机 diff
rows row_pks 取行

客户端把 result.items upsert 进本机 B 时务必 WithApplying(或等价),避免回声进 outbox。
空表:result.columns 仍会返回;本机应用其 CREATE TABLE IF NOT EXISTS(见下「表结构同步」)。

成功响应示例:

{
  "success": true,
  "result": {
    "ok": true,
    "applied": false,
    "skipped": true,
    "conflict": false,
    "applied_version": 1710000000000000000,
    "message": "already applied (same version)",
    "client_outbox_id": "outbox-record-uuid"
  }
}

批量:POST .../push/batchbody { "items": [ ... ] },最多 100保序遇错即停。

也可设完整 URLYXD_SYNC_PUSH_URL=...(覆盖默认拼装)。

表结构同步(空表也要建)

仅靠 outbox 行 push 时,空表不会出现在线上(无变更事件)。同步周期应额外:

  1. 本机 → 线上:对本机每张业务表(含 0 行)调用 ensure
POST /api/v1/agent/sync/channels/{channel_id}/schema/ensure
Authorization: Bearer <token>
Content-Type: application/json

{
  "online_db_id": "用户自助必填",
  "tables": [
    {
      "table": "accounts",
      "pk_column": "id",
      "columns": ["id", "username", "password", "email", "nickname", "status", "created_at", "updated_at"]
    }
  ]
}

平台对每张表 CREATE TABLE IF NOT EXISTS(列一律 TEXT + 指定 PK已存在则跳过。

  1. 线上 → 本机:先拉结构,本机缺表则建空表,再 bootstrap 行:
POST /api/v1/agent/sync/channels/{channel_id}/schema
Authorization: Bearer <token>
Content-Type: application/json

{ "online_db_id": "用户自助必填" }

响应 result.tables[].columns + row_count;对本地缺失表执行 CREATE TABLE IF NOT EXISTS(建议 TEXT + PK=pk_column)。
pull/bootstrapresult.columns 同样可用于单表建空表。

5.4 状态接口(建议)

GET /database/sync/status(或你们现有等价路由)对开通用户返回:

  • mode
  • local_dbsync.whitelist_tables / outbox_pending
  • agent.enabled_flag / running / last_error
  • note:未开通勿强塞同步 UI

6. UUID 约定

  • 标准形式:小写 + 连字符 8-4-4-4-12
  • 入库前 normalize;无连字符 32 hex 可规范化
  • 默认建表模板不要改成 UUID只给同步表白名单表用

SQLite 同步表示例:

CREATE TABLE IF NOT EXISTS orders (
  id TEXT PRIMARY KEY NOT NULL,
  title TEXT,
  updated_at TEXT NOT NULL DEFAULT (datetime('now'))
);

7. 从旧双写迁入O7摘要

  1. 该表先停 online_primary 双写。
  2. 观察无残留。
  3. 再进白名单并切 local_dbsync
  4. 装 agent看 outbox 清空。
  5. 禁止静默全员迁移;旧客默认不变。

完整步骤见智建仓:docs/数据同步-迁移手册.md


8. 联调检查表

未开通回归(必过)

  • 不设 local_dbsync:插入自增表与现网一致
  • 无同步强制文案、无强制装 agent
  • 保存不因同步模块报错失败

开通 + 无 agent

  • 白名单表本地立刻可见UUID
  • outbox 增长;响应提示需 agent 才上云
  • 非白名单表仍走原路径

开通 + agent

  • 拉白名单成功并缓存
  • push 后线上 A 出现同一 UUID(不双行)
  • 重复 push 同 version 幂等
  • agent 停:仍可本地保存;恢复后按序追上
  • delete 保序,不换新 UUID
  • 空表:schema/ensure 后线上出现空表Z10c

生产绑定Z12/Z13

  • 启用智能体后换票 / agents/me 已有 sync_bound=true(无需手抄通道)
  • 绑定码 redeem 成功
  • 同号 13531041944lookup → 弹窗 → confirm取消不绑
  • 未用超管号 13531041945 做绑定联调
  • 库选「同步」后可 drain零手填 CHANNEL_ID

冲突

  • 终端冲突处理台
  • 公司管理员打 conflicts API → 403
  • 超管可在平台工作台看 LWW 日志 / 回滚

9. 智建 API 速查

基址:{YXD_ONLINE_API_BASE},鉴权:Authorization: Bearer ...

  • 管理路径:需「数据同步」
  • 用户自助:登录用户 JWT + 本人 Bindingpush 须带 online_db_id
  • LWW 仅超管
方法 路径 谁用
POST /api/v1/auth/token 换票;响应可含 channel_id/online_db_id/sync_boundZ12a
GET /api/v1/agents/me 智能体自查落点Z12b
POST /api/v1/auth/bind-code/redeem 绑定码兑换公开Z13b
POST /api/v1/auth/bind/phone-lookup 同号探测不绑定Z13c
POST /api/v1/auth/bind/phone-confirm 同号确认后绑定Z13c-1
GET/POST/DELETE /api/v1/admin/bind-codes 管理员生成/列表/撤销绑定码Z13a
GET /api/v1/agent/sync/channels/{id}/whitelist agent / 用户 JWT
POST /api/v1/agent/sync/channels/{id}/push agent / 用户 JWT
POST /api/v1/agent/sync/channels/{id}/push/batch agent / 用户 JWT
POST /api/v1/agent/sync/channels/{id}/pull agent / 用户 JWT下行
POST /api/v1/agent/sync/channels/{id}/bootstrap 全量灌库
POST /api/v1/agent/sync/channels/{id}/schema 拉线上表结构(含空表)
POST /api/v1/agent/sync/channels/{id}/schema/ensure 本机空表建到线上Z10
GET/POST /api/v1/admin/sync/bindings 管理员或用户自助
GET /api/v1/platform/dbsync/lww-overrides 仅超管
POST /api/v1/platform/dbsync/lww-overrides/{id}/rollback 仅超管

OpenAPIGET /api/v1/meta/openapi.yaml


10. 本地联调最小步骤

生产路径(推荐)

  1. 智建起栈;公司配置 DefaultRemoteDSN(或联调用空 DSN → sqlite 文件)。
  2. 启用智能体 → 自动绑默认同步通道;或管理员 POST .../admin/bind-codes 发码。
  3. 宇恒:host_key 换票 / agents/mesync_bound;未绑则 lookup→确认 或 redeem。
  4. 本机库选「同步」→ Binding + agent drain不必手填 YXD_SYNC_CHANNEL_ID
  5. 同号联调用 13531041944,禁止超管号。

过渡 / 手建通道(仍可用)

  1. 智建本机起栈(.env 已是本地:AIJZ_PUBLIC_BASE_URL=http://127.0.0.1:8180)。
  2. 控制台建通道:remote 优先本机 Postgres;拷贝通道 ID。
  3. 拿 token管理员登录或智能体 client_credentials
  4. 宇恒设:
YXD_SYNC_MODE=local_dbsync
YXD_SYNC_AGENT=1
YXD_ONLINE_API_BASE=http://127.0.0.1:8180
YXD_SYNC_CHANNEL_ID=...
YXD_SYNC_ACCESS_TOKEN=...
  1. 对白名单表插一行 → 看本机 B + outbox → agent 推上 A → 比对 UUID。
  2. 多服务器演练:再建第二条通道指向另一 PostgresCHANNEL_ID 验证不串库。
  3. 空表sync 周期调 schema/ensure + schemaZ10c否则空表不会出现在线上。

11. 相关文档(智建仓)

文档 内容
松离线-dbsync方案-最终版.md 双方冻结方案
联调后修改意见-宇恒松离线.md 联调结论 + Z12/Z13 产品路径
docs/同步表约定.md UUID / 接口约定
docs/数据同步-开通说明.md 管理员开通
docs/数据同步-迁移手册.md 旧客迁移
docs/数据同步-中间件.md 平台能力与 API
docs/发版说明-数据同步.md 默认无感发版说明

分工提醒智建只提供平台通道、校验、push/whitelist/Binding/LWW/绑定 API写网关、outbox、agent、未开通回归、Z13 弹窗与选同步即用由宇恒在己方仓库实现,勿改智建仓业务代码。