Pre Merge pull request !73 from wcs/master

pull/73/MERGE
wcs 3 weeks ago committed by Gitee
commit cf7a85653a
No known key found for this signature in database
GPG Key ID: 173E9B9CA92EEF8F

@ -1,6 +1,10 @@
name: Java CI
on: [push]
on:
push:
branches: [ "master" ]
pull_request:
branches: [ "master" ]
jobs:
build:
@ -8,10 +12,12 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: Set up JDK 1.8
uses: actions/setup-java@v1
with:
java-version: 1.8
- name: Build with Maven
run: mvn -B package --file pom.xml
- uses: actions/checkout@v4
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
cache: maven
- name: Build with Maven
run: mvn -B package --file pom.xml

@ -10,8 +10,8 @@
<a href="https://github.com/xuxueli/xxl-job/actions">
<img src="https://github.com/xuxueli/xxl-job/workflows/Java%20CI/badge.svg" >
</a>
<a href="https://maven-badges.herokuapp.com/maven-central/com.xuxueli/xxl-job/">
<img src="https://maven-badges.herokuapp.com/maven-central/com.xuxueli/xxl-job/badge.svg" >
<a href="https://central.sonatype.com/artifact/com.xuxueli/xxl-job-core">
<img src="https://img.shields.io/maven-central/v/com.xuxueli/xxl-job-core" >
</a>
<a href="https://github.com/xuxueli/xxl-job/releases">
<img src="https://img.shields.io/github/release/xuxueli/xxl-job.svg" >
@ -46,10 +46,23 @@ XXL-JOB is an open source and free project, with its ongoing development made po
XXL-JOB 是一个开源且免费项目,其正在进行的开发完全得益于支持者的支持。开源不易,[前往赞助项目开发](https://www.xuxueli.com/page/donate.html )
<!-- supporter start -->
<h5 style="color: silver;" >金牌赞助方</h5>
<a href="https://www.jetbrains.com/idea/?from=xuxueli.com" title="jetbrains" target="_blank" >
<img width="55px" src="http://www.xuxueli.com/page/static/images/logo_intellij.jpeg" >
</a>
<h3 style="color: #E6BE8A;" >金牌赞助方</h3>
<table>
<tr>
<td>
<a href="https://www.aliyun.com/product/aliware/mse?utm_content=g_1000401794" title="" target="_blank" >
<img width="150px" src="http://www.xuxueli.com/page/static/images/logo_aliyun2.png" >
<br>
<span style="text-decoration:underline;color: #E6BE8A;" >阿里云 提供云上托管 XXL-JOB</span>
</a>
</td>
<td>
<a href="https://www.mall4j.com/cn/?statId=10" title="" target="_blank" >
<img width="150px" src="http://www.xuxueli.com/page/static/images/logo_mail4j.png" >
</a>
</td>
</tr>
</table>
<!-- supporter end -->
@ -93,13 +106,15 @@ XXL-JOB 是一个开源且免费项目,其正在进行的开发完全得益于
- 28、推送maven中央仓库: 将会把最新稳定版推送到maven中央仓库, 方便用户接入和使用;
- 29、运行报表支持实时查看运行数据如任务数量、调度次数、执行器数量等以及调度报表如调度日期分布图调度成功分布图等
- 30、全异步任务调度流程全异步化设计实现如异步调度、异步运行、异步回调等有效对密集调度进行流量削峰理论上支持任意时长任务的运行
- 31、跨语言:调度中心与执行器提供语言无关的 RESTful API 服务,第三方任意语言可据此对接调度中心或者实现执行器。除此之外,还提供了 “多任务模式”和“httpJobHandler”等其他跨语言方案
- 31、跨语言/OpenAPI调度中心与执行器提供语言无关的 OpenApiRESTful 格式),第三方任意语言可据此对接调度中心或者实现执行器,实现多语言支持。除此之外,还提供了 “多任务模式”和“httpJobHandler”等其他跨语言方案
- 32、国际化调度中心支持国际化设置提供中文、英文两种可选语言默认为中文
- 33、容器化提供官方docker镜像并实时更新推送dockerhub进一步实现产品开箱即用
- 34、线程池隔离调度线程池进行隔离拆分慢任务自动降级进入"Slow"线程池,避免耗尽调度线程,提高系统稳定性;
- 35、用户管理支持在线管理系统用户存在管理员、普通用户两种角色
- 36、权限控制执行器维度进行权限控制管理员拥有全量权限普通用户需要分配执行器权限后才允许相关操作
- 37、AI任务原生提供AI执行器并内置多个AI任务Handler与spring-ai、ollama、dify等集成打通支持快速开发AI类任务。
- 38、审计日志记录任务操作敏感信息用于系统监控、审计和安全分析可快速追溯异常行为以及定位排查问题。
- 39、优雅停机调度中心停机检测时间轮非空时主动等待调度完成客户端停机检测存在运行中任务时停止接收新任务并主动等待任务执行完成
## Development
于2015年中我在github上创建XXL-JOB项目仓库并提交第一个commit随之进行系统结构设计UI选型交互设计……
@ -827,6 +842,41 @@ XXL-JOB 是一个开源且免费项目,其正在进行的开发完全得益于
- 686、广州博依特智能信息科技有限公司
- 687、河南宠呦呦信息技术有限公司
- 688、陕西星邑空间技术有限公司
- 689、广东西欧克实业有限公司
- 690、唱吧麦颂KTV
- 691、联通云
- 692、北京爱话本科技有限公司
- 693、北京起创科技有限公司
- 694、平安证券【平安证券】
- 695、合肥中科类脑智能技术有限公司
- 696、南京同仁堂健康产业有限公司【同仁堂】
- 697、铜仁市碧江区智惠加油站
- 698、惟客数据
- 699、凤凰新闻【凤凰新闻】
- 700、深圳王力智能
- 701、返利网数字科技股份有限公司
- 702、上海阜能信息科技有限公司
- 703、深圳市极能超电数字科技有限公司
- 704、海目星激光科技集团股份有限公司
- 705、安克创新科技股份有限公司【安克】
- 706、大庆点神科技有限公司
- 707、浙江零跑科技股份有限公司【零跑】
- 708、成都成电金盘健康数据技术有限公司
- 709、成都极米科技股份有限公司【极米】
- 710、顺德职业技术大学
- 711、中邮证券有限责任公司【中邮证券】
- 712、志豪链云科技有限公司
- 713、湖南万鲸科技有限公司
- 714、广州万表
- 715、再惠上海网络科技有限公司
- 716、上海爱诚裕信息科技有限公司
- 717、杭州迈瑞数字科技有限公司
- 718、广州串联网络科技有限公司
- 719、上海乐研化学试剂
- 720、智现未来
- 721、大庆点神科技有限公司
- 722、深圳市中科环球科技有限公司
- 723、江苏金箭车业制造有限公司
- ……
> 更多接入的公司,欢迎在 [登记地址](https://github.com/xuxueli/xxl-job/issues/1 ) 登记,登记仅仅为了产品推广。

@ -1,10 +1,10 @@
## 《Distributed task scheduling framework XXL-JOB》
[![Actions Status](https://github.com/xuxueli/xxl-job/workflows/Java%20CI/badge.svg)](https://github.com/xuxueli/xxl-job/actions)
[![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.xuxueli/xxl-job/badge.svg)](https://maven-badges.herokuapp.com/maven-central/com.xuxueli/xxl-job/)
[![Build Status](https://github.com/xuxueli/xxl-job/workflows/Java%20CI/badge.svg)](https://github.com/xuxueli/xxl-job/actions)
[![Maven Central](https://img.shields.io/maven-central/v/com.xuxueli/xxl-job-core)](https://central.sonatype.com/artifact/com.xuxueli/xxl-job-core)
[![GitHub release](https://img.shields.io/github/release/xuxueli/xxl-job.svg)](https://github.com/xuxueli/xxl-job/releases)
[![GitHub stars](https://img.shields.io/github/stars/xuxueli/xxl-job)](https://github.com/xuxueli/xxl-job/)
[![Docker Status](https://img.shields.io/docker/pulls/xuxueli/xxl-job-admin)](https://hub.docker.com/r/xuxueli/xxl-job-admin/)
[![Docker pulls](https://img.shields.io/docker/pulls/xuxueli/xxl-job-admin)](https://hub.docker.com/r/xuxueli/xxl-job-admin/)
[![License](https://img.shields.io/badge/license-GPLv3-blue.svg)](http://www.gnu.org/licenses/gpl-3.0.html)
[![donate](https://img.shields.io/badge/%24-donate-ff69b4.svg?style=flat)](https://www.xuxueli.com/page/donate.html)
@ -335,7 +335,452 @@ So far, XXL-JOB has access to a number of companies online product line, access
- 275、卡思优派人力资源集团
- 276、南京观为智慧软件科技有限公司
- 277、杭州城市大脑科技有限公司
- 278、猿辅导
- 278、猿辅导【猿辅导】
- 279、洛阳健创网络科技有限公司
- 280、魔力耳朵
- 281、亿阳信通
- 282、上海招鲤科技有限公司
- 283、四川商旅无忧科技服务有限公司
- 284、UU跑腿
- 285、北京老虎证券【老虎证券】
- 286、悠活省吧北京网络科技有限公司
- 287、F5未来商店
- 288、深圳环阳通信息技术有限公司
- 289、遠傳電信
- 290、作业帮北京教育科技有限公司【作业帮】
- 291、成都科鸿智信科技有限公司
- 292、北京木屋时代科技有限公司
- 293、大学通哈尔滨科技有限责任公司
- 294、浙江华坤道威数据科技有限公司
- 295、吉祥航空【吉祥航空】
- 296、南京圆周网络科技有限公司
- 297、广州市洋葱omall电子商务
- 298、天津联物科技有限公司
- 299、跑哪儿科技北京有限公司
- 300、深圳市美西西餐饮有限公司(喜茶)
- 301、平安不动产有限公司【平安】
- 302、江苏中海昇物联科技有限公司
- 303、湖南牙医帮科技有限公司
- 304、重庆民航凯亚信息技术有限公司易通航
- 305、递易上海智能科技有限公司
- 306、亚朵
- 307、浙江新课堂教育股份有限公司
- 308、北京蜂创科技有限公司
- 309、德一智慧城市信息系统有限公司
- 310、北京翼点科技有限公司
- 311、湖南智数新维度信息科技有限公司
- 312、北京玖扬博文文化发展有限公司
- 313、上海宇珩信息科技有限公司
- 314、全景智联武汉科技有限公司
- 315、天津易客满国际物流有限公司
- 316、南京爱福路汽车科技有限公司
- 317、我房旅居集团
- 318、湛江亲邻科技有限公司
- 319、深圳市姜科网络有限公司
- 320、青岛日日顺物流有限公司
- 321、南京太川信息技术有限公司
- 322、美图之家科技优先公司【美图】
- 323、南京太川信息技术有限公司
- 324、众薪科技北京有限公司
- 325、武汉安安物联科技有限公司
- 326、北京智客朗道网络科技有限公司
- 327、深圳市超级猩猩健身管理管理有限公司
- 328、重庆达志科技有限公司
- 329、上海享评信息科技有限公司
- 330、薪得付信息科技
- 331、跟谁学
- 332、中道苏州旅游网络科技有限公司
- 333、广州小卫科技有限公司
- 334、上海非码网络科技有限公司
- 335、途家网网络技术北京有限公司【途家】
- 336、广州辉凡信息科技有限公司
- 337、天维尔信息科技股份有限公司
- 338、上海极豆科技有限公司
- 339、苏州触达信息技术有限公司
- 340、北京热云科技有限公司
- 341、中智企服北京科技有限公司
- 342、易联云计算杭州有限责任公司
- 343、青岛航空股份有限公司【青岛航空】
- 344、山西博睿通科技有限公司
- 345、网易杭州网络有限公司【网易】
- 346、北京果果乐学科技有限公司
- 347、百望股份有限公司
- 348、中保金服深圳科技有限公司
- 349、天津运友物流科技股份有限公司
- 350、广东创能科技股份有限公司
- 351、上海倚博信息科技有限公司
- 352、深圳百果园实业集团股份有限公司
- 353、广州细刻网络科技有限公司
- 354、武汉鸿业众创科技有限公司
- 355、金锡科技广州有限公司
- 356、易瑞国际电子商务有限公司
- 357、奇点云
- 358、中视信息科技有限公司
- 359、开源项目:datax-web
- 360、云知声智能科技股份有限公司
- 361、开源项目:bboss
- 362、成都深驾科技有限公司
- 363、FunPlus【趣加】
- 364、杭州创匠信科技有限公司
- 365、龙匠北京科技发展有限公司
- 366、广州一链通互联网科技有限公司
- 367、上海星艾网络科技有限公司
- 368、虎博网络技术(上海)有限公司
- 369、青岛优米信息技术有限公司
- 370、八维通科技有限公司
- 371、烟台合享智星数据科技有限公司
- 372、东吴证券股份有限公司
- 373、中通云仓股份有限公司【中通】
- 374、北京加菲猫科技有限公司
- 375、北京匠心演绎科技有限公司
- 376、宝贝走天下
- 377、厦门众库科技有限公司
- 378、海通证券数据中心
- 389、湖南快乐通宝小额贷款有限公司
- 380、浙江大华技术股份有限公司
- 381、杭州魔筷科技有限公司
- 382、青岛掌讯通区块链科技有限公司
- 383、新大陆金融科技
- 384、常州玺拓软件科技有限公司
- 385、北京正保网格教育科技有限公司
- 386、统一企业中国投资有限公司【统一】
- 387、微革网络科技有限公司
- 388、杭州融易算科技有限公司
- 399、青岛上啥班网络科技有限公司
- 390、京东酒世界
- 391、杭州爱博仕科技有限公司
- 392、五星金服控股有限公司
- 393、福建乐摩物联科技有限公司
- 394、百炼智能科技有限公司
- 395、山东能源数智云科技有限公司
- 396、招商局能源运输股份有限公司
- 397、三一集团【三一】
- 398、东巴文深圳健康管理有限公司
- 399、索易软件
- 400、深圳市宁远科技有限公司
- 401、熙牛医疗
- 402、南京智鹤电子科技有限公司
- 403、嘀嗒出行【嘀嗒出行】
- 404、广州虎牙信息科技有限公司【虎牙】
- 405、广州欧莱雅百库网络科技有限公司【欧莱雅】
- 406、微微科技有限公司
- 407、我爱我家房地产经纪有限公司【我爱我家】
- 408、九号发现
- 409、薪人薪事
- 410、武汉氪细胞网络技术有限公司
- 411、广州市斯凯奇商业有限公司
- 412、微淼商学院
- 413、杭州车盛科技有限公司
- 414、深兰科技上海有限公司
- 415、安徽中科美络信息技术有限公司
- 416、比亚迪汽车工业有限公司【比亚迪】
- 417、湖南小桔信息技术有限公司
- 418、安徽科大国创软件科技有限公司
- 419、克而瑞
- 420、陕西云基华海信息技术有限公司
- 421、安徽深宁科技有限公司
- 422、广东康爱多数字健康有限公司
- 423、嘉里电子商务
- 424、上海时代光华教育发展有限公司
- 425、CityDo
- 426、上海禹知信息科技有限公司
- 427、广东智瑞科技有限公司
- 428、西安爱铭网络科技有限公司
- 429、心医国际数字医疗系统(大连)有限公司
- 430、乐其电商
- 431、锐达科技
- 432、天津长城滨银汽车金融有限公司
- 433、代码网
- 434、东莞市东城乔伦软件开发工作室
- 435、浙江百应科技有限公司
- 436、上海力爱帝信息技术有限公司(Red E)
- 437、云徙科技有限公司
- 438、北京康智乐思网络科技有限公司【大姨吗APP】
- 439、安徽开元瞬视科技有限公司
- 440、立方
- 441、厦门纵行科技
- 442、乐山-菲尼克斯半导体有限公司
- 443、武汉光谷联合集团有限公司
- 444、上海金仕达软件科技有限公司
- 445、深圳易世通达科技有限公司
- 446、爱动超越人工智能科技北京有限责任公司
- 447、迪普信北京科技有限公司
- 448、掌站科技北京有限公司
- 449、深圳市华云中盛股份有限公司
- 450、上海原圈科技有限公司
- 451、广州赞赏信息科技有限公司
- 452、Amber Group
- 453、德威国际货运代理上海公司
- 454、浙江杰夫兄弟智慧科技有限公司
- 455、信也科技
- 456、开思时代科技深圳有限公司
- 457、大连槐德科技有限公司
- 458、同程生活
- 459、松果出行
- 460、企鹅杏仁集团
- 461、宁波科云信息科技有限公司
- 462、上海格蓝威驰信息科技有限公司
- 463、杭州趣淘鲸科技有限公司
- 464、湖州市数字惠民科技有限公司
- 465、乐普北京医疗器械股份有限公司
- 466、广州市晴川高新技术开发有限公司
- 467、山西缇客科技有限公司
- 468、徐州卡西穆电子商务有限公司
- 469、格创东智科技有限公司
- 470、世纪龙信息网络有限责任公司
- 471、邦道科技有限公司
- 472、河南中盟新云科技股份有限公司
- 473、横琴人寿保险有限公司
- 474、上海海隆华钟信息技术有限公司
- 475、上海久湛
- 476、上海仙豆智能机器人有限公司
- 477、广州汇尚网络科技有限公司
- 478、深圳市阿卡索资讯股份有限公司
- 479、青岛佳家康健康管理有限责任公司
- 480、蓝城兄弟
- 481、成都天府通金融服务股份有限公司
- 482、深圳云镖网络科技有限公司
- 483、上海影创科技
- 484、成都艾拉物联
- 485、北京客邻尚品网络技术有限公司
- 486、IT实战联盟
- 487、杭州尤拉夫科技有限公司
- 488、中大检测(湖南)股份有限公司
- 489、江苏电老虎工业互联网股份有限公司
- 490、上海助通信息科技有限公司
- 491、北京符节科技有限公司
- 492、杭州英祐科技有限公司
- 493、江苏电老虎工业互联网股份有限公司
- 494、深圳市点猫科技有限公司
- 495、杭州天音
- 496、深圳市二十一科技互联网有限公司
- 497、海南海口翎度科技
- 498、北京小趣智品科技有限公司
- 499、广州石竹计算机软件有限公司
- 500、深圳市惟客数据科技有限公司
- 501、中国医疗器械有限公司
- 502、上海云谦科技有限公司
- 503、上海磐农信息科技有限公司
- 504、广州领航食品有限公司
- 505、青岛掌讯通区块链科技有限公司
- 506、北京新网数码信息技术有限公司
- 507、超体信息科技(深圳)有限公司
- 508、长沙店帮手信息科技有限公司
- 509、上海助弓装饰工程有限公司
- 510、杭州寻联网络科技有限公司
- 511、成都大淘客科技有限公司
- 512、松果出行
- 513、深圳市唤梦科技有限公司
- 514、上汽集团商用车技术中心
- 515、北京中航讯科技股份有限公司
- 516、北龙中网(北京)科技有限责任公司
- 517、前海超级前台(深圳)信息技术有限公司
- 518、上海中商网络股份有限公司
- 519、上海助通信息科技有限公司
- 520、宁波聚臻智能科技有限公司
- 521、上海零动数码科技股份有限公司
- 522、浙江学海教育科技有限公司
- 523、聚学云(山东)信息技术有限公司
- 524、多氟多新材料股份有限公司
- 525、智慧眼科技股份有限公司
- 526、广东智通人才连锁股份有限公司
- 527、世纪开元智印互联科技集团股份有限公司
- 528、北京理想汽车【理想汽车】
- 529、巽逸科技(重庆)有限公司
- 530、义乌购电子商务有限公司
- 531、深圳市珂莱蒂尔服饰有限公司
- 532、江西国泰利民信息科技有限公司
- 533、广西广电大数据科技有限公司
- 534、杭州艾麦科技有限公司
- 535、广州小滴科技有限公司
- 536、佳缘科技股份有限公司
- 537、上海深擎信息科技有限公司
- 538、武商网
- 539、福建民本信息科技有限公司
- 540、杭州惠合信息科技有限公司
- 541、厦门爱立得科技有限公司
- 542、成都拟合未来科技有限公司
- 543、宁波聚臻智能科技有限公司
- 544、广东百慧科技有限公司
- 545、笨马网络
- 546、深圳市信安数字科技有限公司
- 547、深圳市思乐数据技术有限公司
- 548、四川绿源集科技有限公司
- 549、湖南云医链生物科技有限公司
- 550、杭州源诚科技有限公司
- 551、北京开课吧科技有限公司
- 552、北京多来点信息技术有限公司
- 553、JEECG BOOT低代码开发平台
- 554、苏州同元软控信息技术有限公司
- 555、江苏大泰信息技术有限公司
- 556、北京大禹汇智
- 557、北京盛哲科技有限公司
- 558、广州钛动科技有限公司
- 559、北京大禹汇智科技有限公司
- 560、湖南鼎翰文化股份有限公司
- 561、苏州安软信息科技有限公司
- 562、芒果tv
- 563、上海艺赛旗软件股份有限公司
- 564、中盈优创资讯科技有限公司
- 565、乐乎公寓
- 566、启明信息
- 567、苏州安软
- 568、南京富金的软件科技有限公司
- 569、深圳市新科聚合网络技术有限公司
- 570、你好现在(北京)科技股份有限公司
- 571、360考试宝典
- 572、北京一零科技有限公司
- 573、厦门星纵信息
- 574、Dalligent Solusi Indonesia
- 575、深圳华普物联科技有限公司
- 576、深圳行健自动化股份有限公司
- 577、深圳市富融信息科技服务有限公司
- 578、蓝鸟云
- 579、上海澎博财经资讯有限公司
- 580、北京小鸦科技有限公司
- 581、杭州盈泉云科技有限公司
- 582、惟客数据
- 583、GOSO香蜜闺秀
- 584、普乐师上海数字科技有限公司
- 585、西安市雁塔区咖北堂网络科技部
- 586、宁波聚臻智能科技有限公司
- 587、普乐师数字科技有限公司
- 588、江苏蟹联网科技有限公司
- 589、杭州未智科技有限公司
- 590、安吉智行物流有限公司
- 591、华生大家居集团有限公司
- 592、美心食品广州有限公司
- 593、货拉拉【货拉拉APP】
- 594、杭州思韬瑞科技有限公司
- 595、杭州玖融科技有限公司
- 596、北京优海网络科技有限公司
- 597、浙江大维高新技术股份有限公司
- 598、粤港澳大湾区数字经济研究院
- 599、普康杭州健康科技有限公司
- 600、华西证券股份有限公司【华西证券】
- 601、杭州海康机器人股份有限公司【海康】
- 602、河南宸邦信息技术有限公司
- 603、成都次元节点网络科技有限公司
- 604、富士康科技集团【富士康】
- 605、青岛东软载波科技股份有限公司
- 606、小菊快跑科技有限公司
- 607、视源股份
- 608、宁波聚臻智能科技有限公司
- 609、阔天科技有限公司
- 610、网宿科技有限公司
- 611、南京梵鼎信息技术有限公司
- 612、房天下【房天下】
- 613、特瓦特能源科技有限公司
- 614、拓迪智能科技有限公司
- 615、东软集团【东软】
- 616、开普云
- 617、领课网络
- 618、南京特维软件有限公司
- 619、福建易联众保睿通信息科技有限公司
- 620、浙江核心同花顺金融科技有限公司【同花顺】
- 621、浙江博观瑞思科技有限公司
- 622、北京新美互通科技有限公司
- 623、北京有生博大软件股份有限公司
- 624、时代中国
- 625、鱼泡网
- 626、一粒方糖安徽科技有限公司
- 627、北京外研在线数字科技有限公司
- 628、德电中国通信技术有限公司
- 629、杭州寻联网络科技有限公司
- 630、橙联中国有限公司
- 631、北京承启通科技有限公司
- 632、银联数据服务有限公司【银联】
- 633、上海晶确科技有限公司
- 634、亚信科技有限公司
- 635、福建新航物联网科技有限公司
- 636、上扬软件
- 637、深蓝汽车科技有限公司
- 638、南昌节点汇智科技有限公司
- 639、锐明技术
- 640、再造再生健康科技有限公司
- 641、华宝证券
- 642、卓正医疗
- 643、深圳湛信科技
- 644、陕西鑫众为软件有限公司
- 645、深圳市润农科技有限公司
- 646、庚商教育智能科技有限公司
- 647、杭州祎声科技
- 648、四川久远银海软件股份有限公司
- 649、GeeFox极狐低代码
- 650、浙江和仁科技股份有限公司
- 651、宁波聚臻智能科技有限公司
- 652、福建福昕软件开发股份有限公司【福昕】
- 653、广州中长康达信息技术有限公司
- 654、武汉趣改信息科技有限公司
- 655、北京华夏思源科技发展有限公司
- 656、宁波关关通科技有限公司
- 657、青岛吕氏餐饮有限公司
- 658、杭州乐刻网络科技有限公司
- 659、上海红瓦信息科技有限公司
- 660、陕西旅小宝信息科技有限公司
- 661、中科卓恒(大连)科技有限公司
- 662、北京华益精点生物技术有限公司
- 663、马士基中国航运有限公司【马士基】
- 664、陕西美咚网络科技有限公司
- 665、山东新北洋信息技术股份有限公司
- 666、福建中瑞文化发展集团有限公司
- 667、黑龙江省建工集团有限责任公司【黑龙江省建工】
- 668、志信能达安全科技(广州)有限公司
- 669、重庆开源共创科技有限公司
- 670、华泰人寿保险股份有限公司【华泰人寿】
- 671、成都盘古纵横集团
- 672、北京果果乐学科技有限公司
- 673、北京凌云空间科技有限公司
- 674、临工重机股份有限公司
- 675、上海热风时尚管理集团【热风】
- 676、HashKey Exchange
- 677、傲基深圳跨境商务股份有限公司
- 678、青岛文达通科技股份有限公司
- 679、杭州普罗云科技有限公司
- 680、浙江云鹭科技有限公司
- 681、中山市芯宏柿网络科技有限公司
- 682、深圳市家家顺物联科技
- 683、重庆斑西科技有限公司
- 684、福建省泰古信息技术有限公司
- 685、贵阳永青仪电科技有限公司
- 686、广州博依特智能信息科技有限公司
- 687、河南宠呦呦信息技术有限公司
- 688、陕西星邑空间技术有限公司
- 689、广东西欧克实业有限公司
- 690、唱吧麦颂KTV
- 691、联通云
- 692、北京爱话本科技有限公司
- 693、北京起创科技有限公司
- 694、平安证券【平安证券】
- 695、合肥中科类脑智能技术有限公司
- 696、南京同仁堂健康产业有限公司【同仁堂】
- 697、铜仁市碧江区智惠加油站
- 698、惟客数据
- 699、凤凰新闻【凤凰新闻】
- 700、深圳王力智能
- 701、返利网数字科技股份有限公司
- 702、上海阜能信息科技有限公司
- 703、深圳市极能超电数字科技有限公司
- 704、海目星激光科技集团股份有限公司
- 705、安克创新科技股份有限公司【安克】
- 706、大庆点神科技有限公司
- 707、浙江零跑科技股份有限公司【零跑】
- 708、成都成电金盘健康数据技术有限公司
- 709、成都极米科技股份有限公司【极米】
- 710、顺德职业技术大学
- 711、中邮证券有限责任公司【中邮证券】
- 712、志豪链云科技有限公司
- 713、湖南万鲸科技有限公司
- 714、广州万表
- 715、再惠上海网络科技有限公司
- 716、上海爱诚裕信息科技有限公司
- 717、杭州迈瑞数字科技有限公司
- 718、广州串联网络科技有限公司
- 719、上海乐研化学试剂
- 720、智现未来
- 721、大庆点神科技有限公司
- 722、深圳市中科环球科技有限公司
- 723、江苏金箭车业制造有限公司
- ……
> 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.
@ -361,7 +806,7 @@ Source repository address | Release Download
<dependency>
<groupId>com.xuxueli</groupId>
<artifactId>xxl-job-core</artifactId>
<version>1.8.2</version>
<version>${version}</version>
</dependency>
```
@ -370,11 +815,8 @@ Source repository address | Release Download
- [Gitter](https://gitter.im/xuxueli/xxl-job)
### 1.5 Environment
- JDK1.7+
- Servlet/JSP Spec3.1/2.3
- Tomcat8.5.x/Jetty9.2.x
- Spring-boot1.5.x/Spring4.x
- Mysql5.6+
- JDK1.8+
- Mysql5.7+
- Maven3+
@ -1003,7 +1445,7 @@ The scheduling center provides API services for executors and business parties t
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 location: com.xxl.job.core.openapi.AdminBiz.java
The scheduling center API service requests reference codecom.xxl.job.adminbiz.AdminBizTest.java
@ -1215,17 +1657,34 @@ Tips: V1.3.x release has been published , enter the maintenance phase, branch a
- 2,Standardize project directory for extend multi executors;
- 3,add JFinal type executor sample project;
### TODO LIST
- 1,Task privilege management:control privilege on executor, check privilege on core operations;
- 2,Task slice routing:using consistent Hash algorithm to calculate slice order as stable as possible, even if there is fluctuation in the registration machine will not cause large fluctuations in the order of slice. Currently using IP natural sorting can meet the demandto be determined;
- 3,Failure retry optimization:The current failure to retry logic is execute the request logic once again after the scheduled request fails。The optimization point is retry for both scheduling and execution failures, retry a full schedule when retryingThis may lead schedule failure to an infinite loopto be determined;
- 4,write file when callback failedread the log when viewing the logcallback confirm after rebooting;
- 5,Task dependencyflow chartchild task + aggregation tasklog of each node;
- 6,Scheduled task priority;
- 7,Remove quartz dependencies and rewrite scheduld module:insert the next execution record into delayqueue when add or resume task, schedule center cluster compete distributed locksuccessful nodes bulk load expired delayqueue data and batch execution;
- 8,springboot and docker imageand push docker image to the central warehousefurther realize product out of the box;
- 9,globalization:schedule center interface and Official documentsadd English version;
- 10,executor removal:notify schedule center and remove the corresponding execute node when executor is destroyed, improve the timeliness of executor state recognized;
### 6.43 version v3.3.2 Release Notes[2026-01-01]
- 1、【Optimization】Graceful Shutdown: When the scheduling center shuts down, it actively waits for scheduling completion when detecting non-empty time wheels; when the client shuts down, it stops receiving new tasks and actively waits for task execution completion when detecting running tasks;
- 2、【New Feature】Docker Compose Configuration: Added Docker Compose configuration to support one-click configuration and startup of scheduling center clusters;
<details>
<summary>Docker Compose startup steps</summary>
```
// Download XXL-JOB
git clone --branch "$(curl -s https://api.github.com/repos/xuxueli/xxl-job/releases/latest | jq -r .tag_name)" https://github.com/xuxueli/xxl-job.git
// Build XXL-JOB
mvn clean package -Dmaven.test.skip=true
// Start XXL-JOB
MYSQL_PATH={自定义数据库持久化目录} docker compose up -d
// Stop XXL-JOB
docker compose down
```
</details>
- 3、【Optimization】 Scheduling Center Operation Experience: Table interaction adjusted to single-row selection mode; disabled pagination cycling; optimized pagination limit text;
- 4、【Optimization】 Scheduling Thread Transaction Commit Logic: Adjusted to avoid thread abnormal exit under edge conditions, enhancing robustness;
- 5、【Optimization】 Scheduling Log List Sorting Logic: Optimized for improved readability;
- 6、【Optimization】 Scheduling Center OpenAPI Communication Token: Adjusted to optional instead of required; merged PR-3892;
- 7、【Optimization】 Executor Detail Interface Permissions: Adjusted to support regular users viewing registered nodes; merged PR-3882;
- 8、【Optimization】 Task Parameter LogDateTime Generation Logic: Adjusted to ensure consistency for the same batch of scheduling in sharding broadcast scenarios;
- 9、【Upgrade】 Maven Dependencies: Upgraded multiple dependencies to newer versions, such as spring, netty, xxl-sso, xxl-tool, etc.;
- 10、【Optimization】 Unified Project Dependency Management Structure: Unified dependency versions to parent pom for improved maintainability;
## 7. Other

@ -1,10 +1,10 @@
## 《分布式任务调度平台XXL-JOB》
[![Actions Status](https://github.com/xuxueli/xxl-job/workflows/Java%20CI/badge.svg)](https://github.com/xuxueli/xxl-job/actions)
[![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.xuxueli/xxl-job/badge.svg)](https://maven-badges.herokuapp.com/maven-central/com.xuxueli/xxl-job/)
[![Build Status](https://github.com/xuxueli/xxl-job/workflows/Java%20CI/badge.svg)](https://github.com/xuxueli/xxl-job/actions)
[![Maven Central](https://img.shields.io/maven-central/v/com.xuxueli/xxl-job-core)](https://central.sonatype.com/artifact/com.xuxueli/xxl-job-core)
[![GitHub release](https://img.shields.io/github/release/xuxueli/xxl-job.svg)](https://github.com/xuxueli/xxl-job/releases)
[![GitHub stars](https://img.shields.io/github/stars/xuxueli/xxl-job)](https://github.com/xuxueli/xxl-job/)
[![Docker Status](https://img.shields.io/docker/pulls/xuxueli/xxl-job-admin)](https://hub.docker.com/r/xuxueli/xxl-job-admin/)
[![Docker pulls](https://img.shields.io/docker/pulls/xuxueli/xxl-job-admin)](https://hub.docker.com/r/xuxueli/xxl-job-admin/)
[![License](https://img.shields.io/badge/license-GPLv3-blue.svg)](http://www.gnu.org/licenses/gpl-3.0.html)
[![donate](https://img.shields.io/badge/%24-donate-ff69b4.svg?style=flat)](https://www.xuxueli.com/page/donate.html)
@ -51,12 +51,16 @@ XXL-JOB是一个分布式任务调度平台其核心设计目标是开发迅
- 28、推送maven中央仓库: 将会把最新稳定版推送到maven中央仓库, 方便用户接入和使用;
- 29、运行报表支持实时查看运行数据如任务数量、调度次数、执行器数量等以及调度报表如调度日期分布图调度成功分布图等
- 30、全异步任务调度流程全异步化设计实现如异步调度、异步运行、异步回调等有效对密集调度进行流量削峰理论上支持任意时长任务的运行
- 31、跨语言:调度中心与执行器提供语言无关的 RESTful API 服务,第三方任意语言可据此对接调度中心或者实现执行器。除此之外,还提供了 “多任务模式”和“httpJobHandler”等其他跨语言方案
- 31、跨语言/OpenAPI调度中心与执行器提供语言无关的 OpenApiRESTful 格式),第三方任意语言可据此对接调度中心或者实现执行器,实现多语言支持。除此之外,还提供了 “多任务模式”和“httpJobHandler”等其他跨语言方案
- 32、国际化调度中心支持国际化设置提供中文、英文两种可选语言默认为中文
- 33、容器化提供官方docker镜像并实时更新推送dockerhub进一步实现产品开箱即用
- 34、线程池隔离调度线程池进行隔离拆分慢任务自动降级进入"Slow"线程池,避免耗尽调度线程,提高系统稳定性;
- 35、用户管理支持在线管理系统用户存在管理员、普通用户两种角色
- 36、权限控制执行器维度进行权限控制管理员拥有全量权限普通用户需要分配执行器权限后才允许相关操作
- 37、AI任务原生提供AI执行器并内置多个AI任务Handler与spring-ai、ollama、dify等集成打通支持快速开发AI类任务。
- 38、审计日志记录任务操作敏感信息用于系统监控、审计和安全分析可快速追溯异常行为以及定位排查问题。
- 39、优雅停机调度中心停机检测时间轮非空时主动等待调度完成客户端停机检测存在运行中任务时停止接收新任务并主动等待任务执行完成
### 1.4 发展
于2015年中我在github上创建XXL-JOB项目仓库并提交第一个commit随之进行系统结构设计UI选型交互设计……
@ -90,7 +94,7 @@ XXL-JOB是一个分布式任务调度平台其核心设计目标是开发迅
于2021-12-06XXL-JOB参与"[2021年度OSC中国开源项目评选](https://www.oschina.net/project/top_cn_2021) "评比,在当时已录入的一万多个开源项目中角逐,最终当选"最受欢迎项目"。
> 我司大众点评目前已接入XXL-JOB内部别名《Ferrari》Ferrari基于XXL-JOB的V1.1版本定制而成,新接入应用推荐升级最新版本)。
据最新统计, 自2016-01-21接入至2017-12-01期间该系统已调度约100万次表现优异。新接入应用推荐使用最新版本因为经过数十个版本的更新系统的任务模型、UI交互模型以及底层调度通讯模型都有了较大的优化和提升核心功能更加稳定高效。
> 据最新统计, 自2016-01-21接入至2017-12-01期间该系统已调度约100万次表现优异。新接入应用推荐使用最新版本因为经过数十个版本的更新系统的任务模型、UI交互模型以及底层调度通讯模型都有了较大的优化和提升核心功能更加稳定高效。
至今XXL-JOB已接入多家公司的线上产品线接入场景如电商业务O2O业务和大数据作业等截止最新统计时间为止XXL-JOB已接入的公司包括不限于
@ -782,7 +786,41 @@ XXL-JOB是一个分布式任务调度平台其核心设计目标是开发迅
- 686、广州博依特智能信息科技有限公司
- 687、河南宠呦呦信息技术有限公司
- 688、陕西星邑空间技术有限公司
- 689、广东西欧克实业有限公司
- 690、唱吧麦颂KTV
- 691、联通云
- 692、北京爱话本科技有限公司
- 693、北京起创科技有限公司
- 694、平安证券【平安证券】
- 695、合肥中科类脑智能技术有限公司
- 696、南京同仁堂健康产业有限公司【同仁堂】
- 697、铜仁市碧江区智惠加油站
- 698、惟客数据
- 699、凤凰新闻【凤凰新闻】
- 700、深圳王力智能
- 701、返利网数字科技股份有限公司
- 702、上海阜能信息科技有限公司
- 703、深圳市极能超电数字科技有限公司
- 704、海目星激光科技集团股份有限公司
- 705、安克创新科技股份有限公司【安克】
- 706、大庆点神科技有限公司
- 707、浙江零跑科技股份有限公司【零跑】
- 708、成都成电金盘健康数据技术有限公司
- 709、成都极米科技股份有限公司【极米】
- 710、顺德职业技术大学
- 711、中邮证券有限责任公司【中邮证券】
- 712、志豪链云科技有限公司
- 713、湖南万鲸科技有限公司
- 714、广州万表
- 715、再惠上海网络科技有限公司
- 716、上海爱诚裕信息科技有限公司
- 717、杭州迈瑞数字科技有限公司
- 718、广州串联网络科技有限公司
- 719、上海乐研化学试剂
- 720、智现未来
- 721、大庆点神科技有限公司
- 722、深圳市中科环球科技有限公司
- 723、江苏金箭车业制造有限公司
- ……
> 更多接入的公司,欢迎在 [登记地址](https://github.com/xuxueli/xxl-job/issues/1 ) 登记,登记仅仅为了产品推广。
@ -803,7 +841,7 @@ XXL-JOB是一个分布式任务调度平台其核心设计目标是开发迅
--- | ---
[https://github.com/xuxueli/xxl-job](https://github.com/xuxueli/xxl-job) | [Download](https://github.com/xuxueli/xxl-job/releases)
[http://gitee.com/xuxueli0323/xxl-job](http://gitee.com/xuxueli0323/xxl-job) | [Download](http://gitee.com/xuxueli0323/xxl-job/releases)
[https://gitcode.com/xuxueli0323/xxl-job](https://gitcode.com/xuxueli0323/xxl-job) | [Download](https://gitcode.com/xuxueli0323/xxl-job/tags)
[https://gitcode.com/xuxueli/xxl-job](https://gitcode.com/xuxueli/xxl-job) | [Download](https://gitcode.com/xuxueli/xxl-job/tags)
#### 中央仓库地址
@ -819,9 +857,9 @@ XXL-JOB是一个分布式任务调度平台其核心设计目标是开发迅
### 1.6 环境
- Maven3+
- Jdk1.8+
- Mysql8.0+
- Maven3+
- Jdk17+ (说明版本3.x及以上要求Jdk17+版本2.x及以下支持Jdk1.8)
- Mysql8.0+
## 二、快速入门
@ -888,8 +926,11 @@ xxl.job.timeout=3
xxl.job.i18n=zh_CN
## 调度线程池最大线程配置【必填】
xxl.job.triggerpool.fast.max=200
xxl.job.triggerpool.slow.max=100
xxl.job.triggerpool.fast.max=300
xxl.job.triggerpool.slow.max=200
### 调度触发后,批量更新任务批次数量【必填】
xxl.job.schedule.batchsize=100
### 调度中心日志表数据保存天数 [必填]过期日志自动清理限制大于等于7时生效否则, 如-1关闭自动清理功能
xxl.job.logretentiondays=30
@ -921,19 +962,28 @@ xxl.job.logretentiondays=30
- 下载镜像
```
// Docker地址https://hub.docker.com/r/xuxueli/xxl-job-admin/ (建议指定版本号)
docker pull xuxueli/xxl-job-admin
/**
* Docker地址https://hub.docker.com/r/xuxueli/xxl-job-admin/
* 建议指定版本号拉取镜像;
*/
docker pull xuxueli/xxl-job-admin:{指定版本}
```
- 创建容器并运行
```
/**
* 如需自定义 mysql 等配置,可通过 "-e PARAMS" 指定,参数格式 PARAMS="--key=value --key2=value2"
* 配置项参考文件:/xxl-job/xxl-job-admin/src/main/resources/application.properties
* 如需自定义 JVM内存参数 等配置,可通过 "-e JAVA_OPTS" 指定,参数格式 JAVA_OPTS="-Xmx512m"
* 如需自定义 “项目配置文件” 中配置项,比如 mysql 配置,可通过 "-e PARAMS" 指定,参数格式: -e PARAMS="--key=value --key2=value2"
* (配置项参考文件:/xxl-job/xxl-job-admin/src/main/resources/application.properties
* 如需自定义 “JVM内存参数”可通过 "-e JAVA_OPTS" 指定,参数格式: -e JAVA_OPTS="-Xmx512m"
* 如需自定义 “日志文件目录”,可通过 "-e LOG_HOME" 指定,参数格式: -e LOG_HOME=/data/applogs
*/
docker run -e PARAMS="--spring.datasource.url=jdbc:mysql://127.0.0.1:3306/xxl_job?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghai" -p 8080:8080 -v /tmp:/data/applogs --name xxl-job-admin -d xuxueli/xxl-job-admin:{指定版本}
docker run -d \
-e PARAMS="--spring.datasource.url=jdbc:mysql://127.0.0.1:3306/xxl_job?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghai" \
-p 8080:8080 \
-v /tmp:/data/applogs \
--name xxl-job-admin \
xuxueli/xxl-job-admin:{指定版本}
```
@ -957,18 +1007,18 @@ docker run -e PARAMS="--spring.datasource.url=jdbc:mysql://127.0.0.1:3306/xxl_jo
```
### 调度中心部署根地址 [选填]:如调度中心集群部署存在多个地址则用逗号分隔。执行器将会使用该地址进行"执行器心跳注册"和"任务结果回调";为空则关闭自动注册;
xxl.job.admin.addresses=http://127.0.0.1:8080/xxl-job-admin
### 调度中心通讯TOKEN [选填]:非空时启用;
xxl.job.admin.accessToken=default_token
### 调度中心通讯超时时间[选填]单位秒默认3s
xxl.job.admin.timeout=3
### 执行器启用开关 [选填]:默认开启,关闭时不进行执行器初始化;
xxl.job.executor.enabled=true
### 执行器AppName [选填]:执行器心跳注册分组依据;为空则关闭自动注册
xxl.job.executor.appname=xxl-job-executor-sample
### 执行器注册 [选填]:优先使用该配置作为注册地址,为空时使用内嵌服务 ”IP:PORT“ 作为注册地址。从而更灵活的支持容器类型执行器动态IP和动态映射端口问题。
xxl.job.executor.address=
### 执行器IP [选填]默认为空表示自动获取IP多网卡时可手动设置指定IP该IP不会绑定Host仅作为通讯用;地址信息用于 "执行器注册" 和 "调度中心请求并触发任务"
### 执行器IP [选填]默认为空表示自动获取IP多网卡时可手动设置指定IP该IP不会绑定Host仅作为通讯使用;地址信息用于 "执行器注册" 和 "调度中心请求并触发任务"
xxl.job.executor.ip=
### 执行器端口号 [选填]小于等于0则自动获取默认端口为9999单机部署多个执行器时注意要配置不同执行器端口
xxl.job.executor.port=9999
@ -976,6 +1026,8 @@ xxl.job.executor.port=9999
xxl.job.executor.logpath=/data/applogs/xxl-job/jobhandler
### 执行器日志文件保存天数 [选填] 过期日志自动清理, 限制值大于等于3时生效; 否则, 如-1, 关闭自动清理功能;
xxl.job.executor.logretentiondays=30
### 任务扫描排除路径 [选填] 任务扫描时忽略指定包路径下的Bean支持配置包路径前缀多个逗号分隔
xxl.job.executor.excludedpackage=org.springframework,spring
```
#### 步骤三:执行器组件配置
@ -1004,7 +1056,7 @@ public XxlJobSpringExecutor xxlJobExecutor() {
```
#### 步骤四:部署执行器项目:
如果已经正确进行上述配置可将执行器项目编译打部署系统提供多种执行器Sample示例项目选择其中一个即可各自的部署方式如下。
如果已经正确进行上述配置,可将执行器项目编译打部署系统提供多种执行器Sample示例项目选择其中一个即可各自的部署方式如下。
xxl-job-executor-sample-springboot项目编译打包成springboot类型的可执行JAR包命令启动即可
xxl-job-executor-sample-frameless项目编译打包成JAR包命令启动即可
@ -1035,8 +1087,8 @@ public XxlJobSpringExecutor xxlJobExecutor() {
#### 步骤二“GLUE模式(Java)” 任务开发:
请点击任务右侧 “GLUE” 按钮,进入 “GLUE编辑器开发界面” 见下图。“GLUE模式(Java)” 运行模式的任务默认已经初始化了示例任务代码即打印Hello World。
“GLUE模式(Java)” 运行模式的任务实际上是一段继承自IJobHandler的Java类代码它在执行器项目中运行可使用@Resource/@Autowire注入执行器中的其他服务,详细介绍请查看第三章节)
请点击任务右侧 “GLUE IDE” 按钮,进入 “GLUE编辑器开发界面” 见下图。“GLUE模式(Java)” 运行模式的任务默认已经初始化了示例任务代码即打印Hello World。
“GLUE模式(Java)” 运行模式的任务实际上是一段继承自IJobHandler的Java类代码它在执行器项目中运行可使用@Resource/@Autowire注入执行器中的其他服务详细介绍请查看第三章节
![输入图片说明](https://www.xuxueli.com/doc/static/xxl-job/images/img_Fgql.png "在这里输入图片标题")
@ -1055,6 +1107,7 @@ public XxlJobSpringExecutor xxlJobExecutor() {
![输入图片说明](https://www.xuxueli.com/doc/static/xxl-job/images/img_eYrv.png "在这里输入图片标题")
## 三、任务详解
### 配置属性详细说明:
@ -1078,7 +1131,7 @@ public XxlJobSpringExecutor xxlJobExecutor() {
任务配置:
- 运行模式:
BEAN模式任务以JobHandler方式维护在执行器端需要结合 "JobHandler" 属性匹配执行器中任务;
GLUE模式(Java)任务以源码方式维护在调度中心该模式的任务实际上是一段继承自IJobHandler的Java类代码并 "groovy" 源码方式维护,它在执行器项目中运行,可使用@Resource/@Autowire注入执行器中的其他服务;
GLUE模式(Java)任务以源码方式维护在调度中心该模式的任务实际上是一段继承自IJobHandler的Java类代码并 "groovy" 源码方式维护,它在执行器项目中运行,可使用@Resource/@Autowire注入执行器中的其他服务
GLUE模式(Shell):任务以源码方式维护在调度中心;该模式的任务实际上是一段 "shell" 脚本;
GLUE模式(Python):任务以源码方式维护在调度中心;该模式的任务实际上是一段 "python" 脚本;
GLUE模式(PHP):任务以源码方式维护在调度中心;该模式的任务实际上是一段 "php" 脚本;
@ -1154,7 +1207,7 @@ Bean模式任务支持基于方法的开发方式每个任务对应一个
4、任务结果默认任务结果为 "成功" 状态,不需要主动设置;如有诉求,比如设置任务结果为失败,可以通过 "XxlJobHelper.handleFail/handleSuccess" 自主设置任务结果;
```
// 可参考Sample示例执行器中的 "com.xxl.job.executor.service.jobhandler.SampleXxlJob" ,如下:
// 可参考Sample示例执行器中的 "com.xxl.job.executor.jobhandler.SampleXxlJob" ,如下:
@XxlJob("demoJobHandler")
public void demoJobHandler() throws Exception {
XxlJobHelper.log("XXL-JOB, Hello World.");
@ -1166,18 +1219,106 @@ public void demoJobHandler() throws Exception {
![输入图片说明](https://www.xuxueli.com/doc/static/xxl-job/images/img_ZAsz.png "在这里输入图片标题")
#### 原生内置Bean模式任务
为方便用户参考与快速实用示例执行器内原生提供多个Bean模式任务Handler可以直接配置实用,如下:
#### 原生内置Bean模式任务(通用执行器)
为方便用户参考与快速使用,提供 “通用执行器” 并内置多个Bean模式任务Handler可以直接配置使用,如下:
- demoJobHandler简单示例任务任务内部模拟耗时任务逻辑用户可在线体验Rolling Log等功能
- shardingJobHandler分片示例任务任务内部模拟处理分片参数可参考熟悉分片任务
- httpJobHandler通用HTTP任务Handler业务方只需要提供HTTP链接等信息即可不限制语言、平台。示例任务入参如下
```
url: http://www.xxx.com
method: get 或 post
data: post-data
```
- commandJobHandler通用命令行任务Handler业务方只需要提供命令行即可如 “pwd”命令
**通用执行器说明:**
- AppNamexxl-job-executor-sample
- 执行器代码:
- xxl-job-executor-sample-springbootspringboot版本
- xxl-job-executor-sample-frameless无框架版本
**执行器内置任务列表:**
- a、demoJobHandler简单示例任务任务内部模拟耗时任务逻辑用户可在线体验Rolling Log等功能
- b、shardingJobHandler分片示例任务任务内部模拟处理分片参数可参考熟悉分片任务
- c、httpJobHandler通用HTTP任务Handler业务方只需要提供HTTP链接等信息即可不限制语言、平台。任务入参示例如下
```
// 1、简单示例
{
"url": "http://www.baidu.com",
"method": "GET",
"data": "hello world"
}
// 2、完整参数示例
{
"url": "http://www.baidu.com", // 请求URL
"method": "POST", // 请求方法支持GET、POST、HEAD、OPTIONS、PUT、DELETE、TRACE
"contentType": "application/json", // 请求内容类型支持application/json、application/x-www-form-urlencoded、application/xml、text/html、text/xml、text/plain
"headers": { // 请求Headerkey-value结构
"header01": "value01"
},
"cookies": { // 请求Cookiekey-value结构
"cookie01": "value01"
},
"timeout": 3000, // 请求超时时间,默认 3000单位毫秒
"data": "request body data", // 请求Body数据仅针对 POST 请求有效
"form": { // 请求Form数据仅针对 GET 请求有效
"key01": "value01"
},
"auth": "auth data" // 请求认证信息, 通过Basic Auth方式认证
}
```
- d、commandJobHandler通用命令行任务Handler业务方只需要提供命令行即可命令及参数之间通过空格隔开如任务参数 "ls la" 或 "pwd" 将会执行命令并输出数据;
#### 原生内置Bean模式任务AI执行器
为方便用户参考与快速使用,提供 “AI执行器” 并内置多个Bean模式 AI任务Handler与spring-ai、ollama、dify等集成打通支持快速开发AI类任务如下
**AI执行器说明**
- AppNamexxl-job-executor-sample-ai
- 执行器代码xxl-job-executor-sample-springboot-ai
**执行器内置任务列表:**
- a、ollamaJobHandler OllamaChat任务支持自定义prompt、input等输入信息。示例任务入参如下
```
{
"input": "{输入信息,必填信息}",
"prompt": "{模型prompt可选信息}",
"model": "{模型实现如qwen3.5:2b可选信息}"
}
```
- b、difyWorkflowJobHandlerDifyWorkflow 任务支持自定义inputs、user、baseUrl、apiKey 等输入信息,示例参数如下;
```
{
"inputs":{ // inputs 为dify工作流任务参数参数不固定结合各自 workflow 自行定义。
"input":"{用户输入信息}" // 该参数为示例变量,需要 workflow 的“开始”节点 自定义参数 “input”可自行调整或删除。
},
"user": "xxl-job", // 用户标识,选填
"baseUrl": "http://localhost/v1", // Dify应用的 访问API 地址,需要从 Dify 系统获取;
"apiKey": "xxx" // Dify应用的 API-Key需要从 Dify 系统获取;
}
```
- c、openClawJobHandler OpenClaw任务支持自定义prompt、input等输入信息。示例任务入参如下
```
{
"input": "{输入信息,必填信息}",
"prompt": "{模型prompt可选信息}"
}
```
- 依赖1参考 [Ollama本地化部署大模型](https://www.xuxueli.com/blog/?blog=./notebook/13-AI/%E4%BD%BF%E7%94%A8Ollama%E6%9C%AC%E5%9C%B0%E5%8C%96%E9%83%A8%E7%BD%B2DeepSeek.md) 执行器示例部署“qwen2.5:1.5b”模型,也可自定选择其他模型版本。
- 依赖2参考 [使用DeepSeek与Dify搭建AI助手](https://www.xuxueli.com/blog/?blog=./notebook/13-AI/%E4%BD%BF%E7%94%A8DeepSeek%E4%B8%8EDify%E6%90%AD%E5%BB%BAAI%E5%8A%A9%E6%89%8B.md)执行器示例新建Dify DifyWork应用并在开始节点添加“input”参数可结合实际情况调整。
- 依赖3启动示例 “AI执行器” 相关配置文件说明如下:
```
// ollama 配置
spring.ai.ollama.base-url=http://localhost:11434
spring.ai.ollama.chat.enabled=true
// Model模型配置注意此处配置模型版本、必须本地先通过ollama进行安装运行。
spring.ai.ollama.chat.options.model=qwen2.5:1.5b
spring.ai.ollama.chat.options.temperature=0.8
// dify 配置;选择相关 workflow 应用,切换 “访问API” 页面获取 url 地址信息.
dify.base-url=http://localhost/v1
// dify api-key选择相关 workflow 应用并进入 “访问API” 页面,右上角 “API 密钥” 入口获取 api-key。
dify.api-key={自行获取并修改}
```
### 3.3 GLUE模式(Java)
@ -1189,7 +1330,7 @@ public void demoJobHandler() throws Exception {
![输入图片说明](https://www.xuxueli.com/doc/static/xxl-job/images/img_tJOq.png "在这里输入图片标题")
#### 步骤二:开发任务代码:
选中指定任务点击该任务右侧“GLUE”按钮将会前往GLUE任务的Web IDE界面在该界面支持对任务代码进行开发也可以在IDE中开发完成后复制粘贴到编辑中
选中指定任务点击该任务右侧“GLUE”按钮将会前往GLUE任务的Web IDE界面在该界面支持对任务代码进行开发也可以在IDE中开发完成后复制粘贴到编辑中)。
版本回溯功能支持30个版本的版本回溯在GLUE任务的Web IDE界面选择右上角下拉框“版本回溯”会列出该GLUE的更新历史选择相应版本即可显示该版本代码保存后GLUE代码即回退到对应的历史版本
@ -1201,7 +1342,7 @@ public void demoJobHandler() throws Exception {
参考上文“配置属性详细说明”对新建的任务进行参数配置,运行模式选中 "GLUE模式(Shell)"
#### 步骤二:开发任务代码:
选中指定任务点击该任务右侧“GLUE”按钮将会前往GLUE任务的Web IDE界面在该界面支持对任务代码进行开发也可以在IDE中开发完成后复制粘贴到编辑中
选中指定任务点击该任务右侧“GLUE”按钮将会前往GLUE任务的Web IDE界面在该界面支持对任务代码进行开发也可以在IDE中开发完成后复制粘贴到编辑中)。
该模式的任务实际上是一段 "shell" 脚本;
@ -1213,7 +1354,7 @@ public void demoJobHandler() throws Exception {
参考上文“配置属性详细说明”对新建的任务进行参数配置,运行模式选中 "GLUE模式(Python)"
#### 步骤二:开发任务代码:
选中指定任务点击该任务右侧“GLUE”按钮将会前往GLUE任务的Web IDE界面在该界面支持对任务代码进行开发也可以在IDE中开发完成后复制粘贴到编辑中
选中指定任务点击该任务右侧“GLUE”按钮将会前往GLUE任务的Web IDE界面在该界面支持对任务代码进行开发也可以在IDE中开发完成后复制粘贴到编辑中)。
该模式的任务实际上是一段 "python" 脚本;
@ -1225,7 +1366,7 @@ public void demoJobHandler() throws Exception {
参考上文“配置属性详细说明”对新建的任务进行参数配置,运行模式选中 "GLUE模式(NodeJS)"
#### 步骤二:开发任务代码:
选中指定任务点击该任务右侧“GLUE”按钮将会前往GLUE任务的Web IDE界面在该界面支持对任务代码进行开发也可以在IDE中开发完成后复制粘贴到编辑中
选中指定任务点击该任务右侧“GLUE”按钮将会前往GLUE任务的Web IDE界面在该界面支持对任务代码进行开发也可以在IDE中开发完成后复制粘贴到编辑中)。
该模式的任务实际上是一段 "nodeJS" 脚本;
@ -1258,7 +1399,7 @@ public void demoJobHandler() throws Exception {
注册方式:调度中心获取执行器地址的方式;
自动注册:执行器自动进行执行器注册,调度中心通过底层注册表可以动态发现执行器机器地址;
手动录入:人工手动录入执行器的地址信息,多地址逗号分隔,供调度中心使用;
机器地址:"注册方式"为"手动录入"时有效,支持人工维护执行器的地址信息;
机器地址:"注册方式"为"手动录入"时有效,支持人工维护执行器的地址信息;注册地址格式可参考“http://127.0.0.1:9999/”,为执行器内嵌服务地址;
### 4.2 新建任务
进入任务管理界面,点击“新增任务”按钮,在弹出的“新增任务”界面配置任务属性后保存即可。详情页参考章节 "三、任务详解"。
@ -1284,7 +1425,7 @@ public void demoJobHandler() throws Exception {
![输入图片说明](https://www.xuxueli.com/doc/static/xxl-job/images/img_ZAhX.png "在这里输入图片标题")
### 4.7 查看调度日志
点击“日志”按钮,可以查看任务历史调度日志。在历史调日志界面可查看每次任务调度的调度结果、执行结果等,点击“执行日志”按钮可查看执行器完整日志。
点击“日志”按钮,可以查看任务历史调度日志。在历史调日志界面可查看每次任务调度的调度结果、执行结果等,点击“执行日志”按钮可查看执行器完整日志。
![输入图片说明](https://www.xuxueli.com/doc/static/xxl-job/images/img_ZAhX.png "在这里输入图片标题")
@ -1356,6 +1497,7 @@ try{
![输入图片说明](https://www.xuxueli.com/doc/static/xxl-job/images/img_1002.png "在这里输入图片标题")
## 五、总体设计
### 5.1 源码目录介绍
- /doc :文档资料
@ -1381,7 +1523,7 @@ XXL-JOB调度模块基于自研调度组件并支持集群部署调度数据
#### 5.3.1 设计思想
将调度行为抽象形成“调度中心”公共平台,而平台自身并不承担业务逻辑,“调度中心”负责发起调度请求。
将任务抽象成分散的JobHandler交由“执行器”统一管理“执行器”负责接收调度请求并执行对应的JobHandler中业务逻辑。
将任务抽象成分散的JobHandler交由“执行器”统一管理“执行器”负责接收调度请求并执行对应的JobHandler中业务逻辑。
因此,“调度”和“任务”两部分可以相互解耦,提高系统整体稳定性和扩展性;
@ -1401,7 +1543,7 @@ XXL-JOB调度模块基于自研调度组件并支持集群部署调度数据
#### 5.4.1 quartz的不足
Quartz作为开源作业调度中的佼佼者是作业调度的首选。但是集群环境中Quartz采用API的方式对任务进行管理从而可以避免上述问题但是同样存在以下问题
- 问题一调用API的方式操作任务,不人性化;
- 问题一调用API的方式操作任务不人性化
- 问题二需要持久化业务QuartzJobBean到底层数据表中系统侵入性相当严重。
- 问题三调度逻辑和QuartzJobBean耦合在同一个项目中这将导致一个问题在调度任务数量逐渐增多同时调度任务逻辑逐渐加重的情况下此时调度系统的性能将大大受限于业务
- 问题四quartz底层以“抢占式”获取DB锁并由抢占成功节点负责运行任务会导致节点负载悬殊非常大而XXL-JOB通过执行器实现“协同分配式”运行任务充分发挥集群优势负载各节点均衡。
@ -1414,7 +1556,7 @@ XXL-JOB最终选择自研调度组件早期调度组件基于Quartz
XXL-JOB中“调度模块”和“任务模块”完全解耦调度模块进行任务调度时将会解析不同的任务参数发起远程调用调用各自的远程执行器服务。这种调用模型类似RPC调用调度中心提供调用代理的功能而执行器提供远程服务的功能。
#### 5.4.3 调度中心HA集群
基于数据库的集群方案数据库选用Mysql集群分布式并发环境中进行定时任务调度时会在各个节点上报任务,存到数据库中,执行时会从数据库中取出触发器来执行,如果触发器的名称和执行时间相同,则只有一个节点去执行此任务。
基于数据库的集群方案数据库选用Mysql集群分布式并发环境中进行定时任务调度时会在各个节点上报任务存到数据库中执行时会从数据库中取出触发器来执行如果触发器的名称和执行时间相同则只有一个节点去执行此任务。
#### 5.4.4 调度线程池
调度采用线程池方式实现,避免单线程因阻塞而引起任务调度延迟。
@ -1458,7 +1600,7 @@ xxl-job-admin#com.xxl.job.admin.controller.JobApiController.callback
调度中心每次进行任务调度,都会记录一条任务日志,任务日志主要包括以下三部分内容:
- 任务信息包括“执行器地址”、“JobHandler”和“执行参数”等属性点击任务ID按钮可查看根据这些参数可以精确的定位任务执行的具体机器和任务代码
- 调度信息:包括“调度时间”、“调度结果”和“调度日志”等,根据这些参数,可以了解“调度中心”发起调度请求时具体情况。
- 调度信息:包括“调度时间”、“调度结果”和“调度日志”等,根据这些参数,可以了解“调度中心”发起调度请求时具体情况。
- 执行信息:包括“执行时间”、“执行结果”和“执行日志”等,根据这些参数,可以了解在“执行器”端任务执行的具体情况;
调度日志,针对单次调度,属性说明如下:
@ -1493,7 +1635,7 @@ xxl-job-admin#com.xxl.job.admin.controller.JobApiController.callback
得益于上述两点优化,理论上默认配置下的调度中心,单机能够支撑 5000 任务并发运行稳定运行;
实际场景中由于调度中心与执行器网络ping延迟不同、DB读写耗时不同、任务调度密集程度不同会导致任务量上限上下波动。
实际场景中由于调度中心与执行器网络ping延迟不同、DB读写耗时不同、任务调度密集程度不同会导致任务量上限上下波动。
如若需要支撑更多的任务量,可以通过 "调大调度线程数" 、"降低调度中心与执行器ping延迟" 和 "提升机器配置" 几种方式优化。
@ -1562,7 +1704,7 @@ XXL-JOB会为每次调度请求生成一个单独的日志文件需要通过
### 5.8 任务执行结果
自v1.6.2之后,任务执行结果通过 "IJobHandler" 的返回值 "ReturnT" 进行判断;
当返回值符合 "ReturnT.code == ReturnT.SUCCESS_CODE" 时表示任务执行成功,否则表示任务执行失败,而且可以通过 "ReturnT.msg" 回调错误信息给调度中心;
当返回值符合 "ReturnT#code == 200" 时表示任务执行成功,否则表示任务执行失败,而且可以通过 "ReturnT#msg" 回调错误信息给调度中心;
从而,在任务逻辑中可以方便的控制任务执行结果;
### 5.9 分片广播 & 动态分片
@ -1635,7 +1777,7 @@ echo "分片总数 total = $3"
### 5.15 跨语言
XXL-JOB是一个跨语言的任务调度平台主要体现在如下几个方面
- 1、RESTful API:调度中心与执行器提供语言无关的 RESTful API 服务,第三方任意语言可据此对接调度中心或者实现执行器。(可参考章节 “调度中心/执行器 RESTful API”
- 1、OpenApiRESTful 格式):调度中心与执行器提供语言无关的 RESTful API 服务,第三方任意语言可据此对接调度中心或者实现执行器,实现多语言支持。(可参考章节 “调度中心/执行器 RESTful API”
- 2、多任务模式提供Java、Python、PHP……等十来种任务模式可参考章节 “5.5 任务 "运行模式" ”;理论上可扩展任意语言任务模式;
- 2、提供基于HTTP的任务HandlerBean任务JobHandler="httpJobHandler"业务方只需要提供HTTP链接等相关信息即可不限制语言、平台可参考章节 “原生内置Bean模式任务”
@ -1644,10 +1786,30 @@ XXL-JOB是一个跨语言的任务调度平台主要体现在如下几个方
### 5.17 调度中心Docker镜像构建
可以通过以下命令快速构建调度中心,并启动运行;
```
/**
* build package
*/
mvn clean package
docker build -t xuxueli/xxl-job-admin:{version} ./xxl-job-admin
docker run --name xxl-job-admin -p 8080:8080 -d xuxueli/xxl-job-admin
/**
* build docker image
*/
docker build -t xuxueli/xxl-job-admin:{指定版本} ./xxl-job-admin
/**
* 如需自定义 “项目配置文件” 中配置项,比如 mysql 配置,可通过 "-e PARAMS" 指定,参数格式: -e PARAMS="--key=value --key2=value2"
* (配置项参考文件:/xxl-job/xxl-job-admin/src/main/resources/application.properties
* 如需自定义 “JVM内存参数”可通过 "-e JAVA_OPTS" 指定,参数格式: -e JAVA_OPTS="-Xmx512m"
* 如需自定义 “日志文件目录”,可通过 "-e LOG_HOME" 指定,参数格式: -e LOG_HOME=/data/applogs
*/
docker run -d \
-e PARAMS="--spring.datasource.url=jdbc:mysql://127.0.0.1:3306/xxl_job?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghai" \
-p 8080:8080 \
-v /tmp:/data/applogs \
--name xxl-job-admin \
xuxueli/xxl-job-admin:{指定版本}
```
### 5.20 避免任务重复执行
@ -1655,8 +1817,8 @@ docker run --name xxl-job-admin -p 8080:8080 -d xuxueli/xxl-job-admin
针对上述情况,可以通过结合 "单机路由策略(如:第一台、一致性哈希)" + "阻塞策略(如:单机串行、丢弃后续调度)" 来规避,最终避免任务重复执行。
### 5.21 命令行任务
原生提供通用命令行任务HandlerBean任务"CommandJobHandler");业务方只需要提供命令行即可;
如任务参数 "pwd" 将会执行命令并输出数据;
原生提供通用命令行任务HandlerBean任务"CommandJobHandler");业务方只需要提供命令行即可,命令及参数之间通过空格隔开
如任务参数 "ls la" 或 "pwd" 将会执行命令并输出数据;
### 5.22 日志自动清理
XXL-JOB日志主要包含如下两部分均支持日志自动清理说明如下
@ -1668,13 +1830,48 @@ XXL-JOB日志主要包含如下两部分均支持日志自动清理说明
针对该问题,调度中心提供内置组件进行处理,逻辑为:调度记录停留在 "运行中" 状态超过10min且对应执行器心跳注册失败不在线则将本地调度主动标记失败
### 5.24 Docker Compose 快速部署
支持通过 Docker Compose 方式部署并启动 XXL-JOB包括数据库、调度中心、示例执行器。
## 六、调度中心/执行器 RESTful API
- 第一步:克隆 XXL-JOB
```
git clone --branch "$(curl -s https://api.github.com/repos/xuxueli/xxl-job/releases/latest | jq -r .tag_name)" https://github.com/xuxueli/xxl-job.git
```
- 第二步:构建 XXL-JOB
```
// 注意:如下命令需要在项目仓库根目录执行
mvn clean package -Dmaven.test.skip=true
```
- 第三步:配置 XXL-JOB
```
// 注意前往docker目录自定义 .env 配置;如修改 MYSQL_PATH 配置设置Mysql数据持久化目录
cd ./docker
cat .env
```
- 第四步:启动 XXL-JOB
```
// 启动
docker compose up -d
// 停止
docker compose down
```
### 5.25 优雅停机
针对任务调度场景,优雅停机包括调度中心与执行器两部分:
- 调度中心优雅停机:调度中心停机时,如果检测到时间轮非空则主动等待等待一段时间,等待调度处理完成;否则立即停机;
- 执行器优雅停机:执行器停机时,如果检测到存在运行中任务,在停止接收新任务后会主动等待一段时间,等待任务执行完成;否则立即停机;
## 六、调度中心/执行器 OpenApi
XXL-JOB 目标是一种跨平台、跨语言的任务调度规范和协议。
针对Java应用可以直接通过官方提供的调度中心与执行器方便快速的接入和使用调度中心可以参考上文 “快速入门” 章节。
针对非Java应用可借助 XXL-JOB 的标准 RESTful API 方便的实现多语言支持。
针对非Java应用可借助 XXL-JOB 的标准 OpenApiRESTful API 方便的实现多语言支持。
- 调度中心 RESTful API
- 说明调度中心提供给执行器使用的API不局限于官方执行器使用第三方可使用该API来实现执行器
@ -1687,7 +1884,7 @@ XXL-JOB 目标是一种跨平台、跨语言的任务调度规范和协议。
### 6.1 调度中心 RESTful API
API服务位置com.xxl.job.core.biz.AdminBiz com.xxl.job.admin.controller.JobApiController
API服务位置com.xxl.job.core.openapi.AdminBiz com.xxl.job.admin.controller.JobApiController
API服务请求参考代码com.xxl.job.adminbiz.AdminBizTest
#### a、任务回调
@ -1769,7 +1966,7 @@ Header
### 6.2 执行器 RESTful API
API服务位置com.xxl.job.core.biz.ExecutorBiz
API服务位置com.xxl.job.core.openapi.ExecutorBiz
API服务请求参考代码com.xxl.job.executorbiz.ExecutorBizTest
#### a、心跳检测
@ -1831,7 +2028,7 @@ Header
"jobId":1, // 任务ID
"executorHandler":"demoJobHandler", // 任务标识
"executorParams":"demoJobHandler", // 任务参数
"executorBlockStrategy":"COVER_EARLY", // 任务阻塞策略,可选值参考 com.xxl.job.core.enums.ExecutorBlockStrategyEnum
"executorBlockStrategy":"COVER_EARLY", // 任务阻塞策略,可选值参考 com.xxl.job.core.constant.ExecutorBlockStrategyEnum
"executorTimeout":0, // 任务超时时间,单位秒,大于零时生效
"logId":1, // 本次调度日志ID
"logDateTime":1586629003729, // 本次调度日志时间
@ -2215,7 +2412,7 @@ Tips: 历史版本(V1.3.x)目前已经Release至稳定版本, 进入维护阶段
- 6、任务状态优化仅运行状态"NORMAL"任务关联至quartz降低quartz底层数据存储与调度压力
- 7、任务状态规范新增任务默认停止状态任务更新时保持任务状态不变
- 8、IP获取逻辑优化优先遍历网卡来获取可用IP
- 9、任务新增的API服务接口返回任务ID方便调用方用;
- 9、任务新增的API服务接口返回任务ID方便调用方使用;
- 10、组件化优化移除对 spring 的依赖非spring应用选用 "XxlJobExecutor" 、spring应用选用 "XxlJobSpringExecutor" 作为执行器组件;
- 11、任务RollingLog展示逻辑优化修复超时任务无法查看的问题
- 12、多项UI组件升级到最新版本CodeMirror、Echarts、Jquery 等;
@ -2312,7 +2509,7 @@ Tips: 历史版本(V1.3.x)目前已经Release至稳定版本, 进入维护阶段
@XxlJob("demoJobHandler")
public ReturnT<String> execute(String param) {
XxlJobLogger.log("hello world");
return ReturnT.SUCCESS;
return ReturnT.ofSuccess();
}
```
- 2、移除commons-exec采用原生方式实现降低第三方依赖
@ -2418,7 +2615,7 @@ public void execute() {
- 6、【修复】漏洞修复包括 "CVE-2024-42681" 子任务越权漏洞修复、"CVE-2023-33779" 任务API越权问题修复
- 7、【升级】多个项目依赖升级至较新稳定版本涉及netty、groovy、gson、springboot、mybatis等
### 7.36 版本 v2.5.0 Release Notes[2024-01-11]
### 7.36 版本 v2.5.0 Release Notes[2025-01-11]
- 1、【优化】框架基础守护线程异常处理逻辑优化避免极端情况下因Error导致调度终止问题
- 2、【优化】底层通讯超时时间支持自定义默认3秒缓解网络抖动导致任务通讯超时问题可参考 xxl-job-admin 和 samples 示例代码自行配置;
- 3、【修复】调度中心快慢线程池优化拒绝策略避免因默认AbortPolicy导致调度结果丢失问题
@ -2435,19 +2632,224 @@ public void execute() {
- 14、【升级】多个项目依赖升级至较新稳定版本涉及netty、slf4j、junit等
**备注:**
- a、本次升级数据模型及通讯协议向前兼容v2.4.*代码和系统可无缝升级;建议对比新旧版本数据库初始化脚本补充索引;
- b、计划下个大版本升级 v3.0,将会基于 jdk17 与 springboot3.x 构建版本v2.5.x将会继续维护问题及漏洞将会及时跟进修复。
- a、本次升级数据模型及通讯协议向前兼容v2.4.*代码和系统可无缝升级该版本优化了“xxl_job_log”表索引建议低版本参考调整
- b、版本v2.5.x为基于jdk8的最后的大版本将会长期持续维护问题及漏洞将会及时跟进修复。
- c、下个大版本v3.0)将会基于 jdk17 与 springboot3.x 构建;
### 7.37 版本 v3.0.0 Release Notes[2025-02-07]
- 1、【升级】调度中心升级至 SpringBoot3 + JDK17
- 2、【升级】Docker镜像升级镜像构建基于JDK17
- 3、【优化】IP获取逻辑优化优先遍历网卡来获取可用IP
- 4、【优化】通用命令行任务(“commandJobHandler”)优化,支持多参数执行,命令及参数之间通过空格隔开;如任务参数 "ls la" 或 "pwd" 将会执行命令并输出数据;
- 5、【优化】通用HTTP任务httpJobHandler优化任务参数格式调整为json格式
- 6、【升级】多个项目依赖升级至较新稳定版本涉及 netty、groovy、spring/springboot 等;
**备注:**
- a、本次升级数据模型及通讯协议向前兼容v2.4.*及后续版本可无缝升级;
- b、版本3.x开始要求Jdk17版本2.x及以下支持Jdk1.8。如对Jdk版本有诉求可选择接入不同版本;
### 7.38 版本 v3.1.0 Release Notes[2025-05-01]
- 1、【新增】新增提供 “AI执行器” 并内置多个Bean模式 AI任务Handler与spring-ai、ollama、dify等集成打通支持快速开发AI类任务。
- AppNamexxl-job-executor-sample-ai
- 执行器代码xxl-job-executor-sample-springboot-ai
- 执行器初始化脚本执行参考SQL脚本或自行人工创建
```
INSERT INTO `xxl_job_group`(`app_name`, `title`, `address_type`, `address_list`, `update_time`)
VALUES ('xxl-job-executor-sample-ai', 'AI执行器Sample', 0, NULL, now());
```
- 2、【新增】新增多个 Bean模式 AI任务Handler如 ollamaJobHandler、difyWorkflowJobHandler 等支持快速集成开发AI任务。任务配置可参考 [AI执行器](https://www.xuxueli.com/xxl-job/#原生内置Bean模式任务AI执行器)
- a、ollamaJobHandler OllamaChat任务支持自定义prompt、input等输入信息。
- b、difyWorkflowJobHandlerDifyWorkflow 任务支持自定义inputs、user、baseUrl、apiKey等输入信息。
- 3、【修复】合并PR-3708、PR-3704解决固定速度调度模式下下次计算执行时间小概率间隔超长时不准问题。
- 4、【修复】任务操作逻辑优化修复边界情况下逻辑中断问题 (ISSUE-2081)。
- 5、【修复】调度中心Cron前端组件优化解决week配置与后端兼容性问题 (ISSUE-2220)。
- 6、【修复】任务RollingLog权限逻辑调整修复非管理员账号越权访问问题 (ISSUE-3705)。
- 7、【优化】Glue IDE调整版本回溯支持查看修改时间
- 8、【优化】任务RollingLog调整XSS过滤支持白名单排出提升日志易读性
- 9、【优化】执行器日志文件保存天数logretentiondays调整最小保留时间调整至3天。
- 10、【升级】多个项目依赖升级至较新稳定版本涉及 gson、groovy、spring/springboot、mysql 等;
### 7.39 版本 v3.1.1 Release Notes[2025-06-23]
- 1、【调整】AI任务difyWorkflowJobHandler优化针对 “baseUrl、apiKey” 等Dify配置信息从执行器侧文件类配置调整至调度中心“任务参数”动态配置支持多Dify应用集成并提升研发效率
- 2、【优化】合并PR-2417修复任务管理时JobHandler录入空格问题
- 3、【优化】合并PR-2504规避SQL注入问题
- 4、【升级】多个项目依赖升级至较新稳定版本涉及 netty、spring/springboot、groovy 等;
### 7.40 版本 v3.2.0 Release Notes[2025-08-24]
- 1、【强化】AI任务ollamaJobHandler优化针对 “model” 模型配置信息,从执行器侧文件类配置调整至调度中心“任务参数”动态配置,支持集成多模型、并结合任务动态配置切换。
- 2、【安全】登录认证重构密码加密算法从Md5改为Sha256登录态改为登录后动态随机生成提升系统安全性需要针对用户表进行字段调整同时需要重新初始化密码信息相关SQL脚本如下
```
// 1、用户表password字段需要调整长度执行如下命令
ALTER TABLE xxl_job_user
MODIFY COLUMN `password` varchar(100) NOT NULL COMMENT '密码加密信息';
ALTER TABLE xxl_job_user
ADD COLUMN `token` varchar(100) DEFAULT NULL COMMENT '登录token';
// 2、存量用户密码需要修改可执行如下命令将密码初始化 “123456”也可以自行通过 “SHA256Tool.sha256” 工具生成其他初始化密码;
UPDATE xxl_job_user t SET t.password = '8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c92' WHERE t.username = {用户名};
```
- 3、【强化】GLUE模式(Python) 扩展,支持 "GLUE(Python3)" 与 "GLUE(Python2)" 两种模式,分别支持 python3/2 多版本;
- 4、【强化】调度中心系统日志调整支持启动时指定 -DLOG_HOME 参数自定义日志位置;同时优化日志格式提升易读性;
- 5、【优化】任务Bean扫描规则调整过滤冗余不必要扫描避免系统组件提前初始化
- 6、【优化】登录信息页面空值处理优化避免空值影响ftl渲染
- 7、【优化】异常页面处理逻辑优化新增兜底落地页配置
- 8、【重构】ReturnT 重构简化代码结构提升API易用性以及可维护性
- 9、【重构】项目结构重构提升可维护性与易读性
- 10、【修复】漏洞修复CVE-2025-7787针对 httpJobHandler 支持配置URL白名单限制防止服务器端请求伪造SSRF攻击。
- 11、【修复】合并PR-3738修复拼写问题
- 12、【修复】合并PR-3506修复小概率情况下任务重复调度问题
- 13、【修复】合并PR-3747修复异常情况下资源泄漏风险
- 14、【修复】IDOR越权问题修复提升任务操作及日志管理安全性
- 15、【升级】升级多项maven依赖至较新版本如 netty、groovy、mybatis、spring、spring-ai、dify 等;
### 7.41 版本 v3.3.0 Release Notes[2025-11-29]
- 1、【新增】执行器新增“任务扫描排除路径”配置项(xxl.job.executor.excludedpackage),任务扫描时忽略指定包路径下的任务;
- 2、【优化】执行器任务Bean扫描逻辑调整优化懒加载Bean检测及过滤机制避免提前初始化类问题
- 3、【新增】合并PR-3840执行器支持通过XxlJobHelper获取任务触发时间戳XxlJobHelper组件完善支持通过“XxlJobHelper.getLogId/getLogDateTime/getLogFileName”方法获取执行日志相关信息
- 4、【升级】调度中心UI框架升级统一交互组件支持多主题、多标签与局部渲染等升级UI组件及性能
- 5、【优化】调度时间轮组件强化保障不重不漏调度时间轮单刻度数据去重避免极端情况下任务重复执行时间轮转动时校验临近刻度避免极端情况下遗漏刻度
- 6、【优化】调度任务锁逻辑优化事务SQL下沉至Mapper层统一管理并增加测试用例提升代码可读性以及可维护性
- 7、【优化】调度快慢线程池默认配置上调提升默认配置单机负载调度预读任务数计算系数下调降低事务颗粒度提升性能及稳定性
- 8、【性能】调度中心调整资源加载逻辑移除不必要的拦截器提升页面加载性能
- 9、【优化】优化日志列表页面展示逻辑新增展示“日志ID”与“任务名称”信息
- 10、【优化】报表统计SQL优化修复小概率情况下查询null值问题报表初始化SQL优化修复小概率情况增改竞争问题
- 11、【优化】优日志报告与清理逻辑增加清理过期日志的异常捕获避免线程异常退出
- 12、【优化】任务回调失败日志读写磁盘逻辑优化解决极端情况下大文件读写内存问题
- 13、【升级】Http通讯组件升级基于接口代理方式重构通讯组件提升组件性能及扩展性
- 14、【重构】规范API交互协议通用响应结构体调整为Response调度中心API统一为Response封装数据
注意响应结构体从ReturnT升级为Response其中属性值“content”会调整为“data”通过openapi交互场景需要关注
- 15、【重构】调度过期策略、调度类型策略逻辑重构代码组件化拆分并完善日志提升健壮性及可维护性
- 16、【重构】调度中心底层组件重构组件初始化以及销毁逻辑统一处理任务触发及和回调逻辑优化避免资源泄漏风险
- 17、【重构】调度中心底层组件模块化拆分移除组件单例以及静态代码逻辑提升组件可维护性
- 18、【重构】重构Rolling日志读写逻辑解决边界条件下异常情况优化读写性能
- 19、【修复】脚本任务process销毁逻辑优化解决风险情况下脚本进程无法终止问题
- 20、【修复】合并PR-2369修复脚本任务参数取值问题
- 21、【新增】任务审计日志记录任务操作敏感日志信息如任务新建/更新/删除/启停/触发以及GLUE代码更新等用于系统监控、审计和安全分析可快速追溯异常行为以及定位排查问题等。
当前任务审计日志以Info级别输出在系统日志中可通过关键词 "xxl-job operation log:" 检索过滤)
- 22、【强化】通用HTTP任务httpJobHandler强化支持更丰富请求参数设置完整参数示例如下
<details>
<summary>完整参数示例参考:</summary>
```
{
"url": "http://www.baidu.com",
"method": "POST",
"contentType": "application/json",
"headers": {
"header01": "value01"
},
"cookies": {
"cookie01": "value01"
},
"timeout": 3000,
"data": "request body data",
"form": {
"key01": "value01"
},
"auth": "auth data"
}
```
</details>
- 23、【优化】调度组件日志完善提升边界情况下问题定位效率
- 24、【升级】升级多项maven依赖至较新版本如 netty、groovy、springboot、spring-ai、dify、mybatis、xxl-sso 等;
**备注:**
- a、本次升级数据模型向前兼容v3.2.*版本可直接升级不需要进行数据库表调整;
- b、本次升级针对客户端rollinglog依赖字段做规范约束如不关注该功能 v2.4.* 及后续版本客户端不需要升级/可兼容,否则需要升级客户端版本;
### 7.42 版本 v3.3.1 Release Notes[2025-12-06]
- 1、【新增】新增“执行器启用开关”配置项(xxl.job.executor.enabled),默认开启,关闭时不进行执行器初始化;
- 2、【修复】调度组件事务代码调整修复DB超时等小概率情况下调度终止问题
- 3、【修复】合并PR-3869修复底层通讯超时设置无效问题
- 4、【优化】执行器删除逻辑优化删除时一并清理注册表数据避免小概率情况下注册数据堆积ISSUE-3669
- 5、【升级】调度中心升级至 SpringBoot4升级多项maven依赖至较新版本如 mybatis、groovy 等;
### 7.43 版本 v3.3.2 Release Notes[2026-01-01]
- 1、【优化】优雅停机调度中心停机检测时间轮非空时主动等待调度完成客户端停机检测存在运行中任务时停止接收新任务并主动等待任务执行完成
- 2、【新增】新增 Docker Compose 配置,支持一键配置启动调度中心集群;
<details>
<summary>Docker Compose启动步骤</summary>
```
// 下载 XXL-JOB
git clone --branch "$(curl -s https://api.github.com/repos/xuxueli/xxl-job/releases/latest | jq -r .tag_name)" https://github.com/xuxueli/xxl-job.git
// 构建 XXL-JOB
mvn clean package -Dmaven.test.skip=true
// 配置 XXL-JOB前往docker目录自定义 .env
cd ./docker
cat .env
// 启动 XXL-JOB
docker compose up -d
// 停止 XXL-JOB
docker compose down
```
</details>
- 3、【优化】调度中心操作体验优化表格交互调整为单行选中模式禁用分页循环优化分页限制文案
- 4、【优化】调度线程事务提交逻辑调整避免边界条件下线程异常退出增强健壮性
- 5、【优化】调度日志列表排序逻辑优化提升易读性
- 6、【优化】调度中心OpenAPI通讯token调整为非必填合并PR-3892
- 7、【优化】执行器详情接口权限调整支持普通用户查看注册节点合并PR-3882
- 8、【优化】任务参数LogDateTime生成逻辑调整分片广播场景下保障同一批调度一致
- 9、【升级】升级多项maven依赖至较新版本如 spring、netty、xxl-sso、xxl-tool 等;
- 10、【优化】统一项目依赖管理结构依赖版本统一到父级pom提升可维护性
### 7.44 版本 v3.4.0 Release Notes[2026-04-05]
- 1、【新增】AI执行器集成OpenClaw: 新增“openClawJobHandler”内置AI任务与OpenClaw集成打通支持快速开发AI类任务
- 2、【增强】任务调度后分批合并更新高频调度场景可百倍降低SQL操作合并执行提升调度性能
任务调度后批量合并更新配置“xxl.job.schedule.batchsize”
- 3、【优化】调度日志支持执行器维度查看提升体验新增调度日志索引提升查询性能
- 4、【优化】一致性哈希路由算法优化重构哈希环逻辑提升代码简洁性
- 5、【优化】Cron解析工具优化解决day-of-month使用L时会跳过非31天的月份问题
- 6、【优化】执行器注册表主键调整为long数据类型防止大规模执行器集群注册数据溢出
- 7、【优化】任务参数长度调整最长支持2048字符
- 8、【优化】执行器名称长度调整最长支持64字符
- 9、【修复】固定间隔模式调度策略调整修复小概率下触发时间偏差问题
- 10、【调整】Docker基础镜像调整为eclipse-temurin
- 11、【优化】父POM依赖配置优化移除容易配置合并PR-3926
- 12、【优化】调度组件触发判断优化合并PR-2502
- 13、【优化】调度日志调整完善日志参数信息合并PR-2761
- 14、【重构】代码重构优化I18N国际化、属性加载、报表SQL等逻辑重构合并PR-2888、PR-3006、PR-3027、PR-3198、PR-3285
- 15、【重构】告警组件初始化重构提升代码可维护性合并PR-2903
- 16、【升级】升级多项maven依赖至较新版本
**备注:**
数据库升级脚本:
```
-- 任务日志表:添加索引
create index I_jobgroup on xxl_job_log (job_group);
-- 执行器表:修改字段长度
alter table xxl_job_group
modify title varchar(64) not null comment '执行器名称';
-- 执行器注册表修改自增ID类型
alter table xxl_job_registry
modify id bigint(20) NOT NULL AUTO_INCREMENT;
-- 任务表:修改字段长度
alter table xxl_job_info
modify executor_param text null comment '任务参数';
-- 日志表:修改字段长度
alter table xxl_job_log
modify executor_param text null comment '任务参数';
```
### 7.45 版本 v3.4.1 Release Notes[ING]
- 1、【TODO】调度中心OpenAPI完善提供任务管理能力封装Agent Skill并推送ClawHub
- 2、【TODO】AccessToken升级执行器维度隔离支持线上化配置升级双端OpenApi适配AccessToken升级
### 7.37 版本 v3.0.0 Release Notes[规划中]
- 14、[规划中]登陆态Token生成逻辑优化混淆登陆时间属性降低token泄漏风险
- 15、[规划中]升级springboot3.x解决2.x老版本漏洞类问题。注意springboot3.x依赖jdk17
### TODO LIST
- 1、调度隔离调度中心针对不同执行器各自维护不同的调度和远程触发组件。
- 2、任务优先级调度与执行阶段按照优先级分配资源。
- 3、多数据库支持DAO层通过JPA实现不限制数据库类型。
- 4、执行器Log清理功能调度中心Log删除时同步删除执行器中的Log文件
- 4、OpenApi
- 执行器Log文件清理支持调度中心远程删除执行器中指定任务的Log文件
- 5、性能优化任务、执行器数据全量本地缓存新增消息表广播通知
- 6、DAG流程任务
- 子任务:废弃
@ -2456,10 +2858,8 @@ public void execute() {
- 分片任务:全部完成后才会出发后置节点;
- 配置并列的"a-b、b-c"路径列表构成串行、并行、dag任务流程"dagre-d3"绘图;任务依赖,流程图,子任务+会签任务,各节点日志;支持根据成功、失败选择分支;
- 7、任务标签方便搜索
- 8、告警增强
- 邮件告警:支持自定义标题、模板格式;
- webhook告警支持自定义告警URL、请求体格式
- 9、安全强化AccessToken动态生成、动态启停控制调度、回调
- 8、GLUE 模式 Web Ide 版本对比功能;
- 9、自定义失败重试时间间隔
- 10、任务导入导出工具灵活支持版本升级、迁移等场景。
- 11、任务日志重构一次调度只记录一条主任务维护起止时间和状态。
- 普通任务:只记录一条主任务;
@ -2467,13 +2867,27 @@ public void execute() {
- 重试任务:失败时,新增主任务。所有调度记录,包括入口调度和重试调度,均挂载主任务上。
- 12、分片任务全部完成后才会出发后置节点
- 13、日期过滤支持多个时间段排除
- 13、GLUE 模式 Web Ide 版本对比功能;
- 14、提供执行器Docker镜像
- 15、脚本任务支持数据参数新版本仅支持单参数不支持需要兼容
- 17、批量调度调度请求入queue调度线程批量获取调度请求并发起远程调度提高线程效率
- 18、执行器端口复用复用容器端口提供通讯服务
- 19、自定义失败重试时间间隔
- 20、安全功能增强通讯加密参数改用加密数据避免AccessToken明文 降低token泄漏风险
- 19、安全功能增强通讯加密参数改用加密数据避免AccessToken明文 降低token泄漏风险
- 20、告警增强
- 邮件告警:支持自定义标题、模板格式;
- webhook告警支持自定义告警URL、请求体格式
- 21、公共告警策略执行器维度设置多告警策略任务勾选启用待评估任务或执行器维度
- 20、日志策略
- 调度日志:全局配置:废弃; 新增“调度日志策略”任务维度自定义保留3天、7天、1个月、3个月、一年、永久
- 执行日志新增“执行RollingLog开关”任务维度自定义支持RollingLog、普通日志slf4j输出、关闭不输出
- 21、AccessToken废弃全局配置支持在线管理动态生成、动态启停
- 22、任务管理OpenAPI;
- 23、调度中心启动参数线上配置告警发送邮箱、Token支持线上配置生效修改不需重启机器
- 24、执行器内嵌server切换tomcat精简依赖
- 25、日志策略新增
- 调度日志策略任务级设置最少保留1天。
- 执行日志策略:可选 RollingLog、slf4jLog
- 清理逻辑,性能重构。
## 八、其他

Binary file not shown.

@ -7,6 +7,34 @@ use `xxl_job`;
SET NAMES utf8mb4;
## —————————————————————— job group and registry ——————————————————
CREATE TABLE `xxl_job_group`
(
`id` int(11) NOT NULL AUTO_INCREMENT,
`app_name` varchar(64) NOT NULL COMMENT '执行器AppName',
`title` varchar(64) NOT NULL COMMENT '执行器名称',
`address_type` tinyint(4) NOT NULL DEFAULT '0' COMMENT '执行器地址类型0=自动注册、1=手动录入',
`address_list` text COMMENT '执行器地址列表,多地址逗号分隔',
`update_time` datetime DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4;
CREATE TABLE `xxl_job_registry`
(
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`registry_group` varchar(50) NOT NULL,
`registry_key` varchar(255) NOT NULL,
`registry_value` varchar(255) NOT NULL,
`update_time` datetime DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `i_g_k_v` (`registry_group`, `registry_key`, `registry_value`) USING BTREE
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4;
## —————————————————————— job info ——————————————————
CREATE TABLE `xxl_job_info`
(
`id` int(11) NOT NULL AUTO_INCREMENT,
@ -20,8 +48,8 @@ CREATE TABLE `xxl_job_info`
`schedule_conf` varchar(128) DEFAULT NULL COMMENT '调度配置,值含义取决于调度类型',
`misfire_strategy` varchar(50) NOT NULL DEFAULT 'DO_NOTHING' COMMENT '调度过期策略',
`executor_route_strategy` varchar(50) DEFAULT NULL COMMENT '执行器路由策略',
`executor_handler` varchar(255) DEFAULT NULL COMMENT '执行器任务handler',
`executor_param` varchar(512) DEFAULT NULL COMMENT '执行器任务参数',
`executor_handler` varchar(255) DEFAULT NULL COMMENT '任务handler',
`executor_param` text DEFAULT NULL COMMENT '任务参数',
`executor_block_strategy` varchar(50) DEFAULT NULL COMMENT '阻塞处理策略',
`executor_timeout` int(11) NOT NULL DEFAULT '0' COMMENT '任务执行超时时间,单位秒',
`executor_fail_retry_count` int(11) NOT NULL DEFAULT '0' COMMENT '失败重试次数',
@ -37,28 +65,43 @@ CREATE TABLE `xxl_job_info`
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4;
CREATE TABLE `xxl_job_logglue`
(
`id` int(11) NOT NULL AUTO_INCREMENT,
`job_id` int(11) NOT NULL COMMENT '任务主键ID',
`glue_type` varchar(50) DEFAULT NULL COMMENT 'GLUE类型',
`glue_source` mediumtext COMMENT 'GLUE源代码',
`glue_remark` varchar(128) NOT NULL COMMENT 'GLUE备注',
`add_time` datetime DEFAULT NULL,
`update_time` datetime DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4;
## —————————————————————— job log and report ——————————————————
CREATE TABLE `xxl_job_log`
(
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`job_group` int(11) NOT NULL COMMENT '执行器主键ID',
`job_id` int(11) NOT NULL COMMENT '任务主键ID',
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`job_group` int(11) NOT NULL COMMENT '执行器主键ID',
`job_id` int(11) NOT NULL COMMENT '任务主键ID',
`executor_address` varchar(255) DEFAULT NULL COMMENT '执行器地址,本次执行的地址',
`executor_handler` varchar(255) DEFAULT NULL COMMENT '执行器任务handler',
`executor_param` varchar(512) DEFAULT NULL COMMENT '执行器任务参数',
`executor_sharding_param` varchar(20) DEFAULT NULL COMMENT '执行器任务分片参数,格式如 1/2',
`executor_fail_retry_count` int(11) NOT NULL DEFAULT '0' COMMENT '失败重试次数',
`executor_handler` varchar(255) DEFAULT NULL COMMENT '任务handler',
`executor_param` text DEFAULT NULL COMMENT '任务参数',
`executor_sharding_param` varchar(20) DEFAULT NULL COMMENT '任务分片参数,格式如 1/2',
`executor_fail_retry_count` int(11) NOT NULL DEFAULT '0' COMMENT '失败重试次数',
`trigger_time` datetime DEFAULT NULL COMMENT '调度-时间',
`trigger_code` int(11) NOT NULL COMMENT '调度-结果',
`trigger_msg` text COMMENT '调度-日志',
`trigger_code` int(11) NOT NULL COMMENT '调度-结果',
`trigger_msg` text COMMENT '调度-日志',
`handle_time` datetime DEFAULT NULL COMMENT '执行-时间',
`handle_code` int(11) NOT NULL COMMENT '执行-状态',
`handle_msg` text COMMENT '执行-日志',
`alarm_status` tinyint(4) NOT NULL DEFAULT '0' COMMENT '告警状态0-默认、1-无需告警、2-告警成功、3-告警失败',
`handle_code` int(11) NOT NULL COMMENT '执行-状态',
`handle_msg` text COMMENT '执行-日志',
`alarm_status` tinyint(4) NOT NULL DEFAULT '0' COMMENT '告警状态0-默认、1-无需告警、2-告警成功、3-告警失败',
PRIMARY KEY (`id`),
KEY `I_trigger_time` (`trigger_time`),
KEY `I_handle_code` (`handle_code`),
KEY `I_jobid_jobgroup` (`job_id`,`job_group`),
KEY `I_job_id` (`job_id`)
KEY `I_jobgroup` (`job_group`),
KEY `I_jobid` (`job_id`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4;
@ -75,48 +118,23 @@ CREATE TABLE `xxl_job_log_report`
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4;
CREATE TABLE `xxl_job_logglue`
(
`id` int(11) NOT NULL AUTO_INCREMENT,
`job_id` int(11) NOT NULL COMMENT '任务主键ID',
`glue_type` varchar(50) DEFAULT NULL COMMENT 'GLUE类型',
`glue_source` mediumtext COMMENT 'GLUE源代码',
`glue_remark` varchar(128) NOT NULL COMMENT 'GLUE备注',
`add_time` datetime DEFAULT NULL,
`update_time` datetime DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4;
## —————————————————————— lock ——————————————————
CREATE TABLE `xxl_job_registry`
CREATE TABLE `xxl_job_lock`
(
`id` int(11) NOT NULL AUTO_INCREMENT,
`registry_group` varchar(50) NOT NULL,
`registry_key` varchar(255) NOT NULL,
`registry_value` varchar(255) NOT NULL,
`update_time` datetime DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `i_g_k_v` (`registry_group`, `registry_key`, `registry_value`) USING BTREE
`lock_name` varchar(50) NOT NULL COMMENT '锁名称',
PRIMARY KEY (`lock_name`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4;
CREATE TABLE `xxl_job_group`
(
`id` int(11) NOT NULL AUTO_INCREMENT,
`app_name` varchar(64) NOT NULL COMMENT '执行器AppName',
`title` varchar(12) NOT NULL COMMENT '执行器名称',
`address_type` tinyint(4) NOT NULL DEFAULT '0' COMMENT '执行器地址类型0=自动注册、1=手动录入',
`address_list` text COMMENT '执行器地址列表,多地址逗号分隔',
`update_time` datetime DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4;
## —————————————————————— user ——————————————————
CREATE TABLE `xxl_job_user`
(
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(50) NOT NULL COMMENT '账号',
`password` varchar(50) NOT NULL COMMENT '密码',
`password` varchar(100) NOT NULL COMMENT '密码加密信息',
`token` varchar(100) DEFAULT NULL COMMENT '登录token',
`role` tinyint(4) NOT NULL COMMENT '角色0-普通用户、1-管理员',
`permission` varchar(255) DEFAULT NULL COMMENT '权限执行器ID列表多个逗号分割',
PRIMARY KEY (`id`),
@ -124,33 +142,49 @@ CREATE TABLE `xxl_job_user`
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4;
CREATE TABLE `xxl_job_lock`
(
`lock_name` varchar(50) NOT NULL COMMENT '锁名称',
PRIMARY KEY (`lock_name`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4;
## —————————————————————— init data ——————————————————
## —————————————————————— for default data ——————————————————
INSERT INTO `xxl_job_group`(`id`, `app_name`, `title`, `address_type`, `address_list`, `update_time`)
VALUES (1, 'xxl-job-executor-sample', '示例执行器', 0, NULL, '2018-11-03 22:21:31');
VALUES (1, 'xxl-job-executor-sample', '通用执行器Sample', 0, NULL, now()),
(2, 'xxl-job-executor-sample-ai', 'AI执行器Sample', 0, NULL, now());
INSERT INTO `xxl_job_info`(`id`, `job_group`, `job_desc`, `add_time`, `update_time`, `author`, `alarm_email`,
`schedule_type`, `schedule_conf`, `misfire_strategy`, `executor_route_strategy`,
`executor_handler`, `executor_param`, `executor_block_strategy`, `executor_timeout`,
`executor_fail_retry_count`, `glue_type`, `glue_source`, `glue_remark`, `glue_updatetime`,
`child_jobid`)
VALUES (1, 1, '测试任务1', '2018-11-03 22:21:31', '2018-11-03 22:21:31', 'XXL', '', 'CRON', '0 0 0 * * ? *',
VALUES (1, 1, '示例任务01', now(), now(), 'XXL', '', 'CRON', '0 0 0 * * ? *',
'DO_NOTHING', 'FIRST', 'demoJobHandler', '', 'SERIAL_EXECUTION', 0, 0, 'BEAN', '', 'GLUE代码初始化',
'2018-11-03 22:21:31', '');
now(), ''),
(2, 2, 'Ollama示例任务', now(), now(), 'XXL', '', 'NONE', '',
'DO_NOTHING', 'FIRST', 'ollamaJobHandler', '{
"input": "慢SQL问题分析思路",
"prompt": "你是一个研发工程师,擅长解决技术类问题。",
"model": "qwen3.5:2b"
}', 'SERIAL_EXECUTION', 0, 0, 'BEAN', '', 'GLUE',
now(), ''),
(3, 2, 'Dify示例任务', now(), now(), 'XXL', '', 'NONE', '',
'DO_NOTHING', 'FIRST', 'difyWorkflowJobHandler', '{
"inputs":{
"input":"查询班级各学科前三名"
},
"user": "xxl-job",
"baseUrl": "http://localhost/v1",
"apiKey": "app-OUVgNUOQRIMokfmuJvBJoUTN"
}', 'SERIAL_EXECUTION', 0, 0, 'BEAN', '', 'GLUE',
now(), ''),
(4, 2, 'OpenClaw示例任务', now(), now(), 'XXL', '', 'NONE', '',
'DO_NOTHING', 'FIRST', 'openClawJobHandler', '{
"input": "查看下上海今天得天气,给出出游建议",
"prompt": "你是一个出游助手,擅长做旅游规划"
}', 'SERIAL_EXECUTION', 0, 0, 'BEAN', '', 'GLUE',
now(), '');
INSERT INTO `xxl_job_user`(`id`, `username`, `password`, `role`, `permission`)
VALUES (1, 'admin', 'e10adc3949ba59abbe56e057f20f883e', 1, NULL);
VALUES (1, 'admin', '8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c92', 1, NULL);
INSERT INTO `xxl_job_lock` (`lock_name`)
VALUES ('schedule_lock');
commit;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 284 KiB

After

Width:  |  Height:  |  Size: 278 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

After

Width:  |  Height:  |  Size: 131 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 135 KiB

After

Width:  |  Height:  |  Size: 115 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 286 KiB

After

Width:  |  Height:  |  Size: 324 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 199 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 267 KiB

After

Width:  |  Height:  |  Size: 322 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 188 KiB

@ -0,0 +1,9 @@
# docker-compose env
# xxl-job, admin
XXL_JOB_ADMIN_PORT=8080
XXL_JOB_ADMIN_CONTEXT_PATH=/xxl-job-admin
# xxl-job, database
MYSQL_ROOT_PASSWORD=root_pwd
MYSQL_PATH=/Users/admin/program/docker/instance/mysql

@ -0,0 +1,75 @@
# docker-compose version
version: '3.8'
services:
mysql:
image: mysql:8.4
container_name: xxl-job-mysql
environment:
# 1、数据库密码设置需要与Admin中配置一致
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
# 2、数据库实例名称需要与Admin中配置一致
MYSQL_DATABASE: xxl_job
ports:
- "3306:3306"
volumes:
# 说明:仅数据库首次初始化时执行;
- ../doc/db/tables_xxl_job.sql:/docker-entrypoint-initdb.d/tables_xxl_job.sql:ro
# 3、数据库持久化目录位置建议自定义
- ${MYSQL_PATH}/conf:/etc/mysql/conf.d
- ${MYSQL_PATH}/logs:/var/log/mysql
- ${MYSQL_PATH}/data:/var/lib/mysql
command: >-
--character-set-server=utf8mb4
--collation-server=utf8mb4_unicode_ci
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
timeout: 20s
retries: 10
networks:
- xxl-job-network
xxl-job-admin:
# 4、本地Build设置如果期望使用推动DockerHub的镜像可以注释当前启用的image、build配置并启用如下设置版本的image配置
#image: xuxueli/xxl-job-admin:{version}
image: xuxueli/xxl-job-admin:local
build:
context: ../xxl-job-admin
dockerfile: Dockerfile
container_name: xxl-job-admin
environment:
# 5、数据库密码设置需要与上文Mysql中保持一致
PARAMS: >-
--spring.datasource.url=jdbc:mysql://mysql:3306/xxl_job?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghai
--spring.datasource.username=root
--spring.datasource.password=${MYSQL_ROOT_PASSWORD}
--server.port=${XXL_JOB_ADMIN_PORT}
--server.servlet.context-path=${XXL_JOB_ADMIN_CONTEXT_PATH}
ports:
- "${XXL_JOB_ADMIN_PORT}:${XXL_JOB_ADMIN_PORT}"
depends_on:
mysql:
condition: service_healthy
networks:
- xxl-job-network
xxl-job-executor-sample-springboot:
image: xuxueli/xxl-job-executor-sample-springboot:local
build:
context: ./xxl-job-executor-samples/xxl-job-executor-sample-springboot
dockerfile: Dockerfile
container_name: xxl-job-executor-sample-springboot
environment:
PARAMS: >-
--xxl.job.admin.addresses=http://xxl-job-admin:${XXL_JOB_ADMIN_PORT}${XXL_JOB_ADMIN_CONTEXT_PATH:-/}
ports:
- "9999:9999"
depends_on:
xxl-job-admin:
condition: service_started
networks:
- xxl-job-network
networks:
xxl-job-network:
driver: bridge

@ -3,7 +3,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>com.xuxueli</groupId>
<artifactId>xxl-job</artifactId>
<version>2.5.0</version>
<version>3.4.0</version>
<packaging>pom</packaging>
<name>${project.artifactId}</name>
@ -21,35 +21,166 @@
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<maven.compiler.encoding>UTF-8</maven.compiler.encoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<maven.test.skip>true</maven.test.skip>
<!-- plugin -->
<maven-source-plugin.version>3.3.1</maven-source-plugin.version>
<maven-javadoc-plugin.version>3.11.2</maven-javadoc-plugin.version>
<maven-gpg-plugin.version>3.2.7</maven-gpg-plugin.version>
<maven-compiler-plugin.version>3.15.0</maven-compiler-plugin.version>
<maven-source-plugin.version>3.4.0</maven-source-plugin.version>
<maven-javadoc-plugin.version>3.12.0</maven-javadoc-plugin.version>
<maven-gpg-plugin.version>3.2.8</maven-gpg-plugin.version>
<central-publishing-maven-plugin.version>0.10.0</central-publishing-maven-plugin.version>
<!-- base -->
<slf4j-api.version>2.0.16</slf4j-api.version>
<junit-jupiter.version>5.11.4</junit-jupiter.version>
<javax.annotation-api.version>1.3.2</javax.annotation-api.version>
<!-- net -->
<netty.version>4.1.116.Final</netty.version>
<gson.version>2.11.0</gson.version>
<slf4j-api.version>2.0.17</slf4j-api.version>
<junit-jupiter.version>6.0.3</junit-jupiter.version>
<!-- jakarta.annotation-api -->
<jakarta.annotation-api.version>3.0.0</jakarta.annotation-api.version>
<!-- spring -->
<spring.version>5.3.39</spring.version>
<spring-boot.version>2.7.18</spring-boot.version>
<!-- db -->
<mybatis-spring-boot-starter.version>2.3.2</mybatis-spring-boot-starter.version>
<mysql-connector-j.version>9.1.0</mysql-connector-j.version>
<!-- dynamic language -->
<groovy.version>4.0.24</groovy.version>
<spring-boot.version>4.0.5</spring-boot.version>
<spring.version>7.0.6</spring.version>
<!-- mybatis & db -->
<mybatis-spring-boot-starter.version>4.0.1</mybatis-spring-boot-starter.version>
<mysql-connector-j.version>9.6.0</mysql-connector-j.version>
<!-- net -->
<netty.version>4.2.12.Final</netty.version>
<!-- groovy -->
<groovy.version>5.0.5</groovy.version>
<!-- xxl-sso -->
<xxl-sso.version>2.4.0</xxl-sso.version>
<!-- xxl-tool -->
<xxl-tool.version>2.5.0</xxl-tool.version>
<!-- gson -->
<gson.version>2.13.2</gson.version>
<!-- spring-ai -->
<spring-ai.version>2.0.0-M4</spring-ai.version>
<!-- dify -->
<dify-java-client.version>1.2.5</dify-java-client.version>
</properties>
<build>
<plugins>
</plugins>
</build>
<dependencyManagement>
<dependencies>
<!-- springboot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- slf4j -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j-api.version}</version>
</dependency>
<!-- slf4j-reload4j -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-reload4j</artifactId>
<version>${slf4j-api.version}</version>
</dependency>
<!-- junit -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>${junit-jupiter.version}</version>
</dependency>
<!-- jakarta -->
<dependency>
<groupId>jakarta.annotation</groupId>
<artifactId>jakarta.annotation-api</artifactId>
<version>${jakarta.annotation-api.version}</version>
</dependency>
<!-- netty -->
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-codec-http</artifactId>
<version>${netty.version}</version>
</dependency>
<!-- gson -->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>${gson.version}</version>
</dependency>
<!-- xxl-tool -->
<dependency>
<groupId>com.xuxueli</groupId>
<artifactId>xxl-tool</artifactId>
<version>${xxl-tool.version}</version>
</dependency>
<!-- groovy -->
<dependency>
<groupId>org.apache.groovy</groupId>
<artifactId>groovy</artifactId>
<version>${groovy.version}</version>
</dependency>
<!-- spring-context -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- mybatis-startermybatis + mybatis-spring + hikaridefault -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>${mybatis-spring-boot-starter.version}</version>
</dependency>
<!-- mysql -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>${mysql-connector-j.version}</version>
</dependency>
<!-- xxl-job-core -->
<dependency>
<groupId>com.xuxueli</groupId>
<artifactId>xxl-job-core</artifactId>
<version>${project.parent.version}</version>
</dependency>
<!-- xxl-sso (xxl-tool、gson) -->
<dependency>
<groupId>com.xuxueli</groupId>
<artifactId>xxl-sso-core</artifactId>
<version>${xxl-sso.version}</version>
</dependency>
<!-- spring-ai: ollama、openai -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-ollama</artifactId>
<version>${spring-ai.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-openai</artifactId>
<version>${spring-ai.version}</version>
</dependency>
<!-- dify -->
<dependency>
<groupId>io.github.imfangs</groupId>
<artifactId>dify-java-client</artifactId>
<version>${dify-java-client.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<licenses>
<license>
@ -57,7 +188,6 @@
<url>https://opensource.org/licenses/GPL-3.0</url>
</license>
</licenses>
<scm>
<tag>master</tag>
<url>https://github.com/xuxueli/xxl-job.git</url>
@ -73,6 +203,11 @@
</developer>
</developers>
<build>
<plugins>
</plugins>
</build>
<profiles>
<profile>
@ -85,6 +220,17 @@
</modules>
<build>
<plugins>
<!-- Compiler -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${maven-compiler-plugin.version}</version>
<configuration>
<source>${maven.compiler.source}</source>
<target>${maven.compiler.target}</target>
<parameters>true</parameters>
</configuration>
</plugin>
<!-- Source -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
@ -133,18 +279,25 @@
</execution>
</executions>
</plugin>
<!-- maven central -->
<plugin>
<groupId>org.sonatype.central</groupId>
<artifactId>central-publishing-maven-plugin</artifactId>
<version>${central-publishing-maven-plugin.version}</version>
<extensions>true</extensions>
<configuration>
<publishingServerId>central</publishingServerId>
<excludeArtifacts>
<artifact>xxl-job-admin</artifact>
<artifact>xxl-job-executor-samples</artifact>
<artifact>xxl-job-executor-sample-frameless</artifact>
<artifact>xxl-job-executor-sample-springboot</artifact>
<artifact>xxl-job-executor-sample-springboot-ai</artifact>
</excludeArtifacts>
</configuration>
</plugin>
</plugins>
</build>
<distributionManagement>
<snapshotRepository>
<id>oss</id>
<url>https://oss.sonatype.org/content/repositories/snapshots/</url>
</snapshotRepository>
<repository>
<id>oss</id>
<url>https://oss.sonatype.org/service/local/staging/deploy/maven2/</url>
</repository>
</distributionManagement>
</profile>
</profiles>

@ -1,11 +1,22 @@
FROM openjdk:8-jre-slim
# base image
#FROM openjdk:21-jdk-slim
FROM eclipse-temurin:17-jre
# maintainer
MAINTAINER xuxueli
# set params
ENV PARAMS=""
# set timezone
ENV TZ=PRC
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
# copy jar
ADD target/xxl-job-admin-*.jar /app.jar
ENTRYPOINT ["sh","-c","java -jar $JAVA_OPTS /app.jar $PARAMS"]
# command
# log home: -e LOG_HOME=/data/applogs
# jvm options: -e JAVA_OPTS="-Xms128m -Xmx128m"
# app params: -e PARAMS="--server.port=8080"
ENTRYPOINT ["sh","-c","java ${LOG_HOME:+-DLOG_HOME=$LOG_HOME} -jar $JAVA_OPTS /app.jar $PARAMS"]

@ -4,7 +4,7 @@
<parent>
<groupId>com.xuxueli</groupId>
<artifactId>xxl-job</artifactId>
<version>2.5.0</version>
<version>3.4.0</version>
</parent>
<artifactId>xxl-job-admin</artifactId>
<packaging>jar</packaging>
@ -13,26 +13,14 @@
<maven.deploy.skip>true</maven.deploy.skip>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!-- starter-webspring-webmvc + autoconfigure + logback + yaml + tomcat -->
<!-- starter-web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- starter-testjunit + spring-test + mockito -->
<!-- starter-test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
@ -44,37 +32,38 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
<!-- mail-starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
<!-- starter-actuator -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- mybatis-startermybatis + mybatis-spring + hikaridefault -->
<!-- mybatis-starter -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>${mybatis-spring-boot-starter.version}</version>
</dependency>
<!-- mysql -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>${mysql-connector-j.version}</version>
</dependency>
<!-- xxl-job-core -->
<dependency>
<groupId>com.xuxueli</groupId>
<artifactId>xxl-job-core</artifactId>
<version>${project.parent.version}</version>
</dependency>
<!-- xxl-sso -->
<dependency>
<groupId>com.xuxueli</groupId>
<artifactId>xxl-sso-core</artifactId>
</dependency>
</dependencies>
@ -94,25 +83,6 @@
</executions>
</plugin>
<!-- docker -->
<!--<plugin>
<groupId>com.spotify</groupId>
<artifactId>docker-maven-plugin</artifactId>
<version>0.4.13</version>
<configuration>
&lt;!&ndash; made of '[a-z0-9-_.]' &ndash;&gt;
<imageName>${project.artifactId}:${project.version}</imageName>
<dockerDirectory>${project.basedir}</dockerDirectory>
<resources>
<resource>
<targetPath>/</targetPath>
<directory>${project.build.directory}</directory>
<include>${project.build.finalName}.jar</include>
</resource>
</resources>
</configuration>
</plugin>-->
</plugins>
</build>

@ -0,0 +1,7 @@
package com.xxl.job.admin.constant;
public class Consts {
public static final String ADMIN_ROLE = "ADMIN";
}

@ -0,0 +1,32 @@
package com.xxl.job.admin.constant;
public enum TriggerStatus {
STOPPED(0, "stopped"),
RUNNING(1, "running");
private int value;
private String desc;
TriggerStatus(int value, String desc) {
this.value = value;
this.desc = desc;
}
public int getValue() {
return value;
}
public void setValue(int value) {
this.value = value;
}
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
}

@ -1,96 +0,0 @@
package com.xxl.job.admin.controller;
import com.xxl.job.admin.controller.annotation.PermissionLimit;
import com.xxl.job.admin.service.impl.LoginService;
import com.xxl.job.admin.service.XxlJobService;
import com.xxl.job.core.biz.model.ReturnT;
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;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.view.RedirectView;
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;
/**
* index controller
* @author xuxueli 2015-12-19 16:13:16
*/
@Controller
public class IndexController {
@Resource
private XxlJobService xxlJobService;
@Resource
private LoginService loginService;
@RequestMapping("/")
public String index(Model model) {
Map<String, Object> dashboardMap = xxlJobService.dashboardInfo();
model.addAllAttributes(dashboardMap);
return "index";
}
@RequestMapping("/chartInfo")
@ResponseBody
public ReturnT<Map<String, Object>> chartInfo(Date startDate, Date endDate) {
ReturnT<Map<String, Object>> chartInfo = xxlJobService.chartInfo(startDate, endDate);
return chartInfo;
}
@RequestMapping("/toLogin")
@PermissionLimit(limit=false)
public ModelAndView toLogin(HttpServletRequest request, HttpServletResponse response,ModelAndView modelAndView) {
if (loginService.ifLogin(request, response) != null) {
modelAndView.setView(new RedirectView("/",true,false));
return modelAndView;
}
return new ModelAndView("login");
}
@RequestMapping(value="login", method=RequestMethod.POST)
@ResponseBody
@PermissionLimit(limit=false)
public ReturnT<String> loginDo(HttpServletRequest request, HttpServletResponse response, String userName, String password, String ifRemember){
boolean ifRem = (ifRemember!=null && ifRemember.trim().length()>0 && "on".equals(ifRemember))?true:false;
return loginService.login(request, response, userName, password, ifRem);
}
@RequestMapping(value="logout", method=RequestMethod.POST)
@ResponseBody
@PermissionLimit(limit=false)
public ReturnT<String> logout(HttpServletRequest request, HttpServletResponse response){
return loginService.logout(request, response);
}
@RequestMapping("/help")
public String help() {
/*if (!PermissionInterceptor.ifLogin(request)) {
return "redirect:/toLogin";
}*/
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));
}
}

@ -1,72 +0,0 @@
package com.xxl.job.admin.controller;
import com.xxl.job.admin.controller.annotation.PermissionLimit;
import com.xxl.job.admin.core.conf.XxlJobAdminConfig;
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 com.xxl.job.core.util.GsonTool;
import com.xxl.job.core.util.XxlJobRemotingUtil;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.List;
/**
* Created by xuxueli on 17/5/10.
*/
@Controller
@RequestMapping("/api")
public class JobApiController {
@Resource
private AdminBiz adminBiz;
/**
* api
*
* @param uri
* @param data
* @return
*/
@RequestMapping("/{uri}")
@ResponseBody
@PermissionLimit(limit=false)
public ReturnT<String> api(HttpServletRequest request, @PathVariable("uri") String uri, @RequestBody(required = false) String data) {
// valid
if (!"POST".equalsIgnoreCase(request.getMethod())) {
return new ReturnT<String>(ReturnT.FAIL_CODE, "invalid request, HttpMethod not support.");
}
if (uri==null || uri.trim().length()==0) {
return new ReturnT<String>(ReturnT.FAIL_CODE, "invalid request, uri-mapping empty.");
}
if (XxlJobAdminConfig.getAdminConfig().getAccessToken()!=null
&& XxlJobAdminConfig.getAdminConfig().getAccessToken().trim().length()>0
&& !XxlJobAdminConfig.getAdminConfig().getAccessToken().equals(request.getHeader(XxlJobRemotingUtil.XXL_JOB_ACCESS_TOKEN))) {
return new ReturnT<String>(ReturnT.FAIL_CODE, "The access token is wrong.");
}
// services mapping
if ("callback".equals(uri)) {
List<HandleCallbackParam> callbackParamList = GsonTool.fromJson(data, List.class, HandleCallbackParam.class);
return adminBiz.callback(callbackParamList);
} else if ("registry".equals(uri)) {
RegistryParam registryParam = GsonTool.fromJson(data, RegistryParam.class);
return adminBiz.registry(registryParam);
} else if ("registryRemove".equals(uri)) {
RegistryParam registryParam = GsonTool.fromJson(data, RegistryParam.class);
return adminBiz.registryRemove(registryParam);
} else {
return new ReturnT<String>(ReturnT.FAIL_CODE, "invalid request, uri-mapping("+ uri +") not found.");
}
}
}

@ -1,100 +0,0 @@
package com.xxl.job.admin.controller;
import com.xxl.job.admin.controller.interceptor.PermissionInterceptor;
import com.xxl.job.admin.core.model.XxlJobInfo;
import com.xxl.job.admin.core.model.XxlJobLogGlue;
import com.xxl.job.admin.core.util.I18nUtil;
import com.xxl.job.admin.dao.XxlJobInfoDao;
import com.xxl.job.admin.dao.XxlJobLogGlueDao;
import com.xxl.job.core.biz.model.ReturnT;
import com.xxl.job.core.glue.GlueTypeEnum;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.Date;
import java.util.List;
/**
* job code controller
* @author xuxueli 2015-12-19 16:13:16
*/
@Controller
@RequestMapping("/jobcode")
public class JobCodeController {
@Resource
private XxlJobInfoDao xxlJobInfoDao;
@Resource
private XxlJobLogGlueDao xxlJobLogGlueDao;
@RequestMapping
public String index(HttpServletRequest request, Model model, int jobId) {
XxlJobInfo jobInfo = xxlJobInfoDao.loadById(jobId);
List<XxlJobLogGlue> jobLogGlues = xxlJobLogGlueDao.findByJobId(jobId);
if (jobInfo == null) {
throw new RuntimeException(I18nUtil.getString("jobinfo_glue_jobid_unvalid"));
}
if (GlueTypeEnum.BEAN == GlueTypeEnum.match(jobInfo.getGlueType())) {
throw new RuntimeException(I18nUtil.getString("jobinfo_glue_gluetype_unvalid"));
}
// valid permission
PermissionInterceptor.validJobGroupPermission(request, jobInfo.getJobGroup());
// Glue类型-字典
model.addAttribute("GlueTypeEnum", GlueTypeEnum.values());
model.addAttribute("jobInfo", jobInfo);
model.addAttribute("jobLogGlues", jobLogGlues);
return "jobcode/jobcode.index";
}
@RequestMapping("/save")
@ResponseBody
public ReturnT<String> save(HttpServletRequest request, int id, String glueSource, String glueRemark) {
// valid
if (glueRemark==null) {
return new ReturnT<String>(500, (I18nUtil.getString("system_please_input") + I18nUtil.getString("jobinfo_glue_remark")) );
}
if (glueRemark.length()<4 || glueRemark.length()>100) {
return new ReturnT<String>(500, I18nUtil.getString("jobinfo_glue_remark_limit"));
}
XxlJobInfo existsJobInfo = xxlJobInfoDao.loadById(id);
if (existsJobInfo == null) {
return new ReturnT<String>(500, I18nUtil.getString("jobinfo_glue_jobid_unvalid"));
}
// valid permission
PermissionInterceptor.validJobGroupPermission(request, existsJobInfo.getJobGroup());
// update new code
existsJobInfo.setGlueSource(glueSource);
existsJobInfo.setGlueRemark(glueRemark);
existsJobInfo.setGlueUpdatetime(new Date());
existsJobInfo.setUpdateTime(new Date());
xxlJobInfoDao.update(existsJobInfo);
// log old code
XxlJobLogGlue xxlJobLogGlue = new XxlJobLogGlue();
xxlJobLogGlue.setJobId(existsJobInfo.getId());
xxlJobLogGlue.setGlueType(existsJobInfo.getGlueType());
xxlJobLogGlue.setGlueSource(glueSource);
xxlJobLogGlue.setGlueRemark(glueRemark);
xxlJobLogGlue.setAddTime(new Date());
xxlJobLogGlue.setUpdateTime(new Date());
xxlJobLogGlueDao.save(xxlJobLogGlue);
// remove code backup more than 30
xxlJobLogGlueDao.removeOld(existsJobInfo.getId(), 30);
return ReturnT.SUCCESS;
}
}

@ -1,204 +0,0 @@
package com.xxl.job.admin.controller;
import com.xxl.job.admin.controller.annotation.PermissionLimit;
import com.xxl.job.admin.core.model.XxlJobGroup;
import com.xxl.job.admin.core.model.XxlJobRegistry;
import com.xxl.job.admin.core.util.I18nUtil;
import com.xxl.job.admin.dao.XxlJobGroupDao;
import com.xxl.job.admin.dao.XxlJobInfoDao;
import com.xxl.job.admin.dao.XxlJobRegistryDao;
import com.xxl.job.core.biz.model.ReturnT;
import com.xxl.job.core.enums.RegistryConfig;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.*;
/**
* job group controller
* @author xuxueli 2016-10-02 20:52:56
*/
@Controller
@RequestMapping("/jobgroup")
public class JobGroupController {
@Resource
public XxlJobInfoDao xxlJobInfoDao;
@Resource
public XxlJobGroupDao xxlJobGroupDao;
@Resource
private XxlJobRegistryDao xxlJobRegistryDao;
@RequestMapping
@PermissionLimit(adminuser = true)
public String index(Model model) {
return "jobgroup/jobgroup.index";
}
@RequestMapping("/pageList")
@ResponseBody
@PermissionLimit(adminuser = true)
public Map<String, Object> pageList(HttpServletRequest request,
@RequestParam(required = false, defaultValue = "0") int start,
@RequestParam(required = false, defaultValue = "10") int length,
String appname, String title) {
// page query
List<XxlJobGroup> list = xxlJobGroupDao.pageList(start, length, appname, title);
int list_count = xxlJobGroupDao.pageListCount(start, length, appname, title);
// package result
Map<String, Object> maps = new HashMap<String, Object>();
maps.put("recordsTotal", list_count); // 总记录数
maps.put("recordsFiltered", list_count); // 过滤后的总记录数
maps.put("data", list); // 分页列表
return maps;
}
@RequestMapping("/save")
@ResponseBody
@PermissionLimit(adminuser = true)
public ReturnT<String> save(XxlJobGroup xxlJobGroup){
// valid
if (xxlJobGroup.getAppname()==null || xxlJobGroup.getAppname().trim().length()==0) {
return new ReturnT<String>(500, (I18nUtil.getString("system_please_input")+"AppName") );
}
if (xxlJobGroup.getAppname().length()<4 || xxlJobGroup.getAppname().length()>64) {
return new ReturnT<String>(500, I18nUtil.getString("jobgroup_field_appname_length") );
}
if (xxlJobGroup.getAppname().contains(">") || xxlJobGroup.getAppname().contains("<")) {
return new ReturnT<String>(500, "AppName"+I18nUtil.getString("system_unvalid") );
}
if (xxlJobGroup.getTitle()==null || xxlJobGroup.getTitle().trim().length()==0) {
return new ReturnT<String>(500, (I18nUtil.getString("system_please_input") + I18nUtil.getString("jobgroup_field_title")) );
}
if (xxlJobGroup.getTitle().contains(">") || xxlJobGroup.getTitle().contains("<")) {
return new ReturnT<String>(500, I18nUtil.getString("jobgroup_field_title")+I18nUtil.getString("system_unvalid") );
}
if (xxlJobGroup.getAddressType()!=0) {
if (xxlJobGroup.getAddressList()==null || xxlJobGroup.getAddressList().trim().length()==0) {
return new ReturnT<String>(500, I18nUtil.getString("jobgroup_field_addressType_limit") );
}
if (xxlJobGroup.getAddressList().contains(">") || xxlJobGroup.getAddressList().contains("<")) {
return new ReturnT<String>(500, I18nUtil.getString("jobgroup_field_registryList")+I18nUtil.getString("system_unvalid") );
}
String[] addresss = xxlJobGroup.getAddressList().split(",");
for (String item: addresss) {
if (item==null || item.trim().length()==0) {
return new ReturnT<String>(500, I18nUtil.getString("jobgroup_field_registryList_unvalid") );
}
}
}
// process
xxlJobGroup.setUpdateTime(new Date());
int ret = xxlJobGroupDao.save(xxlJobGroup);
return (ret>0)?ReturnT.SUCCESS:ReturnT.FAIL;
}
@RequestMapping("/update")
@ResponseBody
@PermissionLimit(adminuser = true)
public ReturnT<String> update(XxlJobGroup xxlJobGroup){
// valid
if (xxlJobGroup.getAppname()==null || xxlJobGroup.getAppname().trim().length()==0) {
return new ReturnT<String>(500, (I18nUtil.getString("system_please_input")+"AppName") );
}
if (xxlJobGroup.getAppname().length()<4 || xxlJobGroup.getAppname().length()>64) {
return new ReturnT<String>(500, I18nUtil.getString("jobgroup_field_appname_length") );
}
if (xxlJobGroup.getTitle()==null || xxlJobGroup.getTitle().trim().length()==0) {
return new ReturnT<String>(500, (I18nUtil.getString("system_please_input") + I18nUtil.getString("jobgroup_field_title")) );
}
if (xxlJobGroup.getAddressType() == 0) {
// 0=自动注册
List<String> registryList = findRegistryByAppName(xxlJobGroup.getAppname());
String addressListStr = null;
if (registryList!=null && !registryList.isEmpty()) {
Collections.sort(registryList);
addressListStr = "";
for (String item:registryList) {
addressListStr += item + ",";
}
addressListStr = addressListStr.substring(0, addressListStr.length()-1);
}
xxlJobGroup.setAddressList(addressListStr);
} else {
// 1=手动录入
if (xxlJobGroup.getAddressList()==null || xxlJobGroup.getAddressList().trim().length()==0) {
return new ReturnT<String>(500, I18nUtil.getString("jobgroup_field_addressType_limit") );
}
String[] addresss = xxlJobGroup.getAddressList().split(",");
for (String item: addresss) {
if (item==null || item.trim().length()==0) {
return new ReturnT<String>(500, I18nUtil.getString("jobgroup_field_registryList_unvalid") );
}
}
}
// process
xxlJobGroup.setUpdateTime(new Date());
int ret = xxlJobGroupDao.update(xxlJobGroup);
return (ret>0)?ReturnT.SUCCESS:ReturnT.FAIL;
}
private List<String> findRegistryByAppName(String appnameParam){
HashMap<String, List<String>> appAddressMap = new HashMap<String, List<String>>();
List<XxlJobRegistry> list = xxlJobRegistryDao.findAll(RegistryConfig.DEAD_TIMEOUT, new Date());
if (list != null) {
for (XxlJobRegistry item: list) {
if (RegistryConfig.RegistType.EXECUTOR.name().equals(item.getRegistryGroup())) {
String appname = item.getRegistryKey();
List<String> registryList = appAddressMap.get(appname);
if (registryList == null) {
registryList = new ArrayList<String>();
}
if (!registryList.contains(item.getRegistryValue())) {
registryList.add(item.getRegistryValue());
}
appAddressMap.put(appname, registryList);
}
}
}
return appAddressMap.get(appnameParam);
}
@RequestMapping("/remove")
@ResponseBody
@PermissionLimit(adminuser = true)
public ReturnT<String> remove(int id){
// valid
int count = xxlJobInfoDao.pageListCount(0, 10, id, -1, null, null, null);
if (count > 0) {
return new ReturnT<String>(500, I18nUtil.getString("jobgroup_del_limit_0") );
}
List<XxlJobGroup> allList = xxlJobGroupDao.findAll();
if (allList.size() == 1) {
return new ReturnT<String>(500, I18nUtil.getString("jobgroup_del_limit_1") );
}
int ret = xxlJobGroupDao.remove(id);
return (ret>0)?ReturnT.SUCCESS:ReturnT.FAIL;
}
@RequestMapping("/loadById")
@ResponseBody
@PermissionLimit(adminuser = true)
public ReturnT<XxlJobGroup> loadById(int id){
XxlJobGroup jobGroup = xxlJobGroupDao.load(id);
return jobGroup!=null?new ReturnT<XxlJobGroup>(jobGroup):new ReturnT<XxlJobGroup>(ReturnT.FAIL_CODE, null);
}
}

@ -1,155 +0,0 @@
package com.xxl.job.admin.controller;
import com.xxl.job.admin.controller.interceptor.PermissionInterceptor;
import com.xxl.job.admin.core.exception.XxlJobException;
import com.xxl.job.admin.core.model.XxlJobGroup;
import com.xxl.job.admin.core.model.XxlJobInfo;
import com.xxl.job.admin.core.model.XxlJobUser;
import com.xxl.job.admin.core.route.ExecutorRouteStrategyEnum;
import com.xxl.job.admin.core.scheduler.MisfireStrategyEnum;
import com.xxl.job.admin.core.scheduler.ScheduleTypeEnum;
import com.xxl.job.admin.core.thread.JobScheduleHelper;
import com.xxl.job.admin.core.util.I18nUtil;
import com.xxl.job.admin.dao.XxlJobGroupDao;
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 com.xxl.job.core.util.DateUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.*;
/**
* index controller
* @author xuxueli 2015-12-19 16:13:16
*/
@Controller
@RequestMapping("/jobinfo")
public class JobInfoController {
private static Logger logger = LoggerFactory.getLogger(JobInfoController.class);
@Resource
private XxlJobGroupDao xxlJobGroupDao;
@Resource
private XxlJobService xxlJobService;
@RequestMapping
public String index(HttpServletRequest request, Model model, @RequestParam(required = false, defaultValue = "-1") int jobGroup) {
// 枚举-字典
model.addAttribute("ExecutorRouteStrategyEnum", ExecutorRouteStrategyEnum.values()); // 路由策略-列表
model.addAttribute("GlueTypeEnum", GlueTypeEnum.values()); // Glue类型-字典
model.addAttribute("ExecutorBlockStrategyEnum", ExecutorBlockStrategyEnum.values()); // 阻塞处理策略-字典
model.addAttribute("ScheduleTypeEnum", ScheduleTypeEnum.values()); // 调度类型
model.addAttribute("MisfireStrategyEnum", MisfireStrategyEnum.values()); // 调度过期策略
// 执行器列表
List<XxlJobGroup> jobGroupList_all = xxlJobGroupDao.findAll();
// filter group
List<XxlJobGroup> jobGroupList = PermissionInterceptor.filterJobGroupByRole(request, jobGroupList_all);
if (jobGroupList==null || jobGroupList.size()==0) {
throw new XxlJobException(I18nUtil.getString("jobgroup_empty"));
}
model.addAttribute("JobGroupList", jobGroupList);
model.addAttribute("jobGroup", jobGroup);
return "jobinfo/jobinfo.index";
}
@RequestMapping("/pageList")
@ResponseBody
public Map<String, Object> pageList(@RequestParam(required = false, defaultValue = "0") int start,
@RequestParam(required = false, defaultValue = "10") int length,
int jobGroup, int triggerStatus, String jobDesc, String executorHandler, String author) {
return xxlJobService.pageList(start, length, jobGroup, triggerStatus, jobDesc, executorHandler, author);
}
@RequestMapping("/add")
@ResponseBody
public ReturnT<String> add(HttpServletRequest request, XxlJobInfo jobInfo) {
// valid permission
PermissionInterceptor.validJobGroupPermission(request, jobInfo.getJobGroup());
// opt
XxlJobUser loginUser = PermissionInterceptor.getLoginUser(request);
return xxlJobService.add(jobInfo, loginUser);
}
@RequestMapping("/update")
@ResponseBody
public ReturnT<String> update(HttpServletRequest request, XxlJobInfo jobInfo) {
// valid permission
PermissionInterceptor.validJobGroupPermission(request, jobInfo.getJobGroup());
// opt
XxlJobUser loginUser = PermissionInterceptor.getLoginUser(request);
return xxlJobService.update(jobInfo, loginUser);
}
@RequestMapping("/remove")
@ResponseBody
public ReturnT<String> remove(int id) {
return xxlJobService.remove(id);
}
@RequestMapping("/stop")
@ResponseBody
public ReturnT<String> pause(int id) {
return xxlJobService.stop(id);
}
@RequestMapping("/start")
@ResponseBody
public ReturnT<String> start(int id) {
return xxlJobService.start(id);
}
@RequestMapping("/trigger")
@ResponseBody
public ReturnT<String> triggerJob(HttpServletRequest request, int id, String executorParam, String addressList) {
// login user
XxlJobUser loginUser = PermissionInterceptor.getLoginUser(request);
// trigger
return xxlJobService.trigger(loginUser, id, executorParam, addressList);
}
@RequestMapping("/nextTriggerTime")
@ResponseBody
public ReturnT<List<String>> nextTriggerTime(String scheduleType, String scheduleConf) {
XxlJobInfo paramXxlJobInfo = new XxlJobInfo();
paramXxlJobInfo.setScheduleType(scheduleType);
paramXxlJobInfo.setScheduleConf(scheduleConf);
List<String> result = new ArrayList<>();
try {
Date lastTime = new Date();
for (int i = 0; i < 5; i++) {
lastTime = JobScheduleHelper.generateNextValidTime(paramXxlJobInfo, lastTime);
if (lastTime != null) {
result.add(DateUtil.formatDateTime(lastTime));
} else {
break;
}
}
} catch (Exception e) {
logger.error("nextTriggerTime error. scheduleType = {}, scheduleConf= {}", scheduleType, scheduleConf, e);
return new ReturnT<List<String>>(ReturnT.FAIL_CODE, (I18nUtil.getString("schedule_type")+I18nUtil.getString("system_unvalid")) + e.getMessage());
}
return new ReturnT<List<String>>(result);
}
}

@ -1,250 +0,0 @@
package com.xxl.job.admin.controller;
import com.xxl.job.admin.controller.interceptor.PermissionInterceptor;
import com.xxl.job.admin.core.complete.XxlJobCompleter;
import com.xxl.job.admin.core.exception.XxlJobException;
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.scheduler.XxlJobScheduler;
import com.xxl.job.admin.core.util.I18nUtil;
import com.xxl.job.admin.dao.XxlJobGroupDao;
import com.xxl.job.admin.dao.XxlJobInfoDao;
import com.xxl.job.admin.dao.XxlJobLogDao;
import com.xxl.job.core.biz.ExecutorBiz;
import com.xxl.job.core.biz.model.KillParam;
import com.xxl.job.core.biz.model.LogParam;
import com.xxl.job.core.biz.model.LogResult;
import com.xxl.job.core.biz.model.ReturnT;
import com.xxl.job.core.util.DateUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.util.HtmlUtils;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* index controller
* @author xuxueli 2015-12-19 16:13:16
*/
@Controller
@RequestMapping("/joblog")
public class JobLogController {
private static Logger logger = LoggerFactory.getLogger(JobLogController.class);
@Resource
private XxlJobGroupDao xxlJobGroupDao;
@Resource
public XxlJobInfoDao xxlJobInfoDao;
@Resource
public XxlJobLogDao xxlJobLogDao;
@RequestMapping
public String index(HttpServletRequest request, Model model, @RequestParam(required = false, defaultValue = "0") Integer jobId) {
// 执行器列表
List<XxlJobGroup> jobGroupList_all = xxlJobGroupDao.findAll();
// filter group
List<XxlJobGroup> jobGroupList = PermissionInterceptor.filterJobGroupByRole(request, jobGroupList_all);
if (jobGroupList==null || jobGroupList.size()==0) {
throw new XxlJobException(I18nUtil.getString("jobgroup_empty"));
}
model.addAttribute("JobGroupList", jobGroupList);
// 任务
if (jobId > 0) {
XxlJobInfo jobInfo = xxlJobInfoDao.loadById(jobId);
if (jobInfo == null) {
throw new RuntimeException(I18nUtil.getString("jobinfo_field_id") + I18nUtil.getString("system_unvalid"));
}
model.addAttribute("jobInfo", jobInfo);
// valid permission
PermissionInterceptor.validJobGroupPermission(request, jobInfo.getJobGroup());
}
return "joblog/joblog.index";
}
@RequestMapping("/getJobsByGroup")
@ResponseBody
public ReturnT<List<XxlJobInfo>> getJobsByGroup(int jobGroup){
List<XxlJobInfo> list = xxlJobInfoDao.getJobsByGroup(jobGroup);
return new ReturnT<List<XxlJobInfo>>(list);
}
@RequestMapping("/pageList")
@ResponseBody
public Map<String, Object> pageList(HttpServletRequest request,
@RequestParam(required = false, defaultValue = "0") int start,
@RequestParam(required = false, defaultValue = "10") int length,
int jobGroup, int jobId, int logStatus, String filterTime) {
// valid permission
PermissionInterceptor.validJobGroupPermission(request, jobGroup); // 仅管理员支持查询全部;普通用户仅支持查询有权限的 jobGroup
// parse param
Date triggerTimeStart = null;
Date triggerTimeEnd = null;
if (filterTime!=null && filterTime.trim().length()>0) {
String[] temp = filterTime.split(" - ");
if (temp.length == 2) {
triggerTimeStart = DateUtil.parseDateTime(temp[0]);
triggerTimeEnd = DateUtil.parseDateTime(temp[1]);
}
}
// page query
List<XxlJobLog> list = xxlJobLogDao.pageList(start, length, jobGroup, jobId, triggerTimeStart, triggerTimeEnd, logStatus);
int list_count = xxlJobLogDao.pageListCount(start, length, jobGroup, jobId, triggerTimeStart, triggerTimeEnd, logStatus);
// package result
Map<String, Object> maps = new HashMap<String, Object>();
maps.put("recordsTotal", list_count); // 总记录数
maps.put("recordsFiltered", list_count); // 过滤后的总记录数
maps.put("data", list); // 分页列表
return maps;
}
@RequestMapping("/logDetailPage")
public String logDetailPage(int id, Model model){
// base check
ReturnT<String> logStatue = ReturnT.SUCCESS;
XxlJobLog jobLog = xxlJobLogDao.load(id);
if (jobLog == null) {
throw new RuntimeException(I18nUtil.getString("joblog_logid_unvalid"));
}
model.addAttribute("triggerCode", jobLog.getTriggerCode());
model.addAttribute("handleCode", jobLog.getHandleCode());
model.addAttribute("logId", jobLog.getId());
return "joblog/joblog.detail";
}
@RequestMapping("/logDetailCat")
@ResponseBody
public ReturnT<LogResult> logDetailCat(long logId, int fromLineNum){
try {
// valid
XxlJobLog jobLog = xxlJobLogDao.load(logId); // todo, need to improve performance
if (jobLog == null) {
return new ReturnT<LogResult>(ReturnT.FAIL_CODE, I18nUtil.getString("joblog_logid_unvalid"));
}
// log cat
ExecutorBiz executorBiz = XxlJobScheduler.getExecutorBiz(jobLog.getExecutorAddress());
ReturnT<LogResult> logResult = executorBiz.log(new LogParam(jobLog.getTriggerTime().getTime(), logId, fromLineNum));
// is end
if (logResult.getContent()!=null && logResult.getContent().getFromLineNum() > logResult.getContent().getToLineNum()) {
if (jobLog.getHandleCode() > 0) {
logResult.getContent().setEnd(true);
}
}
// fix xss
if (logResult.getContent()!=null && StringUtils.hasText(logResult.getContent().getLogContent())) {
String newLogContent = logResult.getContent().getLogContent();
newLogContent = HtmlUtils.htmlEscape(newLogContent, "UTF-8");
logResult.getContent().setLogContent(newLogContent);
}
return logResult;
} catch (Exception e) {
logger.error(e.getMessage(), e);
return new ReturnT<LogResult>(ReturnT.FAIL_CODE, e.getMessage());
}
}
@RequestMapping("/logKill")
@ResponseBody
public ReturnT<String> logKill(int id){
// base check
XxlJobLog log = xxlJobLogDao.load(id);
XxlJobInfo jobInfo = xxlJobInfoDao.loadById(log.getJobId());
if (jobInfo==null) {
return new ReturnT<String>(500, I18nUtil.getString("jobinfo_glue_jobid_unvalid"));
}
if (ReturnT.SUCCESS_CODE != log.getTriggerCode()) {
return new ReturnT<String>(500, I18nUtil.getString("joblog_kill_log_limit"));
}
// request of kill
ReturnT<String> runResult = null;
try {
ExecutorBiz executorBiz = XxlJobScheduler.getExecutorBiz(log.getExecutorAddress());
runResult = executorBiz.kill(new KillParam(jobInfo.getId()));
} catch (Exception e) {
logger.error(e.getMessage(), e);
runResult = new ReturnT<String>(500, e.getMessage());
}
if (ReturnT.SUCCESS_CODE == runResult.getCode()) {
log.setHandleCode(ReturnT.FAIL_CODE);
log.setHandleMsg( I18nUtil.getString("joblog_kill_log_byman")+":" + (runResult.getMsg()!=null?runResult.getMsg():""));
log.setHandleTime(new Date());
XxlJobCompleter.updateHandleInfoAndFinish(log);
return new ReturnT<String>(runResult.getMsg());
} else {
return new ReturnT<String>(500, runResult.getMsg());
}
}
@RequestMapping("/clearLog")
@ResponseBody
public ReturnT<String> clearLog(HttpServletRequest request, int jobGroup, int jobId, int type){
// valid permission
PermissionInterceptor.validJobGroupPermission(request, jobGroup);
// opt
Date clearBeforeTime = null;
int clearBeforeNum = 0;
if (type == 1) {
clearBeforeTime = DateUtil.addMonths(new Date(), -1); // 清理一个月之前日志数据
} else if (type == 2) {
clearBeforeTime = DateUtil.addMonths(new Date(), -3); // 清理三个月之前日志数据
} else if (type == 3) {
clearBeforeTime = DateUtil.addMonths(new Date(), -6); // 清理六个月之前日志数据
} else if (type == 4) {
clearBeforeTime = DateUtil.addYears(new Date(), -1); // 清理一年之前日志数据
} else if (type == 5) {
clearBeforeNum = 1000; // 清理一千条以前日志数据
} else if (type == 6) {
clearBeforeNum = 10000; // 清理一万条以前日志数据
} else if (type == 7) {
clearBeforeNum = 30000; // 清理三万条以前日志数据
} else if (type == 8) {
clearBeforeNum = 100000; // 清理十万条以前日志数据
} else if (type == 9) {
clearBeforeNum = 0; // 清理所有日志数据
} else {
return new ReturnT<String>(ReturnT.FAIL_CODE, I18nUtil.getString("joblog_clean_type_unvalid"));
}
List<Long> logIds = null;
do {
logIds = xxlJobLogDao.findClearLogIds(jobGroup, jobId, clearBeforeTime, clearBeforeNum, 1000);
if (logIds!=null && logIds.size()>0) {
xxlJobLogDao.clearLog(logIds);
}
} while (logIds!=null && logIds.size()>0);
return ReturnT.SUCCESS;
}
}

@ -1,186 +0,0 @@
package com.xxl.job.admin.controller;
import com.xxl.job.admin.controller.annotation.PermissionLimit;
import com.xxl.job.admin.controller.interceptor.PermissionInterceptor;
import com.xxl.job.admin.core.model.XxlJobGroup;
import com.xxl.job.admin.core.model.XxlJobUser;
import com.xxl.job.admin.core.util.I18nUtil;
import com.xxl.job.admin.dao.XxlJobGroupDao;
import com.xxl.job.admin.dao.XxlJobUserDao;
import com.xxl.job.core.biz.model.ReturnT;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.util.DigestUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author xuxueli 2019-05-04 16:39:50
*/
@Controller
@RequestMapping("/user")
public class JobUserController {
@Resource
private XxlJobUserDao xxlJobUserDao;
@Resource
private XxlJobGroupDao xxlJobGroupDao;
@RequestMapping
@PermissionLimit(adminuser = true)
public String index(Model model) {
// 执行器列表
List<XxlJobGroup> groupList = xxlJobGroupDao.findAll();
model.addAttribute("groupList", groupList);
return "user/user.index";
}
@RequestMapping("/pageList")
@ResponseBody
@PermissionLimit(adminuser = true)
public Map<String, Object> pageList(@RequestParam(required = false, defaultValue = "0") int start,
@RequestParam(required = false, defaultValue = "10") int length,
String username, int role) {
// page list
List<XxlJobUser> list = xxlJobUserDao.pageList(start, length, username, role);
int list_count = xxlJobUserDao.pageListCount(start, length, username, role);
// filter
if (list!=null && list.size()>0) {
for (XxlJobUser item: list) {
item.setPassword(null);
}
}
// package result
Map<String, Object> maps = new HashMap<String, Object>();
maps.put("recordsTotal", list_count); // 总记录数
maps.put("recordsFiltered", list_count); // 过滤后的总记录数
maps.put("data", list); // 分页列表
return maps;
}
@RequestMapping("/add")
@ResponseBody
@PermissionLimit(adminuser = true)
public ReturnT<String> add(XxlJobUser xxlJobUser) {
// valid username
if (!StringUtils.hasText(xxlJobUser.getUsername())) {
return new ReturnT<String>(ReturnT.FAIL_CODE, I18nUtil.getString("system_please_input")+I18nUtil.getString("user_username") );
}
xxlJobUser.setUsername(xxlJobUser.getUsername().trim());
if (!(xxlJobUser.getUsername().length()>=4 && xxlJobUser.getUsername().length()<=20)) {
return new ReturnT<String>(ReturnT.FAIL_CODE, I18nUtil.getString("system_lengh_limit")+"[4-20]" );
}
// valid password
if (!StringUtils.hasText(xxlJobUser.getPassword())) {
return new ReturnT<String>(ReturnT.FAIL_CODE, I18nUtil.getString("system_please_input")+I18nUtil.getString("user_password") );
}
xxlJobUser.setPassword(xxlJobUser.getPassword().trim());
if (!(xxlJobUser.getPassword().length()>=4 && xxlJobUser.getPassword().length()<=20)) {
return new ReturnT<String>(ReturnT.FAIL_CODE, I18nUtil.getString("system_lengh_limit")+"[4-20]" );
}
// md5 password
xxlJobUser.setPassword(DigestUtils.md5DigestAsHex(xxlJobUser.getPassword().getBytes()));
// check repeat
XxlJobUser existUser = xxlJobUserDao.loadByUserName(xxlJobUser.getUsername());
if (existUser != null) {
return new ReturnT<String>(ReturnT.FAIL_CODE, I18nUtil.getString("user_username_repeat") );
}
// write
xxlJobUserDao.save(xxlJobUser);
return ReturnT.SUCCESS;
}
@RequestMapping("/update")
@ResponseBody
@PermissionLimit(adminuser = true)
public ReturnT<String> update(HttpServletRequest request, XxlJobUser xxlJobUser) {
// avoid opt login seft
XxlJobUser loginUser = PermissionInterceptor.getLoginUser(request);
if (loginUser.getUsername().equals(xxlJobUser.getUsername())) {
return new ReturnT<String>(ReturnT.FAIL.getCode(), I18nUtil.getString("user_update_loginuser_limit"));
}
// valid password
if (StringUtils.hasText(xxlJobUser.getPassword())) {
xxlJobUser.setPassword(xxlJobUser.getPassword().trim());
if (!(xxlJobUser.getPassword().length()>=4 && xxlJobUser.getPassword().length()<=20)) {
return new ReturnT<String>(ReturnT.FAIL_CODE, I18nUtil.getString("system_lengh_limit")+"[4-20]" );
}
// md5 password
xxlJobUser.setPassword(DigestUtils.md5DigestAsHex(xxlJobUser.getPassword().getBytes()));
} else {
xxlJobUser.setPassword(null);
}
// write
xxlJobUserDao.update(xxlJobUser);
return ReturnT.SUCCESS;
}
@RequestMapping("/remove")
@ResponseBody
@PermissionLimit(adminuser = true)
public ReturnT<String> remove(HttpServletRequest request, int id) {
// avoid opt login seft
XxlJobUser loginUser = PermissionInterceptor.getLoginUser(request);
if (loginUser.getId() == id) {
return new ReturnT<String>(ReturnT.FAIL.getCode(), I18nUtil.getString("user_update_loginuser_limit"));
}
xxlJobUserDao.delete(id);
return ReturnT.SUCCESS;
}
@RequestMapping("/updatePwd")
@ResponseBody
public ReturnT<String> updatePwd(HttpServletRequest request, String password, String oldPassword){
// valid
if (oldPassword==null || oldPassword.trim().length()==0){
return new ReturnT<String>(ReturnT.FAIL.getCode(), I18nUtil.getString("system_please_input") + I18nUtil.getString("change_pwd_field_oldpwd"));
}
if (password==null || password.trim().length()==0){
return new ReturnT<String>(ReturnT.FAIL.getCode(), I18nUtil.getString("system_please_input") + I18nUtil.getString("change_pwd_field_oldpwd"));
}
password = password.trim();
if (!(password.length()>=4 && password.length()<=20)) {
return new ReturnT<String>(ReturnT.FAIL_CODE, I18nUtil.getString("system_lengh_limit")+"[4-20]" );
}
// md5 password
String md5OldPassword = DigestUtils.md5DigestAsHex(oldPassword.getBytes());
String md5Password = DigestUtils.md5DigestAsHex(password.getBytes());
// valid old pwd
XxlJobUser loginUser = PermissionInterceptor.getLoginUser(request);
XxlJobUser existUser = xxlJobUserDao.loadByUserName(loginUser.getUsername());
if (!md5OldPassword.equals(existUser.getPassword())) {
return new ReturnT<String>(ReturnT.FAIL.getCode(), I18nUtil.getString("change_pwd_field_oldpwd") + I18nUtil.getString("system_unvalid"));
}
// write new
existUser.setPassword(md5Password);
xxlJobUserDao.update(existUser);
return ReturnT.SUCCESS;
}
}

@ -1,29 +0,0 @@
package com.xxl.job.admin.controller.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
*
* @author xuxueli 2015-12-12 18:29:02
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface PermissionLimit {
/**
* ()
*/
boolean limit() default true;
/**
*
*
* @return
*/
boolean adminuser() default false;
}

@ -0,0 +1,127 @@
package com.xxl.job.admin.controller.base;
import com.xxl.job.admin.constant.Consts;
import com.xxl.job.admin.model.dto.XxlBootResourceDTO;
import com.xxl.job.admin.service.XxlJobService;
import com.xxl.job.admin.util.I18nUtil;
import com.xxl.sso.core.annotation.XxlSso;
import com.xxl.sso.core.helper.XxlSsoHelper;
import com.xxl.sso.core.model.LoginInfo;
import com.xxl.tool.core.StringTool;
import com.xxl.tool.response.Response;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
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.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.ModelAndView;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.stream.Collectors;
/**
* index controller
*
* @author xuxueli 2015-12-19 16:13:16
*/
@Controller
public class IndexController {
@Resource
private XxlJobService xxlJobService;
/**
* index
*/
@RequestMapping("/")
@XxlSso
public String index(HttpServletRequest request, Model model) {
// menu resource
List<XxlBootResourceDTO> resourceList = findResourceList(request);
model.addAttribute("resourceList", resourceList);
return "base/index";
}
/**
* fill menu data
*/
private List<XxlBootResourceDTO> findResourceList(HttpServletRequest request){
// login check
Response<LoginInfo> loginInfoResponse = XxlSsoHelper.loginCheckWithAttr(request);
// init menu-list
List<XxlBootResourceDTO> resourceDTOList = Arrays.asList(
new XxlBootResourceDTO(1, 0, I18nUtil.getString("job_dashboard_name"),1, "", "/dashboard", "fa-home", 1, 0, null),
new XxlBootResourceDTO(2, 0, I18nUtil.getString("jobinfo_name"),1, "", "/jobinfo", " fa-clock-o", 2, 0, null),
new XxlBootResourceDTO(3, 0, I18nUtil.getString("joblog_name"),1, "", "/joblog", " fa-database", 3, 0, null),
new XxlBootResourceDTO(4, 0, I18nUtil.getString("jobgroup_name"),1, Consts.ADMIN_ROLE, "/jobgroup", " fa-cloud", 4, 0,null),
new XxlBootResourceDTO(5, 0, I18nUtil.getString("user_manage"),1, Consts.ADMIN_ROLE, "/user", "fa-users", 5, 0, null),
new XxlBootResourceDTO(9, 0, I18nUtil.getString("admin_help"),1, "", "/help", "fa-book", 6, 0, null)
);
// filter by role
if (!XxlSsoHelper.hasRole(loginInfoResponse.getData(), Consts.ADMIN_ROLE).isSuccess()) {
resourceDTOList = resourceDTOList.stream()
.filter(resourceDTO -> StringTool.isBlank(resourceDTO.getPermission() )) // normal user had no permission
.collect(Collectors.toList());
}
resourceDTOList.stream().sorted(Comparator.comparing(XxlBootResourceDTO::getOrder)).toList();
return resourceDTOList;
}
/**
* dashboard
*/
@RequestMapping("/dashboard")
@XxlSso
public String dashboard(HttpServletRequest request, Model model) {
Map<String, Object> dashboardMap = xxlJobService.dashboardInfo();
model.addAllAttributes(dashboardMap);
return "base/dashboard";
}
@RequestMapping("/chartInfo")
@ResponseBody
public Response<Map<String, Object>> chartInfo(@RequestParam("startDate") Date startDate, @RequestParam("endDate") Date endDate) {
Response<Map<String, Object>> chartInfo = xxlJobService.chartInfo(startDate, endDate);
return chartInfo;
}
/**
* help
*/
@RequestMapping("/help")
@XxlSso
public String help() {
return "base/help";
}
@RequestMapping(value = "/errorpage")
@XxlSso(login = false)
public ModelAndView errorPage(HttpServletRequest request, HttpServletResponse response, ModelAndView mv) {
String exceptionMsg = "HTTP Status Code: "+response.getStatus();
mv.addObject("exceptionMsg", exceptionMsg);
mv.setViewName("common/common.errorpage");
return mv;
}
@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));
}
}

@ -0,0 +1,124 @@
package com.xxl.job.admin.controller.base;
import com.xxl.job.admin.mapper.XxlJobUserMapper;
import com.xxl.job.admin.model.XxlJobUser;
import com.xxl.job.admin.util.I18nUtil;
import com.xxl.sso.core.annotation.XxlSso;
import com.xxl.sso.core.helper.XxlSsoHelper;
import com.xxl.sso.core.model.LoginInfo;
import com.xxl.tool.core.StringTool;
import com.xxl.tool.crypto.Sha256Tool;
import com.xxl.tool.id.UUIDTool;
import com.xxl.tool.response.Response;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.view.RedirectView;
/**
* index controller
* @author xuxueli 2015-12-19 16:13:16
*/
@Controller
@RequestMapping("/auth")
public class LoginController {
@Resource
private XxlJobUserMapper xxlJobUserMapper;
@RequestMapping("/login")
@XxlSso(login = false)
public ModelAndView login(HttpServletRequest request, HttpServletResponse response, ModelAndView modelAndView) {
// xxl-sso, logincheck
Response<LoginInfo> loginInfoResponse = XxlSsoHelper.loginCheckWithCookie(request, response);
if (loginInfoResponse.isSuccess()) {
modelAndView.setView(new RedirectView("/",true,false));
return modelAndView;
}
return new ModelAndView("base/login");
}
@RequestMapping(value="/doLogin", method=RequestMethod.POST)
@ResponseBody
@XxlSso(login=false)
public Response<String> doLogin(HttpServletRequest request, HttpServletResponse response, String userName, String password, String ifRemember){
// param
boolean ifRem = StringTool.isNotBlank(ifRemember) && "on".equals(ifRemember);
if (StringTool.isBlank(userName) || StringTool.isBlank(password)){
return Response.ofFail( I18nUtil.getString("login_param_empty") );
}
// valid user、status
XxlJobUser xxlJobUser = xxlJobUserMapper.loadByUserName(userName);
if (xxlJobUser == null) {
return Response.ofFail( I18nUtil.getString("login_param_invalid") );
}
// valid passowrd
String passwordHash = Sha256Tool.sha256(password);
if (!passwordHash.equals(xxlJobUser.getPassword())) {
return Response.ofFail( I18nUtil.getString("login_param_invalid") );
}
// xxl-sso, do login
LoginInfo loginInfo = new LoginInfo(String.valueOf(xxlJobUser.getId()), UUIDTool.getSimpleUUID());
Response<String> result= XxlSsoHelper.loginWithCookie(loginInfo, response, ifRem);
return Response.of(result.getCode(), result.getMsg());
}
@RequestMapping(value="/logout", method=RequestMethod.POST)
@ResponseBody
@XxlSso(login=false)
public Response<String> logout(HttpServletRequest request, HttpServletResponse response){
// xxl-sso, do logout
Response<String> result = XxlSsoHelper.logoutWithCookie(request, response);
return Response.of(result.getCode(), result.getMsg());
}
@RequestMapping("/updatePwd")
@ResponseBody
@XxlSso
public Response<String> updatePwd(HttpServletRequest request, String oldPassword, String password){
// valid
if (oldPassword==null || oldPassword.trim().isEmpty()){
return Response.ofFail(I18nUtil.getString("system_please_input") + I18nUtil.getString("change_pwd_field_oldpwd"));
}
if (password==null || password.trim().isEmpty()){
return Response.ofFail(I18nUtil.getString("system_please_input") + I18nUtil.getString("change_pwd_field_oldpwd"));
}
password = password.trim();
if (!(password.length()>=4 && password.length()<=20)) {
return Response.ofFail(I18nUtil.getString("system_length_limit")+"[4-20]" );
}
// md5 password
String oldPasswordHash = Sha256Tool.sha256(oldPassword);
String passwordHash = Sha256Tool.sha256(password);
// valid old pwd
Response<LoginInfo> loginInfoResponse = XxlSsoHelper.loginCheckWithAttr(request);
XxlJobUser existUser = xxlJobUserMapper.loadByUserName(loginInfoResponse.getData().getUserName());
if (!oldPasswordHash.equals(existUser.getPassword())) {
return Response.ofFail(I18nUtil.getString("change_pwd_field_oldpwd") + I18nUtil.getString("system_invalid"));
}
// write new
existUser.setPassword(passwordHash);
xxlJobUserMapper.update(existUser);
return Response.ofSuccess();
}
}

@ -0,0 +1,117 @@
package com.xxl.job.admin.controller.biz;
import com.xxl.job.admin.mapper.XxlJobInfoMapper;
import com.xxl.job.admin.mapper.XxlJobLogGlueMapper;
import com.xxl.job.admin.model.XxlJobInfo;
import com.xxl.job.admin.model.XxlJobLogGlue;
import com.xxl.job.admin.util.I18nUtil;
import com.xxl.job.admin.util.JobGroupPermissionUtil;
import com.xxl.job.core.glue.GlueTypeEnum;
import com.xxl.sso.core.model.LoginInfo;
import com.xxl.tool.core.StringTool;
import com.xxl.tool.json.GsonTool;
import com.xxl.tool.response.Response;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.Date;
import java.util.List;
/**
* job code controller
* @author xuxueli 2015-12-19 16:13:16
*/
@Controller
@RequestMapping("/jobcode")
public class JobCodeController {
private static final Logger logger = LoggerFactory.getLogger(JobCodeController.class);
@Resource
private XxlJobInfoMapper xxlJobInfoMapper;
@Resource
private XxlJobLogGlueMapper xxlJobLogGlueMapper;
@RequestMapping
public String index(HttpServletRequest request, Model model, @RequestParam("jobId") int jobId) {
XxlJobInfo jobInfo = xxlJobInfoMapper.loadById(jobId);
List<XxlJobLogGlue> jobLogGlues = xxlJobLogGlueMapper.findByJobId(jobId);
if (jobInfo == null) {
throw new RuntimeException(I18nUtil.getString("jobinfo_glue_jobid_invalid"));
}
if (GlueTypeEnum.BEAN == GlueTypeEnum.match(jobInfo.getGlueType())) {
throw new RuntimeException(I18nUtil.getString("jobinfo_glue_gluetype_invalid"));
}
// valid jobGroup permission
JobGroupPermissionUtil.validJobGroupPermission(request, jobInfo.getJobGroup());
// Glue类型-字典
model.addAttribute("GlueTypeEnum", GlueTypeEnum.values());
model.addAttribute("jobInfo", jobInfo);
model.addAttribute("jobLogGlues", jobLogGlues);
return "biz/job.code";
}
@RequestMapping("/save")
@ResponseBody
public Response<String> save(HttpServletRequest request,
@RequestParam("id") int id,
@RequestParam("glueSource") String glueSource,
@RequestParam("glueRemark") String glueRemark) {
// valid
if (StringTool.isBlank(glueSource)) {
return Response.ofFail( (I18nUtil.getString("system_please_input") + I18nUtil.getString("jobinfo_glue_source")) );
}
if (glueRemark==null) {
return Response.ofFail( (I18nUtil.getString("system_please_input") + I18nUtil.getString("jobinfo_glue_remark")) );
}
if (glueRemark.length()<4 || glueRemark.length()>100) {
return Response.ofFail(I18nUtil.getString("jobinfo_glue_remark_limit"));
}
XxlJobInfo existsJobInfo = xxlJobInfoMapper.loadById(id);
if (existsJobInfo == null) {
return Response.ofFail( I18nUtil.getString("jobinfo_glue_jobid_invalid"));
}
// valid jobGroup permission
LoginInfo loginInfo = JobGroupPermissionUtil.validJobGroupPermission(request, existsJobInfo.getJobGroup());
// update new code
existsJobInfo.setGlueSource(glueSource);
existsJobInfo.setGlueRemark(glueRemark);
existsJobInfo.setGlueUpdatetime(new Date());
existsJobInfo.setUpdateTime(new Date());
xxlJobInfoMapper.update(existsJobInfo);
// log old code
XxlJobLogGlue xxlJobLogGlue = new XxlJobLogGlue();
xxlJobLogGlue.setJobId(existsJobInfo.getId());
xxlJobLogGlue.setGlueType(existsJobInfo.getGlueType());
xxlJobLogGlue.setGlueSource(glueSource);
xxlJobLogGlue.setGlueRemark(glueRemark);
xxlJobLogGlue.setAddTime(new Date());
xxlJobLogGlue.setUpdateTime(new Date());
xxlJobLogGlueMapper.save(xxlJobLogGlue);
// remove code backup more than 30
xxlJobLogGlueMapper.removeOld(existsJobInfo.getId(), 30);
// write operation log
logger.info(">>>>>>>>>>> xxl-job operation log: operator = {}, type = {}, content = {}",
loginInfo.getUserName(), "jobcode-update", GsonTool.toJson(xxlJobLogGlue));
return Response.ofSuccess();
}
}

@ -0,0 +1,225 @@
package com.xxl.job.admin.controller.biz;
import com.xxl.job.admin.constant.Consts;
import com.xxl.job.admin.model.XxlJobGroup;
import com.xxl.job.admin.model.XxlJobRegistry;
import com.xxl.job.admin.util.I18nUtil;
import com.xxl.job.admin.mapper.XxlJobGroupMapper;
import com.xxl.job.admin.mapper.XxlJobInfoMapper;
import com.xxl.job.admin.mapper.XxlJobRegistryMapper;
import com.xxl.job.core.constant.Const;
import com.xxl.job.core.constant.RegistType;
import com.xxl.sso.core.annotation.XxlSso;
import com.xxl.tool.core.CollectionTool;
import com.xxl.tool.core.StringTool;
import com.xxl.tool.http.HttpTool;
import com.xxl.tool.response.PageModel;
import com.xxl.tool.response.Response;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.*;
/**
* job group controller
* @author xuxueli 2016-10-02 20:52:56
*/
@Controller
@RequestMapping("/jobgroup")
public class JobGroupController {
@Resource
public XxlJobInfoMapper xxlJobInfoMapper;
@Resource
public XxlJobGroupMapper xxlJobGroupMapper;
@Resource
private XxlJobRegistryMapper xxlJobRegistryMapper;
@RequestMapping
@XxlSso(role = Consts.ADMIN_ROLE)
public String index(Model model) {
return "biz/group.list";
}
@RequestMapping("/pageList")
@ResponseBody
@XxlSso(role = Consts.ADMIN_ROLE)
public Response<PageModel<XxlJobGroup>> pageList(@RequestParam(required = false, defaultValue = "0") int offset,
@RequestParam(required = false, defaultValue = "10") int pagesize,
String appname,
String title) {
// page query
List<XxlJobGroup> list = xxlJobGroupMapper.pageList(offset, pagesize, appname, title);
int list_count = xxlJobGroupMapper.pageListCount(offset, pagesize, appname, title);
// package result
PageModel<XxlJobGroup> pageModel = new PageModel<>();
pageModel.setData(list);
pageModel.setTotal(list_count);
return Response.ofSuccess(pageModel);
}
@RequestMapping("/insert")
@ResponseBody
@XxlSso(role = Consts.ADMIN_ROLE)
public Response<String> insert(XxlJobGroup xxlJobGroup){
// valid
if (StringTool.isBlank(xxlJobGroup.getAppname())) {
return Response.ofFail((I18nUtil.getString("system_please_input")+"AppName") );
}
if (xxlJobGroup.getAppname().length()<4 || xxlJobGroup.getAppname().length()>64) {
return Response.ofFail( I18nUtil.getString("jobgroup_field_appname_length") );
}
if (xxlJobGroup.getAppname().contains(">") || xxlJobGroup.getAppname().contains("<")) {
return Response.ofFail( "AppName"+I18nUtil.getString("system_invalid") );
}
if (StringTool.isBlank(xxlJobGroup.getTitle())) {
return Response.ofFail((I18nUtil.getString("system_please_input") + I18nUtil.getString("jobgroup_field_title")) );
}
if (xxlJobGroup.getTitle().contains(">") || xxlJobGroup.getTitle().contains("<")) {
return Response.ofFail(I18nUtil.getString("jobgroup_field_title")+I18nUtil.getString("system_invalid") );
}
if (xxlJobGroup.getAddressType()!=0) {
if (StringTool.isBlank(xxlJobGroup.getAddressList())) {
return Response.ofFail( I18nUtil.getString("jobgroup_field_addressType_limit") );
}
if (xxlJobGroup.getAddressList().contains(">") || xxlJobGroup.getAddressList().contains("<")) {
return Response.ofFail(I18nUtil.getString("jobgroup_field_registryList")+I18nUtil.getString("system_invalid") );
}
String[] addresss = xxlJobGroup.getAddressList().split(",");
for (String item: addresss) {
if (StringTool.isBlank(item)) {
return Response.ofFail( I18nUtil.getString("jobgroup_field_registryList_invalid") );
}
if (!(HttpTool.isHttp(item) || HttpTool.isHttps(item))) {
return Response.ofFail( I18nUtil.getString("jobgroup_field_registryList_invalid")+"[2]" );
}
}
}
// process
xxlJobGroup.setUpdateTime(new Date());
int ret = xxlJobGroupMapper.save(xxlJobGroup);
return (ret>0)?Response.ofSuccess():Response.ofFail();
}
@RequestMapping("/update")
@ResponseBody
@XxlSso(role = Consts.ADMIN_ROLE)
public Response<String> update(XxlJobGroup xxlJobGroup){
// valid
if (StringTool.isBlank(xxlJobGroup.getAppname())) {
return Response.ofFail((I18nUtil.getString("system_please_input")+"AppName") );
}
if (xxlJobGroup.getAppname().length()<4 || xxlJobGroup.getAppname().length()>64) {
return Response.ofFail( I18nUtil.getString("jobgroup_field_appname_length") );
}
if (StringTool.isBlank(xxlJobGroup.getTitle())) {
return Response.ofFail( (I18nUtil.getString("system_please_input") + I18nUtil.getString("jobgroup_field_title")) );
}
if (xxlJobGroup.getAddressType() == 0) {
// 0=自动注册
List<String> registryList = findRegistryByAppName(xxlJobGroup.getAppname());
String addressListStr = null;
if (CollectionTool.isNotEmpty(registryList)) {
Collections.sort(registryList);
addressListStr = String.join(",", registryList);
}
xxlJobGroup.setAddressList(addressListStr);
} else {
// 1=手动录入
if (StringTool.isBlank(xxlJobGroup.getAddressList())) {
return Response.ofFail( I18nUtil.getString("jobgroup_field_addressType_limit") );
}
String[] addresss = xxlJobGroup.getAddressList().split(",");
for (String item: addresss) {
if (StringTool.isBlank(item)) {
return Response.ofFail(I18nUtil.getString("jobgroup_field_registryList_invalid") );
}
if (!(HttpTool.isHttp(item) || HttpTool.isHttps(item))) {
return Response.ofFail( I18nUtil.getString("jobgroup_field_registryList_invalid")+"[2]" );
}
}
}
// process
xxlJobGroup.setUpdateTime(new Date());
int ret = xxlJobGroupMapper.update(xxlJobGroup);
return (ret>0)?Response.ofSuccess():Response.ofFail();
}
private List<String> findRegistryByAppName(String appnameParam){
HashMap<String, List<String>> appAddressMap = new HashMap<>();
List<XxlJobRegistry> list = xxlJobRegistryMapper.findAll(Const.DEAD_TIMEOUT, new Date());
if (CollectionTool.isNotEmpty(list)) {
for (XxlJobRegistry item: list) {
if (!RegistType.EXECUTOR.name().equals(item.getRegistryGroup())) {
continue;
}
String appname = item.getRegistryKey();
List<String> registryList = appAddressMap.computeIfAbsent(appname, k -> new ArrayList<>());
if (!registryList.contains(item.getRegistryValue())) {
registryList.add(item.getRegistryValue());
}
}
}
return appAddressMap.get(appnameParam);
}
@RequestMapping("/delete")
@ResponseBody
@XxlSso(role = Consts.ADMIN_ROLE)
public Response<String> delete(@RequestParam("ids[]") List<Integer> ids){
// parse id
if (CollectionTool.isEmpty(ids) || ids.size()!=1) {
return Response.ofFail(I18nUtil.getString("system_please_choose") + I18nUtil.getString("system_one") + I18nUtil.getString("system_data"));
}
int id = ids.get(0);
// valid repeat operation
XxlJobGroup xxlJobGroup = xxlJobGroupMapper.load(id);
if (xxlJobGroup == null) {
return Response.ofSuccess();
}
// whether exists job
int count = xxlJobInfoMapper.pageListCount(0, 10, id, -1, null, null, null);
if (count > 0) {
return Response.ofFail( I18nUtil.getString("jobgroup_del_limit_0") );
}
// whether only exists one group
List<XxlJobGroup> allList = xxlJobGroupMapper.findAll();
if (allList.size() == 1) {
return Response.ofFail( I18nUtil.getString("jobgroup_del_limit_1") );
}
// remove group
int ret = xxlJobGroupMapper.remove(id);
// remove registry-data
xxlJobRegistryMapper.removeByRegistryGroupAndKey(RegistType.EXECUTOR.name(), xxlJobGroup.getAppname());
return (ret>0)?Response.ofSuccess():Response.ofFail();
}
@RequestMapping("/loadById")
@ResponseBody
//@XxlSso(role = Consts.ADMIN_ROLE) // open to default user, support show registry nodes
public Response<XxlJobGroup> loadById(@RequestParam("id") int id){
XxlJobGroup jobGroup = xxlJobGroupMapper.load(id);
return jobGroup!=null?Response.ofSuccess(jobGroup):Response.ofFail();
}
}

@ -0,0 +1,211 @@
package com.xxl.job.admin.controller.biz;
import com.xxl.job.admin.mapper.XxlJobGroupMapper;
import com.xxl.job.admin.model.XxlJobGroup;
import com.xxl.job.admin.model.XxlJobInfo;
import com.xxl.job.admin.scheduler.exception.XxlJobException;
import com.xxl.job.admin.scheduler.misfire.MisfireStrategyEnum;
import com.xxl.job.admin.scheduler.route.ExecutorRouteStrategyEnum;
import com.xxl.job.admin.scheduler.type.ScheduleTypeEnum;
import com.xxl.job.admin.service.XxlJobService;
import com.xxl.job.admin.util.I18nUtil;
import com.xxl.job.admin.util.JobGroupPermissionUtil;
import com.xxl.job.core.constant.ExecutorBlockStrategyEnum;
import com.xxl.job.core.glue.GlueTypeEnum;
import com.xxl.sso.core.helper.XxlSsoHelper;
import com.xxl.sso.core.model.LoginInfo;
import com.xxl.tool.core.CollectionTool;
import com.xxl.tool.core.DateTool;
import com.xxl.tool.core.StringTool;
import com.xxl.tool.response.PageModel;
import com.xxl.tool.response.Response;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
/**
* index controller
* @author xuxueli 2015-12-19 16:13:16
*/
@Controller
@RequestMapping("/jobinfo")
public class JobInfoController {
private static Logger logger = LoggerFactory.getLogger(JobInfoController.class);
@Resource
private XxlJobGroupMapper xxlJobGroupMapper;
@Resource
private XxlJobService xxlJobService;
@RequestMapping
public String index(HttpServletRequest request, Model model, @RequestParam(value = "jobGroup", required = false, defaultValue = "-1") int jobGroup) {
// 枚举-字典
model.addAttribute("ExecutorRouteStrategyEnum", ExecutorRouteStrategyEnum.values()); // 路由策略-列表
model.addAttribute("GlueTypeEnum", GlueTypeEnum.values()); // Glue类型-字典
model.addAttribute("ExecutorBlockStrategyEnum", ExecutorBlockStrategyEnum.values()); // 阻塞处理策略-字典
model.addAttribute("ScheduleTypeEnum", ScheduleTypeEnum.values()); // 调度类型
model.addAttribute("MisfireStrategyEnum", MisfireStrategyEnum.values()); // 调度过期策略
// 执行器列表
List<XxlJobGroup> jobGroupListTotal = xxlJobGroupMapper.findAll();
// filter group
List<XxlJobGroup> jobGroupList = JobGroupPermissionUtil.filterJobGroupByPermission(request, jobGroupListTotal);
if (CollectionTool.isEmpty(jobGroupList)) {
throw new XxlJobException(I18nUtil.getString("jobgroup_empty"));
}
// parse jobGroup
if (!(CollectionTool.isNotEmpty(jobGroupList)
&& jobGroupList.stream().map(XxlJobGroup::getId).toList().contains(jobGroup))) {
jobGroup = -1;
}
model.addAttribute("JobGroupList", jobGroupList);
model.addAttribute("jobGroup", jobGroup);
return "biz/job.list";
}
@RequestMapping("/pageList")
@ResponseBody
public Response<PageModel<XxlJobInfo>> pageList(HttpServletRequest request,
@RequestParam(required = false, defaultValue = "0") int offset,
@RequestParam(required = false, defaultValue = "10") int pagesize,
@RequestParam int jobGroup,
@RequestParam int triggerStatus,
@RequestParam String jobDesc,
@RequestParam String executorHandler,
@RequestParam String author) {
// valid jobGroup permission
JobGroupPermissionUtil.validJobGroupPermission(request, jobGroup);
// page
return xxlJobService.pageList(offset, pagesize, jobGroup, triggerStatus, jobDesc, executorHandler, author);
}
@RequestMapping("/insert")
@ResponseBody
public Response<String> add(HttpServletRequest request, XxlJobInfo jobInfo) {
// valid permission
LoginInfo loginInfo = JobGroupPermissionUtil.validJobGroupPermission(request, jobInfo.getJobGroup());
// opt
return xxlJobService.add(jobInfo, loginInfo);
}
@RequestMapping("/update")
@ResponseBody
public Response<String> update(HttpServletRequest request, XxlJobInfo jobInfo) {
// valid permission
LoginInfo loginInfo = JobGroupPermissionUtil.validJobGroupPermission(request, jobInfo.getJobGroup());
// opt
return xxlJobService.update(jobInfo, loginInfo);
}
@RequestMapping("/delete")
@ResponseBody
public Response<String> delete(HttpServletRequest request, @RequestParam("ids[]") List<Integer> ids) {
// valid
if (CollectionTool.isEmpty(ids) || ids.size()!=1) {
return Response.ofFail(I18nUtil.getString("system_please_choose") + I18nUtil.getString("system_one") + I18nUtil.getString("system_data"));
}
// invoke
Response<LoginInfo> loginInfoResponse = XxlSsoHelper.loginCheckWithAttr(request);
return xxlJobService.remove(ids.get(0), loginInfoResponse.getData());
}
@RequestMapping("/stop")
@ResponseBody
public Response<String> pause(HttpServletRequest request, @RequestParam("ids[]") List<Integer> ids) {
// valid
if (CollectionTool.isEmpty(ids) || ids.size()!=1) {
return Response.ofFail(I18nUtil.getString("system_please_choose") + I18nUtil.getString("system_one") + I18nUtil.getString("system_data"));
}
// invoke
Response<LoginInfo> loginInfoResponse = XxlSsoHelper.loginCheckWithAttr(request);
return xxlJobService.stop(ids.get(0), loginInfoResponse.getData());
}
@RequestMapping("/start")
@ResponseBody
public Response<String> start(HttpServletRequest request, @RequestParam("ids[]") List<Integer> ids) {
// valid
if (CollectionTool.isEmpty(ids) || ids.size()!=1) {
return Response.ofFail(I18nUtil.getString("system_please_choose") + I18nUtil.getString("system_one") + I18nUtil.getString("system_data"));
}
// invoke
Response<LoginInfo> loginInfoResponse = XxlSsoHelper.loginCheckWithAttr(request);
return xxlJobService.start(ids.get(0), loginInfoResponse.getData());
}
@RequestMapping("/trigger")
@ResponseBody
public Response<String> triggerJob(HttpServletRequest request,
@RequestParam("id") int id,
@RequestParam("executorParam") String executorParam,
@RequestParam("addressList") String addressList) {
Response<LoginInfo> loginInfoResponse = XxlSsoHelper.loginCheckWithAttr(request);
return xxlJobService.trigger(loginInfoResponse.getData(), id, executorParam, addressList);
}
@RequestMapping("/nextTriggerTime")
@ResponseBody
public Response<List<String>> nextTriggerTime(@RequestParam("scheduleType") String scheduleType,
@RequestParam("scheduleConf") String scheduleConf) {
// valid
if (StringTool.isBlank(scheduleType) || StringTool.isBlank(scheduleConf)) {
return Response.ofSuccess(new ArrayList<>());
}
// param
XxlJobInfo paramXxlJobInfo = new XxlJobInfo();
paramXxlJobInfo.setScheduleType(scheduleType);
paramXxlJobInfo.setScheduleConf(scheduleConf);
// generate
List<String> result = new ArrayList<>();
try {
Date lastTime = new Date();
for (int i = 0; i < 5; i++) {
// generate next trigger time
ScheduleTypeEnum scheduleTypeEnum = ScheduleTypeEnum.match(paramXxlJobInfo.getScheduleType(), ScheduleTypeEnum.NONE);
lastTime = scheduleTypeEnum.getScheduleType().generateNextTriggerTime(paramXxlJobInfo, lastTime);
// collect data
if (lastTime != null) {
result.add(DateTool.formatDateTime(lastTime));
} else {
break;
}
}
} catch (Exception e) {
logger.error(">>>>>>>>>>> nextTriggerTime error. scheduleType = {}, scheduleConf= {}, error:{} ", scheduleType, scheduleConf, e.getMessage());
return Response.ofFail((I18nUtil.getString("schedule_type")+I18nUtil.getString("system_invalid")) + e.getMessage());
}
return Response.ofSuccess(result);
}
}

@ -0,0 +1,324 @@
package com.xxl.job.admin.controller.biz;
import com.xxl.job.admin.mapper.XxlJobGroupMapper;
import com.xxl.job.admin.mapper.XxlJobInfoMapper;
import com.xxl.job.admin.mapper.XxlJobLogMapper;
import com.xxl.job.admin.model.XxlJobGroup;
import com.xxl.job.admin.model.XxlJobInfo;
import com.xxl.job.admin.model.XxlJobLog;
import com.xxl.job.admin.scheduler.config.XxlJobAdminBootstrap;
import com.xxl.job.admin.scheduler.exception.XxlJobException;
import com.xxl.job.admin.service.XxlJobService;
import com.xxl.job.admin.util.I18nUtil;
import com.xxl.job.admin.util.JobGroupPermissionUtil;
import com.xxl.job.core.context.XxlJobContext;
import com.xxl.job.core.openapi.ExecutorBiz;
import com.xxl.job.core.openapi.model.KillRequest;
import com.xxl.job.core.openapi.model.LogRequest;
import com.xxl.job.core.openapi.model.LogResult;
import com.xxl.tool.core.CollectionTool;
import com.xxl.tool.core.DateTool;
import com.xxl.tool.core.StringTool;
import com.xxl.tool.response.PageModel;
import com.xxl.tool.response.Response;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.util.HtmlUtils;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* index controller
* @author xuxueli 2015-12-19 16:13:16
*/
@Controller
@RequestMapping("/joblog")
public class JobLogController {
private static final Logger logger = LoggerFactory.getLogger(JobLogController.class);
@Resource
private XxlJobGroupMapper xxlJobGroupMapper;
@Resource
public XxlJobInfoMapper xxlJobInfoMapper;
@Resource
public XxlJobLogMapper xxlJobLogMapper;
@Autowired
private XxlJobService xxlJobService;
@RequestMapping
public String index(HttpServletRequest request,
Model model,
@RequestParam(value = "jobGroup", required = false, defaultValue = "0") Integer jobGroup,
@RequestParam(value = "jobId", required = false, defaultValue = "0") Integer jobId) {
// 1、init JobGroupList
// find all jobGroup
List<XxlJobGroup> jobGroupListTotal = xxlJobGroupMapper.findAll();
// filter JobGroupList
List<XxlJobGroup> jobGroupList = JobGroupPermissionUtil.filterJobGroupByPermission(request, jobGroupListTotal);
if (CollectionTool.isEmpty(jobGroupList)) {
throw new XxlJobException(I18nUtil.getString("jobgroup_empty"));
}
List<Integer> jobGroupIds = jobGroupList.stream().map(XxlJobGroup::getId).toList();
// 2、check jobId
if (jobId > 0) {
// valid jobId
XxlJobInfo jobInfo = xxlJobInfoMapper.loadById(jobId);
if (jobInfo == null) {
throw new RuntimeException(I18nUtil.getString("jobinfo_field_id") + I18nUtil.getString("system_invalid"));
}
// valid jobGroup
jobGroup = jobInfo.getJobGroup();
}
// 3、init jobGroup, default first 1
if (!jobGroupIds.contains(jobGroup)) {
jobGroup = jobGroupList.get(0).getId();
}
// 4、init jobInfoList
List<XxlJobInfo> jobInfoList = xxlJobInfoMapper.getJobsByGroup(jobGroup);
List<Integer> jobIds = jobInfoList.stream().map(XxlJobInfo::getId).toList();
// 5、init JobId, default 0
if (!jobIds.contains(jobId)) {
jobId = 0;
}
// write
model.addAttribute("JobGroupList", jobGroupList);
model.addAttribute("jobInfoList", jobInfoList);
model.addAttribute("jobGroup", jobGroup);
model.addAttribute("jobId", jobId);
return "biz/log.list";
}
@RequestMapping("/pageList")
@ResponseBody
public Response<PageModel<XxlJobLog>> pageList(HttpServletRequest request,
@RequestParam(required = false, defaultValue = "0") int offset,
@RequestParam(required = false, defaultValue = "10") int pagesize,
@RequestParam int jobGroup,
@RequestParam int jobId,
@RequestParam int logStatus,
@RequestParam String filterTime) {
// valid jobGroup permission
JobGroupPermissionUtil.validJobGroupPermission(request, jobGroup);
// valid jobId
/*if (jobId < 1) {
return Response.ofFail(I18nUtil.getString("system_please_choose") + I18nUtil.getString("jobinfo_job"));
}*/
// parse param
Date triggerTimeStart = null;
Date triggerTimeEnd = null;
if (StringTool.isNotBlank(filterTime)) {
String[] temp = filterTime.split(" - ");
if (temp.length == 2) {
triggerTimeStart = DateTool.parseDateTime(temp[0]);
triggerTimeEnd = DateTool.parseDateTime(temp[1]);
}
}
// page query
List<XxlJobLog> list = xxlJobLogMapper.pageList(offset, pagesize, jobGroup, jobId, triggerTimeStart, triggerTimeEnd, logStatus);
int list_count = xxlJobLogMapper.pageListCount(offset, pagesize, jobGroup, jobId, triggerTimeStart, triggerTimeEnd, logStatus);
// package result
PageModel<XxlJobLog> pageModel = new PageModel<>();
pageModel.setData(list);
pageModel.setTotal(list_count);
return Response.ofSuccess(pageModel);
}
/**
* filter xss tag
*/
private String filter(String originData){
// exclude tag
Map<String, String> excludeTagMap = new HashMap<String, String>();
excludeTagMap.put("<br>", "###TAG_BR###");
excludeTagMap.put("<b>", "###TAG_BOLD###");
excludeTagMap.put("</b>", "###TAG_BOLD_END###");
// replace
for (String key : excludeTagMap.keySet()) {
String value = excludeTagMap.get(key);
originData = originData.replaceAll(key, value);
}
// htmlEscape
originData = HtmlUtils.htmlEscape(originData, "UTF-8");
// replace back
for (String key : excludeTagMap.keySet()) {
String value = excludeTagMap.get(key);
originData = originData.replaceAll(value, key);
}
return originData;
}
@RequestMapping("/logKill")
@ResponseBody
public Response<String> logKill(HttpServletRequest request, @RequestParam("id") long id){
// base check
XxlJobLog log = xxlJobLogMapper.load(id);
XxlJobInfo jobInfo = xxlJobInfoMapper.loadById(log.getJobId());
if (jobInfo==null) {
return Response.ofFail(I18nUtil.getString("jobinfo_glue_jobid_invalid"));
}
if (XxlJobContext.HANDLE_CODE_SUCCESS != log.getTriggerCode()) {
return Response.ofFail( I18nUtil.getString("joblog_kill_log_limit"));
}
// valid JobGroup permission
JobGroupPermissionUtil.validJobGroupPermission(request, jobInfo.getJobGroup());
// request of kill
Response<String> runResult = null;
try {
ExecutorBiz executorBiz = XxlJobAdminBootstrap.getExecutorBiz(log.getExecutorAddress());
runResult = executorBiz.kill(new KillRequest(jobInfo.getId()));
} catch (Exception e) {
logger.error(e.getMessage(), e);
runResult = Response.ofFail( e.getMessage());
}
if (XxlJobContext.HANDLE_CODE_SUCCESS == runResult.getCode()) {
log.setHandleCode(XxlJobContext.HANDLE_CODE_FAIL);
log.setHandleMsg( I18nUtil.getString("joblog_kill_log_byman")+":" + (runResult.getMsg()!=null?runResult.getMsg():""));
log.setHandleTime(new Date());
XxlJobAdminBootstrap.getInstance().getJobCompleter().complete(log);
return Response.ofSuccess(runResult.getMsg());
} else {
return Response.ofFail(runResult.getMsg());
}
}
@RequestMapping("/clearLog")
@ResponseBody
public Response<String> clearLog(HttpServletRequest request,
@RequestParam("jobGroup") int jobGroup,
@RequestParam("jobId") int jobId,
@RequestParam("type") int type){
// valid JobGroup permission
JobGroupPermissionUtil.validJobGroupPermission(request, jobGroup);
// valid jobId
if (jobId < 1) {
return Response.ofFail(I18nUtil.getString("system_please_choose") + I18nUtil.getString("jobinfo_job"));
}
// opt
Date clearBeforeTime = null;
int clearBeforeNum = 0;
if (type == 1) {
clearBeforeTime = DateTool.addMonths(new Date(), -1); // 清理一个月之前日志数据
} else if (type == 2) {
clearBeforeTime = DateTool.addMonths(new Date(), -3); // 清理三个月之前日志数据
} else if (type == 3) {
clearBeforeTime = DateTool.addMonths(new Date(), -6); // 清理六个月之前日志数据
} else if (type == 4) {
clearBeforeTime = DateTool.addYears(new Date(), -1); // 清理一年之前日志数据
} else if (type == 5) {
clearBeforeNum = 1000; // 清理一千条以前日志数据
} else if (type == 6) {
clearBeforeNum = 10000; // 清理一万条以前日志数据
} else if (type == 7) {
clearBeforeNum = 30000; // 清理三万条以前日志数据
} else if (type == 8) {
clearBeforeNum = 100000; // 清理十万条以前日志数据
} else if (type == 9) {
clearBeforeNum = 0; // 清理所有日志数据
} else {
return Response.ofFail(I18nUtil.getString("joblog_clean_type_invalid"));
}
List<Long> logIds = null;
do {
logIds = xxlJobLogMapper.findClearLogIds(jobGroup, jobId, clearBeforeTime, clearBeforeNum, 1000);
if (logIds!=null && !logIds.isEmpty()) {
xxlJobLogMapper.clearLog(logIds);
}
} while (logIds!=null && !logIds.isEmpty());
return Response.ofSuccess();
}
@RequestMapping("/logDetailPage")
public String logDetailPage(HttpServletRequest request, @RequestParam("id") long id, Model model){
// base check
XxlJobLog jobLog = xxlJobLogMapper.load(id);
if (jobLog == null) {
throw new RuntimeException(I18nUtil.getString("joblog_logid_invalid"));
}
// valid permission
JobGroupPermissionUtil.validJobGroupPermission(request, jobLog.getJobGroup());
// load jobInfo
XxlJobInfo jobInfo = xxlJobInfoMapper.loadById(jobLog.getJobId());
// data
model.addAttribute("triggerCode", jobLog.getTriggerCode());
model.addAttribute("handleCode", jobLog.getHandleCode());
model.addAttribute("logId", jobLog.getId());
model.addAttribute("jobInfo", jobInfo);
return "biz/log.detail";
}
@RequestMapping("/logDetailCat")
@ResponseBody
public Response<LogResult> logDetailCat(@RequestParam("logId") long logId, @RequestParam("fromLineNum") int fromLineNum){
try {
// valid
XxlJobLog jobLog = xxlJobLogMapper.load(logId); // todo, need to improve performance
if (jobLog == null) {
return Response.ofFail(I18nUtil.getString("joblog_logid_invalid"));
}
// log cat
ExecutorBiz executorBiz = XxlJobAdminBootstrap.getExecutorBiz(jobLog.getExecutorAddress());
Response<LogResult> logResult = executorBiz.log(new LogRequest(jobLog.getTriggerTime().getTime(), logId, fromLineNum));
// is end
if (logResult.getData()!=null && logResult.getData().getFromLineNum() > logResult.getData().getToLineNum()) {
if (jobLog.getHandleCode() > 0) {
logResult.getData().setEnd(true);
}
}
// fix xss
if (logResult.getData()!=null && StringTool.isNotBlank(logResult.getData().getLogContent())) {
String newLogContent = filter(logResult.getData().getLogContent());
logResult.getData().setLogContent(newLogContent);
}
return logResult;
} catch (Exception e) {
logger.error("logId({}) logDetailCat error: {}", logId, e.getMessage(), e);
return Response.ofFail(e.getMessage());
}
}
}

@ -0,0 +1,200 @@
package com.xxl.job.admin.controller.biz;
import com.xxl.job.admin.constant.Consts;
import com.xxl.job.admin.mapper.XxlJobGroupMapper;
import com.xxl.job.admin.mapper.XxlJobUserMapper;
import com.xxl.job.admin.model.XxlJobGroup;
import com.xxl.job.admin.model.XxlJobUser;
import com.xxl.job.admin.util.I18nUtil;
import com.xxl.sso.core.annotation.XxlSso;
import com.xxl.sso.core.helper.XxlSsoHelper;
import com.xxl.sso.core.model.LoginInfo;
import com.xxl.tool.core.CollectionTool;
import com.xxl.tool.core.StringTool;
import com.xxl.tool.crypto.Sha256Tool;
import com.xxl.tool.response.PageModel;
import com.xxl.tool.response.Response;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author xuxueli 2019-05-04 16:39:50
*/
@Controller
@RequestMapping("/user")
public class JobUserController {
@Resource
private XxlJobUserMapper xxlJobUserMapper;
@Resource
private XxlJobGroupMapper xxlJobGroupMapper;
@RequestMapping
@XxlSso(role = Consts.ADMIN_ROLE)
public String index(Model model) {
// 执行器列表
List<XxlJobGroup> groupList = xxlJobGroupMapper.findAll();
model.addAttribute("groupList", groupList);
return "biz/user.list";
}
@RequestMapping("/pageList")
@ResponseBody
@XxlSso(role = Consts.ADMIN_ROLE)
public Response<PageModel<XxlJobUser>> pageList(@RequestParam(required = false, defaultValue = "0") int offset,
@RequestParam(required = false, defaultValue = "10") int pagesize,
@RequestParam String username,
@RequestParam int role) {
// page list
List<XxlJobUser> list = xxlJobUserMapper.pageList(offset, pagesize, username, role);
int list_count = xxlJobUserMapper.pageListCount(offset, pagesize, username, role);
// filter
if (list!=null && !list.isEmpty()) {
for (XxlJobUser item: list) {
item.setPassword(null);
}
}
// package result
PageModel<XxlJobUser> pageModel = new PageModel<>();
pageModel.setData(list);
pageModel.setTotal(list_count);
return Response.ofSuccess(pageModel);
}
@RequestMapping("/insert")
@ResponseBody
@XxlSso(role = Consts.ADMIN_ROLE)
public Response<String> insert(XxlJobUser xxlJobUser) {
// valid username
if (StringTool.isBlank(xxlJobUser.getUsername())) {
return Response.ofFail(I18nUtil.getString("system_please_input")+I18nUtil.getString("user_username") );
}
xxlJobUser.setUsername(xxlJobUser.getUsername().trim());
if (!(xxlJobUser.getUsername().length()>=4 && xxlJobUser.getUsername().length()<=20)) {
return Response.ofFail(I18nUtil.getString("system_length_limit")+"[4-20]" );
}
// valid password
if (StringTool.isBlank(xxlJobUser.getPassword())) {
return Response.ofFail(I18nUtil.getString("system_please_input")+I18nUtil.getString("user_password") );
}
xxlJobUser.setPassword(xxlJobUser.getPassword().trim());
if (!(xxlJobUser.getPassword().length()>=4 && xxlJobUser.getPassword().length()<=20)) {
return Response.ofFail(I18nUtil.getString("system_length_limit")+"[4-20]" );
}
// md5 password
String passwordHash = Sha256Tool.sha256(xxlJobUser.getPassword());
xxlJobUser.setPassword(passwordHash);
// check repeat
XxlJobUser existUser = xxlJobUserMapper.loadByUserName(xxlJobUser.getUsername());
if (existUser != null) {
return Response.ofFail( I18nUtil.getString("user_username_repeat") );
}
// write
xxlJobUserMapper.save(xxlJobUser);
return Response.ofSuccess();
}
@RequestMapping("/update")
@ResponseBody
@XxlSso(role = Consts.ADMIN_ROLE)
public Response<String> update(HttpServletRequest request, XxlJobUser xxlJobUser) {
// avoid opt login seft
Response<LoginInfo> loginInfoResponse = XxlSsoHelper.loginCheckWithAttr(request);
if (loginInfoResponse.getData().getUserName().equals(xxlJobUser.getUsername())) {
return Response.ofFail(I18nUtil.getString("user_update_loginuser_limit"));
}
// valid password
if (StringTool.isNotBlank(xxlJobUser.getPassword())) {
xxlJobUser.setPassword(xxlJobUser.getPassword().trim());
if (!(xxlJobUser.getPassword().length()>=4 && xxlJobUser.getPassword().length()<=20)) {
return Response.ofFail(I18nUtil.getString("system_length_limit")+"[4-20]" );
}
// md5 password
String passwordHash = Sha256Tool.sha256(xxlJobUser.getPassword());
xxlJobUser.setPassword(passwordHash);
} else {
xxlJobUser.setPassword(null);
}
// write
xxlJobUserMapper.update(xxlJobUser);
return Response.ofSuccess();
}
@RequestMapping("/delete")
@ResponseBody
@XxlSso(role = Consts.ADMIN_ROLE)
public Response<String> delete(HttpServletRequest request, @RequestParam("ids[]") List<Integer> ids) {
// valid
if (CollectionTool.isEmpty(ids) || ids.size()!=1) {
return Response.ofFail(I18nUtil.getString("system_please_choose") + I18nUtil.getString("system_one") + I18nUtil.getString("system_data"));
}
// avoid opt login seft
Response<LoginInfo> loginInfoResponse = XxlSsoHelper.loginCheckWithAttr(request);
if (ids.contains(Integer.parseInt(loginInfoResponse.getData().getUserId()))) {
return Response.ofFail(I18nUtil.getString("user_update_loginuser_limit"));
}
xxlJobUserMapper.delete(ids.get(0));
return Response.ofSuccess();
}
/*@RequestMapping("/updatePwd")
@ResponseBody
public Response<String> updatePwd(HttpServletRequest request,
@RequestParam("password") String password,
@RequestParam("oldPassword") String oldPassword){
// valid
if (oldPassword==null || oldPassword.trim().isEmpty()){
return Response.ofFail(I18nUtil.getString("system_please_input") + I18nUtil.getString("change_pwd_field_oldpwd"));
}
if (password==null || password.trim().isEmpty()){
return Response.ofFail(I18nUtil.getString("system_please_input") + I18nUtil.getString("change_pwd_field_oldpwd"));
}
password = password.trim();
if (!(password.length()>=4 && password.length()<=20)) {
return Response.ofFail(I18nUtil.getString("system_length_limit")+"[4-20]" );
}
// md5 password
String oldPasswordHash = Sha256Tool.sha256(oldPassword);
String passwordHash = Sha256Tool.sha256(password);
// valid old pwd
Response<LoginInfo> loginInfoResponse = XxlSsoHelper.loginCheckWithAttr(request);
XxlJobUser existUser = xxlJobUserMapper.loadByUserName(loginInfoResponse.getData().getUserName());
if (!oldPasswordHash.equals(existUser.getPassword())) {
return Response.ofFail(I18nUtil.getString("change_pwd_field_oldpwd") + I18nUtil.getString("system_invalid"));
}
// write new
existUser.setPassword(passwordHash);
xxlJobUserMapper.update(existUser);
return Response.ofSuccess();
}*/
}

@ -1,42 +0,0 @@
package com.xxl.job.admin.controller.interceptor;
import com.xxl.job.admin.core.util.FtlUtil;
import com.xxl.job.admin.core.util.I18nUtil;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.AsyncHandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
/**
* push cookies to model as cookieMap
*
* @author xuxueli 2015-12-12 18:09:04
*/
@Component
public class CookieInterceptor implements AsyncHandlerInterceptor {
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
// cookie
if (modelAndView!=null && request.getCookies()!=null && request.getCookies().length>0) {
HashMap<String, Cookie> cookieMap = new HashMap<String, Cookie>();
for (Cookie ck : request.getCookies()) {
cookieMap.put(ck.getName(), ck);
}
modelAndView.addObject("cookieMap", cookieMap);
}
// static method
if (modelAndView != null) {
modelAndView.addObject("I18nUtil", FtlUtil.generateStaticModel(I18nUtil.class.getName()));
}
}
}

@ -1,119 +0,0 @@
package com.xxl.job.admin.controller.interceptor;
import com.xxl.job.admin.controller.annotation.PermissionLimit;
import com.xxl.job.admin.core.model.XxlJobGroup;
import com.xxl.job.admin.core.model.XxlJobUser;
import com.xxl.job.admin.core.util.I18nUtil;
import com.xxl.job.admin.service.impl.LoginService;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.AsyncHandlerInterceptor;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
*
*
* @author xuxueli 2015-12-12 18:09:04
*/
@Component
public class PermissionInterceptor implements AsyncHandlerInterceptor {
@Resource
private LoginService loginService;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (!(handler instanceof HandlerMethod)) {
return true; // proceed with the next interceptor
}
// if need login
boolean needLogin = true;
boolean needAdminuser = false;
HandlerMethod method = (HandlerMethod)handler;
PermissionLimit permission = method.getMethodAnnotation(PermissionLimit.class);
if (permission!=null) {
needLogin = permission.limit();
needAdminuser = permission.adminuser();
}
if (needLogin) {
XxlJobUser loginUser = loginService.ifLogin(request, response);
if (loginUser == null) {
response.setStatus(302);
response.setHeader("location", request.getContextPath()+"/toLogin");
return false;
}
if (needAdminuser && loginUser.getRole()!=1) {
throw new RuntimeException(I18nUtil.getString("system_permission_limit"));
}
request.setAttribute(LoginService.LOGIN_IDENTITY_KEY, loginUser); // set loginUser, with request
}
return true; // proceed with the next interceptor
}
// -------------------- permission tool --------------------
/**
* get loginUser
*
* @param request
* @return
*/
public static XxlJobUser getLoginUser(HttpServletRequest request){
XxlJobUser loginUser = (XxlJobUser) request.getAttribute(LoginService.LOGIN_IDENTITY_KEY); // get loginUser, with request
return loginUser;
}
/**
* valid permission by JobGroup
*
* @param request
* @param jobGroup
*/
public static void validJobGroupPermission(HttpServletRequest request, int jobGroup) {
XxlJobUser loginUser = getLoginUser(request);
if (!loginUser.validPermission(jobGroup)) {
throw new RuntimeException(I18nUtil.getString("system_permission_limit") + "[username="+ loginUser.getUsername() +"]");
}
}
/**
* filter XxlJobGroup by role
*
* @param request
* @param jobGroupList_all
* @return
*/
public static List<XxlJobGroup> filterJobGroupByRole(HttpServletRequest request, List<XxlJobGroup> jobGroupList_all){
List<XxlJobGroup> jobGroupList = new ArrayList<>();
if (jobGroupList_all!=null && jobGroupList_all.size()>0) {
XxlJobUser loginUser = PermissionInterceptor.getLoginUser(request);
if (loginUser.getRole() == 1) {
jobGroupList = jobGroupList_all;
} else {
List<String> groupIdStrs = new ArrayList<>();
if (loginUser.getPermission()!=null && loginUser.getPermission().trim().length()>0) {
groupIdStrs = Arrays.asList(loginUser.getPermission().trim().split(","));
}
for (XxlJobGroup groupItem:jobGroupList_all) {
if (groupIdStrs.contains(String.valueOf(groupItem.getId()))) {
jobGroupList.add(groupItem);
}
}
}
}
return jobGroupList;
}
}

@ -1,28 +0,0 @@
package com.xxl.job.admin.controller.interceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import javax.annotation.Resource;
/**
* web mvc config
*
* @author xuxueli 2018-04-02 20:48:20
*/
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Resource
private PermissionInterceptor permissionInterceptor;
@Resource
private CookieInterceptor cookieInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(permissionInterceptor).addPathPatterns("/**");
registry.addInterceptor(cookieInterceptor).addPathPatterns("/**");
}
}

@ -1,165 +0,0 @@
package com.xxl.job.admin.core.conf;
import com.xxl.job.admin.core.alarm.JobAlarmer;
import com.xxl.job.admin.core.scheduler.XxlJobScheduler;
import com.xxl.job.admin.dao.*;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import javax.sql.DataSource;
import java.util.Arrays;
/**
* xxl-job config
*
* @author xuxueli 2017-04-28
*/
@Component
public class XxlJobAdminConfig implements InitializingBean, DisposableBean {
private static XxlJobAdminConfig adminConfig = null;
public static XxlJobAdminConfig getAdminConfig() {
return adminConfig;
}
// ---------------------- XxlJobScheduler ----------------------
private XxlJobScheduler xxlJobScheduler;
@Override
public void afterPropertiesSet() throws Exception {
adminConfig = this;
xxlJobScheduler = new XxlJobScheduler();
xxlJobScheduler.init();
}
@Override
public void destroy() throws Exception {
xxlJobScheduler.destroy();
}
// ---------------------- XxlJobScheduler ----------------------
// conf
@Value("${xxl.job.i18n}")
private String i18n;
@Value("${xxl.job.accessToken}")
private String accessToken;
@Value("${xxl.job.timeout}")
private int timeout;
@Value("${spring.mail.from}")
private String emailFrom;
@Value("${xxl.job.triggerpool.fast.max}")
private int triggerPoolFastMax;
@Value("${xxl.job.triggerpool.slow.max}")
private int triggerPoolSlowMax;
@Value("${xxl.job.logretentiondays}")
private int logretentiondays;
// dao, service
@Resource
private XxlJobLogDao xxlJobLogDao;
@Resource
private XxlJobInfoDao xxlJobInfoDao;
@Resource
private XxlJobRegistryDao xxlJobRegistryDao;
@Resource
private XxlJobGroupDao xxlJobGroupDao;
@Resource
private XxlJobLogReportDao xxlJobLogReportDao;
@Resource
private JavaMailSender mailSender;
@Resource
private DataSource dataSource;
@Resource
private JobAlarmer jobAlarmer;
public String getI18n() {
if (!Arrays.asList("zh_CN", "zh_TC", "en").contains(i18n)) {
return "zh_CN";
}
return i18n;
}
public String getAccessToken() {
return accessToken;
}
public int getTimeout() {
return timeout;
}
public String getEmailFrom() {
return emailFrom;
}
public int getTriggerPoolFastMax() {
if (triggerPoolFastMax < 200) {
return 200;
}
return triggerPoolFastMax;
}
public int getTriggerPoolSlowMax() {
if (triggerPoolSlowMax < 100) {
return 100;
}
return triggerPoolSlowMax;
}
public int getLogretentiondays() {
if (logretentiondays < 7) {
return -1; // Limit greater than or equal to 7, otherwise close
}
return logretentiondays;
}
public XxlJobLogDao getXxlJobLogDao() {
return xxlJobLogDao;
}
public XxlJobInfoDao getXxlJobInfoDao() {
return xxlJobInfoDao;
}
public XxlJobRegistryDao getXxlJobRegistryDao() {
return xxlJobRegistryDao;
}
public XxlJobGroupDao getXxlJobGroupDao() {
return xxlJobGroupDao;
}
public XxlJobLogReportDao getXxlJobLogReportDao() {
return xxlJobLogReportDao;
}
public JavaMailSender getMailSender() {
return mailSender;
}
public DataSource getDataSource() {
return dataSource;
}
public JobAlarmer getJobAlarmer() {
return jobAlarmer;
}
}

@ -1,19 +0,0 @@
package com.xxl.job.admin.core.route.strategy;
import com.xxl.job.admin.core.route.ExecutorRouter;
import com.xxl.job.core.biz.model.ReturnT;
import com.xxl.job.core.biz.model.TriggerParam;
import java.util.List;
/**
* Created by xuxueli on 17/3/10.
*/
public class ExecutorRouteFirst extends ExecutorRouter {
@Override
public ReturnT<String> route(TriggerParam triggerParam, List<String> addressList){
return new ReturnT<String>(addressList.get(0));
}
}

@ -1,19 +0,0 @@
package com.xxl.job.admin.core.route.strategy;
import com.xxl.job.admin.core.route.ExecutorRouter;
import com.xxl.job.core.biz.model.ReturnT;
import com.xxl.job.core.biz.model.TriggerParam;
import java.util.List;
/**
* Created by xuxueli on 17/3/10.
*/
public class ExecutorRouteLast extends ExecutorRouter {
@Override
public ReturnT<String> route(TriggerParam triggerParam, List<String> addressList) {
return new ReturnT<String>(addressList.get(addressList.size()-1));
}
}

@ -1,23 +0,0 @@
package com.xxl.job.admin.core.route.strategy;
import com.xxl.job.admin.core.route.ExecutorRouter;
import com.xxl.job.core.biz.model.ReturnT;
import com.xxl.job.core.biz.model.TriggerParam;
import java.util.List;
import java.util.Random;
/**
* Created by xuxueli on 17/3/10.
*/
public class ExecutorRouteRandom extends ExecutorRouter {
private static Random localRandom = new Random();
@Override
public ReturnT<String> route(TriggerParam triggerParam, List<String> addressList) {
String address = addressList.get(localRandom.nextInt(addressList.size()));
return new ReturnT<String>(address);
}
}

@ -1,39 +0,0 @@
package com.xxl.job.admin.core.scheduler;
import com.xxl.job.admin.core.util.I18nUtil;
/**
* @author xuxueli 2020-10-29 21:11:23
*/
public enum MisfireStrategyEnum {
/**
* do nothing
*/
DO_NOTHING(I18nUtil.getString("misfire_strategy_do_nothing")),
/**
* fire once now
*/
FIRE_ONCE_NOW(I18nUtil.getString("misfire_strategy_fire_once_now"));
private String title;
MisfireStrategyEnum(String title) {
this.title = title;
}
public String getTitle() {
return title;
}
public static MisfireStrategyEnum match(String name, MisfireStrategyEnum defaultItem){
for (MisfireStrategyEnum item: MisfireStrategyEnum.values()) {
if (item.name().equals(name)) {
return item;
}
}
return defaultItem;
}
}

@ -1,46 +0,0 @@
package com.xxl.job.admin.core.scheduler;
import com.xxl.job.admin.core.util.I18nUtil;
/**
* @author xuxueli 2020-10-29 21:11:23
*/
public enum ScheduleTypeEnum {
NONE(I18nUtil.getString("schedule_type_none")),
/**
* schedule by cron
*/
CRON(I18nUtil.getString("schedule_type_cron")),
/**
* schedule by fixed rate (in seconds)
*/
FIX_RATE(I18nUtil.getString("schedule_type_fix_rate")),
/**
* schedule by fix delay (in seconds) after the last time
*/
/*FIX_DELAY(I18nUtil.getString("schedule_type_fix_delay"))*/;
private String title;
ScheduleTypeEnum(String title) {
this.title = title;
}
public String getTitle() {
return title;
}
public static ScheduleTypeEnum match(String name, ScheduleTypeEnum defaultItem){
for (ScheduleTypeEnum item: ScheduleTypeEnum.values()) {
if (item.name().equals(name)) {
return item;
}
}
return defaultItem;
}
}

@ -1,103 +0,0 @@
package com.xxl.job.admin.core.scheduler;
import com.xxl.job.admin.core.conf.XxlJobAdminConfig;
import com.xxl.job.admin.core.thread.*;
import com.xxl.job.admin.core.util.I18nUtil;
import com.xxl.job.core.biz.ExecutorBiz;
import com.xxl.job.core.biz.client.ExecutorBizClient;
import com.xxl.job.core.enums.ExecutorBlockStrategyEnum;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/**
* @author xuxueli 2018-10-28 00:18:17
*/
public class XxlJobScheduler {
private static final Logger logger = LoggerFactory.getLogger(XxlJobScheduler.class);
public void init() throws Exception {
// init i18n
initI18n();
// admin trigger pool start
JobTriggerPoolHelper.toStart();
// admin registry monitor run
JobRegistryHelper.getInstance().start();
// admin fail-monitor run
JobFailMonitorHelper.getInstance().start();
// admin lose-monitor run ( depend on JobTriggerPoolHelper )
JobCompleteHelper.getInstance().start();
// admin log report start
JobLogReportHelper.getInstance().start();
// start-schedule ( depend on JobTriggerPoolHelper )
JobScheduleHelper.getInstance().start();
logger.info(">>>>>>>>> init xxl-job admin success.");
}
public void destroy() throws Exception {
// stop-schedule
JobScheduleHelper.getInstance().toStop();
// admin log report stop
JobLogReportHelper.getInstance().toStop();
// admin lose-monitor stop
JobCompleteHelper.getInstance().toStop();
// admin fail-monitor stop
JobFailMonitorHelper.getInstance().toStop();
// admin registry stop
JobRegistryHelper.getInstance().toStop();
// admin trigger pool stop
JobTriggerPoolHelper.toStop();
}
// ---------------------- I18n ----------------------
private void initI18n(){
for (ExecutorBlockStrategyEnum item:ExecutorBlockStrategyEnum.values()) {
item.setTitle(I18nUtil.getString("jobconf_block_".concat(item.name())));
}
}
// ---------------------- executor-client ----------------------
private static ConcurrentMap<String, ExecutorBiz> executorBizRepository = new ConcurrentHashMap<String, ExecutorBiz>();
public static ExecutorBiz getExecutorBiz(String address) throws Exception {
// valid
if (address==null || address.trim().length()==0) {
return null;
}
// load-cache
address = address.trim();
ExecutorBiz executorBiz = executorBizRepository.get(address);
if (executorBiz != null) {
return executorBiz;
}
// set-cache
executorBiz = new ExecutorBizClient(address,
XxlJobAdminConfig.getAdminConfig().getAccessToken(),
XxlJobAdminConfig.getAdminConfig().getTimeout());
executorBizRepository.put(address, executorBiz);
return executorBiz;
}
}

@ -1,152 +0,0 @@
package com.xxl.job.admin.core.thread;
import com.xxl.job.admin.core.conf.XxlJobAdminConfig;
import com.xxl.job.admin.core.model.XxlJobLogReport;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
/**
* job log report helper
*
* @author xuxueli 2019-11-22
*/
public class JobLogReportHelper {
private static Logger logger = LoggerFactory.getLogger(JobLogReportHelper.class);
private static JobLogReportHelper instance = new JobLogReportHelper();
public static JobLogReportHelper getInstance(){
return instance;
}
private Thread logrThread;
private volatile boolean toStop = false;
public void start(){
logrThread = new Thread(new Runnable() {
@Override
public void run() {
// last clean log time
long lastCleanLogTime = 0;
while (!toStop) {
// 1、log-report refresh: refresh log report in 3 days
try {
for (int i = 0; i < 3; i++) {
// today
Calendar itemDay = Calendar.getInstance();
itemDay.add(Calendar.DAY_OF_MONTH, -i);
itemDay.set(Calendar.HOUR_OF_DAY, 0);
itemDay.set(Calendar.MINUTE, 0);
itemDay.set(Calendar.SECOND, 0);
itemDay.set(Calendar.MILLISECOND, 0);
Date todayFrom = itemDay.getTime();
itemDay.set(Calendar.HOUR_OF_DAY, 23);
itemDay.set(Calendar.MINUTE, 59);
itemDay.set(Calendar.SECOND, 59);
itemDay.set(Calendar.MILLISECOND, 999);
Date todayTo = itemDay.getTime();
// refresh log-report every minute
XxlJobLogReport xxlJobLogReport = new XxlJobLogReport();
xxlJobLogReport.setTriggerDay(todayFrom);
xxlJobLogReport.setRunningCount(0);
xxlJobLogReport.setSucCount(0);
xxlJobLogReport.setFailCount(0);
Map<String, Object> triggerCountMap = XxlJobAdminConfig.getAdminConfig().getXxlJobLogDao().findLogReport(todayFrom, todayTo);
if (triggerCountMap!=null && triggerCountMap.size()>0) {
int triggerDayCount = triggerCountMap.containsKey("triggerDayCount")?Integer.valueOf(String.valueOf(triggerCountMap.get("triggerDayCount"))):0;
int triggerDayCountRunning = triggerCountMap.containsKey("triggerDayCountRunning")?Integer.valueOf(String.valueOf(triggerCountMap.get("triggerDayCountRunning"))):0;
int triggerDayCountSuc = triggerCountMap.containsKey("triggerDayCountSuc")?Integer.valueOf(String.valueOf(triggerCountMap.get("triggerDayCountSuc"))):0;
int triggerDayCountFail = triggerDayCount - triggerDayCountRunning - triggerDayCountSuc;
xxlJobLogReport.setRunningCount(triggerDayCountRunning);
xxlJobLogReport.setSucCount(triggerDayCountSuc);
xxlJobLogReport.setFailCount(triggerDayCountFail);
}
// do refresh
int ret = XxlJobAdminConfig.getAdminConfig().getXxlJobLogReportDao().update(xxlJobLogReport);
if (ret < 1) {
XxlJobAdminConfig.getAdminConfig().getXxlJobLogReportDao().save(xxlJobLogReport);
}
}
} catch (Throwable e) {
if (!toStop) {
logger.error(">>>>>>>>>>> xxl-job, job log report thread error:{}", e);
}
}
// 2、log-clean: switch open & once each day
if (XxlJobAdminConfig.getAdminConfig().getLogretentiondays()>0
&& System.currentTimeMillis() - lastCleanLogTime > 24*60*60*1000) {
// expire-time
Calendar expiredDay = Calendar.getInstance();
expiredDay.add(Calendar.DAY_OF_MONTH, -1 * XxlJobAdminConfig.getAdminConfig().getLogretentiondays());
expiredDay.set(Calendar.HOUR_OF_DAY, 0);
expiredDay.set(Calendar.MINUTE, 0);
expiredDay.set(Calendar.SECOND, 0);
expiredDay.set(Calendar.MILLISECOND, 0);
Date clearBeforeTime = expiredDay.getTime();
// clean expired log
List<Long> logIds = null;
do {
logIds = XxlJobAdminConfig.getAdminConfig().getXxlJobLogDao().findClearLogIds(0, 0, clearBeforeTime, 0, 1000);
if (logIds!=null && logIds.size()>0) {
XxlJobAdminConfig.getAdminConfig().getXxlJobLogDao().clearLog(logIds);
}
} while (logIds!=null && logIds.size()>0);
// update clean time
lastCleanLogTime = System.currentTimeMillis();
}
try {
TimeUnit.MINUTES.sleep(1);
} catch (Throwable e) {
if (!toStop) {
logger.error(e.getMessage(), e);
}
}
}
logger.info(">>>>>>>>>>> xxl-job, job log report thread stop");
}
});
logrThread.setDaemon(true);
logrThread.setName("xxl-job, admin JobLogReportHelper");
logrThread.start();
}
public void toStop(){
toStop = true;
// interrupt and wait
logrThread.interrupt();
try {
logrThread.join();
} catch (Throwable e) {
logger.error(e.getMessage(), e);
}
}
}

@ -1,98 +0,0 @@
package com.xxl.job.admin.core.util;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Cookie.Util
*
* @author xuxueli 2015-12-12 18:01:06
*/
public class CookieUtil {
// 默认缓存时间,单位/秒, 2H
private static final int COOKIE_MAX_AGE = Integer.MAX_VALUE;
// 保存路径,根路径
private static final String COOKIE_PATH = "/";
/**
*
*
* @param response
* @param key
* @param value
* @param ifRemember
*/
public static void set(HttpServletResponse response, String key, String value, boolean ifRemember) {
int age = ifRemember?COOKIE_MAX_AGE:-1;
set(response, key, value, null, COOKIE_PATH, age, true);
}
/**
*
*
* @param response
* @param key
* @param value
* @param maxAge
*/
private static void set(HttpServletResponse response, String key, String value, String domain, String path, int maxAge, boolean isHttpOnly) {
Cookie cookie = new Cookie(key, value);
if (domain != null) {
cookie.setDomain(domain);
}
cookie.setPath(path);
cookie.setMaxAge(maxAge);
cookie.setHttpOnly(isHttpOnly);
response.addCookie(cookie);
}
/**
* value
*
* @param request
* @param key
* @return
*/
public static String getValue(HttpServletRequest request, String key) {
Cookie cookie = get(request, key);
if (cookie != null) {
return cookie.getValue();
}
return null;
}
/**
* Cookie
*
* @param request
* @param key
*/
private static Cookie get(HttpServletRequest request, String key) {
Cookie[] arr_cookie = request.getCookies();
if (arr_cookie != null && arr_cookie.length > 0) {
for (Cookie cookie : arr_cookie) {
if (cookie.getName().equals(key)) {
return cookie;
}
}
}
return null;
}
/**
* Cookie
*
* @param request
* @param response
* @param key
*/
public static void remove(HttpServletRequest request, HttpServletResponse response, String key) {
Cookie cookie = get(request, key);
if (cookie != null) {
set(response, key, "", null, COOKIE_PATH, 0, true);
}
}
}

@ -1,31 +0,0 @@
package com.xxl.job.admin.core.util;
import freemarker.ext.beans.BeansWrapper;
import freemarker.ext.beans.BeansWrapperBuilder;
import freemarker.template.Configuration;
import freemarker.template.TemplateHashModel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* ftl util
*
* @author xuxueli 2018-01-17 20:37:48
*/
public class FtlUtil {
private static Logger logger = LoggerFactory.getLogger(FtlUtil.class);
private static BeansWrapper wrapper = new BeansWrapperBuilder(Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS).build(); //BeansWrapper.getDefaultInstance();
public static TemplateHashModel generateStaticModel(String packageName) {
try {
TemplateHashModel staticModels = wrapper.getStaticModels();
TemplateHashModel fileStatics = (TemplateHashModel) staticModels.get(packageName);
return fileStatics;
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
return null;
}
}

@ -1,79 +0,0 @@
package com.xxl.job.admin.core.util;
import com.xxl.job.admin.core.conf.XxlJobAdminConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.EncodedResource;
import org.springframework.core.io.support.PropertiesLoaderUtils;
import java.io.IOException;
import java.text.MessageFormat;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
/**
* i18n util
*
* @author xuxueli 2018-01-17 20:39:06
*/
public class I18nUtil {
private static Logger logger = LoggerFactory.getLogger(I18nUtil.class);
private static Properties prop = null;
public static Properties loadI18nProp(){
if (prop != null) {
return prop;
}
try {
// build i18n prop
String i18n = XxlJobAdminConfig.getAdminConfig().getI18n();
String i18nFile = MessageFormat.format("i18n/message_{0}.properties", i18n);
// load prop
Resource resource = new ClassPathResource(i18nFile);
EncodedResource encodedResource = new EncodedResource(resource,"UTF-8");
prop = PropertiesLoaderUtils.loadProperties(encodedResource);
} catch (IOException e) {
logger.error(e.getMessage(), e);
}
return prop;
}
/**
* get val of i18n key
*
* @param key
* @return
*/
public static String getString(String key) {
return loadI18nProp().getProperty(key);
}
/**
* get mult val of i18n mult key, as json
*
* @param keys
* @return
*/
public static String getMultString(String... keys) {
Map<String, String> map = new HashMap<String, String>();
Properties prop = loadI18nProp();
if (keys!=null && keys.length>0) {
for (String key: keys) {
map.put(key, prop.getProperty(key));
}
} else {
for (String key: prop.stringPropertyNames()) {
map.put(key, prop.getProperty(key));
}
}
String json = JacksonUtil.writeValueAsString(map);
return json;
}
}

@ -1,92 +0,0 @@
package com.xxl.job.admin.core.util;
import com.fasterxml.jackson.core.JsonGenerationException;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
/**
* Jackson util
*
* 1obj need private and set/get
* 2do not support inner class
*
* @author xuxueli 2015-9-25 18:02:56
*/
public class JacksonUtil {
private static Logger logger = LoggerFactory.getLogger(JacksonUtil.class);
private final static ObjectMapper objectMapper = new ObjectMapper();
public static ObjectMapper getInstance() {
return objectMapper;
}
/**
* beanarrayListMap --> json
*
* @param obj
* @return json string
* @throws Exception
*/
public static String writeValueAsString(Object obj) {
try {
return getInstance().writeValueAsString(obj);
} catch (JsonGenerationException e) {
logger.error(e.getMessage(), e);
} catch (JsonMappingException e) {
logger.error(e.getMessage(), e);
} catch (IOException e) {
logger.error(e.getMessage(), e);
}
return null;
}
/**
* string --> beanMapList(array)
*
* @param jsonStr
* @param clazz
* @return obj
* @throws Exception
*/
public static <T> T readValue(String jsonStr, Class<T> clazz) {
try {
return getInstance().readValue(jsonStr, clazz);
} catch (JsonParseException e) {
logger.error(e.getMessage(), e);
} catch (JsonMappingException e) {
logger.error(e.getMessage(), e);
} catch (IOException e) {
logger.error(e.getMessage(), e);
}
return null;
}
/**
* string --> List<Bean>...
*
* @param jsonStr
* @param parametrized
* @param parameterClasses
* @param <T>
* @return
*/
public static <T> T readValue(String jsonStr, Class<?> parametrized, Class<?>... parameterClasses) {
try {
JavaType javaType = getInstance().getTypeFactory().constructParametricType(parametrized, parameterClasses);
return getInstance().readValue(jsonStr, javaType);
} catch (JsonParseException e) {
logger.error(e.getMessage(), e);
} catch (JsonMappingException e) {
logger.error(e.getMessage(), e);
} catch (IOException e) {
logger.error(e.getMessage(), e);
}
return null;
}
}

@ -1,133 +0,0 @@
package com.xxl.job.admin.core.util;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/**
* local cache tool
*
* @author xuxueli 2018-01-22 21:37:34
*/
public class LocalCacheUtil {
private static ConcurrentMap<String, LocalCacheData> cacheRepository = new ConcurrentHashMap<String, LocalCacheData>(); // 类型建议用抽象父类,兼容性更好;
private static class LocalCacheData{
private String key;
private Object val;
private long timeoutTime;
public LocalCacheData() {
}
public LocalCacheData(String key, Object val, long timeoutTime) {
this.key = key;
this.val = val;
this.timeoutTime = timeoutTime;
}
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
public Object getVal() {
return val;
}
public void setVal(Object val) {
this.val = val;
}
public long getTimeoutTime() {
return timeoutTime;
}
public void setTimeoutTime(long timeoutTime) {
this.timeoutTime = timeoutTime;
}
}
/**
* set cache
*
* @param key
* @param val
* @param cacheTime
* @return
*/
public static boolean set(String key, Object val, long cacheTime){
// clean timeout cache, before set new cache (avoid cache too much)
cleanTimeoutCache();
// set new cache
if (key==null || key.trim().length()==0) {
return false;
}
if (val == null) {
remove(key);
}
if (cacheTime <= 0) {
remove(key);
}
long timeoutTime = System.currentTimeMillis() + cacheTime;
LocalCacheData localCacheData = new LocalCacheData(key, val, timeoutTime);
cacheRepository.put(localCacheData.getKey(), localCacheData);
return true;
}
/**
* remove cache
*
* @param key
* @return
*/
public static boolean remove(String key){
if (key==null || key.trim().length()==0) {
return false;
}
cacheRepository.remove(key);
return true;
}
/**
* get cache
*
* @param key
* @return
*/
public static Object get(String key){
if (key==null || key.trim().length()==0) {
return null;
}
LocalCacheData localCacheData = cacheRepository.get(key);
if (localCacheData!=null && System.currentTimeMillis()<localCacheData.getTimeoutTime()) {
return localCacheData.getVal();
} else {
remove(key);
return null;
}
}
/**
* clean timeout cache
*
* @return
*/
public static boolean cleanTimeoutCache(){
if (!cacheRepository.keySet().isEmpty()) {
for (String key: cacheRepository.keySet()) {
LocalCacheData localCacheData = cacheRepository.get(key);
if (localCacheData!=null && System.currentTimeMillis()>=localCacheData.getTimeoutTime()) {
cacheRepository.remove(key);
}
}
}
return true;
}
}

@ -1,6 +1,6 @@
package com.xxl.job.admin.dao;
package com.xxl.job.admin.mapper;
import com.xxl.job.admin.core.model.XxlJobGroup;
import com.xxl.job.admin.model.XxlJobGroup;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
@ -10,7 +10,7 @@ import java.util.List;
* Created by xuxueli on 16/9/30.
*/
@Mapper
public interface XxlJobGroupDao {
public interface XxlJobGroupMapper {
public List<XxlJobGroup> findAll();

@ -1,6 +1,6 @@
package com.xxl.job.admin.dao;
package com.xxl.job.admin.mapper;
import com.xxl.job.admin.core.model.XxlJobInfo;
import com.xxl.job.admin.model.XxlJobInfo;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
@ -12,7 +12,7 @@ import java.util.List;
* @author xuxueli 2016-1-12 18:03:45
*/
@Mapper
public interface XxlJobInfoDao {
public interface XxlJobInfoMapper {
public List<XxlJobInfo> pageList(@Param("offset") int offset,
@Param("pagesize") int pagesize,
@ -61,5 +61,12 @@ public interface XxlJobInfoDao {
*/
public int scheduleUpdate(XxlJobInfo xxlJobInfo);
/**
* batch update job info
*
* @param jobInfoList
* @return
*/
public int scheduleBatchUpdate(@Param("list") List<XxlJobInfo> jobInfoList);
}

@ -0,0 +1,18 @@
package com.xxl.job.admin.mapper;
import org.apache.ibatis.annotations.Mapper;
/**
* job lock
*
* @author xuxueli 2016-1-12 18:03:45
*/
@Mapper
public interface XxlJobLockMapper {
/**
* get schedule lock
*/
String scheduleLock();
}

@ -1,6 +1,6 @@
package com.xxl.job.admin.dao;
package com.xxl.job.admin.mapper;
import com.xxl.job.admin.core.model.XxlJobLogGlue;
import com.xxl.job.admin.model.XxlJobLogGlue;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
@ -11,7 +11,7 @@ import java.util.List;
* @author xuxueli 2016-5-19 18:04:56
*/
@Mapper
public interface XxlJobLogGlueDao {
public interface XxlJobLogGlueMapper {
public int save(XxlJobLogGlue xxlJobLogGlue);

@ -1,6 +1,6 @@
package com.xxl.job.admin.dao;
package com.xxl.job.admin.mapper;
import com.xxl.job.admin.core.model.XxlJobLog;
import com.xxl.job.admin.model.XxlJobLog;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
@ -13,7 +13,7 @@ import java.util.Map;
* @author xuxueli 2016-1-12 18:03:06
*/
@Mapper
public interface XxlJobLogDao {
public interface XxlJobLogMapper {
// exist jobId not use jobGroup, not exist use jobGroup
public List<XxlJobLog> pageList(@Param("offset") int offset,

@ -1,6 +1,6 @@
package com.xxl.job.admin.dao;
package com.xxl.job.admin.mapper;
import com.xxl.job.admin.core.model.XxlJobLogReport;
import com.xxl.job.admin.model.XxlJobLogReport;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
@ -12,11 +12,13 @@ import java.util.List;
* @author xuxueli 2019-11-22
*/
@Mapper
public interface XxlJobLogReportDao {
public interface XxlJobLogReportMapper {
public int save(XxlJobLogReport xxlJobLogReport);
/*public int save(XxlJobLogReport xxlJobLogReport);
public int update(XxlJobLogReport xxlJobLogReport);
public int update(XxlJobLogReport xxlJobLogReport);*/
public int saveOrUpdate(XxlJobLogReport xxlJobLogReport);
public List<XxlJobLogReport> queryLogReport(@Param("triggerDayFrom") Date triggerDayFrom,
@Param("triggerDayTo") Date triggerDayTo);

@ -1,6 +1,6 @@
package com.xxl.job.admin.dao;
package com.xxl.job.admin.mapper;
import com.xxl.job.admin.core.model.XxlJobRegistry;
import com.xxl.job.admin.model.XxlJobRegistry;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
@ -11,7 +11,7 @@ import java.util.List;
* Created by xuxueli on 16/9/30.
*/
@Mapper
public interface XxlJobRegistryDao {
public interface XxlJobRegistryMapper {
public List<Integer> findDead(@Param("timeout") int timeout,
@Param("nowTime") Date nowTime);
@ -40,4 +40,7 @@ public interface XxlJobRegistryDao {
@Param("registryKey") String registryKey,
@Param("registryValue") String registryValue);
public int removeByRegistryGroupAndKey(@Param("registryGroup") String registryGroup,
@Param("registryKey") String registryKey);
}

@ -1,6 +1,7 @@
package com.xxl.job.admin.dao;
package com.xxl.job.admin.mapper;
import com.xxl.job.admin.core.model.XxlJobUser;
import com.xxl.job.admin.model.XxlJobUser;
import com.xxl.tool.response.Response;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
@ -9,7 +10,7 @@ import java.util.List;
* @author xuxueli 2019-05-04 16:44:59
*/
@Mapper
public interface XxlJobUserDao {
public interface XxlJobUserMapper {
public List<XxlJobUser> pageList(@Param("offset") int offset,
@Param("pagesize") int pagesize,
@ -22,10 +23,14 @@ public interface XxlJobUserDao {
public XxlJobUser loadByUserName(@Param("username") String username);
public XxlJobUser loadById(@Param("id") int id);
public int save(XxlJobUser xxlJobUser);
public int update(XxlJobUser xxlJobUser);
public int delete(@Param("id") int id);
public int updateToken(@Param("id") int id, @Param("token") String token);
}

@ -1,4 +1,6 @@
package com.xxl.job.admin.core.model;
package com.xxl.job.admin.model;
import com.xxl.tool.core.StringTool;
import java.util.ArrayList;
import java.util.Arrays;
@ -20,8 +22,8 @@ public class XxlJobGroup {
// registry list
private List<String> registryList; // 执行器地址列表(系统注册)
public List<String> getRegistryList() {
if (addressList!=null && addressList.trim().length()>0) {
registryList = new ArrayList<String>(Arrays.asList(addressList.split(",")));
if (StringTool.isNotBlank(addressList)) {
registryList = new ArrayList<>(Arrays.asList(addressList.split(",")));
}
return registryList;
}

@ -1,4 +1,4 @@
package com.xxl.job.admin.core.model;
package com.xxl.job.admin.model;
import java.util.Date;
@ -20,25 +20,25 @@ public class XxlJobInfo {
private String author; // 负责人
private String alarmEmail; // 报警邮件
private String scheduleType; // 调度类型
private String scheduleType; // 调度类型ScheduleTypeEnum
private String scheduleConf; // 调度配置,值含义取决于调度类型
private String misfireStrategy; // 调度过期策略
private String misfireStrategy; // 调度过期策略MisfireStrategyEnum
private String executorRouteStrategy; // 执行器路由策略
private String executorRouteStrategy; // 执行器路由策略ExecutorRouteStrategyEnum
private String executorHandler; // 执行器任务Handler名称
private String executorParam; // 执行器,任务参数
private String executorBlockStrategy; // 阻塞处理策略
private String executorBlockStrategy; // 阻塞处理策略ExecutorBlockStrategyEnum
private int executorTimeout; // 任务执行超时时间,单位秒
private int executorFailRetryCount; // 失败重试次数
private String glueType; // GLUE类型 #com.xxl.job.core.glue.GlueTypeEnum
private String glueType; // GLUE类型GlueTypeEnum
private String glueSource; // GLUE源代码
private String glueRemark; // GLUE备注
private Date glueUpdatetime; // GLUE更新时间
private String childJobId; // 子任务ID多个逗号分隔
private int triggerStatus; // 调度状态:0-停止1-运行
private int triggerStatus; // 调度状态:TriggerStatus
private long triggerLastTime; // 上次调度时间
private long triggerNextTime; // 下次调度时间

@ -1,4 +1,4 @@
package com.xxl.job.admin.core.model;
package com.xxl.job.admin.model;
import java.util.Date;

@ -1,75 +1,75 @@
package com.xxl.job.admin.core.model;
import java.util.Date;
/**
* xxl-job log for glue, used to track job code process
* @author xuxueli 2016-5-19 17:57:46
*/
public class XxlJobLogGlue {
private int id;
private int jobId; // 任务主键ID
private String glueType; // GLUE类型 #com.xxl.job.core.glue.GlueTypeEnum
private String glueSource;
private String glueRemark;
private Date addTime;
private Date updateTime;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public int getJobId() {
return jobId;
}
public void setJobId(int jobId) {
this.jobId = jobId;
}
public String getGlueType() {
return glueType;
}
public void setGlueType(String glueType) {
this.glueType = glueType;
}
public String getGlueSource() {
return glueSource;
}
public void setGlueSource(String glueSource) {
this.glueSource = glueSource;
}
public String getGlueRemark() {
return glueRemark;
}
public void setGlueRemark(String glueRemark) {
this.glueRemark = glueRemark;
}
public Date getAddTime() {
return addTime;
}
public void setAddTime(Date addTime) {
this.addTime = addTime;
}
public Date getUpdateTime() {
return updateTime;
}
public void setUpdateTime(Date updateTime) {
this.updateTime = updateTime;
}
}
package com.xxl.job.admin.model;
import java.util.Date;
/**
* xxl-job log for glue, used to track job code process
* @author xuxueli 2016-5-19 17:57:46
*/
public class XxlJobLogGlue {
private int id;
private int jobId; // 任务主键ID
private String glueType; // GLUE类型 #com.xxl.job.core.glue.GlueTypeEnum
private String glueSource;
private String glueRemark;
private Date addTime;
private Date updateTime;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public int getJobId() {
return jobId;
}
public void setJobId(int jobId) {
this.jobId = jobId;
}
public String getGlueType() {
return glueType;
}
public void setGlueType(String glueType) {
this.glueType = glueType;
}
public String getGlueSource() {
return glueSource;
}
public void setGlueSource(String glueSource) {
this.glueSource = glueSource;
}
public String getGlueRemark() {
return glueRemark;
}
public void setGlueRemark(String glueRemark) {
this.glueRemark = glueRemark;
}
public Date getAddTime() {
return addTime;
}
public void setAddTime(Date addTime) {
this.addTime = addTime;
}
public Date getUpdateTime() {
return updateTime;
}
public void setUpdateTime(Date updateTime) {
this.updateTime = updateTime;
}
}

@ -1,17 +1,18 @@
package com.xxl.job.admin.core.model;
package com.xxl.job.admin.model;
import java.util.Date;
public class XxlJobLogReport {
private int id;
private Date triggerDay;
private int runningCount;
private int sucCount;
private int failCount;
private Date updateTime;
public int getId() {
return id;
}
@ -51,4 +52,13 @@ public class XxlJobLogReport {
public void setFailCount(int failCount) {
this.failCount = failCount;
}
public Date getUpdateTime() {
return updateTime;
}
public void setUpdateTime(Date updateTime) {
this.updateTime = updateTime;
}
}

@ -1,4 +1,4 @@
package com.xxl.job.admin.core.model;
package com.xxl.job.admin.model;
import java.util.Date;
@ -7,17 +7,17 @@ import java.util.Date;
*/
public class XxlJobRegistry {
private int id;
private long id;
private String registryGroup;
private String registryKey;
private String registryValue;
private Date updateTime;
public int getId() {
public long getId() {
return id;
}
public void setId(int id) {
public void setId(long id) {
this.id = id;
}

@ -1,6 +1,4 @@
package com.xxl.job.admin.core.model;
import org.springframework.util.StringUtils;
package com.xxl.job.admin.model;
/**
* @author xuxueli 2019-05-04 16:43:12
@ -10,8 +8,9 @@ public class XxlJobUser {
private int id;
private String username; // 账号
private String password; // 密码
private String token; // 登录token
private int role; // 角色0-普通用户、1-管理员
private String permission; // 权限执行器ID列表多个逗号分割
private String permission; // 权限执行器ID列表多个逗号分割
public int getId() {
return id;
@ -37,6 +36,14 @@ public class XxlJobUser {
this.password = password;
}
public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
public int getRole() {
return role;
}
@ -53,21 +60,4 @@ public class XxlJobUser {
this.permission = permission;
}
// plugin
public boolean validPermission(int jobGroup){
if (this.role == 1) {
return true;
} else {
if (StringUtils.hasText(this.permission)) {
for (String permissionItem : this.permission.split(",")) {
if (String.valueOf(jobGroup).equals(permissionItem)) {
return true;
}
}
}
return false;
}
}
}

@ -0,0 +1,185 @@
package com.xxl.job.admin.model.dto;
import java.io.Serializable;
import java.util.Date;
import java.util.List;
/**
* XxlBootResource DTO
*
* Created by xuxueli on 2024-08-04
*/
public class XxlBootResourceDTO implements Serializable {
private static final long serialVersionUID = 42L;
/**
* ID
*/
private int id;
/**
* ID
*/
private int parentId;
/**
*
*/
private String name;
/**
* 0-, 1-, 2-
*/
private int type;
/**
*
*/
private String permission;
/**
*
*/
private String url;
/**
* ICON
*/
private String icon;
/**
*
*/
private int order;
/**
* 0-1-
*/
private int status;
/**
*
*/
private Date addTime;
/**
*
*/
private Date updateTime;
/**
* child data
*/
private List<XxlBootResourceDTO> children;
public XxlBootResourceDTO() {
}
public XxlBootResourceDTO(int id, int parentId, String name, int type, String permission, String url, String icon, int order, int status, List<XxlBootResourceDTO> children) {
this.id = id;
this.parentId = parentId;
this.name = name;
this.type = type;
this.permission = permission;
this.url = url;
this.icon = icon;
this.order = order;
this.status = status;
this.children = children;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public int getParentId() {
return parentId;
}
public void setParentId(int parentId) {
this.parentId = parentId;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getType() {
return type;
}
public void setType(int type) {
this.type = type;
}
public String getPermission() {
return permission;
}
public void setPermission(String permission) {
this.permission = permission;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getIcon() {
return icon;
}
public void setIcon(String icon) {
this.icon = icon;
}
public int getOrder() {
return order;
}
public void setOrder(int order) {
this.order = order;
}
public int getStatus() {
return status;
}
public void setStatus(int status) {
this.status = status;
}
public Date getAddTime() {
return addTime;
}
public void setAddTime(Date addTime) {
this.addTime = addTime;
}
public Date getUpdateTime() {
return updateTime;
}
public void setUpdateTime(Date updateTime) {
this.updateTime = updateTime;
}
public List<XxlBootResourceDTO> getChildren() {
return children;
}
public void setChildren(List<XxlBootResourceDTO> children) {
this.children = children;
}
}

@ -1,7 +1,7 @@
package com.xxl.job.admin.core.alarm;
package com.xxl.job.admin.scheduler.alarm;
import com.xxl.job.admin.core.model.XxlJobInfo;
import com.xxl.job.admin.core.model.XxlJobLog;
import com.xxl.job.admin.model.XxlJobInfo;
import com.xxl.job.admin.model.XxlJobLog;
/**
* @author xuxueli 2020-01-19

@ -1,50 +1,34 @@
package com.xxl.job.admin.core.alarm;
package com.xxl.job.admin.scheduler.alarm;
import com.xxl.job.admin.core.model.XxlJobInfo;
import com.xxl.job.admin.core.model.XxlJobLog;
import com.xxl.job.admin.model.XxlJobInfo;
import com.xxl.job.admin.model.XxlJobLog;
import com.xxl.tool.core.CollectionTool;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* xxl-job alarmer
*
* @author xuxueli 17/7/13.
*/
@Component
public class JobAlarmer implements ApplicationContextAware, InitializingBean {
private static Logger logger = LoggerFactory.getLogger(JobAlarmer.class);
public class JobAlarmer {
private static final Logger logger = LoggerFactory.getLogger(JobAlarmer.class);
private ApplicationContext applicationContext;
@Autowired
private List<JobAlarm> jobAlarmList;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
@Override
public void afterPropertiesSet() throws Exception {
Map<String, JobAlarm> serviceBeanMap = applicationContext.getBeansOfType(JobAlarm.class);
if (serviceBeanMap != null && serviceBeanMap.size() > 0) {
jobAlarmList = new ArrayList<JobAlarm>(serviceBeanMap.values());
}
}
/**
* job alarm
*
* @param info
* @param jobLog
* @return
*/
public boolean alarm(XxlJobInfo info, XxlJobLog jobLog) {
boolean result = false;
if (jobAlarmList!=null && jobAlarmList.size()>0) {
if (CollectionTool.isNotEmpty(jobAlarmList)) {
result = true; // success means all-success
for (JobAlarm alarm: jobAlarmList) {
boolean resultItem = false;
@ -62,4 +46,22 @@ public class JobAlarmer implements ApplicationContextAware, InitializingBean {
return result;
}
/*
// implements SmartInitializingSingleton
// implements ApplicationContextAware, InitializingBean
private ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
@Override
public void afterPropertiesSet() throws Exception {
Map<String, JobAlarm> serviceBeanMap = applicationContext.getBeansOfType(JobAlarm.class);
if (MapTool.isNotEmpty(serviceBeanMap)) {
jobAlarmList = new ArrayList<>(serviceBeanMap.values());
}
}*/
}

@ -1,18 +1,18 @@
package com.xxl.job.admin.core.alarm.impl;
import com.xxl.job.admin.core.alarm.JobAlarm;
import com.xxl.job.admin.core.conf.XxlJobAdminConfig;
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.util.I18nUtil;
import com.xxl.job.core.biz.model.ReturnT;
package com.xxl.job.admin.scheduler.alarm.impl;
import com.xxl.job.admin.scheduler.alarm.JobAlarm;
import com.xxl.job.admin.scheduler.config.XxlJobAdminBootstrap;
import com.xxl.job.admin.model.XxlJobGroup;
import com.xxl.job.admin.model.XxlJobInfo;
import com.xxl.job.admin.model.XxlJobLog;
import com.xxl.job.admin.util.I18nUtil;
import com.xxl.job.core.context.XxlJobContext;
import jakarta.mail.internet.MimeMessage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.stereotype.Component;
import javax.mail.internet.MimeMessage;
import java.text.MessageFormat;
import java.util.Arrays;
import java.util.HashSet;
@ -37,19 +37,19 @@ public class EmailJobAlarm implements JobAlarm {
boolean alarmResult = true;
// send monitor email
if (info!=null && info.getAlarmEmail()!=null && info.getAlarmEmail().trim().length()>0) {
if (info!=null && info.getAlarmEmail()!=null && !info.getAlarmEmail().trim().isEmpty()) {
// alarmContent
String alarmContent = "Alarm Job LogId=" + jobLog.getId();
if (jobLog.getTriggerCode() != ReturnT.SUCCESS_CODE) {
if (jobLog.getTriggerCode() != XxlJobContext.HANDLE_CODE_SUCCESS) {
alarmContent += "<br>TriggerMsg=<br>" + jobLog.getTriggerMsg();
}
if (jobLog.getHandleCode()>0 && jobLog.getHandleCode() != ReturnT.SUCCESS_CODE) {
if (jobLog.getHandleCode()>0 && jobLog.getHandleCode() != XxlJobContext.HANDLE_CODE_SUCCESS) {
alarmContent += "<br>HandleCode=" + jobLog.getHandleMsg();
}
// email info
XxlJobGroup group = XxlJobAdminConfig.getAdminConfig().getXxlJobGroupDao().load(Integer.valueOf(info.getJobGroup()));
XxlJobGroup group = XxlJobAdminBootstrap.getInstance().getXxlJobGroupMapper().load(Integer.valueOf(info.getJobGroup()));
String personal = I18nUtil.getString("admin_name_full");
String title = I18nUtil.getString("jobconf_monitor");
String content = MessageFormat.format(loadEmailJobAlarmTemplate(),
@ -63,15 +63,15 @@ public class EmailJobAlarm implements JobAlarm {
// make mail
try {
MimeMessage mimeMessage = XxlJobAdminConfig.getAdminConfig().getMailSender().createMimeMessage();
MimeMessage mimeMessage = XxlJobAdminBootstrap.getInstance().getMailSender().createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true);
helper.setFrom(XxlJobAdminConfig.getAdminConfig().getEmailFrom(), personal);
helper.setFrom(XxlJobAdminBootstrap.getInstance().getEmailFrom(), personal);
helper.setTo(email);
helper.setSubject(title);
helper.setText(content, true);
XxlJobAdminConfig.getAdminConfig().getMailSender().send(mimeMessage);
XxlJobAdminBootstrap.getInstance().getMailSender().send(mimeMessage);
} catch (Exception e) {
logger.error(">>>>>>>>>>> xxl-job, job fail alarm email send error, JobLogId:{}", jobLog.getId(), e);

@ -1,60 +1,79 @@
package com.xxl.job.admin.core.complete;
import com.xxl.job.admin.core.conf.XxlJobAdminConfig;
import com.xxl.job.admin.core.model.XxlJobInfo;
import com.xxl.job.admin.core.model.XxlJobLog;
import com.xxl.job.admin.core.thread.JobTriggerPoolHelper;
import com.xxl.job.admin.core.trigger.TriggerTypeEnum;
import com.xxl.job.admin.core.util.I18nUtil;
import com.xxl.job.core.biz.model.ReturnT;
package com.xxl.job.admin.scheduler.complete;
import com.xxl.job.admin.mapper.XxlJobInfoMapper;
import com.xxl.job.admin.mapper.XxlJobLogMapper;
import com.xxl.job.admin.model.XxlJobInfo;
import com.xxl.job.admin.model.XxlJobLog;
import com.xxl.job.admin.scheduler.config.XxlJobAdminBootstrap;
import com.xxl.job.admin.scheduler.trigger.TriggerTypeEnum;
import com.xxl.job.admin.util.I18nUtil;
import com.xxl.job.core.context.XxlJobContext;
import com.xxl.tool.core.StringTool;
import com.xxl.tool.response.Response;
import jakarta.annotation.Resource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import java.text.MessageFormat;
/**
* xxl-job job log complete
*
* @author xuxueli 2020-10-30 20:43:10
*/
public class XxlJobCompleter {
private static Logger logger = LoggerFactory.getLogger(XxlJobCompleter.class);
@Component
public class JobCompleter {
private static final Logger logger = LoggerFactory.getLogger(JobCompleter.class);
@Resource
private XxlJobInfoMapper xxlJobInfoMapper;
@Resource
private XxlJobLogMapper xxlJobLogMapper;
/**
* common fresh handle entrance (limit only once)
*
* @param xxlJobLog
* @return
* complate job (limit only once)
*/
public static int updateHandleInfoAndFinish(XxlJobLog xxlJobLog) {
public int complete(XxlJobLog xxlJobLog) {
// finish
finishJob(xxlJobLog);
// 1、process child-job
processChildJob(xxlJobLog);
// text最大64kb 避免长度过长
if (xxlJobLog.getHandleMsg().length() > 15000) {
xxlJobLog.setHandleMsg( xxlJobLog.getHandleMsg().substring(0, 15000) );
}
// fresh handle
return XxlJobAdminConfig.getAdminConfig().getXxlJobLogDao().updateHandleInfo(xxlJobLog);
// 2、fix_delay trigger next
// on the way
// 3、update job handle-info
return xxlJobLogMapper.updateHandleInfo(xxlJobLog);
}
/**
* do somethind to finish job
*/
private static void finishJob(XxlJobLog xxlJobLog){
private void processChildJob(XxlJobLog xxlJobLog){
// 1、handle success, to trigger child job
String triggerChildMsg = null;
if (XxlJobContext.HANDLE_CODE_SUCCESS == xxlJobLog.getHandleCode()) {
XxlJobInfo xxlJobInfo = XxlJobAdminConfig.getAdminConfig().getXxlJobInfoDao().loadById(xxlJobLog.getJobId());
if (xxlJobInfo!=null && xxlJobInfo.getChildJobId()!=null && xxlJobInfo.getChildJobId().trim().length()>0) {
triggerChildMsg = "<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>"+ I18nUtil.getString("jobconf_trigger_child_run") +"<<<<<<<<<<< </span><br>";
XxlJobInfo xxlJobInfo = xxlJobInfoMapper.loadById(xxlJobLog.getJobId());
// process child job
if (xxlJobInfo!=null && StringTool.isNotBlank(xxlJobInfo.getChildJobId())) {
triggerChildMsg = "<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>"+ I18nUtil.getString("jobconf_trigger_child_run") +"<<<<<<<<<<< </span><br>";
String[] childJobIds = xxlJobInfo.getChildJobId().split(",");
for (int i = 0; i < childJobIds.length; i++) {
int childJobId = (childJobIds[i]!=null && childJobIds[i].trim().length()>0 && isNumeric(childJobIds[i]))?Integer.valueOf(childJobIds[i]):-1;
// process eath child
int childJobId = (StringTool.isNotBlank(childJobIds[i]) && StringTool.isNumeric(childJobIds[i]))
?Integer.parseInt(childJobIds[i])
:-1;
if (childJobId > 0) {
// valid
if (childJobId == xxlJobLog.getJobId()) {
@ -63,15 +82,15 @@ public class XxlJobCompleter {
}
// trigger child job
JobTriggerPoolHelper.trigger(childJobId, TriggerTypeEnum.PARENT, -1, null, null, null);
ReturnT<String> triggerChildResult = ReturnT.SUCCESS;
XxlJobAdminBootstrap.getInstance().getJobTriggerPoolHelper().trigger(childJobId, TriggerTypeEnum.PARENT, -1, null, null, null);
Response<String> triggerChildResult = Response.ofSuccess();
// add msg
triggerChildMsg += MessageFormat.format(I18nUtil.getString("jobconf_callback_child_msg1"),
(i+1),
childJobIds.length,
childJobIds[i],
(triggerChildResult.getCode()==ReturnT.SUCCESS_CODE?I18nUtil.getString("system_success"):I18nUtil.getString("system_fail")),
(triggerChildResult.isSuccess()?I18nUtil.getString("system_success"):I18nUtil.getString("system_fail")),
triggerChildResult.getMsg());
} else {
triggerChildMsg += MessageFormat.format(I18nUtil.getString("jobconf_callback_child_msg2"),
@ -84,22 +103,20 @@ public class XxlJobCompleter {
}
}
if (triggerChildMsg != null) {
// 2、append trigger-child message
if (StringTool.isNotBlank(triggerChildMsg)) {
xxlJobLog.setHandleMsg( xxlJobLog.getHandleMsg() + triggerChildMsg );
}
// 2、fix_delay trigger next
// on the way
}
private static boolean isNumeric(String str){
/*private static boolean isNumeric(String str){
try {
int result = Integer.valueOf(str);
return true;
} catch (NumberFormatException e) {
return false;
}
}
}*/
}

@ -0,0 +1,312 @@
package com.xxl.job.admin.scheduler.config;
import com.xxl.job.admin.mapper.*;
import com.xxl.job.admin.scheduler.alarm.JobAlarmer;
import com.xxl.job.admin.scheduler.complete.JobCompleter;
import com.xxl.job.admin.scheduler.thread.*;
import com.xxl.job.admin.scheduler.trigger.JobTrigger;
import com.xxl.job.core.constant.Const;
import com.xxl.job.core.openapi.ExecutorBiz;
import com.xxl.tool.core.StringTool;
import com.xxl.tool.http.HttpTool;
import jakarta.annotation.Resource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.stereotype.Component;
import org.springframework.transaction.PlatformTransactionManager;
import java.util.Arrays;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/**
* xxl-job config
*
* @author xuxueli 2017-04-28
*/
@Component
public class XxlJobAdminBootstrap implements InitializingBean, DisposableBean {
private static final Logger logger = LoggerFactory.getLogger(XxlJobAdminBootstrap.class);
// ---------------------- instance ----------------------
private static XxlJobAdminBootstrap adminConfig = null;
public static XxlJobAdminBootstrap getInstance() {
return adminConfig;
}
// ---------------------- start / stop ----------------------
@Override
public void afterPropertiesSet() throws Exception {
// init instance
adminConfig = this;
// start
doStart();
}
@Override
public void destroy() throws Exception {
// stop
doStop();
}
// job module
private JobTriggerPoolHelper jobTriggerPoolHelper;
private JobRegistryHelper jobRegistryHelper;
private JobFailAlarmMonitorHelper jobFailAlarmMonitorHelper;
private JobCompleteHelper jobCompleteHelper;
private JobLogReportHelper jobLogReportHelper;
private JobScheduleHelper jobScheduleHelper;
public JobTriggerPoolHelper getJobTriggerPoolHelper() {
return jobTriggerPoolHelper;
}
public JobRegistryHelper getJobRegistryHelper() {
return jobRegistryHelper;
}
public JobCompleteHelper getJobCompleteHelper() {
return jobCompleteHelper;
}
/**
* do start
*/
private void doStart() throws Exception {
// trigger-pool start
jobTriggerPoolHelper = new JobTriggerPoolHelper();
jobTriggerPoolHelper.start();
// registry monitor start
jobRegistryHelper = new JobRegistryHelper();
jobRegistryHelper.start();
// fail-alarm monitor start
jobFailAlarmMonitorHelper = new JobFailAlarmMonitorHelper();
jobFailAlarmMonitorHelper.start();
// job complate start ( depend on JobTriggerPoolHelper ) for callback and result-lost
jobCompleteHelper = new JobCompleteHelper();
jobCompleteHelper.start();
// log-report start
jobLogReportHelper = new JobLogReportHelper();
jobLogReportHelper.start();
// job-schedule start ( depend on JobTriggerPoolHelper )
jobScheduleHelper = new JobScheduleHelper();
jobScheduleHelper.start();
logger.info(">>>>>>>>> xxl-job admin start success.");
}
/**
* do stop
*/
private void doStop(){
// job-schedule stop
jobScheduleHelper.stop();
// log-report stop
jobLogReportHelper.stop();
// job complate stop
jobCompleteHelper.stop();
// fail-alarm monitor stop
jobFailAlarmMonitorHelper.stop();
// registry monitor stop
jobRegistryHelper.stop();
// trigger-pool stop
jobTriggerPoolHelper.stop();
logger.info(">>>>>>>>> xxl-job admin stopped.");
}
// ---------------------- executor-client ----------------------
private static ConcurrentMap<String, ExecutorBiz> executorBizRepository = new ConcurrentHashMap<String, ExecutorBiz>();
public static ExecutorBiz getExecutorBiz(String address) throws Exception {
// valid
if (StringTool.isBlank(address)) {
return null;
}
// load-cache
address = address.trim();
ExecutorBiz executorBiz = executorBizRepository.get(address);
if (executorBiz != null) {
return executorBiz;
}
// set-cache
executorBiz = HttpTool.createClient()
.url(address)
.timeout(XxlJobAdminBootstrap.getInstance().getTimeout() * 1000)
.header(Const.XXL_JOB_ACCESS_TOKEN, XxlJobAdminBootstrap.getInstance().getAccessToken())
.proxy(ExecutorBiz.class);
executorBizRepository.put(address, executorBiz);
return executorBiz;
}
// ---------------------- field ----------------------
// conf
@Value("${xxl.job.i18n}")
private String i18n;
@Value("${xxl.job.accessToken}")
private String accessToken;
@Value("${xxl.job.timeout}")
private int timeout;
@Value("${spring.mail.from}")
private String emailFrom;
@Value("${xxl.job.triggerpool.fast.max}")
private int triggerPoolFastMax;
@Value("${xxl.job.triggerpool.slow.max}")
private int triggerPoolSlowMax;
@Value("${xxl.job.schedule.batchsize}")
private int scheduleBatchSize;
@Value("${xxl.job.logretentiondays}")
private int logretentiondays;
// service, mapper
@Resource
private XxlJobLogMapper xxlJobLogMapper;
@Resource
private XxlJobInfoMapper xxlJobInfoMapper;
@Resource
private XxlJobRegistryMapper xxlJobRegistryMapper;
@Resource
private XxlJobGroupMapper xxlJobGroupMapper;
@Resource
private XxlJobLogReportMapper xxlJobLogReportMapper;
@Resource
private XxlJobLockMapper xxlJobLockMapper;
@Resource
private JavaMailSender mailSender;
/*@Resource
private DataSource dataSource;*/
@Resource
private PlatformTransactionManager transactionManager;
@Resource
private JobAlarmer jobAlarmer;
@Resource
private JobTrigger jobTrigger;
@Resource
private JobCompleter jobCompleter;
public String getI18n() {
if (!Arrays.asList("zh_CN", "zh_TC", "en").contains(i18n)) {
return "zh_CN";
}
return i18n;
}
public String getAccessToken() {
return accessToken;
}
public int getTimeout() {
return timeout;
}
public String getEmailFrom() {
return emailFrom;
}
public int getTriggerPoolFastMax() {
if (triggerPoolFastMax < 200) {
return 200;
}
return triggerPoolFastMax;
}
public int getTriggerPoolSlowMax() {
if (triggerPoolSlowMax < 100) {
return 100;
}
return triggerPoolSlowMax;
}
public int getScheduleBatchSize() {
if (!(scheduleBatchSize >=50 && scheduleBatchSize <= 500)) {
return 100;
}
return scheduleBatchSize;
}
public int getLogretentiondays() {
if (logretentiondays < 3) {
return -1; // Limit greater than or equal to 3, otherwise close
}
return logretentiondays;
}
public XxlJobLogMapper getXxlJobLogMapper() {
return xxlJobLogMapper;
}
public XxlJobInfoMapper getXxlJobInfoMapper() {
return xxlJobInfoMapper;
}
public XxlJobRegistryMapper getXxlJobRegistryMapper() {
return xxlJobRegistryMapper;
}
public XxlJobGroupMapper getXxlJobGroupMapper() {
return xxlJobGroupMapper;
}
public XxlJobLogReportMapper getXxlJobLogReportMapper() {
return xxlJobLogReportMapper;
}
public XxlJobLockMapper getXxlJobLockMapper() {
return xxlJobLockMapper;
}
public JavaMailSender getMailSender() {
return mailSender;
}
/*public DataSource getDataSource() {
return dataSource;
}*/
public PlatformTransactionManager getTransactionManager() {
return transactionManager;
}
public JobAlarmer getJobAlarmer() {
return jobAlarmer;
}
public JobTrigger getJobTrigger() {
return jobTrigger;
}
public JobCompleter getJobCompleter() {
return jobCompleter;
}
}

@ -1,4 +1,4 @@
package com.xxl.job.admin.core.exception;
package com.xxl.job.admin.scheduler.exception;
/**
* @author xuxueli 2019-05-04 23:19:29

@ -0,0 +1,17 @@
package com.xxl.job.admin.scheduler.misfire;
/**
* Misfire Handler
*
* @author xuxueli 2020-10-29
*/
public abstract class MisfireHandler {
/**
* misfire handle
*
* @param jobId jobId
*/
public abstract void handle(final int jobId);
}

@ -0,0 +1,54 @@
package com.xxl.job.admin.scheduler.misfire;
import com.xxl.job.admin.scheduler.misfire.strategy.MisfireDoNothing;
import com.xxl.job.admin.scheduler.misfire.strategy.MisfireFireOnceNow;
import com.xxl.job.admin.util.I18nUtil;
/**
* @author xuxueli 2020-10-29 21:11:23
*/
public enum MisfireStrategyEnum {
/**
* do nothing
*/
DO_NOTHING(I18nUtil.getString("misfire_strategy_do_nothing"), new MisfireDoNothing()),
/**
* fire once now
*/
FIRE_ONCE_NOW(I18nUtil.getString("misfire_strategy_fire_once_now"), new MisfireFireOnceNow());
private final String title;
private final MisfireHandler misfireHandler;
MisfireStrategyEnum(String title, MisfireHandler misfireHandler) {
this.title = title;
this.misfireHandler = misfireHandler;
}
public String getTitle() {
return title;
}
public MisfireHandler getMisfireHandler() {
return misfireHandler;
}
/**
* match misfire strategy
*
* @param name name of misfire strategy
* @param defaultItem default misfire strategy
* @return misfire strategy
*/
public static MisfireStrategyEnum match(String name, MisfireStrategyEnum defaultItem){
for (MisfireStrategyEnum item: MisfireStrategyEnum.values()) {
if (item.name().equals(name)) {
return item;
}
}
return defaultItem;
}
}

@ -0,0 +1,15 @@
package com.xxl.job.admin.scheduler.misfire.strategy;
import com.xxl.job.admin.scheduler.misfire.MisfireHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class MisfireDoNothing extends MisfireHandler {
private static final Logger logger = LoggerFactory.getLogger(MisfireDoNothing.class);
@Override
public void handle(int jobId) {
logger.warn(">>>>>>>>>>> xxl-job, schedule MisfireDoNothing: jobId = " + jobId );
}
}

@ -0,0 +1,19 @@
package com.xxl.job.admin.scheduler.misfire.strategy;
import com.xxl.job.admin.scheduler.config.XxlJobAdminBootstrap;
import com.xxl.job.admin.scheduler.misfire.MisfireHandler;
import com.xxl.job.admin.scheduler.trigger.TriggerTypeEnum;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class MisfireFireOnceNow extends MisfireHandler {
protected static Logger logger = LoggerFactory.getLogger(MisfireFireOnceNow.class);
@Override
public void handle(int jobId) {
// FIRE_ONCE_NOW 》 trigger
XxlJobAdminBootstrap.getInstance().getJobTriggerPoolHelper().trigger(jobId, TriggerTypeEnum.MISFIRE, -1, null, null, null);
logger.warn(">>>>>>>>>>> xxl-job, schedule MisfireFireOnceNow: jobId = " + jobId );
}
}

@ -0,0 +1,80 @@
package com.xxl.job.admin.scheduler.openapi;
import com.xxl.job.admin.scheduler.config.XxlJobAdminBootstrap;
import com.xxl.job.core.constant.Const;
import com.xxl.job.core.openapi.AdminBiz;
import com.xxl.job.core.openapi.model.CallbackRequest;
import com.xxl.job.core.openapi.model.RegistryRequest;
import com.xxl.sso.core.annotation.XxlSso;
import com.xxl.tool.core.StringTool;
import com.xxl.tool.json.GsonTool;
import com.xxl.tool.response.Response;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* Created by xuxueli on 17/5/10.
*/
@Controller
public class OpenApiController {
@Resource
private AdminBiz adminBiz;
/**
* api
*/
@RequestMapping("/api/{uri}")
@ResponseBody
@XxlSso(login = false)
public Object api(HttpServletRequest request,
@PathVariable("uri") String uri,
@RequestHeader(value = Const.XXL_JOB_ACCESS_TOKEN, required = false) String accesstoken,
@RequestBody(required = false) String requestBody) {
// valid
if (!"POST".equalsIgnoreCase(request.getMethod())) {
return Response.ofFail("invalid request, HttpMethod not support.");
}
if (StringTool.isBlank(uri)) {
return Response.ofFail("invalid request, uri-mapping empty.");
}
if (StringTool.isBlank(requestBody)) {
return Response.ofFail("invalid request, requestBody empty.");
}
// valid token
if (StringTool.isNotBlank(XxlJobAdminBootstrap.getInstance().getAccessToken())
&& !XxlJobAdminBootstrap.getInstance().getAccessToken().equals(accesstoken)) {
return Response.ofFail("The access token is wrong.");
}
// dispatch request
try {
switch (uri) {
case "callback": {
List<CallbackRequest> callbackParamList = GsonTool.fromJson(requestBody, List.class, CallbackRequest.class);
return adminBiz.callback(callbackParamList);
}
case "registry": {
RegistryRequest registryParam = GsonTool.fromJson(requestBody, RegistryRequest.class);
return adminBiz.registry(registryParam);
}
case "registryRemove": {
RegistryRequest registryParam = GsonTool.fromJson(requestBody, RegistryRequest.class);
return adminBiz.registryRemove(registryParam);
}
default:
return Response.ofFail("invalid request, uri-mapping("+ uri +") not found.");
}
} catch (Exception e) {
return Response.ofFail("openapi invoke error: " + e.getMessage());
}
}
}

@ -1,7 +1,7 @@
package com.xxl.job.admin.core.route;
package com.xxl.job.admin.scheduler.route;
import com.xxl.job.admin.core.route.strategy.*;
import com.xxl.job.admin.core.util.I18nUtil;
import com.xxl.job.admin.scheduler.route.strategy.*;
import com.xxl.job.admin.util.I18nUtil;
/**
* Created by xuxueli on 17/3/10.
@ -34,6 +34,9 @@ public enum ExecutorRouteStrategyEnum {
return router;
}
/**
* match router
*/
public static ExecutorRouteStrategyEnum match(String name, ExecutorRouteStrategyEnum defaultItem){
if (name != null) {
for (ExecutorRouteStrategyEnum item: ExecutorRouteStrategyEnum.values()) {

@ -1,7 +1,7 @@
package com.xxl.job.admin.core.route;
package com.xxl.job.admin.scheduler.route;
import com.xxl.job.core.biz.model.ReturnT;
import com.xxl.job.core.biz.model.TriggerParam;
import com.xxl.job.core.openapi.model.TriggerRequest;
import com.xxl.tool.response.Response;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -16,9 +16,9 @@ public abstract class ExecutorRouter {
/**
* route address
*
* @param addressList
* @param addressList executor address list
* @return ReturnT.content=address
*/
public abstract ReturnT<String> route(TriggerParam triggerParam, List<String> addressList);
public abstract Response<String> route(TriggerRequest triggerParam, List<String> addressList);
}

@ -1,12 +1,12 @@
package com.xxl.job.admin.core.route.strategy;
package com.xxl.job.admin.scheduler.route.strategy;
import com.xxl.job.admin.core.scheduler.XxlJobScheduler;
import com.xxl.job.admin.core.route.ExecutorRouter;
import com.xxl.job.admin.core.util.I18nUtil;
import com.xxl.job.core.biz.ExecutorBiz;
import com.xxl.job.core.biz.model.IdleBeatParam;
import com.xxl.job.core.biz.model.ReturnT;
import com.xxl.job.core.biz.model.TriggerParam;
import com.xxl.job.admin.scheduler.config.XxlJobAdminBootstrap;
import com.xxl.job.admin.scheduler.route.ExecutorRouter;
import com.xxl.job.admin.util.I18nUtil;
import com.xxl.job.core.openapi.ExecutorBiz;
import com.xxl.job.core.openapi.model.IdleBeatRequest;
import com.xxl.job.core.openapi.model.TriggerRequest;
import com.xxl.tool.response.Response;
import java.util.List;
@ -16,17 +16,17 @@ import java.util.List;
public class ExecutorRouteBusyover extends ExecutorRouter {
@Override
public ReturnT<String> route(TriggerParam triggerParam, List<String> addressList) {
public Response<String> route(TriggerRequest triggerParam, List<String> addressList) {
StringBuffer idleBeatResultSB = new StringBuffer();
for (String address : addressList) {
// beat
ReturnT<String> idleBeatResult = null;
Response<String> idleBeatResult = null;
try {
ExecutorBiz executorBiz = XxlJobScheduler.getExecutorBiz(address);
idleBeatResult = executorBiz.idleBeat(new IdleBeatParam(triggerParam.getJobId()));
ExecutorBiz executorBiz = XxlJobAdminBootstrap.getExecutorBiz(address);
idleBeatResult = executorBiz.idleBeat(new IdleBeatRequest(triggerParam.getJobId()));
} catch (Exception e) {
logger.error(e.getMessage(), e);
idleBeatResult = new ReturnT<String>(ReturnT.FAIL_CODE, ""+e );
idleBeatResult = Response.ofFail( ""+e );
}
idleBeatResultSB.append( (idleBeatResultSB.length()>0)?"<br><br>":"")
.append(I18nUtil.getString("jobconf_idleBeat") + "")
@ -35,14 +35,14 @@ public class ExecutorRouteBusyover extends ExecutorRouter {
.append("<br>msg").append(idleBeatResult.getMsg());
// beat success
if (idleBeatResult.getCode() == ReturnT.SUCCESS_CODE) {
if (idleBeatResult.isSuccess()) {
idleBeatResult.setMsg(idleBeatResultSB.toString());
idleBeatResult.setContent(address);
idleBeatResult.setData(address);
return idleBeatResult;
}
}
return new ReturnT<String>(ReturnT.FAIL_CODE, idleBeatResultSB.toString());
return Response.ofFail( idleBeatResultSB.toString());
}
}

@ -1,30 +1,32 @@
package com.xxl.job.admin.core.route.strategy;
package com.xxl.job.admin.scheduler.route.strategy;
import com.xxl.job.admin.core.route.ExecutorRouter;
import com.xxl.job.core.biz.model.ReturnT;
import com.xxl.job.core.biz.model.TriggerParam;
import com.xxl.job.admin.scheduler.route.ExecutorRouter;
import com.xxl.job.core.openapi.model.TriggerRequest;
import com.xxl.tool.response.Response;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.List;
import java.util.SortedMap;
import java.util.Map;
import java.util.TreeMap;
/**
* JOBJOBJOB
* avirtual node
* bhash method replace hashCodeStringhashCodehashCode
*
* Created by xuxueli on 17/3/10.
*/
public class ExecutorRouteConsistentHash extends ExecutorRouter {
private static int VIRTUAL_NODE_NUM = 100;
private static final int VIRTUAL_NODE_NUM = 100;
/**
* get hash code on 2^32 ring (md5hash)
* @param key
* @return
*
* @param key key
* @return hash code
*/
private static long hash(String key) {
@ -37,11 +39,7 @@ public class ExecutorRouteConsistentHash extends ExecutorRouter {
}
md5.reset();
byte[] keyBytes = null;
try {
keyBytes = key.getBytes("UTF-8");
} catch (UnsupportedEncodingException e) {
throw new RuntimeException("Unknown string :" + key, e);
}
keyBytes = key.getBytes(StandardCharsets.UTF_8);
md5.update(keyBytes);
byte[] digest = md5.digest();
@ -52,15 +50,22 @@ public class ExecutorRouteConsistentHash extends ExecutorRouter {
| ((long) (digest[1] & 0xFF) << 8)
| (digest[0] & 0xFF);
long truncateHashCode = hashCode & 0xffffffffL;
return truncateHashCode;
return hashCode & 0xffffffffL;
}
/**
* get address by jobId
*
* @param jobId job id
* @param addressList address list
* @return address
*/
public String hashJob(int jobId, List<String> addressList) {
// 1、hash ring
// ------A1------A2-------A3------
// -----------J1------------------
TreeMap<Long, String> addressRing = new TreeMap<Long, String>();
TreeMap<Long, String> addressRing = new TreeMap<>();
for (String address: addressList) {
for (int i = 0; i < VIRTUAL_NODE_NUM; i++) {
long addressHash = hash("SHARD-" + address + "-NODE-" + i);
@ -68,18 +73,27 @@ public class ExecutorRouteConsistentHash extends ExecutorRouter {
}
}
// 2、generate job-hash
long jobHash = hash(String.valueOf(jobId));
SortedMap<Long, String> lastRing = addressRing.tailMap(jobHash);
// 3、route job-node
Map.Entry<Long, String> ceilingEntry = addressRing.ceilingEntry(jobHash);
if (ceilingEntry != null) {
return ceilingEntry.getValue();
}
/*SortedMap<Long, String> lastRing = addressRing.tailMap(jobHash);
if (!lastRing.isEmpty()) {
return lastRing.get(lastRing.firstKey());
}
}*/
// 4、default first node
return addressRing.firstEntry().getValue();
}
@Override
public ReturnT<String> route(TriggerParam triggerParam, List<String> addressList) {
public Response<String> route(TriggerRequest triggerParam, List<String> addressList) {
String address = hashJob(triggerParam.getJobId(), addressList);
return new ReturnT<String>(address);
return Response.ofSuccess(address);
}
}

@ -1,11 +1,11 @@
package com.xxl.job.admin.core.route.strategy;
package com.xxl.job.admin.scheduler.route.strategy;
import com.xxl.job.admin.core.scheduler.XxlJobScheduler;
import com.xxl.job.admin.core.route.ExecutorRouter;
import com.xxl.job.admin.core.util.I18nUtil;
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.admin.scheduler.config.XxlJobAdminBootstrap;
import com.xxl.job.admin.scheduler.route.ExecutorRouter;
import com.xxl.job.admin.util.I18nUtil;
import com.xxl.job.core.openapi.ExecutorBiz;
import com.xxl.job.core.openapi.model.TriggerRequest;
import com.xxl.tool.response.Response;
import java.util.List;
@ -15,18 +15,18 @@ import java.util.List;
public class ExecutorRouteFailover extends ExecutorRouter {
@Override
public ReturnT<String> route(TriggerParam triggerParam, List<String> addressList) {
public Response<String> route(TriggerRequest triggerParam, List<String> addressList) {
StringBuffer beatResultSB = new StringBuffer();
for (String address : addressList) {
// beat
ReturnT<String> beatResult = null;
Response<String> beatResult = null;
try {
ExecutorBiz executorBiz = XxlJobScheduler.getExecutorBiz(address);
ExecutorBiz executorBiz = XxlJobAdminBootstrap.getExecutorBiz(address);
beatResult = executorBiz.beat();
} catch (Exception e) {
logger.error(e.getMessage(), e);
beatResult = new ReturnT<String>(ReturnT.FAIL_CODE, ""+e );
beatResult = Response.ofFail(e.getMessage() );
}
beatResultSB.append( (beatResultSB.length()>0)?"<br><br>":"")
.append(I18nUtil.getString("jobconf_beat") + "")
@ -35,14 +35,14 @@ public class ExecutorRouteFailover extends ExecutorRouter {
.append("<br>msg").append(beatResult.getMsg());
// beat success
if (beatResult.getCode() == ReturnT.SUCCESS_CODE) {
if (beatResult.isSuccess()) {
beatResult.setMsg(beatResultSB.toString());
beatResult.setContent(address);
beatResult.setData(address);
return beatResult;
}
}
return new ReturnT<String>(ReturnT.FAIL_CODE, beatResultSB.toString());
return Response.ofFail( beatResultSB.toString());
}
}

@ -0,0 +1,19 @@
package com.xxl.job.admin.scheduler.route.strategy;
import com.xxl.job.admin.scheduler.route.ExecutorRouter;
import com.xxl.job.core.openapi.model.TriggerRequest;
import com.xxl.tool.response.Response;
import java.util.List;
/**
* Created by xuxueli on 17/3/10.
*/
public class ExecutorRouteFirst extends ExecutorRouter {
@Override
public Response<String> route(TriggerRequest triggerParam, List<String> addressList){
return Response.ofSuccess(addressList.get(0));
}
}

@ -1,8 +1,8 @@
package com.xxl.job.admin.core.route.strategy;
package com.xxl.job.admin.scheduler.route.strategy;
import com.xxl.job.admin.core.route.ExecutorRouter;
import com.xxl.job.core.biz.model.ReturnT;
import com.xxl.job.core.biz.model.TriggerParam;
import com.xxl.job.admin.scheduler.route.ExecutorRouter;
import com.xxl.job.core.openapi.model.TriggerRequest;
import com.xxl.tool.response.Response;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
@ -17,6 +17,11 @@ import java.util.concurrent.ConcurrentMap;
*/
public class ExecutorRouteLFU extends ExecutorRouter {
/**
* job lfu map
*
* <jobId, <address, count>>
*/
private static ConcurrentMap<Integer, HashMap<String, Integer>> jobLfuMap = new ConcurrentHashMap<Integer, HashMap<String, Integer>>();
private static long CACHE_VALID_TIME = 0;
@ -31,7 +36,7 @@ public class ExecutorRouteLFU extends ExecutorRouter {
// lfu item init
HashMap<String, Integer> lfuItemMap = jobLfuMap.get(jobId); // Key排序可以用TreeMap+构造入参CompareValue排序暂时只能通过ArrayList
if (lfuItemMap == null) {
lfuItemMap = new HashMap<String, Integer>();
lfuItemMap = new HashMap<>();
jobLfuMap.putIfAbsent(jobId, lfuItemMap); // 避免重复覆盖
}
@ -48,32 +53,26 @@ public class ExecutorRouteLFU extends ExecutorRouter {
delKeys.add(existKey);
}
}
if (delKeys.size() > 0) {
if (!delKeys.isEmpty()) {
for (String delKey: delKeys) {
lfuItemMap.remove(delKey);
}
}
// load least userd count address
List<Map.Entry<String, Integer>> lfuItemList = new ArrayList<Map.Entry<String, Integer>>(lfuItemMap.entrySet());
Collections.sort(lfuItemList, new Comparator<Map.Entry<String, Integer>>() {
@Override
public int compare(Map.Entry<String, Integer> o1, Map.Entry<String, Integer> o2) {
return o1.getValue().compareTo(o2.getValue());
}
});
List<Map.Entry<String, Integer>> lfuItemList = new ArrayList<>(lfuItemMap.entrySet());
lfuItemList.sort(Map.Entry.comparingByValue()); // 默认升序, 获取 Value 最小值
Map.Entry<String, Integer> addressItem = lfuItemList.get(0);
String minAddress = addressItem.getKey();
addressItem.setValue(addressItem.getValue() + 1);
return addressItem.getKey();
}
@Override
public ReturnT<String> route(TriggerParam triggerParam, List<String> addressList) {
public Response<String> route(TriggerRequest triggerParam, List<String> addressList) {
String address = route(triggerParam.getJobId(), addressList);
return new ReturnT<String>(address);
return Response.ofSuccess(address);
}
}

@ -1,8 +1,8 @@
package com.xxl.job.admin.core.route.strategy;
package com.xxl.job.admin.scheduler.route.strategy;
import com.xxl.job.admin.core.route.ExecutorRouter;
import com.xxl.job.core.biz.model.ReturnT;
import com.xxl.job.core.biz.model.TriggerParam;
import com.xxl.job.admin.scheduler.route.ExecutorRouter;
import com.xxl.job.core.openapi.model.TriggerRequest;
import com.xxl.tool.response.Response;
import java.util.ArrayList;
import java.util.LinkedHashMap;
@ -19,6 +19,11 @@ import java.util.concurrent.ConcurrentMap;
*/
public class ExecutorRouteLRU extends ExecutorRouter {
/**
* job lru map
*
* <jobId, <address, address>>
*/
private static ConcurrentMap<Integer, LinkedHashMap<String, String>> jobLRUMap = new ConcurrentHashMap<Integer, LinkedHashMap<String, String>>();
private static long CACHE_VALID_TIME = 0;
@ -38,7 +43,7 @@ public class ExecutorRouteLRU extends ExecutorRouter {
* aaccessOrdertrue=访get/putfalse=
* bremoveEldestEntrytrueLinkedHashMaptrueLRU
*/
lruItem = new LinkedHashMap<String, String>(16, 0.75f, true);
lruItem = new LinkedHashMap<>(16, 0.75f, true);
jobLRUMap.putIfAbsent(jobId, lruItem);
}
@ -55,22 +60,21 @@ public class ExecutorRouteLRU extends ExecutorRouter {
delKeys.add(existKey);
}
}
if (delKeys.size() > 0) {
if (!delKeys.isEmpty()) {
for (String delKey: delKeys) {
lruItem.remove(delKey);
}
}
// load
// load first elment, eldest entry
String eldestKey = lruItem.entrySet().iterator().next().getKey();
String eldestValue = lruItem.get(eldestKey);
return eldestValue;
return lruItem.get(eldestKey);
}
@Override
public ReturnT<String> route(TriggerParam triggerParam, List<String> addressList) {
public Response<String> route(TriggerRequest triggerParam, List<String> addressList) {
String address = route(triggerParam.getJobId(), addressList);
return new ReturnT<String>(address);
return Response.ofSuccess(address);
}
}

@ -0,0 +1,19 @@
package com.xxl.job.admin.scheduler.route.strategy;
import com.xxl.job.admin.scheduler.route.ExecutorRouter;
import com.xxl.job.core.openapi.model.TriggerRequest;
import com.xxl.tool.response.Response;
import java.util.List;
/**
* Created by xuxueli on 17/3/10.
*/
public class ExecutorRouteLast extends ExecutorRouter {
@Override
public Response<String> route(TriggerRequest triggerParam, List<String> addressList) {
return Response.ofSuccess(addressList.get(addressList.size()-1));
}
}

@ -0,0 +1,23 @@
package com.xxl.job.admin.scheduler.route.strategy;
import com.xxl.job.admin.scheduler.route.ExecutorRouter;
import com.xxl.job.core.openapi.model.TriggerRequest;
import com.xxl.tool.response.Response;
import java.util.List;
import java.util.Random;
/**
* Created by xuxueli on 17/3/10.
*/
public class ExecutorRouteRandom extends ExecutorRouter {
private static Random localRandom = new Random();
@Override
public Response<String> route(TriggerRequest triggerParam, List<String> addressList) {
String address = addressList.get(localRandom.nextInt(addressList.size()));
return Response.ofSuccess(address);
}
}

@ -1,8 +1,8 @@
package com.xxl.job.admin.core.route.strategy;
package com.xxl.job.admin.scheduler.route.strategy;
import com.xxl.job.admin.core.route.ExecutorRouter;
import com.xxl.job.core.biz.model.ReturnT;
import com.xxl.job.core.biz.model.TriggerParam;
import com.xxl.job.admin.scheduler.route.ExecutorRouter;
import com.xxl.job.core.openapi.model.TriggerRequest;
import com.xxl.tool.response.Response;
import java.util.List;
import java.util.Random;
@ -38,9 +38,9 @@ public class ExecutorRouteRound extends ExecutorRouter {
}
@Override
public ReturnT<String> route(TriggerParam triggerParam, List<String> addressList) {
public Response<String> route(TriggerRequest triggerParam, List<String> addressList) {
String address = addressList.get(count(triggerParam.getJobId())%addressList.size());
return new ReturnT<String>(address);
return Response.ofSuccess(address);
}
}

@ -1,12 +1,12 @@
package com.xxl.job.admin.core.thread;
import com.xxl.job.admin.core.complete.XxlJobCompleter;
import com.xxl.job.admin.core.conf.XxlJobAdminConfig;
import com.xxl.job.admin.core.model.XxlJobLog;
import com.xxl.job.admin.core.util.I18nUtil;
import com.xxl.job.core.biz.model.HandleCallbackParam;
import com.xxl.job.core.biz.model.ReturnT;
import com.xxl.job.core.util.DateUtil;
package com.xxl.job.admin.scheduler.thread;
import com.xxl.job.admin.model.XxlJobLog;
import com.xxl.job.admin.scheduler.config.XxlJobAdminBootstrap;
import com.xxl.job.admin.util.I18nUtil;
import com.xxl.job.core.openapi.model.CallbackRequest;
import com.xxl.job.core.context.XxlJobContext;
import com.xxl.tool.core.DateTool;
import com.xxl.tool.response.Response;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -15,23 +15,22 @@ import java.util.List;
import java.util.concurrent.*;
/**
* job lose-monitor instance
* job complate, for callback and result-lost
*
* @author xuxueli 2015-9-1 18:05:56
*/
public class JobCompleteHelper {
private static Logger logger = LoggerFactory.getLogger(JobCompleteHelper.class);
private static final Logger logger = LoggerFactory.getLogger(JobCompleteHelper.class);
private static JobCompleteHelper instance = new JobCompleteHelper();
public static JobCompleteHelper getInstance(){
return instance;
}
// ---------------------- monitor ----------------------
private ThreadPoolExecutor callbackThreadPool = null;
private Thread monitorThread;
private volatile boolean toStop = false;
/**
* start
*/
public void start(){
// for callback
@ -75,8 +74,8 @@ public class JobCompleteHelper {
while (!toStop) {
try {
// 任务结果丢失处理:调度记录停留在 "运行中" 状态超过10min且对应执行器心跳注册失败不在线则将本地调度主动标记失败
Date losedTime = DateUtil.addMinutes(new Date(), -10);
List<Long> losedJobIds = XxlJobAdminConfig.getAdminConfig().getXxlJobLogDao().findLostJobIds(losedTime);
Date losedTime = DateTool.addMinutes(new Date(), -10);
List<Long> losedJobIds = XxlJobAdminBootstrap.getInstance().getXxlJobLogMapper().findLostJobIds(losedTime);
if (losedJobIds!=null && losedJobIds.size()>0) {
for (Long logId: losedJobIds) {
@ -85,10 +84,10 @@ public class JobCompleteHelper {
jobLog.setId(logId);
jobLog.setHandleTime(new Date());
jobLog.setHandleCode(ReturnT.FAIL_CODE);
jobLog.setHandleCode(XxlJobContext.HANDLE_CODE_FAIL);
jobLog.setHandleMsg( I18nUtil.getString("joblog_lost_fail") );
XxlJobCompleter.updateHandleInfoAndFinish(jobLog);
XxlJobAdminBootstrap.getInstance().getJobCompleter().complete(jobLog);
}
}
@ -117,7 +116,10 @@ public class JobCompleteHelper {
monitorThread.start();
}
public void toStop(){
/**
* stop
*/
public void stop(){
toStop = true;
// stop registryOrRemoveThreadPool
@ -135,30 +137,30 @@ public class JobCompleteHelper {
// ---------------------- helper ----------------------
public ReturnT<String> callback(List<HandleCallbackParam> callbackParamList) {
public Response<String> callback(List<CallbackRequest> callbackParamList) {
callbackThreadPool.execute(new Runnable() {
@Override
public void run() {
for (HandleCallbackParam handleCallbackParam: callbackParamList) {
ReturnT<String> callbackResult = callback(handleCallbackParam);
logger.debug(">>>>>>>>> JobApiController.callback {}, handleCallbackParam={}, callbackResult={}",
(callbackResult.getCode()== ReturnT.SUCCESS_CODE?"success":"fail"), handleCallbackParam, callbackResult);
for (CallbackRequest callbackRequest: callbackParamList) {
Response<String> callbackResult = doCallback(callbackRequest);
logger.debug(">>>>>>>>> JobApiController.callback {}, callbackRequest={}, callbackResult={}",
(callbackResult.isSuccess()?"success":"fail"), callbackRequest, callbackResult);
}
}
});
return ReturnT.SUCCESS;
return Response.ofSuccess();
}
private ReturnT<String> callback(HandleCallbackParam handleCallbackParam) {
private Response<String> doCallback(CallbackRequest handleCallbackParam) {
// valid log item
XxlJobLog log = XxlJobAdminConfig.getAdminConfig().getXxlJobLogDao().load(handleCallbackParam.getLogId());
XxlJobLog log = XxlJobAdminBootstrap.getInstance().getXxlJobLogMapper().load(handleCallbackParam.getLogId());
if (log == null) {
return new ReturnT<String>(ReturnT.FAIL_CODE, "log item not found.");
return Response.ofFail( "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
return Response.ofFail("log repeate callback."); // avoid repeat callback, trigger child job etc
}
// handle msg
@ -174,9 +176,9 @@ public class JobCompleteHelper {
log.setHandleTime(new Date());
log.setHandleCode(handleCallbackParam.getHandleCode());
log.setHandleMsg(handleMsg.toString());
XxlJobCompleter.updateHandleInfoAndFinish(log);
XxlJobAdminBootstrap.getInstance().getJobCompleter().complete(log);
return ReturnT.SUCCESS;
return Response.ofSuccess();
}

@ -1,10 +1,10 @@
package com.xxl.job.admin.core.thread;
package com.xxl.job.admin.scheduler.thread;
import com.xxl.job.admin.core.conf.XxlJobAdminConfig;
import com.xxl.job.admin.core.model.XxlJobInfo;
import com.xxl.job.admin.core.model.XxlJobLog;
import com.xxl.job.admin.core.trigger.TriggerTypeEnum;
import com.xxl.job.admin.core.util.I18nUtil;
import com.xxl.job.admin.model.XxlJobInfo;
import com.xxl.job.admin.model.XxlJobLog;
import com.xxl.job.admin.scheduler.config.XxlJobAdminBootstrap;
import com.xxl.job.admin.scheduler.trigger.TriggerTypeEnum;
import com.xxl.job.admin.util.I18nUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -12,22 +12,22 @@ import java.util.List;
import java.util.concurrent.TimeUnit;
/**
* job monitor instance
* job fail-monitor helper
*
* @author xuxueli 2015-9-1 18:05:56
*/
public class JobFailMonitorHelper {
private static Logger logger = LoggerFactory.getLogger(JobFailMonitorHelper.class);
public class JobFailAlarmMonitorHelper {
private static Logger logger = LoggerFactory.getLogger(JobFailAlarmMonitorHelper.class);
private static JobFailMonitorHelper instance = new JobFailMonitorHelper();
public static JobFailMonitorHelper getInstance(){
return instance;
}
// ---------------------- monitor ----------------------
private Thread monitorThread;
private volatile boolean toStop = false;
/**
* start
*/
public void start(){
monitorThread = new Thread(new Runnable() {
@ -38,42 +38,42 @@ public class JobFailMonitorHelper {
while (!toStop) {
try {
List<Long> failLogIds = XxlJobAdminConfig.getAdminConfig().getXxlJobLogDao().findFailJobLogIds(1000);
List<Long> failLogIds = XxlJobAdminBootstrap.getInstance().getXxlJobLogMapper().findFailJobLogIds(1000);
if (failLogIds!=null && !failLogIds.isEmpty()) {
for (long failLogId: failLogIds) {
// lock log
int lockRet = XxlJobAdminConfig.getAdminConfig().getXxlJobLogDao().updateAlarmStatus(failLogId, 0, -1);
int lockRet = XxlJobAdminBootstrap.getInstance().getXxlJobLogMapper().updateAlarmStatus(failLogId, 0, -1);
if (lockRet < 1) {
continue;
}
XxlJobLog log = XxlJobAdminConfig.getAdminConfig().getXxlJobLogDao().load(failLogId);
XxlJobInfo info = XxlJobAdminConfig.getAdminConfig().getXxlJobInfoDao().loadById(log.getJobId());
XxlJobLog log = XxlJobAdminBootstrap.getInstance().getXxlJobLogMapper().load(failLogId);
XxlJobInfo info = XxlJobAdminBootstrap.getInstance().getXxlJobInfoMapper().loadById(log.getJobId());
// 1、fail retry monitor
if (log.getExecutorFailRetryCount() > 0) {
JobTriggerPoolHelper.trigger(log.getJobId(), TriggerTypeEnum.RETRY, (log.getExecutorFailRetryCount()-1), log.getExecutorShardingParam(), log.getExecutorParam(), null);
String retryMsg = "<br><br><span style=\"color:#F39C12;\" > >>>>>>>>>>>"+ I18nUtil.getString("jobconf_trigger_type_retry") +"<<<<<<<<<<< </span><br>";
XxlJobAdminBootstrap.getInstance().getJobTriggerPoolHelper().trigger(log.getJobId(), TriggerTypeEnum.RETRY, (log.getExecutorFailRetryCount()-1), log.getExecutorShardingParam(), log.getExecutorParam(), null);
String retryMsg = "<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>"+ I18nUtil.getString("jobconf_trigger_type_retry") +"<<<<<<<<<<< </span><br>";
log.setTriggerMsg(log.getTriggerMsg() + retryMsg);
XxlJobAdminConfig.getAdminConfig().getXxlJobLogDao().updateTriggerInfo(log);
XxlJobAdminBootstrap.getInstance().getXxlJobLogMapper().updateTriggerInfo(log);
}
// 2、fail alarm monitor
int newAlarmStatus = 0; // 告警状态0-默认、-1=锁定状态、1-无需告警、2-告警成功、3-告警失败
if (info != null) {
boolean alarmResult = XxlJobAdminConfig.getAdminConfig().getJobAlarmer().alarm(info, log);
boolean alarmResult = XxlJobAdminBootstrap.getInstance().getJobAlarmer().alarm(info, log);
newAlarmStatus = alarmResult?2:3;
} else {
newAlarmStatus = 1;
}
XxlJobAdminConfig.getAdminConfig().getXxlJobLogDao().updateAlarmStatus(failLogId, -1, newAlarmStatus);
XxlJobAdminBootstrap.getInstance().getXxlJobLogMapper().updateAlarmStatus(failLogId, -1, newAlarmStatus);
}
}
} catch (Throwable e) {
if (!toStop) {
logger.error(">>>>>>>>>>> xxl-job, job fail monitor thread error:{}", e);
logger.error(">>>>>>>>>>> xxl-job, job fail monitor thread error:{}", e.getMessage(), e);
}
}
@ -96,7 +96,10 @@ public class JobFailMonitorHelper {
monitorThread.start();
}
public void toStop(){
/**
* stop
*/
public void stop(){
toStop = true;
// interrupt and wait
monitorThread.interrupt();

@ -0,0 +1,162 @@
package com.xxl.job.admin.scheduler.thread;
import com.xxl.job.admin.scheduler.config.XxlJobAdminBootstrap;
import com.xxl.job.admin.model.XxlJobLogReport;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
/**
* job log report helper
*
* @author xuxueli 2019-11-22
*/
public class JobLogReportHelper {
private static final Logger logger = LoggerFactory.getLogger(JobLogReportHelper.class);
private Thread logReportThread;
private volatile boolean toStop = false;
/**
* start
*/
public void start(){
logReportThread = new Thread(new Runnable() {
@Override
public void run() {
// last clean log time
long lastCleanLogTime = 0;
while (!toStop) {
// 1、log-report refresh: refresh log report in 3 days
try {
for (int i = 0; i < 3; i++) {
// today
Calendar itemDay = Calendar.getInstance();
itemDay.add(Calendar.DAY_OF_MONTH, -i);
itemDay.set(Calendar.HOUR_OF_DAY, 0);
itemDay.set(Calendar.MINUTE, 0);
itemDay.set(Calendar.SECOND, 0);
itemDay.set(Calendar.MILLISECOND, 0);
Date todayFrom = itemDay.getTime();
itemDay.set(Calendar.HOUR_OF_DAY, 23);
itemDay.set(Calendar.MINUTE, 59);
itemDay.set(Calendar.SECOND, 59);
itemDay.set(Calendar.MILLISECOND, 999);
Date todayTo = itemDay.getTime();
// refresh log-report every minute
XxlJobLogReport xxlJobLogReport = new XxlJobLogReport();
xxlJobLogReport.setTriggerDay(todayFrom);
xxlJobLogReport.setRunningCount(0);
xxlJobLogReport.setSucCount(0);
xxlJobLogReport.setFailCount(0);
xxlJobLogReport.setUpdateTime(new Date());
// fill count-data
Map<String, Object> triggerCountMap = XxlJobAdminBootstrap.getInstance().getXxlJobLogMapper().findLogReport(todayFrom, todayTo);
if (triggerCountMap!=null && !triggerCountMap.isEmpty()) {
int triggerDayCount = triggerCountMap.containsKey("triggerDayCount")?Integer.parseInt(String.valueOf(triggerCountMap.get("triggerDayCount"))):0;
int triggerDayCountRunning = triggerCountMap.containsKey("triggerDayCountRunning")?Integer.parseInt(String.valueOf(triggerCountMap.get("triggerDayCountRunning"))):0;
int triggerDayCountSuc = triggerCountMap.containsKey("triggerDayCountSuc")?Integer.parseInt(String.valueOf(triggerCountMap.get("triggerDayCountSuc"))):0;
int triggerDayCountFail = triggerDayCount - triggerDayCountRunning - triggerDayCountSuc;
xxlJobLogReport.setRunningCount(triggerDayCountRunning);
xxlJobLogReport.setSucCount(triggerDayCountSuc);
xxlJobLogReport.setFailCount(triggerDayCountFail);
}
// do refresh:
XxlJobAdminBootstrap.getInstance().getXxlJobLogReportMapper().saveOrUpdate(xxlJobLogReport); // 0-fail; 1-save suc; 2-update suc;
/*if (ret < 1) {
XxlJobAdminBootstrap.getInstance().getXxlJobLogReportMapper().save(xxlJobLogReport);
}*/
}
} catch (Throwable e) {
if (!toStop) {
logger.error(">>>>>>>>>>> xxl-job, JobLogReportHelper(log-report refresh) error:{}", e.getMessage(), e);
}
}
// 2、log-clean: switch open & once each day
try {
if (XxlJobAdminBootstrap.getInstance().getLogretentiondays()>0
&& System.currentTimeMillis() - lastCleanLogTime > 24*60*60*1000) {
// expire-time
Calendar expiredDay = Calendar.getInstance();
expiredDay.add(Calendar.DAY_OF_MONTH, -1 * XxlJobAdminBootstrap.getInstance().getLogretentiondays());
expiredDay.set(Calendar.HOUR_OF_DAY, 0);
expiredDay.set(Calendar.MINUTE, 0);
expiredDay.set(Calendar.SECOND, 0);
expiredDay.set(Calendar.MILLISECOND, 0);
Date clearBeforeTime = expiredDay.getTime();
// clean expired log
List<Long> logIds = null;
do {
logIds = XxlJobAdminBootstrap.getInstance().getXxlJobLogMapper().findClearLogIds(0, 0, clearBeforeTime, 0, 1000);
if (logIds!=null && !logIds.isEmpty()) {
XxlJobAdminBootstrap.getInstance().getXxlJobLogMapper().clearLog(logIds);
}
} while (logIds!=null && !logIds.isEmpty());
// update clean time
lastCleanLogTime = System.currentTimeMillis();
}
} catch (Throwable e) {
if (!toStop) {
logger.error(">>>>>>>>>>> xxl-job, JobLogReportHelper(log-clean) error:{}", e.getMessage(), e);
}
}
try {
TimeUnit.MINUTES.sleep(1);
} catch (Throwable e) {
if (!toStop) {
logger.error(e.getMessage(), e);
}
}
}
logger.info(">>>>>>>>>>> xxl-job, job log report thread stop");
}
});
logReportThread.setDaemon(true);
logReportThread.setName("xxl-job, admin JobLogReportHelper");
logReportThread.start();
}
/**
* stop
*/
public void stop(){
toStop = true;
// interrupt and wait
logReportThread.interrupt();
try {
logReportThread.join();
} catch (Throwable e) {
logger.error(e.getMessage(), e);
}
}
}

@ -1,34 +1,36 @@
package com.xxl.job.admin.core.thread;
import com.xxl.job.admin.core.conf.XxlJobAdminConfig;
import com.xxl.job.admin.core.model.XxlJobGroup;
import com.xxl.job.admin.core.model.XxlJobRegistry;
import com.xxl.job.core.biz.model.RegistryParam;
import com.xxl.job.core.biz.model.ReturnT;
import com.xxl.job.core.enums.RegistryConfig;
package com.xxl.job.admin.scheduler.thread;
import com.xxl.job.admin.model.XxlJobGroup;
import com.xxl.job.admin.model.XxlJobRegistry;
import com.xxl.job.admin.scheduler.config.XxlJobAdminBootstrap;
import com.xxl.job.core.constant.RegistType;
import com.xxl.job.core.openapi.model.RegistryRequest;
import com.xxl.job.core.constant.Const;
import com.xxl.tool.core.StringTool;
import com.xxl.tool.response.Response;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;
import java.util.*;
import java.util.concurrent.*;
/**
* job registry instance
* job registry instance helper
*
* @author xuxueli 2016-10-02 19:10:24
*/
public class JobRegistryHelper {
private static Logger logger = LoggerFactory.getLogger(JobRegistryHelper.class);
private static JobRegistryHelper instance = new JobRegistryHelper();
public static JobRegistryHelper getInstance(){
return instance;
}
private ThreadPoolExecutor registryOrRemoveThreadPool = null;
private Thread registryMonitorThread;
private volatile boolean toStop = false;
/**
* start
*/
public void start(){
// for registry or remove
@ -59,21 +61,21 @@ public class JobRegistryHelper {
while (!toStop) {
try {
// auto registry group
List<XxlJobGroup> groupList = XxlJobAdminConfig.getAdminConfig().getXxlJobGroupDao().findByAddressType(0);
List<XxlJobGroup> groupList = XxlJobAdminBootstrap.getInstance().getXxlJobGroupMapper().findByAddressType(0);
if (groupList!=null && !groupList.isEmpty()) {
// remove dead address (admin/executor)
List<Integer> ids = XxlJobAdminConfig.getAdminConfig().getXxlJobRegistryDao().findDead(RegistryConfig.DEAD_TIMEOUT, new Date());
if (ids!=null && ids.size()>0) {
XxlJobAdminConfig.getAdminConfig().getXxlJobRegistryDao().removeDead(ids);
List<Integer> ids = XxlJobAdminBootstrap.getInstance().getXxlJobRegistryMapper().findDead(Const.DEAD_TIMEOUT, new Date());
if (ids!=null && !ids.isEmpty()) {
XxlJobAdminBootstrap.getInstance().getXxlJobRegistryMapper().removeDead(ids);
}
// fresh online address (admin/executor)
HashMap<String, List<String>> appAddressMap = new HashMap<String, List<String>>();
List<XxlJobRegistry> list = XxlJobAdminConfig.getAdminConfig().getXxlJobRegistryDao().findAll(RegistryConfig.DEAD_TIMEOUT, new Date());
List<XxlJobRegistry> list = XxlJobAdminBootstrap.getInstance().getXxlJobRegistryMapper().findAll(Const.DEAD_TIMEOUT, new Date());
if (list != null) {
for (XxlJobRegistry item: list) {
if (RegistryConfig.RegistType.EXECUTOR.name().equals(item.getRegistryGroup())) {
if (RegistType.EXECUTOR.name().equals(item.getRegistryGroup())) {
String appname = item.getRegistryKey();
List<String> registryList = appAddressMap.get(appname);
if (registryList == null) {
@ -104,7 +106,7 @@ public class JobRegistryHelper {
group.setAddressList(addressListStr);
group.setUpdateTime(new Date());
XxlJobAdminConfig.getAdminConfig().getXxlJobGroupDao().update(group);
XxlJobAdminBootstrap.getInstance().getXxlJobGroupMapper().update(group);
}
}
} catch (Throwable e) {
@ -113,7 +115,7 @@ public class JobRegistryHelper {
}
}
try {
TimeUnit.SECONDS.sleep(RegistryConfig.BEAT_TIMEOUT);
TimeUnit.SECONDS.sleep(Const.BEAT_TIMEOUT);
} catch (Throwable e) {
if (!toStop) {
logger.error(">>>>>>>>>>> xxl-job, job registry monitor thread error:{}", e);
@ -128,13 +130,17 @@ public class JobRegistryHelper {
registryMonitorThread.start();
}
public void toStop(){
/**
* stop
*/
public void stop(){
toStop = true;
// stop registryOrRemoveThreadPool
registryOrRemoveThreadPool.shutdownNow();
// stop monitir (interrupt and wait)
// stop monitor (interrupt and wait)
registryMonitorThread.interrupt();
try {
registryMonitorThread.join();
@ -144,15 +150,18 @@ public class JobRegistryHelper {
}
// ---------------------- helper ----------------------
// ---------------------- tool ----------------------
public ReturnT<String> registry(RegistryParam registryParam) {
/**
* registry
*/
public Response<String> registry(RegistryRequest registryParam) {
// valid
if (!StringUtils.hasText(registryParam.getRegistryGroup())
|| !StringUtils.hasText(registryParam.getRegistryKey())
|| !StringUtils.hasText(registryParam.getRegistryValue())) {
return new ReturnT<String>(ReturnT.FAIL_CODE, "Illegal Argument.");
if (StringTool.isBlank(registryParam.getRegistryGroup())
|| StringTool.isBlank(registryParam.getRegistryKey())
|| StringTool.isBlank(registryParam.getRegistryValue())) {
return Response.ofFail("Illegal Argument.");
}
// async execute
@ -160,7 +169,7 @@ public class JobRegistryHelper {
@Override
public void run() {
// 0-fail; 1-save suc; 2-update suc;
int ret = XxlJobAdminConfig.getAdminConfig().getXxlJobRegistryDao().registrySaveOrUpdate(registryParam.getRegistryGroup(), registryParam.getRegistryKey(), registryParam.getRegistryValue(), new Date());
int ret = XxlJobAdminBootstrap.getInstance().getXxlJobRegistryMapper().registrySaveOrUpdate(registryParam.getRegistryGroup(), registryParam.getRegistryKey(), registryParam.getRegistryValue(), new Date());
if (ret == 1) {
// fresh (add)
freshGroupRegistryInfo(registryParam);
@ -175,23 +184,26 @@ public class JobRegistryHelper {
}
});
return ReturnT.SUCCESS;
return Response.ofSuccess();
}
public ReturnT<String> registryRemove(RegistryParam registryParam) {
/**
* registry remove
*/
public Response<String> registryRemove(RegistryRequest registryParam) {
// valid
if (!StringUtils.hasText(registryParam.getRegistryGroup())
|| !StringUtils.hasText(registryParam.getRegistryKey())
|| !StringUtils.hasText(registryParam.getRegistryValue())) {
return new ReturnT<String>(ReturnT.FAIL_CODE, "Illegal Argument.");
if (StringTool.isBlank(registryParam.getRegistryGroup())
|| StringTool.isBlank(registryParam.getRegistryKey())
|| StringTool.isBlank(registryParam.getRegistryValue())) {
return Response.ofFail("Illegal Argument.");
}
// async execute
registryOrRemoveThreadPool.execute(new Runnable() {
@Override
public void run() {
int ret = XxlJobAdminConfig.getAdminConfig().getXxlJobRegistryDao().registryDelete(registryParam.getRegistryGroup(), registryParam.getRegistryKey(), registryParam.getRegistryValue());
int ret = XxlJobAdminBootstrap.getInstance().getXxlJobRegistryMapper().registryDelete(registryParam.getRegistryGroup(), registryParam.getRegistryKey(), registryParam.getRegistryValue());
if (ret > 0) {
// fresh (delete)
freshGroupRegistryInfo(registryParam);
@ -199,10 +211,10 @@ public class JobRegistryHelper {
}
});
return ReturnT.SUCCESS;
return Response.ofSuccess();
}
private void freshGroupRegistryInfo(RegistryParam registryParam){
private void freshGroupRegistryInfo(RegistryRequest registryParam){
// Under consideration, prevent affecting core tables
}

@ -1,16 +1,18 @@
package com.xxl.job.admin.core.thread;
import com.xxl.job.admin.core.conf.XxlJobAdminConfig;
import com.xxl.job.admin.core.cron.CronExpression;
import com.xxl.job.admin.core.model.XxlJobInfo;
import com.xxl.job.admin.core.scheduler.MisfireStrategyEnum;
import com.xxl.job.admin.core.scheduler.ScheduleTypeEnum;
import com.xxl.job.admin.core.trigger.TriggerTypeEnum;
package com.xxl.job.admin.scheduler.thread;
import com.xxl.job.admin.constant.TriggerStatus;
import com.xxl.job.admin.model.XxlJobInfo;
import com.xxl.job.admin.scheduler.config.XxlJobAdminBootstrap;
import com.xxl.job.admin.scheduler.misfire.MisfireStrategyEnum;
import com.xxl.job.admin.scheduler.trigger.TriggerTypeEnum;
import com.xxl.job.admin.scheduler.type.ScheduleTypeEnum;
import com.xxl.tool.core.CollectionTool;
import com.xxl.tool.core.MapTool;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
@ -19,21 +21,27 @@ import java.util.concurrent.TimeUnit;
* @author xuxueli 2019-05-21
*/
public class JobScheduleHelper {
private static Logger logger = LoggerFactory.getLogger(JobScheduleHelper.class);
private static final Logger logger = LoggerFactory.getLogger(JobScheduleHelper.class);
private static JobScheduleHelper instance = new JobScheduleHelper();
public static JobScheduleHelper getInstance(){
return instance;
}
public static final long PRE_READ_MS = 5000; // pre read
/**
* pre-read time for scheduler, increase efficiency
*/
public static final long PRE_READ_MS = 5000;
/*
* elegant shutdown wait seconds
*/
private static final long ELEGANT_SHUTDOWN_WAITING_SECONDS = 10;
private Thread scheduleThread;
private Thread ringThread;
private volatile boolean scheduleThreadToStop = false;
private volatile boolean ringThreadToStop = false;
private volatile static Map<Integer, List<Integer>> ringData = new ConcurrentHashMap<>();
private final Map<Integer, List<Integer>> ringData = new ConcurrentHashMap<>();
/**
* start
*/
public void start(){
// schedule thread
@ -41,6 +49,7 @@ public class JobScheduleHelper {
@Override
public void run() {
// align time
try {
TimeUnit.MILLISECONDS.sleep(5000 - System.currentTimeMillis()%1000 );
} catch (Throwable e) {
@ -50,74 +59,64 @@ public class JobScheduleHelper {
}
logger.info(">>>>>>>>> init xxl-job admin scheduler success.");
// pre-read count: treadpool-size * trigger-qps (each trigger cost 50ms, qps = 1000/50 = 20)
int preReadCount = (XxlJobAdminConfig.getAdminConfig().getTriggerPoolFastMax() + XxlJobAdminConfig.getAdminConfig().getTriggerPoolSlowMax()) * 20;
// pre-read count: treadpool-size * 10 (trigger-qps: 1000ms / 100ms each trigger cost)
int preReadCount = (XxlJobAdminBootstrap.getInstance().getTriggerPoolFastMax() + XxlJobAdminBootstrap.getInstance().getTriggerPoolSlowMax()) * 10;
// do schedule
while (!scheduleThreadToStop) {
// Scan Job
// param
long start = System.currentTimeMillis();
Connection conn = null;
Boolean connAutoCommit = null;
PreparedStatement preparedStatement = null;
boolean preReadSuc = true;
try {
conn = XxlJobAdminConfig.getAdminConfig().getDataSource().getConnection();
connAutoCommit = conn.getAutoCommit();
conn.setAutoCommit(false);
preparedStatement = conn.prepareStatement( "select * from xxl_job_lock where lock_name = 'schedule_lock' for update" );
preparedStatement.execute();
// transaction start
TransactionStatus transactionStatus = null;
try {
transactionStatus = XxlJobAdminBootstrap.getInstance().getTransactionManager().getTransaction(new DefaultTransactionDefinition());
// 1、job lock
String lockedRecord = XxlJobAdminBootstrap.getInstance().getXxlJobLockMapper().scheduleLock();
long nowTime = System.currentTimeMillis();
// tx start
// scan and process job
List<XxlJobInfo> scheduleList = XxlJobAdminBootstrap.getInstance().getXxlJobInfoMapper().scheduleJobQuery(nowTime + PRE_READ_MS, preReadCount);
if (CollectionTool.isNotEmpty(scheduleList)) {
// 1、pre read
long nowTime = System.currentTimeMillis();
List<XxlJobInfo> scheduleList = XxlJobAdminConfig.getAdminConfig().getXxlJobInfoDao().scheduleJobQuery(nowTime + PRE_READ_MS, preReadCount);
if (scheduleList!=null && scheduleList.size()>0) {
// 2、push time-ring
for (XxlJobInfo jobInfo: scheduleList) {
// time-ring jump
if (nowTime > jobInfo.getTriggerNextTime() + PRE_READ_MS) {
// 2.1、trigger-expire > 5spass && make next-trigger-time
logger.warn(">>>>>>>>>>> xxl-job, schedule misfire, jobId = " + jobInfo.getId());
// 1、misfire match
// 1、misfire handle
MisfireStrategyEnum misfireStrategyEnum = MisfireStrategyEnum.match(jobInfo.getMisfireStrategy(), MisfireStrategyEnum.DO_NOTHING);
if (MisfireStrategyEnum.FIRE_ONCE_NOW == misfireStrategyEnum) {
// FIRE_ONCE_NOW 》 trigger
JobTriggerPoolHelper.trigger(jobInfo.getId(), TriggerTypeEnum.MISFIRE, -1, null, null, null);
logger.debug(">>>>>>>>>>> xxl-job, schedule push trigger : jobId = " + jobInfo.getId() );
}
misfireStrategyEnum.getMisfireHandler().handle(jobInfo.getId());
// 2、fresh next
refreshNextValidTime(jobInfo, new Date());
refreshNextTriggerTime(jobInfo, new Date());
} else if (nowTime > jobInfo.getTriggerNextTime()) {
} else if (nowTime >= jobInfo.getTriggerNextTime()) {
// 2.2、trigger-expire < 5sdirect-trigger && make next-trigger-time
// 1、trigger
JobTriggerPoolHelper.trigger(jobInfo.getId(), TriggerTypeEnum.CRON, -1, null, null, null);
logger.debug(">>>>>>>>>>> xxl-job, schedule push trigger : jobId = " + jobInfo.getId() );
// 1、trigger direct
XxlJobAdminBootstrap.getInstance().getJobTriggerPoolHelper().trigger(jobInfo.getId(), TriggerTypeEnum.CRON, -1, null, null, null);
logger.debug(">>>>>>>>>>> xxl-job, schedule expire, direct trigger : jobId = " + jobInfo.getId() );
// 2、fresh next
refreshNextValidTime(jobInfo, new Date());
refreshNextTriggerTime(jobInfo, new Date());
// next-trigger-time in 5s, pre-read again
if (jobInfo.getTriggerStatus()==1 && nowTime + PRE_READ_MS > jobInfo.getTriggerNextTime()) {
if (jobInfo.getTriggerStatus()== TriggerStatus.RUNNING.getValue() && nowTime + PRE_READ_MS > jobInfo.getTriggerNextTime()) {
// 1、make ring second
int ringSecond = (int)((jobInfo.getTriggerNextTime()/1000)%60);
// 2、push time ring
// 2、push time ring (pre read)
pushTimeRing(ringSecond, jobInfo.getId());
logger.debug(">>>>>>>>>>> xxl-job, schedule pre-read, push trigger : jobId = " + jobInfo.getId() );
// 3、fresh next
refreshNextValidTime(jobInfo, new Date(jobInfo.getTriggerNextTime()));
refreshNextTriggerTime(jobInfo, new Date(jobInfo.getTriggerNextTime()));
}
@ -129,68 +128,45 @@ public class JobScheduleHelper {
// 2、push time ring
pushTimeRing(ringSecond, jobInfo.getId());
logger.debug(">>>>>>>>>>> xxl-job, schedule normal, push trigger : jobId = " + jobInfo.getId() );
// 3、fresh next
refreshNextValidTime(jobInfo, new Date(jobInfo.getTriggerNextTime()));
refreshNextTriggerTime(jobInfo, new Date(jobInfo.getTriggerNextTime()));
}
}
// 3、update trigger info
for (XxlJobInfo jobInfo: scheduleList) {
XxlJobAdminConfig.getAdminConfig().getXxlJobInfoDao().scheduleUpdate(jobInfo);
/*for (XxlJobInfo jobInfo: scheduleList) {
XxlJobAdminBootstrap.getInstance().getXxlJobInfoMapper().scheduleUpdate(jobInfo);
}*/
int batchSize = XxlJobAdminBootstrap.getInstance().getScheduleBatchSize();
List<List<XxlJobInfo>> scheduleListBatches = CollectionTool.split(scheduleList, batchSize);
for (List<XxlJobInfo> scheduleListBatch : scheduleListBatches) {
int totalAffected = XxlJobAdminBootstrap.getInstance().getXxlJobInfoMapper().scheduleBatchUpdate(scheduleListBatch);
logger.debug(">>>>>>>>>>> xxl-job, JobScheduleHelper scheduleBatchUpdate records:" + totalAffected);
}
} else {
preReadSuc = false;
}
// tx stop
} catch (Throwable e) {
if (!scheduleThreadToStop) {
logger.error(">>>>>>>>>>> xxl-job, JobScheduleHelper#scheduleThread error:{}", e);
logger.error(">>>>>>>>>>> xxl-job, JobScheduleHelper#scheduleThread error:{}", e.getMessage(), e);
}
} finally {
// commit
if (conn != null) {
try {
conn.commit();
} catch (Throwable e) {
if (!scheduleThreadToStop) {
logger.error(e.getMessage(), e);
}
}
try {
conn.setAutoCommit(connAutoCommit);
} catch (Throwable e) {
if (!scheduleThreadToStop) {
logger.error(e.getMessage(), e);
}
}
try {
conn.close();
} catch (Throwable e) {
if (!scheduleThreadToStop) {
logger.error(e.getMessage(), e);
}
}
}
// close PreparedStatement
if (null != preparedStatement) {
try {
preparedStatement.close();
} catch (Throwable e) {
if (!scheduleThreadToStop) {
logger.error(e.getMessage(), e);
}
// transaction commit
try {
if (transactionStatus != null) {
XxlJobAdminBootstrap.getInstance().getTransactionManager().commit(transactionStatus); // avlid schedule repeat
}
} catch (Throwable e) {
logger.error(">>>>>>>>>>> xxl-job, JobScheduleHelper#scheduleThread transaction commit error:{}", e.getMessage(), e);
}
}
// transaction end
long cost = System.currentTimeMillis()-start;
@ -235,28 +211,37 @@ public class JobScheduleHelper {
try {
// second data
List<Integer> ringItemData = new ArrayList<>();
int nowSecond = Calendar.getInstance().get(Calendar.SECOND); // 避免处理耗时太长,跨过刻度,向前校验一个刻度;
for (int i = 0; i < 2; i++) {
List<Integer> tmpData = ringData.remove( (nowSecond+60-i)%60 );
if (tmpData != null) {
ringItemData.addAll(tmpData);
// collect rind data, by second
int nowSecond = Calendar.getInstance().get(Calendar.SECOND);
for (int i = 0; i <= 2; i++) { // 避免调度遗漏:处理耗时太长、跨过刻度,除当前刻度外 + 向前校验2个刻度
List<Integer> ringItemList = ringData.remove( (nowSecond+60-i)%60 );
if (CollectionTool.isNotEmpty(ringItemList)) {
// distinct for each second
List<Integer> ringItemListDistinct = ringItemList.stream().distinct().toList(); // 避免调度重复:重复推送时间轮刻度,去重只保留一个;;
if (ringItemListDistinct.size() < ringItemList.size()) {
logger.warn(">>>>>>>>>>> xxl-job, time-ring found job repeat beat : " + nowSecond + " = " + ringItemData);
}
// collect ring item
ringItemData.addAll(ringItemListDistinct);
}
}
// ring trigger
logger.debug(">>>>>>>>>>> xxl-job, time-ring beat : " + nowSecond + " = " + Arrays.asList(ringItemData) );
if (ringItemData.size() > 0) {
logger.debug(">>>>>>>>>>> xxl-job, time-ring beat : " + nowSecond + " = " + ringItemData);
if (CollectionTool.isNotEmpty(ringItemData)) {
// do trigger
for (int jobId: ringItemData) {
// do trigger
JobTriggerPoolHelper.trigger(jobId, TriggerTypeEnum.CRON, -1, null, null, null);
XxlJobAdminBootstrap.getInstance().getJobTriggerPoolHelper().trigger(jobId, TriggerTypeEnum.CRON, -1, null, null, null);
}
// clear
ringItemData.clear();
}
} catch (Throwable e) {
if (!ringThreadToStop) {
logger.error(">>>>>>>>>>> xxl-job, JobScheduleHelper#ringThread error:{}", e);
logger.error(">>>>>>>>>>> xxl-job, JobScheduleHelper#ringThread error:{}", e.getMessage(), e);
}
}
}
@ -268,24 +253,35 @@ public class JobScheduleHelper {
ringThread.start();
}
private void refreshNextValidTime(XxlJobInfo jobInfo, Date fromTime) {
/**
* refresh next trigger time of job
*
* @param jobInfo job info
* @param fromTime from time
*/
private void refreshNextTriggerTime(XxlJobInfo jobInfo, Date fromTime) {
try {
Date nextValidTime = generateNextValidTime(jobInfo, fromTime);
if (nextValidTime != null) {
// generate next trigger time
ScheduleTypeEnum scheduleTypeEnum = ScheduleTypeEnum.match(jobInfo.getScheduleType(), ScheduleTypeEnum.NONE);
Date nextTriggerTime = scheduleTypeEnum.getScheduleType().generateNextTriggerTime(jobInfo, fromTime);
// refresh next trigger-time + status
if (nextTriggerTime != null) {
// generate success
jobInfo.setTriggerStatus(-1); // pass, may be Inaccurate
jobInfo.setTriggerLastTime(jobInfo.getTriggerNextTime());
jobInfo.setTriggerNextTime(nextValidTime.getTime());
jobInfo.setTriggerNextTime(nextTriggerTime.getTime());
} else {
// generateNextValidTime fail, stop job
jobInfo.setTriggerStatus(0);
// generate fail, stop job
jobInfo.setTriggerStatus(TriggerStatus.STOPPED.getValue());
jobInfo.setTriggerLastTime(0);
jobInfo.setTriggerNextTime(0);
logger.error(">>>>>>>>>>> xxl-job, refreshNextValidTime fail for job: jobId={}, scheduleType={}, scheduleConf={}",
jobInfo.getId(), jobInfo.getScheduleType(), jobInfo.getScheduleConf());
}
} catch (Throwable e) {
// generateNextValidTime error, stop job
jobInfo.setTriggerStatus(0);
// generate error, stop job
jobInfo.setTriggerStatus(TriggerStatus.STOPPED.getValue());
jobInfo.setTriggerLastTime(0);
jobInfo.setTriggerNextTime(0);
@ -294,19 +290,27 @@ public class JobScheduleHelper {
}
}
/**
* push time ring
*
* @param ringSecond ring second
* @param jobId job id
*/
private void pushTimeRing(int ringSecond, int jobId){
// push async ring
List<Integer> ringItemData = ringData.get(ringSecond);
if (ringItemData == null) {
ringItemData = new ArrayList<Integer>();
ringData.put(ringSecond, ringItemData);
}
ringItemData.add(jobId);
logger.debug(">>>>>>>>>>> xxl-job, schedule push time-ring : " + ringSecond + " = " + Arrays.asList(ringItemData) );
// get ringItemData, init when not exists
List<Integer> ringItemList = ringData.computeIfAbsent(
ringSecond,
k -> new ArrayList<>());
// push async rind
ringItemList.add(jobId);
logger.debug(">>>>>>>>>>> xxl-job, schedule push time-ring : " + ringSecond + " = " + List.of(ringItemList));
}
public void toStop(){
/**
* stop
*/
public void stop(){
// 1、stop schedule
scheduleThreadToStop = true;
@ -325,12 +329,12 @@ public class JobScheduleHelper {
}
}
// if has ring data
// if has ring data, wait for elegent shutdown
boolean hasRingData = false;
if (!ringData.isEmpty()) {
if (MapTool.isNotEmpty(ringData)) {
for (int second : ringData.keySet()) {
List<Integer> tmpData = ringData.get(second);
if (tmpData!=null && tmpData.size()>0) {
List<Integer> ringItemList = ringData.get(second);
if (CollectionTool.isNotEmpty(ringItemList)) {
hasRingData = true;
break;
}
@ -338,7 +342,7 @@ public class JobScheduleHelper {
}
if (hasRingData) {
try {
TimeUnit.SECONDS.sleep(8);
TimeUnit.SECONDS.sleep(ELEGANT_SHUTDOWN_WAITING_SECONDS);
} catch (Throwable e) {
logger.error(e.getMessage(), e);
}
@ -364,17 +368,4 @@ public class JobScheduleHelper {
logger.info(">>>>>>>>>>> xxl-job, JobScheduleHelper stop");
}
// ---------------------- tools ----------------------
public static Date generateNextValidTime(XxlJobInfo jobInfo, Date fromTime) throws Exception {
ScheduleTypeEnum scheduleTypeEnum = ScheduleTypeEnum.match(jobInfo.getScheduleType(), null);
if (ScheduleTypeEnum.CRON == scheduleTypeEnum) {
Date nextValidTime = new CronExpression(jobInfo.getScheduleConf()).getNextValidTimeAfter(fromTime);
return nextValidTime;
} else if (ScheduleTypeEnum.FIX_RATE == scheduleTypeEnum /*|| ScheduleTypeEnum.FIX_DELAY == scheduleTypeEnum*/) {
return new Date(fromTime.getTime() + Integer.valueOf(jobInfo.getScheduleConf())*1000 );
}
return null;
}
}

@ -1,8 +1,7 @@
package com.xxl.job.admin.core.thread;
package com.xxl.job.admin.scheduler.thread;
import com.xxl.job.admin.core.conf.XxlJobAdminConfig;
import com.xxl.job.admin.core.trigger.TriggerTypeEnum;
import com.xxl.job.admin.core.trigger.XxlJobTrigger;
import com.xxl.job.admin.scheduler.config.XxlJobAdminBootstrap;
import com.xxl.job.admin.scheduler.trigger.TriggerTypeEnum;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -15,7 +14,7 @@ import java.util.concurrent.atomic.AtomicInteger;
* @author xuxueli 2018-07-03 21:08:07
*/
public class JobTriggerPoolHelper {
private static Logger logger = LoggerFactory.getLogger(JobTriggerPoolHelper.class);
private static final Logger logger = LoggerFactory.getLogger(JobTriggerPoolHelper.class);
// ---------------------- trigger pool ----------------------
@ -24,10 +23,13 @@ public class JobTriggerPoolHelper {
private ThreadPoolExecutor fastTriggerPool = null;
private ThreadPoolExecutor slowTriggerPool = null;
/**
* start
*/
public void start(){
fastTriggerPool = new ThreadPoolExecutor(
10,
XxlJobAdminConfig.getAdminConfig().getTriggerPoolFastMax(),
XxlJobAdminBootstrap.getInstance().getTriggerPoolFastMax(),
60L,
TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>(2000),
@ -46,7 +48,7 @@ public class JobTriggerPoolHelper {
slowTriggerPool = new ThreadPoolExecutor(
10,
XxlJobAdminConfig.getAdminConfig().getTriggerPoolSlowMax(),
XxlJobAdminBootstrap.getInstance().getTriggerPoolSlowMax(),
60L,
TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>(5000),
@ -64,7 +66,9 @@ public class JobTriggerPoolHelper {
});
}
/**
* stop
*/
public void stop() {
//triggerPool.shutdown();
fastTriggerPool.shutdownNow();
@ -78,15 +82,27 @@ public class JobTriggerPoolHelper {
private volatile ConcurrentMap<Integer, AtomicInteger> jobTimeoutCountMap = new ConcurrentHashMap<>();
// ---------------------- tool ----------------------
/**
* add trigger
* trigger job
*
* @param jobId
* @param triggerType
* @param failRetryCount
* >=0: use this param
* <0: use param from job info config
* @param executorShardingParam
* @param executorParam
* null: use job param
* not null: cover job param
*/
public void addTrigger(final int jobId,
final TriggerTypeEnum triggerType,
final int failRetryCount,
final String executorShardingParam,
final String executorParam,
final String addressList) {
public void trigger(final int jobId,
final TriggerTypeEnum triggerType,
final int failRetryCount,
final String executorShardingParam,
final String executorParam,
final String addressList) {
// choose thread pool
ThreadPoolExecutor triggerPool_ = fastTriggerPool;
@ -104,7 +120,7 @@ public class JobTriggerPoolHelper {
try {
// do trigger
XxlJobTrigger.trigger(jobId, triggerType, failRetryCount, executorShardingParam, executorParam, addressList);
XxlJobAdminBootstrap.getInstance().getJobTrigger().trigger(jobId, triggerType, failRetryCount, executorShardingParam, executorParam, addressList);
} catch (Throwable e) {
logger.error(e.getMessage(), e);
} finally {
@ -135,32 +151,4 @@ public class JobTriggerPoolHelper {
});
}
// ---------------------- helper ----------------------
private static JobTriggerPoolHelper helper = new JobTriggerPoolHelper();
public static void toStart() {
helper.start();
}
public static void toStop() {
helper.stop();
}
/**
* @param jobId
* @param triggerType
* @param failRetryCount
* >=0: use this param
* <0: use param from job info config
* @param executorShardingParam
* @param executorParam
* null: use job param
* not null: cover job param
*/
public static void trigger(int jobId, TriggerTypeEnum triggerType, int failRetryCount, String executorShardingParam, String executorParam, String addressList) {
helper.addTrigger(jobId, triggerType, failRetryCount, executorShardingParam, executorParam, addressList);
}
}

@ -1,29 +1,46 @@
package com.xxl.job.admin.core.trigger;
import com.xxl.job.admin.core.conf.XxlJobAdminConfig;
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.route.ExecutorRouteStrategyEnum;
import com.xxl.job.admin.core.scheduler.XxlJobScheduler;
import com.xxl.job.admin.core.util.I18nUtil;
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 com.xxl.job.core.util.IpUtil;
import com.xxl.job.core.util.ThrowableUtil;
package com.xxl.job.admin.scheduler.trigger;
import com.xxl.job.admin.mapper.XxlJobGroupMapper;
import com.xxl.job.admin.mapper.XxlJobInfoMapper;
import com.xxl.job.admin.mapper.XxlJobLogMapper;
import com.xxl.job.admin.model.XxlJobGroup;
import com.xxl.job.admin.model.XxlJobInfo;
import com.xxl.job.admin.model.XxlJobLog;
import com.xxl.job.admin.scheduler.config.XxlJobAdminBootstrap;
import com.xxl.job.admin.scheduler.route.ExecutorRouteStrategyEnum;
import com.xxl.job.admin.util.I18nUtil;
import com.xxl.job.core.constant.ExecutorBlockStrategyEnum;
import com.xxl.job.core.context.XxlJobContext;
import com.xxl.job.core.openapi.ExecutorBiz;
import com.xxl.job.core.openapi.model.TriggerRequest;
import com.xxl.tool.core.StringTool;
import com.xxl.tool.error.ThrowableTool;
import com.xxl.tool.http.IPTool;
import com.xxl.tool.response.Response;
import jakarta.annotation.Resource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import java.util.Date;
/**
* xxl-job trigger
* Created by xuxueli on 17/7/13.
*
* @author xuxueli 17/7/13.
*/
public class XxlJobTrigger {
private static Logger logger = LoggerFactory.getLogger(XxlJobTrigger.class);
@Component
public class JobTrigger {
private static final Logger logger = LoggerFactory.getLogger(JobTrigger.class);
@Resource
private XxlJobInfoMapper xxlJobInfoMapper;
@Resource
private XxlJobGroupMapper xxlJobGroupMapper;
@Resource
private XxlJobLogMapper xxlJobLogMapper;
/**
* trigger job
@ -34,6 +51,8 @@ public class XxlJobTrigger {
* >=0: use this param
* <0: use param from job info config
* @param executorShardingParam
* null: new sharding, all nodes
* not null: for retry, only one node
* @param executorParam
* null: use job param
* not null: cover job param
@ -41,7 +60,7 @@ public class XxlJobTrigger {
* null: use executor addressList
* not null: cover
*/
public static void trigger(int jobId,
public void trigger(int jobId,
TriggerTypeEnum triggerType,
int failRetryCount,
String executorShardingParam,
@ -49,7 +68,7 @@ public class XxlJobTrigger {
String addressList) {
// load data
XxlJobInfo jobInfo = XxlJobAdminConfig.getAdminConfig().getXxlJobInfoDao().loadById(jobId);
XxlJobInfo jobInfo = xxlJobInfoMapper.loadById(jobId);
if (jobInfo == null) {
logger.warn(">>>>>>>>>>>> trigger fail, jobId invalidjobId={}", jobId);
return;
@ -58,57 +77,67 @@ public class XxlJobTrigger {
jobInfo.setExecutorParam(executorParam);
}
int finalFailRetryCount = failRetryCount>=0?failRetryCount:jobInfo.getExecutorFailRetryCount();
XxlJobGroup group = XxlJobAdminConfig.getAdminConfig().getXxlJobGroupDao().load(jobInfo.getJobGroup());
XxlJobGroup group = xxlJobGroupMapper.load(jobInfo.getJobGroup());
// cover addressList
if (addressList!=null && addressList.trim().length()>0) {
if (StringTool.isNotBlank(addressList)) {
group.setAddressType(1);
group.setAddressList(addressList.trim());
}
// sharding param
int[] shardingParam = null;
Date triggerTime = new Date();
if (executorShardingParam!=null){
String[] shardingArr = executorShardingParam.split("/");
if (shardingArr.length==2 && isNumeric(shardingArr[0]) && isNumeric(shardingArr[1])) {
if (shardingArr.length==2 && StringTool.isNumeric(shardingArr[0]) && StringTool.isNumeric(shardingArr[1])) {
shardingParam = new int[2];
shardingParam[0] = Integer.valueOf(shardingArr[0]);
shardingParam[1] = Integer.valueOf(shardingArr[1]);
shardingParam[0] = Integer.parseInt(shardingArr[0]);
shardingParam[1] = Integer.parseInt(shardingArr[1]);
}
}
if (ExecutorRouteStrategyEnum.SHARDING_BROADCAST==ExecutorRouteStrategyEnum.match(jobInfo.getExecutorRouteStrategy(), null)
&& group.getRegistryList()!=null && !group.getRegistryList().isEmpty()
&& shardingParam==null) {
for (int i = 0; i < group.getRegistryList().size(); i++) {
processTrigger(group, jobInfo, finalFailRetryCount, triggerType, i, group.getRegistryList().size());
processTrigger(group, jobInfo, finalFailRetryCount, triggerType, triggerTime, i, group.getRegistryList().size());
}
} else {
if (shardingParam == null) {
shardingParam = new int[]{0, 1};
}
processTrigger(group, jobInfo, finalFailRetryCount, triggerType, shardingParam[0], shardingParam[1]);
processTrigger(group, jobInfo, finalFailRetryCount, triggerType, triggerTime, shardingParam[0], shardingParam[1]);
}
}
private static boolean isNumeric(String str){
/*private static boolean isNumeric(String str){
try {
int result = Integer.valueOf(str);
return true;
} catch (NumberFormatException e) {
return false;
}
}
}*/
/**
* process trigger with log
*
* @param group job group, registry list may be empty
* @param jobInfo
* @param finalFailRetryCount
* @param triggerType
* @param jobInfo job info
* @param finalFailRetryCount the fail-retry count
* @param triggerType trigger type
* @param triggerTime trigger time
* @param index sharding index
* @param total sharding index
*/
private static void processTrigger(XxlJobGroup group, XxlJobInfo jobInfo, int finalFailRetryCount, TriggerTypeEnum triggerType, int index, int total){
private void processTrigger(XxlJobGroup group,
XxlJobInfo jobInfo,
int finalFailRetryCount,
TriggerTypeEnum triggerType,
Date triggerTime,
int index,
int total){
// param
ExecutorBlockStrategyEnum blockStrategy = ExecutorBlockStrategyEnum.match(jobInfo.getExecutorBlockStrategy(), ExecutorBlockStrategyEnum.SERIAL_EXECUTION); // block strategy
@ -119,12 +148,12 @@ public class XxlJobTrigger {
XxlJobLog jobLog = new XxlJobLog();
jobLog.setJobGroup(jobInfo.getJobGroup());
jobLog.setJobId(jobInfo.getId());
jobLog.setTriggerTime(new Date());
XxlJobAdminConfig.getAdminConfig().getXxlJobLogDao().save(jobLog);
logger.debug(">>>>>>>>>>> xxl-job trigger start, jobId:{}", jobLog.getId());
jobLog.setTriggerTime(triggerTime);
xxlJobLogMapper.save(jobLog);
logger.debug(">>>>>>>>>>> xxl-job trigger start, jobId:{}", jobLog.getJobId());
// 2、init trigger-param
TriggerParam triggerParam = new TriggerParam();
TriggerRequest triggerParam = new TriggerRequest();
triggerParam.setJobId(jobInfo.getId());
triggerParam.setExecutorHandler(jobInfo.getExecutorHandler());
triggerParam.setExecutorParams(jobInfo.getExecutorParam());
@ -140,7 +169,7 @@ public class XxlJobTrigger {
// 3、init address
String address = null;
ReturnT<String> routeAddressResult = null;
Response<String> routeAddressResult = null;
if (group.getRegistryList()!=null && !group.getRegistryList().isEmpty()) {
if (ExecutorRouteStrategyEnum.SHARDING_BROADCAST == executorRouteStrategyEnum) {
if (index < group.getRegistryList().size()) {
@ -150,39 +179,60 @@ public class XxlJobTrigger {
}
} else {
routeAddressResult = executorRouteStrategyEnum.getRouter().route(triggerParam, group.getRegistryList());
if (routeAddressResult.getCode() == ReturnT.SUCCESS_CODE) {
address = routeAddressResult.getContent();
if (routeAddressResult.isSuccess()) {
address = routeAddressResult.getData();
}
}
} else {
routeAddressResult = new ReturnT<String>(ReturnT.FAIL_CODE, I18nUtil.getString("jobconf_trigger_address_empty"));
routeAddressResult = Response.of(XxlJobContext.HANDLE_CODE_FAIL, I18nUtil.getString("jobconf_trigger_address_empty"));
}
// 4、trigger remote executor
ReturnT<String> triggerResult = null;
Response<String> triggerResult = null;
if (address != null) {
triggerResult = runExecutor(triggerParam, address);
triggerResult = doTrigger(triggerParam, address);
} else {
triggerResult = new ReturnT<String>(ReturnT.FAIL_CODE, null);
triggerResult = Response.of(XxlJobContext.HANDLE_CODE_FAIL, "Address Router Fail.");
}
// 5、collection trigger info
StringBuffer triggerMsgSb = new StringBuffer();
// trigger config
StringBuilder triggerMsgSb = new StringBuilder();
triggerMsgSb.append(I18nUtil.getString("jobconf_trigger_type")).append("").append(triggerType.getTitle());
triggerMsgSb.append("<br>").append(I18nUtil.getString("jobconf_trigger_admin_adress")).append("").append(IpUtil.getIp());
triggerMsgSb.append("<br>").append(I18nUtil.getString("jobconf_trigger_admin_adress")).append("").append(IPTool.getIp());
triggerMsgSb.append("<br>").append(I18nUtil.getString("jobconf_trigger_exe_regtype")).append("")
.append( (group.getAddressType() == 0)?I18nUtil.getString("jobgroup_field_addressType_0"):I18nUtil.getString("jobgroup_field_addressType_1") );
triggerMsgSb.append("<br>").append(I18nUtil.getString("jobconf_trigger_exe_regaddress")).append("").append(group.getRegistryList());
triggerMsgSb.append("<br>").append(I18nUtil.getString("jobinfo_field_executorRouteStrategy")).append("").append(executorRouteStrategyEnum.getTitle());
if (shardingParam != null) {
triggerMsgSb.append("("+shardingParam+")");
triggerMsgSb.append("(").append(shardingParam).append(")");
}
triggerMsgSb.append("<br>").append(I18nUtil.getString("jobinfo_field_executorBlockStrategy")).append("").append(blockStrategy.getTitle());
triggerMsgSb.append("<br>").append(I18nUtil.getString("jobinfo_field_timeout")).append("").append(jobInfo.getExecutorTimeout());
triggerMsgSb.append("<br>").append(I18nUtil.getString("jobinfo_field_executorFailRetryCount")).append("").append(finalFailRetryCount);
triggerMsgSb.append("<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>"+ I18nUtil.getString("jobconf_trigger_run") +"<<<<<<<<<<< </span><br>")
.append((routeAddressResult!=null&&routeAddressResult.getMsg()!=null)?routeAddressResult.getMsg()+"<br><br>":"").append(triggerResult.getMsg()!=null?triggerResult.getMsg():"");
// trigger data
triggerMsgSb.append("<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>").append(I18nUtil.getString("jobconf_trigger_run")).append("<<<<<<<<<<< </span><br>");
triggerMsgSb.append("<br>").append(I18nUtil.getString("joblog_field_executorAddress")).append("");
if (StringTool.isNotBlank(address)) {
triggerMsgSb.append(address);
} else if (routeAddressResult!=null && !routeAddressResult.isSuccess() && routeAddressResult.getMsg()!=null) {
triggerMsgSb.append("address route fail, ").append(routeAddressResult.getMsg());
} else {
triggerMsgSb.append("address route fail.");
}
if (StringTool.isNotBlank(jobInfo.getExecutorHandler())) {
triggerMsgSb.append("<br>").append("JobHandler").append("").append(jobInfo.getExecutorHandler());
}
triggerMsgSb.append("<br>").append(I18nUtil.getString("jobinfo_field_executorparam")).append("").append(jobInfo.getExecutorParam());
triggerMsgSb.append("<br>").append(I18nUtil.getString("joblog_field_triggerMsg")).append("");
if (triggerResult.isSuccess()) {
triggerMsgSb.append("success");
} else if (triggerResult.getMsg()!=null) {
triggerMsgSb.append("error, ").append(triggerResult.getMsg());
} else {
triggerMsgSb.append("fail");
}
// 6、save log trigger-info
jobLog.setExecutorAddress(address);
@ -193,34 +243,39 @@ public class XxlJobTrigger {
//jobLog.setTriggerTime();
jobLog.setTriggerCode(triggerResult.getCode());
jobLog.setTriggerMsg(triggerMsgSb.toString());
XxlJobAdminConfig.getAdminConfig().getXxlJobLogDao().updateTriggerInfo(jobLog);
xxlJobLogMapper.updateTriggerInfo(jobLog);
logger.debug(">>>>>>>>>>> xxl-job trigger end, jobId:{}", jobLog.getId());
logger.debug(">>>>>>>>>>> xxl-job trigger end, jobId:{}", jobLog.getJobId());
}
/**
* run executor
* @param triggerParam
* @param address
* @return
* do trigger with address
*
* @param triggerParam trigger param
* @param address the address
* @return return
*/
public static ReturnT<String> runExecutor(TriggerParam triggerParam, String address){
ReturnT<String> runResult = null;
private Response<String> doTrigger(TriggerRequest triggerParam, String address){
try {
ExecutorBiz executorBiz = XxlJobScheduler.getExecutorBiz(address);
runResult = executorBiz.run(triggerParam);
// build client
ExecutorBiz executorBiz = XxlJobAdminBootstrap.getExecutorBiz(address);
// invoke
Response<String> runResult = executorBiz.run(triggerParam);
// build result
StringBuffer runResultSB = new StringBuffer(I18nUtil.getString("jobconf_trigger_run") + "");
runResultSB.append("<br>address").append(address);
runResultSB.append("<br>code").append(runResult.getCode());
runResultSB.append("<br>msg").append(runResult.getMsg());
// return
runResult.setMsg(runResultSB.toString());
return runResult;
} catch (Exception e) {
logger.error(">>>>>>>>>>> xxl-job trigger error, please check if the executor[{}] is running.", address, e);
runResult = new ReturnT<String>(ReturnT.FAIL_CODE, ThrowableUtil.toString(e));
return Response.of(XxlJobContext.HANDLE_CODE_FAIL, ThrowableTool.toString(e));
}
StringBuffer runResultSB = new StringBuffer(I18nUtil.getString("jobconf_trigger_run") + "");
runResultSB.append("<br>address").append(address);
runResultSB.append("<br>code").append(runResult.getCode());
runResultSB.append("<br>msg").append(runResult.getMsg());
runResult.setMsg(runResultSB.toString());
return runResult;
}
}

@ -1,6 +1,6 @@
package com.xxl.job.admin.core.trigger;
package com.xxl.job.admin.scheduler.trigger;
import com.xxl.job.admin.core.util.I18nUtil;
import com.xxl.job.admin.util.I18nUtil;
/**
* trigger type enum

@ -0,0 +1,22 @@
package com.xxl.job.admin.scheduler.type;
import com.xxl.job.admin.model.XxlJobInfo;
import java.util.Date;
/**
* Schedule Type
*
* @author xuxueli 2020-10-29
*/
public abstract class ScheduleType {
/**
* generate next trigger time
*
* @param jobInfo job info
* @param fromTime from time
*/
public abstract Date generateNextTriggerTime(XxlJobInfo jobInfo, Date fromTime) throws Exception;
}

@ -0,0 +1,62 @@
package com.xxl.job.admin.scheduler.type;
import com.xxl.job.admin.scheduler.type.strategy.CronScheduleType;
import com.xxl.job.admin.scheduler.type.strategy.FixRateScheduleType;
import com.xxl.job.admin.scheduler.type.strategy.NoneScheduleType;
import com.xxl.job.admin.util.I18nUtil;
/**
* @author xuxueli 2020-10-29 21:11:23
*/
public enum ScheduleTypeEnum {
NONE(I18nUtil.getString("schedule_type_none"), new NoneScheduleType()),
/**
* schedule by cron
*/
CRON(I18nUtil.getString("schedule_type_cron"), new CronScheduleType()),
/**
* schedule by fixed rate (in seconds)
*/
FIX_RATE(I18nUtil.getString("schedule_type_fix_rate"), new FixRateScheduleType()),
/**
* schedule by fix delay (in seconds) after the last time
*/
/*FIX_DELAY(I18nUtil.getString("schedule_type_fix_delay"))*/;
private final String title;
private final ScheduleType scheduleType;;
ScheduleTypeEnum(String title, ScheduleType scheduleType) {
this.title = title;
this.scheduleType = scheduleType;
}
public String getTitle() {
return title;
}
public ScheduleType getScheduleType() {
return scheduleType;
}
/**
* match by name
*
* @param name name of ScheduleTypeEnum
* @param defaultItem default item
* @return ScheduleTypeEnum
*/
public static ScheduleTypeEnum match(String name, ScheduleTypeEnum defaultItem){
for (ScheduleTypeEnum item: ScheduleTypeEnum.values()) {
if (item.name().equals(name)) {
return item;
}
}
return defaultItem;
}
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save