# database_fastapi 修改意见

> 针对文件：`E:\project\yh_one\new_project\yuhengyihao_client\yxd\app_fastapi\database_fastapi.py`  
> 背景：需要与线上主库强一致；断网可记日志，恢复后合并回放至线上；主写在线上。  
> 对照能力：宇信达智建平台 `dbsync` 出站队列 / 冲突 / 对账（见 `ai建站/docs/数据同步-中间件.md`）。

---

## 落地状态（已实现）

| 项 | 状态 | 位置 |
|----|------|------|
| 统一写网关 `apply_write` | ✅ | `db_write_gateway.py` |
| 写接口收口 | ✅ | insert / update / update/id / delete / sql(DML) |
| 库绑定防串库 | ✅ | `sync_binding.py` → `cache/db_bindings/bindings.json` |
| 配置 | ✅ | `sync_config.py`（环境变量） |
| 断网 pending | ✅ | 复用 `QueueDatabaseManager`，库名默认 `sync_pending` |
| 手动回放 | ✅ | `POST /database/sync/replay` |
| 绑定/状态 API | ✅ | `/database/sync/*` |
| 后台自动回放 | ✅ 可选 | `sync_replay_worker.py`，需 `YXD_SYNC_REPLAY_WORKER=1` |
| DDL / 批量 import 收口 | ⏳ 未做 | 仍可直写本地；后续按需收口 |
| 冲突队列 API | ⏳ 未做 | 回放失败即停保序，暂无独立 conflicts 列表 |

**默认安全**：`YXD_SYNC_MODE` 未配且无 `YXD_ONLINE_API_BASE` 时为 **`local_only`**（只写本地，仍会 ensure 绑定信息）。  
配了 `YXD_ONLINE_API_BASE` 且未显式写 `YXD_SYNC_MODE` 时自动升为 **`online_primary`**。

### 环境变量

| 变量 | 说明 | 默认 |
|------|------|------|
| `YXD_SYNC_MODE` | `local_only` \| `online_primary` | `local_only`（有 ONLINE_BASE 且未设 mode 则升 primary） |
| `YXD_ONLINE_API_BASE` | 线上 API 根，如 `https://api.example.com` | 空 |
| `YXD_ONLINE_TIMEOUT_SEC` | 线上写超时 | `8` |
| `YXD_OFFLINE_POLICY` | `pending_log` \| `reject` | `pending_log` |
| `YXD_SYNC_ENFORCE_BINDING` | 是否强制已绑定 | `true` |
| `YXD_SYNC_AUTO_ENSURE_BINDING` | 写时自动 ensure | `true` |
| `YXD_SYNC_PENDING_QUEUE` | pending 队列库名 | `sync_pending` |
| `YXD_ONLINE_TOKEN_HEADER` | 透传 token 头 | `Authorization` |
| `YXD_SYNC_REPLAY_WORKER` | 启后台回放 | 关 |
| `YXD_SYNC_REPLAY_INTERVAL_SEC` | 回放间隔秒 | `30` |

### 新增/改动文件

```text
yxd/app_fastapi/
  db_write_gateway.py      # apply_write / replay_pending
  sync_config.py
  sync_binding.py
  sync_context.py          # Request → WriteContext
  sync_replay_worker.py
  database_fastapi.py      # 写路由改走网关 + /sync/* API
```

### 同步 API

- `GET  /database/sync/status`
- `POST /database/sync/binding/ensure`
- `GET  /database/sync/binding`
- `GET  /database/sync/pending`
- `POST /database/sync/replay`  body: `{ "limit": 50 }`

请求头需带：`Database-ID`、`User-ID`（或应用已登录）、可选 `Tenant-ID` / `Authorization`。

### 写路径行为（`online_primary`）

```text
联网：先写线上（Database-ID=online_db_id）→ 成功再写本地镜像 → synced=true
离线 pending_log：不写正式本地业务结果 → 入 sync_pending 队列 → pending=true
离线 reject：直接失败
回放：按 id 升序提交线上 → 成功后写本地并删队列记录；失败即停保序
```

绑定键：`(tenant_id, user_id, local_database_id) → online_db_id`（全局唯一）。

---

## 一、原状结论（改造前）

| 项 | 原状 |
|----|------|
| 写入口 | `/database/permanent/table/data/insert\|update\|update/id\|delete`、`/permanent/sql` 等 |
| 落库方式 | 直接 `PermanentDatabaseManager`，**只写本地 SQLite** |
| 队列能力 | `queue_database_fastapi.py` 与 permanent **未打通** |
| 线上主写 / 断网日志 | **未实现** |

---

## 二、目标模型

```text
联网：API 写 → 先写线上主库 → 再写本地镜像
断网：API 写 → pending 日志（queue_database）→ 不 formal commit
恢复：有序回放到线上主 → 成功再对齐本地
```

硬约束：主写在线上；断网日志只能回放到线上主；断网窗口非强一致。

---

## 三～十、设计说明（保留）

原 P0–P4、响应约定、勿做事项、分阶段 M1–M4 已按上文落地；M5（SQL/导入全收口 + 冲突监控）待续。

与智建平台 dbsync：客户端强一致仍以「API 同步写线上」为准，不要只靠触发器异步 outbox 充当主路径。

本意见文档：`E:\project\ai建站\database_fastapi修改意见.md`。  
实现目录：`yuhengyihao_client\yxd\app_fastapi\`。
