Apifox 标签同步刷新与全量存档修复记录
更新时间:2026-07-14;使用模型:Codex GPT-5;用户:Jsmond2016
问题摘要
用户基于 Apifox tag 同步接口后,可能继续在当前面板中手工添加接口,或通过外部插件插入 Quick Mock 接口。此时再次执行“刷新接口”或“确定同步”,原逻辑会删除这些非 Apifox 同步接口。
存档也存在相同的数据边界问题:原逻辑只保存当前 tag 命中的接口,导致恢复存档后,其他 tag、自定义及外部接口丢失。
修复后的原则是:
- Apifox tag 只决定需要从 Apifox 拉取和更新的接口范围。
- 刷新只替换 Apifox 同步产生的接口,不修改用户手工添加或外部插入的数据。
- 存档是当前面板的完整快照,tag 只用于标识迭代及关联迭代文档。
用户操作路径
刷新覆盖
- 选择一个或多个 tag,同步 Apifox 接口。
- 在已同步模块中手工添加接口,或通过外部插件插入接口模块。
- 点击“刷新 Apifox 接口”,确认更新。
- 刷新完成后,手工接口或外部接口从面板中消失。
存档缺失
- 当前面板同时存在 tag 接口、自定义接口和外部接口。
- 选择一个迭代 tag 并执行存档。
- 存档预览及保存结果只包含该 tag 命中的接口。
- 恢复存档后,未命中 tag 的接口无法恢复。
根因分析
刷新按模块整体替换
旧逻辑使用模块的 apiDocUrl 判断模块是否来自 Apifox,并在刷新时先删除所有相同 URL 的模块,再追加最新解析结果。
该判断粒度过粗:
- 用户可能直接在 Apifox 模块中添加自定义接口。
- 外部批量 Quick Mock 模块也会继承当前 Apifox URL。
- 模块共享同一 URL,不代表模块内每个接口都由 Apifox 同步维护。
因此,按 apiDocUrl 整模块替换会误删非 Apifox 数据。
存档错误复用 tag 筛选结果
旧存档逻辑遍历模块,只保留 api.tags 包含所选 tag 的接口,并仅保存这些接口引用的快速联调配置。这使“迭代标识”和“存档内容范围”被错误绑定。
修复方案
接口来源边界
ModuleConfig 增加以下可选元数据:
| 字段 | 说明 |
|---|---|
source | 标识模块来源为 apifox 或 external |
apifoxApiIds | 记录当前模块中由 Apifox 同步流程维护的接口 ID |
新同步的模块会记录完整的 Apifox 接口 ID 清单。用户后续添加的接口不会进入该清单,因此即使它与 Apifox 接口使用相同 tag,也不会在刷新时被覆盖。
外部 Quick Mock 模块会明确标记为 external,不参与 Apifox 差异对比和替换。
刷新与确认同步
同步逻辑提取为独立纯函数,统一处理刷新和弹窗确认同步:
- 只提取
apifoxApiIds中的接口参与新旧差异对比。 - 更新同名模块时,用新 Apifox 接口替换旧同步接口,并追加原模块中的自定义接口。
- Apifox 分组被删除但模块仍有自定义接口时,保留该模块并解除其 Apifox 来源关联。
- 外部模块及其他自定义模块保持原样。
- “合并”策略只追加不存在的 Apifox 接口,不改变已有自定义或外部数据。
历史数据兼容
历史配置没有 source 和 apifoxApiIds。兼容逻辑仅在缺少 ID 清单时,根据以下条件识别旧 Apifox 接口:
- 模块
apiDocUrl与当前 Apifox 配置一致。 - 接口存在同步时写入的 tag。
- 模块不是
quick.mock.external外部模块。
历史配置完成一次实际更新后,会写入新的来源和接口 ID 元数据。新版本不再依赖 tag 判断自定义接口来源。
全量存档
存档继续使用所选 tag 读取迭代文档,但保存内容改为当前面板完整快照:
- 全部模块及模块内全部接口。
- Apifox 来源和接口 ID 元数据。
- 接口 tag、自定义响应等嵌套配置的独立副本。
- 全部快速联调配置。
- 当前 Apifox 配置和对应迭代文档信息。
归档弹窗说明同步调整,明确存档会保存当前面板全部数据。
验收标准
- [x] 刷新 Apifox 后,同模块内手工添加的接口仍然存在。
- [x] 自定义接口即使使用相同 tag,也不会被刷新覆盖。
- [x] 外部 Quick Mock 模块不会参与 Apifox 替换。
- [x] Apifox 已删除分组但仍含自定义接口时,模块和自定义接口继续保留。
- [x] 合并策略只追加新 Apifox 接口。
- [x] 历史同步配置能够继续刷新,并排除旧版外部模块。
- [x] 存档包含当前面板全部模块、接口和快速联调配置。
- [x] 存档内容使用独立数据副本,不直接引用当前 Store 数组。
回归测试
| 场景 | 预期结果 |
|---|---|
| 历史 Apifox 模块混有自定义接口 | 只选取带同步 tag 的旧接口参与更新 |
| 新版 Apifox 模块内自定义接口使用相同 tag | 依据 apifoxApiIds 保留自定义接口 |
外部模块使用相同 apiDocUrl | 外部模块完整保留 |
| Apifox 分组删除但模块仍有自定义接口 | 保留自定义接口,并解除模块 Apifox 关联 |
| 使用合并策略同步新增接口 | 新接口追加,已有自定义和外部数据不变 |
| 存档面板含多个 tag、自定义及外部接口 | 存档包含全部模块和接口 |
| 存档面板含未被接口引用的快速联调配置 | 快速联调配置仍完整保存 |
验证结果
- 用户按原始操作路径验证通过。
- Vitest:5 个测试文件、19 条用例全部通过。
- 定向 ESLint:通过。
- Chrome 生产构建:通过。
- 全量 TypeScript 检查仍有 2 个既有错误,分别位于
ArchiveListModal.tsx和SyncApifoxModal.tsx,与本次修改无关。
任务状态
| 任务 | 状态 | 关联实现 |
|---|---|---|
| 区分 Apifox、手工和外部接口来源 | 已完成 | types/index.ts、apifoxUtils.ts、background/index.ts |
| 刷新及确认同步仅替换 Apifox 接口 | 已完成 | apifoxSyncUtils.ts、SyncApifoxModalButton.tsx |
| 存档改为当前面板全量快照 | 已完成 | archiveUtil.ts、ArchiveModal.tsx |
| 增加同步边界和全量存档回归测试 | 已完成 | apifoxSyncUtils.test.ts、archiveUtil.test.ts |
