8 changed files with 370 additions and 14 deletions
@ -0,0 +1,29 @@ |
|||
package com.gxwebsoft.common.core.annotation; |
|||
|
|||
import java.lang.annotation.ElementType; |
|||
import java.lang.annotation.Retention; |
|||
import java.lang.annotation.RetentionPolicy; |
|||
import java.lang.annotation.Target; |
|||
|
|||
/** |
|||
* 忽略租户隔离注解 |
|||
* |
|||
* 用于标记需要跨租户操作的方法,如定时任务、系统管理等场景 |
|||
* |
|||
* @author WebSoft |
|||
* @since 2025-01-26 |
|||
*/ |
|||
@Target({ElementType.METHOD}) |
|||
@Retention(RetentionPolicy.RUNTIME) |
|||
public @interface IgnoreTenant { |
|||
|
|||
/** |
|||
* 说明信息,用于记录为什么需要忽略租户隔离 |
|||
*/ |
|||
String value() default ""; |
|||
|
|||
/** |
|||
* 是否记录日志 |
|||
*/ |
|||
boolean logAccess() default true; |
|||
} |
@ -0,0 +1,63 @@ |
|||
package com.gxwebsoft.common.core.aspect; |
|||
|
|||
import com.gxwebsoft.common.core.annotation.IgnoreTenant; |
|||
import com.gxwebsoft.common.core.context.TenantContext; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.aspectj.lang.ProceedingJoinPoint; |
|||
import org.aspectj.lang.annotation.Around; |
|||
import org.aspectj.lang.annotation.Aspect; |
|||
import org.aspectj.lang.reflect.MethodSignature; |
|||
import org.springframework.core.annotation.Order; |
|||
import org.springframework.stereotype.Component; |
|||
|
|||
import java.lang.reflect.Method; |
|||
|
|||
/** |
|||
* 忽略租户隔离切面 |
|||
* |
|||
* 自动处理 @IgnoreTenant 注解标记的方法,临时禁用租户隔离 |
|||
* |
|||
* @author WebSoft |
|||
* @since 2025-01-26 |
|||
*/ |
|||
@Slf4j |
|||
@Aspect |
|||
@Component |
|||
@Order(1) // 确保在其他切面之前执行
|
|||
public class IgnoreTenantAspect { |
|||
|
|||
@Around("@annotation(com.gxwebsoft.common.core.annotation.IgnoreTenant)") |
|||
public Object around(ProceedingJoinPoint joinPoint) throws Throwable { |
|||
MethodSignature signature = (MethodSignature) joinPoint.getSignature(); |
|||
Method method = signature.getMethod(); |
|||
IgnoreTenant ignoreTenant = method.getAnnotation(IgnoreTenant.class); |
|||
|
|||
// 记录原始状态
|
|||
boolean originalIgnore = TenantContext.isIgnoreTenant(); |
|||
|
|||
try { |
|||
// 设置忽略租户隔离
|
|||
TenantContext.setIgnoreTenant(true); |
|||
|
|||
// 记录日志
|
|||
if (ignoreTenant.logAccess()) { |
|||
String className = joinPoint.getTarget().getClass().getSimpleName(); |
|||
String methodName = method.getName(); |
|||
String reason = ignoreTenant.value(); |
|||
|
|||
if (reason.isEmpty()) { |
|||
log.debug("执行跨租户操作: {}.{}", className, methodName); |
|||
} else { |
|||
log.debug("执行跨租户操作: {}.{} - {}", className, methodName, reason); |
|||
} |
|||
} |
|||
|
|||
// 执行目标方法
|
|||
return joinPoint.proceed(); |
|||
|
|||
} finally { |
|||
// 恢复原始状态
|
|||
TenantContext.setIgnoreTenant(originalIgnore); |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,67 @@ |
|||
package com.gxwebsoft.common.core.context; |
|||
|
|||
/** |
|||
* 租户上下文管理器 |
|||
* |
|||
* 用于在特定场景下临时禁用租户隔离 |
|||
* |
|||
* @author WebSoft |
|||
* @since 2025-01-26 |
|||
*/ |
|||
public class TenantContext { |
|||
|
|||
private static final ThreadLocal<Boolean> IGNORE_TENANT = new ThreadLocal<>(); |
|||
|
|||
/** |
|||
* 设置忽略租户隔离 |
|||
*/ |
|||
public static void setIgnoreTenant(boolean ignore) { |
|||
IGNORE_TENANT.set(ignore); |
|||
} |
|||
|
|||
/** |
|||
* 是否忽略租户隔离 |
|||
*/ |
|||
public static boolean isIgnoreTenant() { |
|||
Boolean ignore = IGNORE_TENANT.get(); |
|||
return ignore != null && ignore; |
|||
} |
|||
|
|||
/** |
|||
* 清除租户上下文 |
|||
*/ |
|||
public static void clear() { |
|||
IGNORE_TENANT.remove(); |
|||
} |
|||
|
|||
/** |
|||
* 在忽略租户隔离的上下文中执行操作 |
|||
* |
|||
* @param runnable 要执行的操作 |
|||
*/ |
|||
public static void runIgnoreTenant(Runnable runnable) { |
|||
boolean originalIgnore = isIgnoreTenant(); |
|||
try { |
|||
setIgnoreTenant(true); |
|||
runnable.run(); |
|||
} finally { |
|||
setIgnoreTenant(originalIgnore); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 在忽略租户隔离的上下文中执行操作并返回结果 |
|||
* |
|||
* @param supplier 要执行的操作 |
|||
* @return 操作结果 |
|||
*/ |
|||
public static <T> T callIgnoreTenant(java.util.function.Supplier<T> supplier) { |
|||
boolean originalIgnore = isIgnoreTenant(); |
|||
try { |
|||
setIgnoreTenant(true); |
|||
return supplier.get(); |
|||
} finally { |
|||
setIgnoreTenant(originalIgnore); |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,182 @@ |
|||
package com.gxwebsoft.shop; |
|||
|
|||
import com.gxwebsoft.shop.entity.ShopOrder; |
|||
import com.gxwebsoft.shop.service.ShopOrderService; |
|||
import com.gxwebsoft.shop.service.OrderCancelService; |
|||
import com.gxwebsoft.shop.config.OrderConfigProperties; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.junit.jupiter.api.Test; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.boot.test.context.SpringBootTest; |
|||
|
|||
import java.time.LocalDateTime; |
|||
import java.time.format.DateTimeFormatter; |
|||
import java.util.List; |
|||
|
|||
/** |
|||
* 订单查询测试 |
|||
*/ |
|||
@Slf4j |
|||
@SpringBootTest |
|||
public class OrderQueryTest { |
|||
|
|||
@Autowired |
|||
private ShopOrderService shopOrderService; |
|||
|
|||
@Autowired |
|||
private OrderCancelService orderCancelService; |
|||
|
|||
@Autowired |
|||
private OrderConfigProperties orderConfig; |
|||
|
|||
@Test |
|||
public void testQuerySpecificOrder() { |
|||
String orderNo = "1957754623870595072"; |
|||
log.info("查询订单号: {}", orderNo); |
|||
|
|||
ShopOrder order = shopOrderService.getByOutTradeNo(orderNo); |
|||
if (order != null) { |
|||
log.info("订单信息:"); |
|||
log.info(" 订单ID: {}", order.getOrderId()); |
|||
log.info(" 订单号: {}", order.getOrderNo()); |
|||
log.info(" 订单状态: {} (0=待支付, 1=待发货, 2=已取消, 3=已完成)", order.getOrderStatus()); |
|||
log.info(" 支付状态: {} (false=未支付, true=已支付)", order.getPayStatus()); |
|||
log.info(" 创建时间: {}", order.getCreateTime()); |
|||
log.info(" 支付时间: {}", order.getPayTime()); |
|||
log.info(" 取消时间: {}", order.getCancelTime()); |
|||
log.info(" 租户ID: {}", order.getTenantId()); |
|||
log.info(" 订单金额: {}", order.getTotalPrice()); |
|||
log.info(" 取消原因: {}", order.getCancelReason()); |
|||
|
|||
// 检查是否符合自动取消条件
|
|||
checkAutoCancelConditions(order); |
|||
|
|||
// 计算什么时候会符合自动取消条件
|
|||
calculateCancelTime(order); |
|||
} else { |
|||
log.warn("未找到订单号为 {} 的订单", orderNo); |
|||
} |
|||
} |
|||
|
|||
private void checkAutoCancelConditions(ShopOrder order) { |
|||
log.info("\n=== 检查自动取消条件 ==="); |
|||
|
|||
// 1. 检查订单状态
|
|||
boolean statusOk = (order.getOrderStatus() != null && order.getOrderStatus() == 0); |
|||
log.info("1. 订单状态检查: {} (需要为0-待支付)", statusOk ? "✓通过" : "✗不通过"); |
|||
|
|||
// 2. 检查支付状态
|
|||
boolean payStatusOk = (order.getPayStatus() != null && !order.getPayStatus()); |
|||
log.info("2. 支付状态检查: {} (需要为false-未支付)", payStatusOk ? "✓通过" : "✗不通过"); |
|||
|
|||
// 3. 检查创建时间是否超时
|
|||
if (order.getCreateTime() != null) { |
|||
LocalDateTime createTime = order.getCreateTime(); |
|||
LocalDateTime now = LocalDateTime.now(); |
|||
|
|||
// 获取超时配置
|
|||
Integer timeoutMinutes = getTimeoutMinutes(order.getTenantId()); |
|||
LocalDateTime expireTime = createTime.plusMinutes(timeoutMinutes); |
|||
|
|||
boolean timeoutOk = now.isAfter(expireTime); |
|||
long minutesElapsed = java.time.Duration.between(createTime, now).toMinutes(); |
|||
|
|||
log.info("3. 超时检查:"); |
|||
log.info(" 创建时间: {}", createTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))); |
|||
log.info(" 当前时间: {}", now.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))); |
|||
log.info(" 超时配置: {}分钟", timeoutMinutes); |
|||
log.info(" 过期时间: {}", expireTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))); |
|||
log.info(" 已过时间: {}分钟", minutesElapsed); |
|||
log.info(" 是否超时: {} (需要超过{}分钟)", timeoutOk ? "✓是" : "✗否", timeoutMinutes); |
|||
|
|||
// 4. 综合判断
|
|||
boolean shouldCancel = statusOk && payStatusOk && timeoutOk; |
|||
log.info("\n=== 综合判断 ==="); |
|||
log.info("是否符合自动取消条件: {}", shouldCancel ? "✓是" : "✗否"); |
|||
|
|||
if (shouldCancel) { |
|||
log.info("该订单符合自动取消任务的处理条件"); |
|||
} else { |
|||
log.info("该订单不符合自动取消任务的处理条件"); |
|||
if (!statusOk) log.info(" - 订单状态不是待支付状态"); |
|||
if (!payStatusOk) log.info(" - 订单已支付"); |
|||
if (!timeoutOk) log.info(" - 订单未超时"); |
|||
} |
|||
} else { |
|||
log.warn("订单创建时间为空,无法判断是否超时"); |
|||
} |
|||
} |
|||
|
|||
private void calculateCancelTime(ShopOrder order) { |
|||
log.info("\n=== 计算自动取消时间点 ==="); |
|||
|
|||
if (order.getCreateTime() == null) { |
|||
log.warn("订单创建时间为空,无法计算取消时间"); |
|||
return; |
|||
} |
|||
|
|||
// 获取超时配置
|
|||
Integer timeoutMinutes = getTimeoutMinutes(order.getTenantId()); |
|||
LocalDateTime createTime = order.getCreateTime(); |
|||
LocalDateTime cancelTime = createTime.plusMinutes(timeoutMinutes); |
|||
LocalDateTime now = LocalDateTime.now(); |
|||
|
|||
log.info("订单创建时间: {}", createTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))); |
|||
log.info("当前时间: {}", now.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))); |
|||
log.info("超时配置: {}分钟", timeoutMinutes); |
|||
log.info("预计取消时间: {}", cancelTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))); |
|||
|
|||
if (now.isBefore(cancelTime)) { |
|||
long minutesLeft = java.time.Duration.between(now, cancelTime).toMinutes(); |
|||
long secondsLeft = java.time.Duration.between(now, cancelTime).getSeconds() % 60; |
|||
log.info("距离自动取消还有: {}分{}秒", minutesLeft, secondsLeft); |
|||
log.info("状态: ⏰ 等待中"); |
|||
} else { |
|||
long minutesOverdue = java.time.Duration.between(cancelTime, now).toMinutes(); |
|||
log.info("已超时: {}分钟", minutesOverdue); |
|||
log.info("状态: ⚠️ 应该被取消"); |
|||
|
|||
// 检查为什么没有被取消
|
|||
if (order.getPayStatus() != null && order.getPayStatus()) { |
|||
log.info("原因: 订单已支付,不会被自动取消"); |
|||
} else if (order.getOrderStatus() != null && order.getOrderStatus() != 0) { |
|||
log.info("原因: 订单状态不是待支付({}), 不会被自动取消", order.getOrderStatus()); |
|||
} else { |
|||
log.info("原因: 订单符合取消条件,可能定时任务尚未执行或执行失败"); |
|||
} |
|||
} |
|||
} |
|||
|
|||
private Integer getTimeoutMinutes(Integer tenantId) { |
|||
// 检查是否有租户特殊配置
|
|||
List<OrderConfigProperties.TenantCancelConfig> tenantConfigs = orderConfig.getAutoCancel().getTenantConfigs(); |
|||
if (tenantConfigs != null) { |
|||
for (OrderConfigProperties.TenantCancelConfig config : tenantConfigs) { |
|||
if (config.isEnabled() && config.getTenantId().equals(tenantId)) { |
|||
return config.getTimeoutMinutes(); |
|||
} |
|||
} |
|||
} |
|||
|
|||
// 使用默认配置
|
|||
return orderConfig.getAutoCancel().getDefaultTimeoutMinutes(); |
|||
} |
|||
|
|||
@Test |
|||
public void testFindExpiredOrders() { |
|||
log.info("=== 测试查找超时订单 ==="); |
|||
|
|||
Integer defaultTimeout = orderConfig.getAutoCancel().getDefaultTimeoutMinutes(); |
|||
Integer batchSize = orderConfig.getAutoCancel().getBatchSize(); |
|||
|
|||
log.info("默认超时时间: {}分钟", defaultTimeout); |
|||
log.info("批量大小: {}", batchSize); |
|||
|
|||
List<ShopOrder> expiredOrders = orderCancelService.findExpiredUnpaidOrders(defaultTimeout, batchSize); |
|||
log.info("找到{}个超时订单", expiredOrders.size()); |
|||
|
|||
for (ShopOrder order : expiredOrders) { |
|||
log.info("超时订单: {} - 创建时间: {}", order.getOrderNo(), order.getCreateTime()); |
|||
} |
|||
} |
|||
} |
Loading…
Reference in new issue