總概
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究孕、源碼地址
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厨诸、系列
- 微服務(wù)開發(fā)系列 第一篇:項目搭建
- 微服務(wù)開發(fā)系列 第二篇:Nacos
- 微服務(wù)開發(fā)系列 第三篇:OpenFeign
- 微服務(wù)開發(fā)系列 第四篇:分頁查詢
- 微服務(wù)開發(fā)系列 第五篇:Redis
- 微服務(wù)開發(fā)系列 第六篇:Redisson
- 微服務(wù)開發(fā)系列 第七篇:RocketMQ
- 微服務(wù)開發(fā)系列 第八篇:Elasticsearch
- 微服務(wù)開發(fā)系列 第九篇:OAuth2
- 微服務(wù)開發(fā)系列 第十篇:Gateway
- 微服務(wù)開發(fā)系列 第十一篇:XXL-JOB
- 微服務(wù)開發(fā)系列 第十二篇:MongoDB
- 微服務(wù)開發(fā)系列 第n篇:AOP請求日志監(jiān)控
- 微服務(wù)開發(fā)系列 第n篇:自定義校驗注解
一镶殷、部署xxl-job
1.1 下載xxl-job源碼
下載地址:https://github.com/xuxueli/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
1.3.3 訪問xxl-job-admin
調(diào)度中心訪問地址:http://localhost:8081/xxl-job-admin
,該地址執(zhí)行器將會使用到梗逮,作為回調(diào)地址项秉。
默認登錄賬號 “admin/123456”, 登錄后運行界面如下圖所示。
二慷彤、配置部署“執(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
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>
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項目
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í)行器項目宽涌。
三、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";
}
3.2 新建任務(wù)管理
配置JobHandler卸亮,和TaskByAdminCreateJob里配置的@XxlJob保持一致
任務(wù)新建完后,需要手動啟動
3.3 執(zhí)行任務(wù)
啟動狀態(tài)下的任務(wù)玩裙,可以立即執(zhí)行一次
四兼贸、代碼動態(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)用的是哪些接口墨林,我們先從頁面上去抓一個請求看看:
好了,這樣就能定位到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菩收,則是在通過用戶名和密碼調(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é)果风瘦。
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;
}