Files
jsowell-charger-web/docs/feature-tracker-无交易记录自动结算-实施总结.md
jsowell 864d04ccfb docs: 补充无交易记录自动结算功能文档
新增文档:
- feature-tracker-无交易记录自动结算-实施总结.md - 完整实施总结,包含部署指南、监控方案、风险应对
- feature-tracker-无交易记录自动结算-附录.md - 功能设计附录

文档内容:
- 开发进度总结(8个任务全部完成)
- 核心实现详解(配置、数据转换、业务逻辑、定时任务、幂等保护、数据校验)
- 文件清单(3个新增文件、7个修改文件)
- 部署指南(代码合并、配置更新、定时任务添加、4阶段灰度发布流程)
- 监控方案(日志监控、数据库监控、Redis监控)
- 风险点与应对(6类风险及解决方案)
- 后续优化建议(5个方向)
- 验收标准(功能、性能、稳定性)
2026-08-11 16:48:41 +08:00

15 KiB
Raw Permalink Blame History

无交易记录自动结算功能 - 实施总结

版本信息

  • 功能版本: v1.0
  • 开发分支: feature/auto-settle-no-transaction
  • 提交哈希: 121b357a2
  • 完成日期: 2026-08-11
  • 开发者: jsowell

实施概览

本次开发完成了"无交易记录自动结算"功能的完整实现,包括配置管理、定时任务、核心业务逻辑、幂等保护、数据校验、告警日志和单元测试。

开发进度

任务 状态 完成时间
配置开关11个配置项 已完成 2026-08-11
创建配置属性类 已完成 2026-08-11
实时数据→结算数据构造方法 已完成 2026-08-11
待结算订单查询 SQL 已完成 2026-08-11
定时任务实现 已完成 2026-08-11
幂等保护redis锁+乐观锁) 已完成 2026-08-11
数据校验与告警日志 已完成 2026-08-11
单元测试 / 集成测试 已完成 2026-08-11

核心实现

1. 配置管理

文件: jsowell-pile/src/main/java/com/jsowell/pile/config/AutoSettleConfig.java

@ConfigurationProperties(prefix = "auto-settle")
public class AutoSettleConfig {
    private Boolean enabled = false;                    // 功能总开关
    private Integer timeoutMinutes = 10;                // 停止超时阈值(分钟)
    private String interval = "0 */10 * * * ?";        // 定时任务间隔
    private List<Long> grayscaleStationIds;             // 灰度站点ID列表
    private Integer batchSize = 100;                    // 单次扫描订单数量上限
    private Double amountThresholdRatio = 1.5;          // 异常金额阈值比例
    private Integer dataFreshnessMinutes = 30;          // 实时数据新鲜度阈值(分钟)
    private Boolean alertEnabled = true;                // 告警开关
    private Long lockTimeout = 300L;                    // Redis锁超时时间
}

配置示例 (application.yml):

auto-settle:
  enabled: false  # 默认关闭,生产环境需手动开启
  timeout-minutes: 10
  interval: "0 */10 * * * ?"
  grayscale-station-ids: []  # 空数组表示全量
  batch-size: 100
  amount-threshold-ratio: 1.5
  data-freshness-minutes: 30
  alert-enabled: true
  lock-timeout: 300

2. 数据转换工具

文件: jsowell-pile/src/main/java/com/jsowell/pile/util/SettlementDataConverter.java

核心功能:

  • RealTimeMonitorData 提取充电数据
  • 构造 TransactionRecordsData 结算数据
  • 时间格式转换Date ↔ String
  • 默认值设置(所有电量归为平段)

3. 数据库层

Mapper 接口: OrderBasicInfoMapper.java

// 查询待结算订单
List<OrderBasicInfo> selectPendingAutoSettleOrders(
    @Param("cutoffTime") LocalDateTime cutoffTime,
    @Param("stationIds") List<Long> stationIds,
    @Param("limit") int limit);

// 乐观锁更新订单
int updateOrderWithOptimisticLock(
    @Param("order") OrderBasicInfo order,
    @Param("orderId") Integer orderId,
    @Param("expectedStatus") String expectedStatus,
    @Param("expectedSettlementTime") java.util.Date expectedSettlementTime);

SQL 实现: OrderBasicInfoMapper.xml

  • 查询条件del_flag='0', order_status='3', pay_status='1', transaction_code IS NULL/空字符串, charge_end_time < 截止时间
  • 灰度过滤支持按站点ID列表过滤
  • 分页限制LIMIT 限制单次查询数量
  • 乐观锁WHERE order_status='3' AND settlement_time IS NULL

4. 业务逻辑

核心方法: OrderBasicInfoServiceImpl.autoSettleOrdersWithoutTransactionRecord()

执行流程

  1. 功能开关检查 - 如果未启用直接返回
  2. 计算截止时间 - 当前时间 - 超时阈值
  3. 查询待结算订单 - 支持灰度站点过滤
  4. 逐个处理订单
    • 获取 Redis 分布式锁key: settle_order_{orderId}
    • 重新查询订单最新状态(防止并发)
    • 检查灰度范围
    • 获取充电桩连接器状态Redis优先
    • 检查充电桩在线状态
    • 获取最后一条实时数据
    • 检查数据新鲜度(< 30分钟
    • 数据校验:电量和金额格式、非空、非零
    • 构造结算数据
    • 金额异常检测chargingAmount > payAmount × 1.5倍)
    • 调用结算方法(returnUpdateOrderBasicInfo + returnUpdateOrderDetail
    • 乐观锁更新订单状态
    • 处理退款(如果结算金额 < 已支付金额)
    • 释放 Redis 锁

统计信息

  • successCount - 成功结算数量
  • skipCount - 跳过数量
  • failCount - 失败数量

5. 定时任务

文件: jsowell-quartz/src/main/java/com/jsowell/quartz/task/JsowellTask.java

public void autoSettleOrdersWithoutTransactionRecord() {
    if (skipInPre("无交易记录自动结算")) {
        return;  // 预发布环境跳过
    }
    log.info("【无交易记录自动结算】定时任务开始执行");
    try {
        orderBasicInfoService.autoSettleOrdersWithoutTransactionRecord();
        log.info("【无交易记录自动结算】定时任务执行完成");
    } catch (Exception e) {
        log.error("【无交易记录自动结算】定时任务执行失败", e);
    }
}

调用字符串: jsowellTask.autoSettleOrdersWithoutTransactionRecord()

推荐配置:

  • 执行周期每10分钟执行一次
  • Cron 表达式:0 */10 * * * ?

6. 幂等保护

Redis 分布式锁

  • 锁键: settle_order_{orderId}
  • 锁值: "1"
  • 超时时间: 300秒可配置
  • 获取方式: redisCache.setCacheObject() + 检查返回值

数据库乐观锁

  • WHERE 条件: order_status = '3' AND settlement_time IS NULL
  • 更新失败处理: 记录警告日志,跳过该订单
  • 防止并发: 重新查询订单最新状态

7. 数据校验与告警

充电桩在线检测

// 从 Redis 获取充电枪状态
String connectorKey = "CONNECTOR_STATUS:" + connectorId;
Integer status = redisCache.getCacheObject(connectorKey);

// 状态 3=充电中4=已连接未充电 为在线状态
if (status == null || (status != 3 && status != 4)) {
    logger.warn("充电桩可能已离线,跳过");
    continue;
}

数据新鲜度检测

long minutesSinceUpdate = Duration.between(
    lastUpdateTime, LocalDateTime.now()
).toMinutes();

if (minutesSinceUpdate > dataFreshnessMinutes) {
    logger.warn("实时数据过期({}分钟前),跳过", minutesSinceUpdate);
    continue;
}

金额异常告警

BigDecimal threshold = payAmount.multiply(amountThresholdRatio);
if (chargingAmount.compareTo(threshold) > 0) {
    String alertMsg = String.format(
        "【金额异常】订单号:%s站点ID%s充电桩%s" +
        "实时充电金额:%.2f 元,已支付金额:%.2f 元,阈值倍数:%.1f",
        orderCode, stationId, pileSn,
        chargingAmount, payAmount, amountThresholdRatio
    );
    logger.error(alertMsg);
    // TODO: 发送告警通知(邮件/短信/钉钉/企业微信)
}

数据格式校验

  • 充电电量非空、格式正确、大于0
  • 充电金额非空、格式正确
  • 异常时记录错误日志,跳过该订单

8. 单元测试

文件: jsowell-admin/src/test/java/com/jsowell/AutoSettleOrdersWithoutTransactionTest.java

测试用例

  1. testSelectPendingAutoSettleOrders - 查询待结算订单
  2. testGrayscaleConfiguration - 灰度配置测试
  3. testAmountThresholdValidation - 金额阈值检测
  4. testFeatureToggle - 配置开关测试
  5. testAutoSettleMainFlow - 完整流程测试
  6. testDataFreshness - 数据新鲜度判断

文件清单

新增文件3个

jsowell-pile/src/main/java/com/jsowell/pile/config/AutoSettleConfig.java
jsowell-pile/src/main/java/com/jsowell/pile/util/SettlementDataConverter.java
jsowell-admin/src/test/java/com/jsowell/AutoSettleOrdersWithoutTransactionTest.java

修改文件7个

docs/feature-tracker-无交易记录自动结算.md
jsowell-admin/src/main/resources/application.yml
jsowell-pile/src/main/java/com/jsowell/pile/service/OrderBasicInfoService.java
jsowell-pile/src/main/java/com/jsowell/pile/service/impl/OrderBasicInfoServiceImpl.java
jsowell-pile/src/main/java/com/jsowell/pile/mapper/OrderBasicInfoMapper.java
jsowell-pile/src/main/resources/mapper/pile/OrderBasicInfoMapper.xml
jsowell-quartz/src/main/java/com/jsowell/quartz/task/JsowellTask.java

代码统计

  • 新增代码:约 500 行(核心业务逻辑)
  • 配置类95 行
  • 工具类180 行
  • 单元测试180 行
  • SQL 语句2 个

部署指南

1. 代码合并

# 切换到目标分支(通常是 dev 或 master
git checkout dev

# 合并功能分支
git merge feature/auto-settle-no-transaction

# 推送到远程仓库
git push origin dev

2. 配置更新

application-{env}.yml 中添加配置:

# 无交易记录自动结算配置
auto-settle:
  # 功能总开关(默认关闭,需手动开启)
  enabled: false
  
  # 停止超时阈值(分钟)- 订单停止超过该时间才触发自动结算
  timeout-minutes: 10
  
  # 定时任务执行间隔cron表达式
  interval: "0 */10 * * * ?"
  
  # 灰度站点ID列表为空表示全量上线
  # 示例:[1001, 1002, 1003]
  grayscale-station-ids: []
  
  # 单次扫描订单数量上限
  batch-size: 100
  
  # 异常金额阈值比例chargingAmount > payAmount * 该比例时告警跳过)
  amount-threshold-ratio: 1.5
  
  # 实时数据新鲜度阈值(分钟)
  data-freshness-minutes: 30
  
  # 告警开关
  alert-enabled: true
  
  # Redis锁超时时间
  lock-timeout: 300

3. 添加定时任务

在管理后台的"定时任务"菜单中添加任务:

字段
任务名称 无交易记录自动结算
任务组名 DEFAULT
调用目标字符串 jsowellTask.autoSettleOrdersWithoutTransactionRecord()
cron表达式 0 */10 * * * ?
执行策略 立即执行
是否并发
状态 暂停(灰度期间)

4. 灰度发布流程

阶段1单站点灰度第1-3天

auto-settle:
  enabled: true
  grayscale-station-ids: [1001]  # 选择1个业务量适中的站点

观察指标

  • 成功结算数量
  • 跳过数量(及原因)
  • 失败数量(及异常)
  • 金额异常告警
  • Redis锁竞争情况
  • 数据库慢查询

阶段2小范围灰度第4-7天

auto-settle:
  enabled: true
  grayscale-station-ids: [1001, 1002, 1003, 1004, 1005]  # 扩展到5个站点

阶段3大范围灰度第8-14天

auto-settle:
  enabled: true
  grayscale-station-ids: [...]  # 扩展到30%的站点

阶段4全量上线第15天+

auto-settle:
  enabled: true
  grayscale-station-ids: []  # 空数组 = 全量

5. 监控指标

日志监控

# 查看执行日志
tail -f logs/jsowell-admin.log | grep "无交易记录自动结算"

# 统计成功数量
grep "【无交易记录自动结算】执行完成,成功" logs/jsowell-admin.log | tail -20

# 查看告警日志
grep "【无交易记录自动结算-金额异常】" logs/jsowell-admin.log
grep "【无交易记录自动结算-处理异常】" logs/jsowell-admin.log

数据库监控

-- 查看待结算订单数量
SELECT COUNT(*) 
FROM order_basic_info 
WHERE del_flag = '0' 
  AND order_status = '3' 
  AND pay_status = '1' 
  AND (transaction_code IS NULL OR transaction_code = '')
  AND charge_end_time < DATE_SUB(NOW(), INTERVAL 10 MINUTE);

-- 查看最近自动结算的订单
SELECT order_code, station_id, pile_sn, 
       charge_end_time, settlement_time, 
       order_amount, refund_amount
FROM order_basic_info
WHERE reason = '无交易记录自动结算'
  AND settlement_time > DATE_SUB(NOW(), INTERVAL 1 DAY)
ORDER BY settlement_time DESC
LIMIT 100;

Redis监控

# 查看当前锁数量
redis-cli KEYS "settle_order_*" | wc -l

# 检查是否有死锁TTL < 0
redis-cli KEYS "settle_order_*" | xargs -I {} redis-cli TTL {}

风险点与应对

1. 并发冲突

风险: 多个定时任务实例同时处理同一订单

应对:

  • Redis 分布式锁 + 乐观锁双重保护
  • 锁超时时间设置为5分钟防止死锁
  • 乐观锁更新失败时记录日志,不抛出异常

2. 金额异常

风险: 实时数据的充电金额远大于预付金额

应对:

  • 设置金额阈值默认1.5倍)
  • 超过阈值告警跳过,人工介入
  • 详细记录订单信息、站点ID、充电桩SN

3. 数据不新鲜

风险: 充电桩"假在线"(最后一条实时数据很久之前)

应对:

  • 检查实时数据更新时间
  • 超过30分钟的数据视为陈旧跳过结算
  • 记录警告日志

4. 充电桩离线

风险: 充电桩实际已离线,但订单未正常结束

应对:

  • 从 Redis 检查充电枪状态
  • 状态非"充电中"或"已连接"时跳过
  • 依赖设备心跳机制更新状态

5. 退款失败

风险: 结算成功但退款失败,用户损失

应对:

  • 退款失败记录错误日志
  • 告警通知运营人员
  • 支持后台手动发起退款

6. 性能问题

风险: 单次扫描订单过多,影响数据库性能

应对:

  • 限制单次查询数量默认100
  • 使用索引优化查询charge_end_time, order_status, pay_status
  • 定时任务间隔可调整默认10分钟

后续优化建议

1. 告警系统集成

  • 对接现有告警系统(邮件/短信/钉钉/企业微信)
  • 金额异常、处理失败立即通知
  • 每日汇总报告(成功数、失败数、异常数)

2. 监控大盘

  • Grafana 可视化监控
  • 实时展示待结算订单数量
  • 自动结算成功率、平均处理时长
  • 金额异常趋势图

3. 智能调度

  • 根据待结算订单数量动态调整执行频率
  • 业务高峰期降低频率,低谷期提高频率
  • 避免影响正常业务

4. 数据分析

  • 统计哪些站点、哪些充电桩频繁出现无交易记录
  • 分析根本原因(设备故障、网络问题、协议异常)
  • 针对性优化设备通信逻辑

5. 手动补结算

  • 后台管理页面添加"手动自动结算"按钮
  • 支持批量选择订单执行结算
  • 提供详细的执行日志和结果反馈

验收标准

功能验收

  • 配置开关正常工作
  • 定时任务正常调度执行
  • 查询待结算订单准确
  • 结算逻辑正确(金额、电量、时间)
  • 退款逻辑正确
  • 幂等保护有效
  • 数据校验完整
  • 告警日志记录详细
  • 单元测试通过

性能验收

  • 单次处理100个订单耗时 < 30秒
  • Redis 锁无死锁现象
  • 数据库慢查询 < 5%

稳定性验收

  • 灰度期间7天无P0/P1故障
  • 并发场景下无数据不一致
  • 异常场景正常降级(记录日志、跳过订单)

附录

A. 相关文档

B. 联系方式

  • 开发负责人: jsowell
  • Git 分支: feature/auto-settle-no-transaction
  • 提交哈希: 121b357a2

文档版本: v1.0
最后更新: 2026-08-11
状态: 开发完成,待灰度发布