方案一: 定时检测(数据库)
demo
java
复制代码
package com.jysemel.kcnf.todo.job;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReentrantLock;
@Slf4j
@Component
public class TodoDayWarnJob {
private static final Map<String, String> accessTokenCache = new HashMap<>();
// 使用 ReentrantLock
private final ReentrantLock lock = new ReentrantLock();
@SneakyThrows
@Scheduled(cron = "0 0/1 * * * ?")
public void run() {
// 获取锁,避免重复执行
if (lock.tryLock()) {
log.info("每日待办事项提醒.........");
try {
} finally {
// 释放锁
lock.unlock();
}
} else {
log.info("任务正在执行中,跳过本次执行");
}
}
}
优点和缺点
复制代码
小项目首选、快速
缺点: 耗时、并发问题
方案二: 延迟消息队列
优点和缺点
复制代码
小项目首选、快速
缺点: 耗时、并发问题
方案三: 基于redis过期事件
arduino
复制代码
基于redis过期key
开启 Redis 的过期事件通知(默认关闭)
redis.config.notify-keyspace-events "Ex"
demo
kotlin
复制代码
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.listener.PatternTopic;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.data.redis.listener.adapter.MessageListenerAdapter;
@Configuration
public class RedisConfig {
/**
* 配置 Redis 消息监听容器
*/
@Bean
public RedisMessageListenerContainer redisMessageListenerContainer(
RedisConnectionFactory connectionFactory,
MessageListenerAdapter listenerAdapter) {
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
// 订阅所有数据库的过期事件
container.addMessageListener(listenerAdapter, new PatternTopic("__keyevent@*__:expired"));
return container;
}
/**
* 绑定自定义的监听器
*/
@Bean
public MessageListenerAdapter listenerAdapter(OrderExpiredListener listener) {
return new MessageListenerAdapter(listener);
}
}
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.connection.MessageListener;
import org.springframework.stereotype.Component;
@Component
public class OrderExpiredListener implements MessageListener {
private static final Logger log = LoggerFactory.getLogger(OrderExpiredListener.class);
@Override
public void onMessage(Message message, byte[] pattern) {
// 获取过期的 key
String expiredKey = message.toString();
log.info("收到 Redis 过期事件,key: {}", expiredKey);
// 判断是否是订单 key(格式 order:{id})
if (expiredKey != null && expiredKey.startsWith("order:")) {
String orderId = expiredKey.substring(6); // 去掉 "order:"
// 执行订单取消逻辑(幂等处理)
cancelOrder(orderId);
}
}
/**
* 模拟取消订单:更新订单状态为已取消,打印日志
*/
private void cancelOrder(String orderId) {
// 实际项目中可注入 Service 操作数据库
log.info("订单 {} 超时,已自动取消", orderId);
// 这里可以调用数据库更新语句,例如 update orders set status = 'CANCELLED' where id = ? and status = 'NEW'
}
}
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.concurrent.TimeUnit;
@RestController
@RequestMapping("/order")
public class OrderController {
@Autowired
private StringRedisTemplate redisTemplate;
/**
* 创建订单,设置过期时间(秒)
* 例如:http://localhost:8080/order/create?orderId=1001&ttl=10
*/
@GetMapping("/create")
public String createOrder(@RequestParam String orderId,
@RequestParam(defaultValue = "30") long ttl) {
// 模拟将订单信息存入数据库,状态为 NEW
// ...
// 将订单 ID 存入 Redis,设置过期时间
String key = "order:" + orderId;
redisTemplate.opsForValue().set(key, "待支付", ttl, TimeUnit.SECONDS);
return "订单 " + orderId + " 已创建," + ttl + " 秒后过期自动取消";
}
}
方案四: 兜底策略
复制代码
幂等性:所有取消操作必须幂等,防止重复取消导致数据错误。
状态检查:执行取消前应检查订单当前状态(如仅当状态为"待支付"时才取消)。
重试机制:对于执行失败的取消操作,应有重试机制(如结合消息队列重试)。
监控告警:对长时间未处理的订单进行监控,及时发现策略失效。