mirror of
https://codeup.aliyun.com/67c68d4e484ca2f0a13ac3c1/ydc/jsowell-charger-web.git
synced 2026-08-13 17:53:45 +08:00
Merge branch 'feature/auto-settle-no-transaction' into dev
This commit is contained in:
@@ -214,3 +214,27 @@ sms:
|
||||
enable: false
|
||||
host: 127.0.0.1
|
||||
port: 8080
|
||||
|
||||
# 无交易记录自动结算配置
|
||||
auto-settle:
|
||||
# 总开关,默认关闭
|
||||
enabled: false
|
||||
# 停止充电超时阈值(分钟),超过此时间未收到交易记录则触发自动结算
|
||||
timeout-minutes: 10
|
||||
# 定时任务扫描周期(Cron表达式),每10分钟扫描一次
|
||||
interval: '0 */10 * * * ?'
|
||||
# 灰度站点白名单(站点ID列表),仅对这些站点启用自动结算
|
||||
grayscale-station-ids: []
|
||||
# 金额异常阈值倍数,chargingAmount > payAmount * 此值时告警跳过
|
||||
amount-threshold-ratio: 1.5
|
||||
# 单次扫描订单数量上限,防止一次查询过多订单
|
||||
batch-size: 100
|
||||
# 实时数据新鲜度阈值(分钟),数据超过此时间未更新视为桩假在线
|
||||
data-freshness-minutes: 30
|
||||
# 告警开关
|
||||
alert-enabled: true
|
||||
# Redis分布式锁超时时间(秒)
|
||||
lock-timeout-seconds: 60
|
||||
# 失败是否重试,建议false等待下一轮扫描
|
||||
retry-on-failure: false
|
||||
|
||||
|
||||
@@ -0,0 +1,208 @@
|
||||
package com.jsowell;
|
||||
|
||||
import com.jsowell.pile.config.AutoSettleConfig;
|
||||
import com.jsowell.pile.domain.OrderBasicInfo;
|
||||
import com.jsowell.pile.mapper.OrderBasicInfoMapper;
|
||||
import com.jsowell.pile.service.OrderBasicInfoService;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 无交易记录自动结算功能测试
|
||||
*
|
||||
* @author jsowell
|
||||
* @date 2026-08-11
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest
|
||||
public class AutoSettleOrdersWithoutTransactionTest {
|
||||
|
||||
@Autowired
|
||||
private OrderBasicInfoService orderBasicInfoService;
|
||||
|
||||
@Autowired
|
||||
private OrderBasicInfoMapper orderBasicInfoMapper;
|
||||
|
||||
@Autowired
|
||||
private AutoSettleConfig autoSettleConfig;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
System.out.println("=== 无交易记录自动结算测试开始 ===");
|
||||
System.out.println("配置信息:");
|
||||
System.out.println(" - 功能开关:" + autoSettleConfig.getEnabled());
|
||||
System.out.println(" - 超时阈值:" + autoSettleConfig.getTimeoutMinutes() + " 分钟");
|
||||
System.out.println(" - 批次大小:" + autoSettleConfig.getBatchSize());
|
||||
System.out.println(" - 金额阈值倍数:" + autoSettleConfig.getAmountThresholdRatio());
|
||||
System.out.println(" - 数据新鲜度:" + autoSettleConfig.getDataFreshnessMinutes() + " 分钟");
|
||||
System.out.println(" - 灰度站点:" + autoSettleConfig.getGrayscaleStationIds());
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试1:查询待结算订单
|
||||
*/
|
||||
@Test
|
||||
public void testSelectPendingAutoSettleOrders() {
|
||||
System.out.println("\n【测试1】查询待结算订单");
|
||||
|
||||
LocalDateTime cutoffTime = LocalDateTime.now().minusMinutes(autoSettleConfig.getTimeoutMinutes());
|
||||
List<OrderBasicInfo> orders = orderBasicInfoMapper.selectPendingAutoSettleOrders(
|
||||
cutoffTime,
|
||||
autoSettleConfig.getGrayscaleStationIds(),
|
||||
autoSettleConfig.getBatchSize()
|
||||
);
|
||||
|
||||
System.out.println("查询结果:");
|
||||
System.out.println(" - 截止时间:" + cutoffTime);
|
||||
System.out.println(" - 查询数量:" + orders.size());
|
||||
|
||||
if (!orders.isEmpty()) {
|
||||
System.out.println(" - 样例订单:");
|
||||
OrderBasicInfo sample = orders.get(0);
|
||||
System.out.println(" 订单号:" + sample.getOrderCode());
|
||||
System.out.println(" 站点ID:" + sample.getStationId());
|
||||
System.out.println(" 充电桩:" + sample.getPileSn());
|
||||
System.out.println(" 结束时间:" + sample.getChargeEndTime());
|
||||
System.out.println(" 支付金额:" + sample.getPayAmount());
|
||||
}
|
||||
|
||||
assert orders.size() <= autoSettleConfig.getBatchSize() : "查询结果超过批次大小限制";
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试2:灰度配置测试
|
||||
*/
|
||||
@Test
|
||||
public void testGrayscaleConfiguration() {
|
||||
System.out.println("\n【测试2】灰度配置测试");
|
||||
|
||||
boolean grayscaleEnabled = autoSettleConfig.isGrayscaleEnabled();
|
||||
System.out.println("灰度是否启用:" + grayscaleEnabled);
|
||||
|
||||
if (grayscaleEnabled) {
|
||||
List<Long> stationIds = autoSettleConfig.getGrayscaleStationIds();
|
||||
System.out.println("灰度站点数量:" + stationIds.size());
|
||||
System.out.println("灰度站点列表:" + stationIds);
|
||||
|
||||
// 测试站点判断
|
||||
Long testStationId = stationIds.get(0);
|
||||
boolean inGrayscale = autoSettleConfig.isStationInGrayscale(testStationId);
|
||||
System.out.println("站点 " + testStationId + " 在灰度范围内:" + inGrayscale);
|
||||
assert inGrayscale : "灰度站点判断错误";
|
||||
|
||||
boolean notInGrayscale = autoSettleConfig.isStationInGrayscale(999999L);
|
||||
System.out.println("站点 999999 在灰度范围内:" + notInGrayscale);
|
||||
assert !notInGrayscale : "非灰度站点判断错误";
|
||||
} else {
|
||||
System.out.println("未启用灰度,全量上线模式");
|
||||
boolean allInGrayscale = autoSettleConfig.isStationInGrayscale(1L);
|
||||
assert allInGrayscale : "全量模式下所有站点应该在灰度范围内";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试3:金额阈值检测
|
||||
*/
|
||||
@Test
|
||||
public void testAmountThresholdValidation() {
|
||||
System.out.println("\n【测试3】金额阈值检测");
|
||||
|
||||
BigDecimal payAmount = new BigDecimal("10.00");
|
||||
BigDecimal threshold = payAmount.multiply(BigDecimal.valueOf(autoSettleConfig.getAmountThresholdRatio()));
|
||||
|
||||
System.out.println("已支付金额:" + payAmount + " 元");
|
||||
System.out.println("阈值倍数:" + autoSettleConfig.getAmountThresholdRatio());
|
||||
System.out.println("告警阈值:" + threshold + " 元");
|
||||
|
||||
// 正常金额
|
||||
BigDecimal normalAmount = new BigDecimal("8.00");
|
||||
boolean normalPass = normalAmount.compareTo(threshold) <= 0;
|
||||
System.out.println("充电金额 " + normalAmount + " 元 <= 阈值:" + normalPass);
|
||||
assert normalPass : "正常金额应该通过检测";
|
||||
|
||||
// 异常金额
|
||||
BigDecimal abnormalAmount = new BigDecimal("16.00");
|
||||
boolean abnormalPass = abnormalAmount.compareTo(threshold) <= 0;
|
||||
System.out.println("充电金额 " + abnormalAmount + " 元 <= 阈值:" + abnormalPass);
|
||||
assert !abnormalPass : "异常金额应该被告警";
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试4:配置开关测试
|
||||
*/
|
||||
@Test
|
||||
public void testFeatureToggle() {
|
||||
System.out.println("\n【测试4】配置开关测试");
|
||||
|
||||
Boolean enabled = autoSettleConfig.getEnabled();
|
||||
System.out.println("功能总开关:" + enabled);
|
||||
|
||||
if (!enabled) {
|
||||
System.out.println("功能已关闭,自动结算不会执行");
|
||||
System.out.println("如需测试,请在配置文件中设置 auto-settle.enabled=true");
|
||||
} else {
|
||||
System.out.println("功能已开启");
|
||||
}
|
||||
|
||||
Boolean alertEnabled = autoSettleConfig.getAlertEnabled();
|
||||
System.out.println("告警开关:" + alertEnabled);
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试5:完整流程测试(需要功能开关打开)
|
||||
*/
|
||||
@Test
|
||||
public void testAutoSettleMainFlow() {
|
||||
System.out.println("\n【测试5】完整流程测试");
|
||||
|
||||
if (!autoSettleConfig.getEnabled()) {
|
||||
System.out.println("功能未开启,跳过完整流程测试");
|
||||
System.out.println("如需测试,请在配置文件中设置 auto-settle.enabled=true");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// 执行自动结算
|
||||
orderBasicInfoService.autoSettleOrdersWithoutTransactionRecord();
|
||||
System.out.println("自动结算执行完成,请查看日志输出");
|
||||
} catch (Exception e) {
|
||||
System.err.println("自动结算执行失败:" + e.getMessage());
|
||||
e.printStackTrace();
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试6:数据新鲜度判断
|
||||
*/
|
||||
@Test
|
||||
public void testDataFreshness() {
|
||||
System.out.println("\n【测试6】数据新鲜度判断");
|
||||
|
||||
Integer freshnessMinutes = autoSettleConfig.getDataFreshnessMinutes();
|
||||
System.out.println("数据新鲜度阈值:" + freshnessMinutes + " 分钟");
|
||||
|
||||
// 模拟新鲜数据
|
||||
LocalDateTime freshTime = LocalDateTime.now().minusMinutes(10);
|
||||
long freshMinutes = java.time.Duration.between(freshTime, LocalDateTime.now()).toMinutes();
|
||||
boolean isFresh = freshMinutes <= freshnessMinutes;
|
||||
System.out.println("数据时间距今 " + freshMinutes + " 分钟,是否新鲜:" + isFresh);
|
||||
assert isFresh : "10分钟前的数据应该是新鲜的";
|
||||
|
||||
// 模拟陈旧数据
|
||||
LocalDateTime staleTime = LocalDateTime.now().minusMinutes(60);
|
||||
long staleMinutes = java.time.Duration.between(staleTime, LocalDateTime.now()).toMinutes();
|
||||
boolean isStale = staleMinutes > freshnessMinutes;
|
||||
System.out.println("数据时间距今 " + staleMinutes + " 分钟,是否陈旧:" + isStale);
|
||||
assert isStale : "60分钟前的数据应该是陈旧的";
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user