From 16b5ae6c64269bb3a791a8ee4b8e0d298bf65538 Mon Sep 17 00:00:00 2001
From: lengleng
Date: Mon, 9 Nov 2020 14:16:18 +0800
Subject: [PATCH] =?UTF-8?q?:sparkles:=20Introducing=20new=20features.=20#1?=
=?UTF-8?q?841=20=E5=86=85=E7=BD=AE=20xxl-job-admin=20=E6=A8=A1=E5=9D=97?=
=?UTF-8?q?=20=E6=96=B9=E4=BE=BF=E4=BD=BF=E7=94=A8?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
docker-compose.yml | 19 +-
.../job/properties/XxlAdminProperties.java | 2 +-
pigx-visual/pigx-xxl-job-admin/Dockerfile | 18 +
pigx-visual/pigx-xxl-job-admin/pom.xml | 82 +
.../job/admin/PigXxlJobAdminApplication.java | 16 +
.../job/admin/controller/IndexController.java | 99 +
.../admin/controller/JobApiController.java | 77 +
.../admin/controller/JobCodeController.java | 99 +
.../admin/controller/JobGroupController.java | 187 +
.../admin/controller/JobInfoController.java | 175 +
.../admin/controller/JobLogController.java | 256 +
.../job/admin/controller/UserController.java | 175 +
.../annotation/PermissionLimit.java | 27 +
.../interceptor/CookieInterceptor.java | 43 +
.../interceptor/PermissionInterceptor.java | 60 +
.../controller/interceptor/WebMvcConfig.java | 29 +
.../resolver/WebExceptionResolver.java | 69 +
.../xxl/job/admin/core/alarm/JobAlarm.java | 19 +
.../xxl/job/admin/core/alarm/JobAlarmer.java | 67 +
.../admin/core/alarm/impl/EmailJobAlarm.java | 107 +
.../admin/core/conf/XxlJobAdminConfig.java | 163 +
.../job/admin/core/cron/CronExpression.java | 1712 +++
.../admin/core/exception/XxlJobException.java | 15 +
.../xxl/job/admin/core/model/XxlJobGroup.java | 72 +
.../xxl/job/admin/core/model/XxlJobInfo.java | 232 +
.../xxl/job/admin/core/model/XxlJobLog.java | 167 +
.../job/admin/core/model/XxlJobLogGlue.java | 82 +
.../job/admin/core/model/XxlJobLogReport.java | 57 +
.../job/admin/core/model/XxlJobRegistry.java | 60 +
.../xxl/job/admin/core/model/XxlJobUser.java | 78 +
.../job/admin/core/old/RemoteHttpJobBean.java | 33 +
.../core/old/XxlJobDynamicScheduler.java | 430 +
.../job/admin/core/old/XxlJobThreadPool.java | 58 +
.../core/route/ExecutorRouteStrategyEnum.java | 56 +
.../job/admin/core/route/ExecutorRouter.java | 24 +
.../route/strategy/ExecutorRouteBusyover.java | 48 +
.../strategy/ExecutorRouteConsistentHash.java | 84 +
.../route/strategy/ExecutorRouteFailover.java | 48 +
.../route/strategy/ExecutorRouteFirst.java | 19 +
.../core/route/strategy/ExecutorRouteLFU.java | 79 +
.../core/route/strategy/ExecutorRouteLRU.java | 75 +
.../route/strategy/ExecutorRouteLast.java | 19 +
.../route/strategy/ExecutorRouteRandom.java | 23 +
.../route/strategy/ExecutorRouteRound.java | 48 +
.../admin/core/scheduler/XxlJobScheduler.java | 101 +
.../core/thread/JobFailMonitorHelper.java | 128 +
.../admin/core/thread/JobLogReportHelper.java | 167 +
.../core/thread/JobLosedMonitorHelper.java | 103 +
.../core/thread/JobRegistryMonitorHelper.java | 123 +
.../admin/core/thread/JobScheduleHelper.java | 391 +
.../core/thread/JobTriggerPoolHelper.java | 135 +
.../admin/core/trigger/TriggerTypeEnum.java | 28 +
.../job/admin/core/trigger/XxlJobTrigger.java | 238 +
.../xxl/job/admin/core/util/CookieUtil.java | 95 +
.../com/xxl/job/admin/core/util/FtlUtil.java | 34 +
.../com/xxl/job/admin/core/util/I18nUtil.java | 81 +
.../xxl/job/admin/core/util/JacksonUtil.java | 100 +
.../job/admin/core/util/LocalCacheUtil.java | 134 +
.../com/xxl/job/admin/dao/XxlJobGroupDao.java | 33 +
.../com/xxl/job/admin/dao/XxlJobInfoDao.java | 43 +
.../com/xxl/job/admin/dao/XxlJobLogDao.java | 55 +
.../xxl/job/admin/dao/XxlJobLogGlueDao.java | 25 +
.../xxl/job/admin/dao/XxlJobLogReportDao.java | 27 +
.../xxl/job/admin/dao/XxlJobRegistryDao.java | 31 +
.../com/xxl/job/admin/dao/XxlJobUserDao.java | 28 +
.../xxl/job/admin/service/LoginService.java | 105 +
.../xxl/job/admin/service/XxlJobService.java | 78 +
.../job/admin/service/impl/AdminBizImpl.java | 191 +
.../admin/service/impl/XxlJobServiceImpl.java | 409 +
.../src/main/resources/application.yml | 54 +
.../main/resources/i18n/message_en.properties | 264 +
.../resources/i18n/message_zh_CN.properties | 264 +
.../resources/i18n/message_zh_TC.properties | 264 +
.../src/main/resources/logback-spring.xml | 77 +
.../mybatis-mapper/XxlJobGroupMapper.xml | 88 +
.../mybatis-mapper/XxlJobInfoMapper.xml | 229 +
.../mybatis-mapper/XxlJobLogGlueMapper.xml | 71 +
.../mybatis-mapper/XxlJobLogMapper.xml | 261 +
.../mybatis-mapper/XxlJobLogReportMapper.xml | 62 +
.../mybatis-mapper/XxlJobRegistryMapper.xml | 62 +
.../mybatis-mapper/XxlJobUserMapper.xml | 87 +
.../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 | 7 +
.../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 | 7 +
.../dist/css/skins/_all-skins.min.css | 1 +
.../static/adminlte/dist/js/adminlte.min.js | 14 +
.../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 | 156 +
.../src/main/resources/static/js/index.js | 207 +
.../resources/static/js/jobcode.index.1.js | 97 +
.../resources/static/js/jobgroup.index.1.js | 359 +
.../resources/static/js/jobinfo.index.1.js | 682 ++
.../resources/static/js/joblog.detail.1.js | 91 +
.../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 | 1104 ++
.../static/plugins/cronGen/cronGen_en.js | 1104 ++
.../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 | 239 +
.../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 | 172 +
.../templates/jobinfo/jobinfo.index.ftl | 440 +
.../templates/joblog/joblog.detail.ftl | 73 +
.../templates/joblog/joblog.index.ftl | 180 +
.../src/main/resources/templates/login.ftl | 45 +
.../resources/templates/user/user.index.ftl | 188 +
pigx-visual/pom.xml | 1 +
165 files changed, 38308 insertions(+), 10 deletions(-)
create mode 100755 pigx-visual/pigx-xxl-job-admin/Dockerfile
create mode 100755 pigx-visual/pigx-xxl-job-admin/pom.xml
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/PigXxlJobAdminApplication.java
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/controller/IndexController.java
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobApiController.java
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobCodeController.java
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobGroupController.java
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobInfoController.java
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobLogController.java
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/controller/UserController.java
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/controller/annotation/PermissionLimit.java
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/controller/interceptor/CookieInterceptor.java
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/controller/interceptor/PermissionInterceptor.java
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/controller/interceptor/WebMvcConfig.java
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/controller/resolver/WebExceptionResolver.java
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/alarm/JobAlarm.java
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/alarm/JobAlarmer.java
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/alarm/impl/EmailJobAlarm.java
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobAdminConfig.java
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/cron/CronExpression.java
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/exception/XxlJobException.java
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobGroup.java
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobInfo.java
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobLog.java
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobLogGlue.java
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobLogReport.java
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobRegistry.java
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobUser.java
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/RemoteHttpJobBean.java
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/XxlJobDynamicScheduler.java
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/XxlJobThreadPool.java
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/ExecutorRouteStrategyEnum.java
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/ExecutorRouter.java
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteBusyover.java
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteConsistentHash.java
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteFailover.java
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteFirst.java
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteLFU.java
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteLRU.java
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteLast.java
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteRandom.java
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteRound.java
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/scheduler/XxlJobScheduler.java
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobFailMonitorHelper.java
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobLogReportHelper.java
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobLosedMonitorHelper.java
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobRegistryMonitorHelper.java
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobScheduleHelper.java
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobTriggerPoolHelper.java
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/trigger/TriggerTypeEnum.java
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/trigger/XxlJobTrigger.java
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/util/CookieUtil.java
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/util/FtlUtil.java
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/util/I18nUtil.java
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/util/JacksonUtil.java
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/util/LocalCacheUtil.java
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/dao/XxlJobGroupDao.java
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/dao/XxlJobInfoDao.java
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/dao/XxlJobLogDao.java
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/dao/XxlJobLogGlueDao.java
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/dao/XxlJobLogReportDao.java
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/dao/XxlJobRegistryDao.java
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/dao/XxlJobUserDao.java
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/service/LoginService.java
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/service/XxlJobService.java
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/service/impl/AdminBizImpl.java
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/service/impl/XxlJobServiceImpl.java
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/resources/application.yml
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/resources/i18n/message_en.properties
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/resources/i18n/message_zh_CN.properties
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/resources/i18n/message_zh_TC.properties
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/resources/logback-spring.xml
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/resources/mybatis-mapper/XxlJobGroupMapper.xml
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/resources/mybatis-mapper/XxlJobInfoMapper.xml
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/resources/mybatis-mapper/XxlJobLogGlueMapper.xml
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/resources/mybatis-mapper/XxlJobLogMapper.xml
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/resources/mybatis-mapper/XxlJobLogReportMapper.xml
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/resources/mybatis-mapper/XxlJobRegistryMapper.xml
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/resources/mybatis-mapper/XxlJobUserMapper.xml
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/resources/static/adminlte/bower_components/Ionicons/css/ionicons.min.css
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/resources/static/adminlte/bower_components/Ionicons/fonts/ionicons.eot
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/resources/static/adminlte/bower_components/Ionicons/fonts/ionicons.svg
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/resources/static/adminlte/bower_components/Ionicons/fonts/ionicons.ttf
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/resources/static/adminlte/bower_components/Ionicons/fonts/ionicons.woff
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/resources/static/adminlte/bower_components/PACE/pace.min.js
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/resources/static/adminlte/bower_components/PACE/themes/blue/pace-theme-flash.css
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/resources/static/adminlte/bower_components/bootstrap-daterangepicker/daterangepicker.css
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/resources/static/adminlte/bower_components/bootstrap-daterangepicker/daterangepicker.js
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/resources/static/adminlte/bower_components/bootstrap/css/bootstrap.min.css
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/resources/static/adminlte/bower_components/bootstrap/css/bootstrap.min.css.map
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/resources/static/adminlte/bower_components/bootstrap/fonts/glyphicons-halflings-regular.eot
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/resources/static/adminlte/bower_components/bootstrap/fonts/glyphicons-halflings-regular.svg
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/resources/static/adminlte/bower_components/bootstrap/fonts/glyphicons-halflings-regular.ttf
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/resources/static/adminlte/bower_components/bootstrap/fonts/glyphicons-halflings-regular.woff
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/resources/static/adminlte/bower_components/bootstrap/fonts/glyphicons-halflings-regular.woff2
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/resources/static/adminlte/bower_components/bootstrap/js/bootstrap.min.js
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/resources/static/adminlte/bower_components/datatables.net-bs/css/dataTables.bootstrap.min.css
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/resources/static/adminlte/bower_components/datatables.net-bs/js/dataTables.bootstrap.min.js
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/resources/static/adminlte/bower_components/datatables.net/js/jquery.dataTables.min.js
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/resources/static/adminlte/bower_components/fastclick/fastclick.js
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/resources/static/adminlte/bower_components/font-awesome/css/font-awesome.css.map
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/resources/static/adminlte/bower_components/font-awesome/css/font-awesome.min.css
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/resources/static/adminlte/bower_components/font-awesome/fonts/FontAwesome.otf
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/resources/static/adminlte/bower_components/font-awesome/fonts/fontawesome-webfont.eot
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/resources/static/adminlte/bower_components/font-awesome/fonts/fontawesome-webfont.svg
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/resources/static/adminlte/bower_components/font-awesome/fonts/fontawesome-webfont.ttf
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/resources/static/adminlte/bower_components/font-awesome/fonts/fontawesome-webfont.woff
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/resources/static/adminlte/bower_components/font-awesome/fonts/fontawesome-webfont.woff2
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/resources/static/adminlte/bower_components/jquery-slimscroll/jquery.slimscroll.min.js
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/resources/static/adminlte/bower_components/jquery/jquery.min.js
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/resources/static/adminlte/bower_components/moment/moment.min.js
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/resources/static/adminlte/dist/css/AdminLTE.min.css
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/resources/static/adminlte/dist/css/skins/_all-skins.min.css
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/resources/static/adminlte/dist/js/adminlte.min.js
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/resources/static/adminlte/plugins/iCheck/icheck.min.js
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/resources/static/adminlte/plugins/iCheck/square/blue.css
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/resources/static/adminlte/plugins/iCheck/square/blue.png
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/resources/static/adminlte/plugins/iCheck/square/blue@2x.png
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/resources/static/favicon.ico
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/resources/static/js/common.1.js
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/resources/static/js/index.js
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/resources/static/js/jobcode.index.1.js
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/resources/static/js/jobgroup.index.1.js
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/resources/static/js/jobinfo.index.1.js
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/resources/static/js/joblog.detail.1.js
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/resources/static/js/joblog.index.1.js
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/resources/static/js/login.1.js
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/resources/static/js/user.index.1.js
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/resources/static/plugins/codemirror/addon/hint/anyword-hint.js
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/resources/static/plugins/codemirror/addon/hint/show-hint.css
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/resources/static/plugins/codemirror/addon/hint/show-hint.js
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/resources/static/plugins/codemirror/lib/codemirror.css
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/resources/static/plugins/codemirror/lib/codemirror.js
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/resources/static/plugins/codemirror/mode/clike/clike.js
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/resources/static/plugins/codemirror/mode/javascript/javascript.js
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/resources/static/plugins/codemirror/mode/php/php.js
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/resources/static/plugins/codemirror/mode/powershell/powershell.js
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/resources/static/plugins/codemirror/mode/python/python.js
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/resources/static/plugins/codemirror/mode/shell/shell.js
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/resources/static/plugins/cronGen/cronGen.js
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/resources/static/plugins/cronGen/cronGen_en.js
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/resources/static/plugins/echarts/echarts.common.min.js
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/resources/static/plugins/jquery/jquery.cookie.js
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/resources/static/plugins/jquery/jquery.validate.min.js
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/resources/static/plugins/layer/layer.js
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/resources/static/plugins/layer/theme/default/icon-ext.png
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/resources/static/plugins/layer/theme/default/icon.png
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/resources/static/plugins/layer/theme/default/layer.css
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/resources/static/plugins/layer/theme/default/loading-0.gif
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/resources/static/plugins/layer/theme/default/loading-1.gif
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/resources/static/plugins/layer/theme/default/loading-2.gif
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/resources/templates/common/common.exception.ftl
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/resources/templates/common/common.macro.ftl
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/resources/templates/help.ftl
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/resources/templates/index.ftl
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/resources/templates/jobcode/jobcode.index.ftl
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/resources/templates/jobgroup/jobgroup.index.ftl
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/resources/templates/jobinfo/jobinfo.index.ftl
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/resources/templates/joblog/joblog.detail.ftl
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/resources/templates/joblog/joblog.index.ftl
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/resources/templates/login.ftl
create mode 100755 pigx-visual/pigx-xxl-job-admin/src/main/resources/templates/user/user.index.ftl
diff --git a/docker-compose.yml b/docker-compose.yml
index d7d94da10..65aeb6188 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -19,15 +19,6 @@ services:
- ./pigx-mysql:/var/lib/mysql
command: --lower_case_table_names=1
- pigx-xxl:
- container_name: pigx-xxl
- image: xuxueli/xxl-job-admin:2.2.0
- restart: always
- environment:
- PARAMS: "--server.port=9080 --spring.datasource.url=jdbc:mysql://pigx-mysql:3306/pigxx_job?Unicode=true&characterEncoding=UTF-8 --spring.datasource.username=root --spring.datasource.password=root"
- ports:
- - 9080:9080
-
pigx-redis:
container_name: pigx-redis
image: redis:5.0.7
@@ -141,6 +132,16 @@ services:
ports:
- 5006:5006
+ pigxx-job:
+ build:
+ context: ./pigx-visual/pigx-xxl-job-admin
+ restart: always
+ container_name: pigx-job
+ hostname: pigx-job
+ image: pigx-job
+ ports:
+ - 9080:9080
+
pigx-tx-manager:
build:
context: ./pigx-visual/pigx-tx-manager
diff --git a/pigx-common/pigx-common-job/src/main/java/com/pig4cloud/pigx/common/job/properties/XxlAdminProperties.java b/pigx-common/pigx-common-job/src/main/java/com/pig4cloud/pigx/common/job/properties/XxlAdminProperties.java
index f795f4c25..face39ba8 100644
--- a/pigx-common/pigx-common-job/src/main/java/com/pig4cloud/pigx/common/job/properties/XxlAdminProperties.java
+++ b/pigx-common/pigx-common-job/src/main/java/com/pig4cloud/pigx/common/job/properties/XxlAdminProperties.java
@@ -31,6 +31,6 @@ public class XxlAdminProperties {
/**
* 调度中心部署跟地址 [选填]:如调度中心集群部署存在多个地址则用逗号分隔。 执行器将会使用该地址进行"执行器心跳注册"和"任务结果回调";为空则关闭自动注册;
*/
- private String addresses = "http://127.0.0.1:8080/xxl-job-admin";
+ private String addresses = "http://pigx-xxl:9080/xxl-job-admin";
}
diff --git a/pigx-visual/pigx-xxl-job-admin/Dockerfile b/pigx-visual/pigx-xxl-job-admin/Dockerfile
new file mode 100755
index 000000000..e4a41e230
--- /dev/null
+++ b/pigx-visual/pigx-xxl-job-admin/Dockerfile
@@ -0,0 +1,18 @@
+FROM pig4cloud/java:8-jre
+
+MAINTAINER wangiegie@gmail.com
+
+ENV TZ=Asia/Shanghai
+ENV JAVA_OPTS="-Xms128m -Xmx256m -Djava.security.egd=file:/dev/./urandom"
+
+RUN ln -sf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
+
+RUN mkdir -p /pigx-xxl-job-admin
+
+WORKDIR /pigx-xxl-job-admin
+
+EXPOSE 9080
+
+ADD ./target/pigx-xxl-job-admin.jar ./
+
+CMD sleep 120;java $JAVA_OPTS -jar pigx-xxl-job-admin.jar
diff --git a/pigx-visual/pigx-xxl-job-admin/pom.xml b/pigx-visual/pigx-xxl-job-admin/pom.xml
new file mode 100755
index 000000000..b5f61cf9e
--- /dev/null
+++ b/pigx-visual/pigx-xxl-job-admin/pom.xml
@@ -0,0 +1,82 @@
+
+ 4.0.0
+
+ com.pig4cloud
+ pigx-visual
+ 3.10.0
+
+
+ pigx-xxl-job-admin
+ xxl job admin 控制台内置方便开发
+ jar
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ javax.servlet
+ javax.servlet-api
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-freemarker
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-mail
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-actuator
+
+
+
+
+ org.mybatis.spring.boot
+ mybatis-spring-boot-starter
+ 2.1.3
+
+
+
+ mysql
+ mysql-connector-java
+
+
+
+
+ com.xuxueli
+ xxl-job-core
+ ${xxl.job.version}
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-undertow
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+ io.fabric8
+ docker-maven-plugin
+
+
+
+
diff --git a/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/PigXxlJobAdminApplication.java b/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/PigXxlJobAdminApplication.java
new file mode 100755
index 000000000..e667a1981
--- /dev/null
+++ b/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/PigXxlJobAdminApplication.java
@@ -0,0 +1,16 @@
+package com.xxl.job.admin;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+/**
+ * @author xuxueli 2018-10-28 00:38:13
+ */
+@SpringBootApplication
+public class PigXxlJobAdminApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(PigXxlJobAdminApplication.class, args);
+ }
+
+}
\ No newline at end of file
diff --git a/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/controller/IndexController.java b/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/controller/IndexController.java
new file mode 100755
index 000000000..4e78da0da
--- /dev/null
+++ b/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/controller/IndexController.java
@@ -0,0 +1,99 @@
+package com.xxl.job.admin.controller;
+
+import com.xxl.job.admin.controller.annotation.PermissionLimit;
+import com.xxl.job.admin.service.LoginService;
+import com.xxl.job.admin.service.XxlJobService;
+import com.xxl.job.core.biz.model.ReturnT;
+import org.springframework.beans.propertyeditors.CustomDateEditor;
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.Model;
+import org.springframework.web.bind.WebDataBinder;
+import org.springframework.web.bind.annotation.InitBinder;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.web.servlet.ModelAndView;
+import org.springframework.web.servlet.view.RedirectView;
+
+import javax.annotation.Resource;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Map;
+
+/**
+ * index controller
+ *
+ * @author xuxueli 2015-12-19 16:13:16
+ */
+@Controller
+public class IndexController {
+
+ @Resource
+ private XxlJobService xxlJobService;
+
+ @Resource
+ private LoginService loginService;
+
+ @RequestMapping("/")
+ public String index(Model model) {
+
+ Map 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/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/exception/XxlJobException.java b/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/exception/XxlJobException.java
new file mode 100755
index 000000000..bf4eff6c5
--- /dev/null
+++ b/pigx-visual/pigx-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/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobGroup.java b/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobGroup.java
new file mode 100755
index 000000000..1872cb7e7
--- /dev/null
+++ b/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobGroup.java
@@ -0,0 +1,72 @@
+package com.xxl.job.admin.core.model;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Created by xuxueli on 16/9/30.
+ */
+public class XxlJobGroup {
+
+ private int id;
+
+ private String appname;
+
+ private String title;
+
+ private int addressType; // 执行器地址类型:0=自动注册、1=手动录入
+
+ private String addressList; // 执行器地址列表,多地址逗号分隔(手动录入)
+
+ // registry list
+ private List registryList; // 执行器地址列表(系统注册)
+
+ public List getRegistryList() {
+ if (addressList != null && addressList.trim().length() > 0) {
+ registryList = new ArrayList(Arrays.asList(addressList.split(",")));
+ }
+ return registryList;
+ }
+
+ public int getId() {
+ return id;
+ }
+
+ public void setId(int id) {
+ this.id = id;
+ }
+
+ public String getAppname() {
+ return appname;
+ }
+
+ public void setAppname(String appname) {
+ this.appname = appname;
+ }
+
+ public String getTitle() {
+ return title;
+ }
+
+ public void setTitle(String title) {
+ this.title = title;
+ }
+
+ public int getAddressType() {
+ return addressType;
+ }
+
+ public void setAddressType(int addressType) {
+ this.addressType = addressType;
+ }
+
+ public String getAddressList() {
+ return addressList;
+ }
+
+ public void setAddressList(String addressList) {
+ this.addressList = addressList;
+ }
+
+}
diff --git a/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobInfo.java b/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobInfo.java
new file mode 100755
index 000000000..26bb14792
--- /dev/null
+++ b/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobInfo.java
@@ -0,0 +1,232 @@
+package com.xxl.job.admin.core.model;
+
+import java.util.Date;
+
+/**
+ * xxl-job info
+ *
+ * @author xuxueli 2016-1-12 18:25:49
+ */
+public class XxlJobInfo {
+
+ private int id; // 主键ID
+
+ private int jobGroup; // 执行器主键ID
+
+ private String jobCron; // 任务执行CRON表达式
+
+ private String jobDesc;
+
+ private Date addTime;
+
+ private Date updateTime;
+
+ private String author; // 负责人
+
+ private String alarmEmail; // 报警邮件
+
+ private String executorRouteStrategy; // 执行器路由策略
+
+ private String executorHandler; // 执行器,任务Handler名称
+
+ private String executorParam; // 执行器,任务参数
+
+ private String executorBlockStrategy; // 阻塞处理策略
+
+ private int executorTimeout; // 任务执行超时时间,单位秒
+
+ private int executorFailRetryCount; // 失败重试次数
+
+ private String glueType; // GLUE类型 #com.xxl.job.core.glue.GlueTypeEnum
+
+ private String glueSource; // GLUE源代码
+
+ private String glueRemark; // GLUE备注
+
+ private Date glueUpdatetime; // GLUE更新时间
+
+ private String childJobId; // 子任务ID,多个逗号分隔
+
+ private int triggerStatus; // 调度状态:0-停止,1-运行
+
+ private long triggerLastTime; // 上次调度时间
+
+ private long triggerNextTime; // 下次调度时间
+
+ public int getId() {
+ return id;
+ }
+
+ public void setId(int id) {
+ this.id = id;
+ }
+
+ public int getJobGroup() {
+ return jobGroup;
+ }
+
+ public void setJobGroup(int jobGroup) {
+ this.jobGroup = jobGroup;
+ }
+
+ public String getJobCron() {
+ return jobCron;
+ }
+
+ public void setJobCron(String jobCron) {
+ this.jobCron = jobCron;
+ }
+
+ public String getJobDesc() {
+ return jobDesc;
+ }
+
+ public void setJobDesc(String jobDesc) {
+ this.jobDesc = jobDesc;
+ }
+
+ public Date getAddTime() {
+ return addTime;
+ }
+
+ public void setAddTime(Date addTime) {
+ this.addTime = addTime;
+ }
+
+ public Date getUpdateTime() {
+ return updateTime;
+ }
+
+ public void setUpdateTime(Date updateTime) {
+ this.updateTime = updateTime;
+ }
+
+ public String getAuthor() {
+ return author;
+ }
+
+ public void setAuthor(String author) {
+ this.author = author;
+ }
+
+ public String getAlarmEmail() {
+ return alarmEmail;
+ }
+
+ public void setAlarmEmail(String alarmEmail) {
+ this.alarmEmail = alarmEmail;
+ }
+
+ public String getExecutorRouteStrategy() {
+ return executorRouteStrategy;
+ }
+
+ public void setExecutorRouteStrategy(String executorRouteStrategy) {
+ this.executorRouteStrategy = executorRouteStrategy;
+ }
+
+ public String getExecutorHandler() {
+ return executorHandler;
+ }
+
+ public void setExecutorHandler(String executorHandler) {
+ this.executorHandler = executorHandler;
+ }
+
+ public String getExecutorParam() {
+ return executorParam;
+ }
+
+ public void setExecutorParam(String executorParam) {
+ this.executorParam = executorParam;
+ }
+
+ public String getExecutorBlockStrategy() {
+ return executorBlockStrategy;
+ }
+
+ public void setExecutorBlockStrategy(String executorBlockStrategy) {
+ this.executorBlockStrategy = executorBlockStrategy;
+ }
+
+ public int getExecutorTimeout() {
+ return executorTimeout;
+ }
+
+ public void setExecutorTimeout(int executorTimeout) {
+ this.executorTimeout = executorTimeout;
+ }
+
+ public int getExecutorFailRetryCount() {
+ return executorFailRetryCount;
+ }
+
+ public void setExecutorFailRetryCount(int executorFailRetryCount) {
+ this.executorFailRetryCount = executorFailRetryCount;
+ }
+
+ public String getGlueType() {
+ return glueType;
+ }
+
+ public void setGlueType(String glueType) {
+ this.glueType = glueType;
+ }
+
+ public String getGlueSource() {
+ return glueSource;
+ }
+
+ public void setGlueSource(String glueSource) {
+ this.glueSource = glueSource;
+ }
+
+ public String getGlueRemark() {
+ return glueRemark;
+ }
+
+ public void setGlueRemark(String glueRemark) {
+ this.glueRemark = glueRemark;
+ }
+
+ public Date getGlueUpdatetime() {
+ return glueUpdatetime;
+ }
+
+ public void setGlueUpdatetime(Date glueUpdatetime) {
+ this.glueUpdatetime = glueUpdatetime;
+ }
+
+ public String getChildJobId() {
+ return childJobId;
+ }
+
+ public void setChildJobId(String childJobId) {
+ this.childJobId = childJobId;
+ }
+
+ public int getTriggerStatus() {
+ return triggerStatus;
+ }
+
+ public void setTriggerStatus(int triggerStatus) {
+ this.triggerStatus = triggerStatus;
+ }
+
+ public long getTriggerLastTime() {
+ return triggerLastTime;
+ }
+
+ public void setTriggerLastTime(long triggerLastTime) {
+ this.triggerLastTime = triggerLastTime;
+ }
+
+ public long getTriggerNextTime() {
+ return triggerNextTime;
+ }
+
+ public void setTriggerNextTime(long triggerNextTime) {
+ this.triggerNextTime = triggerNextTime;
+ }
+
+}
diff --git a/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobLog.java b/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobLog.java
new file mode 100755
index 000000000..a9da03fc2
--- /dev/null
+++ b/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobLog.java
@@ -0,0 +1,167 @@
+package com.xxl.job.admin.core.model;
+
+import java.util.Date;
+
+/**
+ * xxl-job log, used to track trigger process
+ *
+ * @author xuxueli 2015-12-19 23:19:09
+ */
+public class XxlJobLog {
+
+ private long id;
+
+ // job info
+ private int jobGroup;
+
+ private int jobId;
+
+ // execute info
+ private String executorAddress;
+
+ private String executorHandler;
+
+ private String executorParam;
+
+ private String executorShardingParam;
+
+ private int executorFailRetryCount;
+
+ // trigger info
+ private Date triggerTime;
+
+ private int triggerCode;
+
+ private String triggerMsg;
+
+ // handle info
+ private Date handleTime;
+
+ private int handleCode;
+
+ private String handleMsg;
+
+ // alarm info
+ private int alarmStatus;
+
+ public long getId() {
+ return id;
+ }
+
+ public void setId(long id) {
+ this.id = id;
+ }
+
+ public int getJobGroup() {
+ return jobGroup;
+ }
+
+ public void setJobGroup(int jobGroup) {
+ this.jobGroup = jobGroup;
+ }
+
+ public int getJobId() {
+ return jobId;
+ }
+
+ public void setJobId(int jobId) {
+ this.jobId = jobId;
+ }
+
+ public String getExecutorAddress() {
+ return executorAddress;
+ }
+
+ public void setExecutorAddress(String executorAddress) {
+ this.executorAddress = executorAddress;
+ }
+
+ public String getExecutorHandler() {
+ return executorHandler;
+ }
+
+ public void setExecutorHandler(String executorHandler) {
+ this.executorHandler = executorHandler;
+ }
+
+ public String getExecutorParam() {
+ return executorParam;
+ }
+
+ public void setExecutorParam(String executorParam) {
+ this.executorParam = executorParam;
+ }
+
+ public String getExecutorShardingParam() {
+ return executorShardingParam;
+ }
+
+ public void setExecutorShardingParam(String executorShardingParam) {
+ this.executorShardingParam = executorShardingParam;
+ }
+
+ public int getExecutorFailRetryCount() {
+ return executorFailRetryCount;
+ }
+
+ public void setExecutorFailRetryCount(int executorFailRetryCount) {
+ this.executorFailRetryCount = executorFailRetryCount;
+ }
+
+ public Date getTriggerTime() {
+ return triggerTime;
+ }
+
+ public void setTriggerTime(Date triggerTime) {
+ this.triggerTime = triggerTime;
+ }
+
+ public int getTriggerCode() {
+ return triggerCode;
+ }
+
+ public void setTriggerCode(int triggerCode) {
+ this.triggerCode = triggerCode;
+ }
+
+ public String getTriggerMsg() {
+ return triggerMsg;
+ }
+
+ public void setTriggerMsg(String triggerMsg) {
+ this.triggerMsg = triggerMsg;
+ }
+
+ public Date getHandleTime() {
+ return handleTime;
+ }
+
+ public void setHandleTime(Date handleTime) {
+ this.handleTime = handleTime;
+ }
+
+ public int getHandleCode() {
+ return handleCode;
+ }
+
+ public void setHandleCode(int handleCode) {
+ this.handleCode = handleCode;
+ }
+
+ public String getHandleMsg() {
+ return handleMsg;
+ }
+
+ public void setHandleMsg(String handleMsg) {
+ this.handleMsg = handleMsg;
+ }
+
+ public int getAlarmStatus() {
+ return alarmStatus;
+ }
+
+ public void setAlarmStatus(int alarmStatus) {
+ this.alarmStatus = alarmStatus;
+ }
+
+}
diff --git a/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobLogGlue.java b/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobLogGlue.java
new file mode 100755
index 000000000..040cab06d
--- /dev/null
+++ b/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobLogGlue.java
@@ -0,0 +1,82 @@
+package com.xxl.job.admin.core.model;
+
+import java.util.Date;
+
+/**
+ * xxl-job log for glue, used to track job code process
+ *
+ * @author xuxueli 2016-5-19 17:57:46
+ */
+public class XxlJobLogGlue {
+
+ private int id;
+
+ private int jobId; // 任务主键ID
+
+ private String glueType; // GLUE类型 #com.xxl.job.core.glue.GlueTypeEnum
+
+ private String glueSource;
+
+ private String glueRemark;
+
+ private Date addTime;
+
+ private Date updateTime;
+
+ public int getId() {
+ return id;
+ }
+
+ public void setId(int id) {
+ this.id = id;
+ }
+
+ public int getJobId() {
+ return jobId;
+ }
+
+ public void setJobId(int jobId) {
+ this.jobId = jobId;
+ }
+
+ public String getGlueType() {
+ return glueType;
+ }
+
+ public void setGlueType(String glueType) {
+ this.glueType = glueType;
+ }
+
+ public String getGlueSource() {
+ return glueSource;
+ }
+
+ public void setGlueSource(String glueSource) {
+ this.glueSource = glueSource;
+ }
+
+ public String getGlueRemark() {
+ return glueRemark;
+ }
+
+ public void setGlueRemark(String glueRemark) {
+ this.glueRemark = glueRemark;
+ }
+
+ public Date getAddTime() {
+ return addTime;
+ }
+
+ public void setAddTime(Date addTime) {
+ this.addTime = addTime;
+ }
+
+ public Date getUpdateTime() {
+ return updateTime;
+ }
+
+ public void setUpdateTime(Date updateTime) {
+ this.updateTime = updateTime;
+ }
+
+}
diff --git a/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobLogReport.java b/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobLogReport.java
new file mode 100755
index 000000000..bf7907f8c
--- /dev/null
+++ b/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobLogReport.java
@@ -0,0 +1,57 @@
+package com.xxl.job.admin.core.model;
+
+import java.util.Date;
+
+public class XxlJobLogReport {
+
+ private int id;
+
+ private Date triggerDay;
+
+ private int runningCount;
+
+ private int sucCount;
+
+ private int failCount;
+
+ public int getId() {
+ return id;
+ }
+
+ public void setId(int id) {
+ this.id = id;
+ }
+
+ public Date getTriggerDay() {
+ return triggerDay;
+ }
+
+ public void setTriggerDay(Date triggerDay) {
+ this.triggerDay = triggerDay;
+ }
+
+ public int getRunningCount() {
+ return runningCount;
+ }
+
+ public void setRunningCount(int runningCount) {
+ this.runningCount = runningCount;
+ }
+
+ public int getSucCount() {
+ return sucCount;
+ }
+
+ public void setSucCount(int sucCount) {
+ this.sucCount = sucCount;
+ }
+
+ public int getFailCount() {
+ return failCount;
+ }
+
+ public void setFailCount(int failCount) {
+ this.failCount = failCount;
+ }
+
+}
diff --git a/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobRegistry.java b/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobRegistry.java
new file mode 100755
index 000000000..d8e5da2fa
--- /dev/null
+++ b/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobRegistry.java
@@ -0,0 +1,60 @@
+package com.xxl.job.admin.core.model;
+
+import java.util.Date;
+
+/**
+ * Created by xuxueli on 16/9/30.
+ */
+public class XxlJobRegistry {
+
+ private int id;
+
+ private String registryGroup;
+
+ private String registryKey;
+
+ private String registryValue;
+
+ private Date updateTime;
+
+ public int getId() {
+ return id;
+ }
+
+ public void setId(int id) {
+ this.id = id;
+ }
+
+ public String getRegistryGroup() {
+ return registryGroup;
+ }
+
+ public void setRegistryGroup(String registryGroup) {
+ this.registryGroup = registryGroup;
+ }
+
+ public String getRegistryKey() {
+ return registryKey;
+ }
+
+ public void setRegistryKey(String registryKey) {
+ this.registryKey = registryKey;
+ }
+
+ public String getRegistryValue() {
+ return registryValue;
+ }
+
+ public void setRegistryValue(String registryValue) {
+ this.registryValue = registryValue;
+ }
+
+ public Date getUpdateTime() {
+ return updateTime;
+ }
+
+ public void setUpdateTime(Date updateTime) {
+ this.updateTime = updateTime;
+ }
+
+}
diff --git a/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobUser.java b/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobUser.java
new file mode 100755
index 000000000..04e64d27e
--- /dev/null
+++ b/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobUser.java
@@ -0,0 +1,78 @@
+package com.xxl.job.admin.core.model;
+
+import org.springframework.util.StringUtils;
+
+/**
+ * @author xuxueli 2019-05-04 16:43:12
+ */
+public class XxlJobUser {
+
+ private int id;
+
+ private String username; // 账号
+
+ private String password; // 密码
+
+ private int role; // 角色:0-普通用户、1-管理员
+
+ private String permission; // 权限:执行器ID列表,多个逗号分割
+
+ public int getId() {
+ return id;
+ }
+
+ public void setId(int id) {
+ this.id = id;
+ }
+
+ public String getUsername() {
+ return username;
+ }
+
+ public void setUsername(String username) {
+ this.username = username;
+ }
+
+ public String getPassword() {
+ return password;
+ }
+
+ public void setPassword(String password) {
+ this.password = password;
+ }
+
+ public int getRole() {
+ return role;
+ }
+
+ public void setRole(int role) {
+ this.role = role;
+ }
+
+ public String getPermission() {
+ return permission;
+ }
+
+ public void setPermission(String permission) {
+ this.permission = permission;
+ }
+
+ // plugin
+ public boolean validPermission(int jobGroup) {
+ if (this.role == 1) {
+ return true;
+ }
+ else {
+ if (StringUtils.hasText(this.permission)) {
+ for (String permissionItem : this.permission.split(",")) {
+ if (String.valueOf(jobGroup).equals(permissionItem)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ }
+
+}
diff --git a/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/RemoteHttpJobBean.java b/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/RemoteHttpJobBean.java
new file mode 100755
index 000000000..3cc0b51cf
--- /dev/null
+++ b/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/RemoteHttpJobBean.java
@@ -0,0 +1,33 @@
+// package com.xxl.job.admin.core.jobbean;
+//
+// import com.xxl.job.admin.core.thread.JobTriggerPoolHelper;
+// import com.xxl.job.admin.core.trigger.TriggerTypeEnum;
+// import org.quartz.JobExecutionContext;
+// import org.quartz.JobExecutionException;
+// import org.quartz.JobKey;
+// import org.slf4j.Logger;
+// import org.slf4j.LoggerFactory;
+// import org.springframework.scheduling.quartz.QuartzJobBean;
+//
+/// **
+// * http job bean
+// * “@DisallowConcurrentExecution” disable concurrent, thread size can not be only one,
+// better given more
+// * @author xuxueli 2015-12-17 18:20:34
+// */
+//// @DisallowConcurrentExecution
+// public class RemoteHttpJobBean extends QuartzJobBean {
+// private static Logger logger = LoggerFactory.getLogger(RemoteHttpJobBean.class);
+//
+// @Override
+// protected void executeInternal(JobExecutionContext context)
+// throws JobExecutionException {
+//
+// // load jobId
+// JobKey jobKey = context.getTrigger().getJobKey();
+// Integer jobId = Integer.valueOf(jobKey.getName());
+//
+//
+// }
+//
+// }
\ No newline at end of file
diff --git a/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/XxlJobDynamicScheduler.java b/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/XxlJobDynamicScheduler.java
new file mode 100755
index 000000000..b321f6c73
--- /dev/null
+++ b/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/XxlJobDynamicScheduler.java
@@ -0,0 +1,430 @@
+// package com.xxl.job.admin.core.schedule;
+//
+// import com.xxl.job.admin.core.conf.XxlJobAdminConfig;
+// import com.xxl.job.admin.core.jobbean.RemoteHttpJobBean;
+// import com.xxl.job.admin.core.model.XxlJobInfo;
+// import com.xxl.job.admin.core.thread.JobFailMonitorHelper;
+// import com.xxl.job.admin.core.thread.JobRegistryMonitorHelper;
+// import com.xxl.job.admin.core.thread.JobTriggerPoolHelper;
+// import com.xxl.job.admin.core.util.I18nUtil;
+// import com.xxl.job.core.biz.AdminBiz;
+// import com.xxl.job.core.biz.ExecutorBiz;
+// import com.xxl.job.core.enums.ExecutorBlockStrategyEnum;
+// import com.xxl.rpc.remoting.invoker.XxlRpcInvokerFactory;
+// import com.xxl.rpc.remoting.invoker.call.CallType;
+// import com.xxl.rpc.remoting.invoker.reference.XxlRpcReferenceBean;
+// import com.xxl.rpc.remoting.invoker.route.LoadBalance;
+// import com.xxl.rpc.remoting.net.NetEnum;
+// import com.xxl.rpc.remoting.net.impl.servlet.server.ServletServerHandler;
+// import com.xxl.rpc.remoting.provider.XxlRpcProviderFactory;
+// import com.xxl.rpc.serialize.Serializer;
+// import org.quartz.*;
+// import org.quartz.Trigger.TriggerState;
+// import org.quartz.impl.triggers.CronTriggerImpl;
+// import org.slf4j.Logger;
+// import org.slf4j.LoggerFactory;
+// import org.springframework.util.Assert;
+//
+// import javax.servlet.ServletException;
+// import javax.servlet.http.HttpServletRequest;
+// import javax.servlet.http.HttpServletResponse;
+// import java.io.IOException;
+// import java.util.Date;
+// import java.util.concurrent.ConcurrentHashMap;
+//
+/// **
+// * base quartz scheduler util
+// * @author xuxueli 2015-12-19 16:13:53
+// */
+// public final class XxlJobDynamicScheduler {
+// private static final Logger logger =
+// LoggerFactory.getLogger(XxlJobDynamicScheduler_old.class);
+//
+// // ---------------------- param ----------------------
+//
+// // scheduler
+// private static Scheduler scheduler;
+// public void setScheduler(Scheduler scheduler) {
+// XxlJobDynamicScheduler_old.scheduler = scheduler;
+// }
+//
+//
+// // ---------------------- init + destroy ----------------------
+// public void start() throws Exception {
+// // valid
+// Assert.notNull(scheduler, "quartz scheduler is null");
+//
+// // init i18n
+// initI18n();
+//
+// // admin registry monitor run
+// JobRegistryMonitorHelper.getInstance().start();
+//
+// // admin monitor run
+// JobFailMonitorHelper.getInstance().start();
+//
+// // admin-server
+// initRpcProvider();
+//
+// logger.info(">>>>>>>>> init xxl-job admin success.");
+// }
+//
+//
+// public void destroy() throws Exception {
+// // admin trigger pool stop
+// JobTriggerPoolHelper.toStop();
+//
+// // admin registry stop
+// JobRegistryMonitorHelper.getInstance().toStop();
+//
+// // admin monitor stop
+// JobFailMonitorHelper.getInstance().toStop();
+//
+// // admin-server
+// stopRpcProvider();
+// }
+//
+//
+// // ---------------------- I18n ----------------------
+//
+// private void initI18n(){
+// for (ExecutorBlockStrategyEnum item:ExecutorBlockStrategyEnum.values()) {
+// item.setTitle(I18nUtil.getString("jobconf_block_".concat(item.name())));
+// }
+// }
+//
+//
+// // ---------------------- admin rpc provider (no server version) ----------------------
+// private static ServletServerHandler servletServerHandler;
+// private void initRpcProvider(){
+// // init
+// XxlRpcProviderFactory xxlRpcProviderFactory = new XxlRpcProviderFactory();
+// xxlRpcProviderFactory.initConfig(
+// NetEnum.NETTY_HTTP,
+// Serializer.SerializeEnum.HESSIAN.getSerializer(),
+// null,
+// 0,
+// XxlJobAdminConfig.getAdminConfig().getAccessToken(),
+// null,
+// null);
+//
+// // add services
+// xxlRpcProviderFactory.addService(AdminBiz.class.getName(), null,
+// XxlJobAdminConfig.getAdminConfig().getAdminBiz());
+//
+// // servlet handler
+// servletServerHandler = new ServletServerHandler(xxlRpcProviderFactory);
+// }
+// private void stopRpcProvider() throws Exception {
+// XxlRpcInvokerFactory.getInstance().stop();
+// }
+// public static void invokeAdminService(HttpServletRequest request, HttpServletResponse
+// response) throws IOException, ServletException {
+// servletServerHandler.handle(null, request, response);
+// }
+//
+//
+// // ---------------------- executor-client ----------------------
+// private static ConcurrentHashMap 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 = (ExecutorBiz) new XxlRpcReferenceBean(
+// NetEnum.NETTY_HTTP,
+// Serializer.SerializeEnum.HESSIAN.getSerializer(),
+// CallType.SYNC,
+// LoadBalance.ROUND,
+// ExecutorBiz.class,
+// null,
+// 5000,
+// address,
+// XxlJobAdminConfig.getAdminConfig().getAccessToken(),
+// null,
+// null).getObject();
+//
+// executorBizRepository.put(address, executorBiz);
+// return executorBiz;
+// }
+//
+//
+// // ---------------------- schedule util ----------------------
+//
+// /**
+// * fill job info
+// *
+// * @param jobInfo
+// */
+// public static void fillJobInfo(XxlJobInfo jobInfo) {
+//
+// String name = String.valueOf(jobInfo.getId());
+//
+// // trigger key
+// TriggerKey triggerKey = TriggerKey.triggerKey(name);
+// try {
+//
+// // trigger cron
+// Trigger trigger = scheduler.getTrigger(triggerKey);
+// if (trigger!=null && trigger instanceof CronTriggerImpl) {
+// String cronExpression = ((CronTriggerImpl) trigger).getCronExpression();
+// jobInfo.setJobCron(cronExpression);
+// }
+//
+// // trigger state
+// TriggerState triggerState = scheduler.getTriggerState(triggerKey);
+// if (triggerState!=null) {
+// jobInfo.setJobStatus(triggerState.name());
+// }
+//
+// //JobKey jobKey = new JobKey(jobInfo.getJobName(),
+// String.valueOf(jobInfo.getJobGroup()));
+// //JobDetail jobDetail = scheduler.getJobDetail(jobKey);
+// //String jobClass = jobDetail.getJobClass().getName();
+//
+// } catch (SchedulerException e) {
+// logger.error(e.getMessage(), e);
+// }
+// }
+//
+//
+// /**
+// * add trigger + job
+// *
+// * @param jobName
+// * @param cronExpression
+// * @return
+// * @throws SchedulerException
+// */
+// public static boolean addJob(String jobName, String cronExpression) throws
+// SchedulerException {
+// // 1、job key
+// TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
+// JobKey jobKey = new JobKey(jobName);
+//
+// // 2、valid
+// if (scheduler.checkExists(triggerKey)) {
+// return true; // PASS
+// }
+//
+// // 3、corn trigger
+// CronScheduleBuilder cronScheduleBuilder =
+// CronScheduleBuilder.cronSchedule(cronExpression).withMisfireHandlingInstructionDoNothing();
+// // withMisfireHandlingInstructionDoNothing 忽略掉调度终止过程中忽略的调度
+// CronTrigger cronTrigger =
+// TriggerBuilder.newTrigger().withIdentity(triggerKey).withSchedule(cronScheduleBuilder).build();
+//
+// // 4、job detail
+// Class extends Job> jobClass_ = RemoteHttpJobBean.class; //
+// Class.forName(jobInfo.getJobClass());
+// JobDetail jobDetail = JobBuilder.newJob(jobClass_).withIdentity(jobKey).build();
+//
+// /*if (jobInfo.getJobData()!=null) {
+// JobDataMap jobDataMap = jobDetail.getJobDataMap();
+// jobDataMap.putAll(JacksonUtil.readValue(jobInfo.getJobData(), Map.class));
+// // JobExecutionContext context.getMergedJobDataMap().get("mailGuid");
+// }*/
+//
+// // 5、schedule job
+// Date date = scheduler.scheduleJob(jobDetail, cronTrigger);
+//
+// logger.info(">>>>>>>>>>> addJob success(quartz), jobDetail:{}, cronTrigger:{},
+// date:{}", jobDetail, cronTrigger, date);
+// return true;
+// }
+//
+//
+// /**
+// * remove trigger + job
+// *
+// * @param jobName
+// * @return
+// * @throws SchedulerException
+// */
+// public static boolean removeJob(String jobName) throws SchedulerException {
+//
+// JobKey jobKey = new JobKey(jobName);
+// scheduler.deleteJob(jobKey);
+//
+// /*TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
+// if (scheduler.checkExists(triggerKey)) {
+// scheduler.unscheduleJob(triggerKey); // trigger + job
+// }*/
+//
+// logger.info(">>>>>>>>>>> removeJob success(quartz), jobKey:{}", jobKey);
+// return true;
+// }
+//
+//
+// /**
+// * updateJobCron
+// *
+// * @param jobName
+// * @param cronExpression
+// * @return
+// * @throws SchedulerException
+// */
+// public static boolean updateJobCron(String jobName, String cronExpression) throws
+// SchedulerException {
+//
+// // 1、job key
+// TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
+//
+// // 2、valid
+// if (!scheduler.checkExists(triggerKey)) {
+// return true; // PASS
+// }
+//
+// CronTrigger oldTrigger = (CronTrigger) scheduler.getTrigger(triggerKey);
+//
+// // 3、avoid repeat cron
+// String oldCron = oldTrigger.getCronExpression();
+// if (oldCron.equals(cronExpression)){
+// return true; // PASS
+// }
+//
+// // 4、new cron trigger
+// CronScheduleBuilder cronScheduleBuilder =
+// CronScheduleBuilder.cronSchedule(cronExpression).withMisfireHandlingInstructionDoNothing();
+// oldTrigger =
+// oldTrigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(cronScheduleBuilder).build();
+//
+// // 5、rescheduleJob
+// scheduler.rescheduleJob(triggerKey, oldTrigger);
+//
+// /*
+// JobKey jobKey = new JobKey(jobName);
+//
+// // old job detail
+// JobDetail jobDetail = scheduler.getJobDetail(jobKey);
+//
+// // new trigger
+// HashSet triggerSet = new HashSet();
+// triggerSet.add(cronTrigger);
+// // cover trigger of job detail
+// scheduler.scheduleJob(jobDetail, triggerSet, true);*/
+//
+// logger.info(">>>>>>>>>>> resumeJob success, JobName:{}", jobName);
+// return true;
+// }
+//
+//
+// /**
+// * pause
+// *
+// * @param jobName
+// * @return
+// * @throws SchedulerException
+// */
+// /*public static boolean pauseJob(String jobName) throws SchedulerException {
+//
+// TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
+//
+// boolean result = false;
+// if (scheduler.checkExists(triggerKey)) {
+// scheduler.pauseTrigger(triggerKey);
+// result = true;
+// }
+//
+// logger.info(">>>>>>>>>>> pauseJob {}, triggerKey:{}",
+// (result?"success":"fail"),triggerKey);
+// return result;
+// }*/
+//
+//
+// /**
+// * resume
+// *
+// * @param jobName
+// * @return
+// * @throws SchedulerException
+// */
+// /*public static boolean resumeJob(String jobName) throws SchedulerException {
+//
+// TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
+//
+// boolean result = false;
+// if (scheduler.checkExists(triggerKey)) {
+// scheduler.resumeTrigger(triggerKey);
+// result = true;
+// }
+//
+// logger.info(">>>>>>>>>>> resumeJob {}, triggerKey:{}", (result?"success":"fail"),
+// triggerKey);
+// return result;
+// }*/
+//
+//
+// /**
+// * run
+// *
+// * @param jobName
+// * @return
+// * @throws SchedulerException
+// */
+// /*public static boolean triggerJob(String jobName) throws SchedulerException {
+// // TriggerKey : name + group
+// JobKey jobKey = new JobKey(jobName);
+// TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
+//
+// boolean result = false;
+// if (scheduler.checkExists(triggerKey)) {
+// scheduler.triggerJob(jobKey);
+// result = true;
+// logger.info(">>>>>>>>>>> runJob success, jobKey:{}", jobKey);
+// } else {
+// logger.info(">>>>>>>>>>> runJob fail, jobKey:{}", jobKey);
+// }
+// return result;
+// }*/
+//
+//
+// /**
+// * finaAllJobList
+// *
+// * @return
+// *//*
+// @Deprecated
+// public static List> finaAllJobList(){
+// List> jobList = new ArrayList>();
+//
+// try {
+// if (scheduler.getJobGroupNames()==null || scheduler.getJobGroupNames().size()==0) {
+// return null;
+// }
+// String groupName = scheduler.getJobGroupNames().get(0);
+// Set jobKeys = scheduler.getJobKeys(GroupMatcher.jobGroupEquals(groupName));
+// if (jobKeys!=null && jobKeys.size()>0) {
+// for (JobKey jobKey : jobKeys) {
+// TriggerKey triggerKey = TriggerKey.triggerKey(jobKey.getName(),
+// Scheduler.DEFAULT_GROUP);
+// Trigger trigger = scheduler.getTrigger(triggerKey);
+// JobDetail jobDetail = scheduler.getJobDetail(jobKey);
+// TriggerState triggerState = scheduler.getTriggerState(triggerKey);
+// Map jobMap = new HashMap();
+// jobMap.put("TriggerKey", triggerKey);
+// jobMap.put("Trigger", trigger);
+// jobMap.put("JobDetail", jobDetail);
+// jobMap.put("TriggerState", triggerState);
+// jobList.add(jobMap);
+// }
+// }
+//
+// } catch (SchedulerException e) {
+// logger.error(e.getMessage(), e);
+// return null;
+// }
+// return jobList;
+// }*/
+//
+// }
\ No newline at end of file
diff --git a/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/XxlJobThreadPool.java b/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/XxlJobThreadPool.java
new file mode 100755
index 000000000..daf02128f
--- /dev/null
+++ b/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/XxlJobThreadPool.java
@@ -0,0 +1,58 @@
+// package com.xxl.job.admin.core.quartz;
+//
+// import org.quartz.SchedulerConfigException;
+// import org.quartz.spi.ThreadPool;
+//
+/// **
+// * single thread pool, for async trigger
+// *
+// * @author xuxueli 2019-03-06
+// */
+// public class XxlJobThreadPool implements ThreadPool {
+//
+// @Override
+// public boolean runInThread(Runnable runnable) {
+//
+// // async run
+// runnable.run();
+// return true;
+//
+// //return false;
+// }
+//
+// @Override
+// public int blockForAvailableThreads() {
+// return 1;
+// }
+//
+// @Override
+// public void initialize() throws SchedulerConfigException {
+//
+// }
+//
+// @Override
+// public void shutdown(boolean waitForJobsToComplete) {
+//
+// }
+//
+// @Override
+// public int getPoolSize() {
+// return 1;
+// }
+//
+// @Override
+// public void setInstanceId(String schedInstId) {
+//
+// }
+//
+// @Override
+// public void setInstanceName(String schedName) {
+//
+// }
+//
+// // support
+// public void setThreadCount(int count) {
+// //
+// }
+//
+// }
diff --git a/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/ExecutorRouteStrategyEnum.java b/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/ExecutorRouteStrategyEnum.java
new file mode 100755
index 000000000..90adb7574
--- /dev/null
+++ b/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/ExecutorRouteStrategyEnum.java
@@ -0,0 +1,56 @@
+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/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/ExecutorRouter.java b/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/ExecutorRouter.java
new file mode 100755
index 000000000..9765719d4
--- /dev/null
+++ b/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/ExecutorRouter.java
@@ -0,0 +1,24 @@
+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/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteBusyover.java b/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteBusyover.java
new file mode 100755
index 000000000..8a4b91b45
--- /dev/null
+++ b/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteBusyover.java
@@ -0,0 +1,48 @@
+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/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteConsistentHash.java b/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteConsistentHash.java
new file mode 100755
index 000000000..e60790280
--- /dev/null
+++ b/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteConsistentHash.java
@@ -0,0 +1,84 @@
+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(int 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/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteFailover.java b/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteFailover.java
new file mode 100755
index 000000000..e93cf6888
--- /dev/null
+++ b/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteFailover.java
@@ -0,0 +1,48 @@
+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/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteFirst.java b/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteFirst.java
new file mode 100755
index 000000000..f5630c33c
--- /dev/null
+++ b/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteFirst.java
@@ -0,0 +1,19 @@
+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/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteLFU.java b/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteLFU.java
new file mode 100755
index 000000000..fb0eab633
--- /dev/null
+++ b/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteLFU.java
@@ -0,0 +1,79 @@
+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(int 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);
+ String minAddress = addressItem.getKey();
+ 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/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteLRU.java b/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteLRU.java
new file mode 100755
index 000000000..59b48f5ab
--- /dev/null
+++ b/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteLRU.java
@@ -0,0 +1,75 @@
+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(int 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/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteLast.java b/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteLast.java
new file mode 100755
index 000000000..431815b08
--- /dev/null
+++ b/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteLast.java
@@ -0,0 +1,19 @@
+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/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteRandom.java b/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteRandom.java
new file mode 100755
index 000000000..298c95908
--- /dev/null
+++ b/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteRandom.java
@@ -0,0 +1,23 @@
+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/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteRound.java b/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteRound.java
new file mode 100755
index 000000000..234da4f23
--- /dev/null
+++ b/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteRound.java
@@ -0,0 +1,48 @@
+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(int 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/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/scheduler/XxlJobScheduler.java b/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/scheduler/XxlJobScheduler.java
new file mode 100755
index 000000000..52eb8515d
--- /dev/null
+++ b/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/scheduler/XxlJobScheduler.java
@@ -0,0 +1,101 @@
+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 registry monitor run
+ JobRegistryMonitorHelper.getInstance().start();
+
+ // admin fail-monitor run
+ JobFailMonitorHelper.getInstance().start();
+
+ // admin lose-monitor run
+ JobLosedMonitorHelper.getInstance().start();
+
+ // admin trigger pool start
+ JobTriggerPoolHelper.toStart();
+
+ // admin log report start
+ JobLogReportHelper.getInstance().start();
+
+ // start-schedule
+ 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 trigger pool stop
+ JobTriggerPoolHelper.toStop();
+
+ // admin lose-monitor stop
+ JobLosedMonitorHelper.getInstance().toStop();
+
+ // admin fail-monitor stop
+ JobFailMonitorHelper.getInstance().toStop();
+
+ // admin registry stop
+ JobRegistryMonitorHelper.getInstance().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/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobFailMonitorHelper.java b/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobFailMonitorHelper.java
new file mode 100755
index 000000000..40ad6c58f
--- /dev/null
+++ b/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobFailMonitorHelper.java
@@ -0,0 +1,128 @@
+package com.xxl.job.admin.core.thread;
+
+import com.xxl.job.admin.core.conf.XxlJobAdminConfig;
+import com.xxl.job.admin.core.model.XxlJobInfo;
+import com.xxl.job.admin.core.model.XxlJobLog;
+import com.xxl.job.admin.core.trigger.TriggerTypeEnum;
+import com.xxl.job.admin.core.util.I18nUtil;
+import 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().getXxlJobLogDao()
+ .findFailJobLogIds(1000);
+ if (failLogIds != null && !failLogIds.isEmpty()) {
+ for (long failLogId : failLogIds) {
+
+ // lock log
+ int lockRet = XxlJobAdminConfig.getAdminConfig().getXxlJobLogDao()
+ .updateAlarmStatus(failLogId, 0, -1);
+ if (lockRet < 1) {
+ continue;
+ }
+ XxlJobLog log = XxlJobAdminConfig.getAdminConfig().getXxlJobLogDao().load(failLogId);
+ XxlJobInfo info = XxlJobAdminConfig.getAdminConfig().getXxlJobInfoDao()
+ .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().getXxlJobLogDao().updateTriggerInfo(log);
+ }
+
+ // 2、fail alarm monitor
+ int newAlarmStatus = 0; // 告警状态:0-默认、-1=锁定状态、1-无需告警、2-告警成功、3-告警失败
+ if (info != null && info.getAlarmEmail() != null
+ && info.getAlarmEmail().trim().length() > 0) {
+ boolean alarmResult = XxlJobAdminConfig.getAdminConfig().getJobAlarmer().alarm(info,
+ log);
+ newAlarmStatus = alarmResult ? 2 : 3;
+ }
+ else {
+ newAlarmStatus = 1;
+ }
+
+ XxlJobAdminConfig.getAdminConfig().getXxlJobLogDao().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/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobLogReportHelper.java b/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobLogReportHelper.java
new file mode 100755
index 000000000..d2c7c38e4
--- /dev/null
+++ b/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobLogReportHelper.java
@@ -0,0 +1,167 @@
+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().getXxlJobLogDao()
+ .findLogReport(todayFrom, todayTo);
+ if (triggerCountMap != null && triggerCountMap.size() > 0) {
+ int triggerDayCount = triggerCountMap.containsKey("triggerDayCount")
+ ? Integer.valueOf(String.valueOf(triggerCountMap.get("triggerDayCount"))) : 0;
+ int triggerDayCountRunning = triggerCountMap.containsKey("triggerDayCountRunning")
+ ? Integer.valueOf(String.valueOf(triggerCountMap.get("triggerDayCountRunning")))
+ : 0;
+ int triggerDayCountSuc = triggerCountMap.containsKey("triggerDayCountSuc")
+ ? Integer.valueOf(String.valueOf(triggerCountMap.get("triggerDayCountSuc")))
+ : 0;
+ int triggerDayCountFail = triggerDayCount - triggerDayCountRunning - triggerDayCountSuc;
+
+ xxlJobLogReport.setRunningCount(triggerDayCountRunning);
+ xxlJobLogReport.setSucCount(triggerDayCountSuc);
+ xxlJobLogReport.setFailCount(triggerDayCountFail);
+ }
+
+ // do refresh
+ int ret = XxlJobAdminConfig.getAdminConfig().getXxlJobLogReportDao()
+ .update(xxlJobLogReport);
+ if (ret < 1) {
+ XxlJobAdminConfig.getAdminConfig().getXxlJobLogReportDao().save(xxlJobLogReport);
+ }
+ }
+
+ }
+ catch (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().getXxlJobLogDao().findClearLogIds(0, 0,
+ clearBeforeTime, 0, 1000);
+ if (logIds != null && logIds.size() > 0) {
+ XxlJobAdminConfig.getAdminConfig().getXxlJobLogDao().clearLog(logIds);
+ }
+ }
+ while (logIds != null && logIds.size() > 0);
+
+ // update clean time
+ lastCleanLogTime = System.currentTimeMillis();
+ }
+
+ try {
+ TimeUnit.MINUTES.sleep(1);
+ }
+ catch (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/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobLosedMonitorHelper.java b/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobLosedMonitorHelper.java
new file mode 100755
index 000000000..60cb3b593
--- /dev/null
+++ b/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobLosedMonitorHelper.java
@@ -0,0 +1,103 @@
+package com.xxl.job.admin.core.thread;
+
+import com.xxl.job.admin.core.conf.XxlJobAdminConfig;
+import com.xxl.job.admin.core.model.XxlJobLog;
+import com.xxl.job.admin.core.util.I18nUtil;
+import com.xxl.job.core.biz.model.ReturnT;
+import com.xxl.job.core.util.DateUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Date;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * job lose-monitor instance
+ *
+ * @author xuxueli 2015-9-1 18:05:56
+ */
+public class JobLosedMonitorHelper {
+
+ private static Logger logger = LoggerFactory.getLogger(JobLosedMonitorHelper.class);
+
+ private static JobLosedMonitorHelper instance = new JobLosedMonitorHelper();
+
+ public static JobLosedMonitorHelper 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 {
+ // 任务结果丢失处理:调度记录停留在 "运行中" 状态超过10min,且对应执行器心跳注册失败不在线,则将本地调度主动标记失败;
+ Date losedTime = DateUtil.addMinutes(new Date(), -10);
+ List losedJobIds = XxlJobAdminConfig.getAdminConfig().getXxlJobLogDao()
+ .findLostJobIds(losedTime);
+
+ if (losedJobIds != null && losedJobIds.size() > 0) {
+ for (Long 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"));
+
+ XxlJobAdminConfig.getAdminConfig().getXxlJobLogDao().updateHandleInfo(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;
+ // interrupt and wait
+ monitorThread.interrupt();
+ try {
+ monitorThread.join();
+ }
+ catch (InterruptedException e) {
+ logger.error(e.getMessage(), e);
+ }
+ }
+
+}
diff --git a/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobRegistryMonitorHelper.java b/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobRegistryMonitorHelper.java
new file mode 100755
index 000000000..73b5182f3
--- /dev/null
+++ b/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobRegistryMonitorHelper.java
@@ -0,0 +1,123 @@
+package com.xxl.job.admin.core.thread;
+
+import com.xxl.job.admin.core.conf.XxlJobAdminConfig;
+import com.xxl.job.admin.core.model.XxlJobGroup;
+import com.xxl.job.admin.core.model.XxlJobRegistry;
+import com.xxl.job.core.enums.RegistryConfig;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.*;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * job registry instance
+ *
+ * @author xuxueli 2016-10-02 19:10:24
+ */
+public class JobRegistryMonitorHelper {
+
+ private static Logger logger = LoggerFactory.getLogger(JobRegistryMonitorHelper.class);
+
+ private static JobRegistryMonitorHelper instance = new JobRegistryMonitorHelper();
+
+ public static JobRegistryMonitorHelper getInstance() {
+ return instance;
+ }
+
+ private Thread registryThread;
+
+ private volatile boolean toStop = false;
+
+ public void start() {
+ registryThread = new Thread(new Runnable() {
+ @Override
+ public void run() {
+ while (!toStop) {
+ try {
+ // auto registry group
+ List groupList = XxlJobAdminConfig.getAdminConfig().getXxlJobGroupDao()
+ .findByAddressType(0);
+ if (groupList != null && !groupList.isEmpty()) {
+
+ // remove dead address (admin/executor)
+ List ids = XxlJobAdminConfig.getAdminConfig().getXxlJobRegistryDao()
+ .findDead(RegistryConfig.DEAD_TIMEOUT, new Date());
+ if (ids != null && ids.size() > 0) {
+ XxlJobAdminConfig.getAdminConfig().getXxlJobRegistryDao().removeDead(ids);
+ }
+
+ // fresh online address (admin/executor)
+ HashMap> appAddressMap = new HashMap>();
+ List list = XxlJobAdminConfig.getAdminConfig().getXxlJobRegistryDao()
+ .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);
+ XxlJobAdminConfig.getAdminConfig().getXxlJobGroupDao().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");
+ }
+ });
+ registryThread.setDaemon(true);
+ registryThread.setName("xxl-job, admin JobRegistryMonitorHelper");
+ registryThread.start();
+ }
+
+ public void toStop() {
+ toStop = true;
+ // interrupt and wait
+ registryThread.interrupt();
+ try {
+ registryThread.join();
+ }
+ catch (InterruptedException e) {
+ logger.error(e.getMessage(), e);
+ }
+ }
+
+}
diff --git a/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobScheduleHelper.java b/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobScheduleHelper.java
new file mode 100755
index 000000000..ba07dd4fc
--- /dev/null
+++ b/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobScheduleHelper.java
@@ -0,0 +1,391 @@
+package com.xxl.job.admin.core.thread;
+
+import com.xxl.job.admin.core.conf.XxlJobAdminConfig;
+import com.xxl.job.admin.core.cron.CronExpression;
+import com.xxl.job.admin.core.model.XxlJobInfo;
+import com.xxl.job.admin.core.trigger.TriggerTypeEnum;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+import java.text.ParseException;
+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 = null;
+ PreparedStatement preparedStatement = null;
+
+ boolean preReadSuc = true;
+ try {
+
+ conn = XxlJobAdminConfig.getAdminConfig().getDataSource().getConnection();
+ connAutoCommit = conn.getAutoCommit();
+ conn.setAutoCommit(false);
+
+ preparedStatement = conn.prepareStatement(
+ "select * from xxl_job_lock where lock_name = 'schedule_lock' for update");
+ preparedStatement.execute();
+
+ // tx start
+
+ // 1、pre read
+ long nowTime = System.currentTimeMillis();
+ List scheduleList = XxlJobAdminConfig.getAdminConfig().getXxlJobInfoDao()
+ .scheduleJobQuery(nowTime + PRE_READ_MS, preReadCount);
+ if (scheduleList != null && scheduleList.size() > 0) {
+ // 2、push time-ring
+ for (XxlJobInfo jobInfo : scheduleList) {
+
+ // time-ring jump
+ if (nowTime > jobInfo.getTriggerNextTime() + PRE_READ_MS) {
+ // 2.1、trigger-expire > 5s:pass && make
+ // next-trigger-time
+ logger.warn(">>>>>>>>>>> xxl-job, schedule misfire, jobId = " + jobInfo.getId());
+
+ // 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().getXxlJobInfoDao().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 {
+ 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() {
+
+ // align second
+ try {
+ TimeUnit.MILLISECONDS.sleep(1000 - System.currentTimeMillis() % 1000);
+ }
+ catch (InterruptedException e) {
+ if (!ringThreadToStop) {
+ logger.error(e.getMessage(), e);
+ }
+ }
+
+ while (!ringThreadToStop) {
+
+ 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 (int 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);
+ }
+ }
+
+ // next second, align second
+ try {
+ TimeUnit.MILLISECONDS.sleep(1000 - System.currentTimeMillis() % 1000);
+ }
+ catch (InterruptedException e) {
+ if (!ringThreadToStop) {
+ logger.error(e.getMessage(), 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 ParseException {
+ Date nextValidTime = new CronExpression(jobInfo.getJobCron()).getNextValidTimeAfter(fromTime);
+ if (nextValidTime != null) {
+ jobInfo.setTriggerLastTime(jobInfo.getTriggerNextTime());
+ jobInfo.setTriggerNextTime(nextValidTime.getTime());
+ }
+ else {
+ jobInfo.setTriggerStatus(0);
+ jobInfo.setTriggerLastTime(0);
+ jobInfo.setTriggerNextTime(0);
+ }
+ }
+
+ private void pushTimeRing(int ringSecond, int 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 (int 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");
+ }
+
+}
diff --git a/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobTriggerPoolHelper.java b/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobTriggerPoolHelper.java
new file mode 100755
index 000000000..c6ed34af1
--- /dev/null
+++ b/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobTriggerPoolHelper.java
@@ -0,0 +1,135 @@
+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 int 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(int jobId, TriggerTypeEnum triggerType, int failRetryCount, String executorShardingParam,
+ String executorParam, String addressList) {
+ helper.addTrigger(jobId, triggerType, failRetryCount, executorShardingParam, executorParam, addressList);
+ }
+
+}
diff --git a/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/trigger/TriggerTypeEnum.java b/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/trigger/TriggerTypeEnum.java
new file mode 100755
index 000000000..bda307d42
--- /dev/null
+++ b/pigx-visual/pigx-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"));
+
+ private TriggerTypeEnum(String title) {
+ this.title = title;
+ }
+
+ private String title;
+
+ public String getTitle() {
+ return title;
+ }
+
+}
diff --git a/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/trigger/XxlJobTrigger.java b/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/trigger/XxlJobTrigger.java
new file mode 100755
index 000000000..2dd817bad
--- /dev/null
+++ b/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/trigger/XxlJobTrigger.java
@@ -0,0 +1,238 @@
+package com.xxl.job.admin.core.trigger;
+
+import com.xxl.job.admin.core.conf.XxlJobAdminConfig;
+import com.xxl.job.admin.core.model.XxlJobGroup;
+import com.xxl.job.admin.core.model.XxlJobInfo;
+import com.xxl.job.admin.core.model.XxlJobLog;
+import com.xxl.job.admin.core.route.ExecutorRouteStrategyEnum;
+import com.xxl.job.admin.core.scheduler.XxlJobScheduler;
+import com.xxl.job.admin.core.util.I18nUtil;
+import com.xxl.job.core.biz.ExecutorBiz;
+import com.xxl.job.core.biz.model.ReturnT;
+import com.xxl.job.core.biz.model.TriggerParam;
+import com.xxl.job.core.enums.ExecutorBlockStrategyEnum;
+import com.xxl.job.core.util.IpUtil;
+import com.xxl.job.core.util.ThrowableUtil;
+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(int jobId, TriggerTypeEnum triggerType, int failRetryCount, String executorShardingParam,
+ String executorParam, String addressList) {
+
+ // load data
+ XxlJobInfo jobInfo = XxlJobAdminConfig.getAdminConfig().getXxlJobInfoDao().loadById(jobId);
+ if (jobInfo == null) {
+ logger.warn(">>>>>>>>>>>> trigger fail, jobId invalid,jobId={}", jobId);
+ return;
+ }
+ if (executorParam != null) {
+ jobInfo.setExecutorParam(executorParam);
+ }
+ int finalFailRetryCount = failRetryCount >= 0 ? failRetryCount : jobInfo.getExecutorFailRetryCount();
+ XxlJobGroup group = XxlJobAdminConfig.getAdminConfig().getXxlJobGroupDao().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 {
+ int result = 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) {
+
+ // 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().getXxlJobLogDao().save(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().getXxlJobLogDao().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/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/util/CookieUtil.java b/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/util/CookieUtil.java
new file mode 100755
index 000000000..b7f28ded7
--- /dev/null
+++ b/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/util/CookieUtil.java
@@ -0,0 +1,95 @@
+package com.xxl.job.admin.core.util;
+
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * Cookie.Util
+ *
+ * @author xuxueli 2015-12-12 18:01:06
+ */
+public class CookieUtil {
+
+ // 默认缓存时间,单位/秒, 2H
+ private static final int COOKIE_MAX_AGE = Integer.MAX_VALUE;
+
+ // 保存路径,根路径
+ private static final String COOKIE_PATH = "/";
+
+ /**
+ * 保存
+ * @param response
+ * @param key
+ * @param value
+ * @param ifRemember
+ */
+ public static void set(HttpServletResponse response, String key, String value, boolean ifRemember) {
+ int age = ifRemember ? COOKIE_MAX_AGE : -1;
+ set(response, key, value, null, COOKIE_PATH, age, true);
+ }
+
+ /**
+ * 保存
+ * @param response
+ * @param key
+ * @param value
+ * @param maxAge
+ */
+ private static void set(HttpServletResponse response, String key, String value, String domain, String path,
+ int maxAge, boolean isHttpOnly) {
+ Cookie cookie = new Cookie(key, value);
+ if (domain != null) {
+ cookie.setDomain(domain);
+ }
+ cookie.setPath(path);
+ cookie.setMaxAge(maxAge);
+ cookie.setHttpOnly(isHttpOnly);
+ response.addCookie(cookie);
+ }
+
+ /**
+ * 查询value
+ * @param request
+ * @param key
+ * @return
+ */
+ public static String getValue(HttpServletRequest request, String key) {
+ Cookie cookie = get(request, key);
+ if (cookie != null) {
+ return cookie.getValue();
+ }
+ return null;
+ }
+
+ /**
+ * 查询Cookie
+ * @param request
+ * @param key
+ */
+ private static Cookie get(HttpServletRequest request, String key) {
+ Cookie[] arr_cookie = request.getCookies();
+ if (arr_cookie != null && arr_cookie.length > 0) {
+ for (Cookie cookie : arr_cookie) {
+ if (cookie.getName().equals(key)) {
+ return cookie;
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * 删除Cookie
+ * @param request
+ * @param response
+ * @param key
+ */
+ public static void remove(HttpServletRequest request, HttpServletResponse response, String key) {
+ Cookie cookie = get(request, key);
+ if (cookie != null) {
+ set(response, key, "", null, COOKIE_PATH, 0, true);
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/util/FtlUtil.java b/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/util/FtlUtil.java
new file mode 100755
index 000000000..82e63c9df
--- /dev/null
+++ b/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/util/FtlUtil.java
@@ -0,0 +1,34 @@
+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/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/util/I18nUtil.java b/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/util/I18nUtil.java
new file mode 100755
index 000000000..4cc3ed3e8
--- /dev/null
+++ b/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/util/I18nUtil.java
@@ -0,0 +1,81 @@
+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/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/util/JacksonUtil.java b/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/util/JacksonUtil.java
new file mode 100755
index 000000000..8756faabd
--- /dev/null
+++ b/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/util/JacksonUtil.java
@@ -0,0 +1,100 @@
+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/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/util/LocalCacheUtil.java b/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/util/LocalCacheUtil.java
new file mode 100755
index 000000000..23b4c5813
--- /dev/null
+++ b/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/core/util/LocalCacheUtil.java
@@ -0,0 +1,134 @@
+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() {
+ }
+
+ public LocalCacheData(String key, Object val, long timeoutTime) {
+ this.key = key;
+ this.val = val;
+ this.timeoutTime = timeoutTime;
+ }
+
+ public String getKey() {
+ return key;
+ }
+
+ public void setKey(String key) {
+ this.key = key;
+ }
+
+ public Object getVal() {
+ return val;
+ }
+
+ public void setVal(Object val) {
+ this.val = val;
+ }
+
+ public long getTimeoutTime() {
+ return timeoutTime;
+ }
+
+ public void setTimeoutTime(long timeoutTime) {
+ this.timeoutTime = timeoutTime;
+ }
+
+ }
+
+ /**
+ * set cache
+ * @param key
+ * @param val
+ * @param cacheTime
+ * @return
+ */
+ public static boolean set(String key, Object val, long cacheTime) {
+
+ // clean timeout cache, before set new cache (avoid cache too much)
+ cleanTimeoutCache();
+
+ // set new cache
+ if (key == null || key.trim().length() == 0) {
+ return false;
+ }
+ if (val == null) {
+ remove(key);
+ }
+ if (cacheTime <= 0) {
+ remove(key);
+ }
+ long timeoutTime = System.currentTimeMillis() + cacheTime;
+ LocalCacheData localCacheData = new LocalCacheData(key, val, timeoutTime);
+ cacheRepository.put(localCacheData.getKey(), localCacheData);
+ return true;
+ }
+
+ /**
+ * remove cache
+ * @param key
+ * @return
+ */
+ public static boolean remove(String key) {
+ if (key == null || key.trim().length() == 0) {
+ return false;
+ }
+ cacheRepository.remove(key);
+ return true;
+ }
+
+ /**
+ * get cache
+ * @param key
+ * @return
+ */
+ public static Object get(String key) {
+ if (key == null || key.trim().length() == 0) {
+ return null;
+ }
+ LocalCacheData localCacheData = cacheRepository.get(key);
+ if (localCacheData != null && System.currentTimeMillis() < localCacheData.getTimeoutTime()) {
+ return localCacheData.getVal();
+ }
+ else {
+ remove(key);
+ return null;
+ }
+ }
+
+ /**
+ * clean timeout cache
+ * @return
+ */
+ public static boolean cleanTimeoutCache() {
+ if (!cacheRepository.keySet().isEmpty()) {
+ for (String key : cacheRepository.keySet()) {
+ LocalCacheData localCacheData = cacheRepository.get(key);
+ if (localCacheData != null && System.currentTimeMillis() >= localCacheData.getTimeoutTime()) {
+ cacheRepository.remove(key);
+ }
+ }
+ }
+ return true;
+ }
+
+}
diff --git a/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/dao/XxlJobGroupDao.java b/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/dao/XxlJobGroupDao.java
new file mode 100755
index 000000000..246924f1e
--- /dev/null
+++ b/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/dao/XxlJobGroupDao.java
@@ -0,0 +1,33 @@
+package com.xxl.job.admin.dao;
+
+import com.xxl.job.admin.core.model.XxlJobGroup;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+/**
+ * Created by xuxueli on 16/9/30.
+ */
+@Mapper
+public interface XxlJobGroupDao {
+
+ public List findAll();
+
+ public List findByAddressType(@Param("addressType") int addressType);
+
+ public int save(XxlJobGroup xxlJobGroup);
+
+ public int update(XxlJobGroup xxlJobGroup);
+
+ public int remove(@Param("id") int id);
+
+ public XxlJobGroup load(@Param("id") int id);
+
+ public List pageList(@Param("offset") int offset, @Param("pagesize") int pagesize,
+ @Param("appname") String appname, @Param("title") String title);
+
+ public int pageListCount(@Param("offset") int offset, @Param("pagesize") int pagesize,
+ @Param("appname") String appname, @Param("title") String title);
+
+}
diff --git a/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/dao/XxlJobInfoDao.java b/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/dao/XxlJobInfoDao.java
new file mode 100755
index 000000000..2d2e0ee43
--- /dev/null
+++ b/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/dao/XxlJobInfoDao.java
@@ -0,0 +1,43 @@
+package com.xxl.job.admin.dao;
+
+import com.xxl.job.admin.core.model.XxlJobInfo;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+/**
+ * job info
+ *
+ * @author xuxueli 2016-1-12 18:03:45
+ */
+@Mapper
+public interface XxlJobInfoDao {
+
+ public List pageList(@Param("offset") int offset, @Param("pagesize") int pagesize,
+ @Param("jobGroup") int jobGroup, @Param("triggerStatus") int triggerStatus,
+ @Param("jobDesc") String jobDesc, @Param("executorHandler") String executorHandler,
+ @Param("author") String author);
+
+ public int pageListCount(@Param("offset") int offset, @Param("pagesize") int pagesize,
+ @Param("jobGroup") int jobGroup, @Param("triggerStatus") int triggerStatus,
+ @Param("jobDesc") String jobDesc, @Param("executorHandler") String executorHandler,
+ @Param("author") String author);
+
+ public int save(XxlJobInfo info);
+
+ public XxlJobInfo loadById(@Param("id") int id);
+
+ public int update(XxlJobInfo xxlJobInfo);
+
+ public int delete(@Param("id") long id);
+
+ public List getJobsByGroup(@Param("jobGroup") int jobGroup);
+
+ public int findAllCount();
+
+ public List scheduleJobQuery(@Param("maxNextTime") long maxNextTime, @Param("pagesize") int pagesize);
+
+ public int scheduleUpdate(XxlJobInfo xxlJobInfo);
+
+}
diff --git a/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/dao/XxlJobLogDao.java b/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/dao/XxlJobLogDao.java
new file mode 100755
index 000000000..a42c08b0c
--- /dev/null
+++ b/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/dao/XxlJobLogDao.java
@@ -0,0 +1,55 @@
+package com.xxl.job.admin.dao;
+
+import com.xxl.job.admin.core.model.XxlJobLog;
+import org.apache.ibatis.annotations.Mapper;
+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
+ */
+@Mapper
+public interface XxlJobLogDao {
+
+ // exist jobId not use jobGroup, not exist use jobGroup
+ public List pageList(@Param("offset") int offset, @Param("pagesize") int pagesize,
+ @Param("jobGroup") int jobGroup, @Param("jobId") int jobId,
+ @Param("triggerTimeStart") Date triggerTimeStart, @Param("triggerTimeEnd") Date triggerTimeEnd,
+ @Param("logStatus") int logStatus);
+
+ public int pageListCount(@Param("offset") int offset, @Param("pagesize") int pagesize,
+ @Param("jobGroup") int jobGroup, @Param("jobId") int jobId,
+ @Param("triggerTimeStart") Date triggerTimeStart, @Param("triggerTimeEnd") Date triggerTimeEnd,
+ @Param("logStatus") int logStatus);
+
+ public XxlJobLog load(@Param("id") long id);
+
+ public long save(XxlJobLog xxlJobLog);
+
+ public int updateTriggerInfo(XxlJobLog xxlJobLog);
+
+ public int updateHandleInfo(XxlJobLog xxlJobLog);
+
+ public int delete(@Param("jobId") int jobId);
+
+ public Map findLogReport(@Param("from") Date from, @Param("to") Date to);
+
+ public List findClearLogIds(@Param("jobGroup") int jobGroup, @Param("jobId") int jobId,
+ @Param("clearBeforeTime") Date clearBeforeTime, @Param("clearBeforeNum") int clearBeforeNum,
+ @Param("pagesize") int pagesize);
+
+ public int clearLog(@Param("logIds") List logIds);
+
+ public List findFailJobLogIds(@Param("pagesize") int pagesize);
+
+ public int updateAlarmStatus(@Param("logId") long logId, @Param("oldAlarmStatus") int oldAlarmStatus,
+ @Param("newAlarmStatus") int newAlarmStatus);
+
+ public List findLostJobIds(@Param("losedTime") Date losedTime);
+
+}
diff --git a/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/dao/XxlJobLogGlueDao.java b/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/dao/XxlJobLogGlueDao.java
new file mode 100755
index 000000000..d426f65f8
--- /dev/null
+++ b/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/dao/XxlJobLogGlueDao.java
@@ -0,0 +1,25 @@
+package com.xxl.job.admin.dao;
+
+import com.xxl.job.admin.core.model.XxlJobLogGlue;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+/**
+ * job log for glue
+ *
+ * @author xuxueli 2016-5-19 18:04:56
+ */
+@Mapper
+public interface XxlJobLogGlueDao {
+
+ public int save(XxlJobLogGlue xxlJobLogGlue);
+
+ public List findByJobId(@Param("jobId") int jobId);
+
+ public int removeOld(@Param("jobId") int jobId, @Param("limit") int limit);
+
+ public int deleteByJobId(@Param("jobId") int jobId);
+
+}
diff --git a/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/dao/XxlJobLogReportDao.java b/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/dao/XxlJobLogReportDao.java
new file mode 100755
index 000000000..9d1d9e369
--- /dev/null
+++ b/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/dao/XxlJobLogReportDao.java
@@ -0,0 +1,27 @@
+package com.xxl.job.admin.dao;
+
+import com.xxl.job.admin.core.model.XxlJobLogReport;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.Date;
+import java.util.List;
+
+/**
+ * job log
+ *
+ * @author xuxueli 2019-11-22
+ */
+@Mapper
+public interface XxlJobLogReportDao {
+
+ public int save(XxlJobLogReport xxlJobLogReport);
+
+ public int update(XxlJobLogReport xxlJobLogReport);
+
+ public List queryLogReport(@Param("triggerDayFrom") Date triggerDayFrom,
+ @Param("triggerDayTo") Date triggerDayTo);
+
+ public XxlJobLogReport queryLogReportTotal();
+
+}
diff --git a/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/dao/XxlJobRegistryDao.java b/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/dao/XxlJobRegistryDao.java
new file mode 100755
index 000000000..9b76add12
--- /dev/null
+++ b/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/dao/XxlJobRegistryDao.java
@@ -0,0 +1,31 @@
+package com.xxl.job.admin.dao;
+
+import com.xxl.job.admin.core.model.XxlJobRegistry;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.Date;
+import java.util.List;
+
+/**
+ * Created by xuxueli on 16/9/30.
+ */
+@Mapper
+public interface XxlJobRegistryDao {
+
+ public List findDead(@Param("timeout") int timeout, @Param("nowTime") Date nowTime);
+
+ public int removeDead(@Param("ids") List ids);
+
+ public List findAll(@Param("timeout") int timeout, @Param("nowTime") Date nowTime);
+
+ public int registryUpdate(@Param("registryGroup") String registryGroup, @Param("registryKey") String registryKey,
+ @Param("registryValue") String registryValue, @Param("updateTime") Date updateTime);
+
+ public int registrySave(@Param("registryGroup") String registryGroup, @Param("registryKey") String registryKey,
+ @Param("registryValue") String registryValue, @Param("updateTime") Date updateTime);
+
+ public int registryDelete(@Param("registryGroup") String registryGroup, @Param("registryKey") String registryKey,
+ @Param("registryValue") String registryValue);
+
+}
diff --git a/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/dao/XxlJobUserDao.java b/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/dao/XxlJobUserDao.java
new file mode 100755
index 000000000..c21c3c1fe
--- /dev/null
+++ b/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/dao/XxlJobUserDao.java
@@ -0,0 +1,28 @@
+package com.xxl.job.admin.dao;
+
+import com.xxl.job.admin.core.model.XxlJobUser;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+import java.util.List;
+
+/**
+ * @author xuxueli 2019-05-04 16:44:59
+ */
+@Mapper
+public interface XxlJobUserDao {
+
+ public List pageList(@Param("offset") int offset, @Param("pagesize") int pagesize,
+ @Param("username") String username, @Param("role") int role);
+
+ public int pageListCount(@Param("offset") int offset, @Param("pagesize") int pagesize,
+ @Param("username") String username, @Param("role") int role);
+
+ public XxlJobUser loadByUserName(@Param("username") String username);
+
+ public int save(XxlJobUser xxlJobUser);
+
+ public int update(XxlJobUser xxlJobUser);
+
+ public int delete(@Param("id") int id);
+
+}
diff --git a/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/service/LoginService.java b/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/service/LoginService.java
new file mode 100755
index 000000000..737d4f8a4
--- /dev/null
+++ b/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/service/LoginService.java
@@ -0,0 +1,105 @@
+package com.xxl.job.admin.service;
+
+import com.xxl.job.admin.core.model.XxlJobUser;
+import com.xxl.job.admin.core.util.CookieUtil;
+import com.xxl.job.admin.core.util.I18nUtil;
+import com.xxl.job.admin.core.util.JacksonUtil;
+import com.xxl.job.admin.dao.XxlJobUserDao;
+import com.xxl.job.core.biz.model.ReturnT;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.util.DigestUtils;
+
+import javax.annotation.Resource;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.math.BigInteger;
+
+/**
+ * @author xuxueli 2019-05-04 22:13:264
+ */
+@Configuration
+public class LoginService {
+
+ public static final String LOGIN_IDENTITY_KEY = "XXL_JOB_LOGIN_IDENTITY";
+
+ @Resource
+ private XxlJobUserDao xxlJobUserDao;
+
+ private String makeToken(XxlJobUser xxlJobUser) {
+ String tokenJson = JacksonUtil.writeValueAsString(xxlJobUser);
+ String tokenHex = new BigInteger(tokenJson.getBytes()).toString(16);
+ return tokenHex;
+ }
+
+ private XxlJobUser parseToken(String tokenHex) {
+ XxlJobUser xxlJobUser = null;
+ if (tokenHex != null) {
+ String tokenJson = new String(new BigInteger(tokenHex, 16).toByteArray()); // username_password(md5)
+ xxlJobUser = JacksonUtil.readValue(tokenJson, XxlJobUser.class);
+ }
+ return xxlJobUser;
+ }
+
+ public ReturnT login(HttpServletRequest request, HttpServletResponse response, String username,
+ String password, boolean ifRemember) {
+
+ // param
+ if (username == null || username.trim().length() == 0 || password == null || password.trim().length() == 0) {
+ return new ReturnT(500, I18nUtil.getString("login_param_empty"));
+ }
+
+ // valid passowrd
+ XxlJobUser xxlJobUser = xxlJobUserDao.loadByUserName(username);
+ if (xxlJobUser == null) {
+ return new ReturnT(500, I18nUtil.getString("login_param_unvalid"));
+ }
+ String passwordMd5 = DigestUtils.md5DigestAsHex(password.getBytes());
+ if (!passwordMd5.equals(xxlJobUser.getPassword())) {
+ return new ReturnT(500, I18nUtil.getString("login_param_unvalid"));
+ }
+
+ String loginToken = makeToken(xxlJobUser);
+
+ // do login
+ CookieUtil.set(response, LOGIN_IDENTITY_KEY, loginToken, ifRemember);
+ return ReturnT.SUCCESS;
+ }
+
+ /**
+ * logout
+ * @param request
+ * @param response
+ */
+ public ReturnT logout(HttpServletRequest request, HttpServletResponse response) {
+ CookieUtil.remove(request, response, LOGIN_IDENTITY_KEY);
+ return ReturnT.SUCCESS;
+ }
+
+ /**
+ * logout
+ * @param request
+ * @return
+ */
+ public XxlJobUser ifLogin(HttpServletRequest request, HttpServletResponse response) {
+ String cookieToken = CookieUtil.getValue(request, LOGIN_IDENTITY_KEY);
+ if (cookieToken != null) {
+ XxlJobUser cookieUser = null;
+ try {
+ cookieUser = parseToken(cookieToken);
+ }
+ catch (Exception e) {
+ logout(request, response);
+ }
+ if (cookieUser != null) {
+ XxlJobUser dbUser = xxlJobUserDao.loadByUserName(cookieUser.getUsername());
+ if (dbUser != null) {
+ if (cookieUser.getPassword().equals(dbUser.getPassword())) {
+ return dbUser;
+ }
+ }
+ }
+ }
+ return null;
+ }
+
+}
diff --git a/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/service/XxlJobService.java b/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/service/XxlJobService.java
new file mode 100755
index 000000000..1ec9a2c06
--- /dev/null
+++ b/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/service/XxlJobService.java
@@ -0,0 +1,78 @@
+package com.xxl.job.admin.service;
+
+import com.xxl.job.admin.core.model.XxlJobInfo;
+import com.xxl.job.core.biz.model.ReturnT;
+
+import java.util.Date;
+import java.util.Map;
+
+/**
+ * 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, int jobGroup, int triggerStatus, String jobDesc,
+ String executorHandler, String author);
+
+ /**
+ * add job
+ * @param jobInfo
+ * @return
+ */
+ public ReturnT add(XxlJobInfo jobInfo);
+
+ /**
+ * update job
+ * @param jobInfo
+ * @return
+ */
+ public ReturnT update(XxlJobInfo jobInfo);
+
+ /**
+ * remove job *
+ * @param id
+ * @return
+ */
+ public ReturnT remove(int id);
+
+ /**
+ * start job
+ * @param id
+ * @return
+ */
+ public ReturnT start(int id);
+
+ /**
+ * stop job
+ * @param id
+ * @return
+ */
+ public ReturnT stop(int id);
+
+ /**
+ * dashboard info
+ * @return
+ */
+ public Map dashboardInfo();
+
+ /**
+ * chart info
+ * @param startDate
+ * @param endDate
+ * @return
+ */
+ public ReturnT> chartInfo(Date startDate, Date endDate);
+
+}
diff --git a/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/service/impl/AdminBizImpl.java b/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/service/impl/AdminBizImpl.java
new file mode 100755
index 000000000..e87960bce
--- /dev/null
+++ b/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/service/impl/AdminBizImpl.java
@@ -0,0 +1,191 @@
+package com.xxl.job.admin.service.impl;
+
+import com.xxl.job.admin.core.model.XxlJobInfo;
+import com.xxl.job.admin.core.model.XxlJobLog;
+import com.xxl.job.admin.core.thread.JobTriggerPoolHelper;
+import com.xxl.job.admin.core.trigger.TriggerTypeEnum;
+import com.xxl.job.admin.core.util.I18nUtil;
+import com.xxl.job.admin.dao.XxlJobGroupDao;
+import com.xxl.job.admin.dao.XxlJobInfoDao;
+import com.xxl.job.admin.dao.XxlJobLogDao;
+import com.xxl.job.admin.dao.XxlJobRegistryDao;
+import com.xxl.job.core.biz.AdminBiz;
+import com.xxl.job.core.biz.model.HandleCallbackParam;
+import com.xxl.job.core.biz.model.RegistryParam;
+import com.xxl.job.core.biz.model.ReturnT;
+import com.xxl.job.core.handler.IJobHandler;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Service;
+import org.springframework.util.StringUtils;
+
+import javax.annotation.Resource;
+import java.text.MessageFormat;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * @author xuxueli 2017-07-27 21:54:20
+ */
+@Service
+public class AdminBizImpl implements AdminBiz {
+
+ private static Logger logger = LoggerFactory.getLogger(AdminBizImpl.class);
+
+ @Resource
+ public XxlJobLogDao xxlJobLogDao;
+
+ @Resource
+ private XxlJobInfoDao xxlJobInfoDao;
+
+ @Resource
+ private XxlJobRegistryDao xxlJobRegistryDao;
+
+ @Resource
+ private XxlJobGroupDao xxlJobGroupDao;
+
+ @Override
+ public ReturnT callback(List callbackParamList) {
+ for (HandleCallbackParam handleCallbackParam : callbackParamList) {
+ ReturnT callbackResult = callback(handleCallbackParam);
+ logger.debug(">>>>>>>>> JobApiController.callback {}, handleCallbackParam={}, callbackResult={}",
+ (callbackResult.getCode() == IJobHandler.SUCCESS.getCode() ? "success" : "fail"),
+ handleCallbackParam, callbackResult);
+ }
+
+ return ReturnT.SUCCESS;
+ }
+
+ private ReturnT callback(HandleCallbackParam handleCallbackParam) {
+ // valid log item
+ XxlJobLog log = xxlJobLogDao.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
+ }
+
+ // trigger success, to trigger child job
+ String callbackMsg = null;
+ if (IJobHandler.SUCCESS.getCode() == handleCallbackParam.getExecuteResult().getCode()) {
+ XxlJobInfo xxlJobInfo = xxlJobInfoDao.loadById(log.getJobId());
+ if (xxlJobInfo != null && xxlJobInfo.getChildJobId() != null
+ && xxlJobInfo.getChildJobId().trim().length() > 0) {
+ callbackMsg = "
>>>>>>>>>>>"
+ + I18nUtil.getString("jobconf_trigger_child_run") + "<<<<<<<<<<<
";
+
+ String[] childJobIds = xxlJobInfo.getChildJobId().split(",");
+ for (int i = 0; i < childJobIds.length; i++) {
+ int childJobId = (childJobIds[i] != null && childJobIds[i].trim().length() > 0
+ && isNumeric(childJobIds[i])) ? Integer.valueOf(childJobIds[i]) : -1;
+ if (childJobId > 0) {
+
+ JobTriggerPoolHelper.trigger(childJobId, TriggerTypeEnum.PARENT, -1, null, null, null);
+ ReturnT triggerChildResult = ReturnT.SUCCESS;
+
+ // add msg
+ callbackMsg += MessageFormat.format(I18nUtil.getString("jobconf_callback_child_msg1"), (i + 1),
+ childJobIds.length, childJobIds[i],
+ (triggerChildResult.getCode() == ReturnT.SUCCESS_CODE
+ ? I18nUtil.getString("system_success") : I18nUtil.getString("system_fail")),
+ triggerChildResult.getMsg());
+ }
+ else {
+ callbackMsg += MessageFormat.format(I18nUtil.getString("jobconf_callback_child_msg2"), (i + 1),
+ childJobIds.length, childJobIds[i]);
+ }
+ }
+
+ }
+ }
+
+ // handle msg
+ StringBuffer handleMsg = new StringBuffer();
+ if (log.getHandleMsg() != null) {
+ handleMsg.append(log.getHandleMsg()).append("
");
+ }
+ if (handleCallbackParam.getExecuteResult().getMsg() != null) {
+ handleMsg.append(handleCallbackParam.getExecuteResult().getMsg());
+ }
+ if (callbackMsg != null) {
+ handleMsg.append(callbackMsg);
+ }
+
+ if (handleMsg.length() > 15000) {
+ handleMsg = new StringBuffer(handleMsg.substring(0, 15000)); // text最大64kb
+ // 避免长度过长
+ }
+
+ // success, save log
+ log.setHandleTime(new Date());
+ log.setHandleCode(handleCallbackParam.getExecuteResult().getCode());
+ log.setHandleMsg(handleMsg.toString());
+ xxlJobLogDao.updateHandleInfo(log);
+
+ return ReturnT.SUCCESS;
+ }
+
+ private boolean isNumeric(String str) {
+ try {
+ int result = Integer.valueOf(str);
+ return true;
+ }
+ catch (NumberFormatException e) {
+ return false;
+ }
+ }
+
+ @Override
+ 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.");
+ }
+
+ int ret = xxlJobRegistryDao.registryUpdate(registryParam.getRegistryGroup(), registryParam.getRegistryKey(),
+ registryParam.getRegistryValue(), new Date());
+ if (ret < 1) {
+ xxlJobRegistryDao.registrySave(registryParam.getRegistryGroup(), registryParam.getRegistryKey(),
+ registryParam.getRegistryValue(), new Date());
+
+ // fresh
+ freshGroupRegistryInfo(registryParam);
+ }
+ return ReturnT.SUCCESS;
+ }
+
+ @Override
+ 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.");
+ }
+
+ int ret = xxlJobRegistryDao.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
+ }
+
+}
diff --git a/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/service/impl/XxlJobServiceImpl.java b/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/service/impl/XxlJobServiceImpl.java
new file mode 100755
index 000000000..1f6f6f007
--- /dev/null
+++ b/pigx-visual/pigx-xxl-job-admin/src/main/java/com/xxl/job/admin/service/impl/XxlJobServiceImpl.java
@@ -0,0 +1,409 @@
+package com.xxl.job.admin.service.impl;
+
+import com.xxl.job.admin.core.model.XxlJobGroup;
+import com.xxl.job.admin.core.model.XxlJobInfo;
+import com.xxl.job.admin.core.cron.CronExpression;
+import com.xxl.job.admin.core.model.XxlJobLogReport;
+import com.xxl.job.admin.core.route.ExecutorRouteStrategyEnum;
+import com.xxl.job.admin.core.thread.JobScheduleHelper;
+import com.xxl.job.admin.core.util.I18nUtil;
+import com.xxl.job.admin.dao.*;
+import com.xxl.job.admin.service.XxlJobService;
+import com.xxl.job.core.biz.model.ReturnT;
+import com.xxl.job.core.enums.ExecutorBlockStrategyEnum;
+import com.xxl.job.core.glue.GlueTypeEnum;
+import com.xxl.job.core.util.DateUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import java.text.MessageFormat;
+import java.text.ParseException;
+import java.util.*;
+
+/**
+ * core job action for xxl-job
+ *
+ * @author xuxueli 2016-5-28 15:30:33
+ */
+@Service
+public class XxlJobServiceImpl implements XxlJobService {
+
+ private static Logger logger = LoggerFactory.getLogger(XxlJobServiceImpl.class);
+
+ @Resource
+ private XxlJobGroupDao xxlJobGroupDao;
+
+ @Resource
+ private XxlJobInfoDao xxlJobInfoDao;
+
+ @Resource
+ public XxlJobLogDao xxlJobLogDao;
+
+ @Resource
+ private XxlJobLogGlueDao xxlJobLogGlueDao;
+
+ @Resource
+ private XxlJobLogReportDao xxlJobLogReportDao;
+
+ @Override
+ public Map pageList(int start, int length, int jobGroup, int triggerStatus, String jobDesc,
+ String executorHandler, String author) {
+
+ // page list
+ List list = xxlJobInfoDao.pageList(start, length, jobGroup, triggerStatus, jobDesc, executorHandler,
+ author);
+ int list_count = xxlJobInfoDao.pageListCount(start, length, jobGroup, triggerStatus, jobDesc, executorHandler,
+ author);
+
+ // package result
+ Map maps = new HashMap();
+ maps.put("recordsTotal", list_count); // 总记录数
+ maps.put("recordsFiltered", list_count); // 过滤后的总记录数
+ maps.put("data", list); // 分页列表
+ return maps;
+ }
+
+ @Override
+ public ReturnT add(XxlJobInfo jobInfo) {
+ // valid
+ XxlJobGroup group = xxlJobGroupDao.load(jobInfo.getJobGroup());
+ if (group == null) {
+ return new ReturnT(ReturnT.FAIL_CODE,
+ (I18nUtil.getString("system_please_choose") + I18nUtil.getString("jobinfo_field_jobgroup")));
+ }
+ if (!CronExpression.isValidExpression(jobInfo.getJobCron())) {
+ return new ReturnT(ReturnT.FAIL_CODE, I18nUtil.getString("jobinfo_field_cron_unvalid"));
+ }
+ if (jobInfo.getJobDesc() == null || jobInfo.getJobDesc().trim().length() == 0) {
+ return new ReturnT(ReturnT.FAIL_CODE,
+ (I18nUtil.getString("system_please_input") + I18nUtil.getString("jobinfo_field_jobdesc")));
+ }
+ if (jobInfo.getAuthor() == null || jobInfo.getAuthor().trim().length() == 0) {
+ return new ReturnT(ReturnT.FAIL_CODE,
+ (I18nUtil.getString("system_please_input") + I18nUtil.getString("jobinfo_field_author")));
+ }
+ if (ExecutorRouteStrategyEnum.match(jobInfo.getExecutorRouteStrategy(), null) == null) {
+ return new ReturnT(ReturnT.FAIL_CODE,
+ (I18nUtil.getString("jobinfo_field_executorRouteStrategy") + I18nUtil.getString("system_unvalid")));
+ }
+ if (ExecutorBlockStrategyEnum.match(jobInfo.getExecutorBlockStrategy(), null) == null) {
+ return new ReturnT(ReturnT.FAIL_CODE,
+ (I18nUtil.getString("jobinfo_field_executorBlockStrategy") + I18nUtil.getString("system_unvalid")));
+ }
+ if (GlueTypeEnum.match(jobInfo.getGlueType()) == null) {
+ return new ReturnT(ReturnT.FAIL_CODE,
+ (I18nUtil.getString("jobinfo_field_gluetype") + I18nUtil.getString("system_unvalid")));
+ }
+ if (GlueTypeEnum.BEAN == GlueTypeEnum.match(jobInfo.getGlueType())
+ && (jobInfo.getExecutorHandler() == null || jobInfo.getExecutorHandler().trim().length() == 0)) {
+ return new ReturnT(ReturnT.FAIL_CODE, (I18nUtil.getString("system_please_input") + "JobHandler"));
+ }
+
+ // fix "\r" in shell
+ if (GlueTypeEnum.GLUE_SHELL == GlueTypeEnum.match(jobInfo.getGlueType()) && jobInfo.getGlueSource() != null) {
+ jobInfo.setGlueSource(jobInfo.getGlueSource().replaceAll("\r", ""));
+ }
+
+ // ChildJobId valid
+ if (jobInfo.getChildJobId() != null && jobInfo.getChildJobId().trim().length() > 0) {
+ String[] childJobIds = jobInfo.getChildJobId().split(",");
+ for (String childJobIdItem : childJobIds) {
+ if (childJobIdItem != null && childJobIdItem.trim().length() > 0 && isNumeric(childJobIdItem)) {
+ XxlJobInfo childJobInfo = xxlJobInfoDao.loadById(Integer.parseInt(childJobIdItem));
+ if (childJobInfo == null) {
+ return new ReturnT(ReturnT.FAIL_CODE,
+ MessageFormat.format((I18nUtil.getString("jobinfo_field_childJobId") + "({0})"
+ + I18nUtil.getString("system_not_found")), childJobIdItem));
+ }
+ }
+ else {
+ return new ReturnT(ReturnT.FAIL_CODE,
+ MessageFormat.format((I18nUtil.getString("jobinfo_field_childJobId") + "({0})"
+ + I18nUtil.getString("system_unvalid")), childJobIdItem));
+ }
+ }
+
+ // join , avoid "xxx,,"
+ String temp = "";
+ for (String item : childJobIds) {
+ temp += item + ",";
+ }
+ temp = temp.substring(0, temp.length() - 1);
+
+ jobInfo.setChildJobId(temp);
+ }
+
+ // add in db
+ jobInfo.setAddTime(new Date());
+ jobInfo.setUpdateTime(new Date());
+ jobInfo.setGlueUpdatetime(new Date());
+ xxlJobInfoDao.save(jobInfo);
+ if (jobInfo.getId() < 1) {
+ return new ReturnT(ReturnT.FAIL_CODE,
+ (I18nUtil.getString("jobinfo_field_add") + I18nUtil.getString("system_fail")));
+ }
+
+ return new ReturnT(String.valueOf(jobInfo.getId()));
+ }
+
+ private boolean isNumeric(String str) {
+ try {
+ int result = Integer.valueOf(str);
+ return true;
+ }
+ catch (NumberFormatException e) {
+ return false;
+ }
+ }
+
+ @Override
+ public ReturnT update(XxlJobInfo jobInfo) {
+
+ // valid
+ if (!CronExpression.isValidExpression(jobInfo.getJobCron())) {
+ return new ReturnT(ReturnT.FAIL_CODE, I18nUtil.getString("jobinfo_field_cron_unvalid"));
+ }
+ if (jobInfo.getJobDesc() == null || jobInfo.getJobDesc().trim().length() == 0) {
+ return new ReturnT(ReturnT.FAIL_CODE,
+ (I18nUtil.getString("system_please_input") + I18nUtil.getString("jobinfo_field_jobdesc")));
+ }
+ if (jobInfo.getAuthor() == null || jobInfo.getAuthor().trim().length() == 0) {
+ return new ReturnT(ReturnT.FAIL_CODE,
+ (I18nUtil.getString("system_please_input") + I18nUtil.getString("jobinfo_field_author")));
+ }
+ if (ExecutorRouteStrategyEnum.match(jobInfo.getExecutorRouteStrategy(), null) == null) {
+ return new ReturnT(ReturnT.FAIL_CODE,
+ (I18nUtil.getString("jobinfo_field_executorRouteStrategy") + I18nUtil.getString("system_unvalid")));
+ }
+ if (ExecutorBlockStrategyEnum.match(jobInfo.getExecutorBlockStrategy(), null) == null) {
+ return new ReturnT(ReturnT.FAIL_CODE,
+ (I18nUtil.getString("jobinfo_field_executorBlockStrategy") + I18nUtil.getString("system_unvalid")));
+ }
+
+ // ChildJobId valid
+ if (jobInfo.getChildJobId() != null && jobInfo.getChildJobId().trim().length() > 0) {
+ String[] childJobIds = jobInfo.getChildJobId().split(",");
+ for (String childJobIdItem : childJobIds) {
+ if (childJobIdItem != null && childJobIdItem.trim().length() > 0 && isNumeric(childJobIdItem)) {
+ XxlJobInfo childJobInfo = xxlJobInfoDao.loadById(Integer.parseInt(childJobIdItem));
+ if (childJobInfo == null) {
+ return new ReturnT(ReturnT.FAIL_CODE,
+ MessageFormat.format((I18nUtil.getString("jobinfo_field_childJobId") + "({0})"
+ + I18nUtil.getString("system_not_found")), childJobIdItem));
+ }
+ }
+ else {
+ return new ReturnT(ReturnT.FAIL_CODE,
+ MessageFormat.format((I18nUtil.getString("jobinfo_field_childJobId") + "({0})"
+ + I18nUtil.getString("system_unvalid")), childJobIdItem));
+ }
+ }
+
+ // join , avoid "xxx,,"
+ String temp = "";
+ for (String item : childJobIds) {
+ temp += item + ",";
+ }
+ temp = temp.substring(0, temp.length() - 1);
+
+ jobInfo.setChildJobId(temp);
+ }
+
+ // group valid
+ XxlJobGroup jobGroup = xxlJobGroupDao.load(jobInfo.getJobGroup());
+ if (jobGroup == null) {
+ return new ReturnT(ReturnT.FAIL_CODE,
+ (I18nUtil.getString("jobinfo_field_jobgroup") + I18nUtil.getString("system_unvalid")));
+ }
+
+ // stage job info
+ XxlJobInfo exists_jobInfo = xxlJobInfoDao.loadById(jobInfo.getId());
+ if (exists_jobInfo == null) {
+ return new ReturnT(ReturnT.FAIL_CODE,
+ (I18nUtil.getString("jobinfo_field_id") + I18nUtil.getString("system_not_found")));
+ }
+
+ // next trigger time (5s后生效,避开预读周期)
+ long nextTriggerTime = exists_jobInfo.getTriggerNextTime();
+ if (exists_jobInfo.getTriggerStatus() == 1 && !jobInfo.getJobCron().equals(exists_jobInfo.getJobCron())) {
+ try {
+ Date nextValidTime = new CronExpression(jobInfo.getJobCron())
+ .getNextValidTimeAfter(new Date(System.currentTimeMillis() + JobScheduleHelper.PRE_READ_MS));
+ if (nextValidTime == null) {
+ return new ReturnT(ReturnT.FAIL_CODE, I18nUtil.getString("jobinfo_field_cron_never_fire"));
+ }
+ nextTriggerTime = nextValidTime.getTime();
+ }
+ catch (ParseException e) {
+ logger.error(e.getMessage(), e);
+ return new ReturnT(ReturnT.FAIL_CODE,
+ I18nUtil.getString("jobinfo_field_cron_unvalid") + " | " + e.getMessage());
+ }
+ }
+
+ exists_jobInfo.setJobGroup(jobInfo.getJobGroup());
+ exists_jobInfo.setJobCron(jobInfo.getJobCron());
+ exists_jobInfo.setJobDesc(jobInfo.getJobDesc());
+ exists_jobInfo.setAuthor(jobInfo.getAuthor());
+ exists_jobInfo.setAlarmEmail(jobInfo.getAlarmEmail());
+ exists_jobInfo.setExecutorRouteStrategy(jobInfo.getExecutorRouteStrategy());
+ exists_jobInfo.setExecutorHandler(jobInfo.getExecutorHandler());
+ exists_jobInfo.setExecutorParam(jobInfo.getExecutorParam());
+ exists_jobInfo.setExecutorBlockStrategy(jobInfo.getExecutorBlockStrategy());
+ exists_jobInfo.setExecutorTimeout(jobInfo.getExecutorTimeout());
+ exists_jobInfo.setExecutorFailRetryCount(jobInfo.getExecutorFailRetryCount());
+ exists_jobInfo.setChildJobId(jobInfo.getChildJobId());
+ exists_jobInfo.setTriggerNextTime(nextTriggerTime);
+
+ exists_jobInfo.setUpdateTime(new Date());
+ xxlJobInfoDao.update(exists_jobInfo);
+
+ return ReturnT.SUCCESS;
+ }
+
+ @Override
+ public ReturnT remove(int id) {
+ XxlJobInfo xxlJobInfo = xxlJobInfoDao.loadById(id);
+ if (xxlJobInfo == null) {
+ return ReturnT.SUCCESS;
+ }
+
+ xxlJobInfoDao.delete(id);
+ xxlJobLogDao.delete(id);
+ xxlJobLogGlueDao.deleteByJobId(id);
+ return ReturnT.SUCCESS;
+ }
+
+ @Override
+ public ReturnT start(int id) {
+ XxlJobInfo xxlJobInfo = xxlJobInfoDao.loadById(id);
+
+ // next trigger time (5s后生效,避开预读周期)
+ long nextTriggerTime = 0;
+ try {
+ Date nextValidTime = new CronExpression(xxlJobInfo.getJobCron())
+ .getNextValidTimeAfter(new Date(System.currentTimeMillis() + JobScheduleHelper.PRE_READ_MS));
+ if (nextValidTime == null) {
+ return new ReturnT(ReturnT.FAIL_CODE, I18nUtil.getString("jobinfo_field_cron_never_fire"));
+ }
+ nextTriggerTime = nextValidTime.getTime();
+ }
+ catch (ParseException e) {
+ logger.error(e.getMessage(), e);
+ return new ReturnT(ReturnT.FAIL_CODE,
+ I18nUtil.getString("jobinfo_field_cron_unvalid") + " | " + e.getMessage());
+ }
+
+ xxlJobInfo.setTriggerStatus(1);
+ xxlJobInfo.setTriggerLastTime(0);
+ xxlJobInfo.setTriggerNextTime(nextTriggerTime);
+
+ xxlJobInfo.setUpdateTime(new Date());
+ xxlJobInfoDao.update(xxlJobInfo);
+ return ReturnT.SUCCESS;
+ }
+
+ @Override
+ public ReturnT stop(int id) {
+ XxlJobInfo xxlJobInfo = xxlJobInfoDao.loadById(id);
+
+ xxlJobInfo.setTriggerStatus(0);
+ xxlJobInfo.setTriggerLastTime(0);
+ xxlJobInfo.setTriggerNextTime(0);
+
+ xxlJobInfo.setUpdateTime(new Date());
+ xxlJobInfoDao.update(xxlJobInfo);
+ return ReturnT.SUCCESS;
+ }
+
+ @Override
+ public Map dashboardInfo() {
+
+ int jobInfoCount = xxlJobInfoDao.findAllCount();
+ int jobLogCount = 0;
+ int jobLogSuccessCount = 0;
+ XxlJobLogReport xxlJobLogReport = xxlJobLogReportDao.queryLogReportTotal();
+ if (xxlJobLogReport != null) {
+ jobLogCount = xxlJobLogReport.getRunningCount() + xxlJobLogReport.getSucCount()
+ + xxlJobLogReport.getFailCount();
+ jobLogSuccessCount = xxlJobLogReport.getSucCount();
+ }
+
+ // executor count
+ Set executorAddressSet = new HashSet();
+ List groupList = xxlJobGroupDao.findAll();
+
+ if (groupList != null && !groupList.isEmpty()) {
+ for (XxlJobGroup group : groupList) {
+ if (group.getRegistryList() != null && !group.getRegistryList().isEmpty()) {
+ executorAddressSet.addAll(group.getRegistryList());
+ }
+ }
+ }
+
+ int executorCount = executorAddressSet.size();
+
+ Map dashboardMap = new HashMap();
+ dashboardMap.put("jobInfoCount", jobInfoCount);
+ dashboardMap.put("jobLogCount", jobLogCount);
+ dashboardMap.put("jobLogSuccessCount", jobLogSuccessCount);
+ dashboardMap.put("executorCount", executorCount);
+ return dashboardMap;
+ }
+
+ @Override
+ public ReturnT> chartInfo(Date startDate, Date endDate) {
+
+ // process
+ List triggerDayList = new ArrayList();
+ List triggerDayCountRunningList = new ArrayList();
+ List triggerDayCountSucList = new ArrayList();
+ List triggerDayCountFailList = new ArrayList();
+ int triggerCountRunningTotal = 0;
+ int triggerCountSucTotal = 0;
+ int triggerCountFailTotal = 0;
+
+ List logReportList = xxlJobLogReportDao.queryLogReport(startDate, endDate);
+
+ if (logReportList != null && logReportList.size() > 0) {
+ for (XxlJobLogReport item : logReportList) {
+ String day = DateUtil.formatDate(item.getTriggerDay());
+ int triggerDayCountRunning = item.getRunningCount();
+ int triggerDayCountSuc = item.getSucCount();
+ int triggerDayCountFail = item.getFailCount();
+
+ triggerDayList.add(day);
+ triggerDayCountRunningList.add(triggerDayCountRunning);
+ triggerDayCountSucList.add(triggerDayCountSuc);
+ triggerDayCountFailList.add(triggerDayCountFail);
+
+ triggerCountRunningTotal += triggerDayCountRunning;
+ triggerCountSucTotal += triggerDayCountSuc;
+ triggerCountFailTotal += triggerDayCountFail;
+ }
+ }
+ else {
+ for (int i = -6; i <= 0; i++) {
+ triggerDayList.add(DateUtil.formatDate(DateUtil.addDays(new Date(), i)));
+ triggerDayCountRunningList.add(0);
+ triggerDayCountSucList.add(0);
+ triggerDayCountFailList.add(0);
+ }
+ }
+
+ Map result = new HashMap();
+ result.put("triggerDayList", triggerDayList);
+ result.put("triggerDayCountRunningList", triggerDayCountRunningList);
+ result.put("triggerDayCountSucList", triggerDayCountSucList);
+ result.put("triggerDayCountFailList", triggerDayCountFailList);
+
+ result.put("triggerCountRunningTotal", triggerCountRunningTotal);
+ result.put("triggerCountSucTotal", triggerCountSucTotal);
+ result.put("triggerCountFailTotal", triggerCountFailTotal);
+
+ return new ReturnT>(result);
+ }
+
+}
diff --git a/pigx-visual/pigx-xxl-job-admin/src/main/resources/application.yml b/pigx-visual/pigx-xxl-job-admin/src/main/resources/application.yml
new file mode 100755
index 000000000..ad15c0e0e
--- /dev/null
+++ b/pigx-visual/pigx-xxl-job-admin/src/main/resources/application.yml
@@ -0,0 +1,54 @@
+# 此配置只适合开发测试环境,详细配置参考: http://t.cn/A64RaHJm
+server:
+ port: 9080
+ servlet:
+ context-path: /xxl-job-admin
+
+# xxl
+xxl:
+ job:
+ i18n: zh_CN
+ logretentiondays: 30
+ triggerpool:
+ fast.max: 200
+ slow.max: 200
+
+# mybatis
+mybatis:
+ mapper-locations: classpath:/mybatis-mapper/*Mapper.xml
+
+# spring
+spring:
+ datasource:
+ url: jdbc:mysql://${MYSQL_HOST:pig-mysql}:${MYSQL_PORT:3306}/${MYSQL_DB:pigxx_job}?characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=GMT%2B8&nullCatalogMeansCurrent=true&allowPublicKeyRetrieval=true
+ driver-class-name: com.mysql.cj.jdbc.Driver
+ username: ${MYSQL-PWD:root}
+ password: ${MYSQL-PWD:root}
+ mvc:
+ static-path-pattern: /static/**
+ resources:
+ static-locations: classpath:/static/
+ freemarker:
+ suffix: .ftl
+ request-context-attribute: request
+ settings:
+ number_format: 0.##########
+ mail:
+ host: smtp.mxhichina.com
+ port: 465
+ from: xxxx@gitee.wang
+ username: xxxx@gitee.wang
+ password: xxxx
+ properties:
+ mail:
+ smtp:
+ auth: true
+ ssl.enable: true
+ starttls.enable: false
+ required: false
+
+# close mail health check
+management:
+ health:
+ mail:
+ enabled: false
diff --git a/pigx-visual/pigx-xxl-job-admin/src/main/resources/i18n/message_en.properties b/pigx-visual/pigx-xxl-job-admin/src/main/resources/i18n/message_en.properties
new file mode 100755
index 000000000..ab9f9ea95
--- /dev/null
+++ b/pigx-visual/pigx-xxl-job-admin/src/main/resources/i18n/message_en.properties
@@ -0,0 +1,264 @@
+admin_name=Scheduling Center
+admin_name_full=Distributed Task Scheduling Platform XXL-JOB
+admin_version=2.2.1-SNAPSHOT
+admin_i18n=en
+
+## system
+system_tips=System message
+system_ok=Confirm
+system_close=Close
+system_save=Save
+system_cancel=Cancel
+system_search=Search
+system_status=Status
+system_opt=Operate
+system_please_input=please input
+system_please_choose=please choose
+system_success=success
+system_fail=fail
+system_add_suc=add success
+system_add_fail=add fail
+system_update_suc=update success
+system_update_fail=update fail
+system_all=All
+system_api_error=net error
+system_show=Show
+system_empty=Empty
+system_opt_suc=operate success
+system_opt_fail=operate fail
+system_opt_edit=Edit
+system_opt_del=Delete
+system_opt_copy=Copy
+system_unvalid=illegal
+system_not_found=not exist
+system_nav=Navigation
+system_digits=digits
+system_lengh_limit=Length limit
+system_permission_limit=Permission limit
+system_welcome=Welcome
+
+## daterangepicker
+daterangepicker_ranges_recent_hour=recent one hour
+daterangepicker_ranges_today=today
+daterangepicker_ranges_yesterday=yesterday
+daterangepicker_ranges_this_month=this month
+daterangepicker_ranges_last_month=last month
+daterangepicker_ranges_recent_week=recent one week
+daterangepicker_ranges_recent_month=recent one month
+daterangepicker_custom_name=custom
+daterangepicker_custom_starttime=start time
+daterangepicker_custom_endtime=end time
+daterangepicker_custom_daysofweek=Sun,Mon,Tue,Wed,Thu,Fri,Sat
+daterangepicker_custom_monthnames=Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec
+
+## dataTable
+dataTable_sProcessing=processing...
+dataTable_sLengthMenu= _MENU_ records per page
+dataTable_sZeroRecords=No matching results
+dataTable_sInfo=page _PAGE_ ( Total _PAGES_ pages,_TOTAL_ records )
+dataTable_sInfoEmpty=No Record
+dataTable_sInfoFiltered=(Filtered by _MAX_ results)
+dataTable_sSearch=Search
+dataTable_sEmptyTable=Table data is empty
+dataTable_sLoadingRecords=Loading...
+dataTable_sFirst=FIRST PAGE
+dataTable_sPrevious=Previous Page
+dataTable_sNext=Next Page
+dataTable_sLast=LAST PAGE
+dataTable_sSortAscending=: Rank this column in ascending order
+dataTable_sSortDescending=: Rank this column in descending order
+
+## login
+login_btn=Login
+login_remember_me=Remember Me
+login_username_placeholder=Please enter username
+login_password_placeholder=Please enter password
+login_username_empty=Please enter username
+login_username_lt_4=Username length should not be less than 4
+login_password_empty=Please enter password
+login_password_lt_4=Password length should not be less than 4
+login_success=Login success
+login_fail=Login fail
+login_param_empty=Username or password is empty
+login_param_unvalid=Username or password error
+
+## logout
+logout_btn=Logout
+logout_confirm=Confirm logout?
+logout_success=Logout success
+logout_fail=Logout fail
+
+## change pwd
+change_pwd=Change password
+change_pwd_suc_to_logout=Change password successful, about to log out login
+change_pwd_field_newpwd=new password
+
+## dashboard
+job_dashboard_name=Run report
+job_dashboard_job_num=Job number
+job_dashboard_job_num_tip=The number of tasks running in the scheduling center
+job_dashboard_trigger_num=trigger number
+job_dashboard_trigger_num_tip=The number of trigger record scheduled by the scheduling center
+job_dashboard_jobgroup_num=Executor number
+job_dashboard_jobgroup_num_tip=The number of online executor machines perceived by the scheduling center
+job_dashboard_report=Scheduling report
+job_dashboard_report_loaddata_fail=Scheduling report load data error
+job_dashboard_date_report=Date distribution
+job_dashboard_rate_report=Percentage distribution
+
+## job info
+jobinfo_name=Job Manage
+jobinfo_job=Job
+jobinfo_field_add=Add Job
+jobinfo_field_update=Edit Job
+jobinfo_field_id=Job ID
+jobinfo_field_jobgroup=Executor
+jobinfo_field_jobdesc=Job description
+jobinfo_field_timeout=Job timeout period
+jobinfo_field_gluetype=GLUE Type
+jobinfo_field_executorparam=Param
+jobinfo_field_cron_unvalid=The Cron is illegal
+jobinfo_field_cron_never_fire=The Cron will never fire
+jobinfo_field_author=Author
+jobinfo_field_alarmemail=Alarm email
+jobinfo_field_alarmemail_placeholder=Please enter alarm mail, if there are more than one comma separated
+jobinfo_field_executorRouteStrategy=Route Strategy
+jobinfo_field_childJobId=Child Job ID
+jobinfo_field_childJobId_placeholder=Please enter the Child job ID, if there are more than one comma separated
+jobinfo_field_executorBlockStrategy=Block Strategy
+jobinfo_field_executorFailRetryCount=Fail Retry Count
+jobinfo_field_executorFailRetryCount_placeholder=Fail Retry Count. effect if greater than zero
+jobinfo_script_location=Script location
+jobinfo_shard_index=Shard index
+jobinfo_shard_total=Shard total
+jobinfo_opt_stop=Stop
+jobinfo_opt_start=Start
+jobinfo_opt_log=Query Log
+jobinfo_opt_run=Run Once
+jobinfo_opt_run_tips=Please input the address for this trigger. Null will be obtained from the executor
+jobinfo_opt_registryinfo=Registry Info
+jobinfo_opt_next_time=Next trigger time
+jobinfo_glue_remark=Resource Remark
+jobinfo_glue_remark_limit=Resource Remark length is limited to 4~100
+jobinfo_glue_rollback=Version Backtrack
+jobinfo_glue_jobid_unvalid=Job ID is illegal
+jobinfo_glue_gluetype_unvalid=The job is not GLUE Type
+jobinfo_field_executorTimeout_placeholder=Job Timeout period,in seconds. effect if greater than zero
+
+## job log
+joblog_name=Trigger Log
+joblog_status=Status
+joblog_status_all=All
+joblog_status_suc=Success
+joblog_status_fail=Fail
+joblog_status_running=Running
+joblog_field_triggerTime=Trigger Time
+joblog_field_triggerCode=Trigger Result
+joblog_field_triggerMsg=Trigger Msg
+joblog_field_handleTime=Handle Time
+joblog_field_handleCode=Handle Result
+joblog_field_handleMsg=Trigger Msg
+joblog_field_executorAddress=Executor Address
+joblog_clean=Clean
+joblog_clean_log=Clean Log
+joblog_clean_type=Clean Type
+joblog_clean_type_1=Clean up log data a month ago
+joblog_clean_type_2=Clean up log data three month ago
+joblog_clean_type_3=Clean up log data six month ago
+joblog_clean_type_4=Clean up log data a year ago
+joblog_clean_type_5=Clean up log data a thousand record ago
+joblog_clean_type_6=Clean up log data ten thousand record ago
+joblog_clean_type_7=Clean up log data thirty thousand record ago
+joblog_clean_type_8=Clean up log data hundred thousand record ago
+joblog_clean_type_9=Clean up all log data
+joblog_clean_type_unvalid=Clean type is illegal
+joblog_handleCode_200=Success
+joblog_handleCode_500=Fail
+joblog_handleCode_502=Timeout
+joblog_kill_log=Kill Job
+joblog_kill_log_limit=Trigger Fail, can not kill job
+joblog_kill_log_byman=Manual operation, kill job
+joblog_lost_fail=Job result lost, marked as failure
+joblog_rolling_log=Rolling log
+joblog_rolling_log_refresh=Refresh
+joblog_rolling_log_triggerfail=The job trigger fail, can not view the rolling log
+joblog_rolling_log_failoften=The request for the Rolling log is terminated, the number of failed requests exceeds the limit, Reload the log on the refresh page
+joblog_logid_unvalid=Log ID is illegal
+
+## job group
+jobgroup_name=Executor Manage
+jobgroup_list=Executor List
+jobgroup_add=Add Executor
+jobgroup_edit=Edit Executor
+jobgroup_del=Delete Executor
+jobgroup_field_title=Title
+jobgroup_field_addressType=Registry Type
+jobgroup_field_addressType_0=Automatic registration
+jobgroup_field_addressType_1=Manual registration
+jobgroup_field_addressType_limit=Manually registration type, the machine address must not be empty
+jobgroup_field_registryList=machine address
+jobgroup_field_registryList_unvalid=registry machine address is illegal
+jobgroup_field_registryList_placeholder=Please enter the machine address, if there are more than one comma separated
+jobgroup_field_appname_limit=Limit the beginning of a lowercase letter, consists of lowercase letters、number and hyphen.
+jobgroup_field_appname_length=AppName length is limited to 4~64
+jobgroup_field_title_length=Title length is limited to 4~12
+jobgroup_field_order_digits=Please enter a positive integer
+jobgroup_field_orderrange=Order is limited to 1~1000
+jobgroup_del_limit_0=Refuse to delete, the executor is being used
+jobgroup_del_limit_1=Refuses to delete, the system retains at least one executor
+jobgroup_empty=There is no valid executor. Please contact the administrator
+
+## job conf
+jobconf_block_SERIAL_EXECUTION=Serial execution
+jobconf_block_DISCARD_LATER=Discard Later
+jobconf_block_COVER_EARLY=Cover Early
+jobconf_route_first=First
+jobconf_route_last=Last
+jobconf_route_round=Round
+jobconf_route_random=Random
+jobconf_route_consistenthash=Consistent Hash
+jobconf_route_lfu=Least Frequently Used
+jobconf_route_lru=Least Recently Used
+jobconf_route_failover=Failover
+jobconf_route_busyover=Busyover
+jobconf_route_shard=Sharding Broadcast
+jobconf_idleBeat=Idle check
+jobconf_beat=Heartbeats
+jobconf_monitor=Task Scheduling Center monitor alarm
+jobconf_monitor_detail=monitor alarm details
+jobconf_monitor_alarm_title=Alarm Type
+jobconf_monitor_alarm_type=Trigger Fail
+jobconf_monitor_alarm_content=Alarm Content
+jobconf_trigger_admin_adress=Trigger machine address
+jobconf_trigger_exe_regtype=Execotor-Registry Type
+jobconf_trigger_exe_regaddress=Execotor-Registry Address
+jobconf_trigger_address_empty=Trigger Fail:registry address is empty
+jobconf_trigger_run=Trigger Job
+jobconf_trigger_child_run=Trigger child job
+jobconf_callback_child_msg1={0}/{1} [Job ID={2}], Trigger {3}, Trigger msg: {4}
+jobconf_callback_child_msg2={0}/{1} [Job ID={2}], Trigger Fail, Trigger msg: Job ID is illegal
+jobconf_trigger_type=Job trigger type
+jobconf_trigger_type_cron=Cron trigger
+jobconf_trigger_type_manual=Manual trigger
+jobconf_trigger_type_parent=Parent job trigger
+jobconf_trigger_type_api=Api trigger
+jobconf_trigger_type_retry=Fail retry trigger
+
+## user
+user_manage=User Manage
+user_username=Username
+user_password=Password
+user_role=Role
+user_role_admin=Admin User
+user_role_normal=Normal User
+user_permission=Permission
+user_add=Add User
+user_update=Edit User
+user_username_repeat=Username Repeat
+user_username_valid=Restrictions start with a lowercase letter and consist of lowercase letters and Numbers
+user_password_update_placeholder=Please input password, empty means not update
+user_update_loginuser_limit=Operation of current login account is not allowed
+
+## help
+job_help=Tutorial
+job_help_document=Official Document
diff --git a/pigx-visual/pigx-xxl-job-admin/src/main/resources/i18n/message_zh_CN.properties b/pigx-visual/pigx-xxl-job-admin/src/main/resources/i18n/message_zh_CN.properties
new file mode 100755
index 000000000..1fe8d230f
--- /dev/null
+++ b/pigx-visual/pigx-xxl-job-admin/src/main/resources/i18n/message_zh_CN.properties
@@ -0,0 +1,264 @@
+admin_name=任务调度中心
+admin_name_full=分布式任务调度平台XXL-JOB
+admin_version=2.2.1-SNAPSHOT
+admin_i18n=
+
+## system
+system_tips=系统提示
+system_ok=确定
+system_close=关闭
+system_save=保存
+system_cancel=取消
+system_search=搜索
+system_status=状态
+system_opt=操作
+system_please_input=请输入
+system_please_choose=请选择
+system_success=成功
+system_fail=失败
+system_add_suc=新增成功
+system_add_fail=新增失败
+system_update_suc=更新成功
+system_update_fail=更新失败
+system_all=全部
+system_api_error=接口异常
+system_show=查看
+system_empty=无
+system_opt_suc=操作成功
+system_opt_fail=操作失败
+system_opt_edit=编辑
+system_opt_del=删除
+system_opt_copy=复制
+system_unvalid=非法
+system_not_found=不存在
+system_nav=导航
+system_digits=整数
+system_lengh_limit=长度限制
+system_permission_limit=权限拦截
+system_welcome=欢迎
+
+## daterangepicker
+daterangepicker_ranges_recent_hour=最近一小时
+daterangepicker_ranges_today=今日
+daterangepicker_ranges_yesterday=昨日
+daterangepicker_ranges_this_month=本月
+daterangepicker_ranges_last_month=上个月
+daterangepicker_ranges_recent_week=最近一周
+daterangepicker_ranges_recent_month=最近一月
+daterangepicker_custom_name=自定义
+daterangepicker_custom_starttime=起始时间
+daterangepicker_custom_endtime=结束时间
+daterangepicker_custom_daysofweek=日,一,二,三,四,五,六
+daterangepicker_custom_monthnames=一月,二月,三月,四月,五月,六月,七月,八月,九月,十月,十一月,十二月
+
+## dataTable
+dataTable_sProcessing=处理中...
+dataTable_sLengthMenu=每页 _MENU_ 条记录
+dataTable_sZeroRecords=没有匹配结果
+dataTable_sInfo=第 _PAGE_ 页 ( 总共 _PAGES_ 页,_TOTAL_ 条记录 )
+dataTable_sInfoEmpty=无记录
+dataTable_sInfoFiltered=(由 _MAX_ 项结果过滤)
+dataTable_sSearch=搜索
+dataTable_sEmptyTable=表中数据为空
+dataTable_sLoadingRecords=载入中...
+dataTable_sFirst=首页
+dataTable_sPrevious=上页
+dataTable_sNext=下页
+dataTable_sLast=末页
+dataTable_sSortAscending=: 以升序排列此列
+dataTable_sSortDescending=: 以降序排列此列
+
+## login
+login_btn=登录
+login_remember_me=记住密码
+login_username_placeholder=请输入登录账号
+login_password_placeholder=请输入登录密码
+login_username_empty=请输入登录账号
+login_username_lt_4=登录账号不应低于4位
+login_password_empty=请输入登录密码
+login_password_lt_4=登录密码不应低于4位
+login_success=登录成功
+login_fail=登录失败
+login_param_empty=账号或密码为空
+login_param_unvalid=账号或密码错误
+
+## logout
+logout_btn=注销
+logout_confirm=确认注销登录?
+logout_success=注销成功
+logout_fail=注销失败
+
+## change pwd
+change_pwd=修改密码
+change_pwd_suc_to_logout=修改密码成功,即将注销登陆
+change_pwd_field_newpwd=新密码
+
+## dashboard
+job_dashboard_name=运行报表
+job_dashboard_job_num=任务数量
+job_dashboard_job_num_tip=调度中心运行的任务数量
+job_dashboard_trigger_num=调度次数
+job_dashboard_trigger_num_tip=调度中心触发的调度次数
+job_dashboard_jobgroup_num=执行器数量
+job_dashboard_jobgroup_num_tip=调度中心在线的执行器机器数量
+job_dashboard_report=调度报表
+job_dashboard_report_loaddata_fail=调度报表数据加载异常
+job_dashboard_date_report=日期分布图
+job_dashboard_rate_report=成功比例图
+
+## job info
+jobinfo_name=任务管理
+jobinfo_job=任务
+jobinfo_field_add=新增
+jobinfo_field_update=更新任务
+jobinfo_field_id=任务ID
+jobinfo_field_jobgroup=执行器
+jobinfo_field_jobdesc=任务描述
+jobinfo_field_gluetype=运行模式
+jobinfo_field_executorparam=任务参数
+jobinfo_field_cron_unvalid=Cron格式非法
+jobinfo_field_cron_never_fire=Cron非法,永远不会触发
+jobinfo_field_author=负责人
+jobinfo_field_timeout=任务超时时间
+jobinfo_field_alarmemail=报警邮件
+jobinfo_field_alarmemail_placeholder=请输入报警邮件,多个邮件地址则逗号分隔
+jobinfo_field_executorRouteStrategy=路由策略
+jobinfo_field_childJobId=子任务ID
+jobinfo_field_childJobId_placeholder=请输入子任务的任务ID,如存在多个则逗号分隔
+jobinfo_field_executorBlockStrategy=阻塞处理策略
+jobinfo_field_executorFailRetryCount=失败重试次数
+jobinfo_field_executorFailRetryCount_placeholder=失败重试次数,大于零时生效
+jobinfo_script_location=脚本位置
+jobinfo_shard_index=分片序号
+jobinfo_shard_total=分片总数
+jobinfo_opt_stop=停止
+jobinfo_opt_start=启动
+jobinfo_opt_log=查询日志
+jobinfo_opt_run=执行一次
+jobinfo_opt_run_tips=请输入本次执行的机器地址,为空则从执行器获取
+jobinfo_opt_registryinfo=注册节点
+jobinfo_opt_next_time=下次执行时间
+jobinfo_glue_remark=源码备注
+jobinfo_glue_remark_limit=源码备注长度限制为4~100
+jobinfo_glue_rollback=版本回溯
+jobinfo_glue_jobid_unvalid=任务ID非法
+jobinfo_glue_gluetype_unvalid=该任务非GLUE模式
+jobinfo_field_executorTimeout_placeholder=任务超时时间,单位秒,大于零时生效
+
+## job log
+joblog_name=调度日志
+joblog_status=状态
+joblog_status_all=全部
+joblog_status_suc=成功
+joblog_status_fail=失败
+joblog_status_running=进行中
+joblog_field_triggerTime=调度时间
+joblog_field_triggerCode=调度结果
+joblog_field_triggerMsg=调度备注
+joblog_field_handleTime=执行时间
+joblog_field_handleCode=执行结果
+joblog_field_handleMsg=执行备注
+joblog_field_executorAddress=执行器地址
+joblog_clean=清理
+joblog_clean_log=日志清理
+joblog_clean_type=清理方式
+joblog_clean_type_1=清理一个月之前日志数据
+joblog_clean_type_2=清理三个月之前日志数据
+joblog_clean_type_3=清理六个月之前日志数据
+joblog_clean_type_4=清理一年之前日志数据
+joblog_clean_type_5=清理一千条以前日志数据
+joblog_clean_type_6=清理一万条以前日志数据
+joblog_clean_type_7=清理三万条以前日志数据
+joblog_clean_type_8=清理十万条以前日志数据
+joblog_clean_type_9=清理所有日志数据
+joblog_clean_type_unvalid=清理类型参数异常
+joblog_handleCode_200=成功
+joblog_handleCode_500=失败
+joblog_handleCode_502=失败(超时)
+joblog_kill_log=终止任务
+joblog_kill_log_limit=调度失败,无法终止日志
+joblog_kill_log_byman=人为操作,主动终止
+joblog_lost_fail=任务结果丢失,标记失败
+joblog_rolling_log=执行日志
+joblog_rolling_log_refresh=刷新
+joblog_rolling_log_triggerfail=任务发起调度失败,无法查看执行日志
+joblog_rolling_log_failoften=终止请求Rolling日志,请求失败次数超上限,可刷新页面重新加载日志
+joblog_logid_unvalid=日志ID非法
+
+## job group
+jobgroup_name=执行器管理
+jobgroup_list=执行器列表
+jobgroup_add=新增执行器
+jobgroup_edit=编辑执行器
+jobgroup_del=删除执行器
+jobgroup_field_title=名称
+jobgroup_field_addressType=注册方式
+jobgroup_field_addressType_0=自动注册
+jobgroup_field_addressType_1=手动录入
+jobgroup_field_addressType_limit=手动录入注册方式,机器地址不可为空
+jobgroup_field_registryList=机器地址
+jobgroup_field_registryList_unvalid=机器地址格式非法
+jobgroup_field_registryList_placeholder=请输入执行器地址列表,多地址逗号分隔
+jobgroup_field_appname_limit=限制以小写字母开头,由小写字母、数字和中划线组成
+jobgroup_field_appname_length=AppName长度限制为4~64
+jobgroup_field_title_length=名称长度限制为4~12
+jobgroup_field_order_digits=请输入整数
+jobgroup_field_orderrange=取值范围为1~1000
+jobgroup_del_limit_0=拒绝删除,该执行器使用中
+jobgroup_del_limit_1=拒绝删除, 系统至少保留一个执行器
+jobgroup_empty=不存在有效执行器,请联系管理员
+
+## job conf
+jobconf_block_SERIAL_EXECUTION=单机串行
+jobconf_block_DISCARD_LATER=丢弃后续调度
+jobconf_block_COVER_EARLY=覆盖之前调度
+jobconf_route_first=第一个
+jobconf_route_last=最后一个
+jobconf_route_round=轮询
+jobconf_route_random=随机
+jobconf_route_consistenthash=一致性HASH
+jobconf_route_lfu=最不经常使用
+jobconf_route_lru=最近最久未使用
+jobconf_route_failover=故障转移
+jobconf_route_busyover=忙碌转移
+jobconf_route_shard=分片广播
+jobconf_idleBeat=空闲检测
+jobconf_beat=心跳检测
+jobconf_monitor=任务调度中心监控报警
+jobconf_monitor_detail=监控告警明细
+jobconf_monitor_alarm_title=告警类型
+jobconf_monitor_alarm_type=调度失败
+jobconf_monitor_alarm_content=告警内容
+jobconf_trigger_admin_adress=调度机器
+jobconf_trigger_exe_regtype=执行器-注册方式
+jobconf_trigger_exe_regaddress=执行器-地址列表
+jobconf_trigger_address_empty=调度失败:执行器地址为空
+jobconf_trigger_run=触发调度
+jobconf_trigger_child_run=触发子任务
+jobconf_callback_child_msg1={0}/{1} [任务ID={2}], 触发{3}, 触发备注: {4}
+jobconf_callback_child_msg2={0}/{1} [任务ID={2}], 触发失败, 触发备注: 任务ID格式错误
+jobconf_trigger_type=任务触发类型
+jobconf_trigger_type_cron=Cron触发
+jobconf_trigger_type_manual=手动触发
+jobconf_trigger_type_parent=父任务触发
+jobconf_trigger_type_api=API触发
+jobconf_trigger_type_retry=失败重试触发
+
+## user
+user_manage=用户管理
+user_username=账号
+user_password=密码
+user_role=角色
+user_role_admin=管理员
+user_role_normal=普通用户
+user_permission=权限
+user_add=新增用户
+user_update=更新用户
+user_username_repeat=账号重复
+user_username_valid=限制以小写字母开头,由小写字母、数字组成
+user_password_update_placeholder=请输入新密码,为空则不更新密码
+user_update_loginuser_limit=禁止操作当前登录账号
+
+## help
+job_help=使用教程
+job_help_document=官方文档
\ No newline at end of file
diff --git a/pigx-visual/pigx-xxl-job-admin/src/main/resources/i18n/message_zh_TC.properties b/pigx-visual/pigx-xxl-job-admin/src/main/resources/i18n/message_zh_TC.properties
new file mode 100755
index 000000000..9aaccf9dc
--- /dev/null
+++ b/pigx-visual/pigx-xxl-job-admin/src/main/resources/i18n/message_zh_TC.properties
@@ -0,0 +1,264 @@
+admin_name=任務調度中心
+admin_name_full=分布式任務調度平臺XXL-JOB
+admin_version=2.2.1-SNAPSHOT
+admin_i18n=
+
+## system
+system_tips=系統提示
+system_ok=確定
+system_close=關閉
+system_save=儲存
+system_cancel=取消
+system_search=搜尋
+system_status=狀態
+system_opt=操作
+system_please_input=請輸入
+system_please_choose=请選擇
+system_success=成功
+system_fail=失敗
+system_add_suc=新增成功
+system_add_fail=新增失敗
+system_update_suc=更新成功
+system_update_fail=更新失敗
+system_all=全部
+system_api_error=API錯誤
+system_show=查看
+system_empty=無
+system_opt_suc=操作成功
+system_opt_fail=操作失敗
+system_opt_edit=編輯
+system_opt_del=刪除
+system_opt_copy=復制
+system_unvalid=非法
+system_not_found=不存在
+system_nav=導航
+system_digits=整數
+system_lengh_limit=長度限制
+system_permission_limit=權限控管
+system_welcome=歡迎
+
+## daterangepicker
+daterangepicker_ranges_recent_hour=最近一小時
+daterangepicker_ranges_today=今日
+daterangepicker_ranges_yesterday=昨日
+daterangepicker_ranges_this_month=本月
+daterangepicker_ranges_last_month=上個月
+daterangepicker_ranges_recent_week=最近一周
+daterangepicker_ranges_recent_month=最近一月
+daterangepicker_custom_name=自定義
+daterangepicker_custom_starttime=起始時間
+daterangepicker_custom_endtime=結束時間
+daterangepicker_custom_daysofweek=日,一,二,三,四,五,六
+daterangepicker_custom_monthnames=一月,二月,三月,四月,五月,六月,七月,八月,九月,十月,十一月,十二月
+
+## dataTable
+dataTable_sProcessing=處理中...
+dataTable_sLengthMenu=每頁 _MENU_ 條記錄
+dataTable_sZeroRecords=沒有相符合記錄
+dataTable_sInfo=第 _PAGE_ 頁 ( 總共 _PAGES_ 頁,_TOTAL_ 條記錄 )
+dataTable_sInfoEmpty=無記錄
+dataTable_sInfoFiltered=(由 _MAX_ 項結果過濾)
+dataTable_sSearch=搜尋
+dataTable_sEmptyTable=表中資料為空
+dataTable_sLoadingRecords=載入中...
+dataTable_sFirst=首頁
+dataTable_sPrevious=上頁
+dataTable_sNext=下頁
+dataTable_sLast=末頁
+dataTable_sSortAscending=: 以升幂排序此列
+dataTable_sSortDescending=: 以降幂排序此列
+
+## login
+login_btn=登入
+login_remember_me=記住密碼
+login_username_placeholder=請輸入登入帳號
+login_password_placeholder=請輸入登入密碼
+login_username_empty=請輸入登入帳號
+login_username_lt_4=登入帳號不應低於4位數
+login_password_empty=請輸入登入密碼
+login_password_lt_4=登入密碼不應低於4位數
+login_success=登入成功
+login_fail=登入失敗
+login_param_empty=帳號或密碼為空值
+login_param_unvalid=帳號或密碼錯誤
+
+## logout
+logout_btn=登出
+logout_confirm=確認登出?
+logout_success=登出成功
+logout_fail=登出失敗
+
+## change pwd
+change_pwd=修改密碼
+change_pwd_suc_to_logout=修改密碼成功,即將登出
+change_pwd_field_newpwd=新密碼
+
+## dashboard
+job_dashboard_name=運行報表
+job_dashboard_job_num=任務數量
+job_dashboard_job_num_tip=調度中心運行的任務數量
+job_dashboard_trigger_num=調度次數
+job_dashboard_trigger_num_tip=調度中心觸發的調度次數
+job_dashboard_jobgroup_num=執行器數量
+job_dashboard_jobgroup_num_tip=調度中心在線的執行器機器數量
+job_dashboard_report=調度報表
+job_dashboard_report_loaddata_fail=調度報表資料加載異常
+job_dashboard_date_report=日期分布圖
+job_dashboard_rate_report=成功比例圖
+
+## job info
+jobinfo_name=任務管理
+jobinfo_job=任務
+jobinfo_field_add=新增
+jobinfo_field_update=更新任務
+jobinfo_field_id=任務ID
+jobinfo_field_jobgroup=執行器
+jobinfo_field_jobdesc=任務描述
+jobinfo_field_gluetype=運行模式
+jobinfo_field_executorparam=任務參數
+jobinfo_field_cron_unvalid=Cron 格式非法
+jobinfo_field_cron_never_fire=Cron 格式非法,永遠不會觸發
+jobinfo_field_author=負責人
+jobinfo_field_timeout=任務超時秒數
+jobinfo_field_alarmemail=告警郵件
+jobinfo_field_alarmemail_placeholder=輸入多個告警郵件地址,請以逗號分隔
+jobinfo_field_executorRouteStrategy=路由策略
+jobinfo_field_childJobId=子任務ID
+jobinfo_field_childJobId_placeholder=輸入子任務ID,如有多個請以逗號分隔
+jobinfo_field_executorBlockStrategy=阻塞處理策略
+jobinfo_field_executorFailRetryCount=失敗重試次數
+jobinfo_field_executorFailRetryCount_placeholder=失敗重試次數,大於零時生效
+jobinfo_script_location=腳本位置
+jobinfo_shard_index=分片序號
+jobinfo_shard_total=分片總數
+jobinfo_opt_stop=停止
+jobinfo_opt_start=啟動
+jobinfo_opt_log=查詢日誌
+jobinfo_opt_run=執行一次
+jobinfo_opt_run_tips=請輸入本次執行的機器地址,為空則從執行器獲取
+jobinfo_opt_registryinfo=注冊節點
+jobinfo_opt_next_time=下次執行時間
+jobinfo_glue_remark=源碼備註
+jobinfo_glue_remark_limit=源碼備註長度限制為4~100
+jobinfo_glue_rollback=版本回復
+jobinfo_glue_jobid_unvalid=任務ID非法
+jobinfo_glue_gluetype_unvalid=該任務非GLUE模式
+jobinfo_field_executorTimeout_placeholder=任務超時時間,單位秒,大於零時生效
+
+## job log
+joblog_name=調度日誌
+joblog_status=狀態
+joblog_status_all=全部
+joblog_status_suc=成功
+joblog_status_fail=失敗
+joblog_status_running=進行中
+joblog_field_triggerTime=調度時間
+joblog_field_triggerCode=調度結果
+joblog_field_triggerMsg=調度備註
+joblog_field_handleTime=執行時間
+joblog_field_handleCode=執行结果
+joblog_field_handleMsg=執行備註
+joblog_field_executorAddress=執行器地址
+joblog_clean=清理
+joblog_clean_log=日誌清理
+joblog_clean_type=清理方式
+joblog_clean_type_1=清理一個月之前日誌資料
+joblog_clean_type_2=清理三個月之前日誌資料
+joblog_clean_type_3=清理六個月之前日誌資料
+joblog_clean_type_4=清理一年之前日誌資料
+joblog_clean_type_5=清理一千條以前日誌資料
+joblog_clean_type_6=清理一萬條以前日誌資料
+joblog_clean_type_7=清理三萬條以前日誌資料
+joblog_clean_type_8=清理十萬條以前日誌資料
+joblog_clean_type_9=清理所有日誌資料
+joblog_clean_type_unvalid=清理類型參数異常
+joblog_handleCode_200=成功
+joblog_handleCode_500=失敗
+joblog_handleCode_502=失敗(超時)
+joblog_kill_log=终止任務
+joblog_kill_log_limit=調度失敗,無法终止日誌
+joblog_kill_log_byman=人為操作,主動終止
+joblog_lost_fail=任務結果丟失,標記失敗
+joblog_rolling_log=執行日誌
+joblog_rolling_log_refresh=更新
+joblog_rolling_log_triggerfail=任務發起調度失敗,無法查看執行日誌
+joblog_rolling_log_failoften=終止請求Rolling日誌,請求失敗次數超上限,可刷新頁面重新加載日誌
+joblog_logid_unvalid=日誌ID非法
+
+## job group
+jobgroup_name=執行器管理
+jobgroup_list=執行器列表
+jobgroup_add=新增執行器
+jobgroup_edit=編輯執行器
+jobgroup_del=刪除執行器
+jobgroup_field_title=名稱
+jobgroup_field_addressType=注冊方式
+jobgroup_field_addressType_0=自動注冊
+jobgroup_field_addressType_1=手動登錄
+jobgroup_field_addressType_limit=手動登錄注冊方式,機器地址不可為空
+jobgroup_field_registryList=機器地址
+jobgroup_field_registryList_unvalid=機器地址格式非法
+jobgroup_field_registryList_placeholder=請輸入執行器地址列表,多個地址請以逗號分隔
+jobgroup_field_appname_limit=限制以小寫字母開頭,由小寫字母、數字和中划線組成
+jobgroup_field_appname_length=AppName長度限制為4~64
+jobgroup_field_title_length=名稱長度限制為4~12
+jobgroup_field_order_digits=請輸入整數
+jobgroup_field_orderrange=取值範圍為1~1000
+jobgroup_del_limit_0=拒絕刪除,該執行器使用中
+jobgroup_del_limit_1=拒絕删除,系统至少保留一個執行器
+jobgroup_empty=不存在有效執行器,請聯絡系統管理員
+
+## job conf
+jobconf_block_SERIAL_EXECUTION=單機串行
+jobconf_block_DISCARD_LATER=丢棄后續調度
+jobconf_block_COVER_EARLY=覆蓋之前調度
+jobconf_route_first=第一個
+jobconf_route_last=最後一個
+jobconf_route_round=輪詢
+jobconf_route_random=隨機
+jobconf_route_consistenthash=一致性HASH
+jobconf_route_lfu=最不經常使用
+jobconf_route_lru=最近最久未使用
+jobconf_route_failover=故障轉移
+jobconf_route_busyover=忙碌轉移
+jobconf_route_shard=分片廣播
+jobconf_idleBeat=空閒檢測
+jobconf_beat=心跳檢測
+jobconf_monitor=任務調度中心監控告警
+jobconf_monitor_detail=監控告警明细
+jobconf_monitor_alarm_title=告警類型
+jobconf_monitor_alarm_type=調度失敗
+jobconf_monitor_alarm_content=告警内容
+jobconf_trigger_admin_adress=調度機器
+jobconf_trigger_exe_regtype=執行器-注冊方式
+jobconf_trigger_exe_regaddress=執行器-地址列表
+jobconf_trigger_address_empty=調度失敗:執行器地址為空
+jobconf_trigger_run=觸發調度
+jobconf_trigger_child_run=觸發子任務
+jobconf_callback_child_msg1={0}/{1} [任務ID={2}], 觸發{3}, 觸發備註: {4}
+jobconf_callback_child_msg2={0}/{1} [任務ID={2}], 觸發失败, 觸發備註: 任務ID格式錯誤
+jobconf_trigger_type=任務觸發類型
+jobconf_trigger_type_cron=Cron觸發
+jobconf_trigger_type_manual=手動觸發
+jobconf_trigger_type_parent=父任務觸發
+jobconf_trigger_type_api=API觸發
+jobconf_trigger_type_retry=失敗重試觸發
+
+## user
+user_manage=用户管理
+user_username=帳號
+user_password=密碼
+user_role=角色
+user_role_admin=管理員
+user_role_normal=普通用戶
+user_permission=權限
+user_add=新增用戶
+user_update=更新用戶
+user_username_repeat=帳號重複
+user_username_valid=限制以小寫字母開頭,由小寫字母、數字組成
+user_password_update_placeholder=請輸入新密碼,為空則不更新密碼
+user_update_loginuser_limit=禁止操作當前登入帳號
+
+## help
+job_help=使用教程
+job_help_document=官方文件
\ No newline at end of file
diff --git a/pigx-visual/pigx-xxl-job-admin/src/main/resources/logback-spring.xml b/pigx-visual/pigx-xxl-job-admin/src/main/resources/logback-spring.xml
new file mode 100755
index 000000000..fa238198d
--- /dev/null
+++ b/pigx-visual/pigx-xxl-job-admin/src/main/resources/logback-spring.xml
@@ -0,0 +1,77 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ${CONSOLE_LOG_PATTERN}
+
+
+
+
+
+ ${log.path}/debug.log
+
+ ${log.path}/%d{yyyy-MM, aux}/debug.%d{yyyy-MM-dd}.%i.log.gz
+ 50MB
+ 30
+
+
+ %date [%thread] %-5level [%logger{50}] %file:%line - %msg%n
+
+
+
+
+
+ ${log.path}/error.log
+
+ ${log.path}/%d{yyyy-MM}/error.%d{yyyy-MM-dd}.%i.log.gz
+ 50MB
+ 30
+
+
+ %date [%thread] %-5level [%logger{50}] %file:%line - %msg%n
+
+
+ ERROR
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pigx-visual/pigx-xxl-job-admin/src/main/resources/mybatis-mapper/XxlJobGroupMapper.xml b/pigx-visual/pigx-xxl-job-admin/src/main/resources/mybatis-mapper/XxlJobGroupMapper.xml
new file mode 100755
index 000000000..7635214a3
--- /dev/null
+++ b/pigx-visual/pigx-xxl-job-admin/src/main/resources/mybatis-mapper/XxlJobGroupMapper.xml
@@ -0,0 +1,88 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ t.id,
+ t.app_name,
+ t.title,
+ t.address_type,
+ t.address_list
+
+
+
+
+
+
+
+ INSERT INTO xxl_job_group ( `app_name`, `title`, `address_type`, `address_list`)
+ values ( #{appname}, #{title}, #{addressType}, #{addressList});
+
+
+
+ UPDATE xxl_job_group
+ SET `app_name` = #{appname},
+ `title` = #{title},
+ `address_type` = #{addressType},
+ `address_list` = #{addressList}
+ WHERE id = #{id}
+
+
+
+ DELETE FROM xxl_job_group
+ WHERE id = #{id}
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/pigx-visual/pigx-xxl-job-admin/src/main/resources/mybatis-mapper/XxlJobInfoMapper.xml b/pigx-visual/pigx-xxl-job-admin/src/main/resources/mybatis-mapper/XxlJobInfoMapper.xml
new file mode 100755
index 000000000..cb9048d29
--- /dev/null
+++ b/pigx-visual/pigx-xxl-job-admin/src/main/resources/mybatis-mapper/XxlJobInfoMapper.xml
@@ -0,0 +1,229 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ t.id,
+ t.job_group,
+ t.job_cron,
+ t.job_desc,
+ t.add_time,
+ t.update_time,
+ t.author,
+ t.alarm_email,
+ t.executor_route_strategy,
+ t.executor_handler,
+ t.executor_param,
+ t.executor_block_strategy,
+ t.executor_timeout,
+ t.executor_fail_retry_count,
+ t.glue_type,
+ t.glue_source,
+ t.glue_remark,
+ t.glue_updatetime,
+ t.child_jobid,
+ t.trigger_status,
+ t.trigger_last_time,
+ t.trigger_next_time
+
+
+
+
+
+
+
+ INSERT INTO xxl_job_info (
+ job_group,
+ job_cron,
+ job_desc,
+ add_time,
+ update_time,
+ author,
+ alarm_email,
+ executor_route_strategy,
+ executor_handler,
+ executor_param,
+ executor_block_strategy,
+ executor_timeout,
+ executor_fail_retry_count,
+ glue_type,
+ glue_source,
+ glue_remark,
+ glue_updatetime,
+ child_jobid,
+ trigger_status,
+ trigger_last_time,
+ trigger_next_time
+ ) VALUES (
+ #{jobGroup},
+ #{jobCron},
+ #{jobDesc},
+ #{addTime},
+ #{updateTime},
+ #{author},
+ #{alarmEmail},
+ #{executorRouteStrategy},
+ #{executorHandler},
+ #{executorParam},
+ #{executorBlockStrategy},
+ #{executorTimeout},
+ #{executorFailRetryCount},
+ #{glueType},
+ #{glueSource},
+ #{glueRemark},
+ #{glueUpdatetime},
+ #{childJobId},
+ #{triggerStatus},
+ #{triggerLastTime},
+ #{triggerNextTime}
+ );
+
+
+
+
+
+
+ UPDATE xxl_job_info
+ SET
+ job_group = #{jobGroup},
+ job_cron = #{jobCron},
+ job_desc = #{jobDesc},
+ update_time = #{updateTime},
+ author = #{author},
+ alarm_email = #{alarmEmail},
+ executor_route_strategy = #{executorRouteStrategy},
+ executor_handler = #{executorHandler},
+ executor_param = #{executorParam},
+ executor_block_strategy = #{executorBlockStrategy},
+ executor_timeout = ${executorTimeout},
+ executor_fail_retry_count = ${executorFailRetryCount},
+ glue_type = #{glueType},
+ glue_source = #{glueSource},
+ glue_remark = #{glueRemark},
+ glue_updatetime = #{glueUpdatetime},
+ child_jobid = #{childJobId},
+ trigger_status = #{triggerStatus},
+ trigger_last_time = #{triggerLastTime},
+ trigger_next_time = #{triggerNextTime}
+ WHERE id = #{id}
+
+
+
+ DELETE
+ FROM xxl_job_info
+ WHERE id = #{id}
+
+
+
+
+
+
+
+
+
+
+ UPDATE xxl_job_info
+ SET
+ trigger_last_time = #{triggerLastTime},
+ trigger_next_time = #{triggerNextTime},
+ trigger_status = #{triggerStatus}
+ WHERE id = #{id}
+
+
+
\ No newline at end of file
diff --git a/pigx-visual/pigx-xxl-job-admin/src/main/resources/mybatis-mapper/XxlJobLogGlueMapper.xml b/pigx-visual/pigx-xxl-job-admin/src/main/resources/mybatis-mapper/XxlJobLogGlueMapper.xml
new file mode 100755
index 000000000..699277c51
--- /dev/null
+++ b/pigx-visual/pigx-xxl-job-admin/src/main/resources/mybatis-mapper/XxlJobLogGlueMapper.xml
@@ -0,0 +1,71 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ t.id,
+ t.job_id,
+ t.glue_type,
+ t.glue_source,
+ t.glue_remark,
+ t.add_time,
+ t.update_time
+
+
+
+ INSERT INTO xxl_job_logglue (
+ `job_id`,
+ `glue_type`,
+ `glue_source`,
+ `glue_remark`,
+ `add_time`,
+ `update_time`
+ ) VALUES (
+ #{jobId},
+ #{glueType},
+ #{glueSource},
+ #{glueRemark},
+ #{addTime},
+ #{updateTime}
+ );
+
+
+
+
+
+
+ DELETE FROM xxl_job_logglue
+ WHERE id NOT in(
+ SELECT id FROM(
+ SELECT id FROM xxl_job_logglue
+ WHERE `job_id` = #{jobId}
+ ORDER BY update_time desc
+ LIMIT 0, #{limit}
+ ) t1
+ ) AND `job_id` = #{jobId}
+
+
+
+ DELETE FROM xxl_job_logglue
+ WHERE `job_id` = #{jobId}
+
+
+
\ No newline at end of file
diff --git a/pigx-visual/pigx-xxl-job-admin/src/main/resources/mybatis-mapper/XxlJobLogMapper.xml b/pigx-visual/pigx-xxl-job-admin/src/main/resources/mybatis-mapper/XxlJobLogMapper.xml
new file mode 100755
index 000000000..7944f50e4
--- /dev/null
+++ b/pigx-visual/pigx-xxl-job-admin/src/main/resources/mybatis-mapper/XxlJobLogMapper.xml
@@ -0,0 +1,261 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ t.id,
+ t.job_group,
+ t.job_id,
+ t.executor_address,
+ t.executor_handler,
+ t.executor_param,
+ t.executor_sharding_param,
+ t.executor_fail_retry_count,
+ t.trigger_time,
+ t.trigger_code,
+ t.trigger_msg,
+ t.handle_time,
+ t.handle_code,
+ t.handle_msg,
+ t.alarm_status
+
+
+
+
+
+
+
+
+
+
+ INSERT INTO xxl_job_log (
+ `job_group`,
+ `job_id`,
+ `trigger_time`,
+ `trigger_code`,
+ `handle_code`
+ ) VALUES (
+ #{jobGroup},
+ #{jobId},
+ #{triggerTime},
+ #{triggerCode},
+ #{handleCode}
+ );
+
+
+
+
+ UPDATE xxl_job_log
+ SET
+ `trigger_time`= #{triggerTime},
+ `trigger_code`= #{triggerCode},
+ `trigger_msg`= #{triggerMsg},
+ `executor_address`= #{executorAddress},
+ `executor_handler`=#{executorHandler},
+ `executor_param`= #{executorParam},
+ `executor_sharding_param`= #{executorShardingParam},
+ `executor_fail_retry_count`= #{executorFailRetryCount}
+ WHERE `id`= #{id}
+
+
+
+ UPDATE xxl_job_log
+ SET
+ `handle_time`= #{handleTime},
+ `handle_code`= #{handleCode},
+ `handle_msg`= #{handleMsg}
+ WHERE `id`= #{id}
+
+
+
+ delete from xxl_job_log
+ WHERE job_id = #{jobId}
+
+
+
+
+
+
+
+
+
+ delete from xxl_job_log
+ WHERE id in
+
+ #{item}
+
+
+
+
+
+
+ UPDATE xxl_job_log
+ SET
+ `alarm_status` = #{newAlarmStatus}
+ WHERE `id`= #{logId} AND `alarm_status` = #{oldAlarmStatus}
+
+
+
+
+
\ No newline at end of file
diff --git a/pigx-visual/pigx-xxl-job-admin/src/main/resources/mybatis-mapper/XxlJobLogReportMapper.xml b/pigx-visual/pigx-xxl-job-admin/src/main/resources/mybatis-mapper/XxlJobLogReportMapper.xml
new file mode 100755
index 000000000..579d5f394
--- /dev/null
+++ b/pigx-visual/pigx-xxl-job-admin/src/main/resources/mybatis-mapper/XxlJobLogReportMapper.xml
@@ -0,0 +1,62 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ t.id,
+ t.trigger_day,
+ t.running_count,
+ t.suc_count,
+ t.fail_count
+
+
+
+ INSERT INTO xxl_job_log_report (
+ `trigger_day`,
+ `running_count`,
+ `suc_count`,
+ `fail_count`
+ ) VALUES (
+ #{triggerDay},
+ #{runningCount},
+ #{sucCount},
+ #{failCount}
+ );
+
+
+
+
+ UPDATE xxl_job_log_report
+ SET `running_count` = #{runningCount},
+ `suc_count` = #{sucCount},
+ `fail_count` = #{failCount}
+ WHERE `trigger_day` = #{triggerDay}
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/pigx-visual/pigx-xxl-job-admin/src/main/resources/mybatis-mapper/XxlJobRegistryMapper.xml b/pigx-visual/pigx-xxl-job-admin/src/main/resources/mybatis-mapper/XxlJobRegistryMapper.xml
new file mode 100755
index 000000000..4cae667a2
--- /dev/null
+++ b/pigx-visual/pigx-xxl-job-admin/src/main/resources/mybatis-mapper/XxlJobRegistryMapper.xml
@@ -0,0 +1,62 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ t.id,
+ t.registry_group,
+ t.registry_key,
+ t.registry_value,
+ t.update_time
+
+
+
+
+
+ DELETE FROM xxl_job_registry
+ WHERE id in
+
+ #{item}
+
+
+
+
+
+
+ UPDATE xxl_job_registry
+ SET `update_time` = #{updateTime}
+ WHERE `registry_group` = #{registryGroup}
+ AND `registry_key` = #{registryKey}
+ AND `registry_value` = #{registryValue}
+
+
+
+ INSERT INTO xxl_job_registry( `registry_group` , `registry_key` , `registry_value`, `update_time`)
+ VALUES( #{registryGroup} , #{registryKey} , #{registryValue}, #{updateTime})
+
+
+
+ DELETE FROM xxl_job_registry
+ WHERE registry_group = #{registryGroup}
+ AND registry_key = #{registryKey}
+ AND registry_value = #{registryValue}
+
+
+
\ No newline at end of file
diff --git a/pigx-visual/pigx-xxl-job-admin/src/main/resources/mybatis-mapper/XxlJobUserMapper.xml b/pigx-visual/pigx-xxl-job-admin/src/main/resources/mybatis-mapper/XxlJobUserMapper.xml
new file mode 100755
index 000000000..9e09b4aae
--- /dev/null
+++ b/pigx-visual/pigx-xxl-job-admin/src/main/resources/mybatis-mapper/XxlJobUserMapper.xml
@@ -0,0 +1,87 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ t.id,
+ t.username,
+ t.password,
+ t.role,
+ t.permission
+
+
+
+
+
+
+
+
+
+ INSERT INTO xxl_job_user (
+ username,
+ password,
+ role,
+ permission
+ ) VALUES (
+ #{username},
+ #{password},
+ #{role},
+ #{permission}
+ );
+
+
+
+ UPDATE xxl_job_user
+ SET
+
+ password = #{password},
+
+ role = #{role},
+ permission = #{permission}
+ WHERE id = #{id}
+
+
+
+ DELETE
+ FROM xxl_job_user
+ WHERE id = #{id}
+
+
+
\ No newline at end of file
diff --git a/pigx-visual/pigx-xxl-job-admin/src/main/resources/static/adminlte/bower_components/Ionicons/css/ionicons.min.css b/pigx-visual/pigx-xxl-job-admin/src/main/resources/static/adminlte/bower_components/Ionicons/css/ionicons.min.css
new file mode 100755
index 000000000..baba9e930
--- /dev/null
+++ b/pigx-visual/pigx-xxl-job-admin/src/main/resources/static/adminlte/bower_components/Ionicons/css/ionicons.min.css
@@ -0,0 +1,11 @@
+@charset "UTF-8";/*!
+ Ionicons, v2.0.0
+ Created by Ben Sperry for the Ionic Framework, http://ionicons.com/
+ https://twitter.com/benjsperry https://twitter.com/ionicframework
+ MIT License: https://github.com/driftyco/ionicons
+
+ Android-style icons originally built by Google’s
+ Material Design Icons: https://github.com/google/material-design-icons
+ used under CC BY http://creativecommons.org/licenses/by/4.0/
+ Modified icons to fit ionicon’s grid from original.
+*/@font-face{font-family:"Ionicons";src:url("../fonts/ionicons.eot?v=2.0.0");src:url("../fonts/ionicons.eot?v=2.0.0#iefix") format("embedded-opentype"),url("../fonts/ionicons.ttf?v=2.0.0") format("truetype"),url("../fonts/ionicons.woff?v=2.0.0") format("woff"),url("../fonts/ionicons.svg?v=2.0.0#Ionicons") format("svg");font-weight:normal;font-style:normal}.ion,.ionicons,.ion-alert:before,.ion-alert-circled:before,.ion-android-add:before,.ion-android-add-circle:before,.ion-android-alarm-clock:before,.ion-android-alert:before,.ion-android-apps:before,.ion-android-archive:before,.ion-android-arrow-back:before,.ion-android-arrow-down:before,.ion-android-arrow-dropdown:before,.ion-android-arrow-dropdown-circle:before,.ion-android-arrow-dropleft:before,.ion-android-arrow-dropleft-circle:before,.ion-android-arrow-dropright:before,.ion-android-arrow-dropright-circle:before,.ion-android-arrow-dropup:before,.ion-android-arrow-dropup-circle:before,.ion-android-arrow-forward:before,.ion-android-arrow-up:before,.ion-android-attach:before,.ion-android-bar:before,.ion-android-bicycle:before,.ion-android-boat:before,.ion-android-bookmark:before,.ion-android-bulb:before,.ion-android-bus:before,.ion-android-calendar:before,.ion-android-call:before,.ion-android-camera:before,.ion-android-cancel:before,.ion-android-car:before,.ion-android-cart:before,.ion-android-chat:before,.ion-android-checkbox:before,.ion-android-checkbox-blank:before,.ion-android-checkbox-outline:before,.ion-android-checkbox-outline-blank:before,.ion-android-checkmark-circle:before,.ion-android-clipboard:before,.ion-android-close:before,.ion-android-cloud:before,.ion-android-cloud-circle:before,.ion-android-cloud-done:before,.ion-android-cloud-outline:before,.ion-android-color-palette:before,.ion-android-compass:before,.ion-android-contact:before,.ion-android-contacts:before,.ion-android-contract:before,.ion-android-create:before,.ion-android-delete:before,.ion-android-desktop:before,.ion-android-document:before,.ion-android-done:before,.ion-android-done-all:before,.ion-android-download:before,.ion-android-drafts:before,.ion-android-exit:before,.ion-android-expand:before,.ion-android-favorite:before,.ion-android-favorite-outline:before,.ion-android-film:before,.ion-android-folder:before,.ion-android-folder-open:before,.ion-android-funnel:before,.ion-android-globe:before,.ion-android-hand:before,.ion-android-hangout:before,.ion-android-happy:before,.ion-android-home:before,.ion-android-image:before,.ion-android-laptop:before,.ion-android-list:before,.ion-android-locate:before,.ion-android-lock:before,.ion-android-mail:before,.ion-android-map:before,.ion-android-menu:before,.ion-android-microphone:before,.ion-android-microphone-off:before,.ion-android-more-horizontal:before,.ion-android-more-vertical:before,.ion-android-navigate:before,.ion-android-notifications:before,.ion-android-notifications-none:before,.ion-android-notifications-off:before,.ion-android-open:before,.ion-android-options:before,.ion-android-people:before,.ion-android-person:before,.ion-android-person-add:before,.ion-android-phone-landscape:before,.ion-android-phone-portrait:before,.ion-android-pin:before,.ion-android-plane:before,.ion-android-playstore:before,.ion-android-print:before,.ion-android-radio-button-off:before,.ion-android-radio-button-on:before,.ion-android-refresh:before,.ion-android-remove:before,.ion-android-remove-circle:before,.ion-android-restaurant:before,.ion-android-sad:before,.ion-android-search:before,.ion-android-send:before,.ion-android-settings:before,.ion-android-share:before,.ion-android-share-alt:before,.ion-android-star:before,.ion-android-star-half:before,.ion-android-star-outline:before,.ion-android-stopwatch:before,.ion-android-subway:before,.ion-android-sunny:before,.ion-android-sync:before,.ion-android-textsms:before,.ion-android-time:before,.ion-android-train:before,.ion-android-unlock:before,.ion-android-upload:before,.ion-android-volume-down:before,.ion-android-volume-mute:before,.ion-android-volume-off:before,.ion-android-volume-up:before,.ion-android-walk:before,.ion-android-warning:before,.ion-android-watch:before,.ion-android-wifi:before,.ion-aperture:before,.ion-archive:before,.ion-arrow-down-a:before,.ion-arrow-down-b:before,.ion-arrow-down-c:before,.ion-arrow-expand:before,.ion-arrow-graph-down-left:before,.ion-arrow-graph-down-right:before,.ion-arrow-graph-up-left:before,.ion-arrow-graph-up-right:before,.ion-arrow-left-a:before,.ion-arrow-left-b:before,.ion-arrow-left-c:before,.ion-arrow-move:before,.ion-arrow-resize:before,.ion-arrow-return-left:before,.ion-arrow-return-right:before,.ion-arrow-right-a:before,.ion-arrow-right-b:before,.ion-arrow-right-c:before,.ion-arrow-shrink:before,.ion-arrow-swap:before,.ion-arrow-up-a:before,.ion-arrow-up-b:before,.ion-arrow-up-c:before,.ion-asterisk:before,.ion-at:before,.ion-backspace:before,.ion-backspace-outline:before,.ion-bag:before,.ion-battery-charging:before,.ion-battery-empty:before,.ion-battery-full:before,.ion-battery-half:before,.ion-battery-low:before,.ion-beaker:before,.ion-beer:before,.ion-bluetooth:before,.ion-bonfire:before,.ion-bookmark:before,.ion-bowtie:before,.ion-briefcase:before,.ion-bug:before,.ion-calculator:before,.ion-calendar:before,.ion-camera:before,.ion-card:before,.ion-cash:before,.ion-chatbox:before,.ion-chatbox-working:before,.ion-chatboxes:before,.ion-chatbubble:before,.ion-chatbubble-working:before,.ion-chatbubbles:before,.ion-checkmark:before,.ion-checkmark-circled:before,.ion-checkmark-round:before,.ion-chevron-down:before,.ion-chevron-left:before,.ion-chevron-right:before,.ion-chevron-up:before,.ion-clipboard:before,.ion-clock:before,.ion-close:before,.ion-close-circled:before,.ion-close-round:before,.ion-closed-captioning:before,.ion-cloud:before,.ion-code:before,.ion-code-download:before,.ion-code-working:before,.ion-coffee:before,.ion-compass:before,.ion-compose:before,.ion-connection-bars:before,.ion-contrast:before,.ion-crop:before,.ion-cube:before,.ion-disc:before,.ion-document:before,.ion-document-text:before,.ion-drag:before,.ion-earth:before,.ion-easel:before,.ion-edit:before,.ion-egg:before,.ion-eject:before,.ion-email:before,.ion-email-unread:before,.ion-erlenmeyer-flask:before,.ion-erlenmeyer-flask-bubbles:before,.ion-eye:before,.ion-eye-disabled:before,.ion-female:before,.ion-filing:before,.ion-film-marker:before,.ion-fireball:before,.ion-flag:before,.ion-flame:before,.ion-flash:before,.ion-flash-off:before,.ion-folder:before,.ion-fork:before,.ion-fork-repo:before,.ion-forward:before,.ion-funnel:before,.ion-gear-a:before,.ion-gear-b:before,.ion-grid:before,.ion-hammer:before,.ion-happy:before,.ion-happy-outline:before,.ion-headphone:before,.ion-heart:before,.ion-heart-broken:before,.ion-help:before,.ion-help-buoy:before,.ion-help-circled:before,.ion-home:before,.ion-icecream:before,.ion-image:before,.ion-images:before,.ion-information:before,.ion-information-circled:before,.ion-ionic:before,.ion-ios-alarm:before,.ion-ios-alarm-outline:before,.ion-ios-albums:before,.ion-ios-albums-outline:before,.ion-ios-americanfootball:before,.ion-ios-americanfootball-outline:before,.ion-ios-analytics:before,.ion-ios-analytics-outline:before,.ion-ios-arrow-back:before,.ion-ios-arrow-down:before,.ion-ios-arrow-forward:before,.ion-ios-arrow-left:before,.ion-ios-arrow-right:before,.ion-ios-arrow-thin-down:before,.ion-ios-arrow-thin-left:before,.ion-ios-arrow-thin-right:before,.ion-ios-arrow-thin-up:before,.ion-ios-arrow-up:before,.ion-ios-at:before,.ion-ios-at-outline:before,.ion-ios-barcode:before,.ion-ios-barcode-outline:before,.ion-ios-baseball:before,.ion-ios-baseball-outline:before,.ion-ios-basketball:before,.ion-ios-basketball-outline:before,.ion-ios-bell:before,.ion-ios-bell-outline:before,.ion-ios-body:before,.ion-ios-body-outline:before,.ion-ios-bolt:before,.ion-ios-bolt-outline:before,.ion-ios-book:before,.ion-ios-book-outline:before,.ion-ios-bookmarks:before,.ion-ios-bookmarks-outline:before,.ion-ios-box:before,.ion-ios-box-outline:before,.ion-ios-briefcase:before,.ion-ios-briefcase-outline:before,.ion-ios-browsers:before,.ion-ios-browsers-outline:before,.ion-ios-calculator:before,.ion-ios-calculator-outline:before,.ion-ios-calendar:before,.ion-ios-calendar-outline:before,.ion-ios-camera:before,.ion-ios-camera-outline:before,.ion-ios-cart:before,.ion-ios-cart-outline:before,.ion-ios-chatboxes:before,.ion-ios-chatboxes-outline:before,.ion-ios-chatbubble:before,.ion-ios-chatbubble-outline:before,.ion-ios-checkmark:before,.ion-ios-checkmark-empty:before,.ion-ios-checkmark-outline:before,.ion-ios-circle-filled:before,.ion-ios-circle-outline:before,.ion-ios-clock:before,.ion-ios-clock-outline:before,.ion-ios-close:before,.ion-ios-close-empty:before,.ion-ios-close-outline:before,.ion-ios-cloud:before,.ion-ios-cloud-download:before,.ion-ios-cloud-download-outline:before,.ion-ios-cloud-outline:before,.ion-ios-cloud-upload:before,.ion-ios-cloud-upload-outline:before,.ion-ios-cloudy:before,.ion-ios-cloudy-night:before,.ion-ios-cloudy-night-outline:before,.ion-ios-cloudy-outline:before,.ion-ios-cog:before,.ion-ios-cog-outline:before,.ion-ios-color-filter:before,.ion-ios-color-filter-outline:before,.ion-ios-color-wand:before,.ion-ios-color-wand-outline:before,.ion-ios-compose:before,.ion-ios-compose-outline:before,.ion-ios-contact:before,.ion-ios-contact-outline:before,.ion-ios-copy:before,.ion-ios-copy-outline:before,.ion-ios-crop:before,.ion-ios-crop-strong:before,.ion-ios-download:before,.ion-ios-download-outline:before,.ion-ios-drag:before,.ion-ios-email:before,.ion-ios-email-outline:before,.ion-ios-eye:before,.ion-ios-eye-outline:before,.ion-ios-fastforward:before,.ion-ios-fastforward-outline:before,.ion-ios-filing:before,.ion-ios-filing-outline:before,.ion-ios-film:before,.ion-ios-film-outline:before,.ion-ios-flag:before,.ion-ios-flag-outline:before,.ion-ios-flame:before,.ion-ios-flame-outline:before,.ion-ios-flask:before,.ion-ios-flask-outline:before,.ion-ios-flower:before,.ion-ios-flower-outline:before,.ion-ios-folder:before,.ion-ios-folder-outline:before,.ion-ios-football:before,.ion-ios-football-outline:before,.ion-ios-game-controller-a:before,.ion-ios-game-controller-a-outline:before,.ion-ios-game-controller-b:before,.ion-ios-game-controller-b-outline:before,.ion-ios-gear:before,.ion-ios-gear-outline:before,.ion-ios-glasses:before,.ion-ios-glasses-outline:before,.ion-ios-grid-view:before,.ion-ios-grid-view-outline:before,.ion-ios-heart:before,.ion-ios-heart-outline:before,.ion-ios-help:before,.ion-ios-help-empty:before,.ion-ios-help-outline:before,.ion-ios-home:before,.ion-ios-home-outline:before,.ion-ios-infinite:before,.ion-ios-infinite-outline:before,.ion-ios-information:before,.ion-ios-information-empty:before,.ion-ios-information-outline:before,.ion-ios-ionic-outline:before,.ion-ios-keypad:before,.ion-ios-keypad-outline:before,.ion-ios-lightbulb:before,.ion-ios-lightbulb-outline:before,.ion-ios-list:before,.ion-ios-list-outline:before,.ion-ios-location:before,.ion-ios-location-outline:before,.ion-ios-locked:before,.ion-ios-locked-outline:before,.ion-ios-loop:before,.ion-ios-loop-strong:before,.ion-ios-medical:before,.ion-ios-medical-outline:before,.ion-ios-medkit:before,.ion-ios-medkit-outline:before,.ion-ios-mic:before,.ion-ios-mic-off:before,.ion-ios-mic-outline:before,.ion-ios-minus:before,.ion-ios-minus-empty:before,.ion-ios-minus-outline:before,.ion-ios-monitor:before,.ion-ios-monitor-outline:before,.ion-ios-moon:before,.ion-ios-moon-outline:before,.ion-ios-more:before,.ion-ios-more-outline:before,.ion-ios-musical-note:before,.ion-ios-musical-notes:before,.ion-ios-navigate:before,.ion-ios-navigate-outline:before,.ion-ios-nutrition:before,.ion-ios-nutrition-outline:before,.ion-ios-paper:before,.ion-ios-paper-outline:before,.ion-ios-paperplane:before,.ion-ios-paperplane-outline:before,.ion-ios-partlysunny:before,.ion-ios-partlysunny-outline:before,.ion-ios-pause:before,.ion-ios-pause-outline:before,.ion-ios-paw:before,.ion-ios-paw-outline:before,.ion-ios-people:before,.ion-ios-people-outline:before,.ion-ios-person:before,.ion-ios-person-outline:before,.ion-ios-personadd:before,.ion-ios-personadd-outline:before,.ion-ios-photos:before,.ion-ios-photos-outline:before,.ion-ios-pie:before,.ion-ios-pie-outline:before,.ion-ios-pint:before,.ion-ios-pint-outline:before,.ion-ios-play:before,.ion-ios-play-outline:before,.ion-ios-plus:before,.ion-ios-plus-empty:before,.ion-ios-plus-outline:before,.ion-ios-pricetag:before,.ion-ios-pricetag-outline:before,.ion-ios-pricetags:before,.ion-ios-pricetags-outline:before,.ion-ios-printer:before,.ion-ios-printer-outline:before,.ion-ios-pulse:before,.ion-ios-pulse-strong:before,.ion-ios-rainy:before,.ion-ios-rainy-outline:before,.ion-ios-recording:before,.ion-ios-recording-outline:before,.ion-ios-redo:before,.ion-ios-redo-outline:before,.ion-ios-refresh:before,.ion-ios-refresh-empty:before,.ion-ios-refresh-outline:before,.ion-ios-reload:before,.ion-ios-reverse-camera:before,.ion-ios-reverse-camera-outline:before,.ion-ios-rewind:before,.ion-ios-rewind-outline:before,.ion-ios-rose:before,.ion-ios-rose-outline:before,.ion-ios-search:before,.ion-ios-search-strong:before,.ion-ios-settings:before,.ion-ios-settings-strong:before,.ion-ios-shuffle:before,.ion-ios-shuffle-strong:before,.ion-ios-skipbackward:before,.ion-ios-skipbackward-outline:before,.ion-ios-skipforward:before,.ion-ios-skipforward-outline:before,.ion-ios-snowy:before,.ion-ios-speedometer:before,.ion-ios-speedometer-outline:before,.ion-ios-star:before,.ion-ios-star-half:before,.ion-ios-star-outline:before,.ion-ios-stopwatch:before,.ion-ios-stopwatch-outline:before,.ion-ios-sunny:before,.ion-ios-sunny-outline:before,.ion-ios-telephone:before,.ion-ios-telephone-outline:before,.ion-ios-tennisball:before,.ion-ios-tennisball-outline:before,.ion-ios-thunderstorm:before,.ion-ios-thunderstorm-outline:before,.ion-ios-time:before,.ion-ios-time-outline:before,.ion-ios-timer:before,.ion-ios-timer-outline:before,.ion-ios-toggle:before,.ion-ios-toggle-outline:before,.ion-ios-trash:before,.ion-ios-trash-outline:before,.ion-ios-undo:before,.ion-ios-undo-outline:before,.ion-ios-unlocked:before,.ion-ios-unlocked-outline:before,.ion-ios-upload:before,.ion-ios-upload-outline:before,.ion-ios-videocam:before,.ion-ios-videocam-outline:before,.ion-ios-volume-high:before,.ion-ios-volume-low:before,.ion-ios-wineglass:before,.ion-ios-wineglass-outline:before,.ion-ios-world:before,.ion-ios-world-outline:before,.ion-ipad:before,.ion-iphone:before,.ion-ipod:before,.ion-jet:before,.ion-key:before,.ion-knife:before,.ion-laptop:before,.ion-leaf:before,.ion-levels:before,.ion-lightbulb:before,.ion-link:before,.ion-load-a:before,.ion-load-b:before,.ion-load-c:before,.ion-load-d:before,.ion-location:before,.ion-lock-combination:before,.ion-locked:before,.ion-log-in:before,.ion-log-out:before,.ion-loop:before,.ion-magnet:before,.ion-male:before,.ion-man:before,.ion-map:before,.ion-medkit:before,.ion-merge:before,.ion-mic-a:before,.ion-mic-b:before,.ion-mic-c:before,.ion-minus:before,.ion-minus-circled:before,.ion-minus-round:before,.ion-model-s:before,.ion-monitor:before,.ion-more:before,.ion-mouse:before,.ion-music-note:before,.ion-navicon:before,.ion-navicon-round:before,.ion-navigate:before,.ion-network:before,.ion-no-smoking:before,.ion-nuclear:before,.ion-outlet:before,.ion-paintbrush:before,.ion-paintbucket:before,.ion-paper-airplane:before,.ion-paperclip:before,.ion-pause:before,.ion-person:before,.ion-person-add:before,.ion-person-stalker:before,.ion-pie-graph:before,.ion-pin:before,.ion-pinpoint:before,.ion-pizza:before,.ion-plane:before,.ion-planet:before,.ion-play:before,.ion-playstation:before,.ion-plus:before,.ion-plus-circled:before,.ion-plus-round:before,.ion-podium:before,.ion-pound:before,.ion-power:before,.ion-pricetag:before,.ion-pricetags:before,.ion-printer:before,.ion-pull-request:before,.ion-qr-scanner:before,.ion-quote:before,.ion-radio-waves:before,.ion-record:before,.ion-refresh:before,.ion-reply:before,.ion-reply-all:before,.ion-ribbon-a:before,.ion-ribbon-b:before,.ion-sad:before,.ion-sad-outline:before,.ion-scissors:before,.ion-search:before,.ion-settings:before,.ion-share:before,.ion-shuffle:before,.ion-skip-backward:before,.ion-skip-forward:before,.ion-social-android:before,.ion-social-android-outline:before,.ion-social-angular:before,.ion-social-angular-outline:before,.ion-social-apple:before,.ion-social-apple-outline:before,.ion-social-bitcoin:before,.ion-social-bitcoin-outline:before,.ion-social-buffer:before,.ion-social-buffer-outline:before,.ion-social-chrome:before,.ion-social-chrome-outline:before,.ion-social-codepen:before,.ion-social-codepen-outline:before,.ion-social-css3:before,.ion-social-css3-outline:before,.ion-social-designernews:before,.ion-social-designernews-outline:before,.ion-social-dribbble:before,.ion-social-dribbble-outline:before,.ion-social-dropbox:before,.ion-social-dropbox-outline:before,.ion-social-euro:before,.ion-social-euro-outline:before,.ion-social-facebook:before,.ion-social-facebook-outline:before,.ion-social-foursquare:before,.ion-social-foursquare-outline:before,.ion-social-freebsd-devil:before,.ion-social-github:before,.ion-social-github-outline:before,.ion-social-google:before,.ion-social-google-outline:before,.ion-social-googleplus:before,.ion-social-googleplus-outline:before,.ion-social-hackernews:before,.ion-social-hackernews-outline:before,.ion-social-html5:before,.ion-social-html5-outline:before,.ion-social-instagram:before,.ion-social-instagram-outline:before,.ion-social-javascript:before,.ion-social-javascript-outline:before,.ion-social-linkedin:before,.ion-social-linkedin-outline:before,.ion-social-markdown:before,.ion-social-nodejs:before,.ion-social-octocat:before,.ion-social-pinterest:before,.ion-social-pinterest-outline:before,.ion-social-python:before,.ion-social-reddit:before,.ion-social-reddit-outline:before,.ion-social-rss:before,.ion-social-rss-outline:before,.ion-social-sass:before,.ion-social-skype:before,.ion-social-skype-outline:before,.ion-social-snapchat:before,.ion-social-snapchat-outline:before,.ion-social-tumblr:before,.ion-social-tumblr-outline:before,.ion-social-tux:before,.ion-social-twitch:before,.ion-social-twitch-outline:before,.ion-social-twitter:before,.ion-social-twitter-outline:before,.ion-social-usd:before,.ion-social-usd-outline:before,.ion-social-vimeo:before,.ion-social-vimeo-outline:before,.ion-social-whatsapp:before,.ion-social-whatsapp-outline:before,.ion-social-windows:before,.ion-social-windows-outline:before,.ion-social-wordpress:before,.ion-social-wordpress-outline:before,.ion-social-yahoo:before,.ion-social-yahoo-outline:before,.ion-social-yen:before,.ion-social-yen-outline:before,.ion-social-youtube:before,.ion-social-youtube-outline:before,.ion-soup-can:before,.ion-soup-can-outline:before,.ion-speakerphone:before,.ion-speedometer:before,.ion-spoon:before,.ion-star:before,.ion-stats-bars:before,.ion-steam:before,.ion-stop:before,.ion-thermometer:before,.ion-thumbsdown:before,.ion-thumbsup:before,.ion-toggle:before,.ion-toggle-filled:before,.ion-transgender:before,.ion-trash-a:before,.ion-trash-b:before,.ion-trophy:before,.ion-tshirt:before,.ion-tshirt-outline:before,.ion-umbrella:before,.ion-university:before,.ion-unlocked:before,.ion-upload:before,.ion-usb:before,.ion-videocamera:before,.ion-volume-high:before,.ion-volume-low:before,.ion-volume-medium:before,.ion-volume-mute:before,.ion-wand:before,.ion-waterdrop:before,.ion-wifi:before,.ion-wineglass:before,.ion-woman:before,.ion-wrench:before,.ion-xbox:before{display:inline-block;font-family:"Ionicons";speak:none;font-style:normal;font-weight:normal;font-variant:normal;text-transform:none;text-rendering:auto;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.ion-alert:before{content:"\f101"}.ion-alert-circled:before{content:"\f100"}.ion-android-add:before{content:"\f2c7"}.ion-android-add-circle:before{content:"\f359"}.ion-android-alarm-clock:before{content:"\f35a"}.ion-android-alert:before{content:"\f35b"}.ion-android-apps:before{content:"\f35c"}.ion-android-archive:before{content:"\f2c9"}.ion-android-arrow-back:before{content:"\f2ca"}.ion-android-arrow-down:before{content:"\f35d"}.ion-android-arrow-dropdown:before{content:"\f35f"}.ion-android-arrow-dropdown-circle:before{content:"\f35e"}.ion-android-arrow-dropleft:before{content:"\f361"}.ion-android-arrow-dropleft-circle:before{content:"\f360"}.ion-android-arrow-dropright:before{content:"\f363"}.ion-android-arrow-dropright-circle:before{content:"\f362"}.ion-android-arrow-dropup:before{content:"\f365"}.ion-android-arrow-dropup-circle:before{content:"\f364"}.ion-android-arrow-forward:before{content:"\f30f"}.ion-android-arrow-up:before{content:"\f366"}.ion-android-attach:before{content:"\f367"}.ion-android-bar:before{content:"\f368"}.ion-android-bicycle:before{content:"\f369"}.ion-android-boat:before{content:"\f36a"}.ion-android-bookmark:before{content:"\f36b"}.ion-android-bulb:before{content:"\f36c"}.ion-android-bus:before{content:"\f36d"}.ion-android-calendar:before{content:"\f2d1"}.ion-android-call:before{content:"\f2d2"}.ion-android-camera:before{content:"\f2d3"}.ion-android-cancel:before{content:"\f36e"}.ion-android-car:before{content:"\f36f"}.ion-android-cart:before{content:"\f370"}.ion-android-chat:before{content:"\f2d4"}.ion-android-checkbox:before{content:"\f374"}.ion-android-checkbox-blank:before{content:"\f371"}.ion-android-checkbox-outline:before{content:"\f373"}.ion-android-checkbox-outline-blank:before{content:"\f372"}.ion-android-checkmark-circle:before{content:"\f375"}.ion-android-clipboard:before{content:"\f376"}.ion-android-close:before{content:"\f2d7"}.ion-android-cloud:before{content:"\f37a"}.ion-android-cloud-circle:before{content:"\f377"}.ion-android-cloud-done:before{content:"\f378"}.ion-android-cloud-outline:before{content:"\f379"}.ion-android-color-palette:before{content:"\f37b"}.ion-android-compass:before{content:"\f37c"}.ion-android-contact:before{content:"\f2d8"}.ion-android-contacts:before{content:"\f2d9"}.ion-android-contract:before{content:"\f37d"}.ion-android-create:before{content:"\f37e"}.ion-android-delete:before{content:"\f37f"}.ion-android-desktop:before{content:"\f380"}.ion-android-document:before{content:"\f381"}.ion-android-done:before{content:"\f383"}.ion-android-done-all:before{content:"\f382"}.ion-android-download:before{content:"\f2dd"}.ion-android-drafts:before{content:"\f384"}.ion-android-exit:before{content:"\f385"}.ion-android-expand:before{content:"\f386"}.ion-android-favorite:before{content:"\f388"}.ion-android-favorite-outline:before{content:"\f387"}.ion-android-film:before{content:"\f389"}.ion-android-folder:before{content:"\f2e0"}.ion-android-folder-open:before{content:"\f38a"}.ion-android-funnel:before{content:"\f38b"}.ion-android-globe:before{content:"\f38c"}.ion-android-hand:before{content:"\f2e3"}.ion-android-hangout:before{content:"\f38d"}.ion-android-happy:before{content:"\f38e"}.ion-android-home:before{content:"\f38f"}.ion-android-image:before{content:"\f2e4"}.ion-android-laptop:before{content:"\f390"}.ion-android-list:before{content:"\f391"}.ion-android-locate:before{content:"\f2e9"}.ion-android-lock:before{content:"\f392"}.ion-android-mail:before{content:"\f2eb"}.ion-android-map:before{content:"\f393"}.ion-android-menu:before{content:"\f394"}.ion-android-microphone:before{content:"\f2ec"}.ion-android-microphone-off:before{content:"\f395"}.ion-android-more-horizontal:before{content:"\f396"}.ion-android-more-vertical:before{content:"\f397"}.ion-android-navigate:before{content:"\f398"}.ion-android-notifications:before{content:"\f39b"}.ion-android-notifications-none:before{content:"\f399"}.ion-android-notifications-off:before{content:"\f39a"}.ion-android-open:before{content:"\f39c"}.ion-android-options:before{content:"\f39d"}.ion-android-people:before{content:"\f39e"}.ion-android-person:before{content:"\f3a0"}.ion-android-person-add:before{content:"\f39f"}.ion-android-phone-landscape:before{content:"\f3a1"}.ion-android-phone-portrait:before{content:"\f3a2"}.ion-android-pin:before{content:"\f3a3"}.ion-android-plane:before{content:"\f3a4"}.ion-android-playstore:before{content:"\f2f0"}.ion-android-print:before{content:"\f3a5"}.ion-android-radio-button-off:before{content:"\f3a6"}.ion-android-radio-button-on:before{content:"\f3a7"}.ion-android-refresh:before{content:"\f3a8"}.ion-android-remove:before{content:"\f2f4"}.ion-android-remove-circle:before{content:"\f3a9"}.ion-android-restaurant:before{content:"\f3aa"}.ion-android-sad:before{content:"\f3ab"}.ion-android-search:before{content:"\f2f5"}.ion-android-send:before{content:"\f2f6"}.ion-android-settings:before{content:"\f2f7"}.ion-android-share:before{content:"\f2f8"}.ion-android-share-alt:before{content:"\f3ac"}.ion-android-star:before{content:"\f2fc"}.ion-android-star-half:before{content:"\f3ad"}.ion-android-star-outline:before{content:"\f3ae"}.ion-android-stopwatch:before{content:"\f2fd"}.ion-android-subway:before{content:"\f3af"}.ion-android-sunny:before{content:"\f3b0"}.ion-android-sync:before{content:"\f3b1"}.ion-android-textsms:before{content:"\f3b2"}.ion-android-time:before{content:"\f3b3"}.ion-android-train:before{content:"\f3b4"}.ion-android-unlock:before{content:"\f3b5"}.ion-android-upload:before{content:"\f3b6"}.ion-android-volume-down:before{content:"\f3b7"}.ion-android-volume-mute:before{content:"\f3b8"}.ion-android-volume-off:before{content:"\f3b9"}.ion-android-volume-up:before{content:"\f3ba"}.ion-android-walk:before{content:"\f3bb"}.ion-android-warning:before{content:"\f3bc"}.ion-android-watch:before{content:"\f3bd"}.ion-android-wifi:before{content:"\f305"}.ion-aperture:before{content:"\f313"}.ion-archive:before{content:"\f102"}.ion-arrow-down-a:before{content:"\f103"}.ion-arrow-down-b:before{content:"\f104"}.ion-arrow-down-c:before{content:"\f105"}.ion-arrow-expand:before{content:"\f25e"}.ion-arrow-graph-down-left:before{content:"\f25f"}.ion-arrow-graph-down-right:before{content:"\f260"}.ion-arrow-graph-up-left:before{content:"\f261"}.ion-arrow-graph-up-right:before{content:"\f262"}.ion-arrow-left-a:before{content:"\f106"}.ion-arrow-left-b:before{content:"\f107"}.ion-arrow-left-c:before{content:"\f108"}.ion-arrow-move:before{content:"\f263"}.ion-arrow-resize:before{content:"\f264"}.ion-arrow-return-left:before{content:"\f265"}.ion-arrow-return-right:before{content:"\f266"}.ion-arrow-right-a:before{content:"\f109"}.ion-arrow-right-b:before{content:"\f10a"}.ion-arrow-right-c:before{content:"\f10b"}.ion-arrow-shrink:before{content:"\f267"}.ion-arrow-swap:before{content:"\f268"}.ion-arrow-up-a:before{content:"\f10c"}.ion-arrow-up-b:before{content:"\f10d"}.ion-arrow-up-c:before{content:"\f10e"}.ion-asterisk:before{content:"\f314"}.ion-at:before{content:"\f10f"}.ion-backspace:before{content:"\f3bf"}.ion-backspace-outline:before{content:"\f3be"}.ion-bag:before{content:"\f110"}.ion-battery-charging:before{content:"\f111"}.ion-battery-empty:before{content:"\f112"}.ion-battery-full:before{content:"\f113"}.ion-battery-half:before{content:"\f114"}.ion-battery-low:before{content:"\f115"}.ion-beaker:before{content:"\f269"}.ion-beer:before{content:"\f26a"}.ion-bluetooth:before{content:"\f116"}.ion-bonfire:before{content:"\f315"}.ion-bookmark:before{content:"\f26b"}.ion-bowtie:before{content:"\f3c0"}.ion-briefcase:before{content:"\f26c"}.ion-bug:before{content:"\f2be"}.ion-calculator:before{content:"\f26d"}.ion-calendar:before{content:"\f117"}.ion-camera:before{content:"\f118"}.ion-card:before{content:"\f119"}.ion-cash:before{content:"\f316"}.ion-chatbox:before{content:"\f11b"}.ion-chatbox-working:before{content:"\f11a"}.ion-chatboxes:before{content:"\f11c"}.ion-chatbubble:before{content:"\f11e"}.ion-chatbubble-working:before{content:"\f11d"}.ion-chatbubbles:before{content:"\f11f"}.ion-checkmark:before{content:"\f122"}.ion-checkmark-circled:before{content:"\f120"}.ion-checkmark-round:before{content:"\f121"}.ion-chevron-down:before{content:"\f123"}.ion-chevron-left:before{content:"\f124"}.ion-chevron-right:before{content:"\f125"}.ion-chevron-up:before{content:"\f126"}.ion-clipboard:before{content:"\f127"}.ion-clock:before{content:"\f26e"}.ion-close:before{content:"\f12a"}.ion-close-circled:before{content:"\f128"}.ion-close-round:before{content:"\f129"}.ion-closed-captioning:before{content:"\f317"}.ion-cloud:before{content:"\f12b"}.ion-code:before{content:"\f271"}.ion-code-download:before{content:"\f26f"}.ion-code-working:before{content:"\f270"}.ion-coffee:before{content:"\f272"}.ion-compass:before{content:"\f273"}.ion-compose:before{content:"\f12c"}.ion-connection-bars:before{content:"\f274"}.ion-contrast:before{content:"\f275"}.ion-crop:before{content:"\f3c1"}.ion-cube:before{content:"\f318"}.ion-disc:before{content:"\f12d"}.ion-document:before{content:"\f12f"}.ion-document-text:before{content:"\f12e"}.ion-drag:before{content:"\f130"}.ion-earth:before{content:"\f276"}.ion-easel:before{content:"\f3c2"}.ion-edit:before{content:"\f2bf"}.ion-egg:before{content:"\f277"}.ion-eject:before{content:"\f131"}.ion-email:before{content:"\f132"}.ion-email-unread:before{content:"\f3c3"}.ion-erlenmeyer-flask:before{content:"\f3c5"}.ion-erlenmeyer-flask-bubbles:before{content:"\f3c4"}.ion-eye:before{content:"\f133"}.ion-eye-disabled:before{content:"\f306"}.ion-female:before{content:"\f278"}.ion-filing:before{content:"\f134"}.ion-film-marker:before{content:"\f135"}.ion-fireball:before{content:"\f319"}.ion-flag:before{content:"\f279"}.ion-flame:before{content:"\f31a"}.ion-flash:before{content:"\f137"}.ion-flash-off:before{content:"\f136"}.ion-folder:before{content:"\f139"}.ion-fork:before{content:"\f27a"}.ion-fork-repo:before{content:"\f2c0"}.ion-forward:before{content:"\f13a"}.ion-funnel:before{content:"\f31b"}.ion-gear-a:before{content:"\f13d"}.ion-gear-b:before{content:"\f13e"}.ion-grid:before{content:"\f13f"}.ion-hammer:before{content:"\f27b"}.ion-happy:before{content:"\f31c"}.ion-happy-outline:before{content:"\f3c6"}.ion-headphone:before{content:"\f140"}.ion-heart:before{content:"\f141"}.ion-heart-broken:before{content:"\f31d"}.ion-help:before{content:"\f143"}.ion-help-buoy:before{content:"\f27c"}.ion-help-circled:before{content:"\f142"}.ion-home:before{content:"\f144"}.ion-icecream:before{content:"\f27d"}.ion-image:before{content:"\f147"}.ion-images:before{content:"\f148"}.ion-information:before{content:"\f14a"}.ion-information-circled:before{content:"\f149"}.ion-ionic:before{content:"\f14b"}.ion-ios-alarm:before{content:"\f3c8"}.ion-ios-alarm-outline:before{content:"\f3c7"}.ion-ios-albums:before{content:"\f3ca"}.ion-ios-albums-outline:before{content:"\f3c9"}.ion-ios-americanfootball:before{content:"\f3cc"}.ion-ios-americanfootball-outline:before{content:"\f3cb"}.ion-ios-analytics:before{content:"\f3ce"}.ion-ios-analytics-outline:before{content:"\f3cd"}.ion-ios-arrow-back:before{content:"\f3cf"}.ion-ios-arrow-down:before{content:"\f3d0"}.ion-ios-arrow-forward:before{content:"\f3d1"}.ion-ios-arrow-left:before{content:"\f3d2"}.ion-ios-arrow-right:before{content:"\f3d3"}.ion-ios-arrow-thin-down:before{content:"\f3d4"}.ion-ios-arrow-thin-left:before{content:"\f3d5"}.ion-ios-arrow-thin-right:before{content:"\f3d6"}.ion-ios-arrow-thin-up:before{content:"\f3d7"}.ion-ios-arrow-up:before{content:"\f3d8"}.ion-ios-at:before{content:"\f3da"}.ion-ios-at-outline:before{content:"\f3d9"}.ion-ios-barcode:before{content:"\f3dc"}.ion-ios-barcode-outline:before{content:"\f3db"}.ion-ios-baseball:before{content:"\f3de"}.ion-ios-baseball-outline:before{content:"\f3dd"}.ion-ios-basketball:before{content:"\f3e0"}.ion-ios-basketball-outline:before{content:"\f3df"}.ion-ios-bell:before{content:"\f3e2"}.ion-ios-bell-outline:before{content:"\f3e1"}.ion-ios-body:before{content:"\f3e4"}.ion-ios-body-outline:before{content:"\f3e3"}.ion-ios-bolt:before{content:"\f3e6"}.ion-ios-bolt-outline:before{content:"\f3e5"}.ion-ios-book:before{content:"\f3e8"}.ion-ios-book-outline:before{content:"\f3e7"}.ion-ios-bookmarks:before{content:"\f3ea"}.ion-ios-bookmarks-outline:before{content:"\f3e9"}.ion-ios-box:before{content:"\f3ec"}.ion-ios-box-outline:before{content:"\f3eb"}.ion-ios-briefcase:before{content:"\f3ee"}.ion-ios-briefcase-outline:before{content:"\f3ed"}.ion-ios-browsers:before{content:"\f3f0"}.ion-ios-browsers-outline:before{content:"\f3ef"}.ion-ios-calculator:before{content:"\f3f2"}.ion-ios-calculator-outline:before{content:"\f3f1"}.ion-ios-calendar:before{content:"\f3f4"}.ion-ios-calendar-outline:before{content:"\f3f3"}.ion-ios-camera:before{content:"\f3f6"}.ion-ios-camera-outline:before{content:"\f3f5"}.ion-ios-cart:before{content:"\f3f8"}.ion-ios-cart-outline:before{content:"\f3f7"}.ion-ios-chatboxes:before{content:"\f3fa"}.ion-ios-chatboxes-outline:before{content:"\f3f9"}.ion-ios-chatbubble:before{content:"\f3fc"}.ion-ios-chatbubble-outline:before{content:"\f3fb"}.ion-ios-checkmark:before{content:"\f3ff"}.ion-ios-checkmark-empty:before{content:"\f3fd"}.ion-ios-checkmark-outline:before{content:"\f3fe"}.ion-ios-circle-filled:before{content:"\f400"}.ion-ios-circle-outline:before{content:"\f401"}.ion-ios-clock:before{content:"\f403"}.ion-ios-clock-outline:before{content:"\f402"}.ion-ios-close:before{content:"\f406"}.ion-ios-close-empty:before{content:"\f404"}.ion-ios-close-outline:before{content:"\f405"}.ion-ios-cloud:before{content:"\f40c"}.ion-ios-cloud-download:before{content:"\f408"}.ion-ios-cloud-download-outline:before{content:"\f407"}.ion-ios-cloud-outline:before{content:"\f409"}.ion-ios-cloud-upload:before{content:"\f40b"}.ion-ios-cloud-upload-outline:before{content:"\f40a"}.ion-ios-cloudy:before{content:"\f410"}.ion-ios-cloudy-night:before{content:"\f40e"}.ion-ios-cloudy-night-outline:before{content:"\f40d"}.ion-ios-cloudy-outline:before{content:"\f40f"}.ion-ios-cog:before{content:"\f412"}.ion-ios-cog-outline:before{content:"\f411"}.ion-ios-color-filter:before{content:"\f414"}.ion-ios-color-filter-outline:before{content:"\f413"}.ion-ios-color-wand:before{content:"\f416"}.ion-ios-color-wand-outline:before{content:"\f415"}.ion-ios-compose:before{content:"\f418"}.ion-ios-compose-outline:before{content:"\f417"}.ion-ios-contact:before{content:"\f41a"}.ion-ios-contact-outline:before{content:"\f419"}.ion-ios-copy:before{content:"\f41c"}.ion-ios-copy-outline:before{content:"\f41b"}.ion-ios-crop:before{content:"\f41e"}.ion-ios-crop-strong:before{content:"\f41d"}.ion-ios-download:before{content:"\f420"}.ion-ios-download-outline:before{content:"\f41f"}.ion-ios-drag:before{content:"\f421"}.ion-ios-email:before{content:"\f423"}.ion-ios-email-outline:before{content:"\f422"}.ion-ios-eye:before{content:"\f425"}.ion-ios-eye-outline:before{content:"\f424"}.ion-ios-fastforward:before{content:"\f427"}.ion-ios-fastforward-outline:before{content:"\f426"}.ion-ios-filing:before{content:"\f429"}.ion-ios-filing-outline:before{content:"\f428"}.ion-ios-film:before{content:"\f42b"}.ion-ios-film-outline:before{content:"\f42a"}.ion-ios-flag:before{content:"\f42d"}.ion-ios-flag-outline:before{content:"\f42c"}.ion-ios-flame:before{content:"\f42f"}.ion-ios-flame-outline:before{content:"\f42e"}.ion-ios-flask:before{content:"\f431"}.ion-ios-flask-outline:before{content:"\f430"}.ion-ios-flower:before{content:"\f433"}.ion-ios-flower-outline:before{content:"\f432"}.ion-ios-folder:before{content:"\f435"}.ion-ios-folder-outline:before{content:"\f434"}.ion-ios-football:before{content:"\f437"}.ion-ios-football-outline:before{content:"\f436"}.ion-ios-game-controller-a:before{content:"\f439"}.ion-ios-game-controller-a-outline:before{content:"\f438"}.ion-ios-game-controller-b:before{content:"\f43b"}.ion-ios-game-controller-b-outline:before{content:"\f43a"}.ion-ios-gear:before{content:"\f43d"}.ion-ios-gear-outline:before{content:"\f43c"}.ion-ios-glasses:before{content:"\f43f"}.ion-ios-glasses-outline:before{content:"\f43e"}.ion-ios-grid-view:before{content:"\f441"}.ion-ios-grid-view-outline:before{content:"\f440"}.ion-ios-heart:before{content:"\f443"}.ion-ios-heart-outline:before{content:"\f442"}.ion-ios-help:before{content:"\f446"}.ion-ios-help-empty:before{content:"\f444"}.ion-ios-help-outline:before{content:"\f445"}.ion-ios-home:before{content:"\f448"}.ion-ios-home-outline:before{content:"\f447"}.ion-ios-infinite:before{content:"\f44a"}.ion-ios-infinite-outline:before{content:"\f449"}.ion-ios-information:before{content:"\f44d"}.ion-ios-information-empty:before{content:"\f44b"}.ion-ios-information-outline:before{content:"\f44c"}.ion-ios-ionic-outline:before{content:"\f44e"}.ion-ios-keypad:before{content:"\f450"}.ion-ios-keypad-outline:before{content:"\f44f"}.ion-ios-lightbulb:before{content:"\f452"}.ion-ios-lightbulb-outline:before{content:"\f451"}.ion-ios-list:before{content:"\f454"}.ion-ios-list-outline:before{content:"\f453"}.ion-ios-location:before{content:"\f456"}.ion-ios-location-outline:before{content:"\f455"}.ion-ios-locked:before{content:"\f458"}.ion-ios-locked-outline:before{content:"\f457"}.ion-ios-loop:before{content:"\f45a"}.ion-ios-loop-strong:before{content:"\f459"}.ion-ios-medical:before{content:"\f45c"}.ion-ios-medical-outline:before{content:"\f45b"}.ion-ios-medkit:before{content:"\f45e"}.ion-ios-medkit-outline:before{content:"\f45d"}.ion-ios-mic:before{content:"\f461"}.ion-ios-mic-off:before{content:"\f45f"}.ion-ios-mic-outline:before{content:"\f460"}.ion-ios-minus:before{content:"\f464"}.ion-ios-minus-empty:before{content:"\f462"}.ion-ios-minus-outline:before{content:"\f463"}.ion-ios-monitor:before{content:"\f466"}.ion-ios-monitor-outline:before{content:"\f465"}.ion-ios-moon:before{content:"\f468"}.ion-ios-moon-outline:before{content:"\f467"}.ion-ios-more:before{content:"\f46a"}.ion-ios-more-outline:before{content:"\f469"}.ion-ios-musical-note:before{content:"\f46b"}.ion-ios-musical-notes:before{content:"\f46c"}.ion-ios-navigate:before{content:"\f46e"}.ion-ios-navigate-outline:before{content:"\f46d"}.ion-ios-nutrition:before{content:"\f470"}.ion-ios-nutrition-outline:before{content:"\f46f"}.ion-ios-paper:before{content:"\f472"}.ion-ios-paper-outline:before{content:"\f471"}.ion-ios-paperplane:before{content:"\f474"}.ion-ios-paperplane-outline:before{content:"\f473"}.ion-ios-partlysunny:before{content:"\f476"}.ion-ios-partlysunny-outline:before{content:"\f475"}.ion-ios-pause:before{content:"\f478"}.ion-ios-pause-outline:before{content:"\f477"}.ion-ios-paw:before{content:"\f47a"}.ion-ios-paw-outline:before{content:"\f479"}.ion-ios-people:before{content:"\f47c"}.ion-ios-people-outline:before{content:"\f47b"}.ion-ios-person:before{content:"\f47e"}.ion-ios-person-outline:before{content:"\f47d"}.ion-ios-personadd:before{content:"\f480"}.ion-ios-personadd-outline:before{content:"\f47f"}.ion-ios-photos:before{content:"\f482"}.ion-ios-photos-outline:before{content:"\f481"}.ion-ios-pie:before{content:"\f484"}.ion-ios-pie-outline:before{content:"\f483"}.ion-ios-pint:before{content:"\f486"}.ion-ios-pint-outline:before{content:"\f485"}.ion-ios-play:before{content:"\f488"}.ion-ios-play-outline:before{content:"\f487"}.ion-ios-plus:before{content:"\f48b"}.ion-ios-plus-empty:before{content:"\f489"}.ion-ios-plus-outline:before{content:"\f48a"}.ion-ios-pricetag:before{content:"\f48d"}.ion-ios-pricetag-outline:before{content:"\f48c"}.ion-ios-pricetags:before{content:"\f48f"}.ion-ios-pricetags-outline:before{content:"\f48e"}.ion-ios-printer:before{content:"\f491"}.ion-ios-printer-outline:before{content:"\f490"}.ion-ios-pulse:before{content:"\f493"}.ion-ios-pulse-strong:before{content:"\f492"}.ion-ios-rainy:before{content:"\f495"}.ion-ios-rainy-outline:before{content:"\f494"}.ion-ios-recording:before{content:"\f497"}.ion-ios-recording-outline:before{content:"\f496"}.ion-ios-redo:before{content:"\f499"}.ion-ios-redo-outline:before{content:"\f498"}.ion-ios-refresh:before{content:"\f49c"}.ion-ios-refresh-empty:before{content:"\f49a"}.ion-ios-refresh-outline:before{content:"\f49b"}.ion-ios-reload:before{content:"\f49d"}.ion-ios-reverse-camera:before{content:"\f49f"}.ion-ios-reverse-camera-outline:before{content:"\f49e"}.ion-ios-rewind:before{content:"\f4a1"}.ion-ios-rewind-outline:before{content:"\f4a0"}.ion-ios-rose:before{content:"\f4a3"}.ion-ios-rose-outline:before{content:"\f4a2"}.ion-ios-search:before{content:"\f4a5"}.ion-ios-search-strong:before{content:"\f4a4"}.ion-ios-settings:before{content:"\f4a7"}.ion-ios-settings-strong:before{content:"\f4a6"}.ion-ios-shuffle:before{content:"\f4a9"}.ion-ios-shuffle-strong:before{content:"\f4a8"}.ion-ios-skipbackward:before{content:"\f4ab"}.ion-ios-skipbackward-outline:before{content:"\f4aa"}.ion-ios-skipforward:before{content:"\f4ad"}.ion-ios-skipforward-outline:before{content:"\f4ac"}.ion-ios-snowy:before{content:"\f4ae"}.ion-ios-speedometer:before{content:"\f4b0"}.ion-ios-speedometer-outline:before{content:"\f4af"}.ion-ios-star:before{content:"\f4b3"}.ion-ios-star-half:before{content:"\f4b1"}.ion-ios-star-outline:before{content:"\f4b2"}.ion-ios-stopwatch:before{content:"\f4b5"}.ion-ios-stopwatch-outline:before{content:"\f4b4"}.ion-ios-sunny:before{content:"\f4b7"}.ion-ios-sunny-outline:before{content:"\f4b6"}.ion-ios-telephone:before{content:"\f4b9"}.ion-ios-telephone-outline:before{content:"\f4b8"}.ion-ios-tennisball:before{content:"\f4bb"}.ion-ios-tennisball-outline:before{content:"\f4ba"}.ion-ios-thunderstorm:before{content:"\f4bd"}.ion-ios-thunderstorm-outline:before{content:"\f4bc"}.ion-ios-time:before{content:"\f4bf"}.ion-ios-time-outline:before{content:"\f4be"}.ion-ios-timer:before{content:"\f4c1"}.ion-ios-timer-outline:before{content:"\f4c0"}.ion-ios-toggle:before{content:"\f4c3"}.ion-ios-toggle-outline:before{content:"\f4c2"}.ion-ios-trash:before{content:"\f4c5"}.ion-ios-trash-outline:before{content:"\f4c4"}.ion-ios-undo:before{content:"\f4c7"}.ion-ios-undo-outline:before{content:"\f4c6"}.ion-ios-unlocked:before{content:"\f4c9"}.ion-ios-unlocked-outline:before{content:"\f4c8"}.ion-ios-upload:before{content:"\f4cb"}.ion-ios-upload-outline:before{content:"\f4ca"}.ion-ios-videocam:before{content:"\f4cd"}.ion-ios-videocam-outline:before{content:"\f4cc"}.ion-ios-volume-high:before{content:"\f4ce"}.ion-ios-volume-low:before{content:"\f4cf"}.ion-ios-wineglass:before{content:"\f4d1"}.ion-ios-wineglass-outline:before{content:"\f4d0"}.ion-ios-world:before{content:"\f4d3"}.ion-ios-world-outline:before{content:"\f4d2"}.ion-ipad:before{content:"\f1f9"}.ion-iphone:before{content:"\f1fa"}.ion-ipod:before{content:"\f1fb"}.ion-jet:before{content:"\f295"}.ion-key:before{content:"\f296"}.ion-knife:before{content:"\f297"}.ion-laptop:before{content:"\f1fc"}.ion-leaf:before{content:"\f1fd"}.ion-levels:before{content:"\f298"}.ion-lightbulb:before{content:"\f299"}.ion-link:before{content:"\f1fe"}.ion-load-a:before{content:"\f29a"}.ion-load-b:before{content:"\f29b"}.ion-load-c:before{content:"\f29c"}.ion-load-d:before{content:"\f29d"}.ion-location:before{content:"\f1ff"}.ion-lock-combination:before{content:"\f4d4"}.ion-locked:before{content:"\f200"}.ion-log-in:before{content:"\f29e"}.ion-log-out:before{content:"\f29f"}.ion-loop:before{content:"\f201"}.ion-magnet:before{content:"\f2a0"}.ion-male:before{content:"\f2a1"}.ion-man:before{content:"\f202"}.ion-map:before{content:"\f203"}.ion-medkit:before{content:"\f2a2"}.ion-merge:before{content:"\f33f"}.ion-mic-a:before{content:"\f204"}.ion-mic-b:before{content:"\f205"}.ion-mic-c:before{content:"\f206"}.ion-minus:before{content:"\f209"}.ion-minus-circled:before{content:"\f207"}.ion-minus-round:before{content:"\f208"}.ion-model-s:before{content:"\f2c1"}.ion-monitor:before{content:"\f20a"}.ion-more:before{content:"\f20b"}.ion-mouse:before{content:"\f340"}.ion-music-note:before{content:"\f20c"}.ion-navicon:before{content:"\f20e"}.ion-navicon-round:before{content:"\f20d"}.ion-navigate:before{content:"\f2a3"}.ion-network:before{content:"\f341"}.ion-no-smoking:before{content:"\f2c2"}.ion-nuclear:before{content:"\f2a4"}.ion-outlet:before{content:"\f342"}.ion-paintbrush:before{content:"\f4d5"}.ion-paintbucket:before{content:"\f4d6"}.ion-paper-airplane:before{content:"\f2c3"}.ion-paperclip:before{content:"\f20f"}.ion-pause:before{content:"\f210"}.ion-person:before{content:"\f213"}.ion-person-add:before{content:"\f211"}.ion-person-stalker:before{content:"\f212"}.ion-pie-graph:before{content:"\f2a5"}.ion-pin:before{content:"\f2a6"}.ion-pinpoint:before{content:"\f2a7"}.ion-pizza:before{content:"\f2a8"}.ion-plane:before{content:"\f214"}.ion-planet:before{content:"\f343"}.ion-play:before{content:"\f215"}.ion-playstation:before{content:"\f30a"}.ion-plus:before{content:"\f218"}.ion-plus-circled:before{content:"\f216"}.ion-plus-round:before{content:"\f217"}.ion-podium:before{content:"\f344"}.ion-pound:before{content:"\f219"}.ion-power:before{content:"\f2a9"}.ion-pricetag:before{content:"\f2aa"}.ion-pricetags:before{content:"\f2ab"}.ion-printer:before{content:"\f21a"}.ion-pull-request:before{content:"\f345"}.ion-qr-scanner:before{content:"\f346"}.ion-quote:before{content:"\f347"}.ion-radio-waves:before{content:"\f2ac"}.ion-record:before{content:"\f21b"}.ion-refresh:before{content:"\f21c"}.ion-reply:before{content:"\f21e"}.ion-reply-all:before{content:"\f21d"}.ion-ribbon-a:before{content:"\f348"}.ion-ribbon-b:before{content:"\f349"}.ion-sad:before{content:"\f34a"}.ion-sad-outline:before{content:"\f4d7"}.ion-scissors:before{content:"\f34b"}.ion-search:before{content:"\f21f"}.ion-settings:before{content:"\f2ad"}.ion-share:before{content:"\f220"}.ion-shuffle:before{content:"\f221"}.ion-skip-backward:before{content:"\f222"}.ion-skip-forward:before{content:"\f223"}.ion-social-android:before{content:"\f225"}.ion-social-android-outline:before{content:"\f224"}.ion-social-angular:before{content:"\f4d9"}.ion-social-angular-outline:before{content:"\f4d8"}.ion-social-apple:before{content:"\f227"}.ion-social-apple-outline:before{content:"\f226"}.ion-social-bitcoin:before{content:"\f2af"}.ion-social-bitcoin-outline:before{content:"\f2ae"}.ion-social-buffer:before{content:"\f229"}.ion-social-buffer-outline:before{content:"\f228"}.ion-social-chrome:before{content:"\f4db"}.ion-social-chrome-outline:before{content:"\f4da"}.ion-social-codepen:before{content:"\f4dd"}.ion-social-codepen-outline:before{content:"\f4dc"}.ion-social-css3:before{content:"\f4df"}.ion-social-css3-outline:before{content:"\f4de"}.ion-social-designernews:before{content:"\f22b"}.ion-social-designernews-outline:before{content:"\f22a"}.ion-social-dribbble:before{content:"\f22d"}.ion-social-dribbble-outline:before{content:"\f22c"}.ion-social-dropbox:before{content:"\f22f"}.ion-social-dropbox-outline:before{content:"\f22e"}.ion-social-euro:before{content:"\f4e1"}.ion-social-euro-outline:before{content:"\f4e0"}.ion-social-facebook:before{content:"\f231"}.ion-social-facebook-outline:before{content:"\f230"}.ion-social-foursquare:before{content:"\f34d"}.ion-social-foursquare-outline:before{content:"\f34c"}.ion-social-freebsd-devil:before{content:"\f2c4"}.ion-social-github:before{content:"\f233"}.ion-social-github-outline:before{content:"\f232"}.ion-social-google:before{content:"\f34f"}.ion-social-google-outline:before{content:"\f34e"}.ion-social-googleplus:before{content:"\f235"}.ion-social-googleplus-outline:before{content:"\f234"}.ion-social-hackernews:before{content:"\f237"}.ion-social-hackernews-outline:before{content:"\f236"}.ion-social-html5:before{content:"\f4e3"}.ion-social-html5-outline:before{content:"\f4e2"}.ion-social-instagram:before{content:"\f351"}.ion-social-instagram-outline:before{content:"\f350"}.ion-social-javascript:before{content:"\f4e5"}.ion-social-javascript-outline:before{content:"\f4e4"}.ion-social-linkedin:before{content:"\f239"}.ion-social-linkedin-outline:before{content:"\f238"}.ion-social-markdown:before{content:"\f4e6"}.ion-social-nodejs:before{content:"\f4e7"}.ion-social-octocat:before{content:"\f4e8"}.ion-social-pinterest:before{content:"\f2b1"}.ion-social-pinterest-outline:before{content:"\f2b0"}.ion-social-python:before{content:"\f4e9"}.ion-social-reddit:before{content:"\f23b"}.ion-social-reddit-outline:before{content:"\f23a"}.ion-social-rss:before{content:"\f23d"}.ion-social-rss-outline:before{content:"\f23c"}.ion-social-sass:before{content:"\f4ea"}.ion-social-skype:before{content:"\f23f"}.ion-social-skype-outline:before{content:"\f23e"}.ion-social-snapchat:before{content:"\f4ec"}.ion-social-snapchat-outline:before{content:"\f4eb"}.ion-social-tumblr:before{content:"\f241"}.ion-social-tumblr-outline:before{content:"\f240"}.ion-social-tux:before{content:"\f2c5"}.ion-social-twitch:before{content:"\f4ee"}.ion-social-twitch-outline:before{content:"\f4ed"}.ion-social-twitter:before{content:"\f243"}.ion-social-twitter-outline:before{content:"\f242"}.ion-social-usd:before{content:"\f353"}.ion-social-usd-outline:before{content:"\f352"}.ion-social-vimeo:before{content:"\f245"}.ion-social-vimeo-outline:before{content:"\f244"}.ion-social-whatsapp:before{content:"\f4f0"}.ion-social-whatsapp-outline:before{content:"\f4ef"}.ion-social-windows:before{content:"\f247"}.ion-social-windows-outline:before{content:"\f246"}.ion-social-wordpress:before{content:"\f249"}.ion-social-wordpress-outline:before{content:"\f248"}.ion-social-yahoo:before{content:"\f24b"}.ion-social-yahoo-outline:before{content:"\f24a"}.ion-social-yen:before{content:"\f4f2"}.ion-social-yen-outline:before{content:"\f4f1"}.ion-social-youtube:before{content:"\f24d"}.ion-social-youtube-outline:before{content:"\f24c"}.ion-soup-can:before{content:"\f4f4"}.ion-soup-can-outline:before{content:"\f4f3"}.ion-speakerphone:before{content:"\f2b2"}.ion-speedometer:before{content:"\f2b3"}.ion-spoon:before{content:"\f2b4"}.ion-star:before{content:"\f24e"}.ion-stats-bars:before{content:"\f2b5"}.ion-steam:before{content:"\f30b"}.ion-stop:before{content:"\f24f"}.ion-thermometer:before{content:"\f2b6"}.ion-thumbsdown:before{content:"\f250"}.ion-thumbsup:before{content:"\f251"}.ion-toggle:before{content:"\f355"}.ion-toggle-filled:before{content:"\f354"}.ion-transgender:before{content:"\f4f5"}.ion-trash-a:before{content:"\f252"}.ion-trash-b:before{content:"\f253"}.ion-trophy:before{content:"\f356"}.ion-tshirt:before{content:"\f4f7"}.ion-tshirt-outline:before{content:"\f4f6"}.ion-umbrella:before{content:"\f2b7"}.ion-university:before{content:"\f357"}.ion-unlocked:before{content:"\f254"}.ion-upload:before{content:"\f255"}.ion-usb:before{content:"\f2b8"}.ion-videocamera:before{content:"\f256"}.ion-volume-high:before{content:"\f257"}.ion-volume-low:before{content:"\f258"}.ion-volume-medium:before{content:"\f259"}.ion-volume-mute:before{content:"\f25a"}.ion-wand:before{content:"\f358"}.ion-waterdrop:before{content:"\f25b"}.ion-wifi:before{content:"\f25c"}.ion-wineglass:before{content:"\f2b9"}.ion-woman:before{content:"\f25d"}.ion-wrench:before{content:"\f2ba"}.ion-xbox:before{content:"\f30c"}
diff --git a/pigx-visual/pigx-xxl-job-admin/src/main/resources/static/adminlte/bower_components/Ionicons/fonts/ionicons.eot b/pigx-visual/pigx-xxl-job-admin/src/main/resources/static/adminlte/bower_components/Ionicons/fonts/ionicons.eot
new file mode 100755
index 0000000000000000000000000000000000000000..92a3f20a39267ae7f45144f412a995a663730360
GIT binary patch
literal 120724
zcmdqKdz@TFnLm8$+;4sQ^u4F2r>`^JbHDX;cWyJ&2?^vzn1m2QHVK^zA>4-mf#uqe
ztRjLU0wN-gh=_m~kVOQ97Eyt9F|sbA>(3ooKQ7C#E&->q-
z={i-XPMtbcPgOnls@(ch#{K$L#xaiP=pWC?f|Eeb+l*4HC)|6^Zp3)v{yQPjG1AOC
z65HGg?gH+7?ksMH6JLZ!CU+ut2DcM=mvHCdKE-Xo{T}WjuHT6VanIp%
ze_Ri;>Ej-*#tGcS@yD+}@w^+fKXB~T7myo2>Ewao#q+oQ0}5@#eRaoq+a}NB&rjlo
zdB}Tr$KH!moR2N#I4`wZJagMclkdNen%Jv#+^c7v``5evCzC@zgYR+dp2^*3Y}?77
z^OY~-*)I@EyOALr5~omC5clo7&%5}t#X6eFu_rjrxcb}+c5J)#iLO^sKfWhDciy(k
zCWS@pOL&fZE_MF4^Uk=(`-}5A_RVtv>A#u0;G&B^``kbNoMYd58_j>o=^Oet(vdjf
zbnl}7;tY384&GO|74LIy{C=J1SRJ=&W5`fFe)ae3Tz_*6oto&C#y|d_DTkkCd$^dB
zMFnZlIj#br(?5}2$=>E9WUpqsaDPH0{x7bD+dp?+P~q9Bfb_(!Wjne0Tio0K?Y04J
zt00@|ZvIQ*?kUda+&+Lub==pSm}3-I5c~*8b9KbkKTgkK)hI*p2L*b+
znP2AwYAS+&cLnF$^t{#x%{0X+H}D+d3c?gZ9ifKrHq(?}J0`e!N6K)G=XJ!5z}1kR
zLP&8A47h26IqvU6n`%h>C(?-78l>+>Tt`T895==BPW$VP=e5Q&ikqRrQ8{Nwn%{Gt
z)scs1PF}51p7JP6&54`OD5hu4`zq2DD(F9jW_}%c6q@&qn8wx_U&PMyW?Va_?00Za
z;n;i*c@$o2ggWB6p@w@36*mz492nLc`SgrJ)qU=y=~*+Rklxd{uedSVOMN?1rdgI^
z%CCJuz_ZzYQW@MI6>5$9>YRKk$06XEGZ*N-jzID8$U~Z=^i~8Uk7|1j_w`1Yn{MVI
z=B69Zt8>aC-+6DU(Z|XkKXclGcets>GbEkw*XNY2%z-oV9hhH(=g4>F(FVk|M!<6?
zuY1n@G}4rRH|8>xqcnvI!c-%!HPTa!c?OK=l%?l&=bLPHtm%8ma{}I@{Mly}prLa=JRT!T)h!j5L4g~QjNS?Bd>zE{sCbMc@H5_Ol7E@l;+%+s~sIX
z`6$EvJM!sS-3g4P8tE$HDTLabn9|KK_t`Y^n}MF!=aj27?h!k1I`;V#o>Rc{*?!S|
zwULkX?0ZzE)(CeX#yvOT#!h)qJ}0I!^+uRR+`LaAU1{7;A#R3hBmIR&TtiHAfC4?M
zBUBJ5pJ)u_(fu?+igVXv4QUD>U(ND79#NQWOC9NHgxZ|B*!&JXqcGJ7jeA;~PCpS-
zx)FHi`xWQjNptU`9ByadpE@R8!~LAVy^g{Nvw8O*O`*~VQ-~?l8X<+4LhVDs6!IxF
zo^wBH+&A;*#7>`@_jQ!}pipbnMYOv?zngW>#y2*~P}!+QNFk7vop=5P@BBO7dHe8tzn6Zm|ypY`vdjK@ucyJ*>0Y19`fw+?DxEiu`O9wTJL%Ly(_(Ud0(Hf-O|~zDVB)c*_vqG-1>HWWBl>>uiMtNJ(ExqrNrLEt;yZV=TgI|`%*8Y
zUQf?UU!AFBUdgs+SGLF7AMc2Ftn2t)ZZLN^zcOFT-`yGSe4z97F0SkRuDiP)>w2l%
z>R#79)&1L^OwYQWTYFyU`E{?^yQ%l~-s!$v--f<(`>yM|qwnFqSNh)Um;2lMH}+rN
z|MI|327Wi#J9zt0cB@JqwL8L5t3JTf)%YB5?oxA=TXE)AD%Ej?B?
z%Qu%_nzw$wIRC)>-zYn@ys>!c;)@sGy!e?V=8`*)TXfuIOZ%6uU;6m6caMMW_+Osz%8H+?cw=RJ{hTR*k-tfy)
zW2cUtdflndZ5-bC%xQzC-Ei7(H?7-r&!%@b_iuiB^BY^*x74;guw{BHx3#o&{ni_{
zPHlZ+>+@TGvGtA9xzo+l+fN@ned+0!pMJ;bhpNk~TdNOMXSTI(+r909ZQtMa=63(~
z1>1LRpW6P?_P2NR?^v_rt{wFqzuw91Ozm8`bN9~cciy@4>7BnjBY4K9Gp;=2=`((_
zi`(_&uGh~@oH=~vxo6&a=1+EuyA!+5-F@rsr+2@6mVMUoXPtl6oo5|7yZ7u3XYV=t
z*0Uc!`xj@=oYQ{Jl5@^J=azFGIOnxL@|ykE+<(n8wXxa_wWn*-*RH?z-fQ2!Zuq)Q
z*Ij(w%lo)}!F`o|)qU6RyKmnQ_x)x+w|{v5#{G})KlC?C{$}cLe*O{hBejpb{L%17
zOV{^b|NO@`f9&lWN;mAe;l3LVe!TPJmw)`ekH7Ts-`(iHv2^2x8z*nP+b4hwlC5z3+Tk|MCrA{`GzGebKMsNmLiLW0zumI-F@h84{!xQl*o`2%k-_pO8`qtiWJ@cgXv$s6^@Uw5!xK~2ao^Y%@<-XEP3JJi}s7hzxc{a
z=e{)kckliEqL=xX`(Ix3^2j*1yHl4}m%7M~wBv8zT$_VV+vM6aYTQy0PeQhW8F7iT&