# 基于 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 工程骨架