FtpTool/docs/ftp-sync-tool-design.md
2026-04-15 15:58:23 +08:00

604 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 基于 FTP 中转的配置双向同步工具设计方案
## 1. 文档目的
本文档用于说明一套基于 FTP 中转的配置同步工具设计方案,满足以下目标:
- 开发环境定时从 Git 拉取新配置,并通过生产环境 `push` 接口推送到生产
- 生产环境定时从 `pull` 接口拉取新配置,并同步回开发环境 Git
- 在开发环境与生产环境不能直接互通时,通过 FTP 服务作为中转通道完成双向同步
## 2. 已知约束
### 2.1 技术约束
- JDK`1.8`
- Spring Boot`2.7.18`
- 轻量数据库:`H2` 或同类开源可商用数据库
- 其他依赖必须为开源可商用组件
### 2.2 网络与部署约束
- 无法登录 FTP 所在服务器主机
- 只能访问 FTP 服务:`IP + 端口 + 用户名/密码`
- 网络拓扑如下:
```text
开发环境 <----> FTP A <----> 生产环境
```
说明:
- 开发环境可以访问 FTP A
- 生产环境可以访问 FTP A
- 开发与生产不假设可以直接互通
## 3. 设计原则
- 同一套程序,按不同 `profile` 部署在开发和生产两端
- 通过 FTP 传递标准化同步包,避免环境间直接通信依赖
- 使用本地状态库记录同步任务、检查点、应答信息,保证可追踪、可恢复
- 同步流程必须具备幂等控制,避免重复推送、重复提交
- 开发到生产、生产回开发必须隔离处理,避免双向同步形成死循环
## 4. 总体方案
推荐采用“**双端代理 + FTP 中转 + 本地状态库**”架构:
- `Sync-Agent-Dev`:部署在开发环境
- `Sync-Agent-Prod`:部署在生产环境
- `FTP A`:作为唯一中转通道
- `H2`:记录同步状态、任务、检查点、重试信息
整体结构如下:
```text
开发环境
Sync-Agent-Dev
|- 拉取 Git
|- 上传/下载 FTP A
|- 写入 Git
生产环境
Sync-Agent-Prod
|- 调用生产 pull 接口
|- 调用生产 push 接口
|- 上传/下载 FTP A
中转
FTP A
|- dev-to-prod/
|- prod-to-dev/
|- ack/
|- failed/
```
## 5. 部署模式
建议只维护一套代码,通过 Spring Profile 控制角色:
- `dev-agent`:启用开发侧能力
- `prod-agent`:启用生产侧能力
### 5.1 开发侧职责
- 定时拉取 Git 指定分支的新配置
- 判断是否存在新的有效版本
- 打包配置并上传到 FTP
- 下载生产侧回传的同步包
- 将生产侧回传配置写入 Git
- 提交并推送到远端仓库
### 5.2 生产侧职责
- 轮询 FTP获取开发侧上传的配置包
- 校验后调用生产 `push` 接口导入配置
- 定时调用生产 `pull` 接口拉取最新配置
- 打包并上传回 FTP供开发侧消费
## 6. 技术选型
| 分类 | 选型 | 说明 |
| --- | --- | --- |
| 运行时 | JDK 1.8 | 满足约束 |
| 框架 | Spring Boot 2.7.18 | 主体框架 |
| 调度 | Spring Scheduling | 实现定时任务 |
| 重试 | Spring Retry | 失败重试 |
| 数据库 | H2 File Mode | 轻量、嵌入式、可持久化 |
| Git 操作 | JGit | 纯 Java 实现 |
| FTP 操作 | Apache Commons Net | 主流 FTP 客户端 |
| JSON | Jackson | 标准序列化组件 |
| 日志 | SLF4J + Logback | 默认日志能力 |
### 6.1 数据库模式建议
虽然需求提到“类似 H2 的轻量化内存数据库”,但本场景不建议纯内存模式,原因如下:
- 服务重启后需要保留同步检查点
- 失败任务需要支持补偿和人工追踪
- 需要记录包处理状态,避免重复消费
因此建议使用:
- `H2 File Mode`
即本地文件数据库,仍然轻量,但支持状态持久化。
## 7. 核心业务流程
系统包含两条主链路。
### 7.1 链路一:开发 Git -> 生产 push 接口
用途:将开发环境 Git 中的新配置推送到生产环境。
流程如下:
1. `dev-agent` 定时拉取 Git 指定分支
2. 判断 Git 最新提交是否为新的有效配置版本
3. 将配置目录打包为标准同步包
4. 上传至 FTP 路径 `dev-to-prod/out/`
5. `prod-agent` 轮询 FTP发现新包后下载
6. 校验包完整性、幂等键和来源信息
7. 调用生产环境 `push` 接口导入配置
8. 成功后生成 `ack` 文件上传到 FTP
9. `dev-agent` 读取 `ack`,将任务状态更新为成功
建议时序图如下:
```mermaid
sequenceDiagram
participant G as Git(开发)
participant D as Sync-Agent-Dev
participant F as FTP A
participant P as Sync-Agent-Prod
participant API as 生产Push接口
D->>G: 定时 pull 配置
D->>D: 检查是否有新版本
D->>D: 打包 zip + manifest
D->>F: 上传 dev-to-prod/out/
P->>F: 轮询并下载新包
P->>P: 校验 hash/traceId
P->>API: 调用 push 接口
API-->>P: 返回处理结果
P->>F: 上传 ack
D->>F: 读取 ack
D->>D: 更新状态为成功
```
### 7.2 链路二:生产 pull 接口 -> 开发 Git
用途:将生产环境当前配置回传到开发环境,形成配置镜像或审计记录。
流程如下:
1. `prod-agent` 定时调用生产 `pull` 接口
2. 将返回配置标准化后计算版本标识或内容哈希
3. 如果与上次同步结果不同,则打包上传到 FTP `prod-to-dev/out/`
4. `dev-agent` 轮询 FTP 并下载新包
5. 解包后写入本地 Git 工作目录
6. 提交 commit 并推送到远端 Git
7. 成功后写回 `ack`
建议时序图如下:
```mermaid
sequenceDiagram
participant API as 生产Pull接口
participant P as Sync-Agent-Prod
participant F as FTP A
participant D as Sync-Agent-Dev
participant G as Git(开发)
P->>API: 定时调用 pull 接口
API-->>P: 返回当前配置
P->>P: 标准化并计算 hash
P->>F: 上传 prod-to-dev/out/
D->>F: 轮询并下载新包
D->>D: 解包并写入工作区
D->>G: commit + push
D->>F: 上传 ack
```
## 8. 标准同步包设计
为保证跨环境处理一致,建议所有同步内容封装为统一格式的压缩包。
### 8.1 包结构
```text
package.zip
|- manifest.json
|- config/
|- sha256.txt
```
### 8.2 manifest 字段建议
```json
{
"traceId": "uuid",
"direction": "DEV_TO_PROD",
"sourceEnv": "DEV",
"sourceVersion": "gitCommitId",
"contentHash": "sha256",
"createdAt": "2026-04-15T10:00:00+08:00"
}
```
### 8.3 字段说明
- `traceId`:本次同步唯一流水号
- `direction`:同步方向,例如 `DEV_TO_PROD``PROD_TO_DEV`
- `sourceEnv`:来源环境
- `sourceVersion`:来源版本号,开发侧通常为 Git Commit ID
- `contentHash`:配置内容哈希,便于判断重复包
- `createdAt`:包生成时间
## 9. FTP 目录规划
建议在 FTP A 上使用如下目录结构:
```text
/dev-to-prod/out/
/dev-to-prod/ack/
/prod-to-dev/out/
/prod-to-dev/ack/
/failed/
```
目录说明:
- `/dev-to-prod/out/`:开发侧发往生产侧的同步包
- `/dev-to-prod/ack/`:生产侧返回的处理应答
- `/prod-to-dev/out/`:生产侧发往开发侧的同步包
- `/prod-to-dev/ack/`:开发侧返回的处理应答
- `/failed/`:失败包归档目录
### 9.1 上传规范
为避免消费端读取到半截文件,建议采用临时文件上传策略:
1. 先上传为 `.tmp`
2. 上传完成后重命名为正式 `.zip`
3. 消费端只处理 `.zip` 文件
## 10. Git 分支策略
这是方案中的关键设计点。
不建议将“开发配置推生产”和“生产配置回传开发”写到同一个 Git 分支,否则极易形成循环同步。
建议拆分为两个分支:
- `config-dev-main`:开发主配置分支
- `config-prod-snapshot`:生产配置镜像分支
同步规则:
- `DEV -> PROD` 只消费 `config-dev-main`
- `PROD -> DEV` 只写入 `config-prod-snapshot`
### 10.1 这样设计的好处
- 避免双向同步形成闭环
- 生产回传配置不会覆盖开发主线
- 便于审计“生产当前实际配置”
### 10.2 机器人提交标记
建议同步工具在 commit message 中增加固定前缀,例如:
```text
sync(prod->git): traceId=xxx version=xxx
```
开发侧扫描 Git 时应忽略同步机器人生成的提交,进一步降低环路风险。
## 11. 本地状态库设计
建议至少建立以下 3 张表。
### 11.1 `sync_checkpoint`
用于记录各方向的最后一次成功检查点。
| 字段 | 类型 | 说明 |
| --- | --- | --- |
| id | bigint | 主键 |
| direction | varchar | 同步方向 |
| last_success_version | varchar | 最后成功版本 |
| last_success_hash | varchar | 最后成功内容哈希 |
| updated_at | timestamp | 更新时间 |
### 11.2 `sync_task`
用于记录每次同步任务生命周期。
| 字段 | 类型 | 说明 |
| --- | --- | --- |
| id | bigint | 主键 |
| trace_id | varchar | 流水号 |
| direction | varchar | 同步方向 |
| source_version | varchar | 来源版本 |
| package_name | varchar | 包文件名 |
| status | varchar | 状态 |
| retry_count | int | 重试次数 |
| error_msg | clob | 错误信息 |
| created_at | timestamp | 创建时间 |
| updated_at | timestamp | 更新时间 |
### 11.3 `sync_ack`
用于记录应答信息。
| 字段 | 类型 | 说明 |
| --- | --- | --- |
| id | bigint | 主键 |
| trace_id | varchar | 流水号 |
| ack_side | varchar | 应答方 |
| ack_status | varchar | 应答状态 |
| ack_time | timestamp | 应答时间 |
| remark | varchar | 备注 |
## 12. 幂等与一致性设计
### 12.1 幂等键建议
建议以如下组合作为幂等键:
```text
direction + sourceVersion + contentHash
```
约束效果:
- 已经处理过的包不能重复推送
- 已经提交过的生产快照不能重复写 Git
### 12.2 一致性策略
本方案属于跨系统、跨网络的异步同步,不适合做强一致事务。
建议采用:
- “本地落库 + 外部调用 + 最终一致”模式
- 每一步记录状态
- 失败后允许自动重试或人工补偿
## 13. 失败处理与补偿机制
### 13.1 自动重试
以下场景建议自动重试:
- FTP 上传失败
- FTP 下载失败
- 生产 `push` 接口调用失败
- 生产 `pull` 接口调用失败
- Git push 失败
建议策略:
- 最大重试次数:`3 ~ 5`
- 重试间隔:指数退避,例如 `30s / 60s / 120s`
### 13.2 失败归档
连续失败后建议:
- 将包移动到 FTP 的 `/failed/`
- 将任务状态置为 `FAILED`
- 记录完整错误信息
- 触发告警
### 13.3 人工补偿
后续可以增加一个管理接口,支持:
-`traceId` 重新执行
- 重置任务状态
- 查看失败原因
## 14. 安全设计
### 14.1 传输安全
优先级建议如下:
1. 优先使用 `FTPS`
2. 如果只能使用普通 FTP建议对同步包内容做 AES 加密
### 14.2 凭据管理
以下信息不得写死在代码中:
- FTP 地址、端口、用户名、密码
- Git 用户名、密码或 Token
- 生产接口认证信息
建议通过以下方式外置:
- `application-*.properties`
- 环境变量
- 启动参数
### 14.3 审计日志
建议记录:
- 谁发起了同步
- 同步方向
- 来源版本
- 包名
- 接口调用结果
- 异常原因
## 15. 项目结构建议
有两种实现方式。
### 15.1 方案 A单工程 + Profile 切换
适用于项目规模较小、交付快的场景。
```text
sync-tool
|- src/main/java
| |- config
| |- ftp
| |- git
| |- job
| |- package
| |- repository
| |- service
| |- web
|- src/main/resources
| |- application.properties
| |- application-dev-agent.properties
| |- application-prod-agent.properties
```
### 15.2 方案 B多模块拆分
适用于后续可能演化较多、职责更清晰的场景。
```text
sync-tool
|- common
|- dev-agent
|- prod-agent
```
当前建议优先采用:
- `方案 A单工程 + Profile`
理由:
- 实现成本低
- 运维简单
- 早期更适合快速打通链路
## 16. 核心模块划分
建议按职责拆分以下模块:
- `GitService`
- 拉取仓库
- 检查最新提交
- 提交并推送生产回传配置
- `FtpService`
- 上传、下载、重命名、目录扫描
- `PackageService`
- 生成 zip
- 生成 manifest
- 校验 hash
- `SyncTaskService`
- 任务创建
- 状态变更
- 检查点维护
- `ProdPushService`
- 调用生产 `push` 接口
- `ProdPullService`
- 调用生产 `pull` 接口
- `AckService`
- 生成和消费 ack 文件
- `JobScheduler`
- 各类定时任务调度
## 17. 定时任务建议
### 17.1 开发侧任务
- `GitPullJob`
- 周期拉取 Git 并检查是否有新配置
- `UploadDevPackageJob`
- 将待同步配置上传到 FTP
- `ConsumeProdPackageJob`
- 下载生产回传包并写入 Git
- `AckScanJob`
- 扫描生产侧 ack 并更新任务状态
### 17.2 生产侧任务
- `ConsumeDevPackageJob`
- 下载开发侧同步包并调用生产 `push`
- `PullProdConfigJob`
- 定时调用生产 `pull` 接口
- `UploadProdPackageJob`
- 将拉取结果上传到 FTP
- `AckScanJob`
- 扫描开发侧 ack 并更新任务状态
## 18. 一期 MVP 建议
建议按最小可交付版本分阶段实施。
### 阶段 1打通主链路
- 建立 Spring Boot 工程
- 集成 H2、JGit、FTP
- 实现开发到生产的全量包同步
- 实现生产 `push` 接口调用
### 阶段 2打通回传链路
- 接入生产 `pull` 接口
- 实现生产到开发的 FTP 回传
- 实现开发侧写入 Git 并推送
### 阶段 3增强稳定性
- 增加重试
- 增加 ack 机制
- 增加失败归档
- 增加告警与审计日志
## 19. 风险与注意事项
### 19.1 最大风险:双向同步闭环
如果生产回传配置写入开发主分支,再被开发侧识别为“新配置”,会再次推送到生产,形成无限循环。
规避措施:
- 使用独立镜像分支
- 识别机器人提交
- 使用幂等键
### 19.2 配置冲突风险
如果开发和生产都会修改同一份配置,且要求双向合并,则不能简单用文件覆盖方式处理。
当前建议:
- 将生产回传定义为“镜像/审计”
- 不直接回写开发主配置分支
### 19.3 FTP 能力限制
如果 FTP 不支持原子重命名、目录权限受限或稳定性较差,需要额外做兼容与重试。
## 20. 结论
在当前网络条件下,推荐采用“**开发代理 + 生产代理 + FTP 中转 + H2 状态库**”的双端部署方案。
该方案具备以下特点:
- 不依赖开发与生产直接互通
- 满足开发到生产、生产到开发的双向同步需求
- 支持状态记录、失败重试、幂等控制和审计追踪
- 适合使用 `Java 1.8 + Spring Boot 2.7.18` 快速落地
## 21. 后续可继续细化内容
后续可以基于本方案继续输出:
- `application.properties` 配置项设计
- H2 建表 SQL
- 核心类图与接口设计
- 各定时任务的时序与状态流转
- Spring Boot 工程骨架