Enable Binding-scoped agent push/pull, empty-table schema ensure, SyncPage inspect/drop-table, default module import, and agent-bound publish docs from the 宇恒联调意见. Co-authored-by: Cursor <cursoragent@cursor.com>
210 lines
8.5 KiB
Markdown
210 lines
8.5 KiB
Markdown
# 跨库数据同步中间件
|
||
|
||
支持方言 **Postgres(生产线上库 A 推荐)** / MySQL / SQLite。
|
||
**形态 B(本机 agent)**:终端 agent 经平台 **push** 写线上 A;平台**不直连用户本机 SQLite**。
|
||
|
||
冲突策略默认 **自动 LWW(源端覆盖)**;落败写入**平台超级管理员**覆盖日志。公司管理员**无冲突台**。
|
||
|
||
## 权限与隔离
|
||
|
||
| 项 | 说明 |
|
||
|----|------|
|
||
| 谁可配通道 | 公司**顶级权限(管理员)**,「数据同步」 |
|
||
| Agent push / 拉白名单 | JWT 含「数据同步」(人类管理员或智能体凭证) |
|
||
| LWW 覆盖审计 | **仅平台超级管理员**;公司 top → 403 |
|
||
| 数据隔离 | 通道带 `tenant_id`;公司 A 看不到公司 B |
|
||
|
||
## 推荐场景(松离线 B→A,A=Postgres)
|
||
|
||
| 端 | 角色 |
|
||
|----|------|
|
||
| **B** | 本机正式库(UUID 主键);开通且白名单表本地可见 |
|
||
| **A** | 线上 **Postgres**(通道 `remote`);agent 经平台 push 幂等写入 |
|
||
| **Agent** | 读本机 outbox → `POST /api/v1/agent/sync/channels/:id/push` |
|
||
|
||
推荐配置:
|
||
|
||
1. 通道:`remote.driver=postgres` + DSN;方向 **`local_to_remote`**;策略 **`lww_source`**。
|
||
2. 表白名单须 UUID TEXT/UUID PK + FK 闭包(保存时校验)。
|
||
3. 客户端显式 `local_dbsync`(见开通说明);未开通用户零感。
|
||
4. 怀疑漏数时点 **同步修复(对账)**(有最小间隔限流)。
|
||
|
||
### 单服务器 / 多服务器(同一套 API)
|
||
|
||
| 部署 | 配置 |
|
||
|------|------|
|
||
| 单服务器 | 一条通道指向该 Postgres;一个 `channel_id` |
|
||
| 多服务器 | **每库一条通道**(不同 remote DSN);客户端按库选择 `channel_id` / Binding |
|
||
|
||
协议相同,均为「经智建 push」,无需第二套接口。
|
||
|
||
如何保证**不漏、不多**:
|
||
|
||
| 手段 | 防什么 |
|
||
|------|--------|
|
||
| 触发器 / 本机 outbox → agent push | 漏 |
|
||
| `WithApplying` / 远端应用不回写 outbox | 多(回声) |
|
||
| 目标 meta 同 version 跳过 | 多(重复投递) |
|
||
| LWW + 超管覆盖日志 | 并发同 PK |
|
||
| 主键对账 reconcile | 漏(存量差) |
|
||
|
||
## 能力一览
|
||
|
||
| 项 | 说明 |
|
||
|----|------|
|
||
| 方言 | 生产 A:`postgres`;兼容 `mysql`;`sqlite` 仅联调临时 A(不宜多 writer) |
|
||
| 方向 | 本地→线上(推荐)/ 线上→本地 / 双向 |
|
||
| 冲突 | 默认 `lww_source`;`lww_target`;`queue` 仅调试(租户不可见) |
|
||
| Agent | `GET .../agent/sync/.../whitelist`;`POST .../push`(管理员/智能体「数据同步」,或登录用户 + 本人 Binding) |
|
||
| Binding | `GET/POST /api/v1/admin/sync/bindings`(同上鉴权;普通用户仅本人) |
|
||
| 对账 | `POST .../reconcile`(默认最少间隔 300s;仍需「数据同步」) |
|
||
| 审计 | `GET /api/v1/platform/dbsync/lww-overrides`(超管) |
|
||
|
||
持久化:`data/dbsync/channels.json`、`bindings.json`、`lww_overrides.json`(及遗留 `conflicts.json`)。
|
||
|
||
## 用户自助同步(库级三态)
|
||
|
||
终端用户可在客户端对名下库选择仅本地 / 仅线上 / 同步;选 **同步** 后用**登录 JWT**(无需单独「数据同步」管理员权)调 agent API。
|
||
|
||
| 角色 | 范围 |
|
||
|------|------|
|
||
| 「数据同步」管理员 / 智能体 | 租户内通道;push 不强制 Binding |
|
||
| 普通登录用户 | 仅本人 Binding:`channel_id` + `online_db_id`;push **须带** `online_db_id` |
|
||
|
||
### 表白名单策略(Z4,已冻结)
|
||
|
||
| 项 | 行为 |
|
||
|----|------|
|
||
| **权限源** | 登录 JWT + 本人 Binding 的 `online_db_id`(不是通道 `tables`) |
|
||
| **push** | 接受 Binding 库内**任意表**;表不存在时**自动建表**(TEXT 列 + PK) |
|
||
| **通道 tables** | 仅历史兼容 / whitelist 接口展示;**不拒收** |
|
||
|
||
管理员不必再为业务表维护白名单。宇恒默认 `YXD_SYNC_ENFORCE_WHITELIST=0`。
|
||
|
||
### 下行 / 仅线上(Z5)
|
||
|
||
形态 B:平台**不直连本机 B**,只读线上 A,把行返回给本机 agent 写入 B(建议 `WithApplying` 防回声)。
|
||
|
||
| mode | 用途 |
|
||
|------|------|
|
||
| `bootstrap` | 全量分页灌库(「仅线上」首启 / 本地空库) |
|
||
| `pks` | 只列 A 上主键,客户端 diff 后再 `rows` |
|
||
| `rows` | 按 `row_pks` 取行补齐 |
|
||
|
||
```http
|
||
POST /api/v1/agent/sync/channels/{id}/pull
|
||
POST /api/v1/agent/sync/channels/{id}/bootstrap # 强制 mode=bootstrap
|
||
```
|
||
|
||
```json
|
||
{
|
||
"mode": "bootstrap",
|
||
"table": "orders",
|
||
"after_pk": "",
|
||
"limit": 200,
|
||
"online_db_id": "用户自助必填"
|
||
}
|
||
```
|
||
|
||
循环直到 `result.has_more=false`。单页默认 200、上限 500。
|
||
|
||
## Agent push 幂等与重复投递
|
||
|
||
- **正确性**:同 `(table, row_pk, version)` 已落地 → `result.skipped=true` 且 HTTP 2xx;线上 A **不双行**。
|
||
- **允许**客户端重复 push(例如多 agent / 双 drain);去重是**客户端优化**(进程内 drain 锁),不是平台必做。
|
||
- 通道 stats:`pushed_applied` / `pushed_skipped` 可观察重推噪声。
|
||
- remote 暂不可达:HTTP **503** + `retryable: true`(agent 可稍后重试,勿换 UUID)。
|
||
|
||
- **Postgres A** 适合正常并发;若联调仍用 SQLite 作 A,请客户端**按库串行 drain**。
|
||
- 平台对 remote 连接做**池化复用 + 通道启动预热**,避免每次 push 冷开 SQLite 触发超时。
|
||
|
||
## 请求日志怎么查
|
||
|
||
| 来源 | 位置 / 接口 | 内容 |
|
||
|------|-------------|------|
|
||
| Platform HTTP | `.runtime/logs/platform.err.log`(go-zero) | 路径、耗时、状态码;超时曾见 `slowcall(3000ms)` |
|
||
| Platform dbsync | 同文件,关键字 `dbsync push` / `dbsync pull` | channel、table、pk、user、outcome、dur、`req=`(X-Request-Id) |
|
||
| Gateway | `.runtime/logs/gateway.out.log` | sync 路径与 ≥500:`gateway POST /api/... status=... req=` |
|
||
| 审计 API | `GET /api/v1/audit/logs`(需「查看审计」) | `action` 含 `dbsync.push` / `dbsync.pull` / `*.error`,detail JSON 带 `req_id` |
|
||
|
||
联调时用响应头 / 请求头 `X-Request-Id` 可在三处日志对齐同一笔请求。
|
||
|
||
## 线上 Postgres DSN 示例
|
||
|
||
```text
|
||
postgres://sync_user:pass@127.0.0.1:5432/app_online?sslmode=disable
|
||
```
|
||
|
||
联调临时 SQLite A(不推荐生产):
|
||
|
||
```text
|
||
file:E:/path/to/remote.db?_pragma=foreign_keys(1)
|
||
```
|
||
|
||
勿用反斜杠;平台会自动规范化并补 `busy_timeout`。
|
||
|
||
## 控制台用法
|
||
|
||
1. **数据同步** → 新建通道(remote 选 **postgres**)→ 测试 → 保存 → 启动
|
||
2. 默认策略选 **源端覆盖**;方向首期用 **本地 → 线上**
|
||
3. 点 **同步修复** 做主键对账(勿连续狂点,有限流)
|
||
4. LWW 明细在 **平台工作台**(超管),不在公司同步页
|
||
5. 多服务器:为每台库再新建一条通道,把对应 `channel_id` 发给客户端
|
||
|
||
开通与迁移:见 [数据同步-开通说明.md](./数据同步-开通说明.md)、[数据同步-迁移手册.md](./数据同步-迁移手册.md)。
|
||
|
||
## API
|
||
|
||
### 公司管理员(「数据同步」)
|
||
|
||
| 方法 | 路径 |
|
||
|------|------|
|
||
| GET/POST | `/api/v1/admin/sync/channels` |
|
||
| GET/PUT/DELETE | `/api/v1/admin/sync/channels/{id}` |
|
||
| POST | `/api/v1/admin/sync/test` |
|
||
| POST | `/api/v1/admin/sync/channels/{id}/prepare\|start\|stop` |
|
||
| POST | `/api/v1/admin/sync/channels/{id}/reconcile` |
|
||
| POST | `/api/v1/admin/sync/channels/{id}/ingest`(写通道 local,偏形态 A) |
|
||
| GET/POST | `/api/v1/admin/sync/bindings` |
|
||
| GET/POST | `/api/v1/admin/sync/conflicts*` → **403**(已迁超管审计) |
|
||
|
||
### 本机 Agent / 用户自助
|
||
|
||
鉴权:Bearer = 「数据同步」**或** 已登录人类 JWT(后者须先 `POST .../bindings` 且 push 带本人 `online_db_id`)。
|
||
|
||
| 方法 | 路径 |
|
||
|------|------|
|
||
| GET | `/api/v1/agent/sync/channels/{id}/whitelist` |
|
||
| POST | `/api/v1/agent/sync/channels/{id}/push` |
|
||
| POST | `/api/v1/agent/sync/channels/{id}/push/batch` |
|
||
| POST | `/api/v1/agent/sync/channels/{id}/pull` |
|
||
| POST | `/api/v1/agent/sync/channels/{id}/bootstrap` |
|
||
| GET/POST | `/api/v1/admin/sync/bindings`(用户自助亦可) |
|
||
|
||
### 平台超级管理员
|
||
|
||
| 方法 | 路径 |
|
||
|------|------|
|
||
| GET | `/api/v1/platform/dbsync/lww-overrides` |
|
||
| POST | `/api/v1/platform/dbsync/lww-overrides/{id}/rollback` |
|
||
|
||
### push 示例
|
||
|
||
```json
|
||
POST /api/v1/agent/sync/channels/{id}/push
|
||
{
|
||
"table": "orders",
|
||
"op": "insert",
|
||
"row_pk": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee",
|
||
"row": { "id": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee", "title": "x" },
|
||
"version": 1710000000000000000,
|
||
"client_outbox_id": "..."
|
||
}
|
||
```
|
||
|
||
## 相关
|
||
|
||
- [同步表约定.md](./同步表约定.md)
|
||
- [数据同步-开通说明.md](./数据同步-开通说明.md)
|
||
- [数据同步-迁移手册.md](./数据同步-迁移手册.md)
|