feat: harden loose-offline sync for user JWT, schema, and console ops
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>
This commit is contained in:
@@ -35,6 +35,22 @@ CREATE TABLE IF NOT EXISTS orders (
|
||||
|
||||
形态 B 下本机 SQLite 可能不可达:至少 **remote** 须能完成校验。
|
||||
|
||||
### 线上库 A DSN(生产统一 Postgres)
|
||||
|
||||
```text
|
||||
postgres://user:pass@127.0.0.1:5432/app_online?sslmode=disable
|
||||
```
|
||||
|
||||
联调临时可用 SQLite(不宜多 writer):
|
||||
|
||||
```text
|
||||
file:E:/data/ajz_remote.db?_pragma=foreign_keys(1)
|
||||
```
|
||||
|
||||
使用正斜杠;平台会规范化反斜杠并补 `busy_timeout`。
|
||||
|
||||
单服务器一条通道即可;多服务器为**每台线上库各建一条通道**,客户端选用对应 `channel_id`。
|
||||
|
||||
## 宇恒开通(opt-in)
|
||||
|
||||
```bash
|
||||
@@ -55,6 +71,9 @@ YXD_SYNC_DBSYNC_TABLES=orders,order_items
|
||||
|
||||
推送落到通道 **remote**(线上 A),幂等认客户端 version;平台不连用户本机 SQLite。
|
||||
|
||||
**幂等**:同 version 再 push → `{ success:true, result:{ skipped:true, ... } }`;重复 HTTP 安全,线上不双行。
|
||||
**可重试**:remote IO 失败 → `503` + `retryable:true`(勿换 UUID)。
|
||||
|
||||
宇恒建议环境变量(由对方配置,不在智建仓改):
|
||||
|
||||
```bash
|
||||
|
||||
123
docs/数据同步-中间件.md
123
docs/数据同步-中间件.md
@@ -1,6 +1,7 @@
|
||||
# 跨库数据同步中间件
|
||||
|
||||
支持 **SQLite ↔ MySQL ↔ Postgres**。平台侧 worker 可轮询两端 outbox;**形态 B(本机 agent)** 下由终端 agent 经平台 **push** 写线上 A,平台**不直连用户本机 SQLite**。
|
||||
支持方言 **Postgres(生产线上库 A 推荐)** / MySQL / SQLite。
|
||||
**形态 B(本机 agent)**:终端 agent 经平台 **push** 写线上 A;平台**不直连用户本机 SQLite**。
|
||||
|
||||
冲突策略默认 **自动 LWW(源端覆盖)**;落败写入**平台超级管理员**覆盖日志。公司管理员**无冲突台**。
|
||||
|
||||
@@ -13,20 +14,29 @@
|
||||
| LWW 覆盖审计 | **仅平台超级管理员**;公司 top → 403 |
|
||||
| 数据隔离 | 通道带 `tenant_id`;公司 A 看不到公司 B |
|
||||
|
||||
## 推荐场景(松离线 B→A)
|
||||
## 推荐场景(松离线 B→A,A=Postgres)
|
||||
|
||||
| 端 | 角色 |
|
||||
|----|------|
|
||||
| **B** | 本机正式库(UUID 主键);开通且白名单表本地可见 |
|
||||
| **A** | 线上库;agent 经平台 push 幂等写入 |
|
||||
| **A** | 线上 **Postgres**(通道 `remote`);agent 经平台 push 幂等写入 |
|
||||
| **Agent** | 读本机 outbox → `POST /api/v1/agent/sync/channels/:id/push` |
|
||||
|
||||
推荐配置:
|
||||
|
||||
1. 通道:`local` 描述本机表名单,`remote` = A 的 DSN;方向 **`local_to_remote`**;策略 **`lww_source`**。
|
||||
2. 表白名单须 UUID TEXT PK + FK 闭包(保存时校验)。
|
||||
1. 通道:`remote.driver=postgres` + DSN;方向 **`local_to_remote`**;策略 **`lww_source`**。
|
||||
2. 表白名单须 UUID TEXT/UUID PK + FK 闭包(保存时校验)。
|
||||
3. 客户端显式 `local_dbsync`(见开通说明);未开通用户零感。
|
||||
4. 怀疑漏数时点 **同步修复(对账)**(有最小间隔限流);双向通道自动对账约 **15 分钟** 一次。
|
||||
4. 怀疑漏数时点 **同步修复(对账)**(有最小间隔限流)。
|
||||
|
||||
### 单服务器 / 多服务器(同一套 API)
|
||||
|
||||
| 部署 | 配置 |
|
||||
|------|------|
|
||||
| 单服务器 | 一条通道指向该 Postgres;一个 `channel_id` |
|
||||
| 多服务器 | **每库一条通道**(不同 remote DSN);客户端按库选择 `channel_id` / Binding |
|
||||
|
||||
协议相同,均为「经智建 push」,无需第二套接口。
|
||||
|
||||
如何保证**不漏、不多**:
|
||||
|
||||
@@ -42,22 +52,104 @@
|
||||
|
||||
| 项 | 说明 |
|
||||
|----|------|
|
||||
| 方言 | `sqlite` / `mysql` / `postgres` |
|
||||
| 方言 | 生产 A:`postgres`;兼容 `mysql`;`sqlite` 仅联调临时 A(不宜多 writer) |
|
||||
| 方向 | 本地→线上(推荐)/ 线上→本地 / 双向 |
|
||||
| 冲突 | 默认 `lww_source`;`lww_target`;`queue` 仅调试(租户不可见) |
|
||||
| Agent | `GET .../agent/sync/.../whitelist`;`POST .../push` |
|
||||
| Binding | `GET/POST /api/v1/admin/sync/bindings` |
|
||||
| 对账 | `POST .../reconcile`(默认最少间隔 300s) |
|
||||
| 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`、`lww_overrides.json`(及遗留 `conflicts.json`)。
|
||||
持久化:`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. **数据同步** → 新建通道(UUID 表白名单)→ 测试 → 保存 → 启动
|
||||
1. **数据同步** → 新建通道(remote 选 **postgres**)→ 测试 → 保存 → 启动
|
||||
2. 默认策略选 **源端覆盖**;方向首期用 **本地 → 线上**
|
||||
3. 点 **同步修复** 做主键对账(勿连续狂点,有限流)
|
||||
4. LWW 明细在 **平台工作台**(超管),不在公司同步页
|
||||
5. 多服务器:为每台库再新建一条通道,把对应 `channel_id` 发给客户端
|
||||
|
||||
开通与迁移:见 [数据同步-开通说明.md](./数据同步-开通说明.md)、[数据同步-迁移手册.md](./数据同步-迁移手册.md)。
|
||||
|
||||
@@ -76,13 +168,18 @@
|
||||
| GET/POST | `/api/v1/admin/sync/bindings` |
|
||||
| GET/POST | `/api/v1/admin/sync/conflicts*` → **403**(已迁超管审计) |
|
||||
|
||||
### 本机 Agent
|
||||
### 本机 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`(用户自助亦可) |
|
||||
|
||||
### 平台超级管理员
|
||||
|
||||
|
||||
@@ -13,19 +13,70 @@
|
||||
|
||||
智建控制台「数据同步」配的是**通道 + 表白名单 + 线上 DSN**;是否走 `local_dbsync` 由客户端环境变量决定,平台**不会**替全员切默认。
|
||||
|
||||
## 线上库 A:统一 Postgres
|
||||
|
||||
| 场景 | 推荐 |
|
||||
|------|------|
|
||||
| **生产 A** | **`postgres`**(经智建 agent push 写入;可与平台同机或独立实例) |
|
||||
| 本机 B | 宇恒侧多为 SQLite(通道 `local` 仅描述表白名单,形态 B 下平台可不连 B) |
|
||||
| 联调临时 A | 可用 sqlite 文件;**不宜**多 agent 并发写 |
|
||||
|
||||
Postgres DSN 示例:
|
||||
|
||||
```text
|
||||
postgres://user:pass@127.0.0.1:5432/app_online?sslmode=disable
|
||||
```
|
||||
|
||||
## 单服务器 / 多服务器(同一套 API)
|
||||
|
||||
同步**一律经智建平台**(agent → whitelist/push),不因服务器数量改协议。
|
||||
|
||||
| 部署 | 怎么配 |
|
||||
|------|--------|
|
||||
| **单服务器** | 公司一条通道:`remote` 指向该 Postgres;宇恒配置一个 `YXD_SYNC_CHANNEL_ID` |
|
||||
| **多服务器** | **每台线上库一条通道**(不同 remote DSN);宇恒按 `local_database_id` / Binding 选用对应 `channel_id`(或多 agent 实例各绑一通道) |
|
||||
|
||||
Binding:`POST /api/v1/admin/sync/bindings` 登记 `local_database_id → online_db_id`,并带上 `channel_id`。
|
||||
管理员 Token 或**登录用户 JWT**均可登记;普通用户只能登记/查看本人 Binding。
|
||||
|
||||
## 模块与本机库(一份表 · Z8)
|
||||
|
||||
| 原则 | 说明 |
|
||||
|------|------|
|
||||
| **本机 B 管理面** | 模块业务表应能在宇恒本机库(如「AI建站智能体API」)里增删改查/导入,与普通表同一体验 |
|
||||
| **上云** | 开通 sync 后经 outbox → agent push → 智能体绑定的线上库 A |
|
||||
| **智能体绑库** | 控制台「用户管理」为智能体填 `channel_id` / `online_db_id` / `database_name`;「数据同步」可按智能体筛通道 |
|
||||
| **发布落库** | 智能体 Token 新建发布时,若已设 `database_name`,优先 `database_per_app` 写入该库 |
|
||||
|
||||
不要默认维持「模块只在平台 `/apps` schema、松离线又一套 SQLite」双轨;对账时本机有、线上无 = 尚未 push,不是串库。
|
||||
|
||||
## 用户自助(库级三态)
|
||||
|
||||
客户端对每个库可选:仅本地 / 仅线上 / 同步。选同步后:
|
||||
|
||||
1. 用户 JWT → `POST /api/v1/admin/sync/bindings`(带 `channel_id`)
|
||||
2. 同 JWT → `GET .../agent/sync/channels/{id}/whitelist`
|
||||
3. 同 JWT → `POST .../push`,Body 须含本人 `online_db_id`
|
||||
|
||||
通道表白名单**非空**时只同步命中表;**空名单**时平台接受整库表名(UUID PK)。
|
||||
「仅线上 / 下行拉齐」:`POST .../agent/sync/channels/{id}/pull`(`mode=bootstrap|pks|rows`)或 `.../bootstrap`;平台返回 A 上行,客户端写入本机 B。
|
||||
**空表也要两侧建齐**:本机空表 → `POST .../schema/ensure`;线上空表 → `POST .../schema`(或 pull 的 `columns`)在本机 `CREATE IF NOT EXISTS`。仅靠 outbox 行 push **不会**带上空表。
|
||||
控制台「查看线上表」可 **删表**(`POST .../admin/sync/channels/{id}/drop-table`):只删当前查看侧,**不同步** `DROP` 到另一侧;本机仍有同名表时下次 push/ensure 可能再建回来。
|
||||
|
||||
## 开通步骤(增值)
|
||||
|
||||
1. 公司管理员在「数据同步」建通道:`local`(本机 B 描述)+ `remote`(线上 A DSN),表白名单须 **UUID TEXT PK** + FK 闭包。
|
||||
2. 默认方向 **本地 → 线上**,冲突策略 **源端覆盖(lww_source)**。
|
||||
3. 客户端显式设 `YXD_SYNC_MODE=local_dbsync`,并配置通道 ID / token(见 `同步表约定.md`)。
|
||||
4. 装本机 sync agent 后变更才会上云;未装 agent:**本地可保存**,文案须为「需 agent 才上云」。
|
||||
5. Binding(可选):登记 `local_database_id → online_db_id`,见 API `/api/v1/admin/sync/bindings`。
|
||||
1. 「数据同步」新建通道:`remote.driver=postgres` + 线上 DSN;表白名单须 **UUID TEXT/UUID PK** + FK 闭包。
|
||||
2. 方向 **本地 → 线上**,策略 **源端覆盖(lww_source)**。
|
||||
3. 客户端显式 `YXD_SYNC_MODE=local_dbsync`,配置 `YXD_ONLINE_API_BASE`、`YXD_SYNC_CHANNEL_ID`、token(见 `同步表约定.md`)。
|
||||
4. 装本机 sync agent 后才上云;未装:**本地可保存**,文案「需 agent 才上云」。
|
||||
5. 多库时登记 Binding,避免串库。
|
||||
|
||||
## 谁能看什么
|
||||
|
||||
| 角色 | 可见 |
|
||||
|------|------|
|
||||
| 公司管理员 | 通道配置、对账(同步修复)、统计;**无**冲突台 / LWW 覆盖明细 |
|
||||
| 普通登录用户 | 不可配通道;可自助 Binding + 用登录 JWT push 本人库 |
|
||||
| 平台超级管理员 | LWW 覆盖审计(平台工作台) |
|
||||
| 未开通终端用户 | **零同步文案**,无强制状态条 |
|
||||
|
||||
|
||||
Reference in New Issue
Block a user