feat: ship loose-offline dbsync (validate, agent push, LWW audit)
Add UUID/FK channel checks, agent whitelist/push APIs, bindings, super-admin LWW audit with rollback, reconcile rate limits, and sync docs. Default customers stay opt-in; company conflict UI is removed. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -6,7 +6,11 @@
|
||||
|------|------|
|
||||
| [智能体-生成发布-能力说明.md](./智能体-生成发布-能力说明.md) | **需求 / 能力边界**(做什么、不做什么、工作流、验收) |
|
||||
| [智能体-生成发布-API.md](./智能体-生成发布-API.md) | **接口契约**(路径、请求/响应、宿主回执字段) |
|
||||
| [数据同步-中间件.md](./数据同步-中间件.md) | 跨库实时同步(SQLite/MySQL/Postgres) |
|
||||
| [数据同步-中间件.md](./数据同步-中间件.md) | 跨库同步能力与 API(LWW / agent push) |
|
||||
| [数据同步-开通说明.md](./数据同步-开通说明.md) | 管理员开通:默认无感、opt-in |
|
||||
| [数据同步-迁移手册.md](./数据同步-迁移手册.md) | O7:旧客从双写迁入(禁止静默) |
|
||||
| [同步表约定.md](./同步表约定.md) | UUID 同步表模板与 agent 接口约定 |
|
||||
| [发版说明-数据同步.md](./发版说明-数据同步.md) | 默认无感 / 增值开通发版摘录 |
|
||||
|
||||
> 业务用语称「**模块**」。HTTP 路径仍为 `/api/v1/apps/...`。智能体默认无需配置模块白名单即可自建发布。
|
||||
|
||||
|
||||
35
docs/发版说明-数据同步.md
Normal file
35
docs/发版说明-数据同步.md
Normal file
@@ -0,0 +1,35 @@
|
||||
# 发版说明 · 数据同步(智建平台)
|
||||
|
||||
## 对默认客户
|
||||
|
||||
- **无感**:未显式开通时,不改变现有保存 / 自增表行为。
|
||||
- `local_dbsync` 为**增值开通**(客户端显式配置);平台**不会**全员切默认。
|
||||
|
||||
## 本版本智建已交付
|
||||
|
||||
| 能力 | 说明 |
|
||||
|------|------|
|
||||
| 通道校验 | UUID TEXT PK + FK 闭包;拒绝自增整数作同步键 |
|
||||
| Agent API | 拉白名单、push / push batch → 线上 A |
|
||||
| Binding | `local_database_id ↔ online_db_id` 登记查询 |
|
||||
| LWW | 默认源端覆盖;超管审计 + 单行回滚;公司 conflicts → 403 |
|
||||
| 同步修复 | 对账限流(默认 300s) |
|
||||
|
||||
## 管理员文档
|
||||
|
||||
- [数据同步-开通说明.md](./数据同步-开通说明.md)
|
||||
- [数据同步-迁移手册.md](./数据同步-迁移手册.md)(旧客迁入,禁止静默)
|
||||
- [同步表约定.md](./同步表约定.md)
|
||||
- [数据同步-中间件.md](./数据同步-中间件.md)
|
||||
|
||||
## 配置(platform)
|
||||
|
||||
```yaml
|
||||
DBSync:
|
||||
Enabled: true
|
||||
DataDir: ./data/dbsync
|
||||
LwwAuditTTLDays: 90
|
||||
ReconcileMinSec: 300
|
||||
```
|
||||
|
||||
客户端 agent / 写网关由对接方自行落地;接口以本文档与 OpenAPI `/api/v1/meta/openapi.yaml` 为准。
|
||||
76
docs/同步表约定.md
Normal file
76
docs/同步表约定.md
Normal file
@@ -0,0 +1,76 @@
|
||||
# 同步表约定(M0)
|
||||
|
||||
> 依据:`松离线-dbsync方案-最终版.md`
|
||||
> 默认建表模板**仍为自增**;仅「同步表」使用下列模板。
|
||||
|
||||
## 同步表模板(UUID 主键)
|
||||
|
||||
SQLite 示例:
|
||||
|
||||
```sql
|
||||
CREATE TABLE IF NOT EXISTS orders (
|
||||
id TEXT PRIMARY KEY NOT NULL, -- UUID,小写带连字符
|
||||
-- ... 业务列 ...
|
||||
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
);
|
||||
```
|
||||
|
||||
Postgres 示例:
|
||||
|
||||
```sql
|
||||
CREATE TABLE IF NOT EXISTS orders (
|
||||
id UUID PRIMARY KEY, -- 或 TEXT
|
||||
-- ... 业务列 ...
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
```
|
||||
|
||||
## 入通道校验(平台已启用)
|
||||
|
||||
保存 / 启用同步通道时:
|
||||
|
||||
1. 白名单非空
|
||||
2. 可达端(通常为 **remote 线上库**)上,各表主键列须为 **TEXT/VARCHAR/UUID** 类(拒绝 INTEGER/SERIAL)
|
||||
3. **外键闭包**:白名单内表若引用名单外表(或反之一侧在名单),保存失败
|
||||
|
||||
形态 B 下本机 SQLite 可能不可达:至少 **remote** 须能完成校验。
|
||||
|
||||
## 宇恒开通(opt-in)
|
||||
|
||||
```bash
|
||||
YXD_SYNC_MODE=local_dbsync
|
||||
YXD_SYNC_DBSYNC_TABLES=orders,order_items
|
||||
# 或写入 cache/db_sync/whitelist.json: {"tables":["orders","order_items"]}
|
||||
```
|
||||
|
||||
未设置 `local_dbsync` 时行为与现网一致。
|
||||
|
||||
## M2 本机 agent(B→A)— 智建已提供的接口
|
||||
|
||||
宇恒侧自行实现 agent;智建只提供下列 API(需「数据同步」权限):
|
||||
|
||||
- `GET /api/v1/agent/sync/channels/:id/whitelist`
|
||||
- `POST /api/v1/agent/sync/channels/:id/push` — body: `{table,op,row_pk,row,version,client_outbox_id}`
|
||||
- `POST /api/v1/agent/sync/channels/:id/push/batch`
|
||||
|
||||
推送落到通道 **remote**(线上 A),幂等认客户端 version;平台不连用户本机 SQLite。
|
||||
|
||||
宇恒建议环境变量(由对方配置,不在智建仓改):
|
||||
|
||||
```bash
|
||||
YXD_SYNC_MODE=local_dbsync
|
||||
YXD_SYNC_AGENT=1
|
||||
YXD_ONLINE_API_BASE=https://aisite.example.com
|
||||
YXD_SYNC_CHANNEL_ID=<通道ID>
|
||||
YXD_SYNC_ACCESS_TOKEN=<含「数据同步」权限的 JWT 或 agent token>
|
||||
```
|
||||
|
||||
## M3 超管 LWW 审计(智建)
|
||||
|
||||
- 存储:`data/dbsync/lww_overrides.json`(与租户 conflicts 分离)
|
||||
- API:`GET /api/v1/platform/dbsync/lww-overrides`(**仅平台超级管理员**)
|
||||
- 公司侧:`/api/v1/admin/sync/conflicts*` → **403**
|
||||
- 双入口写入:worker `drain` + agent `push` 在 LWW 覆盖/保留时记审计
|
||||
- TTL:默认 90 天(`DBSync.LwwAuditTTLDays`)
|
||||
- 对账限流:手动默认 300 秒(`DBSync.ReconcileMinSec`)
|
||||
- 超管回滚:`POST /api/v1/platform/dbsync/lww-overrides/:id/rollback`(仅 `applied_source`;按 `loser_payload` 写回线上 A)
|
||||
122
docs/数据同步-中间件.md
122
docs/数据同步-中间件.md
@@ -1,69 +1,69 @@
|
||||
# 跨库数据同步中间件
|
||||
|
||||
支持 **SQLite ↔ MySQL ↔ Postgres**,不要求两端同一种数据库。变更经 **outbox 队列** 近实时投递;冲突进 **冲突队列**。
|
||||
支持 **SQLite ↔ MySQL ↔ Postgres**。平台侧 worker 可轮询两端 outbox;**形态 B(本机 agent)** 下由终端 agent 经平台 **push** 写线上 A,平台**不直连用户本机 SQLite**。
|
||||
|
||||
冲突策略默认 **自动 LWW(源端覆盖)**;落败写入**平台超级管理员**覆盖日志。公司管理员**无冲突台**。
|
||||
|
||||
## 权限与隔离
|
||||
|
||||
| 项 | 说明 |
|
||||
|----|------|
|
||||
| 谁可配 | 仅公司**顶级权限(管理员)**,权限名「数据同步」 |
|
||||
| 谁不可 | 编辑 / 只读、智能体账号(即使有「发布模块」) |
|
||||
| 数据隔离 | 通道与冲突带 `tenant_id`;公司 A 看不到公司 B 的通道/DSN |
|
||||
| 多服务器 | 同一公司可建多条通道,分别填 B、C 等库的 DSN |
|
||||
| 谁可配通道 | 公司**顶级权限(管理员)**,「数据同步」 |
|
||||
| Agent push / 拉白名单 | JWT 含「数据同步」(人类管理员或智能体凭证) |
|
||||
| LWW 覆盖审计 | **仅平台超级管理员**;公司 top → 403 |
|
||||
| 数据隔离 | 通道带 `tenant_id`;公司 A 看不到公司 B |
|
||||
|
||||
## 典型场景:A / B / C
|
||||
## 推荐场景(松离线 B→A)
|
||||
|
||||
| 端 | 角色 |
|
||||
|----|------|
|
||||
| **A** | 线上库(用户增删改) |
|
||||
| **B** | 本地库(本机业务 + 接收 C) |
|
||||
| **C** | 额外数据源(Excel / API / 导入),只写入 **B** |
|
||||
| **B** | 本机正式库(UUID 主键);开通且白名单表本地可见 |
|
||||
| **A** | 线上库;agent 经平台 push 幂等写入 |
|
||||
| **Agent** | 读本机 outbox → `POST /api/v1/agent/sync/channels/:id/push` |
|
||||
|
||||
推荐配置:
|
||||
|
||||
1. 建一条通道:`local` = B,`remote` = A,**方向 `bidirectional`**,冲突策略 `queue`(或 LWW)。
|
||||
2. C 的数据用 **ingest API**(或业务直接写 B)写入本地;触发器进 outbox,再推到 A。
|
||||
3. A 上用户改的数据经 outbox 拉回 B。
|
||||
4. 怀疑漏数时点 **对账**,或等双向通道约每分钟自动对账。
|
||||
1. 通道:`local` 描述本机表名单,`remote` = A 的 DSN;方向 **`local_to_remote`**;策略 **`lww_source`**。
|
||||
2. 表白名单须 UUID TEXT PK + FK 闭包(保存时校验)。
|
||||
3. 客户端显式 `local_dbsync`(见开通说明);未开通用户零感。
|
||||
4. 怀疑漏数时点 **同步修复(对账)**(有最小间隔限流);双向通道自动对账约 **15 分钟** 一次。
|
||||
|
||||
如何保证**不漏、不多**:
|
||||
|
||||
| 手段 | 防什么 |
|
||||
|------|--------|
|
||||
| 表触发器 → `_ajz_sync_outbox` | 漏(本地/线上变更必入队) |
|
||||
| 应用远端时 `WithApplying`(触发器不写 outbox) | 多(A↔B 回声环) |
|
||||
| 目标 meta 版本相等则跳过 | 多(重复投递) |
|
||||
| 目标版本更新 → 冲突队列 / LWW | 并发改同一行 |
|
||||
| 主键对账 reconcile | 漏(存量差、触发器未装前的行) |
|
||||
| C→B upsert 同主键 | 多(重复灌入) |
|
||||
| 触发器 / 本机 outbox → agent push | 漏 |
|
||||
| `WithApplying` / 远端应用不回写 outbox | 多(回声) |
|
||||
| 目标 meta 同 version 跳过 | 多(重复投递) |
|
||||
| LWW + 超管覆盖日志 | 并发同 PK |
|
||||
| 主键对账 reconcile | 漏(存量差) |
|
||||
|
||||
```
|
||||
C ──ingest/写库──► B (local) ◄──bidirectional outbox──► A (remote)
|
||||
```
|
||||
|
||||
## 能力
|
||||
## 能力一览
|
||||
|
||||
| 项 | 说明 |
|
||||
|----|------|
|
||||
| 方言 | `sqlite` / `mysql` / `postgres` |
|
||||
| 实时性 | 表触发器写 `_ajz_sync_outbox`,worker 默认每 500ms 拉取 |
|
||||
| 方向 | 本地→线上 / 线上→本地 / **双向**(A↔B 场景用这个) |
|
||||
| 冲突 | `queue`(入队)/ `lww_source` / `lww_target` |
|
||||
| 对账 | `POST .../reconcile`;双向运行中约每分钟自动一次 |
|
||||
| 外部源 | `POST .../ingest`:C → B,再同步到 A |
|
||||
| 配置 | 控制台「数据同步」页;可改线上 DSN |
|
||||
| 方向 | 本地→线上(推荐)/ 线上→本地 / 双向 |
|
||||
| 冲突 | 默认 `lww_source`;`lww_target`;`queue` 仅调试(租户不可见) |
|
||||
| Agent | `GET .../agent/sync/.../whitelist`;`POST .../push` |
|
||||
| Binding | `GET/POST /api/v1/admin/sync/bindings` |
|
||||
| 对账 | `POST .../reconcile`(默认最少间隔 300s) |
|
||||
| 审计 | `GET /api/v1/platform/dbsync/lww-overrides`(超管) |
|
||||
|
||||
配置与冲突持久化:`data/dbsync/channels.json`、`conflicts.json`(Docker:`.runtime/dbsync`)。
|
||||
持久化:`data/dbsync/channels.json`、`lww_overrides.json`(及遗留 `conflicts.json`)。
|
||||
|
||||
## 控制台用法
|
||||
|
||||
1. 登录 → **数据同步** → **新建通道**
|
||||
2. 本地 B:如 `sqlite` + `file:./data/local.db`,表名逗号分隔
|
||||
3. 线上 A:`mysql` + `user:pass@tcp(host:3306)/db?parseTime=true`
|
||||
4. 方向选 **双向** → **测试连接** → **保存** → **启动**
|
||||
5. 需要补漏时点 **对账**;C 数据走业务写 B 或调用 ingest API
|
||||
1. **数据同步** → 新建通道(UUID 表白名单)→ 测试 → 保存 → 启动
|
||||
2. 默认策略选 **源端覆盖**;方向首期用 **本地 → 线上**
|
||||
3. 点 **同步修复** 做主键对账(勿连续狂点,有限流)
|
||||
4. LWW 明细在 **平台工作台**(超管),不在公司同步页
|
||||
|
||||
## API(需公司顶级权限「数据同步」/ 管理员)
|
||||
开通与迁移:见 [数据同步-开通说明.md](./数据同步-开通说明.md)、[数据同步-迁移手册.md](./数据同步-迁移手册.md)。
|
||||
|
||||
## API
|
||||
|
||||
### 公司管理员(「数据同步」)
|
||||
|
||||
| 方法 | 路径 |
|
||||
|------|------|
|
||||
@@ -72,29 +72,41 @@ C ──ingest/写库──► B (local) ◄──bidirectional outbox──►
|
||||
| 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` |
|
||||
| GET | `/api/v1/admin/sync/conflicts` |
|
||||
| POST | `/api/v1/admin/sync/conflicts/{id}/resolve` |
|
||||
| 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**(已迁超管审计) |
|
||||
|
||||
### ingest 示例
|
||||
### 本机 Agent
|
||||
|
||||
| 方法 | 路径 |
|
||||
|------|------|
|
||||
| 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` |
|
||||
|
||||
### 平台超级管理员
|
||||
|
||||
| 方法 | 路径 |
|
||||
|------|------|
|
||||
| GET | `/api/v1/platform/dbsync/lww-overrides` |
|
||||
| POST | `/api/v1/platform/dbsync/lww-overrides/{id}/rollback` |
|
||||
|
||||
### push 示例
|
||||
|
||||
```json
|
||||
POST /api/v1/admin/sync/channels/{id}/ingest
|
||||
POST /api/v1/agent/sync/channels/{id}/push
|
||||
{
|
||||
"table": "article",
|
||||
"source": "excel",
|
||||
"rows": [
|
||||
{ "id": "c-001", "title": "来自 C" }
|
||||
]
|
||||
"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": "..."
|
||||
}
|
||||
```
|
||||
|
||||
按主键 upsert 写入本地 B,触发器入 outbox,worker 再推到线上 A。
|
||||
## 相关
|
||||
|
||||
## 注意
|
||||
|
||||
- 两端业务表结构需兼容(同名列);主键默认 `id`,可用 `pk_columns` 覆盖。
|
||||
- MySQL 需账号有建触发器权限。
|
||||
- 密钥在 DSN 中;列表页会打码显示。
|
||||
- 「实时」为亚秒级轮询 + 触发器,非 MySQL binlog CDC;同机延迟通常 <1s。
|
||||
- 对账按**主键集合**补缺行,不做逐字段内容 diff;同 PK 内容冲突仍靠版本 / 冲突队列。
|
||||
- [同步表约定.md](./同步表约定.md)
|
||||
- [数据同步-开通说明.md](./数据同步-开通说明.md)
|
||||
- [数据同步-迁移手册.md](./数据同步-迁移手册.md)
|
||||
|
||||
36
docs/数据同步-开通说明.md
Normal file
36
docs/数据同步-开通说明.md
Normal file
@@ -0,0 +1,36 @@
|
||||
# 数据同步 · 开通说明(管理员)
|
||||
|
||||
> 依据:`松离线-dbsync方案-最终版.md`
|
||||
> **默认客户无感**:未显式开通时,终端保存与自增表行为与现网一致。
|
||||
|
||||
## 三分模式(客户端配置,智建通道为表白名单源)
|
||||
|
||||
| 模式 | 含义 | 谁改 |
|
||||
|------|------|------|
|
||||
| `local_only` | 仅本地,无同步 | 默认之一 |
|
||||
| `online_primary` | HTTP 双写 / 离线 pending(旧路径) | 已配线上 API 且未写 MODE 时兼容升 |
|
||||
| `local_dbsync` | 松离线 + 表白名单 + 本机 agent | **仅显式配置**,禁止自动升 |
|
||||
|
||||
智建控制台「数据同步」配的是**通道 + 表白名单 + 线上 DSN**;是否走 `local_dbsync` 由客户端环境变量决定,平台**不会**替全员切默认。
|
||||
|
||||
## 开通步骤(增值)
|
||||
|
||||
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`。
|
||||
|
||||
## 谁能看什么
|
||||
|
||||
| 角色 | 可见 |
|
||||
|------|------|
|
||||
| 公司管理员 | 通道配置、对账(同步修复)、统计;**无**冲突台 / LWW 覆盖明细 |
|
||||
| 平台超级管理员 | LWW 覆盖审计(平台工作台) |
|
||||
| 未开通终端用户 | **零同步文案**,无强制状态条 |
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [同步表约定.md](./同步表约定.md)
|
||||
- [数据同步-迁移手册.md](./数据同步-迁移手册.md)(旧客从 HTTP 双写迁入)
|
||||
- [数据同步-中间件.md](./数据同步-中间件.md)
|
||||
40
docs/数据同步-迁移手册.md
Normal file
40
docs/数据同步-迁移手册.md
Normal file
@@ -0,0 +1,40 @@
|
||||
# 数据同步 · 迁移手册(O7)
|
||||
|
||||
> 从旧路径 `online_primary`(HTTP 双写)迁到 `local_dbsync`(松离线 + agent)。
|
||||
> **禁止静默迁移、禁止全员一刀切关双写。**
|
||||
|
||||
## 硬规则
|
||||
|
||||
1. **旧客默认不变**:未评估、未签字前不得改默认模式。
|
||||
2. **同表互斥**:某表不得「一边 HTTP 双写、一边进 dbsync 白名单」。
|
||||
3. **顺序不可颠倒**:先停该表双写 → 再进白名单 / 切 `local_dbsync`。
|
||||
4. **agent 非安装强依赖**:迁移观察期允许只落本地 + 积压;须告知「需 agent 才上云」。
|
||||
|
||||
## 推荐流程(单表 / 单库)
|
||||
|
||||
| 步骤 | 动作 | 验收 |
|
||||
|------|------|------|
|
||||
| 1 | 智建通道准备:目标表白名单、UUID PK 校验通过、remote DSN 可达 | 保存通道成功 |
|
||||
| 2 | 客户端仍 `online_primary`:对该表**停止**双写(或从双写表白名单移除) | 该表仅写本地或仅走约定路径 |
|
||||
| 3 | 观察 ≥1 个业务周期:无双写残留、无重复行 | 抽查线上/本地主键 |
|
||||
| 4 | 显式设 `YXD_SYNC_MODE=local_dbsync`,写入表白名单缓存/通道拉名单 | `/sync/status` 显示 local_dbsync |
|
||||
| 5 | 登记 Binding(可选)`local_database_id → online_db_id` | GET bindings 命中 |
|
||||
| 6 | 装本机 agent,观察 outbox 清空、同 UUID 上云 | agent push 成功;超管可查 LWW(若有覆盖) |
|
||||
| 7 | 确认稳定后再扩大白名单;**勿**对全员默认切模式 | 旧客未改默认 |
|
||||
|
||||
## 回滚
|
||||
|
||||
1. 客户端改回 `online_primary` 或 `local_only`。
|
||||
2. 智建侧可停通道 / 缩表白名单(勿删线上数据)。
|
||||
3. 未推完的 outbox 由对方客户端自行处理;平台不强制清。
|
||||
|
||||
## 新客评估(M4)
|
||||
|
||||
仅对**新客**或**书面确认的迁移客**评估是否默认 `local_dbsync`。
|
||||
M4 前:**禁止**全员切默认。
|
||||
|
||||
## 相关
|
||||
|
||||
- [数据同步-开通说明.md](./数据同步-开通说明.md)
|
||||
- [同步表约定.md](./同步表约定.md)
|
||||
- 冻结方案:`松离线-dbsync方案-最终版.md` §8 / O7
|
||||
Reference in New Issue
Block a user