氚云访客历史同步漏数据?分页方式、日期语义、头像容错三处全改
氚云访客同步跑了几个月,某天业务反馈”刘树明这条访客记录根本没入库”。查代码、查日志、查公网接口——数据其实是有的,但被同步逻辑漏掉了。
排查下来发现这不是一个 bug,而是三个 bug 叠加:分页方式有缺陷、日期语义理解错了、头像上传失败会阻断主数据入库。任何一个 bug 单拿出来都不会让数据丢失,三个叠在一起就稳定漏数据。
本文把三个根因、四个修复点、服务器验证步骤完整拆开,让你下次遇到第三方同步漏数据时能直接套用这套排查思路。
🎧 文章导读
🎵 背景音乐

图1:分页方式、日期语义、头像容错——三层叠在一起才让访客漏入库
一、问题现象:业务反馈某访客”凭空消失”
业务方反馈:氚云上有”刘树明”这条访客预约记录,但在 through 库 visitor_appointment 表里查不到。
排查的过程是这样:
- 先查同步日志,发现刘树明的同步日志只在”已存在,跳过”那一种结果中出现——这说明同步任务跑过这条数据,但当时判定为已存在。
- 反查
visitor_appointment表,确实没有peo_id = '79a8ec3d-0fa2-4652-9faa-51a0fce6dfef'的记录。 - 那”已存在”判定的依据是什么?查代码发现是按
peo_id查的,可新数据根本没这个peo_id,于是出现”查到已存在但实际不存在”的诡异状态。
继续往上游查,发现了三个独立但叠加的 bug。
二、根因 1:历史同步分页方式有缺陷
2.1 旧逻辑的问题
旧实现是”先取 objectId,再按 objectId 处理”的两阶段流程:
1 | 阶段一:分页拉取所有 objectId |
这个流程有两个隐患:
- 阶段一和阶段二之间存在数据漂移:如果分页中途有新数据进入,
objectId集合可能漏或重复。 - 阶段二失败时没有断点保护:一旦失败,已经处理过的
objectId和未处理的混在一起,重试可能跳过或重复。
2.2 修复方案
改为单阶段分页,直接按氚云分页响应逐页同步:
1 | 每页响应 → 同步该页 → 推进断点 → 拉下一页 |
每页内的 objectId 重复只同步一次;某页失败立即停止,且不推进 Redis 断点——避免失败数据被静默跳过。
单阶段分页的好处是”所见即所得”,分页中途的数据漂移窗口被关掉了一半。
三、根因 2:历史补跑日期语义错误
3.1 旧代码理解错了什么
氚云请求中的 startTime/endTime 字段,更接近表单创建或提交日期,并不等于返回记录中的”访客来访开始时间”。
这个差异在大部分场景下看不出来——如果表单创建日期和来访日期一致,一切正常。但刘树明这条记录刚好横跨月底:
| 字段 | 值 |
|---|---|
| 氚云查询日期归属 | 2026-06-30 |
返回 startTime |
2026-07-01 00:00:00 |
返回 endTime |
2026-09-30 00:00:00 |
如果按”查询日期 2026-06-30“补跑,旧代码会用 2026-06-30 作为 apply_date 入库——查询日期是创建表单的日期,不是访客实际来访日期。
3.2 修复方案
- 历史任务按”查询日期”翻页——保持和定时任务一致;
- 入库
apply_date/begin_date/end_date按响应中的来访起止时间计算——这才是业务的真实日期。
修复后刘树明这条记录的入库结果是:
1 | visitor_name: 刘树明 |
source = 3 是 SOURCE_3(氚云),apply_date 用了响应的 startTime,符合业务预期。
同步任务的日期语义错位是定时任务最容易踩的坑。第三方接口的”日期字段”通常是请求参数语义,不一定是响应数据语义。
四、根因 3:头像上传失败阻断主数据入库
4.1 旧逻辑的问题
公网氚云接口响应里包含较大的头像 Base64,size=100 在本地公网链路容易触发 Feign 读取超时。同时,本地 aided-service(头像上传服务)未启动时,旧逻辑会因头像上传失败而丢弃整条访客数据。
也就是说:头像上传失败 → 整条访客主数据不入库。这违反了”附属信息失败不应该阻断主数据”的原则。
4.2 修复方案
头像上传失败时只记录 WARN,主数据继续保存:
1 | try { |
附属信息和主数据的关系:头像、缩略图、附件都属于附属信息,不应阻断主数据的保存。这是容错设计里最基本的原则——核心业务数据优先于附属数据。
五、本次修复的四个代码点

图2:SyncController + VisitingAircraftSyncServiceImpl + VisitorAppointmentServiceImpl 三处协同修复
5.1 SyncController
- 历史同步改为单阶段分页,默认每页 100 条。
/sync/triggerHistorySync增加可选参数:startDate、endDate、pageSize。pageSize限制为 1~100;不传时仍为 100,不影响原定时任务。- 同一页出现重复
objectId时只同步一次。 - 当某日出现失败时立即停止,并且不推进 Redis 断点,避免失败数据被静默跳过。
- 修正本地 through 端口注释为
18134。
5.2 VisitingAircraftSyncServiceImpl
applyDate优先使用氚云响应中的startTime。- 时间解析失败时,回退日期使用本次补跑日期,不再错误使用服务器当天日期。
- 保留原有起止时间相等时拉开为
08:00-18:00的逻辑。 - 去掉重复查库去重。
- 头像上传失败时继续保存访客主数据。
5.3 VisitorAppointmentServiceImpl
- 仅
SOURCE_3(氚云)保留上游传入的历史applyDate。 - 其他来源继续保持旧行为:
applyDate使用当前入库时间,避免影响访客机等现有业务。
为什么只对 SOURCE_3 保留历史 applyDate?因为只有氚云这条链路是”补跑历史数据”的场景,其他来源都是实时同步,用当前入库时间更合适。容错规则要按业务场景分别设计,不能一刀切。
六、本地验证结果
6.1 自动化测试
- 共执行 11 个测试。
- 结果:
Failures: 0, Errors: 0。 - 覆盖:历史日期语义、默认日期、单阶段分页、同页去重、失败断点、成功断点、分页大小校验、头像失败继续入库。
6.2 构建
1 | mvn -pl service-provider/through-service -am package -DskipTests |
产物:
1 | E:\nanwang\smart-park-cloud-0617\service-provider\through-service\target\through-service-1.0.0-SNAPSHOT.jar |
6.3 真实接口和数据库验证
公网氚云接口已查到刘树明:
1 | objectId: 79a8ec3d-0fa2-4652-9faa-51a0fce6dfef |
本地补跑 2026-06-30 后数据库结果:
1 | visitor_name: 刘树明 |
重复检查未发现重复的 SOURCE_3 + peo_id 数据。
七、服务器部署验证
7.1 部署范围
只需要部署新打包的 through-service。Nacos 中氚云 host 使用服务器实际可访问的地址;如果内网代理稳定,生产环境无需因为本地公网测试而改成公网地址。
[!warning] 凭证安全
EngineSecret是接口密钥,不要写进日志、截图或知识库。此前已经在聊天和截图中暴露,条件允许时建议在氚云后台轮换。
7.2 先做单日小范围补跑
刘树明归属于氚云查询日期 2026-06-30:
1 | curl -X POST 'http://127.0.0.1:18134/sync/triggerHistorySync?startDate=2026-06-30&endDate=2026-06-30&pageSize=100' |
如果生产内网接口仍超时,把 pageSize 临时调小:
1 | curl -X POST 'http://127.0.0.1:18134/sync/triggerHistorySync?startDate=2026-06-30&endDate=2026-06-30&pageSize=10' |
公网链路本地验证时使用 pageSize=1 最稳定,但全历史补跑会很慢。
7.3 观察日志
1 | tail -Fn0 /data/work/zhyq/logs/through/through-all.log \ |
预期:
- 当日同步完成且失败数为 0。
- aided 不可用时可能出现”头像上传失败,继续保存访客主数据”WARN,但访客仍应入库。
- 不应因单条失败继续推进该日断点。
7.4 查库
1 | SELECT id, visitor_name, source, peo_id, apply_date, begin_date, end_date, create_time |
预期:source=3,且三个业务时间与本地验证结果一致。
7.5 再补跑历史数据
建议按月或按年分段执行,避免单个 HTTP 请求长时间占用 Tomcat 线程。每段完成后检查同步失败数和 Redis 断点,再执行下一段。
[!warning] 断点注意事项
显式补跑较早日期会把共享 Redis 断点更新到该日期。之后无参数执行会从断点下一天继续,并通过数据库去重;部署验证期间不要并发触发多段历史补跑。
八、经验总结
8.1 同步漏数据的 5 步排查法
- 数据到底有没有被拉过? 看同步任务的处理日志。
- 如果拉过,”已存在”判定依据是什么? 查去重 SQL 的查询条件。
- 如果没拉过,分页是否漏了? 看分页边界、断点推进、重复 objectId 处理。
- 日期语义对不对? 查请求参数日期 vs 响应数据日期。
- 附属信息失败有没有阻断主数据? 查 try-catch 范围。
按这个顺序排查,大部分同步漏数据问题都能在 1 小时内定位。
8.2 同步任务的 4 个不可妥协点
| 不可妥协点 | 原因 |
|---|---|
| 失败不推进断点 | 避免失败数据被静默跳过 |
| 主数据与附属信息分离保存 | 头像失败不应阻断访客主数据 |
| 日期语义必须验证 | 请求日期 ≠ 响应日期时不能想当然 |
| 同页去重只一次 | objectId 重复时按”一次”处理,避免重复插入 |
8.3 补跑历史数据的 3 个最佳实践
- 单日先验:先补跑 1 天,确认日志和数据库都符合预期,再补跑更大范围。
- 分段执行:按月或按年分段,每段完成后检查失败数和断点,再执行下一段。
- 不并发补跑:多个补跑请求会互相覆盖 Redis 断点,导致数据丢失或重复。
8.4 同步链路的可观测性建议
- 每条同步记录的处理路径(拉取 → 去重 → 入库 → 标记状态)应该有明确日志。
- 失败原因应该分门别类:分页失败 vs 去重失败 vs 入库失败 vs 头像失败。
- Redis 断点应该有专门的 metric 或日志,方便运维观察”当前同步到哪里了”。
九、结语
第三方同步漏数据是看起来”很神秘的 bug”——因为日志里有这条数据的处理记录,但数据库里就是没有。这次排查下来,其实就是分页、日期、容错三个老问题叠加在一起。
同步链路的设计原则:
- 单阶段优于两阶段;
- 日期语义要按响应数据来;
- 主数据优先于附属数据;
- 失败要显式,不要静默跳过。
下次再遇到第三方同步漏数据,按这套排查思路走一遍,大概率能在 1 小时内定位。
同步链路没有银弹,只有把每一层的失败模式都想到、补上对应的处理逻辑。