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