XXL-JOB动态创建任务详解篇2

CodeWorld-Cloud-Shop XXL-JOB动态创建任务详解篇2—奶妈级教学

前言

我们在上一节CodeWorld-Cloud-Shop XXL-JOB动态创建任务详解篇1—奶妈级教学讲解了XXL-JOB我们在创建任务的缺陷
那么这一节我们呢将继续实现怎么来动态创建任务

修改xxl-job-admin的接口

我们知道了,我们在调用接口前呢,就需要登录才能调用,不过后来我们发现可以加上一个 PermissionLimit并设置limit为false
那么这样就不用去登录就可以调用接口

JobInfoController添加如下接口

// 自定义方法
/**
* 添加任务
* @param jobInfo
* @return
*/
@RequestMapping("/addJob")
@ResponseBody
@PermissionLimit(limit = false)
public ReturnT<String> addJob(@RequestBody XxlJobInfo jobInfo) {
return xxlJobService.add(jobInfo);
}
我们在上面可以看见大致和原来的添加接口一致,这里我们加上了
@PermissionLimit(limit = false) // 可以用登录直接放行
@RequestBody // 接收json类型的参数

这里我们只需要添加这一个接口就可以啦,里面实现的逻辑就和原来的一样,不做任何的修改
我们只需要提供需要的参数就可以啦
xxl-job-add-job

创建一个新的项目

我们这里实现的一个需求就是
我们创建一个User用户,在创建时间的基础上加上1分钟,在这个时间自动添加
例如:2020-01-01 12:00:00这个时间创建 那么我们应该在2020-01-01 12:00:01这个时间添加

POM文件

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.xuxueli</groupId>
<artifactId>xxl-job-core</artifactId>
<version>2.2.0</version>
</dependency>

<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.47</version>
</dependency>

<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.0.6</version>
</dependency>

application.yml文件

server:
port: 10001

xxl:
job:
accessToken:
admin:
addresses: http://localhost:9999/xxl-job-admin
executor:
address:
appname: xxl-job-test
ip:
logpath: /data/applogs/xxl-job/jobhandler
logretentiondays: 30
port: 10010

这些配置我相信你再熟悉不过了吧,我们在第一节就看见过啦
那么既然配置文件有了,那么我们也要创建一个配置类

XxlJobConfig

XxlJobConfig

@Configuration
@Slf4j
public class XxlJobConfig {

@Value("${xxl.job.admin.addresses}")
private String adminAddresses;

@Value("${xxl.job.accessToken}")
private String accessToken;

@Value("${xxl.job.executor.appname}")
private String appname;

@Value("${xxl.job.executor.address}")
private String address;

@Value("${xxl.job.executor.ip}")
private String ip;

@Value("${xxl.job.executor.port}")
private int port;

@Value("${xxl.job.executor.logpath}")
private String logPath;

@Value("${xxl.job.executor.logretentiondays}")
private int logRetentionDays;

@Bean
public XxlJobSpringExecutor xxlJobExecutor() {
log.info(">>>>>>>>>>> xxl-job config init.");
XxlJobSpringExecutor xxlJobSpringExecutor = new XxlJobSpringExecutor();
xxlJobSpringExecutor.setAdminAddresses(adminAddresses);
xxlJobSpringExecutor.setAppname(appname);
xxlJobSpringExecutor.setAddress(address);
xxlJobSpringExecutor.setIp(ip);
xxlJobSpringExecutor.setPort(port);
xxlJobSpringExecutor.setAccessToken(accessToken);
xxlJobSpringExecutor.setLogPath(logPath);
xxlJobSpringExecutor.setLogRetentionDays(logRetentionDays);
return xxlJobSpringExecutor;
}
}

基本的工具类

DateUtils 日期工具类

/**
* 日期工具类
*
* @author Lenovo
*/
public class DateUtils {

/**
* 日期格式yyyy-MM-dd
*/
public static final String pattern_date = "yyyy-MM-dd";

/**
* 日期时间格式yyyy-MM-dd HH:mm:ss
*/
public static final String pattern_time = "yyyy-MM-dd HH:mm:ss";

/**
* 描述:日期格式化
*
* @param date 日期
* @return 输出格式为 yyyy-MM-dd 的字串
*/
public static String formatDate(Date date) {

return formatDate(date, pattern_time);

}

/**
* 描述:日期格式化
*
* @param date 日期
* @param pattern 格式化类型
* @return
*/
public static String formatDate(Date date, String pattern) {

SimpleDateFormat dateFormat = new SimpleDateFormat(pattern);

return dateFormat.format(date);
}

/***
* convert Date to cron ,eg. "0 07 10 15 1 ? 2016"
* @param date : 时间点
* @return
*/
public static String getCron(Date date) {
String dateFormat = "ss mm HH dd MM ? yyyy";
return formatDate(date, dateFormat);
}
}

JsonUtils 工具类

public class JsonUtils {

public static final ObjectMapper mapper = new ObjectMapper();

private static final Logger logger = LoggerFactory.getLogger(JsonUtils.class);

@Nullable
public static String serialize(Object obj) {
if (obj == null) {
return null;
}
if (obj.getClass() == String.class) {
return (String) obj;
}
try {
return mapper.writeValueAsString(obj);
} catch (JsonProcessingException e) {
logger.error("json序列化出错:" + obj, e);
return null;
}
}

@Nullable
public static <T> T parse(String json, Class<T> tClass) {
try {
return mapper.readValue(json, tClass);
} catch (IOException e) {
logger.error("json解析出错:" + json, e);
return null;
}
}

@Nullable
public static <E> List<E> parseList(String json, Class<E> eClass) {
try {
return mapper.readValue(json, mapper.getTypeFactory().constructCollectionType(List.class, eClass));
} catch (IOException e) {
logger.error("json解析出错:" + json, e);
return null;
}
}

@Nullable
public static <K, V> Map<K, V> parseMap(String json, Class<K> kClass, Class<V> vClass) {
try {
return mapper.readValue(json, mapper.getTypeFactory().constructMapType(Map.class, kClass, vClass));
} catch (IOException e) {
logger.error("json解析出错:" + json, e);
return null;
}
}

@Nullable
public static <T> T nativeRead(String json, TypeReference<T> type) {
try {
return mapper.readValue(json, type);
} catch (IOException e) {
logger.error("json解析出错:" + json, e);
return null;
}
}
}

创建Response响应信息

ApiResponse

@Data
public class FcResponse<T> {

private Integer code;

private String msg;

private T data;

public FcResponse(Integer code, String msg, T data) {
this.code = code;
this.msg = msg;
this.data = data;
}
}

创建一个domain

XxlJobInfo

@Data
public class XxlJobInfo {

private int id; // 主键ID

private int jobGroup; // 执行器主键ID
private String jobDesc;

private Date addTime;
private Date updateTime;

private String author; // 负责人
private String alarmEmail; // 报警邮件

private String scheduleType; // 调度类型
private String scheduleConf; // 调度配置,值含义取决于调度类型
private String misfireStrategy; // 调度过期策略

private String executorRouteStrategy; // 执行器路由策略
private String executorHandler; // 执行器,任务Handler名称
private String executorParam; // 执行器,任务参数
private String executorBlockStrategy; // 阻塞处理策略
private int executorTimeout; // 任务执行超时时间,单位秒
private int executorFailRetryCount; // 失败重试次数

private String glueType; // GLUE类型 #com.xxl.job.core.glue.GlueTypeEnum
private String glueSource; // GLUE源代码
private String glueRemark; // GLUE备注
private Date glueUpdatetime; // GLUE更新时间

private String childJobId; // 子任务ID,多个逗号分隔

private int triggerStatus; // 调度状态:0-停止,1-运行
private long triggerLastTime; // 上次调度时间
private long triggerNextTime; // 下次调度时间
}

那么这个就是我们需要传入的参数

XxlUtil工具类

XxlUtil

@Component
public class XxlUtil {

/**
* 地址
*/
private final String adminAddress = "http://localhost:9999/xxl-job-admin";

@Autowired(required = false)
private final RestTemplate restTemplate = new RestTemplate();

// 请求Url
private static final String ADD_INFO_URL = "/jobinfo/addJob";
private static final String UPDATE_INFO_URL = "/jobinfo/updateJob";
private static final String REMOVE_INFO_URL = "/jobinfo/removeJob";
private static final String GET_GROUP_ID = "/jobgroup/loadByAppName";

/**
* 添加任务
* @param xxlJobInfo
* @param appName
* @return
*/
public String addJob(XxlJobInfo xxlJobInfo, String appName){
Map<String, Object> params = new HashMap<>();
params.put("appName",appName);
String json = JsonUtils.serialize(params);
String result = doPost(adminAddress + GET_GROUP_ID, json);
JSONObject jsonObject = JSON.parseObject(result);
Map<String, Object> map = (Map<String, Object>) jsonObject.get("content");
Integer groupId = (Integer) map.get("id");
xxlJobInfo.setJobGroup(groupId);
String json2 = JSONObject.toJSONString(xxlJobInfo);
return doPost(adminAddress + ADD_INFO_URL, json2);
}

/**
* 远程调用
* @param url
* @param json
*/
private String doPost(String url, String json) {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<String> entity = new HttpEntity<>(json,headers);
ResponseEntity<String> responseEntity = restTemplate.postForEntity(url, entity, String.class);
return responseEntity.getBody();
}
}

这里对代码进行讲解下

 /**
* 远程调用
* @param url
* @param json
*/
private String doPost(String url, String json) {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<String> entity = new HttpEntity<>(json,headers);
ResponseEntity<String> responseEntity = restTemplate.postForEntity(url, entity, String.class);
return responseEntity.getBody();

// 这个就是我们进行远程调用的方法,我们一个是请求的url,第二个就是参数
然后调用返回主体信息
       Map<String, Object> params = new HashMap<>();
params.put("appName",appName);
String json = JsonUtils.serialize(params);
String result = doPost(adminAddress + GET_GROUP_ID, json);
JSONObject jsonObject = JSON.parseObject(result);
Map<String, Object> map = (Map<String, Object>) jsonObject.get("content");
Integer groupId = (Integer) map.get("id");
xxlJobInfo.setJobGroup(groupId);
String json2 = JSONObject.toJSONString(xxlJobInfo);
return doPost(adminAddress + ADD_INFO_URL, json2);

其中有一个appName,何为appName--执行器的名称,也就是我们的任务是在哪一个执行器下执行的
首先呢我们需要通过执行器的名称来查询执行器的id,对应数据库中的xxl_job_group中的id字段
我们在xxl_job_info这个表中也可以清楚的看见有个job_group这个字段
这样我们将其设置到xxlJobInfo中,然后进行远程调用我们的添加接口,这样就实现了远程调用

在上面我们提到根据执行器名称来查询执行器id,那么这个接口在哪里呢,其实和任务添加的一样,我们也需要去开发一个新的接口

修改xxl-job-admin中的JobGroupController

添加如下接口

@RequestMapping("/loadByAppName")
@ResponseBody
@PermissionLimit(limit = false)
public ReturnT<XxlJobGroup> loadByAppName(@RequestBody Map<String,Object> map){
XxlJobGroup jobGroup = xxlJobGroupDao.loadByAppName(map);
return jobGroup!=null?new ReturnT<XxlJobGroup>(jobGroup):new ReturnT<XxlJobGroup>(ReturnT.FAIL_CODE, null);
}

继续创建dao下的方法

XxlJobGroup loadByAppName(Map<String, Object> map);

创建查询语句

<select id="loadByAppName" parameterType="hashmap" resultMap="XxlJobGroup">
SELECT <include refid="Base_Column_List" />
FROM xxl_job_group AS t
WHERE t.app_name = #{appName}
</select>

这样我们的执行器id就查询出来了

好了,既然我们的远程调用接口写好了,我们将来写本地的接口

首先创建UserController

UserController

@RestController
@RequestMapping("codeworld-xxl")
public class XxlController {

@Autowired(required = false)
private XxlService xxlService;

/**
* 添加任务
* @return
*/
@PostMapping("add-job")
public FcResponse<Void> addJob(){
return this.xxlService.addJob();
}
}

接着UserService

UserService

public interface XxlService {
/**
* 添加任务
* @return
*/
FcResponse<Void> addJob();
}

再接着UserServiceImpl

UserServiceImpl

@Service
@Slf4j
public class XxlServiceImpl implements XxlService {

@Autowired(required = false)
private XxlUtil xxlUtil;

/**
* 添加任务
*
* @return
*/
@Override
public FcResponse<Void> addJob() {
User user = new User();
user.setId(1000L);
user.setUserName("code");
user.setCreateTime(new Date());
XxlJobInfo xxlJobInfo = new XxlJobInfo();
xxlJobInfo.setJobDesc("测试用户定时添加");
xxlJobInfo.setAuthor("admin");
xxlJobInfo.setScheduleType("CRON");
Date startTime = new Date(user.getCreateTime().getTime() + 60000);
xxlJobInfo.setScheduleConf(DateUtils.getCron(startTime));
xxlJobInfo.setGlueType("BEAN");
xxlJobInfo.setExecutorHandler("addUser");
xxlJobInfo.setExecutorParam(JsonUtils.serialize(user));
xxlJobInfo.setExecutorRouteStrategy("FIRST");
xxlJobInfo.setMisfireStrategy("DO_NOTHING");
xxlJobInfo.setExecutorBlockStrategy("SERIAL_EXECUTION");
xxlJobInfo.setTriggerStatus(1);
xxlUtil.addJob(xxlJobInfo, "xxl-job-test");
log.info("任务已添加,将在{}开始执行任务",DateUtil.date(startTime));
return new FcResponse<>(1,"任务添加成功",null);
}
}

这里呢我们就直接指定创建用户了,重点不是用户而是我们的XxlJobInfo

xxlJobInfo.setJobDesc("测试用户定时添加"); // 描述信息
xxlJobInfo.setAuthor("admin"); // 执行人
xxlJobInfo.setScheduleType("CRON"); // 执行类型 采用CRON表达式
Date startTime = new Date(user.getCreateTime().getTime() + 60000); // 在创建时间上加上1分钟
xxlJobInfo.setScheduleConf(DateUtils.getCron(startTime)); // 设置cron
xxlJobInfo.setGlueType("BEAN"); // 设置GLUE类型为BEAN模式
xxlJobInfo.setExecutorHandler("addUser"); // 设置任务Handler名称
xxlJobInfo.setExecutorParam(JsonUtils.serialize(user)); // 设置参数
xxlJobInfo.setExecutorRouteStrategy("FIRST"); // 执行器路由策略
xxlJobInfo.setMisfireStrategy("DO_NOTHING"); // 调度过期策略
xxlJobInfo.setExecutorBlockStrategy("SERIAL_EXECUTION"); // 阻塞处理策略
xxlJobInfo.setTriggerStatus(1); // 调度状态:0-停止,1-运行
xxlUtil.addJob(xxlJobInfo, "xxl-job-test"); // 调用工具类方法,xxl-job-test为执行器名称

那么到这里我们接口写完了
那么我们应该怎么执行任务呢

UserTask

UserTask

@Component
@Slf4j
public class UserTask {

@XxlJob(value = "addUser")
public ReturnT<String> addUser(String param){
log.info("开始执行任务,在指定时间添加用户,收到参数{},当前时间{}",param, DateUtil.date(new Date()));
return ReturnT.SUCCESS;
}
}

这个就是我们执行任务的方法,可以看见我们在这个@XxxlJob(value=”addUser”)和参数里的xxlJobInfo.setExecutorHandler(“addUser”); 是一一对应的

好了到这里我么的全部详解就说明完了

不过说了这么多,到底行不行呢?

实践

首先先启动xxl-job-admin这个项目

xxl-job-start

启动本地项目

xxl-job-customize
看见这个就是xxl加载成功啦

我们需要去创建一个执行器,名称是xxl-job-test

这个就不用上图片了吧,去CodeWorld-Cloud-Shop XXL-JOB入门详解篇—奶妈级教学

调用接口

http://localhost:10001/codeworld-xxl/add-job
add-job-customize
出现这个说明接口调用成功啦
再来看我们的控制台
add-task-success
说明我们的任务已经添加成功啦
然后等着1分钟后看看会不会执行任务
add-task-result

哇哦,看来执行成功了,那么我们就这样成功了。
那么轮播图的问题就解决了,动态创建任务实现实现了轮播图的上线和下线
代码地址

好了,本次的技术解析就到这里了?如果觉得不错的话,点亮一下小星星codeworld-cloud-shop
只看不点,不是好孩子哦!!

欢迎加入QQ群(964285437)

QQ群