|
|
@@ -22,8 +22,7 @@ import java.math.RoundingMode;
|
|
|
import java.util.ArrayList;
|
|
|
import java.util.List;
|
|
|
import java.util.Map;
|
|
|
-import java.util.concurrent.CountDownLatch;
|
|
|
-import java.util.concurrent.TimeUnit;
|
|
|
+import java.util.concurrent.*;
|
|
|
import java.util.concurrent.atomic.AtomicReference;
|
|
|
import java.util.function.Supplier;
|
|
|
|
|
|
@@ -51,7 +50,7 @@ public class CardConsumeValidation {
|
|
|
RemoteMealTypeVo mealTypeVo,
|
|
|
XfCardLimitedVo cardLimitedVo, Map<String, Boolean> mapCardLimited) {
|
|
|
|
|
|
- // public R<ErrorInfo> cardValidation(CardConsumeValidationContext validationContext) {
|
|
|
+ // public R<ErrorInfo> cardValidation(CardConsumeValidationContext validationContext) {
|
|
|
// 1.初始化验证上下文
|
|
|
CardConsumeValidationContext validationContext = CardConsumeValidationContext.create(bo, termVo, userCardVo, mealTypeVo.getTypeId());
|
|
|
|
|
|
@@ -72,56 +71,121 @@ public class CardConsumeValidation {
|
|
|
|
|
|
private R<ErrorInfo> executeCardValidationChain(CardConsumeValidationContext ctx) {
|
|
|
// 创建验证任务列表
|
|
|
- List<Supplier<R<ErrorInfo>>> validationTasks = new ArrayList<>();
|
|
|
+ List<Callable<R<ErrorInfo>>> validationTasks = new ArrayList<>();
|
|
|
validationTasks.add(() -> dealCardDiscount(ctx));
|
|
|
validationTasks.add(() -> dealCardLimited(ctx));
|
|
|
|
|
|
- // 用于存储第一个错误结果
|
|
|
+ // 存储第一个错误结果
|
|
|
AtomicReference<R<ErrorInfo>> firstError = new AtomicReference<>(null);
|
|
|
-
|
|
|
- // 使用CountDownLatch跟踪任务完成
|
|
|
- CountDownLatch latch = new CountDownLatch(validationTasks.size());
|
|
|
+ List<Future<R<ErrorInfo>>> futures = new ArrayList<>();
|
|
|
|
|
|
// 提交所有验证任务
|
|
|
- for (Supplier<R<ErrorInfo>> task : validationTasks) {
|
|
|
- taskExecutor.execute(() -> {
|
|
|
+ for (Callable<R<ErrorInfo>> task : validationTasks) {
|
|
|
+ futures.add(taskExecutor.submit(task));
|
|
|
+ }
|
|
|
+ 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 = task.get();
|
|
|
- // 如果发现错误且尚未设置错误结果
|
|
|
- if (result != null && R.isError(result) &&
|
|
|
- firstError.compareAndSet(null, result)) {
|
|
|
- // 取消其他任务(通过中断)
|
|
|
- taskExecutor.getThreadPoolExecutor().getQueue().clear();
|
|
|
+ R<ErrorInfo> result = future.get();
|
|
|
+ if (result != null && R.isError(result)) {
|
|
|
+ if (firstError.compareAndSet(null, result)) {
|
|
|
+ // 发现错误,立即取消其他任务
|
|
|
+ futures.forEach(f -> f.cancel(true)); // 发现错误立即取消其他任务
|
|
|
+ }
|
|
|
}
|
|
|
- } catch (Exception e) {
|
|
|
- log.error("系统错误1", e);
|
|
|
+ } catch (ExecutionException e) {
|
|
|
+ log.error("{}执行异常", getTaskName(taskIndex), e);
|
|
|
if (firstError.compareAndSet(null, commonCheck.createError(TradeStatusEnum.SysError))) {
|
|
|
- taskExecutor.getThreadPoolExecutor().getQueue().clear();
|
|
|
+ futures.forEach(f -> f.cancel(true));
|
|
|
}
|
|
|
} finally {
|
|
|
- latch.countDown();
|
|
|
+ log.info("{}结束,耗时:{} ms", getTaskName(taskIndex), System.currentTimeMillis() - starTime);
|
|
|
+ taskIndex++;
|
|
|
}
|
|
|
- });
|
|
|
- }
|
|
|
+ }
|
|
|
|
|
|
- try {
|
|
|
- // 等待所有任务完成或超时
|
|
|
- //if (!latch.await(VALIDATION_TIMEOUT, TimeUnit.MILLISECONDS)) {
|
|
|
- // return commonCheck.createError(TradeStatusEnum.VALIDATION_TIMEOUT);
|
|
|
- //}
|
|
|
- latch.await();
|
|
|
- // 返回第一个发现的错误,如果没有错误则返回成功
|
|
|
- // return firstError.get() != null ? firstError.get() : R.ok();
|
|
|
- if(firstError.get() != null){
|
|
|
+ // 如果已有错误,直接返回
|
|
|
+ if (firstError.get() != null) {
|
|
|
return firstError.get();
|
|
|
- } else {
|
|
|
+ }
|
|
|
+
|
|
|
+ // 所有前置任务通过,执行最后一个任务
|
|
|
+ long starTime = System.currentTimeMillis();
|
|
|
+ try {
|
|
|
return dealCardQuota(ctx);
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("{}执行异常", getTaskName(2), e);
|
|
|
+ return commonCheck.createError(TradeStatusEnum.DayLimitMoney, "日限额验证失败");
|
|
|
+ } finally {
|
|
|
+ log.info("{}结束,耗时:{} ms", getTaskName(2), System.currentTimeMillis() - starTime);
|
|
|
}
|
|
|
+
|
|
|
} catch (InterruptedException e) {
|
|
|
- Thread.currentThread().interrupt();
|
|
|
- log.error("系统错误", e);
|
|
|
+ Thread.currentThread().interrupt(); // 保留中断状态
|
|
|
+ log.error("验证过程被中断", e);
|
|
|
return commonCheck.createError(TradeStatusEnum.SysError);
|
|
|
}
|
|
|
+
|
|
|
+ // 使用CountDownLatch跟踪任务完成
|
|
|
+ // CountDownLatch latch = new CountDownLatch(validationTasks.size());
|
|
|
+ //
|
|
|
+ // // 提交所有验证任务
|
|
|
+ // for (Supplier<R<ErrorInfo>> task : validationTasks) {
|
|
|
+ // taskExecutor.execute(() -> {
|
|
|
+ // try {
|
|
|
+ // R<ErrorInfo> result = task.get();
|
|
|
+ // // 如果发现错误且尚未设置错误结果
|
|
|
+ // if (result != null && R.isError(result) &&
|
|
|
+ // firstError.compareAndSet(null, result)) {
|
|
|
+ // // 取消其他任务(通过中断)
|
|
|
+ // taskExecutor.getThreadPoolExecutor().getQueue().clear();
|
|
|
+ // }
|
|
|
+ // } catch (Exception e) {
|
|
|
+ // log.error("系统错误1", e);
|
|
|
+ // if (firstError.compareAndSet(null, commonCheck.createError(TradeStatusEnum.SysError))) {
|
|
|
+ // taskExecutor.getThreadPoolExecutor().getQueue().clear();
|
|
|
+ // }
|
|
|
+ // } finally {
|
|
|
+ // latch.countDown();
|
|
|
+ // }
|
|
|
+ // });
|
|
|
+ // }
|
|
|
+ //
|
|
|
+ // try {
|
|
|
+ // // 等待所有任务完成或超时
|
|
|
+ // //if (!latch.await(VALIDATION_TIMEOUT, TimeUnit.MILLISECONDS)) {
|
|
|
+ // // return commonCheck.createError(TradeStatusEnum.VALIDATION_TIMEOUT);
|
|
|
+ // //}
|
|
|
+ // latch.await();
|
|
|
+ // // 返回第一个发现的错误,如果没有错误则返回成功
|
|
|
+ // // return firstError.get() != null ? firstError.get() : R.ok();
|
|
|
+ // if(firstError.get() != null){
|
|
|
+ // return firstError.get();
|
|
|
+ // } else {
|
|
|
+ // return dealCardQuota(ctx);
|
|
|
+ // }
|
|
|
+ // } catch (InterruptedException e) {
|
|
|
+ // Thread.currentThread().interrupt();
|
|
|
+ // log.error("系统错误", e);
|
|
|
+ // return commonCheck.createError(TradeStatusEnum.SysError);
|
|
|
+ // }
|
|
|
+ }
|
|
|
+
|
|
|
+ private String getTaskName(int taskIndex) {
|
|
|
+ return switch (taskIndex) {
|
|
|
+ case 0 -> "卡片折扣计算";
|
|
|
+ case 1 -> "卡片限次验证";
|
|
|
+ case 2 -> "卡片限额验证";
|
|
|
+ default -> "未知任务";
|
|
|
+ };
|
|
|
}
|
|
|
|
|
|
// region 卡片折扣处理
|
|
|
@@ -146,9 +210,9 @@ public class CardConsumeValidation {
|
|
|
String cardType = String.valueOf(ctx.getCardType());
|
|
|
Long mealType = Long.valueOf(ctx.getLastMeal());
|
|
|
RemoteDiscountVo discountVo = discountVos.stream()
|
|
|
- .filter(p -> cardType.equals(p.getCardType())
|
|
|
- && mealType.equals(p.getMealType())
|
|
|
- && validationParam.getIsUse().equals(p.getStatus())).findFirst().orElse(null);
|
|
|
+ .filter(p -> cardType.equals(p.getCardType())
|
|
|
+ && mealType.equals(p.getMealType())
|
|
|
+ && validationParam.getIsUse().equals(p.getStatus())).findFirst().orElse(null);
|
|
|
;
|
|
|
|
|
|
XfCardLimitedVo cardLimitedVo = ctx.getCardLimitedVo();
|
|
|
@@ -192,8 +256,8 @@ public class CardConsumeValidation {
|
|
|
// 4. 计算折扣金额(统一处理精度)
|
|
|
BigDecimal discountFactor = selectedRate.divide(PERCENT_DIVISOR, 2, RoundingMode.HALF_UP);
|
|
|
return consumeValue
|
|
|
- .multiply(discountFactor)
|
|
|
- .setScale(2, RoundingMode.HALF_UP);
|
|
|
+ .multiply(discountFactor)
|
|
|
+ .setScale(2, RoundingMode.HALF_UP);
|
|
|
}
|
|
|
// endregion
|
|
|
|
|
|
@@ -217,8 +281,8 @@ public class CardConsumeValidation {
|
|
|
}
|
|
|
Long cardType = ctx.getCardType();
|
|
|
RemoteLimitedVo limitedVo = limitedCards.stream()
|
|
|
- .filter(p -> cardType.equals(p.getCardType())
|
|
|
- && validationParam.getIsUse().equals(p.getStatus())).findFirst().orElse(null);
|
|
|
+ .filter(p -> cardType.equals(p.getCardType())
|
|
|
+ && validationParam.getIsUse().equals(p.getStatus())).findFirst().orElse(null);
|
|
|
if (limitedVo == null) {
|
|
|
return R.ok();
|
|
|
}
|
|
|
@@ -237,13 +301,13 @@ public class CardConsumeValidation {
|
|
|
/**
|
|
|
* 检查每日消费次数限制。
|
|
|
*
|
|
|
- * @param limitedVo 卡片的限制信息对象,包含有关每日消费限额的数据(如每日最大消费次数或金额)。
|
|
|
- * 通过此对象可以获取卡片的每日消费限制规则。
|
|
|
+ * @param limitedVo 卡片的限制信息对象,包含有关每日消费限额的数据(如每日最大消费次数或金额)。
|
|
|
+ * 通过此对象可以获取卡片的每日消费限制规则。
|
|
|
* @param currentDayCount 当前日期内的消费次数或金额,表示在当天已经发生的消费总量。
|
|
|
* 这个参数用于与限制规则进行比较,以判断是否超出限制。
|
|
|
* @return 返回一个泛型为 ErrorInfo 的结果对象 R<ErrorInfo>。
|
|
|
- * 如果未超出限制,返回成功结果;
|
|
|
- * 如果超出限制,返回包含错误信息的结果(例如超限的具体原因)。
|
|
|
+ * 如果未超出限制,返回成功结果;
|
|
|
+ * 如果超出限制,返回包含错误信息的结果(例如超限的具体原因)。
|
|
|
*/
|
|
|
private R<ErrorInfo> checkDailyLimit(RemoteLimitedVo limitedVo, Long currentDayCount) {
|
|
|
Long dayLimit = limitedVo.getDailyCount();
|
|
|
@@ -256,15 +320,15 @@ public class CardConsumeValidation {
|
|
|
/**
|
|
|
* 检查每餐消费次数限制。
|
|
|
*
|
|
|
- * @param limitedVo 卡片的限制信息对象,包含有关餐次消费限额的数据(如每餐最大消费次数或金额)。
|
|
|
- * 通过此对象可以获取卡片的餐次消费限制规则。
|
|
|
- * @param mealType 餐类标识符,用于区分不同的餐次类型(例如早餐、午餐、晚餐等)。
|
|
|
- * 这个参数决定了要针对哪一类餐次进行消费限制的检查。
|
|
|
+ * @param limitedVo 卡片的限制信息对象,包含有关餐次消费限额的数据(如每餐最大消费次数或金额)。
|
|
|
+ * 通过此对象可以获取卡片的餐次消费限制规则。
|
|
|
+ * @param mealType 餐类标识符,用于区分不同的餐次类型(例如早餐、午餐、晚餐等)。
|
|
|
+ * 这个参数决定了要针对哪一类餐次进行消费限制的检查。
|
|
|
* @param currentMealCount 当前餐次内的消费次数或金额,表示在当前餐次已经发生的消费总量。
|
|
|
* 这个参数用于与限制规则进行比较,以判断是否超出限制。
|
|
|
* @return 返回一个泛型为 ErrorInfo 的结果对象 R<ErrorInfo>。
|
|
|
- * 如果未超出限制,返回成功结果;
|
|
|
- * 如果超出限制,返回包含错误信息的结果(例如超限的具体原因)。
|
|
|
+ * 如果未超出限制,返回成功结果;
|
|
|
+ * 如果超出限制,返回包含错误信息的结果(例如超限的具体原因)。
|
|
|
*/
|
|
|
private R<ErrorInfo> checkMealLimit(RemoteLimitedVo limitedVo, String mealType, Long currentMealCount) {
|
|
|
Map<String, Long> mealLimits = Map.of(
|
|
|
@@ -281,7 +345,7 @@ public class CardConsumeValidation {
|
|
|
|
|
|
if (currentMealCount >= mealLimit) {
|
|
|
String mealName = MEAL_TYPE_NAMES.getOrDefault(mealType, "未知餐次");
|
|
|
- return commonCheck.createError(TradeStatusEnum.MealLimitTimes,String.format("卡类%s次数限制", mealName));
|
|
|
+ return commonCheck.createError(TradeStatusEnum.MealLimitTimes, String.format("卡类%s次数限制", mealName));
|
|
|
}
|
|
|
return R.ok();
|
|
|
}
|
|
|
@@ -307,8 +371,8 @@ public class CardConsumeValidation {
|
|
|
}
|
|
|
Long cardType = ctx.getCardType();
|
|
|
RemoteQuotaVo quotaVo = quotaCards.stream()
|
|
|
- .filter(p -> cardType.equals(p.getCardType())
|
|
|
- && validationParam.getIsUse().equals(p.getStatus())).findFirst().orElse(null);
|
|
|
+ .filter(p -> cardType.equals(p.getCardType())
|
|
|
+ && validationParam.getIsUse().equals(p.getStatus())).findFirst().orElse(null);
|
|
|
if (quotaVo == null) {
|
|
|
return R.ok();
|
|
|
}
|
|
|
@@ -327,17 +391,17 @@ public class CardConsumeValidation {
|
|
|
/**
|
|
|
* 检查每日消费额度限制。
|
|
|
* 如果设置的日限额>0 并且当天已消费金额+当餐消费的金额比设置的大,则超额不能消费
|
|
|
- * @param quotaVo 卡片的限制信息对象,包含有关每日消费限额的数据(如每日最大消费额度或金额)。
|
|
|
- * 通过此对象可以获取卡片的每日消费限制规则。
|
|
|
- * @param dayMoney 当前日期内的消费额度或金额,表示在当天已经发生的消费总量。
|
|
|
- * 这个参数用于与限制规则进行比较,以判断是否超出限制。
|
|
|
- * @param consumeMoney 消费金额
|
|
|
*
|
|
|
+ * @param quotaVo 卡片的限制信息对象,包含有关每日消费限额的数据(如每日最大消费额度或金额)。
|
|
|
+ * 通过此对象可以获取卡片的每日消费限制规则。
|
|
|
+ * @param dayMoney 当前日期内的消费额度或金额,表示在当天已经发生的消费总量。
|
|
|
+ * 这个参数用于与限制规则进行比较,以判断是否超出限制。
|
|
|
+ * @param consumeMoney 消费金额
|
|
|
* @return 返回一个泛型为 ErrorInfo 的结果对象 R<ErrorInfo>。
|
|
|
- * 如果未超出限制,返回成功结果;
|
|
|
- * 如果超出限制,返回包含错误信息的结果(例如超限的具体原因)。
|
|
|
+ * 如果未超出限制,返回成功结果;
|
|
|
+ * 如果超出限制,返回包含错误信息的结果(例如超限的具体原因)。
|
|
|
*/
|
|
|
- private R<ErrorInfo> checkDailyQuota(RemoteQuotaVo quotaVo, BigDecimal dayMoney,BigDecimal consumeMoney) {
|
|
|
+ private R<ErrorInfo> checkDailyQuota(RemoteQuotaVo quotaVo, BigDecimal dayMoney, BigDecimal consumeMoney) {
|
|
|
BigDecimal dayQuotaMoney = quotaVo.getDailyMoney();
|
|
|
if (dayQuotaMoney.compareTo(BigDecimal.ZERO) > 0 && dayQuotaMoney.compareTo(dayMoney.add(consumeMoney)) < 0) {
|
|
|
return commonCheck.createError(TradeStatusEnum.DayLimitMoney, "卡类日限制额度");
|
|
|
@@ -348,17 +412,17 @@ public class CardConsumeValidation {
|
|
|
/**
|
|
|
* 检查每餐消费额度限制。
|
|
|
*
|
|
|
- * @param quotaVo 卡片的限制信息对象,包含有关餐次消费限额的数据(如每餐最大消费额度或金额)。
|
|
|
- * 通过此对象可以获取卡片的餐次消费限制规则。
|
|
|
- * @param mealType 餐类标识符,用于区分不同的餐次类型(例如早餐、午餐、晚餐等)。
|
|
|
- * 这个参数决定了要针对哪一类餐次进行消费限制的检查。
|
|
|
- * @param mealMoney 当 当前餐次内的消费额度或金额,表示在当前餐次已经发生的消费总量。
|
|
|
+ * @param quotaVo 卡片的限制信息对象,包含有关餐次消费限额的数据(如每餐最大消费额度或金额)。
|
|
|
+ * 通过此对象可以获取卡片的餐次消费限制规则。
|
|
|
+ * @param mealType 餐类标识符,用于区分不同的餐次类型(例如早餐、午餐、晚餐等)。
|
|
|
+ * 这个参数决定了要针对哪一类餐次进行消费限制的检查。
|
|
|
+ * @param mealMoney 当 当前餐次内的消费额度或金额,表示在当前餐次已经发生的消费总量。
|
|
|
* @param consumeMoney 当 当前餐次的消费金额。表示在当前餐次中再次消费的金额
|
|
|
* @return 返回一个泛型为 ErrorInfo 的结果对象 R<ErrorInfo>。
|
|
|
- * 如果未超出限制,返回成功结果;
|
|
|
- * 如果超出限制,返回包含错误信息的结果(例如超限的具体原因)。
|
|
|
+ * 如果未超出限制,返回成功结果;
|
|
|
+ * 如果超出限制,返回包含错误信息的结果(例如超限的具体原因)。
|
|
|
*/
|
|
|
- private R<ErrorInfo> checkMealQuota(RemoteQuotaVo quotaVo, String mealType, BigDecimal mealMoney,BigDecimal consumeMoney) {
|
|
|
+ private R<ErrorInfo> checkMealQuota(RemoteQuotaVo quotaVo, String mealType, BigDecimal mealMoney, BigDecimal consumeMoney) {
|
|
|
Map<String, BigDecimal> mealQuotas = Map.of(
|
|
|
"1", quotaVo.getOneMoney(),
|
|
|
"2", quotaVo.getTwoMoney(),
|
|
|
@@ -371,7 +435,7 @@ public class CardConsumeValidation {
|
|
|
return null;
|
|
|
}
|
|
|
|
|
|
- if (mealQuota.compareTo(BigDecimal.ZERO)>0) {
|
|
|
+ if (mealQuota.compareTo(BigDecimal.ZERO) > 0) {
|
|
|
if (mealQuota.compareTo(mealMoney.add(consumeMoney)) < 0) {
|
|
|
String mealName = MEAL_TYPE_NAMES.getOrDefault(mealType, "未知餐次");
|
|
|
return commonCheck.createError(TradeStatusEnum.MealLimitMoney, String.format("卡类%s额度限制", mealName));
|