Files
jsowell-charger-web/plan/2026-03-21_08-48-24-ykc-midnight-offline-fix-tracker.md
2026-03-21 11:15:33 +08:00

434 lines
18 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.
---
mode: plan
cwd: /Users/guoqiusi/Workspace/jsowell-charger-web
task: 云快充1.6协议凌晨离线问题修复与协议对齐
complexity: high
planning_method: builtin
created_at: 2026-03-21T08:48:24+08:00
---
# Plan: 云快充1.6凌晨离线修复跟踪
## 任务概述
当前项目中,云快充 1.6 协议设备在凌晨时段存在离线现象。经代码与协议文档对照,未发现明确的“凌晨主动断链”定时任务,更可能是夜间网络/SIM 抖动触发了当前偏激进的离线判定逻辑,导致短时弱网被放大为设备离线。
本计划用于跟踪以下三类工作:
1. 先止血:降低“短时抖动即离线”的敏感度。
2. 再纠偏:按协议修正登录、心跳、费率、对时流程。
3. 最后收敛:统一实现入口,减少后续维护风险。
## 关键结论
- 协议要求:
- `0x03` 心跳为 10 秒周期上送,连续 3 次未收到才视为网络异常并重新登录。
- `0x13` 实时监测数据周期上送,待机 5 分钟、充电 15 秒。
- `0x05 -> 0x06 -> 0x09 -> 0x0A` 为费率模型校验与拉取闭环。
- `0x56` 对时为 1 天周期发送。
- 当前实现中的高风险点:
- Netty 读空闲 30 秒即触发关闭连接。
- 断链后立即将枪口置离线,并将充电中订单改异常。
- 桩状态按“任意枪口离线即整桩离线”计算。
- `0x05` 校验请求当前固定返回“一致”,未真正校验费率模型。
- 登录后主动下发 `0x58`,与协议标准流程不完全一致。
- 对时仅在登录后发送一次,未发现每日自动对时任务。
- 线上实际走老 Handler 链路,新 Strategy 链路当前不生效。
## 目标与验收口径
### 业务目标
- 凌晨 `00:00-06:00` 时段,短时网络抖动不再导致桩状态频繁离线。
- 单次 `30-40s` 的弱网抖动不应直接把充电中订单改为异常。
- 桩在线状态与真实链路状态更一致,减少前端“在线/离线闪断”。
### 协议目标
- `0x03` 按协议节奏与容错逻辑处理。
- `0x05/0x06/0x09/0x0A` 恢复为真实校验与请求链路。
- `0x56/0x55` 具备每日自动对时能力和执行记录。
### 验收标准
- 短时网络抖动:
- 30-40 秒无上行数据后恢复,不直接离线。
- 宽限期内重连成功,不产生订单异常。
- 长时网络中断:
- 超出配置阈值后,设备进入离线状态。
- 若有正在充电订单,按规则进入异常处理。
- 协议链路:
- 桩上报旧费率模型时,平台能正确返回不一致并等待 `0x09` 请求。
- 每日自动对时后,可看到 `0x55` 应答记录。
- 排障能力:
- 日志能够还原“最后正常通信 -> 空闲超时 -> 关闭连接 -> 是否重连成功”的完整链路。
## 执行批次
### 第一批:先止血
- 调整 Netty 空闲断链策略。
- 引入断链宽限期,避免瞬断即离线。
- 补连接生命周期日志。
- 修复 channel 映射清理。
### 第二批:协议纠偏
- 修复费率模型校验与拉取闭环。
- 新增每日自动对时。
- 将关键阈值配置化。
### 第三批:实现收敛
- 收敛云快充双实现,只保留一条实际生效链路。
- 补齐专项回归验证与联调记录。
## 任务清单
### YKC-001
- 优先级P0
- 状态done
- 目标将云快充连接保活从“30 秒空闲直接断开”改为“允许短时抖动,按连续丢心跳或宽限期判定断链”。
- 当前进展:
- 已按第一批止血方案落地为 `15s tick + 连续 3 次 READER_IDLE 才关闭`
- 已补 `idleCount / lastFrameType / lastReceiveAt / lastSerialNumber / disconnectReason` 连接属性。
- 修改文件:
- `/Users/guoqiusi/Workspace/jsowell-charger-web/jsowell-netty/src/main/java/com/jsowell/netty/server/yunkuaichong/NettyServerChannelInitializer.java`
- `/Users/guoqiusi/Workspace/jsowell-charger-web/jsowell-netty/src/main/java/com/jsowell/netty/server/yunkuaichong/NettyServerHandler.java`
- 设计要点:
- `IdleStateHandler` 读空闲阈值调整为 `40-45s` 或等效容错值。
- `userEventTriggered` 不再第一次超时就 `close()`
- 结合最近通信时间和连续空闲次数处理。
- 验收标准:
- 单次 `30-40s` 无上行数据不直接断链。
- 凌晨弱网时连接不会频繁抖动为离线。
- 风险:
- 阈值过宽会延迟真实离线发现,需要与业务确认容忍度。
### YKC-002
- 优先级P0
- 状态done
- 目标:增加“断链宽限期”,避免瞬断立刻将枪口置离线、订单置异常。
- 当前进展:
- 已落地 `60s` 离线确认宽限期。
- 已引入 `pile_pending_disconnect``pile_offline_confirmed` 标记,并在恢复通信后自动清理。
- 修改文件:
- `/Users/guoqiusi/Workspace/jsowell-charger-web/jsowell-netty/src/main/java/com/jsowell/netty/service/yunkuaichong/impl/YKCBusinessServiceImpl.java`
- `/Users/guoqiusi/Workspace/jsowell-charger-web/jsowell-pile/src/main/java/com/jsowell/pile/service/impl/OrderBasicInfoServiceImpl.java`
- 设计要点:
- `exit()` 中先记录“断链待确认”状态。
- 宽限期结束且未恢复登录时,才正式落离线和订单异常。
- 若宽限期内重连,取消离线确认。
- 验收标准:
- 短时断链后在宽限期内重连,不产生订单异常。
- 真正长断链后仍能正确离线。
- 风险:
- 需防止宽限期任务重复执行或状态竞争。
### YKC-003
- 优先级P0
- 状态in_progress
- 目标:补齐连接生命周期日志,支持“凌晨离线”专项排查。
- 当前进展:
- 已补空闲告警、连续空闲关闭、异常断链、宽限恢复、正式离线确认等主链路日志。
- `LoginRequestHandler` / `UploadRealTimeMonitorHandler` 维度的专项日志尚未单独补齐。
- 修改文件:
- `/Users/guoqiusi/Workspace/jsowell-charger-web/jsowell-netty/src/main/java/com/jsowell/netty/server/yunkuaichong/NettyServerHandler.java`
- `/Users/guoqiusi/Workspace/jsowell-charger-web/jsowell-netty/src/main/java/com/jsowell/netty/handler/yunkuaichong/HeartbeatRequestHandler.java`
- `/Users/guoqiusi/Workspace/jsowell-charger-web/jsowell-netty/src/main/java/com/jsowell/netty/handler/yunkuaichong/LoginRequestHandler.java`
- `/Users/guoqiusi/Workspace/jsowell-charger-web/jsowell-netty/src/main/java/com/jsowell/netty/handler/yunkuaichong/UploadRealTimeMonitorHandler.java`
- 设计要点:
- 统一打印 `pileSn``channelId`、远端 IP、最后一帧类型、最后上报时间、IdleState 类型、重连耗时。
- 对凌晨时段日志加清晰关键词,便于筛查。
- 验收标准:
- 一次离线事件能从日志完整还原链路。
- 风险:
- 需控制日志量,避免高频心跳刷爆日志。
### YKC-004
- 优先级P0
- 状态done
- 目标:修复 channel 映射清理不完整的问题,避免旧连接残留。
- 当前进展:
- 已补 `removeByPileSnAndChannelId()` 安全删除。
- 已补 `channelId -> pileSn` 反向映射维护,并修复删除时的脏映射残留问题。
- 修改文件:
- `/Users/guoqiusi/Workspace/jsowell-charger-web/jsowell-netty/src/main/java/com/jsowell/netty/service/yunkuaichong/impl/YKCBusinessServiceImpl.java`
- `/Users/guoqiusi/Workspace/jsowell-charger-web/jsowell-common/src/main/java/com/jsowell/common/enums/ykc/PileChannelEntity.java`
- 设计要点:
- 恢复 `removeByPileSn/removeByChannelId` 的正常调用。
- 确保断链、异常、重连时旧 `channel` 被清理。
- 验收标准:
- 同一桩重连后只保留一个有效 `channel`
- 下行命令不会打到死连接。
- 风险:
- 需避免误删新连接。
### YKC-005
- 优先级P1
- 状态in_progress
- 目标:将桩离线判定从“任意枪状态为离线即整桩离线”改为“最近通信时间 + 枪状态 + 宽限期”的综合判定。
- 当前进展:
- 已完成 `checkPileOffLine()` 优先识别 `PILE_OFFLINE_CONFIRMED` 标记。
- 仍未重写 `getPileStatus/getPileStatusV2` 的整桩聚合规则,暂按第一批止血范围保留现状。
- 修改文件:
- `/Users/guoqiusi/Workspace/jsowell-charger-web/jsowell-pile/src/main/java/com/jsowell/pile/service/impl/PileConnectorInfoServiceImpl.java`
- 设计要点:
- `getPileStatus/getPileStatusV2/checkPileOffLine` 使用统一判定逻辑。
- 将固定 3 分钟阈值改为可配置。
- 验收标准:
- 前端在线状态与真实通信状态一致。
- 短时抖动不出现整桩离线闪断。
- 风险:
- 旧缓存与新判定逻辑可能短期不一致,需要同步清理策略。
### YKC-006
- 优先级P1
- 状态todo
- 目标:按协议修正费率模型交互,去掉“固定返回一致”的实现。
- 修改文件:
- `/Users/guoqiusi/Workspace/jsowell-charger-web/jsowell-netty/src/main/java/com/jsowell/netty/handler/yunkuaichong/BillingTemplateValidateRequestHandler.java`
- `/Users/guoqiusi/Workspace/jsowell-charger-web/jsowell-netty/src/main/java/com/jsowell/netty/handler/yunkuaichong/BillingTemplateRequestHandler.java`
- `/Users/guoqiusi/Workspace/jsowell-charger-web/jsowell-netty/src/main/java/com/jsowell/netty/handler/yunkuaichong/LoginRequestHandler.java`
- 设计要点:
- `0x05` 真比较平台模型号与桩上报模型号。
- 不一致返回 `0x01`
- 由桩继续发 `0x09`,平台回 `0x0A`
- 登录后主动 `0x58` 改为开关控制或移除。
- 验收标准:
- 费率模型链路符合协议。
- 跨天费率切换时行为可解释、可追踪。
- 风险:
- 需确认现网桩程序是否依赖“登录后强推模板”的兼容行为。
### YKC-007
- 优先级P1
- 状态done
- 目标:补每日自动对时,降低跨天时钟漂移的影响。
- 当前进展:
- 已新增 `jsowellTask.dailyProofreadTimeForYkcV160()`
- 已补 `0x56` 下发记录和 `0x55` 应答记录。
- 待上线时补 Quartz 任务配置。
- 修改文件:
- `/Users/guoqiusi/Workspace/jsowell-charger-web/jsowell-pile/src/main/java/com/jsowell/pile/service/impl/YKCPushCommandServiceImpl.java`
- `/Users/guoqiusi/Workspace/jsowell-charger-web/jsowell-netty/src/main/java/com/jsowell/netty/handler/yunkuaichong/TimeCheckSettingResponseHandler.java`
- `/Users/guoqiusi/Workspace/jsowell-charger-web/jsowell-quartz/src/main/java/com/jsowell/quartz/task/JsowellTask.java`
- 设计要点:
- 每日定时给在线桩批量发 `0x56`
- 记录 `0x55` 应答结果与桩回传时间。
- 验收标准:
- 每日有自动对时执行记录。
- 对时失败可重试,可观测。
- 风险:
- 大批量对时可能集中打到凌晨,需控制任务节奏。
### YKC-008
- 优先级P1
- 状态todo
- 目标:将关键超时和离线参数配置化,避免硬编码。
- 修改文件:
- `/Users/guoqiusi/Workspace/jsowell-charger-web/jsowell-netty/src/main/java/com/jsowell/netty/server/yunkuaichong/NettyServerChannelInitializer.java`
- `/Users/guoqiusi/Workspace/jsowell-charger-web/jsowell-pile/src/main/java/com/jsowell/pile/service/impl/PileConnectorInfoServiceImpl.java`
- `/Users/guoqiusi/Workspace/jsowell-charger-web/jsowell-admin/src/main/resources/application.yml`
- 设计要点:
- 至少抽出:
- `readerIdleSeconds`
- `offlineConfirmSeconds`
- `lastConnectionOfflineMinutes`
- `dailyTimeSyncCron`
- 验收标准:
- 不改代码即可在环境配置中调整离线策略。
- 风险:
- 需注意各环境默认值兼容。
### YKC-009
- 优先级P2
- 状态todo
- 目标:收敛云快充双实现,避免后续修错入口。
- 修改文件:
- `/Users/guoqiusi/Workspace/jsowell-charger-web/jsowell-netty/src/main/java/com/jsowell/netty/service/yunkuaichong/impl/YKCBusinessServiceImpl.java`
- `/Users/guoqiusi/Workspace/jsowell-charger-web/jsowell-netty/src/main/java/com/jsowell/netty/handler/yunkuaichong`
- `/Users/guoqiusi/Workspace/jsowell-charger-web/jsowell-netty/src/main/java/com/jsowell/netty/strategy/ykc`
- 设计要点:
- 确认唯一线上入口。
- 如果保留老 Handler则冻结或移除 Strategy。
- 如果迁移到 Strategy则需一次性迁完。
- 验收标准:
- 线上只存在一套实际生效的云快充处理链路。
- 风险:
- 涉及面较大,需单独回归。
### YKC-010
- 优先级P1
- 状态todo
- 目标:补专项回归验证和凌晨专项验证脚本或测试用例。
- 修改文件:
- `/Users/guoqiusi/Workspace/jsowell-charger-web/jsowell-admin/src/test/java`
- 设计要点:
- 覆盖场景:
- `30-40s` 无心跳但恢复。
- 宽限期内重连。
- 费率模型不一致重新请求。
- 每日对时成功和失败。
- 验收标准:
- 每个修复点至少有一条可复现、可回归的验证路径。
- 风险:
- 若缺真实协议模拟器,需先补测试桩或伪报文能力。
## 推荐执行顺序
1. YKC-001
2. YKC-002
3. YKC-003
4. YKC-004
5. YKC-005
6. YKC-008
7. YKC-006
8. YKC-007
9. YKC-010
10. YKC-009
## 文件级改造备注
### 连接与断链主链路
- `NettyServerChannelInitializer`
- 负责 IdleState 参数配置。
- `NettyServerHandler`
- 负责空闲事件、异常事件、连接关闭事件的断链控制。
- `YKCBusinessServiceImpl`
- 负责断链后离线和订单状态变更逻辑。
### 在线状态与业务影响主链路
- `PileChannelEntity`
- 管理桩与 channel 的映射。
- `PileConnectorInfoServiceImpl`
- 管理枪口状态、桩状态聚合与前端在线判断。
- `OrderBasicInfoServiceImpl`
- 管理充电订单异常状态的最终落库。
### 协议对齐主链路
- `LoginRequestHandler`
- 登录后当前有对时和费率主动推送逻辑。
- `HeartbeatRequestHandler`
- 心跳包处理与最后通信时间刷新。
- `BillingTemplateValidateRequestHandler`
- 当前实现与协议偏差最大。
- `BillingTemplateRequestHandler`
- 负责 `0x09 -> 0x0A`
- `TimeCheckSettingResponseHandler`
- 用于记录 `0x55` 对时应答。
## 风险清单
- 现网桩程序可能已适配当前“非标准但可用”的平台行为,协议纠偏前需确认兼容性。
- 离线判定放宽后,真实离线的发现速度会变慢,需要平衡运维体验。
- 订单异常处理若改为延迟确认,需防止漏处理真正断电或断网导致的异常订单。
- 双实现收敛前,所有改动应明确打在老 Handler 链路上,避免误改新 Strategy 无法生效。
## 当前进度快照
- 更新时间2026-03-21 11:14:17 +0800
- `done`
- `YKC-001`
- `YKC-002`
- `YKC-004`
- `YKC-007`
- `in_progress`
- `YKC-003`
- `YKC-005`
- `todo`
- `YKC-006`
- `YKC-008`
- `YKC-009`
- `YKC-010`
## 进度记录模板
```md
### 进度记录
- 任务ID: YKC-001
- 优先级: P0
- 状态: todo
- 负责人:
- 开始时间:
- 完成时间:
- 目标: 调整 Netty 空闲断链策略,避免 30 秒无上行直接断链
- 修改文件:
- /Users/guoqiusi/Workspace/jsowell-charger-web/jsowell-netty/src/main/java/com/jsowell/netty/server/yunkuaichong/NettyServerChannelInitializer.java
- /Users/guoqiusi/Workspace/jsowell-charger-web/jsowell-netty/src/main/java/com/jsowell/netty/server/yunkuaichong/NettyServerHandler.java
- 验收结果:
- 备注/风险:
```
## 最终上线注意项
- 本次建议作为一个整体上线,包含:
- 第一批凌晨离线止血。
- `YKC-007` 云快充 1.6 每日自动对时。
- 当前最终基线以“离线判定正确性优先”为准,同时保留必要性能优化:
- 保留 `EchoServerHandler` 绑定 `businessGroup`
- 保留 `PileChannelEntity``channelId -> pileSn` 反向映射。
- 保留 `NettyServerHandler` 的消息处理耗时监控。
- 不采用“心跳全异步 + 公共线程池 DiscardOldestPolicy”组合。
- 已确认的最终收口原则:
- `HeartbeatRequestHandler``saveLastTimeAndCheckChannel()` 必须同步执行,保证任意上行立即刷新 `PILE_LAST_CONNECTION`,并立即清除 `PILE_PENDING_DISCONNECT / PILE_OFFLINE_CONFIRMED`
- `HeartbeatRequestHandler``updateStatus()` 继续异步执行,避免数据库操作拖长心跳回包链路。
- `ThreadPoolConfig.threadPoolTaskExecutor` 保持 `CallerRunsPolicy`,避免关键任务被静默丢弃。
- `PileChannelEntity.removeByPileSnAndChannelId()` 必须同步清理反向映射,避免旧连接脏数据残留。
### Quartz 配置建议
- 新增每日对时任务:
- `invokeTarget`: `jsowellTask.dailyProofreadTimeForYkcV160()`
- 不建议把任务压在 `00:00` 整点,建议放到低峰时段,例如 `03:15`
- 每日对时任务默认只扫描“当前有活连接的云快充 1.6 桩”,并按小间隔逐台发送,避免凌晨集中打满链路。
### 上线后重点观察
- 是否还出现“凌晨批量掉线后立刻落离线”的情况。
- 是否能看到 `pile_pending_disconnect` 在恢复通信后于宽限期内被清除。
- 是否出现“正式离线确认”日志明显下降。
- 是否能看到 `0x56` 下发记录与 `0x55` 应答记录。
- 是否持续出现 `【性能警告】` 日志堆积。
### 建议回归项
- 连续触发 1 次、2 次、3 次 `READER_IDLE`
- 预期仅第 3 次关闭连接。
- 第 3 次空闲关闭后 60 秒内重连:
- 预期不落 `OFF_NETWORK`,不改订单异常。
- 第 3 次空闲关闭后 60 秒内未恢复:
- 预期才正式落离线。
- 手工执行一次 `jsowellTask.dailyProofreadTimeForYkcV160()`
- 预期仅对在线云快充 1.6 桩发对时。
- 检查 `TimeCheckSettingResponseHandler`
- 预期能记录 `0x55` 对时应答及桩端回传时间。
### 编译验证记录
- 已执行:
- `mvn -pl jsowell-quartz,jsowell-netty -am -DskipTests compile`
- `mvn -pl jsowell-netty,jsowell-framework,jsowell-common -am -DskipTests compile`
- 结果:
- 通过。
## 当前建议
- 先以第一批任务为本周主线,尽快止住凌晨离线误判。
- 第二批在止血完成后推进,避免协议纠偏与连接策略变更叠加,增加联调复杂度。
- 第三批收敛放在前两批稳定后执行。