From aa74bd741ed624f491ba4279346a8230b06f40d4 Mon Sep 17 00:00:00 2001
From: wangmingwei
Date: Tue, 21 Apr 2026 17:18:06 +0800
Subject: [PATCH] =?UTF-8?q?=E5=88=9D=E5=A7=8B=E4=BB=A3=E7=A0=81?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.gitignore | 35 +
README.md | 240 +
pom.xml | 37 +
xxl-job-admin/Dockerfile | 22 +
xxl-job-admin/pom.xml | 185 +
.../xxl/job/admin/XxlJobAdminApplication.java | 20 +
.../xxl/job/admin/config/XxlJobListener.java | 35 +
.../job/admin/controller/IndexController.java | 97 +
.../admin/controller/JobApiController.java | 109 +
.../admin/controller/JobCodeController.java | 103 +
.../admin/controller/JobGroupController.java | 205 +
.../admin/controller/JobInfoController.java | 156 +
.../admin/controller/JobLogController.java | 259 +
.../admin/controller/JobUserController.java | 188 +
.../annotation/PermissionLimit.java | 30 +
.../interceptor/CookieInterceptor.java | 41 +
.../interceptor/PermissionInterceptor.java | 121 +
.../controller/interceptor/WebMvcConfig.java | 29 +
.../resolver/WebExceptionResolver.java | 67 +
.../controller/rest/HandlerController.java | 34 +
.../admin/controller/rest/LogController.java | 55 +
.../rest/ScheduleTaskController.java | 132 +
.../controller/rest/XxlJobInfoController.java | 24 +
.../xxl/job/admin/core/alarm/JobAlarm.java | 21 +
.../xxl/job/admin/core/alarm/JobAlarmer.java | 67 +
.../admin/core/alarm/impl/EmailJobAlarm.java | 120 +
.../admin/core/complete/XxlJobCompleter.java | 107 +
.../admin/core/conf/XxlJobAdminConfig.java | 172 +
.../job/admin/core/cron/CronExpression.java | 1652 +++
.../admin/core/exception/XxlJobException.java | 15 +
.../job/admin/core/model/XxlJobLogGlue.java | 34 +
.../job/admin/core/model/XxlJobLogReport.java | 35 +
.../job/admin/core/model/XxlJobRegistry.java | 30 +
.../xxl/job/admin/core/model/XxlJobUser.java | 46 +
.../core/route/ExecutorRouteStrategyEnum.java | 49 +
.../job/admin/core/route/ExecutorRouter.java | 25 +
.../route/strategy/ExecutorRouteBusyover.java | 49 +
.../strategy/ExecutorRouteConsistentHash.java | 86 +
.../route/strategy/ExecutorRouteFailover.java | 49 +
.../route/strategy/ExecutorRouteFirst.java | 20 +
.../core/route/strategy/ExecutorRouteLFU.java | 78 +
.../core/route/strategy/ExecutorRouteLRU.java | 77 +
.../route/strategy/ExecutorRouteLast.java | 20 +
.../route/strategy/ExecutorRouteRandom.java | 24 +
.../route/strategy/ExecutorRouteRound.java | 47 +
.../core/scheduler/MisfireStrategyEnum.java | 40 +
.../core/scheduler/ScheduleTypeEnum.java | 47 +
.../admin/core/scheduler/XxlJobScheduler.java | 102 +
.../admin/core/thread/JobCompleteHelper.java | 186 +
.../core/thread/JobFailMonitorHelper.java | 112 +
.../admin/core/thread/JobLogReportHelper.java | 153 +
.../admin/core/thread/JobRegistryHelper.java | 242 +
.../admin/core/thread/JobScheduleHelper.java | 426 +
.../core/thread/JobTriggerPoolHelper.java | 151 +
.../admin/core/trigger/TriggerTypeEnum.java | 28 +
.../job/admin/core/trigger/XxlJobTrigger.java | 284 +
.../xxl/job/admin/core/util/CookieUtil.java | 99 +
.../com/xxl/job/admin/core/util/FtlUtil.java | 32 +
.../com/xxl/job/admin/core/util/I18nUtil.java | 80 +
.../xxl/job/admin/core/util/JacksonUtil.java | 93 +
.../job/admin/core/util/LocalCacheUtil.java | 118 +
.../com/xxl/job/admin/dao/XxlJobGroupDao.java | 11 +
.../com/xxl/job/admin/dao/XxlJobInfoDao.java | 13 +
.../com/xxl/job/admin/dao/XxlJobLogDao.java | 29 +
.../xxl/job/admin/dao/XxlJobLogGlueDao.java | 13 +
.../xxl/job/admin/dao/XxlJobLogReportDao.java | 15 +
.../xxl/job/admin/dao/XxlJobRegistryDao.java | 12 +
.../com/xxl/job/admin/dao/XxlJobUserDao.java | 12 +
.../job/admin/mapper/HandlerNameMapper.java | 16 +
.../xxl/job/admin/mapper/TimeTaskMapper.java | 17 +
.../job/admin/service/HandlerNameService.java | 56 +
.../job/admin/service/TimetaskService.java | 90 +
.../job/admin/service/XxlJobGroupService.java | 49 +
.../job/admin/service/XxlJobInfoService.java | 59 +
.../admin/service/XxlJobLogGlueService.java | 18 +
.../admin/service/XxlJobLogReportService.java | 20 +
.../job/admin/service/XxlJobLogService.java | 88 +
.../admin/service/XxlJobRegistryService.java | 33 +
.../xxl/job/admin/service/XxlJobService.java | 99 +
.../job/admin/service/XxlJobUserService.java | 29 +
.../job/admin/service/impl/AdminBizImpl.java | 42 +
.../service/impl/HandlerNameServiceImpl.java | 56 +
.../service/impl/TimetaskServiceImpl.java | 276 +
.../service/impl/XxlJobGroupServiceImpl.java | 108 +
.../service/impl/XxlJobInfoServiceImpl.java | 133 +
.../impl/XxlJobLogGlueServiceImpl.java | 53 +
.../impl/XxlJobLogReportServiceImpl.java | 45 +
.../service/impl/XxlJobLogServiceImpl.java | 287 +
.../service/impl/XxlJobLoginService.java | 112 +
.../impl/XxlJobRegistryServiceImpl.java | 76 +
.../admin/service/impl/XxlJobServiceImpl.java | 481 +
.../service/impl/XxlJobUserServiceImpl.java | 76 +
.../src/main/resources/application-dev.yml | 48 +
.../main/resources/application-preview.yml | 48 +
.../src/main/resources/application-prod.yml | 48 +
.../src/main/resources/application-test.yml | 0
.../src/main/resources/application.yml | 70 +
.../src/main/resources/bootstrap.yml | 44 +
.../main/resources/i18n/message_en.properties | 277 +
.../resources/i18n/message_zh_CN.properties | 277 +
.../resources/i18n/message_zh_TC.properties | 277 +
.../src/main/resources/logback-spring.xml | 286 +
.../mybatis-mapper/XxlJobGroupMapper.xml | 6 +
.../mybatis-mapper/XxlJobInfoMapper.xml | 6 +
.../mybatis-mapper/XxlJobLogGlueMapper.xml | 26 +
.../mybatis-mapper/XxlJobLogMapper.xml | 136 +
.../mybatis-mapper/XxlJobLogReportMapper.xml | 30 +
.../mybatis-mapper/XxlJobRegistryMapper.xml | 22 +
.../mybatis-mapper/XxlJobUserMapper.xml | 22 +
.../Ionicons/css/ionicons.min.css | 11 +
.../Ionicons/fonts/ionicons.eot | Bin 0 -> 120724 bytes
.../Ionicons/fonts/ionicons.svg | 2230 ++++
.../Ionicons/fonts/ionicons.ttf | Bin 0 -> 188508 bytes
.../Ionicons/fonts/ionicons.woff | Bin 0 -> 67904 bytes
.../bower_components/PACE/pace.min.js | 2 +
.../PACE/themes/blue/pace-theme-flash.css | 77 +
.../daterangepicker.css | 269 +
.../daterangepicker.js | 1653 +++
.../bootstrap/css/bootstrap.min.css | 6 +
.../bootstrap/css/bootstrap.min.css.map | 1 +
.../fonts/glyphicons-halflings-regular.eot | Bin 0 -> 20127 bytes
.../fonts/glyphicons-halflings-regular.svg | 288 +
.../fonts/glyphicons-halflings-regular.ttf | Bin 0 -> 45404 bytes
.../fonts/glyphicons-halflings-regular.woff | Bin 0 -> 23424 bytes
.../fonts/glyphicons-halflings-regular.woff2 | Bin 0 -> 18028 bytes
.../bootstrap/js/bootstrap.min.js | 6 +
.../css/dataTables.bootstrap.min.css | 1 +
.../js/dataTables.bootstrap.min.js | 8 +
.../js/jquery.dataTables.min.js | 166 +
.../bower_components/fastclick/fastclick.js | 841 ++
.../font-awesome/css/font-awesome.css.map | 7 +
.../font-awesome/css/font-awesome.min.css | 4 +
.../font-awesome/fonts/FontAwesome.otf | Bin 0 -> 134808 bytes
.../fonts/fontawesome-webfont.eot | Bin 0 -> 165742 bytes
.../fonts/fontawesome-webfont.svg | 2671 +++++
.../fonts/fontawesome-webfont.ttf | Bin 0 -> 165548 bytes
.../fonts/fontawesome-webfont.woff | Bin 0 -> 98024 bytes
.../fonts/fontawesome-webfont.woff2 | Bin 0 -> 77160 bytes
.../jquery.slimscroll.min.js | 16 +
.../bower_components/jquery/jquery.min.js | 2 +
.../bower_components/moment/moment.min.js | 1 +
.../static/adminlte/dist/css/AdminLTE.min.css | 8 +
.../dist/css/skins/_all-skins.min.css | 1 +
.../static/adminlte/dist/js/adminlte.min.js | 13 +
.../adminlte/plugins/iCheck/icheck.min.js | 10 +
.../adminlte/plugins/iCheck/square/blue.css | 62 +
.../adminlte/plugins/iCheck/square/blue.png | Bin 0 -> 2185 bytes
.../plugins/iCheck/square/blue@2x.png | Bin 0 -> 4485 bytes
.../src/main/resources/static/favicon.ico | Bin 0 -> 4286 bytes
.../src/main/resources/static/js/common.1.js | 164 +
.../src/main/resources/static/js/index.js | 207 +
.../resources/static/js/jobcode.index.1.js | 97 +
.../resources/static/js/jobgroup.index.1.js | 370 +
.../resources/static/js/jobinfo.index.1.js | 739 ++
.../resources/static/js/joblog.detail.1.js | 89 +
.../resources/static/js/joblog.index.1.js | 396 +
.../src/main/resources/static/js/login.1.js | 66 +
.../main/resources/static/js/user.index.1.js | 328 +
.../codemirror/addon/hint/anyword-hint.js | 41 +
.../codemirror/addon/hint/show-hint.css | 36 +
.../codemirror/addon/hint/show-hint.js | 434 +
.../plugins/codemirror/lib/codemirror.css | 346 +
.../plugins/codemirror/lib/codemirror.js | 9698 +++++++++++++++++
.../plugins/codemirror/mode/clike/clike.js | 879 ++
.../codemirror/mode/javascript/javascript.js | 899 ++
.../static/plugins/codemirror/mode/php/php.js | 234 +
.../codemirror/mode/powershell/powershell.js | 398 +
.../plugins/codemirror/mode/python/python.js | 409 +
.../plugins/codemirror/mode/shell/shell.js | 152 +
.../static/plugins/cronGen/cronGen.js | 1106 ++
.../static/plugins/cronGen/cronGen_en.js | 1106 ++
.../plugins/echarts/echarts.common.min.js | 22 +
.../static/plugins/jquery/jquery.cookie.js | 117 +
.../plugins/jquery/jquery.validate.min.js | 4 +
.../resources/static/plugins/layer/layer.js | 2 +
.../plugins/layer/theme/default/icon-ext.png | Bin 0 -> 5911 bytes
.../plugins/layer/theme/default/icon.png | Bin 0 -> 11493 bytes
.../plugins/layer/theme/default/layer.css | 1 +
.../plugins/layer/theme/default/loading-0.gif | Bin 0 -> 5793 bytes
.../plugins/layer/theme/default/loading-1.gif | Bin 0 -> 701 bytes
.../plugins/layer/theme/default/loading-2.gif | Bin 0 -> 1787 bytes
.../templates/common/common.exception.ftl | 31 +
.../templates/common/common.macro.ftl | 243 +
.../src/main/resources/templates/help.ftl | 47 +
.../src/main/resources/templates/index.ftl | 147 +
.../templates/jobcode/jobcode.index.ftl | 164 +
.../templates/jobgroup/jobgroup.index.ftl | 191 +
.../templates/jobinfo/jobinfo.index.ftl | 540 +
.../templates/joblog/joblog.detail.ftl | 70 +
.../templates/joblog/joblog.index.ftl | 180 +
.../src/main/resources/templates/login.ftl | 45 +
.../resources/templates/user/user.index.ftl | 188 +
xxl-job-core/pom.xml | 104 +
.../java/com/xxl/job/core/biz/AdminBiz.java | 54 +
.../com/xxl/job/core/biz/ExecutorBiz.java | 46 +
.../job/core/biz/client/AdminBizClient.java | 73 +
.../core/biz/client/ExecutorBizClient.java | 57 +
.../job/core/biz/impl/ExecutorBizImpl.java | 173 +
.../core/biz/model/HandleCallbackParam.java | 68 +
.../xxl/job/core/biz/model/IdleBeatParam.java | 29 +
.../com/xxl/job/core/biz/model/KillParam.java | 29 +
.../com/xxl/job/core/biz/model/LogParam.java | 48 +
.../com/xxl/job/core/biz/model/LogResult.java | 57 +
.../core/biz/model/RegistryHandlerName.java | 53 +
.../xxl/job/core/biz/model/RegistryParam.java | 55 +
.../com/xxl/job/core/biz/model/ReturnT.java | 58 +
.../xxl/job/core/biz/model/TriggerParam.java | 145 +
.../xxl/job/core/context/XxlJobContext.java | 123 +
.../xxl/job/core/context/XxlJobHelper.java | 256 +
.../core/enums/ExecutorBlockStrategyEnum.java | 36 +
.../xxl/job/core/enums/RegistryConfig.java | 14 +
.../xxl/job/core/executor/XxlJobExecutor.java | 337 +
.../executor/impl/XxlJobSimpleExecutor.java | 68 +
.../executor/impl/XxlJobSpringExecutor.java | 155 +
.../com/xxl/job/core/glue/GlueFactory.java | 91 +
.../com/xxl/job/core/glue/GlueTypeEnum.java | 54 +
.../job/core/glue/impl/SpringGlueFactory.java | 82 +
.../com/xxl/job/core/handler/IJobHandler.java | 39 +
.../core/handler/annotation/JobHandler.java | 49 +
.../job/core/handler/annotation/XxlJob.java | 31 +
.../job/core/handler/impl/GlueJobHandler.java | 39 +
.../core/handler/impl/MethodJobHandler.java | 54 +
.../core/handler/impl/ScriptJobHandler.java | 94 +
.../xxl/job/core/log/XxlJobFileAppender.java | 221 +
.../com/xxl/job/core/server/EmbedServer.java | 257 +
.../core/thread/ExecutorRegistryThread.java | 130 +
.../core/thread/JobLogFileCleanThread.java | 125 +
.../com/xxl/job/core/thread/JobThread.java | 256 +
.../core/thread/TriggerCallbackThread.java | 271 +
.../java/com/xxl/job/core/util/DateUtil.java | 157 +
.../java/com/xxl/job/core/util/FileUtil.java | 182 +
.../java/com/xxl/job/core/util/GsonTool.java | 94 +
.../java/com/xxl/job/core/util/IpUtil.java | 204 +
.../xxl/job/core/util/JdkSerializeTool.java | 51 +
.../java/com/xxl/job/core/util/NetUtil.java | 71 +
.../com/xxl/job/core/util/ScriptUtil.java | 235 +
.../com/xxl/job/core/util/ShardingUtil.java | 72 +
.../com/xxl/job/core/util/ThrowableUtil.java | 25 +
.../xxl/job/core/util/XxlJobRemotingUtil.java | 167 +
yunzhupaas-scheduletask-client/pom.xml | 39 +
.../config/RegisterAddressConfig.java | 60 +
.../scheduletask/config/XxlJobConfig.java | 79 +
yunzhupaas-scheduletask-model/pom.xml | 30 +
.../entity/HandlerNameEntity.java | 38 +
.../scheduletask/entity/TimeTaskEntity.java | 148 +
.../entity/TimeTaskLogEntity.java | 51 +
.../scheduletask/entity/XxlJobGroup.java | 52 +
.../scheduletask/entity/XxlJobInfo.java | 92 +
.../scheduletask/entity/XxlJobLog.java | 61 +
.../scheduletask/model/ContentNewModel.java | 73 +
.../scheduletask/model/TaskCrForm.java | 43 +
.../scheduletask/model/TaskInfoVO.java | 23 +
.../scheduletask/model/TaskLogVO.java | 22 +
.../scheduletask/model/TaskMethodsVO.java | 35 +
.../scheduletask/model/TaskPage.java | 23 +
.../model/TaskParameterModel.java | 47 +
.../scheduletask/model/TaskUpForm.java | 17 +
.../yunzhupaas/scheduletask/model/TaskVO.java | 34 +
.../scheduletask/model/UpdateTaskModel.java | 18 +
259 files changed, 45901 insertions(+)
create mode 100644 .gitignore
create mode 100644 README.md
create mode 100644 pom.xml
create mode 100644 xxl-job-admin/Dockerfile
create mode 100644 xxl-job-admin/pom.xml
create mode 100644 xxl-job-admin/src/main/java/com/xxl/job/admin/XxlJobAdminApplication.java
create mode 100644 xxl-job-admin/src/main/java/com/xxl/job/admin/config/XxlJobListener.java
create mode 100644 xxl-job-admin/src/main/java/com/xxl/job/admin/controller/IndexController.java
create mode 100644 xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobApiController.java
create mode 100644 xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobCodeController.java
create mode 100644 xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobGroupController.java
create mode 100644 xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobInfoController.java
create mode 100644 xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobLogController.java
create mode 100644 xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobUserController.java
create mode 100644 xxl-job-admin/src/main/java/com/xxl/job/admin/controller/annotation/PermissionLimit.java
create mode 100644 xxl-job-admin/src/main/java/com/xxl/job/admin/controller/interceptor/CookieInterceptor.java
create mode 100644 xxl-job-admin/src/main/java/com/xxl/job/admin/controller/interceptor/PermissionInterceptor.java
create mode 100644 xxl-job-admin/src/main/java/com/xxl/job/admin/controller/interceptor/WebMvcConfig.java
create mode 100644 xxl-job-admin/src/main/java/com/xxl/job/admin/controller/resolver/WebExceptionResolver.java
create mode 100644 xxl-job-admin/src/main/java/com/xxl/job/admin/controller/rest/HandlerController.java
create mode 100644 xxl-job-admin/src/main/java/com/xxl/job/admin/controller/rest/LogController.java
create mode 100644 xxl-job-admin/src/main/java/com/xxl/job/admin/controller/rest/ScheduleTaskController.java
create mode 100644 xxl-job-admin/src/main/java/com/xxl/job/admin/controller/rest/XxlJobInfoController.java
create mode 100644 xxl-job-admin/src/main/java/com/xxl/job/admin/core/alarm/JobAlarm.java
create mode 100644 xxl-job-admin/src/main/java/com/xxl/job/admin/core/alarm/JobAlarmer.java
create mode 100644 xxl-job-admin/src/main/java/com/xxl/job/admin/core/alarm/impl/EmailJobAlarm.java
create mode 100644 xxl-job-admin/src/main/java/com/xxl/job/admin/core/complete/XxlJobCompleter.java
create mode 100644 xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobAdminConfig.java
create mode 100644 xxl-job-admin/src/main/java/com/xxl/job/admin/core/cron/CronExpression.java
create mode 100644 xxl-job-admin/src/main/java/com/xxl/job/admin/core/exception/XxlJobException.java
create mode 100644 xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobLogGlue.java
create mode 100644 xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobLogReport.java
create mode 100644 xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobRegistry.java
create mode 100644 xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobUser.java
create mode 100644 xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/ExecutorRouteStrategyEnum.java
create mode 100644 xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/ExecutorRouter.java
create mode 100644 xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteBusyover.java
create mode 100644 xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteConsistentHash.java
create mode 100644 xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteFailover.java
create mode 100644 xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteFirst.java
create mode 100644 xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteLFU.java
create mode 100644 xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteLRU.java
create mode 100644 xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteLast.java
create mode 100644 xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteRandom.java
create mode 100644 xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteRound.java
create mode 100644 xxl-job-admin/src/main/java/com/xxl/job/admin/core/scheduler/MisfireStrategyEnum.java
create mode 100644 xxl-job-admin/src/main/java/com/xxl/job/admin/core/scheduler/ScheduleTypeEnum.java
create mode 100644 xxl-job-admin/src/main/java/com/xxl/job/admin/core/scheduler/XxlJobScheduler.java
create mode 100644 xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobCompleteHelper.java
create mode 100644 xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobFailMonitorHelper.java
create mode 100644 xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobLogReportHelper.java
create mode 100644 xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobRegistryHelper.java
create mode 100644 xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobScheduleHelper.java
create mode 100644 xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobTriggerPoolHelper.java
create mode 100644 xxl-job-admin/src/main/java/com/xxl/job/admin/core/trigger/TriggerTypeEnum.java
create mode 100644 xxl-job-admin/src/main/java/com/xxl/job/admin/core/trigger/XxlJobTrigger.java
create mode 100644 xxl-job-admin/src/main/java/com/xxl/job/admin/core/util/CookieUtil.java
create mode 100644 xxl-job-admin/src/main/java/com/xxl/job/admin/core/util/FtlUtil.java
create mode 100644 xxl-job-admin/src/main/java/com/xxl/job/admin/core/util/I18nUtil.java
create mode 100644 xxl-job-admin/src/main/java/com/xxl/job/admin/core/util/JacksonUtil.java
create mode 100644 xxl-job-admin/src/main/java/com/xxl/job/admin/core/util/LocalCacheUtil.java
create mode 100644 xxl-job-admin/src/main/java/com/xxl/job/admin/dao/XxlJobGroupDao.java
create mode 100644 xxl-job-admin/src/main/java/com/xxl/job/admin/dao/XxlJobInfoDao.java
create mode 100644 xxl-job-admin/src/main/java/com/xxl/job/admin/dao/XxlJobLogDao.java
create mode 100644 xxl-job-admin/src/main/java/com/xxl/job/admin/dao/XxlJobLogGlueDao.java
create mode 100644 xxl-job-admin/src/main/java/com/xxl/job/admin/dao/XxlJobLogReportDao.java
create mode 100644 xxl-job-admin/src/main/java/com/xxl/job/admin/dao/XxlJobRegistryDao.java
create mode 100644 xxl-job-admin/src/main/java/com/xxl/job/admin/dao/XxlJobUserDao.java
create mode 100644 xxl-job-admin/src/main/java/com/xxl/job/admin/mapper/HandlerNameMapper.java
create mode 100644 xxl-job-admin/src/main/java/com/xxl/job/admin/mapper/TimeTaskMapper.java
create mode 100644 xxl-job-admin/src/main/java/com/xxl/job/admin/service/HandlerNameService.java
create mode 100644 xxl-job-admin/src/main/java/com/xxl/job/admin/service/TimetaskService.java
create mode 100644 xxl-job-admin/src/main/java/com/xxl/job/admin/service/XxlJobGroupService.java
create mode 100644 xxl-job-admin/src/main/java/com/xxl/job/admin/service/XxlJobInfoService.java
create mode 100644 xxl-job-admin/src/main/java/com/xxl/job/admin/service/XxlJobLogGlueService.java
create mode 100644 xxl-job-admin/src/main/java/com/xxl/job/admin/service/XxlJobLogReportService.java
create mode 100644 xxl-job-admin/src/main/java/com/xxl/job/admin/service/XxlJobLogService.java
create mode 100644 xxl-job-admin/src/main/java/com/xxl/job/admin/service/XxlJobRegistryService.java
create mode 100644 xxl-job-admin/src/main/java/com/xxl/job/admin/service/XxlJobService.java
create mode 100644 xxl-job-admin/src/main/java/com/xxl/job/admin/service/XxlJobUserService.java
create mode 100644 xxl-job-admin/src/main/java/com/xxl/job/admin/service/impl/AdminBizImpl.java
create mode 100644 xxl-job-admin/src/main/java/com/xxl/job/admin/service/impl/HandlerNameServiceImpl.java
create mode 100644 xxl-job-admin/src/main/java/com/xxl/job/admin/service/impl/TimetaskServiceImpl.java
create mode 100644 xxl-job-admin/src/main/java/com/xxl/job/admin/service/impl/XxlJobGroupServiceImpl.java
create mode 100644 xxl-job-admin/src/main/java/com/xxl/job/admin/service/impl/XxlJobInfoServiceImpl.java
create mode 100644 xxl-job-admin/src/main/java/com/xxl/job/admin/service/impl/XxlJobLogGlueServiceImpl.java
create mode 100644 xxl-job-admin/src/main/java/com/xxl/job/admin/service/impl/XxlJobLogReportServiceImpl.java
create mode 100644 xxl-job-admin/src/main/java/com/xxl/job/admin/service/impl/XxlJobLogServiceImpl.java
create mode 100644 xxl-job-admin/src/main/java/com/xxl/job/admin/service/impl/XxlJobLoginService.java
create mode 100644 xxl-job-admin/src/main/java/com/xxl/job/admin/service/impl/XxlJobRegistryServiceImpl.java
create mode 100644 xxl-job-admin/src/main/java/com/xxl/job/admin/service/impl/XxlJobServiceImpl.java
create mode 100644 xxl-job-admin/src/main/java/com/xxl/job/admin/service/impl/XxlJobUserServiceImpl.java
create mode 100644 xxl-job-admin/src/main/resources/application-dev.yml
create mode 100644 xxl-job-admin/src/main/resources/application-preview.yml
create mode 100644 xxl-job-admin/src/main/resources/application-prod.yml
rename README => xxl-job-admin/src/main/resources/application-test.yml (100%)
create mode 100644 xxl-job-admin/src/main/resources/application.yml
create mode 100644 xxl-job-admin/src/main/resources/bootstrap.yml
create mode 100644 xxl-job-admin/src/main/resources/i18n/message_en.properties
create mode 100644 xxl-job-admin/src/main/resources/i18n/message_zh_CN.properties
create mode 100644 xxl-job-admin/src/main/resources/i18n/message_zh_TC.properties
create mode 100644 xxl-job-admin/src/main/resources/logback-spring.xml
create mode 100644 xxl-job-admin/src/main/resources/mybatis-mapper/XxlJobGroupMapper.xml
create mode 100644 xxl-job-admin/src/main/resources/mybatis-mapper/XxlJobInfoMapper.xml
create mode 100644 xxl-job-admin/src/main/resources/mybatis-mapper/XxlJobLogGlueMapper.xml
create mode 100644 xxl-job-admin/src/main/resources/mybatis-mapper/XxlJobLogMapper.xml
create mode 100644 xxl-job-admin/src/main/resources/mybatis-mapper/XxlJobLogReportMapper.xml
create mode 100644 xxl-job-admin/src/main/resources/mybatis-mapper/XxlJobRegistryMapper.xml
create mode 100644 xxl-job-admin/src/main/resources/mybatis-mapper/XxlJobUserMapper.xml
create mode 100644 xxl-job-admin/src/main/resources/static/adminlte/bower_components/Ionicons/css/ionicons.min.css
create mode 100644 xxl-job-admin/src/main/resources/static/adminlte/bower_components/Ionicons/fonts/ionicons.eot
create mode 100644 xxl-job-admin/src/main/resources/static/adminlte/bower_components/Ionicons/fonts/ionicons.svg
create mode 100644 xxl-job-admin/src/main/resources/static/adminlte/bower_components/Ionicons/fonts/ionicons.ttf
create mode 100644 xxl-job-admin/src/main/resources/static/adminlte/bower_components/Ionicons/fonts/ionicons.woff
create mode 100644 xxl-job-admin/src/main/resources/static/adminlte/bower_components/PACE/pace.min.js
create mode 100644 xxl-job-admin/src/main/resources/static/adminlte/bower_components/PACE/themes/blue/pace-theme-flash.css
create mode 100644 xxl-job-admin/src/main/resources/static/adminlte/bower_components/bootstrap-daterangepicker/daterangepicker.css
create mode 100644 xxl-job-admin/src/main/resources/static/adminlte/bower_components/bootstrap-daterangepicker/daterangepicker.js
create mode 100644 xxl-job-admin/src/main/resources/static/adminlte/bower_components/bootstrap/css/bootstrap.min.css
create mode 100644 xxl-job-admin/src/main/resources/static/adminlte/bower_components/bootstrap/css/bootstrap.min.css.map
create mode 100644 xxl-job-admin/src/main/resources/static/adminlte/bower_components/bootstrap/fonts/glyphicons-halflings-regular.eot
create mode 100644 xxl-job-admin/src/main/resources/static/adminlte/bower_components/bootstrap/fonts/glyphicons-halflings-regular.svg
create mode 100644 xxl-job-admin/src/main/resources/static/adminlte/bower_components/bootstrap/fonts/glyphicons-halflings-regular.ttf
create mode 100644 xxl-job-admin/src/main/resources/static/adminlte/bower_components/bootstrap/fonts/glyphicons-halflings-regular.woff
create mode 100644 xxl-job-admin/src/main/resources/static/adminlte/bower_components/bootstrap/fonts/glyphicons-halflings-regular.woff2
create mode 100644 xxl-job-admin/src/main/resources/static/adminlte/bower_components/bootstrap/js/bootstrap.min.js
create mode 100644 xxl-job-admin/src/main/resources/static/adminlte/bower_components/datatables.net-bs/css/dataTables.bootstrap.min.css
create mode 100644 xxl-job-admin/src/main/resources/static/adminlte/bower_components/datatables.net-bs/js/dataTables.bootstrap.min.js
create mode 100644 xxl-job-admin/src/main/resources/static/adminlte/bower_components/datatables.net/js/jquery.dataTables.min.js
create mode 100644 xxl-job-admin/src/main/resources/static/adminlte/bower_components/fastclick/fastclick.js
create mode 100644 xxl-job-admin/src/main/resources/static/adminlte/bower_components/font-awesome/css/font-awesome.css.map
create mode 100644 xxl-job-admin/src/main/resources/static/adminlte/bower_components/font-awesome/css/font-awesome.min.css
create mode 100644 xxl-job-admin/src/main/resources/static/adminlte/bower_components/font-awesome/fonts/FontAwesome.otf
create mode 100644 xxl-job-admin/src/main/resources/static/adminlte/bower_components/font-awesome/fonts/fontawesome-webfont.eot
create mode 100644 xxl-job-admin/src/main/resources/static/adminlte/bower_components/font-awesome/fonts/fontawesome-webfont.svg
create mode 100644 xxl-job-admin/src/main/resources/static/adminlte/bower_components/font-awesome/fonts/fontawesome-webfont.ttf
create mode 100644 xxl-job-admin/src/main/resources/static/adminlte/bower_components/font-awesome/fonts/fontawesome-webfont.woff
create mode 100644 xxl-job-admin/src/main/resources/static/adminlte/bower_components/font-awesome/fonts/fontawesome-webfont.woff2
create mode 100644 xxl-job-admin/src/main/resources/static/adminlte/bower_components/jquery-slimscroll/jquery.slimscroll.min.js
create mode 100644 xxl-job-admin/src/main/resources/static/adminlte/bower_components/jquery/jquery.min.js
create mode 100644 xxl-job-admin/src/main/resources/static/adminlte/bower_components/moment/moment.min.js
create mode 100644 xxl-job-admin/src/main/resources/static/adminlte/dist/css/AdminLTE.min.css
create mode 100644 xxl-job-admin/src/main/resources/static/adminlte/dist/css/skins/_all-skins.min.css
create mode 100644 xxl-job-admin/src/main/resources/static/adminlte/dist/js/adminlte.min.js
create mode 100644 xxl-job-admin/src/main/resources/static/adminlte/plugins/iCheck/icheck.min.js
create mode 100644 xxl-job-admin/src/main/resources/static/adminlte/plugins/iCheck/square/blue.css
create mode 100644 xxl-job-admin/src/main/resources/static/adminlte/plugins/iCheck/square/blue.png
create mode 100644 xxl-job-admin/src/main/resources/static/adminlte/plugins/iCheck/square/blue@2x.png
create mode 100644 xxl-job-admin/src/main/resources/static/favicon.ico
create mode 100644 xxl-job-admin/src/main/resources/static/js/common.1.js
create mode 100644 xxl-job-admin/src/main/resources/static/js/index.js
create mode 100644 xxl-job-admin/src/main/resources/static/js/jobcode.index.1.js
create mode 100644 xxl-job-admin/src/main/resources/static/js/jobgroup.index.1.js
create mode 100644 xxl-job-admin/src/main/resources/static/js/jobinfo.index.1.js
create mode 100644 xxl-job-admin/src/main/resources/static/js/joblog.detail.1.js
create mode 100644 xxl-job-admin/src/main/resources/static/js/joblog.index.1.js
create mode 100644 xxl-job-admin/src/main/resources/static/js/login.1.js
create mode 100644 xxl-job-admin/src/main/resources/static/js/user.index.1.js
create mode 100644 xxl-job-admin/src/main/resources/static/plugins/codemirror/addon/hint/anyword-hint.js
create mode 100644 xxl-job-admin/src/main/resources/static/plugins/codemirror/addon/hint/show-hint.css
create mode 100644 xxl-job-admin/src/main/resources/static/plugins/codemirror/addon/hint/show-hint.js
create mode 100644 xxl-job-admin/src/main/resources/static/plugins/codemirror/lib/codemirror.css
create mode 100644 xxl-job-admin/src/main/resources/static/plugins/codemirror/lib/codemirror.js
create mode 100644 xxl-job-admin/src/main/resources/static/plugins/codemirror/mode/clike/clike.js
create mode 100644 xxl-job-admin/src/main/resources/static/plugins/codemirror/mode/javascript/javascript.js
create mode 100644 xxl-job-admin/src/main/resources/static/plugins/codemirror/mode/php/php.js
create mode 100644 xxl-job-admin/src/main/resources/static/plugins/codemirror/mode/powershell/powershell.js
create mode 100644 xxl-job-admin/src/main/resources/static/plugins/codemirror/mode/python/python.js
create mode 100644 xxl-job-admin/src/main/resources/static/plugins/codemirror/mode/shell/shell.js
create mode 100644 xxl-job-admin/src/main/resources/static/plugins/cronGen/cronGen.js
create mode 100644 xxl-job-admin/src/main/resources/static/plugins/cronGen/cronGen_en.js
create mode 100644 xxl-job-admin/src/main/resources/static/plugins/echarts/echarts.common.min.js
create mode 100644 xxl-job-admin/src/main/resources/static/plugins/jquery/jquery.cookie.js
create mode 100644 xxl-job-admin/src/main/resources/static/plugins/jquery/jquery.validate.min.js
create mode 100644 xxl-job-admin/src/main/resources/static/plugins/layer/layer.js
create mode 100644 xxl-job-admin/src/main/resources/static/plugins/layer/theme/default/icon-ext.png
create mode 100644 xxl-job-admin/src/main/resources/static/plugins/layer/theme/default/icon.png
create mode 100644 xxl-job-admin/src/main/resources/static/plugins/layer/theme/default/layer.css
create mode 100644 xxl-job-admin/src/main/resources/static/plugins/layer/theme/default/loading-0.gif
create mode 100644 xxl-job-admin/src/main/resources/static/plugins/layer/theme/default/loading-1.gif
create mode 100644 xxl-job-admin/src/main/resources/static/plugins/layer/theme/default/loading-2.gif
create mode 100644 xxl-job-admin/src/main/resources/templates/common/common.exception.ftl
create mode 100644 xxl-job-admin/src/main/resources/templates/common/common.macro.ftl
create mode 100644 xxl-job-admin/src/main/resources/templates/help.ftl
create mode 100644 xxl-job-admin/src/main/resources/templates/index.ftl
create mode 100644 xxl-job-admin/src/main/resources/templates/jobcode/jobcode.index.ftl
create mode 100644 xxl-job-admin/src/main/resources/templates/jobgroup/jobgroup.index.ftl
create mode 100644 xxl-job-admin/src/main/resources/templates/jobinfo/jobinfo.index.ftl
create mode 100644 xxl-job-admin/src/main/resources/templates/joblog/joblog.detail.ftl
create mode 100644 xxl-job-admin/src/main/resources/templates/joblog/joblog.index.ftl
create mode 100644 xxl-job-admin/src/main/resources/templates/login.ftl
create mode 100644 xxl-job-admin/src/main/resources/templates/user/user.index.ftl
create mode 100644 xxl-job-core/pom.xml
create mode 100644 xxl-job-core/src/main/java/com/xxl/job/core/biz/AdminBiz.java
create mode 100644 xxl-job-core/src/main/java/com/xxl/job/core/biz/ExecutorBiz.java
create mode 100644 xxl-job-core/src/main/java/com/xxl/job/core/biz/client/AdminBizClient.java
create mode 100644 xxl-job-core/src/main/java/com/xxl/job/core/biz/client/ExecutorBizClient.java
create mode 100644 xxl-job-core/src/main/java/com/xxl/job/core/biz/impl/ExecutorBizImpl.java
create mode 100644 xxl-job-core/src/main/java/com/xxl/job/core/biz/model/HandleCallbackParam.java
create mode 100644 xxl-job-core/src/main/java/com/xxl/job/core/biz/model/IdleBeatParam.java
create mode 100644 xxl-job-core/src/main/java/com/xxl/job/core/biz/model/KillParam.java
create mode 100644 xxl-job-core/src/main/java/com/xxl/job/core/biz/model/LogParam.java
create mode 100644 xxl-job-core/src/main/java/com/xxl/job/core/biz/model/LogResult.java
create mode 100644 xxl-job-core/src/main/java/com/xxl/job/core/biz/model/RegistryHandlerName.java
create mode 100644 xxl-job-core/src/main/java/com/xxl/job/core/biz/model/RegistryParam.java
create mode 100644 xxl-job-core/src/main/java/com/xxl/job/core/biz/model/ReturnT.java
create mode 100644 xxl-job-core/src/main/java/com/xxl/job/core/biz/model/TriggerParam.java
create mode 100644 xxl-job-core/src/main/java/com/xxl/job/core/context/XxlJobContext.java
create mode 100644 xxl-job-core/src/main/java/com/xxl/job/core/context/XxlJobHelper.java
create mode 100644 xxl-job-core/src/main/java/com/xxl/job/core/enums/ExecutorBlockStrategyEnum.java
create mode 100644 xxl-job-core/src/main/java/com/xxl/job/core/enums/RegistryConfig.java
create mode 100644 xxl-job-core/src/main/java/com/xxl/job/core/executor/XxlJobExecutor.java
create mode 100644 xxl-job-core/src/main/java/com/xxl/job/core/executor/impl/XxlJobSimpleExecutor.java
create mode 100644 xxl-job-core/src/main/java/com/xxl/job/core/executor/impl/XxlJobSpringExecutor.java
create mode 100644 xxl-job-core/src/main/java/com/xxl/job/core/glue/GlueFactory.java
create mode 100644 xxl-job-core/src/main/java/com/xxl/job/core/glue/GlueTypeEnum.java
create mode 100644 xxl-job-core/src/main/java/com/xxl/job/core/glue/impl/SpringGlueFactory.java
create mode 100644 xxl-job-core/src/main/java/com/xxl/job/core/handler/IJobHandler.java
create mode 100644 xxl-job-core/src/main/java/com/xxl/job/core/handler/annotation/JobHandler.java
create mode 100644 xxl-job-core/src/main/java/com/xxl/job/core/handler/annotation/XxlJob.java
create mode 100644 xxl-job-core/src/main/java/com/xxl/job/core/handler/impl/GlueJobHandler.java
create mode 100644 xxl-job-core/src/main/java/com/xxl/job/core/handler/impl/MethodJobHandler.java
create mode 100644 xxl-job-core/src/main/java/com/xxl/job/core/handler/impl/ScriptJobHandler.java
create mode 100644 xxl-job-core/src/main/java/com/xxl/job/core/log/XxlJobFileAppender.java
create mode 100644 xxl-job-core/src/main/java/com/xxl/job/core/server/EmbedServer.java
create mode 100644 xxl-job-core/src/main/java/com/xxl/job/core/thread/ExecutorRegistryThread.java
create mode 100644 xxl-job-core/src/main/java/com/xxl/job/core/thread/JobLogFileCleanThread.java
create mode 100644 xxl-job-core/src/main/java/com/xxl/job/core/thread/JobThread.java
create mode 100644 xxl-job-core/src/main/java/com/xxl/job/core/thread/TriggerCallbackThread.java
create mode 100644 xxl-job-core/src/main/java/com/xxl/job/core/util/DateUtil.java
create mode 100644 xxl-job-core/src/main/java/com/xxl/job/core/util/FileUtil.java
create mode 100644 xxl-job-core/src/main/java/com/xxl/job/core/util/GsonTool.java
create mode 100644 xxl-job-core/src/main/java/com/xxl/job/core/util/IpUtil.java
create mode 100644 xxl-job-core/src/main/java/com/xxl/job/core/util/JdkSerializeTool.java
create mode 100644 xxl-job-core/src/main/java/com/xxl/job/core/util/NetUtil.java
create mode 100644 xxl-job-core/src/main/java/com/xxl/job/core/util/ScriptUtil.java
create mode 100644 xxl-job-core/src/main/java/com/xxl/job/core/util/ShardingUtil.java
create mode 100644 xxl-job-core/src/main/java/com/xxl/job/core/util/ThrowableUtil.java
create mode 100644 xxl-job-core/src/main/java/com/xxl/job/core/util/XxlJobRemotingUtil.java
create mode 100644 yunzhupaas-scheduletask-client/pom.xml
create mode 100644 yunzhupaas-scheduletask-client/src/main/java/com/yunzhupaas/scheduletask/config/RegisterAddressConfig.java
create mode 100644 yunzhupaas-scheduletask-client/src/main/java/com/yunzhupaas/scheduletask/config/XxlJobConfig.java
create mode 100644 yunzhupaas-scheduletask-model/pom.xml
create mode 100644 yunzhupaas-scheduletask-model/src/main/java/com/yunzhupaas/scheduletask/entity/HandlerNameEntity.java
create mode 100644 yunzhupaas-scheduletask-model/src/main/java/com/yunzhupaas/scheduletask/entity/TimeTaskEntity.java
create mode 100644 yunzhupaas-scheduletask-model/src/main/java/com/yunzhupaas/scheduletask/entity/TimeTaskLogEntity.java
create mode 100644 yunzhupaas-scheduletask-model/src/main/java/com/yunzhupaas/scheduletask/entity/XxlJobGroup.java
create mode 100644 yunzhupaas-scheduletask-model/src/main/java/com/yunzhupaas/scheduletask/entity/XxlJobInfo.java
create mode 100644 yunzhupaas-scheduletask-model/src/main/java/com/yunzhupaas/scheduletask/entity/XxlJobLog.java
create mode 100644 yunzhupaas-scheduletask-model/src/main/java/com/yunzhupaas/scheduletask/model/ContentNewModel.java
create mode 100644 yunzhupaas-scheduletask-model/src/main/java/com/yunzhupaas/scheduletask/model/TaskCrForm.java
create mode 100644 yunzhupaas-scheduletask-model/src/main/java/com/yunzhupaas/scheduletask/model/TaskInfoVO.java
create mode 100644 yunzhupaas-scheduletask-model/src/main/java/com/yunzhupaas/scheduletask/model/TaskLogVO.java
create mode 100644 yunzhupaas-scheduletask-model/src/main/java/com/yunzhupaas/scheduletask/model/TaskMethodsVO.java
create mode 100644 yunzhupaas-scheduletask-model/src/main/java/com/yunzhupaas/scheduletask/model/TaskPage.java
create mode 100644 yunzhupaas-scheduletask-model/src/main/java/com/yunzhupaas/scheduletask/model/TaskParameterModel.java
create mode 100644 yunzhupaas-scheduletask-model/src/main/java/com/yunzhupaas/scheduletask/model/TaskUpForm.java
create mode 100644 yunzhupaas-scheduletask-model/src/main/java/com/yunzhupaas/scheduletask/model/TaskVO.java
create mode 100644 yunzhupaas-scheduletask-model/src/main/java/com/yunzhupaas/scheduletask/model/UpdateTaskModel.java
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..c0c8926
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,35 @@
+# Compiled class file
+*.class
+
+# Log file
+*.log
+
+# BlueJ files
+*.ctxt
+
+# Mobile Tools for Java (J2ME)
+.mtj.tmp/
+
+# IDEA artifacts and output dirs
+*.iml
+*.ipr
+*.iws
+.idea
+out
+test-output
+atlassian-ide-plugin.xml
+.gradletasknamecache
+classes/
+target/
+
+# Package Files #
+*.jar
+*.war
+*.nar
+*.ear
+*.zip
+*.tar.gz
+*.rar
+
+# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
+hs_err_pid*
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..ecc8215
--- /dev/null
+++ b/README.md
@@ -0,0 +1,240 @@
+> 特别说明:源码、JDK、MySQL、Redis等安装或存放路径禁止包含中文、空格、特殊字符等
+
+## 一 项目结构
+
+```text
+yunzhupaas_scheduletask
+ ├── yunzhupaas-scheduletask-client - 调度客户端配置模块
+ ├── yunzhupaas-scheduletask-model- 实体模型模块
+ ├── xxl-job-admin - 调度服务端
+ └── xxl-job-core - 调度服务端核心模块
+```
+
+## 二 环境要求
+
+### 2.1 开发环境
+
+| 类目 | 版本说明或建议 |
+| --- |------------------|
+| 硬件 | 开发电脑建议使用I3及以上CPU,16G及以上内存 |
+| 操作系统 | Windows 10/11,MacOS |
+| JDK | 默认使用JDK 21,兼容JDK 8/11、JDK17(需调整部分代码),推荐使用 `OpenJDK`,如 `Liberica JDK`、`Eclipse Temurin`、`Alibaba Dragonwell`、`BiSheng` 等发行版; |
+| Maven | 依赖管理工具,推荐使用 `3.6.3` 及以上版本 |
+| Redis | 数据缓存,推荐使用 `5.0` 及以上版本 |
+| 数据库 | 兼容 `MySQL 5.7.x/8.x`、`SQLServer 2012+`、`Oracle 11g`、`PostgreSQL 12+`、`达梦数据库(DM8)`、`人大金仓数据库(KingbaseES_V8R6)` |
+| IDE | 代码集成开发环境,推荐使用 `IDEA2024` 及以上版本,兼容 `Eclipse`、 `Spring Tool Suite` 等IDE工具 |
+
+### 2.2 运行环境
+
+> 服务端运行环境,适用于测试或生产环境
+
+| 类目 | 版本说明或建议 |
+| --- |-----------------------------------------------|
+| 服务器配置 | 建议至少在 4C/16G/50G 的机器配置下运行;|
+| 操作系统 | 建议使用 `Windows Server 2019` 及以上版本或主流 `Linux` 发行版本,推荐使用 `Linux` 环境;兼容 `统信UOS`,`OpenEuler`,`麒麟服务器版` 等信创环境; |
+| JRE | 默认使用JRE 21,兼容JRE 8/11、JRE17(需调整部分代码);推荐使用 `OpenJDK`,如 `Liberica JDK`、`Eclipse Temurin`、`Alibaba Dragonwell`、`BiSheng` 等发行版; |
+| Redis | 数据缓存,推荐使用 `5.0` 及以上版本 |
+| 数据库 | 兼容 `MySQL 5.7.x/8.x`、`SQLServer 2012+`、`Oracle 11g`、`PostgreSQL 12+`、`达梦数据库(DM8)`、`人大金仓数据库(KingbaseES_V8R6)` |
+
+## 三 关联项目
+
+> 为以下项目提供基础依赖
+
+| 项目 | 分支 | 说明 |
+| --- | --- | --- |
+| yunzhupaas-common | v5.2.x-stable | Java基础依赖项目源码 |
+| yunzhupaas-java-boot | v5.2.x-stable | Java单体后端项目源码 |
+| yunzhupaas-java-cloud | v5.2.x-stable | Java微服务后端项目源码 |
+
+## 四 使用方式
+
+> 本项目为任务调度的基础依赖和服务端,
作为客户端依赖时需要上传到私服或使用本地安装的方式引用该项目,
作为服务端时需要单独部署
+
+### 4.1 作为客户端依赖
+
+#### 4.1.1 前置条件
+
+##### 4.1.1.1 本地安装yunzhupaas-common-core
+
+IDEA中打开 `yunzhupaas-common` 项目, 双击右侧 `Maven` 中 `yunzhupaas-common` > `yunzhupaas-boot-common` > `yunzhupaas-common-core` > `Lifecycle` > `install`,将 `yunzhupaas-common-core` 包安装至本地
+
+##### 4.1.1.2 本地安装dependencies
+
+IDEA中打开 `yunzhupaas-common` 项目,双击右侧 `Maven` 中 `yunzhupaas-common` > `yunzhupaas-dependencies` > `Lifecycle` > `install`,将 `yunzhupaas-dependencies` 包安装至本地
+
+#### 4.1.2 本地安装
+
+在IDEA中,双击右侧 `Maven` 中 `yunzhupaas-scheduletask-starter` > `Lifecycle` > `install`,将 `yunzhupaas-scheduletask-client` 包安装至本地
+
+#### 4.1.3 私服发布
+> 若无Maven私服,忽略本节内容
+
+##### 4.1.3.1 配置Maven
+
+打开Maven安装目录中的 `conf/setttings.xml`,
+
+在 `` 节点增加 `` ,如下所示:
+
+```xml
+
+
+ maven-releases
+ admin(账号,结合私服配置设置)
+ 123456(密码,结合私服配置设置)
+
+```
+
+##### 4.1.3.2 配置项目
+
+> 注意:pom.xml里 `` 和 setting.xml 配置里 `` 对应。
+
+IDEA打开 `yunzhupaas-common` 项目, 修改 `yunzhupaas-dependencies/pom.xml` 文件中私服配置
+
+```xml
+
+
+ maven-releases
+ maven-releases
+ http://nexus.yunzhupaas.com/repository/maven-releases/
+
+
+```
+
+##### 4.1.3.3 发布到私服
+
+在IDEA中,双击右侧 `Maven` 中 `yunzhupaas-scheduletask-starter` > `Lifecycle` > `deploy` 发布至私服。
+
+### 4.2 作为服务端
+
+#### 4.2.1 项目配置
+
+##### 4.2.1.1 调整运行端口
+> 根据实际需求调整
+
+打开编辑 `xxl-job-admin/src/main/resources/application.yml`,第 19 行
+
+```yaml
+ port: 30020
+```
+
+##### 4.2.1.2 指定环境配置
+
+打开编辑 `xxl-job-admin/src/main/resources/application.yml`,第 25 行
+
+环境说明:
+
+- `application-dev.yml` 开发环境(默认)
+- `application-preview.yml` 预生产环境
+- `application-test.yml` 测试环境
+- `application-prod.yml` 生产环境
+
+> 以开发环境为例,根据实际需求修改
+
+```yaml
+# application.yml第 6 行,可选值:dev(开发环境-默认)、test(测试环境)、preview(预生产环境)、prod(生产环境)
+active: dev
+```
+
+#### 4.2.1 数据源配置
+
+打开 `xxl-job-admin/src/main/resources/application-dev.yml` 修改数据源,配置示例如下:
+
+##### 4.2.1.1 MySQL数据库
+
+```yaml
+ datasource:
+ url: jdbc:mysql://127.0.0.1:3306/yunzhupaas_xxljob?useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true&serverTimezone=GMT%2B8&useSSL=false
+ username: root
+ password: 123456
+ driver-class-name: com.mysql.cj.jdbc.Driver
+```
+
+##### 4.2.1.2 SQLServer数据库
+
+```yaml
+ datasource:
+ url: jdbc:sqlserver://127.0.0.1:1433;SelectMethod=cursor;Databasename=yunzhupaas_xxljob
+ username: sa
+ password: 123456
+ driver-class-name: com.microsoft.sqlserver.jdbc.SQLServerDriver
+```
+
+##### 4.2.1.3 Oracle数据库
+
+```yaml
+ datasource:
+ url: jdbc:oracle:thin:@127.0.0.1:1521:orcl
+ username: YUNZHUPAAS_XXLJOB
+ password: dbpasswd
+ driver-class-name: oracle.jdbc.OracleDriver
+```
+
+##### 4.2.1.4 PostgreSQL数据库
+
+```yaml
+ datasource:
+ url: jdbc:postgresql://127.0.0.1:5432/yunzhupaas_xxljob
+ username: dbuser
+ password: dbpasswd
+ driver-class-name: org.postgresql.Driver
+```
+
+##### 4.2.1.5 达梦dm8数据库
+
+```yaml
+ datasource:
+ url: jdbc:dm://127.0.0.1:5236/YUNZHUPAAS_XXLJOB?yunzhupaasDateTimeBehavior=convertToNull&useUnicode=true&characterEncoding=utf-8
+ username: dbuser
+ password: dbpasswd
+ driver-class-name: dm.jdbc.driver.DmDriver
+```
+
+##### 4.2.1.6 人大金仓KingbaseES数据库
+
+```yaml
+ datasource:
+ url: jdbc:kingbase8://127.0.0.1:54321/yunzhupaas_xxljob
+ username: dbuser
+ password: dbpasswd
+ driver-class-name: com.kingbase8.Driver
+```
+
+#### 4.2.2 打包部署
+
+在IDEA中,在左侧 `Project` 中,右击 `yunzhupaas-scheduletask` > `xxl-job-admin` > `pom.xml` 并选择 `Add as Maven Project` 将 `xxl-job-admin` 转为 Maven 项目,然后双击右侧 `Maven` 中 `xxl-job-admin` > `Lifecycle` > `package`, 将 `/xxl-job-admin/target/xxl-job-admin-5.0.0-RELEASE.jar` 上传至服务器部署即可。
+
+#### 4.2.3 关联项目配置
+
+配置如下所示
+
+```yaml
+# ===================== 任务调度配置 =====================
+xxl:
+ job:
+ accessToken: '432e62f3b488bc861d91b0e274e850cc'
+ i18n: zh_CN
+ logretentiondays: 30
+ triggerpool:
+ fast:
+ max: 200
+ slow:
+ max: 100
+ admin:
+ # xxl-job服务端地址
+ addresses: http://127.0.0.1:30020/xxl-job-admin/
+ executor:
+ address: ''
+ appname: xxl-job-executor-sample1
+ ip: ''
+ logpath: /data/applogs/xxl-job/jobhandler
+ logretentiondays: 30
+ port: 9999
+```
+
+##### 4.2.3.1 yunzhupaas-java-boot项目
+
+IDEA打开 `yunzhupaas-java-boot` 项目, 编辑 `yunzhupaas-admin/src/main/resources/application-x.yml` 文件( `application-x.yml` 为环境配置,如 `application-dev.yml` )
+
+##### 4.2.3.2 yunzhupaas-java-cloud项目
+
+登录 `Nacos` 控制台,依次点击 `配置管理` > `配置列表` > `develop`,编辑 `datasource-scheduletask.yaml`。
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..4d353dd
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,37 @@
+
+
+ 4.0.0
+
+
+ yunzhupaas-dependencies
+ com.yunzhupaas
+ 5.2.0-RELEASE
+
+
+ com.yunzhupaas
+ yunzhupaas-scheduletask-starter
+ pom
+ 5.2.0-RELEASE
+
+ 任务调度项目,集成xxl-job
+
+
+ xxl-job-admin
+ xxl-job-core
+ yunzhupaas-scheduletask-client
+ yunzhupaas-scheduletask-model
+
+
+
+
+
+
+
+ org.projectlombok
+ lombok
+
+
+
+
diff --git a/xxl-job-admin/Dockerfile b/xxl-job-admin/Dockerfile
new file mode 100644
index 0000000..1faffde
--- /dev/null
+++ b/xxl-job-admin/Dockerfile
@@ -0,0 +1,22 @@
+# 基础镜像
+FROM bellsoft/liberica-openjre-rocky:21
+# FROM bellsoft/liberica-openjre-rocky:17
+# FROM bellsoft/liberica-openjre-rocky:11
+# FROM bellsoft/liberica-openjre-rocky:8
+LABEL maintainer=yunzhupaas-team
+
+# 设置时区
+ENV TZ=Asia/Shanghai
+RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
+
+# 指定运行时的工作目录
+WORKDIR /data/yunzhupaassoft/scheduletaskApi
+
+# 将构建产物jar包拷贝到运行时目录中
+COPY target/*.jar ./yunzhupaas-scheduletask-server.jar
+
+# 指定容器内运行端口
+EXPOSE 30020
+
+# 指定容器启动时要运行的命令
+ENTRYPOINT ["/bin/sh","-c","java -Dfile.encoding=utf8 -Djava.security.egd=file:/dev/./urandom -jar yunzhupaas-scheduletask-server.jar"]
diff --git a/xxl-job-admin/pom.xml b/xxl-job-admin/pom.xml
new file mode 100644
index 0000000..9244087
--- /dev/null
+++ b/xxl-job-admin/pom.xml
@@ -0,0 +1,185 @@
+
+ 4.0.0
+
+
+ yunzhupaas-dependencies
+ com.yunzhupaas
+ 5.2.0-RELEASE
+
+
+
+ com.yunzhupaas
+ xxl-job-admin
+ jar
+ 5.2.0-RELEASE
+
+
+ true
+
+
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-freemarker
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-mail
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-actuator
+
+
+
+ com.yunzhupaas
+ yunzhupaas-common-core
+ ${project.version}
+
+
+
+
+ com.yunzhupaas
+ xxl-job-core
+ ${project.version}
+
+
+
+
+ com.yunzhupaas
+ yunzhupaas-scheduletask-model
+ ${project.version}
+
+
+
+ com.mysql
+ mysql-connector-j
+
+
+
+
+ com.microsoft.sqlserver
+ mssql-jdbc
+
+
+
+
+ com.oracle.database.jdbc
+ ojdbc8
+
+
+ com.oracle.database.nls
+ orai18n
+
+
+
+ com.dameng
+ DmJdbcDriver18
+
+
+
+ cn.com.kingbase
+ kingbase8
+
+
+
+ org.postgresql
+ postgresql
+
+
+
+
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+ repackage
+
+
+
+
+
+
+ com.spotify
+ docker-maven-plugin
+ 0.4.13
+
+
+ ${project.artifactId}:${project.version}
+ ${project.basedir}
+
+
+ /
+ ${project.build.directory}
+ ${project.build.finalName}.jar
+
+
+
+
+
+
+
+
+
+
+ boot3
+
+ [17,)
+
+
+
+ com.baomidou
+ mybatis-plus-spring-boot3-starter
+ ${mybatis-plus.vesion}
+
+
+
+
+ boot2
+
+ (,17)
+
+
+
+ com.baomidou
+ mybatis-plus-boot-starter
+ ${mybatis-plus.vesion}
+
+
+
+
+
+
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/XxlJobAdminApplication.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/XxlJobAdminApplication.java
new file mode 100644
index 0000000..f517077
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/XxlJobAdminApplication.java
@@ -0,0 +1,20 @@
+package com.xxl.job.admin;
+
+import org.mybatis.spring.annotation.MapperScan;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.scheduling.annotation.EnableAsync;
+
+/**
+ * @author xuxueli 2018-10-28 00:38:13
+ */
+@EnableAsync
+@SpringBootApplication(scanBasePackages = { "com.xxl.job", "com.yunzhupaas" })
+@MapperScan(basePackages = { "com.xxl.job.admin.dao", "com.xxl.job.admin.mapper" })
+public class XxlJobAdminApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(XxlJobAdminApplication.class, args);
+ }
+
+}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/config/XxlJobListener.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/config/XxlJobListener.java
new file mode 100644
index 0000000..5281560
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/config/XxlJobListener.java
@@ -0,0 +1,35 @@
+package com.xxl.job.admin.config;
+
+import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
+import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
+import com.xxl.job.admin.service.HandlerNameService;
+import com.yunzhupaas.util.context.SpringContext;
+
+import org.springframework.boot.ApplicationArguments;
+import org.springframework.boot.ApplicationRunner;
+import org.springframework.context.annotation.Bean;
+import org.springframework.stereotype.Component;
+
+/**
+ *
+ * @author 云筑产品开发平台组
+ * @version V3.1.0
+ * @copyright 深圳市乐程软件有限公司
+ * @date 2024/3/16 8:49
+ */
+@Component
+public class XxlJobListener implements ApplicationRunner {
+ @Override
+ public void run(ApplicationArguments args) throws Exception {
+ HandlerNameService handlerNameService = SpringContext.getBean(HandlerNameService.class);
+ handlerNameService.removeAll();
+ }
+
+ @Bean
+ public MybatisPlusInterceptor pageHelper() {
+ MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
+ mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
+ return mybatisPlusInterceptor;
+ }
+}
+
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/IndexController.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/IndexController.java
new file mode 100644
index 0000000..bafd160
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/IndexController.java
@@ -0,0 +1,97 @@
+package com.xxl.job.admin.controller;
+
+import com.xxl.job.admin.controller.annotation.PermissionLimit;
+import com.xxl.job.admin.service.impl.XxlJobLoginService;
+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 jakarta.annotation.Resource;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.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 XxlJobLoginService loginService;
+
+
+ @RequestMapping("/")
+ public String index(Model model) {
+
+ Map dashboardMap = xxlJobService.dashboardInfo();
+ model.addAllAttributes(dashboardMap);
+
+ return "index";
+ }
+
+ @RequestMapping("/chartInfo")
+ @ResponseBody
+ public ReturnT
+ *
+ *
+ * @author Sharada Jambula, James House
+ * @author Contributions from Mads Henderson
+ * @author Refactoring from CronTrigger to CronExpression by Aaron Craven
+ *
+ * Borrowed from quartz v2.3.1
+ *
+ */
+public final class CronExpression implements Serializable, Cloneable {
+
+ private static final long serialVersionUID = 12423409423L;
+
+ protected static final int SECOND = 0;
+ protected static final int MINUTE = 1;
+ protected static final int HOUR = 2;
+ protected static final int DAY_OF_MONTH = 3;
+ protected static final int MONTH = 4;
+ protected static final int DAY_OF_WEEK = 5;
+ protected static final int YEAR = 6;
+ protected static final int ALL_SPEC_INT = 99; // '*'
+ protected static final int NO_SPEC_INT = 98; // '?'
+ protected static final Integer ALL_SPEC = ALL_SPEC_INT;
+ protected static final Integer NO_SPEC = NO_SPEC_INT;
+
+ protected static final Map monthMap = new HashMap(20);
+ protected static final Map dayMap = new HashMap(60);
+ static {
+ monthMap.put("JAN", 0);
+ monthMap.put("FEB", 1);
+ monthMap.put("MAR", 2);
+ monthMap.put("APR", 3);
+ monthMap.put("MAY", 4);
+ monthMap.put("JUN", 5);
+ monthMap.put("JUL", 6);
+ monthMap.put("AUG", 7);
+ monthMap.put("SEP", 8);
+ monthMap.put("OCT", 9);
+ monthMap.put("NOV", 10);
+ monthMap.put("DEC", 11);
+
+ dayMap.put("SUN", 1);
+ dayMap.put("MON", 2);
+ dayMap.put("TUE", 3);
+ dayMap.put("WED", 4);
+ dayMap.put("THU", 5);
+ dayMap.put("FRI", 6);
+ dayMap.put("SAT", 7);
+ }
+
+ private final String cronExpression;
+ private TimeZone timeZone = null;
+ protected transient TreeSet seconds;
+ protected transient TreeSet minutes;
+ protected transient TreeSet hours;
+ protected transient TreeSet daysOfMonth;
+ protected transient TreeSet months;
+ protected transient TreeSet daysOfWeek;
+ protected transient TreeSet years;
+
+ protected transient boolean lastdayOfWeek = false;
+ protected transient int nthdayOfWeek = 0;
+ protected transient boolean lastdayOfMonth = false;
+ protected transient boolean nearestWeekday = false;
+ protected transient int lastdayOffset = 0;
+ protected transient boolean expressionParsed = false;
+
+ public static final int MAX_YEAR = Calendar.getInstance().get(Calendar.YEAR) + 100;
+
+ /**
+ * Constructs a new CronExpression based on the specified
+ * parameter.
+ *
+ * @param cronExpression String representation of the cron expression the
+ * new object should represent
+ * @throws java.text.ParseException
+ * if the string expression cannot be parsed into a valid
+ * CronExpression
+ */
+ public CronExpression(String cronExpression) throws ParseException {
+ if (cronExpression == null) {
+ throw new IllegalArgumentException("cronExpression cannot be null");
+ }
+
+ this.cronExpression = cronExpression.toUpperCase(Locale.US);
+
+ buildExpression(this.cronExpression);
+ }
+
+ /**
+ * Constructs a new {@code CronExpression} as a copy of an existing
+ * instance.
+ *
+ * @param expression
+ * The existing cron expression to be copied
+ */
+ public CronExpression(CronExpression expression) {
+ /*
+ * We don't call the other constructor here since we need to swallow the
+ * ParseException. We also elide some of the sanity checking as it is
+ * not logically trippable.
+ */
+ this.cronExpression = expression.getCronExpression();
+ try {
+ buildExpression(cronExpression);
+ } catch (ParseException ex) {
+ throw new AssertionError();
+ }
+ if (expression.getTimeZone() != null) {
+ setTimeZone((TimeZone) expression.getTimeZone().clone());
+ }
+ }
+
+ /**
+ * Indicates whether the given date satisfies the cron expression. Note that
+ * milliseconds are ignored, so two Dates falling on different milliseconds
+ * of the same second will always have the same result here.
+ *
+ * @param date the date to evaluate
+ * @return a boolean indicating whether the given date satisfies the cron
+ * expression
+ */
+ public boolean isSatisfiedBy(Date date) {
+ Calendar testDateCal = Calendar.getInstance(getTimeZone());
+ testDateCal.setTime(date);
+ testDateCal.set(Calendar.MILLISECOND, 0);
+ Date originalDate = testDateCal.getTime();
+
+ testDateCal.add(Calendar.SECOND, -1);
+
+ Date timeAfter = getTimeAfter(testDateCal.getTime());
+
+ return ((timeAfter != null) && (timeAfter.equals(originalDate)));
+ }
+
+ /**
+ * Returns the next date/time after the given date/time which
+ * satisfies the cron expression.
+ *
+ * @param date the date/time at which to begin the search for the next valid
+ * date/time
+ * @return the next valid date/time
+ */
+ public Date getNextValidTimeAfter(Date date) {
+ return getTimeAfter(date);
+ }
+
+ /**
+ * Returns the next date/time after the given date/time which does
+ * not satisfy the expression
+ *
+ * @param date the date/time at which to begin the search for the next
+ * invalid date/time
+ * @return the next valid date/time
+ */
+ public Date getNextInvalidTimeAfter(Date date) {
+ long difference = 1000;
+
+ //move back to the nearest second so differences will be accurate
+ Calendar adjustCal = Calendar.getInstance(getTimeZone());
+ adjustCal.setTime(date);
+ adjustCal.set(Calendar.MILLISECOND, 0);
+ Date lastDate = adjustCal.getTime();
+
+ Date newDate;
+
+ //FUTURE_TODO: (QUARTZ-481) IMPROVE THIS! The following is a BAD solution to this problem. Performance will be very bad here, depending on the cron expression. It is, however A solution.
+
+ //keep getting the next included time until it's farther than one second
+ // apart. At that point, lastDate is the last valid fire time. We return
+ // the second immediately following it.
+ while (difference == 1000) {
+ newDate = getTimeAfter(lastDate);
+ if(newDate == null) {
+ break;
+ }
+ difference = newDate.getTime() - lastDate.getTime();
+
+ if (difference == 1000) {
+ lastDate = newDate;
+ }
+ }
+
+ return new Date(lastDate.getTime() + 1000);
+ }
+
+ /**
+ * Returns the time zone for which this CronExpression
+ * will be resolved.
+ */
+ public TimeZone getTimeZone() {
+ if (timeZone == null) {
+ timeZone = TimeZone.getDefault();
+ }
+
+ return timeZone;
+ }
+
+ /**
+ * Sets the time zone for which this CronExpression
+ * will be resolved.
+ */
+ public void setTimeZone(TimeZone timeZone) {
+ this.timeZone = timeZone;
+ }
+
+ /**
+ * Returns the string representation of the CronExpression
+ *
+ * @return a string representation of the CronExpression
+ */
+ @Override
+ public String toString() {
+ return cronExpression;
+ }
+
+ /**
+ * Indicates whether the specified cron expression can be parsed into a
+ * valid cron expression
+ *
+ * @param cronExpression the expression to evaluate
+ * @return a boolean indicating whether the given expression is a valid cron
+ * expression
+ */
+ public static boolean isValidExpression(String cronExpression) {
+
+ try {
+ new CronExpression(cronExpression);
+ } catch (ParseException pe) {
+ return false;
+ }
+
+ return true;
+ }
+
+ public static void validateExpression(String cronExpression) throws ParseException {
+
+ new CronExpression(cronExpression);
+ }
+
+
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // Expression Parsing Functions
+ //
+ ////////////////////////////////////////////////////////////////////////////
+
+ protected void buildExpression(String expression) throws ParseException {
+ expressionParsed = true;
+
+ try {
+
+ if (seconds == null) {
+ seconds = new TreeSet();
+ }
+ if (minutes == null) {
+ minutes = new TreeSet();
+ }
+ if (hours == null) {
+ hours = new TreeSet();
+ }
+ if (daysOfMonth == null) {
+ daysOfMonth = new TreeSet();
+ }
+ if (months == null) {
+ months = new TreeSet();
+ }
+ if (daysOfWeek == null) {
+ daysOfWeek = new TreeSet();
+ }
+ if (years == null) {
+ years = new TreeSet();
+ }
+
+ int exprOn = SECOND;
+
+ StringTokenizer exprsTok = new StringTokenizer(expression, " \t",
+ false);
+
+ while (exprsTok.hasMoreTokens() && exprOn <= YEAR) {
+ String expr = exprsTok.nextToken().trim();
+
+ // throw an exception if L is used with other days of the month
+ if(exprOn == DAY_OF_MONTH && expr.indexOf('L') != -1 && expr.length() > 1 && expr.contains(",")) {
+ throw new ParseException("Support for specifying 'L' and 'LW' with other days of the month is not implemented", -1);
+ }
+ // throw an exception if L is used with other days of the week
+ if(exprOn == DAY_OF_WEEK && expr.indexOf('L') != -1 && expr.length() > 1 && expr.contains(",")) {
+ throw new ParseException("Support for specifying 'L' with other days of the week is not implemented", -1);
+ }
+ if(exprOn == DAY_OF_WEEK && expr.indexOf('#') != -1 && expr.indexOf('#', expr.indexOf('#') +1) != -1) {
+ throw new ParseException("Support for specifying multiple \"nth\" days is not implemented.", -1);
+ }
+
+ StringTokenizer vTok = new StringTokenizer(expr, ",");
+ while (vTok.hasMoreTokens()) {
+ String v = vTok.nextToken();
+ storeExpressionVals(0, v, exprOn);
+ }
+
+ exprOn++;
+ }
+
+ if (exprOn <= DAY_OF_WEEK) {
+ throw new ParseException("Unexpected end of expression.",
+ expression.length());
+ }
+
+ if (exprOn <= YEAR) {
+ storeExpressionVals(0, "*", YEAR);
+ }
+
+ TreeSet dow = getSet(DAY_OF_WEEK);
+ TreeSet dom = getSet(DAY_OF_MONTH);
+
+ // Copying the logic from the UnsupportedOperationException below
+ boolean dayOfMSpec = !dom.contains(NO_SPEC);
+ boolean dayOfWSpec = !dow.contains(NO_SPEC);
+
+ if (!dayOfMSpec || dayOfWSpec) {
+ if (!dayOfWSpec || dayOfMSpec) {
+ throw new ParseException(
+ "Support for specifying both a day-of-week AND a day-of-month parameter is not implemented.", 0);
+ }
+ }
+ } catch (ParseException pe) {
+ throw pe;
+ } catch (Exception e) {
+ throw new ParseException("Illegal cron expression format ("
+ + e.toString() + ")", 0);
+ }
+ }
+
+ protected int storeExpressionVals(int pos, String s, int type)
+ throws ParseException {
+
+ int incr = 0;
+ int i = skipWhiteSpace(pos, s);
+ if (i >= s.length()) {
+ return i;
+ }
+ char c = s.charAt(i);
+ if ((c >= 'A') && (c <= 'Z') && (!s.equals("L")) && (!s.equals("LW")) && (!s.matches("^L-[0-9]*[W]?"))) {
+ String sub = s.substring(i, i + 3);
+ int sval = -1;
+ int eval = -1;
+ if (type == MONTH) {
+ sval = getMonthNumber(sub) + 1;
+ if (sval <= 0) {
+ throw new ParseException("Invalid Month value: '" + sub + "'", i);
+ }
+ if (s.length() > i + 3) {
+ c = s.charAt(i + 3);
+ if (c == '-') {
+ i += 4;
+ sub = s.substring(i, i + 3);
+ eval = getMonthNumber(sub) + 1;
+ if (eval <= 0) {
+ throw new ParseException("Invalid Month value: '" + sub + "'", i);
+ }
+ }
+ }
+ } else if (type == DAY_OF_WEEK) {
+ sval = getDayOfWeekNumber(sub);
+ if (sval < 0) {
+ throw new ParseException("Invalid Day-of-Week value: '"
+ + sub + "'", i);
+ }
+ if (s.length() > i + 3) {
+ c = s.charAt(i + 3);
+ if (c == '-') {
+ i += 4;
+ sub = s.substring(i, i + 3);
+ eval = getDayOfWeekNumber(sub);
+ if (eval < 0) {
+ throw new ParseException(
+ "Invalid Day-of-Week value: '" + sub
+ + "'", i);
+ }
+ } else if (c == '#') {
+ try {
+ i += 4;
+ nthdayOfWeek = Integer.parseInt(s.substring(i));
+ if (nthdayOfWeek < 1 || nthdayOfWeek > 5) {
+ throw new Exception();
+ }
+ } catch (Exception e) {
+ throw new ParseException(
+ "A numeric value between 1 and 5 must follow the '#' option",
+ i);
+ }
+ } else if (c == 'L') {
+ lastdayOfWeek = true;
+ i++;
+ }
+ }
+
+ } else {
+ throw new ParseException(
+ "Illegal characters for this position: '" + sub + "'",
+ i);
+ }
+ if (eval != -1) {
+ incr = 1;
+ }
+ addToSet(sval, eval, incr, type);
+ return (i + 3);
+ }
+
+ if (c == '?') {
+ i++;
+ if ((i + 1) < s.length()
+ && (s.charAt(i) != ' ' && s.charAt(i + 1) != '\t')) {
+ throw new ParseException("Illegal character after '?': "
+ + s.charAt(i), i);
+ }
+ if (type != DAY_OF_WEEK && type != DAY_OF_MONTH) {
+ throw new ParseException(
+ "'?' can only be specified for Day-of-Month or Day-of-Week.",
+ i);
+ }
+ if (type == DAY_OF_WEEK && !lastdayOfMonth) {
+ int val = daysOfMonth.last();
+ if (val == NO_SPEC_INT) {
+ throw new ParseException(
+ "'?' can only be specified for Day-of-Month -OR- Day-of-Week.",
+ i);
+ }
+ }
+
+ addToSet(NO_SPEC_INT, -1, 0, type);
+ return i;
+ }
+
+ if (c == '*' || c == '/') {
+ if (c == '*' && (i + 1) >= s.length()) {
+ addToSet(ALL_SPEC_INT, -1, incr, type);
+ return i + 1;
+ } else if (c == '/'
+ && ((i + 1) >= s.length() || s.charAt(i + 1) == ' ' || s
+ .charAt(i + 1) == '\t')) {
+ throw new ParseException("'/' must be followed by an integer.", i);
+ } else if (c == '*') {
+ i++;
+ }
+ c = s.charAt(i);
+ if (c == '/') { // is an increment specified?
+ i++;
+ if (i >= s.length()) {
+ throw new ParseException("Unexpected end of string.", i);
+ }
+
+ incr = getNumericValue(s, i);
+
+ i++;
+ if (incr > 10) {
+ i++;
+ }
+ checkIncrementRange(incr, type, i);
+ } else {
+ incr = 1;
+ }
+
+ addToSet(ALL_SPEC_INT, -1, incr, type);
+ return i;
+ } else if (c == 'L') {
+ i++;
+ if (type == DAY_OF_MONTH) {
+ lastdayOfMonth = true;
+ }
+ if (type == DAY_OF_WEEK) {
+ addToSet(7, 7, 0, type);
+ }
+ if(type == DAY_OF_MONTH && s.length() > i) {
+ c = s.charAt(i);
+ if(c == '-') {
+ ValueSet vs = getValue(0, s, i+1);
+ lastdayOffset = vs.value;
+ if(lastdayOffset > 30) {
+ throw new ParseException("Offset from last day must be <= 30", i + 1);
+ }
+ i = vs.pos;
+ }
+ if(s.length() > i) {
+ c = s.charAt(i);
+ if(c == 'W') {
+ nearestWeekday = true;
+ i++;
+ }
+ }
+ }
+ return i;
+ } else if (c >= '0' && c <= '9') {
+ int val = Integer.parseInt(String.valueOf(c));
+ i++;
+ if (i >= s.length()) {
+ addToSet(val, -1, -1, type);
+ } else {
+ c = s.charAt(i);
+ if (c >= '0' && c <= '9') {
+ ValueSet vs = getValue(val, s, i);
+ val = vs.value;
+ i = vs.pos;
+ }
+ i = checkNext(i, s, val, type);
+ return i;
+ }
+ } else {
+ throw new ParseException("Unexpected character: " + c, i);
+ }
+
+ return i;
+ }
+
+ private void checkIncrementRange(int incr, int type, int idxPos) throws ParseException {
+ if (incr > 59 && (type == SECOND || type == MINUTE)) {
+ throw new ParseException("Increment > 60 : " + incr, idxPos);
+ } else if (incr > 23 && (type == HOUR)) {
+ throw new ParseException("Increment > 24 : " + incr, idxPos);
+ } else if (incr > 31 && (type == DAY_OF_MONTH)) {
+ throw new ParseException("Increment > 31 : " + incr, idxPos);
+ } else if (incr > 7 && (type == DAY_OF_WEEK)) {
+ throw new ParseException("Increment > 7 : " + incr, idxPos);
+ } else if (incr > 12 && (type == MONTH)) {
+ throw new ParseException("Increment > 12 : " + incr, idxPos);
+ }
+ }
+
+ protected int checkNext(int pos, String s, int val, int type)
+ throws ParseException {
+
+ int end = -1;
+ int i = pos;
+
+ if (i >= s.length()) {
+ addToSet(val, end, -1, type);
+ return i;
+ }
+
+ char c = s.charAt(pos);
+
+ if (c == 'L') {
+ if (type == DAY_OF_WEEK) {
+ if(val < 1 || val > 7) {
+ throw new ParseException("Day-of-Week values must be between 1 and 7", -1);
+ }
+ lastdayOfWeek = true;
+ } else {
+ throw new ParseException("'L' option is not valid here. (pos=" + i + ")", i);
+ }
+ TreeSet set = getSet(type);
+ set.add(val);
+ i++;
+ return i;
+ }
+
+ if (c == 'W') {
+ if (type == DAY_OF_MONTH) {
+ nearestWeekday = true;
+ } else {
+ throw new ParseException("'W' option is not valid here. (pos=" + i + ")", i);
+ }
+ if(val > 31) {
+ throw new ParseException("The 'W' option does not make sense with values larger than 31 (max number of days in a month)", i);
+ }
+ TreeSet set = getSet(type);
+ set.add(val);
+ i++;
+ return i;
+ }
+
+ if (c == '#') {
+ if (type != DAY_OF_WEEK) {
+ throw new ParseException("'#' option is not valid here. (pos=" + i + ")", i);
+ }
+ i++;
+ try {
+ nthdayOfWeek = Integer.parseInt(s.substring(i));
+ if (nthdayOfWeek < 1 || nthdayOfWeek > 5) {
+ throw new Exception();
+ }
+ } catch (Exception e) {
+ throw new ParseException(
+ "A numeric value between 1 and 5 must follow the '#' option",
+ i);
+ }
+
+ TreeSet set = getSet(type);
+ set.add(val);
+ i++;
+ return i;
+ }
+
+ if (c == '-') {
+ i++;
+ c = s.charAt(i);
+ int v = Integer.parseInt(String.valueOf(c));
+ end = v;
+ i++;
+ if (i >= s.length()) {
+ addToSet(val, end, 1, type);
+ return i;
+ }
+ c = s.charAt(i);
+ if (c >= '0' && c <= '9') {
+ ValueSet vs = getValue(v, s, i);
+ end = vs.value;
+ i = vs.pos;
+ }
+ if (i < s.length() && ((c = s.charAt(i)) == '/')) {
+ i++;
+ c = s.charAt(i);
+ int v2 = Integer.parseInt(String.valueOf(c));
+ i++;
+ if (i >= s.length()) {
+ addToSet(val, end, v2, type);
+ return i;
+ }
+ c = s.charAt(i);
+ if (c >= '0' && c <= '9') {
+ ValueSet vs = getValue(v2, s, i);
+ int v3 = vs.value;
+ addToSet(val, end, v3, type);
+ i = vs.pos;
+ return i;
+ } else {
+ addToSet(val, end, v2, type);
+ return i;
+ }
+ } else {
+ addToSet(val, end, 1, type);
+ return i;
+ }
+ }
+
+ if (c == '/') {
+ if ((i + 1) >= s.length() || s.charAt(i + 1) == ' ' || s.charAt(i + 1) == '\t') {
+ throw new ParseException("'/' must be followed by an integer.", i);
+ }
+
+ i++;
+ c = s.charAt(i);
+ int v2 = Integer.parseInt(String.valueOf(c));
+ i++;
+ if (i >= s.length()) {
+ checkIncrementRange(v2, type, i);
+ addToSet(val, end, v2, type);
+ return i;
+ }
+ c = s.charAt(i);
+ if (c >= '0' && c <= '9') {
+ ValueSet vs = getValue(v2, s, i);
+ int v3 = vs.value;
+ checkIncrementRange(v3, type, i);
+ addToSet(val, end, v3, type);
+ i = vs.pos;
+ return i;
+ } else {
+ throw new ParseException("Unexpected character '" + c + "' after '/'", i);
+ }
+ }
+
+ addToSet(val, end, 0, type);
+ i++;
+ return i;
+ }
+
+ public String getCronExpression() {
+ return cronExpression;
+ }
+
+ public String getExpressionSummary() {
+ StringBuilder buf = new StringBuilder();
+
+ buf.append("seconds: ");
+ buf.append(getExpressionSetSummary(seconds));
+ buf.append("\n");
+ buf.append("minutes: ");
+ buf.append(getExpressionSetSummary(minutes));
+ buf.append("\n");
+ buf.append("hours: ");
+ buf.append(getExpressionSetSummary(hours));
+ buf.append("\n");
+ buf.append("daysOfMonth: ");
+ buf.append(getExpressionSetSummary(daysOfMonth));
+ buf.append("\n");
+ buf.append("months: ");
+ buf.append(getExpressionSetSummary(months));
+ buf.append("\n");
+ buf.append("daysOfWeek: ");
+ buf.append(getExpressionSetSummary(daysOfWeek));
+ buf.append("\n");
+ buf.append("lastdayOfWeek: ");
+ buf.append(lastdayOfWeek);
+ buf.append("\n");
+ buf.append("nearestWeekday: ");
+ buf.append(nearestWeekday);
+ buf.append("\n");
+ buf.append("NthDayOfWeek: ");
+ buf.append(nthdayOfWeek);
+ buf.append("\n");
+ buf.append("lastdayOfMonth: ");
+ buf.append(lastdayOfMonth);
+ buf.append("\n");
+ buf.append("years: ");
+ buf.append(getExpressionSetSummary(years));
+ buf.append("\n");
+
+ return buf.toString();
+ }
+
+ protected String getExpressionSetSummary(java.util.Set set) {
+
+ if (set.contains(NO_SPEC)) {
+ return "?";
+ }
+ if (set.contains(ALL_SPEC)) {
+ return "*";
+ }
+
+ StringBuilder buf = new StringBuilder();
+
+ Iterator itr = set.iterator();
+ boolean first = true;
+ while (itr.hasNext()) {
+ Integer iVal = itr.next();
+ String val = iVal.toString();
+ if (!first) {
+ buf.append(",");
+ }
+ buf.append(val);
+ first = false;
+ }
+
+ return buf.toString();
+ }
+
+ protected String getExpressionSetSummary(java.util.ArrayList list) {
+
+ if (list.contains(NO_SPEC)) {
+ return "?";
+ }
+ if (list.contains(ALL_SPEC)) {
+ return "*";
+ }
+
+ StringBuilder buf = new StringBuilder();
+
+ Iterator itr = list.iterator();
+ boolean first = true;
+ while (itr.hasNext()) {
+ Integer iVal = itr.next();
+ String val = iVal.toString();
+ if (!first) {
+ buf.append(",");
+ }
+ buf.append(val);
+ first = false;
+ }
+
+ return buf.toString();
+ }
+
+ protected int skipWhiteSpace(int i, String s) {
+ for (; i < s.length() && (s.charAt(i) == ' ' || s.charAt(i) == '\t'); i++) {
+ }
+
+ return i;
+ }
+
+ protected int findNextWhiteSpace(int i, String s) {
+ for (; i < s.length() && (s.charAt(i) != ' ' || s.charAt(i) != '\t'); i++) {
+ }
+
+ return i;
+ }
+
+ protected void addToSet(int val, int end, int incr, int type)
+ throws ParseException {
+
+ TreeSet set = getSet(type);
+
+ if (type == SECOND || type == MINUTE) {
+ if ((val < 0 || val > 59 || end > 59) && (val != ALL_SPEC_INT)) {
+ throw new ParseException(
+ "Minute and Second values must be between 0 and 59",
+ -1);
+ }
+ } else if (type == HOUR) {
+ if ((val < 0 || val > 23 || end > 23) && (val != ALL_SPEC_INT)) {
+ throw new ParseException(
+ "Hour values must be between 0 and 23", -1);
+ }
+ } else if (type == DAY_OF_MONTH) {
+ if ((val < 1 || val > 31 || end > 31) && (val != ALL_SPEC_INT)
+ && (val != NO_SPEC_INT)) {
+ throw new ParseException(
+ "Day of month values must be between 1 and 31", -1);
+ }
+ } else if (type == MONTH) {
+ if ((val < 1 || val > 12 || end > 12) && (val != ALL_SPEC_INT)) {
+ throw new ParseException(
+ "Month values must be between 1 and 12", -1);
+ }
+ } else if (type == DAY_OF_WEEK) {
+ if ((val == 0 || val > 7 || end > 7) && (val != ALL_SPEC_INT)
+ && (val != NO_SPEC_INT)) {
+ throw new ParseException(
+ "Day-of-Week values must be between 1 and 7", -1);
+ }
+ }
+
+ if ((incr == 0 || incr == -1) && val != ALL_SPEC_INT) {
+ if (val != -1) {
+ set.add(val);
+ } else {
+ set.add(NO_SPEC);
+ }
+
+ return;
+ }
+
+ int startAt = val;
+ int stopAt = end;
+
+ if (val == ALL_SPEC_INT && incr <= 0) {
+ incr = 1;
+ set.add(ALL_SPEC); // put in a marker, but also fill values
+ }
+
+ if (type == SECOND || type == MINUTE) {
+ if (stopAt == -1) {
+ stopAt = 59;
+ }
+ if (startAt == -1 || startAt == ALL_SPEC_INT) {
+ startAt = 0;
+ }
+ } else if (type == HOUR) {
+ if (stopAt == -1) {
+ stopAt = 23;
+ }
+ if (startAt == -1 || startAt == ALL_SPEC_INT) {
+ startAt = 0;
+ }
+ } else if (type == DAY_OF_MONTH) {
+ if (stopAt == -1) {
+ stopAt = 31;
+ }
+ if (startAt == -1 || startAt == ALL_SPEC_INT) {
+ startAt = 1;
+ }
+ } else if (type == MONTH) {
+ if (stopAt == -1) {
+ stopAt = 12;
+ }
+ if (startAt == -1 || startAt == ALL_SPEC_INT) {
+ startAt = 1;
+ }
+ } else if (type == DAY_OF_WEEK) {
+ if (stopAt == -1) {
+ stopAt = 7;
+ }
+ if (startAt == -1 || startAt == ALL_SPEC_INT) {
+ startAt = 1;
+ }
+ } else if (type == YEAR) {
+ if (stopAt == -1) {
+ stopAt = MAX_YEAR;
+ }
+ if (startAt == -1 || startAt == ALL_SPEC_INT) {
+ startAt = 1970;
+ }
+ }
+
+ // if the end of the range is before the start, then we need to overflow into
+ // the next day, month etc. This is done by adding the maximum amount for that
+ // type, and using modulus max to determine the value being added.
+ int max = -1;
+ if (stopAt < startAt) {
+ switch (type) {
+ case SECOND : max = 60; break;
+ case MINUTE : max = 60; break;
+ case HOUR : max = 24; break;
+ case MONTH : max = 12; break;
+ case DAY_OF_WEEK : max = 7; break;
+ case DAY_OF_MONTH : max = 31; break;
+ case YEAR : throw new IllegalArgumentException("Start year must be less than stop year");
+ default : throw new IllegalArgumentException("Unexpected type encountered");
+ }
+ stopAt += max;
+ }
+
+ for (int i = startAt; i <= stopAt; i += incr) {
+ if (max == -1) {
+ // ie: there's no max to overflow over
+ set.add(i);
+ } else {
+ // take the modulus to get the real value
+ int i2 = i % max;
+
+ // 1-indexed ranges should not include 0, and should include their max
+ if (i2 == 0 && (type == MONTH || type == DAY_OF_WEEK || type == DAY_OF_MONTH) ) {
+ i2 = max;
+ }
+
+ set.add(i2);
+ }
+ }
+ }
+
+ TreeSet getSet(int type) {
+ switch (type) {
+ case SECOND:
+ return seconds;
+ case MINUTE:
+ return minutes;
+ case HOUR:
+ return hours;
+ case DAY_OF_MONTH:
+ return daysOfMonth;
+ case MONTH:
+ return months;
+ case DAY_OF_WEEK:
+ return daysOfWeek;
+ case YEAR:
+ return years;
+ default:
+ return null;
+ }
+ }
+
+ protected ValueSet getValue(int v, String s, int i) {
+ char c = s.charAt(i);
+ StringBuilder s1 = new StringBuilder(String.valueOf(v));
+ while (c >= '0' && c <= '9') {
+ s1.append(c);
+ i++;
+ if (i >= s.length()) {
+ break;
+ }
+ c = s.charAt(i);
+ }
+ ValueSet val = new ValueSet();
+
+ val.pos = (i < s.length()) ? i : i + 1;
+ val.value = Integer.parseInt(s1.toString());
+ return val;
+ }
+
+ protected int getNumericValue(String s, int i) {
+ int endOfVal = findNextWhiteSpace(i, s);
+ String val = s.substring(i, endOfVal);
+ return Integer.parseInt(val);
+ }
+
+ protected int getMonthNumber(String s) {
+ Integer integer = monthMap.get(s);
+
+ if (integer == null) {
+ return -1;
+ }
+
+ return integer;
+ }
+
+ protected int getDayOfWeekNumber(String s) {
+ Integer integer = dayMap.get(s);
+
+ if (integer == null) {
+ return -1;
+ }
+
+ return integer;
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // Computation Functions
+ //
+ ////////////////////////////////////////////////////////////////////////////
+
+ public Date getTimeAfter(Date afterTime) {
+
+ // Computation is based on Gregorian year only.
+ Calendar cl = new java.util.GregorianCalendar(getTimeZone());
+
+ // move ahead one second, since we're computing the time *after* the
+ // given time
+ afterTime = new Date(afterTime.getTime() + 1000);
+ // CronTrigger does not deal with milliseconds
+ cl.setTime(afterTime);
+ cl.set(Calendar.MILLISECOND, 0);
+
+ boolean gotOne = false;
+ // loop until we've computed the next time, or we've past the endTime
+ while (!gotOne) {
+
+ //if (endTime != null && cl.getTime().after(endTime)) return null;
+ if(cl.get(Calendar.YEAR) > 2999) { // prevent endless loop...
+ return null;
+ }
+
+ SortedSet st = null;
+ int t = 0;
+
+ int sec = cl.get(Calendar.SECOND);
+ int min = cl.get(Calendar.MINUTE);
+
+ // get second.................................................
+ st = seconds.tailSet(sec);
+ if (st != null && st.size() != 0) {
+ sec = st.first();
+ } else {
+ sec = seconds.first();
+ min++;
+ cl.set(Calendar.MINUTE, min);
+ }
+ cl.set(Calendar.SECOND, sec);
+
+ min = cl.get(Calendar.MINUTE);
+ int hr = cl.get(Calendar.HOUR_OF_DAY);
+ t = -1;
+
+ // get minute.................................................
+ st = minutes.tailSet(min);
+ if (st != null && st.size() != 0) {
+ t = min;
+ min = st.first();
+ } else {
+ min = minutes.first();
+ hr++;
+ }
+ if (min != t) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, min);
+ setCalendarHour(cl, hr);
+ continue;
+ }
+ cl.set(Calendar.MINUTE, min);
+
+ hr = cl.get(Calendar.HOUR_OF_DAY);
+ int day = cl.get(Calendar.DAY_OF_MONTH);
+ t = -1;
+
+ // get hour...................................................
+ st = hours.tailSet(hr);
+ if (st != null && st.size() != 0) {
+ t = hr;
+ hr = st.first();
+ } else {
+ hr = hours.first();
+ day++;
+ }
+ if (hr != t) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.DAY_OF_MONTH, day);
+ setCalendarHour(cl, hr);
+ continue;
+ }
+ cl.set(Calendar.HOUR_OF_DAY, hr);
+
+ day = cl.get(Calendar.DAY_OF_MONTH);
+ int mon = cl.get(Calendar.MONTH) + 1;
+ // '+ 1' because calendar is 0-based for this field, and we are
+ // 1-based
+ t = -1;
+ int tmon = mon;
+
+ // get day...................................................
+ boolean dayOfMSpec = !daysOfMonth.contains(NO_SPEC);
+ boolean dayOfWSpec = !daysOfWeek.contains(NO_SPEC);
+ if (dayOfMSpec && !dayOfWSpec) { // get day by day of month rule
+ st = daysOfMonth.tailSet(day);
+ if (lastdayOfMonth) {
+ if(!nearestWeekday) {
+ t = day;
+ day = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+ day -= lastdayOffset;
+ if(t > day) {
+ mon++;
+ if(mon > 12) {
+ mon = 1;
+ tmon = 3333; // ensure test of mon != tmon further below fails
+ cl.add(Calendar.YEAR, 1);
+ }
+ day = 1;
+ }
+ } else {
+ t = day;
+ day = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+ day -= lastdayOffset;
+
+ java.util.Calendar tcal = java.util.Calendar.getInstance(getTimeZone());
+ tcal.set(Calendar.SECOND, 0);
+ tcal.set(Calendar.MINUTE, 0);
+ tcal.set(Calendar.HOUR_OF_DAY, 0);
+ tcal.set(Calendar.DAY_OF_MONTH, day);
+ tcal.set(Calendar.MONTH, mon - 1);
+ tcal.set(Calendar.YEAR, cl.get(Calendar.YEAR));
+
+ int ldom = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+ int dow = tcal.get(Calendar.DAY_OF_WEEK);
+
+ if(dow == Calendar.SATURDAY && day == 1) {
+ day += 2;
+ } else if(dow == Calendar.SATURDAY) {
+ day -= 1;
+ } else if(dow == Calendar.SUNDAY && day == ldom) {
+ day -= 2;
+ } else if(dow == Calendar.SUNDAY) {
+ day += 1;
+ }
+
+ tcal.set(Calendar.SECOND, sec);
+ tcal.set(Calendar.MINUTE, min);
+ tcal.set(Calendar.HOUR_OF_DAY, hr);
+ tcal.set(Calendar.DAY_OF_MONTH, day);
+ tcal.set(Calendar.MONTH, mon - 1);
+ Date nTime = tcal.getTime();
+ if(nTime.before(afterTime)) {
+ day = 1;
+ mon++;
+ }
+ }
+ } else if(nearestWeekday) {
+ t = day;
+ day = daysOfMonth.first();
+
+ java.util.Calendar tcal = java.util.Calendar.getInstance(getTimeZone());
+ tcal.set(Calendar.SECOND, 0);
+ tcal.set(Calendar.MINUTE, 0);
+ tcal.set(Calendar.HOUR_OF_DAY, 0);
+ tcal.set(Calendar.DAY_OF_MONTH, day);
+ tcal.set(Calendar.MONTH, mon - 1);
+ tcal.set(Calendar.YEAR, cl.get(Calendar.YEAR));
+
+ int ldom = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+ int dow = tcal.get(Calendar.DAY_OF_WEEK);
+
+ if(dow == Calendar.SATURDAY && day == 1) {
+ day += 2;
+ } else if(dow == Calendar.SATURDAY) {
+ day -= 1;
+ } else if(dow == Calendar.SUNDAY && day == ldom) {
+ day -= 2;
+ } else if(dow == Calendar.SUNDAY) {
+ day += 1;
+ }
+
+
+ tcal.set(Calendar.SECOND, sec);
+ tcal.set(Calendar.MINUTE, min);
+ tcal.set(Calendar.HOUR_OF_DAY, hr);
+ tcal.set(Calendar.DAY_OF_MONTH, day);
+ tcal.set(Calendar.MONTH, mon - 1);
+ Date nTime = tcal.getTime();
+ if(nTime.before(afterTime)) {
+ day = daysOfMonth.first();
+ mon++;
+ }
+ } else if (st != null && st.size() != 0) {
+ t = day;
+ day = st.first();
+ // make sure we don't over-run a short month, such as february
+ int lastDay = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+ if (day > lastDay) {
+ day = daysOfMonth.first();
+ mon++;
+ }
+ } else {
+ day = daysOfMonth.first();
+ mon++;
+ }
+
+ if (day != t || mon != tmon) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, day);
+ cl.set(Calendar.MONTH, mon - 1);
+ // '- 1' because calendar is 0-based for this field, and we
+ // are 1-based
+ continue;
+ }
+ } else if (dayOfWSpec && !dayOfMSpec) { // get day by day of week rule
+ if (lastdayOfWeek) { // are we looking for the last XXX day of
+ // the month?
+ int dow = daysOfWeek.first(); // desired
+ // d-o-w
+ int cDow = cl.get(Calendar.DAY_OF_WEEK); // current d-o-w
+ int daysToAdd = 0;
+ if (cDow < dow) {
+ daysToAdd = dow - cDow;
+ }
+ if (cDow > dow) {
+ daysToAdd = dow + (7 - cDow);
+ }
+
+ int lDay = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+
+ if (day + daysToAdd > lDay) { // did we already miss the
+ // last one?
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, 1);
+ cl.set(Calendar.MONTH, mon);
+ // no '- 1' here because we are promoting the month
+ continue;
+ }
+
+ // find date of last occurrence of this day in this month...
+ while ((day + daysToAdd + 7) <= lDay) {
+ daysToAdd += 7;
+ }
+
+ day += daysToAdd;
+
+ if (daysToAdd > 0) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, day);
+ cl.set(Calendar.MONTH, mon - 1);
+ // '- 1' here because we are not promoting the month
+ continue;
+ }
+
+ } else if (nthdayOfWeek != 0) {
+ // are we looking for the Nth XXX day in the month?
+ int dow = daysOfWeek.first(); // desired
+ // d-o-w
+ int cDow = cl.get(Calendar.DAY_OF_WEEK); // current d-o-w
+ int daysToAdd = 0;
+ if (cDow < dow) {
+ daysToAdd = dow - cDow;
+ } else if (cDow > dow) {
+ daysToAdd = dow + (7 - cDow);
+ }
+
+ boolean dayShifted = false;
+ if (daysToAdd > 0) {
+ dayShifted = true;
+ }
+
+ day += daysToAdd;
+ int weekOfMonth = day / 7;
+ if (day % 7 > 0) {
+ weekOfMonth++;
+ }
+
+ daysToAdd = (nthdayOfWeek - weekOfMonth) * 7;
+ day += daysToAdd;
+ if (daysToAdd < 0
+ || day > getLastDayOfMonth(mon, cl
+ .get(Calendar.YEAR))) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, 1);
+ cl.set(Calendar.MONTH, mon);
+ // no '- 1' here because we are promoting the month
+ continue;
+ } else if (daysToAdd > 0 || dayShifted) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, day);
+ cl.set(Calendar.MONTH, mon - 1);
+ // '- 1' here because we are NOT promoting the month
+ continue;
+ }
+ } else {
+ int cDow = cl.get(Calendar.DAY_OF_WEEK); // current d-o-w
+ int dow = daysOfWeek.first(); // desired
+ // d-o-w
+ st = daysOfWeek.tailSet(cDow);
+ if (st != null && st.size() > 0) {
+ dow = st.first();
+ }
+
+ int daysToAdd = 0;
+ if (cDow < dow) {
+ daysToAdd = dow - cDow;
+ }
+ if (cDow > dow) {
+ daysToAdd = dow + (7 - cDow);
+ }
+
+ int lDay = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+
+ if (day + daysToAdd > lDay) { // will we pass the end of
+ // the month?
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, 1);
+ cl.set(Calendar.MONTH, mon);
+ // no '- 1' here because we are promoting the month
+ continue;
+ } else if (daysToAdd > 0) { // are we swithing days?
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, day + daysToAdd);
+ cl.set(Calendar.MONTH, mon - 1);
+ // '- 1' because calendar is 0-based for this field,
+ // and we are 1-based
+ continue;
+ }
+ }
+ } else { // dayOfWSpec && !dayOfMSpec
+ throw new UnsupportedOperationException(
+ "Support for specifying both a day-of-week AND a day-of-month parameter is not implemented.");
+ }
+ cl.set(Calendar.DAY_OF_MONTH, day);
+
+ mon = cl.get(Calendar.MONTH) + 1;
+ // '+ 1' because calendar is 0-based for this field, and we are
+ // 1-based
+ int year = cl.get(Calendar.YEAR);
+ t = -1;
+
+ // test for expressions that never generate a valid fire date,
+ // but keep looping...
+ if (year > MAX_YEAR) {
+ return null;
+ }
+
+ // get month...................................................
+ st = months.tailSet(mon);
+ if (st != null && st.size() != 0) {
+ t = mon;
+ mon = st.first();
+ } else {
+ mon = months.first();
+ year++;
+ }
+ if (mon != t) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, 1);
+ cl.set(Calendar.MONTH, mon - 1);
+ // '- 1' because calendar is 0-based for this field, and we are
+ // 1-based
+ cl.set(Calendar.YEAR, year);
+ continue;
+ }
+ cl.set(Calendar.MONTH, mon - 1);
+ // '- 1' because calendar is 0-based for this field, and we are
+ // 1-based
+
+ year = cl.get(Calendar.YEAR);
+ t = -1;
+
+ // get year...................................................
+ st = years.tailSet(year);
+ if (st != null && st.size() != 0) {
+ t = year;
+ year = st.first();
+ } else {
+ return null; // ran out of years...
+ }
+
+ if (year != t) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, 1);
+ cl.set(Calendar.MONTH, 0);
+ // '- 1' because calendar is 0-based for this field, and we are
+ // 1-based
+ cl.set(Calendar.YEAR, year);
+ continue;
+ }
+ cl.set(Calendar.YEAR, year);
+
+ gotOne = true;
+ } // while( !done )
+
+ return cl.getTime();
+ }
+
+ /**
+ * Advance the calendar to the particular hour paying particular attention
+ * to daylight saving problems.
+ *
+ * @param cal the calendar to operate on
+ * @param hour the hour to set
+ */
+ protected void setCalendarHour(Calendar cal, int hour) {
+ cal.set(java.util.Calendar.HOUR_OF_DAY, hour);
+ if (cal.get(java.util.Calendar.HOUR_OF_DAY) != hour && hour != 24) {
+ cal.set(java.util.Calendar.HOUR_OF_DAY, hour + 1);
+ }
+ }
+
+ /**
+ * NOT YET IMPLEMENTED: Returns the time before the given time
+ * that the CronExpression matches.
+ */
+ public Date getTimeBefore(Date endTime) {
+ // FUTURE_TODO: implement QUARTZ-423
+ return null;
+ }
+
+ /**
+ * NOT YET IMPLEMENTED: Returns the final time that the
+ * CronExpression will match.
+ */
+ public Date getFinalFireTime() {
+ // FUTURE_TODO: implement QUARTZ-423
+ return null;
+ }
+
+ protected boolean isLeapYear(int year) {
+ return ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0));
+ }
+
+ protected int getLastDayOfMonth(int monthNum, int year) {
+
+ switch (monthNum) {
+ case 1:
+ return 31;
+ case 2:
+ return (isLeapYear(year)) ? 29 : 28;
+ case 3:
+ return 31;
+ case 4:
+ return 30;
+ case 5:
+ return 31;
+ case 6:
+ return 30;
+ case 7:
+ return 31;
+ case 8:
+ return 31;
+ case 9:
+ return 30;
+ case 10:
+ return 31;
+ case 11:
+ return 30;
+ case 12:
+ return 31;
+ default:
+ throw new IllegalArgumentException("Illegal month number: "
+ + monthNum);
+ }
+ }
+
+
+ private void readObject(java.io.ObjectInputStream stream)
+ throws java.io.IOException, ClassNotFoundException {
+
+ stream.defaultReadObject();
+ try {
+ buildExpression(cronExpression);
+ } catch (Exception ignore) {
+ } // never happens
+ }
+
+ @Override
+ @Deprecated
+ public Object clone() {
+ return new CronExpression(this);
+ }
+}
+
+class ValueSet {
+ public int value;
+
+ public int pos;
+}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/exception/XxlJobException.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/exception/XxlJobException.java
new file mode 100644
index 0000000..345c150
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/exception/XxlJobException.java
@@ -0,0 +1,15 @@
+package com.xxl.job.admin.core.exception;
+
+/**
+ * @author xuxueli 2019-05-04 23:19:29
+ */
+public class XxlJobException extends RuntimeException {
+
+ public XxlJobException() {
+ }
+ public XxlJobException(String message) {
+ super(message);
+ }
+
+}
+
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobLogGlue.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobLogGlue.java
new file mode 100644
index 0000000..cfe2d2e
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobLogGlue.java
@@ -0,0 +1,34 @@
+package com.xxl.job.admin.core.model;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.util.Date;
+
+/**
+ * xxl-job log for glue, used to track job code process
+ * @author xuxueli 2016-5-19 17:57:46
+ */
+@Data
+@TableName("xxl_job_logglue")
+public class XxlJobLogGlue {
+ @TableId(type = IdType.ASSIGN_ID)
+ private String id;
+ @TableField("JOB_ID")
+ private String jobId; // 任务主键ID
+ @TableField("GLUE_TYPE")
+ private String glueType; // GLUE类型 #com.xxl.job.core.glue.GlueTypeEnum
+ @TableField("GLUE_SOURCE")
+ private String glueSource;
+ @TableField("GLUE_REMARK")
+ private String glueRemark;
+ @TableField("ADD_TIME")
+ private Date addTime;
+ @TableField("UPDATE_TIME")
+ private Date updateTime;
+
+}
+
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobLogReport.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobLogReport.java
new file mode 100644
index 0000000..19d6c1d
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobLogReport.java
@@ -0,0 +1,35 @@
+package com.xxl.job.admin.core.model;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.util.Date;
+
+@Data
+@TableName("xxl_job_log_report")
+public class XxlJobLogReport {
+
+ @TableId(type = IdType.ASSIGN_ID)
+ private String id;
+
+ @TableField(value = "TRIGGER_DAY")
+ private Date triggerDay;
+
+
+ @TableField(value = "RUNNING_COUNT")
+ private int runningCount;
+
+ @TableField(value = "SUC_COUNT")
+ private int sucCount;
+
+ @TableField(value = "FAIL_COUNT")
+ private int failCount;
+
+ @TableField(value = "UPDATE_TIME")
+ private Date updateTime;
+
+}
+
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobRegistry.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobRegistry.java
new file mode 100644
index 0000000..3d184e0
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobRegistry.java
@@ -0,0 +1,30 @@
+package com.xxl.job.admin.core.model;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.util.Date;
+
+/**
+ * Created by xuxueli on 16/9/30.
+ */
+@Data
+@TableName("xxl_job_registry")
+public class XxlJobRegistry {
+
+ @TableId(type = IdType.ASSIGN_ID)
+ private String id;
+ @TableField("REGISTRY_GROUP")
+ private String registryGroup;
+ @TableField("REGISTRY_KEY")
+ private String registryKey;
+ @TableField("REGISTRY_VALUE")
+ private String registryValue;
+ @TableField("UPDATE_TIME")
+ private Date updateTime;
+
+}
+
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobUser.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobUser.java
new file mode 100644
index 0000000..58c0c8b
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobUser.java
@@ -0,0 +1,46 @@
+package com.xxl.job.admin.core.model;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import org.springframework.util.StringUtils;
+
+/**
+ * @author xuxueli 2019-05-04 16:43:12
+ */
+@Data
+@TableName("xxl_job_user")
+public class XxlJobUser {
+
+ @TableId(type = IdType.ASSIGN_ID)
+ private String id;
+ @TableField("USERNAME")
+ private String username; // 账号
+ @TableField("PASSWORD")
+ private String password; // 密码
+ @TableField("ROLE")
+ private int role; // 角色:0-普通用户、1-管理员
+ @TableField("PERMISSION")
+ private String permission; // 权限:执行器ID列表,多个逗号分割
+
+ // plugin
+ public boolean validPermission(String jobGroup){
+ if (this.role == 1) {
+ return true;
+ } else {
+ if (StringUtils.hasText(this.permission)) {
+ for (String permissionItem : this.permission.split(",")) {
+ if (jobGroup.equals(permissionItem)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ }
+
+}
+
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/ExecutorRouteStrategyEnum.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/ExecutorRouteStrategyEnum.java
new file mode 100644
index 0000000..1315427
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/ExecutorRouteStrategyEnum.java
@@ -0,0 +1,49 @@
+package com.xxl.job.admin.core.route;
+
+import com.xxl.job.admin.core.route.strategy.*;
+import com.xxl.job.admin.core.util.I18nUtil;
+
+/**
+ * Created by xuxueli on 17/3/10.
+ */
+public enum ExecutorRouteStrategyEnum {
+
+ FIRST(I18nUtil.getString("jobconf_route_first"), new ExecutorRouteFirst()),
+ LAST(I18nUtil.getString("jobconf_route_last"), new ExecutorRouteLast()),
+ ROUND(I18nUtil.getString("jobconf_route_round"), new ExecutorRouteRound()),
+ RANDOM(I18nUtil.getString("jobconf_route_random"), new ExecutorRouteRandom()),
+ CONSISTENT_HASH(I18nUtil.getString("jobconf_route_consistenthash"), new ExecutorRouteConsistentHash()),
+ LEAST_FREQUENTLY_USED(I18nUtil.getString("jobconf_route_lfu"), new ExecutorRouteLFU()),
+ LEAST_RECENTLY_USED(I18nUtil.getString("jobconf_route_lru"), new ExecutorRouteLRU()),
+ FAILOVER(I18nUtil.getString("jobconf_route_failover"), new ExecutorRouteFailover()),
+ BUSYOVER(I18nUtil.getString("jobconf_route_busyover"), new ExecutorRouteBusyover()),
+ SHARDING_BROADCAST(I18nUtil.getString("jobconf_route_shard"), null);
+
+ ExecutorRouteStrategyEnum(String title, ExecutorRouter router) {
+ this.title = title;
+ this.router = router;
+ }
+
+ private String title;
+ private ExecutorRouter router;
+
+ public String getTitle() {
+ return title;
+ }
+ public ExecutorRouter getRouter() {
+ return router;
+ }
+
+ public static ExecutorRouteStrategyEnum match(String name, ExecutorRouteStrategyEnum defaultItem){
+ if (name != null) {
+ for (ExecutorRouteStrategyEnum item: ExecutorRouteStrategyEnum.values()) {
+ if (item.name().equals(name)) {
+ return item;
+ }
+ }
+ }
+ return defaultItem;
+ }
+
+}
+
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/ExecutorRouter.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/ExecutorRouter.java
new file mode 100644
index 0000000..d790340
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/ExecutorRouter.java
@@ -0,0 +1,25 @@
+package com.xxl.job.admin.core.route;
+
+import com.xxl.job.core.biz.model.ReturnT;
+import com.xxl.job.core.biz.model.TriggerParam;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+
+/**
+ * Created by xuxueli on 17/3/10.
+ */
+public abstract class ExecutorRouter {
+ protected static Logger logger = LoggerFactory.getLogger(ExecutorRouter.class);
+
+ /**
+ * route address
+ *
+ * @param addressList
+ * @return ReturnT.content=address
+ */
+ public abstract ReturnT route(TriggerParam triggerParam, List addressList);
+
+}
+
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteBusyover.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteBusyover.java
new file mode 100644
index 0000000..c67f14e
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteBusyover.java
@@ -0,0 +1,49 @@
+package com.xxl.job.admin.core.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 java.util.List;
+
+/**
+ * Created by xuxueli on 17/3/10.
+ */
+public class ExecutorRouteBusyover extends ExecutorRouter {
+
+ @Override
+ public ReturnT route(TriggerParam triggerParam, List addressList) {
+ StringBuffer idleBeatResultSB = new StringBuffer();
+ for (String address : addressList) {
+ // beat
+ ReturnT idleBeatResult = null;
+ try {
+ ExecutorBiz executorBiz = XxlJobScheduler.getExecutorBiz(address);
+ idleBeatResult = executorBiz.idleBeat(new IdleBeatParam(triggerParam.getJobId()));
+ } catch (Exception e) {
+ logger.error(e.getMessage(), e);
+ idleBeatResult = new ReturnT(ReturnT.FAIL_CODE, ""+e );
+ }
+ idleBeatResultSB.append( (idleBeatResultSB.length()>0)?"
":"")
+ .append(I18nUtil.getString("jobconf_idleBeat") + ":")
+ .append("
address:").append(address)
+ .append("
code:").append(idleBeatResult.getCode())
+ .append("
msg:").append(idleBeatResult.getMsg());
+
+ // beat success
+ if (idleBeatResult.getCode() == ReturnT.SUCCESS_CODE) {
+ idleBeatResult.setMsg(idleBeatResultSB.toString());
+ idleBeatResult.setContent(address);
+ return idleBeatResult;
+ }
+ }
+
+ return new ReturnT(ReturnT.FAIL_CODE, idleBeatResultSB.toString());
+ }
+
+}
+
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteConsistentHash.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteConsistentHash.java
new file mode 100644
index 0000000..90acdd6
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteConsistentHash.java
@@ -0,0 +1,86 @@
+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.io.UnsupportedEncodingException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.List;
+import java.util.SortedMap;
+import java.util.TreeMap;
+
+/**
+ * 分组下机器地址相同,不同JOB均匀散列在不同机器上,保证分组下机器分配JOB平均;且每个JOB固定调度其中一台机器;
+ * a、virtual node:解决不均衡问题
+ * b、hash method replace hashCode:String的hashCode可能重复,需要进一步扩大hashCode的取值范围
+ * Created by xuxueli on 17/3/10.
+ */
+public class ExecutorRouteConsistentHash extends ExecutorRouter {
+
+ private static int VIRTUAL_NODE_NUM = 100;
+
+ /**
+ * get hash code on 2^32 ring (md5散列的方式计算hash值)
+ * @param key
+ * @return
+ */
+ private static long hash(String key) {
+
+ // md5 byte
+ MessageDigest md5;
+ try {
+ md5 = MessageDigest.getInstance("MD5");
+ } catch (NoSuchAlgorithmException e) {
+ throw new RuntimeException("MD5 not supported", e);
+ }
+ md5.reset();
+ byte[] keyBytes = null;
+ try {
+ keyBytes = key.getBytes("UTF-8");
+ } catch (UnsupportedEncodingException e) {
+ throw new RuntimeException("Unknown string :" + key, e);
+ }
+
+ md5.update(keyBytes);
+ byte[] digest = md5.digest();
+
+ // hash code, Truncate to 32-bits
+ long hashCode = ((long) (digest[3] & 0xFF) << 24)
+ | ((long) (digest[2] & 0xFF) << 16)
+ | ((long) (digest[1] & 0xFF) << 8)
+ | (digest[0] & 0xFF);
+
+ long truncateHashCode = hashCode & 0xffffffffL;
+ return truncateHashCode;
+ }
+
+ public String hashJob(String jobId, List addressList) {
+
+ // ------A1------A2-------A3------
+ // -----------J1------------------
+ TreeMap addressRing = new TreeMap();
+ for (String address: addressList) {
+ for (int i = 0; i < VIRTUAL_NODE_NUM; i++) {
+ long addressHash = hash("SHARD-" + address + "-NODE-" + i);
+ addressRing.put(addressHash, address);
+ }
+ }
+
+ long jobHash = hash(String.valueOf(jobId));
+ SortedMap lastRing = addressRing.tailMap(jobHash);
+ if (!lastRing.isEmpty()) {
+ return lastRing.get(lastRing.firstKey());
+ }
+ return addressRing.firstEntry().getValue();
+ }
+
+ @Override
+ public ReturnT route(TriggerParam triggerParam, List addressList) {
+ String address = hashJob(triggerParam.getJobId(), addressList);
+ return new ReturnT(address);
+ }
+
+}
+
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteFailover.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteFailover.java
new file mode 100644
index 0000000..ea22a5b
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteFailover.java
@@ -0,0 +1,49 @@
+package com.xxl.job.admin.core.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 java.util.List;
+
+/**
+ * Created by xuxueli on 17/3/10.
+ */
+public class ExecutorRouteFailover extends ExecutorRouter {
+
+ @Override
+ public ReturnT route(TriggerParam triggerParam, List addressList) {
+
+ StringBuffer beatResultSB = new StringBuffer();
+ for (String address : addressList) {
+ // beat
+ ReturnT beatResult = null;
+ try {
+ ExecutorBiz executorBiz = XxlJobScheduler.getExecutorBiz(address);
+ beatResult = executorBiz.beat();
+ } catch (Exception e) {
+ logger.error(e.getMessage(), e);
+ beatResult = new ReturnT(ReturnT.FAIL_CODE, ""+e );
+ }
+ beatResultSB.append( (beatResultSB.length()>0)?"
":"")
+ .append(I18nUtil.getString("jobconf_beat") + ":")
+ .append("
address:").append(address)
+ .append("
code:").append(beatResult.getCode())
+ .append("
msg:").append(beatResult.getMsg());
+
+ // beat success
+ if (beatResult.getCode() == ReturnT.SUCCESS_CODE) {
+
+ beatResult.setMsg(beatResultSB.toString());
+ beatResult.setContent(address);
+ return beatResult;
+ }
+ }
+ return new ReturnT(ReturnT.FAIL_CODE, beatResultSB.toString());
+
+ }
+}
+
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteFirst.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteFirst.java
new file mode 100644
index 0000000..aa2a954
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteFirst.java
@@ -0,0 +1,20 @@
+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 route(TriggerParam triggerParam, List addressList){
+ return new ReturnT(addressList.get(0));
+ }
+
+}
+
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteLFU.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteLFU.java
new file mode 100644
index 0000000..ed8c936
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteLFU.java
@@ -0,0 +1,78 @@
+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.*;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+/**
+ * 单个JOB对应的每个执行器,使用频率最低的优先被选举
+ * a(*)、LFU(Least Frequently Used):最不经常使用,频率/次数
+ * b、LRU(Least Recently Used):最近最久未使用,时间
+ *
+ * Created by xuxueli on 17/3/10.
+ */
+public class ExecutorRouteLFU extends ExecutorRouter {
+
+ private static ConcurrentMap> jobLfuMap = new ConcurrentHashMap<>();
+ private static long CACHE_VALID_TIME = 0;
+
+ public String route(String jobId, List addressList) {
+
+ // cache clear
+ if (System.currentTimeMillis() > CACHE_VALID_TIME) {
+ jobLfuMap.clear();
+ CACHE_VALID_TIME = System.currentTimeMillis() + 1000 * 60 * 60 * 24;
+ }
+
+ // lfu item init
+ HashMap lfuItemMap = jobLfuMap.get(jobId); // Key排序可以用TreeMap+构造入参Compare;Value排序暂时只能通过ArrayList;
+ if (lfuItemMap == null) {
+ lfuItemMap = new HashMap();
+ jobLfuMap.putIfAbsent(jobId, lfuItemMap); // 避免重复覆盖
+ }
+
+ // put new
+ for (String address : addressList) {
+ if (!lfuItemMap.containsKey(address) || lfuItemMap.get(address) > 1000000) {
+ lfuItemMap.put(address, new Random().nextInt(addressList.size())); // 初始化时主动Random一次,缓解首次压力
+ }
+ }
+ // remove old
+ List delKeys = new ArrayList<>();
+ for (String existKey : lfuItemMap.keySet()) {
+ if (!addressList.contains(existKey)) {
+ delKeys.add(existKey);
+ }
+ }
+ if (delKeys.size() > 0) {
+ for (String delKey : delKeys) {
+ lfuItemMap.remove(delKey);
+ }
+ }
+
+ // load least userd count address
+ List> lfuItemList = new ArrayList>(lfuItemMap.entrySet());
+ Collections.sort(lfuItemList, new Comparator>() {
+ @Override
+ public int compare(Map.Entry o1, Map.Entry o2) {
+ return o1.getValue().compareTo(o2.getValue());
+ }
+ });
+
+ Map.Entry addressItem = lfuItemList.get(0);
+ addressItem.setValue(addressItem.getValue() + 1);
+
+ return addressItem.getKey();
+ }
+
+ @Override
+ public ReturnT route(TriggerParam triggerParam, List addressList) {
+ String address = route(triggerParam.getJobId(), addressList);
+ return new ReturnT(address);
+ }
+
+}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteLRU.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteLRU.java
new file mode 100644
index 0000000..42df95d
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteLRU.java
@@ -0,0 +1,77 @@
+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.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+/**
+ * 单个JOB对应的每个执行器,最久为使用的优先被选举
+ * a、LFU(Least Frequently Used):最不经常使用,频率/次数
+ * b(*)、LRU(Least Recently Used):最近最久未使用,时间
+ *
+ * Created by xuxueli on 17/3/10.
+ */
+public class ExecutorRouteLRU extends ExecutorRouter {
+
+ private static ConcurrentMap> jobLRUMap = new ConcurrentHashMap<>();
+ private static long CACHE_VALID_TIME = 0;
+
+ public String route(String jobId, List addressList) {
+
+ // cache clear
+ if (System.currentTimeMillis() > CACHE_VALID_TIME) {
+ jobLRUMap.clear();
+ CACHE_VALID_TIME = System.currentTimeMillis() + 1000*60*60*24;
+ }
+
+ // init lru
+ LinkedHashMap lruItem = jobLRUMap.get(jobId);
+ if (lruItem == null) {
+ /**
+ * LinkedHashMap
+ * a、accessOrder:true=访问顺序排序(get/put时排序);false=插入顺序排期;
+ * b、removeEldestEntry:新增元素时将会调用,返回true时会删除最老元素;可封装LinkedHashMap并重写该方法,比如定义最大容量,超出是返回true即可实现固定长度的LRU算法;
+ */
+ lruItem = new LinkedHashMap(16, 0.75f, true);
+ jobLRUMap.putIfAbsent(jobId, lruItem);
+ }
+
+ // put new
+ for (String address: addressList) {
+ if (!lruItem.containsKey(address)) {
+ lruItem.put(address, address);
+ }
+ }
+ // remove old
+ List delKeys = new ArrayList<>();
+ for (String existKey: lruItem.keySet()) {
+ if (!addressList.contains(existKey)) {
+ delKeys.add(existKey);
+ }
+ }
+ if (delKeys.size() > 0) {
+ for (String delKey: delKeys) {
+ lruItem.remove(delKey);
+ }
+ }
+
+ // load
+ String eldestKey = lruItem.entrySet().iterator().next().getKey();
+ String eldestValue = lruItem.get(eldestKey);
+ return eldestValue;
+ }
+
+ @Override
+ public ReturnT route(TriggerParam triggerParam, List addressList) {
+ String address = route(triggerParam.getJobId(), addressList);
+ return new ReturnT(address);
+ }
+
+}
+
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteLast.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteLast.java
new file mode 100644
index 0000000..8c1ca42
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteLast.java
@@ -0,0 +1,20 @@
+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 route(TriggerParam triggerParam, List addressList) {
+ return new ReturnT(addressList.get(addressList.size()-1));
+ }
+
+}
+
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteRandom.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteRandom.java
new file mode 100644
index 0000000..20d5d2c
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteRandom.java
@@ -0,0 +1,24 @@
+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 route(TriggerParam triggerParam, List addressList) {
+ String address = addressList.get(localRandom.nextInt(addressList.size()));
+ return new ReturnT(address);
+ }
+
+}
+
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteRound.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteRound.java
new file mode 100644
index 0000000..135580c
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteRound.java
@@ -0,0 +1,47 @@
+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;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * Created by xuxueli on 17/3/10.
+ */
+public class ExecutorRouteRound extends ExecutorRouter {
+
+ private static ConcurrentMap routeCountEachJob = new ConcurrentHashMap<>();
+ private static long CACHE_VALID_TIME = 0;
+
+ private static int count(String jobId) {
+ // cache clear
+ if (System.currentTimeMillis() > CACHE_VALID_TIME) {
+ routeCountEachJob.clear();
+ CACHE_VALID_TIME = System.currentTimeMillis() + 1000*60*60*24;
+ }
+
+ AtomicInteger count = routeCountEachJob.get(jobId);
+ if (count == null || count.get() > 1000000) {
+ // 初始化时主动Random一次,缓解首次压力
+ count = new AtomicInteger(new Random().nextInt(100));
+ } else {
+ // count++
+ count.addAndGet(1);
+ }
+ routeCountEachJob.put(jobId, count);
+ return count.get();
+ }
+
+ @Override
+ public ReturnT route(TriggerParam triggerParam, List addressList) {
+ String address = addressList.get(count(triggerParam.getJobId())%addressList.size());
+ return new ReturnT(address);
+ }
+
+}
+
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/scheduler/MisfireStrategyEnum.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/scheduler/MisfireStrategyEnum.java
new file mode 100644
index 0000000..e85399b
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/scheduler/MisfireStrategyEnum.java
@@ -0,0 +1,40 @@
+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;
+ }
+
+}
+
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/scheduler/ScheduleTypeEnum.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/scheduler/ScheduleTypeEnum.java
new file mode 100644
index 0000000..e6ab56c
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/scheduler/ScheduleTypeEnum.java
@@ -0,0 +1,47 @@
+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;
+ }
+
+}
+
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/scheduler/XxlJobScheduler.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/scheduler/XxlJobScheduler.java
new file mode 100644
index 0000000..43e215f
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/scheduler/XxlJobScheduler.java
@@ -0,0 +1,102 @@
+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 executorBizRepository = new ConcurrentHashMap();
+ 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());
+
+ executorBizRepository.put(address, executorBiz);
+ return executorBiz;
+ }
+
+}
+
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobCompleteHelper.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobCompleteHelper.java
new file mode 100644
index 0000000..840a017
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobCompleteHelper.java
@@ -0,0 +1,186 @@
+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.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;
+import com.yunzhupaas.scheduletask.entity.XxlJobLog;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Date;
+import java.util.List;
+import java.util.concurrent.*;
+
+/**
+ * job lose-monitor instance
+ *
+ * @author xuxueli 2015-9-1 18:05:56
+ */
+public class JobCompleteHelper {
+ private static 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;
+ public void start(){
+
+ // for callback
+ callbackThreadPool = new ThreadPoolExecutor(
+ 2,
+ 20,
+ 30L,
+ TimeUnit.SECONDS,
+ new LinkedBlockingQueue(3000),
+ new ThreadFactory() {
+ @Override
+ public Thread newThread(Runnable r) {
+ return new Thread(r, "xxl-job, admin JobLosedMonitorHelper-callbackThreadPool-" + r.hashCode());
+ }
+ },
+ new RejectedExecutionHandler() {
+ @Override
+ public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
+ r.run();
+ logger.warn(">>>>>>>>>>> xxl-job, callback too fast, match threadpool rejected handler(run now).");
+ }
+ });
+
+
+ // for monitor
+ monitorThread = new Thread(new Runnable() {
+
+ @Override
+ public void run() {
+
+ // wait for JobTriggerPoolHelper-init
+ try {
+ TimeUnit.MILLISECONDS.sleep(50);
+ } catch (InterruptedException e) {
+ if (!toStop) {
+ logger.error(e.getMessage(), e);
+ }
+ }
+
+ // monitor
+ while (!toStop) {
+ try {
+ // 任务结果丢失处理:调度记录停留在 "运行中" 状态超过10min,且对应执行器心跳注册失败不在线,则将本地调度主动标记失败;
+ Date losedTime = DateUtil.addMinutes(new Date(), -10);
+ List losedJobIds = XxlJobAdminConfig.getAdminConfig().getXxlJobLogService().findLostJobIds(losedTime);
+
+ if (losedJobIds!=null && losedJobIds.size()>0) {
+ for (String logId: losedJobIds) {
+
+ XxlJobLog jobLog = new XxlJobLog();
+ jobLog.setId(logId);
+
+ jobLog.setHandleTime(new Date());
+ jobLog.setHandleCode(ReturnT.FAIL_CODE);
+ jobLog.setHandleMsg( I18nUtil.getString("joblog_lost_fail") );
+
+ XxlJobCompleter.updateHandleInfoAndFinish(jobLog);
+ }
+
+ }
+ } catch (Exception e) {
+ if (!toStop) {
+ logger.error(">>>>>>>>>>> xxl-job, job fail monitor thread error:{}", e);
+ }
+ }
+
+ try {
+ TimeUnit.SECONDS.sleep(60);
+ } catch (Exception e) {
+ if (!toStop) {
+ logger.error(e.getMessage(), e);
+ }
+ }
+
+ }
+
+ logger.info(">>>>>>>>>>> xxl-job, JobLosedMonitorHelper stop");
+
+ }
+ });
+ monitorThread.setDaemon(true);
+ monitorThread.setName("xxl-job, admin JobLosedMonitorHelper");
+ monitorThread.start();
+ }
+
+ public void toStop(){
+ toStop = true;
+
+ // stop registryOrRemoveThreadPool
+ callbackThreadPool.shutdownNow();
+
+ // stop monitorThread (interrupt and wait)
+ monitorThread.interrupt();
+ try {
+ monitorThread.join();
+ } catch (InterruptedException e) {
+ logger.error(e.getMessage(), e);
+ }
+ }
+
+
+ // ---------------------- helper ----------------------
+
+ public ReturnT callback(List callbackParamList) {
+
+ callbackThreadPool.execute(new Runnable() {
+ @Override
+ public void run() {
+ for (HandleCallbackParam handleCallbackParam: callbackParamList) {
+ ReturnT callbackResult = callback(handleCallbackParam);
+ logger.debug(">>>>>>>>> JobApiController.callback {}, handleCallbackParam={}, callbackResult={}",
+ (callbackResult.getCode()== ReturnT.SUCCESS_CODE?"success":"fail"), handleCallbackParam, callbackResult);
+ }
+ }
+ });
+
+ return ReturnT.SUCCESS;
+ }
+
+ private ReturnT callback(HandleCallbackParam handleCallbackParam) {
+ // valid log item
+ XxlJobLog log = XxlJobAdminConfig.getAdminConfig().getXxlJobLogService().load(handleCallbackParam.getLogId());
+ if (log == null) {
+ return new ReturnT(ReturnT.FAIL_CODE, "log item not found.");
+ }
+ if (log.getHandleCode() > 0) {
+ return new ReturnT(ReturnT.FAIL_CODE, "log repeate callback."); // avoid repeat callback, trigger child job etc
+ }
+
+ // handle msg
+ StringBuffer handleMsg = new StringBuffer();
+ if (log.getHandleMsg()!=null) {
+ handleMsg.append(log.getHandleMsg()).append("
");
+ }
+ if (handleCallbackParam.getHandleMsg() != null) {
+ handleMsg.append(handleCallbackParam.getHandleMsg());
+ }
+
+ // success, save log
+ log.setHandleTime(new Date());
+ log.setHandleCode(handleCallbackParam.getHandleCode());
+ log.setHandleMsg(handleMsg.toString());
+ XxlJobCompleter.updateHandleInfoAndFinish(log);
+
+ return ReturnT.SUCCESS;
+ }
+
+
+
+}
+
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobFailMonitorHelper.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobFailMonitorHelper.java
new file mode 100644
index 0000000..9f256e4
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobFailMonitorHelper.java
@@ -0,0 +1,112 @@
+package com.xxl.job.admin.core.thread;
+
+import com.xxl.job.admin.core.conf.XxlJobAdminConfig;
+import com.xxl.job.admin.core.trigger.TriggerTypeEnum;
+import com.xxl.job.admin.core.util.I18nUtil;
+import com.yunzhupaas.scheduletask.entity.XxlJobInfo;
+import com.yunzhupaas.scheduletask.entity.XxlJobLog;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * job monitor instance
+ *
+ * @author xuxueli 2015-9-1 18:05:56
+ */
+public class JobFailMonitorHelper {
+ private static Logger logger = LoggerFactory.getLogger(JobFailMonitorHelper.class);
+
+ private static JobFailMonitorHelper instance = new JobFailMonitorHelper();
+ public static JobFailMonitorHelper getInstance(){
+ return instance;
+ }
+
+ // ---------------------- monitor ----------------------
+
+ private Thread monitorThread;
+ private volatile boolean toStop = false;
+ public void start(){
+ monitorThread = new Thread(new Runnable() {
+
+ @Override
+ public void run() {
+
+ // monitor
+ while (!toStop) {
+ try {
+
+ List failLogIds = XxlJobAdminConfig.getAdminConfig().getXxlJobLogService().findFailJobLogIds(1000);
+ if (failLogIds!=null && !failLogIds.isEmpty()) {
+ for (String failLogId: failLogIds) {
+
+ // lock log
+ int lockRet = XxlJobAdminConfig.getAdminConfig().getXxlJobLogService().updateAlarmStatus(failLogId, 0, -1);
+ if (lockRet < 1) {
+ continue;
+ }
+ XxlJobLog log = XxlJobAdminConfig.getAdminConfig().getXxlJobLogService().load(failLogId);
+ XxlJobInfo info = XxlJobAdminConfig.getAdminConfig().getXxlJobInfoService().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 = ","+ I18nUtil.getString("jobconf_trigger_type_retry") +",";
+ log.setTriggerMsg(log.getTriggerMsg() + retryMsg);
+ XxlJobAdminConfig.getAdminConfig().getXxlJobLogService().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);
+ newAlarmStatus = alarmResult?2:3;
+ } else {
+ newAlarmStatus = 1;
+ }
+
+ XxlJobAdminConfig.getAdminConfig().getXxlJobLogService().updateAlarmStatus(failLogId, -1, newAlarmStatus);
+ }
+ }
+
+ } catch (Exception e) {
+ if (!toStop) {
+ logger.error(">>>>>>>>>>> xxl-job, job fail monitor thread error:{}", e);
+ }
+ }
+
+ try {
+ TimeUnit.SECONDS.sleep(10);
+ } catch (Exception e) {
+ if (!toStop) {
+ logger.error(e.getMessage(), e);
+ }
+ }
+
+ }
+
+ logger.info(">>>>>>>>>>> xxl-job, job fail monitor thread stop");
+
+ }
+ });
+ monitorThread.setDaemon(true);
+ monitorThread.setName("xxl-job, admin JobFailMonitorHelper");
+ monitorThread.start();
+ }
+
+ public void toStop(){
+ toStop = true;
+ // interrupt and wait
+ monitorThread.interrupt();
+ try {
+ monitorThread.join();
+ } catch (InterruptedException e) {
+ logger.error(e.getMessage(), e);
+ }
+ }
+
+}
+
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobLogReportHelper.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobLogReportHelper.java
new file mode 100644
index 0000000..c85c9e4
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobLogReportHelper.java
@@ -0,0 +1,153 @@
+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 triggerCountMap = XxlJobAdminConfig.getAdminConfig().getXxlJobLogService().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().getXxlJobLogReportService().update(xxlJobLogReport);
+ if (ret < 1) {
+ XxlJobAdminConfig.getAdminConfig().getXxlJobLogReportService().create(xxlJobLogReport);
+ }
+ }
+
+ } catch (Exception 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 logIds = null;
+ do {
+ logIds = XxlJobAdminConfig.getAdminConfig().getXxlJobLogService().findClearLogIds("0", "0", clearBeforeTime, 0, 1000);
+ if (logIds!=null && logIds.size()>0) {
+ XxlJobAdminConfig.getAdminConfig().getXxlJobLogService().clearLog(logIds);
+ }
+ } while (logIds!=null && logIds.size()>0);
+
+ // update clean time
+ lastCleanLogTime = System.currentTimeMillis();
+ }
+
+ try {
+ TimeUnit.MINUTES.sleep(1);
+ } catch (Exception 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 (InterruptedException e) {
+ logger.error(e.getMessage(), e);
+ }
+ }
+
+}
+
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobRegistryHelper.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobRegistryHelper.java
new file mode 100644
index 0000000..57a6b8f
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobRegistryHelper.java
@@ -0,0 +1,242 @@
+package com.xxl.job.admin.core.thread;
+
+import com.xxl.job.admin.core.conf.XxlJobAdminConfig;
+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;
+import com.yunzhupaas.scheduletask.entity.XxlJobGroup;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.util.StringUtils;
+
+import java.util.*;
+import java.util.concurrent.*;
+
+/**
+ * job registry instance
+ * @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;
+
+ public void start(){
+
+ // for registry or remove
+ registryOrRemoveThreadPool = new ThreadPoolExecutor(
+ 2,
+ 10,
+ 30L,
+ TimeUnit.SECONDS,
+ new LinkedBlockingQueue(2000),
+ new ThreadFactory() {
+ @Override
+ public Thread newThread(Runnable r) {
+ return new Thread(r, "xxl-job, admin JobRegistryMonitorHelper-registryOrRemoveThreadPool-" + r.hashCode());
+ }
+ },
+ new RejectedExecutionHandler() {
+ @Override
+ public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
+ r.run();
+ logger.warn(">>>>>>>>>>> xxl-job, registry or remove too fast, match threadpool rejected handler(run now).");
+ }
+ });
+
+ // for monitor
+ registryMonitorThread = new Thread(new Runnable() {
+ @Override
+ public void run() {
+ while (!toStop) {
+ try {
+ // auto registry group
+ List groupList = XxlJobAdminConfig.getAdminConfig().getXxlJobGroupService().findByAddressType(0);
+ if (groupList!=null && !groupList.isEmpty()) {
+
+ // remove dead address (admin/executor)
+ List ids = XxlJobAdminConfig.getAdminConfig().getXxlJobRegistryService().findDead(RegistryConfig.DEAD_TIMEOUT, new Date());
+ if (ids!=null && ids.size()>0) {
+ XxlJobAdminConfig.getAdminConfig().getXxlJobRegistryService().removeDead(ids);
+ }
+
+ // fresh online address (admin/executor)
+ HashMap> appAddressMap = new HashMap>();
+ List list = XxlJobAdminConfig.getAdminConfig().getXxlJobRegistryService().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 registryList = appAddressMap.get(appname);
+ if (registryList == null) {
+ registryList = new ArrayList();
+ }
+
+ if (!registryList.contains(item.getRegistryValue())) {
+ registryList.add(item.getRegistryValue());
+ }
+ appAddressMap.put(appname, registryList);
+ }
+ }
+ }
+
+ // fresh group address
+ for (XxlJobGroup group: groupList) {
+ List registryList = appAddressMap.get(group.getAppname());
+ String addressListStr = null;
+ if (registryList!=null && !registryList.isEmpty()) {
+ Collections.sort(registryList);
+ StringBuilder addressListSB = new StringBuilder();
+ for (String item:registryList) {
+ addressListSB.append(item).append(",");
+ }
+ addressListStr = addressListSB.toString();
+ addressListStr = addressListStr.substring(0, addressListStr.length()-1);
+ }
+ group.setAddressList(addressListStr);
+ group.setUpdateTime(new Date());
+
+ XxlJobAdminConfig.getAdminConfig().getXxlJobGroupService().update(group);
+ }
+ }
+ } catch (Exception e) {
+ if (!toStop) {
+ logger.error(">>>>>>>>>>> xxl-job, job registry monitor thread error:{}", e);
+ }
+ }
+ try {
+ TimeUnit.SECONDS.sleep(RegistryConfig.BEAT_TIMEOUT);
+ } catch (InterruptedException e) {
+ if (!toStop) {
+ logger.error(">>>>>>>>>>> xxl-job, job registry monitor thread error:{}", e);
+ }
+ }
+ }
+ logger.info(">>>>>>>>>>> xxl-job, job registry monitor thread stop");
+ }
+ });
+ registryMonitorThread.setDaemon(true);
+ registryMonitorThread.setName("xxl-job, admin JobRegistryMonitorHelper-registryMonitorThread");
+ registryMonitorThread.start();
+ }
+
+ public void toStop(){
+ toStop = true;
+
+ // stop registryOrRemoveThreadPool
+ registryOrRemoveThreadPool.shutdownNow();
+
+ // stop monitir (interrupt and wait)
+ registryMonitorThread.interrupt();
+ try {
+ registryMonitorThread.join();
+ } catch (InterruptedException e) {
+ logger.error(e.getMessage(), e);
+ }
+ }
+
+
+ // ---------------------- helper ----------------------
+
+ public ReturnT registry(RegistryParam registryParam) {
+
+ // valid
+ if (!StringUtils.hasText(registryParam.getRegistryGroup())
+ || !StringUtils.hasText(registryParam.getRegistryKey())
+ || !StringUtils.hasText(registryParam.getRegistryValue())) {
+ return new ReturnT(ReturnT.FAIL_CODE, "Illegal Argument.");
+ }
+
+ // async execute
+ registryOrRemoveThreadPool.execute(new Runnable() {
+ @Override
+ public void run() {
+ int ret = XxlJobAdminConfig.getAdminConfig().getXxlJobRegistryService().registryUpdate(registryParam.getRegistryGroup(), registryParam.getRegistryKey(), registryParam.getRegistryValue(), new Date());
+ if (ret < 1) {
+ XxlJobAdminConfig.getAdminConfig().getXxlJobRegistryService().registrySave(registryParam.getRegistryGroup(), registryParam.getRegistryKey(), registryParam.getRegistryValue(), new Date());
+
+ // fresh
+ freshGroupRegistryInfo(registryParam);
+ }
+ }
+ });
+
+ return ReturnT.SUCCESS;
+ }
+
+ public ReturnT registryRemove(RegistryParam registryParam) {
+
+ // valid
+ if (!StringUtils.hasText(registryParam.getRegistryGroup())
+ || !StringUtils.hasText(registryParam.getRegistryKey())
+ || !StringUtils.hasText(registryParam.getRegistryValue())) {
+ return new ReturnT(ReturnT.FAIL_CODE, "Illegal Argument.");
+ }
+
+ // async execute
+ registryOrRemoveThreadPool.execute(new Runnable() {
+ @Override
+ public void run() {
+ int ret = XxlJobAdminConfig.getAdminConfig().getXxlJobRegistryService().registryDelete(registryParam.getRegistryGroup(), registryParam.getRegistryKey(), registryParam.getRegistryValue());
+ if (ret > 0) {
+ // fresh
+ freshGroupRegistryInfo(registryParam);
+ }
+ }
+ });
+
+ return ReturnT.SUCCESS;
+ }
+
+ private void freshGroupRegistryInfo(RegistryParam registryParam){
+ // Under consideration, prevent affecting core tables
+ // 通过appname查询是否有重名的appname
+ List groupByAppname = XxlJobAdminConfig.getAdminConfig().getXxlJobGroupService().findByAppname(registryParam.getRegistryKey());
+ // 如果appname有重复的,则直接替换内容,且等于1,则直接替换内容,如果大于1则抛出异常
+ if (groupByAppname.size() == 1) {
+ XxlJobGroup xxlJobGroup = new XxlJobGroup();
+ // 设置Appname
+ xxlJobGroup.setAppname(registryParam.getRegistryKey());
+ // 设置title
+ xxlJobGroup.setTitle(registryParam.getRegistryKey());
+ // 默认都是自动注册
+ xxlJobGroup.setAddressType(0);
+ // 设置修改时间
+ xxlJobGroup.setUpdateTime(new Date());
+ // 设置ip
+ if (groupByAppname.get(0).getAddressList().contains(registryParam.getRegistryValue())) {
+ xxlJobGroup.setAddressList(groupByAppname.get(0).getAddressList() + "," + registryParam.getRegistryValue());
+ } else {
+ xxlJobGroup.setAddressList(registryParam.getRegistryValue());
+ }
+ XxlJobAdminConfig.getAdminConfig().getXxlJobGroupService().updateByAppname(xxlJobGroup);
+ } else if (groupByAppname.size() == 0) {
+ XxlJobGroup xxlJobGroup = new XxlJobGroup();
+ // 设置Appname
+ xxlJobGroup.setAppname(registryParam.getRegistryKey());
+ // 设置title
+ xxlJobGroup.setTitle(registryParam.getRegistryKey());
+ // 默认都是自动注册
+ xxlJobGroup.setAddressType(0);
+ // 设置修改时间
+ xxlJobGroup.setUpdateTime(new Date());
+ // 设置ip
+ xxlJobGroup.setAddressList(registryParam.getRegistryValue());
+ XxlJobAdminConfig.getAdminConfig().getXxlJobGroupService().create(xxlJobGroup);
+ } else {
+ logger.error(registryParam.getRegistryKey() + "已重复,请检查appname属性");
+ }
+ }
+
+
+}
+
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobScheduleHelper.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobScheduleHelper.java
new file mode 100644
index 0000000..4f92b6f
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobScheduleHelper.java
@@ -0,0 +1,426 @@
+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.scheduler.MisfireStrategyEnum;
+import com.xxl.job.admin.core.scheduler.ScheduleTypeEnum;
+import com.xxl.job.admin.core.trigger.TriggerTypeEnum;
+import com.yunzhupaas.scheduletask.entity.XxlJobInfo;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @author xuxueli 2019-05-21
+ */
+public class JobScheduleHelper {
+ private static 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
+
+ private Thread scheduleThread;
+ private Thread ringThread;
+ private volatile boolean scheduleThreadToStop = false;
+ private volatile boolean ringThreadToStop = false;
+ private volatile static Map> ringData = new ConcurrentHashMap<>();
+
+ public void start() {
+
+ // schedule thread
+ scheduleThread = new Thread(new Runnable() {
+ @Override
+ public void run() {
+
+ try {
+ TimeUnit.MILLISECONDS.sleep(5000 - System.currentTimeMillis() % 1000);
+ } catch (InterruptedException e) {
+ if (!scheduleThreadToStop) {
+ logger.error(e.getMessage(), e);
+ }
+ }
+ 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;
+
+ while (!scheduleThreadToStop) {
+
+ // Scan Job
+ long start = System.currentTimeMillis();
+
+ Connection conn = null;
+ boolean connAutoCommit = true; // 默认为 true,在获取连接后更新
+ PreparedStatement preparedStatement = null;
+
+ boolean preReadSuc = true;
+ boolean autoCommitSet = false; // 标记是否已设置 AutoCommit
+ try {
+
+ conn = XxlJobAdminConfig.getAdminConfig().getDataSource().getConnection();
+ connAutoCommit = conn.getAutoCommit();
+
+ // 获取查询语句
+ String selectSQL = getSelectSQL(conn.getMetaData().getDriverName());
+ preparedStatement = conn.prepareStatement(selectSQL);
+ preparedStatement.execute();
+
+ // 设置事务手动提交(在 execute 之后)
+ conn.setAutoCommit(false);
+ autoCommitSet = true;
+
+ // tx start
+
+ // 1、pre read
+ long nowTime = System.currentTimeMillis();
+ List scheduleList = XxlJobAdminConfig.getAdminConfig().getXxlJobInfoService()
+ .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 > 5s:pass && make next-trigger-time
+ logger.warn(">>>>>>>>>>> xxl-job, schedule misfire, jobId = " + jobInfo.getId());
+
+ // 1、misfire match
+ 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());
+ }
+
+ // 2、fresh next
+ refreshNextValidTime(jobInfo, new Date());
+
+ } else if (nowTime > jobInfo.getTriggerNextTime()) {
+ // 2.2、trigger-expire < 5s:direct-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());
+
+ // 2、fresh next
+ refreshNextValidTime(jobInfo, new Date());
+
+ // next-trigger-time in 5s, pre-read again
+ if (jobInfo.getTriggerStatus() == 1
+ && nowTime + PRE_READ_MS > jobInfo.getTriggerNextTime()) {
+
+ // 1、make ring second
+ int ringSecond = (int) ((jobInfo.getTriggerNextTime() / 1000) % 60);
+
+ // 2、push time ring
+ pushTimeRing(ringSecond, jobInfo.getId());
+
+ // 3、fresh next
+ refreshNextValidTime(jobInfo, new Date(jobInfo.getTriggerNextTime()));
+
+ }
+
+ } else {
+ // 2.3、trigger-pre-read:time-ring trigger && make next-trigger-time
+
+ // 1、make ring second
+ int ringSecond = (int) ((jobInfo.getTriggerNextTime() / 1000) % 60);
+
+ // 2、push time ring
+ pushTimeRing(ringSecond, jobInfo.getId());
+
+ // 3、fresh next
+ refreshNextValidTime(jobInfo, new Date(jobInfo.getTriggerNextTime()));
+
+ }
+
+ }
+
+ // 3、update trigger info
+ for (XxlJobInfo jobInfo : scheduleList) {
+ XxlJobAdminConfig.getAdminConfig().getXxlJobInfoService().scheduleUpdate(jobInfo);
+ }
+
+ } else {
+ preReadSuc = false;
+ }
+
+ // tx stop
+
+ } catch (Exception e) {
+ if (!scheduleThreadToStop) {
+ logger.error(">>>>>>>>>>> xxl-job, JobScheduleHelper#scheduleThread error:{}", e);
+ }
+ } finally {
+
+ // commit
+ if (conn != null) {
+ try {
+ conn.commit();
+ } catch (SQLException e) {
+ if (!scheduleThreadToStop) {
+ logger.error(e.getMessage(), e);
+ }
+ }
+ try {
+ if (autoCommitSet) {
+ conn.setAutoCommit(connAutoCommit);
+ }
+ } catch (SQLException e) {
+ if (!scheduleThreadToStop) {
+ logger.error(e.getMessage(), e);
+ }
+ }
+ try {
+ conn.close();
+ } catch (SQLException e) {
+ if (!scheduleThreadToStop) {
+ logger.error(e.getMessage(), e);
+ }
+ }
+ }
+
+ // close PreparedStatement
+ if (null != preparedStatement) {
+ try {
+ preparedStatement.close();
+ } catch (SQLException e) {
+ if (!scheduleThreadToStop) {
+ logger.error(e.getMessage(), e);
+ }
+ }
+ }
+ }
+ long cost = System.currentTimeMillis() - start;
+
+ // Wait seconds, align second
+ if (cost < 1000) { // scan-overtime, not wait
+ try {
+ // pre-read period: success > scan each second; fail > skip this period;
+ TimeUnit.MILLISECONDS
+ .sleep((preReadSuc ? 1000 : PRE_READ_MS) - System.currentTimeMillis() % 1000);
+ } catch (InterruptedException e) {
+ if (!scheduleThreadToStop) {
+ logger.error(e.getMessage(), e);
+ }
+ }
+ }
+
+ }
+
+ logger.info(">>>>>>>>>>> xxl-job, JobScheduleHelper#scheduleThread stop");
+ }
+ });
+ scheduleThread.setDaemon(true);
+ scheduleThread.setName("xxl-job, admin JobScheduleHelper#scheduleThread");
+ scheduleThread.start();
+
+ // ring thread
+ ringThread = new Thread(new Runnable() {
+ @Override
+ public void run() {
+
+ while (!ringThreadToStop) {
+
+ // align second
+ try {
+ TimeUnit.MILLISECONDS.sleep(1000 - System.currentTimeMillis() % 1000);
+ } catch (InterruptedException e) {
+ if (!ringThreadToStop) {
+ logger.error(e.getMessage(), e);
+ }
+ }
+
+ try {
+ // second data
+ List ringItemData = new ArrayList<>();
+ int nowSecond = Calendar.getInstance().get(Calendar.SECOND); // 避免处理耗时太长,跨过刻度,向前校验一个刻度;
+ for (int i = 0; i < 2; i++) {
+ List tmpData = ringData.remove((nowSecond + 60 - i) % 60);
+ if (tmpData != null) {
+ ringItemData.addAll(tmpData);
+ }
+ }
+
+ // ring trigger
+ logger.debug(">>>>>>>>>>> xxl-job, time-ring beat : " + nowSecond + " = "
+ + Arrays.asList(ringItemData));
+ if (ringItemData.size() > 0) {
+ // do trigger
+ for (String jobId : ringItemData) {
+ // do trigger
+ JobTriggerPoolHelper.trigger(jobId, TriggerTypeEnum.CRON, -1, null, null, null);
+ }
+ // clear
+ ringItemData.clear();
+ }
+ } catch (Exception e) {
+ if (!ringThreadToStop) {
+ logger.error(">>>>>>>>>>> xxl-job, JobScheduleHelper#ringThread error:{}", e);
+ }
+ }
+ }
+ logger.info(">>>>>>>>>>> xxl-job, JobScheduleHelper#ringThread stop");
+ }
+ });
+ ringThread.setDaemon(true);
+ ringThread.setName("xxl-job, admin JobScheduleHelper#ringThread");
+ ringThread.start();
+ }
+
+ private void refreshNextValidTime(XxlJobInfo jobInfo, Date fromTime) throws Exception {
+ try {
+ Date nextValidTime = generateNextValidTime(jobInfo, fromTime);
+ if (nextValidTime != null) {
+ jobInfo.setTriggerLastTime(jobInfo.getTriggerNextTime());
+ jobInfo.setTriggerNextTime(nextValidTime.getTime());
+ } else {
+ // generateNextValidTime fail, stop job
+ jobInfo.setTriggerStatus(0);
+ jobInfo.setTriggerLastTime(0L);
+ jobInfo.setTriggerNextTime(0L);
+ logger.error(
+ ">>>>>>>>>>> xxl-job, refreshNextValidTime fail for job: jobId={}, scheduleType={}, scheduleConf={}",
+ jobInfo.getId(), jobInfo.getScheduleType(), jobInfo.getScheduleConf());
+ }
+ } catch (Exception e) {
+ // generateNextValidTime error, stop job
+ jobInfo.setTriggerStatus(0);
+ jobInfo.setTriggerLastTime(0L);
+ jobInfo.setTriggerNextTime(0L);
+
+ logger.error(
+ ">>>>>>>>>>> xxl-job, refreshNextValidTime error for job: jobId={}, scheduleType={}, scheduleConf={}",
+ jobInfo.getId(), jobInfo.getScheduleType(), jobInfo.getScheduleConf(), e);
+ }
+ }
+
+ private void pushTimeRing(Integer ringSecond, String jobId) {
+ // push async ring
+ List ringItemData = ringData.get(ringSecond);
+ if (ringItemData == null) {
+ ringItemData = new ArrayList<>();
+ ringData.put(ringSecond, ringItemData);
+ }
+ ringItemData.add(jobId);
+
+ logger.debug(
+ ">>>>>>>>>>> xxl-job, schedule push time-ring : " + ringSecond + " = " + Arrays.asList(ringItemData));
+ }
+
+ public void toStop() {
+
+ // 1、stop schedule
+ scheduleThreadToStop = true;
+ try {
+ TimeUnit.SECONDS.sleep(1); // wait
+ } catch (InterruptedException e) {
+ logger.error(e.getMessage(), e);
+ }
+ if (scheduleThread.getState() != Thread.State.TERMINATED) {
+ // interrupt and wait
+ scheduleThread.interrupt();
+ try {
+ scheduleThread.join();
+ } catch (InterruptedException e) {
+ logger.error(e.getMessage(), e);
+ }
+ }
+
+ // if has ring data
+ boolean hasRingData = false;
+ if (!ringData.isEmpty()) {
+ for (Integer second : ringData.keySet()) {
+ List tmpData = ringData.get(second);
+ if (tmpData != null && tmpData.size() > 0) {
+ hasRingData = true;
+ break;
+ }
+ }
+ }
+ if (hasRingData) {
+ try {
+ TimeUnit.SECONDS.sleep(8);
+ } catch (InterruptedException e) {
+ logger.error(e.getMessage(), e);
+ }
+ }
+
+ // stop ring (wait job-in-memory stop)
+ ringThreadToStop = true;
+ try {
+ TimeUnit.SECONDS.sleep(1);
+ } catch (InterruptedException e) {
+ logger.error(e.getMessage(), e);
+ }
+ if (ringThread.getState() != Thread.State.TERMINATED) {
+ // interrupt and wait
+ ringThread.interrupt();
+ try {
+ ringThread.join();
+ } catch (InterruptedException e) {
+ logger.error(e.getMessage(), e);
+ }
+ }
+
+ logger.info(">>>>>>>>>>> xxl-job, JobScheduleHelper stop");
+ }
+
+ // ---------------------- tools ----------------------
+ public static Date generateNextValidTime(XxlJobInfo jobInfo, Date fromTime) throws Exception {
+ try {
+ 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);
+ }
+ } catch (Exception e) {
+ logger.warn(
+ ">>>>>>>>>>> xxl-job, scheduleConf is invalid for job: jobId={}, scheduleType={}, scheduleConf={}, msg={}",
+ jobInfo.getId(), jobInfo.getScheduleType(), jobInfo.getScheduleConf(), e.getMessage());
+ }
+ return null;
+ }
+
+ /**
+ * 得到当前库类型
+ *
+ * @param driverClassName
+ * @return
+ */
+ private static String getSelectSQL(String driverClassName) {
+ if (driverClassName.contains("sqlserver")) {
+ return "select * from xxl_job_lock with(UPDLOCK) where lock_name = 'schedule_lock'";
+ } else if (driverClassName.contains("oracle")) {
+ return "select * from xxl_job_lock where lock_name = 'schedule_lock' for update";
+ }
+ // else if (driverClassName.contains("kingbase8")) {
+ // return "KingbaseES";
+ // }
+ return "select * from xxl_job_lock where lock_name = 'schedule_lock' for update";
+ }
+
+}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobTriggerPoolHelper.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobTriggerPoolHelper.java
new file mode 100644
index 0000000..c9b3116
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobTriggerPoolHelper.java
@@ -0,0 +1,151 @@
+package com.xxl.job.admin.core.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 org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.concurrent.*;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * job trigger thread pool helper
+ *
+ * @author xuxueli 2018-07-03 21:08:07
+ */
+public class JobTriggerPoolHelper {
+ private static Logger logger = LoggerFactory.getLogger(JobTriggerPoolHelper.class);
+
+
+ // ---------------------- trigger pool ----------------------
+
+ // fast/slow thread pool
+ private ThreadPoolExecutor fastTriggerPool = null;
+ private ThreadPoolExecutor slowTriggerPool = null;
+
+ public void start(){
+ fastTriggerPool = new ThreadPoolExecutor(
+ 10,
+ XxlJobAdminConfig.getAdminConfig().getTriggerPoolFastMax(),
+ 60L,
+ TimeUnit.SECONDS,
+ new LinkedBlockingQueue(1000),
+ new ThreadFactory() {
+ @Override
+ public Thread newThread(Runnable r) {
+ return new Thread(r, "xxl-job, admin JobTriggerPoolHelper-fastTriggerPool-" + r.hashCode());
+ }
+ });
+
+ slowTriggerPool = new ThreadPoolExecutor(
+ 10,
+ XxlJobAdminConfig.getAdminConfig().getTriggerPoolSlowMax(),
+ 60L,
+ TimeUnit.SECONDS,
+ new LinkedBlockingQueue(2000),
+ new ThreadFactory() {
+ @Override
+ public Thread newThread(Runnable r) {
+ return new Thread(r, "xxl-job, admin JobTriggerPoolHelper-slowTriggerPool-" + r.hashCode());
+ }
+ });
+ }
+
+
+ public void stop() {
+ //triggerPool.shutdown();
+ fastTriggerPool.shutdownNow();
+ slowTriggerPool.shutdownNow();
+ logger.info(">>>>>>>>> xxl-job trigger thread pool shutdown success.");
+ }
+
+
+ // job timeout count
+ private volatile long minTim = System.currentTimeMillis()/60000; // ms > min
+ private volatile ConcurrentMap jobTimeoutCountMap = new ConcurrentHashMap<>();
+
+
+ /**
+ * add trigger
+ */
+ public void addTrigger(final String jobId,
+ final TriggerTypeEnum triggerType,
+ final int failRetryCount,
+ final String executorShardingParam,
+ final String executorParam,
+ final String addressList) {
+
+ // choose thread pool
+ ThreadPoolExecutor triggerPool_ = fastTriggerPool;
+ AtomicInteger jobTimeoutCount = jobTimeoutCountMap.get(jobId);
+ if (jobTimeoutCount!=null && jobTimeoutCount.get() > 10) { // job-timeout 10 times in 1 min
+ triggerPool_ = slowTriggerPool;
+ }
+
+ // trigger
+ triggerPool_.execute(new Runnable() {
+ @Override
+ public void run() {
+
+ long start = System.currentTimeMillis();
+
+ try {
+ // do trigger
+ XxlJobTrigger.trigger(jobId, triggerType, failRetryCount, executorShardingParam, executorParam, addressList);
+ } catch (Exception e) {
+ logger.error(e.getMessage(), e);
+ } finally {
+
+ // check timeout-count-map
+ long minTim_now = System.currentTimeMillis()/60000;
+ if (minTim != minTim_now) {
+ minTim = minTim_now;
+ jobTimeoutCountMap.clear();
+ }
+
+ // incr timeout-count-map
+ long cost = System.currentTimeMillis()-start;
+ if (cost > 500) { // ob-timeout threshold 500ms
+ AtomicInteger timeoutCount = jobTimeoutCountMap.putIfAbsent(jobId, new AtomicInteger(1));
+ if (timeoutCount != null) {
+ timeoutCount.incrementAndGet();
+ }
+ }
+
+ }
+
+ }
+ });
+ }
+
+
+
+ // ---------------------- 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(String jobId, TriggerTypeEnum triggerType, int failRetryCount, String executorShardingParam, String executorParam, String addressList) {
+ helper.addTrigger(jobId, triggerType, failRetryCount, executorShardingParam, executorParam, addressList);
+ }
+
+}
+
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/trigger/TriggerTypeEnum.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/trigger/TriggerTypeEnum.java
new file mode 100644
index 0000000..9c730a6
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/trigger/TriggerTypeEnum.java
@@ -0,0 +1,28 @@
+package com.xxl.job.admin.core.trigger;
+
+import com.xxl.job.admin.core.util.I18nUtil;
+
+/**
+ * trigger type enum
+ *
+ * @author xuxueli 2018-09-16 04:56:41
+ */
+public enum TriggerTypeEnum {
+
+ MANUAL(I18nUtil.getString("jobconf_trigger_type_manual")),
+ CRON(I18nUtil.getString("jobconf_trigger_type_cron")),
+ RETRY(I18nUtil.getString("jobconf_trigger_type_retry")),
+ PARENT(I18nUtil.getString("jobconf_trigger_type_parent")),
+ API(I18nUtil.getString("jobconf_trigger_type_api")),
+ MISFIRE(I18nUtil.getString("jobconf_trigger_type_misfire"));
+
+ private TriggerTypeEnum(String title){
+ this.title = title;
+ }
+ private String title;
+ public String getTitle() {
+ return title;
+ }
+
+}
+
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/trigger/XxlJobTrigger.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/trigger/XxlJobTrigger.java
new file mode 100644
index 0000000..8c10a8a
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/trigger/XxlJobTrigger.java
@@ -0,0 +1,284 @@
+package com.xxl.job.admin.core.trigger;
+
+import com.xxl.job.admin.core.conf.XxlJobAdminConfig;
+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.admin.service.XxlJobLogService;
+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;
+import com.yunzhupaas.base.UserInfo;
+import com.yunzhupaas.scheduletask.entity.TimeTaskEntity;
+import com.yunzhupaas.scheduletask.entity.XxlJobGroup;
+import com.yunzhupaas.scheduletask.entity.XxlJobInfo;
+import com.yunzhupaas.scheduletask.entity.XxlJobLog;
+import com.yunzhupaas.scheduletask.model.ContentNewModel;
+import com.yunzhupaas.util.JsonUtil;
+import com.yunzhupaas.util.StringUtil;
+import com.xxl.job.admin.service.TimetaskService;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Date;
+
+/**
+ * xxl-job trigger
+ * Created by xuxueli on 17/7/13.
+ */
+public class XxlJobTrigger {
+ private static Logger logger = LoggerFactory.getLogger(XxlJobTrigger.class);
+
+ /**
+ * 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
+ * @param addressList
+ * null: use executor addressList
+ * not null: cover
+ */
+ public static void trigger(String jobId,
+ TriggerTypeEnum triggerType,
+ int failRetryCount,
+ String executorShardingParam,
+ String executorParam,
+ String addressList) {
+
+ // load data
+ XxlJobInfo jobInfo = XxlJobAdminConfig.getAdminConfig().getXxlJobInfoService().loadById(jobId);
+ if (jobInfo == null) {
+ logger.warn(">>>>>>>>>>>> trigger fail, jobId invalid,jobId={}", jobId);
+ return;
+ }
+ TimeTaskEntity timeTaskEntity = JsonUtil.getJsonToBean(jobInfo.getExecutorParam(), TimeTaskEntity.class);
+ if (timeTaskEntity != null) {
+ ContentNewModel contentNewModel = JsonUtil.getJsonToBean(jobInfo.getExecutorParam(), ContentNewModel.class);
+ if (contentNewModel != null && contentNewModel.getStartTime() != null
+ && contentNewModel.getStartTime() > System.currentTimeMillis()) {
+ logger.debug("任务未到开始时间,jobId={}", jobId);
+ return;
+ }
+ if (contentNewModel != null && contentNewModel.getEndTime() != null
+ && contentNewModel.getEndTime() < System.currentTimeMillis()) {
+ logger.debug("任务到结束时间,jobId={}", jobId);
+ jobInfo.setTriggerStatus(0);
+ XxlJobAdminConfig.getAdminConfig().getXxlJobInfoService().update(jobInfo);
+ return;
+ }
+ }
+ logger.debug("当前正在执行id:" + jobId);
+ if (executorParam != null) {
+ jobInfo.setExecutorParam(executorParam);
+ }
+ int finalFailRetryCount = failRetryCount >= 0 ? failRetryCount : jobInfo.getExecutorFailRetryCount();
+ XxlJobGroup group = XxlJobAdminConfig.getAdminConfig().getXxlJobGroupService().load(jobInfo.getJobGroup());
+
+ // cover addressList
+ if (addressList != null && addressList.trim().length() > 0) {
+ group.setAddressType(1);
+ group.setAddressList(addressList.trim());
+ }
+
+ // sharding param
+ int[] shardingParam = null;
+ if (executorShardingParam != null) {
+ String[] shardingArr = executorShardingParam.split("/");
+ if (shardingArr.length == 2 && isNumeric(shardingArr[0]) && isNumeric(shardingArr[1])) {
+ shardingParam = new int[2];
+ shardingParam[0] = Integer.valueOf(shardingArr[0]);
+ shardingParam[1] = Integer.valueOf(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());
+ }
+ } else {
+ if (shardingParam == null) {
+ shardingParam = new int[] { 0, 1 };
+ }
+ processTrigger(group, jobInfo, finalFailRetryCount, triggerType, shardingParam[0], shardingParam[1]);
+ }
+
+ }
+
+ private static boolean isNumeric(String str) {
+ try {
+ Integer.valueOf(str);
+ return true;
+ } catch (NumberFormatException e) {
+ return false;
+ }
+ }
+
+ /**
+ * @param group job group, registry list may be empty
+ * @param jobInfo
+ * @param finalFailRetryCount
+ * @param triggerType
+ * @param index sharding index
+ * @param total sharding index
+ */
+ private static void processTrigger(XxlJobGroup group, XxlJobInfo jobInfo, int finalFailRetryCount,
+ TriggerTypeEnum triggerType, int index, int total) {
+
+ // 修改调用次数和最后执行时间
+ TimetaskService timetaskService = XxlJobAdminConfig.getAdminConfig().getTimetaskService();
+ XxlJobLogService xxlJobLogService = XxlJobAdminConfig.getAdminConfig().getXxlJobLogService();
+ // 得到日志数量,并修改执行次数
+ long count = xxlJobLogService.queryCountByJobId(jobInfo.getId());
+ UserInfo userInfo = new UserInfo();
+ if (StringUtil.isNotEmpty(jobInfo.getTenantId())) {
+ userInfo.setTenantId(jobInfo.getTenantId());
+ }
+ TimeTaskEntity entity = timetaskService.getInfo(jobInfo.getTaskId(), userInfo);
+ entity.setRunCount((int) count + 1);
+ entity.setNextRunTime(new Date(jobInfo.getTriggerNextTime()));
+ entity.setLastRunTime(new Date());
+ timetaskService.updateTask(jobInfo.getTaskId(), entity);
+
+ // param
+ ExecutorBlockStrategyEnum blockStrategy = ExecutorBlockStrategyEnum.match(jobInfo.getExecutorBlockStrategy(),
+ ExecutorBlockStrategyEnum.SERIAL_EXECUTION); // block strategy
+ ExecutorRouteStrategyEnum executorRouteStrategyEnum = ExecutorRouteStrategyEnum
+ .match(jobInfo.getExecutorRouteStrategy(), null); // route strategy
+ String shardingParam = (ExecutorRouteStrategyEnum.SHARDING_BROADCAST == executorRouteStrategyEnum)
+ ? String.valueOf(index).concat("/").concat(String.valueOf(total))
+ : null;
+
+ // 1、save log-id
+ XxlJobLog jobLog = new XxlJobLog();
+ jobLog.setJobGroup(jobInfo.getJobGroup());
+ jobLog.setJobId(jobInfo.getId());
+ jobLog.setTriggerTime(new Date());
+ XxlJobAdminConfig.getAdminConfig().getXxlJobLogService().create(jobLog);
+ logger.debug(">>>>>>>>>>> xxl-job trigger start, jobId:{}", jobLog.getId());
+
+ // 2、init trigger-param
+ TriggerParam triggerParam = new TriggerParam();
+ triggerParam.setJobId(jobInfo.getId());
+ triggerParam.setExecutorHandler(jobInfo.getExecutorHandler());
+ triggerParam.setExecutorParams(jobInfo.getExecutorParam());
+ triggerParam.setExecutorBlockStrategy(jobInfo.getExecutorBlockStrategy());
+ triggerParam.setExecutorTimeout(jobInfo.getExecutorTimeout());
+ triggerParam.setLogId(jobLog.getId());
+ triggerParam.setLogDateTime(jobLog.getTriggerTime().getTime());
+ triggerParam.setGlueType(jobInfo.getGlueType());
+ triggerParam.setGlueSource(jobInfo.getGlueSource());
+ triggerParam.setGlueUpdatetime(jobInfo.getGlueUpdatetime().getTime());
+ triggerParam.setBroadcastIndex(index);
+ triggerParam.setBroadcastTotal(total);
+
+ // 3、init address
+ String address = null;
+ ReturnT routeAddressResult = null;
+ if (group.getRegistryList() != null && !group.getRegistryList().isEmpty()) {
+ if (ExecutorRouteStrategyEnum.SHARDING_BROADCAST == executorRouteStrategyEnum) {
+ if (index < group.getRegistryList().size()) {
+ address = group.getRegistryList().get(index);
+ } else {
+ address = group.getRegistryList().get(0);
+ }
+ } else {
+ routeAddressResult = executorRouteStrategyEnum.getRouter().route(triggerParam, group.getRegistryList());
+ if (routeAddressResult.getCode() == ReturnT.SUCCESS_CODE) {
+ address = routeAddressResult.getContent();
+ }
+ }
+ } else {
+ routeAddressResult = new ReturnT(ReturnT.FAIL_CODE,
+ I18nUtil.getString("jobconf_trigger_address_empty"));
+ }
+
+ // 4、trigger remote executor
+ ReturnT triggerResult = null;
+ if (address != null) {
+ triggerResult = runExecutor(triggerParam, address);
+ } else {
+ triggerResult = new ReturnT(ReturnT.FAIL_CODE, null);
+ }
+
+ // 5、collection trigger info
+ StringBuffer triggerMsgSb = new StringBuffer();
+ triggerMsgSb.append(I18nUtil.getString("jobconf_trigger_type")).append(":").append(triggerType.getTitle());
+ triggerMsgSb.append(",").append(I18nUtil.getString("jobconf_trigger_admin_adress")).append(":")
+ .append(IpUtil.getIp());
+ triggerMsgSb.append(",").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(",").append(I18nUtil.getString("jobconf_trigger_exe_regaddress")).append(":")
+ .append(group.getRegistryList());
+ triggerMsgSb.append(",").append(I18nUtil.getString("jobinfo_field_executorRouteStrategy")).append(":")
+ .append(executorRouteStrategyEnum.getTitle());
+ if (shardingParam != null) {
+ triggerMsgSb.append("(" + shardingParam + ")");
+ }
+ triggerMsgSb.append(",").append(I18nUtil.getString("jobinfo_field_executorBlockStrategy")).append(":")
+ .append(blockStrategy.getTitle());
+ triggerMsgSb.append(",").append(I18nUtil.getString("jobinfo_field_timeout")).append(":")
+ .append(jobInfo.getExecutorTimeout());
+ triggerMsgSb.append(",").append(I18nUtil.getString("jobinfo_field_executorFailRetryCount")).append(":")
+ .append(finalFailRetryCount);
+
+ triggerMsgSb.append("," + I18nUtil.getString("jobconf_trigger_run") + ",")
+ .append((routeAddressResult != null && routeAddressResult.getMsg() != null)
+ ? routeAddressResult.getMsg() + ","
+ : "")
+ .append(triggerResult.getMsg() != null ? triggerResult.getMsg() : "");
+
+ // 6、save log trigger-info
+ jobLog.setExecutorAddress(address);
+ jobLog.setExecutorHandler(jobInfo.getExecutorHandler());
+ jobLog.setExecutorParam(jobInfo.getExecutorParam());
+ jobLog.setExecutorShardingParam(shardingParam);
+ jobLog.setExecutorFailRetryCount(finalFailRetryCount);
+ // jobLog.setTriggerTime();
+ jobLog.setTriggerCode(triggerResult.getCode());
+ jobLog.setTriggerMsg(triggerMsgSb.toString());
+ XxlJobAdminConfig.getAdminConfig().getXxlJobLogService().updateTriggerInfo(jobLog);
+
+ logger.debug(">>>>>>>>>>> xxl-job trigger end, jobId:{}", jobLog.getId());
+ }
+
+ /**
+ * run executor
+ *
+ * @param triggerParam
+ * @param address
+ * @return
+ */
+ public static ReturnT runExecutor(TriggerParam triggerParam, String address) {
+ ReturnT runResult = null;
+ try {
+ ExecutorBiz executorBiz = XxlJobScheduler.getExecutorBiz(address);
+ runResult = executorBiz.run(triggerParam);
+ } catch (Exception e) {
+ logger.error(">>>>>>>>>>> xxl-job trigger error, please check if the executor[{}] is running.", address, e);
+ runResult = new ReturnT(ReturnT.FAIL_CODE, ThrowableUtil.toString(e));
+ }
+
+ StringBuffer runResultSB = new StringBuffer(I18nUtil.getString("jobconf_trigger_run") + ":");
+ runResultSB.append(",address:").append(address);
+ runResultSB.append(",code:").append(runResult.getCode());
+ runResultSB.append(",msg:").append(runResult.getMsg());
+
+ runResult.setMsg(runResultSB.toString());
+ return runResult;
+ }
+
+}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/util/CookieUtil.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/util/CookieUtil.java
new file mode 100644
index 0000000..6e3ff2b
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/util/CookieUtil.java
@@ -0,0 +1,99 @@
+package com.xxl.job.admin.core.util;
+
+import jakarta.servlet.http.Cookie;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.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);
+ }
+ }
+
+}
+
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/util/FtlUtil.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/util/FtlUtil.java
new file mode 100644
index 0000000..75d80c7
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/util/FtlUtil.java
@@ -0,0 +1,32 @@
+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;
+ }
+
+}
+
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/util/I18nUtil.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/util/I18nUtil.java
new file mode 100644
index 0000000..5e044ad
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/util/I18nUtil.java
@@ -0,0 +1,80 @@
+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 map = new HashMap();
+
+ 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;
+ }
+
+}
+
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/util/JacksonUtil.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/util/JacksonUtil.java
new file mode 100644
index 0000000..41b29bb
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/util/JacksonUtil.java
@@ -0,0 +1,93 @@
+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
+ *
+ * 1、obj need private and set/get;
+ * 2、do 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;
+ }
+
+ /**
+ * bean、array、List、Map --> 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 --> bean、Map、List(array)
+ *
+ * @param jsonStr
+ * @param clazz
+ * @return obj
+ * @throws Exception
+ */
+ public static T readValue(String jsonStr, Class 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...
+ *
+ * @param jsonStr
+ * @param parametrized
+ * @param parameterClasses
+ * @param
+ * @return
+ */
+ public static 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;
+ }
+}
+
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/util/LocalCacheUtil.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/util/LocalCacheUtil.java
new file mode 100644
index 0000000..f614b3f
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/util/LocalCacheUtil.java
@@ -0,0 +1,118 @@
+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 cacheRepository = new ConcurrentHashMap(); // 类型建议用抽象父类,兼容性更好;
+
+ private static class LocalCacheData {
+ private String key;
+ private Object val;
+ private long timeoutTime;
+
+ public LocalCacheData(String key, Object val, long timeoutTime) {
+ this.key = key;
+ this.val = val;
+ this.timeoutTime = timeoutTime;
+ }
+
+ public String getKey() {
+ return key;
+ }
+
+ public Object getVal() {
+ return val;
+ }
+
+ public long getTimeoutTime() {
+ return 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;
+ }
+
+}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/dao/XxlJobGroupDao.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/dao/XxlJobGroupDao.java
new file mode 100644
index 0000000..3babe1a
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/dao/XxlJobGroupDao.java
@@ -0,0 +1,11 @@
+package com.xxl.job.admin.dao;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.yunzhupaas.scheduletask.entity.XxlJobGroup;
+
+/**
+ * Created by xuxueli on 16/9/30.
+ */
+public interface XxlJobGroupDao extends BaseMapper {
+}
+
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/dao/XxlJobInfoDao.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/dao/XxlJobInfoDao.java
new file mode 100644
index 0000000..b3dfcdc
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/dao/XxlJobInfoDao.java
@@ -0,0 +1,13 @@
+package com.xxl.job.admin.dao;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.yunzhupaas.scheduletask.entity.XxlJobInfo;
+
+/**
+ * job info
+ * @author xuxueli 2016-1-12 18:03:45
+ */
+public interface XxlJobInfoDao extends BaseMapper {
+
+}
+
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/dao/XxlJobLogDao.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/dao/XxlJobLogDao.java
new file mode 100644
index 0000000..910b4f8
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/dao/XxlJobLogDao.java
@@ -0,0 +1,29 @@
+package com.xxl.job.admin.dao;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.yunzhupaas.scheduletask.entity.XxlJobLog;
+
+import org.apache.ibatis.annotations.Param;
+
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * job log
+ * @author xuxueli 2016-1-12 18:03:06
+ */
+public interface XxlJobLogDao extends BaseMapper {
+
+
+ Map findLogReport(@Param("from") Date from,
+ @Param("to") Date to, @Param("dbType") String dbType);
+
+ Map oracleFindLogReport(@Param("from") String from,
+ @Param("to") String to, @Param("dbType") String dbType);
+
+
+ List findLostJobIds(@Param("losedTime") String losedTime, @Param("dbType") String dbType);
+
+}
+
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/dao/XxlJobLogGlueDao.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/dao/XxlJobLogGlueDao.java
new file mode 100644
index 0000000..85a59fb
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/dao/XxlJobLogGlueDao.java
@@ -0,0 +1,13 @@
+package com.xxl.job.admin.dao;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.xxl.job.admin.core.model.XxlJobLogGlue;
+
+/**
+ * job log for glue
+ * @author xuxueli 2016-5-19 18:04:56
+ */
+public interface XxlJobLogGlueDao extends BaseMapper {
+
+}
+
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/dao/XxlJobLogReportDao.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/dao/XxlJobLogReportDao.java
new file mode 100644
index 0000000..ce05ffa
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/dao/XxlJobLogReportDao.java
@@ -0,0 +1,15 @@
+package com.xxl.job.admin.dao;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.xxl.job.admin.core.model.XxlJobLogReport;
+
+/**
+ * job log
+ * @author xuxueli 2019-11-22
+ */
+public interface XxlJobLogReportDao extends BaseMapper {
+
+ XxlJobLogReport queryLogReportTotal();
+
+}
+
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/dao/XxlJobRegistryDao.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/dao/XxlJobRegistryDao.java
new file mode 100644
index 0000000..9403c81
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/dao/XxlJobRegistryDao.java
@@ -0,0 +1,12 @@
+package com.xxl.job.admin.dao;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.xxl.job.admin.core.model.XxlJobRegistry;
+
+/**
+ * Created by xuxueli on 16/9/30.
+ */
+public interface XxlJobRegistryDao extends BaseMapper {
+
+}
+
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/dao/XxlJobUserDao.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/dao/XxlJobUserDao.java
new file mode 100644
index 0000000..f90e839
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/dao/XxlJobUserDao.java
@@ -0,0 +1,12 @@
+package com.xxl.job.admin.dao;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.xxl.job.admin.core.model.XxlJobUser;
+
+/**
+ * @author xuxueli 2019-05-04 16:44:59
+ */
+public interface XxlJobUserDao extends BaseMapper {
+
+}
+
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/mapper/HandlerNameMapper.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/mapper/HandlerNameMapper.java
new file mode 100644
index 0000000..4e8175c
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/mapper/HandlerNameMapper.java
@@ -0,0 +1,16 @@
+package com.xxl.job.admin.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.yunzhupaas.scheduletask.entity.HandlerNameEntity;
+
+/**
+ * 任务名称表
+ *
+ * @author :云筑产品开发平台组
+ * @version: V3.1.0
+ * @copyright 深圳市乐程软件有限公司
+ * @date :2022/3/29 11:08
+ */
+public interface HandlerNameMapper extends BaseMapper {
+}
+
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/mapper/TimeTaskMapper.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/mapper/TimeTaskMapper.java
new file mode 100644
index 0000000..2213dc7
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/mapper/TimeTaskMapper.java
@@ -0,0 +1,17 @@
+package com.xxl.job.admin.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.yunzhupaas.scheduletask.entity.TimeTaskEntity;
+
+/**
+ * 定时任务
+ *
+ * @author 云筑产品开发平台组
+ * @version V3.1.0
+ * @copyright 深圳市乐程软件有限公司
+ * @date 2023/09/27
+ */
+public interface TimeTaskMapper extends BaseMapper {
+
+}
+
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/service/HandlerNameService.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/service/HandlerNameService.java
new file mode 100644
index 0000000..93097f5
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/service/HandlerNameService.java
@@ -0,0 +1,56 @@
+package com.xxl.job.admin.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.yunzhupaas.scheduletask.entity.HandlerNameEntity;
+
+import java.util.List;
+
+/**
+ * 任务名称业务类
+ *
+ * @author :云筑产品开发平台组
+ * @version: V3.1.0
+ * @copyright 深圳市乐程软件有限公司
+ * @date :2022/3/29 11:10
+ */
+public interface HandlerNameService extends IService {
+
+ /**
+ * 创建任务名称
+ *
+ * @param entity
+ * @return
+ */
+ boolean create(HandlerNameEntity entity);
+
+ /**
+ * 创建任务名称
+ *
+ * @param entity
+ * @return
+ */
+ boolean delete(HandlerNameEntity entity);
+
+ /**
+ * 获取本地方法
+ *
+ * @return
+ */
+ List queryList();
+
+ /**
+ * 删除所有数据
+ *
+ * @return
+ */
+ boolean removeAll();
+
+ /**
+ * 获取本地方法
+ *
+ * @param localHostTaskId
+ * @return
+ */
+ HandlerNameEntity getInfo(String localHostTaskId);
+}
+
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/service/TimetaskService.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/service/TimetaskService.java
new file mode 100644
index 0000000..f1e1a40
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/service/TimetaskService.java
@@ -0,0 +1,90 @@
+package com.xxl.job.admin.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.yunzhupaas.base.Pagination;
+import com.yunzhupaas.base.UserInfo;
+import com.yunzhupaas.scheduletask.entity.TimeTaskEntity;
+
+import java.util.List;
+
+/**
+ * 定时任务
+ *
+ * @author 云筑产品开发平台组
+ * @version V3.1.0
+ * @copyright 深圳市乐程软件有限公司
+ * @date 2023/09/27
+ */
+public interface TimetaskService extends IService {
+
+
+ /**
+ * 列表
+ *
+ * @param pagination 分页
+ * @return
+ */
+ List getList(Pagination pagination, UserInfo userInfo);
+ /**
+ * 信息
+ *
+ * @param id 主键值
+ * @return
+ */
+ TimeTaskEntity getInfo(String id, UserInfo userInfo);
+
+ /**
+ * 验证名称
+ *
+ * @param fullName 名称
+ * @param id 主键值
+ * @return
+ */
+ boolean isExistByFullName(String fullName, String id);
+
+ /**
+ * 验证编码
+ *
+ * @param enCode 编码
+ * @param id 主键值
+ * @return
+ */
+ boolean isExistByEnCode(String enCode, String id);
+
+ /**
+ * 创建
+ *
+ * @param entity 实体对象
+ */
+ boolean create(TimeTaskEntity entity, UserInfo userInfo);
+
+ /**
+ * 日程调度
+ *
+ * @param entity 实体对象
+ */
+ boolean schedule(TimeTaskEntity entity);
+
+ /**
+ * 更新
+ *
+ * @param id 主键值
+ * @param entity 实体对象
+ */
+ boolean update(String id, TimeTaskEntity entity, UserInfo userInfo);
+
+ /**
+ * 删除
+ *
+ * @param entity 实体对象
+ */
+ void delete(TimeTaskEntity entity);
+
+ /**
+ * 修改执行次数
+ * @param taskId
+ * @param entity
+ */
+ void updateTask(String taskId, TimeTaskEntity entity);
+}
+
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/service/XxlJobGroupService.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/service/XxlJobGroupService.java
new file mode 100644
index 0000000..3713f63
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/service/XxlJobGroupService.java
@@ -0,0 +1,49 @@
+package com.xxl.job.admin.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.yunzhupaas.scheduletask.entity.XxlJobGroup;
+
+import java.util.List;
+
+public interface XxlJobGroupService extends IService {
+
+ List findAll();
+
+ List findByAddressType(int addressType);
+
+ int create(XxlJobGroup xxlJobGroup);
+
+ int update(XxlJobGroup xxlJobGroup);
+
+ int remove(String id);
+
+ XxlJobGroup load(String id);
+
+ List pageList(int offset,
+ int pagesize,
+ String appname,
+ String title);
+
+ long pageListCount(int offset,
+ int pagesize,
+ String appname,
+ String title);
+
+ /**
+ * 通过Appname查询分组
+ *
+ * @param appname
+ * @return
+ */
+ List findByAppname(String appname);
+
+ /**
+ * 通过appname修改数据
+ *
+ * @param xxlJobGroup
+ * @return
+ */
+ int updateByAppname(XxlJobGroup xxlJobGroup);
+
+}
+
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/service/XxlJobInfoService.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/service/XxlJobInfoService.java
new file mode 100644
index 0000000..033cc3e
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/service/XxlJobInfoService.java
@@ -0,0 +1,59 @@
+package com.xxl.job.admin.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.yunzhupaas.scheduletask.entity.XxlJobInfo;
+
+import java.util.List;
+
+public interface XxlJobInfoService extends IService {
+
+ List pageList(int offset,
+ int pagesize,
+ String jobGroup,
+ int triggerStatus,
+ String jobDesc,
+ String executorHandler,
+ String author);
+
+ long pageListCount(int offset,
+ int pagesize,
+ String jobGroup,
+ int triggerStatus,
+ String jobDesc,
+ String executorHandler,
+ String author);
+
+ int create(XxlJobInfo info);
+
+ XxlJobInfo loadById(String id);
+
+ int update(XxlJobInfo xxlJobInfo);
+
+ int delete(String id);
+
+ List getJobsByGroup(String jobGroup);
+
+ int findAllCount();
+
+ List scheduleJobQuery(long maxNextTime, int pagesize );
+
+ int scheduleUpdate(XxlJobInfo xxlJobInfo);
+
+ /**
+ * 通过taskId获取任务信息
+ *
+ * @param taskId
+ * @return
+ */
+ XxlJobInfo queryByTaskId(String taskId);
+
+ /**
+ * 通过taskId删除任务
+ *
+ * @param taskId
+ * @return
+ */
+ boolean deleteByTaskId(String taskId);
+
+}
+
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/service/XxlJobLogGlueService.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/service/XxlJobLogGlueService.java
new file mode 100644
index 0000000..d10abc1
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/service/XxlJobLogGlueService.java
@@ -0,0 +1,18 @@
+package com.xxl.job.admin.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.xxl.job.admin.core.model.XxlJobLogGlue;
+
+import java.util.List;
+
+public interface XxlJobLogGlueService extends IService {
+
+ int create(XxlJobLogGlue xxlJobLogGlue);
+
+ List findByJobId(String jobId);
+
+ int removeOld(String jobId, int limit);
+
+ int deleteByJobId(String jobId);
+}
+
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/service/XxlJobLogReportService.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/service/XxlJobLogReportService.java
new file mode 100644
index 0000000..4900245
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/service/XxlJobLogReportService.java
@@ -0,0 +1,20 @@
+package com.xxl.job.admin.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.xxl.job.admin.core.model.XxlJobLogReport;
+
+import java.util.Date;
+import java.util.List;
+
+public interface XxlJobLogReportService extends IService {
+
+ int create(XxlJobLogReport xxlJobLogReport);
+
+ int update(XxlJobLogReport xxlJobLogReport);
+
+ List queryLogReport(Date triggerDayFrom,
+ Date triggerDayTo);
+
+ XxlJobLogReport queryLogReportTotal();
+}
+
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/service/XxlJobLogService.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/service/XxlJobLogService.java
new file mode 100644
index 0000000..405046c
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/service/XxlJobLogService.java
@@ -0,0 +1,88 @@
+package com.xxl.job.admin.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.yunzhupaas.scheduletask.entity.XxlJobLog;
+import com.yunzhupaas.scheduletask.model.TaskPage;
+
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * xxljoblog
+ *
+ * @author :云筑产品开发平台组
+ * @version: V3.1.0
+ * @copyright 深圳市乐程软件有限公司
+ * @date :2022/3/29 15:05
+ */
+public interface XxlJobLogService extends IService {
+
+ /**
+ * 分页获取调用日志
+ *
+ * @param taskId 任务id
+ * @param taskPage
+ * @return
+ */
+ List getList(String taskId, TaskPage taskPage);
+
+ /**
+ * 通过jobid获取日志总数
+ *
+ * @param jobId 任务id
+ * @return
+ */
+ Long queryCountByJobId(String jobId);
+
+
+ // exist jobId not use jobGroup, not exist use jobGroup
+ List pageList(int offset,
+ int pagesize,
+ String jobGroup,
+ String jobId,
+ Date triggerTimeStart,
+ Date triggerTimeEnd,
+ int logStatus);
+
+ int pageListCount(int offset,
+ int pagesize,
+ String jobGroup,
+ String jobId,
+ Date triggerTimeStart,
+ Date triggerTimeEnd,
+ int logStatus);
+
+ XxlJobLog load(String id);
+
+ long create(XxlJobLog xxlJobLog);
+
+ int updateTriggerInfo(XxlJobLog xxlJobLog);
+
+ int updateHandleInfo(XxlJobLog xxlJobLog);
+
+ int delete(String jobId);
+
+ Map findLogReport(Date from,
+ Date to);
+
+ List findClearLogIds(String jobGroup,
+ String jobId,
+ Date clearBeforeTime,
+ int clearBeforeNum,
+ int pagesize);
+
+ int clearLog(List logIds);
+
+ List findFailJobLogIds(int pagesize);
+
+ int updateAlarmStatus(String logId,
+ int oldAlarmStatus,
+ int newAlarmStatus);
+
+ List findLostJobIds(Date losedTime);
+
+ boolean deleteByTaskId(String taskId);
+
+}
+
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/service/XxlJobRegistryService.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/service/XxlJobRegistryService.java
new file mode 100644
index 0000000..32f7588
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/service/XxlJobRegistryService.java
@@ -0,0 +1,33 @@
+package com.xxl.job.admin.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.xxl.job.admin.core.model.XxlJobRegistry;
+
+import java.util.Date;
+import java.util.List;
+
+public interface XxlJobRegistryService extends IService {
+
+ List findDead(int timeout,
+ Date nowTime);
+
+ int removeDead(List ids);
+
+ List findAll(int timeout,
+ Date nowTime);
+
+ int registryUpdate(String registryGroup,
+ String registryKey,
+ String registryValue,
+ Date updateTime);
+
+ int registrySave(String registryGroup,
+ String registryKey,
+ String registryValue,
+ Date updateTime);
+
+ int registryDelete(String registryGroup,
+ String registryKey,
+ String registryValue);
+}
+
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/service/XxlJobService.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/service/XxlJobService.java
new file mode 100644
index 0000000..4f01a23
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/service/XxlJobService.java
@@ -0,0 +1,99 @@
+package com.xxl.job.admin.service;
+
+
+import com.xxl.job.admin.core.model.XxlJobUser;
+import com.xxl.job.core.biz.model.ReturnT;
+import com.yunzhupaas.scheduletask.entity.XxlJobInfo;
+
+import java.util.Date;
+import java.util.Map;
+
+/**
+ * core job action for xxl-job
+ *
+ * @author xuxueli 2016-5-28 15:30:33
+ */
+public interface XxlJobService {
+
+ /**
+ * page list
+ *
+ * @param start
+ * @param length
+ * @param jobGroup
+ * @param jobDesc
+ * @param executorHandler
+ * @param author
+ * @return
+ */
+ public Map pageList(int start, int length, String jobGroup, int triggerStatus, String jobDesc, String executorHandler, String author);
+
+ /**
+ * add job
+ *
+ * @param jobInfo
+ * @return
+ */
+ public ReturnT add(XxlJobInfo jobInfo, XxlJobUser loginUser);
+
+ /**
+ * update job
+ *
+ * @param jobInfo
+ * @return
+ */
+ public ReturnT update(XxlJobInfo jobInfo, XxlJobUser loginUser);
+
+ /**
+ * remove job
+ * *
+ * @param id
+ * @return
+ */
+ public ReturnT remove(String id);
+
+ /**
+ * start job
+ *
+ * @param id
+ * @return
+ */
+ public ReturnT start(String id);
+
+ /**
+ * stop job
+ *
+ * @param id
+ * @return
+ */
+ public ReturnT stop(String id);
+
+ /**
+ * trigger
+ *
+ * @param loginUser
+ * @param jobId
+ * @param executorParam
+ * @param addressList
+ * @return
+ */
+ public ReturnT trigger(XxlJobUser loginUser, String jobId, String executorParam, String addressList);
+
+ /**
+ * dashboard info
+ *
+ * @return
+ */
+ public Map dashboardInfo();
+
+ /**
+ * chart info
+ *
+ * @param startDate
+ * @param endDate
+ * @return
+ */
+ public ReturnT> chartInfo(Date startDate, Date endDate);
+
+}
+
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/service/XxlJobUserService.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/service/XxlJobUserService.java
new file mode 100644
index 0000000..22a18a2
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/service/XxlJobUserService.java
@@ -0,0 +1,29 @@
+package com.xxl.job.admin.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.xxl.job.admin.core.model.XxlJobUser;
+
+import java.util.List;
+
+public interface XxlJobUserService extends IService {
+
+ List pageList(int offset,
+ int pagesize,
+ String username,
+ int role);
+
+ int pageListCount(int offset,
+ int pagesize,
+ String username,
+ int role);
+
+ XxlJobUser loadByUserName(String username);
+
+ int create(XxlJobUser xxlJobUser);
+
+ int update(XxlJobUser xxlJobUser);
+
+ int delete(String id);
+
+}
+
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/service/impl/AdminBizImpl.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/service/impl/AdminBizImpl.java
new file mode 100644
index 0000000..4e6dcc1
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/service/impl/AdminBizImpl.java
@@ -0,0 +1,42 @@
+package com.xxl.job.admin.service.impl;
+
+import com.xxl.job.admin.core.thread.JobCompleteHelper;
+import com.xxl.job.admin.core.thread.JobRegistryHelper;
+import com.xxl.job.core.biz.AdminBiz;
+import com.xxl.job.core.biz.model.HandleCallbackParam;
+import com.xxl.job.core.biz.model.RegistryHandlerName;
+import com.xxl.job.core.biz.model.RegistryParam;
+import com.xxl.job.core.biz.model.ReturnT;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+/**
+ * @author xuxueli 2017-07-27 21:54:20
+ */
+@Service
+public class AdminBizImpl implements AdminBiz {
+
+
+ @Override
+ public ReturnT callback(List callbackParamList) {
+ return JobCompleteHelper.getInstance().callback(callbackParamList);
+ }
+
+ @Override
+ public ReturnT registry(RegistryParam registryParam) {
+ return JobRegistryHelper.getInstance().registry(registryParam);
+ }
+
+ @Override
+ public ReturnT registryRemove(RegistryParam registryParam) {
+ return JobRegistryHelper.getInstance().registryRemove(registryParam);
+ }
+
+ @Override
+ public ReturnT registryHandlerName(RegistryHandlerName registryHandlerName) {
+ return null;
+ }
+
+}
+
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/service/impl/HandlerNameServiceImpl.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/service/impl/HandlerNameServiceImpl.java
new file mode 100644
index 0000000..839b3ab
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/service/impl/HandlerNameServiceImpl.java
@@ -0,0 +1,56 @@
+package com.xxl.job.admin.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.xxl.job.admin.mapper.HandlerNameMapper;
+import com.xxl.job.admin.service.HandlerNameService;
+import com.yunzhupaas.scheduletask.entity.HandlerNameEntity;
+
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+/**
+ * 任务名称业务实现类
+ *
+ * @author :云筑产品开发平台组
+ * @version: V3.1.0
+ * @copyright 深圳市乐程软件有限公司
+ * @date :2022/3/29 11:10
+ */
+@Service
+public class HandlerNameServiceImpl extends ServiceImpl implements HandlerNameService {
+
+ @Override
+ public boolean create(HandlerNameEntity entity) {
+ entity.setId(entity.getHandlerName());
+ return this.save(entity);
+ }
+
+ @Override
+ public boolean delete(HandlerNameEntity entity) {
+ QueryWrapper queryWrapper = new QueryWrapper<>();
+ queryWrapper.lambda().eq(HandlerNameEntity::getExecutor, entity.getExecutor()).eq(HandlerNameEntity::getHandlerName, entity.getHandlerName());
+ return this.remove(queryWrapper);
+ }
+
+ @Override
+ public List queryList() {
+ QueryWrapper queryWrapper = new QueryWrapper<>();
+ queryWrapper.lambda().groupBy(HandlerNameEntity::getHandlerName);
+ return this.list();
+ }
+
+ @Override
+ public boolean removeAll() {
+ return this.remove(new QueryWrapper());
+ }
+
+ @Override
+ public HandlerNameEntity getInfo(String localHostTaskId) {
+ QueryWrapper queryWrapper = new QueryWrapper<>();
+ queryWrapper.lambda().eq(HandlerNameEntity::getId, localHostTaskId);
+ return this.getOne(queryWrapper);
+ }
+}
+
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/service/impl/TimetaskServiceImpl.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/service/impl/TimetaskServiceImpl.java
new file mode 100644
index 0000000..d8dfed2
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/service/impl/TimetaskServiceImpl.java
@@ -0,0 +1,276 @@
+package com.xxl.job.admin.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.xxl.job.admin.core.conf.XxlJobAdminConfig;
+import com.xxl.job.admin.mapper.TimeTaskMapper;
+import com.xxl.job.admin.service.HandlerNameService;
+import com.xxl.job.admin.service.TimetaskService;
+import com.xxl.job.admin.service.XxlJobService;
+import com.yunzhupaas.base.Pagination;
+import com.yunzhupaas.base.UserInfo;
+import com.yunzhupaas.scheduletask.entity.*;
+import com.yunzhupaas.scheduletask.model.ContentNewModel;
+import com.yunzhupaas.util.*;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 定时任务
+ *
+ * @author 云筑产品开发平台组
+ * @version V3.1.0
+ * @copyright 深圳市乐程软件有限公司
+ * @date 2023/09/27
+ */
+@Service
+public class TimetaskServiceImpl extends ServiceImpl implements TimetaskService {
+
+ @Autowired
+ private HandlerNameService handlerNameService;
+ @Autowired
+ private XxlJobService xxlJobService;
+
+ @Override
+ public List getList(Pagination pagination, UserInfo userInfo) {
+ QueryWrapper queryWrapper = new QueryWrapper<>();
+ if (pagination.getKeyword() != null) {
+ queryWrapper.lambda().and(
+ t -> t.like(TimeTaskEntity::getEnCode, pagination.getKeyword())
+ .or().like(TimeTaskEntity::getFullName, pagination.getKeyword()));
+ }
+ // 验证是否为多租户
+ if (StringUtil.isNotEmpty(userInfo.getTenantId())) {
+ queryWrapper.lambda().eq(TimeTaskEntity::getTenantId, userInfo.getTenantId());
+ }
+ // 排序
+ queryWrapper.lambda().orderByAsc(TimeTaskEntity::getSortCode).orderByDesc(TimeTaskEntity::getCreatorTime);
+ Page page = new Page<>(pagination.getCurrentPage(), pagination.getPageSize());
+ IPage iPage = this.page(page, queryWrapper);
+ return pagination.setData(iPage.getRecords(), page.getTotal());
+ }
+
+ @Override
+ public TimeTaskEntity getInfo(String id, UserInfo userInfo) {
+ QueryWrapper queryWrapper = new QueryWrapper<>();
+ queryWrapper.lambda().eq(TimeTaskEntity::getId, id);
+ // 验证是否为多租户
+ if (StringUtil.isNotEmpty(userInfo.getTenantId())) {
+ queryWrapper.lambda().eq(TimeTaskEntity::getTenantId, userInfo.getTenantId());
+ }
+ return this.getOne(queryWrapper);
+ }
+
+ @Override
+ public boolean isExistByFullName(String fullName, String id) {
+ QueryWrapper queryWrapper = new QueryWrapper<>();
+ queryWrapper.lambda().eq(TimeTaskEntity::getFullName, fullName);
+ if (!StringUtil.isEmpty(id)) {
+ queryWrapper.lambda().ne(TimeTaskEntity::getId, id);
+ }
+ return this.count(queryWrapper) > 0 ? true : false;
+ }
+
+ @Override
+ public boolean isExistByEnCode(String enCode, String id) {
+ QueryWrapper queryWrapper = new QueryWrapper<>();
+ queryWrapper.lambda().eq(TimeTaskEntity::getEnCode, enCode);
+ if (!StringUtil.isEmpty(id)) {
+ queryWrapper.lambda().ne(TimeTaskEntity::getId, id);
+ }
+ return this.count(queryWrapper) > 0 ? true : false;
+ }
+
+ @Override
+ public boolean create(TimeTaskEntity entity, UserInfo userInfo) {
+ entity.setId(StringUtil.isNotEmpty(entity.getId()) ? entity.getId() : RandomUtil.uuId());
+ ContentNewModel model = JsonUtil.getJsonToBean(entity.getExecuteContent(), ContentNewModel.class);
+ model.setUserInfo(userInfo);
+ // 得到token
+ model.setToken(userInfo.getToken());
+ // 添加时间
+ Date date = new Date();
+ entity.setCreatorTime(date);
+ entity.setCreatorUserId(userInfo.getUserId());
+ entity.setTenantId(userInfo.getTenantId());
+ // 将任务添加到info表中
+ // 构造模型
+ XxlJobInfo xxlJobInfo = buildModel(entity, model, new XxlJobInfo(), date, userInfo);
+ // 如果是本地方法
+ boolean flag = true;
+ if ("3".equals(entity.getExecuteType())) {
+ // 获取本地方法对应的executor和handlerName
+ HandlerNameEntity handlerNameEntity = handlerNameService.getInfo(model.getLocalHostTaskId());
+ if (handlerNameEntity == null) {
+ flag = false;
+ } else {
+ // 获取执行器
+ List xxlJobGroup = XxlJobAdminConfig.getAdminConfig().getXxlJobGroupService()
+ .findByAppname(handlerNameEntity.getExecutor());
+ xxlJobInfo.setJobGroup(xxlJobGroup.size() == 1 ? xxlJobGroup.get(0).getId() : null);
+ xxlJobInfo.setScheduleConf(model.getCron());
+ xxlJobInfo.setExecutorHandler(handlerNameEntity.getHandlerName());
+ }
+ }
+ // 保存到yunzhupaas的表中
+ this.save(entity);
+ if (flag) {
+ XxlJobAdminConfig.getAdminConfig().getXxlJobInfoService().create(xxlJobInfo);
+ // 开始调度
+ xxlJobService.start(xxlJobInfo.getId());
+ }
+ return flag;
+ }
+
+ @Override
+ public boolean schedule(TimeTaskEntity entity) {
+ String id = entity.getId();
+ boolean flag = true;
+ ContentNewModel model = JsonUtil.getJsonToBean(entity.getExecuteContent(), ContentNewModel.class);
+ UserInfo userInfo = model.getUserInfo();
+ TimeTaskEntity info = getInfo(id, userInfo);
+ if (info == null) {
+ create(entity, userInfo);
+ }
+ return flag;
+ }
+
+ @Override
+ public boolean update(String id, TimeTaskEntity entity, UserInfo userInfo) {
+ entity.setId(id);
+ entity.setLastModifyTime(DateUtil.getNowDate());
+ entity.setLastModifyUserId(userInfo.getUserId());
+ entity.setTenantId(userInfo.getTenantId());
+
+ ContentNewModel model = JsonUtil.getJsonToBean(entity.getExecuteContent(), ContentNewModel.class);
+ model.setUserInfo(userInfo);
+ // 得到token
+ model.setToken(userInfo.getToken());
+ // 获取当前时间
+ Date date = new Date();
+ entity.setLastModifyTime(date);
+ entity.setLastModifyUserId(userInfo.getUserId());
+ // 修改任务
+ // 通过任务id得到任务
+ XxlJobInfo jobInfo = XxlJobAdminConfig.getAdminConfig().getXxlJobInfoService().queryByTaskId(id);
+ if (jobInfo == null) {
+ jobInfo = new XxlJobInfo();
+ }
+ // 构造模型
+ XxlJobInfo xxlJobInfo = buildModel(entity, model, jobInfo, date, userInfo);
+ // 如果是本地方法
+ boolean flag = true;
+ if ("3".equals(entity.getExecuteType())) {
+ // 获取本地方法对应的executor和handlerName
+ HandlerNameEntity handlerNameEntity = handlerNameService.getInfo(model.getLocalHostTaskId());
+ if (handlerNameEntity == null) {
+ flag = false;
+ } else {
+ // 获取执行器
+ List xxlJobGroup = XxlJobAdminConfig.getAdminConfig().getXxlJobGroupService()
+ .findByAppname(handlerNameEntity.getExecutor());
+ xxlJobInfo.setJobGroup(xxlJobGroup.size() == 1 ? xxlJobGroup.get(0).getId() : null);
+ xxlJobInfo.setScheduleConf(model.getCron());
+ xxlJobInfo.setExecutorHandler(handlerNameEntity.getHandlerName());
+ }
+ }
+ if (flag) {
+ XxlJobAdminConfig.getAdminConfig().getXxlJobInfoService().update(xxlJobInfo);
+ }
+
+ // return修改结果
+ return this.updateById(entity);
+ }
+
+ @Override
+ public void delete(TimeTaskEntity entity) {
+ this.removeById(entity.getId());
+ // 删除任务
+ XxlJobAdminConfig.getAdminConfig().getXxlJobInfoService().deleteByTaskId(entity.getId());
+
+ XxlJobAdminConfig.getAdminConfig().getXxlJobLogService().deleteByTaskId(entity.getId());
+ }
+
+ @Override
+ public void updateTask(String taskId, TimeTaskEntity entity) {
+ entity.setId(taskId);
+ this.updateById(entity);
+ }
+
+ /**
+ * 构造任务并启动
+ *
+ * @param entity
+ * @param date
+ * @param model
+ */
+ private XxlJobInfo buildModel(TimeTaskEntity entity, ContentNewModel model, XxlJobInfo xxlJobInfo, Date date,
+ UserInfo userInfo) {
+
+ // 默认一个执行器主键ID
+ xxlJobInfo.setJobGroup("8");
+
+ // 默认一个执行器描述
+ xxlJobInfo.setJobDesc(entity.getFullName());
+ // 负责人
+ xxlJobInfo.setAuthor(userInfo.getUserId());
+ // 调度类型
+ xxlJobInfo.setScheduleType("CRON");
+ // 添加时间
+ xxlJobInfo.setAddTime(date);
+ // 调度配置,值含义取决于调度类型
+ xxlJobInfo.setScheduleConf(model.getCron());
+ // 调度过期策略(默认忽略)
+ xxlJobInfo.setMisfireStrategy("DO_NOTHING");
+ // 执行器路由策略(默认第一个)
+ xxlJobInfo.setExecutorRouteStrategy("FIRST");
+
+ // 执行器任务handler(执行任务的handler)
+ xxlJobInfo.setExecutorHandler("defaultHandler");
+ // 执行参数
+ // 将ContentNewModel当做参数传递给任务执行器
+ model.setExecuteType(entity.getExecuteType());
+ xxlJobInfo.setExecutorParam(JsonUtil.getObjectToString(model));
+
+ // 阻塞处理策略(默认单行串行)
+ xxlJobInfo.setExecutorBlockStrategy("SERIAL_EXECUTION");
+ // 任务执行超时时间,单位秒(默认0秒)
+ xxlJobInfo.setExecutorTimeout(0);
+ // 失败重试次数(默认0)
+ xxlJobInfo.setExecutorFailRetryCount(0);
+ // GLUE类型(默认BEAN)
+ xxlJobInfo.setGlueType("BEAN");
+ // GLUE源代码
+ xxlJobInfo.setGlueRemark("GLUE代码初始化");
+ // 调度状态:0-停止,1-运行(默认运行)
+ xxlJobInfo.setTriggerStatus(entity.getEnabledMark());
+ // 上次调度时间
+ xxlJobInfo.setTriggerLastTime(0L);
+ // 下次调度时间
+ xxlJobInfo.setTriggerNextTime(0L);
+ // 创建时间GLUE
+ xxlJobInfo.setGlueUpdatetime(date);
+ // GLUE源代码
+ xxlJobInfo.setGlueSource("");
+ // 子任务ID,多个逗号分隔
+ xxlJobInfo.setChildJobId("");
+ // 租户id
+ xxlJobInfo.setTenantId(userInfo.getTenantId());
+ // 创建时增加任务id
+ xxlJobInfo.setTaskId(entity.getId());
+
+ // 修改时间
+ if (entity.getLastModifyTime() != null) {
+ xxlJobInfo.setUpdateTime(entity.getLastModifyTime());
+ }
+
+ return xxlJobInfo;
+ }
+
+}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/service/impl/XxlJobGroupServiceImpl.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/service/impl/XxlJobGroupServiceImpl.java
new file mode 100644
index 0000000..d1d325e
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/service/impl/XxlJobGroupServiceImpl.java
@@ -0,0 +1,108 @@
+package com.xxl.job.admin.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.xxl.job.admin.dao.XxlJobGroupDao;
+import com.xxl.job.admin.service.XxlJobGroupService;
+import com.yunzhupaas.scheduletask.entity.XxlJobGroup;
+import com.yunzhupaas.util.StringUtil;
+
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+@Service
+public class XxlJobGroupServiceImpl extends ServiceImpl implements XxlJobGroupService {
+
+ @Override
+ public List findAll() {
+ QueryWrapper queryWrapper = new QueryWrapper<>();
+ queryWrapper.lambda().orderByAsc(XxlJobGroup::getAppname)
+ .orderByAsc(XxlJobGroup::getTitle)
+ .orderByAsc(XxlJobGroup::getId);
+ return this.list(queryWrapper);
+ }
+
+ @Override
+ public List findByAddressType(int addressType) {
+ QueryWrapper queryWrapper = new QueryWrapper<>();
+ queryWrapper.lambda().eq(XxlJobGroup::getAddressType, addressType);
+ queryWrapper.lambda().orderByAsc(XxlJobGroup::getAppname)
+ .orderByAsc(XxlJobGroup::getTitle)
+ .orderByAsc(XxlJobGroup::getId);
+ return this.list(queryWrapper);
+ }
+
+ @Override
+ public int create(XxlJobGroup xxlJobGroup) {
+ return this.save(xxlJobGroup) ? 1 : 0;
+ }
+
+ @Override
+ public int update(XxlJobGroup xxlJobGroup) {
+ return this.updateById(xxlJobGroup) ? 1 : 0;
+ }
+
+ @Override
+ public int remove(String id) {
+ return this.removeById(id) ? 1 : 0;
+ }
+
+ @Override
+ public XxlJobGroup load(String id) {
+ QueryWrapper queryWrapper = new QueryWrapper<>();
+ queryWrapper.lambda().eq(XxlJobGroup::getId, id);
+ return this.getOne(queryWrapper);
+ }
+
+ @Override
+ public List pageList(int offset, int pagesize, String appname, String title) {
+ QueryWrapper queryWrapper = new QueryWrapper<>();
+ if (StringUtil.isNotEmpty(appname)) {
+ queryWrapper.lambda().like(XxlJobGroup::getAppname, appname);
+ }
+ if (StringUtil.isNotEmpty(title)) {
+ queryWrapper.lambda().like(XxlJobGroup::getTitle, title);
+ }
+ queryWrapper.lambda().orderByAsc(XxlJobGroup::getAppname)
+ .orderByAsc(XxlJobGroup::getTitle)
+ .orderByAsc(XxlJobGroup::getId);
+ Page page = new Page<>(offset/pagesize + 1, pagesize);
+ IPage iPage = this.page(page, queryWrapper);
+ return iPage.getRecords();
+ }
+
+ @Override
+ public long pageListCount(int offset, int pagesize, String appname, String title) {
+ QueryWrapper queryWrapper = new QueryWrapper<>();
+ if (StringUtil.isNotEmpty(appname)) {
+ queryWrapper.lambda().like(XxlJobGroup::getAppname, appname);
+ }
+ if (StringUtil.isNotEmpty(title)) {
+ queryWrapper.lambda().like(XxlJobGroup::getTitle, title);
+ }
+ return this.count(queryWrapper);
+ }
+
+ @Override
+ public List findByAppname(String appname) {
+ QueryWrapper queryWrapper = new QueryWrapper<>();
+ queryWrapper.lambda().eq(XxlJobGroup::getAppname, appname);
+ return this.list(queryWrapper);
+ }
+
+ @Override
+ public int updateByAppname(XxlJobGroup xxlJobGroup) {
+ UpdateWrapper updateWrapper = new UpdateWrapper<>();
+ updateWrapper.lambda().eq(XxlJobGroup::getAppname, xxlJobGroup.getAppname());
+ updateWrapper.lambda().set(XxlJobGroup::getTitle, xxlJobGroup.getTitle());
+ updateWrapper.lambda().set(XxlJobGroup::getAddressType, xxlJobGroup.getAddressType());
+ updateWrapper.lambda().set(XxlJobGroup::getAddressList, xxlJobGroup.getAddressList());
+ updateWrapper.lambda().set(XxlJobGroup::getUpdateTime, xxlJobGroup.getUpdateTime());
+ return this.update(updateWrapper) ? 1 : 0;
+ }
+}
+
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/service/impl/XxlJobInfoServiceImpl.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/service/impl/XxlJobInfoServiceImpl.java
new file mode 100644
index 0000000..0a5b979
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/service/impl/XxlJobInfoServiceImpl.java
@@ -0,0 +1,133 @@
+package com.xxl.job.admin.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.xxl.job.admin.dao.XxlJobInfoDao;
+import com.xxl.job.admin.service.XxlJobInfoService;
+import com.yunzhupaas.scheduletask.entity.XxlJobInfo;
+import com.yunzhupaas.util.StringUtil;
+
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+@Service
+public class XxlJobInfoServiceImpl extends ServiceImpl implements XxlJobInfoService {
+
+ @Override
+ public List pageList(int offset, int pagesize, String jobGroup, int triggerStatus, String jobDesc, String executorHandler, String author) {
+ QueryWrapper queryWrapper = new QueryWrapper<>();
+ if (StringUtil.isNotEmpty(jobGroup) && !"0".equals(jobGroup)) {
+ queryWrapper.lambda().eq(XxlJobInfo::getJobGroup, jobGroup);
+ }
+ if (triggerStatus >= 0) {
+ queryWrapper.lambda().eq(XxlJobInfo::getTriggerStatus, triggerStatus);
+ }
+ if (StringUtil.isNotEmpty(jobDesc)) {
+ queryWrapper.lambda().like(XxlJobInfo::getJobDesc, jobDesc);
+ }
+ if (StringUtil.isNotEmpty(executorHandler)) {
+ queryWrapper.lambda().like(XxlJobInfo::getExecutorHandler, executorHandler);
+ }
+ if (StringUtil.isNotEmpty(author)) {
+ queryWrapper.lambda().like(XxlJobInfo::getAuthor, author);
+ }
+ queryWrapper.lambda().orderByDesc(XxlJobInfo::getId);
+ Page page = new Page<>(offset/pagesize + 1, pagesize);
+ IPage iPage = this.page(page, queryWrapper);
+ return iPage.getRecords();
+ }
+
+ @Override
+ public long pageListCount(int offset, int pagesize, String jobGroup, int triggerStatus, String jobDesc, String executorHandler, String author) {
+ QueryWrapper queryWrapper = new QueryWrapper<>();
+ if (StringUtil.isNotEmpty(jobGroup) && !"0".equals(jobGroup)) {
+ queryWrapper.lambda().eq(XxlJobInfo::getJobGroup, jobGroup);
+ }
+ if (triggerStatus >= 0) {
+ queryWrapper.lambda().eq(XxlJobInfo::getTriggerStatus, triggerStatus);
+ }
+ if (StringUtil.isNotEmpty(jobDesc)) {
+ queryWrapper.lambda().like(XxlJobInfo::getJobDesc, jobDesc);
+ }
+ if (StringUtil.isNotEmpty(executorHandler)) {
+ queryWrapper.lambda().like(XxlJobInfo::getExecutorHandler, executorHandler);
+ }
+ if (StringUtil.isNotEmpty(author)) {
+ queryWrapper.lambda().like(XxlJobInfo::getAuthor, author);
+ }
+ return this.count(queryWrapper);
+ }
+
+ @Override
+ public int create(XxlJobInfo info) {
+ return this.save(info) ? 1 : 0;
+ }
+
+ @Override
+ public XxlJobInfo loadById(String id) {
+ QueryWrapper queryWrapper = new QueryWrapper<>();
+ queryWrapper.lambda().eq(XxlJobInfo::getId, id);
+ return this.getOne(queryWrapper);
+ }
+
+ @Override
+ public int update(XxlJobInfo xxlJobInfo) {
+ return this.updateById(xxlJobInfo) ? 1 : 0;
+ }
+
+ @Override
+ public int delete(String id) {
+ return this.removeById(id) ? 1 : 0;
+ }
+
+ @Override
+ public List getJobsByGroup(String jobGroup) {
+ QueryWrapper queryWrapper = new QueryWrapper<>();
+ queryWrapper.lambda().eq(XxlJobInfo::getJobGroup, jobGroup);
+ return this.list(queryWrapper);
+ }
+
+ @Override
+ public int findAllCount() {
+ return this.list().size();
+ }
+
+ @Override
+ public List scheduleJobQuery(long maxNextTime, int pagesize) {
+ QueryWrapper queryWrapper = new QueryWrapper<>();
+ queryWrapper.lambda().eq(XxlJobInfo::getTriggerStatus, 1);
+ queryWrapper.lambda().le(XxlJobInfo::getTriggerNextTime, maxNextTime);
+ queryWrapper.lambda().orderByAsc(XxlJobInfo::getId);
+ return this.list(queryWrapper);
+ }
+
+ @Override
+ public int scheduleUpdate(XxlJobInfo xxlJobInfo) {
+ UpdateWrapper updateWrapper = new UpdateWrapper<>();
+ updateWrapper.lambda().eq(XxlJobInfo::getId, xxlJobInfo.getId());
+ updateWrapper.lambda().set(XxlJobInfo::getTriggerLastTime, xxlJobInfo.getTriggerLastTime());
+ updateWrapper.lambda().set(XxlJobInfo::getTriggerNextTime, xxlJobInfo.getTriggerNextTime());
+ updateWrapper.lambda().set(XxlJobInfo::getTriggerStatus, xxlJobInfo.getTriggerStatus());
+ return this.update(updateWrapper) ? 1 : 0;
+ }
+
+ @Override
+ public XxlJobInfo queryByTaskId(String taskId) {
+ QueryWrapper queryWrapper = new QueryWrapper<>();
+ queryWrapper.lambda().eq(XxlJobInfo::getTaskId, taskId);
+ return this.getOne(queryWrapper);
+ }
+
+ @Override
+ public boolean deleteByTaskId(String taskId) {
+ QueryWrapper queryWrapper = new QueryWrapper<>();
+ queryWrapper.lambda().eq(XxlJobInfo::getTaskId, taskId);
+ return this.remove(queryWrapper);
+ }
+
+}
+
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/service/impl/XxlJobLogGlueServiceImpl.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/service/impl/XxlJobLogGlueServiceImpl.java
new file mode 100644
index 0000000..7083c76
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/service/impl/XxlJobLogGlueServiceImpl.java
@@ -0,0 +1,53 @@
+package com.xxl.job.admin.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.xxl.job.admin.core.model.XxlJobLogGlue;
+import com.xxl.job.admin.dao.XxlJobLogGlueDao;
+import com.xxl.job.admin.service.XxlJobLogGlueService;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+@Service
+public class XxlJobLogGlueServiceImpl extends ServiceImpl implements XxlJobLogGlueService {
+
+ @Override
+ public int create(XxlJobLogGlue xxlJobLogGlue) {
+ return this.save(xxlJobLogGlue) ? 1 : 0;
+ }
+
+ @Override
+ public List findByJobId(String jobId) {
+ QueryWrapper queryWrapper = new QueryWrapper<>();
+ queryWrapper.lambda().eq(XxlJobLogGlue::getJobId, jobId);
+ queryWrapper.lambda().orderByDesc(XxlJobLogGlue::getId);
+ return this.list(queryWrapper);
+ }
+
+ @Override
+ public int removeOld(String jobId, int limit) {
+ QueryWrapper queryWrapper1 = new QueryWrapper<>();
+ queryWrapper1.lambda().eq(XxlJobLogGlue::getJobId, jobId);
+ queryWrapper1.lambda().orderByDesc(XxlJobLogGlue::getUpdateTime);
+ Page page = new Page<>(0, limit);
+ IPage iPage = this.page(page, queryWrapper1);
+ List ids = iPage.getRecords().stream().map(XxlJobLogGlue::getId).collect(Collectors.toList());
+
+ QueryWrapper queryWrapper = new QueryWrapper<>();
+ queryWrapper.lambda().notIn(XxlJobLogGlue::getId, ids);
+ queryWrapper.lambda().eq(XxlJobLogGlue::getJobId, jobId);
+ return this.remove(queryWrapper) ? 1 : 0;
+ }
+
+ @Override
+ public int deleteByJobId(String jobId) {
+ QueryWrapper queryWrapper = new QueryWrapper<>();
+ queryWrapper.lambda().eq(XxlJobLogGlue::getJobId, jobId);
+ return this.remove(queryWrapper) ? 1 : 0;
+ }
+}
+
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/service/impl/XxlJobLogReportServiceImpl.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/service/impl/XxlJobLogReportServiceImpl.java
new file mode 100644
index 0000000..9536aa9
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/service/impl/XxlJobLogReportServiceImpl.java
@@ -0,0 +1,45 @@
+package com.xxl.job.admin.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.xxl.job.admin.core.model.XxlJobLogReport;
+import com.xxl.job.admin.dao.XxlJobLogReportDao;
+import com.xxl.job.admin.service.XxlJobLogReportService;
+import org.springframework.stereotype.Service;
+
+import java.util.Date;
+import java.util.List;
+
+@Service
+public class XxlJobLogReportServiceImpl extends ServiceImpl implements XxlJobLogReportService {
+
+ @Override
+ public int create(XxlJobLogReport xxlJobLogReport) {
+ return this.save(xxlJobLogReport) ? 1 : 0;
+ }
+
+ @Override
+ public int update(XxlJobLogReport xxlJobLogReport) {
+ UpdateWrapper updateWrapper = new UpdateWrapper<>();
+ updateWrapper.lambda().eq(XxlJobLogReport::getTriggerDay, xxlJobLogReport.getTriggerDay());
+ updateWrapper.lambda().set(XxlJobLogReport::getRunningCount, xxlJobLogReport.getRunningCount());
+ updateWrapper.lambda().set(XxlJobLogReport::getSucCount, xxlJobLogReport.getSucCount());
+ updateWrapper.lambda().set(XxlJobLogReport::getFailCount, xxlJobLogReport.getFailCount());
+ return this.update(updateWrapper) ? 1 : 0;
+ }
+
+ @Override
+ public List queryLogReport(Date triggerDayFrom, Date triggerDayTo) {
+ QueryWrapper queryWrapper = new QueryWrapper<>();
+ queryWrapper.lambda().between(XxlJobLogReport::getTriggerDay, triggerDayFrom, triggerDayTo);
+ queryWrapper.lambda().orderByAsc(XxlJobLogReport::getTriggerDay);
+ return this.list(queryWrapper);
+ }
+
+ @Override
+ public XxlJobLogReport queryLogReportTotal() {
+ return null;
+ }
+}
+
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/service/impl/XxlJobLogServiceImpl.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/service/impl/XxlJobLogServiceImpl.java
new file mode 100644
index 0000000..1d145ee
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/service/impl/XxlJobLogServiceImpl.java
@@ -0,0 +1,287 @@
+package com.xxl.job.admin.service.impl;
+
+import cn.hutool.core.util.ObjectUtil;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.zaxxer.hikari.HikariDataSource;
+import com.xxl.job.admin.dao.XxlJobLogDao;
+import com.xxl.job.admin.service.XxlJobLogService;
+import com.yunzhupaas.scheduletask.entity.XxlJobLog;
+import com.yunzhupaas.scheduletask.model.TaskPage;
+import com.yunzhupaas.util.DateUtil;
+import com.yunzhupaas.util.StringUtil;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import javax.sql.DataSource;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * XxJobLogServiceImpl
+ *
+ * @author :云筑产品开发平台组
+ * @version: V3.1.0
+ * @copyright 深圳市乐程软件有限公司
+ * @date :2022/3/29 15:06
+ */
+@Service
+public class XxlJobLogServiceImpl extends ServiceImpl implements XxlJobLogService {
+
+ @Autowired
+ private DataSource dataSource;
+
+ @Override
+ public List getList(String taskId, TaskPage taskPage) {
+ QueryWrapper queryWrapper = new QueryWrapper<>();
+ queryWrapper.lambda().eq(XxlJobLog::getJobId, taskId);
+ // 日期范围
+ if (ObjectUtil.isNotEmpty(taskPage.getStartTime()) && ObjectUtil.isNotEmpty(taskPage.getEndTime())) {
+ queryWrapper.lambda().between(XxlJobLog::getTriggerTime, new Date(taskPage.getStartTime()),
+ new Date(taskPage.getEndTime()));
+ }
+ if (taskPage.getRunResult() != null) {
+ if (taskPage.getRunResult() == 1) {
+ queryWrapper.lambda().ne(XxlJobLog::getHandleCode, 200);
+ } else {
+ queryWrapper.lambda().eq(XxlJobLog::getHandleCode, 200);
+ }
+ }
+ queryWrapper.lambda().orderByDesc(XxlJobLog::getTriggerTime);
+ queryWrapper.select(XxlJobLog.class, t -> !"executor_param".equals(t.getColumn()));
+ Page