# 附录:无交易记录自动结算功能 本文档为 `feature-tracker-无交易记录自动结算.md` 的附录部分 --- ## 八、附录 ### 8.1 关键代码位置索引 | 代码位置 | 说明 | 行号 | |---------|------|------| | `TransactionRecordsRequestHandler.processOrder()` | 交易记录触发结算入口 | jsowell-netty/.../TransactionRecordsRequestHandler.java:683 | | `OrderService.manualSettlementOrder()` | 人工结算逻辑(可复用实时数据构造) | jsowell-admin/.../OrderService.java:1008-1027 | | `YKCBusinessServiceImpl` | 桩离线标记异常订单 | jsowell-netty/.../YKCBusinessServiceImpl.java:166 | | `ChargeEndHandler` | 0x19充电结束帧处理 | jsowell-netty/.../ChargeEndHandler.java | | `RealTimeMonitorData` | 实时检测数据实体类 | jsowell-common/.../domain/RealTimeMonitorData.java | | `order_monitor_data` 表 | 实时数据落库 | MySQL | | Redis `PILE_REAL_TIME_MONITOR_DATA` | 实时数据缓存 | Redis | | `settle_order_` 锁前缀 | 结算分布式锁 | Redis | ### 8.2 数据流程图 ```mermaid sequenceDiagram participant Timer as 定时任务 participant DB as 数据库 participant Redis as Redis participant OrderLogic as 结算逻辑 participant Alert as 告警系统 Timer->>Timer: 检查配置开关 alt 开关关闭 Timer->>Timer: 退出 end Timer->>DB: 查询待结算订单
(状态=待结算 + 桩在线 + 停止>10min) DB-->>Timer: 订单列表(限batch-size条) loop 遍历每个订单 Timer->>Redis: 尝试获取锁
settle_order_{orderId} alt 获取锁失败 Timer->>Timer: 跳过该订单 end Timer->>DB: 查询最后一条实时数据 alt 数据异常检查 Timer->>Timer: 检查 chargingDegree > 0 Timer->>Timer: 检查 chargingAmount > 0 Timer->>Timer: 检查金额阈值 alt 任一检查不通过 Timer->>Alert: 发送告警 Timer->>Timer: 跳过该订单 end end Timer->>Timer: 构造 TransactionRecordsData Timer->>OrderLogic: settleOrder(data, orderInfo) OrderLogic-->>Timer: 结算结果 alt 结算成功 Timer->>Timer: 记录成功日志 else 结算失败 Timer->>Alert: 发送告警 Timer->>Timer: 记录失败日志 end Timer->>Redis: 释放锁 end ``` ### 8.3 状态流转图 ```mermaid stateDiagram-v2 [*] --> 充电中: 开始充电 充电中 --> 待结算: 收到0x19结束帧
或桩停止上报 待结算 --> 已结算: 收到交易记录帧
(现有逻辑) 待结算 --> 已结算: 定时任务自动结算
(新增逻辑) 待结算 --> 已结算: 人工结算 待结算 --> 异常订单: 桩离线
(现有逻辑) 已结算 --> [*] 异常订单 --> [*] note right of 待结算 定时任务触发条件: 1. 桩在线 2. 停止>10分钟 3. 有实时数据 4. 在灰度站点范围内 end note ``` ### 8.4 配置项完整清单 ```yaml # application.yml 或 application-{env}.yml auto-settle: # ========== 基础配置 ========== enabled: false # 总开关,默认关闭 timeout-minutes: 10 # 停止超时阈值(分钟) interval: '0 */10 * * * ?' # 扫描周期(Cron表达式,每10分钟) grayscale-station-ids: # 灰度站点白名单 - 1001 - 1002 # ========== 金额与数据校验 ========== amount-threshold-ratio: 1.5 # 金额异常阈值倍数(chargingAmount > payAmount * 1.5 时告警) batch-size: 100 # 单次扫描订单数量上限 data-freshness-minutes: 30 # 实时数据新鲜度阈值(分钟) # ========== 并发与容错 ========== alert-enabled: true # 告警开关 lock-timeout-seconds: 60 # redis分布式锁超时时间(秒) retry-on-failure: false # 失败是否重试(建议false,等下一轮) # ========== 监控指标 ========== metrics: enabled: true # 是否启用Prometheus指标 prefix: "auto_settle" # 指标前缀 ``` ### 8.5 数据库查询SQL示例 #### 查询待结算订单(方案D:直接JOIN) ```sql SELECT obi.order_id, obi.order_no, obi.pile_connector_id, obi.pay_amount, obi.charge_end_time, obi.station_id, pci.connector_status, omd.charging_degree, omd.charging_amount, omd.date_time as last_monitor_time FROM order_basic_info obi INNER JOIN pile_connector_info pci ON obi.pile_connector_id = pci.connector_id LEFT JOIN ( SELECT order_id, charging_degree, charging_amount, date_time, ROW_NUMBER() OVER (PARTITION BY order_id ORDER BY date_time DESC, id DESC) as rn FROM order_monitor_data ) omd ON obi.order_id = omd.order_id AND omd.rn = 1 WHERE obi.order_status = 'STAY_SETTLEMENT' -- 待结算 AND pci.connector_status != '0' -- 桩在线 AND obi.station_id IN (?, ?, ...) -- 灰度站点 AND ( -- 依据1:有充电结束时间且超过10分钟 (obi.charge_end_time IS NOT NULL AND obi.charge_end_time < DATE_SUB(NOW(), INTERVAL 10 MINUTE)) OR -- 依据2:最后实时数据超过10分钟 (omd.date_time IS NOT NULL AND omd.date_time < DATE_SUB(NOW(), INTERVAL 10 MINUTE)) ) AND omd.charging_degree > 0 -- 有充电度数 AND omd.charging_amount > 0 -- 有充电金额 AND omd.date_time > DATE_SUB(NOW(), INTERVAL 30 MINUTE) -- 数据新鲜度 LIMIT 100; ``` #### 查询最后一条实时数据 ```sql SELECT * FROM order_monitor_data WHERE order_id = ? ORDER BY date_time DESC, id DESC LIMIT 1; ``` ### 8.6 Redis Key 设计 | Key 模式 | 说明 | 过期时间 | 示例值 | |---------|------|---------|--------| | `settle_order_{orderId}` | 结算分布式锁 | 60秒 | `settle_order_123456` | | `PILE_REAL_TIME_MONITOR_DATA:{connectorId}` | 实时数据缓存 | 持久化 | `PILE_REAL_TIME_MONITOR_DATA:ABC001-1` | | `auto_settle_alert:{orderId}` | 告警去重(可选) | 3600秒 | `auto_settle_alert_123456` | ### 8.7 日志规范 #### INFO 级别日志 ```java log.info("[自动结算] 开始扫描待结算订单,灰度站点数量:{}", stationIds.size()); log.info("[自动结算] 扫描到{}个待结算订单", orders.size()); log.info("[自动结算] 订单{}自动结算成功,金额:{}元,耗时:{}ms", orderId, chargingAmount, duration); ``` #### WARN 级别日志 ```java log.warn("[自动结算] 订单{}充电度数异常:{},跳过结算", orderId, chargingDegree); log.warn("[自动结算] 订单{}充电金额异常:{},跳过结算", orderId, chargingAmount); log.warn("[自动结算] 订单{}状态已变更,跳过结算", orderId); log.warn("[自动结算] 订单{}获取锁失败,跳过", orderId); ``` #### ERROR 级别日志(需发送告警) ```java log.error("[自动结算] 订单{}金额异常:实际{}元 > 预付{}元 * {},跳过结算", orderId, chargingAmount, payAmount, threshold); log.error("[自动结算] 订单{}结算失败:{}", orderId, e.getMessage(), e); log.error("[自动结算] 定时任务执行失败:{}", e.getMessage(), e); ``` ### 8.8 Prometheus 监控指标 ```java // 建议采集的指标 auto_settle_scan_count // 扫描次数 auto_settle_order_found_count // 发现待结算订单数 auto_settle_success_count // 结算成功数 auto_settle_skip_count{reason="lock_failed"} // 跳过数(锁失败) auto_settle_skip_count{reason="data_invalid"} // 跳过数(数据异常) auto_settle_skip_count{reason="amount_exceed"} // 跳过数(金额超限) auto_settle_failure_count // 结算失败数 auto_settle_duration_seconds // 扫描耗时 auto_settle_order_duration_seconds // 单个订单结算耗时 ``` --- ## 九、审查意见总结 ### ✅ 文档优点 1. **风险识别全面**:6个风险点覆盖了金额准确性、并发、影响面等核心问题 2. **决策清晰**:对R1(金额策略)、R4(停止判定)都有明确决策并记录 3. **实现思路稳健**:不改核心链路,新增入口,灰度上线 4. **业务条件合理**:桩在线、停止超10分钟、有实时数据,三个条件都是必要的 ### ⚠️ 需补充内容(已在第六章列出) | 类别 | 问题编号 | 状态 | 优先级 | |------|---------|------|--------| | 并发幂等 | 6.1, 6.5 | ⬜ 待回答 | P0 | | 数据校验 | 6.2, 6.4, 6.7 | ⬜ 待回答 | P0 | | 性能优化 | 6.3 | ⬜ 待回答 | P1 | | 业务逻辑 | 6.6 | ⬜ 待回答 | P0 | | 配置设计 | 6.8 | ⬜ 待回答 | P1 | **建议行动路径**: 1. ✅ 步骤1:需求评审确认(已完成) 2. 🔴 步骤2:回答第六章的8个待明确问题(**当前阻塞**) 3. ⬜ 步骤3:更新文档版本号为 v0.4 4. ⬜ 步骤4:进入开发阶段(第五章进度追踪) --- ## 十、版本历史 | 版本 | 日期 | 变更内容 | 作者 | |------|------|---------|------| | v0.1 | 2026-08-11 | 初稿:需求分析、风险点、实现方案、开发进度追踪框架 | - | | v0.2 | 2026-08-11 | 确认决策:金额以桩端 chargingAmount 为准;双依据判定都启用;特定站点灰度 | - | | v0.3 | 2026-08-11 | 新增8个待明确问题(第六章);扩充测试计划至29个场景(第七章);新增附录 | Claude | | v0.4 | 2026-08-11 | ✅ 完成8个问题的决策回答;更新已决策问题列表(11个决策);状态更新为"方案已确认,待开发" | Claude | --- **文档结束**