Merge branch 'master' into master

pull/6/head
许雪里 7 years ago committed by GitHub
commit e4c931747a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

11
.gitignore vendored

@ -1,10 +1,7 @@
# for eclipse
/.settings/
/.project
# for idea
.idea
.classpath
.project
*.iml
/.idea
*/target
target/
.DS_Store
.gitattributes

@ -1,6 +1,6 @@
language: java
jdk:
- oraclejdk7
- oraclejdk8
install: mvn install -DskipTests=true -Dmaven.javadoc.skip=true
#script: mvn test
script: mvn -DskipTests=true clean package

@ -23,7 +23,10 @@
</a>
<a href="https://gitter.im/xuxueli/xxl-job?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge">
<img src="https://badges.gitter.im/xuxueli/xxl-job.svg" >
</a>
</a>
<a href="http://www.xuxueli.com/page/donate.html">
<img src="https://img.shields.io/badge/%24-donate-ff69b4.svg?style=flat-square" >
</a>
</p>
</p>
@ -44,28 +47,30 @@ XXL-JOB是一个轻量级分布式任务调度框架其核心设计目标是
## Features
- 1、简单支持通过Web页面对任务进行CRUD操作操作简单一分钟上手
- 2、动态支持动态修改任务状态、暂停/恢复任务,以及终止运行中任务,即时生效;
- 3、调度中心HA中心式调度采用中心式设计“调度中心”基于集群Quartz实现可保证调度中心HA
- 3、调度中心HA中心式调度采用中心式设计“调度中心”基于集群Quartz实现并支持集群部署可保证调度中心HA
- 4、执行器HA分布式任务分布式执行任务"执行器"支持集群部署可保证任务执行HA
- 5、任务Failover执行器集群部署时任务路由策略选择"故障转移"情况下调度失败时将会平滑切换执行器进行Failover
- 6、一致性“调度中心”通过DB锁保证集群分布式调度的一致性, 一次任务调度只会触发一次执行;
- 7、自定义任务参数支持在线配置调度任务入参即时生效
- 8、调度线程池调度系统多线程触发调度运行确保调度精确执行不被堵塞
- 9、弹性扩容缩容一旦有新执行器机器上线或者下线下次调度时将会重新分配任务
- 10、邮件报警任务失败时支持邮件报警支持配置多邮件地址群发报警邮件
- 11、状态监控支持实时监控任务进度
- 12、Rolling执行日志支持在线查看调度结果并且支持以Rolling方式实时查看执行器输出的完整的执行日志
- 13、GLUE提供Web IDE支持在线开发任务逻辑代码动态发布实时编译生效省略部署上线的过程。支持30个版本的历史版本回溯。
- 14、数据加密调度中心和执行器之间的通讯进行数据加密提升调度信息安全性
- 15、任务依赖支持配置子任务依赖当父任务执行结束且执行成功后将会主动触发一次子任务的执行, 多个子任务用逗号分隔;
- 16、推送maven中央仓库: 将会把最新稳定版推送到maven中央仓库, 方便用户接入和使用;
- 17、任务注册: 执行器会周期性自动注册任务, 调度中心将会自动发现注册的任务并触发执行。同时,也支持手动录入执行器地址;
- 18、路由策略执行器集群部署时提供丰富的路由策略包括第一个、最后一个、轮询、随机、一致性HASH、最不经常使用、最近最久未使用、故障转移、忙碌转移等
- 19、运行报表支持实时查看运行数据如任务数量、调度次数、执行器数量等以及调度报表如调度日期分布图调度成功分布图等
- 20、脚本任务支持以GLUE模式开发和运行脚本任务包括Shell、Python等类型脚本;
- 21、阻塞处理策略调度过于密集执行器来不及处理时的处理策略策略包括单机串行默认、丢弃后续调度、覆盖之前调度
- 22、失败处理策略调度失败时的处理策略策略包括失败告警默认、失败重试
- 23、分片广播任务执行器集群部署时任务路由策略选择"分片广播"情况下,一次任务调度将会广播触发对应集群中所有执行器执行一次任务,同时传递分片参数;可根据分片参数开发分片任务;
- 24、动态分片分片广播任务以执行器为维度进行分片支持动态扩容执行器集群从而动态增加分片数量协同进行业务处理在进行大数据量业务操作时可显著提升任务处理能力和速度。
- 5、注册中心: 执行器会周期性自动注册任务, 调度中心将会自动发现注册的任务并触发执行。同时,也支持手动录入执行器地址;
- 6、弹性扩容缩容一旦有新执行器机器上线或者下线下次调度时将会重新分配任务
- 7、路由策略执行器集群部署时提供丰富的路由策略包括第一个、最后一个、轮询、随机、一致性HASH、最不经常使用、最近最久未使用、故障转移、忙碌转移等
- 8、故障转移任务路由策略选择"故障转移"情况下如果执行器集群中某一台机器故障将会自动Failover切换到一台正常的执行器发送调度请求。
- 9、失败处理策略调度失败时的处理策略策略包括失败告警默认、失败重试
- 10、失败重试调度中心调度失败且启用"失败重试"策略时,将会自动重试一次;执行器执行失败且回调失败重试状态时,也将会自动重试一次;
- 11、阻塞处理策略调度过于密集执行器来不及处理时的处理策略策略包括单机串行默认、丢弃后续调度、覆盖之前调度
- 12、分片广播任务执行器集群部署时任务路由策略选择"分片广播"情况下,一次任务调度将会广播触发集群中所有执行器执行一次任务,可根据分片参数开发分片任务;
- 13、动态分片分片广播任务以执行器为维度进行分片支持动态扩容执行器集群从而动态增加分片数量协同进行业务处理在进行大数据量业务操作时可显著提升任务处理能力和速度。
- 14、事件触发除了"Cron方式"和"任务依赖方式"触发任务执行之外支持基于事件的触发任务方式。调度中心提供触发任务单次执行的API服务可根据业务事件灵活触发。
- 15、任务进度监控支持实时监控任务进度
- 16、Rolling实时日志支持在线查看调度结果并且支持以Rolling方式实时查看执行器输出的完整的执行日志
- 17、GLUE提供Web IDE支持在线开发任务逻辑代码动态发布实时编译生效省略部署上线的过程。支持30个版本的历史版本回溯。
- 18、脚本任务支持以GLUE模式开发和运行脚本任务包括Shell、Python、NodeJS等类型脚本;
- 19、任务依赖支持配置子任务依赖当父任务执行结束且执行成功后将会主动触发一次子任务的执行, 多个子任务用逗号分隔;
- 20、一致性“调度中心”通过DB锁保证集群分布式调度的一致性, 一次任务调度只会触发一次执行;
- 21、自定义任务参数支持在线配置调度任务入参即时生效
- 22、调度线程池调度系统多线程触发调度运行确保调度精确执行不被堵塞
- 23、数据加密调度中心和执行器之间的通讯进行数据加密提升调度信息安全性
- 24、邮件报警任务失败时支持邮件报警支持配置多邮件地址群发报警邮件
- 25、推送maven中央仓库: 将会把最新稳定版推送到maven中央仓库, 方便用户接入和使用;
- 26、运行报表支持实时查看运行数据如任务数量、调度次数、执行器数量等以及调度报表如调度日期分布图调度成功分布图等
## Development
@ -79,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-12-11XXL-JOB有幸参会《[InfoQ ArchSummit全球架构师峰会](http://bj2017.archsummit.com/)》,并被拍拍贷架构总监"杨波老师"在专题 "[微服务原理、基础架构和开源实践](http://bj2017.archsummit.com/training/2)" 中现场介绍。
> 我司大众点评目前已接入XXL-JOB内部别名《Ferrari》Ferrari基于XXL-JOB的V1.1版本定制而成,新接入应用推荐升级最新版本)。**
据最新统计, 自2016-01-21接入至2017-07-07期间该系统已调度约60万余次表现优异。新接入应用推荐使用最新版本因为经过数个大版本的更新系统的任务模型、UI交互模型以及底层调度通讯模型都有了较大的优化和提升核心功能更加稳定高效。
据最新统计, 自2016-01-21接入至2017-12-01期间该系统已调度约100万表现优异。新接入应用推荐使用最新版本因为经过数个大版本的更新系统的任务模型、UI交互模型以及底层调度通讯模型都有了较大的优化和提升核心功能更加稳定高效。
至今XXL-JOB已接入多家公司的线上产品线接入场景如电商业务O2O业务和大数据作业等截止2016-07-19为止XXL-JOB已接入的公司包括不限于
@ -127,6 +134,22 @@ XXL-JOB是一个轻量级分布式任务调度框架其核心设计目标是
- 41、广州瀚农网络科技有限公司
- 42、享点科技有限公司
- 43、杭州比智科技有限公司
- 44、圳临界线网络科技有限公司
- 45、广州知识圈网络科技有限公司
- 46、国誉商业上海有限公司
- 47、海尔消费金融有限公司嗨付、够花 (海尔)
- 48、广州巴图鲁信息科技有限公司
- 49、深圳市鹏海运电子数据交换有限公司
- 50、深圳市亚飞电子商务有限公司
- 51、上海趣医网络有限公司
- 52、聚金资本
- 53、北京父母邦网络科技有限公司
- 54、中山元赫软件科技有限公司
- 55、中商惠民(北京)电子商务有限公司
- 56、凯京集团
- 57、华夏票联北京科技有限公司
- 58、拍拍贷
- 59、北京尚德机构在线教育有限公司
- ……
> 更多接入的公司,欢迎在 [登记地址](https://github.com/xuxueli/xxl-job/issues/1 ) 登记,登记仅仅为了产品推广。
@ -136,12 +159,7 @@ XXL-JOB是一个轻量级分布式任务调度框架其核心设计目标是
## Communication
- 腾讯QQ群6399758605
- 腾讯QQ群5138274130
- 腾讯QQ群4464762661
- 腾讯QQ群3242151780
- 腾讯QQ群2438249535
- 腾讯QQ群1367260654
- [社区交流](http://www.xuxueli.com/page/community.html)
- [Gitter](https://gitter.im/xuxueli/xxl-job)
@ -161,9 +179,6 @@ This product is open source and free, and will continue to provide free communit
## Donate
No matter how much the amount is enough to express your thought, thank you very much
无论金额多少都足够表达您这份心意,非常感谢
No matter how much the amount is enough to express your thought, thank you very much [To donate](http://www.xuxueli.com/page/donate.html )
微信:<img src="https://raw.githubusercontent.com/xuxueli/xxl-job/master/doc/images/donate-wechat.png" width="200">
支付宝:<img src="https://raw.githubusercontent.com/xuxueli/xxl-job/master/doc/images/donate-alipay.jpg" width="200">
无论金额多少都足够表达您这份心意,非常感谢 [前往捐赠](http://www.xuxueli.com/page/donate.html )

@ -5,6 +5,8 @@
[![GitHub release](https://img.shields.io/github/release/xuxueli/xxl-job.svg)](https://github.com/xuxueli/xxl-job/releases)
[![License](https://img.shields.io/badge/license-GPLv3-blue.svg)](http://www.gnu.org/licenses/gpl-3.0.html)
[![Gitter](https://badges.gitter.im/xuxueli/xxl-job.svg)](https://gitter.im/xuxueli/xxl-job?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
[![donate](https://img.shields.io/badge/%24-donate-ff69b4.svg?style=flat-square)](http://www.xuxueli.com/page/donate.html)
## 1. Brief introduction
@ -35,6 +37,9 @@ XXL-JOB is a lightweight distributed task scheduling framework, the core design
- 22.Failure handling strategy:Handling strategy when scheduling fails, the strategy includes: failure alarm (default), failure retry;
- 23.Sharding broadcast task: When an executor cluster is deployed, task routing strategy select "sharding broadcast", a task schedule will broadcast all the actuators in the cluster to perform it once, you can develop sharding tasks based on sharding parameters;
- 24.Dynamic sharding: The sharding broadcast task is sharded by the executors to support the dynamic expansion of the executor cluster to dynamically increase the number of shardings and cooperate with the business handle; In the large amount of data operations can significantly improve the task processing capacity and speed.
- 25、Event triggerIn addition to "Cron" and "Task Dependency" to trigger tasks, support event-based triggering tasks. The dispatch center provides API service that triggers a single execution of the task, it can be triggered flexibly according to business events.
### 1.3 Development
In 2015, I created the XXL-JOB project repository on github and submitted the first commit, followed by the system structure design, UI selection, interactive design ...
In 2015 - November, XXL-JOB finally RELEASE the first big version of V1.0, then I will be released to OSCHINA, XXL-JOB OSCHINA won the popular recommendation of @红薯, the same period reached OSCHINA's " Popular move "ranked first and git.oschina open source software monthly heat ranked first, especially thanks for @红薯, thank you for the attention and support.
@ -89,6 +94,17 @@ So far, XXL-JOB has access to a number of companies online product line, access
- 41、广州瀚农网络科技有限公司
- 42、享点科技有限公司
- 43、杭州比智科技有限公司
- 44、圳临界线网络科技有限公司
- 45、广州知识圈网络科技有限公司
- 46、国誉商业上海有限公司
- 47、海尔消费金融有限公司嗨付、够花 (海尔)
- 48、广州巴图鲁信息科技有限公司
- 49、深圳市鹏海运电子数据交换有限公司
- 50、深圳市亚飞电子商务有限公司
- 51、上海趣医网络有限公司
- 52、聚金资本
- 53、北京父母邦网络科技有限公司
- 54、中山元赫软件科技有限公司
- ……
> The company that access and use this product is welcome to register at the [address](https://github.com/xuxueli/xxl-job/issues/1 ), only for product promotion.
@ -114,18 +130,12 @@ Source repository address | Release Download
<dependency>
<groupId>com.xuxueli</groupId>
<artifactId>xxl-job-core</artifactId>
<version>1.8.1</version>
<version>1.8.2</version>
</dependency>
```
#### Technical exchange group (technical exchange only)
- Tecent QQ Group 6399758605
- Tecent QQ Group 5138274130
- Tecent QQ Group 4464762661
- Tecent QQ Group 3242151780
- Tecent QQ Group 2438249535
- Tecent QQ Group 1367260654
#### Technical exchange group
- [社区交流](http://www.xuxueli.com/page/community.html)
- [Gitter](https://gitter.im/xuxueli/xxl-job)
### 1.5 Environment
@ -339,7 +349,7 @@ On the log console,you can view task execution log on the executor immediately a
GLUE模式(Java)task source code is maintened in the schedule center,it must implement IJobHandler and explain by "groovy" in the executor instance,inject other bean instace by annotation @Resource/@Autowire.
GLUE模式(Shell)its source code is a shell script and maintained in the schedule center.
GLUE模式(Python)its source code is a python script and maintained in the schedule center.
- JobHandlerits used in "BEAN模式",its instance is defined by annotation @JobHander on the JobHandler class name.
- JobHandlerits used in "BEAN模式",its instance is defined by annotation @JobHandler on the JobHandler class name.
- 子任务Keyevery task has a unique key (task Key can acquire from task list)when main task is done successfully its child task stand for by this key will be scheduled.
- 阻塞处理策略the stategy handle the task when this task is scheduled too frequently and the task is block to wait for cpu time.
单机串行默认task schedule request go into the FIFO queue and execute serially.
@ -358,7 +368,7 @@ The task logic exist in the executor project as JobHandler,the develop steps as
#### Step 1:develp obHandler in the executor project
- 1, create new java class implent com.xxl.job.core.handler.IJobHandler;
- 2, if you add @Component annotation on the top of the class name its will be managed as a bean instance by spring container;
- 3, add “@JobHander(value=" customize jobhandler name")” annotationthe value stand for JobHandler name,it will be used as JobHandler property when create a new task in the schedule center.
- 3, add “@JobHandler(value=" customize jobhandler name")” annotationthe value stand for JobHandler name,it will be used as JobHandler property when create a new task in the schedule center.
go and see DemoJobHandler in the xxl-job-executor-example project, as shown below
![输入图片说明](https://static.oschina.net/uploads/img/201607/23232347_oLlM.png "在这里输入图片标题")
@ -674,7 +684,7 @@ On the task log page ,you can see matched child task and triggered child task
### 5.5 Task "run mode" analysis
#### 5.5.1 "Bean模式" task
Development steps:go and see "chapter 3" .
principle: every Bean mode task is a Spring Bean instance and it is maintained in executor projects Spring container. task class nedd to add “@JobHander(value="name")” annotation, because executor identify task bean instance in spring container through annotation. Task class nedd to implements interface IJobHandler, task logic code in method execute(), the task logic in execute() method will be executed when executor received a schedule request from schedule center.
principle: every Bean mode task is a Spring Bean instance and it is maintained in executor projects Spring container. task class nedd to add “@JobHandler(value="name")” annotation, because executor identify task bean instance in spring container through annotation. Task class nedd to implements interface IJobHandler, task logic code in method execute(), the task logic in execute() method will be executed when executor received a schedule request from schedule center.
#### 5.5.2 "GLUE模式(Java)" task
Development steps:go and see "chapter 3" .
@ -695,7 +705,7 @@ Executor is actually an embedded Jetty server with default port 9999, as shown b
![输入图片说明](https://static.oschina.net/uploads/img/201703/10174923_TgNO.png "在这里输入图片标题")
Executor will identify Bean mode task in spring container through @JobHander When project start, it will be managed use the value of annotation as key.
Executor will identify Bean mode task in spring container through @JobHandler When project start, it will be managed use the value of annotation as key.
When executor received schedule request from schedule center, if task type is “Bean模式” it will match bean mode task in Spring container and call its execute() method and execute task logic. if task type is “GLUE模式”, it will load Glue code, instantiate a Java object and inject other spring servicenotice: the spring service injected in Glue code must exist in the same executor project, then call execute() method and execute task logic.
@ -758,6 +768,18 @@ There are only two settings when communication between scheduler center and exec
- one:do not configure AccessToken on both, close security check.
- two:configure the same AccessToken on both;
### 5.11 Dispatching center API services
The scheduling center provides API services for executors and business parties to choose to use, and the currently available API services are available.
1. Job result callback service;
2. Executor registration service;
3. Executor registration remove services;
4. Triggers a single execution service, and support the task to be triggered according to the business event;
The scheduling center API service location: com.xxl.job.core.biz.AdminBiz.java
The scheduling center API service requests reference codecom.xxl.job.dao.impl.AdminBizTest.java
## 6 Version update log
### 6.1 version V1.1.xNew features [2015-12-05]
@ -790,13 +812,13 @@ There are only two settings when communication between scheduler center and exec
- stability;
### 6.3 version V1.3.0New features [2016-05-19]
- 1、discard local task module, remote task was recommended, easy to decouple system, the JobHander of task was called executor.
- 1、discard local task module, remote task was recommended, easy to decouple system, the JobHandler of task was called executor.
- 2、dicard underlying communication type servlet, JETTY was recommended, schedule and callback bidirectional communication, rebuild the communication logic;
- 3、UI interactive optimization:optimize left menu expansion and menu item selected status , task list opens the table with compression optimization;
- 4、【important】executor is subdivided into two develop mode:BEAN、GLUE:
Introduction to the executor mode:
- BEAN mode executor:every executor is a Spring Bean instanceit was recognized and scheduled by XXL-JOB through @JobHander annotation;
- BEAN mode executor:every executor is a Spring Bean instanceit was recognized and scheduled by XXL-JOB through @JobHandler annotation;
-GLUE mode executor:every executor corresponds to a piece of codeedited and maintained online by Web, Dynamic compile and takes effect in real time, executor is responsible for loading GLUE code and executing;
### 6.4 version V1.3.1New features [2016-05-23]
@ -995,7 +1017,4 @@ This product is open source and free, and will continue to provide free communit
---
### Donate
No matter how much the amount is enough to express your thought, thank you very much
Webchat<img src="https://raw.githubusercontent.com/xuxueli/xxl-job/master/doc/images/donate-wechat.png" width="200">
Alipay<img src="https://raw.githubusercontent.com/xuxueli/xxl-job/master/doc/images/donate-alipay.jpg" width="200">
No matter how much the amount is enough to express your thought, thank you very much [To donate](http://www.xuxueli.com/page/donate.html )

@ -5,6 +5,8 @@
[![GitHub release](https://img.shields.io/github/release/xuxueli/xxl-job.svg)](https://github.com/xuxueli/xxl-job/releases)
[![License](https://img.shields.io/badge/license-GPLv3-blue.svg)](http://www.gnu.org/licenses/gpl-3.0.html)
[![Gitter](https://badges.gitter.im/xuxueli/xxl-job.svg)](https://gitter.im/xuxueli/xxl-job?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
[![donate](https://img.shields.io/badge/%24-donate-ff69b4.svg?style=flat-square)](http://www.xuxueli.com/page/donate.html)
## 一、简介
@ -14,28 +16,30 @@ XXL-JOB是一个轻量级分布式任务调度框架其核心设计目标是
### 1.2 特性
- 1、简单支持通过Web页面对任务进行CRUD操作操作简单一分钟上手
- 2、动态支持动态修改任务状态、暂停/恢复任务,以及终止运行中任务,即时生效;
- 3、调度中心HA中心式调度采用中心式设计“调度中心”基于集群Quartz实现可保证调度中心HA
- 3、调度中心HA中心式调度采用中心式设计“调度中心”基于集群Quartz实现并支持集群部署可保证调度中心HA
- 4、执行器HA分布式任务分布式执行任务"执行器"支持集群部署可保证任务执行HA
- 5、任务Failover执行器集群部署时任务路由策略选择"故障转移"情况下调度失败时将会平滑切换执行器进行Failover
- 6、一致性“调度中心”通过DB锁保证集群分布式调度的一致性, 一次任务调度只会触发一次执行;
- 7、自定义任务参数支持在线配置调度任务入参即时生效
- 8、调度线程池调度系统多线程触发调度运行确保调度精确执行不被堵塞
- 9、弹性扩容缩容一旦有新执行器机器上线或者下线下次调度时将会重新分配任务
- 10、邮件报警任务失败时支持邮件报警支持配置多邮件地址群发报警邮件
- 11、状态监控支持实时监控任务进度
- 12、Rolling执行日志支持在线查看调度结果并且支持以Rolling方式实时查看执行器输出的完整的执行日志
- 13、GLUE提供Web IDE支持在线开发任务逻辑代码动态发布实时编译生效省略部署上线的过程。支持30个版本的历史版本回溯。
- 14、数据加密调度中心和执行器之间的通讯进行数据加密提升调度信息安全性
- 15、任务依赖支持配置子任务依赖当父任务执行结束且执行成功后将会主动触发一次子任务的执行, 多个子任务用逗号分隔;
- 16、推送maven中央仓库: 将会把最新稳定版推送到maven中央仓库, 方便用户接入和使用;
- 17、任务注册: 执行器会周期性自动注册任务, 调度中心将会自动发现注册的任务并触发执行。同时,也支持手动录入执行器地址;
- 18、路由策略执行器集群部署时提供丰富的路由策略包括第一个、最后一个、轮询、随机、一致性HASH、最不经常使用、最近最久未使用、故障转移、忙碌转移等
- 19、运行报表支持实时查看运行数据如任务数量、调度次数、执行器数量等以及调度报表如调度日期分布图调度成功分布图等
- 20、脚本任务支持以GLUE模式开发和运行脚本任务包括Shell、Python等类型脚本;
- 21、阻塞处理策略调度过于密集执行器来不及处理时的处理策略策略包括单机串行默认、丢弃后续调度、覆盖之前调度
- 22、失败处理策略调度失败时的处理策略策略包括失败告警默认、失败重试
- 23、分片广播任务执行器集群部署时任务路由策略选择"分片广播"情况下,一次任务调度将会广播触发集群中所有执行器执行一次任务,可根据分片参数开发分片任务;
- 24、动态分片分片广播任务以执行器为维度进行分片支持动态扩容执行器集群从而动态增加分片数量协同进行业务处理在进行大数据量业务操作时可显著提升任务处理能力和速度。
- 5、注册中心: 执行器会周期性自动注册任务, 调度中心将会自动发现注册的任务并触发执行。同时,也支持手动录入执行器地址;
- 6、弹性扩容缩容一旦有新执行器机器上线或者下线下次调度时将会重新分配任务
- 7、路由策略执行器集群部署时提供丰富的路由策略包括第一个、最后一个、轮询、随机、一致性HASH、最不经常使用、最近最久未使用、故障转移、忙碌转移等
- 8、故障转移任务路由策略选择"故障转移"情况下如果执行器集群中某一台机器故障将会自动Failover切换到一台正常的执行器发送调度请求。
- 9、失败处理策略调度失败时的处理策略策略包括失败告警默认、失败重试
- 10、失败重试调度中心调度失败且启用"失败重试"策略时,将会自动重试一次;执行器执行失败且回调失败重试状态时,也将会自动重试一次;
- 11、阻塞处理策略调度过于密集执行器来不及处理时的处理策略策略包括单机串行默认、丢弃后续调度、覆盖之前调度
- 12、分片广播任务执行器集群部署时任务路由策略选择"分片广播"情况下,一次任务调度将会广播触发集群中所有执行器执行一次任务,可根据分片参数开发分片任务;
- 13、动态分片分片广播任务以执行器为维度进行分片支持动态扩容执行器集群从而动态增加分片数量协同进行业务处理在进行大数据量业务操作时可显著提升任务处理能力和速度。
- 14、事件触发除了"Cron方式"和"任务依赖方式"触发任务执行之外支持基于事件的触发任务方式。调度中心提供触发任务单次执行的API服务可根据业务事件灵活触发。
- 15、任务进度监控支持实时监控任务进度
- 16、Rolling实时日志支持在线查看调度结果并且支持以Rolling方式实时查看执行器输出的完整的执行日志
- 17、GLUE提供Web IDE支持在线开发任务逻辑代码动态发布实时编译生效省略部署上线的过程。支持30个版本的历史版本回溯。
- 18、脚本任务支持以GLUE模式开发和运行脚本任务包括Shell、Python、NodeJS等类型脚本;
- 19、任务依赖支持配置子任务依赖当父任务执行结束且执行成功后将会主动触发一次子任务的执行, 多个子任务用逗号分隔;
- 20、一致性“调度中心”通过DB锁保证集群分布式调度的一致性, 一次任务调度只会触发一次执行;
- 21、自定义任务参数支持在线配置调度任务入参即时生效
- 22、调度线程池调度系统多线程触发调度运行确保调度精确执行不被堵塞
- 23、数据加密调度中心和执行器之间的通讯进行数据加密提升调度信息安全性
- 24、邮件报警任务失败时支持邮件报警支持配置多邮件地址群发报警邮件
- 25、推送maven中央仓库: 将会把最新稳定版推送到maven中央仓库, 方便用户接入和使用;
- 26、运行报表支持实时查看运行数据如任务数量、调度次数、执行器数量等以及调度报表如调度日期分布图调度成功分布图等
### 1.3 发展
于2015年中我在github上创建XXL-JOB项目仓库并提交第一个commit随之进行系统结构设计UI选型交互设计……
@ -48,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-12-11XXL-JOB有幸参会《[InfoQ ArchSummit全球架构师峰会](http://bj2017.archsummit.com/)》,并被拍拍贷架构总监"杨波老师"在专题 "[微服务原理、基础架构和开源实践](http://bj2017.archsummit.com/training/2)" 中现场介绍。
> 我司大众点评目前已接入XXL-JOB内部别名《Ferrari》Ferrari基于XXL-JOB的V1.1版本定制而成,新接入应用推荐升级最新版本)。
据最新统计, 自2016-01-21接入至2017-07-07期间该系统已调度约60万余次表现优异。新接入应用推荐使用最新版本因为经过数个大版本的更新系统的任务模型、UI交互模型以及底层调度通讯模型都有了较大的优化和提升核心功能更加稳定高效。
据最新统计, 自2016-01-21接入至2017-12-01期间该系统已调度约100万表现优异。新接入应用推荐使用最新版本因为经过数个大版本的更新系统的任务模型、UI交互模型以及底层调度通讯模型都有了较大的优化和提升核心功能更加稳定高效。
至今XXL-JOB已接入多家公司的线上产品线接入场景如电商业务O2O业务和大数据作业等截止2016-07-19为止XXL-JOB已接入的公司包括不限于
@ -96,6 +102,22 @@ XXL-JOB是一个轻量级分布式任务调度框架其核心设计目标是
- 41、广州瀚农网络科技有限公司
- 42、享点科技有限公司
- 43、杭州比智科技有限公司
- 44、圳临界线网络科技有限公司
- 45、广州知识圈网络科技有限公司
- 46、国誉商业上海有限公司
- 47、海尔消费金融有限公司嗨付、够花 (海尔)
- 48、广州巴图鲁信息科技有限公司
- 49、深圳市鹏海运电子数据交换有限公司
- 50、深圳市亚飞电子商务有限公司
- 51、上海趣医网络有限公司
- 52、聚金资本
- 53、北京父母邦网络科技有限公司
- 54、中山元赫软件科技有限公司
- 55、中商惠民(北京)电子商务有限公司
- 56、凯京集团
- 57、华夏票联北京科技有限公司
- 58、拍拍贷
- 59、北京尚德机构在线教育有限公司
- ……
> 更多接入的公司,欢迎在 [登记地址](https://github.com/xuxueli/xxl-job/issues/1 ) 登记,登记仅仅为了产品推广。
@ -125,18 +147,13 @@ XXL-JOB是一个轻量级分布式任务调度框架其核心设计目标是
<dependency>
<groupId>com.xuxueli</groupId>
<artifactId>xxl-job-core</artifactId>
<version>1.8.1</version>
<version>1.8.2</version>
</dependency>
```
#### 技术交流
- 腾讯QQ群6399758605
- 腾讯QQ群5138274130
- 腾讯QQ群4464762661
- 腾讯QQ群3242151780
- 腾讯QQ群2438249535
- 腾讯QQ群1367260654
- [社区交流](http://www.xuxueli.com/page/community.html)
- [Gitter](https://gitter.im/xuxueli/xxl-job)
### 1.5 环境
- JDK1.7+
@ -174,7 +191,7 @@ XXL-JOB是一个轻量级分布式任务调度框架其核心设计目标是
### 2.3 配置部署“调度中心”
调度中心项目xxl-job-admin
作用:统一管理任务调度平台上调度任务,负责触发调度执行。
作用:统一管理任务调度平台上调度任务,负责触发调度执行,并且提供任务管理平台
#### 步骤一:调度中心配置:
调度中心配置文件地址:
@ -223,7 +240,7 @@ XXL-JOB是一个轻量级分布式任务调度框架其核心设计目标是
### 2.4 配置部署“执行器项目”
“执行器”项目xxl-job-executor-sample-spring (如新建执行器项目可参考该Sample示例执行器项目的配置步骤)
作用:负责接收“调度中心”的调度并执行;
作用:负责接收“调度中心”的调度并执行;可直接部署执行器,也可以将执行器集成到现有业务项目中。
#### 步骤一maven依赖
确认pom文件中引入了 "xxl-job-core" 的maven依赖
@ -334,16 +351,16 @@ XXL-JOB是一个轻量级分布式任务调度框架其核心设计目标是
- 执行器:任务的绑定的执行器,任务触发调度时将会自动发现注册成功的执行器, 实现任务自动发现功能; 另一方面也可以方便的进行任务分组。每个任务必须绑定一个执行器, 可在 "执行器管理" 进行设置;
- 描述:任务的描述信息,便于任务管理;
- 路由策略:当执行器集群部署时,提供丰富的路由策略,包括;
FIRST第一个固定选择第一个执行器;
LAST最后一个固定选择最后一个执行器;
FIRST第一个固定选择第一个器;
LAST最后一个固定选择最后一个器;
ROUND轮询
RANDOM随机随机选择在线的执行器;
CONSISTENT_HASH一致性HASH分组下机器地址相同不同JOB均匀散列在不同机器上保证分组下机器分配JOB平均且每个JOB固定调度其中一台机器
LEAST_FREQUENTLY_USED最不经常使用单个JOB对应的每个执行器使用频率最低的优先被选举;
LEAST_RECENTLY_USED最近最久未使用单个JOB对应的每个执行器最久为使用的优先被选举;
RANDOM随机随机选择在线的器;
CONSISTENT_HASH一致性HASH每个任务按照Hash算法固定选择某一台机器且所有任务均匀散列在不同机器上。
LEAST_FREQUENTLY_USED最不经常使用使用频率最低的机器优先被选举;
LEAST_RECENTLY_USED最近最久未使用最久为使用的机器优先被选举;
FAILOVER故障转移按照顺序依次进行心跳检测第一个心跳检测成功的机器选定为目标执行器并发起调度
BUSYOVER忙碌转移按照顺序依次进行空闲检测第一个空闲检测成功的机器选定为目标执行器并发起调度
SHARDING_BROADCAST(分片广播):广播触发对应集群中所有执行器执行一次任务,同时传递分片参数;可根据分片参数开发分片任务;
SHARDING_BROADCAST(分片广播):广播触发对应集群中所有器执行一次任务,同时传递分片参数;可根据分片参数开发分片任务;
- Cron触发任务执行的Cron表达式
- 运行模式:
@ -351,15 +368,16 @@ XXL-JOB是一个轻量级分布式任务调度框架其核心设计目标是
GLUE模式(Java)任务以源码方式维护在调度中心该模式的任务实际上是一段继承自IJobHandler的Java类代码并 "groovy" 源码方式维护,它在执行器项目中运行,可使用@Resource/@Autowire注入执行器里中的其他服务
GLUE模式(Shell):任务以源码方式维护在调度中心;该模式的任务实际上是一段 "shell" 脚本;
GLUE模式(Python):任务以源码方式维护在调度中心;该模式的任务实际上是一段 "python" 脚本;
- JobHandler运行模式为 "BEAN模式" 时生效对应执行器中新开发的JobHandler类“@JobHander”注解自定义的value值
GLUE模式(NodeJS):任务以源码方式维护在调度中心;该模式的任务实际上是一段 "nodejs" 脚本;
- JobHandler运行模式为 "BEAN模式" 时生效对应执行器中新开发的JobHandler类“@JobHandler”注解自定义的value值
- 子任务Key每个任务都拥有一个唯一的任务Key(任务Key可以从任务列表获取)当本任务执行结束并且执行成功时将会触发子任务Key所对应的任务的一次主动调度。
- 阻塞处理策略:调度过于密集执行器来不及处理时的处理策略;
单机串行默认调度请求进入单机执行器后调度请求进入FIFO队列并以串行方式运行
丢弃后续调度:调度请求进入单机执行器后,发现执行器存在运行的调度任务,本次请求将会被丢弃并标记为失败;
覆盖之前调度:调度请求进入单机执行器后,发现执行器存在运行的调度任务,将会终止运行中的调度任务并清空队列,然后运行本地调度任务;
- 失败处理策略;调度失败时的处理策略;
失败告警(默认):调度失败时,将会触发失败报警,如发送报警邮件;
失败重试:调度失败时,将会主动进行一次失败重试调度重试调度后仍然失败将会触发一失败告警。注意当任务以failover方式路由时每次失败重试将会触发新一轮路由。
失败告警(默认):调度失败和执行失败时,都将会触发失败报警,默认会发送报警邮件;
失败重试:调度失败时,除了进行失败告警之外,将会自动重试一次;注意在执行失败时不会重试,而是根据回调返回值判断是否重试;
- 执行参数:任务执行所需的参数,多个参数时用逗号分隔,任务执行时将会把多个参数转换成数组传入;
- 报警邮件:任务调度失败时邮件通知的邮箱地址,支持配置多邮箱地址,配置多个邮箱地址时用逗号分隔;
- 负责人:任务的负责人;
@ -370,13 +388,13 @@ XXL-JOB是一个轻量级分布式任务调度框架其核心设计目标是
#### 步骤一执行器项目中开发JobHandler
- 1、 新建一个继承com.xxl.job.core.handler.IJobHandler的Java类
- 2、 该类被Spring容器扫描为Bean实例如加“@Component”注解
- 3、 添加 “@JobHander(value="自定义jobhandler名称")”注解注解的value值为自定义的JobHandler名称该名称对应的是调度中心新建任务的JobHandler属性的值。
- 3、 添加 “@JobHandler(value="自定义jobhandler名称")”注解注解的value值为自定义的JobHandler名称该名称对应的是调度中心新建任务的JobHandler属性的值。
可参考Sample示例执行器中的DemoJobHandler见下图
![输入图片说明](https://static.oschina.net/uploads/img/201607/23232347_oLlM.png "在这里输入图片标题")
#### 步骤二:调度中心,新建调度任务
参考上文“配置属性详细说明”对新建的任务进行参数配置,运行模式选中 "BEAN模式"JobHandler属性填写任务注解@JobHander中定义的值
参考上文“配置属性详细说明”对新建的任务进行参数配置,运行模式选中 "BEAN模式"JobHandler属性填写任务注解@JobHandler中定义的值;
![输入图片说明](https://static.oschina.net/uploads/img/201704/27225124_yrcO.png "在这里输入图片标题")
@ -419,6 +437,16 @@ XXL-JOB是一个轻量级分布式任务调度框架其核心设计目标是
![输入图片说明](https://static.oschina.net/uploads/img/201704/27232305_BPLG.png "在这里输入图片标题")
### 3.5 GLUE模式(NodeJS)
#### 步骤一:调度中心,新建调度任务
参考上文“配置属性详细说明”对新建的任务进行参数配置,运行模式选中 "GLUE模式(NodeJS)"
#### 步骤二:开发任务代码:
选中指定任务点击该任务右侧“GLUE”按钮将会前往GLUE任务的Web IDE界面在该界面支持对任务代码进行开发也可以在IDE中开发完成后复制粘贴到编辑中
该模式的任务实际上是一段 "nodejS" 脚本;
## 四、任务管理
@ -560,7 +588,7 @@ XXL-JOB首先定制了Quartz原生表结构前缀XXL_JOB_QRTZ_
#### 5.3.2 系统组成
- **调度模块(调度中心)**
负责管理调度信息,按照调度配置发出调度请求,自身不承担业务代码。调度系统与任务解耦,提高了系统可用性和稳定性,同时调度系统性能不再受限于任务模块;
支持可视化、简单且动态的管理调度信息包括任务新建更新删除GLUE开发和任务报警等所有上述操作都会实时生效同时支持监控调度结果以及执行日志支持执行器Failover。
支持可视化、简单且动态的管理调度信息包括任务新建更新删除GLUE开发和任务报警等所有上述操作都会实时生效同时支持监控调度结果以及执行日志支持执行器Failover。
- **执行模块(执行器)**
负责接收调度请求并执行任务逻辑。任务模块专注于任务的执行等操作,开发和维护更加简单和高效;
接收“调度中心”的执行请求、终止请求和日志请求等。
@ -595,18 +623,24 @@ org.quartz.jobStore.clusterCheckinInterval: 1000
```
#### 5.4.4 调度线程池
默认线程池中线程的数量为10个,避免单线程因阻塞而引起任务调度延迟。
调度采用线程池方式实现,避免单线程因阻塞而引起任务调度延迟。
```
org.quartz.threadPool.class: org.quartz.simpl.SimpleThreadPool
org.quartz.threadPool.threadCount: 10
org.quartz.threadPool.threadCount: 15
org.quartz.threadPool.threadPriority: 5
org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread: true
```
XXL-JOB系统中业务逻辑在远程执行器执行调度中心每次调度仅仅负责一次调度请求执行器会将请求存入执行队列并且立即响应调度中心相比直接在quartz的QuartzJobBean中执行业务逻辑差别就像大象和羽毛
XXL-JOB系统中业务逻辑在远程执行器执行调度中心每次触发调度时仅发送一次调度请求执行器会将请求存入执行队列并且立即响应调度中心相比直接在quartz的QuartzJobBean中执行业务逻辑极大的降低了调度线程占用
XXL-JOB调度中心中每个JOB逻辑非常 “轻”单个JOB一次运行平均耗时基本在 "10ms" 之内基本为一次请求的网络开销因此可以保证使用有限的线程支撑大量的JOB并发运行
理论上采用推荐机器配置 "4核4G内存"情况下,单线程可以承担 100quartz最小时间粒度1000ms/触发一次任务耗时10ms个密集任务每秒执行一次的正常调度触发。因此默认配置的15个线程理论上可以承担起1500个密集任务的正常运行。
XXL-JOB调度中心中每个JOB逻辑非常 “轻”单个JOB一次运行平均耗时基本在 "100ms" 之内基本是网络开销因此可以保证使用有限的线程支撑大量的JOB并发运行上面配置的10个线程至少可以支撑100个JOB正常运行
实际场景中调度请求网络耗时不同、DB读写耗时不同、任务密集或稀疏调度情况不同会导致任务量上限会上下波动。
如若需要支撑更多的任务量,可以通过 "调大调度线程数" 和 "提升机器配置" 两种方式实现。
#### 5.4.5 @DisallowConcurrentExecution
XXL-JOB调度模块的“调度中心”默认不使用该注解即默认开启并行机制因为RemoteHttpJobBean为公共QuartzJobBean这样在多线程调度的情况下调度模块被阻塞的几率很低大大提高了调度系统的承载量。
@ -691,13 +725,13 @@ xxl-job-admin#com.xxl.job.admin.controller.JobApiController.callback
### 5.5 任务 "运行模式" 剖析
#### 5.5.1 "Bean模式" 任务
开发步骤:可参考 "章节三"
原理每个Bean模式任务都是一个Spring的Bean类实例它被维护在“执行器”项目的Spring容器中。任务类需要加“@JobHander(value="名称")”注解因为“执行器”会根据该注解识别Spring容器中的任务。任务类需要继承统一接口“IJobHandler”任务逻辑在execute方法中开发因为“执行器”在接收到调度中心的调度请求时将会调用“IJobHandler”的execute方法执行任务逻辑。
原理每个Bean模式任务都是一个Spring的Bean类实例它被维护在“执行器”项目的Spring容器中。任务类需要加“@JobHandler(value="名称")”注解因为“执行器”会根据该注解识别Spring容器中的任务。任务类需要继承统一接口“IJobHandler”任务逻辑在execute方法中开发因为“执行器”在接收到调度中心的调度请求时将会调用“IJobHandler”的execute方法执行任务逻辑。
#### 5.5.2 "GLUE模式(Java)" 任务
开发步骤:可参考 "章节三"
原理:每个 "GLUE模式(Java)" 任务的代码实际上是“一个继承自“IJobHandler”的实现类的类代码”“执行器”接收到“调度中心”的调度请求时会通过Groovy类加载器加载此代码实例化成Java对象同时注入此代码中声明的Spring服务请确保Glue代码中的服务和类引用在“执行器”项目中存在然后调用该对象的execute方法执行任务逻辑。
#### 5.5.3 GLUE模式(Shell) + GLUE模式(Python)
#### 5.5.3 GLUE模式(Shell) + GLUE模式(Python) + GLUE模式(NodeJS)
开发步骤:可参考 "章节三"
原理脚本任务的源码托管在调度中心脚本逻辑在执行器运行。当触发脚本任务时执行器会加载脚本源码在执行器机器上生成一份脚本文件然后通过Java代码调用该脚本并且实时将脚本输出日志写到任务日志文件中从而在调度中心可以实时监控脚本运行情况脚本返回码为0时表示执行成功其他标示执行失败。
@ -705,6 +739,7 @@ xxl-job-admin#com.xxl.job.admin.controller.JobApiController.callback
- shell脚本任务运行模式选择为 "GLUE模式(Shell)"时支持 "shell" 脚本任务;
- python脚本任务运行模式选择为 "GLUE模式(Python)"时支持 "python" 脚本任务;
- nodejs脚本务运行模式选择为 "GLUE模式(NodeJS)"时支持 "nodejs" 脚本任务;
#### 5.5.4 执行器
@ -712,7 +747,7 @@ xxl-job-admin#com.xxl.job.admin.controller.JobApiController.callback
![输入图片说明](https://static.oschina.net/uploads/img/201703/10174923_TgNO.png "在这里输入图片标题")
在项目启动时,执行器会通过“@JobHander”识别Spring容器中“Bean模式任务”以注解的value属性为key管理起来。
在项目启动时,执行器会通过“@JobHandler”识别Spring容器中“Bean模式任务”以注解的value属性为key管理起来。
“执行器”接收到“调度中心”的调度请求时如果任务类型为“Bean模式”将会匹配Spring容器中的“Bean模式任务”然后调用其execute方法执行任务逻辑。如果任务类型为“GLUE模式”将会加载GLue代码实例化Java对象注入依赖的Spring服务注意Glue代码中注入的Spring服务必须存在与该“执行器”项目的Spring容器中然后调用execute方法执行任务逻辑。
@ -756,11 +791,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开始),执行器集群列表中当前执行器的序号;
total总分片数执行器集群的总机器数量
@ -779,6 +824,39 @@ XXL-JOB会为每次调度请求生成一个单独的日志文件需要通过
- 设置一调度中心和执行器均不设置AccessToken关闭安全性校验
- 设置二调度中心和执行器设置了相同的AccessToken
### 5.11 调度中心API服务
调度中心提供了API服务供执行器和业务方选择使用目前提供的API服务有
1、任务结果回调服务
2、执行器注册服务
3、执行器注册摘除服务
4、触发任务单次执行服务支持任务根据业务事件触发
调度中心API服务位置com.xxl.job.core.biz.AdminBiz.java
调度中心API服务请求参考代码com.xxl.job.adminbiz.AdminBizTest.java
### 5.12 执行器API服务
执行器提供了API服务供调度中心选择使用目前提供的API服务有
1、心跳检测
2、忙碌检测
3、触发任务执行
4、获取Rolling Log
5、终止任务
执行器API服务位置com.xxl.job.core.biz.ExecutorBiz
执行器API服务请求参考代码com.xxl.executor.test.DemoJobHandlerTest
### 5.13 故障转移 & 失败重试
一次完整任务流程包括"调度(调度中心) + 执行(执行器)"两个阶段。
- "故障转移"发生在调度阶段在执行器集群部署时如果某一台执行器发生故障该策略支持自动进行Failover切换到一台正常的执行器机器并且完成调度请求流程。
- "失败重试"发生在"调度 + 执行"两个阶段,如下:
- 调度中心调度失败时,任务失败处理策略选择"失败重试",将会自动重试一次;
- 执行器运行失败时,任务执行结果返回"失败重试IJobHandler.FAIL_RETRY"回调,将会自动重试一次;
## 六、版本更新日志
### 6.1 版本 V1.1.x新特性[2015-12-05]
@ -811,13 +889,13 @@ XXL-JOB会为每次调度请求生成一个单独的日志文件需要通过
- 稳定性;
### 6.3 版本 V1.3.0,新特性[2016-05-19]
- 1、遗弃“本地任务”模式推荐使用“远程任务”易于系统解耦任务对应的JobHander统称为“执行器”
- 1、遗弃“本地任务”模式推荐使用“远程任务”易于系统解耦任务对应的JobHandler统称为“执行器”
- 2、遗弃“servlet”方式底层系统通讯推荐使用JETTY方式调度+回调双向通讯,重构通讯逻辑;
- 3、UI交互优化左侧菜单展开状态优化菜单项选中状态优化任务列表打开表格有压缩优化
- 4、【重要】“执行器”细分为BEAN、GLUE两种开发模式简介见下文
“执行器” 模式简介:
- BEAN模式执行器每个执行器都是Spring的一个Bean实例XXL-JOB通过注解@JobHander识别和调度执行器
- BEAN模式执行器每个执行器都是Spring的一个Bean实例XXL-JOB通过注解@JobHandler识别和调度执行器
-GLUE模式执行器每个执行器对应一段代码在线Web编辑和维护动态编译生效执行器负责加载GLUE代码和执行
### 6.4 版本 V1.3.1,新特性[2016-05-23]
@ -982,30 +1060,61 @@ Tips: 历史版本(V1.3.x)目前已经Release至稳定版本, 进入维护阶段
- 10、springboot版本执行器升级至1.5.6.RELEASE版本
- 11、统一maven依赖版本管理
### 6.18 版本 V1.8.2 特性[Coding]
- 1、解决执行器回调URL不支持配置HTTPS时问题
- 2、规范项目目录方便扩展多执行器
- 3、新增JFinal类型执行器sample示例项目
- 4、执行器手动设置IP时将会绑定Host
- 5、项目主页搭建提供中英文文档(http://www.xuxueli.com/xxl-job)
- 6、执行器回调线程销毁前, 批量回调队列中数据,防止任务结果丢失;
- 7、执行器注册线程销毁时, 主动摘除注册机器信息,提高执行器注册的实时性;
- 8、调度中心任务监控线程销毁时批量对失败任务告警防止告警信息丢失
- 9、调度中心API服务支持API方式触发任务执行
- 10、事件调度系统支持Cron、子任务触发、事件触发API三种方式触发任务调度
### 6.19 版本 V1.8.2 特性[2017-09-04]
- 1、项目主页搭建提供中英文文档http://www.xuxueli.com/xxl-job
- 2、JFinal执行器Sample示例项目
- 3、事件触发除了"Cron方式"和"任务依赖方式"触发任务执行之外支持基于事件的触发任务方式。调度中心提供触发任务单次执行的API服务可根据业务事件灵活触发。
- 4、执行器摘除执行器销毁时主动通知调度中心并摘除对应执行器节点提高执行器状态感知的时效性。
- 5、执行器手动设置IP时将会绑定Host
- 6、规范项目目录方便扩展多执行器
- 7、解决执行器回调URL不支持配置HTTPS时问题
- 8、执行器回调线程销毁前, 批量回调队列中数据,防止任务结果丢失;
- 9、调度中心任务监控线程销毁时批量对失败任务告警防止告警信息丢失
- 10、任务日志文件路径时间戳格式化时SimpleDateFormat并发问题解决
### 6.20 版本 V1.9.0 特性[迭代中]
- 1、新增任务运行模式 "GLUE模式(NodeJS) "支持NodeJS脚本任务
- 2、脚本任务Shell、Python和Nodejs等支持获取分片参数
- 3、失败重试完整支持调度中心调度失败且启用"失败重试"策略时,将会自动重试一次;执行器执行失败且回调失败重试状态(新增失败重试状态返回值)时,也将会自动重试一次;
- 4、失败告警策略扩展默认提供邮件失败告警可扩展短信等扩展代码位置为 "JobFailMonitorHelper.failAlarm"
- 5、执行器端口支持自动生成(小于等于0时),避免端口定义冲突;
- 6、调度报表优化支持时间区间筛选
- 7、Log组件支持输出异常栈信息底层实现优化
- 8、告警邮件样式优化调整为表格形式邮件组件调整为commons-email简化邮件操作
- 9、项目依赖升级如spring、jackson等
- 10、任务日志记录发起调度的机器信息
- 11、交互优化如登陆注销
- 12、任务Cron长度扩展支持至128位支持负责类型Cron设置
- 13、执行器地址录入交互优化地址长度扩展支持至512位支持大规模执行器集群配置
- 14、任务参数“IJobHandler.execute”入参改为“String params”增强入参通用性。
- 15、JobHandler提供init/destroy方法支持在JobHandler初始化和销毁时进行附加操作
- 16、任务注解调整为 “@JobHandler”与任务抽象接口统一
- 17、修复任务监控线程被耗时任务阻塞的问题
- 18、修复任务监控线程无法监控任务触发和执行状态均未0的问题
- 19、执行器动态代理对象拦截非业务方法的执行
- 20、修复JobThread捕获Error错误不更新JobLog的问题
- 21、修复任务列表界面左侧菜单合并时样式错乱问题
- 22、调度中心项目日志配置改为xml文件格式
- 23、Log地址格式兼容支持非"/"结尾路径配置;
- 24、底层系统日志级别规范调整清理遗留代码
- 25、建表SQL优化支持同步创建制定编码的库和表
### TODO LIST
- 1、任务权限管理执行器为粒度分配权限核心操作校验权限
- 2、任务分片路由分片采用一致性Hash算法计算出尽量稳定的分片顺序即使注册机器存在波动也不会引起分批分片顺序大的波动目前采用IP自然排序可以满足需求待定
- 3、失败重试优化目前失败重试逻辑为在本次调度请求失败后重新执行一次请求逻辑。优化点为针对调度和执行失败时均做失败重试重试时重新触发一次完整调度这将可能导致失败是调度死循环待定。
- 4、回调失败写文件查看日志时读文件确认重启后回调确认
- 3、任务单机多线程:提升任务单机并行处理能力;
- 4、回调失败丢包问题:执行器回调失败写文件,重启或周期性回调重试;调度中心周期性请求并同步未回调的执行结果
- 5、任务依赖流程图子任务+会签任务,各节点日志;
- 6、调度任务优先级
- 7、移除quartz依赖重写调度模块新增或恢复任务时将下次执行记录插入delayqueue调度中心集群竞争分布式锁成功节点批量加载到期delayqueue数据批量执行。
- 8、springboot 和 docker镜像并且推送docker镜像到中央仓库更进一步实现产品开箱即用
- 9、国际化调度中心界面。
- 10、执行器摘除执行器销毁时主动通知调度中心并摘除对应执行器节点提高执行器状态感知的时效性。
- 11、任务类方法"IJobHandler.execute"的参数类型改为"string",进一步方便参数传递;任务注解和任务类统一并改为"JobHandler"";
- 10、任务告警逻辑调整任务调度以及任务回调失败时均推送监控队列。后期考虑通过任务Log字段控制告警状态
- 11、执行器Log清理功能调度中心Log删除时同步删除执行器中的Log文件
- 12、Bean模式任务JobHandler自动从执行器中查询展示为下拉框选择后自动填充任务名称等属性
- 13、任务事件触发API服务优化支持调用时动态传参
## 七、其他
@ -1023,22 +1132,4 @@ Tips: 历史版本(V1.3.x)目前已经Release至稳定版本, 进入维护阶段
---
### 捐赠
无论金额多少都足够表达您这份心意,非常感谢
微信:<img src="https://raw.githubusercontent.com/xuxueli/xxl-job/master/doc/images/donate-wechat.png" width="200">
支付宝:<img src="https://raw.githubusercontent.com/xuxueli/xxl-job/master/doc/images/donate-alipay.jpg" width="200">
> 自2017-03-29起XXL系列接收用户捐赠捐赠记录如下, 感谢你们的支持!:
捐赠时间 | 金额 | 名称 | 留言
--- | --- | --- | ---
2017-08-17 | 10.00¥ | 微信 | 不行,还得感谢一把
2017-08-17 | 10.00¥ | 微信 | 感谢,好项目,好作者
2017-08-16 | 3.00¥ | 微信 |
2017-06-30 | 10.00¥ | 石头哥哥* | 支持下xxl开源系列
2017-06-16 | 10.00¥ | 劳巴* | xxl-job对我的帮助很大辛苦你们的帮助辛苦了
2017-06-12 | 6.66¥ | 凌浦* |
2017-06-12 | 10.00¥ | Henry* | 支持XXL
2017-06-12 | 10.00¥ | loioi* | 感谢您的开源项目!
2017-05-10 | 10.00¥ | 阿杜杜不是阿木木* | 感谢您的开源项目!
无论金额多少都足够表达您这份心意,非常感谢 [前往捐赠](http://www.xuxueli.com/page/donate.html )

@ -1,3 +1,7 @@
CREATE database if NOT EXISTS `xxl-job` default character set utf8 collate utf8_general_ci;
use `xxl-job`;
CREATE TABLE XXL_JOB_QRTZ_JOB_DETAILS
(
@ -156,7 +160,7 @@ CREATE TABLE `XXL_JOB_QRTZ_TRIGGER_INFO` (
`alarm_email` varchar(255) DEFAULT NULL COMMENT '报警邮件',
`executor_route_strategy` varchar(50) DEFAULT NULL COMMENT '执行器路由策略',
`executor_handler` varchar(255) DEFAULT NULL COMMENT '执行器任务handler',
`executor_param` varchar(255) DEFAULT NULL COMMENT '执行器任务参数',
`executor_param` varchar(512) DEFAULT NULL COMMENT '执行器任务参数',
`executor_block_strategy` varchar(50) DEFAULT NULL COMMENT '阻塞处理策略',
`executor_fail_strategy` varchar(50) DEFAULT NULL COMMENT '失败处理策略',
`glue_type` varchar(50) NOT NULL COMMENT 'GLUE类型',
@ -174,7 +178,7 @@ CREATE TABLE `XXL_JOB_QRTZ_TRIGGER_LOG` (
`glue_type` varchar(50) DEFAULT NULL COMMENT 'GLUE类型',
`executor_address` varchar(255) DEFAULT NULL COMMENT '执行器地址,本次执行的地址',
`executor_handler` varchar(255) DEFAULT NULL COMMENT '执行器任务handler',
`executor_param` varchar(255) DEFAULT NULL COMMENT 'executor_param',
`executor_param` varchar(512) DEFAULT NULL COMMENT '执行器任务参数',
`trigger_time` datetime DEFAULT NULL COMMENT '调度-时间',
`trigger_code` varchar(255) NOT NULL DEFAULT '0' COMMENT '调度-结果',
`trigger_msg` varchar(2048) DEFAULT NULL COMMENT '调度-日志',
@ -210,7 +214,7 @@ CREATE TABLE `XXL_JOB_QRTZ_TRIGGER_GROUP` (
`title` varchar(12) NOT NULL COMMENT '执行器名称',
`order` tinyint(4) NOT NULL DEFAULT '0' COMMENT '排序',
`address_type` tinyint(4) NOT NULL DEFAULT '0' COMMENT '执行器地址类型0=自动注册、1=手动录入',
`address_list` varchar(200) DEFAULT NULL COMMENT '执行器地址列表,多地址逗号分隔',
`address_list` varchar(512) DEFAULT NULL COMMENT '执行器地址列表,多地址逗号分隔',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 594 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 130 KiB

@ -3,7 +3,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>com.xuxueli</groupId>
<artifactId>xxl-job</artifactId>
<version>1.8.2-SNAPSHOT</version>
<version>1.9.0-SNAPSHOT</version>
<packaging>pom</packaging>
<name>${project.artifactId}</name>
@ -20,31 +20,32 @@
<javax.servlet-api.version>3.0.1</javax.servlet-api.version>
<jsp-api.version>2.2</jsp-api.version>
<spring.version>3.2.18.RELEASE</spring.version>
<jackson-mapper-asl.version>1.9.13</jackson-mapper-asl.version>
<aspectjweaver.version>1.8.7</aspectjweaver.version>
<spring.version>4.3.13.RELEASE</spring.version>
<jackson.version>2.9.3</jackson.version>
<aspectjweaver.version>1.8.13</aspectjweaver.version>
<slf4j-api.version>1.7.25</slf4j-api.version>
<freemarker.version>2.3.20</freemarker.version>
<junit.version>4.11</junit.version>
<freemarker.version>2.3.23</freemarker.version>
<junit.version>4.12</junit.version>
<jetty-server.version>9.2.22.v20170606</jetty-server.version>
<hessian.version>4.0.51</hessian.version>
<httpclient.version>4.5.4</httpclient.version>
<jetty-server.version>9.4.6.v20170531</jetty-server.version>
<hessian.version>4.0.38</hessian.version>
<httpclient.version>4.3.6</httpclient.version>
<commons-exec.version>1.3</commons-exec.version>
<commons-beanutils.version>1.9.2</commons-beanutils.version>
<commons-lang.version>2.6</commons-lang.version>
<commons-collections4.version>4.1</commons-collections4.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>
<mysql-connector-java.version>5.1.29</mysql-connector-java.version>
<mybatis-spring.version>1.2.2</mybatis-spring.version>
<mybatis.version>3.2.8</mybatis.version>
<mysql-connector-java.version>5.1.45</mysql-connector-java.version>
<mybatis-spring.version>1.3.1</mybatis-spring.version>
<mybatis.version>3.4.5</mybatis.version>
<groovy-all.version>2.4.5</groovy-all.version>
<mail.version>1.4.6</mail.version>
<groovy-all.version>2.4.13</groovy-all.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>
<build>

@ -4,7 +4,7 @@
<parent>
<groupId>com.xuxueli</groupId>
<artifactId>xxl-job</artifactId>
<version>1.8.2-SNAPSHOT</version>
<version>1.9.0-SNAPSHOT</version>
</parent>
<artifactId>xxl-job-admin</artifactId>
<packaging>war</packaging>
@ -40,18 +40,22 @@
</dependency>
<!-- jackson (support spring json) -->
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-mapper-asl</artifactId>
<version>${jackson-mapper-asl.version}</version>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
</dependency>
<!-- slf4j -->
<!-- servlet -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>${slf4j-api.version}</version>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>${javax.servlet-api.version}</version>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>${jsp-api.version}</version>
</dependency>
<!-- freemarker -->
<dependency>
<groupId>org.freemarker</groupId>
@ -59,37 +63,37 @@
<version>${freemarker.version}</version>
</dependency>
<!-- commons-beanutils -->
<!-- slf4j -->
<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
<version>${commons-beanutils.version}</version>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>${slf4j-api.version}</version>
</dependency>
<!-- commons-lang -->
<!-- junit -->
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>${commons-lang.version}</version>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<!-- servlet -->
<!-- commons-collections4 -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>${javax.servlet-api.version}</version>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>${commons-collections4.version}</version>
</dependency>
<!-- commons-lang3 -->
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>${jsp-api.version}</version>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>${commons-lang3.version}</version>
</dependency>
<!-- junit -->
<!-- commons-email -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
<groupId>org.apache.commons</groupId>
<artifactId>commons-email</artifactId>
<version>${commons-email.version}</version>
</dependency>
<!-- c3p0 -->
@ -116,7 +120,6 @@
<version>${mybatis.version}</version>
</dependency>
<!-- httpclient -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
@ -124,13 +127,6 @@
<version>${httpclient.version}</version>
</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 -->
<dependency>
<groupId>org.quartz-scheduler</groupId>

@ -5,9 +5,12 @@ import com.xxl.job.admin.controller.interceptor.PermissionInterceptor;
import com.xxl.job.admin.core.util.PropertiesUtil;
import com.xxl.job.admin.service.XxlJobService;
import com.xxl.job.core.biz.model.ReturnT;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.propertyeditors.CustomDateEditor;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
@ -15,6 +18,8 @@ import org.springframework.web.bind.annotation.ResponseBody;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;
/**
@ -38,8 +43,8 @@ public class IndexController {
@RequestMapping("/triggerChartDate")
@ResponseBody
public ReturnT<Map<String, Object>> triggerChartDate() {
ReturnT<Map<String, Object>> triggerChartDate = xxlJobService.triggerChartDate();
public ReturnT<Map<String, Object>> triggerChartDate(Date startDate, Date endDate) {
ReturnT<Map<String, Object>> triggerChartDate = xxlJobService.triggerChartDate(startDate, endDate);
return triggerChartDate;
}
@ -91,5 +96,12 @@ public class IndexController {
return "help";
}
@InitBinder
public void initBinder(WebDataBinder binder) {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
dateFormat.setLenient(false);
binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, true));
}
}

@ -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.XxlJobInfoDao;
import com.xxl.job.core.biz.model.ReturnT;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;

@ -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.ReturnT;
import com.xxl.job.core.rpc.netcom.NetComClientProxy;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.time.DateUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;

@ -1,17 +1,17 @@
package com.xxl.job.admin.controller.interceptor;
import java.util.HashMap;
import org.apache.commons.lang3.ArrayUtils;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang.ArrayUtils;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import java.util.HashMap;
/**
* push cookies to model as cookieMap
*
* @author xuxueli 2015-12-12 18:09:04
*/
public class CookieInterceptor extends HandlerInterceptorAdapter {

@ -12,11 +12,12 @@ import java.math.BigInteger;
/**
* ,
*
* @author xuxueli 2015-12-12 18:09:04
*/
public class PermissionInterceptor extends HandlerInterceptorAdapter {
public static final String LOGIN_IDENTITY_KEY = "LOGIN_IDENTITY";
public static final String LOGIN_IDENTITY_KEY = "XXL_JOB_LOGIN_IDENTITY";
public static final String LOGIN_IDENTITY_TOKEN;
static {
String username = PropertiesUtil.getString("xxl.job.login.username");

@ -1,94 +0,0 @@
package com.xxl.job.admin.core.jobbean;
//package com.xxl.job.action.job;
//
//import java.io.PrintWriter;
//import java.io.StringWriter;
//import java.util.Date;
//import java.util.HashMap;
//import java.util.Map;
//
//import org.apache.commons.lang.StringUtils;
//import org.quartz.JobExecutionContext;
//import org.quartz.JobExecutionException;
//import org.quartz.JobKey;
//import org.slf4j.Logger;
//import org.slf4j.LoggerFactory;
//import org.springframework.scheduling.quartz.QuartzJobBean;
//
//import com.xxl.job.client.handler.HandlerRouter;
//import com.xxl.job.client.util.XxlJobNetCommUtil.RemoteCallBack;
//import com.xxl.job.client.util.JacksonUtil;
//import com.xxl.job.core.model.XxlJobInfo;
//import com.xxl.job.core.model.XxlJobLog;
//import com.xxl.job.core.thread.JobFailMonitorHelper;
//import com.xxl.job.core.util.DynamicSchedulerUtil;
//
///**
// * http job bean
// * @author xuxueli 2015-12-17 18:20:34
// */
//@Deprecated
//public abstract class LocalNomalJobBean extends QuartzJobBean {
// private static Logger logger = LoggerFactory.getLogger(LocalNomalJobBean.class);
//
// @Override
// protected void executeInternal(JobExecutionContext context)
// throws JobExecutionException {
// JobKey jobKey = context.getTrigger().getJobKey();
//
// XxlJobInfo jobInfo = DynamicSchedulerUtil.xxlJobInfoDao.load(jobKey.getGroup(), jobKey.getName());
// @SuppressWarnings("unchecked")
// HashMap<String, String> jobDataMap = (HashMap<String, String>) JacksonUtil.readValueRefer(jobInfo.getJobData(), Map.class);
//
// // save log
// XxlJobLog jobLog = new XxlJobLog();
// jobLog.setJobGroup(jobInfo.getJobGroup());
// jobLog.setJobName(jobInfo.getJobName());
// jobLog.setJobCron(jobInfo.getJobCron());
// jobLog.setJobDesc(jobInfo.getJobDesc());
// jobLog.setJobClass(jobInfo.getJobClass());
// jobLog.setJobData(jobInfo.getJobData());
//
// jobLog.setJobClass(RemoteHttpJobBean.class.getName());
// jobLog.setJobData(jobInfo.getJobData());
// DynamicSchedulerUtil.xxlJobLogDao.save(jobLog);
// logger.info(">>>>>>>>>>> xxl-job trigger start, jobLog:{}", jobLog);
//
// // trigger request
// String handler_params = jobDataMap.get(HandlerRouter.HANDLER_PARAMS);
// String[] handlerParams = null;
// if (StringUtils.isNotBlank(handler_params)) {
// handlerParams = handler_params.split(",");
// }
//
// jobLog.setTriggerTime(new Date());
// jobLog.setTriggerStatus(RemoteCallBack.SUCCESS);
// jobLog.setTriggerMsg(null);
//
// try {
// Object responseMsg = this.handle(handlerParams);
//
// jobLog.setHandleTime(new Date());
// jobLog.setHandleStatus(RemoteCallBack.SUCCESS);
// jobLog.setHandleMsg(JacksonUtil.writeValueAsString(responseMsg));
// } catch (Exception e) {
// logger.info("JobThread Exception:", e);
// StringWriter out = new StringWriter();
// e.printStackTrace(new PrintWriter(out));
//
// jobLog.setHandleTime(new Date());
// jobLog.setHandleStatus(RemoteCallBack.FAIL);
// jobLog.setHandleMsg(out.toString());
// }
//
// // update trigger info
// DynamicSchedulerUtil.xxlJobLogDao.updateTriggerInfo(jobLog);
// DynamicSchedulerUtil.xxlJobLogDao.updateHandleInfo(jobLog);
// JobFailMonitorHelper.monitor(jobLog.getId());
// logger.info(">>>>>>>>>>> xxl-job trigger end, jobLog.id:{}, jobLog:{}", jobLog.getId(), jobLog);
//
// }
//
// public abstract Object handle(String... param);
//
//}

@ -1,30 +0,0 @@
package com.xxl.job.admin.core.jobbean.impl;
//package com.xxl.job.action.job.impl;
//
//import java.util.concurrent.TimeUnit;
//
//import org.quartz.DisallowConcurrentExecution;
//
//import com.xxl.job.action.job.LocalNomalJobBean;
//
///**
// * demo job bean for no-concurrent
// * @author xuxueli 2016-3-12 14:25:14
// */
//@Deprecated
//@DisallowConcurrentExecution // 串行;线程数要多配置几个,否则不生效;
//public class DemoConcurrentJobBean extends LocalNomalJobBean {
//
// @Override
// public Object handle(String... param) {
//
// try {
// TimeUnit.SECONDS.sleep(10);
// } catch (InterruptedException e) {
// logger.error(e.getMessage(), e);
// }
//
// return false;
// }
//
//}

@ -1,32 +0,0 @@
package com.xxl.job.admin.core.jobbean.impl;
//package com.xxl.job.action.job.impl;
//
//import java.util.concurrent.TimeUnit;
//
//import org.slf4j.Logger;
//import org.slf4j.LoggerFactory;
//
//import com.xxl.job.action.job.LocalNomalJobBean;
//
///**
// * demo job bean for concurrent
// * @author xuxueli 2016-3-12 14:25:57
// */
//@Deprecated
//public class DemoNomalJobBean extends LocalNomalJobBean {
// private static Logger Logger = LoggerFactory.getLogger(DemoNomalJobBean.class);
//
// @Override
// public Object handle(String... param) {
// Logger.info("DemoNomalJobBean run :" + param);
//
// try {
// TimeUnit.SECONDS.sleep(10);
// } catch (InterruptedException e) {
// logger.error(e.getMessage(), e);
// }
//
// return false;
// }
//
//}

@ -1,6 +1,6 @@
package com.xxl.job.admin.core.model;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang3.StringUtils;
import java.util.ArrayList;
import java.util.Arrays;

@ -4,8 +4,11 @@ import com.xxl.job.admin.core.model.XxlJobGroup;
import com.xxl.job.admin.core.model.XxlJobInfo;
import com.xxl.job.admin.core.model.XxlJobLog;
import com.xxl.job.admin.core.schedule.XxlJobDynamicScheduler;
import com.xxl.job.admin.core.util.JobKeyUtil;
import com.xxl.job.admin.core.util.MailUtil;
import com.xxl.job.core.biz.model.ReturnT;
import com.xxl.job.core.handler.IJobHandler;
import org.apache.commons.collections4.CollectionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -26,6 +29,8 @@ public class JobFailMonitorHelper {
return instance;
}
// ---------------------- monitor ----------------------
private LinkedBlockingQueue<Integer> queue = new LinkedBlockingQueue<Integer>(0xfff8);
private Thread monitorThread;
@ -35,33 +40,41 @@ public class JobFailMonitorHelper {
@Override
public void run() {
// monitor
while (!toStop) {
try {
Integer jobLogId = JobFailMonitorHelper.instance.queue.take();
if (jobLogId != null && jobLogId > 0) {
XxlJobLog log = XxlJobDynamicScheduler.xxlJobLogDao.load(jobLogId);
if (log!=null) {
if (ReturnT.SUCCESS_CODE==log.getTriggerCode() && log.getHandleCode()==0) {
// job running, wait + again monitor
TimeUnit.SECONDS.sleep(10);
List<Integer> jobLogIdList = new ArrayList<Integer>();
int drainToNum = JobFailMonitorHelper.instance.queue.drainTo(jobLogIdList);
if (CollectionUtils.isNotEmpty(jobLogIdList)) {
for (Integer jobLogId : jobLogIdList) {
if (jobLogId==null || jobLogId==0) {
continue;
}
XxlJobLog log = XxlJobDynamicScheduler.xxlJobLogDao.load(jobLogId);
if (log == null) {
continue;
}
if (IJobHandler.SUCCESS.getCode() == log.getTriggerCode() && log.getHandleCode() == 0) {
JobFailMonitorHelper.monitor(jobLogId);
logger.info(">>>>>>>>>>> job monitor, job running, JobLogId:{}", jobLogId);
}
if (ReturnT.SUCCESS_CODE==log.getTriggerCode() && ReturnT.SUCCESS_CODE==log.getHandleCode()) {
} else if (IJobHandler.SUCCESS.getCode() == log.getHandleCode()) {
// job success, pass
logger.info(">>>>>>>>>>> job monitor, job success, JobLogId:{}", jobLogId);
}
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,
sendMonitorEmail(log);
failAlarm(log);
logger.info(">>>>>>>>>>> job monitor, job fail, JobLogId:{}", jobLogId);
} else {
JobFailMonitorHelper.monitor(jobLogId);
logger.info(">>>>>>>>>>> job monitor, job status unknown, JobLogId:{}", jobLogId);
}
}
}
TimeUnit.SECONDS.sleep(10);
} catch (Exception e) {
logger.error("job monitor error:{}", e);
}
@ -75,7 +88,7 @@ public class JobFailMonitorHelper {
XxlJobLog log = XxlJobDynamicScheduler.xxlJobLogDao.load(jobLogId);
if (ReturnT.FAIL_CODE == log.getTriggerCode()|| ReturnT.FAIL_CODE==log.getHandleCode()) {
// job fail,
sendMonitorEmail(log);
failAlarm(log);
logger.info(">>>>>>>>>>> job monitor last, job fail, JobLogId:{}", jobLogId);
}
}
@ -87,24 +100,6 @@ public class JobFailMonitorHelper {
monitorThread.start();
}
/**
* send monitor email
* @param jobLog
*/
private void sendMonitorEmail(XxlJobLog jobLog){
XxlJobInfo info = XxlJobDynamicScheduler.xxlJobInfoDao.loadById(jobLog.getJobId());
if (info!=null && info.getAlarmEmail()!=null && info.getAlarmEmail().trim().length()>0) {
Set<String> emailSet = new HashSet<String>(Arrays.asList(info.getAlarmEmail().split(",")));
for (String email: emailSet) {
String title = "《调度监控报警》(任务调度中心XXL-JOB)";
XxlJobGroup group = XxlJobDynamicScheduler.xxlJobGroupDao.load(Integer.valueOf(info.getJobGroup()));
String content = MessageFormat.format("任务调度失败, 执行器名称:{0}, 任务描述:{1}.", group!=null?group.getTitle():"null", info.getJobDesc());
MailUtil.sendMail(email, title, content, false, null);
}
}
}
public void toStop(){
toStop = true;
// interrupt and wait
@ -120,5 +115,55 @@ public class JobFailMonitorHelper {
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>JobKey</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
*
* @param jobLog
*/
private void failAlarm(XxlJobLog jobLog){
// send monitor email
XxlJobInfo info = XxlJobDynamicScheduler.xxlJobInfoDao.loadById(jobLog.getJobId());
if (info!=null && info.getAlarmEmail()!=null && info.getAlarmEmail().trim().length()>0) {
Set<String> emailSet = new HashSet<String>(Arrays.asList(info.getAlarmEmail().split(",")));
for (String email: emailSet) {
XxlJobGroup group = XxlJobDynamicScheduler.xxlJobGroupDao.load(Integer.valueOf(info.getJobGroup()));
String title = "调度中心监控报警";
String content = MessageFormat.format(mailBodyTemplate, group!=null?group.getTitle():"null", JobKeyUtil.formatJobKey(info), info.getJobDesc());
MailUtil.sendMail(email, title, content);
}
}
// TODO, custom alarm strategy, such as sms
}
}

@ -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.schedule.XxlJobDynamicScheduler;
import com.xxl.job.core.enums.RegistryConfig;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@ -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.TriggerParam;
import com.xxl.job.core.enums.ExecutorBlockStrategyEnum;
import org.apache.commons.collections.CollectionUtils;
import com.xxl.job.core.util.IpUtil;
import org.apache.commons.collections4.CollectionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -35,7 +36,7 @@ public class XxlJobTrigger {
// load data
XxlJobInfo jobInfo = XxlJobDynamicScheduler.xxlJobInfoDao.loadById(jobId); // job info
if (jobInfo == null) {
logger.warn(">>>>>>>>>>>> xxl-job trigger fail, jobId invalidjobId={}", jobId);
logger.warn(">>>>>>>>>>>> trigger fail, jobId invalidjobId={}", jobId);
return;
}
XxlJobGroup group = XxlJobDynamicScheduler.xxlJobGroupDao.load(jobInfo.getJobGroup()); // group info
@ -66,11 +67,12 @@ public class XxlJobTrigger {
ReturnT<String> triggerResult = new ReturnT<String>(null);
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(failStrategy.getTitle());
triggerMsgSb.append("<br>地址列表:").append(group.getRegistryList());
triggerMsgSb.append("<br>路由策略:").append(executorRouteStrategyEnum.getTitle()).append("("+i+"/"+addressList.size()+")"); // update01
// 3、trigger-valid
if (triggerResult.getCode()==ReturnT.SUCCESS_CODE && CollectionUtils.isEmpty(addressList)) {
@ -115,72 +117,73 @@ public class XxlJobTrigger {
logger.debug(">>>>>>>>>>> xxl-job trigger end, jobId:{}", jobLog.getId());
}
return;
}
// 1、save log-id
XxlJobLog jobLog = new XxlJobLog();
jobLog.setJobGroup(jobInfo.getJobGroup());
jobLog.setJobId(jobInfo.getId());
XxlJobDynamicScheduler.xxlJobLogDao.save(jobLog);
logger.debug(">>>>>>>>>>> xxl-job trigger start, jobId:{}", jobLog.getId());
// 2、prepare trigger-info
//jobLog.setExecutorAddress(executorAddress);
jobLog.setGlueType(jobInfo.getGlueType());
jobLog.setExecutorHandler(jobInfo.getExecutorHandler());
jobLog.setExecutorParam(jobInfo.getExecutorParam());
jobLog.setTriggerTime(new Date());
ReturnT<String> triggerResult = new ReturnT<String>(null);
StringBuffer triggerMsgSb = new StringBuffer();
triggerMsgSb.append("注册方式:").append( (group.getAddressType() == 0)?"自动注册":"手动录入" );
triggerMsgSb.append("<br>阻塞处理策略:").append(blockStrategy.getTitle());
triggerMsgSb.append("<br>失败处理策略:").append(failStrategy.getTitle());
triggerMsgSb.append("<br>地址列表:").append(group.getRegistryList());
triggerMsgSb.append("<br>路由策略:").append(executorRouteStrategyEnum.getTitle());
// 3、trigger-valid
if (triggerResult.getCode()==ReturnT.SUCCESS_CODE && CollectionUtils.isEmpty(addressList)) {
triggerResult.setCode(ReturnT.FAIL_CODE);
triggerMsgSb.append("<br>----------------------<br>").append("调度失败:").append("执行器地址为空");
}
} else {
// 1、save log-id
XxlJobLog jobLog = new XxlJobLog();
jobLog.setJobGroup(jobInfo.getJobGroup());
jobLog.setJobId(jobInfo.getId());
XxlJobDynamicScheduler.xxlJobLogDao.save(jobLog);
logger.debug(">>>>>>>>>>> xxl-job trigger start, jobId:{}", jobLog.getId());
// 2、prepare trigger-info
//jobLog.setExecutorAddress(executorAddress);
jobLog.setGlueType(jobInfo.getGlueType());
jobLog.setExecutorHandler(jobInfo.getExecutorHandler());
jobLog.setExecutorParam(jobInfo.getExecutorParam());
jobLog.setTriggerTime(new Date());
ReturnT<String> triggerResult = new ReturnT<String>(null);
StringBuffer triggerMsgSb = new StringBuffer();
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(failStrategy.getTitle());
// 3、trigger-valid
if (triggerResult.getCode()==ReturnT.SUCCESS_CODE && CollectionUtils.isEmpty(addressList)) {
triggerResult.setCode(ReturnT.FAIL_CODE);
triggerMsgSb.append("<br>----------------------<br>").append("调度失败:").append("执行器地址为空");
}
if (triggerResult.getCode() == ReturnT.SUCCESS_CODE) {
// 4.1、trigger-param
TriggerParam triggerParam = new TriggerParam();
triggerParam.setJobId(jobInfo.getId());
triggerParam.setExecutorHandler(jobInfo.getExecutorHandler());
triggerParam.setExecutorParams(jobInfo.getExecutorParam());
triggerParam.setExecutorBlockStrategy(jobInfo.getExecutorBlockStrategy());
triggerParam.setLogId(jobLog.getId());
triggerParam.setLogDateTim(jobLog.getTriggerTime().getTime());
triggerParam.setGlueType(jobInfo.getGlueType());
triggerParam.setGlueSource(jobInfo.getGlueSource());
triggerParam.setGlueUpdatetime(jobInfo.getGlueUpdatetime().getTime());
triggerParam.setBroadcastIndex(0);
triggerParam.setBroadcastTotal(1);
// 4.2、trigger-run (route run / trigger remote executor)
triggerResult = executorRouteStrategyEnum.getRouter().routeRun(triggerParam, addressList);
triggerMsgSb.append("<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>").append(triggerResult.getMsg());
// 4.3、trigger (fail retry)
if (triggerResult.getCode()!=ReturnT.SUCCESS_CODE && failStrategy == ExecutorFailStrategyEnum.FAIL_RETRY) {
if (triggerResult.getCode() == ReturnT.SUCCESS_CODE) {
// 4.1、trigger-param
TriggerParam triggerParam = new TriggerParam();
triggerParam.setJobId(jobInfo.getId());
triggerParam.setExecutorHandler(jobInfo.getExecutorHandler());
triggerParam.setExecutorParams(jobInfo.getExecutorParam());
triggerParam.setExecutorBlockStrategy(jobInfo.getExecutorBlockStrategy());
triggerParam.setLogId(jobLog.getId());
triggerParam.setLogDateTim(jobLog.getTriggerTime().getTime());
triggerParam.setGlueType(jobInfo.getGlueType());
triggerParam.setGlueSource(jobInfo.getGlueSource());
triggerParam.setGlueUpdatetime(jobInfo.getGlueUpdatetime().getTime());
triggerParam.setBroadcastIndex(0);
triggerParam.setBroadcastTotal(1);
// 4.2、trigger-run (route run / trigger remote executor)
triggerResult = executorRouteStrategyEnum.getRouter().routeRun(triggerParam, addressList);
triggerMsgSb.append("<br><br><span style=\"color:#F39C12;\" > >>>>>>>>>>>失败重试<<<<<<<<<<< </span><br>").append(triggerResult.getMsg());
triggerMsgSb.append("<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发调度<<<<<<<<<<< </span><br>").append(triggerResult.getMsg());
// 4.3、trigger (fail retry)
if (triggerResult.getCode()!=ReturnT.SUCCESS_CODE && failStrategy == ExecutorFailStrategyEnum.FAIL_RETRY) {
triggerResult = executorRouteStrategyEnum.getRouter().routeRun(triggerParam, addressList);
triggerMsgSb.append("<br><br><span style=\"color:#F39C12;\" > >>>>>>>>>>>调度失败重试<<<<<<<<<<< </span><br>").append(triggerResult.getMsg());
}
}
}
// 5、save trigger-info
jobLog.setExecutorAddress(triggerResult.getContent());
jobLog.setTriggerCode(triggerResult.getCode());
jobLog.setTriggerMsg(triggerMsgSb.toString());
XxlJobDynamicScheduler.xxlJobLogDao.updateTriggerInfo(jobLog);
// 5、save trigger-info
jobLog.setExecutorAddress(triggerResult.getContent());
jobLog.setTriggerCode(triggerResult.getCode());
jobLog.setTriggerMsg(triggerMsgSb.toString());
XxlJobDynamicScheduler.xxlJobLogDao.updateTriggerInfo(jobLog);
// 6、monitor triger
JobFailMonitorHelper.monitor(jobLog.getId());
logger.debug(">>>>>>>>>>> xxl-job trigger end, jobId:{}", jobLog.getId());
}
// 6、monitor triger
JobFailMonitorHelper.monitor(jobLog.getId());
logger.debug(">>>>>>>>>>> xxl-job trigger end, jobId:{}", jobLog.getId());
}
/**
@ -195,7 +198,7 @@ public class XxlJobTrigger {
ExecutorBiz executorBiz = XxlJobDynamicScheduler.getExecutorBiz(address);
runResult = executorBiz.run(triggerParam);
} catch (Exception e) {
logger.error(e.getMessage(), e);
logger.error(">>>>>>>>>>> xxl-job trigger error, please check if the executor[{}] is running.", address, e);
runResult = new ReturnT<String>(ReturnT.FAIL_CODE, ""+e );
}

@ -0,0 +1,44 @@
package com.xxl.job.admin.core.util;
import com.xxl.job.admin.core.model.XxlJobInfo;
import org.apache.commons.lang3.StringUtils;
/**
* job key util
*
* @author xuxueli 2017-12-22 18:48:45
*/
public class JobKeyUtil {
/**
* format job key
*
* @param xxlJobInfo
* @return
*/
public static String formatJobKey(XxlJobInfo xxlJobInfo){
return String.valueOf(xxlJobInfo.getJobGroup())
.concat("_").concat(String.valueOf(xxlJobInfo.getId()));
}
/**
* parse jobId from JobKey
*
* @param jobKey
* @return
*/
public static int parseJobId(String jobKey){
if (jobKey!=null && jobKey.trim().length()>0) {
String[] jobKeyArr = jobKey.split("_");
if (jobKeyArr.length == 2) {
String jobIdStr = jobKeyArr[1];
if (StringUtils.isNotBlank(jobIdStr) && StringUtils.isNumeric(jobIdStr)) {
int jobId = Integer.valueOf(jobIdStr);
return jobId;
}
}
}
return -1;
}
}

@ -1,21 +1,16 @@
package com.xxl.job.admin.core.util;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.mail.DefaultAuthenticator;
import org.apache.commons.mail.EmailException;
import org.apache.commons.mail.HtmlEmail;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.JavaMailSenderImpl;
import org.springframework.mail.javamail.MimeMessageHelper;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeUtility;
import java.io.File;
import java.util.Properties;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.nio.charset.Charset;
/**
* .Util
*
* @author xuxueli 2016-3-12 15:06:20
*/
public class MailUtil {
@ -25,156 +20,52 @@ public class MailUtil {
private static String port;
private static String username;
private static String password;
private static String sendFrom;
private static String sendNick;
static{
host = PropertiesUtil.getString("xxl.job.mail.host");
port = PropertiesUtil.getString("xxl.job.mail.port");
username = PropertiesUtil.getString("xxl.job.mail.username");
password = PropertiesUtil.getString("xxl.job.mail.password");
sendFrom = PropertiesUtil.getString("xxl.job.mail.sendFrom");
sendNick = PropertiesUtil.getString("xxl.job.mail.sendNick");
}
/**
<!-- spring mail sender -->
<bean id="javaMailSender" class="org.springframework.mail.javamail.JavaMailSenderImpl" scope="singleton" >
<property name="host" value="${mail.host}" /> <!-- SMTPIP -->
<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 sendFrom : 发送人邮箱
* //@param sendNick : 发送人昵称
* @param toAddress :
* @param mailSubject :
* @param mailBody :
* @param mailBodyIsHtml: ,true:HTML;false:
* @param attachments :
*
* @param toAddress
* @param mailSubject
* @param mailBody
* @return
*/
@SuppressWarnings("null")
public static boolean sendMailSpring(String toAddress, String mailSubject, String mailBody, boolean mailBodyIsHtml,File[] attachments) {
JavaMailSender javaMailSender = null;//ResourceBundle.getInstance().getJavaMailSender();
public static boolean sendMail(String toAddress, String mailSubject, String mailBody){
try {
MimeMessage mimeMessage = javaMailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, ArrayUtils.isNotEmpty(attachments), "UTF-8"); // 设置utf-8或GBK编码否则邮件会有乱码;multipart,true表示文件上传
helper.setFrom(sendFrom, sendNick);
helper.setTo(toAddress);
// Create the email message
HtmlEmail email = new HtmlEmail();
// 设置收件人抄送的名片和地址(相当于群发了)
//helper.setCc(InternetAddress.parse(MimeUtility.encodeText("邮箱001") + " <@163.com>," + MimeUtility.encodeText("邮箱002") + " <@foxmail.com>"));
//email.setDebug(true); // 将会打印一些log
//email.setTLS(true); // 是否TLS校验某些邮箱需要TLS安全校验同理有SSL校验
//email.setSSL(true);
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.info("{}", e);
}
return false;
}
/**
* () (JavaMail)
*
* @param toAddress :
* @param mailSubject :
* @param mailBody :
* @param mailBodyIsHtml: ,true:HTML;false:
* //@param inLineFile : 内嵌文件
* @param attachments :
*/
public static boolean sendMail (String toAddress, String mailSubject, String mailBody,
boolean mailBodyIsHtml, File[] attachments){
try {
// 创建邮件发送类 JavaMailSender (用于发送多元化邮件包括附件图片html 等 )
JavaMailSenderImpl mailSender = new JavaMailSenderImpl();
mailSender.setHost(host); // 设置邮件服务主机
mailSender.setUsername(username); // 发送者邮箱的用户名
mailSender.setPassword(password); // 发送者邮箱的密码
//配置文件用于实例化java.mail.session
Properties pro = new Properties();
pro.put("mail.smtp.auth", "true"); // 登录SMTP服务器,需要获得授权 (网易163邮箱新近注册的邮箱均不能授权,测试 sohu 的邮箱可以获得授权)
pro.put("mail.smtp.socketFactory.port", port);
pro.put("mail.smtp.socketFactory.fallback", "false");
mailSender.setJavaMailProperties(pro);
//创建多元化邮件 (创建 mimeMessage 帮助类,用于封装信息至 mimeMessage)
MimeMessage mimeMessage = mailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, ArrayUtils.isNotEmpty(attachments), "UTF-8");
helper.setFrom(sendFrom, sendNick);
helper.setTo(toAddress);
email.setHostName(host);
email.setSmtpPort(Integer.valueOf(port));
//email.setSslSmtpPort(port);
email.setAuthenticator(new DefaultAuthenticator(username, password));
email.setCharset(Charset.defaultCharset().name());
email.setFrom(username, sendNick);
email.addTo(toAddress);
email.setSubject(mailSubject);
email.setMsg(mailBody);
helper.setSubject(mailSubject);
helper.setText(mailBody, mailBodyIsHtml);
// 添加内嵌文件第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);
}
}
mailSender.send(mimeMessage);
//email.attach(attachment); // add the attachment
email.send(); // send the email
return true;
} catch (Exception e) {
} catch (EmailException e) {
logger.error(e.getMessage(), e);
}
return false;
}
static int total = 0;
public static void main(String[] args) {
ExecutorService exec = Executors.newCachedThreadPool();
for (int i = 0; i < 20; i++) {
exec.execute(new Thread(new Runnable() {
@Override
public void run() {
while(total < 10){
String mailBody = "<html><head><meta http-equiv="
+ "Content-Type"
+ " content="
+ "text/html; charset=gb2312"
+ "></head><body><h1>新书快递通知</h1>你的新书快递申请已推送新书,请到<a href=''>空间"
+ "</a>中查看</body></html>";
sendMail("ovono802302@163.com", "测试邮件", mailBody, false, null);
System.out.println(total);
total++;
}
}
}));
}
}
}

@ -12,6 +12,7 @@ import java.util.Properties;
/**
* properties util
*
* @author xuxueli 2015-8-28 10:35:53
*/
public class PropertiesUtil {
@ -33,9 +34,5 @@ public class PropertiesUtil {
}
return null;
}
public static void main(String[] args) {
System.out.println(getString("xxl.job.login.username"));
}
}

@ -4,6 +4,7 @@ package com.xxl.job.admin.service;
import com.xxl.job.admin.core.model.XxlJobInfo;
import com.xxl.job.core.biz.model.ReturnT;
import java.util.Date;
import java.util.Map;
/**
@ -29,6 +30,6 @@ public interface XxlJobService {
public Map<String,Object> dashboardInfo();
public ReturnT<Map<String,Object>> triggerChartDate();
public ReturnT<Map<String,Object>> triggerChartDate(Date startDate, Date endDate);
}

@ -5,6 +5,7 @@ import com.xxl.job.admin.core.model.XxlJobInfo;
import com.xxl.job.admin.core.model.XxlJobLog;
import com.xxl.job.admin.core.schedule.XxlJobDynamicScheduler;
import com.xxl.job.admin.core.trigger.XxlJobTrigger;
import com.xxl.job.admin.core.util.JobKeyUtil;
import com.xxl.job.admin.dao.XxlJobInfoDao;
import com.xxl.job.admin.dao.XxlJobLogDao;
import com.xxl.job.admin.dao.XxlJobRegistryDao;
@ -13,7 +14,8 @@ import com.xxl.job.core.biz.AdminBiz;
import com.xxl.job.core.biz.model.HandleCallbackParam;
import com.xxl.job.core.biz.model.RegistryParam;
import com.xxl.job.core.biz.model.ReturnT;
import org.apache.commons.lang.StringUtils;
import com.xxl.job.core.handler.IJobHandler;
import org.apache.commons.lang3.StringUtils;
import org.quartz.SchedulerException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -45,8 +47,8 @@ public class AdminBizImpl implements AdminBiz {
public ReturnT<String> callback(List<HandleCallbackParam> callbackParamList) {
for (HandleCallbackParam handleCallbackParam: callbackParamList) {
ReturnT<String> callbackResult = callback(handleCallbackParam);
logger.info("JobApiController.callback {}, handleCallbackParam={}, callbackResult={}",
(callbackResult.getCode()==ReturnT.SUCCESS_CODE?"success":"fail"), handleCallbackParam, callbackResult);
logger.info(">>>>>>>>> JobApiController.callback {}, handleCallbackParam={}, callbackResult={}",
(callbackResult.getCode()==IJobHandler.SUCCESS.getCode()?"success":"fail"), handleCallbackParam, callbackResult);
}
return ReturnT.SUCCESS;
@ -58,28 +60,39 @@ public class AdminBizImpl implements AdminBiz {
if (log == null) {
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
String childTriggerMsg = null;
if (ReturnT.SUCCESS_CODE==handleCallbackParam.getExecuteResult().getCode() && ReturnT.SUCCESS_CODE!=log.getHandleCode()) {
// trigger success, to trigger child job
String callbackMsg = null;
if (IJobHandler.SUCCESS.getCode() == handleCallbackParam.getExecuteResult().getCode()) {
XxlJobInfo xxlJobInfo = xxlJobInfoDao.loadById(log.getJobId());
if (xxlJobInfo!=null && StringUtils.isNotBlank(xxlJobInfo.getChildJobKey())) {
childTriggerMsg = "<hr>";
callbackMsg = "<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>触发子任务<<<<<<<<<<< </span><br>";
String[] childJobKeys = xxlJobInfo.getChildJobKey().split(",");
for (int i = 0; i < childJobKeys.length; i++) {
String[] jobKeyArr = childJobKeys[i].split("_");
if (jobKeyArr!=null && jobKeyArr.length==2) {
ReturnT<String> triggerChildResult = xxlJobService.triggerJob(Integer.valueOf(jobKeyArr[1]));
int childJobId = JobKeyUtil.parseJobId(childJobKeys[i]);
if (childJobId > 0) {
ReturnT<String> triggerChildResult = xxlJobService.triggerJob(childJobId);
// add msg
childTriggerMsg += MessageFormat.format("<br> {0}/{1} 触发子任务{2}, 子任务Key: {3}, 子任务触发备注: {4}",
(i+1), childJobKeys.length, (triggerChildResult.getCode()==ReturnT.SUCCESS_CODE?"成功":"失败"), childJobKeys[i], triggerChildResult.getMsg());
callbackMsg += MessageFormat.format("{0}/{1} [JobKey={2}], 触发{3}, 触发备注: {4} <br>",
(i+1), childJobKeys.length, childJobKeys[i], (triggerChildResult.getCode()==ReturnT.SUCCESS_CODE?"成功":"失败"), triggerChildResult.getMsg());
} else {
childTriggerMsg += MessageFormat.format("<br> {0}/{1} 触发子任务失败, 子任务Key格式错误, 子任务Key: {2}",
callbackMsg += MessageFormat.format(" {0}/{1} [JobKey={2}], 触发失败, 触发备注: JobKey格式错误 <br>",
(i+1), childJobKeys.length, childJobKeys[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
@ -90,8 +103,8 @@ public class AdminBizImpl implements AdminBiz {
if (handleCallbackParam.getExecuteResult().getMsg() != null) {
handleMsg.append(handleCallbackParam.getExecuteResult().getMsg());
}
if (childTriggerMsg !=null) {
handleMsg.append("<br>子任务触发备注:").append(childTriggerMsg);
if (callbackMsg != null) {
handleMsg.append(callbackMsg);
}
// success, save log

@ -5,6 +5,7 @@ import com.xxl.job.admin.core.model.XxlJobGroup;
import com.xxl.job.admin.core.model.XxlJobInfo;
import com.xxl.job.admin.core.route.ExecutorRouteStrategyEnum;
import com.xxl.job.admin.core.schedule.XxlJobDynamicScheduler;
import com.xxl.job.admin.core.util.JobKeyUtil;
import com.xxl.job.admin.dao.XxlJobGroupDao;
import com.xxl.job.admin.dao.XxlJobInfoDao;
import com.xxl.job.admin.dao.XxlJobLogDao;
@ -13,10 +14,10 @@ import com.xxl.job.admin.service.XxlJobService;
import com.xxl.job.core.biz.model.ReturnT;
import com.xxl.job.core.enums.ExecutorBlockStrategyEnum;
import com.xxl.job.core.glue.GlueTypeEnum;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.time.DateUtils;
import org.apache.commons.lang.time.FastDateFormat;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateUtils;
import org.apache.commons.lang3.time.FastDateFormat;
import org.quartz.CronExpression;
import org.quartz.SchedulerException;
import org.slf4j.Logger;
@ -107,11 +108,11 @@ public class XxlJobServiceImpl implements XxlJobService {
if (StringUtils.isNotBlank(jobInfo.getChildJobKey())) {
String[] childJobKeys = jobInfo.getChildJobKey().split(",");
for (String childJobKeyItem: childJobKeys) {
String[] childJobKeyArr = childJobKeyItem.split("_");
if (childJobKeyArr.length!=2) {
int childJobId = JobKeyUtil.parseJobId(childJobKeyItem);
if (childJobId <= 0) {
return new ReturnT<String>(ReturnT.FAIL_CODE, MessageFormat.format("子任务Key({0})格式错误", childJobKeyItem));
}
XxlJobInfo childJobInfo = xxlJobInfoDao.loadById(Integer.valueOf(childJobKeyArr[1]));
XxlJobInfo childJobInfo = xxlJobInfoDao.loadById(childJobId);
if (childJobInfo==null) {
return new ReturnT<String>(ReturnT.FAIL_CODE, MessageFormat.format("子任务Key({0})无效", childJobKeyItem));
}
@ -170,11 +171,11 @@ public class XxlJobServiceImpl implements XxlJobService {
if (StringUtils.isNotBlank(jobInfo.getChildJobKey())) {
String[] childJobKeys = jobInfo.getChildJobKey().split(",");
for (String childJobKeyItem: childJobKeys) {
String[] childJobKeyArr = childJobKeyItem.split("_");
if (childJobKeyArr.length!=2) {
int childJobId = JobKeyUtil.parseJobId(childJobKeyItem);
if (childJobId <= 0) {
return new ReturnT<String>(ReturnT.FAIL_CODE, MessageFormat.format("子任务Key({0})格式错误", childJobKeyItem));
}
XxlJobInfo childJobInfo = xxlJobInfoDao.loadById(Integer.valueOf(childJobKeyArr[1]));
XxlJobInfo childJobInfo = xxlJobInfoDao.loadById(childJobId);
if (childJobInfo==null) {
return new ReturnT<String>(ReturnT.FAIL_CODE, MessageFormat.format("子任务Key({0})无效", childJobKeyItem));
}
@ -310,18 +311,15 @@ public class XxlJobServiceImpl implements XxlJobService {
}
@Override
public ReturnT<Map<String, Object>> triggerChartDate() {
Date from = DateUtils.addDays(new Date(), -30);
Date to = new Date();
public ReturnT<Map<String, Object>> triggerChartDate(Date startDate, Date endDate) {
List<String> triggerDayList = new ArrayList<String>();
List<Integer> triggerDayCountSucList = new ArrayList<Integer>();
List<Integer> triggerDayCountFailList = new ArrayList<Integer>();
int triggerCountSucTotal = 0;
int triggerCountFailTotal = 0;
List<Map<String, Object>> triggerCountMapAll = xxlJobLogDao.triggerCountByDay(from, to, -1);
List<Map<String, Object>> triggerCountMapSuc = xxlJobLogDao.triggerCountByDay(from, to, ReturnT.SUCCESS_CODE);
List<Map<String, Object>> triggerCountMapAll = xxlJobLogDao.triggerCountByDay(startDate, endDate, -1);
List<Map<String, Object>> triggerCountMapSuc = xxlJobLogDao.triggerCountByDay(startDate, endDate, ReturnT.SUCCESS_CODE);
if (CollectionUtils.isNotEmpty(triggerCountMapAll)) {
for (Map<String, Object> item: triggerCountMapAll) {
String day = String.valueOf(item.get("triggerDay"));

@ -1,10 +0,0 @@
log4j.rootLogger=info,console,logFile
log4j.appender.console=org.apache.log4j.ConsoleAppender
log4j.appender.console.layout=org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=%d - xxl-job-admin - %p [%c] - <%m>%n
log4j.appender.logFile=org.apache.log4j.DailyRollingFileAppender
log4j.appender.logFile.File=/data/applogs/xxl-job/xxl-job-admin.log
log4j.appender.logFile.layout=org.apache.log4j.PatternLayout
log4j.appender.logFile.layout.ConversionPattern=%d - xxl-job-admin - %p [%c] - <%m>%n

@ -0,0 +1,27 @@
<?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-admin [%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-admin.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-admin [%c]-[%t]-[%M]-[%L]-[%p] %m%n"/>
</layout>
</appender>
<root>
<level value="INFO" />
<appender-ref ref="CONSOLE" />
<appender-ref ref="FILE" />
</root>
</log4j:configuration>

@ -3,9 +3,9 @@
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
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/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" />

@ -3,9 +3,9 @@
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:tx="http://www.springframework.org/schema/tx"
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/spring-tx-3.0.xsd">
http://www.springframework.org/schema/tx/spring-tx.xsd">
<bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">

@ -4,11 +4,11 @@
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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/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/spring-mvc-3.0.xsd">
http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<mvc:annotation-driven />
<context:component-scan base-package="com.xxl.job.admin.controller" />

@ -9,7 +9,6 @@ xxl.job.mail.host=smtp.163.com
xxl.job.mail.port=25
xxl.job.mail.username=ovono802302@163.com
xxl.job.mail.password=asdfzxcv
xxl.job.mail.sendFrom=ovono802302@163.com
xxl.job.mail.sendNick=《任务调度平台XXL-JOB》
### xxl-job login

@ -175,7 +175,7 @@
<#macro commonFooter >
<footer class="main-footer">
Powered by <b>XXL-JOB</b> 1.8.2
Powered by <b>XXL-JOB</b> 1.9.0
<div class="pull-right hidden-xs">
<strong>Copyright &copy; 2015-${.now?string('yyyy')} &nbsp;
<a href="http://www.xuxueli.com/" target="_blank" >xuxueli</a>

@ -4,6 +4,8 @@
<title></title>
<#import "/common/common.macro.ftl" as netCommon>
<@netCommon.commonStyle />
<!-- daterangepicker -->
<link rel="stylesheet" href="${request.contextPath}/static/adminlte/plugins/daterangepicker/daterangepicker.css">
</head>
<body class="hold-transition skin-blue sidebar-mini <#if cookieMap?exists && "off" == cookieMap["xxljob_adminlte_settings"].value >sidebar-collapse</#if> ">
<div class="wrapper">
@ -43,7 +45,7 @@
<div class="progress">
<div class="progress-bar" style="width: 100%"></div>
</div>
<span class="progress-description"></span>
<span class="progress-description"></span>
</div>
</div>
</div>
@ -82,7 +84,7 @@
<div class="progress">
<div class="progress-bar" style="width: 100%"></div>
</div>
<span class="progress-description"></span>
<span class="progress-description">线</span>
</div>
</div>
</div>
@ -94,8 +96,20 @@
<div class="col-md-12">
<div class="box">
<div class="box-header with-border">
<h3 class="box-title"></h3>
<h3 class="box-title"></h3>
<#--<input type="text" class="form-control" id="filterTime" readonly >-->
<!-- tools box -->
<div class="pull-right box-tools">
<button type="button" class="btn btn-primary btn-sm daterange pull-right" data-toggle="tooltip" id="filterTime" >
<i class="fa fa-calendar"></i>
</button>
<#--<button type="button" class="btn btn-primary btn-sm pull-right" data-widget="collapse" data-toggle="tooltip" title="" style="margin-right: 5px;" data-original-title="Collapse">
<i class="fa fa-minus"></i>
</button>-->
</div>
<!-- /. tools -->
</div>
<div class="box-body">
<div class="row">
@ -113,7 +127,6 @@
</div>
</div>
</section>
<!-- /.content -->
</div>
@ -123,10 +136,11 @@
<@netCommon.commonFooter />
</div>
<@netCommon.commonScript />
<#--<script src="${request.contextPath}/static/adminlte/plugins/daterangepicker/moment.min.js"></script>
<script src="${request.contextPath}/static/adminlte/plugins/daterangepicker/daterangepicker.js"></script>-->
<!-- daterangepicker -->
<script src="${request.contextPath}/static/adminlte/plugins/daterangepicker/moment.min.js"></script>
<script src="${request.contextPath}/static/adminlte/plugins/daterangepicker/daterangepicker.js"></script>
<#-- echarts -->
<script src="${request.contextPath}/static/plugins/echarts/echarts.common.min.js"></script>
<script src="${request.contextPath}/static/js/index.js"></script>
</body>
</html>

@ -109,15 +109,34 @@
</div>
<@netCommon.commonScript />
<#assign glueTypeModeSrc = "${request.contextPath}/static/plugins/codemirror/mode/clike/clike.js" />
<#assign glueTypeIdeMode = "text/x-java" />
<#if jobInfo.glueType == "GLUE_GROOVY" >
<#assign glueTypeModeSrc = "${request.contextPath}/static/plugins/codemirror/mode/clike/clike.js" />
<#assign glueTypeIdeMode = "text/x-java" />
<#elseif jobInfo.glueType == "GLUE_SHELL" >
<#assign glueTypeModeSrc = "${request.contextPath}/static/plugins/codemirror/mode/shell/shell.js" />
<#assign glueTypeIdeMode = "text/x-sh" />
<#elseif jobInfo.glueType == "GLUE_PYTHON" >
<#assign glueTypeModeSrc = "${request.contextPath}/static/plugins/codemirror/mode/python/python.js" />
<#assign glueTypeIdeMode = "text/x-python" />
<#elseif jobInfo.glueType == "GLUE_NODEJS" >
<#assign glueTypeModeSrc = "${request.contextPath}/static/plugins/codemirror/mode/javascript/javascript.js" />
<#assign glueTypeIdeMode = "text/javascript" />
</#if>
<script src="${request.contextPath}/static/plugins/codemirror/lib/codemirror.js"></script>
<script src="${request.contextPath}/static/plugins/codemirror/mode/clike/clike.js"></script>
<script src="${request.contextPath}/static/plugins/codemirror/mode/shell/shell.js"></script>
<script src="${request.contextPath}/static/plugins/codemirror/mode/python/python.js"></script>
<script src="${glueTypeModeSrc}"></script>
<script src="${request.contextPath}/static/plugins/codemirror/addon/hint/show-hint.js"></script>
<script src="${request.contextPath}/static/plugins/codemirror/addon/hint/anyword-hint.js"></script>
<script>
var id = '${jobInfo.id}';
var glueType = '${jobInfo.glueType}';
var ideMode = '${glueTypeIdeMode}';
</script>
<script src="${request.contextPath}/static/js/jobcode.index.1.js"></script>

@ -53,7 +53,20 @@
<td>${group.appName}</td>
<td>${group.title}</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>
<button class="btn btn-warning btn-xs update"
id="${group.id}"
@ -107,7 +120,9 @@
</div>
<div class="form-group">
<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>
<hr>
<div class="form-group">
@ -153,7 +168,9 @@
</div>
<div class="form-group">
<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>
<hr>
<div class="form-group">

@ -63,7 +63,7 @@
<h3 class="box-title"></h3>
</div>
<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>
<tr>
<th name="id" >id</th>
@ -127,7 +127,7 @@
</select>
</div>
<label for="lastname" class="col-sm-2 control-label">Cron<font color="red">*</font></label>
<div class="col-sm-4"><input type="text" class="form-control" name="jobCron" placeholder="请输入“Cron”" maxlength="20" ></div>
<div class="col-sm-4"><input type="text" class="form-control" name="jobCron" placeholder="请输入“Cron”" maxlength="128" ></div>
</div>
<div class="form-group">
<label for="firstname" class="col-sm-2 control-label"><font color="red">*</font></label>
@ -143,7 +143,7 @@
</div>
<div class="form-group">
<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>
<div class="col-sm-4"><input type="text" class="form-control" name="childJobKey" placeholder="请输入子任务的任务Key,如存在多个逗号分隔" maxlength="100" ></div>
</div>
@ -192,7 +192,7 @@ import com.xxl.job.core.handler.IJobHandler;
public class DemoGlueJobHandler extends IJobHandler {
@Override
public ReturnT<String> execute(String... params) throws Exception {
public ReturnT<String> execute(String param) throws Exception {
XxlJobLogger.log("XXL-JOB, Hello World.");
return ReturnT.SUCCESS;
}
@ -204,12 +204,15 @@ public class DemoGlueJobHandler extends IJobHandler {
echo "xxl-job: hello shell"
echo "脚本位置:$0"
echo "参数数量:$#"
echo "任务参数:$1"
echo "分片序号 = $2"
echo "分片总数 = $3"
<#--echo "参数数量:$#"
for param in $*
do
echo "参数 : $param"
sleep 1s
done
done-->
echo "Good bye!"
exit 0
@ -221,19 +224,40 @@ import time
import sys
print "xxl-job: hello python"
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)
print "参数", i, sys.argv[i]
print "参数", i, sys.argv[i]-->
print "Good bye!"
exit(0)<#--
exit(0)
<#--
import logging
logging.basicConfig(level=logging.DEBUG)
logging.info("脚本文件:" + sys.argv[0])
-->
</textarea>
<textarea class="glueSource_nodejs" style="display:none;" >
#!/usr/bin/env node
console.log("xxl-job: hello nodejs")
var arguments = process.argv
console.log("脚本文件: " + arguments[1])
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("Good bye!")
process.exit(0)
</textarea>
</form>
</div>
</div>
@ -271,7 +295,7 @@ logging.info("脚本文件:" + sys.argv[0])
</select>
</div>
<label for="lastname" class="col-sm-2 control-label">Cron<font color="red">*</font></label>
<div class="col-sm-4"><input type="text" class="form-control" name="jobCron" placeholder="请输入“Cron”" maxlength="20" ></div>
<div class="col-sm-4"><input type="text" class="form-control" name="jobCron" placeholder="请输入“Cron”" maxlength="128" ></div>
</div>
<div class="form-group">
<label for="firstname" class="col-sm-2 control-label"><font color="red">*</font></label>
@ -287,7 +311,7 @@ logging.info("脚本文件:" + sys.argv[0])
</div>
<div class="form-group">
<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>
<div class="col-sm-4"><input type="text" class="form-control" name="childJobKey" placeholder="请输入子任务的任务Key,如存在多个逗号分隔" maxlength="100" ></div>
</div>

@ -7,14 +7,18 @@ $(function(){
$.post(base_url + "/logout", function(data, status) {
if (data.code == "200") {
layer.open({
layer.msg('');
setTimeout(function(){
window.location.href = base_url + "/";
}, 500);
/*layer.open({
title: '',
content: '',
icon: '1',
end: function(layero, index){
window.location.href = base_url + "/";
}
});
});*/
} else {
layer.open({
title: '',

@ -5,29 +5,74 @@
$(function () {
// 过滤时间
var _startDate = moment().subtract(1, 'months'); // 默认,最近一月
var _endDate = moment();
$('#filterTime').daterangepicker({
autoApply:false,
singleDatePicker:false,
showDropdowns:false, // 是否显示年月选择条件
timePicker: true, // 是否显示小时和分钟选择条件
timePickerIncrement: 10, // 时间的增量,单位为分钟
timePicker24Hour : true,
opens : 'left', //日期选择框的弹出位置
ranges: {
//'最近1小时': [moment().subtract(1, 'hours'), moment()],
'': [moment().startOf('day'), moment().endOf('day')],
'': [moment().subtract(1, 'days').startOf('day'), moment().subtract(1, 'days').endOf('day')],
'': [moment().startOf('month'), moment().endOf('month')],
'': [moment().subtract(1, 'months').startOf('month'), moment().subtract(1, 'months').endOf('month')],
'1': [moment().subtract(1, 'weeks'), moment()],
'1': [_startDate, _endDate]
},
locale : {
format: 'YYYY-MM-DD HH:mm:ss',
separator : ' - ',
customRangeLabel : '',
applyLabel : '',
cancelLabel : '',
fromLabel : '',
toLabel : '',
daysOfWeek : [ '日', '一', '二', '三', '四', '五', '六' ],
monthNames : [ '', '', '', '', '', '', '', '', '', '', '', '' ],
firstDay : 1
},
startDate:_startDate,
endDate: _endDate
}, function (start, end, label) {
freshChartDate(start, end);
});
freshChartDate(_startDate, _endDate);
/**
*
*
* @param startDate
* @param endDate
*/
$.ajax({
type : 'POST',
url : base_url + '/triggerChartDate',
data : { },
dataType : "json",
success : function(data){
if (data.code == 200) {
lineChartInit(data)
pieChartInit(data);
} else {
layer.open({
title: '',
content: (data.msg || ''),
icon: '2'
});
function freshChartDate(startDate, endDate) {
$.ajax({
type : 'POST',
url : base_url + '/triggerChartDate',
data : {
'startDate':startDate.format('YYYY-MM-DD HH:mm:ss'),
'endDate':endDate.format('YYYY-MM-DD HH:mm:ss')
},
dataType : "json",
success : function(data){
if (data.code == 200) {
lineChartInit(data)
pieChartInit(data);
} else {
layer.open({
title: '',
content: (data.msg || ''),
icon: '2'
});
}
}
}
});
});
}
/**
* 线
@ -151,38 +196,4 @@ $(function () {
pieChart.setOption(option);
}
// 过滤时间
/*$('#filterTime').daterangepicker({
autoApply:false,
singleDatePicker:false,
showDropdowns:false, // 是否显示年月选择条件
timePicker: true, // 是否显示小时和分钟选择条件
timePickerIncrement: 10, // 时间的增量,单位为分钟
timePicker24Hour : true,
opens : 'left', //日期选择框的弹出位置
ranges: {
'1': [moment().subtract(1, 'hours'), moment()],
'': [moment().startOf('day'), moment().endOf('day')],
'': [moment().subtract(1, 'days').startOf('day'), moment().subtract(1, 'days').endOf('day')],
'7': [moment().subtract(6, 'days'), moment()],
'30': [moment().subtract(29, 'days'), moment()],
'': [moment().startOf('month'), moment().endOf('month')],
'': [moment().subtract(1, 'month').startOf('month'), moment().subtract(1, 'month').endOf('month')]
},
locale : {
format: 'YYYY-MM-DD HH:mm:ss',
separator : ' - ',
customRangeLabel : '',
applyLabel : '',
cancelLabel : '',
fromLabel : '',
toLabel : '',
daysOfWeek : [ '日', '一', '二', '三', '四', '五', '六' ],
monthNames : [ '', '', '', '', '', '', '', '', '', '', '', '' ],
firstDay : 1,
startDate: moment().startOf('day'),
endDate: moment().endOf('day')
}
});*/
});

@ -8,34 +8,28 @@ $(function() {
});*/
var codeEditor;
function initIde(glueType, glueSource) {
var ideMode = "text/x-java";
if ('GLUE_GROOVY'==glueType){
ideMode = "text/x-java";
} else if ('GLUE_SHELL'==glueType){
ideMode = "text/x-sh";
} else if ('GLUE_PYTHON'==glueType){
ideMode = "text/x-python";
function initIde(glueSource) {
if (codeEditor == null) {
codeEditor = CodeMirror(document.getElementById("ideWindow"), {
mode : ideMode,
lineNumbers : true,
matchBrackets : true,
value: glueSource
});
} else {
codeEditor.setValue(glueSource);
}
codeEditor = CodeMirror(document.getElementById("ideWindow"), {
mode : ideMode,
lineNumbers : true,
matchBrackets : true,
value: glueSource
});
}
initIde(glueType, $("#version_now").val());
initIde($("#version_now").val());
// code change
$(".source_version").click(function(){
var glueType = $(this).attr('glueType');
var sourceId = $(this).attr('version');
var temp = $( "#" + sourceId ).val();
codeEditor.setValue('');
initIde(glueType, temp);
//codeEditor.setValue('');
initIde(temp);
});
// code source save

@ -122,11 +122,13 @@ $(function() {
// 注册方式,切换
$("#addModal input[name=addressType], #updateModal input[name=addressType]").click(function(){
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) {
$addressList.css("background-color", "#eee"); // 自动注册
$addressList.attr("readonly","readonly");
$addressList.val("");
$addressList.attr("readonly","readonly");
} else {
$addressList.css("background-color", "white");
$addressList.removeAttr("readonly");
}
});
@ -144,7 +146,7 @@ $(function() {
//$("#updateModal .form input[name='addressType'][value='"+ addressType +"']").attr('checked', 'true');
$("#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');
});

@ -55,6 +55,8 @@ $(function() {
return "GLUE模式(Shell)";
} else if ('GLUE_PYTHON'==row.glueType) {
return "GLUE模式(Python)";
}else if ('GLUE_NODEJS'==row.glueType){
return "GLUE模式(Nodejs)";
} else if ('BEAN'==row.glueType) {
return "BEAN模式" + row.executorHandler;
}
@ -341,6 +343,8 @@ $(function() {
$("#addModal .form textarea[name='glueSource']").val( $("#addModal .form .glueSource_shell").val() );
} else if ('GLUE_PYTHON'==glueType){
$("#addModal .form textarea[name='glueSource']").val( $("#addModal .form .glueSource_python").val() );
} else if ('GLUE_NODEJS'==glueType){
$("#addModal .form textarea[name='glueSource']").val( $("#addModal .form .glueSource_nodejs").val() );
}
});

@ -105,6 +105,8 @@ $(function() {
glueTypeTitle = "GLUE模式(Shell)";
} else if ('GLUE_PYTHON'==row.glueType) {
glueTypeTitle = "GLUE模式(Python)";
}else if ('GLUE_NODEJS'==row.glueType) {
glueTypeTitle = "GLUE模式(Nodejs)";
} else if ('BEAN'==row.glueType) {
glueTypeTitle = "BEAN模式" + row.executorHandler;
}
@ -144,9 +146,16 @@ $(function() {
{
"data": 'triggerCode',
"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',
@ -163,7 +172,17 @@ $(function() {
{
"data": 'handleCode',
"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(){
submitHandler : function(form) {
$.post(base_url + "/login", $("#loginForm").serialize(), function(data, status) {
if (data.code == "200") {
layer.open({
layer.msg('');
setTimeout(function(){
window.location.href = base_url;
}, 500);
/*layer.open({
title: '',
content: '',
icon: '1',
end: function(layero, index){
window.location.href = base_url;
}
});
});*/
} else {
layer.open({
title: '',

@ -0,0 +1,805 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: http://codemirror.net/LICENSE
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"));
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
"use strict";
function expressionAllowed(stream, state, backUp) {
return /^(?:operator|sof|keyword c|case|new|export|default|[\[{}\(,;:]|=>)$/.test(state.lastType) ||
(state.lastType == "quasi" && /\{\s*$/.test(stream.string.slice(0, stream.pos - (backUp || 0))))
}
CodeMirror.defineMode("javascript", function(config, parserConfig) {
var indentUnit = config.indentUnit;
var statementIndent = parserConfig.statementIndent;
var jsonldMode = parserConfig.jsonld;
var jsonMode = parserConfig.json || jsonldMode;
var isTS = parserConfig.typescript;
var wordRE = parserConfig.wordCharacters || /[\w$\xa1-\uffff]/;
// Tokenizer
var keywords = function(){
function kw(type) {return {type: type, style: "keyword"};}
var A = kw("keyword a"), B = kw("keyword b"), C = kw("keyword c");
var operator = kw("operator"), atom = {type: "atom", style: "atom"};
var jsKeywords = {
"if": kw("if"), "while": A, "with": A, "else": B, "do": B, "try": B, "finally": B,
"return": C, "break": C, "continue": C, "new": kw("new"), "delete": C, "throw": C, "debugger": C,
"var": kw("var"), "const": kw("var"), "let": kw("var"),
"function": kw("function"), "catch": kw("catch"),
"for": kw("for"), "switch": kw("switch"), "case": kw("case"), "default": kw("default"),
"in": operator, "typeof": operator, "instanceof": operator,
"true": atom, "false": atom, "null": atom, "undefined": atom, "NaN": atom, "Infinity": atom,
"this": kw("this"), "class": kw("class"), "super": kw("atom"),
"yield": C, "export": kw("export"), "import": kw("import"), "extends": C,
"await": C, "async": kw("async")
};
// Extend the 'normal' keywords with the TypeScript language extensions
if (isTS) {
var type = {type: "variable", style: "variable-3"};
var tsKeywords = {
// object-like things
"interface": kw("class"),
"implements": C,
"namespace": C,
"module": kw("module"),
"enum": kw("module"),
"type": kw("type"),
// scope modifiers
"public": kw("modifier"),
"private": kw("modifier"),
"protected": kw("modifier"),
"abstract": kw("modifier"),
// operators
"as": operator,
// types
"string": type, "number": type, "boolean": type, "any": type
};
for (var attr in tsKeywords) {
jsKeywords[attr] = tsKeywords[attr];
}
}
return jsKeywords;
}();
var isOperatorChar = /[+\-*&%=<>!?|~^@]/;
var isJsonldKeyword = /^@(context|id|value|language|type|container|list|set|reverse|index|base|vocab|graph)"/;
function readRegexp(stream) {
var escaped = false, next, inSet = false;
while ((next = stream.next()) != null) {
if (!escaped) {
if (next == "/" && !inSet) return;
if (next == "[") inSet = true;
else if (inSet && next == "]") inSet = false;
}
escaped = !escaped && next == "\\";
}
}
// Used as scratch variables to communicate multiple values without
// consing up tons of objects.
var type, content;
function ret(tp, style, cont) {
type = tp; content = cont;
return style;
}
function tokenBase(stream, state) {
var ch = stream.next();
if (ch == '"' || ch == "'") {
state.tokenize = tokenString(ch);
return state.tokenize(stream, state);
} else if (ch == "." && stream.match(/^\d+(?:[eE][+\-]?\d+)?/)) {
return ret("number", "number");
} else if (ch == "." && stream.match("..")) {
return ret("spread", "meta");
} else if (/[\[\]{}\(\),;\:\.]/.test(ch)) {
return ret(ch);
} else if (ch == "=" && stream.eat(">")) {
return ret("=>", "operator");
} else if (ch == "0" && stream.eat(/x/i)) {
stream.eatWhile(/[\da-f]/i);
return ret("number", "number");
} else if (ch == "0" && stream.eat(/o/i)) {
stream.eatWhile(/[0-7]/i);
return ret("number", "number");
} else if (ch == "0" && stream.eat(/b/i)) {
stream.eatWhile(/[01]/i);
return ret("number", "number");
} else if (/\d/.test(ch)) {
stream.match(/^\d*(?:\.\d*)?(?:[eE][+\-]?\d+)?/);
return ret("number", "number");
} else if (ch == "/") {
if (stream.eat("*")) {
state.tokenize = tokenComment;
return tokenComment(stream, state);
} else if (stream.eat("/")) {
stream.skipToEnd();
return ret("comment", "comment");
} else if (expressionAllowed(stream, state, 1)) {
readRegexp(stream);
stream.match(/^\b(([gimyu])(?![gimyu]*\2))+\b/);
return ret("regexp", "string-2");
} else {
stream.eatWhile(isOperatorChar);
return ret("operator", "operator", stream.current());
}
} else if (ch == "`") {
state.tokenize = tokenQuasi;
return tokenQuasi(stream, state);
} else if (ch == "#") {
stream.skipToEnd();
return ret("error", "error");
} else if (isOperatorChar.test(ch)) {
if (ch != ">" || !state.lexical || state.lexical.type != ">")
stream.eatWhile(isOperatorChar);
return ret("operator", "operator", stream.current());
} else if (wordRE.test(ch)) {
stream.eatWhile(wordRE);
var word = stream.current(), known = keywords.propertyIsEnumerable(word) && keywords[word];
return (known && state.lastType != ".") ? ret(known.type, known.style, word) :
ret("variable", "variable", word);
}
}
function tokenString(quote) {
return function(stream, state) {
var escaped = false, next;
if (jsonldMode && stream.peek() == "@" && stream.match(isJsonldKeyword)){
state.tokenize = tokenBase;
return ret("jsonld-keyword", "meta");
}
while ((next = stream.next()) != null) {
if (next == quote && !escaped) break;
escaped = !escaped && next == "\\";
}
if (!escaped) state.tokenize = tokenBase;
return ret("string", "string");
};
}
function tokenComment(stream, state) {
var maybeEnd = false, ch;
while (ch = stream.next()) {
if (ch == "/" && maybeEnd) {
state.tokenize = tokenBase;
break;
}
maybeEnd = (ch == "*");
}
return ret("comment", "comment");
}
function tokenQuasi(stream, state) {
var escaped = false, next;
while ((next = stream.next()) != null) {
if (!escaped && (next == "`" || next == "$" && stream.eat("{"))) {
state.tokenize = tokenBase;
break;
}
escaped = !escaped && next == "\\";
}
return ret("quasi", "string-2", stream.current());
}
var brackets = "([{}])";
// This is a crude lookahead trick to try and notice that we're
// parsing the argument patterns for a fat-arrow function before we
// actually hit the arrow token. It only works if the arrow is on
// the same line as the arguments and there's no strange noise
// (comments) in between. Fallback is to only notice when we hit the
// arrow, and not declare the arguments as locals for the arrow
// body.
function findFatArrow(stream, state) {
if (state.fatArrowAt) state.fatArrowAt = null;
var arrow = stream.string.indexOf("=>", stream.start);
if (arrow < 0) return;
if (isTS) { // Try to skip TypeScript return type declarations after the arguments
var m = /:\s*(?:\w+(?:<[^>]*>|\[\])?|\{[^}]*\})\s*$/.exec(stream.string.slice(stream.start, arrow))
if (m) arrow = m.index
}
var depth = 0, sawSomething = false;
for (var pos = arrow - 1; pos >= 0; --pos) {
var ch = stream.string.charAt(pos);
var bracket = brackets.indexOf(ch);
if (bracket >= 0 && bracket < 3) {
if (!depth) { ++pos; break; }
if (--depth == 0) { if (ch == "(") sawSomething = true; break; }
} else if (bracket >= 3 && bracket < 6) {
++depth;
} else if (wordRE.test(ch)) {
sawSomething = true;
} else if (/["'\/]/.test(ch)) {
return;
} else if (sawSomething && !depth) {
++pos;
break;
}
}
if (sawSomething && !depth) state.fatArrowAt = pos;
}
// Parser
var atomicTypes = {"atom": true, "number": true, "variable": true, "string": true, "regexp": true, "this": true, "jsonld-keyword": true};
function JSLexical(indented, column, type, align, prev, info) {
this.indented = indented;
this.column = column;
this.type = type;
this.prev = prev;
this.info = info;
if (align != null) this.align = align;
}
function inScope(state, varname) {
for (var v = state.localVars; v; v = v.next)
if (v.name == varname) return true;
for (var cx = state.context; cx; cx = cx.prev) {
for (var v = cx.vars; v; v = v.next)
if (v.name == varname) return true;
}
}
function parseJS(state, style, type, content, stream) {
var cc = state.cc;
// Communicate our context to the combinators.
// (Less wasteful than consing up a hundred closures on every call.)
cx.state = state; cx.stream = stream; cx.marked = null, cx.cc = cc; cx.style = style;
if (!state.lexical.hasOwnProperty("align"))
state.lexical.align = true;
while(true) {
var combinator = cc.length ? cc.pop() : jsonMode ? expression : statement;
if (combinator(type, content)) {
while(cc.length && cc[cc.length - 1].lex)
cc.pop()();
if (cx.marked) return cx.marked;
if (type == "variable" && inScope(state, content)) return "variable-2";
return style;
}
}
}
// Combinator utils
var cx = {state: null, column: null, marked: null, cc: null};
function pass() {
for (var i = arguments.length - 1; i >= 0; i--) cx.cc.push(arguments[i]);
}
function cont() {
pass.apply(null, arguments);
return true;
}
function register(varname) {
function inList(list) {
for (var v = list; v; v = v.next)
if (v.name == varname) return true;
return false;
}
var state = cx.state;
cx.marked = "def";
if (state.context) {
if (inList(state.localVars)) return;
state.localVars = {name: varname, next: state.localVars};
} else {
if (inList(state.globalVars)) return;
if (parserConfig.globalVars)
state.globalVars = {name: varname, next: state.globalVars};
}
}
// Combinators
var defaultVars = {name: "this", next: {name: "arguments"}};
function pushcontext() {
cx.state.context = {prev: cx.state.context, vars: cx.state.localVars};
cx.state.localVars = defaultVars;
}
function popcontext() {
cx.state.localVars = cx.state.context.vars;
cx.state.context = cx.state.context.prev;
}
function pushlex(type, info) {
var result = function() {
var state = cx.state, indent = state.indented;
if (state.lexical.type == "stat") indent = state.lexical.indented;
else for (var outer = state.lexical; outer && outer.type == ")" && outer.align; outer = outer.prev)
indent = outer.indented;
state.lexical = new JSLexical(indent, cx.stream.column(), type, null, state.lexical, info);
};
result.lex = true;
return result;
}
function poplex() {
var state = cx.state;
if (state.lexical.prev) {
if (state.lexical.type == ")")
state.indented = state.lexical.indented;
state.lexical = state.lexical.prev;
}
}
poplex.lex = true;
function expect(wanted) {
function exp(type) {
if (type == wanted) return cont();
else if (wanted == ";") return pass();
else return cont(exp);
};
return exp;
}
function statement(type, value) {
if (type == "var") return cont(pushlex("vardef", value.length), vardef, expect(";"), poplex);
if (type == "keyword a") return cont(pushlex("form"), parenExpr, statement, poplex);
if (type == "keyword b") return cont(pushlex("form"), statement, poplex);
if (type == "{") return cont(pushlex("}"), block, poplex);
if (type == ";") return cont();
if (type == "if") {
if (cx.state.lexical.info == "else" && cx.state.cc[cx.state.cc.length - 1] == poplex)
cx.state.cc.pop()();
return cont(pushlex("form"), parenExpr, statement, poplex, maybeelse);
}
if (type == "function") return cont(functiondef);
if (type == "for") return cont(pushlex("form"), forspec, statement, poplex);
if (type == "variable") return cont(pushlex("stat"), maybelabel);
if (type == "switch") return cont(pushlex("form"), parenExpr, pushlex("}", "switch"), expect("{"),
block, poplex, poplex);
if (type == "case") return cont(expression, expect(":"));
if (type == "default") return cont(expect(":"));
if (type == "catch") return cont(pushlex("form"), pushcontext, expect("("), funarg, expect(")"),
statement, poplex, popcontext);
if (type == "class") return cont(pushlex("form"), className, poplex);
if (type == "export") return cont(pushlex("stat"), afterExport, poplex);
if (type == "import") return cont(pushlex("stat"), afterImport, poplex);
if (type == "module") return cont(pushlex("form"), pattern, pushlex("}"), expect("{"), block, poplex, poplex)
if (type == "type") return cont(typeexpr, expect("operator"), typeexpr, expect(";"));
if (type == "async") return cont(statement)
if (value == "@") return cont(expression, statement)
return pass(pushlex("stat"), expression, expect(";"), poplex);
}
function expression(type) {
return expressionInner(type, false);
}
function expressionNoComma(type) {
return expressionInner(type, true);
}
function parenExpr(type) {
if (type != "(") return pass()
return cont(pushlex(")"), expression, expect(")"), poplex)
}
function expressionInner(type, noComma) {
if (cx.state.fatArrowAt == cx.stream.start) {
var body = noComma ? arrowBodyNoComma : arrowBody;
if (type == "(") return cont(pushcontext, pushlex(")"), commasep(pattern, ")"), poplex, expect("=>"), body, popcontext);
else if (type == "variable") return pass(pushcontext, pattern, expect("=>"), body, popcontext);
}
var maybeop = noComma ? maybeoperatorNoComma : maybeoperatorComma;
if (atomicTypes.hasOwnProperty(type)) return cont(maybeop);
if (type == "function") return cont(functiondef, maybeop);
if (type == "class") return cont(pushlex("form"), classExpression, poplex);
if (type == "keyword c" || type == "async") return cont(noComma ? maybeexpressionNoComma : maybeexpression);
if (type == "(") return cont(pushlex(")"), maybeexpression, expect(")"), poplex, maybeop);
if (type == "operator" || type == "spread") return cont(noComma ? expressionNoComma : expression);
if (type == "[") return cont(pushlex("]"), arrayLiteral, poplex, maybeop);
if (type == "{") return contCommasep(objprop, "}", null, maybeop);
if (type == "quasi") return pass(quasi, maybeop);
if (type == "new") return cont(maybeTarget(noComma));
return cont();
}
function maybeexpression(type) {
if (type.match(/[;\}\)\],]/)) return pass();
return pass(expression);
}
function maybeexpressionNoComma(type) {
if (type.match(/[;\}\)\],]/)) return pass();
return pass(expressionNoComma);
}
function maybeoperatorComma(type, value) {
if (type == ",") return cont(expression);
return maybeoperatorNoComma(type, value, false);
}
function maybeoperatorNoComma(type, value, noComma) {
var me = noComma == false ? maybeoperatorComma : maybeoperatorNoComma;
var expr = noComma == false ? expression : expressionNoComma;
if (type == "=>") return cont(pushcontext, noComma ? arrowBodyNoComma : arrowBody, popcontext);
if (type == "operator") {
if (/\+\+|--/.test(value)) return cont(me);
if (value == "?") return cont(expression, expect(":"), expr);
return cont(expr);
}
if (type == "quasi") { return pass(quasi, me); }
if (type == ";") return;
if (type == "(") return contCommasep(expressionNoComma, ")", "call", me);
if (type == ".") return cont(property, me);
if (type == "[") return cont(pushlex("]"), maybeexpression, expect("]"), poplex, me);
}
function quasi(type, value) {
if (type != "quasi") return pass();
if (value.slice(value.length - 2) != "${") return cont(quasi);
return cont(expression, continueQuasi);
}
function continueQuasi(type) {
if (type == "}") {
cx.marked = "string-2";
cx.state.tokenize = tokenQuasi;
return cont(quasi);
}
}
function arrowBody(type) {
findFatArrow(cx.stream, cx.state);
return pass(type == "{" ? statement : expression);
}
function arrowBodyNoComma(type) {
findFatArrow(cx.stream, cx.state);
return pass(type == "{" ? statement : expressionNoComma);
}
function maybeTarget(noComma) {
return function(type) {
if (type == ".") return cont(noComma ? targetNoComma : target);
else return pass(noComma ? expressionNoComma : expression);
};
}
function target(_, value) {
if (value == "target") { cx.marked = "keyword"; return cont(maybeoperatorComma); }
}
function targetNoComma(_, value) {
if (value == "target") { cx.marked = "keyword"; return cont(maybeoperatorNoComma); }
}
function maybelabel(type) {
if (type == ":") return cont(poplex, statement);
return pass(maybeoperatorComma, expect(";"), poplex);
}
function property(type) {
if (type == "variable") {cx.marked = "property"; return cont();}
}
function objprop(type, value) {
if (type == "async") {
cx.marked = "property";
return cont(objprop);
} else if (type == "variable" || cx.style == "keyword") {
cx.marked = "property";
if (value == "get" || value == "set") return cont(getterSetter);
return cont(afterprop);
} else if (type == "number" || type == "string") {
cx.marked = jsonldMode ? "property" : (cx.style + " property");
return cont(afterprop);
} else if (type == "jsonld-keyword") {
return cont(afterprop);
} else if (type == "modifier") {
return cont(objprop)
} else if (type == "[") {
return cont(expression, expect("]"), afterprop);
} else if (type == "spread") {
return cont(expression);
} else if (type == ":") {
return pass(afterprop)
}
}
function getterSetter(type) {
if (type != "variable") return pass(afterprop);
cx.marked = "property";
return cont(functiondef);
}
function afterprop(type) {
if (type == ":") return cont(expressionNoComma);
if (type == "(") return pass(functiondef);
}
function commasep(what, end, sep) {
function proceed(type, value) {
if (sep ? sep.indexOf(type) > -1 : type == ",") {
var lex = cx.state.lexical;
if (lex.info == "call") lex.pos = (lex.pos || 0) + 1;
return cont(function(type, value) {
if (type == end || value == end) return pass()
return pass(what)
}, proceed);
}
if (type == end || value == end) return cont();
return cont(expect(end));
}
return function(type, value) {
if (type == end || value == end) return cont();
return pass(what, proceed);
};
}
function contCommasep(what, end, info) {
for (var i = 3; i < arguments.length; i++)
cx.cc.push(arguments[i]);
return cont(pushlex(end, info), commasep(what, end), poplex);
}
function block(type) {
if (type == "}") return cont();
return pass(statement, block);
}
function maybetype(type, value) {
if (isTS) {
if (type == ":") return cont(typeexpr);
if (value == "?") return cont(maybetype);
}
}
function typeexpr(type) {
if (type == "variable") {cx.marked = "variable-3"; return cont(afterType);}
if (type == "string" || type == "number" || type == "atom") return cont(afterType);
if (type == "{") return cont(pushlex("}"), commasep(typeprop, "}", ",;"), poplex)
if (type == "(") return cont(commasep(typearg, ")"), maybeReturnType)
}
function maybeReturnType(type) {
if (type == "=>") return cont(typeexpr)
}
function typeprop(type, value) {
if (type == "variable" || cx.style == "keyword") {
cx.marked = "property"
return cont(typeprop)
} else if (value == "?") {
return cont(typeprop)
} else if (type == ":") {
return cont(typeexpr)
}
}
function typearg(type) {
if (type == "variable") return cont(typearg)
else if (type == ":") return cont(typeexpr)
}
function afterType(type, value) {
if (value == "<") return cont(pushlex(">"), commasep(typeexpr, ">"), poplex, afterType)
if (value == "|" || type == ".") return cont(typeexpr)
if (type == "[") return cont(expect("]"), afterType)
}
function vardef() {
return pass(pattern, maybetype, maybeAssign, vardefCont);
}
function pattern(type, value) {
if (type == "modifier") return cont(pattern)
if (type == "variable") { register(value); return cont(); }
if (type == "spread") return cont(pattern);
if (type == "[") return contCommasep(pattern, "]");
if (type == "{") return contCommasep(proppattern, "}");
}
function proppattern(type, value) {
if (type == "variable" && !cx.stream.match(/^\s*:/, false)) {
register(value);
return cont(maybeAssign);
}
if (type == "variable") cx.marked = "property";
if (type == "spread") return cont(pattern);
if (type == "}") return pass();
return cont(expect(":"), pattern, maybeAssign);
}
function maybeAssign(_type, value) {
if (value == "=") return cont(expressionNoComma);
}
function vardefCont(type) {
if (type == ",") return cont(vardef);
}
function maybeelse(type, value) {
if (type == "keyword b" && value == "else") return cont(pushlex("form", "else"), statement, poplex);
}
function forspec(type) {
if (type == "(") return cont(pushlex(")"), forspec1, expect(")"), poplex);
}
function forspec1(type) {
if (type == "var") return cont(vardef, expect(";"), forspec2);
if (type == ";") return cont(forspec2);
if (type == "variable") return cont(formaybeinof);
return pass(expression, expect(";"), forspec2);
}
function formaybeinof(_type, value) {
if (value == "in" || value == "of") { cx.marked = "keyword"; return cont(expression); }
return cont(maybeoperatorComma, forspec2);
}
function forspec2(type, value) {
if (type == ";") return cont(forspec3);
if (value == "in" || value == "of") { cx.marked = "keyword"; return cont(expression); }
return pass(expression, expect(";"), forspec3);
}
function forspec3(type) {
if (type != ")") cont(expression);
}
function functiondef(type, value) {
if (value == "*") {cx.marked = "keyword"; return cont(functiondef);}
if (type == "variable") {register(value); return cont(functiondef);}
if (type == "(") return cont(pushcontext, pushlex(")"), commasep(funarg, ")"), poplex, maybetype, statement, popcontext);
if (isTS && value == "<") return cont(pushlex(">"), commasep(typeexpr, ">"), poplex, functiondef)
}
function funarg(type) {
if (type == "spread") return cont(funarg);
return pass(pattern, maybetype, maybeAssign);
}
function classExpression(type, value) {
// Class expressions may have an optional name.
if (type == "variable") return className(type, value);
return classNameAfter(type, value);
}
function className(type, value) {
if (type == "variable") {register(value); return cont(classNameAfter);}
}
function classNameAfter(type, value) {
if (value == "<") return cont(pushlex(">"), commasep(typeexpr, ">"), poplex, classNameAfter)
if (value == "extends" || value == "implements" || (isTS && type == ","))
return cont(isTS ? typeexpr : expression, classNameAfter);
if (type == "{") return cont(pushlex("}"), classBody, poplex);
}
function classBody(type, value) {
if (type == "variable" || cx.style == "keyword") {
if ((value == "async" || value == "static" || value == "get" || value == "set" ||
(isTS && (value == "public" || value == "private" || value == "protected" || value == "readonly" || value == "abstract"))) &&
cx.stream.match(/^\s+[\w$\xa1-\uffff]/, false)) {
cx.marked = "keyword";
return cont(classBody);
}
cx.marked = "property";
return cont(isTS ? classfield : functiondef, classBody);
}
if (type == "[")
return cont(expression, expect("]"), isTS ? classfield : functiondef, classBody)
if (value == "*") {
cx.marked = "keyword";
return cont(classBody);
}
if (type == ";") return cont(classBody);
if (type == "}") return cont();
if (value == "@") return cont(expression, classBody)
}
function classfield(type, value) {
if (value == "?") return cont(classfield)
if (type == ":") return cont(typeexpr, maybeAssign)
if (value == "=") return cont(expressionNoComma)
return pass(functiondef)
}
function afterExport(type, value) {
if (value == "*") { cx.marked = "keyword"; return cont(maybeFrom, expect(";")); }
if (value == "default") { cx.marked = "keyword"; return cont(expression, expect(";")); }
if (type == "{") return cont(commasep(exportField, "}"), maybeFrom, expect(";"));
return pass(statement);
}
function exportField(type, value) {
if (value == "as") { cx.marked = "keyword"; return cont(expect("variable")); }
if (type == "variable") return pass(expressionNoComma, exportField);
}
function afterImport(type) {
if (type == "string") return cont();
return pass(importSpec, maybeMoreImports, maybeFrom);
}
function importSpec(type, value) {
if (type == "{") return contCommasep(importSpec, "}");
if (type == "variable") register(value);
if (value == "*") cx.marked = "keyword";
return cont(maybeAs);
}
function maybeMoreImports(type) {
if (type == ",") return cont(importSpec, maybeMoreImports)
}
function maybeAs(_type, value) {
if (value == "as") { cx.marked = "keyword"; return cont(importSpec); }
}
function maybeFrom(_type, value) {
if (value == "from") { cx.marked = "keyword"; return cont(expression); }
}
function arrayLiteral(type) {
if (type == "]") return cont();
return pass(commasep(expressionNoComma, "]"));
}
function isContinuedStatement(state, textAfter) {
return state.lastType == "operator" || state.lastType == "," ||
isOperatorChar.test(textAfter.charAt(0)) ||
/[,.]/.test(textAfter.charAt(0));
}
// Interface
return {
startState: function(basecolumn) {
var state = {
tokenize: tokenBase,
lastType: "sof",
cc: [],
lexical: new JSLexical((basecolumn || 0) - indentUnit, 0, "block", false),
localVars: parserConfig.localVars,
context: parserConfig.localVars && {vars: parserConfig.localVars},
indented: basecolumn || 0
};
if (parserConfig.globalVars && typeof parserConfig.globalVars == "object")
state.globalVars = parserConfig.globalVars;
return state;
},
token: function(stream, state) {
if (stream.sol()) {
if (!state.lexical.hasOwnProperty("align"))
state.lexical.align = false;
state.indented = stream.indentation();
findFatArrow(stream, state);
}
if (state.tokenize != tokenComment && stream.eatSpace()) return null;
var style = state.tokenize(stream, state);
if (type == "comment") return style;
state.lastType = type == "operator" && (content == "++" || content == "--") ? "incdec" : type;
return parseJS(state, style, type, content, stream);
},
indent: function(state, textAfter) {
if (state.tokenize == tokenComment) return CodeMirror.Pass;
if (state.tokenize != tokenBase) return 0;
var firstChar = textAfter && textAfter.charAt(0), lexical = state.lexical, top
// Kludge to prevent 'maybelse' from blocking lexical scope pops
if (!/^\s*else\b/.test(textAfter)) for (var i = state.cc.length - 1; i >= 0; --i) {
var c = state.cc[i];
if (c == poplex) lexical = lexical.prev;
else if (c != maybeelse) break;
}
while ((lexical.type == "stat" || lexical.type == "form") &&
(firstChar == "}" || ((top = state.cc[state.cc.length - 1]) &&
(top == maybeoperatorComma || top == maybeoperatorNoComma) &&
!/^[,\.=+\-*:?[\(]/.test(textAfter))))
lexical = lexical.prev;
if (statementIndent && lexical.type == ")" && lexical.prev.type == "stat")
lexical = lexical.prev;
var type = lexical.type, closing = firstChar == type;
if (type == "vardef") return lexical.indented + (state.lastType == "operator" || state.lastType == "," ? lexical.info + 1 : 0);
else if (type == "form" && firstChar == "{") return lexical.indented;
else if (type == "form") return lexical.indented + indentUnit;
else if (type == "stat")
return lexical.indented + (isContinuedStatement(state, textAfter) ? statementIndent || indentUnit : 0);
else if (lexical.info == "switch" && !closing && parserConfig.doubleIndentSwitch != false)
return lexical.indented + (/^(?:case|default)\b/.test(textAfter) ? indentUnit : 2 * indentUnit);
else if (lexical.align) return lexical.column + (closing ? 0 : 1);
else return lexical.indented + (closing ? 0 : indentUnit);
},
electricInput: /^\s*(?:case .*?:|default:|\{|\})$/,
blockCommentStart: jsonMode ? null : "/*",
blockCommentEnd: jsonMode ? null : "*/",
lineComment: jsonMode ? null : "//",
fold: "brace",
closeBrackets: "()[]{}''\"\"``",
helperType: jsonMode ? "json" : "javascript",
jsonldMode: jsonldMode,
jsonMode: jsonMode,
expressionAllowed: expressionAllowed,
skipExpression: function(state) {
var top = state.cc[state.cc.length - 1]
if (top == expression || top == expressionNoComma) state.cc.pop()
}
};
});
CodeMirror.registerHelper("wordChars", "javascript", /[\w$]/);
CodeMirror.defineMIME("text/javascript", "javascript");
CodeMirror.defineMIME("text/ecmascript", "javascript");
CodeMirror.defineMIME("application/javascript", "javascript");
CodeMirror.defineMIME("application/x-javascript", "javascript");
CodeMirror.defineMIME("application/ecmascript", "javascript");
CodeMirror.defineMIME("application/json", {name: "javascript", json: true});
CodeMirror.defineMIME("application/x-json", {name: "javascript", json: true});
CodeMirror.defineMIME("application/ld+json", {name: "javascript", jsonld: true});
CodeMirror.defineMIME("text/typescript", { name: "javascript", typescript: true });
CodeMirror.defineMIME("application/typescript", { name: "javascript", typescript: true });
});

@ -0,0 +1,27 @@
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();
}
}

@ -0,0 +1,47 @@
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());
}
}

@ -1,4 +1,4 @@
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.dao.XxlJobGroupDao;

@ -1,78 +1,78 @@
package com.xxl.job.dao.impl;
import com.xxl.job.admin.core.model.XxlJobInfo;
import com.xxl.job.admin.dao.XxlJobInfoDao;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import javax.annotation.Resource;
import java.util.Date;
import java.util.List;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath*:spring/applicationcontext-*.xml")
public class XxlJobInfoTest {
@Resource
private XxlJobInfoDao xxlJobInfoDao;
@Test
public void pageList(){
List<XxlJobInfo> list = xxlJobInfoDao.pageList(0, 20, 0, null);
int list_count = xxlJobInfoDao.pageListCount(0, 20, 0, null);
System.out.println(list);
System.out.println(list_count);
List<XxlJobInfo> list2 = xxlJobInfoDao.getJobsByGroup(1);
}
@Test
public void save_load(){
XxlJobInfo info = new XxlJobInfo();
info.setJobGroup(1);
info.setJobCron("jobCron");
info.setJobDesc("desc");
info.setAuthor("setAuthor");
info.setAlarmEmail("setAlarmEmail");
info.setExecutorRouteStrategy("setExecutorRouteStrategy");
info.setExecutorHandler("setExecutorHandler");
info.setExecutorParam("setExecutorParam");
info.setExecutorBlockStrategy("setExecutorBlockStrategy");
info.setExecutorFailStrategy("setExecutorFailStrategy");
info.setGlueType("setGlueType");
info.setGlueSource("setGlueSource");
info.setGlueRemark("setGlueRemark");
info.setChildJobKey("setChildJobKey");
int count = xxlJobInfoDao.save(info);
XxlJobInfo info2 = xxlJobInfoDao.loadById(info.getId());
info2.setJobCron("jobCron2");
info2.setJobDesc("desc2");
info2.setAuthor("setAuthor2");
info2.setAlarmEmail("setAlarmEmail2");
info2.setExecutorRouteStrategy("setExecutorRouteStrategy2");
info2.setExecutorHandler("setExecutorHandler2");
info2.setExecutorParam("setExecutorParam2");
info2.setExecutorBlockStrategy("setExecutorBlockStrategy2");
info2.setExecutorFailStrategy("setExecutorFailStrategy2");
info2.setGlueType("setGlueType2");
info2.setGlueSource("setGlueSource2");
info2.setGlueRemark("setGlueRemark2");
info2.setGlueUpdatetime(new Date());
info2.setChildJobKey("setChildJobKey2");
int item2 = xxlJobInfoDao.update(info2);
xxlJobInfoDao.delete(info2.getId());
List<XxlJobInfo> list2 = xxlJobInfoDao.getJobsByGroup(1);
int ret3 = xxlJobInfoDao.findAllCount();
}
}
package com.xxl.job.admin.dao;
import com.xxl.job.admin.core.model.XxlJobInfo;
import com.xxl.job.admin.dao.XxlJobInfoDao;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import javax.annotation.Resource;
import java.util.Date;
import java.util.List;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath*:spring/applicationcontext-*.xml")
public class XxlJobInfoDaoTest {
@Resource
private XxlJobInfoDao xxlJobInfoDao;
@Test
public void pageList(){
List<XxlJobInfo> list = xxlJobInfoDao.pageList(0, 20, 0, null);
int list_count = xxlJobInfoDao.pageListCount(0, 20, 0, null);
System.out.println(list);
System.out.println(list_count);
List<XxlJobInfo> list2 = xxlJobInfoDao.getJobsByGroup(1);
}
@Test
public void save_load(){
XxlJobInfo info = new XxlJobInfo();
info.setJobGroup(1);
info.setJobCron("jobCron");
info.setJobDesc("desc");
info.setAuthor("setAuthor");
info.setAlarmEmail("setAlarmEmail");
info.setExecutorRouteStrategy("setExecutorRouteStrategy");
info.setExecutorHandler("setExecutorHandler");
info.setExecutorParam("setExecutorParam");
info.setExecutorBlockStrategy("setExecutorBlockStrategy");
info.setExecutorFailStrategy("setExecutorFailStrategy");
info.setGlueType("setGlueType");
info.setGlueSource("setGlueSource");
info.setGlueRemark("setGlueRemark");
info.setChildJobKey("setChildJobKey");
int count = xxlJobInfoDao.save(info);
XxlJobInfo info2 = xxlJobInfoDao.loadById(info.getId());
info2.setJobCron("jobCron2");
info2.setJobDesc("desc2");
info2.setAuthor("setAuthor2");
info2.setAlarmEmail("setAlarmEmail2");
info2.setExecutorRouteStrategy("setExecutorRouteStrategy2");
info2.setExecutorHandler("setExecutorHandler2");
info2.setExecutorParam("setExecutorParam2");
info2.setExecutorBlockStrategy("setExecutorBlockStrategy2");
info2.setExecutorFailStrategy("setExecutorFailStrategy2");
info2.setGlueType("setGlueType2");
info2.setGlueSource("setGlueSource2");
info2.setGlueRemark("setGlueRemark2");
info2.setGlueUpdatetime(new Date());
info2.setChildJobKey("setChildJobKey2");
int item2 = xxlJobInfoDao.update(info2);
xxlJobInfoDao.delete(info2.getId());
List<XxlJobInfo> list2 = xxlJobInfoDao.getJobsByGroup(1);
int ret3 = xxlJobInfoDao.findAllCount();
}
}

@ -1,8 +1,8 @@
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.dao.XxlJobLogDao;
import org.apache.commons.lang.time.DateUtils;
import org.apache.commons.lang3.time.DateUtils;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;

@ -1,4 +1,4 @@
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.dao.XxlJobLogGlueDao;

@ -1,4 +1,4 @@
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.dao.XxlJobRegistryDao;

@ -0,0 +1,44 @@
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>JobKey</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>";
mailBodyTemplate = MessageFormat.format(mailBodyTemplate, "执行器A", "1_1", "任务A1");
boolean ret = MailUtil.sendMail("931591021@qq.com", "调度中心监控报警" , mailBodyTemplate);
System.out.println(ret);
}
}

@ -0,0 +1,18 @@
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"));
}
}

@ -1,4 +1,4 @@
package com.xxl.job.dao.impl;
package com.xxl.job.adminbiz;
import com.xxl.job.core.biz.AdminBiz;
import com.xxl.job.core.biz.model.RegistryParam;
@ -9,7 +9,8 @@ import org.junit.Assert;
import org.junit.Test;
/**
* admin-api client, test
* admin api test
*
* @author xuxueli 2017-07-28 22:14:52
*/
public class AdminBizTest {
@ -18,6 +19,11 @@ public class AdminBizTest {
private static String addressUrl = "http://127.0.0.1:8080/xxl-job-admin".concat(AdminBiz.MAPPING);
private static String accessToken = null;
/**
* registry executor
*
* @throws Exception
*/
@Test
public void registryTest() throws Exception {
AdminBiz adminBiz = (AdminBiz) new NetComClientProxy(AdminBiz.class, addressUrl, accessToken).getObject();
@ -28,6 +34,11 @@ public class AdminBizTest {
Assert.assertTrue(returnT.getCode() == ReturnT.SUCCESS_CODE);
}
/**
* registry executor remove
*
* @throws Exception
*/
@Test
public void registryRemove() throws Exception {
AdminBiz adminBiz = (AdminBiz) new NetComClientProxy(AdminBiz.class, addressUrl, accessToken).getObject();
@ -38,6 +49,11 @@ public class AdminBizTest {
Assert.assertTrue(returnT.getCode() == ReturnT.SUCCESS_CODE);
}
/**
* trigger job for once
*
* @throws Exception
*/
@Test
public void triggerJob() throws Exception {
AdminBiz adminBiz = (AdminBiz) new NetComClientProxy(AdminBiz.class, addressUrl, accessToken).getObject();

@ -4,7 +4,7 @@
<parent>
<groupId>com.xuxueli</groupId>
<artifactId>xxl-job</artifactId>
<version>1.8.2-SNAPSHOT</version>
<version>1.9.0-SNAPSHOT</version>
</parent>
<artifactId>xxl-job-core</artifactId>
<packaging>jar</packaging>
@ -49,11 +49,11 @@
<!-- jackson -->
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-mapper-asl</artifactId>
<version>${jackson-mapper-asl.version}</version>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
</dependency>
<!-- httpclient -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>

@ -59,7 +59,7 @@ public class ExecutorBizImpl implements ExecutorBiz {
@Override
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);
LogResult logResult = XxlJobFileAppender.readLog(logFileName, fromLineNum);
@ -74,7 +74,8 @@ public class ExecutorBizImpl implements ExecutorBiz {
String removeOldReason = null;
// validjobHandler + jobThread
if (GlueTypeEnum.BEAN==GlueTypeEnum.match(triggerParam.getGlueType())) {
GlueTypeEnum glueTypeEnum = GlueTypeEnum.match(triggerParam.getGlueType());
if (GlueTypeEnum.BEAN == glueTypeEnum) {
// new jobhandler
IJobHandler newJobHandler = XxlJobExecutor.loadJobHandler(triggerParam.getExecutorHandler());
@ -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
if (jobThread != null &&
@ -119,8 +120,7 @@ public class ExecutorBizImpl implements ExecutorBiz {
return new ReturnT<String>(ReturnT.FAIL_CODE, e.getMessage());
}
}
} else if (GlueTypeEnum.GLUE_SHELL==GlueTypeEnum.match(triggerParam.getGlueType())
|| GlueTypeEnum.GLUE_PYTHON==GlueTypeEnum.match(triggerParam.getGlueType()) ) {
} else if (glueTypeEnum!=null && glueTypeEnum.isScript()) {
// valid old jobThread
if (jobThread != null &&

@ -4,11 +4,12 @@ import com.xxl.job.core.biz.AdminBiz;
import com.xxl.job.core.biz.ExecutorBiz;
import com.xxl.job.core.biz.impl.ExecutorBizImpl;
import com.xxl.job.core.handler.IJobHandler;
import com.xxl.job.core.handler.annotation.JobHander;
import com.xxl.job.core.handler.annotation.JobHandler;
import com.xxl.job.core.log.XxlJobFileAppender;
import com.xxl.job.core.rpc.netcom.NetComClientProxy;
import com.xxl.job.core.rpc.netcom.NetComServerFactory;
import com.xxl.job.core.thread.JobThread;
import com.xxl.job.core.util.NetUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
@ -28,7 +29,7 @@ public class XxlJobExecutor implements ApplicationContextAware {
// ---------------------- param ----------------------
private String ip;
private int port = 9999;
private int port;
private String appName;
private String adminAddresses;
private String accessToken;
@ -71,14 +72,10 @@ public class XxlJobExecutor implements ApplicationContextAware {
initAdminBizList(adminAddresses, accessToken);
// init executor-jobHandlerRepository
if (applicationContext != null) {
initJobHandlerRepository(applicationContext);
}
initJobHandlerRepository(applicationContext);
// init logpath
if (logPath!=null && logPath.trim().length()>0) {
XxlJobFileAppender.logPath = logPath;
}
XxlJobFileAppender.initLogPath(logPath);
// init executor-server
initExecutorServer(port, ip, appName, accessToken);
@ -121,6 +118,10 @@ public class XxlJobExecutor implements ApplicationContextAware {
// ---------------------- executor-server(jetty) ----------------------
private NetComServerFactory serverFactory = new NetComServerFactory();
private void initExecutorServer(int port, String ip, String appName, String accessToken) throws Exception {
// valid param
port = port>0?port: NetUtil.findAvailablePort(9999);
// start server
NetComServerFactory.putService(ExecutorBiz.class, new ExecutorBizImpl()); // rpc-service, base on jetty
NetComServerFactory.setAccessToken(accessToken);
serverFactory.start(port, ip, appName); // jetty + registry
@ -140,13 +141,17 @@ public class XxlJobExecutor implements ApplicationContextAware {
return jobHandlerRepository.get(name);
}
private static void initJobHandlerRepository(ApplicationContext applicationContext){
if (applicationContext == null) {
return;
}
// init job handler action
Map<String, Object> serviceBeanMap = applicationContext.getBeansWithAnnotation(JobHander.class);
Map<String, Object> serviceBeanMap = applicationContext.getBeansWithAnnotation(JobHandler.class);
if (serviceBeanMap!=null && serviceBeanMap.size()>0) {
for (Object serviceBean : serviceBeanMap.values()) {
if (serviceBean instanceof IJobHandler){
String name = serviceBean.getClass().getAnnotation(JobHander.class).value();
String name = serviceBean.getClass().getAnnotation(JobHandler.class).value();
IJobHandler handler = (IJobHandler) serviceBean;
if (loadJobHandler(name) != null) {
throw new RuntimeException("xxl-job jobhandler naming conflicts.");

@ -5,19 +5,40 @@ package com.xxl.job.core.glue;
*/
public enum GlueTypeEnum {
BEAN("BEAN模式"),
GLUE_GROOVY("GLUE模式(Java)"),
GLUE_SHELL("GLUE模式(Shell)"),
GLUE_PYTHON("GLUE模式(Python)");
BEAN("BEAN模式", false, null, null),
GLUE_GROOVY("GLUE模式(Java)", false, null, null),
GLUE_SHELL("GLUE模式(Shell)", true, "bash", ".sh"),
GLUE_PYTHON("GLUE模式(Python)", true, "python", ".py"),
GLUE_NODEJS("GLUE模式(Nodejs)", true, "node", ".js");
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.isScript = isScript;
this.cmd = cmd;
this.suffix = suffix;
}
public String getDesc() {
return desc;
}
public boolean isScript() {
return isScript;
}
public String getCmd() {
return cmd;
}
public String getSuffix() {
return suffix;
}
public static GlueTypeEnum match(String name){
for (GlueTypeEnum item: GlueTypeEnum.values()) {
if (item.name().equals(name)) {
@ -26,4 +47,5 @@ public enum GlueTypeEnum {
}
return null;
}
}

@ -3,17 +3,45 @@ package com.xxl.job.core.handler;
import com.xxl.job.core.biz.model.ReturnT;
/**
* remote job handler
* job handler
*
* @author xuxueli 2015-12-19 19:06:38
*/
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
* @param params
* execute handler, invoked when executor receives a scheduling request
*
* @param param
* @return
* @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
}
}

@ -13,7 +13,7 @@ import java.lang.annotation.Target;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface JobHander {
public @interface JobHandler {
String value() default "";

@ -21,9 +21,9 @@ public class GlueJobHandler extends IJobHandler {
}
@Override
public ReturnT<String> execute(String... params) throws Exception {
public ReturnT<String> execute(String param) throws Exception {
XxlJobLogger.log("----------- glue.version:"+ glueUpdatetime +" -----------");
return jobHandler.execute(params);
return jobHandler.execute(param);
}
}

@ -6,6 +6,7 @@ import com.xxl.job.core.handler.IJobHandler;
import com.xxl.job.core.log.XxlJobFileAppender;
import com.xxl.job.core.log.XxlJobLogger;
import com.xxl.job.core.util.ScriptUtil;
import com.xxl.job.core.util.ShardingUtil;
/**
* Created by xuxueli on 17/4/27.
@ -29,29 +30,38 @@ public class ScriptJobHandler extends IJobHandler {
}
@Override
public ReturnT<String> execute(String... params) throws Exception {
// cmd + script-file-name
String cmd = "bash";
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");
public ReturnT<String> execute(String param) throws Exception {
if (!glueType.isScript()) {
return new ReturnT<String>(IJobHandler.FAIL.getCode(), "glueType["+ glueType +"] invalid.");
}
// cmd
String cmd = glueType.getCmd();
// 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);
// log file
String logFileName = XxlJobFileAppender.logPath.concat(XxlJobFileAppender.contextHolder.get());
String logFileName = XxlJobFileAppender.contextHolder.get();
// script params0=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
XxlJobLogger.log("----------- script file:"+ scriptFileName +" -----------");
int exitValue = ScriptUtil.execToFile(cmd, scriptFileName, logFileName, params);
ReturnT<String> result = (exitValue==0)?ReturnT.SUCCESS:new ReturnT<String>(ReturnT.FAIL_CODE, "script exit value("+exitValue+") is failed");
int exitValue = ScriptUtil.execToFile(cmd, scriptFileName, logFileName, scriptParams);
ReturnT<String> result = (exitValue==0)?IJobHandler.SUCCESS:new ReturnT<String>(IJobHandler.FAIL.getCode(), "script exit value("+exitValue+") is failed");
return result;
}

@ -18,11 +18,35 @@ public class XxlJobFileAppender {
// for JobThread (support log for child thread of job handler)
//public static ThreadLocal<String> contextHolder = new ThreadLocal<String>();
public static final InheritableThreadLocal<String> contextHolder = new InheritableThreadLocal<String>();
public static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); // TODO, concurrent issues
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 logId
@ -30,21 +54,18 @@ public class XxlJobFileAppender {
*/
public static String makeLogFileName(Date triggerDate, int logId) {
// filePath/
File filePathDir = new File(logPath);
if (!filePathDir.exists()) {
filePathDir.mkdirs();
}
// filePath/yyyy-MM-dd/
String nowFormat = sdf.format(new Date());
File filePathDateDir = new File(filePathDir, nowFormat);
if (!filePathDateDir.exists()) {
filePathDateDir.mkdirs();
}
// filePath/yyyy-MM-dd/9999.log
String logFileName = XxlJobFileAppender.sdf.format(triggerDate).concat("/").concat(String.valueOf(logId)).concat(".log");
// filePath/yyyy-MM-dd
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); // avoid concurrent problem, can not be static
File logFilePath = new File(getLogPath(), sdf.format(triggerDate));
if (!logFilePath.exists()) {
logFilePath.mkdir();
}
// filePath/yyyy-MM-dd/9999.log
String logFileName = logFilePath.getPath()
.concat("/")
.concat(String.valueOf(logId))
.concat(".log");
return logFileName;
}
@ -56,17 +77,11 @@ public class XxlJobFileAppender {
*/
public static void appendLog(String logFileName, String appendLog) {
// log
if (appendLog == null) {
appendLog = "";
}
appendLog += "\r\n";
// log file
if (logFileName==null || logFileName.trim().length()==0) {
return;
}
File logFile = new File(logPath, logFileName);
File logFile = new File(logFileName);
if (!logFile.exists()) {
try {
@ -76,6 +91,12 @@ public class XxlJobFileAppender {
return;
}
}
// log
if (appendLog == null) {
appendLog = "";
}
appendLog += "\r\n";
// append file content
try {
@ -111,7 +132,7 @@ public class XxlJobFileAppender {
if (logFileName==null || logFileName.trim().length()==0) {
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()) {
return new LogResult(fromLineNum, 0, "readLog fail, logFile not exists", true);

@ -3,6 +3,8 @@ package com.xxl.job.core.log;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.text.MessageFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
@ -12,51 +14,68 @@ import java.util.Date;
*/
public class XxlJobLogger {
private static Logger logger = LoggerFactory.getLogger("xxl-job logger");
private static SimpleDateFormat xxlJobLoggerFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
private static SimpleDateFormat xxlJobLoggerFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); // TODOconcurrent issue
/**
* append log
*
* @param callInfo
* @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 callInfo = stackTraceElements[1];
StackTraceElement callInfo = stackTraceElements[1];*/
StringBuffer stringBuffer = new StringBuffer();
stringBuffer.append(xxlJobLoggerFormat.format(new Date())).append(" ")
.append("["+ callInfo.getClassName() +"]").append("-")
.append("["+ callInfo.getMethodName() +"]").append("-")
.append("["+ callInfo.getClassName() + "#" + callInfo.getMethodName() +"]").append("-")
.append("["+ callInfo.getLineNumber() +"]").append("-")
.append("["+ Thread.currentThread().getName() +"]").append(" ")
.append(appendLog!=null?appendLog:"");
String formatAppendLog = stringBuffer.toString();
// appendlog
XxlJobFileAppender.appendLog(logFileName, formatAppendLog);
logger.warn("[{}]: {}", logFileName, formatAppendLog);
String logFileName = XxlJobFileAppender.contextHolder.get();
if (logFileName!=null && logFileName.trim().length()>0) {
XxlJobFileAppender.appendLog(logFileName, formatAppendLog);
} else {
logger.info(">>>>>>>>>>> {}", formatAppendLog);
}
}
/**
* append log with pattern
*
* @
*
* @param appendLogPattern like "aaa {0} bbb {1} ccc"
* @param appendLogArguments like "111, true"
*/
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);
}
}

@ -36,6 +36,12 @@ public class NetComClientProxy implements FactoryBean<Object> {
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// filter method like "Object.toString()"
if (Object.class.getName().equals(method.getDeclaringClass().getName())) {
logger.error(">>>>>>>>>>> xxl-rpc proxy class-method not support [{}.{}]", method.getDeclaringClass().getName(), method.getName());
throw new RuntimeException("xxl-rpc proxy class-method not support");
}
// request
RpcRequest request = new RpcRequest();
@ -46,7 +52,7 @@ public class NetComClientProxy implements FactoryBean<Object> {
request.setMethodName(method.getName());
request.setParameterTypes(method.getParameterTypes());
request.setParameters(args);
// send
RpcResponse response = client.send(request);

@ -44,7 +44,7 @@ public class JettyServer {
try {
// Start server
server.start();
logger.info(">>>>>>>>>>>> xxl-job jetty server start success at port:{}.", port);
logger.info(">>>>>>>>>>> xxl-job jetty server start success at port:{}.", port);
// Start Registry-Server
ExecutorRegistryThread.getInstance().start(port, ip, appName);

@ -28,11 +28,11 @@ public class ExecutorRegistryThread extends Thread {
// valid
if (appName==null || appName.trim().length()==0) {
logger.warn(">>>>>>>>>>>> xxl-job, executor registry config fail, appName is null.");
logger.warn(">>>>>>>>>>> xxl-job, executor registry config fail, appName is null.");
return;
}
if (XxlJobExecutor.getAdminBizList() == null) {
logger.warn(">>>>>>>>>>>> xxl-job, executor registry config fail, adminAddresses is null.");
logger.warn(">>>>>>>>>>> xxl-job, executor registry config fail, adminAddresses is null.");
return;
}
@ -99,7 +99,7 @@ public class ExecutorRegistryThread extends Thread {
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
logger.warn(">>>>>>>>>>>> xxl-job, executor registry thread destory.");
logger.info(">>>>>>>>>>> xxl-job, executor registry thread destory.");
}
});

@ -57,7 +57,7 @@ public class JobThread extends Thread{
public ReturnT<String> pushTriggerQueue(TriggerParam triggerParam) {
// avoid repeat
if (triggerLogIdSet.contains(triggerParam.getLogId())) {
logger.debug("repeate trigger job, logId:{}", triggerParam.getLogId());
logger.info(">>>>>>>>>>> repeate trigger job, logId:{}", triggerParam.getLogId());
return new ReturnT<String>(ReturnT.FAIL_CODE, "repeate trigger job, logId:" + triggerParam.getLogId());
}
@ -91,59 +91,42 @@ public class JobThread extends Thread{
@Override
public void run() {
// init
try {
handler.init();
} catch (Throwable e) {
logger.error(e.getMessage(), e);
}
// execute
while(!toStop){
running = false;
idleTimes++;
try {
TriggerParam triggerParam = null;
ReturnT<String> executeResult = null;
try {
// to check toStop signal, we need cycle, so wo cannot use queue.take(), instand of poll(timeout)
TriggerParam triggerParam = triggerQueue.poll(3L, TimeUnit.SECONDS);
triggerParam = triggerQueue.poll(3L, TimeUnit.SECONDS);
if (triggerParam!=null) {
running = true;
idleTimes = 0;
triggerLogIdSet.remove(triggerParam.getLogId());
// parse param
String[] handlerParams = (triggerParam.getExecutorParams()!=null && triggerParam.getExecutorParams().trim().length()>0)
? (String[])(Arrays.asList(triggerParam.getExecutorParams().split(",")).toArray()) : null;
// handle job
ReturnT<String> executeResult = null;
try {
// log filename: yyyy-MM-dd/9999.log
String logFileName = XxlJobFileAppender.makeLogFileName(new Date(triggerParam.getLogDateTim()), triggerParam.getLogId());
XxlJobFileAppender.contextHolder.set(logFileName);
ShardingUtil.setShardingVo(new ShardingUtil.ShardingVO(triggerParam.getBroadcastIndex(), triggerParam.getBroadcastTotal()));
XxlJobLogger.log("<br>----------- xxl-job job execute start -----------<br>----------- Params:" + Arrays.toString(handlerParams));
executeResult = handler.execute(handlerParams);
if (executeResult == null) {
executeResult = ReturnT.FAIL;
}
XxlJobLogger.log("<br>----------- xxl-job job execute end(finish) -----------<br>----------- ReturnT:" + executeResult);
} catch (Exception e) {
if (toStop) {
XxlJobLogger.log("<br>----------- JobThread toStop, stopReason:" + stopReason);
}
StringWriter stringWriter = new StringWriter();
e.printStackTrace(new PrintWriter(stringWriter));
String errorMsg = stringWriter.toString();
executeResult = new ReturnT<String>(ReturnT.FAIL_CODE, errorMsg);
XxlJobLogger.log("<br>----------- JobThread Exception:" + errorMsg + "<br>----------- xxl-job job execute end(error) -----------");
}
// callback handler info
if (!toStop) {
// commonm
TriggerCallbackThread.pushCallBack(new HandleCallbackParam(triggerParam.getLogId(), executeResult));
} else {
// is killed
ReturnT<String> stopResult = new ReturnT<String>(ReturnT.FAIL_CODE, stopReason + " [业务运行中,被强制终止]");
TriggerCallbackThread.pushCallBack(new HandleCallbackParam(triggerParam.getLogId(), stopResult));
// log filename, like "logPath/yyyy-MM-dd/9999.log"
String logFileName = XxlJobFileAppender.makeLogFileName(new Date(triggerParam.getLogDateTim()), triggerParam.getLogId());
XxlJobFileAppender.contextHolder.set(logFileName);
ShardingUtil.setShardingVo(new ShardingUtil.ShardingVO(triggerParam.getBroadcastIndex(), triggerParam.getBroadcastTotal()));
// execute
XxlJobLogger.log("<br>----------- xxl-job job execute start -----------<br>----------- Param:" + triggerParam.getExecutorParams());
executeResult = handler.execute(triggerParam.getExecutorParams());
if (executeResult == null) {
executeResult = IJobHandler.FAIL;
}
XxlJobLogger.log("<br>----------- xxl-job job execute end(finish) -----------<br>----------- ReturnT:" + executeResult);
} else {
if (idleTimes > 30) {
XxlJobExecutor.removeJobThread(jobId, "excutor idel times over limit.");
@ -151,16 +134,30 @@ public class JobThread extends Thread{
}
} catch (Throwable e) {
if (toStop) {
XxlJobLogger.log("<br>----------- xxl-job toStop, stopReason:" + stopReason);
XxlJobLogger.log("<br>----------- JobThread toStop, stopReason:" + stopReason);
}
StringWriter stringWriter = new StringWriter();
e.printStackTrace(new PrintWriter(stringWriter));
String errorMsg = stringWriter.toString();
XxlJobLogger.log("----------- xxl-job JobThread Exception:" + errorMsg);
}
}
executeResult = new ReturnT<String>(ReturnT.FAIL_CODE, errorMsg);
XxlJobLogger.log("<br>----------- JobThread Exception:" + errorMsg + "<br>----------- xxl-job job execute end(error) -----------");
} finally {
if(triggerParam != null) {
// callback handler info
if (!toStop) {
// commonm
TriggerCallbackThread.pushCallBack(new HandleCallbackParam(triggerParam.getLogId(), executeResult));
} else {
// is killed
ReturnT<String> stopResult = new ReturnT<String>(ReturnT.FAIL_CODE, stopReason + " [业务运行中,被强制终止]");
TriggerCallbackThread.pushCallBack(new HandleCallbackParam(triggerParam.getLogId(), stopResult));
}
}
}
}
// callback trigger request in queue
while(triggerQueue !=null && triggerQueue.size()>0){
TriggerParam triggerParam = triggerQueue.poll();
@ -170,7 +167,14 @@ public class JobThread extends Thread{
TriggerCallbackThread.pushCallBack(new HandleCallbackParam(triggerParam.getLogId(), stopResult));
}
}
logger.info(">>>>>>>>>>>> xxl-job JobThread stoped, hashCode:{}", Thread.currentThread());
// destroy
try {
handler.destroy();
} catch (Throwable e) {
logger.error(e.getMessage(), e);
}
logger.info(">>>>>>>>>>> xxl-job JobThread stoped, hashCode:{}", Thread.currentThread());
}
}

@ -40,7 +40,7 @@ public class TriggerCallbackThread {
// valid
if (XxlJobExecutor.getAdminBizList() == null) {
logger.warn(">>>>>>>>>>>> xxl-job, executor callback config fail, adminAddresses is null.");
logger.warn(">>>>>>>>>>> xxl-job, executor callback config fail, adminAddresses is null.");
return;
}
@ -80,7 +80,7 @@ public class TriggerCallbackThread {
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
logger.warn(">>>>>>>>>>>> xxl-job, executor callback thread destory.");
logger.info(">>>>>>>>>>> xxl-job, executor callback thread destory.");
}
});

@ -64,7 +64,7 @@ public class HttpClientUtil {
EntityUtils.consume(entity);
}
} catch (Exception e) {
logger.error("", e);
logger.error(e.getMessage(), e);
throw e;
} finally {
httpPost.releaseConnection();

@ -1,11 +1,10 @@
package com.xxl.job.core.util;
import org.codehaus.jackson.JsonGenerationException;
import org.codehaus.jackson.JsonParseException;
import org.codehaus.jackson.map.JsonMappingException;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.type.TypeReference;
import com.fasterxml.jackson.core.JsonGenerationException;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@ -0,0 +1,70 @@
package com.xxl.job.core.util;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.net.ServerSocket;
/**
* net util
*
* @author xuxueli 2017-11-29 17:00:25
*/
public class NetUtil {
private static Logger logger = LoggerFactory.getLogger(NetUtil.class);
/**
* find avaliable port
*
* @param defaultPort
* @return
*/
public static int findAvailablePort(int defaultPort) {
int portTmp = defaultPort;
while (portTmp < 65535) {
if (!isPortUsed(portTmp)) {
return portTmp;
} else {
portTmp++;
}
}
portTmp = defaultPort--;
while (portTmp > 0) {
if (!isPortUsed(portTmp)) {
return portTmp;
} else {
portTmp--;
}
}
throw new IllegalStateException("no available port.");
}
/**
* check port used
*
* @param port
* @return
*/
public static boolean isPortUsed(int port) {
boolean used = false;
ServerSocket serverSocket = null;
try {
serverSocket = new ServerSocket(port);
used = false;
} catch (IOException e) {
logger.debug(">>>>>>>>>>> xxl-job, port[{}] is in use.", port);
used = true;
} finally {
if (serverSocket != null) {
try {
serverSocket.close();
} catch (IOException e) {
logger.info("");
}
}
}
return used;
}
}

@ -1,11 +1,9 @@
package com.xxl.job.core.util;
import com.xxl.job.core.log.XxlJobFileAppender;
import org.apache.commons.exec.CommandLine;
import org.apache.commons.exec.DefaultExecutor;
import org.apache.commons.exec.PumpStreamHandler;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
@ -27,18 +25,6 @@ public class ScriptUtil {
* @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
FileOutputStream fileOutputStream = null;
try {

@ -5,7 +5,7 @@
<parent>
<groupId>com.xuxueli</groupId>
<artifactId>xxl-job</artifactId>
<version>1.8.2-SNAPSHOT</version>
<version>1.9.0-SNAPSHOT</version>
</parent>
<artifactId>xxl-job-executor-samples</artifactId>
<packaging>pom</packaging>

@ -5,7 +5,7 @@
<parent>
<artifactId>xxl-job-executor-samples</artifactId>
<groupId>com.xuxueli</groupId>
<version>1.8.2-SNAPSHOT</version>
<version>1.9.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>xxl-job-executor-sample-jfinal</artifactId>
@ -16,7 +16,14 @@
<dependency>
<groupId>com.jfinal</groupId>
<artifactId>jfinal</artifactId>
<version>2.0</version>
<version>2.2</version>
</dependency>
<!-- slf4j -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>${slf4j-api.version}</version>
</dependency>
<!-- xxl-job -->
@ -26,11 +33,6 @@
<version>${project.parent.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>${slf4j-api.version}</version>
</dependency>
</dependencies>
</project>

@ -20,14 +20,14 @@ import java.util.concurrent.TimeUnit;
public class DemoJobHandler extends IJobHandler {
@Override
public ReturnT<String> execute(String... params) throws Exception {
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 ReturnT.SUCCESS;
return SUCCESS;
}
}

@ -14,7 +14,7 @@ import com.xxl.job.core.util.ShardingUtil;
public class ShardingJobHandler extends IJobHandler {
@Override
public ReturnT<String> execute(String... params) throws Exception {
public ReturnT<String> execute(String param) throws Exception {
// 分片参数
ShardingUtil.ShardingVO shardingVO = ShardingUtil.getShardingVo();
@ -29,7 +29,7 @@ public class ShardingJobHandler extends IJobHandler {
}
}
return ReturnT.SUCCESS;
return SUCCESS;
}
}

@ -7,7 +7,7 @@ xxl.job.executor.ip=
xxl.job.executor.port=9997
### 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.accessToken=

@ -3,7 +3,11 @@
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>jfinal-demo</display-name>
<display-name>xxl-job-executor-sample-jfinal</display-name>
<context-param>
<param-name>webAppRootKey</param-name>
<param-value>xxl-job-executor-sample-jfinal</param-value>
</context-param>
<!-- jfinal -->
<filter>

@ -4,7 +4,7 @@
<parent>
<groupId>com.xuxueli</groupId>
<artifactId>xxl-job-executor-samples</artifactId>
<version>1.8.2-SNAPSHOT</version>
<version>1.9.0-SNAPSHOT</version>
</parent>
<artifactId>xxl-job-executor-sample-spring</artifactId>
<packaging>war</packaging>

@ -2,7 +2,7 @@ package com.xxl.job.executor.service.jobhandler;
import com.xxl.job.core.biz.model.ReturnT;
import com.xxl.job.core.handler.IJobHandler;
import com.xxl.job.core.handler.annotation.JobHander;
import com.xxl.job.core.handler.annotation.JobHandler;
import com.xxl.job.core.log.XxlJobLogger;
import org.springframework.stereotype.Component;
@ -15,24 +15,24 @@ import java.util.concurrent.TimeUnit;
*
* 1com.xxl.job.core.handler.IJobHandlerJava
* 2SpringBean@Component
* 3 @JobHander(value="自定义jobhandler名称")valueJobHandlerJobHandler
* 3 @JobHandler(value="自定义jobhandler名称")valueJobHandlerJobHandler
* 4 "XxlJobLogger.log"
*
* @author xuxueli 2015-12-19 19:43:36
*/
@JobHander(value="demoJobHandler")
@JobHandler(value="demoJobHandler")
@Component
public class DemoJobHandler extends IJobHandler {
@Override
public ReturnT<String> execute(String... params) throws Exception {
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 ReturnT.SUCCESS;
return SUCCESS;
}
}

@ -2,7 +2,7 @@ package com.xxl.job.executor.service.jobhandler;
import com.xxl.job.core.biz.model.ReturnT;
import com.xxl.job.core.handler.IJobHandler;
import com.xxl.job.core.handler.annotation.JobHander;
import com.xxl.job.core.handler.annotation.JobHandler;
import com.xxl.job.core.log.XxlJobLogger;
import com.xxl.job.core.util.ShardingUtil;
import org.springframework.stereotype.Service;
@ -13,12 +13,12 @@ import org.springframework.stereotype.Service;
*
* @author xuxueli 2017-07-25 20:56:50
*/
@JobHander(value="shardingJobHandler")
@JobHandler(value="shardingJobHandler")
@Service
public class ShardingJobHandler extends IJobHandler {
@Override
public ReturnT<String> execute(String... params) throws Exception {
public ReturnT<String> execute(String param) throws Exception {
// 分片参数
ShardingUtil.ShardingVO shardingVO = ShardingUtil.getShardingVo();
@ -33,7 +33,7 @@ public class ShardingJobHandler extends IJobHandler {
}
}
return ReturnT.SUCCESS;
return SUCCESS;
}
}

@ -3,9 +3,9 @@
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
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/spring-context-3.0.xsd">
http://www.springframework.org/schema/context/spring-context.xsd">
<bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="fileEncoding" value="utf-8" />

@ -7,7 +7,7 @@ xxl.job.executor.ip=
xxl.job.executor.port=9999
### 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.accessToken=

@ -3,6 +3,7 @@
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"
id="WebApp_ID" version="2.5">
<display-name>xxl-job-executor-sample-spring</display-name>
<context-param>
<param-name>webAppRootKey</param-name>
<param-value>xxl-job-executor-sample-spring</param-value>

@ -6,7 +6,7 @@
<parent>
<groupId>com.xuxueli</groupId>
<artifactId>xxl-job-executor-samples</artifactId>
<version>1.8.2-SNAPSHOT</version>
<version>1.9.0-SNAPSHOT</version>
</parent>
<artifactId>xxl-job-executor-sample-springboot</artifactId>
<packaging>jar</packaging>

@ -1,18 +1,18 @@
package com.xxl.job.executor.mvc.controller;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
@EnableAutoConfiguration
public class IndexController {
@RequestMapping("/")
@ResponseBody
String index() {
return "xxl job executor running.";
}
}
//package com.xxl.job.executor.mvc.controller;
//
//import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
//import org.springframework.stereotype.Controller;
//import org.springframework.web.bind.annotation.RequestMapping;
//import org.springframework.web.bind.annotation.ResponseBody;
//
//@Controller
//@EnableAutoConfiguration
//public class IndexController {
//
// @RequestMapping("/")
// @ResponseBody
// String index() {
// return "xxl job executor running.";
// }
//
//}

@ -2,9 +2,9 @@ package com.xxl.job.executor.service.jobhandler;
import com.xxl.job.core.biz.model.ReturnT;
import com.xxl.job.core.handler.IJobHandler;
import com.xxl.job.core.handler.annotation.JobHander;
import com.xxl.job.core.handler.annotation.JobHandler;
import com.xxl.job.core.log.XxlJobLogger;
import org.springframework.stereotype.Service;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
@ -13,26 +13,26 @@ import java.util.concurrent.TimeUnit;
* HandlerDemoBean
*
*
* 1 IJobHandler
* 2Spring @Service
* 3@JobHander valueJobKey;JobKey;
* 1com.xxl.job.core.handler.IJobHandlerJava
* 2SpringBean@Component
* 3@JobHandler(value="自定义jobhandler名称")valueJobHandlerJobHandler
* 4 "XxlJobLogger.log"
*
* @author xuxueli 2015-12-19 19:43:36
*/
@JobHander(value="demoJobHandler")
@Service
@JobHandler(value="demoJobHandler")
@Component
public class DemoJobHandler extends IJobHandler {
@Override
public ReturnT<String> execute(String... params) throws Exception {
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 ReturnT.SUCCESS;
return SUCCESS;
}
}

@ -2,7 +2,7 @@ package com.xxl.job.executor.service.jobhandler;
import com.xxl.job.core.biz.model.ReturnT;
import com.xxl.job.core.handler.IJobHandler;
import com.xxl.job.core.handler.annotation.JobHander;
import com.xxl.job.core.handler.annotation.JobHandler;
import com.xxl.job.core.log.XxlJobLogger;
import com.xxl.job.core.util.ShardingUtil;
import org.springframework.stereotype.Service;
@ -13,12 +13,12 @@ import org.springframework.stereotype.Service;
*
* @author xuxueli 2017-07-25 20:56:50
*/
@JobHander(value="shardingJobHandler")
@JobHandler(value="shardingJobHandler")
@Service
public class ShardingJobHandler extends IJobHandler {
@Override
public ReturnT<String> execute(String... params) throws Exception {
public ReturnT<String> execute(String param) throws Exception {
// 分片参数
ShardingUtil.ShardingVO shardingVO = ShardingUtil.getShardingVo();
@ -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
### xxl-job executor address
xxl.job.executor.appname=xxl-job-executor-sample
xxl.job.executor.ip=
xxl.job.executor.port=9998
xxl.job.executor.port=-1
### 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.accessToken=

Loading…
Cancel
Save