增加定时任务检查订单是否逾期

This commit is contained in:
2025-08-10 19:30:05 +08:00
parent 287ee6d268
commit 0160f324ea
8 changed files with 190 additions and 62 deletions

View File

@ -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 {

View File

@ -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);
}
}

View File

@ -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

View File

@ -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<OrderMainPO> 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){

View File

@ -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<OrderMainPO> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.in(OrderMainPO::getOrderStatus,
Collections.singletonList(OrderStatusEnum.RENT_ING.getCode()))
.lt(OrderMainPO::getEndRentTime, LocalDateTime.now());
List<OrderMainPO> 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<OrderMainPO> 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;
}
}
}

View File

@ -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;
}
}

View File

@ -37,6 +37,7 @@ spring:
redis:
host: 115.190.8.52
port: 6379
database: 0
lettuce:
pool:
max-active: 8