微服務(wù)開發(fā)系列 第十一篇:XXL-JOB

總概

A、技術(shù)棧
  • 開發(fā)語言:Java 1.8
  • 數(shù)據(jù)庫:MySQL林艘、Redis、MongoDB混坞、Elasticsearch
  • 微服務(wù)框架:Spring Cloud Alibaba
  • 微服務(wù)網(wǎng)關(guān):Spring Cloud Gateway
  • 服務(wù)注冊和配置中心:Nacos
  • 分布式事務(wù):Seata
  • 鏈路追蹤框架:Sleuth
  • 服務(wù)降級與熔斷:Sentinel
  • ORM框架:MyBatis-Plus
  • 分布式任務(wù)調(diào)度平臺:XXL-JOB
  • 消息中間件:RocketMQ
  • 分布式鎖:Redisson
  • 權(quán)限:OAuth2
  • DevOps:Jenkins狐援、Docker、K8S
B究孕、源碼地址

alanchenyan/ac-mall2-cloud

C啥酱、本節(jié)實現(xiàn)目標
  • 搭建xxl-job環(huán)境
  • xxl-job-admin平臺創(chuàng)建定時任務(wù)
  • 動態(tài)創(chuàng)建定時任務(wù),實現(xiàn)動態(tài)創(chuàng)建15分鐘未支付自動關(guān)閉訂單的定時任務(wù)
D厨诸、系列

一镶殷、部署xxl-job

1.1 下載xxl-job源碼

下載地址:https://github.com/xuxueli/xxl-job

xxl-job源碼
1.2 初始化“調(diào)度數(shù)據(jù)庫”

調(diào)度數(shù)據(jù)庫初始化SQL腳本” 位置為:/xxl-job/doc/db/tables_xxl_job.sql

調(diào)度中心支持集群部署微酬,集群情況下各節(jié)點務(wù)必連接同一個MySQL實例绘趋。如果MySQL做主從颤陶,調(diào)度中心集群節(jié)點務(wù)必強制走主庫。

1.3 部署調(diào)度中心 xxl-job-admin
1.3.1 xxl-job-admin項目

xxl-job源碼里有3個項目:xxl-job-admin埋心、xxl-job-core指郁、xxl-job-executor-samples忙上,

  • xxl-job-admin:調(diào)度中心
  • xxl-job-core:公共依賴
  • xxl-job-executor-samples:執(zhí)行器Sample示例(選擇合適的版本執(zhí)行器拷呆,可直接使用,也可以參考其并將現(xiàn)有項目改造成執(zhí)行器)

我們部署調(diào)度中心需要用到xxl-job-admin

1.3.2 啟動xxl-job-admin

xxl-job-admin調(diào)度中心項目用IDEA打開疫粥,配置文件地址:/xxl-job/xxl-job-admin/src/main/resources/application.properties

我們修改三個地方:

  • 端口:端口默認是8080茬斧,這里我們將端口改成8081
  • 數(shù)據(jù)庫配置:修改數(shù)據(jù)庫地址和賬號密碼
  • accessToken:默認是default_token

修改完后啟動xxl-job-admin

修改端口
修改數(shù)據(jù)庫地址和賬號密碼
修改accessToken
1.3.3 訪問xxl-job-admin

調(diào)度中心訪問地址:http://localhost:8081/xxl-job-admin ,該地址執(zhí)行器將會使用到梗逮,作為回調(diào)地址项秉。

默認登錄賬號 “admin/123456”, 登錄后運行界面如下圖所示。

xxl-job-admin

二慷彤、配置部署“執(zhí)行器項目”

執(zhí)行器項目:xxl-job-executor-sample-springboot娄蔼,可直接使用,也可以參考其并將現(xiàn)有項目改造成執(zhí)行器底哗。

執(zhí)行器項目作用:負責接收“調(diào)度中心”的調(diào)度并執(zhí)行岁诉;可直接部署執(zhí)行器,也可以將執(zhí)行器集成到現(xiàn)有業(yè)務(wù)項目中跋选。

我們不用xxl-job-executor-sample-springboot涕癣,而是直接用mall-order項目來做為執(zhí)行器項目。

2.1 新增執(zhí)行管理器

先在xxl-job-admin上新增一個執(zhí)行管理器:executor-order

新增執(zhí)行管理器
新增執(zhí)行管理器
2.2 maven依賴

在mall-pom項目的pom.xml里引入xxl-job-core的maven依賴

<dependency>
    <groupId>com.xuxueli</groupId>
    <artifactId>xxl-job-core</artifactId>
    <version>${xxljob.version}</version>
</dependency>
xxl-job-core
2.3 執(zhí)行器組件配置

執(zhí)行器組件前标,配置文件地址:/xxl-job/xxl-job-executor-samples/xxl-job-executor-sample-springboot/src/main/java/com/xxl/job/executor/core/config/XxlJobConfig.java

將該代碼復(fù)制到mall-order項目

XxlJobConfig
2.4 配置xxl-job連接信息

修改mall-order服務(wù)里的bootstrap-dev.yml配置信息

server:
  port: 7030

spring:
  application:
    name: mall-order

  cloud:
    nacos:
      config:
        server-addr: 127.0.0.1:8848
        namespace: dev_id
        file-extension: yml
        shared-configs:
          - data-id: common.yml
            group: DEFAULT_GROUP
            refresh: true
      discovery:
        namespace: dev_id

swagger:
  enabled: true
  title: 訂單服務(wù)
  basePackage: com.ac.order.controller
  version: 1.0
  description: 訂單服務(wù)相關(guān)接口

xxl:
  job:
    # 執(zhí)行器通訊TOKEN [選填]:非空時啟用坠韩;
    accessToken: 123456
    admin:
      # 調(diào)度中心部署跟地址 [選填]:如調(diào)度中心集群部署存在多個地址則用逗號分隔。執(zhí)行器將會使用該地址進行"執(zhí)行器心跳注冊"和"任務(wù)結(jié)果回調(diào)"炼列;為空則關(guān)閉自動注冊只搁;
      addresses: http://127.0.0.1:8081/xxl-job-admin
    executor:
      # 執(zhí)行器AppName [選填]:執(zhí)行器心跳注冊分組依據(jù);為空則關(guān)閉自動注冊
      app-name: executor-order
      # 執(zhí)行器注冊 [選填]:優(yōu)先使用該配置作為注冊地址俭尖,為空時使用內(nèi)嵌服務(wù) ”IP:PORT“ 作為注冊地址氢惋。從而更靈活的支持容器類型執(zhí)行器動態(tài)IP和動態(tài)映射端口問題。
      address: ''
      # 執(zhí)行器IP [選填]:默認為空表示自動獲取IP目溉,多網(wǎng)卡時可手動設(shè)置指定IP明肮,該IP不會綁定Host僅作為通訊實用;地址信息用于 "執(zhí)行器注冊" 和 "調(diào)度中心請求并觸發(fā)任務(wù)"缭付;
      ip: ''
      # 執(zhí)行器端口號 [選填]:小于等于0則自動獲仁凉馈;默認端口為9999陷猫,單機部署多個執(zhí)行器時秫舌,注意要配置不同執(zhí)行器端口的妖;
      port: -1
      # 執(zhí)行器運行日志文件存儲磁盤路徑 [選填] :需要對該路徑擁有讀寫權(quán)限;為空則使用默認路徑足陨;
      log-path: /data/logs/task-log
      # 執(zhí)行器日志保存天數(shù) [選填] :值大于3時生效嫂粟,啟用執(zhí)行器Log文件定期清理功能,否則不生效墨缘;
      log-retention-days: -1
修改配置
2.5 啟動mall-order服務(wù)

啟動mall-order服務(wù)星虹,mall-order服務(wù)會自動注冊到executor-order執(zhí)行器下,此時镊讼,mall-order就是一個執(zhí)行器項目宽涌。

executor-order執(zhí)行器

三、xxl-job-admin平臺創(chuàng)建定時任務(wù)

BEAN模式(方法形式)蝶棋,xxl-job-admin平臺創(chuàng)建定時任務(wù)

3.1 新建定時任務(wù)執(zhí)行方法類
/**
 * @author Alan Chen
 * @description xxl-job-admin平臺創(chuàng)建定時任務(wù)
 * @date 2023/05/17
 */
@Slf4j
@Component
public class TaskByAdminCreateJob {

    @XxlJob(value = XXLJobHandlerConstant.TASK_BY_ADMIN_CREATE)
    public void doJob() {
        try {
            // 獲取任務(wù)ID
            long jobId = XxlJobHelper.getJobId();
            log.info("TaskByTimeJob doJob,jobId={},param={}", jobId, XxlJobHelper.getJobParam());

            // 獲取任務(wù)參數(shù)
            String[] params = StrUtil.splitToArray(XxlJobHelper.getJobParam(), ',');
            if (params.length <= 1) {
                String error = StrUtil.format("TaskByTimeJob.doJob,失敗, 原因: 參數(shù)缺失, 任務(wù)ID: {}", jobId);
                log.info(error);
                XxlJobHelper.handleFail(error);
                return;
            }

            // 業(yè)務(wù)邏輯
            String memberId = params[0];
            String memberName = params[1];
            String logInfo = StrUtil.format("TaskByTimeJob.doJob,成功,memberId={},memberName={}", memberId, memberName);
            log.info(logInfo);
            XxlJobHelper.handleSuccess(logInfo);
        } catch (Exception e) {
            String error = StrUtil.format("TaskByTimeJob.doJob,失敗, msg={}", e.getMessage());
            log.info(error);
            XxlJobHelper.handleFail(error);
        }
    }
}
package com.ac.common.constant;

public class XXLJobHandlerConstant {

    /**
     * xxl-job-admin平臺創(chuàng)建定時任務(wù)
     */
    public static final String TASK_BY_ADMIN_CREATE = "TASK_BY_ADMIN_CREATE";
}
定時執(zhí)行任務(wù)
3.2 新建任務(wù)管理
新建任務(wù)管理
設(shè)置執(zhí)行時間

配置JobHandler卸亮,和TaskByAdminCreateJob里配置的@XxlJob保持一致

配置

任務(wù)新建完后,需要手動啟動


啟動
3.3 執(zhí)行任務(wù)

啟動狀態(tài)下的任務(wù)玩裙,可以立即執(zhí)行一次


執(zhí)行一次
執(zhí)行參數(shù)
后臺打印結(jié)果

四兼贸、代碼動態(tài)創(chuàng)建定時任務(wù)

4.1 背景說明

xxl-job-admin平臺手動創(chuàng)建定時任務(wù),使用起來雖然方便吃溅,可以有時候溶诞,我們就是需要在代碼中動態(tài)創(chuàng)建一個定時任務(wù),而不是到頁面上進行配置罕偎。比如用戶下單后很澄,我們需要動態(tài)創(chuàng)建一個15分鐘未支付自動關(guān)閉訂單的定時任務(wù)。

4.2 xxljob接口梳理

我們先到github上拉一份xxl-job的源碼下來颜及,結(jié)合著文檔和代碼甩苛,先梳理一下各個模塊都是干什么的:

  • xxl-job-admin:任務(wù)調(diào)度中心,啟動后就可以訪問管理頁面俏站,進行執(zhí)行器和任務(wù)的注冊讯蒲、以及任務(wù)調(diào)用等功能了

  • xxl-job-core:公共依賴,項目中使用到xxl-job時要引入的依賴包

  • xxl-job-executor-samples:執(zhí)行示例肄扎,分別包含了springboot版本和不使用框架的版本

為了弄清楚注冊和查詢executor和jobHandler調(diào)用的是哪些接口墨林,我們先從頁面上去抓一個請求看看:

執(zhí)行管理器接口

好了,這樣就能定位到xxl-job-admin模塊中xxl-job-admin/jobgroup/pageList這個接口犯祠。

按照這個思路旭等,可以找到下面這幾個關(guān)鍵接口:

/jobgroup/pageList:執(zhí)行器列表的條件查詢
/jobgroup/save:添加執(zhí)行器
/jobinfo/pageList:任務(wù)列表的條件查詢
/jobinfo/add:添加任務(wù)

但是如果直接調(diào)用這些接口,那么就會發(fā)現(xiàn)它會跳轉(zhuǎn)到xxl-job-admin的的登錄頁面衡载。

其實想想也明白搔耕,出于安全性考慮,調(diào)度中心的接口也不可能允許裸調(diào)的痰娱。那么再回頭看一下剛才頁面上的請求就會發(fā)現(xiàn)弃榨,它在Headers中添加了一條名為XXL_JOB_LOGIN_IDENTITY的cookie:

cookie

至于這條cookie菩收,則是在通過用戶名和密碼調(diào)用調(diào)度中心的/login接口時返回的,在返回的response可以直接拿到鲸睛。只要保存下來娜饵,并在之后每次請求時攜帶,就能夠正常訪問其他接口了官辈。

到這里箱舞,我們需要的5個接口就基本準備齊了,接下來準備開始正式的改造工作钧萍。

4.3 動態(tài)創(chuàng)建定時任務(wù)實現(xiàn)
4.3.1 XxlJobInfo和XxlJobGroup類

在調(diào)用調(diào)度中心的接口前褐缠,先把xxl-job-admin模塊中的XxlJobInfo和XxlJobGroup這兩個類拿到我們的mall-common項目中政鼠,用于接收接口調(diào)用的結(jié)果风瘦。

XxlJobGroup
4.3.2 登錄接口

在調(diào)用業(yè)務(wù)接口前,需要通過登錄接口獲取cookie公般,并在獲取到cookie后万搔,緩存到本地的Map中。

   
    private final Map<String, String> loginCookie = new HashMap<>();

    private final String adminAddresses = "http://127.0.0.1:8081/xxl-job-admin";
    private final String username = "admin";
    private final String password = "123456";

    public String login() {
        String url = adminAddresses + "/login";
        HttpResponse response = HttpRequest.post(url)
                .form("userName", username)
                .form("password", password)
                .execute();
        List<HttpCookie> cookies = response.getCookies();
        Optional<HttpCookie> cookieOpt = cookies.stream()
                .filter(cookie -> cookie.getName().equals("XXL_JOB_LOGIN_IDENTITY")).findFirst();
        if (!cookieOpt.isPresent())
            throw new RuntimeException("get xxl-job cookie error!");

        String value = cookieOpt.get().getValue();
        loginCookie.put("XXL_JOB_LOGIN_IDENTITY", value);

        log.info("XxlJobComponent.login.token={}", value);
        return value;
    }
4.3.3 獲取cookie

其他接口在調(diào)用時官帘,直接從緩存中獲取cookie瞬雹,如果緩存中不存在則調(diào)用/login接口,為了避免這一過程失敗刽虹,允許最多重試3次酗捌。

    public String getCookie() {
        for (int i = 0; i < 3; i++) {
            String cookieStr = loginCookie.get("XXL_JOB_LOGIN_IDENTITY");
            if (cookieStr != null) {
                return "XXL_JOB_LOGIN_IDENTITY=" + cookieStr;
            }
            login();
        }
        throw new RuntimeException("get xxl-job cookie error!");
    }
4.3.4 通過appName獲取執(zhí)行管理器ID
/**
     * 通過appName獲取執(zhí)行管理器ID
     *
     * @param appName
     * @return
     */
    private int getJobGroupId(String appName) {
        List<XxlJobGroup> jobGroupList = listJobGroup(appName);
        if (CollectionUtil.isEmpty(jobGroupList)) {
            return -1;
        }
        return jobGroupList.get(0).getId();
    }

    /**
     * 獲取執(zhí)行管理器列表
     *
     * @param appName
     * @return
     */
    private List<XxlJobGroup> listJobGroup(String appName) {
        String url = adminAddresses + "/jobgroup/pageList";
        HttpResponse response = HttpRequest.post(url)
                .form("appname", appName)
                .cookie(getCookie())
                .execute();

        String body = response.body();
        JSONArray array = JSONUtil.parse(body).getByPath("data", JSONArray.class);
        List<XxlJobGroup> list = array.stream()
                .map(o -> JSONUtil.toBean((JSONObject) o, XxlJobGroup.class))
                .collect(Collectors.toList());
        return list;
    }
4.3.5 創(chuàng)建&啟動定時任務(wù)
 /**
     * 啟動定時任務(wù)
     *
     * @param jobId
     * @return
     */
    private boolean startJob(Integer jobId) {
        String url = adminAddresses + "/jobinfo/start";
        Map<String, Object> paramMap = new HashMap<>();
        paramMap.put("id", jobId);

        HttpResponse response = HttpRequest.post(url)
                .form(paramMap)
                .cookie(getCookie())
                .execute();

        JSON json = JSONUtil.parse(response.body());
        Object code = json.getByPath("code");
        return code.equals(200);
    }

    /**
     * 創(chuàng)建定時任務(wù)
     *
     * @param xxlJobInfo
     * @return
     */
    private Integer addJobInfo(XxlJobInfo xxlJobInfo) {
        String url = adminAddresses + "/jobinfo/add";
        Map<String, Object> paramMap = BeanUtil.beanToMap(xxlJobInfo);
        HttpResponse response = HttpRequest.post(url)
                .form(paramMap)
                .cookie(getCookie())
                .execute();

        JSON json = JSONUtil.parse(response.body());
        Object code = json.getByPath("code");
        if (code.equals(200)) {
            Object content = json.getByPath("content");
            if (content == null) {
                return -1;
            }
            return Integer.valueOf((String) content);
        }
        log.info("創(chuàng)建定時任務(wù)失敗");
        return -1;
    }
4.3.5 XxlJobComponent完整代碼
package com.ac.order.component;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.json.JSON;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.ac.common.xxljob.XxlJobGroup;
import com.ac.common.xxljob.XxlJobInfo;
import com.ac.order.cmd.AddDefaultXxlJobCmd;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import java.net.HttpCookie;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;

@Slf4j
@Component
public class XxlJobComponent {

    private final Map<String, String> loginCookie = new HashMap<>();

    private final String adminAddresses = "http://127.0.0.1:8081/xxl-job-admin";
    private final String username = "admin";
    private final String password = "123456";

    /**
     * 創(chuàng)建定時定時任務(wù)并啟動
     *
     * @param cmd
     * @return
     */
    public boolean addAndStartJob(AddDefaultXxlJobCmd cmd) {
        int jobGroup = getJobGroupId(cmd.getAppName());
        if (jobGroup == -1) {
            log.error("獲取執(zhí)行管理器ID失敗,appName={}", cmd.getAppName());
            return false;
        }

        cmd.setJobGroup(jobGroup);
        XxlJobInfo jobInfo = convertDefaultJobInfo(cmd);

        //創(chuàng)建定時任務(wù)
        Integer id = this.addJobInfo(jobInfo);
        if (id == -1) {
            log.error("創(chuàng)建定時任務(wù)失敗,cmd={}", cmd);
            return false;
        }
        //啟動定時任務(wù)
        return this.startJob(id);
    }

    /**
     * xxl-job登錄
     *
     * @return
     */
    public String login() {
        String url = adminAddresses + "/login";
        HttpResponse response = HttpRequest.post(url)
                .form("userName", username)
                .form("password", password)
                .execute();
        List<HttpCookie> cookies = response.getCookies();
        Optional<HttpCookie> cookieOpt = cookies.stream()
                .filter(cookie -> cookie.getName().equals("XXL_JOB_LOGIN_IDENTITY")).findFirst();
        if (!cookieOpt.isPresent())
            throw new RuntimeException("get xxl-job cookie error!");

        String value = cookieOpt.get().getValue();
        loginCookie.put("XXL_JOB_LOGIN_IDENTITY", value);

        log.info("XxlJobComponent.login.token={}", value);
        return value;
    }

    /**
     * 其他接口在調(diào)用時,直接從緩存中獲取cookie涌哲,如果緩存中不存在則調(diào)用/login接口胖缤,為了避免這一過程失敗,允許最多重試3次
     *
     * @return
     */
    public String getCookie() {
        for (int i = 0; i < 3; i++) {
            String cookieStr = loginCookie.get("XXL_JOB_LOGIN_IDENTITY");
            if (cookieStr != null) {
                return "XXL_JOB_LOGIN_IDENTITY=" + cookieStr;
            }
            login();
        }
        throw new RuntimeException("get xxl-job cookie error!");
    }

    /**
     * 通過appName獲取執(zhí)行管理器ID
     *
     * @param appName
     * @return
     */
    private int getJobGroupId(String appName) {
        List<XxlJobGroup> jobGroupList = listJobGroup(appName);
        if (CollectionUtil.isEmpty(jobGroupList)) {
            return -1;
        }
        return jobGroupList.get(0).getId();
    }

    /**
     * 獲取執(zhí)行管理器列表
     *
     * @param appName
     * @return
     */
    private List<XxlJobGroup> listJobGroup(String appName) {
        String url = adminAddresses + "/jobgroup/pageList";
        HttpResponse response = HttpRequest.post(url)
                .form("appname", appName)
                .cookie(getCookie())
                .execute();

        String body = response.body();
        JSONArray array = JSONUtil.parse(body).getByPath("data", JSONArray.class);
        List<XxlJobGroup> list = array.stream()
                .map(o -> JSONUtil.toBean((JSONObject) o, XxlJobGroup.class))
                .collect(Collectors.toList());
        return list;
    }

    /**
     * 啟動定時任務(wù)
     *
     * @param jobId
     * @return
     */
    private boolean startJob(Integer jobId) {
        String url = adminAddresses + "/jobinfo/start";
        Map<String, Object> paramMap = new HashMap<>();
        paramMap.put("id", jobId);

        HttpResponse response = HttpRequest.post(url)
                .form(paramMap)
                .cookie(getCookie())
                .execute();

        JSON json = JSONUtil.parse(response.body());
        Object code = json.getByPath("code");
        return code.equals(200);
    }

    /**
     * 創(chuàng)建定時任務(wù)
     *
     * @param xxlJobInfo
     * @return
     */
    private Integer addJobInfo(XxlJobInfo xxlJobInfo) {
        String url = adminAddresses + "/jobinfo/add";
        Map<String, Object> paramMap = BeanUtil.beanToMap(xxlJobInfo);
        HttpResponse response = HttpRequest.post(url)
                .form(paramMap)
                .cookie(getCookie())
                .execute();

        JSON json = JSONUtil.parse(response.body());
        Object code = json.getByPath("code");
        if (code.equals(200)) {
            Object content = json.getByPath("content");
            if (content == null) {
                return -1;
            }
            return Integer.valueOf((String) content);
        }
        log.info("創(chuàng)建定時任務(wù)失敗");
        return -1;
    }

    /**
     * 定時任務(wù)對象轉(zhuǎn)換
     *
     * @param cmd
     * @return
     */
    private XxlJobInfo convertDefaultJobInfo(AddDefaultXxlJobCmd cmd) {
        XxlJobInfo jobInfo = new XxlJobInfo();
        /*基礎(chǔ)配置*/
        jobInfo.setJobGroup(cmd.getJobGroup());
        jobInfo.setJobDesc(cmd.getJobDesc());
        jobInfo.setAuthor("SYSTEM");
        jobInfo.setAlarmEmail("test.126.com");
        //調(diào)度配置
        jobInfo.setScheduleType("CRON");
        jobInfo.setScheduleConf(cmd.getScheduleConf());
        //任務(wù)配置
        jobInfo.setGlueType("BEAN");
        jobInfo.setExecutorHandler(cmd.getExecutorHandler());
        jobInfo.setExecutorParam(cmd.getExecutorParam());

        /*高級配置*/
        //路由策略
        jobInfo.setExecutorRouteStrategy("CONSISTENT_HASH");
        //調(diào)度過期策略 DO_NOTHING忽略 FIRE_ONCE_NOW立即執(zhí)行一次
        jobInfo.setMisfireStrategy("FIRE_ONCE_NOW");
        //阻塞處理策略
        jobInfo.setExecutorBlockStrategy("SERIAL_EXECUTION");

        return jobInfo;
    }
}
4.4 訂單未付款自動關(guān)閉15分鐘倒計時
4.4.1 訂單任務(wù)類
package com.ac.order.task;

import cn.hutool.core.util.StrUtil;
import com.ac.common.constant.XXLJobHandlerConstant;
import com.ac.core.util.DateUtil;
import com.ac.order.cmd.AddDefaultXxlJobCmd;
import com.ac.order.component.XxlJobComponent;
import com.xxl.job.core.context.XxlJobHelper;
import com.xxl.job.core.handler.annotation.XxlJob;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;

/**
 * @author Alan Chen
 * @description 訂單未支付倒計時關(guān)閉
 * @date 2023/05/17
 */
@Slf4j
@Component
public class AutoCancelOrderJob {

    @Resource
    private XxlJobComponent xxlJobComponent;

    /**
     * 訂單未付款自動關(guān)閉15分鐘倒計時
     *
     * @param orderNo
     */
    public void addJob(String orderNo) {
        try {
            log.info("AutoCancelOrderJob.addJob,orderNo={}", orderNo);

            String executorParam = orderNo;

            LocalDateTime now = LocalDateTime.now();
            //6小時后執(zhí)行
            LocalDateTime offset = DateUtil.offset(now, 15, ChronoUnit.MINUTES);
            String scheduleConf = DateUtil.getCron(cn.hutool.core.date.DateUtil.date(offset));

            AddDefaultXxlJobCmd cmd = AddDefaultXxlJobCmd.builder()
                    .appName("executor-order")
                    .jobDesc("訂單未付款自動關(guān)閉15分鐘倒計時")
                    .scheduleConf(scheduleConf)
                    .executorHandler(XXLJobHandlerConstant.AUTO_CANCEL_ORDER)
                    .executorParam(executorParam)
                    .build();
            xxlJobComponent.addAndStartJob(cmd);
        } catch (Exception e) {
            log.info("AutoCancelOrderJob.addJob,啟動任務(wù)失敗,msg={}", e.getMessage());
        }
    }

    @XxlJob(value = XXLJobHandlerConstant.AUTO_CANCEL_ORDER)
    public void doJob() {
        try {
            // 獲取任務(wù)ID
            long jobId = XxlJobHelper.getJobId();
            log.info("AutoCancelOrderJob.doJob,jobId={},param={}", jobId, XxlJobHelper.getJobParam());

            // 獲取任務(wù)參數(shù)
            String orderNo = XxlJobHelper.getJobParam();
            // 業(yè)務(wù)邏輯

            log.info("模擬業(yè)務(wù)邏輯,AutoCancelOrderJob.doJob,關(guān)閉訂單,orderNo={}", orderNo);

            String logInfo = StrUtil.format("AutoCancelOrderJob.doJob,成功關(guān)閉訂單,orderNo={}", orderNo);
            log.info(logInfo);
            XxlJobHelper.handleSuccess(logInfo);
        } catch (Exception e) {
            String error = StrUtil.format("AutoCancelOrderJob.doJob,失敗, msg={}", e.getMessage());
            log.info(error);
            XxlJobHelper.handleFail(error);
        }
    }
}
4.4.2 Controller
   @ApiOperation(value = "訂單未付款自動關(guān)閉15分鐘倒計時")
    @GetMapping("autoCancelOrder")
    public boolean autoCancelOrder(@RequestParam String orderNo) {
        autoCancelOrderJob.addJob(orderNo);
        return true;
    }
4.4.3 測試
Postman
xxljob-admin
執(zhí)行一次
參數(shù)自動填充
控制臺日志
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末阀圾,一起剝皮案震驚了整個濱河市哪廓,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌初烘,老刑警劉巖涡真,帶你破解...
    沈念sama閱讀 212,884評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異肾筐,居然都是意外死亡哆料,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,755評論 3 385
  • 文/潘曉璐 我一進店門吗铐,熙熙樓的掌柜王于貴愁眉苦臉地迎上來东亦,“玉大人,你說我怎么就攤上這事抓歼。” “怎么了?”我有些...
    開封第一講書人閱讀 158,369評論 0 348
  • 文/不壞的土叔 我叫張陵晋控,是天一觀的道長普筹。 經(jīng)常有香客問我,道長兢榨,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,799評論 1 285
  • 正文 為了忘掉前任,我火速辦了婚禮充坑,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘染突。我一直安慰自己捻爷,他們只是感情好,可當我...
    茶點故事閱讀 65,910評論 6 386
  • 文/花漫 我一把揭開白布份企。 她就那樣靜靜地躺著也榄,像睡著了一般。 火紅的嫁衣襯著肌膚如雪司志。 梳的紋絲不亂的頭發(fā)上甜紫,一...
    開封第一講書人閱讀 50,096評論 1 291
  • 那天,我揣著相機與錄音骂远,去河邊找鬼囚霸。 笑死,一個胖子當著我的面吹牛激才,可吹牛的內(nèi)容都是我干的拓型。 我是一名探鬼主播,決...
    沈念sama閱讀 39,159評論 3 411
  • 文/蒼蘭香墨 我猛地睜開眼瘸恼,長吁一口氣:“原來是場噩夢啊……” “哼劣挫!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起钞脂,我...
    開封第一講書人閱讀 37,917評論 0 268
  • 序言:老撾萬榮一對情侶失蹤揣云,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后冰啃,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體邓夕,經(jīng)...
    沈念sama閱讀 44,360評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,673評論 2 327
  • 正文 我和宋清朗相戀三年阎毅,在試婚紗的時候發(fā)現(xiàn)自己被綠了焚刚。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,814評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡扇调,死狀恐怖矿咕,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤碳柱,帶...
    沈念sama閱讀 34,509評論 4 334
  • 正文 年R本政府宣布捡絮,位于F島的核電站,受9級特大地震影響莲镣,放射性物質(zhì)發(fā)生泄漏福稳。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 40,156評論 3 317
  • 文/蒙蒙 一瑞侮、第九天 我趴在偏房一處隱蔽的房頂上張望的圆。 院中可真熱鬧,春花似錦半火、人聲如沸越妈。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽梅掠。三九已至,卻和暖如春藐鹤,著一層夾襖步出監(jiān)牢的瞬間瓤檐,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,123評論 1 267
  • 我被黑心中介騙來泰國打工娱节, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人祭示。 一個月前我還...
    沈念sama閱讀 46,641評論 2 362
  • 正文 我出身青樓肄满,卻偏偏與公主長得像,于是被迫代替她去往敵國和親质涛。 傳聞我的和親對象是個殘疾皇子稠歉,可洞房花燭夜當晚...
    茶點故事閱讀 43,728評論 2 351

推薦閱讀更多精彩內(nèi)容