From 0160f324ea9529710acc2765757419aaf74d8beb Mon Sep 17 00:00:00 2001 From: zhangli <123879394@qq.com> Date: Sun, 10 Aug 2025 19:30:05 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=AE=9A=E6=97=B6=E4=BB=BB?= =?UTF-8?q?=E5=8A=A1=E6=A3=80=E6=9F=A5=E8=AE=A2=E5=8D=95=E6=98=AF=E5=90=A6?= =?UTF-8?q?=E9=80=BE=E6=9C=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 11 ++ src/main/java/com/sczx/order/Application.java | 4 + .../sczx/order/config/SchedulerConfig.java | 20 ++++ .../com/sczx/order/service/OrderService.java | 25 ----- .../order/service/impl/OrderServiceImpl.java | 42 +------- .../com/sczx/order/task/OrderOverdueTask.java | 101 ++++++++++++++++++ .../java/com/sczx/order/utils/OrderUtil.java | 48 +++++++++ src/main/resources/application.yml | 1 + 8 files changed, 190 insertions(+), 62 deletions(-) create mode 100644 src/main/java/com/sczx/order/config/SchedulerConfig.java create mode 100644 src/main/java/com/sczx/order/task/OrderOverdueTask.java diff --git a/pom.xml b/pom.xml index ab97c4c..530db57 100644 --- a/pom.xml +++ b/pom.xml @@ -227,6 +227,17 @@ fastjson 1.2.83 + + + net.javacrumbs.shedlock + shedlock-spring + 4.44.0 + + + net.javacrumbs.shedlock + shedlock-provider-redis-spring + 4.44.0 + diff --git a/src/main/java/com/sczx/order/Application.java b/src/main/java/com/sczx/order/Application.java index f36cc1c..d6001c2 100644 --- a/src/main/java/com/sczx/order/Application.java +++ b/src/main/java/com/sczx/order/Application.java @@ -3,6 +3,7 @@ package com.sczx.order; import com.sczx.order.common.constant.SystemConstants; import com.sczx.order.utils.ComputerInfo; import lombok.extern.slf4j.Slf4j; +import net.javacrumbs.shedlock.spring.annotation.EnableSchedulerLock; import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @@ -12,6 +13,7 @@ import org.springframework.cloud.openfeign.EnableFeignClients; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.core.env.Environment; import org.springframework.retry.annotation.EnableRetry; +import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.transaction.annotation.EnableTransactionManagement; import java.io.IOException; @@ -24,6 +26,8 @@ import java.io.IOException; @EnableTransactionManagement @EnableHystrix @MapperScan("com.sczx.order.mapper") // 扫描 Mapper 接口 +@EnableScheduling +@EnableSchedulerLock(defaultLockAtMostFor = "10m") public class Application { public static void main(String[] args) throws IOException { diff --git a/src/main/java/com/sczx/order/config/SchedulerConfig.java b/src/main/java/com/sczx/order/config/SchedulerConfig.java new file mode 100644 index 0000000..c6405a5 --- /dev/null +++ b/src/main/java/com/sczx/order/config/SchedulerConfig.java @@ -0,0 +1,20 @@ +package com.sczx.order.config; + +import net.javacrumbs.shedlock.core.LockProvider; +import net.javacrumbs.shedlock.provider.redis.spring.RedisLockProvider; +import net.javacrumbs.shedlock.spring.annotation.EnableSchedulerLock; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.scheduling.annotation.EnableScheduling; + +@Configuration +@EnableScheduling +@EnableSchedulerLock(defaultLockAtMostFor = "10m") +public class SchedulerConfig { + + @Bean + public LockProvider lockProvider(RedisConnectionFactory connectionFactory) { + return new RedisLockProvider(connectionFactory); + } +} diff --git a/src/main/java/com/sczx/order/service/OrderService.java b/src/main/java/com/sczx/order/service/OrderService.java index 4871136..b11ed46 100644 --- a/src/main/java/com/sczx/order/service/OrderService.java +++ b/src/main/java/com/sczx/order/service/OrderService.java @@ -4,9 +4,6 @@ package com.sczx.order.service; import com.baomidou.mybatisplus.core.metadata.IPage; import com.sczx.order.dto.*; -import java.math.BigDecimal; -import java.time.LocalDateTime; - public interface OrderService { /** @@ -45,28 +42,6 @@ public interface OrderService { */ OrderDetailDTO getOrderDetailByOrderNo(String orderNo); - /** - * 计算订单逾期天数 - * @param endRentTime - * @return - */ - Integer getOrderOverdueDays(LocalDateTime endRentTime); - - /** - * 计算订单即将到期天数 - * @param endRentTime - * @return - */ - Integer getOrderExpectedDays(LocalDateTime endRentTime); - - /** - * 计算逾期金额 - * @param overdueDays - * @param overdueFee - * @return - */ - BigDecimal getOrderOverdueAmount(Integer overdueDays, BigDecimal overdueFee); - /** * 查询用户当前未完成的订单 * @param customerId diff --git a/src/main/java/com/sczx/order/service/impl/OrderServiceImpl.java b/src/main/java/com/sczx/order/service/impl/OrderServiceImpl.java index 1445d18..374f739 100644 --- a/src/main/java/com/sczx/order/service/impl/OrderServiceImpl.java +++ b/src/main/java/com/sczx/order/service/impl/OrderServiceImpl.java @@ -33,7 +33,6 @@ import org.springframework.util.CollectionUtils; import java.math.BigDecimal; import java.time.LocalDateTime; -import java.time.temporal.ChronoUnit; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -316,7 +315,7 @@ public class OrderServiceImpl implements OrderService { } } //生成租车订单 - BigDecimal overDueAmount = getOrderOverdueAmount(orderMainPO.getOverdueDays(), orderMainPO.getOverdueFee()); + BigDecimal overDueAmount = OrderUtil.getOrderOverdueAmount(orderMainPO.getOverdueDays(), orderMainPO.getOverdueFee()); //生成租车子订单 OrderSubPO rentOrder = new OrderSubPO(); rentOrder.setOrderId(orderMainPO.getOrderId()); @@ -437,12 +436,12 @@ public class OrderServiceImpl implements OrderService { //如果是租车中,需要判断是否逾期了 log.info("判断订单是否逾期"); if(orderMainPO.getEndRentTime()!=null){ - Integer overdueDays = getOrderOverdueDays(orderMainPO.getEndRentTime()); + Integer overdueDays = OrderUtil.getOrderOverdueDays(orderMainPO.getEndRentTime()); log.info("预计还车时间:{},订单逾期天数:{}",orderMainPO.getEndRentTime(),overdueDays); //逾期天数>0,则改为逾期,并且计算逾期天数以及逾期金额 if(overdueDays>0){ orderDetailDTO.setOverdueDays(overdueDays); - orderDetailDTO.setOverdueAmount(getOrderOverdueAmount(overdueDays, orderMainPO.getOverdueFee())); + orderDetailDTO.setOverdueAmount(OrderUtil.getOrderOverdueAmount(overdueDays, orderMainPO.getOverdueFee())); orderDetailDTO.setOrderStatus(OrderStatusEnum.RENT_OVERDUE.getCode()); LambdaUpdateWrapper updateWrapper = new LambdaUpdateWrapper<>(); updateWrapper.set(OrderMainPO::getOrderStatus, OrderStatusEnum.RENT_OVERDUE.getCode()); @@ -451,14 +450,14 @@ public class OrderServiceImpl implements OrderService { orderMainRepo.update(updateWrapper); }else { //没逾期则计算到期天数 - orderDetailDTO.setExpectedDays(getOrderExpectedDays(orderMainPO.getEndRentTime())); + orderDetailDTO.setExpectedDays(OrderUtil.getOrderExpectedDays(orderMainPO.getEndRentTime())); } } } else if(OrderStatusEnum.RENT_OVERDUE.getCode().equalsIgnoreCase(orderMainPO.getOrderStatus())){ log.info("订单已逾期的,计算逾期金额"); if(orderMainPO.getEndRentTime()!=null){ - orderDetailDTO.setOverdueAmount(getOrderOverdueAmount(orderMainPO.getOverdueDays(), orderMainPO.getOverdueFee())); + orderDetailDTO.setOverdueAmount(OrderUtil.getOrderOverdueAmount(orderMainPO.getOverdueDays(), orderMainPO.getOverdueFee())); } } else if(OrderStatusEnum.WAIT_PAY.getCode().equalsIgnoreCase(orderMainPO.getOrderStatus())||OrderStatusEnum.RERENT_WAIT_PAY.getCode().equalsIgnoreCase(orderMainPO.getOrderStatus())){ //TODO 待支付状态要拉起支付 @@ -467,37 +466,6 @@ public class OrderServiceImpl implements OrderService { return orderDetailDTO; } - @Override - public Integer getOrderOverdueDays(LocalDateTime endRentTime) { - if(endRentTime!=null){ - LocalDateTime now = LocalDateTime.now(); - if(now.isAfter(endRentTime)){ - return (int) ChronoUnit.DAYS.between(endRentTime, now); - } - } - return 0; - } - - @Override - public Integer getOrderExpectedDays(LocalDateTime endRentTime) { - if(endRentTime!=null){ - LocalDateTime now = LocalDateTime.now(); - if(now.isBefore(endRentTime)){ - return (int) ChronoUnit.DAYS.between(now, endRentTime); - } - } - return 0; - } - - @Override - public BigDecimal getOrderOverdueAmount(Integer overdueDays, BigDecimal overdueFee) { - if(overdueDays!=null&&overdueFee!=null){ - BigDecimal overdueDaysBd = new BigDecimal(overdueDays); - return overdueDaysBd.multiply(overdueFee); - } - return null; - } - @Override public OrderDetailDTO getCurrentNoEndOrder(Long customerId) { if(customerId==null){ diff --git a/src/main/java/com/sczx/order/task/OrderOverdueTask.java b/src/main/java/com/sczx/order/task/OrderOverdueTask.java new file mode 100644 index 0000000..ee071b1 --- /dev/null +++ b/src/main/java/com/sczx/order/task/OrderOverdueTask.java @@ -0,0 +1,101 @@ +package com.sczx.order.task; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import com.sczx.order.common.enums.OrderStatusEnum; +import com.sczx.order.po.OrderMainPO; +import com.sczx.order.repository.OrderMainRepo; +import com.sczx.order.utils.OrderUtil; +import lombok.extern.slf4j.Slf4j; +import net.javacrumbs.shedlock.spring.annotation.SchedulerLock; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import java.time.LocalDateTime; +import java.util.Collections; +import java.util.List; + +@Slf4j +@Component +public class OrderOverdueTask { + @Autowired + private OrderMainRepo orderMainRepo; + + /** + * 每30分钟检查一次逾期订单 + * 使用ShedLock确保在分布式环境下只有一个实例执行 + * 分布式锁机制:ShedLock使用Redis作为锁存储,确保同一时间只有一个服务实例执行定时任务 + * 任务名称:@SchedulerLock 注解中的 name 属性标识任务名称,相同名称的任务在分布式环境下互斥执行 + * 锁时长配置: + * lockAtMostFor:锁最多持有时间,防止节点宕机导致锁无法释放 + * lockAtLeastFor:锁最少持有时间,防止任务执行过快导致频繁执行 + */ + @Scheduled(cron = "0 */30 * * * ?") + @SchedulerLock(name = "checkOverdueOrders", lockAtMostFor = "9m", lockAtLeastFor = "1m") + public void checkOverdueOrders() { + log.info("开始执行逾期订单检查任务"); + try { + processOverdueOrders(); + log.info("逾期订单检查任务执行完成"); + } catch (Exception e) { + log.error("执行逾期订单检查任务失败", e); + } + } + + /** + * 处理逾期订单 + */ + private void processOverdueOrders() { + try { + // 查询所有进行中的订单且当前时间已超过预计还车时间 + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.in(OrderMainPO::getOrderStatus, + Collections.singletonList(OrderStatusEnum.RENT_ING.getCode())) + .lt(OrderMainPO::getEndRentTime, LocalDateTime.now()); + + List overdueOrders = orderMainRepo.list(queryWrapper); + + if (overdueOrders.isEmpty()) { + log.info("未发现逾期订单"); + return; + } + + log.info("发现{}个逾期订单,开始处理", overdueOrders.size()); + + int successCount = 0; + for (OrderMainPO order : overdueOrders) { + try { + //先计算逾期天数 + Integer overdueDays = OrderUtil.getOrderOverdueDays(order.getEndRentTime()); + if(overdueDays>0){ + // 更新订单状态为逾期 + LambdaUpdateWrapper updateWrapper = new LambdaUpdateWrapper<>(); + updateWrapper.set(OrderMainPO::getOrderStatus, OrderStatusEnum.RENT_OVERDUE.getCode()) + .set(OrderMainPO::getOverdueDays, overdueDays) + .eq(OrderMainPO::getOrderId, order.getOrderId()) + // 确保状态未被其他节点修改 + .eq(OrderMainPO::getOrderStatus, OrderStatusEnum.RENT_ING.getCode()); + + boolean updated = orderMainRepo.update(updateWrapper); + + if (updated ) { + log.info("订单{}已逾期,状态已更新", order.getOrderNo()); + successCount++; + } else { + log.info("订单{}状态已变更,无需更新", order.getOrderNo()); + } + } + + } catch (Exception e) { + log.error("更新订单{}状态失败", order.getOrderNo(), e); + } + } + + log.info("逾期订单处理完成,成功处理{}个订单", successCount); + } catch (Exception e) { + log.error("处理逾期订单失败", e); + throw e; + } + } +} diff --git a/src/main/java/com/sczx/order/utils/OrderUtil.java b/src/main/java/com/sczx/order/utils/OrderUtil.java index 461f73b..8764af0 100644 --- a/src/main/java/com/sczx/order/utils/OrderUtil.java +++ b/src/main/java/com/sczx/order/utils/OrderUtil.java @@ -1,7 +1,9 @@ package com.sczx.order.utils; +import java.math.BigDecimal; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; +import java.time.temporal.ChronoUnit; import java.util.UUID; public class OrderUtil { @@ -37,4 +39,50 @@ public class OrderUtil { String uuidSuffix = UUID.randomUUID().toString().replace("-", "").substring(0, 6).toUpperCase(); return prefix + timestamp + uuidSuffix; // sub代表子订单号 } + + /** + * 计算订单逾期天数 + * @param endRentTime 订单结束时间 + * @return 逾期天数 + */ + public static Integer getOrderOverdueDays(LocalDateTime endRentTime) { + if(endRentTime!=null){ + LocalDateTime now = LocalDateTime.now(); + if(now.isAfter(endRentTime)){ + return (int) ChronoUnit.DAYS.between(endRentTime, now); + } + } + return 0; + } + + + /** + * 计算订单预计天数 + * @param endRentTime 订单结束时间 + * @return 逾期天数 + */ + public static Integer getOrderExpectedDays(LocalDateTime endRentTime) { + if(endRentTime!=null){ + LocalDateTime now = LocalDateTime.now(); + if(now.isBefore(endRentTime)){ + return (int) ChronoUnit.DAYS.between(now, endRentTime); + } + } + return 0; + } + + + /** + * 计算订单逾期金额 + * @param overdueDays 逾期天数 + * @param overdueFee 逾期费用 + * @return 逾期金额 + */ + public static BigDecimal getOrderOverdueAmount(Integer overdueDays, BigDecimal overdueFee) { + if(overdueDays!=null&&overdueFee!=null){ + BigDecimal overdueDaysBd = new BigDecimal(overdueDays); + return overdueDaysBd.multiply(overdueFee); + } + return null; + } } diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 568db2b..ad4c445 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -37,6 +37,7 @@ spring: redis: host: 115.190.8.52 port: 6379 + database: 0 lettuce: pool: max-active: 8