# spring-boot-demo-task-xxl-job > 此 demo 主要演示了 Spring Boot 如何集成 XXL-JOB 实现分布式定时任务,并提供绕过 `xxl-job-admin` 对定时任务的管理的方法,包括定时任务列表,触发器列表,新增定时任务,删除定时任务,停止定时任务,启动定时任务,修改定时任务,手动触发定时任务。 ## 1. xxl-job-admin调度中心 > https://github.com/xuxueli/xxl-job.git 克隆 调度中心代码 ```bash $ git clone https://github.com/xuxueli/xxl-job.git ``` ### 1.1. 创建调度中心的表结构 数据库脚本地址:`/xxl-job/doc/db/tables_xxl_job.sql` ### 1.2. 修改 application.properties ```properties server.port=18080 spring.datasource.url=jdbc:mysql://127.0.0.1:3306/xxl_job?Unicode=true&characterEncoding=UTF-8&useSSL=false spring.datasource.username=root spring.datasource.password=root ``` ### 1.3. 修改日志配置文件 logback.xml ```xml ``` ### 1.4. 启动调度中心 Run `XxlJobAdminApplication` 默认用户名密码:admin/admin ![image-20190808105554414](https://static.xkcoding.com/spring-boot-demo/2019-08-08-025555.png) ![image-20190808105628852](https://static.xkcoding.com/spring-boot-demo/2019-08-08-025629.png) ## 2. 编写执行器项目 ### 2.1. pom.xml ```xml 4.0.0 spring-boot-demo-task-xxl-job 1.0.0-SNAPSHOT jar spring-boot-demo-task-xxl-job Demo project for Spring Boot com.xkcoding spring-boot-demo 1.0.0-SNAPSHOT UTF-8 UTF-8 1.8 2.1.0 org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-configuration-processor true com.xuxueli xxl-job-core ${xxl-job.version} org.springframework.boot spring-boot-starter-test test cn.hutool hutool-all com.google.guava guava org.projectlombok lombok true spring-boot-demo-task-xxl-job org.springframework.boot spring-boot-maven-plugin ``` ### 2.2. 编写 配置类 XxlJobProps.java ```java /** *

* xxl-job 配置 *

* * @author yangkai.shen * @date Created in 2019-08-07 10:25 */ @Data @ConfigurationProperties(prefix = "xxl.job") public class XxlJobProps { /** * 调度中心配置 */ private XxlJobAdminProps admin; /** * 执行器配置 */ private XxlJobExecutorProps executor; /** * 与调度中心交互的accessToken */ private String accessToken; @Data public static class XxlJobAdminProps { /** * 调度中心地址 */ private String address; } @Data public static class XxlJobExecutorProps { /** * 执行器名称 */ private String appName; /** * 执行器 IP */ private String ip; /** * 执行器端口 */ private int port; /** * 执行器日志 */ private String logPath; /** * 执行器日志保留天数,-1 */ private int logRetentionDays; } } ``` ### 2.3. 编写配置文件 application.yml ```yaml server: port: 8080 servlet: context-path: /demo xxl: job: # 执行器通讯TOKEN [选填]:非空时启用; access-token: admin: # 调度中心部署跟地址 [选填]:如调度中心集群部署存在多个地址则用逗号分隔。执行器将会使用该地址进行"执行器心跳注册"和"任务结果回调";为空则关闭自动注册; address: http://localhost:18080/xxl-job-admin executor: # 执行器AppName [选填]:执行器心跳注册分组依据;为空则关闭自动注册 app-name: spring-boot-demo-task-xxl-job-executor # 执行器IP [选填]:默认为空表示自动获取IP,多网卡时可手动设置指定IP,该IP不会绑定Host仅作为通讯实用;地址信息用于 "执行器注册" 和 "调度中心请求并触发任务"; ip: # 执行器端口号 [选填]:小于等于0则自动获取;默认端口为9999,单机部署多个执行器时,注意要配置不同执行器端口; port: 9999 # 执行器运行日志文件存储磁盘路径 [选填] :需要对该路径拥有读写权限;为空则使用默认路径; log-path: logs/spring-boot-demo-task-xxl-job/task-log # 执行器日志保存天数 [选填] :值大于3时生效,启用执行器Log文件定期清理功能,否则不生效; log-retention-days: -1 ``` ### 2.4. 编写自动装配类 XxlConfig.java ```java /** *

* xxl-job 自动装配 *

* * @author yangkai.shen * @date Created in 2019-08-07 10:20 */ @Slf4j @Configuration @EnableConfigurationProperties(XxlJobProps.class) @RequiredArgsConstructor(onConstructor_ = @Autowired) public class XxlJobConfig { private final XxlJobProps xxlJobProps; @Bean(initMethod = "start", destroyMethod = "destroy") public XxlJobSpringExecutor xxlJobExecutor() { log.info(">>>>>>>>>>> xxl-job config init."); XxlJobSpringExecutor xxlJobSpringExecutor = new XxlJobSpringExecutor(); xxlJobSpringExecutor.setAdminAddresses(xxlJobProps.getAdmin().getAddress()); xxlJobSpringExecutor.setAccessToken(xxlJobProps.getAccessToken()); xxlJobSpringExecutor.setAppName(xxlJobProps.getExecutor().getAppName()); xxlJobSpringExecutor.setIp(xxlJobProps.getExecutor().getIp()); xxlJobSpringExecutor.setPort(xxlJobProps.getExecutor().getPort()); xxlJobSpringExecutor.setLogPath(xxlJobProps.getExecutor().getLogPath()); xxlJobSpringExecutor.setLogRetentionDays(xxlJobProps.getExecutor().getLogRetentionDays()); return xxlJobSpringExecutor; } } ``` ### 2.5. 编写具体的定时逻辑 DemoTask.java ```java /** *

* 测试定时任务 *

* * @author yangkai.shen * @date Created in 2019-08-07 10:15 */ @Slf4j @Component @JobHandler("demoTask") public class DemoTask extends IJobHandler { /** * execute handler, invoked when executor receives a scheduling request * * @param param 定时任务参数 * @return 执行状态 * @throws Exception 任务异常 */ @Override public ReturnT execute(String param) throws Exception { // 可以动态获取传递过来的参数,根据参数不同,当前调度的任务不同 log.info("【param】= {}", param); XxlJobLogger.log("demo task run at : {}", DateUtil.now()); return RandomUtil.randomInt(1, 11) % 2 == 0 ? SUCCESS : FAIL; } } ``` ### 2.6. 启动执行器 Run `SpringBootDemoTaskXxlJobApplication` ## 3. 配置定时任务 ### 3.1. 将启动的执行器添加到调度中心 执行器管理 - 新增执行器 ![image-20190808105910203](https://static.xkcoding.com/spring-boot-demo/2019-08-08-025910.png) ### 3.2. 添加定时任务 任务管理 - 新增 - 保存 ![image-20190808110127956](https://static.xkcoding.com/spring-boot-demo/2019-08-08-030128.png) ### 3.3. 启停定时任务 任务列表的操作列,拥有以下操作:执行、启动/停止、日志、编辑、删除 执行:单次触发任务,不影响定时逻辑 启动:启动定时任务 停止:停止定时任务 日志:查看当前任务执行日志 编辑:更新定时任务 删除:删除定时任务 ## 4. 使用API添加定时任务 > 实际场景中,如果添加定时任务都需要手动在 xxl-job-admin 去操作,这样可能比较麻烦,用户更希望在自己的页面,添加定时任务参数、定时调度表达式,然后通过 API 的方式添加定时任务 ### 4.1. 改造xxl-job-admin #### 4.1.1. 修改 JobGroupController.java ```java ... // 添加执行器列表 @RequestMapping("/list") @ResponseBody // 去除权限校验 @PermissionLimit(limit = false) public ReturnT> list(){ return new ReturnT<>(xxlJobGroupDao.findAll()); } ... ``` #### 4.1.2. 修改 JobInfoController.java ```java // 分别在 pageList、add、update、remove、pause、start、triggerJob 方法上添加注解,去除权限校验 @PermissionLimit(limit = false) ``` ### 4.2. 改造 执行器项目 #### 4.2.1. 添加手动触发类 ```java /** *

* 手动操作 xxl-job *

* * @author yangkai.shen * @date Created in 2019-08-07 14:58 */ @Slf4j @RestController @RequestMapping("/xxl-job") @RequiredArgsConstructor(onConstructor_ = @Autowired) public class ManualOperateController { private final static String baseUri = "http://127.0.0.1:18080/xxl-job-admin"; private final static String JOB_INFO_URI = "/jobinfo"; private final static String JOB_GROUP_URI = "/jobgroup"; /** * 任务组列表,xxl-job叫做触发器列表 */ @GetMapping("/group") public String xxlJobGroup() { HttpResponse execute = HttpUtil.createGet(baseUri + JOB_GROUP_URI + "/list").execute(); log.info("【execute】= {}", execute); return execute.body(); } /** * 分页任务列表 * * @param page 当前页,第一页 -> 0 * @param size 每页条数,默认10 * @return 分页任务列表 */ @GetMapping("/list") public String xxlJobList(Integer page, Integer size) { Map jobInfo = Maps.newHashMap(); jobInfo.put("start", page != null ? page : 0); jobInfo.put("length", size != null ? size : 10); jobInfo.put("jobGroup", 2); jobInfo.put("triggerStatus", -1); HttpResponse execute = HttpUtil.createGet(baseUri + JOB_INFO_URI + "/pageList").form(jobInfo).execute(); log.info("【execute】= {}", execute); return execute.body(); } /** * 测试手动保存任务 */ @GetMapping("/add") public String xxlJobAdd() { Map jobInfo = Maps.newHashMap(); jobInfo.put("jobGroup", 2); jobInfo.put("jobCron", "0 0/1 * * * ? *"); jobInfo.put("jobDesc", "手动添加的任务"); jobInfo.put("author", "admin"); jobInfo.put("executorRouteStrategy", "ROUND"); jobInfo.put("executorHandler", "demoTask"); jobInfo.put("executorParam", "手动添加的任务的参数"); jobInfo.put("executorBlockStrategy", ExecutorBlockStrategyEnum.SERIAL_EXECUTION); jobInfo.put("glueType", GlueTypeEnum.BEAN); HttpResponse execute = HttpUtil.createGet(baseUri + JOB_INFO_URI + "/add").form(jobInfo).execute(); log.info("【execute】= {}", execute); return execute.body(); } /** * 测试手动触发一次任务 */ @GetMapping("/trigger") public String xxlJobTrigger() { Map jobInfo = Maps.newHashMap(); jobInfo.put("id", 4); jobInfo.put("executorParam", JSONUtil.toJsonStr(jobInfo)); HttpResponse execute = HttpUtil.createGet(baseUri + JOB_INFO_URI + "/trigger").form(jobInfo).execute(); log.info("【execute】= {}", execute); return execute.body(); } /** * 测试手动删除任务 */ @GetMapping("/remove") public String xxlJobRemove() { Map jobInfo = Maps.newHashMap(); jobInfo.put("id", 4); HttpResponse execute = HttpUtil.createGet(baseUri + JOB_INFO_URI + "/remove").form(jobInfo).execute(); log.info("【execute】= {}", execute); return execute.body(); } /** * 测试手动停止任务 */ @GetMapping("/stop") public String xxlJobStop() { Map jobInfo = Maps.newHashMap(); jobInfo.put("id", 4); HttpResponse execute = HttpUtil.createGet(baseUri + JOB_INFO_URI + "/stop").form(jobInfo).execute(); log.info("【execute】= {}", execute); return execute.body(); } /** * 测试手动启动任务 */ @GetMapping("/start") public String xxlJobStart() { Map jobInfo = Maps.newHashMap(); jobInfo.put("id", 4); HttpResponse execute = HttpUtil.createGet(baseUri + JOB_INFO_URI + "/start").form(jobInfo).execute(); log.info("【execute】= {}", execute); return execute.body(); } } ``` > 后端其余代码请 clone 本项目,查看具体代码 ## 参考 - [《分布式任务调度平台xxl-job》](http://www.xuxueli.com/xxl-job/#/)