Unverified 提交 8ed4d055 authored 作者: thinking_fioa's avatar thinking_fioa 提交者: GitHub

Merge pull request #1 from xuxueli/master

pull xuxueli/xxl-job
...@@ -47,29 +47,30 @@ XXL-JOB是一个轻量级分布式任务调度框架,其核心设计目标是 ...@@ -47,29 +47,30 @@ XXL-JOB是一个轻量级分布式任务调度框架,其核心设计目标是
## Features ## Features
- 1、简单:支持通过Web页面对任务进行CRUD操作,操作简单,一分钟上手; - 1、简单:支持通过Web页面对任务进行CRUD操作,操作简单,一分钟上手;
- 2、动态:支持动态修改任务状态、暂停/恢复任务,以及终止运行中任务,即时生效; - 2、动态:支持动态修改任务状态、暂停/恢复任务,以及终止运行中任务,即时生效;
- 3、调度中心HA(中心式):调度采用中心式设计,“调度中心”基于集群Quartz实现,可保证调度中心HA; - 3、调度中心HA(中心式):调度采用中心式设计,“调度中心”基于集群Quartz实现并支持集群部署,可保证调度中心HA;
- 4、执行器HA(分布式):任务分布式执行,任务"执行器"支持集群部署,可保证任务执行HA; - 4、执行器HA(分布式):任务分布式执行,任务"执行器"支持集群部署,可保证任务执行HA;
- 5、任务Failover:执行器集群部署时,任务路由策略选择"故障转移"情况下调度失败时将会平滑切换执行器进行Failover; - 5、注册中心: 执行器会周期性自动注册任务, 调度中心将会自动发现注册的任务并触发执行。同时,也支持手动录入执行器地址;
- 6、一致性:“调度中心”通过DB锁保证集群分布式调度的一致性, 一次任务调度只会触发一次执行; - 6、弹性扩容缩容:一旦有新执行器机器上线或者下线,下次调度时将会重新分配任务;
- 7、自定义任务参数:支持在线配置调度任务入参,即时生效; - 7、路由策略:执行器集群部署时提供丰富的路由策略,包括:第一个、最后一个、轮询、随机、一致性HASH、最不经常使用、最近最久未使用、故障转移、忙碌转移等;
- 8、调度线程池:调度系统多线程触发调度运行,确保调度精确执行,不被堵塞; - 8、故障转移:任务路由策略选择"故障转移"情况下,如果执行器集群中某一台机器故障,将会自动Failover切换到一台正常的执行器发送调度请求。
- 9、弹性扩容缩容:一旦有新执行器机器上线或者下线,下次调度时将会重新分配任务; - 9、失败处理策略;调度失败时的处理策略,策略包括:失败告警(默认)、失败重试;
- 10、邮件报警:任务失败时支持邮件报警,支持配置多邮件地址群发报警邮件; - 10、失败重试:调度中心调度失败且启用"失败重试"策略时,将会自动重试一次;执行器执行失败且回调失败重试状态时,也将会自动重试一次;
- 11、状态监控:支持实时监控任务进度; - 11、阻塞处理策略:调度过于密集执行器来不及处理时的处理策略,策略包括:单机串行(默认)、丢弃后续调度、覆盖之前调度;
- 12、Rolling执行日志:支持在线查看调度结果,并且支持以Rolling方式实时查看执行器输出的完整的执行日志; - 12、分片广播任务:执行器集群部署时,任务路由策略选择"分片广播"情况下,一次任务调度将会广播触发集群中所有执行器执行一次任务,可根据分片参数开发分片任务;
- 13、GLUE:提供Web IDE,支持在线开发任务逻辑代码,动态发布,实时编译生效,省略部署上线的过程。支持30个版本的历史版本回溯。 - 13、动态分片:分片广播任务以执行器为维度进行分片,支持动态扩容执行器集群从而动态增加分片数量,协同进行业务处理;在进行大数据量业务操作时可显著提升任务处理能力和速度。
- 14、数据加密:调度中心和执行器之间的通讯进行数据加密,提升调度信息安全性; - 14、事件触发:除了"Cron方式"和"任务依赖方式"触发任务执行之外,支持基于事件的触发任务方式。调度中心提供触发任务单次执行的API服务,可根据业务事件灵活触发。
- 15、任务依赖:支持配置子任务依赖,当父任务执行结束且执行成功后将会主动触发一次子任务的执行, 多个子任务用逗号分隔; - 15、任务进度监控:支持实时监控任务进度;
- 16、推送maven中央仓库: 将会把最新稳定版推送到maven中央仓库, 方便用户接入和使用; - 16、Rolling实时日志:支持在线查看调度结果,并且支持以Rolling方式实时查看执行器输出的完整的执行日志;
- 17、任务注册: 执行器会周期性自动注册任务, 调度中心将会自动发现注册的任务并触发执行。同时,也支持手动录入执行器地址; - 17、GLUE:提供Web IDE,支持在线开发任务逻辑代码,动态发布,实时编译生效,省略部署上线的过程。支持30个版本的历史版本回溯。
- 18、路由策略:执行器集群部署时提供丰富的路由策略,包括:第一个、最后一个、轮询、随机、一致性HASH、最不经常使用、最近最久未使用、故障转移、忙碌转移等; - 18、脚本任务:支持以GLUE模式开发和运行脚本任务,包括Shell、Python、NodeJS等类型脚本;
- 19、运行报表:支持实时查看运行数据,如任务数量、调度次数、执行器数量等;以及调度报表,如调度日期分布图,调度成功分布图等; - 19、任务依赖:支持配置子任务依赖,当父任务执行结束且执行成功后将会主动触发一次子任务的执行, 多个子任务用逗号分隔;
- 20、脚本任务:支持以GLUE模式开发和运行脚本任务,包括Shell、Python等类型脚本; - 20、一致性:“调度中心”通过DB锁保证集群分布式调度的一致性, 一次任务调度只会触发一次执行;
- 21、阻塞处理策略:调度过于密集执行器来不及处理时的处理策略,策略包括:单机串行(默认)、丢弃后续调度、覆盖之前调度; - 21、自定义任务参数:支持在线配置调度任务入参,即时生效;
- 22、失败处理策略;调度失败时的处理策略,策略包括:失败告警(默认)、失败重试; - 22、调度线程池:调度系统多线程触发调度运行,确保调度精确执行,不被堵塞;
- 23、分片广播任务:执行器集群部署时,任务路由策略选择"分片广播"情况下,一次任务调度将会广播触发对应集群中所有执行器执行一次任务,同时传递分片参数;可根据分片参数开发分片任务; - 23、数据加密:调度中心和执行器之间的通讯进行数据加密,提升调度信息安全性;
- 24、动态分片:分片广播任务以执行器为维度进行分片,支持动态扩容执行器集群从而动态增加分片数量,协同进行业务处理;在进行大数据量业务操作时可显著提升任务处理能力和速度。 - 24、邮件报警:任务失败时支持邮件报警,支持配置多邮件地址群发报警邮件;
- 25、事件触发:除了"Cron方式"和"任务依赖方式"触发任务执行之外,支持基于事件的触发任务方式。调度中心提供触发任务单次执行的API服务,可根据业务事件灵活触发。 - 25、推送maven中央仓库: 将会把最新稳定版推送到maven中央仓库, 方便用户接入和使用;
- 26、运行报表:支持实时查看运行数据,如任务数量、调度次数、执行器数量等;以及调度报表,如调度日期分布图,调度成功分布图等;
## Development ## Development
...@@ -83,8 +84,10 @@ XXL-JOB是一个轻量级分布式任务调度框架,其核心设计目标是 ...@@ -83,8 +84,10 @@ XXL-JOB是一个轻量级分布式任务调度框架,其核心设计目标是
于2017-05-13,在上海举办的 "[第62期开源中国源创会](https://www.oschina.net/event/2236961)" 的 "放码过来" 环节,我登台对XXL-JOB做了演讲,台下五百位在场观众反响热烈([图文回顾](https://www.oschina.net/question/2686220_2242120) )。 于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版本定制而成,新接入应用推荐升级最新版本)。** > 我司大众点评目前已接入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已接入的公司包括不限于: 至今,XXL-JOB已接入多家公司的线上产品线,接入场景如电商业务,O2O业务和大数据作业等,截止2016-07-19为止,XXL-JOB已接入的公司包括不限于:
...@@ -145,6 +148,8 @@ XXL-JOB是一个轻量级分布式任务调度框架,其核心设计目标是 ...@@ -145,6 +148,8 @@ XXL-JOB是一个轻量级分布式任务调度框架,其核心设计目标是
- 55、中商惠民(北京)电子商务有限公司 - 55、中商惠民(北京)电子商务有限公司
- 56、凯京集团 - 56、凯京集团
- 57、华夏票联(北京)科技有限公司 - 57、华夏票联(北京)科技有限公司
- 58、拍拍贷
- 59、北京尚德机构在线教育有限公司
- …… - ……
> 更多接入的公司,欢迎在 [登记地址](https://github.com/xuxueli/xxl-job/issues/1 ) 登记,登记仅仅为了产品推广。 > 更多接入的公司,欢迎在 [登记地址](https://github.com/xuxueli/xxl-job/issues/1 ) 登记,登记仅仅为了产品推广。
......
...@@ -16,29 +16,30 @@ XXL-JOB是一个轻量级分布式任务调度框架,其核心设计目标是 ...@@ -16,29 +16,30 @@ XXL-JOB是一个轻量级分布式任务调度框架,其核心设计目标是
### 1.2 特性 ### 1.2 特性
- 1、简单:支持通过Web页面对任务进行CRUD操作,操作简单,一分钟上手; - 1、简单:支持通过Web页面对任务进行CRUD操作,操作简单,一分钟上手;
- 2、动态:支持动态修改任务状态、暂停/恢复任务,以及终止运行中任务,即时生效; - 2、动态:支持动态修改任务状态、暂停/恢复任务,以及终止运行中任务,即时生效;
- 3、调度中心HA(中心式):调度采用中心式设计,“调度中心”基于集群Quartz实现,可保证调度中心HA; - 3、调度中心HA(中心式):调度采用中心式设计,“调度中心”基于集群Quartz实现并支持集群部署,可保证调度中心HA;
- 4、执行器HA(分布式):任务分布式执行,任务"执行器"支持集群部署,可保证任务执行HA; - 4、执行器HA(分布式):任务分布式执行,任务"执行器"支持集群部署,可保证任务执行HA;
- 5、任务Failover:执行器集群部署时,任务路由策略选择"故障转移"情况下调度失败时将会平滑切换执行器进行Failover; - 5、注册中心: 执行器会周期性自动注册任务, 调度中心将会自动发现注册的任务并触发执行。同时,也支持手动录入执行器地址;
- 6、一致性:“调度中心”通过DB锁保证集群分布式调度的一致性, 一次任务调度只会触发一次执行; - 6、弹性扩容缩容:一旦有新执行器机器上线或者下线,下次调度时将会重新分配任务;
- 7、自定义任务参数:支持在线配置调度任务入参,即时生效; - 7、路由策略:执行器集群部署时提供丰富的路由策略,包括:第一个、最后一个、轮询、随机、一致性HASH、最不经常使用、最近最久未使用、故障转移、忙碌转移等;
- 8、调度线程池:调度系统多线程触发调度运行,确保调度精确执行,不被堵塞; - 8、故障转移:任务路由策略选择"故障转移"情况下,如果执行器集群中某一台机器故障,将会自动Failover切换到一台正常的执行器发送调度请求。
- 9、弹性扩容缩容:一旦有新执行器机器上线或者下线,下次调度时将会重新分配任务; - 9、失败处理策略;调度失败时的处理策略,策略包括:失败告警(默认)、失败重试;
- 10、邮件报警:任务失败时支持邮件报警,支持配置多邮件地址群发报警邮件; - 10、失败重试:调度中心调度失败且启用"失败重试"策略时,将会自动重试一次;执行器执行失败且回调失败重试状态时,也将会自动重试一次;
- 11、状态监控:支持实时监控任务进度; - 11、阻塞处理策略:调度过于密集执行器来不及处理时的处理策略,策略包括:单机串行(默认)、丢弃后续调度、覆盖之前调度;
- 12、Rolling执行日志:支持在线查看调度结果,并且支持以Rolling方式实时查看执行器输出的完整的执行日志; - 12、分片广播任务:执行器集群部署时,任务路由策略选择"分片广播"情况下,一次任务调度将会广播触发集群中所有执行器执行一次任务,可根据分片参数开发分片任务;
- 13、GLUE:提供Web IDE,支持在线开发任务逻辑代码,动态发布,实时编译生效,省略部署上线的过程。支持30个版本的历史版本回溯。 - 13、动态分片:分片广播任务以执行器为维度进行分片,支持动态扩容执行器集群从而动态增加分片数量,协同进行业务处理;在进行大数据量业务操作时可显著提升任务处理能力和速度。
- 14、数据加密:调度中心和执行器之间的通讯进行数据加密,提升调度信息安全性; - 14、事件触发:除了"Cron方式"和"任务依赖方式"触发任务执行之外,支持基于事件的触发任务方式。调度中心提供触发任务单次执行的API服务,可根据业务事件灵活触发。
- 15、任务依赖:支持配置子任务依赖,当父任务执行结束且执行成功后将会主动触发一次子任务的执行, 多个子任务用逗号分隔; - 15、任务进度监控:支持实时监控任务进度;
- 16、推送maven中央仓库: 将会把最新稳定版推送到maven中央仓库, 方便用户接入和使用; - 16、Rolling实时日志:支持在线查看调度结果,并且支持以Rolling方式实时查看执行器输出的完整的执行日志;
- 17、任务注册: 执行器会周期性自动注册任务, 调度中心将会自动发现注册的任务并触发执行。同时,也支持手动录入执行器地址; - 17、GLUE:提供Web IDE,支持在线开发任务逻辑代码,动态发布,实时编译生效,省略部署上线的过程。支持30个版本的历史版本回溯。
- 18、路由策略:执行器集群部署时提供丰富的路由策略,包括:第一个、最后一个、轮询、随机、一致性HASH、最不经常使用、最近最久未使用、故障转移、忙碌转移等; - 18、脚本任务:支持以GLUE模式开发和运行脚本任务,包括Shell、Python、NodeJS等类型脚本;
- 19、运行报表:支持实时查看运行数据,如任务数量、调度次数、执行器数量等;以及调度报表,如调度日期分布图,调度成功分布图等; - 19、任务依赖:支持配置子任务依赖,当父任务执行结束且执行成功后将会主动触发一次子任务的执行, 多个子任务用逗号分隔;
- 20、脚本任务:支持以GLUE模式开发和运行脚本任务,包括Shell、Python等类型脚本; - 20、一致性:“调度中心”通过DB锁保证集群分布式调度的一致性, 一次任务调度只会触发一次执行;
- 21、阻塞处理策略:调度过于密集执行器来不及处理时的处理策略,策略包括:单机串行(默认)、丢弃后续调度、覆盖之前调度; - 21、自定义任务参数:支持在线配置调度任务入参,即时生效;
- 22、失败处理策略;调度失败时的处理策略,策略包括:失败告警(默认)、失败重试; - 22、调度线程池:调度系统多线程触发调度运行,确保调度精确执行,不被堵塞;
- 23、分片广播任务:执行器集群部署时,任务路由策略选择"分片广播"情况下,一次任务调度将会广播触发集群中所有执行器执行一次任务,可根据分片参数开发分片任务; - 23、数据加密:调度中心和执行器之间的通讯进行数据加密,提升调度信息安全性;
- 24、动态分片:分片广播任务以执行器为维度进行分片,支持动态扩容执行器集群从而动态增加分片数量,协同进行业务处理;在进行大数据量业务操作时可显著提升任务处理能力和速度。 - 24、邮件报警:任务失败时支持邮件报警,支持配置多邮件地址群发报警邮件;
- 25、事件触发:除了"Cron方式"和"任务依赖方式"触发任务执行之外,支持基于事件的触发任务方式。调度中心提供触发任务单次执行的API服务,可根据业务事件灵活触发。 - 25、推送maven中央仓库: 将会把最新稳定版推送到maven中央仓库, 方便用户接入和使用;
- 26、运行报表:支持实时查看运行数据,如任务数量、调度次数、执行器数量等;以及调度报表,如调度日期分布图,调度成功分布图等;
### 1.3 发展 ### 1.3 发展
于2015年中,我在github上创建XXL-JOB项目仓库并提交第一个commit,随之进行系统结构设计,UI选型,交互设计…… 于2015年中,我在github上创建XXL-JOB项目仓库并提交第一个commit,随之进行系统结构设计,UI选型,交互设计……
...@@ -51,8 +52,10 @@ XXL-JOB是一个轻量级分布式任务调度框架,其核心设计目标是 ...@@ -51,8 +52,10 @@ XXL-JOB是一个轻量级分布式任务调度框架,其核心设计目标是
于2017-05-13,在上海举办的 "[第62期开源中国源创会](https://www.oschina.net/event/2236961)" 的 "放码过来" 环节,我登台对XXL-JOB做了演讲,台下五百位在场观众反响热烈([图文回顾](https://www.oschina.net/question/2686220_2242120) )。 于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版本定制而成,新接入应用推荐升级最新版本)。 > 我司大众点评目前已接入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已接入的公司包括不限于: 至今,XXL-JOB已接入多家公司的线上产品线,接入场景如电商业务,O2O业务和大数据作业等,截止2016-07-19为止,XXL-JOB已接入的公司包括不限于:
...@@ -113,6 +116,8 @@ XXL-JOB是一个轻量级分布式任务调度框架,其核心设计目标是 ...@@ -113,6 +116,8 @@ XXL-JOB是一个轻量级分布式任务调度框架,其核心设计目标是
- 55、中商惠民(北京)电子商务有限公司 - 55、中商惠民(北京)电子商务有限公司
- 56、凯京集团 - 56、凯京集团
- 57、华夏票联(北京)科技有限公司 - 57、华夏票联(北京)科技有限公司
- 58、拍拍贷
- 59、北京尚德机构在线教育有限公司
- …… - ……
> 更多接入的公司,欢迎在 [登记地址](https://github.com/xuxueli/xxl-job/issues/1 ) 登记,登记仅仅为了产品推广。 > 更多接入的公司,欢迎在 [登记地址](https://github.com/xuxueli/xxl-job/issues/1 ) 登记,登记仅仅为了产品推广。
...@@ -181,6 +186,7 @@ XXL-JOB是一个轻量级分布式任务调度框架,其核心设计目标是 ...@@ -181,6 +186,7 @@ XXL-JOB是一个轻量级分布式任务调度框架,其核心设计目标是
:xxl-job-executor-sample-spring:Spring版本,通过Spring容器管理执行器,比较通用,推荐这种方式; :xxl-job-executor-sample-spring:Spring版本,通过Spring容器管理执行器,比较通用,推荐这种方式;
:xxl-job-executor-sample-springboot:Springboot版本,通过Springboot管理执行器; :xxl-job-executor-sample-springboot:Springboot版本,通过Springboot管理执行器;
:xxl-job-executor-sample-jfinal:JFinal版本,通过JFinal管理执行器; :xxl-job-executor-sample-jfinal:JFinal版本,通过JFinal管理执行器;
:xxl-job-executor-sample-nutz:Nutz版本,通过Nutz管理执行器;
### 2.3 配置部署“调度中心” ### 2.3 配置部署“调度中心”
...@@ -226,15 +232,17 @@ XXL-JOB是一个轻量级分布式任务调度框架,其核心设计目标是 ...@@ -226,15 +232,17 @@ XXL-JOB是一个轻量级分布式任务调度框架,其核心设计目标是
至此“调度中心”项目已经部署成功。 至此“调度中心”项目已经部署成功。
#### 步骤三:调度中心集群(可选): #### 步骤三:调度中心集群(可选):
调度中心支持集群部署,提升调度系统可用性。 调度中心支持集群部署,提升调度系统容灾和可用性。
集群部署唯一要求为:保证每个集群节点配置(db和登陆账号等)保持一致。调度中心通过db配置区分不同集群。
调度中心在集群部署时可通过nginx负载均衡,此时可以为集群分配一个域名。该域名一方面可以用于访问,另一方面也可以用于配置执行器回调地址。 调度中心集群部署时,几点要求和建议:
- DB配置保持一致;
- 登陆账号配置保持一致;
- 集群机器时钟保持一致(单机集群忽视);
- 建议:推荐通过nginx为调度中心集群做负载均衡,分配域名。调度中心访问、执行器回调配置、调用API服务等操作均通过该域名进行。
### 2.4 配置部署“执行器项目” ### 2.4 配置部署“执行器项目”
“执行器”项目:xxl-job-executor-sample-spring (如新建执行器项目,可参考该Sample示例执行器项目的配置步骤;) “执行器”项目:xxl-job-executor-sample-spring (提供多种版本执行器供选择,现以Spring版本为例,可直接使用,也可以参考其并将现有项目改造成执行器)
作用:负责接收“调度中心”的调度并执行;可直接部署执行器,也可以将执行器集成到现有业务项目中。 作用:负责接收“调度中心”的调度并执行;可直接部署执行器,也可以将执行器集成到现有业务项目中。
#### 步骤一:maven依赖 #### 步骤一:maven依赖
...@@ -278,15 +286,15 @@ XXL-JOB是一个轻量级分布式任务调度框架,其核心设计目标是 ...@@ -278,15 +286,15 @@ XXL-JOB是一个轻量级分布式任务调度框架,其核心设计目标是
<bean id="xxlJobExecutor" class="com.xxl.job.core.executor.XxlJobExecutor" init-method="start" destroy-method="destroy" > <bean id="xxlJobExecutor" class="com.xxl.job.core.executor.XxlJobExecutor" init-method="start" destroy-method="destroy" >
<!-- 执行器IP[选填],为空则自动获取 --> <!-- 执行器IP[选填],为空则自动获取 -->
<property name="ip" value="${xxl.job.executor.ip}" /> <property name="ip" value="${xxl.job.executor.ip}" />
<!-- 执行器端口号[必须] --> <!-- 执行器端口号[选填],为空则自动获取 -->
<property name="port" value="${xxl.job.executor.port}" /> <property name="port" value="${xxl.job.executor.port}" />
<!-- 执行器AppName[选填],为空则关闭自动注册 --> <!-- 执行器AppName[选填],为空则关闭自动注册 -->
<property name="appName" value="${xxl.job.executor.appname}" /> <property name="appName" value="${xxl.job.executor.appname}" />
<!-- 执行器注册中心地址[选填],为空则关闭自动注册 --> <!-- 执行器注册中心地址[选填],为空则关闭自动注册 -->
<property name="adminAddresses" value="${xxl.job.admin.addresses}" /> <property name="adminAddresses" value="${xxl.job.admin.addresses}" />
<!-- 执行器日志路径[必填] --> <!-- 执行器日志路径[选填],为空则使用默认路径 -->
<property name="logPath" value="${xxl.job.executor.logpath}" /> <property name="logPath" value="${xxl.job.executor.logpath}" />
<!-- 访问令牌,非空则进行匹配校验[选填] --> <!-- 访问令牌[选填],非空则进行匹配校验 -->
<property name="accessToken" value="${xxl.job.accessToken}" /> <property name="accessToken" value="${xxl.job.accessToken}" />
</bean> </bean>
``` ```
...@@ -316,7 +324,8 @@ XXL-JOB是一个轻量级分布式任务调度框架,其核心设计目标是 ...@@ -316,7 +324,8 @@ XXL-JOB是一个轻量级分布式任务调度框架,其核心设计目标是
![输入图片说明](https://static.oschina.net/uploads/img/201704/27205910_o8HQ.png "在这里输入图片标题") ![输入图片说明](https://static.oschina.net/uploads/img/201704/27205910_o8HQ.png "在这里输入图片标题")
![输入图片说明](https://static.oschina.net/uploads/img/201704/27210202_SE2u.png "在这里输入图片标题") ![输入图片说明](https://static.oschina.net/uploads/img/201712/25183654_ZAsz.png "在这里输入图片标题")
#### 步骤二:“GLUE模式(Java)” 任务开发: #### 步骤二:“GLUE模式(Java)” 任务开发:
请点击任务右侧 “GLUE” 按钮,进入 “GLUE编辑器开发界面” ,见下图。“GLUE模式(Java)” 运行模式的任务默认已经初始化了示例任务代码,即打印Hello World。 请点击任务右侧 “GLUE” 按钮,进入 “GLUE编辑器开发界面” ,见下图。“GLUE模式(Java)” 运行模式的任务默认已经初始化了示例任务代码,即打印Hello World。
...@@ -365,14 +374,14 @@ XXL-JOB是一个轻量级分布式任务调度框架,其核心设计目标是 ...@@ -365,14 +374,14 @@ XXL-JOB是一个轻量级分布式任务调度框架,其核心设计目标是
GLUE模式(Python):任务以源码方式维护在调度中心;该模式的任务实际上是一段 "python" 脚本; GLUE模式(Python):任务以源码方式维护在调度中心;该模式的任务实际上是一段 "python" 脚本;
GLUE模式(NodeJS):任务以源码方式维护在调度中心;该模式的任务实际上是一段 "nodejs" 脚本; GLUE模式(NodeJS):任务以源码方式维护在调度中心;该模式的任务实际上是一段 "nodejs" 脚本;
- JobHandler:运行模式为 "BEAN模式" 时生效,对应执行器中新开发的JobHandler类“@JobHandler”注解自定义的value值; - JobHandler:运行模式为 "BEAN模式" 时生效,对应执行器中新开发的JobHandler类“@JobHandler”注解自定义的value值;
- 子任务Key:每个任务都拥有一个唯一的任务Key(任务Key可以从任务列表获取),当本任务执行结束并且执行成功时,将会触发子任务Key所对应的任务的一次主动调度。 - 子任务:每个任务都拥有一个唯一的任务ID(任务ID可以从任务列表获取),当本任务执行结束并且执行成功时,将会触发子任务ID所对应的任务的一次主动调度。
- 阻塞处理策略:调度过于密集执行器来不及处理时的处理策略; - 阻塞处理策略:调度过于密集执行器来不及处理时的处理策略;
单机串行(默认):调度请求进入单机执行器后,调度请求进入FIFO队列并以串行方式运行; 单机串行(默认):调度请求进入单机执行器后,调度请求进入FIFO队列并以串行方式运行;
丢弃后续调度:调度请求进入单机执行器后,发现执行器存在运行的调度任务,本次请求将会被丢弃并标记为失败; 丢弃后续调度:调度请求进入单机执行器后,发现执行器存在运行的调度任务,本次请求将会被丢弃并标记为失败;
覆盖之前调度:调度请求进入单机执行器后,发现执行器存在运行的调度任务,将会终止运行中的调度任务并清空队列,然后运行本地调度任务; 覆盖之前调度:调度请求进入单机执行器后,发现执行器存在运行的调度任务,将会终止运行中的调度任务并清空队列,然后运行本地调度任务;
- 失败处理策略;调度失败时的处理策略; - 失败处理策略;调度失败时的处理策略;
失败告警(默认):调度失败时,将会触发失败报警,如发送报警邮件; 失败告警(默认):调度失败和执行失败时,都将会触发失败报警,默认会发送报警邮件;
失败重试:调度失败时,将会主动进行一次失败重试调度,重试调度后仍然失败将会触发一失败告警。注意当任务以failover方式路由时,每次失败重试将会触发新一轮路由。 失败重试:调度失败时,除了进行失败告警之外,将会自动重试一次;注意在执行失败时不会重试,而是根据回调返回值判断是否重试;
- 执行参数:任务执行所需的参数,多个参数时用逗号分隔,任务执行时将会把多个参数转换成数组传入; - 执行参数:任务执行所需的参数,多个参数时用逗号分隔,任务执行时将会把多个参数转换成数组传入;
- 报警邮件:任务调度失败时邮件通知的邮箱地址,支持配置多邮箱地址,配置多个邮箱地址时用逗号分隔; - 报警邮件:任务调度失败时邮件通知的邮箱地址,支持配置多邮箱地址,配置多个邮箱地址时用逗号分隔;
- 负责人:任务的负责人; - 负责人:任务的负责人;
...@@ -381,9 +390,11 @@ XXL-JOB是一个轻量级分布式任务调度框架,其核心设计目标是 ...@@ -381,9 +390,11 @@ XXL-JOB是一个轻量级分布式任务调度框架,其核心设计目标是
任务逻辑以JobHandler的形式存在于“执行器”所在项目中,开发流程如下: 任务逻辑以JobHandler的形式存在于“执行器”所在项目中,开发流程如下:
#### 步骤一:执行器项目中,开发JobHandler: #### 步骤一:执行器项目中,开发JobHandler:
- 1、 新建一个继承com.xxl.job.core.handler.IJobHandler的Java类;
- 2、 该类被Spring容器扫描为Bean实例,如加“@Component”注解; - 1、继承"IJobHandler":“com.xxl.job.core.handler.IJobHandler”;
- 3、 添加 “@JobHandler(value="自定义jobhandler名称")”注解,注解的value值为自定义的JobHandler名称,该名称对应的是调度中心新建任务的JobHandler属性的值。 - 2、注册到Spring容器:添加“@Component”注解,被Spring容器扫描为Bean实例;
- 3、注册到执行器工厂:添加“@JobHandler(value="自定义jobhandler名称")”注解,注解value值对应的是调度中心新建任务的JobHandler属性的值。
- 4、执行日志:需要通过 "XxlJobLogger.log" 打印执行日志;
(可参考Sample示例执行器中的DemoJobHandler,见下图) (可参考Sample示例执行器中的DemoJobHandler,见下图)
![输入图片说明](https://static.oschina.net/uploads/img/201607/23232347_oLlM.png "在这里输入图片标题") ![输入图片说明](https://static.oschina.net/uploads/img/201607/23232347_oLlM.png "在这里输入图片标题")
...@@ -391,7 +402,7 @@ XXL-JOB是一个轻量级分布式任务调度框架,其核心设计目标是 ...@@ -391,7 +402,7 @@ XXL-JOB是一个轻量级分布式任务调度框架,其核心设计目标是
#### 步骤二:调度中心,新建调度任务 #### 步骤二:调度中心,新建调度任务
参考上文“配置属性详细说明”对新建的任务进行参数配置,运行模式选中 "BEAN模式",JobHandler属性填写任务注解“@JobHandler”中定义的值; 参考上文“配置属性详细说明”对新建的任务进行参数配置,运行模式选中 "BEAN模式",JobHandler属性填写任务注解“@JobHandler”中定义的值;
![输入图片说明](https://static.oschina.net/uploads/img/201704/27225124_yrcO.png "在这里输入图片标题") ![输入图片说明](https://static.oschina.net/uploads/img/201712/25183654_ZAsz.png "在这里输入图片标题")
### 3.2 GLUE模式(Java) ### 3.2 GLUE模式(Java)
任务以源码方式维护在调度中心,支持通过Web IDE在线更新,实时编译和生效,因此不需要指定JobHandler。开发流程如下: 任务以源码方式维护在调度中心,支持通过Web IDE在线更新,实时编译和生效,因此不需要指定JobHandler。开发流程如下:
...@@ -399,7 +410,7 @@ XXL-JOB是一个轻量级分布式任务调度框架,其核心设计目标是 ...@@ -399,7 +410,7 @@ XXL-JOB是一个轻量级分布式任务调度框架,其核心设计目标是
#### 步骤一:调度中心,新建调度任务: #### 步骤一:调度中心,新建调度任务:
参考上文“配置属性详细说明”对新建的任务进行参数配置,运行模式选中 "GLUE模式(Java)"; 参考上文“配置属性详细说明”对新建的任务进行参数配置,运行模式选中 "GLUE模式(Java)";
![输入图片说明](https://static.oschina.net/uploads/img/201704/27210202_SE2u.png "在这里输入图片标题") ![输入图片说明](https://static.oschina.net/uploads/img/201712/25183837_tJOq.png "在这里输入图片标题")
#### 步骤二:开发任务代码: #### 步骤二:开发任务代码:
选中指定任务,点击该任务右侧“GLUE”按钮,将会前往GLUE任务的Web IDE界面,在该界面支持对任务代码进行开发(也可以在IDE中开发完成后,复制粘贴到编辑中)。 选中指定任务,点击该任务右侧“GLUE”按钮,将会前往GLUE任务的Web IDE界面,在该界面支持对任务代码进行开发(也可以在IDE中开发完成后,复制粘贴到编辑中)。
...@@ -453,7 +464,8 @@ XXL-JOB是一个轻量级分布式任务调度框架,其核心设计目标是 ...@@ -453,7 +464,8 @@ XXL-JOB是一个轻量级分布式任务调度框架,其核心设计目标是
2、"执行器列表" 中显示在线的执行器列表, 可通过"OnLine 机器"查看对应执行器的集群机器。 2、"执行器列表" 中显示在线的执行器列表, 可通过"OnLine 机器"查看对应执行器的集群机器。
点击按钮 "+新增执行器" 弹框如下图, 可新增执行器配置: 点击按钮 "+新增执行器" 弹框如下图, 可新增执行器配置:
![输入图片说明](https://static.oschina.net/uploads/img/201703/12223617_g3Im.png "在这里输入图片标题")
![输入图片说明](https://static.oschina.net/uploads/img/201712/25183958_V3vF.png "在这里输入图片标题")
### 执行器属性说明 ### 执行器属性说明
...@@ -493,7 +505,7 @@ XXL-JOB是一个轻量级分布式任务调度框架,其核心设计目标是 ...@@ -493,7 +505,7 @@ XXL-JOB是一个轻量级分布式任务调度框架,其核心设计目标是
![输入图片说明](https://static.oschina.net/uploads/img/201607/24133500_9235.png "在这里输入图片标题") ![输入图片说明](https://static.oschina.net/uploads/img/201607/24133500_9235.png "在这里输入图片标题")
![输入图片说明](https://static.oschina.net/uploads/img/201704/27232850_inc8.png "在这里输入图片标题") ![输入图片说明](https://static.oschina.net/uploads/img/201712/25184206_UDSo.png "在这里输入图片标题")
调度时间:"调度中心"触发本次调度并向"执行器"发送任务执行信号的时间; 调度时间:"调度中心"触发本次调度并向"执行器"发送任务执行信号的时间;
调度结果:"调度中心"触发本次调度的结果,200表示成功,500或其他表示失败; 调度结果:"调度中心"触发本次调度的结果,200表示成功,500或其他表示失败;
...@@ -677,7 +689,6 @@ xxl-job-admin#com.xxl.job.admin.controller.JobApiController.callback ...@@ -677,7 +689,6 @@ xxl-job-admin#com.xxl.job.admin.controller.JobApiController.callback
执行器如若集群部署,调度中心将会感知到在线的所有执行器,如“127.0.0.1:9997, 127.0.0.1:9998, 127.0.0.1:9999”。 执行器如若集群部署,调度中心将会感知到在线的所有执行器,如“127.0.0.1:9997, 127.0.0.1:9998, 127.0.0.1:9999”。
当任务"路由策略"选择"故障转移(FAILOVER)"时,当调度中心每次发起调度请求时,会按照顺序对执行器发出心跳检测请求,第一个检测为存活状态的执行器将会被选定并发送调度请求。 当任务"路由策略"选择"故障转移(FAILOVER)"时,当调度中心每次发起调度请求时,会按照顺序对执行器发出心跳检测请求,第一个检测为存活状态的执行器将会被选定并发送调度请求。
![输入图片说明](https://static.oschina.net/uploads/img/201705/11221144_P128.png "在这里输入图片标题")
调度成功后,可在日志监控界面查看“调度备注”,如下; 调度成功后,可在日志监控界面查看“调度备注”,如下;
![输入图片说明](https://static.oschina.net/uploads/img/201703/12230733_jrdI.png "在这里输入图片标题") ![输入图片说明](https://static.oschina.net/uploads/img/201703/12230733_jrdI.png "在这里输入图片标题")
...@@ -688,12 +699,10 @@ xxl-job-admin#com.xxl.job.admin.controller.JobApiController.callback ...@@ -688,12 +699,10 @@ xxl-job-admin#com.xxl.job.admin.controller.JobApiController.callback
#### 5.4.9 调度日志 #### 5.4.9 调度日志
调度中心每次进行任务调度,都会记录一条任务日志,任务日志主要包括以下三部分内容: 调度中心每次进行任务调度,都会记录一条任务日志,任务日志主要包括以下三部分内容:
- 任务信息:包括“执行器地址”、“JobHandler”和“执行参数”等属性,根据这些参数,可以精确的定位任务执行的具体机器和任务代码; - 任务信息:包括“执行器地址”、“JobHandler”和“执行参数”等属性,点击任务ID按钮可查看,根据这些参数,可以精确的定位任务执行的具体机器和任务代码;
- 调度信息:包括“调度时间”、“调度结果”和“调度日志”等,根据这些参数,可以了解“调度中心”发起调度请求时具体情况。 - 调度信息:包括“调度时间”、“调度结果”和“调度日志”等,根据这些参数,可以了解“调度中心”发起调度请求时具体情况。
- 执行信息:包括“执行时间”、“执行结果”和“执行日志”等,根据这些参数,可以了解在“执行器”端任务执行的具体情况; - 执行信息:包括“执行时间”、“执行结果”和“执行日志”等,根据这些参数,可以了解在“执行器”端任务执行的具体情况;
![输入图片说明](https://static.oschina.net/uploads/img/201703/12221436_c8Ru.png "在这里输入图片标题")
调度日志,针对单次调度,属性说明如下: 调度日志,针对单次调度,属性说明如下:
- 执行器地址:任务执行的机器地址; - 执行器地址:任务执行的机器地址;
- JobHandler:Bean模式表示任务执行的JobHandler名称; - JobHandler:Bean模式表示任务执行的JobHandler名称;
...@@ -707,9 +716,9 @@ xxl-job-admin#com.xxl.job.admin.controller.JobApiController.callback ...@@ -707,9 +716,9 @@ xxl-job-admin#com.xxl.job.admin.controller.JobApiController.callback
- 执行日志:任务执行过程中,业务代码中打印的完整执行日志,见“4.7 查看执行日志”; - 执行日志:任务执行过程中,业务代码中打印的完整执行日志,见“4.7 查看执行日志”;
#### 5.4.10 任务依赖 #### 5.4.10 任务依赖
原理:XXL-JOB中每个任务都对应有一个任务Key,同时,每个任务支持设置属性“子任务Key”,因此,通过“任务Key”可以匹配任务依赖关系。 原理:XXL-JOB中每个任务都对应有一个任务ID,同时,每个任务支持设置属性“子任务ID”,因此,通过“任务ID”可以匹配任务依赖关系。
当父任务执行结束并且执行成功时,将会根据“子任务Key”匹配子任务依赖,如果匹配到子任务,将会主动触发一次子任务的执行。 当父任务执行结束并且执行成功时,将会根据“子任务ID”匹配子任务依赖,如果匹配到子任务,将会主动触发一次子任务的执行。
在任务日志界面,点击任务的“执行备注”的“查看”按钮,可以看到匹配子任务以及触发子任务执行的日志信息,如无信息则表示未触发子任务执行,可参考下图。 在任务日志界面,点击任务的“执行备注”的“查看”按钮,可以看到匹配子任务以及触发子任务执行的日志信息,如无信息则表示未触发子任务执行,可参考下图。
...@@ -738,9 +747,7 @@ xxl-job-admin#com.xxl.job.admin.controller.JobApiController.callback ...@@ -738,9 +747,7 @@ xxl-job-admin#com.xxl.job.admin.controller.JobApiController.callback
#### 5.5.4 执行器 #### 5.5.4 执行器
执行器实际上是一个内嵌的Jetty服务器,默认端口9999,如下图配置文件所示(参数:xxl.job.executor.port)。 执行器实际上是一个内嵌的Jetty服务器,默认端口9999(配置项:xxl.job.executor.port)。
![输入图片说明](https://static.oschina.net/uploads/img/201703/10174923_TgNO.png "在这里输入图片标题")
在项目启动时,执行器会通过“@JobHandler”识别Spring容器中“Bean模式任务”,以注解的value属性为key管理起来。 在项目启动时,执行器会通过“@JobHandler”识别Spring容器中“Bean模式任务”,以注解的value属性为key管理起来。
...@@ -786,11 +793,21 @@ XXL-JOB会为每次调度请求生成一个单独的日志文件,需要通过 ...@@ -786,11 +793,21 @@ XXL-JOB会为每次调度请求生成一个单独的日志文件,需要通过
"分片广播" 以执行器为维度进行分片,支持动态扩容执行器集群从而动态增加分片数量,协同进行业务处理;在进行大数据量业务操作时可显著提升任务处理能力和速度。 "分片广播" 以执行器为维度进行分片,支持动态扩容执行器集群从而动态增加分片数量,协同进行业务处理;在进行大数据量业务操作时可显著提升任务处理能力和速度。
"分片广播" 和普通任务开发流程一致,不同之处在于可以可以获取分片参数,获取分片参数对象的代码如下(可参考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开始),执行器集群列表中当前执行器的序号; index:当前分片序号(从0开始),执行器集群列表中当前执行器的序号;
total:总分片数,执行器集群的总机器数量; total:总分片数,执行器集群的总机器数量;
...@@ -819,7 +836,7 @@ XXL-JOB会为每次调度请求生成一个单独的日志文件,需要通过 ...@@ -819,7 +836,7 @@ XXL-JOB会为每次调度请求生成一个单独的日志文件,需要通过
调度中心API服务位置:com.xxl.job.core.biz.AdminBiz.java 调度中心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服务 ### 5.12 执行器API服务
执行器提供了API服务,供调度中心选择使用,目前提供的API服务有: 执行器提供了API服务,供调度中心选择使用,目前提供的API服务有:
...@@ -834,6 +851,15 @@ XXL-JOB会为每次调度请求生成一个单独的日志文件,需要通过 ...@@ -834,6 +851,15 @@ XXL-JOB会为每次调度请求生成一个单独的日志文件,需要通过
执行器API服务请求参考代码:com.xxl.executor.test.DemoJobHandlerTest 执行器API服务请求参考代码:com.xxl.executor.test.DemoJobHandlerTest
### 5.13 故障转移 & 失败重试
一次完整任务流程包括"调度(调度中心) + 执行(执行器)"两个阶段。
- "故障转移"发生在调度阶段,在执行器集群部署时,如果某一台执行器发生故障,该策略支持自动进行Failover切换到一台正常的执行器机器并且完成调度请求流程。
- "失败重试"发生在"调度 + 执行"两个阶段,如下:
- 调度中心调度失败时,任务失败处理策略选择"失败重试",将会自动重试一次;
- 执行器运行失败时,任务执行结果返回"失败重试(IJobHandler.FAIL_RETRY)"回调,将会自动重试一次;
## 六、版本更新日志 ## 六、版本更新日志
### 6.1 版本 V1.1.x,新特性[2015-12-05] ### 6.1 版本 V1.1.x,新特性[2015-12-05]
**【于V1.1.x版本,XXL-JOB正式应用于我司,内部定制别名为 “Ferrari”,新接入应用推荐使用最新版本】** **【于V1.1.x版本,XXL-JOB正式应用于我司,内部定制别名为 “Ferrari”,新接入应用推荐使用最新版本】**
...@@ -1049,40 +1075,51 @@ Tips: 历史版本(V1.3.x)目前已经Release至稳定版本, 进入维护阶段 ...@@ -1049,40 +1075,51 @@ Tips: 历史版本(V1.3.x)目前已经Release至稳定版本, 进入维护阶段
- 10、任务日志文件路径时间戳格式化时SimpleDateFormat并发问题解决; - 10、任务日志文件路径时间戳格式化时SimpleDateFormat并发问题解决;
### 6.20 版本 V1.9.0 特性[迭代中] ### 6.20 版本 V1.9.0 特性[迭代中]
- 1、新增任务运行模式 "GLUE模式(NodeJS) ",支持NodeJS脚本任务; - 1、新增Nutz执行器Sample示例项目;
- 2、失败告警策略扩展:默认提供邮件失败告警,可扩展短信等,扩展代码位置为 "JobFailMonitorHelper.failAlarm"; - 2、新增任务运行模式 "GLUE模式(NodeJS) ",支持NodeJS脚本任务;
- 3、修复任务监控线程被耗时任务阻塞的问题; - 3、脚本任务Shell、Python和Nodejs等支持获取分片参数;
- 4、修复任务监控线程无法监控任务触发和执行状态均未0的问题; - 4、失败重试,完整支持:调度中心调度失败且启用"失败重试"策略时,将会自动重试一次;执行器执行失败且回调失败重试状态(新增失败重试状态返回值)时,也将会自动重试一次;
- 5、调度中心项目日志配置改为xml文件格式; - 5、失败告警策略扩展:默认提供邮件失败告警,可扩展短信等,扩展代码位置为 "JobFailMonitorHelper.failAlarm";
- 6、执行器动态代理对象,拦截非业务方法的执行; - 6、执行器端口支持自动生成(小于等于0时),避免端口定义冲突;
- 7、底层系统日志级别规范调整,清理遗留代码; - 7、调度报表优化,支持时间区间筛选;
- 8、修改JobThread捕获Error错误不更新JobLog的问题; - 8、Log组件支持输出异常栈信息,底层实现优化;
- 9、任务注解调整为 “@JobHandler”,与任务注解统一; - 9、告警邮件样式优化,调整为表格形式,邮件组件调整为commons-email简化邮件操作;
- 10、执行器端口支持随机生成(小于等于0时),避免端口定义冲突; - 10、项目依赖升级,如spring、jackson等;
- 11、任务Cron长度扩展支持至128位; - 11、任务日志,记录发起调度的机器信息;
- 12、调度报表优化,支持时间区间筛选; - 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 ### TODO LIST
- 1、任务权限管理:执行器为粒度分配权限,核心操作校验权限; - 1、任务权限管理:执行器为粒度分配权限,核心操作校验权限;
- 2、任务分片路由:分片采用一致性Hash算法计算出尽量稳定的分片顺序,即使注册机器存在波动也不会引起分批分片顺序大的波动;目前采用IP自然排序,可以满足需求,待定; - 2、任务分片路由:分片采用一致性Hash算法计算出尽量稳定的分片顺序,即使注册机器存在波动也不会引起分批分片顺序大的波动;目前采用IP自然排序,可以满足需求,待定;
- 3、失败重试完整支持:任务流程分为触发和执行,目前仅支持触发失败的重试;后续支持任务执行失败的重试,通过任务返回值判断。 - 3、任务单机多线程:提升任务单机并行处理能力;
- 4、回调失败丢包问题:执行器回调失败写文件,重启或周期性回调重试;调度中心周期性请求并同步未回调的执行结果; - 4、回调失败丢包问题:执行器回调失败写文件,重启或周期性回调重试;调度中心周期性请求并同步未回调的执行结果;
- 5、任务依赖,流程图,子任务+会签任务,各节点日志; - 5、任务依赖,流程图,子任务+会签任务,各节点日志;
- 6、调度任务优先级; - 6、调度任务优先级;
- 7、移除quartz依赖,重写调度模块:新增或恢复任务时将下次执行记录插入delayqueue,调度中心集群竞争分布式锁,成功节点批量加载到期delayqueue数据,批量执行。 - 7、移除quartz依赖,重写调度模块:新增或恢复任务时将下次执行记录插入delayqueue,调度中心集群竞争分布式锁,成功节点批量加载到期delayqueue数据,批量执行。
- 8、springboot 和 docker镜像,并且推送docker镜像到中央仓库,更进一步实现产品开箱即用; - 8、springboot 和 docker镜像,并且推送docker镜像到中央仓库,更进一步实现产品开箱即用;
- 9、国际化:调度中心界面。 - 9、国际化:调度中心界面。
- 10、任务类方法"IJobHandler.execute"的参数类型改为"string",进一步方便参数传递;任务注解和任务类统一并改为"JobHandler""; - 10、任务告警逻辑调整:任务调度,以及任务回调失败时,均推送监控队列。后期考虑通过任务Log字段控制告警状态;
- 11、任务日志,记录发起调度的机器信息; - 11、执行器Log清理功能:调度中心Log删除时同步删除执行器中的Log文件;
- 12、任务告警逻辑调整:任务调度,以及任务回调失败时,均推送监控队列。后期考虑通过任务Log字段控制告警状态; - 12、Bean模式任务,JobHandler自动从执行器中查询展示为下拉框,选择后自动填充任务名称等属性;
- 13、执行器Log清理功能:调度中心Log删除时同步删除执行器中的Log文件; - 13、API事件触发类型任务(更类似MQ消息)支持"动态传参、延时消费";该类型任务不走Quartz,单独建立MQ消息表,调度中心竞争触发;
- 14、脚本任务 Shell、Python和Nodejs,如何友好获取分片参数; - 14、任务依赖增强,新增任务类型 "流程任务",流程节点可挂载普通类型任务,承担任务依赖功能。现有子任务模型取消;需要考虑任务依赖死循环问题;
- 15、Bean模式任务,JobHandler自动从执行器中查询展示为下拉框,选择后自动填充任务名称等属性;
- 16、任务告警邮件优化,调整为表格形式;
- 17、JobHandler提供 init/destroy 方法,支持自定义任务线程销毁逻辑;
- 18、cron表达式的最大长度调整,兼容复杂类型cron;
- 19、执行器回调地址/日志地址格式兼容,是否已"/"结尾均支持;
- 20、任务单机多线程:提升任务单机并行处理能力;
## 七、其他 ## 七、其他
......
CREATE database if NOT EXISTS `xxl-job` default character set utf8 collate utf8_general_ci; CREATE database if NOT EXISTS `xxl-job` default character set utf8 collate utf8_general_ci;
use `xxl-job`; use `xxl-job`;
CREATE TABLE XXL_JOB_QRTZ_JOB_DETAILS CREATE TABLE XXL_JOB_QRTZ_JOB_DETAILS
( (
SCHED_NAME VARCHAR(120) NOT NULL, SCHED_NAME VARCHAR(120) NOT NULL,
...@@ -158,14 +160,14 @@ CREATE TABLE `XXL_JOB_QRTZ_TRIGGER_INFO` ( ...@@ -158,14 +160,14 @@ CREATE TABLE `XXL_JOB_QRTZ_TRIGGER_INFO` (
`alarm_email` varchar(255) DEFAULT NULL COMMENT '报警邮件', `alarm_email` varchar(255) DEFAULT NULL COMMENT '报警邮件',
`executor_route_strategy` varchar(50) DEFAULT NULL COMMENT '执行器路由策略', `executor_route_strategy` varchar(50) DEFAULT NULL COMMENT '执行器路由策略',
`executor_handler` varchar(255) DEFAULT NULL COMMENT '执行器任务handler', `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_block_strategy` varchar(50) DEFAULT NULL COMMENT '阻塞处理策略',
`executor_fail_strategy` varchar(50) DEFAULT NULL COMMENT '失败处理策略', `executor_fail_strategy` varchar(50) DEFAULT NULL COMMENT '失败处理策略',
`glue_type` varchar(50) NOT NULL COMMENT 'GLUE类型', `glue_type` varchar(50) NOT NULL COMMENT 'GLUE类型',
`glue_source` text COMMENT 'GLUE源代码', `glue_source` text COMMENT 'GLUE源代码',
`glue_remark` varchar(128) DEFAULT NULL COMMENT 'GLUE备注', `glue_remark` varchar(128) DEFAULT NULL COMMENT 'GLUE备注',
`glue_updatetime` datetime 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`) PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8; ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
...@@ -176,7 +178,7 @@ CREATE TABLE `XXL_JOB_QRTZ_TRIGGER_LOG` ( ...@@ -176,7 +178,7 @@ CREATE TABLE `XXL_JOB_QRTZ_TRIGGER_LOG` (
`glue_type` varchar(50) DEFAULT NULL COMMENT 'GLUE类型', `glue_type` varchar(50) DEFAULT NULL COMMENT 'GLUE类型',
`executor_address` varchar(255) DEFAULT NULL COMMENT '执行器地址,本次执行的地址', `executor_address` varchar(255) DEFAULT NULL COMMENT '执行器地址,本次执行的地址',
`executor_handler` varchar(255) DEFAULT NULL COMMENT '执行器任务handler', `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_time` datetime DEFAULT NULL COMMENT '调度-时间',
`trigger_code` varchar(255) NOT NULL DEFAULT '0' COMMENT '调度-结果', `trigger_code` varchar(255) NOT NULL DEFAULT '0' COMMENT '调度-结果',
`trigger_msg` varchar(2048) DEFAULT NULL COMMENT '调度-日志', `trigger_msg` varchar(2048) DEFAULT NULL COMMENT '调度-日志',
...@@ -212,7 +214,7 @@ CREATE TABLE `XXL_JOB_QRTZ_TRIGGER_GROUP` ( ...@@ -212,7 +214,7 @@ CREATE TABLE `XXL_JOB_QRTZ_TRIGGER_GROUP` (
`title` varchar(12) NOT NULL COMMENT '执行器名称', `title` varchar(12) NOT NULL COMMENT '执行器名称',
`order` tinyint(4) NOT NULL DEFAULT '0' COMMENT '排序', `order` tinyint(4) NOT NULL DEFAULT '0' COMMENT '排序',
`address_type` tinyint(4) NOT NULL DEFAULT '0' COMMENT '执行器地址类型:0=自动注册、1=手动录入', `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`) PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8; ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
......
...@@ -20,31 +20,31 @@ ...@@ -20,31 +20,31 @@
<javax.servlet-api.version>3.0.1</javax.servlet-api.version> <javax.servlet-api.version>3.0.1</javax.servlet-api.version>
<jsp-api.version>2.2</jsp-api.version> <jsp-api.version>2.2</jsp-api.version>
<spring.version>3.2.18.RELEASE</spring.version> <spring.version>4.3.13.RELEASE</spring.version>
<jackson-mapper-asl.version>1.9.13</jackson-mapper-asl.version> <jackson.version>2.9.3</jackson.version>
<aspectjweaver.version>1.8.7</aspectjweaver.version> <aspectjweaver.version>1.8.13</aspectjweaver.version>
<slf4j-api.version>1.7.25</slf4j-api.version> <slf4j-api.version>1.7.25</slf4j-api.version>
<freemarker.version>2.3.20</freemarker.version> <freemarker.version>2.3.23</freemarker.version>
<junit.version>4.11</junit.version> <junit.version>4.12</junit.version>
<jetty-server.version>9.2.22.v20170606</jetty-server.version> <jetty-server.version>9.2.22.v20170606</jetty-server.version>
<hessian.version>4.0.38</hessian.version> <hessian.version>4.0.51</hessian.version>
<httpclient.version>4.3.6</httpclient.version> <httpclient.version>4.5.4</httpclient.version>
<commons-exec.version>1.3</commons-exec.version> <commons-exec.version>1.3</commons-exec.version>
<commons-beanutils.version>1.9.2</commons-beanutils.version> <commons-collections4.version>4.1</commons-collections4.version>
<commons-lang.version>2.6</commons-lang.version> <commons-lang3.version>3.7</commons-lang3.version>
<commons-email.version>1.5</commons-email.version>
<c3p0.version>0.9.5.2</c3p0.version> <c3p0.version>0.9.5.2</c3p0.version>
<mysql-connector-java.version>5.1.29</mysql-connector-java.version> <mysql-connector-java.version>5.1.45</mysql-connector-java.version>
<mybatis-spring.version>1.2.2</mybatis-spring.version> <mybatis-spring.version>1.3.1</mybatis-spring.version>
<mybatis.version>3.2.8</mybatis.version> <mybatis.version>3.4.5</mybatis.version>
<groovy-all.version>2.4.5</groovy-all.version> <groovy-all.version>2.4.13</groovy-all.version>
<mail.version>1.4.6</mail.version>
<quartz.version>2.3.0</quartz.version> <quartz.version>2.3.0</quartz.version>
<spring-boot.version>1.5.6.RELEASE</spring-boot.version> <spring-boot.version>1.5.9.RELEASE</spring-boot.version>
</properties> </properties>
<build> <build>
......
...@@ -40,18 +40,22 @@ ...@@ -40,18 +40,22 @@
</dependency> </dependency>
<!-- jackson (support spring json) --> <!-- jackson (support spring json) -->
<dependency> <dependency>
<groupId>org.codehaus.jackson</groupId> <groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-mapper-asl</artifactId> <artifactId>jackson-databind</artifactId>
<version>${jackson-mapper-asl.version}</version> <version>${jackson.version}</version>
</dependency> </dependency>
<!-- slf4j --> <!-- servlet -->
<dependency> <dependency>
<groupId>org.slf4j</groupId> <groupId>javax.servlet</groupId>
<artifactId>slf4j-log4j12</artifactId> <artifactId>javax.servlet-api</artifactId>
<version>${slf4j-api.version}</version> <version>${javax.servlet-api.version}</version>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>${jsp-api.version}</version>
</dependency> </dependency>
<!-- freemarker --> <!-- freemarker -->
<dependency> <dependency>
<groupId>org.freemarker</groupId> <groupId>org.freemarker</groupId>
...@@ -59,37 +63,37 @@ ...@@ -59,37 +63,37 @@
<version>${freemarker.version}</version> <version>${freemarker.version}</version>
</dependency> </dependency>
<!-- commons-beanutils --> <!-- slf4j -->
<dependency> <dependency>
<groupId>commons-beanutils</groupId> <groupId>org.slf4j</groupId>
<artifactId>commons-beanutils</artifactId> <artifactId>slf4j-log4j12</artifactId>
<version>${commons-beanutils.version}</version> <version>${slf4j-api.version}</version>
</dependency> </dependency>
<!-- commons-lang --> <!-- junit -->
<dependency> <dependency>
<groupId>commons-lang</groupId> <groupId>junit</groupId>
<artifactId>commons-lang</artifactId> <artifactId>junit</artifactId>
<version>${commons-lang.version}</version> <version>${junit.version}</version>
<scope>test</scope>
</dependency> </dependency>
<!-- servlet --> <!-- commons-collections4 -->
<dependency> <dependency>
<groupId>javax.servlet</groupId> <groupId>org.apache.commons</groupId>
<artifactId>javax.servlet-api</artifactId> <artifactId>commons-collections4</artifactId>
<version>${javax.servlet-api.version}</version> <version>${commons-collections4.version}</version>
</dependency> </dependency>
<!-- commons-lang3 -->
<dependency> <dependency>
<groupId>javax.servlet.jsp</groupId> <groupId>org.apache.commons</groupId>
<artifactId>jsp-api</artifactId> <artifactId>commons-lang3</artifactId>
<version>${jsp-api.version}</version> <version>${commons-lang3.version}</version>
</dependency> </dependency>
<!-- commons-email -->
<!-- junit -->
<dependency> <dependency>
<groupId>junit</groupId> <groupId>org.apache.commons</groupId>
<artifactId>junit</artifactId> <artifactId>commons-email</artifactId>
<version>${junit.version}</version> <version>${commons-email.version}</version>
<scope>test</scope>
</dependency> </dependency>
<!-- c3p0 --> <!-- c3p0 -->
...@@ -116,7 +120,6 @@ ...@@ -116,7 +120,6 @@
<version>${mybatis.version}</version> <version>${mybatis.version}</version>
</dependency> </dependency>
<!-- httpclient --> <!-- httpclient -->
<dependency> <dependency>
<groupId>org.apache.httpcomponents</groupId> <groupId>org.apache.httpcomponents</groupId>
...@@ -124,13 +127,6 @@ ...@@ -124,13 +127,6 @@
<version>${httpclient.version}</version> <version>${httpclient.version}</version>
</dependency> </dependency>
<!-- javax.mail -->
<dependency>
<groupId>javax.mail</groupId>
<artifactId>mail</artifactId>
<version>${mail.version}</version>
</dependency>
<!-- quartz :quartz-2.2.3/c3p0-0.9.1.1/slf4j-api-1.6.6 --> <!-- quartz :quartz-2.2.3/c3p0-0.9.1.1/slf4j-api-1.6.6 -->
<dependency> <dependency>
<groupId>org.quartz-scheduler</groupId> <groupId>org.quartz-scheduler</groupId>
......
...@@ -2,10 +2,9 @@ package com.xxl.job.admin.controller; ...@@ -2,10 +2,9 @@ package com.xxl.job.admin.controller;
import com.xxl.job.admin.controller.annotation.PermessionLimit; import com.xxl.job.admin.controller.annotation.PermessionLimit;
import com.xxl.job.admin.controller.interceptor.PermissionInterceptor; 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.admin.service.XxlJobService;
import com.xxl.job.core.biz.model.ReturnT; 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.beans.propertyeditors.CustomDateEditor;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import org.springframework.ui.Model; import org.springframework.ui.Model;
...@@ -61,18 +60,21 @@ public class IndexController { ...@@ -61,18 +60,21 @@ public class IndexController {
@ResponseBody @ResponseBody
@PermessionLimit(limit=false) @PermessionLimit(limit=false)
public ReturnT<String> loginDo(HttpServletRequest request, HttpServletResponse response, String userName, String password, String ifRemember){ public ReturnT<String> loginDo(HttpServletRequest request, HttpServletResponse response, String userName, String password, String ifRemember){
if (!PermissionInterceptor.ifLogin(request)) { // valid
if (StringUtils.isNotBlank(userName) && StringUtils.isNotBlank(password) if (PermissionInterceptor.ifLogin(request)) {
&& PropertiesUtil.getString("xxl.job.login.username").equals(userName) return ReturnT.SUCCESS;
&& 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 { // param
return new ReturnT<String>(500, "账号或密码错误"); if (StringUtils.isBlank(userName) || StringUtils.isBlank(password)){
return new ReturnT<String>(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<String>(500, "账号或密码错误");
} }
return ReturnT.SUCCESS; return ReturnT.SUCCESS;
} }
......
...@@ -4,7 +4,7 @@ import com.xxl.job.admin.core.model.XxlJobGroup; ...@@ -4,7 +4,7 @@ import com.xxl.job.admin.core.model.XxlJobGroup;
import com.xxl.job.admin.dao.XxlJobGroupDao; import com.xxl.job.admin.dao.XxlJobGroupDao;
import com.xxl.job.admin.dao.XxlJobInfoDao; import com.xxl.job.admin.dao.XxlJobInfoDao;
import com.xxl.job.core.biz.model.ReturnT; 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.stereotype.Controller;
import org.springframework.ui.Model; import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
......
...@@ -11,8 +11,8 @@ import com.xxl.job.core.biz.ExecutorBiz; ...@@ -11,8 +11,8 @@ import com.xxl.job.core.biz.ExecutorBiz;
import com.xxl.job.core.biz.model.LogResult; import com.xxl.job.core.biz.model.LogResult;
import com.xxl.job.core.biz.model.ReturnT; import com.xxl.job.core.biz.model.ReturnT;
import com.xxl.job.core.rpc.netcom.NetComClientProxy; import com.xxl.job.core.rpc.netcom.NetComClientProxy;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang.time.DateUtils; import org.apache.commons.lang3.time.DateUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
......
package com.xxl.job.admin.controller.interceptor; 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.Cookie;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import org.apache.commons.lang.ArrayUtils;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
/** /**
* push cookies to model as cookieMap * push cookies to model as cookieMap
*
* @author xuxueli 2015-12-12 18:09:04 * @author xuxueli 2015-12-12 18:09:04
*/ */
public class CookieInterceptor extends HandlerInterceptorAdapter { public class CookieInterceptor extends HandlerInterceptorAdapter {
......
...@@ -3,6 +3,7 @@ package com.xxl.job.admin.controller.interceptor; ...@@ -3,6 +3,7 @@ package com.xxl.job.admin.controller.interceptor;
import com.xxl.job.admin.controller.annotation.PermessionLimit; import com.xxl.job.admin.controller.annotation.PermessionLimit;
import com.xxl.job.admin.core.util.CookieUtil; import com.xxl.job.admin.core.util.CookieUtil;
import com.xxl.job.admin.core.util.PropertiesUtil; 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.method.HandlerMethod;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
...@@ -12,20 +13,38 @@ import java.math.BigInteger; ...@@ -12,20 +13,38 @@ import java.math.BigInteger;
/** /**
* 权限拦截, 简易版 * 权限拦截, 简易版
*
* @author xuxueli 2015-12-12 18:09:04 * @author xuxueli 2015-12-12 18:09:04
*/ */
public class PermissionInterceptor extends HandlerInterceptorAdapter { 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; public static final String LOGIN_IDENTITY_TOKEN;
static { static {
String username = PropertiesUtil.getString("xxl.job.login.username"); String username = PropertiesUtil.getString("xxl.job.login.username");
String password = PropertiesUtil.getString("xxl.job.login.password"); 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, 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;
} }
public static boolean login(HttpServletResponse response, boolean ifRemember){ // do login
CookieUtil.set(response, LOGIN_IDENTITY_KEY, LOGIN_IDENTITY_TOKEN, ifRemember); CookieUtil.set(response, LOGIN_IDENTITY_KEY, LOGIN_IDENTITY_TOKEN, ifRemember);
return true; return true;
} }
...@@ -40,6 +59,8 @@ public class PermissionInterceptor extends HandlerInterceptorAdapter { ...@@ -40,6 +59,8 @@ public class PermissionInterceptor extends HandlerInterceptorAdapter {
return true; return true;
} }
@Override @Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
......
package com.xxl.job.admin.core.model; 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.ArrayList;
import java.util.Arrays; import java.util.Arrays;
......
...@@ -31,7 +31,7 @@ public class XxlJobInfo { ...@@ -31,7 +31,7 @@ public class XxlJobInfo {
private String glueRemark; // GLUE备注 private String glueRemark; // GLUE备注
private Date glueUpdatetime; // GLUE更新时间 private Date glueUpdatetime; // GLUE更新时间
private String childJobKey; // 子任务Key private String childJobId; // 子任务ID,多个逗号分隔
// copy from quartz // copy from quartz
private String jobStatus; // 任务状态 【base on quartz】 private String jobStatus; // 任务状态 【base on quartz】
...@@ -172,12 +172,12 @@ public class XxlJobInfo { ...@@ -172,12 +172,12 @@ public class XxlJobInfo {
this.glueUpdatetime = glueUpdatetime; this.glueUpdatetime = glueUpdatetime;
} }
public String getChildJobKey() { public String getChildJobId() {
return childJobKey; return childJobId;
} }
public void setChildJobKey(String childJobKey) { public void setChildJobId(String childJobId) {
this.childJobKey = childJobKey; this.childJobId = childJobId;
} }
public String getJobStatus() { public String getJobStatus() {
......
...@@ -6,7 +6,8 @@ import com.xxl.job.admin.core.model.XxlJobLog; ...@@ -6,7 +6,8 @@ import com.xxl.job.admin.core.model.XxlJobLog;
import com.xxl.job.admin.core.schedule.XxlJobDynamicScheduler; import com.xxl.job.admin.core.schedule.XxlJobDynamicScheduler;
import com.xxl.job.admin.core.util.MailUtil; import com.xxl.job.admin.core.util.MailUtil;
import com.xxl.job.core.biz.model.ReturnT; 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.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
...@@ -27,6 +28,8 @@ public class JobFailMonitorHelper { ...@@ -27,6 +28,8 @@ public class JobFailMonitorHelper {
return instance; return instance;
} }
// ---------------------- monitor ----------------------
private LinkedBlockingQueue<Integer> queue = new LinkedBlockingQueue<Integer>(0xfff8); private LinkedBlockingQueue<Integer> queue = new LinkedBlockingQueue<Integer>(0xfff8);
private Thread monitorThread; private Thread monitorThread;
...@@ -51,13 +54,15 @@ public class JobFailMonitorHelper { ...@@ -51,13 +54,15 @@ public class JobFailMonitorHelper {
if (log == null) { if (log == null) {
continue; continue;
} }
if (ReturnT.SUCCESS_CODE == log.getTriggerCode() && log.getHandleCode() == 0) { if (IJobHandler.SUCCESS.getCode() == log.getTriggerCode() && log.getHandleCode() == 0) {
JobFailMonitorHelper.monitor(jobLogId); JobFailMonitorHelper.monitor(jobLogId);
logger.info(">>>>>>>>>>> job monitor, job running, JobLogId:{}", 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 // job success, pass
logger.info(">>>>>>>>>>> job monitor, job success, JobLogId:{}", jobLogId); 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, // job fail,
failAlarm(log); failAlarm(log);
logger.info(">>>>>>>>>>> job monitor, job fail, JobLogId:{}", jobLogId); logger.info(">>>>>>>>>>> job monitor, job fail, JobLogId:{}", jobLogId);
...@@ -94,6 +99,46 @@ public class JobFailMonitorHelper { ...@@ -94,6 +99,46 @@ public class JobFailMonitorHelper {
monitorThread.start(); monitorThread.start();
} }
public void toStop(){
toStop = true;
// interrupt and wait
monitorThread.interrupt();
try {
monitorThread.join();
} catch (InterruptedException e) {
logger.error(e.getMessage(), e);
}
}
// producer
public static void monitor(int jobLogId){
getInstance().queue.offer(jobLogId);
}
// ---------------------- alarm ----------------------
// email alarm template
private static final String mailBodyTemplate = "<h5>监控告警明细:</span>" +
"<table border=\"1\" cellpadding=\"3\" style=\"border-collapse:collapse; width:80%;\" >\n" +
" <thead style=\"font-weight: bold;color: #ffffff;background-color: #ff8c00;\" >" +
" <tr>\n" +
" <td>执行器</td>\n" +
" <td>任务ID</td>\n" +
" <td>任务描述</td>\n" +
" <td>告警类型</td>\n" +
" </tr>\n" +
" <thead/>\n" +
" <tbody>\n" +
" <tr>\n" +
" <td>{0}</td>\n" +
" <td>{1}</td>\n" +
" <td>{2}</td>\n" +
" <td>调度失败</td>\n" +
" </tr>\n" +
" <tbody>\n" +
"</table>";
/** /**
* fail alarm * fail alarm
* *
...@@ -107,31 +152,17 @@ public class JobFailMonitorHelper { ...@@ -107,31 +152,17 @@ public class JobFailMonitorHelper {
Set<String> emailSet = new HashSet<String>(Arrays.asList(info.getAlarmEmail().split(","))); Set<String> emailSet = new HashSet<String>(Arrays.asList(info.getAlarmEmail().split(",")));
for (String email: emailSet) { for (String email: emailSet) {
String title = "《调度监控报警》(任务调度中心XXL-JOB)";
XxlJobGroup group = XxlJobDynamicScheduler.xxlJobGroupDao.load(Integer.valueOf(info.getJobGroup())); 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 String title = "调度中心监控报警";
String content = MessageFormat.format(mailBodyTemplate, group!=null?group.getTitle():"null", info.getId(), info.getJobDesc());
}
public void toStop(){ MailUtil.sendMail(email, title, content);
toStop = true;
// interrupt and wait
monitorThread.interrupt();
try {
monitorThread.join();
} catch (InterruptedException e) {
logger.error(e.getMessage(), e);
} }
} }
// producer // TODO, custom alarm strategy, such as sms
public static void monitor(int jobLogId){
getInstance().queue.offer(jobLogId);
} }
} }
...@@ -4,8 +4,8 @@ import com.xxl.job.admin.core.model.XxlJobGroup; ...@@ -4,8 +4,8 @@ import com.xxl.job.admin.core.model.XxlJobGroup;
import com.xxl.job.admin.core.model.XxlJobRegistry; import com.xxl.job.admin.core.model.XxlJobRegistry;
import com.xxl.job.admin.core.schedule.XxlJobDynamicScheduler; import com.xxl.job.admin.core.schedule.XxlJobDynamicScheduler;
import com.xxl.job.core.enums.RegistryConfig; import com.xxl.job.core.enums.RegistryConfig;
import org.apache.commons.collections.CollectionUtils; import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
......
...@@ -11,7 +11,8 @@ import com.xxl.job.core.biz.ExecutorBiz; ...@@ -11,7 +11,8 @@ import com.xxl.job.core.biz.ExecutorBiz;
import com.xxl.job.core.biz.model.ReturnT; import com.xxl.job.core.biz.model.ReturnT;
import com.xxl.job.core.biz.model.TriggerParam; import com.xxl.job.core.biz.model.TriggerParam;
import com.xxl.job.core.enums.ExecutorBlockStrategyEnum; 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.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
...@@ -66,11 +67,12 @@ public class XxlJobTrigger { ...@@ -66,11 +67,12 @@ public class XxlJobTrigger {
ReturnT<String> triggerResult = new ReturnT<String>(null); ReturnT<String> triggerResult = new ReturnT<String>(null);
StringBuffer triggerMsgSb = new StringBuffer(); StringBuffer triggerMsgSb = new StringBuffer();
triggerMsgSb.append("注册方式:").append( (group.getAddressType() == 0)?"自动注册":"手动录入" ); triggerMsgSb.append("调度机器:").append(IpUtil.getIp());
triggerMsgSb.append("<br>执行器-注册方式:").append( (group.getAddressType() == 0)?"自动注册":"手动录入" );
triggerMsgSb.append("<br>执行器-地址列表:").append(group.getRegistryList());
triggerMsgSb.append("<br>路由策略:").append(executorRouteStrategyEnum.getTitle()).append("("+i+"/"+addressList.size()+")"); // update01
triggerMsgSb.append("<br>阻塞处理策略:").append(blockStrategy.getTitle()); triggerMsgSb.append("<br>阻塞处理策略:").append(blockStrategy.getTitle());
triggerMsgSb.append("<br>失败处理策略:").append(failStrategy.getTitle()); triggerMsgSb.append("<br>失败处理策略:").append(failStrategy.getTitle());
triggerMsgSb.append("<br>地址列表:").append(group.getRegistryList());
triggerMsgSb.append("<br>路由策略:").append(executorRouteStrategyEnum.getTitle()).append("("+i+"/"+addressList.size()+")"); // update01
// 3、trigger-valid // 3、trigger-valid
if (triggerResult.getCode()==ReturnT.SUCCESS_CODE && CollectionUtils.isEmpty(addressList)) { if (triggerResult.getCode()==ReturnT.SUCCESS_CODE && CollectionUtils.isEmpty(addressList)) {
...@@ -115,9 +117,7 @@ public class XxlJobTrigger { ...@@ -115,9 +117,7 @@ public class XxlJobTrigger {
logger.debug(">>>>>>>>>>> xxl-job trigger end, jobId:{}", jobLog.getId()); logger.debug(">>>>>>>>>>> xxl-job trigger end, jobId:{}", jobLog.getId());
} }
return; } else {
}
// 1、save log-id // 1、save log-id
XxlJobLog jobLog = new XxlJobLog(); XxlJobLog jobLog = new XxlJobLog();
jobLog.setJobGroup(jobInfo.getJobGroup()); jobLog.setJobGroup(jobInfo.getJobGroup());
...@@ -134,11 +134,12 @@ public class XxlJobTrigger { ...@@ -134,11 +134,12 @@ public class XxlJobTrigger {
ReturnT<String> triggerResult = new ReturnT<String>(null); ReturnT<String> triggerResult = new ReturnT<String>(null);
StringBuffer triggerMsgSb = new StringBuffer(); StringBuffer triggerMsgSb = new StringBuffer();
triggerMsgSb.append("注册方式:").append( (group.getAddressType() == 0)?"自动注册":"手动录入" ); triggerMsgSb.append("调度机器:").append(IpUtil.getIp());
triggerMsgSb.append("<br>执行器-注册方式:").append( (group.getAddressType() == 0)?"自动注册":"手动录入" );
triggerMsgSb.append("<br>执行器-地址列表:").append(group.getRegistryList());
triggerMsgSb.append("<br>路由策略:").append(executorRouteStrategyEnum.getTitle());
triggerMsgSb.append("<br>阻塞处理策略:").append(blockStrategy.getTitle()); triggerMsgSb.append("<br>阻塞处理策略:").append(blockStrategy.getTitle());
triggerMsgSb.append("<br>失败处理策略:").append(failStrategy.getTitle()); triggerMsgSb.append("<br>失败处理策略:").append(failStrategy.getTitle());
triggerMsgSb.append("<br>地址列表:").append(group.getRegistryList());
triggerMsgSb.append("<br>路由策略:").append(executorRouteStrategyEnum.getTitle());
// 3、trigger-valid // 3、trigger-valid
if (triggerResult.getCode()==ReturnT.SUCCESS_CODE && CollectionUtils.isEmpty(addressList)) { if (triggerResult.getCode()==ReturnT.SUCCESS_CODE && CollectionUtils.isEmpty(addressList)) {
...@@ -168,7 +169,7 @@ public class XxlJobTrigger { ...@@ -168,7 +169,7 @@ public class XxlJobTrigger {
// 4.3、trigger (fail retry) // 4.3、trigger (fail retry)
if (triggerResult.getCode()!=ReturnT.SUCCESS_CODE && failStrategy == ExecutorFailStrategyEnum.FAIL_RETRY) { if (triggerResult.getCode()!=ReturnT.SUCCESS_CODE && failStrategy == ExecutorFailStrategyEnum.FAIL_RETRY) {
triggerResult = executorRouteStrategyEnum.getRouter().routeRun(triggerParam, addressList); triggerResult = executorRouteStrategyEnum.getRouter().routeRun(triggerParam, addressList);
triggerMsgSb.append("<br><br><span style=\"color:#F39C12;\" > >>>>>>>>>>>失败重试<<<<<<<<<<< </span><br>").append(triggerResult.getMsg()); triggerMsgSb.append("<br><br><span style=\"color:#F39C12;\" > >>>>>>>>>>>调度失败重试<<<<<<<<<<< </span><br>").append(triggerResult.getMsg());
} }
} }
...@@ -183,6 +184,8 @@ public class XxlJobTrigger { ...@@ -183,6 +184,8 @@ public class XxlJobTrigger {
logger.debug(">>>>>>>>>>> xxl-job trigger end, jobId:{}", jobLog.getId()); logger.debug(">>>>>>>>>>> xxl-job trigger end, jobId:{}", jobLog.getId());
} }
}
/** /**
* run executor * run executor
* @param triggerParam * @param triggerParam
...@@ -195,7 +198,7 @@ public class XxlJobTrigger { ...@@ -195,7 +198,7 @@ public class XxlJobTrigger {
ExecutorBiz executorBiz = XxlJobDynamicScheduler.getExecutorBiz(address); ExecutorBiz executorBiz = XxlJobDynamicScheduler.getExecutorBiz(address);
runResult = executorBiz.run(triggerParam); runResult = executorBiz.run(triggerParam);
} catch (Exception e) { } 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<String>(ReturnT.FAIL_CODE, ""+e ); runResult = new ReturnT<String>(ReturnT.FAIL_CODE, ""+e );
} }
......
...@@ -6,9 +6,11 @@ import javax.servlet.http.HttpServletResponse; ...@@ -6,9 +6,11 @@ import javax.servlet.http.HttpServletResponse;
/** /**
* Cookie.Util * Cookie.Util
*
* @author xuxueli 2015-12-12 18:01:06 * @author xuxueli 2015-12-12 18:01:06
*/ */
public class CookieUtil { public class CookieUtil {
// 默认缓存时间,单位/秒, 2H // 默认缓存时间,单位/秒, 2H
private static final int COOKIE_MAX_AGE = 60 * 60 * 2; private static final int COOKIE_MAX_AGE = 60 * 60 * 2;
// 保存路径,根路径 // 保存路径,根路径
...@@ -16,43 +18,39 @@ public class CookieUtil { ...@@ -16,43 +18,39 @@ public class CookieUtil {
/** /**
* 保存 * 保存
*
* @param response * @param response
* @param key * @param key
* @param value * @param value
* @param ifRemember * @param ifRemember
*/ */
public static void set(HttpServletResponse response, String key, String value, boolean ifRemember) { public static void set(HttpServletResponse response, String key, String value, boolean ifRemember) {
int age = ifRemember?COOKIE_MAX_AGE:-1;
int age = COOKIE_MAX_AGE; set(response, key, value, null, COOKIE_PATH, age, true);
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);
} }
/** /**
* 保存 * 保存
*
* @param response * @param response
* @param key * @param key
* @param value * @param value
* @param maxAge * @param maxAge
*/ */
private static void set(HttpServletResponse response, private static void set(HttpServletResponse response, String key, String value, String domain, String path, int maxAge, boolean isHttpOnly) {
String key, String value, int maxAge, String path) {
Cookie cookie = new Cookie(key, value); Cookie cookie = new Cookie(key, value);
cookie.setMaxAge(maxAge); // Cookie过期时间,单位/秒 if (domain != null) {
cookie.setPath(path); // Cookie适用的路径 cookie.setDomain(domain);
}
cookie.setPath(path);
cookie.setMaxAge(maxAge);
cookie.setHttpOnly(isHttpOnly);
response.addCookie(cookie); response.addCookie(cookie);
} }
/** /**
* 查询value * 查询value
*
* @param request * @param request
* @param key * @param key
* @return * @return
...@@ -67,6 +65,7 @@ public class CookieUtil { ...@@ -67,6 +65,7 @@ public class CookieUtil {
/** /**
* 查询Cookie * 查询Cookie
*
* @param request * @param request
* @param key * @param key
*/ */
...@@ -84,15 +83,15 @@ public class CookieUtil { ...@@ -84,15 +83,15 @@ public class CookieUtil {
/** /**
* 删除Cookie * 删除Cookie
*
* @param request * @param request
* @param response * @param response
* @param key * @param key
* @param domainName
*/ */
public static void remove(HttpServletRequest request, HttpServletResponse response, String key) { public static void remove(HttpServletRequest request, HttpServletResponse response, String key) {
Cookie cookie = get(request, key); Cookie cookie = get(request, key);
if (cookie != null) { if (cookie != null) {
set(response, key, "", 0, COOKIE_PATH); set(response, key, "", null, COOKIE_PATH, 0, true);
} }
} }
......
package com.xxl.job.admin.core.util; 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.Logger;
import org.slf4j.LoggerFactory; 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 java.nio.charset.Charset;
import javax.mail.internet.MimeUtility;
import java.io.File;
import java.util.Properties;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/** /**
* 邮件发送.Util * 邮件发送.Util
*
* @author xuxueli 2016-3-12 15:06:20 * @author xuxueli 2016-3-12 15:06:20
*/ */
public class MailUtil { public class MailUtil {
...@@ -35,152 +30,42 @@ public class MailUtil { ...@@ -35,152 +30,42 @@ public class MailUtil {
} }
/** /**
<!-- spring mail sender -->
<bean id="javaMailSender" class="org.springframework.mail.javamail.JavaMailSenderImpl" scope="singleton" >
<property name="host" value="${mail.host}" /> <!-- SMTP发送邮件的服务器的IP和端口 -->
<property name="port" value="${mail.port}" />
<property name="username" value="${mail.username}" /> <!-- 登录SMTP邮件发送服务器的用户名和密码 -->
<property name="password" value="${mail.password}" />
<property name="javaMailProperties"> <!-- 获得邮件会话属性,验证登录邮件服务器是否成功 -->
<props>
<prop key="mail.smtp.auth">true</prop>
<prop key="prop">true</prop>
<!-- <prop key="mail.smtp.timeout">25000</prop> -->
</props>
</property>
</bean>
*/
/**
* 发送邮件 (完整版)(结合Spring)
* *
* //@param javaMailSender: 发送Bean * @param toAddress 收件人邮箱
* //@param sendFrom : 发送人邮箱 * @param mailSubject 邮件主题
* //@param sendNick : 发送人昵称 * @param mailBody 邮件正文
* @param toAddress : 收件人邮箱 * @return
* @param mailSubject : 邮件主题
* @param mailBody : 邮件正文
* @param mailBodyIsHtml: 邮件正文格式,true:HTML格式;false:文本格式
* @param attachments : 附件
*/ */
@SuppressWarnings("null") public static boolean sendMail(String toAddress, String mailSubject, String mailBody){
public static boolean sendMailSpring(String toAddress, String mailSubject, String mailBody, boolean mailBodyIsHtml,File[] attachments) {
JavaMailSender javaMailSender = null;//ResourceBundle.getInstance().getJavaMailSender();
try {
MimeMessage mimeMessage = javaMailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, ArrayUtils.isNotEmpty(attachments), "UTF-8"); // 设置utf-8或GBK编码,否则邮件会有乱码;multipart,true表示文件上传
helper.setFrom(username, sendNick);
helper.setTo(toAddress);
// 设置收件人抄送的名片和地址(相当于群发了)
//helper.setCc(InternetAddress.parse(MimeUtility.encodeText("邮箱001") + " <@163.com>," + MimeUtility.encodeText("邮箱002") + " <@foxmail.com>"));
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);
return true;
} catch (Exception 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 { try {
// 创建邮件发送类 JavaMailSender (用于发送多元化邮件,包括附件,图片,html 等) // Create the email message
JavaMailSenderImpl mailSender = new JavaMailSenderImpl(); HtmlEmail email = new HtmlEmail();
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) //email.setDebug(true); // 将会打印一些log
MimeMessage mimeMessage = mailSender.createMimeMessage(); //email.setTLS(true); // 是否TLS校验,,某些邮箱需要TLS安全校验,同理有SSL校验
MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, ArrayUtils.isNotEmpty(attachments), "UTF-8"); //email.setSSL(true);
helper.setFrom(username, sendNick); email.setHostName(host);
helper.setTo(toAddress); email.setSmtpPort(Integer.valueOf(port));
//email.setSslSmtpPort(port);
email.setAuthenticator(new DefaultAuthenticator(username, password));
email.setCharset(Charset.defaultCharset().name());
helper.setSubject(mailSubject); email.setFrom(username, sendNick);
helper.setText(mailBody, mailBodyIsHtml); email.addTo(toAddress);
email.setSubject(mailSubject);
// 设置收件人抄送的名片和地址(相当于群发) email.setMsg(mailBody);
//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);
}
}*/
// 群发 //email.attach(attachment); // add the attachment
//MimeMessage[] mailMessages = { mimeMessage };
mailSender.send(mimeMessage); email.send(); // send the email
return true; return true;
} catch (Exception e) { } catch (EmailException e) {
logger.error(e.getMessage(), 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 = "<html><head><meta http-equiv="
+ "Content-Type"
+ " content="
+ "text/html; charset=gb2312"
+ "></head><body><h1>新书快递通知</h1>你的新书快递申请已推送新书,请到<a href=''>空间"
+ "</a>中查看</body></html>";
sendMail("931591021@qq.com", "测试邮件", mailBody, true, null);
System.out.println(total);
total++;
}
}
}));
} }
return false;
} }
} }
...@@ -12,6 +12,7 @@ import java.util.Properties; ...@@ -12,6 +12,7 @@ import java.util.Properties;
/** /**
* properties util * properties util
*
* @author xuxueli 2015-8-28 10:35:53 * @author xuxueli 2015-8-28 10:35:53
*/ */
public class PropertiesUtil { public class PropertiesUtil {
...@@ -34,8 +35,4 @@ public class PropertiesUtil { ...@@ -34,8 +35,4 @@ public class PropertiesUtil {
return null; return null;
} }
public static void main(String[] args) {
System.out.println(getString("xxl.job.login.username"));
}
} }
package com.xxl.job.admin.service.impl; 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.XxlJobInfo;
import com.xxl.job.admin.core.model.XxlJobLog; 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.XxlJobInfoDao;
import com.xxl.job.admin.dao.XxlJobLogDao; import com.xxl.job.admin.dao.XxlJobLogDao;
import com.xxl.job.admin.dao.XxlJobRegistryDao; import com.xxl.job.admin.dao.XxlJobRegistryDao;
...@@ -13,8 +10,8 @@ import com.xxl.job.core.biz.AdminBiz; ...@@ -13,8 +10,8 @@ import com.xxl.job.core.biz.AdminBiz;
import com.xxl.job.core.biz.model.HandleCallbackParam; import com.xxl.job.core.biz.model.HandleCallbackParam;
import com.xxl.job.core.biz.model.RegistryParam; import com.xxl.job.core.biz.model.RegistryParam;
import com.xxl.job.core.biz.model.ReturnT; import com.xxl.job.core.biz.model.ReturnT;
import org.apache.commons.lang.StringUtils; import com.xxl.job.core.handler.IJobHandler;
import org.quartz.SchedulerException; import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
...@@ -46,7 +43,7 @@ public class AdminBizImpl implements AdminBiz { ...@@ -46,7 +43,7 @@ public class AdminBizImpl implements AdminBiz {
for (HandleCallbackParam handleCallbackParam: callbackParamList) { for (HandleCallbackParam handleCallbackParam: callbackParamList) {
ReturnT<String> callbackResult = callback(handleCallbackParam); ReturnT<String> callbackResult = callback(handleCallbackParam);
logger.info(">>>>>>>>> JobApiController.callback {}, handleCallbackParam={}, callbackResult={}", 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; return ReturnT.SUCCESS;
...@@ -58,28 +55,39 @@ public class AdminBizImpl implements AdminBiz { ...@@ -58,28 +55,39 @@ public class AdminBizImpl implements AdminBiz {
if (log == null) { if (log == null) {
return new ReturnT<String>(ReturnT.FAIL_CODE, "log item not found."); return new ReturnT<String>(ReturnT.FAIL_CODE, "log item not found.");
} }
if (log.getHandleCode() > 0) {
return new ReturnT<String>(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 // trigger success, to trigger child job
String childTriggerMsg = null; String callbackMsg = null;
if (ReturnT.SUCCESS_CODE==handleCallbackParam.getExecuteResult().getCode() && ReturnT.SUCCESS_CODE!=log.getHandleCode()) { if (IJobHandler.SUCCESS.getCode() == handleCallbackParam.getExecuteResult().getCode()) {
XxlJobInfo xxlJobInfo = xxlJobInfoDao.loadById(log.getJobId()); XxlJobInfo xxlJobInfo = xxlJobInfoDao.loadById(log.getJobId());
if (xxlJobInfo!=null && StringUtils.isNotBlank(xxlJobInfo.getChildJobKey())) { if (xxlJobInfo!=null && StringUtils.isNotBlank(xxlJobInfo.getChildJobId())) {
childTriggerMsg = "<hr>"; callbackMsg = "<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发子任务<<<<<<<<<<< </span><br>";
String[] childJobKeys = xxlJobInfo.getChildJobKey().split(",");
for (int i = 0; i < childJobKeys.length; i++) { String[] childJobIds = xxlJobInfo.getChildJobId().split(",");
String[] jobKeyArr = childJobKeys[i].split("_"); for (int i = 0; i < childJobIds.length; i++) {
if (jobKeyArr!=null && jobKeyArr.length==2) { int childJobId = (StringUtils.isNotBlank(childJobIds[i]) && StringUtils.isNumeric(childJobIds[i]))?Integer.valueOf(childJobIds[i]):-1;
ReturnT<String> triggerChildResult = xxlJobService.triggerJob(Integer.valueOf(jobKeyArr[1])); if (childJobId > 0) {
ReturnT<String> triggerChildResult = xxlJobService.triggerJob(childJobId);
// add msg // add msg
childTriggerMsg += MessageFormat.format("<br> {0}/{1} 触发子任务{2}, 子任务Key: {3}, 子任务触发备注: {4}", callbackMsg += MessageFormat.format("{0}/{1} [任务ID={2}], 触发{3}, 触发备注: {4} <br>",
(i+1), childJobKeys.length, (triggerChildResult.getCode()==ReturnT.SUCCESS_CODE?"成功":"失败"), childJobKeys[i], triggerChildResult.getMsg()); (i+1), childJobIds.length, childJobIds[i], (triggerChildResult.getCode()==ReturnT.SUCCESS_CODE?"成功":"失败"), triggerChildResult.getMsg());
} else { } else {
childTriggerMsg += MessageFormat.format("<br> {0}/{1} 触发子任务失败, 子任务Key格式错误, 子任务Key: {2}", callbackMsg += MessageFormat.format(" {0}/{1} [任务ID={2}], 触发失败, 触发备注: 任务ID格式错误 <br>",
(i+1), childJobKeys.length, childJobKeys[i]); (i+1), childJobIds.length, childJobIds[i]);
} }
} }
} }
} else if (IJobHandler.FAIL_RETRY.getCode() == handleCallbackParam.getExecuteResult().getCode()){
ReturnT<String> retryTriggerResult = xxlJobService.triggerJob(log.getJobId());
callbackMsg = "<br><br><span style=\"color:#F39C12;\" > >>>>>>>>>>>执行失败重试<<<<<<<<<<< </span><br>";
callbackMsg += MessageFormat.format("触发{0}, 触发备注: {1}",
(retryTriggerResult.getCode()==ReturnT.SUCCESS_CODE?"成功":"失败"), retryTriggerResult.getMsg());
} }
// handle msg // handle msg
...@@ -90,8 +98,8 @@ public class AdminBizImpl implements AdminBiz { ...@@ -90,8 +98,8 @@ public class AdminBizImpl implements AdminBiz {
if (handleCallbackParam.getExecuteResult().getMsg() != null) { if (handleCallbackParam.getExecuteResult().getMsg() != null) {
handleMsg.append(handleCallbackParam.getExecuteResult().getMsg()); handleMsg.append(handleCallbackParam.getExecuteResult().getMsg());
} }
if (childTriggerMsg !=null) { if (callbackMsg != null) {
handleMsg.append("<br>子任务触发备注:").append(childTriggerMsg); handleMsg.append(callbackMsg);
} }
// success, save log // success, save log
......
...@@ -13,10 +13,10 @@ import com.xxl.job.admin.service.XxlJobService; ...@@ -13,10 +13,10 @@ import com.xxl.job.admin.service.XxlJobService;
import com.xxl.job.core.biz.model.ReturnT; import com.xxl.job.core.biz.model.ReturnT;
import com.xxl.job.core.enums.ExecutorBlockStrategyEnum; import com.xxl.job.core.enums.ExecutorBlockStrategyEnum;
import com.xxl.job.core.glue.GlueTypeEnum; import com.xxl.job.core.glue.GlueTypeEnum;
import org.apache.commons.collections.CollectionUtils; import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang.time.DateUtils; import org.apache.commons.lang3.time.DateUtils;
import org.apache.commons.lang.time.FastDateFormat; import org.apache.commons.lang3.time.FastDateFormat;
import org.quartz.CronExpression; import org.quartz.CronExpression;
import org.quartz.SchedulerException; import org.quartz.SchedulerException;
import org.slf4j.Logger; import org.slf4j.Logger;
...@@ -103,19 +103,20 @@ public class XxlJobServiceImpl implements XxlJobService { ...@@ -103,19 +103,20 @@ public class XxlJobServiceImpl implements XxlJobService {
jobInfo.setGlueSource(jobInfo.getGlueSource().replaceAll("\r", "")); jobInfo.setGlueSource(jobInfo.getGlueSource().replaceAll("\r", ""));
} }
// childJobKey valid // ChildJobId valid
if (StringUtils.isNotBlank(jobInfo.getChildJobKey())) { if (StringUtils.isNotBlank(jobInfo.getChildJobId())) {
String[] childJobKeys = jobInfo.getChildJobKey().split(","); String[] childJobIds = StringUtils.split(jobInfo.getChildJobId(), ",");
for (String childJobKeyItem: childJobKeys) { for (String childJobIdItem: childJobIds) {
String[] childJobKeyArr = childJobKeyItem.split("_"); if (StringUtils.isNotBlank(childJobIdItem) && StringUtils.isNumeric(childJobIdItem)) {
if (childJobKeyArr.length!=2) { XxlJobInfo childJobInfo = xxlJobInfoDao.loadById(Integer.valueOf(childJobIdItem));
return new ReturnT<String>(ReturnT.FAIL_CODE, MessageFormat.format("子任务Key({0})格式错误", childJobKeyItem));
}
XxlJobInfo childJobInfo = xxlJobInfoDao.loadById(Integer.valueOf(childJobKeyArr[1]));
if (childJobInfo==null) { if (childJobInfo==null) {
return new ReturnT<String>(ReturnT.FAIL_CODE, MessageFormat.format("子任务Key({0})无效", childJobKeyItem)); return new ReturnT<String>(ReturnT.FAIL_CODE, MessageFormat.format("子任务ID({0})无效", childJobIdItem));
}
} else {
return new ReturnT<String>(ReturnT.FAIL_CODE, MessageFormat.format("子任务ID({0})格式错误", childJobIdItem));
} }
} }
jobInfo.setChildJobId(StringUtils.join(childJobIds, ","));
} }
// add in db // add in db
...@@ -166,19 +167,24 @@ public class XxlJobServiceImpl implements XxlJobService { ...@@ -166,19 +167,24 @@ public class XxlJobServiceImpl implements XxlJobService {
return new ReturnT<String>(ReturnT.FAIL_CODE, "失败处理策略非法"); return new ReturnT<String>(ReturnT.FAIL_CODE, "失败处理策略非法");
} }
// childJobKey valid // ChildJobId valid
if (StringUtils.isNotBlank(jobInfo.getChildJobKey())) { if (StringUtils.isNotBlank(jobInfo.getChildJobId())) {
String[] childJobKeys = jobInfo.getChildJobKey().split(","); String[] childJobIds = StringUtils.split(jobInfo.getChildJobId(), ",");
for (String childJobKeyItem: childJobKeys) { for (String childJobIdItem: childJobIds) {
String[] childJobKeyArr = childJobKeyItem.split("_"); if (StringUtils.isNotBlank(childJobIdItem) && StringUtils.isNumeric(childJobIdItem)) {
if (childJobKeyArr.length!=2) { XxlJobInfo childJobInfo = xxlJobInfoDao.loadById(Integer.valueOf(childJobIdItem));
return new ReturnT<String>(ReturnT.FAIL_CODE, MessageFormat.format("子任务Key({0})格式错误", childJobKeyItem));
}
XxlJobInfo childJobInfo = xxlJobInfoDao.loadById(Integer.valueOf(childJobKeyArr[1]));
if (childJobInfo==null) { if (childJobInfo==null) {
return new ReturnT<String>(ReturnT.FAIL_CODE, MessageFormat.format("子任务Key({0})无效", childJobKeyItem)); return new ReturnT<String>(ReturnT.FAIL_CODE, MessageFormat.format("子任务ID({0})无效", childJobIdItem));
}
// avoid cycle relate
if (childJobInfo.getId() == jobInfo.getId()) {
return new ReturnT<String>(ReturnT.FAIL_CODE, MessageFormat.format("子任务ID({0})不可与父任务重复", childJobIdItem));
}
} else {
return new ReturnT<String>(ReturnT.FAIL_CODE, MessageFormat.format("子任务ID({0})格式错误", childJobIdItem));
} }
} }
jobInfo.setChildJobId(StringUtils.join(childJobIds, ","));
} }
// stage job info // stage job info
...@@ -197,7 +203,7 @@ public class XxlJobServiceImpl implements XxlJobService { ...@@ -197,7 +203,7 @@ public class XxlJobServiceImpl implements XxlJobService {
exists_jobInfo.setExecutorParam(jobInfo.getExecutorParam()); exists_jobInfo.setExecutorParam(jobInfo.getExecutorParam());
exists_jobInfo.setExecutorBlockStrategy(jobInfo.getExecutorBlockStrategy()); exists_jobInfo.setExecutorBlockStrategy(jobInfo.getExecutorBlockStrategy());
exists_jobInfo.setExecutorFailStrategy(jobInfo.getExecutorFailStrategy()); exists_jobInfo.setExecutorFailStrategy(jobInfo.getExecutorFailStrategy());
exists_jobInfo.setChildJobKey(jobInfo.getChildJobKey()); exists_jobInfo.setChildJobId(jobInfo.getChildJobId());
xxlJobInfoDao.update(exists_jobInfo); xxlJobInfoDao.update(exists_jobInfo);
// fresh quartz // fresh quartz
......
...@@ -27,7 +27,7 @@ ...@@ -27,7 +27,7 @@
<result column="glue_remark" property="glueRemark" /> <result column="glue_remark" property="glueRemark" />
<result column="glue_updatetime" property="glueUpdatetime" /> <result column="glue_updatetime" property="glueUpdatetime" />
<result column="child_jobkey" property="childJobKey" /> <result column="child_jobid" property="childJobId" />
</resultMap> </resultMap>
<sql id="Base_Column_List"> <sql id="Base_Column_List">
...@@ -48,7 +48,7 @@ ...@@ -48,7 +48,7 @@
t.glue_source, t.glue_source,
t.glue_remark, t.glue_remark,
t.glue_updatetime, t.glue_updatetime,
t.child_jobkey t.child_jobid
</sql> </sql>
<select id="pageList" parameterType="java.util.HashMap" resultMap="XxlJobInfo"> <select id="pageList" parameterType="java.util.HashMap" resultMap="XxlJobInfo">
...@@ -97,7 +97,7 @@ ...@@ -97,7 +97,7 @@
glue_source, glue_source,
glue_remark, glue_remark,
glue_updatetime, glue_updatetime,
child_jobkey child_jobid
) VALUES ( ) VALUES (
#{jobGroup}, #{jobGroup},
#{jobCron}, #{jobCron},
...@@ -115,7 +115,7 @@ ...@@ -115,7 +115,7 @@
#{glueSource}, #{glueSource},
#{glueRemark}, #{glueRemark},
NOW(), NOW(),
#{childJobKey} #{childJobId}
); );
<!--<selectKey resultType="java.lang.Integer" order="AFTER" keyProperty="id"> <!--<selectKey resultType="java.lang.Integer" order="AFTER" keyProperty="id">
SELECT LAST_INSERT_ID() SELECT LAST_INSERT_ID()
...@@ -146,7 +146,7 @@ ...@@ -146,7 +146,7 @@
glue_source = #{glueSource}, glue_source = #{glueSource},
glue_remark = #{glueRemark}, glue_remark = #{glueRemark},
glue_updatetime = #{glueUpdatetime}, glue_updatetime = #{glueUpdatetime},
child_jobkey = #{childJobKey} child_jobid = #{childJobId}
WHERE id = #{id} WHERE id = #{id}
</update> </update>
......
...@@ -3,9 +3,9 @@ ...@@ -3,9 +3,9 @@
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context" xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd"> http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.xxl.job.admin.service, com.xxl.job.admin.dao" /> <context:component-scan base-package="com.xxl.job.admin.service, com.xxl.job.admin.dao" />
......
...@@ -3,9 +3,9 @@ ...@@ -3,9 +3,9 @@
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:tx="http://www.springframework.org/schema/tx" xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.0.xsd"> http://www.springframework.org/schema/tx/spring-tx.xsd">
<bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
......
...@@ -4,11 +4,11 @@ ...@@ -4,11 +4,11 @@
xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd"> http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<mvc:annotation-driven /> <mvc:annotation-driven />
<context:component-scan base-package="com.xxl.job.admin.controller" /> <context:component-scan base-package="com.xxl.job.admin.controller" />
......
...@@ -53,7 +53,20 @@ ...@@ -53,7 +53,20 @@
<td>${group.appName}</td> <td>${group.appName}</td>
<td>${group.title}</td> <td>${group.title}</td>
<td><#if group.addressType==0>自动注册<#else>手动录入</#if></td> <td><#if group.addressType==0>自动注册<#else>手动录入</#if></td>
<td><#if group.registryList?exists><#list group.registryList as item><span class="badge bg-green">${item}</span><br></#list></#if></td> <td>
<#if group.registryList?exists>
<#list group.registryList as item>
<span class="badge bg-green" title="${item}" >
<#if item?length gt 35>
${item?substring(0, 35)}...
<#else>
${item}
</#if>
</span>
<br>
</#list>
</#if>
</td>
<td> <td>
<button class="btn btn-warning btn-xs update" <button class="btn btn-warning btn-xs update"
id="${group.id}" id="${group.id}"
...@@ -107,7 +120,9 @@ ...@@ -107,7 +120,9 @@
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="lastname" class="col-sm-2 control-label">机器地址<font color="red">*</font></label> <label for="lastname" class="col-sm-2 control-label">机器地址<font color="red">*</font></label>
<div class="col-sm-10"><input type="text" class="form-control" name="addressList" placeholder="请输入执行器地址列表,多地址逗号分隔" maxlength="200" readonly="readonly" ></div> <div class="col-sm-10">
<textarea class="textarea" name="addressList" maxlength="512" placeholder="请输入执行器地址列表,多地址逗号分隔" readonly="readonly" style="background-color:#eee; width: 100%; height: 100px; font-size: 14px; line-height: 10px; border: 1px solid #dddddd; padding: 10px;"></textarea>
</div>
</div> </div>
<hr> <hr>
<div class="form-group"> <div class="form-group">
...@@ -153,7 +168,9 @@ ...@@ -153,7 +168,9 @@
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="lastname" class="col-sm-2 control-label">机器地址<font color="red">*</font></label> <label for="lastname" class="col-sm-2 control-label">机器地址<font color="red">*</font></label>
<div class="col-sm-10"><input type="text" class="form-control" name="addressList" placeholder="请输入执行器地址列表,多地址逗号分隔" maxlength="200" readonly="readonly" ></div> <div class="col-sm-10">
<textarea class="textarea" name="addressList" maxlength="512" placeholder="请输入执行器地址列表,多地址逗号分隔" readonly="readonly" style="background-color:#eee; width: 100%; height: 100px; font-size: 14px; line-height: 10px; border: 1px solid #dddddd; padding: 10px;"></textarea>
</div>
</div> </div>
<hr> <hr>
<div class="form-group"> <div class="form-group">
......
...@@ -63,12 +63,11 @@ ...@@ -63,12 +63,11 @@
<h3 class="box-title">调度列表</h3> <h3 class="box-title">调度列表</h3>
</div> </div>
<div class="box-body" > <div class="box-body" >
<table id="job_list" class="table table-bordered table-striped"> <table id="job_list" class="table table-bordered table-striped" width="100%" >
<thead> <thead>
<tr> <tr>
<th name="id" >id</th> <th name="id" >任务ID</th>
<th name="jobGroup" >jobGroup</th> <th name="jobGroup" >jobGroup</th>
<th name="childJobKey" >JobKey</th>
<th name="jobDesc" >描述</th> <th name="jobDesc" >描述</th>
<th name="glueType" >运行模式</th> <th name="glueType" >运行模式</th>
<th name="executorParam" >任务参数</th> <th name="executorParam" >任务参数</th>
...@@ -143,9 +142,9 @@ ...@@ -143,9 +142,9 @@
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="firstname" class="col-sm-2 control-label">执行参数<font color="black">*</font></label> <label for="firstname" class="col-sm-2 control-label">执行参数<font color="black">*</font></label>
<div class="col-sm-4"><input type="text" class="form-control" name="executorParam" placeholder="请输入“执行参数”" maxlength="100" ></div> <div class="col-sm-4"><input type="text" class="form-control" name="executorParam" placeholder="请输入“执行参数”" maxlength="512" ></div>
<label for="lastname" class="col-sm-2 control-label">子任务Key<font color="black">*</font></label> <label for="lastname" class="col-sm-2 control-label">子任务ID<font color="black">*</font></label>
<div class="col-sm-4"><input type="text" class="form-control" name="childJobKey" placeholder="请输入子任务的任务Key,如存在多个逗号分隔" maxlength="100" ></div> <div class="col-sm-4"><input type="text" class="form-control" name="childJobId" placeholder="请输入子任务的任务ID,如存在多个逗号分隔" maxlength="100" ></div>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="firstname" class="col-sm-2 control-label">阻塞处理策略<font color="red">*</font></label> <label for="firstname" class="col-sm-2 control-label">阻塞处理策略<font color="red">*</font></label>
...@@ -192,7 +191,7 @@ import com.xxl.job.core.handler.IJobHandler; ...@@ -192,7 +191,7 @@ import com.xxl.job.core.handler.IJobHandler;
public class DemoGlueJobHandler extends IJobHandler { public class DemoGlueJobHandler extends IJobHandler {
@Override @Override
public ReturnT<String> execute(String... params) throws Exception { public ReturnT<String> execute(String param) throws Exception {
XxlJobLogger.log("XXL-JOB, Hello World."); XxlJobLogger.log("XXL-JOB, Hello World.");
return ReturnT.SUCCESS; return ReturnT.SUCCESS;
} }
...@@ -204,12 +203,15 @@ public class DemoGlueJobHandler extends IJobHandler { ...@@ -204,12 +203,15 @@ public class DemoGlueJobHandler extends IJobHandler {
echo "xxl-job: hello shell" echo "xxl-job: hello shell"
echo "脚本位置:$0" echo "脚本位置:$0"
echo "参数数量:$#" echo "任务参数:$1"
echo "分片序号 = $2"
echo "分片总数 = $3"
<#--echo "参数数量:$#"
for param in $* for param in $*
do do
echo "参数 : $param" echo "参数 : $param"
sleep 1s sleep 1s
done done-->
echo "Good bye!" echo "Good bye!"
exit 0 exit 0
...@@ -221,13 +223,18 @@ import time ...@@ -221,13 +223,18 @@ import time
import sys import sys
print "xxl-job: hello python" print "xxl-job: hello python"
print "脚本文件:", sys.argv[0] print "脚本文件:", sys.argv[0]
for i in range(1, len(sys.argv)): print "任务参数:", sys.argv[1]
print "分片序号:", sys.argv[2]
print "分片总数:", sys.argv[3]
<#--for i in range(1, len(sys.argv)):
time.sleep(1) time.sleep(1)
print "参数", i, sys.argv[i] print "参数", i, sys.argv[i]-->
print "Good bye!" print "Good bye!"
exit(0)<#-- exit(0)
<#--
import logging import logging
logging.basicConfig(level=logging.DEBUG) logging.basicConfig(level=logging.DEBUG)
logging.info("脚本文件:" + sys.argv[0]) logging.info("脚本文件:" + sys.argv[0])
...@@ -240,9 +247,12 @@ console.log("xxl-job: hello nodejs") ...@@ -240,9 +247,12 @@ console.log("xxl-job: hello nodejs")
var arguments = process.argv var arguments = process.argv
console.log("脚本文件: " + arguments[1]) console.log("脚本文件: " + arguments[1])
for (var i = 2; i < arguments.length; i++){ console.log("任务参数: " + arguments[2])
console.log("分片序号: " + arguments[3])
console.log("分片总数: " + arguments[4])
<#--for (var i = 2; i < arguments.length; i++){
console.log("参数 %s = %s", (i-1), arguments[i]); console.log("参数 %s = %s", (i-1), arguments[i]);
} }-->
console.log("Good bye!") console.log("Good bye!")
process.exit(0) process.exit(0)
...@@ -300,9 +310,9 @@ process.exit(0) ...@@ -300,9 +310,9 @@ process.exit(0)
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="firstname" class="col-sm-2 control-label">执行参数<font color="black">*</font></label> <label for="firstname" class="col-sm-2 control-label">执行参数<font color="black">*</font></label>
<div class="col-sm-4"><input type="text" class="form-control" name="executorParam" placeholder="请输入“执行参数”" maxlength="100" ></div> <div class="col-sm-4"><input type="text" class="form-control" name="executorParam" placeholder="请输入“执行参数”" maxlength="512" ></div>
<label for="lastname" class="col-sm-2 control-label">子任务Key<font color="black">*</font></label> <label for="lastname" class="col-sm-2 control-label">子任务ID<font color="black">*</font></label>
<div class="col-sm-4"><input type="text" class="form-control" name="childJobKey" placeholder="请输入子任务的任务Key,如存在多个逗号分隔" maxlength="100" ></div> <div class="col-sm-4"><input type="text" class="form-control" name="childJobId" placeholder="请输入子任务的任务ID,如存在多个逗号分隔" maxlength="100" ></div>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="firstname" class="col-sm-2 control-label">阻塞处理策略<font color="red">*</font></label> <label for="firstname" class="col-sm-2 control-label">阻塞处理策略<font color="red">*</font></label>
......
...@@ -90,10 +90,8 @@ ...@@ -90,10 +90,8 @@
<table id="joblog_list" class="table table-bordered table-striped display" width="100%" > <table id="joblog_list" class="table table-bordered table-striped display" width="100%" >
<thead> <thead>
<tr> <tr>
<th name="id" >id</th>
<th name="jobGroup" >执行器ID</th>
<th name="jobId" >任务ID</th> <th name="jobId" >任务ID</th>
<th name="JobKey" >JobKey</th> <th name="jobGroup" >执行器ID</th>
<#--<th name="executorAddress" >执行器地址</th> <#--<th name="executorAddress" >执行器地址</th>
<th name="glueType" >运行模式</th> <th name="glueType" >运行模式</th>
<th name="executorParam" >任务参数</th>--> <th name="executorParam" >任务参数</th>-->
......
...@@ -7,14 +7,18 @@ $(function(){ ...@@ -7,14 +7,18 @@ $(function(){
$.post(base_url + "/logout", function(data, status) { $.post(base_url + "/logout", function(data, status) {
if (data.code == "200") { if (data.code == "200") {
layer.open({ layer.msg('注销成功');
setTimeout(function(){
window.location.href = base_url + "/";
}, 500);
/*layer.open({
title: '系统提示', title: '系统提示',
content: '注销成功', content: '注销成功',
icon: '1', icon: '1',
end: function(layero, index){ end: function(layero, index){
window.location.href = base_url + "/"; window.location.href = base_url + "/";
} }
}); });*/
} else { } else {
layer.open({ layer.open({
title: '系统提示', title: '系统提示',
......
...@@ -122,11 +122,13 @@ $(function() { ...@@ -122,11 +122,13 @@ $(function() {
// 注册方式,切换 // 注册方式,切换
$("#addModal input[name=addressType], #updateModal input[name=addressType]").click(function(){ $("#addModal input[name=addressType], #updateModal input[name=addressType]").click(function(){
var addressType = $(this).val(); var addressType = $(this).val();
var $addressList = $(this).parents("form").find("input[name=addressList]"); var $addressList = $(this).parents("form").find("textarea[name=addressList]");
if (addressType == 0) { if (addressType == 0) {
$addressList.val(""); $addressList.css("background-color", "#eee"); // 自动注册
$addressList.attr("readonly","readonly"); $addressList.attr("readonly","readonly");
$addressList.val("");
} else { } else {
$addressList.css("background-color", "white");
$addressList.removeAttr("readonly"); $addressList.removeAttr("readonly");
} }
}); });
...@@ -144,7 +146,7 @@ $(function() { ...@@ -144,7 +146,7 @@ $(function() {
//$("#updateModal .form input[name='addressType'][value='"+ addressType +"']").attr('checked', 'true'); //$("#updateModal .form input[name='addressType'][value='"+ addressType +"']").attr('checked', 'true');
$("#updateModal .form input[name='addressType'][value='"+ addressType +"']").click(); $("#updateModal .form input[name='addressType'][value='"+ addressType +"']").click();
// 机器地址 // 机器地址
$("#updateModal .form input[name='addressList']").val($(this).attr("addressList")); $("#updateModal .form textarea[name='addressList']").val($(this).attr("addressList"));
$('#updateModal').modal({backdrop: false, keyboard: false}).modal('show'); $('#updateModal').modal({backdrop: false, keyboard: false}).modal('show');
}); });
......
...@@ -20,7 +20,7 @@ $(function() { ...@@ -20,7 +20,7 @@ $(function() {
"ordering": false, "ordering": false,
//"scrollX": true, // X轴滚动条,取消自适应 //"scrollX": true, // X轴滚动条,取消自适应
"columns": [ "columns": [
{ "data": 'id', "bSortable": false, "visible" : false}, { "data": 'id', "bSortable": false, "visible" : true},
{ {
"data": 'jobGroup', "data": 'jobGroup',
"visible" : false, "visible" : false,
...@@ -34,15 +34,6 @@ $(function() { ...@@ -34,15 +34,6 @@ $(function() {
return data; return data;
} }
}, },
{
"data": 'childJobKey',
"width":'10%',
"visible" : true,
"render": function ( data, type, row ) {
var jobKey = row.jobGroup + "_" + row.id;
return jobKey;
}
},
{ "data": 'jobDesc', "visible" : true,"width":'20%'}, { "data": 'jobDesc', "visible" : true,"width":'20%'},
{ {
"data": 'glueType', "data": 'glueType',
...@@ -372,7 +363,7 @@ $(function() { ...@@ -372,7 +363,7 @@ $(function() {
$('#updateModal .form select[name=executorRouteStrategy] option[value='+ row.executorRouteStrategy +']').prop('selected', true); $('#updateModal .form select[name=executorRouteStrategy] option[value='+ row.executorRouteStrategy +']').prop('selected', true);
$("#updateModal .form input[name='executorHandler']").val( row.executorHandler ); $("#updateModal .form input[name='executorHandler']").val( row.executorHandler );
$("#updateModal .form input[name='executorParam']").val( row.executorParam ); $("#updateModal .form input[name='executorParam']").val( row.executorParam );
$("#updateModal .form input[name='childJobKey']").val( row.childJobKey ); $("#updateModal .form input[name='childJobId']").val( row.childJobId );
$('#updateModal .form select[name=executorBlockStrategy] option[value='+ row.executorBlockStrategy +']').prop('selected', true); $('#updateModal .form select[name=executorBlockStrategy] option[value='+ row.executorBlockStrategy +']').prop('selected', true);
$('#updateModal .form select[name=executorFailStrategy] option[value='+ row.executorFailStrategy +']').prop('selected', true); $('#updateModal .form select[name=executorFailStrategy] option[value='+ row.executorFailStrategy +']').prop('selected', true);
$('#updateModal .form select[name=glueType] option[value='+ row.glueType +']').prop('selected', true); $('#updateModal .form select[name=glueType] option[value='+ row.glueType +']').prop('selected', true);
......
...@@ -89,15 +89,10 @@ $(function() { ...@@ -89,15 +89,10 @@ $(function() {
"ordering": false, "ordering": false,
//"scrollX": false, //"scrollX": false,
"columns": [ "columns": [
{ "data": 'id', "bSortable": false, "visible" : false},
{ "data": 'jobGroup', "visible" : false},
{ "data": 'jobId', "visible" : false},
{ {
"data": 'JobKey', "data": 'jobId',
"visible" : true, "visible" : true,
"render": function ( data, type, row ) { "render": function ( data, type, row ) {
var jobKey = row.jobGroup + "_" + row.jobId;
var glueTypeTitle = row.glueType; var glueTypeTitle = row.glueType;
if ('GLUE_GROOVY'==row.glueType) { if ('GLUE_GROOVY'==row.glueType) {
glueTypeTitle = "GLUE模式(Java)"; glueTypeTitle = "GLUE模式(Java)";
...@@ -116,27 +111,10 @@ $(function() { ...@@ -116,27 +111,10 @@ $(function() {
temp += '<br>运行模式:' + glueTypeTitle; temp += '<br>运行模式:' + glueTypeTitle;
temp += '<br>任务参数:' + row.executorParam; temp += '<br>任务参数:' + row.executorParam;
return '<a class="logTips" href="javascript:;" >'+ jobKey +'<span style="display:none;">'+ temp +'</span></a>'; return '<a class="logTips" href="javascript:;" >'+ row.jobId +'<span style="display:none;">'+ temp +'</span></a>';
} }
}, },
// { "data": 'executorAddress', "visible" : true}, { "data": 'jobGroup', "visible" : false},
// {
// "data": 'glueType',
// "visible" : true,
// "render": function ( data, type, row ) {
// if ('GLUE_GROOVY'==row.glueType) {
// return "GLUE模式(Java)";
// } else if ('GLUE_SHELL'==row.glueType) {
// return "GLUE模式(Shell)";
// } else if ('GLUE_PYTHON'==row.glueType) {
// return "GLUE模式(Python)";
// } else if ('BEAN'==row.glueType) {
// return "BEAN模式:" + row.executorHandler;
// }
// return row.executorHandler;
// }
// },
// { "data": 'executorParam', "visible" : true},
{ {
"data": 'triggerTime', "data": 'triggerTime',
"render": function ( data, type, row ) { "render": function ( data, type, row ) {
...@@ -146,9 +124,16 @@ $(function() { ...@@ -146,9 +124,16 @@ $(function() {
{ {
"data": 'triggerCode', "data": 'triggerCode',
"render": function ( data, type, row ) { "render": function ( data, type, row ) {
return (data==200)?'<span style="color: green">成功</span>':(data==500)?'<span style="color: red">失败</span>':(data==0)?'':data; var html = data;
if (data == 200) {
html = '<span style="color: green">成功</span>';
} else if (data == 500) {
html = '<span style="color: red">失败</span>';
} else if (data == 0) {
html = '';
}
return html;
} }
}, },
{ {
"data": 'triggerMsg', "data": 'triggerMsg',
...@@ -165,7 +150,17 @@ $(function() { ...@@ -165,7 +150,17 @@ $(function() {
{ {
"data": 'handleCode', "data": 'handleCode',
"render": function ( data, type, row ) { "render": function ( data, type, row ) {
return (data==200)?'<span style="color: green">成功</span>':(data==500)?'<span style="color: red">失败</span>':(data==0)?'':data; var html = data;
if (data == 200) {
html = '<span style="color: green">成功</span>';
} else if (data == 500) {
html = '<span style="color: red">失败</span>';
} else if (data == 501) {
html = '<span style="color: red">失败重试</span>';
} else if (data == 0) {
html = '';
}
return html;
} }
}, },
{ {
......
...@@ -48,14 +48,18 @@ $(function(){ ...@@ -48,14 +48,18 @@ $(function(){
submitHandler : function(form) { submitHandler : function(form) {
$.post(base_url + "/login", $("#loginForm").serialize(), function(data, status) { $.post(base_url + "/login", $("#loginForm").serialize(), function(data, status) {
if (data.code == "200") { if (data.code == "200") {
layer.open({ layer.msg('登录成功');
setTimeout(function(){
window.location.href = base_url;
}, 500);
/*layer.open({
title: '系统提示', title: '系统提示',
content: '登录成功', content: '登录成功',
icon: '1', icon: '1',
end: function(layero, index){ end: function(layero, index){
window.location.href = base_url; window.location.href = base_url;
} }
}); });*/
} else { } else {
layer.open({ layer.open({
title: '系统提示', title: '系统提示',
......
package com.xxl.job.admin.controller;
import org.junit.Before;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
@WebAppConfiguration
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath*:spring/*.xml"})
public class AbstractSpringMvcTest {
@Autowired
private WebApplicationContext applicationContext;
protected MockMvc mockMvc;
@Before
public void setup() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.applicationContext).build();
}
}
package com.xxl.job.admin.controller;
import com.xxl.job.admin.controller.interceptor.PermissionInterceptor;
import com.xxl.job.admin.core.util.PropertiesUtil;
import org.junit.Before;
import org.junit.Test;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import javax.servlet.http.Cookie;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
public class JobInfoControllerTest extends AbstractSpringMvcTest {
private Cookie cookie;
@Before
public void login() throws Exception {
MvcResult ret = mockMvc.perform(
post("/login")
.contentType(MediaType.APPLICATION_FORM_URLENCODED)
.param("userName", PropertiesUtil.getString("xxl.job.login.username"))
.param("password", PropertiesUtil.getString("xxl.job.login.password"))
).andReturn();
cookie = ret.getResponse().getCookie(PermissionInterceptor.LOGIN_IDENTITY_KEY);
}
@Test
public void testAdd() throws Exception {
MultiValueMap<String, String> parameters = new LinkedMultiValueMap<String, String>();
parameters.add("jobGroup", "1");
MvcResult ret = mockMvc.perform(
post("/jobinfo/pageList")
.contentType(MediaType.APPLICATION_FORM_URLENCODED)
//.content(paramsJson)
.params(parameters)
.cookie(cookie)
).andReturn();
System.out.println(ret.getResponse().getContentAsString());
}
}
package com.xxl.job.dao.impl; package com.xxl.job.admin.dao;
import com.xxl.job.admin.core.model.XxlJobGroup; import com.xxl.job.admin.core.model.XxlJobGroup;
import com.xxl.job.admin.dao.XxlJobGroupDao; import com.xxl.job.admin.dao.XxlJobGroupDao;
......
package com.xxl.job.dao.impl; package com.xxl.job.admin.dao;
import com.xxl.job.admin.core.model.XxlJobInfo; import com.xxl.job.admin.core.model.XxlJobInfo;
import com.xxl.job.admin.dao.XxlJobInfoDao; import com.xxl.job.admin.dao.XxlJobInfoDao;
...@@ -13,7 +13,7 @@ import java.util.List; ...@@ -13,7 +13,7 @@ import java.util.List;
@RunWith(SpringJUnit4ClassRunner.class) @RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath*:spring/applicationcontext-*.xml") @ContextConfiguration(locations = "classpath*:spring/applicationcontext-*.xml")
public class XxlJobInfoTest { public class XxlJobInfoDaoTest {
@Resource @Resource
private XxlJobInfoDao xxlJobInfoDao; private XxlJobInfoDao xxlJobInfoDao;
...@@ -45,7 +45,7 @@ public class XxlJobInfoTest { ...@@ -45,7 +45,7 @@ public class XxlJobInfoTest {
info.setGlueType("setGlueType"); info.setGlueType("setGlueType");
info.setGlueSource("setGlueSource"); info.setGlueSource("setGlueSource");
info.setGlueRemark("setGlueRemark"); info.setGlueRemark("setGlueRemark");
info.setChildJobKey("setChildJobKey"); info.setChildJobId("1");
int count = xxlJobInfoDao.save(info); int count = xxlJobInfoDao.save(info);
...@@ -63,7 +63,7 @@ public class XxlJobInfoTest { ...@@ -63,7 +63,7 @@ public class XxlJobInfoTest {
info2.setGlueSource("setGlueSource2"); info2.setGlueSource("setGlueSource2");
info2.setGlueRemark("setGlueRemark2"); info2.setGlueRemark("setGlueRemark2");
info2.setGlueUpdatetime(new Date()); info2.setGlueUpdatetime(new Date());
info2.setChildJobKey("setChildJobKey2"); info2.setChildJobId("1");
int item2 = xxlJobInfoDao.update(info2); int item2 = xxlJobInfoDao.update(info2);
......
package com.xxl.job.dao.impl; package com.xxl.job.admin.dao;
import com.xxl.job.admin.core.model.XxlJobLog; import com.xxl.job.admin.core.model.XxlJobLog;
import com.xxl.job.admin.dao.XxlJobLogDao; import com.xxl.job.admin.dao.XxlJobLogDao;
import org.apache.commons.lang.time.DateUtils; import org.apache.commons.lang3.time.DateUtils;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.ContextConfiguration;
......
package com.xxl.job.dao.impl; package com.xxl.job.admin.dao;
import com.xxl.job.admin.core.model.XxlJobLogGlue; import com.xxl.job.admin.core.model.XxlJobLogGlue;
import com.xxl.job.admin.dao.XxlJobLogGlueDao; import com.xxl.job.admin.dao.XxlJobLogGlueDao;
......
package com.xxl.job.dao.impl; package com.xxl.job.admin.dao;
import com.xxl.job.admin.core.model.XxlJobRegistry; import com.xxl.job.admin.core.model.XxlJobRegistry;
import com.xxl.job.admin.dao.XxlJobRegistryDao; import com.xxl.job.admin.dao.XxlJobRegistryDao;
......
package com.xxl.job.admin.util;
import com.xxl.job.admin.core.util.MailUtil;
import org.junit.Test;
import java.text.MessageFormat;
/**
* email util test
*
* @author xuxueli 2017-12-22 17:16:23
*/
public class MailUtilTest {
@Test
public void registryTest() throws Exception {
String mailBodyTemplate = "<h5>监控告警明细:</span>" +
"<table border=\"1\" cellpadding=\"3\" style=\"border-collapse:collapse; width:80%;\" >\n" +
" <thead style=\"font-weight: bold;color: #ffffff;background-color: #ff8c00;\" >" +
" <tr>\n" +
" <td>执行器</td>\n" +
" <td>任务ID</td>\n" +
" <td>任务描述</td>\n" +
" <td>告警类型</td>\n" +
" </tr>\n" +
" <thead/>\n" +
" <tbody>\n" +
" <tr>\n" +
" <td>{0}</td>\n" +
" <td>{1}</td>\n" +
" <td>{2}</td>\n" +
" <td>调度失败</td>\n" +
" </tr>\n" +
" <tbody>\n" +
"</table>";
String title = "调度中心监控报警";
String content = MessageFormat.format(mailBodyTemplate, "执行器A", "01", "任务A1");
boolean ret = MailUtil.sendMail("931591021@qq.com", title, content);
System.out.println(ret);
}
}
package com.xxl.job.admin.util;
import com.xxl.job.admin.core.util.PropertiesUtil;
import org.junit.Test;
/**
* prop util test
*
* @author xuxueli 2017-12-25 15:17:36
*/
public class PropertiesUtilTest {
@Test
public void registryTest() throws Exception {
System.out.println(PropertiesUtil.getString("xxl.job.login.username"));
}
}
package com.xxl.job.dao.impl; package com.xxl.job.adminbiz;
import com.xxl.job.core.biz.AdminBiz; import com.xxl.job.core.biz.AdminBiz;
import com.xxl.job.core.biz.model.RegistryParam; import com.xxl.job.core.biz.model.RegistryParam;
......
...@@ -49,9 +49,9 @@ ...@@ -49,9 +49,9 @@
<!-- jackson --> <!-- jackson -->
<dependency> <dependency>
<groupId>org.codehaus.jackson</groupId> <groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-mapper-asl</artifactId> <artifactId>jackson-databind</artifactId>
<version>${jackson-mapper-asl.version}</version> <version>${jackson.version}</version>
</dependency> </dependency>
<!-- httpclient --> <!-- httpclient -->
......
...@@ -59,7 +59,7 @@ public class ExecutorBizImpl implements ExecutorBiz { ...@@ -59,7 +59,7 @@ public class ExecutorBizImpl implements ExecutorBiz {
@Override @Override
public ReturnT<LogResult> log(long logDateTim, int logId, int fromLineNum) { public ReturnT<LogResult> log(long logDateTim, int logId, int fromLineNum) {
// log filename: yyyy-MM-dd/9999.log // log filename: logPath/yyyy-MM-dd/9999.log
String logFileName = XxlJobFileAppender.makeLogFileName(new Date(logDateTim), logId); String logFileName = XxlJobFileAppender.makeLogFileName(new Date(logDateTim), logId);
LogResult logResult = XxlJobFileAppender.readLog(logFileName, fromLineNum); LogResult logResult = XxlJobFileAppender.readLog(logFileName, fromLineNum);
...@@ -74,7 +74,8 @@ public class ExecutorBizImpl implements ExecutorBiz { ...@@ -74,7 +74,8 @@ public class ExecutorBizImpl implements ExecutorBiz {
String removeOldReason = null; String removeOldReason = null;
// valid:jobHandler + jobThread // valid:jobHandler + jobThread
if (GlueTypeEnum.BEAN==GlueTypeEnum.match(triggerParam.getGlueType())) { GlueTypeEnum glueTypeEnum = GlueTypeEnum.match(triggerParam.getGlueType());
if (GlueTypeEnum.BEAN == glueTypeEnum) {
// new jobhandler // new jobhandler
IJobHandler newJobHandler = XxlJobExecutor.loadJobHandler(triggerParam.getExecutorHandler()); IJobHandler newJobHandler = XxlJobExecutor.loadJobHandler(triggerParam.getExecutorHandler());
...@@ -96,7 +97,7 @@ public class ExecutorBizImpl implements ExecutorBiz { ...@@ -96,7 +97,7 @@ public class ExecutorBizImpl implements ExecutorBiz {
} }
} }
} else if (GlueTypeEnum.GLUE_GROOVY==GlueTypeEnum.match(triggerParam.getGlueType())) { } else if (GlueTypeEnum.GLUE_GROOVY == glueTypeEnum) {
// valid old jobThread // valid old jobThread
if (jobThread != null && if (jobThread != null &&
...@@ -119,9 +120,7 @@ public class ExecutorBizImpl implements ExecutorBiz { ...@@ -119,9 +120,7 @@ public class ExecutorBizImpl implements ExecutorBiz {
return new ReturnT<String>(ReturnT.FAIL_CODE, e.getMessage()); return new ReturnT<String>(ReturnT.FAIL_CODE, e.getMessage());
} }
} }
} else if (GlueTypeEnum.GLUE_SHELL==GlueTypeEnum.match(triggerParam.getGlueType()) } else if (glueTypeEnum!=null && glueTypeEnum.isScript()) {
|| GlueTypeEnum.GLUE_PYTHON==GlueTypeEnum.match(triggerParam.getGlueType())
|| GlueTypeEnum.GLUE_NODEJS==GlueTypeEnum.match(triggerParam.getGlueType())) {
// valid old jobThread // valid old jobThread
if (jobThread != null && if (jobThread != null &&
......
...@@ -72,14 +72,10 @@ public class XxlJobExecutor implements ApplicationContextAware { ...@@ -72,14 +72,10 @@ public class XxlJobExecutor implements ApplicationContextAware {
initAdminBizList(adminAddresses, accessToken); initAdminBizList(adminAddresses, accessToken);
// init executor-jobHandlerRepository // init executor-jobHandlerRepository
if (applicationContext != null) {
initJobHandlerRepository(applicationContext); initJobHandlerRepository(applicationContext);
}
// init logpath // init logpath
if (logPath!=null && logPath.trim().length()>0) { XxlJobFileAppender.initLogPath(logPath);
XxlJobFileAppender.logPath = logPath;
}
// init executor-server // init executor-server
initExecutorServer(port, ip, appName, accessToken); initExecutorServer(port, ip, appName, accessToken);
...@@ -145,6 +141,10 @@ public class XxlJobExecutor implements ApplicationContextAware { ...@@ -145,6 +141,10 @@ public class XxlJobExecutor implements ApplicationContextAware {
return jobHandlerRepository.get(name); return jobHandlerRepository.get(name);
} }
private static void initJobHandlerRepository(ApplicationContext applicationContext){ private static void initJobHandlerRepository(ApplicationContext applicationContext){
if (applicationContext == null) {
return;
}
// init job handler action // init job handler action
Map<String, Object> serviceBeanMap = applicationContext.getBeansWithAnnotation(JobHandler.class); Map<String, Object> serviceBeanMap = applicationContext.getBeansWithAnnotation(JobHandler.class);
......
...@@ -5,20 +5,40 @@ package com.xxl.job.core.glue; ...@@ -5,20 +5,40 @@ package com.xxl.job.core.glue;
*/ */
public enum GlueTypeEnum { public enum GlueTypeEnum {
BEAN("BEAN模式"), BEAN("BEAN模式", false, null, null),
GLUE_GROOVY("GLUE模式(Java)"), GLUE_GROOVY("GLUE模式(Java)", false, null, null),
GLUE_SHELL("GLUE模式(Shell)"), GLUE_SHELL("GLUE模式(Shell)", true, "bash", ".sh"),
GLUE_PYTHON("GLUE模式(Python)"), GLUE_PYTHON("GLUE模式(Python)", true, "python", ".py"),
GLUE_NODEJS("GLUE模式(Nodejs)"); GLUE_NODEJS("GLUE模式(Nodejs)", true, "node", ".js");
private String desc; private String desc;
private GlueTypeEnum(String desc) { private boolean isScript;
private String cmd;
private String suffix;
private GlueTypeEnum(String desc, boolean isScript, String cmd, String suffix) {
this.desc = desc; this.desc = desc;
this.isScript = isScript;
this.cmd = cmd;
this.suffix = suffix;
} }
public String getDesc() { public String getDesc() {
return desc; return desc;
} }
public boolean isScript() {
return isScript;
}
public String getCmd() {
return cmd;
}
public String getSuffix() {
return suffix;
}
public static GlueTypeEnum match(String name){ public static GlueTypeEnum match(String name){
for (GlueTypeEnum item: GlueTypeEnum.values()) { for (GlueTypeEnum item: GlueTypeEnum.values()) {
if (item.name().equals(name)) { if (item.name().equals(name)) {
...@@ -27,4 +47,5 @@ public enum GlueTypeEnum { ...@@ -27,4 +47,5 @@ public enum GlueTypeEnum {
} }
return null; return null;
} }
} }
...@@ -3,17 +3,45 @@ package com.xxl.job.core.handler; ...@@ -3,17 +3,45 @@ package com.xxl.job.core.handler;
import com.xxl.job.core.biz.model.ReturnT; import com.xxl.job.core.biz.model.ReturnT;
/** /**
* remote job handler * job handler
*
* @author xuxueli 2015-12-19 19:06:38 * @author xuxueli 2015-12-19 19:06:38
*/ */
public abstract class IJobHandler { public abstract class IJobHandler {
/** success */
public static final ReturnT<String> SUCCESS = new ReturnT<String>(200, null);
/** fail */
public static final ReturnT<String> FAIL = new ReturnT<String>(500, null);
/** fail retry */
public static final ReturnT<String> FAIL_RETRY = new ReturnT<String>(501, null);
/** /**
* job handler * execute handler, invoked when executor receives a scheduling request
* @param params *
* @param param
* @return * @return
* @throws Exception * @throws Exception
*/ */
public abstract ReturnT<String> execute(String... params) throws Exception; public abstract ReturnT<String> execute(String param) throws Exception;
/**
* init handler, invoked when JobThread init
*/
public void init() {
// TODO
}
/**
* destroy handler, invoked when JobThread destroy
*/
public void destroy() {
// TODO
}
} }
...@@ -21,9 +21,9 @@ public class GlueJobHandler extends IJobHandler { ...@@ -21,9 +21,9 @@ public class GlueJobHandler extends IJobHandler {
} }
@Override @Override
public ReturnT<String> execute(String... params) throws Exception { public ReturnT<String> execute(String param) throws Exception {
XxlJobLogger.log("----------- glue.version:"+ glueUpdatetime +" -----------"); XxlJobLogger.log("----------- glue.version:"+ glueUpdatetime +" -----------");
return jobHandler.execute(params); return jobHandler.execute(param);
} }
} }
...@@ -6,6 +6,7 @@ import com.xxl.job.core.handler.IJobHandler; ...@@ -6,6 +6,7 @@ import com.xxl.job.core.handler.IJobHandler;
import com.xxl.job.core.log.XxlJobFileAppender; import com.xxl.job.core.log.XxlJobFileAppender;
import com.xxl.job.core.log.XxlJobLogger; import com.xxl.job.core.log.XxlJobLogger;
import com.xxl.job.core.util.ScriptUtil; import com.xxl.job.core.util.ScriptUtil;
import com.xxl.job.core.util.ShardingUtil;
/** /**
* Created by xuxueli on 17/4/27. * Created by xuxueli on 17/4/27.
...@@ -29,32 +30,38 @@ public class ScriptJobHandler extends IJobHandler { ...@@ -29,32 +30,38 @@ public class ScriptJobHandler extends IJobHandler {
} }
@Override @Override
public ReturnT<String> execute(String... params) throws Exception { public ReturnT<String> execute(String param) throws Exception {
// cmd + script-file-name if (!glueType.isScript()) {
String cmd = "bash"; return new ReturnT<String>(IJobHandler.FAIL.getCode(), "glueType["+ glueType +"] invalid.");
String scriptFileName = null;
if (GlueTypeEnum.GLUE_SHELL == glueType) {
cmd = "bash";
scriptFileName = XxlJobFileAppender.logPath.concat("gluesource/").concat(String.valueOf(jobId)).concat("_").concat(String.valueOf(glueUpdatetime)).concat(".sh");
} else if (GlueTypeEnum.GLUE_PYTHON == glueType) {
cmd = "python";
scriptFileName = XxlJobFileAppender.logPath.concat("gluesource/").concat(String.valueOf(jobId)).concat("_").concat(String.valueOf(glueUpdatetime)).concat(".py");
} else if (GlueTypeEnum.GLUE_NODEJS == glueType) {
cmd = "node";
scriptFileName = XxlJobFileAppender.logPath.concat("gluesource/").concat(String.valueOf(jobId)).concat("_").concat(String.valueOf(glueUpdatetime)).concat(".js");
} }
// cmd
String cmd = glueType.getCmd();
// make script file // make script file
String scriptFileName = XxlJobFileAppender.getLogPath()
.concat("/gluesource/")
.concat(String.valueOf(jobId))
.concat("_")
.concat(String.valueOf(glueUpdatetime))
.concat(glueType.getSuffix());
ScriptUtil.markScriptFile(scriptFileName, gluesource); ScriptUtil.markScriptFile(scriptFileName, gluesource);
// log file // log file
String logFileName = XxlJobFileAppender.logPath.concat(XxlJobFileAppender.contextHolder.get()); String logFileName = XxlJobFileAppender.contextHolder.get();
// script params:0=param、1=分片序号、2=分片总数
ShardingUtil.ShardingVO shardingVO = ShardingUtil.getShardingVo();
String[] scriptParams = new String[3];
scriptParams[0] = param;
scriptParams[1] = String.valueOf(shardingVO.getIndex());
scriptParams[2] = String.valueOf(shardingVO.getTotal());
// invoke // invoke
XxlJobLogger.log("----------- script file:"+ scriptFileName +" -----------"); XxlJobLogger.log("----------- script file:"+ scriptFileName +" -----------");
int exitValue = ScriptUtil.execToFile(cmd, scriptFileName, logFileName, params); int exitValue = ScriptUtil.execToFile(cmd, scriptFileName, logFileName, scriptParams);
ReturnT<String> result = (exitValue==0)?ReturnT.SUCCESS:new ReturnT<String>(ReturnT.FAIL_CODE, "script exit value("+exitValue+") is failed"); ReturnT<String> result = (exitValue==0)?IJobHandler.SUCCESS:new ReturnT<String>(IJobHandler.FAIL.getCode(), "script exit value("+exitValue+") is failed");
return result; return result;
} }
......
...@@ -18,10 +18,35 @@ public class XxlJobFileAppender { ...@@ -18,10 +18,35 @@ public class XxlJobFileAppender {
// for JobThread (support log for child thread of job handler) // for JobThread (support log for child thread of job handler)
//public static ThreadLocal<String> contextHolder = new ThreadLocal<String>(); //public static ThreadLocal<String> contextHolder = new ThreadLocal<String>();
public static final InheritableThreadLocal<String> contextHolder = new InheritableThreadLocal<String>(); public static final InheritableThreadLocal<String> contextHolder = new InheritableThreadLocal<String>();
public static String logPath = "/data/applogs/xxl-job/jobhandler/";
// log base path
private static String logBasePath = "/data/applogs/xxl-job/jobhandler";
public static void initLogPath(String logPath){
// init
if (logPath!=null && logPath.trim().length()>0) {
logBasePath = logPath;
}
// mk base dir
File logPathDir = new File(logBasePath);
if (!logPathDir.exists()) {
logPathDir.mkdirs();
}
logBasePath = logPathDir.getPath();
// mk glue dir
File glueBaseDir = new File(logPathDir, "gluesource");
if (!glueBaseDir.exists()) {
glueBaseDir.mkdirs();
}
}
public static String getLogPath() {
return logBasePath;
}
/** /**
* log filename: yyyy-MM-dd/9999.log * log filename, like "logPath/yyyy-MM-dd/9999.log"
* *
* @param triggerDate * @param triggerDate
* @param logId * @param logId
...@@ -29,23 +54,18 @@ public class XxlJobFileAppender { ...@@ -29,23 +54,18 @@ public class XxlJobFileAppender {
*/ */
public static String makeLogFileName(Date triggerDate, int logId) { public static String makeLogFileName(Date triggerDate, int logId) {
// filePath/ // filePath/yyyy-MM-dd
File filePathDir = new File(logPath);
if (!filePathDir.exists()) {
filePathDir.mkdirs();
}
// filePath/yyyy-MM-dd/
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); // avoid concurrent problem, can not be static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); // avoid concurrent problem, can not be static
File logFilePath = new File(getLogPath(), sdf.format(triggerDate));
String nowFormat = sdf.format(new Date()); if (!logFilePath.exists()) {
File filePathDateDir = new File(filePathDir, nowFormat); logFilePath.mkdir();
if (!filePathDateDir.exists()) {
filePathDateDir.mkdirs();
} }
// filePath/yyyy-MM-dd/9999.log // filePath/yyyy-MM-dd/9999.log
String logFileName = sdf.format(triggerDate).concat("/").concat(String.valueOf(logId)).concat(".log"); String logFileName = logFilePath.getPath()
.concat("/")
.concat(String.valueOf(logId))
.concat(".log");
return logFileName; return logFileName;
} }
...@@ -57,17 +77,11 @@ public class XxlJobFileAppender { ...@@ -57,17 +77,11 @@ public class XxlJobFileAppender {
*/ */
public static void appendLog(String logFileName, String appendLog) { public static void appendLog(String logFileName, String appendLog) {
// log
if (appendLog == null) {
appendLog = "";
}
appendLog += "\r\n";
// log file // log file
if (logFileName==null || logFileName.trim().length()==0) { if (logFileName==null || logFileName.trim().length()==0) {
return; return;
} }
File logFile = new File(logPath, logFileName); File logFile = new File(logFileName);
if (!logFile.exists()) { if (!logFile.exists()) {
try { try {
...@@ -78,6 +92,12 @@ public class XxlJobFileAppender { ...@@ -78,6 +92,12 @@ public class XxlJobFileAppender {
} }
} }
// log
if (appendLog == null) {
appendLog = "";
}
appendLog += "\r\n";
// append file content // append file content
try { try {
FileOutputStream fos = null; FileOutputStream fos = null;
...@@ -112,7 +132,7 @@ public class XxlJobFileAppender { ...@@ -112,7 +132,7 @@ public class XxlJobFileAppender {
if (logFileName==null || logFileName.trim().length()==0) { if (logFileName==null || logFileName.trim().length()==0) {
return new LogResult(fromLineNum, 0, "readLog fail, logFile not found", true); return new LogResult(fromLineNum, 0, "readLog fail, logFile not found", true);
} }
File logFile = new File(logPath, logFileName); File logFile = new File(logFileName);
if (!logFile.exists()) { if (!logFile.exists()) {
return new LogResult(fromLineNum, 0, "readLog fail, logFile not exists", true); return new LogResult(fromLineNum, 0, "readLog fail, logFile not exists", true);
......
...@@ -3,6 +3,8 @@ package com.xxl.job.core.log; ...@@ -3,6 +3,8 @@ package com.xxl.job.core.log;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.text.MessageFormat; import java.text.MessageFormat;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.Date; import java.util.Date;
...@@ -17,46 +19,63 @@ public class XxlJobLogger { ...@@ -17,46 +19,63 @@ public class XxlJobLogger {
/** /**
* append log * append log
* *
* @param callInfo
* @param appendLog * @param appendLog
*/ */
public static void log(String appendLog) { private static void logDetail(StackTraceElement callInfo, String appendLog) {
// logFileName
String logFileName = XxlJobFileAppender.contextHolder.get();
if (logFileName==null || logFileName.trim().length()==0) {
return;
}
// "yyyy-MM-dd HH:mm:ss [ClassName]-[MethodName]-[LineNumber]-[ThreadName] log"; /*// "yyyy-MM-dd HH:mm:ss [ClassName]-[MethodName]-[LineNumber]-[ThreadName] log";
StackTraceElement[] stackTraceElements = new Throwable().getStackTrace(); StackTraceElement[] stackTraceElements = new Throwable().getStackTrace();
StackTraceElement callInfo = stackTraceElements[1]; StackTraceElement callInfo = stackTraceElements[1];*/
StringBuffer stringBuffer = new StringBuffer(); StringBuffer stringBuffer = new StringBuffer();
stringBuffer.append(xxlJobLoggerFormat.format(new Date())).append(" ") stringBuffer.append(xxlJobLoggerFormat.format(new Date())).append(" ")
.append("["+ callInfo.getClassName() +"]").append("-") .append("["+ callInfo.getClassName() + "#" + callInfo.getMethodName() +"]").append("-")
.append("["+ callInfo.getMethodName() +"]").append("-")
.append("["+ callInfo.getLineNumber() +"]").append("-") .append("["+ callInfo.getLineNumber() +"]").append("-")
.append("["+ Thread.currentThread().getName() +"]").append(" ") .append("["+ Thread.currentThread().getName() +"]").append(" ")
.append(appendLog!=null?appendLog:""); .append(appendLog!=null?appendLog:"");
String formatAppendLog = stringBuffer.toString(); String formatAppendLog = stringBuffer.toString();
// appendlog // appendlog
String logFileName = XxlJobFileAppender.contextHolder.get();
if (logFileName!=null && logFileName.trim().length()>0) {
XxlJobFileAppender.appendLog(logFileName, formatAppendLog); XxlJobFileAppender.appendLog(logFileName, formatAppendLog);
} else {
logger.debug(">>>>>>>>>>> [{}]: {}", logFileName, formatAppendLog); logger.info(">>>>>>>>>>> {}", formatAppendLog);
}
} }
/** /**
* append log with pattern * append log with pattern
* *
* @
*
* @param appendLogPattern like "aaa {0} bbb {1} ccc" * @param appendLogPattern like "aaa {0} bbb {1} ccc"
* @param appendLogArguments like "111, true" * @param appendLogArguments like "111, true"
*/ */
public static void log(String appendLogPattern, Object ... appendLogArguments) { public static void log(String appendLogPattern, Object ... appendLogArguments) {
String appendLog = MessageFormat.format(appendLogPattern, appendLogArguments);
log(appendLog); String appendLog = appendLogPattern;
if (appendLogArguments!=null && appendLogArguments.length>0) {
appendLog = MessageFormat.format(appendLogPattern, appendLogArguments);
}
StackTraceElement callInfo = new Throwable().getStackTrace()[1];
logDetail(callInfo, appendLog);
}
/**
* append exception stack
*
* @param e
*/
public static void log(Throwable e) {
StringWriter stringWriter = new StringWriter();
e.printStackTrace(new PrintWriter(stringWriter));
String appendLog = stringWriter.toString();
StackTraceElement callInfo = new Throwable().getStackTrace()[1];
logDetail(callInfo, appendLog);
} }
} }
...@@ -92,6 +92,14 @@ public class JobThread extends Thread{ ...@@ -92,6 +92,14 @@ public class JobThread extends Thread{
@Override @Override
public void run() { public void run() {
// init
try {
handler.init();
} catch (Throwable e) {
logger.error(e.getMessage(), e);
}
// execute
while(!toStop){ while(!toStop){
running = false; running = false;
idleTimes++; idleTimes++;
...@@ -106,21 +114,16 @@ public class JobThread extends Thread{ ...@@ -106,21 +114,16 @@ public class JobThread extends Thread{
idleTimes = 0; idleTimes = 0;
triggerLogIdSet.remove(triggerParam.getLogId()); triggerLogIdSet.remove(triggerParam.getLogId());
// parse param // log filename, like "logPath/yyyy-MM-dd/9999.log"
String[] handlerParams = (triggerParam.getExecutorParams()!=null && triggerParam.getExecutorParams().trim().length()>0)
? (String[])(Arrays.asList(triggerParam.getExecutorParams().split(",")).toArray()) : null;
// log filename: yyyy-MM-dd/9999.log
String logFileName = XxlJobFileAppender.makeLogFileName(new Date(triggerParam.getLogDateTim()), triggerParam.getLogId()); String logFileName = XxlJobFileAppender.makeLogFileName(new Date(triggerParam.getLogDateTim()), triggerParam.getLogId());
XxlJobFileAppender.contextHolder.set(logFileName); XxlJobFileAppender.contextHolder.set(logFileName);
ShardingUtil.setShardingVo(new ShardingUtil.ShardingVO(triggerParam.getBroadcastIndex(), triggerParam.getBroadcastTotal())); ShardingUtil.setShardingVo(new ShardingUtil.ShardingVO(triggerParam.getBroadcastIndex(), triggerParam.getBroadcastTotal()));
// execute // execute
XxlJobLogger.log("<br>----------- xxl-job job execute start -----------<br>----------- Params:" + Arrays.toString(handlerParams)); XxlJobLogger.log("<br>----------- xxl-job job execute start -----------<br>----------- Param:" + triggerParam.getExecutorParams());
executeResult = handler.execute(handlerParams); executeResult = handler.execute(triggerParam.getExecutorParams());
if (executeResult == null) { if (executeResult == null) {
executeResult = ReturnT.FAIL; executeResult = IJobHandler.FAIL;
} }
XxlJobLogger.log("<br>----------- xxl-job job execute end(finish) -----------<br>----------- ReturnT:" + executeResult); XxlJobLogger.log("<br>----------- xxl-job job execute end(finish) -----------<br>----------- ReturnT:" + executeResult);
...@@ -165,6 +168,13 @@ public class JobThread extends Thread{ ...@@ -165,6 +168,13 @@ public class JobThread extends Thread{
} }
} }
// destroy
try {
handler.destroy();
} catch (Throwable e) {
logger.error(e.getMessage(), e);
}
logger.info(">>>>>>>>>>> xxl-job JobThread stoped, hashCode:{}", Thread.currentThread()); logger.info(">>>>>>>>>>> xxl-job JobThread stoped, hashCode:{}", Thread.currentThread());
} }
} }
package com.xxl.job.core.util; package com.xxl.job.core.util;
import com.fasterxml.jackson.core.JsonGenerationException;
import org.codehaus.jackson.JsonGenerationException; import com.fasterxml.jackson.core.JsonParseException;
import org.codehaus.jackson.JsonParseException; import com.fasterxml.jackson.core.type.TypeReference;
import org.codehaus.jackson.map.JsonMappingException; import com.fasterxml.jackson.databind.JsonMappingException;
import org.codehaus.jackson.map.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import org.codehaus.jackson.type.TypeReference;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
......
package com.xxl.job.core.util; package com.xxl.job.core.util;
import com.xxl.job.core.log.XxlJobFileAppender;
import org.apache.commons.exec.CommandLine; import org.apache.commons.exec.CommandLine;
import org.apache.commons.exec.DefaultExecutor; import org.apache.commons.exec.DefaultExecutor;
import org.apache.commons.exec.PumpStreamHandler; import org.apache.commons.exec.PumpStreamHandler;
import java.io.File;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
...@@ -27,18 +25,6 @@ public class ScriptUtil { ...@@ -27,18 +25,6 @@ public class ScriptUtil {
* @throws IOException * @throws IOException
*/ */
public static void markScriptFile(String scriptFileName, String content) throws IOException { public static void markScriptFile(String scriptFileName, String content) throws IOException {
// filePath/
File filePathDir = new File(XxlJobFileAppender.logPath);
if (!filePathDir.exists()) {
filePathDir.mkdirs();
}
// filePath/gluesource/
File filePathSourceDir = new File(filePathDir, "gluesource");
if (!filePathSourceDir.exists()) {
filePathSourceDir.mkdirs();
}
// make file, filePath/gluesource/666-123456789.py // make file, filePath/gluesource/666-123456789.py
FileOutputStream fileOutputStream = null; FileOutputStream fileOutputStream = null;
try { try {
......
...@@ -14,6 +14,7 @@ ...@@ -14,6 +14,7 @@
<module>xxl-job-executor-sample-spring</module> <module>xxl-job-executor-sample-spring</module>
<module>xxl-job-executor-sample-springboot</module> <module>xxl-job-executor-sample-springboot</module>
<module>xxl-job-executor-sample-jfinal</module> <module>xxl-job-executor-sample-jfinal</module>
<module>xxl-job-executor-sample-nutz</module>
</modules> </modules>
</project> </project>
\ No newline at end of file
...@@ -16,7 +16,7 @@ ...@@ -16,7 +16,7 @@
<dependency> <dependency>
<groupId>com.jfinal</groupId> <groupId>com.jfinal</groupId>
<artifactId>jfinal</artifactId> <artifactId>jfinal</artifactId>
<version>2.0</version> <version>2.2</version>
</dependency> </dependency>
<!-- slf4j --> <!-- slf4j -->
......
...@@ -17,8 +17,9 @@ public class JFinalCoreConfig extends JFinalConfig { ...@@ -17,8 +17,9 @@ public class JFinalCoreConfig extends JFinalConfig {
private Logger logger = LoggerFactory.getLogger(JFinalCoreConfig.class); private Logger logger = LoggerFactory.getLogger(JFinalCoreConfig.class);
// ---------------------- xxl-job executor ---------------------- // ---------------------- xxl-job executor ----------------------
XxlJobExecutor xxlJobExecutor = null; private XxlJobExecutor xxlJobExecutor = null;
private void initXxlJobExecutor() { private void initXxlJobExecutor() {
// registry jobhandler // registry jobhandler
XxlJobExecutor.registJobHandler("demoJobHandler", new DemoJobHandler()); XxlJobExecutor.registJobHandler("demoJobHandler", new DemoJobHandler());
XxlJobExecutor.registJobHandler("shardingJobHandler", new ShardingJobHandler()); XxlJobExecutor.registJobHandler("shardingJobHandler", new ShardingJobHandler());
......
...@@ -7,4 +7,5 @@ public class IndexController extends Controller { ...@@ -7,4 +7,5 @@ public class IndexController extends Controller {
public void index(){ public void index(){
renderText("xxl job executor running."); renderText("xxl job executor running.");
} }
} }
...@@ -6,28 +6,27 @@ import com.xxl.job.core.log.XxlJobLogger; ...@@ -6,28 +6,27 @@ import com.xxl.job.core.log.XxlJobLogger;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
/** /**
* 任务Handler的一个Demo(Bean模式) * 任务Handler示例(Bean模式)
* *
* 开发步骤: * 开发步骤:
* 1、继承 “IJobHandler” * 1、继承"IJobHandler":“com.xxl.job.core.handler.IJobHandler”
* 2、执行日志:需要通过 "XxlJobLogger.log" 打印执行日志; * 2、注册到执行器工厂:在 "JFinalCoreConfig.initXxlJobExecutor" 中手动注册,注解key值对应的是调度中心新建任务的JobHandler属性的值。
* 3、在 "JFinalCoreConfig" 中注册,执行Jobhandler名称 * 3、执行日志:需要通过 "XxlJobLogger.log" 打印执行日志
* *
* @author xuxueli 2015-12-19 19:43:36 * @author xuxueli 2015-12-19 19:43:36
*/ */
public class DemoJobHandler extends IJobHandler { public class DemoJobHandler extends IJobHandler {
@Override @Override
public ReturnT<String> execute(String... params) throws Exception { public ReturnT<String> execute(String param) throws Exception {
XxlJobLogger.log("XXL-JOB, Hello World."); XxlJobLogger.log("XXL-JOB, Hello World.");
for (int i = 0; i < 5; i++) { for (int i = 0; i < 5; i++) {
XxlJobLogger.log("beat at:" + i); XxlJobLogger.log("beat at:" + i);
TimeUnit.SECONDS.sleep(2); TimeUnit.SECONDS.sleep(2);
} }
return ReturnT.SUCCESS; return SUCCESS;
} }
} }
...@@ -14,7 +14,7 @@ import com.xxl.job.core.util.ShardingUtil; ...@@ -14,7 +14,7 @@ import com.xxl.job.core.util.ShardingUtil;
public class ShardingJobHandler extends IJobHandler { public class ShardingJobHandler extends IJobHandler {
@Override @Override
public ReturnT<String> execute(String... params) throws Exception { public ReturnT<String> execute(String param) throws Exception {
// 分片参数 // 分片参数
ShardingUtil.ShardingVO shardingVO = ShardingUtil.getShardingVo(); ShardingUtil.ShardingVO shardingVO = ShardingUtil.getShardingVo();
...@@ -29,7 +29,7 @@ public class ShardingJobHandler extends IJobHandler { ...@@ -29,7 +29,7 @@ public class ShardingJobHandler extends IJobHandler {
} }
} }
return ReturnT.SUCCESS; return SUCCESS;
} }
} }
...@@ -4,10 +4,10 @@ xxl.job.admin.addresses=http://127.0.0.1:8080/xxl-job-admin ...@@ -4,10 +4,10 @@ xxl.job.admin.addresses=http://127.0.0.1:8080/xxl-job-admin
### xxl-job executor address ### xxl-job executor address
xxl.job.executor.appname=xxl-job-executor-sample xxl.job.executor.appname=xxl-job-executor-sample
xxl.job.executor.ip= xxl.job.executor.ip=
xxl.job.executor.port=9997 xxl.job.executor.port=9996
### xxl-job log path ### xxl-job log path
xxl.job.executor.logpath=/data/applogs/xxl-job/jobhandler/ xxl.job.executor.logpath=/data/applogs/xxl-job/jobhandler
### xxl-job, access token ### xxl-job, access token
xxl.job.accessToken= xxl.job.accessToken=
\ No newline at end of file
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
id="WebApp_ID" version="2.5"> id="WebApp_ID" version="2.5">
<display-name>xxl-job-executor-sample-jfinal</display-name> <display-name>xxl-job-executor-sample-jfinal</display-name>
<context-param> <context-param>
<param-name>webAppRootKey</param-name> <param-name>webAppRootKey</param-name>
...@@ -23,4 +24,9 @@ ...@@ -23,4 +24,9 @@
<url-pattern>/*</url-pattern> <url-pattern>/*</url-pattern>
</filter-mapping> </filter-mapping>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
</welcome-file-list>
</web-app> </web-app>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>com.xuxueli</groupId>
<artifactId>xxl-job-executor-samples</artifactId>
<version>1.9.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>xxl-job-executor-sample-nutz</artifactId>
<packaging>war</packaging>
<dependencies>
<!-- nutz -->
<dependency>
<groupId>org.nutz</groupId>
<artifactId>nutz</artifactId>
<version>1.r.62</version>
</dependency>
<!-- slf4j -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>${slf4j-api.version}</version>
</dependency>
<!-- xxl-job -->
<dependency>
<groupId>com.xuxueli</groupId>
<artifactId>xxl-job-core</artifactId>
<version>${project.parent.version}</version>
</dependency>
</dependencies>
</project>
\ No newline at end of file
package com.xuxueli.executor.sample.nutz;
import com.xuxueli.executor.sample.nutz.config.NutzSetup;
import org.nutz.mvc.annotation.*;
import org.nutz.mvc.ioc.provider.ComboIocProvider;
/**
* nutz module
*
* @author xuxueli 2017-12-25 17:58:43
*/
@IocBy(type = ComboIocProvider.class,
args = {"*org.nutz.ioc.loader.annotation.AnnotationIocLoader",
"com.xuxueli.executor.sample.nutz"})
@Encoding(input = "utf-8", output = "utf-8")
@Modules(scanPackage = true)
@Localization("msg")
@Ok("json")
@Fail("json")
@SetupBy(NutzSetup.class)
public class MainModule {
}
package com.xuxueli.executor.sample.nutz.config;
import com.xxl.job.core.executor.XxlJobExecutor;
import com.xxl.job.core.handler.IJobHandler;
import com.xxl.job.core.handler.annotation.JobHandler;
import org.nutz.ioc.impl.PropertiesProxy;
import org.nutz.mvc.NutConfig;
import org.nutz.mvc.Setup;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* nutz setup
*
* @author xuxueli 2017-12-25 17:58:43
*/
public class NutzSetup implements Setup {
private Logger logger = LoggerFactory.getLogger(NutzSetup.class);
//public static final Log logger = Logs.get();
private XxlJobExecutor xxlJobExecutor = null;
@Override
public void init(NutConfig cfg) {
// regist JobHandler
String[] beanNames = cfg.getIoc().getNamesByType(IJobHandler.class);
if (beanNames==null || beanNames.length==0) {
return;
}
for (String beanName : beanNames) {
IJobHandler jobHandler = cfg.getIoc().get(IJobHandler.class, beanName);
String name = jobHandler.getClass().getAnnotation(JobHandler.class).value();
XxlJobExecutor.registJobHandler(name, jobHandler);
}
// load executor prop
PropertiesProxy xxlJobProp = new PropertiesProxy("xxl-job-executor.properties");
// init executor
xxlJobExecutor = new XxlJobExecutor();
xxlJobExecutor.setIp(xxlJobProp.get("xxl.job.executor.ip"));
xxlJobExecutor.setPort(xxlJobProp.getInt("xxl.job.executor.port"));
xxlJobExecutor.setAppName(xxlJobProp.get("xxl.job.executor.appname"));
xxlJobExecutor.setAdminAddresses(xxlJobProp.get("xxl.job.admin.addresses"));
xxlJobExecutor.setLogPath(xxlJobProp.get("xxl.job.executor.logpath"));
xxlJobExecutor.setAccessToken(xxlJobProp.get("xxl.job.accessToken"));
// start executor
try {
xxlJobExecutor.start();
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
}
@Override
public void destroy(NutConfig cfg) {
if (xxlJobExecutor != null) {
xxlJobExecutor.destroy();
}
}
}
package com.xuxueli.executor.sample.nutz.jobhandler;
import com.xxl.job.core.biz.model.ReturnT;
import com.xxl.job.core.handler.IJobHandler;
import com.xxl.job.core.handler.annotation.JobHandler;
import com.xxl.job.core.log.XxlJobLogger;
import org.nutz.ioc.loader.annotation.IocBean;
import java.util.concurrent.TimeUnit;
/**
* 任务Handler示例(Bean模式)
*
* 开发步骤:
* 1、继承"IJobHandler":“com.xxl.job.core.handler.IJobHandler”;
* 2、注册到Nutz容器:添加“@IocBean”注解,被Nutz容器扫描为Bean实例;
* 3、注册到执行器工厂:添加“@JobHandler(value="自定义jobhandler名称")”注解,注解value值对应的是调度中心新建任务的JobHandler属性的值。
* 4、执行日志:需要通过 "XxlJobLogger.log" 打印执行日志;
*
* @author xuxueli 2015-12-19 19:43:36
*/
@JobHandler(value="demoJobHandler")
@IocBean
public class DemoJobHandler extends IJobHandler {
@Override
public ReturnT<String> execute(String param) throws Exception {
XxlJobLogger.log("XXL-JOB, Hello World.");
for (int i = 0; i < 5; i++) {
XxlJobLogger.log("beat at:" + i);
TimeUnit.SECONDS.sleep(2);
}
return SUCCESS;
}
}
package com.xuxueli.executor.sample.nutz.jobhandler;
import com.xxl.job.core.handler.annotation.JobHandler;
import org.nutz.ioc.loader.annotation.IocBean;
import com.xxl.job.core.biz.model.ReturnT;
import com.xxl.job.core.handler.IJobHandler;
import com.xxl.job.core.log.XxlJobLogger;
import com.xxl.job.core.util.ShardingUtil;
/**
* 分片广播任务
*
* @author xuxueli 2017-07-25 20:56:50
*/
@JobHandler(value="shardingJobHandler")
@IocBean
public class ShardingJobHandler extends IJobHandler {
@Override
public ReturnT<String> execute(String param) throws Exception {
// 分片参数
ShardingUtil.ShardingVO shardingVO = ShardingUtil.getShardingVo();
XxlJobLogger.log("分片参数:当前分片序号 = {0}, 总分片数 = {1}", shardingVO.getIndex(), shardingVO.getTotal());
// 业务逻辑
for (int i = 0; i < shardingVO.getTotal(); i++) {
if (i == shardingVO.getIndex()) {
XxlJobLogger.log("第 {0} 片, 命中分片开始处理", i);
} else {
XxlJobLogger.log("第 {0} 片, 忽略", i);
}
}
return SUCCESS;
}
}
package com.xuxueli.executor.sample.nutz.module;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.mvc.annotation.At;
import org.nutz.mvc.annotation.Ok;
@IocBean
public class IndexModule {
@At("/")
@Ok("json")
public String index() {
return "xxl job executor running.";
}
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE log4j:configuration PUBLIC "-//log4j/log4j Configuration//EN" "log4j.dtd">
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/" threshold="null" debug="null">
<appender name="CONSOLE" class="org.apache.log4j.ConsoleAppender">
<param name="Target" value="System.out" />
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%-d{yyyy-MM-dd HH:mm:ss} xxl-job-executor-sample-nutz [%c]-[%t]-[%M]-[%L]-[%p] %m%n"/>
</layout>
</appender>
<appender name="FILE" class="org.apache.log4j.DailyRollingFileAppender">
<param name="file" value="/data/applogs/xxl-job/xxl-job-executor-sample-nutz.log"/>
<param name="append" value="true"/>
<param name="encoding" value="UTF-8"/>
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%-d{yyyy-MM-dd HH:mm:ss} xxl-job-executor-sample-nutz [%c]-[%t]-[%M]-[%L]-[%p] %m%n"/>
</layout>
</appender>
<root>
<level value="INFO" />
<appender-ref ref="CONSOLE" />
<appender-ref ref="FILE" />
</root>
</log4j:configuration>
\ No newline at end of file
### xxl-job admin address list, such as "http://address" or "http://address01,http://address02"
xxl.job.admin.addresses=http://127.0.0.1:8080/xxl-job-admin
### xxl-job executor address
xxl.job.executor.appname=xxl-job-executor-sample
xxl.job.executor.ip=
xxl.job.executor.port=9997
### xxl-job log path
xxl.job.executor.logpath=/data/applogs/xxl-job/jobhandler
### xxl-job, access token
xxl.job.accessToken=
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
id="WebApp_ID" version="2.5">
<display-name>xxl-job-executor-sample-nutz</display-name>
<context-param>
<param-name>webAppRootKey</param-name>
<param-value>xxl-job-executor-sample-nutz</param-value>
</context-param>
<!-- nutz -->
<filter>
<filter-name>nutz</filter-name>
<filter-class>org.nutz.mvc.NutFilter</filter-class>
<init-param>
<param-name>modules</param-name>
<param-value>com.xuxueli.executor.sample.nutz.MainModule</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>nutz</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
</welcome-file-list>
</web-app>
...@@ -10,12 +10,12 @@ import java.util.concurrent.TimeUnit; ...@@ -10,12 +10,12 @@ import java.util.concurrent.TimeUnit;
/** /**
* 任务Handler的一个Demo(Bean模式) * 任务Handler示例(Bean模式)
* *
* 开发步骤: * 开发步骤:
* 1、新建一个继承com.xxl.job.core.handler.IJobHandler的Java类 * 1、继承"IJobHandler":“com.xxl.job.core.handler.IJobHandler”
* 2、该类被Spring容器扫描为Bean实例,如加“@Component”注解 * 2、注册到Spring容器:添加“@Component”注解,被Spring容器扫描为Bean实例
* 3、添加 “@JobHandler(value="自定义jobhandler名称")”注解,注解的value值为自定义的JobHandler名称,该名称对应的是调度中心新建任务的JobHandler属性的值。 * 3、注册到执行器工厂:添加“@JobHandler(value="自定义jobhandler名称")”注解,注解value值对应的是调度中心新建任务的JobHandler属性的值。
* 4、执行日志:需要通过 "XxlJobLogger.log" 打印执行日志; * 4、执行日志:需要通过 "XxlJobLogger.log" 打印执行日志;
* *
* @author xuxueli 2015-12-19 19:43:36 * @author xuxueli 2015-12-19 19:43:36
...@@ -25,14 +25,14 @@ import java.util.concurrent.TimeUnit; ...@@ -25,14 +25,14 @@ import java.util.concurrent.TimeUnit;
public class DemoJobHandler extends IJobHandler { public class DemoJobHandler extends IJobHandler {
@Override @Override
public ReturnT<String> execute(String... params) throws Exception { public ReturnT<String> execute(String param) throws Exception {
XxlJobLogger.log("XXL-JOB, Hello World."); XxlJobLogger.log("XXL-JOB, Hello World.");
for (int i = 0; i < 5; i++) { for (int i = 0; i < 5; i++) {
XxlJobLogger.log("beat at:" + i); XxlJobLogger.log("beat at:" + i);
TimeUnit.SECONDS.sleep(2); TimeUnit.SECONDS.sleep(2);
} }
return ReturnT.SUCCESS; return SUCCESS;
} }
} }
...@@ -18,7 +18,7 @@ import org.springframework.stereotype.Service; ...@@ -18,7 +18,7 @@ import org.springframework.stereotype.Service;
public class ShardingJobHandler extends IJobHandler { public class ShardingJobHandler extends IJobHandler {
@Override @Override
public ReturnT<String> execute(String... params) throws Exception { public ReturnT<String> execute(String param) throws Exception {
// 分片参数 // 分片参数
ShardingUtil.ShardingVO shardingVO = ShardingUtil.getShardingVo(); ShardingUtil.ShardingVO shardingVO = ShardingUtil.getShardingVo();
...@@ -33,7 +33,7 @@ public class ShardingJobHandler extends IJobHandler { ...@@ -33,7 +33,7 @@ public class ShardingJobHandler extends IJobHandler {
} }
} }
return ReturnT.SUCCESS; return SUCCESS;
} }
} }
...@@ -3,9 +3,9 @@ ...@@ -3,9 +3,9 @@
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context" xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd"> http://www.springframework.org/schema/context/spring-context.xsd">
<bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="fileEncoding" value="utf-8" /> <property name="fileEncoding" value="utf-8" />
...@@ -25,15 +25,15 @@ ...@@ -25,15 +25,15 @@
<bean id="xxlJobExecutor" class="com.xxl.job.core.executor.XxlJobExecutor" init-method="start" destroy-method="destroy" > <bean id="xxlJobExecutor" class="com.xxl.job.core.executor.XxlJobExecutor" init-method="start" destroy-method="destroy" >
<!-- 执行器IP[选填],为空则自动获取 --> <!-- 执行器IP[选填],为空则自动获取 -->
<property name="ip" value="${xxl.job.executor.ip}" /> <property name="ip" value="${xxl.job.executor.ip}" />
<!-- 执行器端口号[必须] --> <!-- 执行器端口号[选填],为空则自动获取 -->
<property name="port" value="${xxl.job.executor.port}" /> <property name="port" value="${xxl.job.executor.port}" />
<!-- 执行器AppName[选填],为空则关闭自动注册 --> <!-- 执行器AppName[选填],为空则关闭自动注册 -->
<property name="appName" value="${xxl.job.executor.appname}" /> <property name="appName" value="${xxl.job.executor.appname}" />
<!-- 执行器注册中心地址[选填],为空则关闭自动注册 --> <!-- 执行器注册中心地址[选填],为空则关闭自动注册 -->
<property name="adminAddresses" value="${xxl.job.admin.addresses}" /> <property name="adminAddresses" value="${xxl.job.admin.addresses}" />
<!-- 执行器日志路径[必填] --> <!-- 执行器日志路径[选填],为空则使用默认路径 -->
<property name="logPath" value="${xxl.job.executor.logpath}" /> <property name="logPath" value="${xxl.job.executor.logpath}" />
<!-- 访问令牌,非空则进行匹配校验[选填] --> <!-- 访问令牌[选填],非空则进行匹配校验 -->
<property name="accessToken" value="${xxl.job.accessToken}" /> <property name="accessToken" value="${xxl.job.accessToken}" />
</bean> </bean>
......
...@@ -7,7 +7,7 @@ xxl.job.executor.ip= ...@@ -7,7 +7,7 @@ xxl.job.executor.ip=
xxl.job.executor.port=9999 xxl.job.executor.port=9999
### xxl-job log path ### xxl-job log path
xxl.job.executor.logpath=/data/applogs/xxl-job/jobhandler/ xxl.job.executor.logpath=/data/applogs/xxl-job/jobhandler
### xxl-job, access token ### xxl-job, access token
xxl.job.accessToken= xxl.job.accessToken=
\ No newline at end of file
...@@ -3,12 +3,14 @@ ...@@ -3,12 +3,14 @@
xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
id="WebApp_ID" version="2.5"> id="WebApp_ID" version="2.5">
<display-name>xxl-job-executor-sample-spring</display-name> <display-name>xxl-job-executor-sample-spring</display-name>
<context-param> <context-param>
<param-name>webAppRootKey</param-name> <param-name>webAppRootKey</param-name>
<param-value>xxl-job-executor-sample-spring</param-value> <param-value>xxl-job-executor-sample-spring</param-value>
</context-param> </context-param>
<!-- spring -->
<context-param> <context-param>
<param-name>contextConfigLocation</param-name> <param-name>contextConfigLocation</param-name>
<param-value>classpath*:applicationcontext-*.xml</param-value> <param-value>classpath*:applicationcontext-*.xml</param-value>
...@@ -21,6 +23,7 @@ ...@@ -21,6 +23,7 @@
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener> </listener>
<welcome-file-list> <welcome-file-list>
<welcome-file>index.html</welcome-file> <welcome-file>index.html</welcome-file>
</welcome-file-list> </welcome-file-list>
......
200 i am alive.
\ No newline at end of file \ No newline at end of file
package com.xxl.job.executor.mvc.controller; //package com.xxl.job.executor.mvc.controller;
//
import org.springframework.boot.autoconfigure.EnableAutoConfiguration; //import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.stereotype.Controller; //import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping; //import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody; //import org.springframework.web.bind.annotation.ResponseBody;
//
@Controller //@Controller
@EnableAutoConfiguration //@EnableAutoConfiguration
public class IndexController { //public class IndexController {
//
@RequestMapping("/") // @RequestMapping("/")
@ResponseBody // @ResponseBody
String index() { // String index() {
return "xxl job executor running."; // return "xxl job executor running.";
} // }
//
} //}
\ No newline at end of file \ No newline at end of file
...@@ -4,35 +4,35 @@ import com.xxl.job.core.biz.model.ReturnT; ...@@ -4,35 +4,35 @@ import com.xxl.job.core.biz.model.ReturnT;
import com.xxl.job.core.handler.IJobHandler; import com.xxl.job.core.handler.IJobHandler;
import com.xxl.job.core.handler.annotation.JobHandler; import com.xxl.job.core.handler.annotation.JobHandler;
import com.xxl.job.core.log.XxlJobLogger; import com.xxl.job.core.log.XxlJobLogger;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
/** /**
* 任务Handler的一个Demo(Bean模式) * 任务Handler示例(Bean模式)
* *
* 开发步骤: * 开发步骤:
* 1、继承 “IJobHandler” * 1、继承"IJobHandler":“com.xxl.job.core.handler.IJobHandler”
* 2、装配到Spring,例如加 “@Service” 注解 * 2、注册到Spring容器:添加“@Component”注解,被Spring容器扫描为Bean实例
* 3、加 “@JobHandler” 注解,注解value值为新增任务生成的JobKey的值;多个JobKey用逗号分割; * 3、注册到执行器工厂:添加“@JobHandler(value="自定义jobhandler名称")”注解,注解value值对应的是调度中心新建任务的JobHandler属性的值。
* 4、执行日志:需要通过 "XxlJobLogger.log" 打印执行日志; * 4、执行日志:需要通过 "XxlJobLogger.log" 打印执行日志;
* *
* @author xuxueli 2015-12-19 19:43:36 * @author xuxueli 2015-12-19 19:43:36
*/ */
@JobHandler(value="demoJobHandler") @JobHandler(value="demoJobHandler")
@Service @Component
public class DemoJobHandler extends IJobHandler { public class DemoJobHandler extends IJobHandler {
@Override @Override
public ReturnT<String> execute(String... params) throws Exception { public ReturnT<String> execute(String param) throws Exception {
XxlJobLogger.log("XXL-JOB, Hello World."); XxlJobLogger.log("XXL-JOB, Hello World.");
for (int i = 0; i < 5; i++) { for (int i = 0; i < 5; i++) {
XxlJobLogger.log("beat at:" + i); XxlJobLogger.log("beat at:" + i);
TimeUnit.SECONDS.sleep(2); TimeUnit.SECONDS.sleep(2);
} }
return ReturnT.SUCCESS; return SUCCESS;
} }
} }
...@@ -18,7 +18,7 @@ import org.springframework.stereotype.Service; ...@@ -18,7 +18,7 @@ import org.springframework.stereotype.Service;
public class ShardingJobHandler extends IJobHandler { public class ShardingJobHandler extends IJobHandler {
@Override @Override
public ReturnT<String> execute(String... params) throws Exception { public ReturnT<String> execute(String param) throws Exception {
// 分片参数 // 分片参数
ShardingUtil.ShardingVO shardingVO = ShardingUtil.getShardingVo(); ShardingUtil.ShardingVO shardingVO = ShardingUtil.getShardingVo();
...@@ -33,7 +33,7 @@ public class ShardingJobHandler extends IJobHandler { ...@@ -33,7 +33,7 @@ public class ShardingJobHandler extends IJobHandler {
} }
} }
return ReturnT.SUCCESS; return SUCCESS;
} }
} }
...@@ -11,10 +11,10 @@ xxl.job.admin.addresses=http://127.0.0.1:8080/xxl-job-admin ...@@ -11,10 +11,10 @@ xxl.job.admin.addresses=http://127.0.0.1:8080/xxl-job-admin
### xxl-job executor address ### xxl-job executor address
xxl.job.executor.appname=xxl-job-executor-sample xxl.job.executor.appname=xxl-job-executor-sample
xxl.job.executor.ip= xxl.job.executor.ip=
xxl.job.executor.port=-1 xxl.job.executor.port=9998
### xxl-job log path ### xxl-job log path
xxl.job.executor.logpath=/data/applogs/xxl-job/jobhandler/ xxl.job.executor.logpath=/data/applogs/xxl-job/jobhandler
### xxl-job, access token ### xxl-job, access token
xxl.job.accessToken= xxl.job.accessToken=
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论