|
@@ -1,9 +1,8 @@
|
|
|
package org.dromara.server.consume.check;
|
|
package org.dromara.server.consume.check;
|
|
|
-import java.util.Date;
|
|
|
|
|
|
|
|
|
|
-import cn.hutool.core.bean.BeanUtil;
|
|
|
|
|
import cn.hutool.core.date.DateUtil;
|
|
import cn.hutool.core.date.DateUtil;
|
|
|
import cn.hutool.core.util.ObjectUtil;
|
|
import cn.hutool.core.util.ObjectUtil;
|
|
|
|
|
+import cn.hutool.core.util.StrUtil;
|
|
|
import lombok.RequiredArgsConstructor;
|
|
import lombok.RequiredArgsConstructor;
|
|
|
import lombok.extern.slf4j.Slf4j;
|
|
import lombok.extern.slf4j.Slf4j;
|
|
|
import org.dromara.backstage.api.domain.vo.RemoteCardVo;
|
|
import org.dromara.backstage.api.domain.vo.RemoteCardVo;
|
|
@@ -16,12 +15,12 @@ import org.dromara.common.core.domain.model.ErrorInfo;
|
|
|
import org.dromara.common.core.enums.ConsumeRecordTypeEnum;
|
|
import org.dromara.common.core.enums.ConsumeRecordTypeEnum;
|
|
|
import org.dromara.common.core.enums.TradeStatusEnum;
|
|
import org.dromara.common.core.enums.TradeStatusEnum;
|
|
|
import org.dromara.common.core.utils.RecordIdUtils;
|
|
import org.dromara.common.core.utils.RecordIdUtils;
|
|
|
-import org.dromara.common.json.utils.JsonUtils;
|
|
|
|
|
import org.dromara.common.redis.utils.RedisUtils;
|
|
import org.dromara.common.redis.utils.RedisUtils;
|
|
|
import org.dromara.server.common.domain.consume.bo.ConsumptionBo;
|
|
import org.dromara.server.common.domain.consume.bo.ConsumptionBo;
|
|
|
import org.dromara.server.common.util.CardDateUtils;
|
|
import org.dromara.server.common.util.CardDateUtils;
|
|
|
import org.dromara.server.consume.business.BaseBusiness;
|
|
import org.dromara.server.consume.business.BaseBusiness;
|
|
|
import org.dromara.server.consume.domain.convert.RemoteVoConvert;
|
|
import org.dromara.server.consume.domain.convert.RemoteVoConvert;
|
|
|
|
|
+import org.dromara.server.consume.domain.vo.PtBagVo;
|
|
|
import org.dromara.server.consume.domain.vo.XfCardLimitedVo;
|
|
import org.dromara.server.consume.domain.vo.XfCardLimitedVo;
|
|
|
import org.dromara.server.consume.domain.vo.XfConsumeDetailOriginalVo;
|
|
import org.dromara.server.consume.domain.vo.XfConsumeDetailOriginalVo;
|
|
|
import org.dromara.server.consume.domain.vo.XfTermVo;
|
|
import org.dromara.server.consume.domain.vo.XfTermVo;
|
|
@@ -31,14 +30,14 @@ import org.springframework.stereotype.Service;
|
|
|
|
|
|
|
|
import java.math.BigDecimal;
|
|
import java.math.BigDecimal;
|
|
|
import java.text.MessageFormat;
|
|
import java.text.MessageFormat;
|
|
|
-import java.time.Duration;
|
|
|
|
|
import java.time.LocalDate;
|
|
import java.time.LocalDate;
|
|
|
import java.time.LocalDateTime;
|
|
import java.time.LocalDateTime;
|
|
|
import java.time.ZoneId;
|
|
import java.time.ZoneId;
|
|
|
-import java.util.*;
|
|
|
|
|
-import java.util.concurrent.*;
|
|
|
|
|
-import java.util.concurrent.atomic.AtomicReference;
|
|
|
|
|
-import java.util.function.Supplier;
|
|
|
|
|
|
|
+import java.util.Date;
|
|
|
|
|
+import java.util.List;
|
|
|
|
|
+import java.util.Map;
|
|
|
|
|
+import java.util.Optional;
|
|
|
|
|
+import java.util.concurrent.TimeUnit;
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
* 请求消费业务处理类
|
|
* 请求消费业务处理类
|
|
@@ -62,12 +61,13 @@ public class ConsumeRequestCheck {
|
|
|
|
|
|
|
|
public R<ErrorInfo> checkConsume(ConsumptionBo bo, RemoteUserAccountVo userAccountVo,
|
|
public R<ErrorInfo> checkConsume(ConsumptionBo bo, RemoteUserAccountVo userAccountVo,
|
|
|
RemoteCardVo userCardVo, XfTermVo useTermVo,
|
|
RemoteCardVo userCardVo, XfTermVo useTermVo,
|
|
|
- Map<String, Boolean> mapCardLimited, XfCardLimitedVo cardLimitedVo) {
|
|
|
|
|
- // 如果消费时间为2000年,认为消费机时钟错误,更改消费时间为当前时间
|
|
|
|
|
|
|
+ Map<String, Boolean> mapCardLimited, XfCardLimitedVo cardLimitedVo, List<PtBagVo> deductionBags) {
|
|
|
|
|
+ // 如果消费时间为2000年或大于当前年份,认为消费机时钟错误,更改消费时间为当前时间
|
|
|
Date consumeDate = bo.getConsumeDate();
|
|
Date consumeDate = bo.getConsumeDate();
|
|
|
LocalDate localDate = consumeDate.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
|
|
LocalDate localDate = consumeDate.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
|
|
|
|
|
+ int justYear = DateUtil.year(new Date());
|
|
|
|
|
|
|
|
- if (localDate.getYear() == 2000) {
|
|
|
|
|
|
|
+ if (localDate.getYear() == 2000 || localDate.getYear() > justYear) {
|
|
|
consumeDate = new Date();
|
|
consumeDate = new Date();
|
|
|
bo.setConsumeDate(consumeDate);
|
|
bo.setConsumeDate(consumeDate);
|
|
|
}
|
|
}
|
|
@@ -79,7 +79,7 @@ public class ConsumeRequestCheck {
|
|
|
// 获取餐类
|
|
// 获取餐类
|
|
|
long startTime = System.currentTimeMillis();
|
|
long startTime = System.currentTimeMillis();
|
|
|
RemoteMealTypeVo remoteMealTypeVo = commonCheck.getMealType(consumeDate);
|
|
RemoteMealTypeVo remoteMealTypeVo = commonCheck.getMealType(consumeDate);
|
|
|
- log.info("[请求交易]-[获取餐类当前餐类]-[耗时:{} ms] ", System.currentTimeMillis()-startTime);
|
|
|
|
|
|
|
+ log.info("[请求交易]-[获取餐类当前餐类]-[耗时:{} ms] ", System.currentTimeMillis() - startTime);
|
|
|
if (ObjectUtil.isEmpty(remoteMealTypeVo)) {
|
|
if (ObjectUtil.isEmpty(remoteMealTypeVo)) {
|
|
|
return CheckError.createErrorResponse(400, ApiErrorTypeConstants.NOT_FOUND, "不在交易时段",
|
|
return CheckError.createErrorResponse(400, ApiErrorTypeConstants.NOT_FOUND, "不在交易时段",
|
|
|
MessageFormat.format("非营业时段,不允许交易。消费时间[{0}]",
|
|
MessageFormat.format("非营业时段,不允许交易。消费时间[{0}]",
|
|
@@ -93,7 +93,6 @@ public class ConsumeRequestCheck {
|
|
|
int statusFlag = bo.getStatusFlag();
|
|
int statusFlag = bo.getStatusFlag();
|
|
|
if (statusFlag == Integer.parseInt(ConsumeRecordTypeEnum.XFJXF_1.code())
|
|
if (statusFlag == Integer.parseInt(ConsumeRecordTypeEnum.XFJXF_1.code())
|
|
|
|| statusFlag == Integer.parseInt(ConsumeRecordTypeEnum.XFJXF_4.code())) {
|
|
|| statusFlag == Integer.parseInt(ConsumeRecordTypeEnum.XFJXF_4.code())) {
|
|
|
-
|
|
|
|
|
// 设备是否可以消费验证
|
|
// 设备是否可以消费验证
|
|
|
result = checkTermLimitedAndOther(bo, useTermVo, userCardVo, remoteMealTypeVo);
|
|
result = checkTermLimitedAndOther(bo, useTermVo, userCardVo, remoteMealTypeVo);
|
|
|
if (R.isError(result)) {
|
|
if (R.isError(result)) {
|
|
@@ -105,24 +104,14 @@ public class ConsumeRequestCheck {
|
|
|
return obtainResult(result);
|
|
return obtainResult(result);
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
// 余额校验,余额不足不能交易,如果有折扣,则消费金额是以折扣金额为准的,所以余额验证放在最后
|
|
// 余额校验,余额不足不能交易,如果有折扣,则消费金额是以折扣金额为准的,所以余额验证放在最后
|
|
|
- result = checkWalletBalance(bo);
|
|
|
|
|
|
|
+ // List<PtBagVo> deductionBags = new ArrayList<>();
|
|
|
|
|
+ result = checkWalletBalance(bo, useTermVo, deductionBags);
|
|
|
if (R.isError(result)) {
|
|
if (R.isError(result)) {
|
|
|
return result;
|
|
return result;
|
|
|
}
|
|
}
|
|
|
RemoteVoConvert.INSTANCE.copyXfCardLimitedVo(cardLimitedVo, xfCardLimitedVo);
|
|
RemoteVoConvert.INSTANCE.copyXfCardLimitedVo(cardLimitedVo, xfCardLimitedVo);
|
|
|
- // cardLimitedVo
|
|
|
|
|
- // cardLimitedVo.setLimitedId(xfCardLimitedVo.getLimitedId());
|
|
|
|
|
- // cardLimitedVo.setCardNo(xfCardLimitedVo.getCardNo());
|
|
|
|
|
- // cardLimitedVo.setDayCount(xfCardLimitedVo.getDayCount());
|
|
|
|
|
- // cardLimitedVo.setDayMoney(xfCardLimitedVo.getDayMoney());
|
|
|
|
|
- // cardLimitedVo.setMealCount(xfCardLimitedVo.getMealCount());
|
|
|
|
|
- // cardLimitedVo.setMealMoney(xfCardLimitedVo.getMealMoney());
|
|
|
|
|
- // cardLimitedVo.setDayDiscountCount(xfCardLimitedVo.getDayDiscountCount());
|
|
|
|
|
- // cardLimitedVo.setMealDiscountCount(xfCardLimitedVo.getMealDiscountCount());
|
|
|
|
|
- // cardLimitedVo.setLastPay(xfCardLimitedVo.getLastPay());
|
|
|
|
|
- // cardLimitedVo.setLastMeal(xfCardLimitedVo.getLastMeal());
|
|
|
|
|
- // BeanUtil.copyProperties(xfCardLimitedVo, cardLimitedVo);
|
|
|
|
|
return R.ok();
|
|
return R.ok();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -136,7 +125,7 @@ public class ConsumeRequestCheck {
|
|
|
public R<ErrorInfo> completeConsumeRequest(ConsumptionBo bo, RemoteUserAccountVo userAccountVo,
|
|
public R<ErrorInfo> completeConsumeRequest(ConsumptionBo bo, RemoteUserAccountVo userAccountVo,
|
|
|
RemoteCardVo userCardVo, XfTermVo useTermVo,
|
|
RemoteCardVo userCardVo, XfTermVo useTermVo,
|
|
|
Map<String, Boolean> mapCardLimited,
|
|
Map<String, Boolean> mapCardLimited,
|
|
|
- XfCardLimitedVo cardLimitedVo) {
|
|
|
|
|
|
|
+ XfCardLimitedVo cardLimitedVo, List<PtBagVo> deductionBags) {
|
|
|
// 生成原始消费记录
|
|
// 生成原始消费记录
|
|
|
XfConsumeDetailOriginalVo originalVo = new XfConsumeDetailOriginalVo();
|
|
XfConsumeDetailOriginalVo originalVo = new XfConsumeDetailOriginalVo();
|
|
|
R<ErrorInfo> result = baseBusiness.createOriginalOrder(bo, userAccountVo, originalVo);
|
|
R<ErrorInfo> result = baseBusiness.createOriginalOrder(bo, userAccountVo, originalVo);
|
|
@@ -146,7 +135,7 @@ public class ConsumeRequestCheck {
|
|
|
bo.setRecordId(originalVo.getRecordId());
|
|
bo.setRecordId(originalVo.getRecordId());
|
|
|
bo.setOriginalId(originalVo.getOriginalId());
|
|
bo.setOriginalId(originalVo.getOriginalId());
|
|
|
|
|
|
|
|
- commonCheck.updateValidationData(bo, userCardVo, mapCardLimited, cardLimitedVo);
|
|
|
|
|
|
|
+ commonCheck.updateValidationData(bo, userCardVo, mapCardLimited, cardLimitedVo, deductionBags);
|
|
|
|
|
|
|
|
return R.ok();
|
|
return R.ok();
|
|
|
}
|
|
}
|
|
@@ -159,26 +148,46 @@ public class ConsumeRequestCheck {
|
|
|
* 该方法用于检查消费业务对象中的消费金额是否小于或等于用户钱包的可用余额。
|
|
* 该方法用于检查消费业务对象中的消费金额是否小于或等于用户钱包的可用余额。
|
|
|
* 如果余额不足,则返回错误信息;否则返回成功结果。
|
|
* 如果余额不足,则返回错误信息;否则返回成功结果。
|
|
|
*
|
|
*
|
|
|
- * @param bo 消费业务对象,包含消费相关的基础信息(如消费金额、用户信息等)
|
|
|
|
|
|
|
+ * @param bo 消费业务对象,包含消费相关的基础信息(如消费金额、用户信息等)
|
|
|
|
|
+ * @param useTermVo 消费机对象
|
|
|
|
|
+ * @param deductionBags 扣款钱包对象
|
|
|
* @return 如果校验失败,则返回包含错误信息的 R 对象;如果校验成功,则返回表示成功的 R 对象
|
|
* @return 如果校验失败,则返回包含错误信息的 R 对象;如果校验成功,则返回表示成功的 R 对象
|
|
|
*/
|
|
*/
|
|
|
- private R<ErrorInfo> checkWalletBalance(ConsumptionBo bo) {
|
|
|
|
|
|
|
+ private R<ErrorInfo> checkWalletBalance(ConsumptionBo bo, XfTermVo useTermVo, List<PtBagVo> deductionBags) {
|
|
|
|
|
+ long userId = bo.getUserId();
|
|
|
long startTime = System.currentTimeMillis();
|
|
long startTime = System.currentTimeMillis();
|
|
|
BigDecimal consumeMoney = bo.getConsumeMoney();
|
|
BigDecimal consumeMoney = bo.getConsumeMoney();
|
|
|
- BigDecimal totalBalance = bagService.getUserTotalBalance(bo.getUserId());
|
|
|
|
|
- if (ObjectUtil.isEmpty(totalBalance)) {
|
|
|
|
|
- totalBalance = BigDecimal.ZERO;
|
|
|
|
|
|
|
+ StringBuilder sb = new StringBuilder();
|
|
|
|
|
+ // 设备的扣费钱包字符串
|
|
|
|
|
+ String consumeType = useTermVo.getConsumeType();
|
|
|
|
|
+ // 分解扣费钱包,设置为可能有空值 ""或" ",要过滤掉
|
|
|
|
|
+ List<String> bagCodes = StrUtil.split(consumeType, ",").stream()
|
|
|
|
|
+ .filter(s -> s != null && !s.trim().isEmpty())
|
|
|
|
|
+ .toList();
|
|
|
|
|
+
|
|
|
|
|
+ BigDecimal totalBalance = BigDecimal.ZERO;
|
|
|
|
|
+ PtBagVo bagVo;
|
|
|
|
|
+ // 计算扣费钱包的总金额
|
|
|
|
|
+ for (String bagCode : bagCodes) {
|
|
|
|
|
+ BigDecimal balance = bagService.getUserBagBalanceFromCache(String.valueOf(userId), bagCode);
|
|
|
|
|
+ totalBalance = totalBalance.add(balance);
|
|
|
|
|
+ bagVo = new PtBagVo();
|
|
|
|
|
+ bagVo.setUserId(userId);
|
|
|
|
|
+ bagVo.setBagCode(bagCode);
|
|
|
|
|
+ bagVo.setBalance(balance);
|
|
|
|
|
+ deductionBags.add(bagVo);
|
|
|
}
|
|
}
|
|
|
if (consumeMoney.compareTo(totalBalance) > 0) {
|
|
if (consumeMoney.compareTo(totalBalance) > 0) {
|
|
|
- log.info("[请求交易]-[钱包余额验证完成]-[耗时:{} ms]",System.currentTimeMillis()-startTime);
|
|
|
|
|
|
|
+ log.info("[请求交易]-[钱包余额验证完成]-[耗时:{} ms]", System.currentTimeMillis() - startTime);
|
|
|
return CheckError.createErrorResponse(400, ApiErrorTypeConstants.CONSUME_CHECK_FAIL, "账户余额不足",
|
|
return CheckError.createErrorResponse(400, ApiErrorTypeConstants.CONSUME_CHECK_FAIL, "账户余额不足",
|
|
|
MessageFormat.format("总余额[{0}],消费金额[{1}]", totalBalance, consumeMoney));
|
|
MessageFormat.format("总余额[{0}],消费金额[{1}]", totalBalance, consumeMoney));
|
|
|
}
|
|
}
|
|
|
|
|
+ // 模拟扣费并更新对应扣费钱包余额,主要用于后面入原始表成功后更新钱包的余额缓存
|
|
|
|
|
+ baseBusiness.simulateDeduction(consumeMoney, deductionBags);
|
|
|
// 计算扣费后的余额
|
|
// 计算扣费后的余额
|
|
|
BigDecimal balance = totalBalance.subtract(consumeMoney);
|
|
BigDecimal balance = totalBalance.subtract(consumeMoney);
|
|
|
bo.setBalance(balance);
|
|
bo.setBalance(balance);
|
|
|
- baseBusiness.resetUserBalance(bo.getUserId(), balance);
|
|
|
|
|
- log.info("[请求交易]-[钱包余额验证完成]-[耗时:{} ms]",System.currentTimeMillis()-startTime);
|
|
|
|
|
|
|
+ log.info("[请求交易]-[钱包余额验证完成]-[耗时:{} ms]", System.currentTimeMillis() - startTime);
|
|
|
return R.ok();
|
|
return R.ok();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -202,15 +211,12 @@ public class ConsumeRequestCheck {
|
|
|
|
|
|
|
|
String keyName = String.format("%s:%s", CacheNames.XF_ORIGINAL_ID, originalId);
|
|
String keyName = String.format("%s:%s", CacheNames.XF_ORIGINAL_ID, originalId);
|
|
|
if (RedisUtils.hasKey(keyName)) {
|
|
if (RedisUtils.hasKey(keyName)) {
|
|
|
- log.info("[请求交易]-[检查是否重复请求]-[耗时:{} ms] ", System.currentTimeMillis()-startTime);
|
|
|
|
|
|
|
+ log.info("[请求交易]-[检查是否重复请求]-[耗时:{} ms] ", System.currentTimeMillis() - startTime);
|
|
|
return CheckError.createErrorResponse(400, ApiErrorTypeConstants.CONSUME_CHECK_FAIL, "原始消费记录存在",
|
|
return CheckError.createErrorResponse(400, ApiErrorTypeConstants.CONSUME_CHECK_FAIL, "原始消费记录存在",
|
|
|
MessageFormat.format("原始消费记录已存在:{0}", originalId));
|
|
MessageFormat.format("原始消费记录已存在:{0}", originalId));
|
|
|
}
|
|
}
|
|
|
bo.setOriginalId(originalId);
|
|
bo.setOriginalId(originalId);
|
|
|
-
|
|
|
|
|
- // 将当笔原始消费记录标识放入缓存,1小时过期
|
|
|
|
|
- RedisUtils.setCacheObject(keyName, "",Duration.ofHours(1));
|
|
|
|
|
- log.info("[请求交易]-[检查是否重复请求]-[耗时:{} ms] ", System.currentTimeMillis()-startTime);
|
|
|
|
|
|
|
+ log.info("[请求交易]-[检查是否重复请求]-[耗时:{} ms] ", System.currentTimeMillis() - startTime);
|
|
|
return R.ok();
|
|
return R.ok();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -233,8 +239,8 @@ public class ConsumeRequestCheck {
|
|
|
new ValidationTaskDes<>("设备每餐限制验证", () -> validateMealLimit(validationContext)),
|
|
new ValidationTaskDes<>("设备每餐限制验证", () -> validateMealLimit(validationContext)),
|
|
|
new ValidationTaskDes<>("设备每日限制验证", () -> validateDailyLimit(validationContext))
|
|
new ValidationTaskDes<>("设备每日限制验证", () -> validateDailyLimit(validationContext))
|
|
|
);
|
|
);
|
|
|
- R<ErrorInfo>result = taskExecutor.executeParallelTasks(tasks, 5, TimeUnit.SECONDS);
|
|
|
|
|
- log.info("[请求交易]-[设备消费验证完成]-[耗时:{} ms]",System.currentTimeMillis()-startTime);
|
|
|
|
|
|
|
+ R<ErrorInfo> result = taskExecutor.executeParallelTasks(tasks, 5, TimeUnit.SECONDS);
|
|
|
|
|
+ log.info("[请求交易]-[设备消费验证完成]-[耗时:{} ms]", System.currentTimeMillis() - startTime);
|
|
|
|
|
|
|
|
return result;
|
|
return result;
|
|
|
}
|
|
}
|
|
@@ -282,253 +288,6 @@ public class ConsumeRequestCheck {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- /**
|
|
|
|
|
- * 执行消费终端校验链。
|
|
|
|
|
- * <p>
|
|
|
|
|
- * 该方法用于执行一系列与消费终端相关的校验逻辑,确保消费请求符合所有业务规则。
|
|
|
|
|
- * 校验链包括但不限于以下内容:
|
|
|
|
|
- * 1. 消费间隔校验。
|
|
|
|
|
- * 2. 单次消费限额校验。
|
|
|
|
|
- * 3. 卡片有效期校验。
|
|
|
|
|
- * 4. 卡类限制校验。
|
|
|
|
|
- * 5. 餐次限次校验。
|
|
|
|
|
- * 6. 日限次和日限额校验。
|
|
|
|
|
- * <p>
|
|
|
|
|
- * 如果任意一个校验失败,则立即返回错误信息并停止后续校验。
|
|
|
|
|
- *
|
|
|
|
|
- * @param context 校验上下文对象,包含消费相关的所有必要信息(如消费金额、终端设备信息、用户卡片信息等)
|
|
|
|
|
- * @return 如果校验成功,返回表示成功的 R 对象;如果校验失败,返回包含错误信息的 R 对象
|
|
|
|
|
- */
|
|
|
|
|
- private R<ErrorInfo> executeTermValidationChain(TermConsumeValidationContext context) {
|
|
|
|
|
- long startTime = System.currentTimeMillis();
|
|
|
|
|
- // 创建CountDownLatch 有1个任务返回错误时就唤醒主线程 // 使用CountDownLatch跟踪任务完成
|
|
|
|
|
- CountDownLatch latch = new CountDownLatch(1);
|
|
|
|
|
- // 用于存储第一个错误结果
|
|
|
|
|
- AtomicReference<R<ErrorInfo>> firstError = new AtomicReference<>(null);
|
|
|
|
|
- // 创建验证任务列表
|
|
|
|
|
- List<Callable<R<ErrorInfo>>> validationTasks = getTermValidationCallables(context, firstError, latch);
|
|
|
|
|
-
|
|
|
|
|
- // 所有任务的结果
|
|
|
|
|
- List<Future<R<ErrorInfo>>> futures = new ArrayList<>();
|
|
|
|
|
- // 提交所有任务
|
|
|
|
|
- for (Callable<R<ErrorInfo>> task : validationTasks) {
|
|
|
|
|
- futures.add(threadPoolTaskExecutor.submit(task));
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // 最终执行的结果,true 为正常执行结束及latch减至了0;false 为超时
|
|
|
|
|
- boolean finallyResult;
|
|
|
|
|
- try {
|
|
|
|
|
- // 阻塞主线程,等待执行结果; latch 为0时,会唤醒主线程,最多阻塞10s
|
|
|
|
|
- finallyResult = latch.await(10, TimeUnit.SECONDS);
|
|
|
|
|
- } catch (InterruptedException e) {
|
|
|
|
|
- log.error("executeTermValidationChain- main 被中断 :{}", e.getMessage());
|
|
|
|
|
- Thread.currentThread().interrupt();
|
|
|
|
|
- // 取消所有任务
|
|
|
|
|
- futures.forEach(f -> f.cancel(true));
|
|
|
|
|
- return CheckError.createError(TradeStatusEnum.SysError);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // 循环取消所有任务
|
|
|
|
|
- futures.forEach(f -> f.cancel(true));
|
|
|
|
|
- if (!finallyResult) {
|
|
|
|
|
- log.error("executeTermValidationChain- 执行消费终端校验链10s超时");
|
|
|
|
|
- return CheckError.createError(TradeStatusEnum.SysError);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- R<ErrorInfo> errorInfoR = firstError.get();
|
|
|
|
|
- log.info("executeTermValidationChain- 执行消费终端校验链耗时:{}ms", System.currentTimeMillis() - startTime);
|
|
|
|
|
- if (errorInfoR != null && R.isError(errorInfoR)) {
|
|
|
|
|
- return errorInfoR;
|
|
|
|
|
- }else {
|
|
|
|
|
- return R.ok();
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
-
|
|
|
|
|
-// int taskIndex = 0;
|
|
|
|
|
-// try {
|
|
|
|
|
-// for (Future<R<ErrorInfo>> future : futures) {
|
|
|
|
|
-// long starTime = System.currentTimeMillis();
|
|
|
|
|
-// if (firstError.get() != null) {
|
|
|
|
|
-//
|
|
|
|
|
-// future.cancel(true); // 取消剩余任务
|
|
|
|
|
-// continue;
|
|
|
|
|
-// }
|
|
|
|
|
-//
|
|
|
|
|
-// try {
|
|
|
|
|
-// // R<ErrorInfo> result = future.get(VALIDATION_TIMEOUT, TimeUnit.MILLISECONDS);
|
|
|
|
|
-// R<ErrorInfo> result = future.get();
|
|
|
|
|
-// // 发现错误,立即取消其他任务
|
|
|
|
|
-// if (result != null && R.isError(result)) {
|
|
|
|
|
-//
|
|
|
|
|
-// if (firstError.compareAndSet(null, result)) {
|
|
|
|
|
-// futures.forEach(f -> f.cancel(true));
|
|
|
|
|
-// }
|
|
|
|
|
-// }
|
|
|
|
|
-// } catch (ExecutionException e) {
|
|
|
|
|
-// log.error("{}验证执行异常", getTaskName(taskIndex), e);
|
|
|
|
|
-// if (firstError.compareAndSet(null, createError(TradeStatusEnum.SysError))) {
|
|
|
|
|
-// futures.forEach(f -> f.cancel(true));
|
|
|
|
|
-// }
|
|
|
|
|
-// } finally {
|
|
|
|
|
-// log.info("{}结束,耗时:{} ms", getTaskName(taskIndex), System.currentTimeMillis() - starTime);
|
|
|
|
|
-// taskIndex++;
|
|
|
|
|
-// }
|
|
|
|
|
-// }
|
|
|
|
|
-// return firstError.get() != null ? firstError.get() : R.ok();
|
|
|
|
|
-//
|
|
|
|
|
-// } catch (InterruptedException e) {
|
|
|
|
|
-// Thread.currentThread().interrupt(); // 保留中断状态
|
|
|
|
|
-// log.error("验证过程被中断", e);
|
|
|
|
|
-// return createError(TradeStatusEnum.SysError);
|
|
|
|
|
-// }
|
|
|
|
|
-
|
|
|
|
|
-
|
|
|
|
|
- //// 创建验证任务列表
|
|
|
|
|
- //List<Supplier<R<ErrorInfo>>> validationTasks = new ArrayList<>();
|
|
|
|
|
- //
|
|
|
|
|
- //validationTasks.add(() -> validateSwipeInterval(context));
|
|
|
|
|
- //validationTasks.add(() -> validateSingleLimit(context));
|
|
|
|
|
- //validationTasks.add(() -> validateCardValidity(context));
|
|
|
|
|
- //validationTasks.add(() -> validateCardType(context));
|
|
|
|
|
- //validationTasks.add(() -> validateMealLimit(context));
|
|
|
|
|
- //validationTasks.add(() -> validateDailyLimit(context));
|
|
|
|
|
- //
|
|
|
|
|
- //// 用于存储第一个错误结果
|
|
|
|
|
- //AtomicReference<R<ErrorInfo>> firstError = new AtomicReference<>(null);
|
|
|
|
|
- //
|
|
|
|
|
- //// 使用CountDownLatch跟踪任务完成
|
|
|
|
|
- //CountDownLatch latch = new CountDownLatch(validationTasks.size());
|
|
|
|
|
- //
|
|
|
|
|
- //// 提交所有验证任务
|
|
|
|
|
- //for (Supplier<R<ErrorInfo>> task : validationTasks) {
|
|
|
|
|
- // threadPoolTaskExecutor.execute(() -> {
|
|
|
|
|
- // try {
|
|
|
|
|
- // R<ErrorInfo> result = task.get();
|
|
|
|
|
- // // 如果发现错误且尚未设置错误结果
|
|
|
|
|
- // if (result != null && R.isError(result) &&
|
|
|
|
|
- // firstError.compareAndSet(null, result)) {
|
|
|
|
|
- // // 取消其他任务(通过中断)
|
|
|
|
|
- // threadPoolTaskExecutor.getThreadPoolExecutor().getQueue().clear();
|
|
|
|
|
- // }
|
|
|
|
|
- // } catch (Exception e) {
|
|
|
|
|
- // if (firstError.compareAndSet(null, commonCheck.createError(TradeStatusEnum.SysError))) {
|
|
|
|
|
- // threadPoolTaskExecutor.getThreadPoolExecutor().getQueue().clear();
|
|
|
|
|
- // }
|
|
|
|
|
- // } finally {
|
|
|
|
|
- // latch.countDown();
|
|
|
|
|
- // }
|
|
|
|
|
- // });
|
|
|
|
|
- //}
|
|
|
|
|
- //
|
|
|
|
|
- //try {
|
|
|
|
|
- // // 等待所有任务完成或超时
|
|
|
|
|
- // //if (!latch.await(300, TimeUnit.MILLISECONDS)) {
|
|
|
|
|
- // // return commonCheck.createError(TradeStatusEnum.VALIDATION_TIMEOUT);
|
|
|
|
|
- // //}
|
|
|
|
|
- // latch.await();
|
|
|
|
|
- //
|
|
|
|
|
- // // 返回第一个发现的错误,如果没有错误则返回成功
|
|
|
|
|
- // log.info("校验设备完成,耗时:{} ms",System.currentTimeMillis()-startTime);
|
|
|
|
|
- // return firstError.get() != null ? firstError.get() : R.ok();
|
|
|
|
|
- //} catch (InterruptedException e) {
|
|
|
|
|
- // Thread.currentThread().interrupt();
|
|
|
|
|
- // return commonCheck.createError(TradeStatusEnum.SysError);
|
|
|
|
|
- //}
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * 获取任务列表
|
|
|
|
|
- * @param context
|
|
|
|
|
- * @param firstError
|
|
|
|
|
- * @param latch
|
|
|
|
|
- * @return
|
|
|
|
|
- */
|
|
|
|
|
- private List<Callable<R<ErrorInfo>>> getTermValidationCallables(TermConsumeValidationContext context,
|
|
|
|
|
- AtomicReference<R<ErrorInfo>> firstError, CountDownLatch latch) {
|
|
|
|
|
- List<Callable<R<ErrorInfo>>> validationTasks = new ArrayList<>();
|
|
|
|
|
-
|
|
|
|
|
- // 消费时间间隔验证
|
|
|
|
|
- validationTasks.add(() -> {
|
|
|
|
|
- R<ErrorInfo> errorInfoR = firstError.get();
|
|
|
|
|
- if (errorInfoR != null && R.isError(errorInfoR)) {
|
|
|
|
|
- return null;
|
|
|
|
|
- }
|
|
|
|
|
- R<ErrorInfo> infoR = validateSwipeInterval(context);
|
|
|
|
|
- if (infoR != null && R.isError(infoR)) {
|
|
|
|
|
- // 设置错误结果
|
|
|
|
|
- firstError.set(infoR);
|
|
|
|
|
- latch.countDown();
|
|
|
|
|
- }
|
|
|
|
|
- return infoR;
|
|
|
|
|
- });
|
|
|
|
|
- // 单次消费限额判断
|
|
|
|
|
- validationTasks.add(() -> {
|
|
|
|
|
- R<ErrorInfo> errorInfoR = firstError.get();
|
|
|
|
|
- if (errorInfoR != null && R.isError(errorInfoR)) {
|
|
|
|
|
- return null;
|
|
|
|
|
- }
|
|
|
|
|
- R<ErrorInfo> infoR = validateSingleLimit(context);
|
|
|
|
|
- if (infoR != null && R.isError(infoR)) {
|
|
|
|
|
- firstError.set(infoR);
|
|
|
|
|
- latch.countDown();
|
|
|
|
|
- }
|
|
|
|
|
- return infoR;
|
|
|
|
|
- });
|
|
|
|
|
- // 卡有效期验证
|
|
|
|
|
- validationTasks.add(() -> {
|
|
|
|
|
- R<ErrorInfo> errorInfoR = firstError.get();
|
|
|
|
|
- if (errorInfoR != null && R.isError(errorInfoR)) {
|
|
|
|
|
- return null;
|
|
|
|
|
- }
|
|
|
|
|
- R<ErrorInfo> infoR = validateCardValidity(context);
|
|
|
|
|
- if (infoR != null && R.isError(infoR)) {
|
|
|
|
|
- firstError.set(infoR);
|
|
|
|
|
- latch.countDown();
|
|
|
|
|
- }
|
|
|
|
|
- return infoR;
|
|
|
|
|
- });
|
|
|
|
|
- // 卡类型验证
|
|
|
|
|
- validationTasks.add(() -> {
|
|
|
|
|
- R<ErrorInfo> errorInfoR = firstError.get();
|
|
|
|
|
- if (errorInfoR != null && R.isError(errorInfoR)) {
|
|
|
|
|
- return null;
|
|
|
|
|
- }
|
|
|
|
|
- R<ErrorInfo> infoR = validateCardType(context);
|
|
|
|
|
- if (infoR != null && R.isError(infoR)) {
|
|
|
|
|
- firstError.set(infoR);
|
|
|
|
|
- latch.countDown();
|
|
|
|
|
- }
|
|
|
|
|
- return infoR;
|
|
|
|
|
- });
|
|
|
|
|
- // 餐次验证
|
|
|
|
|
- validationTasks.add(() -> {
|
|
|
|
|
- R<ErrorInfo> errorInfoR = firstError.get();
|
|
|
|
|
- if (errorInfoR != null && R.isError(errorInfoR)) {
|
|
|
|
|
- return null;
|
|
|
|
|
- }
|
|
|
|
|
- R<ErrorInfo> infoR = validateMealLimit(context);
|
|
|
|
|
- if (infoR != null && R.isError(infoR)) {
|
|
|
|
|
- firstError.set(infoR);
|
|
|
|
|
- latch.countDown();
|
|
|
|
|
- }
|
|
|
|
|
- return infoR;
|
|
|
|
|
- });
|
|
|
|
|
- // 日限额验证
|
|
|
|
|
- validationTasks.add(() -> {
|
|
|
|
|
- R<ErrorInfo> errorInfoR = firstError.get();
|
|
|
|
|
- if (errorInfoR != null && R.isError(errorInfoR)) {
|
|
|
|
|
- return null;
|
|
|
|
|
- }
|
|
|
|
|
- R<ErrorInfo> infoR = validateDailyLimit(context);
|
|
|
|
|
- if (infoR != null && R.isError(infoR)) {
|
|
|
|
|
- firstError.set(infoR);
|
|
|
|
|
- latch.countDown();
|
|
|
|
|
- }
|
|
|
|
|
- return infoR;
|
|
|
|
|
- });
|
|
|
|
|
- return validationTasks;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
private R<ErrorInfo> validateSwipeInterval(TermConsumeValidationContext ctx) {
|
|
private R<ErrorInfo> validateSwipeInterval(TermConsumeValidationContext ctx) {
|
|
|
if (ctx.getTermSwipeInterval() <= 0) return null;
|
|
if (ctx.getTermSwipeInterval() <= 0) return null;
|
|
|
|
|
|
|
@@ -539,25 +298,30 @@ public class ConsumeRequestCheck {
|
|
|
return intervalMin < ctx.getTermSwipeInterval() ?
|
|
return intervalMin < ctx.getTermSwipeInterval() ?
|
|
|
CheckError.createError(TradeStatusEnum.TimeInterval) : null;
|
|
CheckError.createError(TradeStatusEnum.TimeInterval) : null;
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
private R<ErrorInfo> validateSingleLimit(TermConsumeValidationContext ctx) {
|
|
private R<ErrorInfo> validateSingleLimit(TermConsumeValidationContext ctx) {
|
|
|
if (ctx.getTermSingleMoney().compareTo(BigDecimal.ZERO) <= 0) return null;
|
|
if (ctx.getTermSingleMoney().compareTo(BigDecimal.ZERO) <= 0) return null;
|
|
|
return ctx.getConsumeValue().compareTo(ctx.getTermSingleMoney()) > 0 ?
|
|
return ctx.getConsumeValue().compareTo(ctx.getTermSingleMoney()) > 0 ?
|
|
|
CheckError.createError(TradeStatusEnum.OnceBigMoney) : null;
|
|
CheckError.createError(TradeStatusEnum.OnceBigMoney) : null;
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
private R<ErrorInfo> validateCardValidity(TermConsumeValidationContext ctx) {
|
|
private R<ErrorInfo> validateCardValidity(TermConsumeValidationContext ctx) {
|
|
|
if (ctx.getFactoryId() == 0 || !ctx.isTermUseValidity()) return null;
|
|
if (ctx.getFactoryId() == 0 || !ctx.isTermUseValidity()) return null;
|
|
|
return ctx.getCurrentTime().isAfter(ctx.getExpiryTime()) ?
|
|
return ctx.getCurrentTime().isAfter(ctx.getExpiryTime()) ?
|
|
|
CheckError.createError(TradeStatusEnum.CardValidDate) : null;
|
|
CheckError.createError(TradeStatusEnum.CardValidDate) : null;
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
private R<ErrorInfo> validateCardType(TermConsumeValidationContext ctx) {
|
|
private R<ErrorInfo> validateCardType(TermConsumeValidationContext ctx) {
|
|
|
int mask = 1 << (ctx.getCardType() - 1);
|
|
int mask = 1 << (ctx.getCardType() - 1);
|
|
|
return (mask & ctx.getTermCardType()) == 0 ?
|
|
return (mask & ctx.getTermCardType()) == 0 ?
|
|
|
CheckError.createError(TradeStatusEnum.CardTypeLimit) : null;
|
|
CheckError.createError(TradeStatusEnum.CardTypeLimit) : null;
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
private R<ErrorInfo> validateMealLimit(TermConsumeValidationContext ctx) {
|
|
private R<ErrorInfo> validateMealLimit(TermConsumeValidationContext ctx) {
|
|
|
return ctx.getTermMealCount() > 0 && ctx.getMealCount() >= ctx.getTermMealCount() ?
|
|
return ctx.getTermMealCount() > 0 && ctx.getMealCount() >= ctx.getTermMealCount() ?
|
|
|
CheckError.createError(TradeStatusEnum.MealLimitTimes) : null;
|
|
CheckError.createError(TradeStatusEnum.MealLimitTimes) : null;
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
private R<ErrorInfo> validateDailyLimit(TermConsumeValidationContext ctx) {
|
|
private R<ErrorInfo> validateDailyLimit(TermConsumeValidationContext ctx) {
|
|
|
// 日限次验证
|
|
// 日限次验证
|
|
|
if (ctx.getTermDayCount() > 0 && ctx.getDayCount() >= ctx.getTermDayCount()) {
|
|
if (ctx.getTermDayCount() > 0 && ctx.getDayCount() >= ctx.getTermDayCount()) {
|