mirror of
https://codeup.aliyun.com/67c68d4e484ca2f0a13ac3c1/ydc/jsowell-charger-web.git
synced 2026-08-13 17:53:45 +08:00
274 lines
9.8 KiB
Markdown
274 lines
9.8 KiB
Markdown
|
|
# 附录:无交易记录自动结算功能
|
|||
|
|
|
|||
|
|
本文档为 `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: 查询待结算订单<br/>(状态=待结算 + 桩在线 + 停止>10min)
|
|||
|
|
DB-->>Timer: 订单列表(限batch-size条)
|
|||
|
|
|
|||
|
|
loop 遍历每个订单
|
|||
|
|
Timer->>Redis: 尝试获取锁<br/>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结束帧<br/>或桩停止上报
|
|||
|
|
|
|||
|
|
待结算 --> 已结算: 收到交易记录帧<br/>(现有逻辑)
|
|||
|
|
待结算 --> 已结算: 定时任务自动结算<br/>(新增逻辑)
|
|||
|
|
待结算 --> 已结算: 人工结算
|
|||
|
|
|
|||
|
|
待结算 --> 异常订单: 桩离线<br/>(现有逻辑)
|
|||
|
|
|
|||
|
|
已结算 --> [*]
|
|||
|
|
异常订单 --> [*]
|
|||
|
|
|
|||
|
|
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 |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
**文档结束**
|