|
|
@@ -6,7 +6,6 @@ import cn.hutool.core.util.ObjectUtil;
|
|
|
import cn.hutool.core.util.StrUtil;
|
|
|
import lombok.RequiredArgsConstructor;
|
|
|
import lombok.extern.slf4j.Slf4j;
|
|
|
-import org.apache.commons.lang3.time.DateFormatUtils;
|
|
|
import org.dromara.backstage.api.domain.vo.RemoteCardVo;
|
|
|
import org.dromara.backstage.api.domain.vo.RemoteMealTypeVo;
|
|
|
import org.dromara.backstage.api.domain.vo.RemoteUserAccountVo;
|
|
|
@@ -16,15 +15,12 @@ import org.dromara.common.core.constant.Constants;
|
|
|
import org.dromara.common.core.domain.R;
|
|
|
import org.dromara.common.core.domain.model.ErrorInfo;
|
|
|
import org.dromara.common.core.enums.CardStatusEnum;
|
|
|
-import org.dromara.common.core.enums.TradeStatusEnum;
|
|
|
import org.dromara.common.core.enums.UserAccountStatusEnum;
|
|
|
-import org.dromara.common.json.utils.JsonUtils;
|
|
|
import org.dromara.common.redis.utils.RedisUtils;
|
|
|
import org.dromara.server.common.constant.ConsumeConstants;
|
|
|
import org.dromara.server.common.domain.consume.bo.ConsumptionBo;
|
|
|
import org.dromara.server.common.util.CardDateUtils;
|
|
|
import org.dromara.server.consume.business.*;
|
|
|
-import org.dromara.server.consume.cache.CardCacheManager;
|
|
|
import org.dromara.server.consume.domain.vo.XfCardLimitedVo;
|
|
|
import org.dromara.server.consume.domain.vo.XfTermVo;
|
|
|
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
|
|
@@ -32,14 +28,11 @@ import org.springframework.stereotype.Service;
|
|
|
|
|
|
import java.math.BigDecimal;
|
|
|
import java.text.MessageFormat;
|
|
|
-import java.time.Duration;
|
|
|
import java.time.LocalDateTime;
|
|
|
import java.time.ZoneId;
|
|
|
import java.util.*;
|
|
|
import java.util.concurrent.*;
|
|
|
import java.util.concurrent.atomic.AtomicBoolean;
|
|
|
-import java.util.concurrent.atomic.AtomicReference;
|
|
|
-import java.util.function.Supplier;
|
|
|
|
|
|
/**
|
|
|
* 消费业务通用验证类
|
|
|
@@ -54,26 +47,39 @@ import java.util.function.Supplier;
|
|
|
@Service
|
|
|
@RequiredArgsConstructor
|
|
|
public class CommonCheck {
|
|
|
- // 异步线程执行超时时间,以毫秒为单位
|
|
|
- private static final long VALIDATION_TIMEOUT = 500;
|
|
|
+ // 多任务执行总体超时时间,以毫秒为单位
|
|
|
+ private static final long TIMEOUT_TOTAL = 3000;
|
|
|
|
|
|
private final BaseBusiness baseBusiness;
|
|
|
private final EmployeeBusiness employeeBusiness;
|
|
|
private final CardBusiness cardBusiness;
|
|
|
- private final ThreadPoolTaskExecutor taskExecutor;
|
|
|
+ private final ThreadPoolTaskExecutor threadPoolTaskExecutor;
|
|
|
private final TermBusiness termBusiness;
|
|
|
+ private final ParallelTaskExecutor taskExecutor;
|
|
|
|
|
|
- //region 账户、卡片与设备验证
|
|
|
+ //region 账户、卡片与设备的有效性验证
|
|
|
|
|
|
/**
|
|
|
* 账户、设备有效性验证
|
|
|
+ * <p>
|
|
|
+ * 1.验证传入的参数是否满足要求:机号大于0,卡流水号、物理卡号、人员流水号、人员编号必有一个
|
|
|
+ * 2.根据机号验证是否是系统中的设备
|
|
|
+ * 3.根据卡流水号或物理卡号验证是否是系统中的卡片、卡片的状态是否正常,卡正常后还要根据卡查询账户,验证账户是否有效
|
|
|
+ * 4.根据人员流水号或人员编号验证是否是系统中的账户、账户的状态是否正常,如果账户正常则根据账户获取正常的主卡
|
|
|
*
|
|
|
* @param ctx 验证上下文
|
|
|
* @return 校验结果,包含错误信息或成功标识
|
|
|
*/
|
|
|
public R<ErrorInfo> consumeValidation(AllowConsumeValidationContext ctx) {
|
|
|
- // 1. 执行异步验证链
|
|
|
- R<ErrorInfo> result = executeTermValidationChain(ctx);
|
|
|
+ // 使用线程池并行验证
|
|
|
+ List<ValidationTaskDes<R<ErrorInfo>>> tasks = List.of(
|
|
|
+ new ValidationTaskDes<>("参数有效性验证", () -> checkParam(ctx)),
|
|
|
+ new ValidationTaskDes<>("设备有效性验证", () -> checkTerm(ctx)),
|
|
|
+ new ValidationTaskDes<>("账户有效性验证", () -> checkUserAccount(ctx))
|
|
|
+ );
|
|
|
+ //
|
|
|
+ R<ErrorInfo> result = taskExecutor.executeParallelTasks(tasks, TIMEOUT_TOTAL, TimeUnit.MILLISECONDS);
|
|
|
+ // R<ErrorInfo> result = executeTermValidationChain(ctx);
|
|
|
if (R.isError(result)) {
|
|
|
return result;
|
|
|
}
|
|
|
@@ -87,196 +93,142 @@ public class CommonCheck {
|
|
|
* @return 校验结果,包含错误信息或成功标识
|
|
|
*/
|
|
|
private R<ErrorInfo> executeTermValidationChain(AllowConsumeValidationContext ctx) {
|
|
|
- // 创建CountDownLatch 有1个任务返回错误时就唤醒主线程 // 使用CountDownLatch跟踪任务完成
|
|
|
- CountDownLatch latch = new CountDownLatch(1);
|
|
|
- // 用于存储第一个错误结果
|
|
|
- AtomicReference<R<ErrorInfo>> firstError = new AtomicReference<>(null);
|
|
|
- // 创建验证任务列表
|
|
|
- List<Callable<R<ErrorInfo>>> validationTasks = getTermValidCallables(ctx, firstError, latch);
|
|
|
-
|
|
|
- // 所有任务的结果集合
|
|
|
- List<Future<R<ErrorInfo>>> futures = new ArrayList<>();
|
|
|
- // 提交所有任务
|
|
|
- for (Callable<R<ErrorInfo>> task : validationTasks) {
|
|
|
- futures.add(taskExecutor.submit(task));
|
|
|
- }
|
|
|
-
|
|
|
- long starTime = System.currentTimeMillis();
|
|
|
- // 最终执行的结果,true 为正常执行结束及latch减至了0;false 为超时
|
|
|
- boolean finallyResult;
|
|
|
+ // 1. 使用LinkedHashMap保持任务顺序(便于调试)
|
|
|
+ final Map<String, Callable<R<ErrorInfo>>> validationTasks = new LinkedHashMap<>();
|
|
|
+ validationTasks.put("参数有效性验证", () -> checkParam(ctx));
|
|
|
+ validationTasks.put("设备有效性验证", () -> checkTerm(ctx));
|
|
|
+ validationTasks.put("账户有效性验证", () -> checkUserAccount(ctx));
|
|
|
+
|
|
|
+ // 2. 快速失败标志和获取任务完成结果
|
|
|
+ final AtomicBoolean hasError = new AtomicBoolean(false);
|
|
|
+ final ExecutorCompletionService<R<ErrorInfo>> completionService = new ExecutorCompletionService<>(threadPoolTaskExecutor);
|
|
|
+
|
|
|
+ // 3. 使用IdentityHashMap优化Future查找
|
|
|
+ final Map<Future<R<ErrorInfo>>, String> futureToName = new IdentityHashMap<>();
|
|
|
+ final List<Future<R<ErrorInfo>>> allFutures = new ArrayList<>();
|
|
|
+ // 4. 提取常量
|
|
|
+ final int totalTasks = validationTasks.size();
|
|
|
+ final long deadline = System.nanoTime() + TimeUnit.MILLISECONDS.toNanos(TIMEOUT_TOTAL);
|
|
|
+ // 5. 提交任务(优化日志封装)
|
|
|
+ validationTasks.forEach((taskName, task) -> {
|
|
|
+ Callable<R<ErrorInfo>> loggedTask = createLoggedTask(taskName, task, hasError);
|
|
|
+ Future<R<ErrorInfo>> future = completionService.submit(loggedTask);
|
|
|
+ futureToName.put(future, taskName);
|
|
|
+ allFutures.add(future);
|
|
|
+ });
|
|
|
+ R<ErrorInfo> firstError = null;
|
|
|
+ int completedTasks = 0;
|
|
|
try {
|
|
|
- // 阻塞主线程,等待执行结果; latch 为0时,会唤醒主线程,最多阻塞10s
|
|
|
- finallyResult = latch.await(10, TimeUnit.SECONDS);
|
|
|
+ while (completedTasks < totalTasks && !hasError.get()) {
|
|
|
+ // 5. 使用纳秒级精确时间计算
|
|
|
+ long remainingNanos = deadline - System.nanoTime();
|
|
|
+ if (remainingNanos <= 0) {
|
|
|
+ // 设置错误标志
|
|
|
+ hasError.set(true);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ Future<R<ErrorInfo>> future = completionService.poll(remainingNanos, TimeUnit.NANOSECONDS);
|
|
|
+ if (future == null) {
|
|
|
+ firstError = CheckError.createTimeoutError();
|
|
|
+ // 设置错误标志
|
|
|
+ hasError.set(true);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ completedTasks++;
|
|
|
+ String taskName = futureToName.get(future);
|
|
|
+
|
|
|
+ try {
|
|
|
+ R<ErrorInfo> result = future.get();
|
|
|
+ if (R.isError(result)) {
|
|
|
+ firstError = result;
|
|
|
+ log.info("{}验证失败: {}", taskName, result);
|
|
|
+ // 设置错误标志并立即取消其他任务
|
|
|
+ hasError.set(true);
|
|
|
+ cancelPendingFutures(allFutures);
|
|
|
+ }
|
|
|
+ } catch (ExecutionException e) {
|
|
|
+ firstError = CheckError.createExecutionError(taskName, e.getCause());
|
|
|
+ hasError.set(true);
|
|
|
+ cancelPendingFutures(allFutures);
|
|
|
+ } catch (CancellationException e) {
|
|
|
+ log.info("{}已提前终止", taskName);
|
|
|
+ completedTasks--; // 调整计数器
|
|
|
+ }
|
|
|
+ }
|
|
|
} catch (InterruptedException e) {
|
|
|
- log.error("executeTermValidationChain- main 被中断 :{}", e.getMessage());
|
|
|
Thread.currentThread().interrupt();
|
|
|
- // 取消所有任务
|
|
|
- futures.forEach(f -> f.cancel(true));
|
|
|
- return createError(TradeStatusEnum.SysError);
|
|
|
+ firstError = CheckError.createInterruptionError();
|
|
|
+ hasError.set(true);
|
|
|
+ cancelPendingFutures(allFutures);
|
|
|
+ } finally {
|
|
|
+ // 6. 确保所有任务最终被清理
|
|
|
+ if (firstError == null && completedTasks < totalTasks) {
|
|
|
+ cancelPendingFutures(allFutures);
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
- // 循环取消所有任务
|
|
|
- futures.forEach(f -> f.cancel(true));
|
|
|
- if (!finallyResult) {
|
|
|
- log.error("executeTermValidationChain- 设备、账户、参数校验10s超时");
|
|
|
- return createError(TradeStatusEnum.SysError);
|
|
|
- }
|
|
|
+ return firstError != null ? firstError : R.ok();
|
|
|
+ }
|
|
|
|
|
|
- R<ErrorInfo> errorInfoR = firstError.get();
|
|
|
- log.info("executeTermValidationChain- 设备、账户、参数校验耗时:{}ms", System.currentTimeMillis() - starTime);
|
|
|
- if (errorInfoR != null && R.isError(errorInfoR)) {
|
|
|
- return errorInfoR;
|
|
|
- }else {
|
|
|
- return R.ok();
|
|
|
+ /**
|
|
|
+ * 取消所有未完成的任务。
|
|
|
+ *
|
|
|
+ * @param futures 任务的 Future 对象列表
|
|
|
+ */
|
|
|
+ private void cancelPendingFutures(List<Future<R<ErrorInfo>>> futures) {
|
|
|
+ for (Future<R<ErrorInfo>> f : futures) {
|
|
|
+ if (!f.isDone()) {
|
|
|
+ f.cancel(true);
|
|
|
+ }
|
|
|
}
|
|
|
-
|
|
|
-// 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);
|
|
|
-// }
|
|
|
-
|
|
|
- // // 提交所有验证任务
|
|
|
- // for (int i = 0, j = validationTasks.size(); i < j; i++) {
|
|
|
- // long starTime = System.currentTimeMillis();
|
|
|
- // final int taskIndex = i;
|
|
|
- // final Supplier<R<ErrorInfo>> task = validationTasks.get(taskIndex);
|
|
|
- // taskExecutor.execute(() -> {
|
|
|
- // if (cancelled.get()) {
|
|
|
- // latch.countDown();
|
|
|
- // return;
|
|
|
- // }
|
|
|
- //
|
|
|
- // try {
|
|
|
- // R<ErrorInfo> result = task.get();
|
|
|
- // // 如果发现错误且尚未设置错误结果
|
|
|
- // if (result != null && R.isError(result) &&
|
|
|
- // firstError.compareAndSet(null, result)) {
|
|
|
- // // 取消其他任务(通过中断)
|
|
|
- // taskExecutor.getThreadPoolExecutor().getQueue().clear();
|
|
|
- // }
|
|
|
- // } catch (Exception e) {
|
|
|
- // if (firstError.compareAndSet(null, createError(TradeStatusEnum.SysError))) {
|
|
|
- // taskExecutor.getThreadPoolExecutor().getQueue().clear();
|
|
|
- // }
|
|
|
- // } finally {
|
|
|
- // log.info("{}完成,耗时:{} ms",getTaskName(taskIndex),System.currentTimeMillis()-starTime);
|
|
|
- // latch.countDown();
|
|
|
- // }
|
|
|
- // });
|
|
|
- // }
|
|
|
- //
|
|
|
- // try {
|
|
|
- // // 等待所有任务完成或超时
|
|
|
- // if (!latch.await(VALIDATION_TIMEOUT, TimeUnit.MILLISECONDS)) {
|
|
|
- // return createError(TradeStatusEnum.VALIDATION_TIMEOUT);
|
|
|
- // }
|
|
|
- //
|
|
|
- // // 返回第一个发现的错误,如果没有错误则返回成功
|
|
|
- // return firstError.get() != null ? firstError.get() : R.ok();
|
|
|
- // } catch (InterruptedException e) {
|
|
|
- // Thread.currentThread().interrupt();
|
|
|
- // log.error("error:{}", e.getMessage(), e);
|
|
|
- // return createError(TradeStatusEnum.SysError);
|
|
|
- // }
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 获取所有终端的验证任务
|
|
|
+ * 创建一个带有日志记录的任务。
|
|
|
*
|
|
|
- * @param ctx
|
|
|
- * @return
|
|
|
+ * @param taskName 任务名称
|
|
|
+ * @param task 需要执行的任务
|
|
|
+ * @param hasError 用于检查是否有其他任务已经失败的标志
|
|
|
+ * @return 带有日志记录的 Callable 任务
|
|
|
*/
|
|
|
- private List<Callable<R<ErrorInfo>>> getTermValidCallables(AllowConsumeValidationContext ctx,
|
|
|
- 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;
|
|
|
+ private Callable<R<ErrorInfo>> createLoggedTask(String taskName, Callable<R<ErrorInfo>> task, AtomicBoolean hasError) {
|
|
|
+ return () -> {
|
|
|
+ // 快速失败检查
|
|
|
+ if (hasError.get()) {
|
|
|
+ log.warn("{}-跳过执行(已有失败)", taskName);
|
|
|
+ throw new CancellationException("任务已取消");
|
|
|
}
|
|
|
- R<ErrorInfo> infoR = checkParam(ctx);
|
|
|
- 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 = checkTerm(ctx);
|
|
|
- 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;
|
|
|
+ // 主动检查中断状态
|
|
|
+ if (Thread.currentThread().isInterrupted()) {
|
|
|
+ log.info("{}-被中断", taskName);
|
|
|
+ throw new CancellationException("线程已中断");
|
|
|
}
|
|
|
- R<ErrorInfo> infoR = checkUserAccount(ctx);
|
|
|
- if (infoR != null && R.isError(infoR)) {
|
|
|
- // 存储错误结果
|
|
|
- firstError.set(infoR);
|
|
|
- latch.countDown();
|
|
|
+ final long startTime = System.currentTimeMillis();
|
|
|
+ log.info("{}-开始执行", taskName);
|
|
|
+
|
|
|
+ try {
|
|
|
+ R<ErrorInfo> result = task.call();
|
|
|
+
|
|
|
+ if (Thread.currentThread().isInterrupted()) {
|
|
|
+ throw new InterruptedException("任务执行中被取消");
|
|
|
+ }
|
|
|
+ return result;
|
|
|
+ } catch (InterruptedException e) {
|
|
|
+ Thread.currentThread().interrupt();
|
|
|
+ log.info("{}-执行中被取消", taskName);
|
|
|
+ throw new CancellationException("线程已中断");
|
|
|
+ } finally {
|
|
|
+ final long duration = System.currentTimeMillis() - startTime;
|
|
|
+ log.info("{}-结束-耗时:{}ms", taskName, duration);
|
|
|
}
|
|
|
- return infoR;
|
|
|
- });
|
|
|
- return validationTasks;
|
|
|
- }
|
|
|
-
|
|
|
- private String getTaskName(int taskIndex) {
|
|
|
- return switch (taskIndex) {
|
|
|
- case 0 -> "参数验证";
|
|
|
- case 1 -> "终端验证";
|
|
|
- case 2 -> "账户验证";
|
|
|
- default -> "未知任务";
|
|
|
};
|
|
|
}
|
|
|
|
|
|
- // region 消费参数校验
|
|
|
+ // endregion
|
|
|
|
|
|
+ // region 消费参数校验
|
|
|
/**
|
|
|
* 消费参数校验
|
|
|
* <p>1.必须有设备机号或mac地址,如果是机号则机号>0</p>
|
|
|
@@ -288,15 +240,15 @@ public class CommonCheck {
|
|
|
public R<ErrorInfo> checkParam(AllowConsumeValidationContext ctx) {
|
|
|
// 1. 校验设备标识参数
|
|
|
if (isTerminalInvalid(ctx)) {
|
|
|
- return createErrorResponse(1, ApiErrorTypeConstants.PARAM_ERROR,
|
|
|
+ return CheckError.createErrorResponse(1, ApiErrorTypeConstants.PARAM_ERROR,
|
|
|
"设备机号不正确", "设备机号必须大于零或MAC地址不能为空!");
|
|
|
}
|
|
|
|
|
|
// 2. 校验用户标识参数
|
|
|
if (isUserIdentifierInvalid(ctx)) {
|
|
|
- return createErrorResponse(1, ApiErrorTypeConstants.PARAM_ERROR,
|
|
|
+ return CheckError.createErrorResponse(1, ApiErrorTypeConstants.PARAM_ERROR,
|
|
|
"交易人员标识不满足",
|
|
|
- "必须提供 [CardNo | FactoryId | userNo | userNumb] 中至少1项来标识交易用户");
|
|
|
+ "必须提供 [cardID | factoryFixID | employeeID | employeeStrID] 中至少1项来标识交易用户");
|
|
|
}
|
|
|
|
|
|
// 3. 所有参数校验通过
|
|
|
@@ -338,14 +290,16 @@ public class CommonCheck {
|
|
|
* @param ctx 消费有效性校验上下文
|
|
|
* @return 检查结果
|
|
|
*/
|
|
|
- public R<ErrorInfo> checkTerm(AllowConsumeValidationContext ctx) {
|
|
|
+ public R<ErrorInfo> checkTerm(AllowConsumeValidationContext ctx) throws InterruptedException {
|
|
|
String msgInfo = ObjectUtil.isEmpty(ctx.getTermMac()) ? ctx.getTermNo().toString() : ctx.getTermMac();
|
|
|
String checkParam = ObjectUtil.isNotEmpty(ctx.getTermMac()) ? ctx.getTermMac() : ctx.getTermNo().toString();
|
|
|
Integer checkMode = ObjectUtil.isNotEmpty(ctx.getTermMac()) ? ConsumeConstants.TERM_MAC : ConsumeConstants.TERM_NO;
|
|
|
+ // 延时1毫秒以便捕获中断响应
|
|
|
+ Thread.sleep(1);
|
|
|
XfTermVo termVo = termBusiness.getTermFromCache(checkParam, checkMode);
|
|
|
|
|
|
if (ObjectUtil.isEmpty(termVo)) {
|
|
|
- return createErrorResponse(400, ApiErrorTypeConstants.OBJECT_NOT_EXISTS,
|
|
|
+ return CheckError.createErrorResponse(400, ApiErrorTypeConstants.OBJECT_NOT_EXISTS,
|
|
|
"设备不存在",
|
|
|
MessageFormat.format("机号或MAC为[{0}]的设备不存在,不允许交易", msgInfo));
|
|
|
}
|
|
|
@@ -368,12 +322,14 @@ public class CommonCheck {
|
|
|
* @param ctx 消费有效性校验上下文,包含消费相关的基础信息(如用户流水号、消费金额等)
|
|
|
* @return 如果校验失败,则返回包含错误信息的 R 对象;如果校验成功,则返回表示成功的 R 对象
|
|
|
*/
|
|
|
- public R<ErrorInfo> checkUserAccount(AllowConsumeValidationContext ctx) {
|
|
|
+ public R<ErrorInfo> checkUserAccount(AllowConsumeValidationContext ctx) throws InterruptedException {
|
|
|
long cardNo = ObjectUtil.isEmpty(ctx.getCardNo()) ? 0 : ctx.getCardNo();
|
|
|
long userNo = ObjectUtil.isEmpty(ctx.getUserNo()) ? 0 : ctx.getUserNo();
|
|
|
long factoryId = ObjectUtil.isEmpty(ctx.getFactoryId()) ? 0 : ctx.getFactoryId();
|
|
|
String userNumb = ObjectUtil.isEmpty(ctx.getUserNumb()) ? null : ctx.getUserNumb();
|
|
|
|
|
|
+ // 延时1毫秒以便捕获中断响应
|
|
|
+ Thread.sleep(1);
|
|
|
// 如果卡流水号>0验证卡信息
|
|
|
if (cardNo > 0) {
|
|
|
return checkCardNo(ctx);
|
|
|
@@ -407,7 +363,7 @@ public class CommonCheck {
|
|
|
Long factoryId = ctx.getFactoryId();
|
|
|
Long cardFactoryId = ctx.getUserCardVo().getFactoryId();
|
|
|
if (factoryId.compareTo(0L) > 0 && !cardFactoryId.equals(factoryId)) {
|
|
|
- return createErrorResponse(400, ApiErrorTypeConstants.PARAM_ERROR,
|
|
|
+ return CheckError.createErrorResponse(400, ApiErrorTypeConstants.PARAM_ERROR,
|
|
|
"卡片不正确",
|
|
|
MessageFormat.format("物理卡号不一致,不允许交易。传入值[{0}],系统值[{1}]", factoryId,
|
|
|
cardFactoryId));
|
|
|
@@ -450,7 +406,6 @@ public class CommonCheck {
|
|
|
return R.ok();
|
|
|
}
|
|
|
|
|
|
-
|
|
|
/**
|
|
|
* 根据用户流水号校验账户是否可以消费。
|
|
|
*
|
|
|
@@ -469,7 +424,7 @@ public class CommonCheck {
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 根据用户编号号校验账户是否可以消费。
|
|
|
+ * 根据用户编号校验账户是否可以消费。
|
|
|
*
|
|
|
* @param ctx 消费有效性校验上下文,包含消费相关的基础信息(如用户流水号、消费金额等)
|
|
|
* @return 如果校验失败,则返回包含错误信息的 R 对象;如果校验成功,则返回表示成功的 R 对象
|
|
|
@@ -483,9 +438,14 @@ public class CommonCheck {
|
|
|
}
|
|
|
// 校验卡片信息
|
|
|
return checkCardAfterUser(ctx);
|
|
|
-
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * 在用户检查之后验证卡片。
|
|
|
+ *
|
|
|
+ * @param ctx 验证上下文
|
|
|
+ * @return 验证结果
|
|
|
+ */
|
|
|
private R<ErrorInfo> checkCardAfterUser(AllowConsumeValidationContext ctx) {
|
|
|
R<ErrorInfo> result;
|
|
|
Long userId = ctx.getUserAccountVo().getUserId();
|
|
|
@@ -514,31 +474,31 @@ public class CommonCheck {
|
|
|
RemoteUserAccountVo accountVo = employeeBusiness.getAccountFromCache(checkParam, checkMode);
|
|
|
// 账户不存在,不允许交易
|
|
|
if (accountVo == null) {
|
|
|
- return createErrorResponse(400, ApiErrorTypeConstants.NOT_FOUND,
|
|
|
+ return CheckError.createErrorResponse(400, ApiErrorTypeConstants.NOT_FOUND,
|
|
|
"账户不存在",
|
|
|
MessageFormat.format("流水号或Id为[{0}]的账户不存在,不允许交易", checkParam));
|
|
|
}
|
|
|
// 账户被冻结,不允许交易
|
|
|
if (ObjectUtil.equals(accountVo.getFreezeStatus(), Constants.SYS_YES)) {
|
|
|
- return createErrorResponse(400, ApiErrorTypeConstants.BAD_REQUEST,
|
|
|
+ return CheckError.createErrorResponse(400, ApiErrorTypeConstants.BAD_REQUEST,
|
|
|
"账户被冻结",
|
|
|
MessageFormat.format("流水号或Id为[{0}]的账户被冻结,不允许交易", checkParam));
|
|
|
}
|
|
|
// 账户状态不正常,不允许交易
|
|
|
if (ObjectUtil.equals(accountVo.getStatus(), Constants.SYS_NO)) {
|
|
|
- return createErrorResponse(400, ApiErrorTypeConstants.NOT_FOUND,
|
|
|
+ return CheckError.createErrorResponse(400, ApiErrorTypeConstants.NOT_FOUND,
|
|
|
"账户状态不正常",
|
|
|
MessageFormat.format("流水号或Id为[{0}]的账户状态不正常,不允许交易", checkParam));
|
|
|
}
|
|
|
// 未开户,不允许交易
|
|
|
if (!ObjectUtil.equals(accountVo.getAccountStatus(), UserAccountStatusEnum.IS_OPEN.code().toString())) {
|
|
|
- return createErrorResponse(400, ApiErrorTypeConstants.NOT_FOUND,
|
|
|
+ return CheckError.createErrorResponse(400, ApiErrorTypeConstants.NOT_FOUND,
|
|
|
"账户尚未开户",
|
|
|
MessageFormat.format("流水号或Id为[{0}]的账户尚未开户,不允许交易", checkParam));
|
|
|
}
|
|
|
// 账户已过有效期,不允许消费
|
|
|
if (accountVo.getLifespan().getTime() < System.currentTimeMillis()) {
|
|
|
- return createErrorResponse(400, ApiErrorTypeConstants.BAD_REQUEST,
|
|
|
+ return CheckError.createErrorResponse(400, ApiErrorTypeConstants.BAD_REQUEST,
|
|
|
"账户过期",
|
|
|
MessageFormat.format("流水号或Id为[{0}]的账户已过期,不允许交易", checkParam));
|
|
|
}
|
|
|
@@ -562,13 +522,13 @@ public class CommonCheck {
|
|
|
RemoteCardVo cardVo = cardBusiness.getCardFromCache(checkParam, checkMode);
|
|
|
// 卡片不存在,不允许消费
|
|
|
if (cardVo == null) {
|
|
|
- return createErrorResponse(400, ApiErrorTypeConstants.CARD_NOT_EXISTS,
|
|
|
+ return CheckError.createErrorResponse(400, ApiErrorTypeConstants.CARD_NOT_EXISTS,
|
|
|
"卡片不存在",
|
|
|
MessageFormat.format("流水号或用户Id或物理卡号为[{0}]的卡片不存在,不允许交易", checkParam));
|
|
|
}
|
|
|
// 卡片状态不正常,不允许消费
|
|
|
if (!String.valueOf(CardStatusEnum.NORMAL.code()).equals(cardVo.getStatus())) {
|
|
|
- return createErrorResponse(400, ApiErrorTypeConstants.CARD_STATUS_NOT_NORMAL,
|
|
|
+ return CheckError.createErrorResponse(400, ApiErrorTypeConstants.CARD_STATUS_NOT_NORMAL,
|
|
|
"卡片状态不正确",
|
|
|
MessageFormat.format("流水号或用户Id或物理卡号为[{0}]的卡片状态不正确,不允许交易", checkParam));
|
|
|
|
|
|
@@ -587,43 +547,31 @@ public class CommonCheck {
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 创建错误响应对象
|
|
|
+ * 设置校验后的账户与卡片数据。
|
|
|
+ * <p>
|
|
|
+ * 该方法用于在校验完成后,将相关的账户信息和卡片信息设置到业务对象中,以便后续的业务逻辑使用。
|
|
|
*
|
|
|
- * @param code 错误码
|
|
|
- * @param type 错误类型
|
|
|
- * @param msg 错误消息
|
|
|
- * @param detail 错误详情
|
|
|
- * @return 封装好的错误响应
|
|
|
+ * @param ctx 消费有效性校验上下文,包含消费相关的基础信息
|
|
|
*/
|
|
|
- public R<ErrorInfo> createErrorResponse(int code, String type, String msg, String detail) {
|
|
|
- ErrorInfo errorInfo = new ErrorInfo(code, type, msg, detail);
|
|
|
- return R.fail(errorInfo);
|
|
|
- }
|
|
|
+ private void setCheckAfterAccountData(AllowConsumeValidationContext ctx) {
|
|
|
+ RemoteUserAccountVo accountVo = ctx.getUserAccountVo();
|
|
|
+ RemoteCardVo cardVo = ctx.getUserCardVo();
|
|
|
+ // 重置部部分卡片信息
|
|
|
+ ctx.getBo().setCardNo(cardVo.getCardNo());
|
|
|
+ ctx.getBo().setFactoryId(cardVo.getFactoryId());
|
|
|
+ ctx.getBo().setCardTypeName(cardVo.getCardTypeName());
|
|
|
|
|
|
- /**
|
|
|
- * 创建错误响应对象
|
|
|
- *
|
|
|
- * @param status 交易状态
|
|
|
- * @return 封装好的错误响应
|
|
|
- */
|
|
|
- public R<ErrorInfo> createError(TradeStatusEnum status) {
|
|
|
- return createError(status, status.getName());
|
|
|
+ // 重置部分账户信息
|
|
|
+ ctx.getBo().setUserId(accountVo.getUserId());
|
|
|
+ ctx.getBo().setRealName(StrUtil.isEmpty(accountVo.getRealName()) ? "----" : accountVo.getRealName());
|
|
|
+ ctx.getBo().setUserNo(accountVo.getUserNo());
|
|
|
+ ctx.getBo().setUserNumb(accountVo.getUserNumb());
|
|
|
+ ctx.getBo().setTenantId(accountVo.getTenantId());
|
|
|
+ ctx.getBo().setExpireDate(accountVo.getLifespan());
|
|
|
+ ctx.getBo().setDeptName(accountVo.getDeptName());
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * 创建错误响应对象
|
|
|
- *
|
|
|
- * @param status 交易状态
|
|
|
- * @param message 错误消息
|
|
|
- * @return 封装好的错误响应
|
|
|
- */
|
|
|
- public R<ErrorInfo> createError(TradeStatusEnum status, String message) {
|
|
|
- return R.fail(new ErrorInfo(
|
|
|
- 400,
|
|
|
- status.toString(),
|
|
|
- message,
|
|
|
- status.getName()));
|
|
|
- }
|
|
|
+ // endregion
|
|
|
|
|
|
/**
|
|
|
* 根据消费日期获取对应的餐次类型。
|
|
|
@@ -667,48 +615,10 @@ public class CommonCheck {
|
|
|
Long mealType = bo.getMealType();
|
|
|
Date consumeDate = bo.getConsumeDate();
|
|
|
BigDecimal consumeMoney = bo.getConsumeMoney();
|
|
|
- String currentDateStr = DateFormatUtils.format(new Date(), "yyyy-MM-dd");
|
|
|
- String consumeDateStr = DateFormatUtils.format(consumeDate, "yyyy-MM-dd");
|
|
|
- Long userId = bo.getUserId();
|
|
|
- BigDecimal balance = bo.getBalance();
|
|
|
-
|
|
|
- //// 将当笔原始消费记录标识放入缓存,1天过期
|
|
|
- //Set<String> originalId = Collections.singleton(bo.getOriginalId());
|
|
|
- //RedisUtils.setCacheSet(CacheNames.XF_ORIGINAL_ID, originalId);
|
|
|
- //RedisUtils.expire(CacheNames.XF_ORIGINAL_ID, Duration.ofHours(5));
|
|
|
|
|
|
- // if (ObjectUtil.equals(currentDateStr, consumeDateStr)) {
|
|
|
// 重置卡天当日消费数据
|
|
|
- taskExecutor.execute(() -> baseBusiness.resetCardConsumeInfo(userCardVo, mealType, consumeMoney, consumeDate));
|
|
|
+ threadPoolTaskExecutor.execute(() -> baseBusiness.resetCardConsumeInfo(userCardVo, mealType, consumeMoney, consumeDate));
|
|
|
// 重置卡片当日限制数据
|
|
|
- taskExecutor.execute(() -> baseBusiness.restCardLimitedInfo(mapCardLimited, cardLimitedVo, consumeMoney));
|
|
|
- // 重置人员当日总卡余
|
|
|
- // taskExecutor.submit(() -> baseBusiness.resetUserBalance(userId, balance));
|
|
|
- // }
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * 设置校验后的账户与卡片数据。
|
|
|
- * <p>
|
|
|
- * 该方法用于在校验完成后,将相关的账户信息和卡片信息设置到业务对象中,以便后续的业务逻辑使用。
|
|
|
- *
|
|
|
- * @param ctx 消费有效性校验上下文,包含消费相关的基础信息
|
|
|
- */
|
|
|
- private void setCheckAfterAccountData(AllowConsumeValidationContext ctx) {
|
|
|
- RemoteUserAccountVo accountVo = ctx.getUserAccountVo();
|
|
|
- RemoteCardVo cardVo = ctx.getUserCardVo();
|
|
|
- // 重置部部分卡片信息
|
|
|
- ctx.getBo().setCardNo(cardVo.getCardNo());
|
|
|
- ctx.getBo().setFactoryId(cardVo.getFactoryId());
|
|
|
- ctx.getBo().setCardTypeName(cardVo.getCardTypeName());
|
|
|
-
|
|
|
- // 重置部分账户信息
|
|
|
- ctx.getBo().setUserId(accountVo.getUserId());
|
|
|
- ctx.getBo().setRealName(StrUtil.isEmpty(accountVo.getRealName()) ? "----" : accountVo.getRealName());
|
|
|
- ctx.getBo().setUserNo(accountVo.getUserNo());
|
|
|
- ctx.getBo().setUserNumb(accountVo.getUserNumb());
|
|
|
- ctx.getBo().setTenantId(accountVo.getTenantId());
|
|
|
- ctx.getBo().setExpireDate(accountVo.getLifespan());
|
|
|
- ctx.getBo().setDeptName(accountVo.getDeptName());
|
|
|
+ threadPoolTaskExecutor.execute(() -> baseBusiness.restCardLimitedInfo(mapCardLimited, cardLimitedVo, consumeMoney));
|
|
|
}
|
|
|
}
|