最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

SpringBoot整合 Quartz實(shí)現(xiàn)定時(shí)推送實(shí)戰(zhàn)指南

 更新時(shí)間:2025年12月10日 14:54:07   作者:喝汽水的貓^  
文章介紹了Spring Boot中使用Quartz動(dòng)態(tài)定時(shí)任務(wù)和任務(wù)持久化實(shí)現(xiàn)多條不確定結(jié)束時(shí)間并提前N分鐘推送的方案,本文結(jié)合實(shí)例代碼給大家介紹的非常詳細(xì),感興趣的朋友跟隨小編一起看看吧

前言

根據(jù)需求(多條不確定的結(jié)束時(shí)間 + 提前 N 分鐘推送),Spring Boot 中最優(yōu)方案是結(jié)合 Quartz 動(dòng)態(tài)定時(shí)任務(wù) + 任務(wù)持久化,支持動(dòng)態(tài)添加 / 刪除結(jié)束時(shí)間、自動(dòng)計(jì)算提前 N 分鐘的觸發(fā)點(diǎn),且能應(yīng)對(duì)服務(wù)重啟后任務(wù)不丟失的問(wèn)題。

一、Quartz 是什么?

Quartz 是一個(gè)功能強(qiáng)大、開(kāi)源的任務(wù)調(diào)度框架,用于實(shí)現(xiàn)定時(shí)、周期性或基于特定規(guī)則的任務(wù)執(zhí)行。簡(jiǎn)單來(lái)說(shuō),它就是 Java 生態(tài)中 “定時(shí)任務(wù)” 的標(biāo)準(zhǔn)解決方案,支持復(fù)雜的調(diào)度邏輯,廣泛應(yīng)用于后臺(tái)系統(tǒng)的定時(shí)任務(wù)場(chǎng)景(如定時(shí)備份、數(shù)據(jù)同步、定時(shí)推送、報(bào)表生成等)。

1、核心定位:解決什么問(wèn)題?

日常開(kāi)發(fā)中,我們常需要 “在特定時(shí)間執(zhí)行某段代碼”,比如:

  • 每天凌晨 2 點(diǎn)執(zhí)行數(shù)據(jù)庫(kù)備份;
  • 每隔 30 分鐘同步一次第三方數(shù)據(jù);
  • 每月 1 號(hào)生成上月報(bào)表;
  • 某個(gè)固定時(shí)間點(diǎn)觸發(fā)短信推送。

Java 原生提供了 Timer/TimerTask,但存在明顯缺陷(如單線程執(zhí)行、不支持復(fù)雜表達(dá)式、異常會(huì)導(dǎo)致線程終止),無(wú)法滿足企業(yè)級(jí)需求。而 Quartz 彌補(bǔ)了這些不足,提供了:

  • 復(fù)雜調(diào)度規(guī)則(支持 Cron 表達(dá)式,覆蓋幾乎所有時(shí)間場(chǎng)景);
  • 任務(wù)與調(diào)度解耦(任務(wù)邏輯和執(zhí)行規(guī)則分離);
  • 高可用(支持集群部署,避免單點(diǎn)故障);
  • 可持久化(任務(wù)和調(diào)度狀態(tài)可存儲(chǔ)到數(shù)據(jù)庫(kù),重啟后不丟失);
  • 并發(fā)控制(支持任務(wù)并發(fā)執(zhí)行或串行執(zhí)行)。

2、Quartz 核心組件

Quartz 的架構(gòu)設(shè)計(jì)清晰,核心組件分為 3 類(lèi),需理解它們的職責(zé)和關(guān)系:

組件作用
Job(任務(wù))具體要執(zhí)行的 “業(yè)務(wù)邏輯”,需實(shí)現(xiàn) org.quartz.Job 接口(重寫(xiě) execute() 方法)。
JobDetail(任務(wù)詳情)描述 Job 的元數(shù)據(jù)(如任務(wù)名稱、分組、是否持久化等),是 Quartz 內(nèi)部管理 Job 的載體(Job 本身是業(yè)務(wù)邏輯,JobDetail 是管理配置)。
Trigger(觸發(fā)器)定義 Job 的 “執(zhí)行規(guī)則”(何時(shí)執(zhí)行、執(zhí)行頻率),是觸發(fā) Job 執(zhí)行的 “開(kāi)關(guān)”。常見(jiàn)實(shí)現(xiàn):SimpleTrigger:簡(jiǎn)單規(guī)則(如延遲 5 秒執(zhí)行、每隔 10 秒執(zhí)行 3 次);CronTrigger:復(fù)雜規(guī)則(基于 Cron 表達(dá)式,如每天 02:00 執(zhí)行)。
Scheduler(調(diào)度器)Quartz 的核心 “大腦”,負(fù)責(zé)管理 JobDetail 和 Trigger,根據(jù) Trigger 規(guī)則觸發(fā) Job 執(zhí)行。需通過(guò) SchedulerFactory 創(chuàng)建,是任務(wù)調(diào)度的入口。

二、使用步驟

1. 引入依賴(Spring Boot + Quartz + 數(shù)據(jù)庫(kù))

需要 Quartz 核心依賴、Spring 整合包,以及數(shù)據(jù)庫(kù)依賴(用于任務(wù)持久化):

<!-- Spring Boot 整合 Quartz -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
<!-- 數(shù)據(jù)庫(kù)依賴(以 MySQL 為例,用于任務(wù)持久化) -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
    <groupId>com.mysql</groupId>
    <artifactId>mysql-connector-j</artifactId>
    <scope>runtime</scope>
</dependency>

2. 配置文件(application.yml)

配置數(shù)據(jù)庫(kù)連接和 Quartz 持久化(關(guān)鍵:禁用內(nèi)存存儲(chǔ),啟用數(shù)據(jù)庫(kù)存儲(chǔ)):

yaml
spring:
  # 數(shù)據(jù)庫(kù)配置(Quartz 任務(wù)將存儲(chǔ)到該庫(kù))
  datasource:
    url: jdbc:mysql://localhost:3306/quartz_db?useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver
  # JPA 配置(可選,用于存儲(chǔ)業(yè)務(wù)結(jié)束時(shí)間,方便管理)
  jpa:
    hibernate:
      ddl-auto: update
    show-sql: true
# Quartz 配置(核心:數(shù)據(jù)庫(kù)持久化)
quartz:
  job-store-type: jdbc # 任務(wù)存儲(chǔ)類(lèi)型:jdbc(數(shù)據(jù)庫(kù)),默認(rèn)是 memory(內(nèi)存)
  jdbc:
    initialize-schema: always # 自動(dòng)創(chuàng)建 Quartz 所需表(首次啟動(dòng)用 always,后續(xù)改為 never)
  properties:
    org:
      quartz:
        scheduler:
          instanceName: pushScheduler
          instanceId: AUTO # 集群模式下自動(dòng)分配實(shí)例ID
        jobStore:
          class: org.quartz.impl.jdbcjobstore.JobStoreTX
          driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate
          tablePrefix: QRTZ_ # Quartz 表前綴(自動(dòng)創(chuàng)建的表會(huì)帶此前綴)
          isClustered: false # 單節(jié)點(diǎn)用 false,集群部署改為 true
          clusterCheckinInterval: 20000
        threadPool:
          class: org.quartz.simpl.SimpleThreadPool
          threadCount: 10 # 線程池大小

3. 定義業(yè)務(wù)實(shí)體(存儲(chǔ)結(jié)束時(shí)間,可選但推薦)

用于記錄用戶添加的 “結(jié)束時(shí)間”,方便后續(xù)查詢、修改、刪除任務(wù)(關(guān)聯(lián) Quartz 任務(wù) ID):

import jakarta.persistence.*;
import lombok.Data;
import java.time.LocalDateTime;
@Data
@Entity
@Table(name = "push_task")
public class PushTask {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id; // 自增ID
    @Column(name = "end_time", nullable = false)
    private LocalDateTime endTime; // 原始結(jié)束時(shí)間(如明天3點(diǎn))
    @Column(name = "trigger_time", nullable = false)
    private LocalDateTime triggerTime; // 推送觸發(fā)時(shí)間(endTime -5分鐘)
    @Column(name = "quartz_job_id", unique = true, nullable = false)
    private String quartzJobId; // 關(guān)聯(lián) Quartz 的 JobDetail ID
    @Column(name = "task_desc")
    private String taskDesc; // 任務(wù)描述(如“訂單123超時(shí)提醒”)
    @Column(name = "status")
    private Integer status; // 狀態(tài):0-未觸發(fā) 1-已觸發(fā) 2-已取消
}

4. 實(shí)現(xiàn)消息推送 Job(Quartz 任務(wù)邏輯)

Quartz 任務(wù)的核心邏輯:觸發(fā)時(shí)執(zhí)行消息推送,同時(shí)更新業(yè)務(wù)任務(wù)狀態(tài):

import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
/**
 * 消息推送 Job(Quartz 任務(wù))
 */
@Component
public class PushMessageJob implements Job {
    private static final Logger logger = LoggerFactory.getLogger(PushMessageJob.class);
    @Autowired
    private PushTaskRepository pushTaskRepository; // 后續(xù)定義的 DAO 接口
    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        // 1. 從 Job 上下文獲取參數(shù)( quartzJobId + 任務(wù)描述 )
        String quartzJobId = context.getJobDetail().getKey().getName();
        String taskDesc = (String) context.getJobDetail().getJobDataMap().get("taskDesc");
        String triggerTimeDate = (String) context.getJobDetail().getJobDataMap().get("triggerTime");
        LocalDateTime triggerTime = null;
        if (null != triggerTimeDate) {
            triggerTime = LocalDateTime.parse(triggerTimeDate);
        }
        try {
            // 2. 核心:執(zhí)行消息推送(替換為你的實(shí)際邏輯:短信、APP推送、郵件等)
            doPush(taskDesc, triggerTime);
            // 3. 更新業(yè)務(wù)任務(wù)狀態(tài)為“已觸發(fā)”
            pushTaskRepository.updateStatusByQuartzJobId(1, quartzJobId);
            logger.info("消息推送成功!quartzJobId:{},任務(wù)描述:{},觸發(fā)時(shí)間:{}",
                    quartzJobId, taskDesc, triggerTime);
        } catch (Exception e) {
            logger.error("消息推送失敗!quartzJobId:{}", quartzJobId, e);
            // 可選:拋出異常觸發(fā) Quartz 重試(需配置重試策略)
            throw new JobExecutionException("推送失敗,觸發(fā)重試", e, true);
        }
    }
    /**
     * 實(shí)際推送邏輯(根據(jù)業(yè)務(wù)需求替換)
     */
    private void doPush(String taskDesc, LocalDateTime triggerTime) {
        // 示例1:打印日志(實(shí)際場(chǎng)景替換為第三方推送接口)
        System.out.printf("[推送通知] 任務(wù)描述:%s,觸發(fā)時(shí)間:%s,內(nèi)容:即將到達(dá)結(jié)束時(shí)間(提前5分鐘提醒)%n",
                taskDesc, triggerTime);
        // 示例2:調(diào)用 HTTP 推送接口(如極光推送)
        // RestTemplate restTemplate = new RestTemplate();
        // String pushUrl = "https://api.jpush.cn/v3/push";
        // PushRequest request = new PushRequest();
        // request.setContent("即將到達(dá)結(jié)束時(shí)間:" + taskDesc);
        // restTemplate.postForObject(pushUrl, request, String.class);
    }
}

5. 定義 DAO 接口(操作業(yè)務(wù)任務(wù)表)

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
public interface PushTaskRepository extends JpaRepository<PushTask, Long> {
    // 根據(jù) QuartzJobId 查詢?nèi)蝿?wù)
    PushTask findByQuartzJobId(String quartzJobId);
    // 更新任務(wù)狀態(tài)
    @Modifying
    @Transactional
    @Query("update PushTask t set t.status = :status where t.quartzJobId = :quartzJobId")
    int updateStatusByQuartzJobId(Integer status, String quartzJobId);
    // 根據(jù)狀態(tài)和觸發(fā)時(shí)間查詢未執(zhí)行的任務(wù)(服務(wù)重啟后恢復(fù)任務(wù)用)
    @Query("select t from PushTask t where t.status = 0 and t.triggerTime > :now")
    List<PushTask> findUnTriggeredTasks(LocalDateTime now);
}

6. 動(dòng)態(tài)定時(shí)任務(wù)服務(wù)(核心:添加 / 刪除 / 恢復(fù)任務(wù))

封裝 Quartz API,實(shí)現(xiàn)動(dòng)態(tài)添加任務(wù)(自動(dòng)計(jì)算提前 5 分鐘)、刪除任務(wù)、服務(wù)重啟后恢復(fù)未觸發(fā)任務(wù):

import org.quartz.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.util.List;
import java.util.UUID;
@Service
public class DynamicQuartzService {
    @Autowired
    private Scheduler scheduler;
    @Autowired
    private PushTaskRepository pushTaskRepository;
    // 任務(wù)組名(統(tǒng)一分組,方便管理)
    private static final String JOB_GROUP = "PUSH_JOB_GROUP";
    private static final String TRIGGER_GROUP = "PUSH_TRIGGER_GROUP";
    /**
     * 核心方法:添加推送任務(wù)(自動(dòng)計(jì)算提前5分鐘觸發(fā))
     *
     * @params endTime   原始結(jié)束時(shí)間(如明天3點(diǎn))
     * @params triggerTime   觸發(fā)時(shí)間(如明天2點(diǎn))
     * @params taskDesc  任務(wù)描述(如“訂單123超時(shí)提醒”)
     * @return 保存的業(yè)務(wù)任務(wù)
     */
       public PushTask addPushTask(LocalDateTime endTime, LocalDateTime triggerTime,String taskDesc) throws SchedulerException {
        PaAssert.notNull(endTime, "結(jié)束時(shí)間不能為空!");
        PaAssert.notNull(triggerTime, "觸發(fā)時(shí)間不能為空!");
        // 校驗(yàn)結(jié)束時(shí)間
        LocalDateTime now = LocalDateTime.now();
        if (endTime.isBefore(now) || endTime.isEqual(now)) {
            throw new IllegalArgumentException("結(jié)束時(shí)間必須晚于當(dāng)前時(shí)間!");
        }
        if (triggerTime.isBefore(now) || triggerTime.isEqual(now)) {
            throw new IllegalArgumentException("觸發(fā)時(shí)間必須晚于當(dāng)前時(shí)間!");
        }
        // 3. 生成唯一的 QuartzJobId(避免重復(fù))
        String quartzJobId = "PUSH_JOB_" + UUID.randomUUID().toString().replace("-", "");
        // 4. 構(gòu)建 Quartz JobDetail(綁定 PushMessageJob)
        JobDetail jobDetail = JobBuilder.newJob(PushMessageJob.class)
                .withIdentity(quartzJobId, JOB_GROUP) // 任務(wù)ID:quartzJobId,組名:JOB_GROUP
                .usingJobData("taskDesc", taskDesc)
                .usingJobData("triggerTime", String.valueOf(triggerTime))
                .storeDurably() // 持久化(無(wú)觸發(fā)器時(shí)也保留)
                .build();
        // 5. 構(gòu)建 CronTrigger(根據(jù) triggerTime 生成 Cron 表達(dá)式)
        String cronExpression = buildCronExpression(triggerTime);
        CronTrigger trigger = TriggerBuilder.newTrigger()
                .withIdentity(quartzJobId, TRIGGER_GROUP)
                .forJob(jobDetail)
                .withSchedule(CronScheduleBuilder.cronSchedule(cronExpression)
                        .withMisfireHandlingInstructionFireAndProceed()) // 錯(cuò)過(guò)觸發(fā)時(shí)立即執(zhí)行
                .startNow()
                // 關(guān)鍵:觸發(fā)時(shí)間+1分鐘,確保僅執(zhí)行一次(避免循環(huán)或重復(fù)觸發(fā))
                .endAt(Date.from(triggerTime.plusMinutes(1).atZone(ZoneId.systemDefault()).toInstant()))
                .build();
        // 6. 注冊(cè)任務(wù)到 Quartz 調(diào)度器
        scheduler.scheduleJob(jobDetail, trigger);
        // 7. 保存業(yè)務(wù)任務(wù)到數(shù)據(jù)庫(kù)(關(guān)聯(lián) QuartzJobId)
        PushTask pushTask = new PushTask();
        pushTask.setEndTime(endTime);
        pushTask.setTriggerTime(triggerTime);
        pushTask.setQuartzJobId(quartzJobId);
        pushTask.setTaskDesc(taskDesc);
        pushTask.setStatus(0); // 0-未觸發(fā)
        return pushTaskRepository.save(pushTask);
    }
    /**
     * 刪除推送任務(wù)(同時(shí)刪除 Quartz 任務(wù)和業(yè)務(wù)任務(wù))
     * @param quartzJobId 任務(wù)ID(添加任務(wù)時(shí)返回的 quartzJobId)
     */
    public void deletePushTask(String quartzJobId) throws SchedulerException {
        // 1. 停止并刪除 Quartz 任務(wù)
        JobKey jobKey = JobKey.jobKey(quartzJobId, JOB_GROUP);
        TriggerKey triggerKey = TriggerKey.triggerKey(quartzJobId, TRIGGER_GROUP);
        scheduler.pauseTrigger(triggerKey); // 暫停觸發(fā)器
        scheduler.unscheduleJob(triggerKey); // 移除觸發(fā)器
        scheduler.deleteJob(jobKey); // 刪除任務(wù)
        // 2. 更新業(yè)務(wù)任務(wù)狀態(tài)為“已取消”
        pushTaskRepository.updateStatusByQuartzJobId(2, quartzJobId);
    }
    /**
     * 服務(wù)重啟后恢復(fù)未觸發(fā)的任務(wù)(關(guān)鍵:避免任務(wù)丟失)
     */
    public void restoreUnTriggeredTasks() throws SchedulerException {
        // 1. 查詢數(shù)據(jù)庫(kù)中“未觸發(fā)”且“觸發(fā)時(shí)間未到”的任務(wù)
        List<PushTask> unTriggeredTasks = pushTaskRepository.findUnTriggeredTasks(LocalDateTime.now());
        if (unTriggeredTasks.isEmpty()) {
            return;
        }
        // 2. 重新注冊(cè)這些任務(wù)到 Quartz
        for (PushTask task : unTriggeredTasks) {
            String quartzJobId = task.getQuartzJobId();
            LocalDateTime triggerTime = task.getTriggerTime();
            String taskDesc = task.getTaskDesc();
            // 構(gòu)建 JobDetail
            JobDetail jobDetail = JobBuilder.newJob(PushMessageJob.class)
                    .withIdentity(quartzJobId, JOB_GROUP)
                    .usingJobData("taskDesc", taskDesc)
                    .usingJobData("triggerTime", String.valueOf(triggerTime))
                    .storeDurably()
                    .build();
            // 構(gòu)建 Trigger
            String cronExpression = buildCronExpression(triggerTime);
            CronTrigger trigger = TriggerBuilder.newTrigger()
                    .withIdentity(quartzJobId, TRIGGER_GROUP)
                    .forJob(jobDetail)
                    .withSchedule(CronScheduleBuilder.cronSchedule(cronExpression))
                    .startNow()
                    .build();
            // 注冊(cè)任務(wù)
            scheduler.scheduleJob(jobDetail, trigger);
        }
        System.out.printf("恢復(fù)未觸發(fā)的推送任務(wù)數(shù)量:%d%n", unTriggeredTasks.size());
    }
    /**
     * 工具方法:將 LocalDateTime 轉(zhuǎn)換為 Cron 表達(dá)式(精準(zhǔn)到秒)
     * Cron 格式:秒 分 時(shí) 日 月 周 年(年可選)
     * 例:2025-11-01 02:55:00 → 0 55 2 1 11 ? 2025
     */
    private String buildCronExpression(LocalDateTime triggerTime) {
        int second = triggerTime.getSecond(); // 秒(0)
        int minute = triggerTime.getMinute(); // 分(0)
        int hour = triggerTime.getHour();     // 時(shí)(16)
        int day = triggerTime.getDayOfMonth();// 日(21)
        int month = triggerTime.getMonthValue();// 月(11,LocalDateTime 直接是 1-12,無(wú)需+1)
        int year = triggerTime.getYear();     // 年(2025)
        // Cron 格式:秒 分 時(shí) 日 月 ? 年 → 周字段用 ?,避免與日沖突
        return String.format("%d %d %d %d %d ? %d",
                second, minute, hour, day, month, year);
    }
}

7. 啟動(dòng)時(shí)恢復(fù)任務(wù)(監(jiān)聽(tīng)服務(wù)啟動(dòng))

服務(wù)重啟后,自動(dòng)恢復(fù)數(shù)據(jù)庫(kù)中 “未觸發(fā)” 的任務(wù),避免任務(wù)丟失:

import org.quartz.SchedulerException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;
@Component
public class TaskRestoreRunner implements ApplicationRunner {
    @Autowired
    private DynamicQuartzService dynamicQuartzService;
    @Override
    public void run(ApplicationArguments args) throws SchedulerException {
        // 服務(wù)啟動(dòng)后,恢復(fù)未觸發(fā)的任務(wù)
        dynamicQuartzService.restoreUnTriggeredTasks();
    }
}

8. 測(cè)試接口(對(duì)外提供添加 / 刪除任務(wù)的入口)

通過(guò) HTTP 接口測(cè)試動(dòng)態(tài)添加、刪除任務(wù):

import org.quartz.SchedulerException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.web.bind.annotation.*;
import java.time.LocalDateTime;
@RestController
@RequestMapping("/push")
public class PushTaskController {
    @Autowired
    private DynamicQuartzService dynamicQuartzService;
    /**
     * 添加推送任務(wù)
     * @param endTime 結(jié)束時(shí)間(格式:yyyy-MM-dd HH:mm:ss,如 2025-11-01 03:00:00)
     * @param taskDesc 任務(wù)描述
     */
    @PostMapping("/add")
    public PushTask addTask(
            @RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime endTime,
            @RequestParam String taskDesc) throws SchedulerException {
        return dynamicQuartzService.addPushTask(endTime, taskDesc);
    }
    /**
     * 刪除推送任務(wù)
     * @param quartzJobId 任務(wù)ID(添加任務(wù)時(shí)返回的 quartzJobId)
     */
    @PostMapping("/delete")
    public String deleteTask(@RequestParam String quartzJobId) throws SchedulerException {
        dynamicQuartzService.deletePushTask(quartzJobId);
        return "任務(wù)刪除成功!quartzJobId:" + quartzJobId;
    }
}

三、核心功能測(cè)試

1. 添加任務(wù)測(cè)試

發(fā)送 POST 請(qǐng)求:

plaintext http://localhost:8080/push/add?endTime=2025-11-01
03:00:00&taskDesc=訂單123超時(shí)提醒

  • 后臺(tái)會(huì)自動(dòng)計(jì)算觸發(fā)時(shí)間:2025-11-01 02:55:00;
  • 數(shù)據(jù)庫(kù) push_task 表會(huì)新增一條記錄,狀態(tài)為 0(未觸發(fā));
  • Quartz 會(huì)創(chuàng)建對(duì)應(yīng)的 Job 和 Trigger,到 02:55 自動(dòng)觸發(fā)推送。

2. 觸發(fā)效果

到觸發(fā)時(shí)間后,控制臺(tái)會(huì)輸出:

[推送通知] 任務(wù)描述:訂單123超時(shí)提醒,觸發(fā)時(shí)間:2025-11-01T02:55,內(nèi)容:即將到達(dá)結(jié)束時(shí)間(提前5分鐘提醒)

同時(shí) push_task 表中該任務(wù)的狀態(tài)會(huì)更新為 1(已觸發(fā))。

3. 刪除任務(wù)測(cè)試

發(fā)送 POST 請(qǐng)求:

http://localhost:8080/push/delete?quartzJobId=PUSH_JOB_xxx(添加任務(wù)時(shí)返回的
quartzJobId)

  • Quartz 會(huì)刪除對(duì)應(yīng)的 Job 和 Trigger;
  • 數(shù)據(jù)庫(kù)中任務(wù)狀態(tài)更新為 2(已取消)。

四、關(guān)鍵特性說(shuō)明

1. 動(dòng)態(tài)性

  • 支持任意多條結(jié)束時(shí)間:調(diào)用 addPushTask 方法可添加多個(gè)任務(wù),每個(gè)任務(wù)獨(dú)立觸發(fā);
  • 結(jié)束時(shí)間不確定:無(wú)需提前配置 Cron 表達(dá)式,傳入 LocalDateTime 即可自動(dòng)生成觸發(fā)規(guī)則。

2. 可靠性

  • 持久化:任務(wù)信息存儲(chǔ)在數(shù)據(jù)庫(kù),服務(wù)重啟后自動(dòng)恢復(fù)未觸發(fā)的任務(wù);
  • 失敗重試:推送失敗時(shí)拋出異常,Quartz 會(huì)根據(jù)配置的策略重試(默認(rèn)重試 3 次);
  • 集群支持:修改 application.yml 中 quartz.properties.org.quartz.jobStore.isClustered=true,即可支持集群部署(避免重復(fù)觸發(fā))。

3. 靈活性

  • 提前時(shí)間可配置:將 minusMinutes(5) 改為參數(shù)(如 minusMinutes(提前分鐘數(shù))),支- - 持動(dòng)態(tài)調(diào)整提前提醒時(shí)間;
  • 推送邏輯可擴(kuò)展:修改 doPush 方法,整合短信、APP 推送(極光 / 個(gè)推)、郵件等任意渠道。

4. 時(shí)間校驗(yàn)

  • 避免添加已過(guò)期的任務(wù)(結(jié)束時(shí)間早于當(dāng)前時(shí)間);
  • 避免提前 5 分鐘后仍過(guò)期的任務(wù)(如結(jié)束時(shí)間為當(dāng)前時(shí)間 + 3 分鐘,提前 5 分鐘則已過(guò)期)。

五、生產(chǎn)環(huán)境優(yōu)化建議

1.任務(wù)監(jiān)控:通過(guò) Quartz 提供的 API 監(jiān)控任務(wù)狀態(tài)(如查詢所有任務(wù)、觸發(fā)次數(shù)、失敗次數(shù)),或集成 Prometheus + Grafana 可視化監(jiān)控;

2.過(guò)期任務(wù)清理:定時(shí)清理數(shù)據(jù)庫(kù)中 “已觸發(fā)” 或 “已取消” 的任務(wù)(避免表數(shù)據(jù)過(guò)大);

3.推送異步化:如果推送邏輯耗時(shí)較長(zhǎng)(如調(diào)用第三方接口),可在 doPush 中發(fā)送 MQ 消息,由消費(fèi)者異步處理推送(解耦定時(shí)任務(wù)和推送邏輯);

4.日志增強(qiáng):記錄推送結(jié)果(成功 / 失敗)到日志文件或數(shù)據(jù)庫(kù),方便問(wèn)題排查;

5.權(quán)限控制:對(duì) /push/add 和 /push/delete 接口添加權(quán)限校驗(yàn)(如 Token 驗(yàn)證),避免惡意操作。

總結(jié)

本方案通過(guò) Quartz 動(dòng)態(tài)任務(wù) + 數(shù)據(jù)庫(kù)持久化,完美滿足 “多條不確定結(jié)束時(shí)間 + 提前 5 分鐘推送” 的需求,核心優(yōu)勢(shì):

  • 動(dòng)態(tài)靈活:支持任意結(jié)束時(shí)間,無(wú)需提前配置;
  • 可靠穩(wěn)定:任務(wù)持久化、服務(wù)重啟恢復(fù)、失敗重試;
  • 易于擴(kuò)展:支持多推送渠道、集群部署、監(jiān)控告警。

如果需要輕量級(jí)方案(無(wú)需持久化,服務(wù)重啟后任務(wù)可丟失),也可使用 Spring Scheduler + 內(nèi)存緩存,但生產(chǎn)環(huán)境建議優(yōu)先選擇本方案(可靠性更高)

到此這篇關(guān)于SpringBoot整合 Quartz實(shí)現(xiàn)定時(shí)推送實(shí)戰(zhàn)指南的文章就介紹到這了,更多相關(guān)SpringBoot Quartz定時(shí)推送內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Java中的RoundingMode舍入模式詳解

    Java中的RoundingMode舍入模式詳解

    Java中的RoundingMode枚舉類(lèi)用于控制高精度數(shù)值處理時(shí)的舍入行為,提供多種舍入模式,本文給大家介紹Java的RoundingMode舍入模式,感興趣的朋友跟隨小編一起看看吧
    2026-01-01
  • Java類(lèi)的加載連接和初始化實(shí)例分析

    Java類(lèi)的加載連接和初始化實(shí)例分析

    這篇文章主要介紹了Java類(lèi)的加載連接和初始化,結(jié)合具體實(shí)例形式分析了java類(lèi)的加載、連接、初始化相關(guān)原理與實(shí)現(xiàn)技巧,需要的朋友可以參考下
    2019-07-07
  • Java 8中default方法能做什么?不能做什么?

    Java 8中default方法能做什么?不能做什么?

    這篇文章主要給大家介紹了關(guān)于Java 8中default方法能做什么?不能做什么?文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧。
    2018-04-04
  • springboot使用@ConfigurationProperties實(shí)現(xiàn)自動(dòng)綁定配置參數(shù)屬性

    springboot使用@ConfigurationProperties實(shí)現(xiàn)自動(dòng)綁定配置參數(shù)屬性

    這篇文章主要介紹了springboot使用@ConfigurationProperties實(shí)現(xiàn)自動(dòng)綁定配置參數(shù)屬性,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2025-05-05
  • 用Java驗(yàn)證pdf文件的電子章簽名

    用Java驗(yàn)證pdf文件的電子章簽名

    這篇文章主要介紹了如何用Java驗(yàn)證pdf文件的電子章簽名,幫助大家更好的理解和使用Java,感興趣的朋友可以了解下
    2020-12-12
  • 詳解Java子線程異常時(shí)主線程事務(wù)如何回滾

    詳解Java子線程異常時(shí)主線程事務(wù)如何回滾

    如果主線程向線程池提交了一個(gè)任務(wù),如果執(zhí)行這個(gè)任務(wù)過(guò)程中發(fā)生了異常,如何讓主線程捕獲到該異常并且進(jìn)行事務(wù)的回滾?本篇文章帶給你答案
    2022-03-03
  • mybatis-plus實(shí)現(xiàn)打印完整sql語(yǔ)句

    mybatis-plus實(shí)現(xiàn)打印完整sql語(yǔ)句

    這篇文章主要介紹了mybatis-plus實(shí)現(xiàn)打印完整sql語(yǔ)句方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2024-07-07
  • Java線程中的interrupt方法解讀

    Java線程中的interrupt方法解讀

    這篇文章主要介紹了Java線程中的interrupt方法解讀,Java中的interrupt是一種線程間通信的機(jī)制,用于請(qǐng)求中斷線程的執(zhí)行。當(dāng)一個(gè)線程調(diào)用另一個(gè)線程的interrupt()方法時(shí),被調(diào)用線程會(huì)收到一個(gè)中斷信號(hào),可以根據(jù)需要做出相應(yīng)的處理,需要的朋友可以參考下
    2023-10-10
  • java的jps命令使用詳解

    java的jps命令使用詳解

    這篇文章介紹了java的jps命令使用詳解,對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2021-12-12
  • Java設(shè)計(jì)模式之策略模式示例詳解

    Java設(shè)計(jì)模式之策略模式示例詳解

    這篇文章主要為大家詳細(xì)介紹了Java的策略模式,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來(lái)幫助
    2022-03-03

最新評(píng)論

沾化县| 陈巴尔虎旗| 孝昌县| 威远县| 赣榆县| 乌拉特中旗| 桐柏县| 泾川县| 潜山县| 资源县| 出国| 平度市| 苏尼特右旗| 庄浪县| 阳曲县| 望江县| 南召县| 龙门县| 易门县| 同仁县| 清苑县| 闸北区| 平和县| 武宣县| 洛川县| 玉门市| 榆林市| 彭山县| 蕉岭县| 新郑市| 丹江口市| 浦县| 汕尾市| 宜丰县| 雅安市| 鹿邑县| 太湖县| 三都| 澄江县| 邓州市| 二连浩特市|