You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Spring Boot中实现方法于指定日期(Ad实体activeUntil字段)执行?

当然有啦!Spring生态里有好几种成熟的方案能搞定这个「在广告过期时间点发送通知」的需求,我给你拆解几个常用的思路,你可以根据自己的业务场景来选:

方案一:使用Spring Scheduled定时任务(轻量简单)

这是最容易上手的方案,适合时效性要求不是特别高、广告数量适中的场景。核心逻辑是定时扫描数据库,找出已过期且未发送通知的广告,执行发送逻辑。

步骤示例:

  1. 先开启Spring的定时任务支持:
@SpringBootApplication
@EnableScheduling
public class AdApplication {
    public static void main(String[] args) {
        SpringApplication.run(AdApplication.class, args);
    }
}
  1. 编写定时任务类,实现扫描和通知逻辑:
@Component
public class AdExpirationTask {

    @Autowired
    private AdRepository adRepository;
    @Autowired
    private NotificationService notificationService;

    // 每小时执行一次扫描(可根据业务调整cron表达式或fixedRate)
    @Scheduled(cron = "0 0 * * * ?")
    public void checkExpiredAds() {
        LocalDateTime now = LocalDateTime.now();
        // 查询已过期且未发送通知的广告
        List<Ad> expiredAds = adRepository.findByActiveUntilBeforeAndNotificationSentFalse(now);
        
        for (Ad ad : expiredAds) {
            // 给广告发布用户发送通知(这里可以是邮件、短信、APP推送等)
            notificationService.sendExpirationNotification(ad.getPublisher());
            // 标记为已发送,避免重复执行
            ad.setNotificationSent(true);
            adRepository.save(ad);
        }
    }
}

方案二:使用Quartz框架(灵活的动态任务调度)

如果你的广告过期时间完全不固定,且需要精确到每个广告的过期点触发,Quartz会是更合适的选择。它支持动态创建、修改、删除定时任务,每个广告可以对应一个独立的过期任务。

步骤示例:

  1. 引入Quartz依赖:
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
  1. 定义执行通知逻辑的Job类:
public class AdExpirationJob implements Job {

    @Autowired
    private NotificationService notificationService;
    @Autowired
    private AdRepository adRepository;

    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        Long adId = (Long) context.getJobDetail().getJobDataMap().get("adId");
        Ad ad = adRepository.findById(adId).orElse(null);
        
        if (ad != null && !ad.isNotificationSent()) {
            notificationService.sendExpirationNotification(ad.getPublisher());
            ad.setNotificationSent(true);
            adRepository.save(ad);
        }
    }
}
  1. 编写任务调度服务,用于创建/取消广告过期任务:
@Service
public class QuartzJobService {

    @Autowired
    private Scheduler scheduler;

    // 为单个广告创建过期任务
    public void scheduleAdExpirationJob(Ad ad) throws SchedulerException {
        // 构建JobDetail,传入广告ID作为参数
        JobDetail jobDetail = JobBuilder.newJob(AdExpirationJob.class)
                .withIdentity("adExpirationJob_" + ad.getId(), "adGroup")
                .usingJobData("adId", ad.getId())
                .build();

        // 构建Trigger,设置触发时间为activeUntil
        Trigger trigger = TriggerBuilder.newTrigger()
                .withIdentity("adExpirationTrigger_" + ad.getId(), "adGroup")
                .startAt(Date.from(ad.getActiveUntil().atZone(ZoneId.systemDefault()).toInstant()))
                .build();

        // 注册任务到调度器
        scheduler.scheduleJob(jobDetail, trigger);
    }

    // 广告更新/删除时,取消对应的过期任务
    public void cancelAdExpirationJob(Long adId) throws SchedulerException {
        JobKey jobKey = new JobKey("adExpirationJob_" + adId, "adGroup");
        scheduler.deleteJob(jobKey);
    }
}
  1. 在广告创建/更新时调用调度服务:
@Service
public class AdService {

    @Autowired
    private AdRepository adRepository;
    @Autowired
    private QuartzJobService quartzJobService;

    public Ad createAd(Ad ad) throws SchedulerException {
        Ad savedAd = adRepository.save(ad);
        // 为新广告调度过期任务
        quartzJobService.scheduleAdExpirationJob(savedAd);
        return savedAd;
    }

    public Ad updateAd(Long adId, Ad updatedAd) throws SchedulerException {
        Ad existingAd = adRepository.findById(adId).orElseThrow();
        // 先取消旧的过期任务
        quartzJobService.cancelAdExpirationJob(adId);
        // 更新广告字段
        existingAd.setActiveUntil(updatedAd.getActiveUntil());
        existingAd.setNotificationSent(false);
        Ad savedAd = adRepository.save(existingAd);
        // 调度新的过期任务
        quartzJobService.scheduleAdExpirationJob(savedAd);
        return savedAd;
    }
}

方案三:使用延迟消息队列(高并发场景推荐)

如果你的系统广告数量大、并发高,用延迟消息队列会更高效,避免定时扫描带来的数据库压力。核心逻辑是广告创建时发送一条延迟消息,延迟时间等于「过期时间 - 当前时间」,消息到期后自动触发通知逻辑。

以RabbitMQ延迟队列为例:

  1. 引入RabbitMQ依赖:
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
  1. 配置延迟队列:
@Configuration
public class RabbitMQConfig {

    public static final String DELAY_EXCHANGE = "ad.delay.exchange";
    public static final String DELAY_QUEUE = "ad.delay.queue";
    public static final String DELAY_ROUTING_KEY = "ad.expiration";

    @Bean
    public CustomExchange delayExchange() {
        Map<String, Object> args = new HashMap<>();
        args.put("x-delayed-type", "direct");
        return new CustomExchange(DELAY_EXCHANGE, "x-delayed-message", true, false, args);
    }

    @Bean
    public Queue delayQueue() {
        return QueueBuilder.durable(DELAY_QUEUE).build();
    }

    @Bean
    public Binding delayBinding() {
        return BindingBuilder.bind(delayQueue())
                .to(delayExchange())
                .with(DELAY_ROUTING_KEY)
                .noargs();
    }
}
  1. 发送延迟消息:
@Service
public class AdMessageService {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    public void sendAdExpirationDelayMessage(Ad ad) {
        LocalDateTime activeUntil = ad.getActiveUntil();
        long delayMillis = Duration.between(LocalDateTime.now(), activeUntil).toMillis();
        
        if (delayMillis > 0) {
            rabbitTemplate.convertAndSend(RabbitMQConfig.DELAY_EXCHANGE, RabbitMQConfig.DELAY_ROUTING_KEY, ad.getId(), message -> {
                message.getMessageProperties().setHeader("x-delay", delayMillis);
                return message;
            });
        }
    }
}
  1. 消费延迟消息并发送通知:
@Component
public class AdExpirationConsumer {

    @Autowired
    private AdRepository adRepository;
    @Autowired
    private NotificationService notificationService;

    @RabbitListener(queues = RabbitMQConfig.DELAY_QUEUE)
    public void handleAdExpiration(Long adId) {
        Ad ad = adRepository.findById(adId).orElse(null);
        
        if (ad != null && !ad.isNotificationSent()) {
            notificationService.sendExpirationNotification(ad.getPublisher());
            ad.setNotificationSent(true);
            adRepository.save(ad);
        }
    }
}

通用注意事项:

  • 幂等性保障:一定要给Ad实体加个notificationSent(布尔类型)字段,每次执行通知逻辑前先判断这个字段,避免重复发送通知给用户
  • 异常处理:给发送通知的逻辑加异常捕获(比如邮件发送失败、短信接口报错),避免任务因为异常中断
  • 任务清理:如果用Quartz或延迟消息,当广告被删除或者activeUntil被修改时,一定要及时取消旧的任务/消息,避免无效执行

内容的提问来源于stack exchange,提问作者Stefan Radonjic

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.22 08:55:49