diff --git a/README.md b/README.md
index c7d33de..802d8dd 100644
--- a/README.md
+++ b/README.md
@@ -47,29 +47,30 @@
## Features
- 1、简单:支持通过Web页面对任务进行CRUD操作,操作简单,一分钟上手;
- 2、动态:支持动态修改任务状态、暂停/恢复任务,以及终止运行中任务,即时生效;
-- 3、调度中心HA(中心式):调度采用中心式设计,“调度中心”基于集群Quartz实现,可保证调度中心HA;
+- 3、调度中心HA(中心式):调度采用中心式设计,“调度中心”基于集群Quartz实现并支持集群部署,可保证调度中心HA;
- 4、执行器HA(分布式):任务分布式执行,任务"执行器"支持集群部署,可保证任务执行HA;
-- 5、任务Failover:执行器集群部署时,任务路由策略选择"故障转移"情况下调度失败时将会平滑切换执行器进行Failover;
-- 6、一致性:“调度中心”通过DB锁保证集群分布式调度的一致性, 一次任务调度只会触发一次执行;
-- 7、自定义任务参数:支持在线配置调度任务入参,即时生效;
-- 8、调度线程池:调度系统多线程触发调度运行,确保调度精确执行,不被堵塞;
-- 9、弹性扩容缩容:一旦有新执行器机器上线或者下线,下次调度时将会重新分配任务;
-- 10、邮件报警:任务失败时支持邮件报警,支持配置多邮件地址群发报警邮件;
-- 11、状态监控:支持实时监控任务进度;
-- 12、Rolling执行日志:支持在线查看调度结果,并且支持以Rolling方式实时查看执行器输出的完整的执行日志;
-- 13、GLUE:提供Web IDE,支持在线开发任务逻辑代码,动态发布,实时编译生效,省略部署上线的过程。支持30个版本的历史版本回溯。
-- 14、数据加密:调度中心和执行器之间的通讯进行数据加密,提升调度信息安全性;
-- 15、任务依赖:支持配置子任务依赖,当父任务执行结束且执行成功后将会主动触发一次子任务的执行, 多个子任务用逗号分隔;
-- 16、推送maven中央仓库: 将会把最新稳定版推送到maven中央仓库, 方便用户接入和使用;
-- 17、任务注册: 执行器会周期性自动注册任务, 调度中心将会自动发现注册的任务并触发执行。同时,也支持手动录入执行器地址;
-- 18、路由策略:执行器集群部署时提供丰富的路由策略,包括:第一个、最后一个、轮询、随机、一致性HASH、最不经常使用、最近最久未使用、故障转移、忙碌转移等;
-- 19、运行报表:支持实时查看运行数据,如任务数量、调度次数、执行器数量等;以及调度报表,如调度日期分布图,调度成功分布图等;
-- 20、脚本任务:支持以GLUE模式开发和运行脚本任务,包括Shell、Python等类型脚本;
-- 21、阻塞处理策略:调度过于密集执行器来不及处理时的处理策略,策略包括:单机串行(默认)、丢弃后续调度、覆盖之前调度;
-- 22、失败处理策略;调度失败时的处理策略,策略包括:失败告警(默认)、失败重试;
-- 23、分片广播任务:执行器集群部署时,任务路由策略选择"分片广播"情况下,一次任务调度将会广播触发对应集群中所有执行器执行一次任务,同时传递分片参数;可根据分片参数开发分片任务;
-- 24、动态分片:分片广播任务以执行器为维度进行分片,支持动态扩容执行器集群从而动态增加分片数量,协同进行业务处理;在进行大数据量业务操作时可显著提升任务处理能力和速度。
-- 25、事件触发:除了"Cron方式"和"任务依赖方式"触发任务执行之外,支持基于事件的触发任务方式。调度中心提供触发任务单次执行的API服务,可根据业务事件灵活触发。
+- 5、注册中心: 执行器会周期性自动注册任务, 调度中心将会自动发现注册的任务并触发执行。同时,也支持手动录入执行器地址;
+- 6、弹性扩容缩容:一旦有新执行器机器上线或者下线,下次调度时将会重新分配任务;
+- 7、路由策略:执行器集群部署时提供丰富的路由策略,包括:第一个、最后一个、轮询、随机、一致性HASH、最不经常使用、最近最久未使用、故障转移、忙碌转移等;
+- 8、故障转移:任务路由策略选择"故障转移"情况下,如果执行器集群中某一台机器故障,将会自动Failover切换到一台正常的执行器发送调度请求。
+- 9、失败处理策略;调度失败时的处理策略,策略包括:失败告警(默认)、失败重试;
+- 10、失败重试:调度中心调度失败且启用"失败重试"策略时,将会自动重试一次;执行器执行失败且回调失败重试状态时,也将会自动重试一次;
+- 11、阻塞处理策略:调度过于密集执行器来不及处理时的处理策略,策略包括:单机串行(默认)、丢弃后续调度、覆盖之前调度;
+- 12、分片广播任务:执行器集群部署时,任务路由策略选择"分片广播"情况下,一次任务调度将会广播触发集群中所有执行器执行一次任务,可根据分片参数开发分片任务;
+- 13、动态分片:分片广播任务以执行器为维度进行分片,支持动态扩容执行器集群从而动态增加分片数量,协同进行业务处理;在进行大数据量业务操作时可显著提升任务处理能力和速度。
+- 14、事件触发:除了"Cron方式"和"任务依赖方式"触发任务执行之外,支持基于事件的触发任务方式。调度中心提供触发任务单次执行的API服务,可根据业务事件灵活触发。
+- 15、任务进度监控:支持实时监控任务进度;
+- 16、Rolling实时日志:支持在线查看调度结果,并且支持以Rolling方式实时查看执行器输出的完整的执行日志;
+- 17、GLUE:提供Web IDE,支持在线开发任务逻辑代码,动态发布,实时编译生效,省略部署上线的过程。支持30个版本的历史版本回溯。
+- 18、脚本任务:支持以GLUE模式开发和运行脚本任务,包括Shell、Python、NodeJS等类型脚本;
+- 19、任务依赖:支持配置子任务依赖,当父任务执行结束且执行成功后将会主动触发一次子任务的执行, 多个子任务用逗号分隔;
+- 20、一致性:“调度中心”通过DB锁保证集群分布式调度的一致性, 一次任务调度只会触发一次执行;
+- 21、自定义任务参数:支持在线配置调度任务入参,即时生效;
+- 22、调度线程池:调度系统多线程触发调度运行,确保调度精确执行,不被堵塞;
+- 23、数据加密:调度中心和执行器之间的通讯进行数据加密,提升调度信息安全性;
+- 24、邮件报警:任务失败时支持邮件报警,支持配置多邮件地址群发报警邮件;
+- 25、推送maven中央仓库: 将会把最新稳定版推送到maven中央仓库, 方便用户接入和使用;
+- 26、运行报表:支持实时查看运行数据,如任务数量、调度次数、执行器数量等;以及调度报表,如调度日期分布图,调度成功分布图等;
## Development
@@ -83,8 +84,10 @@
于2017-05-13,在上海举办的 "[第62期开源中国源创会](https://www.oschina.net/event/2236961)" 的 "放码过来" 环节,我登台对XXL-JOB做了演讲,台下五百位在场观众反响热烈([图文回顾](https://www.oschina.net/question/2686220_2242120) )。
+于2017-12-11,XXL-JOB有幸参会《[InfoQ ArchSummit全球架构师峰会](http://bj2017.archsummit.com/)》,并被拍拍贷架构总监"杨波老师"在专题 "[微服务原理、基础架构和开源实践](http://bj2017.archsummit.com/training/2)" 中现场介绍。
+
> 我司大众点评目前已接入XXL-JOB,内部别名《Ferrari》(Ferrari基于XXL-JOB的V1.1版本定制而成,新接入应用推荐升级最新版本)。**
-据最新统计, 自2016-01-21接入至2017-07-07期间,该系统已调度约60万余次,表现优异。新接入应用推荐使用最新版本,因为经过数个大版本的更新,系统的任务模型、UI交互模型以及底层调度通讯模型都有了较大的优化和提升,核心功能更加稳定高效。
+据最新统计, 自2016-01-21接入至2017-12-01期间,该系统已调度约100万次,表现优异。新接入应用推荐使用最新版本,因为经过数个大版本的更新,系统的任务模型、UI交互模型以及底层调度通讯模型都有了较大的优化和提升,核心功能更加稳定高效。
至今,XXL-JOB已接入多家公司的线上产品线,接入场景如电商业务,O2O业务和大数据作业等,截止2016-07-19为止,XXL-JOB已接入的公司包括不限于:
@@ -145,6 +148,8 @@
- 55、中商惠民(北京)电子商务有限公司
- 56、凯京集团
- 57、华夏票联(北京)科技有限公司
+ - 58、拍拍贷
+ - 59、北京尚德机构在线教育有限公司
- ……
> 更多接入的公司,欢迎在 [登记地址](https://github.com/xuxueli/xxl-job/issues/1 ) 登记,登记仅仅为了产品推广。
diff --git "a/doc/XXL-JOB\345\256\230\346\226\271\346\226\207\346\241\243.md" "b/doc/XXL-JOB\345\256\230\346\226\271\346\226\207\346\241\243.md"
index a05fe3f..d2aaa47 100644
--- "a/doc/XXL-JOB\345\256\230\346\226\271\346\226\207\346\241\243.md"
+++ "b/doc/XXL-JOB\345\256\230\346\226\271\346\226\207\346\241\243.md"
@@ -16,29 +16,30 @@
### 1.2 特性
- 1、简单:支持通过Web页面对任务进行CRUD操作,操作简单,一分钟上手;
- 2、动态:支持动态修改任务状态、暂停/恢复任务,以及终止运行中任务,即时生效;
-- 3、调度中心HA(中心式):调度采用中心式设计,“调度中心”基于集群Quartz实现,可保证调度中心HA;
+- 3、调度中心HA(中心式):调度采用中心式设计,“调度中心”基于集群Quartz实现并支持集群部署,可保证调度中心HA;
- 4、执行器HA(分布式):任务分布式执行,任务"执行器"支持集群部署,可保证任务执行HA;
-- 5、任务Failover:执行器集群部署时,任务路由策略选择"故障转移"情况下调度失败时将会平滑切换执行器进行Failover;
-- 6、一致性:“调度中心”通过DB锁保证集群分布式调度的一致性, 一次任务调度只会触发一次执行;
-- 7、自定义任务参数:支持在线配置调度任务入参,即时生效;
-- 8、调度线程池:调度系统多线程触发调度运行,确保调度精确执行,不被堵塞;
-- 9、弹性扩容缩容:一旦有新执行器机器上线或者下线,下次调度时将会重新分配任务;
-- 10、邮件报警:任务失败时支持邮件报警,支持配置多邮件地址群发报警邮件;
-- 11、状态监控:支持实时监控任务进度;
-- 12、Rolling执行日志:支持在线查看调度结果,并且支持以Rolling方式实时查看执行器输出的完整的执行日志;
-- 13、GLUE:提供Web IDE,支持在线开发任务逻辑代码,动态发布,实时编译生效,省略部署上线的过程。支持30个版本的历史版本回溯。
-- 14、数据加密:调度中心和执行器之间的通讯进行数据加密,提升调度信息安全性;
-- 15、任务依赖:支持配置子任务依赖,当父任务执行结束且执行成功后将会主动触发一次子任务的执行, 多个子任务用逗号分隔;
-- 16、推送maven中央仓库: 将会把最新稳定版推送到maven中央仓库, 方便用户接入和使用;
-- 17、任务注册: 执行器会周期性自动注册任务, 调度中心将会自动发现注册的任务并触发执行。同时,也支持手动录入执行器地址;
-- 18、路由策略:执行器集群部署时提供丰富的路由策略,包括:第一个、最后一个、轮询、随机、一致性HASH、最不经常使用、最近最久未使用、故障转移、忙碌转移等;
-- 19、运行报表:支持实时查看运行数据,如任务数量、调度次数、执行器数量等;以及调度报表,如调度日期分布图,调度成功分布图等;
-- 20、脚本任务:支持以GLUE模式开发和运行脚本任务,包括Shell、Python等类型脚本;
-- 21、阻塞处理策略:调度过于密集执行器来不及处理时的处理策略,策略包括:单机串行(默认)、丢弃后续调度、覆盖之前调度;
-- 22、失败处理策略;调度失败时的处理策略,策略包括:失败告警(默认)、失败重试;
-- 23、分片广播任务:执行器集群部署时,任务路由策略选择"分片广播"情况下,一次任务调度将会广播触发集群中所有执行器执行一次任务,可根据分片参数开发分片任务;
-- 24、动态分片:分片广播任务以执行器为维度进行分片,支持动态扩容执行器集群从而动态增加分片数量,协同进行业务处理;在进行大数据量业务操作时可显著提升任务处理能力和速度。
-- 25、事件触发:除了"Cron方式"和"任务依赖方式"触发任务执行之外,支持基于事件的触发任务方式。调度中心提供触发任务单次执行的API服务,可根据业务事件灵活触发。
+- 5、注册中心: 执行器会周期性自动注册任务, 调度中心将会自动发现注册的任务并触发执行。同时,也支持手动录入执行器地址;
+- 6、弹性扩容缩容:一旦有新执行器机器上线或者下线,下次调度时将会重新分配任务;
+- 7、路由策略:执行器集群部署时提供丰富的路由策略,包括:第一个、最后一个、轮询、随机、一致性HASH、最不经常使用、最近最久未使用、故障转移、忙碌转移等;
+- 8、故障转移:任务路由策略选择"故障转移"情况下,如果执行器集群中某一台机器故障,将会自动Failover切换到一台正常的执行器发送调度请求。
+- 9、失败处理策略;调度失败时的处理策略,策略包括:失败告警(默认)、失败重试;
+- 10、失败重试:调度中心调度失败且启用"失败重试"策略时,将会自动重试一次;执行器执行失败且回调失败重试状态时,也将会自动重试一次;
+- 11、阻塞处理策略:调度过于密集执行器来不及处理时的处理策略,策略包括:单机串行(默认)、丢弃后续调度、覆盖之前调度;
+- 12、分片广播任务:执行器集群部署时,任务路由策略选择"分片广播"情况下,一次任务调度将会广播触发集群中所有执行器执行一次任务,可根据分片参数开发分片任务;
+- 13、动态分片:分片广播任务以执行器为维度进行分片,支持动态扩容执行器集群从而动态增加分片数量,协同进行业务处理;在进行大数据量业务操作时可显著提升任务处理能力和速度。
+- 14、事件触发:除了"Cron方式"和"任务依赖方式"触发任务执行之外,支持基于事件的触发任务方式。调度中心提供触发任务单次执行的API服务,可根据业务事件灵活触发。
+- 15、任务进度监控:支持实时监控任务进度;
+- 16、Rolling实时日志:支持在线查看调度结果,并且支持以Rolling方式实时查看执行器输出的完整的执行日志;
+- 17、GLUE:提供Web IDE,支持在线开发任务逻辑代码,动态发布,实时编译生效,省略部署上线的过程。支持30个版本的历史版本回溯。
+- 18、脚本任务:支持以GLUE模式开发和运行脚本任务,包括Shell、Python、NodeJS等类型脚本;
+- 19、任务依赖:支持配置子任务依赖,当父任务执行结束且执行成功后将会主动触发一次子任务的执行, 多个子任务用逗号分隔;
+- 20、一致性:“调度中心”通过DB锁保证集群分布式调度的一致性, 一次任务调度只会触发一次执行;
+- 21、自定义任务参数:支持在线配置调度任务入参,即时生效;
+- 22、调度线程池:调度系统多线程触发调度运行,确保调度精确执行,不被堵塞;
+- 23、数据加密:调度中心和执行器之间的通讯进行数据加密,提升调度信息安全性;
+- 24、邮件报警:任务失败时支持邮件报警,支持配置多邮件地址群发报警邮件;
+- 25、推送maven中央仓库: 将会把最新稳定版推送到maven中央仓库, 方便用户接入和使用;
+- 26、运行报表:支持实时查看运行数据,如任务数量、调度次数、执行器数量等;以及调度报表,如调度日期分布图,调度成功分布图等;
### 1.3 发展
于2015年中,我在github上创建XXL-JOB项目仓库并提交第一个commit,随之进行系统结构设计,UI选型,交互设计……
@@ -51,8 +52,10 @@
于2017-05-13,在上海举办的 "[第62期开源中国源创会](https://www.oschina.net/event/2236961)" 的 "放码过来" 环节,我登台对XXL-JOB做了演讲,台下五百位在场观众反响热烈([图文回顾](https://www.oschina.net/question/2686220_2242120) )。
+于2017-12-11,XXL-JOB有幸参会《[InfoQ ArchSummit全球架构师峰会](http://bj2017.archsummit.com/)》,并被拍拍贷架构总监"杨波老师"在专题 "[微服务原理、基础架构和开源实践](http://bj2017.archsummit.com/training/2)" 中现场介绍。
+
> 我司大众点评目前已接入XXL-JOB,内部别名《Ferrari》(Ferrari基于XXL-JOB的V1.1版本定制而成,新接入应用推荐升级最新版本)。
-据最新统计, 自2016-01-21接入至2017-07-07期间,该系统已调度约60万余次,表现优异。新接入应用推荐使用最新版本,因为经过数个大版本的更新,系统的任务模型、UI交互模型以及底层调度通讯模型都有了较大的优化和提升,核心功能更加稳定高效。
+据最新统计, 自2016-01-21接入至2017-12-01期间,该系统已调度约100万次,表现优异。新接入应用推荐使用最新版本,因为经过数个大版本的更新,系统的任务模型、UI交互模型以及底层调度通讯模型都有了较大的优化和提升,核心功能更加稳定高效。
至今,XXL-JOB已接入多家公司的线上产品线,接入场景如电商业务,O2O业务和大数据作业等,截止2016-07-19为止,XXL-JOB已接入的公司包括不限于:
@@ -113,6 +116,8 @@
- 55、中商惠民(北京)电子商务有限公司
- 56、凯京集团
- 57、华夏票联(北京)科技有限公司
+ - 58、拍拍贷
+ - 59、北京尚德机构在线教育有限公司
- ……
> 更多接入的公司,欢迎在 [登记地址](https://github.com/xuxueli/xxl-job/issues/1 ) 登记,登记仅仅为了产品推广。
@@ -181,6 +186,7 @@
:xxl-job-executor-sample-spring:Spring版本,通过Spring容器管理执行器,比较通用,推荐这种方式;
:xxl-job-executor-sample-springboot:Springboot版本,通过Springboot管理执行器;
:xxl-job-executor-sample-jfinal:JFinal版本,通过JFinal管理执行器;
+ :xxl-job-executor-sample-nutz:Nutz版本,通过Nutz管理执行器;
### 2.3 配置部署“调度中心”
@@ -226,15 +232,17 @@
至此“调度中心”项目已经部署成功。
#### 步骤三:调度中心集群(可选):
-调度中心支持集群部署,提升调度系统可用性。
+调度中心支持集群部署,提升调度系统容灾和可用性。
-集群部署唯一要求为:保证每个集群节点配置(db和登陆账号等)保持一致。调度中心通过db配置区分不同集群。
-
-调度中心在集群部署时可通过nginx负载均衡,此时可以为集群分配一个域名。该域名一方面可以用于访问,另一方面也可以用于配置执行器回调地址。
+调度中心集群部署时,几点要求和建议:
+- DB配置保持一致;
+- 登陆账号配置保持一致;
+- 集群机器时钟保持一致(单机集群忽视);
+- 建议:推荐通过nginx为调度中心集群做负载均衡,分配域名。调度中心访问、执行器回调配置、调用API服务等操作均通过该域名进行。
### 2.4 配置部署“执行器项目”
- “执行器”项目:xxl-job-executor-sample-spring (如新建执行器项目,可参考该Sample示例执行器项目的配置步骤;)
+ “执行器”项目:xxl-job-executor-sample-spring (提供多种版本执行器供选择,现以Spring版本为例,可直接使用,也可以参考其并将现有项目改造成执行器)
作用:负责接收“调度中心”的调度并执行;可直接部署执行器,也可以将执行器集成到现有业务项目中。
#### 步骤一:maven依赖
@@ -278,15 +286,15 @@
-
+
-
+
-
+
```
@@ -316,7 +324,8 @@

-
+
+
#### 步骤二:“GLUE模式(Java)” 任务开发:
请点击任务右侧 “GLUE” 按钮,进入 “GLUE编辑器开发界面” ,见下图。“GLUE模式(Java)” 运行模式的任务默认已经初始化了示例任务代码,即打印Hello World。
@@ -365,14 +374,14 @@
GLUE模式(Python):任务以源码方式维护在调度中心;该模式的任务实际上是一段 "python" 脚本;
GLUE模式(NodeJS):任务以源码方式维护在调度中心;该模式的任务实际上是一段 "nodejs" 脚本;
- JobHandler:运行模式为 "BEAN模式" 时生效,对应执行器中新开发的JobHandler类“@JobHandler”注解自定义的value值;
- - 子任务Key:每个任务都拥有一个唯一的任务Key(任务Key可以从任务列表获取),当本任务执行结束并且执行成功时,将会触发子任务Key所对应的任务的一次主动调度。
+ - 子任务:每个任务都拥有一个唯一的任务ID(任务ID可以从任务列表获取),当本任务执行结束并且执行成功时,将会触发子任务ID所对应的任务的一次主动调度。
- 阻塞处理策略:调度过于密集执行器来不及处理时的处理策略;
单机串行(默认):调度请求进入单机执行器后,调度请求进入FIFO队列并以串行方式运行;
丢弃后续调度:调度请求进入单机执行器后,发现执行器存在运行的调度任务,本次请求将会被丢弃并标记为失败;
覆盖之前调度:调度请求进入单机执行器后,发现执行器存在运行的调度任务,将会终止运行中的调度任务并清空队列,然后运行本地调度任务;
- 失败处理策略;调度失败时的处理策略;
- 失败告警(默认):调度失败时,将会触发失败报警,如发送报警邮件;
- 失败重试:调度失败时,将会主动进行一次失败重试调度,重试调度后仍然失败将会触发一失败告警。注意当任务以failover方式路由时,每次失败重试将会触发新一轮路由。
+ 失败告警(默认):调度失败和执行失败时,都将会触发失败报警,默认会发送报警邮件;
+ 失败重试:调度失败时,除了进行失败告警之外,将会自动重试一次;注意在执行失败时不会重试,而是根据回调返回值判断是否重试;
- 执行参数:任务执行所需的参数,多个参数时用逗号分隔,任务执行时将会把多个参数转换成数组传入;
- 报警邮件:任务调度失败时邮件通知的邮箱地址,支持配置多邮箱地址,配置多个邮箱地址时用逗号分隔;
- 负责人:任务的负责人;
@@ -381,9 +390,11 @@
任务逻辑以JobHandler的形式存在于“执行器”所在项目中,开发流程如下:
#### 步骤一:执行器项目中,开发JobHandler:
- - 1、 新建一个继承com.xxl.job.core.handler.IJobHandler的Java类;
- - 2、 该类被Spring容器扫描为Bean实例,如加“@Component”注解;
- - 3、 添加 “@JobHandler(value="自定义jobhandler名称")”注解,注解的value值为自定义的JobHandler名称,该名称对应的是调度中心新建任务的JobHandler属性的值。
+
+ - 1、继承"IJobHandler":“com.xxl.job.core.handler.IJobHandler”;
+ - 2、注册到Spring容器:添加“@Component”注解,被Spring容器扫描为Bean实例;
+ - 3、注册到执行器工厂:添加“@JobHandler(value="自定义jobhandler名称")”注解,注解value值对应的是调度中心新建任务的JobHandler属性的值。
+ - 4、执行日志:需要通过 "XxlJobLogger.log" 打印执行日志;
(可参考Sample示例执行器中的DemoJobHandler,见下图)

@@ -391,7 +402,7 @@
#### 步骤二:调度中心,新建调度任务
参考上文“配置属性详细说明”对新建的任务进行参数配置,运行模式选中 "BEAN模式",JobHandler属性填写任务注解“@JobHandler”中定义的值;
-
+
### 3.2 GLUE模式(Java)
任务以源码方式维护在调度中心,支持通过Web IDE在线更新,实时编译和生效,因此不需要指定JobHandler。开发流程如下:
@@ -399,7 +410,7 @@
#### 步骤一:调度中心,新建调度任务:
参考上文“配置属性详细说明”对新建的任务进行参数配置,运行模式选中 "GLUE模式(Java)";
-
+
#### 步骤二:开发任务代码:
选中指定任务,点击该任务右侧“GLUE”按钮,将会前往GLUE任务的Web IDE界面,在该界面支持对任务代码进行开发(也可以在IDE中开发完成后,复制粘贴到编辑中)。
@@ -453,7 +464,8 @@
2、"执行器列表" 中显示在线的执行器列表, 可通过"OnLine 机器"查看对应执行器的集群机器。
点击按钮 "+新增执行器" 弹框如下图, 可新增执行器配置:
-
+
+
### 执行器属性说明
@@ -493,7 +505,7 @@

-
+
调度时间:"调度中心"触发本次调度并向"执行器"发送任务执行信号的时间;
调度结果:"调度中心"触发本次调度的结果,200表示成功,500或其他表示失败;
@@ -677,7 +689,6 @@
执行器如若集群部署,调度中心将会感知到在线的所有执行器,如“127.0.0.1:9997, 127.0.0.1:9998, 127.0.0.1:9999”。
当任务"路由策略"选择"故障转移(FAILOVER)"时,当调度中心每次发起调度请求时,会按照顺序对执行器发出心跳检测请求,第一个检测为存活状态的执行器将会被选定并发送调度请求。
-
调度成功后,可在日志监控界面查看“调度备注”,如下;

@@ -688,12 +699,10 @@
#### 5.4.9 调度日志
调度中心每次进行任务调度,都会记录一条任务日志,任务日志主要包括以下三部分内容:
-- 任务信息:包括“执行器地址”、“JobHandler”和“执行参数”等属性,根据这些参数,可以精确的定位任务执行的具体机器和任务代码;
+- 任务信息:包括“执行器地址”、“JobHandler”和“执行参数”等属性,点击任务ID按钮可查看,根据这些参数,可以精确的定位任务执行的具体机器和任务代码;
- 调度信息:包括“调度时间”、“调度结果”和“调度日志”等,根据这些参数,可以了解“调度中心”发起调度请求时具体情况。
- 执行信息:包括“执行时间”、“执行结果”和“执行日志”等,根据这些参数,可以了解在“执行器”端任务执行的具体情况;
-
-
调度日志,针对单次调度,属性说明如下:
- 执行器地址:任务执行的机器地址;
- JobHandler:Bean模式表示任务执行的JobHandler名称;
@@ -707,9 +716,9 @@
- 执行日志:任务执行过程中,业务代码中打印的完整执行日志,见“4.7 查看执行日志”;
#### 5.4.10 任务依赖
-原理:XXL-JOB中每个任务都对应有一个任务Key,同时,每个任务支持设置属性“子任务Key”,因此,通过“任务Key”可以匹配任务依赖关系。
+原理:XXL-JOB中每个任务都对应有一个任务ID,同时,每个任务支持设置属性“子任务ID”,因此,通过“任务ID”可以匹配任务依赖关系。
-当父任务执行结束并且执行成功时,将会根据“子任务Key”匹配子任务依赖,如果匹配到子任务,将会主动触发一次子任务的执行。
+当父任务执行结束并且执行成功时,将会根据“子任务ID”匹配子任务依赖,如果匹配到子任务,将会主动触发一次子任务的执行。
在任务日志界面,点击任务的“执行备注”的“查看”按钮,可以看到匹配子任务以及触发子任务执行的日志信息,如无信息则表示未触发子任务执行,可参考下图。
@@ -738,9 +747,7 @@
#### 5.5.4 执行器
-执行器实际上是一个内嵌的Jetty服务器,默认端口9999,如下图配置文件所示(参数:xxl.job.executor.port)。
-
-
+执行器实际上是一个内嵌的Jetty服务器,默认端口9999(配置项:xxl.job.executor.port)。
在项目启动时,执行器会通过“@JobHandler”识别Spring容器中“Bean模式任务”,以注解的value属性为key管理起来。
@@ -786,11 +793,21 @@
"分片广播" 以执行器为维度进行分片,支持动态扩容执行器集群从而动态增加分片数量,协同进行业务处理;在进行大数据量业务操作时可显著提升任务处理能力和速度。
-"分片广播" 和普通任务开发流程一致,不同之处在于可以可以获取分片参数,获取分片参数对象的代码如下(可参考Sample示例执行器中的示例任务"ShardingJobHandler" ):
+"分片广播" 和普通任务开发流程一致,不同之处在于可以可以获取分片参数,获取分片参数进行分片业务处理。
- ShardingUtil.ShardingVO shardingVO = ShardingUtil.getShardingVo();
+- Java语言任务获取分片参数方式:BEAN、GLUE模式(Java)
+```
+// 可参考Sample示例执行器中的示例任务"ShardingJobHandler"了解试用
+ShardingUtil.ShardingVO shardingVO = ShardingUtil.getShardingVo();
+```
+- 脚本语言任务获取分片参数方式:GLUE模式(Shell)、GLUE模式(Python)、GLUE模式(Nodejs)
+```
+// 脚本任务入参固定为三个,依次为:任务传参、分片序号、分片总数。以Shell模式任务为例,获取分片参数代码如下
+echo "分片序号 index = $2"
+echo "分片总数 total = $3"
+```
-该分片参数对象拥有两个属性:
+分片参数属性说明:
index:当前分片序号(从0开始),执行器集群列表中当前执行器的序号;
total:总分片数,执行器集群的总机器数量;
@@ -819,7 +836,7 @@
调度中心API服务位置:com.xxl.job.core.biz.AdminBiz.java
-调度中心API服务请求参考代码:com.xxl.job.dao.impl.AdminBizTest.java
+调度中心API服务请求参考代码:com.xxl.job.adminbiz.AdminBizTest.java
### 5.12 执行器API服务
执行器提供了API服务,供调度中心选择使用,目前提供的API服务有:
@@ -834,6 +851,15 @@
执行器API服务请求参考代码:com.xxl.executor.test.DemoJobHandlerTest
+### 5.13 故障转移 & 失败重试
+一次完整任务流程包括"调度(调度中心) + 执行(执行器)"两个阶段。
+
+- "故障转移"发生在调度阶段,在执行器集群部署时,如果某一台执行器发生故障,该策略支持自动进行Failover切换到一台正常的执行器机器并且完成调度请求流程。
+- "失败重试"发生在"调度 + 执行"两个阶段,如下:
+ - 调度中心调度失败时,任务失败处理策略选择"失败重试",将会自动重试一次;
+ - 执行器运行失败时,任务执行结果返回"失败重试(IJobHandler.FAIL_RETRY)"回调,将会自动重试一次;
+
+
## 六、版本更新日志
### 6.1 版本 V1.1.x,新特性[2015-12-05]
**【于V1.1.x版本,XXL-JOB正式应用于我司,内部定制别名为 “Ferrari”,新接入应用推荐使用最新版本】**
@@ -1049,40 +1075,51 @@
- 10、任务日志文件路径时间戳格式化时SimpleDateFormat并发问题解决;
### 6.20 版本 V1.9.0 特性[迭代中]
-- 1、新增任务运行模式 "GLUE模式(NodeJS) ",支持NodeJS脚本任务;
-- 2、失败告警策略扩展:默认提供邮件失败告警,可扩展短信等,扩展代码位置为 "JobFailMonitorHelper.failAlarm";
-- 3、修复任务监控线程被耗时任务阻塞的问题;
-- 4、修复任务监控线程无法监控任务触发和执行状态均未0的问题;
-- 5、调度中心项目日志配置改为xml文件格式;
-- 6、执行器动态代理对象,拦截非业务方法的执行;
-- 7、底层系统日志级别规范调整,清理遗留代码;
-- 8、修改JobThread捕获Error错误不更新JobLog的问题;
-- 9、任务注解调整为 “@JobHandler”,与任务注解统一;
-- 10、执行器端口支持随机生成(小于等于0时),避免端口定义冲突;
-- 11、任务Cron长度扩展支持至128位;
-- 12、调度报表优化,支持时间区间筛选;
+- 1、新增Nutz执行器Sample示例项目;
+- 2、新增任务运行模式 "GLUE模式(NodeJS) ",支持NodeJS脚本任务;
+- 3、脚本任务Shell、Python和Nodejs等支持获取分片参数;
+- 4、失败重试,完整支持:调度中心调度失败且启用"失败重试"策略时,将会自动重试一次;执行器执行失败且回调失败重试状态(新增失败重试状态返回值)时,也将会自动重试一次;
+- 5、失败告警策略扩展:默认提供邮件失败告警,可扩展短信等,扩展代码位置为 "JobFailMonitorHelper.failAlarm";
+- 6、执行器端口支持自动生成(小于等于0时),避免端口定义冲突;
+- 7、调度报表优化,支持时间区间筛选;
+- 8、Log组件支持输出异常栈信息,底层实现优化;
+- 9、告警邮件样式优化,调整为表格形式,邮件组件调整为commons-email简化邮件操作;
+- 10、项目依赖升级,如spring、jackson等;
+- 11、任务日志,记录发起调度的机器信息;
+- 12、交互优化,如登陆注销;
+- 13、任务Cron长度扩展支持至128位,支持负责类型Cron设置;
+- 14、执行器地址录入交互优化,地址长度扩展支持至512位,支持大规模执行器集群配置;
+- 15、任务参数“IJobHandler.execute”入参改为“String params”,增强入参通用性。
+- 16、JobHandler提供init/destroy方法,支持在JobHandler初始化和销毁时进行附加操作;
+- 17、任务注解调整为 “@JobHandler”,与任务抽象接口统一;
+- 18、修复任务监控线程被耗时任务阻塞的问题;
+- 19、修复任务监控线程无法监控任务触发和执行状态均未0的问题;
+- 20、执行器动态代理对象,拦截非业务方法的执行;
+- 21、修复JobThread捕获Error错误不更新JobLog的问题;
+- 22、修复任务列表界面左侧菜单合并时样式错乱问题;
+- 23、调度中心项目日志配置改为xml文件格式;
+- 24、Log地址格式兼容,支持非"/"结尾路径配置;
+- 25、底层系统日志级别规范调整,清理遗留代码;
+- 26、建表SQL优化,支持同步创建制定编码的库和表;
+- 27、系统安全性优化,登陆Token写Cookie时进行MD5加密,同时Cookie启用HttpOnly;
+- 28、新增"任务ID"属性,移除"JobKey"属性,前者承担所有功能,方便后续增强任务依赖功能。
+- 29、任务循环依赖问题修复,避免子任务与父任务重复导致的调度死循环;
### TODO LIST
- 1、任务权限管理:执行器为粒度分配权限,核心操作校验权限;
- 2、任务分片路由:分片采用一致性Hash算法计算出尽量稳定的分片顺序,即使注册机器存在波动也不会引起分批分片顺序大的波动;目前采用IP自然排序,可以满足需求,待定;
-- 3、失败重试完整支持:任务流程分为触发和执行,目前仅支持触发失败的重试;后续支持任务执行失败的重试,通过任务返回值判断。
+- 3、任务单机多线程:提升任务单机并行处理能力;
- 4、回调失败丢包问题:执行器回调失败写文件,重启或周期性回调重试;调度中心周期性请求并同步未回调的执行结果;
- 5、任务依赖,流程图,子任务+会签任务,各节点日志;
- 6、调度任务优先级;
- 7、移除quartz依赖,重写调度模块:新增或恢复任务时将下次执行记录插入delayqueue,调度中心集群竞争分布式锁,成功节点批量加载到期delayqueue数据,批量执行。
- 8、springboot 和 docker镜像,并且推送docker镜像到中央仓库,更进一步实现产品开箱即用;
- 9、国际化:调度中心界面。
-- 10、任务类方法"IJobHandler.execute"的参数类型改为"string",进一步方便参数传递;任务注解和任务类统一并改为"JobHandler"";
-- 11、任务日志,记录发起调度的机器信息;
-- 12、任务告警逻辑调整:任务调度,以及任务回调失败时,均推送监控队列。后期考虑通过任务Log字段控制告警状态;
-- 13、执行器Log清理功能:调度中心Log删除时同步删除执行器中的Log文件;
-- 14、脚本任务 Shell、Python和Nodejs,如何友好获取分片参数;
-- 15、Bean模式任务,JobHandler自动从执行器中查询展示为下拉框,选择后自动填充任务名称等属性;
-- 16、任务告警邮件优化,调整为表格形式;
-- 17、JobHandler提供 init/destroy 方法,支持自定义任务线程销毁逻辑;
-- 18、cron表达式的最大长度调整,兼容复杂类型cron;
-- 19、执行器回调地址/日志地址格式兼容,是否已"/"结尾均支持;
-- 20、任务单机多线程:提升任务单机并行处理能力;
+- 10、任务告警逻辑调整:任务调度,以及任务回调失败时,均推送监控队列。后期考虑通过任务Log字段控制告警状态;
+- 11、执行器Log清理功能:调度中心Log删除时同步删除执行器中的Log文件;
+- 12、Bean模式任务,JobHandler自动从执行器中查询展示为下拉框,选择后自动填充任务名称等属性;
+- 13、API事件触发类型任务(更类似MQ消息)支持"动态传参、延时消费";该类型任务不走Quartz,单独建立MQ消息表,调度中心竞争触发;
+- 14、任务依赖增强,新增任务类型 "流程任务",流程节点可挂载普通类型任务,承担任务依赖功能。现有子任务模型取消;需要考虑任务依赖死循环问题;
## 七、其他
diff --git a/doc/db/tables_xxl_job.sql b/doc/db/tables_xxl_job.sql
index 87bb23b..ed32895 100644
--- a/doc/db/tables_xxl_job.sql
+++ b/doc/db/tables_xxl_job.sql
@@ -1,6 +1,8 @@
CREATE database if NOT EXISTS `xxl-job` default character set utf8 collate utf8_general_ci;
use `xxl-job`;
+
+
CREATE TABLE XXL_JOB_QRTZ_JOB_DETAILS
(
SCHED_NAME VARCHAR(120) NOT NULL,
@@ -158,14 +160,14 @@
`alarm_email` varchar(255) DEFAULT NULL COMMENT '报警邮件',
`executor_route_strategy` varchar(50) DEFAULT NULL COMMENT '执行器路由策略',
`executor_handler` varchar(255) DEFAULT NULL COMMENT '执行器任务handler',
- `executor_param` varchar(255) DEFAULT NULL COMMENT '执行器任务参数',
+ `executor_param` varchar(512) DEFAULT NULL COMMENT '执行器任务参数',
`executor_block_strategy` varchar(50) DEFAULT NULL COMMENT '阻塞处理策略',
`executor_fail_strategy` varchar(50) DEFAULT NULL COMMENT '失败处理策略',
`glue_type` varchar(50) NOT NULL COMMENT 'GLUE类型',
`glue_source` text COMMENT 'GLUE源代码',
`glue_remark` varchar(128) DEFAULT NULL COMMENT 'GLUE备注',
`glue_updatetime` datetime DEFAULT NULL COMMENT 'GLUE更新时间',
- `child_jobkey` varchar(255) DEFAULT NULL COMMENT '子任务Key',
+ `child_jobid` varchar(255) DEFAULT NULL COMMENT '子任务ID,多个逗号分隔',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
@@ -176,7 +178,7 @@
`glue_type` varchar(50) DEFAULT NULL COMMENT 'GLUE类型',
`executor_address` varchar(255) DEFAULT NULL COMMENT '执行器地址,本次执行的地址',
`executor_handler` varchar(255) DEFAULT NULL COMMENT '执行器任务handler',
- `executor_param` varchar(255) DEFAULT NULL COMMENT 'executor_param',
+ `executor_param` varchar(512) DEFAULT NULL COMMENT '执行器任务参数',
`trigger_time` datetime DEFAULT NULL COMMENT '调度-时间',
`trigger_code` varchar(255) NOT NULL DEFAULT '0' COMMENT '调度-结果',
`trigger_msg` varchar(2048) DEFAULT NULL COMMENT '调度-日志',
@@ -212,7 +214,7 @@
`title` varchar(12) NOT NULL COMMENT '执行器名称',
`order` tinyint(4) NOT NULL DEFAULT '0' COMMENT '排序',
`address_type` tinyint(4) NOT NULL DEFAULT '0' COMMENT '执行器地址类型:0=自动注册、1=手动录入',
- `address_list` varchar(200) DEFAULT NULL COMMENT '执行器地址列表,多地址逗号分隔',
+ `address_list` varchar(512) DEFAULT NULL COMMENT '执行器地址列表,多地址逗号分隔',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
diff --git a/pom.xml b/pom.xml
index d2f3134..f7f3d33 100644
--- a/pom.xml
+++ b/pom.xml
@@ -20,31 +20,31 @@
3.0.1
2.2
-
3.2.18.RELEASE
-
1.9.13
-
1.8.7
+
4.3.13.RELEASE
+
2.9.3
+
1.8.13
1.7.25
-
2.3.20
-
4.11
+
2.3.23
+
4.12
9.2.22.v20170606
-
4.0.38
-
4.3.6
-
+
4.0.51
+
4.5.4
+
1.3
-
1.9.2
-
2.6
+
4.1
+
3.7
+
1.5
0.9.5.2
-
5.1.29
-
1.2.2
-
3.2.8
+
5.1.45
+
1.3.1
+
3.4.5
-
2.4.5
-
1.4.6
+
2.4.13
2.3.0
-
1.5.6.RELEASE
+
1.5.9.RELEASE
diff --git a/xxl-job-admin/pom.xml b/xxl-job-admin/pom.xml
index b86c7eb..2498c23 100644
--- a/xxl-job-admin/pom.xml
+++ b/xxl-job-admin/pom.xml
@@ -40,36 +40,9 @@
- org.codehaus.jackson
- jackson-mapper-asl
- ${jackson-mapper-asl.version}
-
-
-
-
- org.slf4j
- slf4j-log4j12
- ${slf4j-api.version}
-
-
-
-
- org.freemarker
- freemarker
- ${freemarker.version}
-
-
-
-
- commons-beanutils
- commons-beanutils
- ${commons-beanutils.version}
-
-
-
- commons-lang
- commons-lang
- ${commons-lang.version}
+ com.fasterxml.jackson.core
+ jackson-databind
+ ${jackson.version}
@@ -83,7 +56,19 @@
jsp-api
${jsp-api.version}
+
+
+ org.freemarker
+ freemarker
+ ${freemarker.version}
+
+
+
+ org.slf4j
+ slf4j-log4j12
+ ${slf4j-api.version}
+
junit
@@ -92,6 +77,25 @@
test
+
+
+ org.apache.commons
+ commons-collections4
+ ${commons-collections4.version}
+
+
+
+ org.apache.commons
+ commons-lang3
+ ${commons-lang3.version}
+
+
+
+ org.apache.commons
+ commons-email
+ ${commons-email.version}
+
+
com.mchange
@@ -116,7 +120,6 @@
${mybatis.version}
-
org.apache.httpcomponents
@@ -124,13 +127,6 @@
${httpclient.version}
-
-
- javax.mail
- mail
- ${mail.version}
-
-
org.quartz-scheduler
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/IndexController.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/IndexController.java
index cc3c6120..f4b1cad 100644
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/IndexController.java
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/IndexController.java
@@ -2,10 +2,9 @@
import com.xxl.job.admin.controller.annotation.PermessionLimit;
import com.xxl.job.admin.controller.interceptor.PermissionInterceptor;
-import com.xxl.job.admin.core.util.PropertiesUtil;
import com.xxl.job.admin.service.XxlJobService;
import com.xxl.job.core.biz.model.ReturnT;
-import org.apache.commons.lang.StringUtils;
+import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.propertyeditors.CustomDateEditor;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
@@ -61,18 +60,21 @@
@ResponseBody
@PermessionLimit(limit=false)
public ReturnT loginDo(HttpServletRequest request, HttpServletResponse response, String userName, String password, String ifRemember){
- if (!PermissionInterceptor.ifLogin(request)) {
- if (StringUtils.isNotBlank(userName) && StringUtils.isNotBlank(password)
- && PropertiesUtil.getString("xxl.job.login.username").equals(userName)
- && PropertiesUtil.getString("xxl.job.login.password").equals(password)) {
- boolean ifRem = false;
- if (StringUtils.isNotBlank(ifRemember) && "on".equals(ifRemember)) {
- ifRem = true;
- }
- PermissionInterceptor.login(response, ifRem);
- } else {
- return new ReturnT(500, "账号或密码错误");
- }
+ // valid
+ if (PermissionInterceptor.ifLogin(request)) {
+ return ReturnT.SUCCESS;
+ }
+
+ // param
+ if (StringUtils.isBlank(userName) || StringUtils.isBlank(password)){
+ return new ReturnT(500, "账号或密码为空");
+ }
+ boolean ifRem = (StringUtils.isNotBlank(ifRemember) && "on".equals(ifRemember))?true:false;
+
+ // do login
+ boolean loginRet = PermissionInterceptor.login(response, userName, password, ifRem);
+ if (!loginRet) {
+ return new ReturnT(500, "账号或密码错误");
}
return ReturnT.SUCCESS;
}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobGroupController.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobGroupController.java
index 496d34e..6211f3c 100644
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobGroupController.java
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobGroupController.java
@@ -4,7 +4,7 @@
import com.xxl.job.admin.dao.XxlJobGroupDao;
import com.xxl.job.admin.dao.XxlJobInfoDao;
import com.xxl.job.core.biz.model.ReturnT;
-import org.apache.commons.lang.StringUtils;
+import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobLogController.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobLogController.java
index f319273..a31536c 100644
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobLogController.java
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobLogController.java
@@ -11,8 +11,8 @@
import com.xxl.job.core.biz.model.LogResult;
import com.xxl.job.core.biz.model.ReturnT;
import com.xxl.job.core.rpc.netcom.NetComClientProxy;
-import org.apache.commons.lang.StringUtils;
-import org.apache.commons.lang.time.DateUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.time.DateUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/interceptor/CookieInterceptor.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/interceptor/CookieInterceptor.java
index 8072b45..bd84534 100644
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/interceptor/CookieInterceptor.java
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/interceptor/CookieInterceptor.java
@@ -1,17 +1,17 @@
package com.xxl.job.admin.controller.interceptor;
-import java.util.HashMap;
+import org.apache.commons.lang3.ArrayUtils;
+import org.springframework.web.servlet.ModelAndView;
+import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
-
-import org.apache.commons.lang.ArrayUtils;
-import org.springframework.web.servlet.ModelAndView;
-import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
+import java.util.HashMap;
/**
* push cookies to model as cookieMap
+ *
* @author xuxueli 2015-12-12 18:09:04
*/
public class CookieInterceptor extends HandlerInterceptorAdapter {
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/interceptor/PermissionInterceptor.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/interceptor/PermissionInterceptor.java
index ab565e1..1d6facf 100644
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/interceptor/PermissionInterceptor.java
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/interceptor/PermissionInterceptor.java
@@ -3,6 +3,7 @@
import com.xxl.job.admin.controller.annotation.PermessionLimit;
import com.xxl.job.admin.core.util.CookieUtil;
import com.xxl.job.admin.core.util.PropertiesUtil;
+import org.apache.commons.codec.digest.DigestUtils;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
@@ -12,20 +13,38 @@
/**
* 权限拦截, 简易版
+ *
* @author xuxueli 2015-12-12 18:09:04
*/
public class PermissionInterceptor extends HandlerInterceptorAdapter {
-
- public static final String LOGIN_IDENTITY_KEY = "LOGIN_IDENTITY";
+
+
+ public static final String LOGIN_IDENTITY_KEY = "XXL_JOB_LOGIN_IDENTITY";
public static final String LOGIN_IDENTITY_TOKEN;
static {
String username = PropertiesUtil.getString("xxl.job.login.username");
String password = PropertiesUtil.getString("xxl.job.login.password");
- String temp = username + "_" + password;
- LOGIN_IDENTITY_TOKEN = new BigInteger(1, temp.getBytes()).toString(16);
+
+ // login token
+ String tokenTmp = DigestUtils.md5Hex(username + "_" + password);
+ tokenTmp = new BigInteger(1, tokenTmp.getBytes()).toString(16);
+
+ LOGIN_IDENTITY_TOKEN = tokenTmp;
}
-
- public static boolean login(HttpServletResponse response, boolean ifRemember){
+
+
+
+ public static boolean login(HttpServletResponse response, String username, String password, boolean ifRemember){
+
+ // login token
+ String tokenTmp = DigestUtils.md5Hex(username + "_" + password);
+ tokenTmp = new BigInteger(1, tokenTmp.getBytes()).toString(16);
+
+ if (!LOGIN_IDENTITY_TOKEN.equals(tokenTmp)){
+ return false;
+ }
+
+ // do login
CookieUtil.set(response, LOGIN_IDENTITY_KEY, LOGIN_IDENTITY_TOKEN, ifRemember);
return true;
}
@@ -40,6 +59,8 @@
return true;
}
+
+
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobGroup.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobGroup.java
index 85fb4a1..040c922 100644
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobGroup.java
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobGroup.java
@@ -1,6 +1,6 @@
package com.xxl.job.admin.core.model;
-import org.apache.commons.lang.StringUtils;
+import org.apache.commons.lang3.StringUtils;
import java.util.ArrayList;
import java.util.Arrays;
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobInfo.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobInfo.java
index ec1138d..51847ac 100644
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobInfo.java
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobInfo.java
@@ -31,7 +31,7 @@
private String glueRemark; // GLUE备注
private Date glueUpdatetime; // GLUE更新时间
- private String childJobKey; // 子任务Key
+ private String childJobId; // 子任务ID,多个逗号分隔
// copy from quartz
private String jobStatus; // 任务状态 【base on quartz】
@@ -172,12 +172,12 @@
this.glueUpdatetime = glueUpdatetime;
}
- public String getChildJobKey() {
- return childJobKey;
+ public String getChildJobId() {
+ return childJobId;
}
- public void setChildJobKey(String childJobKey) {
- this.childJobKey = childJobKey;
+ public void setChildJobId(String childJobId) {
+ this.childJobId = childJobId;
}
public String getJobStatus() {
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobFailMonitorHelper.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobFailMonitorHelper.java
index a19c178..c9f7f04 100644
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobFailMonitorHelper.java
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobFailMonitorHelper.java
@@ -6,7 +6,8 @@
import com.xxl.job.admin.core.schedule.XxlJobDynamicScheduler;
import com.xxl.job.admin.core.util.MailUtil;
import com.xxl.job.core.biz.model.ReturnT;
-import org.apache.commons.collections.CollectionUtils;
+import com.xxl.job.core.handler.IJobHandler;
+import org.apache.commons.collections4.CollectionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -27,6 +28,8 @@
return instance;
}
+ // ---------------------- monitor ----------------------
+
private LinkedBlockingQueue queue = new LinkedBlockingQueue(0xfff8);
private Thread monitorThread;
@@ -51,13 +54,15 @@
if (log == null) {
continue;
}
- if (ReturnT.SUCCESS_CODE == log.getTriggerCode() && log.getHandleCode() == 0) {
+ if (IJobHandler.SUCCESS.getCode() == log.getTriggerCode() && log.getHandleCode() == 0) {
JobFailMonitorHelper.monitor(jobLogId);
logger.info(">>>>>>>>>>> job monitor, job running, JobLogId:{}", jobLogId);
- } else if (ReturnT.SUCCESS_CODE == log.getTriggerCode() && ReturnT.SUCCESS_CODE == log.getHandleCode()) {
+ } else if (IJobHandler.SUCCESS.getCode() == log.getHandleCode()) {
// job success, pass
logger.info(">>>>>>>>>>> job monitor, job success, JobLogId:{}", jobLogId);
- } else if (ReturnT.FAIL_CODE == log.getTriggerCode() || ReturnT.FAIL_CODE == log.getHandleCode()) {
+ } else if (IJobHandler.FAIL.getCode() == log.getTriggerCode()
+ || IJobHandler.FAIL.getCode() == log.getHandleCode()
+ || IJobHandler.FAIL_RETRY.getCode() == log.getHandleCode() ) {
// job fail,
failAlarm(log);
logger.info(">>>>>>>>>>> job monitor, job fail, JobLogId:{}", jobLogId);
@@ -94,30 +99,6 @@
monitorThread.start();
}
- /**
- * fail alarm
- *
- * @param jobLog
- */
- private void failAlarm(XxlJobLog jobLog){
-
- // send monitor email
- XxlJobInfo info = XxlJobDynamicScheduler.xxlJobInfoDao.loadById(jobLog.getJobId());
- if (info!=null && info.getAlarmEmail()!=null && info.getAlarmEmail().trim().length()>0) {
-
- Set emailSet = new HashSet(Arrays.asList(info.getAlarmEmail().split(",")));
- for (String email: emailSet) {
- String title = "《调度监控报警》(任务调度中心XXL-JOB)";
- XxlJobGroup group = XxlJobDynamicScheduler.xxlJobGroupDao.load(Integer.valueOf(info.getJobGroup()));
- String content = MessageFormat.format("任务调度失败, 执行器名称:{0}, 任务描述:{1}.", group!=null?group.getTitle():"null", info.getJobDesc());
- MailUtil.sendMail(email, title, content, false, null);
- }
- }
-
- // TODO, custom alarm strategy, such as sms
-
- }
-
public void toStop(){
toStop = true;
// interrupt and wait
@@ -133,5 +114,55 @@
public static void monitor(int jobLogId){
getInstance().queue.offer(jobLogId);
}
-
+
+
+ // ---------------------- alarm ----------------------
+
+ // email alarm template
+ private static final String mailBodyTemplate = "监控告警明细:" +
+ "
\n" +
+ " " +
+ " \n" +
+ " 执行器 | \n" +
+ " 任务ID | \n" +
+ " 任务描述 | \n" +
+ " 告警类型 | \n" +
+ "
\n" +
+ " \n" +
+ " \n" +
+ " \n" +
+ " {0} | \n" +
+ " {1} | \n" +
+ " {2} | \n" +
+ " 调度失败 | \n" +
+ "
\n" +
+ " \n" +
+ "
";
+
+ /**
+ * fail alarm
+ *
+ * @param jobLog
+ */
+ private void failAlarm(XxlJobLog jobLog){
+
+ // send monitor email
+ XxlJobInfo info = XxlJobDynamicScheduler.xxlJobInfoDao.loadById(jobLog.getJobId());
+ if (info!=null && info.getAlarmEmail()!=null && info.getAlarmEmail().trim().length()>0) {
+
+ Set emailSet = new HashSet(Arrays.asList(info.getAlarmEmail().split(",")));
+ for (String email: emailSet) {
+ XxlJobGroup group = XxlJobDynamicScheduler.xxlJobGroupDao.load(Integer.valueOf(info.getJobGroup()));
+
+ String title = "调度中心监控报警";
+ String content = MessageFormat.format(mailBodyTemplate, group!=null?group.getTitle():"null", info.getId(), info.getJobDesc());
+
+ MailUtil.sendMail(email, title, content);
+ }
+ }
+
+ // TODO, custom alarm strategy, such as sms
+
+ }
+
}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobRegistryMonitorHelper.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobRegistryMonitorHelper.java
index 94f40a0..647c02e 100644
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobRegistryMonitorHelper.java
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobRegistryMonitorHelper.java
@@ -4,8 +4,8 @@
import com.xxl.job.admin.core.model.XxlJobRegistry;
import com.xxl.job.admin.core.schedule.XxlJobDynamicScheduler;
import com.xxl.job.core.enums.RegistryConfig;
-import org.apache.commons.collections.CollectionUtils;
-import org.apache.commons.lang.StringUtils;
+import org.apache.commons.collections4.CollectionUtils;
+import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/trigger/XxlJobTrigger.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/trigger/XxlJobTrigger.java
index 6482922..bf3c856 100644
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/trigger/XxlJobTrigger.java
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/trigger/XxlJobTrigger.java
@@ -11,7 +11,8 @@
import com.xxl.job.core.biz.model.ReturnT;
import com.xxl.job.core.biz.model.TriggerParam;
import com.xxl.job.core.enums.ExecutorBlockStrategyEnum;
-import org.apache.commons.collections.CollectionUtils;
+import com.xxl.job.core.util.IpUtil;
+import org.apache.commons.collections4.CollectionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -66,11 +67,12 @@
ReturnT triggerResult = new ReturnT(null);
StringBuffer triggerMsgSb = new StringBuffer();
- triggerMsgSb.append("注册方式:").append( (group.getAddressType() == 0)?"自动注册":"手动录入" );
+ triggerMsgSb.append("调度机器:").append(IpUtil.getIp());
+ triggerMsgSb.append("
执行器-注册方式:").append( (group.getAddressType() == 0)?"自动注册":"手动录入" );
+ triggerMsgSb.append("
执行器-地址列表:").append(group.getRegistryList());
+ triggerMsgSb.append("
路由策略:").append(executorRouteStrategyEnum.getTitle()).append("("+i+"/"+addressList.size()+")"); // update01
triggerMsgSb.append("
阻塞处理策略:").append(blockStrategy.getTitle());
triggerMsgSb.append("
失败处理策略:").append(failStrategy.getTitle());
- triggerMsgSb.append("
地址列表:").append(group.getRegistryList());
- triggerMsgSb.append("
路由策略:").append(executorRouteStrategyEnum.getTitle()).append("("+i+"/"+addressList.size()+")"); // update01
// 3、trigger-valid
if (triggerResult.getCode()==ReturnT.SUCCESS_CODE && CollectionUtils.isEmpty(addressList)) {
@@ -115,72 +117,73 @@
logger.debug(">>>>>>>>>>> xxl-job trigger end, jobId:{}", jobLog.getId());
}
- return;
- }
+ } else {
+ // 1、save log-id
+ XxlJobLog jobLog = new XxlJobLog();
+ jobLog.setJobGroup(jobInfo.getJobGroup());
+ jobLog.setJobId(jobInfo.getId());
+ XxlJobDynamicScheduler.xxlJobLogDao.save(jobLog);
+ logger.debug(">>>>>>>>>>> xxl-job trigger start, jobId:{}", jobLog.getId());
- // 1、save log-id
- XxlJobLog jobLog = new XxlJobLog();
- jobLog.setJobGroup(jobInfo.getJobGroup());
- jobLog.setJobId(jobInfo.getId());
- XxlJobDynamicScheduler.xxlJobLogDao.save(jobLog);
- logger.debug(">>>>>>>>>>> xxl-job trigger start, jobId:{}", jobLog.getId());
+ // 2、prepare trigger-info
+ //jobLog.setExecutorAddress(executorAddress);
+ jobLog.setGlueType(jobInfo.getGlueType());
+ jobLog.setExecutorHandler(jobInfo.getExecutorHandler());
+ jobLog.setExecutorParam(jobInfo.getExecutorParam());
+ jobLog.setTriggerTime(new Date());
- // 2、prepare trigger-info
- //jobLog.setExecutorAddress(executorAddress);
- jobLog.setGlueType(jobInfo.getGlueType());
- jobLog.setExecutorHandler(jobInfo.getExecutorHandler());
- jobLog.setExecutorParam(jobInfo.getExecutorParam());
- jobLog.setTriggerTime(new Date());
+ ReturnT triggerResult = new ReturnT(null);
+ StringBuffer triggerMsgSb = new StringBuffer();
+ triggerMsgSb.append("调度机器:").append(IpUtil.getIp());
+ triggerMsgSb.append("
执行器-注册方式:").append( (group.getAddressType() == 0)?"自动注册":"手动录入" );
+ triggerMsgSb.append("
执行器-地址列表:").append(group.getRegistryList());
+ triggerMsgSb.append("
路由策略:").append(executorRouteStrategyEnum.getTitle());
+ triggerMsgSb.append("
阻塞处理策略:").append(blockStrategy.getTitle());
+ triggerMsgSb.append("
失败处理策略:").append(failStrategy.getTitle());
- ReturnT triggerResult = new ReturnT(null);
- StringBuffer triggerMsgSb = new StringBuffer();
- triggerMsgSb.append("注册方式:").append( (group.getAddressType() == 0)?"自动注册":"手动录入" );
- triggerMsgSb.append("
阻塞处理策略:").append(blockStrategy.getTitle());
- triggerMsgSb.append("
失败处理策略:").append(failStrategy.getTitle());
- triggerMsgSb.append("
地址列表:").append(group.getRegistryList());
- triggerMsgSb.append("
路由策略:").append(executorRouteStrategyEnum.getTitle());
-
- // 3、trigger-valid
- if (triggerResult.getCode()==ReturnT.SUCCESS_CODE && CollectionUtils.isEmpty(addressList)) {
- triggerResult.setCode(ReturnT.FAIL_CODE);
- triggerMsgSb.append("
----------------------
").append("调度失败:").append("执行器地址为空");
- }
-
- if (triggerResult.getCode() == ReturnT.SUCCESS_CODE) {
- // 4.1、trigger-param
- TriggerParam triggerParam = new TriggerParam();
- triggerParam.setJobId(jobInfo.getId());
- triggerParam.setExecutorHandler(jobInfo.getExecutorHandler());
- triggerParam.setExecutorParams(jobInfo.getExecutorParam());
- triggerParam.setExecutorBlockStrategy(jobInfo.getExecutorBlockStrategy());
- triggerParam.setLogId(jobLog.getId());
- triggerParam.setLogDateTim(jobLog.getTriggerTime().getTime());
- triggerParam.setGlueType(jobInfo.getGlueType());
- triggerParam.setGlueSource(jobInfo.getGlueSource());
- triggerParam.setGlueUpdatetime(jobInfo.getGlueUpdatetime().getTime());
- triggerParam.setBroadcastIndex(0);
- triggerParam.setBroadcastTotal(1);
-
- // 4.2、trigger-run (route run / trigger remote executor)
- triggerResult = executorRouteStrategyEnum.getRouter().routeRun(triggerParam, addressList);
- triggerMsgSb.append("
>>>>>>>>>>>触发调度<<<<<<<<<<<
").append(triggerResult.getMsg());
-
- // 4.3、trigger (fail retry)
- if (triggerResult.getCode()!=ReturnT.SUCCESS_CODE && failStrategy == ExecutorFailStrategyEnum.FAIL_RETRY) {
- triggerResult = executorRouteStrategyEnum.getRouter().routeRun(triggerParam, addressList);
- triggerMsgSb.append("
>>>>>>>>>>>失败重试<<<<<<<<<<<
").append(triggerResult.getMsg());
+ // 3、trigger-valid
+ if (triggerResult.getCode()==ReturnT.SUCCESS_CODE && CollectionUtils.isEmpty(addressList)) {
+ triggerResult.setCode(ReturnT.FAIL_CODE);
+ triggerMsgSb.append("
----------------------
").append("调度失败:").append("执行器地址为空");
}
+
+ if (triggerResult.getCode() == ReturnT.SUCCESS_CODE) {
+ // 4.1、trigger-param
+ TriggerParam triggerParam = new TriggerParam();
+ triggerParam.setJobId(jobInfo.getId());
+ triggerParam.setExecutorHandler(jobInfo.getExecutorHandler());
+ triggerParam.setExecutorParams(jobInfo.getExecutorParam());
+ triggerParam.setExecutorBlockStrategy(jobInfo.getExecutorBlockStrategy());
+ triggerParam.setLogId(jobLog.getId());
+ triggerParam.setLogDateTim(jobLog.getTriggerTime().getTime());
+ triggerParam.setGlueType(jobInfo.getGlueType());
+ triggerParam.setGlueSource(jobInfo.getGlueSource());
+ triggerParam.setGlueUpdatetime(jobInfo.getGlueUpdatetime().getTime());
+ triggerParam.setBroadcastIndex(0);
+ triggerParam.setBroadcastTotal(1);
+
+ // 4.2、trigger-run (route run / trigger remote executor)
+ triggerResult = executorRouteStrategyEnum.getRouter().routeRun(triggerParam, addressList);
+ triggerMsgSb.append("
>>>>>>>>>>>触发调度<<<<<<<<<<<
").append(triggerResult.getMsg());
+
+ // 4.3、trigger (fail retry)
+ if (triggerResult.getCode()!=ReturnT.SUCCESS_CODE && failStrategy == ExecutorFailStrategyEnum.FAIL_RETRY) {
+ triggerResult = executorRouteStrategyEnum.getRouter().routeRun(triggerParam, addressList);
+ triggerMsgSb.append("
>>>>>>>>>>>调度失败重试<<<<<<<<<<<
").append(triggerResult.getMsg());
+ }
+ }
+
+ // 5、save trigger-info
+ jobLog.setExecutorAddress(triggerResult.getContent());
+ jobLog.setTriggerCode(triggerResult.getCode());
+ jobLog.setTriggerMsg(triggerMsgSb.toString());
+ XxlJobDynamicScheduler.xxlJobLogDao.updateTriggerInfo(jobLog);
+
+ // 6、monitor triger
+ JobFailMonitorHelper.monitor(jobLog.getId());
+ logger.debug(">>>>>>>>>>> xxl-job trigger end, jobId:{}", jobLog.getId());
}
- // 5、save trigger-info
- jobLog.setExecutorAddress(triggerResult.getContent());
- jobLog.setTriggerCode(triggerResult.getCode());
- jobLog.setTriggerMsg(triggerMsgSb.toString());
- XxlJobDynamicScheduler.xxlJobLogDao.updateTriggerInfo(jobLog);
-
- // 6、monitor triger
- JobFailMonitorHelper.monitor(jobLog.getId());
- logger.debug(">>>>>>>>>>> xxl-job trigger end, jobId:{}", jobLog.getId());
}
/**
@@ -195,7 +198,7 @@
ExecutorBiz executorBiz = XxlJobDynamicScheduler.getExecutorBiz(address);
runResult = executorBiz.run(triggerParam);
} catch (Exception e) {
- logger.error(e.getMessage(), e);
+ logger.error(">>>>>>>>>>> xxl-job trigger error, please check if the executor[{}] is running.", address, e);
runResult = new ReturnT(ReturnT.FAIL_CODE, ""+e );
}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/util/CookieUtil.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/util/CookieUtil.java
index 19a6751..31f30ee 100644
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/util/CookieUtil.java
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/util/CookieUtil.java
@@ -6,9 +6,11 @@
/**
* Cookie.Util
+ *
* @author xuxueli 2015-12-12 18:01:06
*/
public class CookieUtil {
+
// 默认缓存时间,单位/秒, 2H
private static final int COOKIE_MAX_AGE = 60 * 60 * 2;
// 保存路径,根路径
@@ -16,43 +18,39 @@
/**
* 保存
+ *
* @param response
* @param key
* @param value
* @param ifRemember
*/
public static void set(HttpServletResponse response, String key, String value, boolean ifRemember) {
-
- int age = COOKIE_MAX_AGE;
- if (ifRemember) {
- age = COOKIE_MAX_AGE;
- } else {
- age = -1;
- }
-
- Cookie cookie = new Cookie(key, value);
- cookie.setMaxAge(age); // Cookie过期时间,单位/秒
- cookie.setPath(COOKIE_PATH); // Cookie适用的路径
- response.addCookie(cookie);
+ int age = ifRemember?COOKIE_MAX_AGE:-1;
+ set(response, key, value, null, COOKIE_PATH, age, true);
}
/**
* 保存
+ *
* @param response
* @param key
* @param value
* @param maxAge
*/
- private static void set(HttpServletResponse response,
- String key, String value, int maxAge, String path) {
+ private static void set(HttpServletResponse response, String key, String value, String domain, String path, int maxAge, boolean isHttpOnly) {
Cookie cookie = new Cookie(key, value);
- cookie.setMaxAge(maxAge); // Cookie过期时间,单位/秒
- cookie.setPath(path); // Cookie适用的路径
+ if (domain != null) {
+ cookie.setDomain(domain);
+ }
+ cookie.setPath(path);
+ cookie.setMaxAge(maxAge);
+ cookie.setHttpOnly(isHttpOnly);
response.addCookie(cookie);
}
/**
* 查询value
+ *
* @param request
* @param key
* @return
@@ -67,6 +65,7 @@
/**
* 查询Cookie
+ *
* @param request
* @param key
*/
@@ -84,15 +83,15 @@
/**
* 删除Cookie
+ *
* @param request
* @param response
* @param key
- * @param domainName
*/
public static void remove(HttpServletRequest request, HttpServletResponse response, String key) {
Cookie cookie = get(request, key);
if (cookie != null) {
- set(response, key, "", 0, COOKIE_PATH);
+ set(response, key, "", null, COOKIE_PATH, 0, true);
}
}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/util/MailUtil.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/util/MailUtil.java
index c37a929..e000b15 100644
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/util/MailUtil.java
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/util/MailUtil.java
@@ -1,21 +1,16 @@
package com.xxl.job.admin.core.util;
-import org.apache.commons.lang.ArrayUtils;
+import org.apache.commons.mail.DefaultAuthenticator;
+import org.apache.commons.mail.EmailException;
+import org.apache.commons.mail.HtmlEmail;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import org.springframework.mail.javamail.JavaMailSender;
-import org.springframework.mail.javamail.JavaMailSenderImpl;
-import org.springframework.mail.javamail.MimeMessageHelper;
-import javax.mail.internet.MimeMessage;
-import javax.mail.internet.MimeUtility;
-import java.io.File;
-import java.util.Properties;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
+import java.nio.charset.Charset;
/**
* 邮件发送.Util
+ *
* @author xuxueli 2016-3-12 15:06:20
*/
public class MailUtil {
@@ -33,154 +28,44 @@
password = PropertiesUtil.getString("xxl.job.mail.password");
sendNick = PropertiesUtil.getString("xxl.job.mail.sendNick");
}
-
+
/**
-
-
-
-
-
-
-
-
- true
- true
-
-
-
-
+ *
+ * @param toAddress 收件人邮箱
+ * @param mailSubject 邮件主题
+ * @param mailBody 邮件正文
+ * @return
*/
- /**
- * 发送邮件 (完整版)(结合Spring)
- *
- * //@param javaMailSender: 发送Bean
- * //@param sendFrom : 发送人邮箱
- * //@param sendNick : 发送人昵称
- * @param toAddress : 收件人邮箱
- * @param mailSubject : 邮件主题
- * @param mailBody : 邮件正文
- * @param mailBodyIsHtml: 邮件正文格式,true:HTML格式;false:文本格式
- * @param attachments : 附件
- */
- @SuppressWarnings("null")
- public static boolean sendMailSpring(String toAddress, String mailSubject, String mailBody, boolean mailBodyIsHtml,File[] attachments) {
- JavaMailSender javaMailSender = null;//ResourceBundle.getInstance().getJavaMailSender();
+ public static boolean sendMail(String toAddress, String mailSubject, String mailBody){
+
try {
- MimeMessage mimeMessage = javaMailSender.createMimeMessage();
- MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, ArrayUtils.isNotEmpty(attachments), "UTF-8"); // 设置utf-8或GBK编码,否则邮件会有乱码;multipart,true表示文件上传
+ // Create the email message
+ HtmlEmail email = new HtmlEmail();
+ //email.setDebug(true); // 将会打印一些log
+ //email.setTLS(true); // 是否TLS校验,,某些邮箱需要TLS安全校验,同理有SSL校验
+ //email.setSSL(true);
- helper.setFrom(username, sendNick);
- helper.setTo(toAddress);
+ email.setHostName(host);
+ email.setSmtpPort(Integer.valueOf(port));
+ //email.setSslSmtpPort(port);
+ email.setAuthenticator(new DefaultAuthenticator(username, password));
+ email.setCharset(Charset.defaultCharset().name());
- // 设置收件人抄送的名片和地址(相当于群发了)
- //helper.setCc(InternetAddress.parse(MimeUtility.encodeText("邮箱001") + " <@163.com>," + MimeUtility.encodeText("邮箱002") + " <@foxmail.com>"));
+ email.setFrom(username, sendNick);
+ email.addTo(toAddress);
+ email.setSubject(mailSubject);
+ email.setMsg(mailBody);
- helper.setSubject(mailSubject);
- helper.setText(mailBody, mailBodyIsHtml);
-
- // 添加附件
- if (ArrayUtils.isNotEmpty(attachments)) {
- for (File file : attachments) {
- helper.addAttachment(MimeUtility.encodeText(file.getName()), file);
- }
- }
-
- // 群发
- //MimeMessage[] mailMessages = { mimeMessage };
-
- javaMailSender.send(mimeMessage);
+ //email.attach(attachment); // add the attachment
+
+ email.send(); // send the email
return true;
- } catch (Exception e) {
+ } catch (EmailException e) {
logger.error(e.getMessage(), e);
+
}
return false;
}
-
- /**
- * 发送邮件 (完整版) (纯JavaMail)
- *
- * @param toAddress : 收件人邮箱
- * @param mailSubject : 邮件主题
- * @param mailBody : 邮件正文
- * @param mailBodyIsHtml: 邮件正文格式,true:HTML格式;false:文本格式
- * //@param inLineFile : 内嵌文件
- * @param attachments : 附件
- */
- public static boolean sendMail (String toAddress, String mailSubject, String mailBody,
- boolean mailBodyIsHtml, File[] attachments){
- try {
- // 创建邮件发送类 JavaMailSender (用于发送多元化邮件,包括附件,图片,html 等)
- JavaMailSenderImpl mailSender = new JavaMailSenderImpl();
- mailSender.setHost(host); // 设置邮件服务主机
- mailSender.setUsername(username); // 发送者邮箱的用户名
- mailSender.setPassword(password); // 发送者邮箱的密码
-
- // 配置文件,用于实例化java.mail.session
- Properties pro = new Properties();
- pro.put("mail.transport.protocol", "smtp");
- pro.put("mail.smtp.auth", "true"); // 登录SMTP服务器,需要获得授权 (网易163邮箱新近注册的邮箱均不能授权,测试 sohu 的邮箱可以获得授权)
- pro.put("mail.smtp.socketFactory.port", port);
- pro.put("mail.smtp.socketFactory.fallback", "false");
- mailSender.setJavaMailProperties(pro);
-
- // 创建多元化邮件 (创建 mimeMessage 帮助类,用于封装信息至 mimeMessage)
- MimeMessage mimeMessage = mailSender.createMimeMessage();
- MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, ArrayUtils.isNotEmpty(attachments), "UTF-8");
-
- helper.setFrom(username, sendNick);
- helper.setTo(toAddress);
- helper.setSubject(mailSubject);
- helper.setText(mailBody, mailBodyIsHtml);
-
- // 设置收件人抄送的名片和地址(相当于群发)
- //helper.setCc(InternetAddress.parse(MimeUtility.encodeText("邮箱001") + " <@163.com>," + MimeUtility.encodeText("邮箱002") + " <@foxmail.com>"));
-
- // 内嵌文件,第1个参数为cid标识这个文件,第2个参数为资源
- //helper.addInline(MimeUtility.encodeText(inLineFile.getName()), inLineFile);
-
- // 添加附件
- /*if (ArrayUtils.isNotEmpty(attachments)) {
- for (File file : attachments) {
- helper.addAttachment(MimeUtility.encodeText(file.getName()), file);
- }
- }*/
-
- // 群发
- //MimeMessage[] mailMessages = { mimeMessage };
-
- mailSender.send(mimeMessage);
- return true;
- } catch (Exception e) {
- logger.error(e.getMessage(), e);
- }
- return false;
- }
-
- static int total = 0;
- public static void main(String[] args) {
-
- ExecutorService exec = Executors.newCachedThreadPool();
- for (int i = 0; i < 1; i++) {
- exec.execute(new Thread(new Runnable() {
- @Override
- public void run() {
- while(total < 1){
- String mailBody = "新书快递通知
你的新书快递申请已推送新书,请到空间"
- + "中查看";
-
- sendMail("931591021@qq.com", "测试邮件", mailBody, true, null);
- System.out.println(total);
- total++;
- }
- }
- }));
- }
- }
-
}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/util/PropertiesUtil.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/util/PropertiesUtil.java
index ed80e70..a3c6007 100644
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/util/PropertiesUtil.java
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/util/PropertiesUtil.java
@@ -12,6 +12,7 @@
/**
* properties util
+ *
* @author xuxueli 2015-8-28 10:35:53
*/
public class PropertiesUtil {
@@ -33,9 +34,5 @@
}
return null;
}
-
- public static void main(String[] args) {
- System.out.println(getString("xxl.job.login.username"));
- }
}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/service/impl/AdminBizImpl.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/service/impl/AdminBizImpl.java
index 718b455..352b4c9 100644
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/service/impl/AdminBizImpl.java
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/service/impl/AdminBizImpl.java
@@ -1,10 +1,7 @@
package com.xxl.job.admin.service.impl;
-import com.xxl.job.admin.controller.JobApiController;
import com.xxl.job.admin.core.model.XxlJobInfo;
import com.xxl.job.admin.core.model.XxlJobLog;
-import com.xxl.job.admin.core.schedule.XxlJobDynamicScheduler;
-import com.xxl.job.admin.core.trigger.XxlJobTrigger;
import com.xxl.job.admin.dao.XxlJobInfoDao;
import com.xxl.job.admin.dao.XxlJobLogDao;
import com.xxl.job.admin.dao.XxlJobRegistryDao;
@@ -13,8 +10,8 @@
import com.xxl.job.core.biz.model.HandleCallbackParam;
import com.xxl.job.core.biz.model.RegistryParam;
import com.xxl.job.core.biz.model.ReturnT;
-import org.apache.commons.lang.StringUtils;
-import org.quartz.SchedulerException;
+import com.xxl.job.core.handler.IJobHandler;
+import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
@@ -46,7 +43,7 @@
for (HandleCallbackParam handleCallbackParam: callbackParamList) {
ReturnT callbackResult = callback(handleCallbackParam);
logger.info(">>>>>>>>> JobApiController.callback {}, handleCallbackParam={}, callbackResult={}",
- (callbackResult.getCode()==ReturnT.SUCCESS_CODE?"success":"fail"), handleCallbackParam, callbackResult);
+ (callbackResult.getCode()==IJobHandler.SUCCESS.getCode()?"success":"fail"), handleCallbackParam, callbackResult);
}
return ReturnT.SUCCESS;
@@ -58,28 +55,39 @@
if (log == null) {
return new ReturnT(ReturnT.FAIL_CODE, "log item not found.");
}
+ if (log.getHandleCode() > 0) {
+ return new ReturnT(ReturnT.FAIL_CODE, "log repeate callback."); // avoid repeat callback, trigger child job etc
+ }
- // trigger success, to trigger child job, and avoid repeat trigger child job
- String childTriggerMsg = null;
- if (ReturnT.SUCCESS_CODE==handleCallbackParam.getExecuteResult().getCode() && ReturnT.SUCCESS_CODE!=log.getHandleCode()) {
+ // trigger success, to trigger child job
+ String callbackMsg = null;
+ if (IJobHandler.SUCCESS.getCode() == handleCallbackParam.getExecuteResult().getCode()) {
XxlJobInfo xxlJobInfo = xxlJobInfoDao.loadById(log.getJobId());
- if (xxlJobInfo!=null && StringUtils.isNotBlank(xxlJobInfo.getChildJobKey())) {
- childTriggerMsg = "
";
- String[] childJobKeys = xxlJobInfo.getChildJobKey().split(",");
- for (int i = 0; i < childJobKeys.length; i++) {
- String[] jobKeyArr = childJobKeys[i].split("_");
- if (jobKeyArr!=null && jobKeyArr.length==2) {
- ReturnT triggerChildResult = xxlJobService.triggerJob(Integer.valueOf(jobKeyArr[1]));
+ if (xxlJobInfo!=null && StringUtils.isNotBlank(xxlJobInfo.getChildJobId())) {
+ callbackMsg = "
>>>>>>>>>>>触发子任务<<<<<<<<<<<
";
+
+ String[] childJobIds = xxlJobInfo.getChildJobId().split(",");
+ for (int i = 0; i < childJobIds.length; i++) {
+ int childJobId = (StringUtils.isNotBlank(childJobIds[i]) && StringUtils.isNumeric(childJobIds[i]))?Integer.valueOf(childJobIds[i]):-1;
+ if (childJobId > 0) {
+ ReturnT triggerChildResult = xxlJobService.triggerJob(childJobId);
+
// add msg
- childTriggerMsg += MessageFormat.format("
{0}/{1} 触发子任务{2}, 子任务Key: {3}, 子任务触发备注: {4}",
- (i+1), childJobKeys.length, (triggerChildResult.getCode()==ReturnT.SUCCESS_CODE?"成功":"失败"), childJobKeys[i], triggerChildResult.getMsg());
+ callbackMsg += MessageFormat.format("{0}/{1} [任务ID={2}], 触发{3}, 触发备注: {4}
",
+ (i+1), childJobIds.length, childJobIds[i], (triggerChildResult.getCode()==ReturnT.SUCCESS_CODE?"成功":"失败"), triggerChildResult.getMsg());
} else {
- childTriggerMsg += MessageFormat.format("
{0}/{1} 触发子任务失败, 子任务Key格式错误, 子任务Key: {2}",
- (i+1), childJobKeys.length, childJobKeys[i]);
+ callbackMsg += MessageFormat.format(" {0}/{1} [任务ID={2}], 触发失败, 触发备注: 任务ID格式错误
",
+ (i+1), childJobIds.length, childJobIds[i]);
}
}
}
+ } else if (IJobHandler.FAIL_RETRY.getCode() == handleCallbackParam.getExecuteResult().getCode()){
+ ReturnT retryTriggerResult = xxlJobService.triggerJob(log.getJobId());
+ callbackMsg = "
>>>>>>>>>>>执行失败重试<<<<<<<<<<<
";
+
+ callbackMsg += MessageFormat.format("触发{0}, 触发备注: {1}",
+ (retryTriggerResult.getCode()==ReturnT.SUCCESS_CODE?"成功":"失败"), retryTriggerResult.getMsg());
}
// handle msg
@@ -90,8 +98,8 @@
if (handleCallbackParam.getExecuteResult().getMsg() != null) {
handleMsg.append(handleCallbackParam.getExecuteResult().getMsg());
}
- if (childTriggerMsg !=null) {
- handleMsg.append("
子任务触发备注:").append(childTriggerMsg);
+ if (callbackMsg != null) {
+ handleMsg.append(callbackMsg);
}
// success, save log
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/service/impl/XxlJobServiceImpl.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/service/impl/XxlJobServiceImpl.java
index 2eccb31..eecef34 100644
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/service/impl/XxlJobServiceImpl.java
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/service/impl/XxlJobServiceImpl.java
@@ -13,10 +13,10 @@
import com.xxl.job.core.biz.model.ReturnT;
import com.xxl.job.core.enums.ExecutorBlockStrategyEnum;
import com.xxl.job.core.glue.GlueTypeEnum;
-import org.apache.commons.collections.CollectionUtils;
-import org.apache.commons.lang.StringUtils;
-import org.apache.commons.lang.time.DateUtils;
-import org.apache.commons.lang.time.FastDateFormat;
+import org.apache.commons.collections4.CollectionUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.time.DateUtils;
+import org.apache.commons.lang3.time.FastDateFormat;
import org.quartz.CronExpression;
import org.quartz.SchedulerException;
import org.slf4j.Logger;
@@ -103,19 +103,20 @@
jobInfo.setGlueSource(jobInfo.getGlueSource().replaceAll("\r", ""));
}
- // childJobKey valid
- if (StringUtils.isNotBlank(jobInfo.getChildJobKey())) {
- String[] childJobKeys = jobInfo.getChildJobKey().split(",");
- for (String childJobKeyItem: childJobKeys) {
- String[] childJobKeyArr = childJobKeyItem.split("_");
- if (childJobKeyArr.length!=2) {
- return new ReturnT(ReturnT.FAIL_CODE, MessageFormat.format("子任务Key({0})格式错误", childJobKeyItem));
- }
- XxlJobInfo childJobInfo = xxlJobInfoDao.loadById(Integer.valueOf(childJobKeyArr[1]));
- if (childJobInfo==null) {
- return new ReturnT(ReturnT.FAIL_CODE, MessageFormat.format("子任务Key({0})无效", childJobKeyItem));
+ // ChildJobId valid
+ if (StringUtils.isNotBlank(jobInfo.getChildJobId())) {
+ String[] childJobIds = StringUtils.split(jobInfo.getChildJobId(), ",");
+ for (String childJobIdItem: childJobIds) {
+ if (StringUtils.isNotBlank(childJobIdItem) && StringUtils.isNumeric(childJobIdItem)) {
+ XxlJobInfo childJobInfo = xxlJobInfoDao.loadById(Integer.valueOf(childJobIdItem));
+ if (childJobInfo==null) {
+ return new ReturnT(ReturnT.FAIL_CODE, MessageFormat.format("子任务ID({0})无效", childJobIdItem));
+ }
+ } else {
+ return new ReturnT(ReturnT.FAIL_CODE, MessageFormat.format("子任务ID({0})格式错误", childJobIdItem));
}
}
+ jobInfo.setChildJobId(StringUtils.join(childJobIds, ","));
}
// add in db
@@ -166,19 +167,24 @@
return new ReturnT(ReturnT.FAIL_CODE, "失败处理策略非法");
}
- // childJobKey valid
- if (StringUtils.isNotBlank(jobInfo.getChildJobKey())) {
- String[] childJobKeys = jobInfo.getChildJobKey().split(",");
- for (String childJobKeyItem: childJobKeys) {
- String[] childJobKeyArr = childJobKeyItem.split("_");
- if (childJobKeyArr.length!=2) {
- return new ReturnT(ReturnT.FAIL_CODE, MessageFormat.format("子任务Key({0})格式错误", childJobKeyItem));
- }
- XxlJobInfo childJobInfo = xxlJobInfoDao.loadById(Integer.valueOf(childJobKeyArr[1]));
- if (childJobInfo==null) {
- return new ReturnT(ReturnT.FAIL_CODE, MessageFormat.format("子任务Key({0})无效", childJobKeyItem));
+ // ChildJobId valid
+ if (StringUtils.isNotBlank(jobInfo.getChildJobId())) {
+ String[] childJobIds = StringUtils.split(jobInfo.getChildJobId(), ",");
+ for (String childJobIdItem: childJobIds) {
+ if (StringUtils.isNotBlank(childJobIdItem) && StringUtils.isNumeric(childJobIdItem)) {
+ XxlJobInfo childJobInfo = xxlJobInfoDao.loadById(Integer.valueOf(childJobIdItem));
+ if (childJobInfo==null) {
+ return new ReturnT(ReturnT.FAIL_CODE, MessageFormat.format("子任务ID({0})无效", childJobIdItem));
+ }
+ // avoid cycle relate
+ if (childJobInfo.getId() == jobInfo.getId()) {
+ return new ReturnT(ReturnT.FAIL_CODE, MessageFormat.format("子任务ID({0})不可与父任务重复", childJobIdItem));
+ }
+ } else {
+ return new ReturnT(ReturnT.FAIL_CODE, MessageFormat.format("子任务ID({0})格式错误", childJobIdItem));
}
}
+ jobInfo.setChildJobId(StringUtils.join(childJobIds, ","));
}
// stage job info
@@ -197,7 +203,7 @@
exists_jobInfo.setExecutorParam(jobInfo.getExecutorParam());
exists_jobInfo.setExecutorBlockStrategy(jobInfo.getExecutorBlockStrategy());
exists_jobInfo.setExecutorFailStrategy(jobInfo.getExecutorFailStrategy());
- exists_jobInfo.setChildJobKey(jobInfo.getChildJobKey());
+ exists_jobInfo.setChildJobId(jobInfo.getChildJobId());
xxlJobInfoDao.update(exists_jobInfo);
// fresh quartz
diff --git a/xxl-job-admin/src/main/resources/mybatis-mapper/XxlJobInfoMapper.xml b/xxl-job-admin/src/main/resources/mybatis-mapper/XxlJobInfoMapper.xml
index 9be9b8e..a5c3abd 100644
--- a/xxl-job-admin/src/main/resources/mybatis-mapper/XxlJobInfoMapper.xml
+++ b/xxl-job-admin/src/main/resources/mybatis-mapper/XxlJobInfoMapper.xml
@@ -27,7 +27,7 @@
-
+
@@ -48,7 +48,7 @@
t.glue_source,
t.glue_remark,
t.glue_updatetime,
- t.child_jobkey
+ t.child_jobid