diff --git a/doc/pg/public.sql b/doc/pg/public.sql
new file mode 100644
index 0000000..c9e6c68
--- /dev/null
+++ b/doc/pg/public.sql
@@ -0,0 +1,432 @@
+/*
+ Navicat Premium Data Transfer
+
+ Source Server : local-PG
+ Source Server Type : PostgreSQL
+ Source Server Version : 160001 (160001)
+ Source Host : localhost:5432
+ Source Catalog : xxl_job
+ Source Schema : public
+
+ Target Server Type : PostgreSQL
+ Target Server Version : 160001 (160001)
+ File Encoding : 65001
+
+ Date: 14/06/2024 10:24:06
+*/
+
+
+-- ----------------------------
+-- Sequence structure for xxl_job_group_id
+-- ----------------------------
+DROP SEQUENCE IF EXISTS "public"."xxl_job_group_id";
+CREATE SEQUENCE "public"."xxl_job_group_id"
+ INCREMENT 1
+ MINVALUE 1
+ MAXVALUE 9223372036854775807
+ START 1
+ CACHE 1;
+ALTER SEQUENCE "public"."xxl_job_group_id" OWNER TO "postgres";
+
+-- ----------------------------
+-- Sequence structure for xxl_job_info_id
+-- ----------------------------
+DROP SEQUENCE IF EXISTS "public"."xxl_job_info_id";
+CREATE SEQUENCE "public"."xxl_job_info_id"
+ INCREMENT 1
+ MINVALUE 1
+ MAXVALUE 9223372036854775807
+ START 1
+ CACHE 1;
+ALTER SEQUENCE "public"."xxl_job_info_id" OWNER TO "postgres";
+
+-- ----------------------------
+-- Sequence structure for xxl_job_log_id
+-- ----------------------------
+DROP SEQUENCE IF EXISTS "public"."xxl_job_log_id";
+CREATE SEQUENCE "public"."xxl_job_log_id"
+ INCREMENT 1
+ MINVALUE 1
+ MAXVALUE 9223372036854775807
+ START 1
+ CACHE 1;
+ALTER SEQUENCE "public"."xxl_job_log_id" OWNER TO "postgres";
+
+-- ----------------------------
+-- Sequence structure for xxl_job_log_report_id
+-- ----------------------------
+DROP SEQUENCE IF EXISTS "public"."xxl_job_log_report_id";
+CREATE SEQUENCE "public"."xxl_job_log_report_id"
+ INCREMENT 1
+ MINVALUE 1
+ MAXVALUE 9223372036854775807
+ START 1
+ CACHE 1;
+ALTER SEQUENCE "public"."xxl_job_log_report_id" OWNER TO "postgres";
+
+-- ----------------------------
+-- Sequence structure for xxl_job_registry_id
+-- ----------------------------
+DROP SEQUENCE IF EXISTS "public"."xxl_job_registry_id";
+CREATE SEQUENCE "public"."xxl_job_registry_id"
+ INCREMENT 1
+ MINVALUE 1
+ MAXVALUE 9223372036854775807
+ START 1
+ CACHE 1;
+ALTER SEQUENCE "public"."xxl_job_registry_id" OWNER TO "postgres";
+
+-- ----------------------------
+-- Table structure for xxl_job_group
+-- ----------------------------
+DROP TABLE IF EXISTS "public"."xxl_job_group";
+CREATE TABLE "public"."xxl_job_group"
+(
+ "id" int4 NOT NULL DEFAULT nextval('xxl_job_group_id'::regclass),
+ "app_name" varchar(64) COLLATE "pg_catalog"."default" NOT NULL,
+ "title" varchar(12) COLLATE "pg_catalog"."default" NOT NULL,
+ "address_type" int2 NOT NULL,
+ "address_list" text COLLATE "pg_catalog"."default",
+ "update_time" timestamp(6)
+)
+;
+ALTER TABLE "public"."xxl_job_group"
+ OWNER TO "postgres";
+COMMENT ON COLUMN "public"."xxl_job_group"."app_name" IS '执行器AppName';
+COMMENT ON COLUMN "public"."xxl_job_group"."title" IS '执行器名称';
+COMMENT ON COLUMN "public"."xxl_job_group"."address_type" IS '执行器地址类型:0=自动注册、1=手动录入';
+COMMENT ON COLUMN "public"."xxl_job_group"."address_list" IS '执行器地址列表,多地址逗号分隔';
+
+-- ----------------------------
+-- Records of xxl_job_group
+-- ----------------------------
+BEGIN;
+INSERT INTO "public"."xxl_job_group" ("id", "app_name", "title", "address_type", "address_list", "update_time")
+VALUES (5, 'demo-api-executor', 'u-boot-job测试', 0, 'http://128.48.168.42:9999/', '2024-06-14 10:17:26.725');
+COMMIT;
+
+-- ----------------------------
+-- Table structure for xxl_job_info
+-- ----------------------------
+DROP TABLE IF EXISTS "public"."xxl_job_info";
+CREATE TABLE "public"."xxl_job_info"
+(
+ "id" int4 NOT NULL DEFAULT nextval('xxl_job_info_id'::regclass),
+ "job_group" int4 NOT NULL,
+ "job_desc" varchar(255) COLLATE "pg_catalog"."default" NOT NULL,
+ "add_time" timestamp(6),
+ "update_time" timestamp(6),
+ "author" varchar(64) COLLATE "pg_catalog"."default",
+ "alarm_email" varchar(255) COLLATE "pg_catalog"."default",
+ "schedule_type" varchar(50) COLLATE "pg_catalog"."default" NOT NULL,
+ "schedule_conf" varchar(128) COLLATE "pg_catalog"."default",
+ "misfire_strategy" varchar(50) COLLATE "pg_catalog"."default" NOT NULL,
+ "executor_route_strategy" varchar(50) COLLATE "pg_catalog"."default",
+ "executor_handler" varchar(255) COLLATE "pg_catalog"."default",
+ "executor_param" varchar(512) COLLATE "pg_catalog"."default",
+ "executor_block_strategy" varchar(50) COLLATE "pg_catalog"."default",
+ "executor_timeout" int4 NOT NULL,
+ "executor_fail_retry_count" int4 NOT NULL,
+ "glue_type" varchar(50) COLLATE "pg_catalog"."default" NOT NULL,
+ "glue_source" text COLLATE "pg_catalog"."default",
+ "glue_remark" varchar(128) COLLATE "pg_catalog"."default",
+ "glue_updatetime" timestamp(6),
+ "child_jobid" varchar(255) COLLATE "pg_catalog"."default",
+ "trigger_status" int2 NOT NULL,
+ "trigger_last_time" int8 NOT NULL,
+ "trigger_next_time" int8 NOT NULL
+)
+;
+ALTER TABLE "public"."xxl_job_info"
+ OWNER TO "postgres";
+COMMENT ON COLUMN "public"."xxl_job_info"."job_group" IS '执行器主键ID';
+COMMENT ON COLUMN "public"."xxl_job_info"."author" IS '作者';
+COMMENT ON COLUMN "public"."xxl_job_info"."alarm_email" IS '报警邮件';
+COMMENT ON COLUMN "public"."xxl_job_info"."schedule_type" IS '调度类型';
+COMMENT ON COLUMN "public"."xxl_job_info"."schedule_conf" IS '调度配置,值含义取决于调度类型';
+COMMENT ON COLUMN "public"."xxl_job_info"."misfire_strategy" IS '调度过期策略';
+COMMENT ON COLUMN "public"."xxl_job_info"."executor_route_strategy" IS '执行器路由策略';
+COMMENT ON COLUMN "public"."xxl_job_info"."executor_handler" IS '执行器任务handler';
+COMMENT ON COLUMN "public"."xxl_job_info"."executor_param" IS '执行器任务参数';
+COMMENT ON COLUMN "public"."xxl_job_info"."executor_block_strategy" IS '阻塞处理策略';
+COMMENT ON COLUMN "public"."xxl_job_info"."executor_timeout" IS '任务执行超时时间,单位秒';
+COMMENT ON COLUMN "public"."xxl_job_info"."executor_fail_retry_count" IS '失败重试次数';
+COMMENT ON COLUMN "public"."xxl_job_info"."glue_type" IS 'GLUE类型';
+COMMENT ON COLUMN "public"."xxl_job_info"."glue_source" IS 'GLUE源代码';
+COMMENT ON COLUMN "public"."xxl_job_info"."glue_remark" IS 'GLUE备注';
+COMMENT ON COLUMN "public"."xxl_job_info"."glue_updatetime" IS 'GLUE更新时间';
+COMMENT ON COLUMN "public"."xxl_job_info"."child_jobid" IS '子任务ID,多个逗号分隔';
+COMMENT ON COLUMN "public"."xxl_job_info"."trigger_status" IS '调度状态:0-停止,1-运行';
+COMMENT ON COLUMN "public"."xxl_job_info"."trigger_last_time" IS '上次调度时间';
+COMMENT ON COLUMN "public"."xxl_job_info"."trigger_next_time" IS '下次调度时间';
+
+
+-- ----------------------------
+-- Table structure for xxl_job_lock
+-- ----------------------------
+DROP TABLE IF EXISTS "public"."xxl_job_lock";
+CREATE TABLE "public"."xxl_job_lock"
+(
+ "lock_name" varchar(50) COLLATE "pg_catalog"."default" NOT NULL
+)
+;
+ALTER TABLE "public"."xxl_job_lock"
+ OWNER TO "postgres";
+COMMENT ON COLUMN "public"."xxl_job_lock"."lock_name" IS '锁名称';
+
+-- ----------------------------
+-- Records of xxl_job_lock
+-- ----------------------------
+BEGIN;
+INSERT INTO "public"."xxl_job_lock" ("lock_name")
+VALUES ('schedule_lock');
+COMMIT;
+
+-- ----------------------------
+-- Table structure for xxl_job_log
+-- ----------------------------
+DROP TABLE IF EXISTS "public"."xxl_job_log";
+CREATE TABLE "public"."xxl_job_log"
+(
+ "id" int8 NOT NULL DEFAULT nextval('xxl_job_log_id'::regclass),
+ "job_group" int4 NOT NULL,
+ "job_id" int4 NOT NULL,
+ "executor_address" varchar(255) COLLATE "pg_catalog"."default",
+ "executor_handler" varchar(255) COLLATE "pg_catalog"."default",
+ "executor_param" varchar(512) COLLATE "pg_catalog"."default",
+ "executor_sharding_param" varchar(20) COLLATE "pg_catalog"."default",
+ "executor_fail_retry_count" int4 NOT NULL DEFAULT 0,
+ "trigger_time" timestamp(6),
+ "trigger_code" int4 NOT NULL,
+ "trigger_msg" text COLLATE "pg_catalog"."default",
+ "handle_time" timestamp(6),
+ "handle_code" int4 NOT NULL,
+ "handle_msg" text COLLATE "pg_catalog"."default",
+ "alarm_status" int2 NOT NULL DEFAULT 0
+)
+;
+ALTER TABLE "public"."xxl_job_log"
+ OWNER TO "postgres";
+COMMENT ON COLUMN "public"."xxl_job_log"."job_group" IS '执行器主键ID';
+COMMENT ON COLUMN "public"."xxl_job_log"."job_id" IS '任务,主键ID';
+COMMENT ON COLUMN "public"."xxl_job_log"."executor_address" IS '执行器地址,本次执行的地址';
+COMMENT ON COLUMN "public"."xxl_job_log"."executor_handler" IS '执行器任务handler';
+COMMENT ON COLUMN "public"."xxl_job_log"."executor_param" IS '执行器任务参数';
+COMMENT ON COLUMN "public"."xxl_job_log"."executor_sharding_param" IS '执行器任务分片参数,格式如 1/2';
+COMMENT ON COLUMN "public"."xxl_job_log"."executor_fail_retry_count" IS '失败重试次数';
+COMMENT ON COLUMN "public"."xxl_job_log"."trigger_time" IS '调度-时间';
+COMMENT ON COLUMN "public"."xxl_job_log"."trigger_code" IS '调度-结果';
+COMMENT ON COLUMN "public"."xxl_job_log"."trigger_msg" IS '调度-日志';
+COMMENT ON COLUMN "public"."xxl_job_log"."handle_time" IS '执行-时间';
+COMMENT ON COLUMN "public"."xxl_job_log"."handle_code" IS '执行-状态';
+COMMENT ON COLUMN "public"."xxl_job_log"."handle_msg" IS '执行-日志';
+COMMENT ON COLUMN "public"."xxl_job_log"."alarm_status" IS '告警状态:0-默认、1-无需告警、2-告警成功、3-告警失败';
+
+
+-- ----------------------------
+-- Table structure for xxl_job_log_report
+-- ----------------------------
+DROP TABLE IF EXISTS "public"."xxl_job_log_report";
+CREATE TABLE "public"."xxl_job_log_report"
+(
+ "id" int4 NOT NULL DEFAULT nextval('xxl_job_log_report_id'::regclass),
+ "trigger_day" timestamp(6),
+ "running_count" int4 NOT NULL DEFAULT 0,
+ "suc_count" int4 NOT NULL DEFAULT 0,
+ "fail_count" int4 NOT NULL DEFAULT 0,
+ "update_time" timestamp(6)
+)
+;
+ALTER TABLE "public"."xxl_job_log_report"
+ OWNER TO "postgres";
+COMMENT ON COLUMN "public"."xxl_job_log_report"."trigger_day" IS '调度-时间';
+COMMENT ON COLUMN "public"."xxl_job_log_report"."running_count" IS '运行中-日志数量';
+COMMENT ON COLUMN "public"."xxl_job_log_report"."suc_count" IS '执行成功-日志数量';
+COMMENT ON COLUMN "public"."xxl_job_log_report"."fail_count" IS '执行失败-日志数量';
+
+
+-- ----------------------------
+-- Table structure for xxl_job_logglue
+-- ----------------------------
+DROP TABLE IF EXISTS "public"."xxl_job_logglue";
+CREATE TABLE "public"."xxl_job_logglue"
+(
+ "id" int4 NOT NULL,
+ "job_id" int4 NOT NULL,
+ "glue_type" varchar(50) COLLATE "pg_catalog"."default",
+ "glue_source" text COLLATE "pg_catalog"."default",
+ "glue_remark" varchar(128) COLLATE "pg_catalog"."default" NOT NULL,
+ "add_time" timestamp(6),
+ "update_time" timestamp(6)
+)
+;
+ALTER TABLE "public"."xxl_job_logglue"
+ OWNER TO "postgres";
+COMMENT ON COLUMN "public"."xxl_job_logglue"."job_id" IS '任务,主键ID';
+COMMENT ON COLUMN "public"."xxl_job_logglue"."glue_type" IS 'GLUE类型';
+COMMENT ON COLUMN "public"."xxl_job_logglue"."glue_source" IS 'GLUE源代码';
+COMMENT ON COLUMN "public"."xxl_job_logglue"."glue_remark" IS 'GLUE备注';
+
+-- ----------------------------
+-- Records of xxl_job_logglue
+-- ----------------------------
+BEGIN;
+COMMIT;
+
+-- ----------------------------
+-- Table structure for xxl_job_registry
+-- ----------------------------
+DROP TABLE IF EXISTS "public"."xxl_job_registry";
+CREATE TABLE "public"."xxl_job_registry"
+(
+ "id" int4 NOT NULL DEFAULT nextval('xxl_job_registry_id'::regclass),
+ "registry_group" varchar(50) COLLATE "pg_catalog"."default" NOT NULL,
+ "registry_key" varchar(255) COLLATE "pg_catalog"."default" NOT NULL,
+ "registry_value" varchar(255) COLLATE "pg_catalog"."default" NOT NULL,
+ "update_time" timestamp(6)
+)
+;
+ALTER TABLE "public"."xxl_job_registry"
+ OWNER TO "postgres";
+
+
+-- ----------------------------
+-- Table structure for xxl_job_user
+-- ----------------------------
+DROP TABLE IF EXISTS "public"."xxl_job_user";
+CREATE TABLE "public"."xxl_job_user"
+(
+ "id" int4 NOT NULL,
+ "username" varchar(50) COLLATE "pg_catalog"."default" NOT NULL,
+ "password" varchar(50) COLLATE "pg_catalog"."default" NOT NULL,
+ "role" int2 NOT NULL,
+ "permission" varchar(255) COLLATE "pg_catalog"."default"
+)
+;
+ALTER TABLE "public"."xxl_job_user"
+ OWNER TO "postgres";
+COMMENT ON COLUMN "public"."xxl_job_user"."username" IS '账号';
+COMMENT ON COLUMN "public"."xxl_job_user"."password" IS '密码';
+COMMENT ON COLUMN "public"."xxl_job_user"."role" IS '角色:0-普通用户、1-管理员';
+COMMENT ON COLUMN "public"."xxl_job_user"."permission" IS '权限:执行器ID列表,多个逗号分割';
+
+-- ----------------------------
+-- Records of xxl_job_user
+-- ----------------------------
+BEGIN;
+INSERT INTO "public"."xxl_job_user" ("id", "username", "password", "role", "permission")
+VALUES (1, 'admin', 'e10adc3949ba59abbe56e057f20f883e', 1, NULL);
+COMMIT;
+
+-- ----------------------------
+-- Alter sequences owned by
+-- ----------------------------
+SELECT setval('"public"."xxl_job_group_id"', 5, true);
+
+-- ----------------------------
+-- Alter sequences owned by
+-- ----------------------------
+SELECT setval('"public"."xxl_job_info_id"', 4, true);
+
+-- ----------------------------
+-- Alter sequences owned by
+-- ----------------------------
+SELECT setval('"public"."xxl_job_log_id"', 30, true);
+
+-- ----------------------------
+-- Alter sequences owned by
+-- ----------------------------
+SELECT setval('"public"."xxl_job_log_report_id"', 7, true);
+
+-- ----------------------------
+-- Alter sequences owned by
+-- ----------------------------
+SELECT setval('"public"."xxl_job_registry_id"', 2, true);
+
+-- ----------------------------
+-- Primary Key structure for table xxl_job_group
+-- ----------------------------
+ALTER TABLE "public"."xxl_job_group"
+ ADD CONSTRAINT "xxl_job_group_pkey" PRIMARY KEY ("id");
+
+-- ----------------------------
+-- Primary Key structure for table xxl_job_info
+-- ----------------------------
+ALTER TABLE "public"."xxl_job_info"
+ ADD CONSTRAINT "xxl_job_info_pkey" PRIMARY KEY ("id");
+
+-- ----------------------------
+-- Primary Key structure for table xxl_job_lock
+-- ----------------------------
+ALTER TABLE "public"."xxl_job_lock"
+ ADD CONSTRAINT "xxl_job_lock_pkey" PRIMARY KEY ("lock_name");
+
+-- ----------------------------
+-- Indexes structure for table xxl_job_log
+-- ----------------------------
+CREATE INDEX "I_handle_code" ON "public"."xxl_job_log" USING btree (
+ "handle_code" "pg_catalog"."int4_ops" ASC NULLS LAST
+ );
+CREATE INDEX "I_trigger_time" ON "public"."xxl_job_log" USING btree (
+ "trigger_time" "pg_catalog"."timestamp_ops" ASC
+ NULLS LAST
+ );
+
+-- ----------------------------
+-- Primary Key structure for table xxl_job_log
+-- ----------------------------
+ALTER TABLE "public"."xxl_job_log"
+ ADD CONSTRAINT "xxl_job_log_pkey" PRIMARY KEY ("id");
+
+-- ----------------------------
+-- Indexes structure for table xxl_job_log_report
+-- ----------------------------
+CREATE UNIQUE INDEX "i_trigger_day" ON "public"."xxl_job_log_report" USING btree (
+ "trigger_day"
+ "pg_catalog"."timestamp_ops" ASC NULLS
+ LAST
+ );
+
+-- ----------------------------
+-- Primary Key structure for table xxl_job_log_report
+-- ----------------------------
+ALTER TABLE "public"."xxl_job_log_report"
+ ADD CONSTRAINT "xxl_job_log_report_pkey" PRIMARY KEY ("id");
+
+-- ----------------------------
+-- Primary Key structure for table xxl_job_logglue
+-- ----------------------------
+ALTER TABLE "public"."xxl_job_logglue"
+ ADD CONSTRAINT "xxl_job_logglue_pkey" PRIMARY KEY ("id");
+
+-- ----------------------------
+-- Indexes structure for table xxl_job_registry
+-- ----------------------------
+CREATE INDEX "i_g_k_v" ON "public"."xxl_job_registry" USING btree (
+ "registry_group" COLLATE "pg_catalog"."default"
+ "pg_catalog"."text_ops" ASC NULLS LAST,
+ "registry_key" COLLATE "pg_catalog"."default"
+ "pg_catalog"."text_ops" ASC NULLS LAST,
+ "registry_value" COLLATE "pg_catalog"."default"
+ "pg_catalog"."text_ops" ASC NULLS LAST
+ );
+
+-- ----------------------------
+-- Primary Key structure for table xxl_job_registry
+-- ----------------------------
+ALTER TABLE "public"."xxl_job_registry"
+ ADD CONSTRAINT "xxl_job_registry_pkey" PRIMARY KEY ("id");
+
+-- ----------------------------
+-- Indexes structure for table xxl_job_user
+-- ----------------------------
+CREATE UNIQUE INDEX "i_username" ON "public"."xxl_job_user" USING btree (
+ "username" COLLATE "pg_catalog"."default"
+ "pg_catalog"."text_ops" ASC NULLS LAST
+ );
+
+-- ----------------------------
+-- Primary Key structure for table xxl_job_user
+-- ----------------------------
+ALTER TABLE "public"."xxl_job_user"
+ ADD CONSTRAINT "xxl_job_user_pkey" PRIMARY KEY ("id");
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..379f82b
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,139 @@
+
+
+ 4.0.0
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 3.3.5
+
+
+ com.xxl.job
+ xxl-job-springboot3
+ 1.0.0
+ xxl-job-springboot3
+ xxl-job-springboot3
+ pom
+
+
+ xxl-job-admin
+ xxl-job-core
+
+
+ UTF-8
+ UTF-8
+ UTF-8
+ 21
+ 21
+ true
+
+ 4.1.108.Final
+ 2.10.1
+
+ 8.3.0
+ 1.2.23
+ 2.0.13
+ 5.10.2
+ 1.3.2
+
+ 4.0.21
+ 3.5.7
+ 3.3.1
+ 3.6.3
+ 3.2.3
+
+
+
+
+
+
+
+
+
+ GNU General Public License version 3
+ https://opensource.org/licenses/GPL-3.0
+
+
+
+
+ master
+ https://github.com/xuxueli/xxl-job.git
+ scm:git:https://github.com/xuxueli/xxl-job.git
+ scm:git:git@github.com:xuxueli/xxl-job.git
+
+
+
+ XXL
+ xuxueli
+ 931591021@qq.com
+ https://github.com/xuxueli
+
+
+
+
+
+
+ release
+
+
+
+
+ org.apache.maven.plugins
+ maven-source-plugin
+
+
+ package
+
+ jar-no-fork
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-javadoc-plugin
+
+
+ package
+
+ jar
+
+
+ none
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-gpg-plugin
+
+ false
+
+
+
+ verify
+
+ sign
+
+
+
+
+
+
+
+
+ oss
+ https://oss.sonatype.org/content/repositories/snapshots/
+
+
+ oss
+ https://oss.sonatype.org/service/local/staging/deploy/maven2/
+
+
+
+
+
+
diff --git a/xxl-job-admin/pom.xml b/xxl-job-admin/pom.xml
new file mode 100644
index 0000000..f5f39a6
--- /dev/null
+++ b/xxl-job-admin/pom.xml
@@ -0,0 +1,123 @@
+
+
+ 4.0.0
+
+ com.xxl.job
+ xxl-job-springboot3
+ 1.0.0
+
+ xxl-job-admin
+ 1.0.1
+ xxl-job-admin
+ xxl-job-admin
+
+ UTF-8
+ UTF-8
+ UTF-8
+ 21
+ 21
+ 21
+ true
+ 4.1.108.Final
+ 2.10.1
+ 2.3.2
+ 8.3.0
+ 2.0.13
+ 5.10.2
+ 1.3.2
+ 3.3.1
+ 3.6.3
+ 3.2.3
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-freemarker
+
+
+
+ org.springframework.boot
+ spring-boot-starter-mail
+
+
+
+ org.springframework.boot
+ spring-boot-starter-actuator
+
+
+
+ org.postgresql
+ postgresql
+ runtime
+
+
+
+
+
+
+
+
+ com.xxl.job
+ xxl-job-core
+ 1.0.0
+
+
+ com.baomidou
+ mybatis-plus-spring-boot3-starter
+ ${mybatis.plus.version}
+
+
+ com.alibaba
+ druid-spring-boot-3-starter
+ ${druid.spring.boot.3.starter.version}
+
+
+ com.xxl.job
+ xxl-job-core
+ 1.0.0
+ compile
+
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+ repackage
+
+
+
+
+
+ com.spotify
+ docker-maven-plugin
+ 0.4.13
+
+ ${project.artifactId}:${project.version}
+ ${project.basedir}
+
+
+ /
+ ${project.build.directory}
+ ${project.build.finalName}.jar
+
+
+
+
+
+
+
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/UXxlJobAdminApplication.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/UXxlJobAdminApplication.java
new file mode 100644
index 0000000..dbdafba
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/UXxlJobAdminApplication.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 UXxlJobAdminApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(UXxlJobAdminApplication.class, args);
+ }
+
+}
\ No newline at end of file
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/IndexController.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/IndexController.java
new file mode 100644
index 0000000..af47c08
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/IndexController.java
@@ -0,0 +1,97 @@
+package com.xxl.job.admin.controller;
+
+import com.xxl.job.admin.controller.annotation.PermissionLimit;
+import com.xxl.job.admin.service.LoginService;
+import com.xxl.job.admin.service.XxlJobService;
+import com.xxl.job.core.biz.model.ReturnT;
+import jakarta.annotation.Resource;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import org.springframework.beans.propertyeditors.CustomDateEditor;
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.Model;
+import org.springframework.web.bind.WebDataBinder;
+import org.springframework.web.bind.annotation.InitBinder;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.web.servlet.ModelAndView;
+import org.springframework.web.servlet.view.RedirectView;
+
+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(final Model model) {
+
+ final Map dashboardMap = this.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 {
+
+ public static final int MAX_YEAR = Calendar.getInstance().get(Calendar.YEAR) + 100;
+ 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);
+ private static final long serialVersionUID = 12423409423L;
+
+ 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;
+ 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;
+ private TimeZone timeZone = null;
+
+ /**
+ * Constructs a new CronExpression
based on the specified
+ * parameter.
+ *
+ * @param cronExpression String representation of the cron expression the
+ * new object should represent
+ * @throws 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 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);
+ }
+
+ /**
+ * 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;
+ }
+
+
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // 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(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(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 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;
+
+ Calendar tcal = 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();
+
+ Calendar tcal = 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(Calendar.HOUR_OF_DAY, hour);
+ if (cal.get(Calendar.HOUR_OF_DAY) != hour && hour != 24) {
+ cal.set(Calendar.HOUR_OF_DAY, hour + 1);
+ }
+ }
+
+ /**
+ * NOT YET IMPLEMENTED: Returns the time before the given time
+ * that the CronExpression
matches.
+ */
+ public Date getTimeBefore(Date endTime) {
+ // FUTURE_TODO: implement QUARTZ-423
+ return null;
+ }
+
+ /**
+ * NOT YET IMPLEMENTED: Returns the final time that the
+ * CronExpression
will match.
+ */
+ public Date getFinalFireTime() {
+ // FUTURE_TODO: implement QUARTZ-423
+ return null;
+ }
+
+ protected boolean isLeapYear(int year) {
+ return ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0));
+ }
+
+ protected int getLastDayOfMonth(int monthNum, int year) {
+
+ switch (monthNum) {
+ case 1:
+ return 31;
+ case 2:
+ return (isLeapYear(year)) ? 29 : 28;
+ case 3:
+ return 31;
+ case 4:
+ return 30;
+ case 5:
+ return 31;
+ case 6:
+ return 30;
+ case 7:
+ return 31;
+ case 8:
+ return 31;
+ case 9:
+ return 30;
+ case 10:
+ return 31;
+ case 11:
+ return 30;
+ case 12:
+ return 31;
+ default:
+ throw new IllegalArgumentException("Illegal month number: "
+ + monthNum);
+ }
+ }
+
+
+ private void readObject(java.io.ObjectInputStream stream)
+ throws java.io.IOException, ClassNotFoundException {
+
+ stream.defaultReadObject();
+ try {
+ buildExpression(cronExpression);
+ } catch (Exception ignore) {
+ } // never happens
+ }
+
+ @Override
+ @Deprecated
+ public Object clone() {
+ return new CronExpression(this);
+ }
+}
+
+class ValueSet {
+ public int value;
+
+ public int pos;
+}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/exception/XxlJobException.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/exception/XxlJobException.java
new file mode 100644
index 0000000..faa6063
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/exception/XxlJobException.java
@@ -0,0 +1,14 @@
+package com.xxl.job.admin.core.exception;
+
+/**
+ * @author xuxueli 2019-05-04 23:19:29
+ */
+public class XxlJobException extends RuntimeException {
+
+ public XxlJobException() {
+ }
+ public XxlJobException(String message) {
+ super(message);
+ }
+
+}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobGroup.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobGroup.java
new file mode 100644
index 0000000..1ebdf1d
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobGroup.java
@@ -0,0 +1,78 @@
+package com.xxl.job.admin.core.model;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Date;
+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; // 执行器地址列表,多地址逗号分隔(手动录入)
+ private Date updateTime;
+
+ // 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;
+ }
+
+ public Date getUpdateTime() {
+ return updateTime;
+ }
+
+ public void setUpdateTime(Date updateTime) {
+ this.updateTime = updateTime;
+ }
+
+}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobInfo.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobInfo.java
new file mode 100644
index 0000000..e47b6dc
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobInfo.java
@@ -0,0 +1,237 @@
+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 jobDesc;
+
+ private Date addTime;
+ private Date updateTime;
+
+ private String author; // 负责人
+ private String alarmEmail; // 报警邮件
+
+ private String scheduleType; // 调度类型
+ private String scheduleConf; // 调度配置,值含义取决于调度类型
+ private String misfireStrategy; // 调度过期策略
+
+ 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 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 getScheduleType() {
+ return scheduleType;
+ }
+
+ public void setScheduleType(String scheduleType) {
+ this.scheduleType = scheduleType;
+ }
+
+ public String getScheduleConf() {
+ return scheduleConf;
+ }
+
+ public void setScheduleConf(String scheduleConf) {
+ this.scheduleConf = scheduleConf;
+ }
+
+ public String getMisfireStrategy() {
+ return misfireStrategy;
+ }
+
+ public void setMisfireStrategy(String misfireStrategy) {
+ this.misfireStrategy = misfireStrategy;
+ }
+
+ 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/xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobLog.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobLog.java
new file mode 100644
index 0000000..7d3072a
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobLog.java
@@ -0,0 +1,157 @@
+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/xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobLogGlue.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobLogGlue.java
new file mode 100644
index 0000000..2f59ffa
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobLogGlue.java
@@ -0,0 +1,75 @@
+package com.xxl.job.admin.core.model;
+
+import java.util.Date;
+
+/**
+ * xxl-job log for glue, used to track job code process
+ * @author xuxueli 2016-5-19 17:57:46
+ */
+public class XxlJobLogGlue {
+
+ private int id;
+ private int jobId; // 任务主键ID
+ private String glueType; // GLUE类型 #com.xxl.job.core.glue.GlueTypeEnum
+ private String glueSource;
+ private String glueRemark;
+ private Date addTime;
+ private Date updateTime;
+
+ public int getId() {
+ return id;
+ }
+
+ public void setId(int id) {
+ this.id = id;
+ }
+
+ public int getJobId() {
+ return jobId;
+ }
+
+ public void setJobId(int jobId) {
+ this.jobId = jobId;
+ }
+
+ public String getGlueType() {
+ return glueType;
+ }
+
+ public void setGlueType(String glueType) {
+ this.glueType = glueType;
+ }
+
+ public String getGlueSource() {
+ return glueSource;
+ }
+
+ public void setGlueSource(String glueSource) {
+ this.glueSource = glueSource;
+ }
+
+ public String getGlueRemark() {
+ return glueRemark;
+ }
+
+ public void setGlueRemark(String glueRemark) {
+ this.glueRemark = glueRemark;
+ }
+
+ public Date getAddTime() {
+ return addTime;
+ }
+
+ public void setAddTime(Date addTime) {
+ this.addTime = addTime;
+ }
+
+ public Date getUpdateTime() {
+ return updateTime;
+ }
+
+ public void setUpdateTime(Date updateTime) {
+ this.updateTime = updateTime;
+ }
+
+}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobLogReport.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobLogReport.java
new file mode 100644
index 0000000..e58ff1a
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobLogReport.java
@@ -0,0 +1,54 @@
+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/xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobRegistry.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobRegistry.java
new file mode 100644
index 0000000..924d6d3
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobRegistry.java
@@ -0,0 +1,55 @@
+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/xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobUser.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobUser.java
new file mode 100644
index 0000000..db17327
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobUser.java
@@ -0,0 +1,73 @@
+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/xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/RemoteHttpJobBean.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/RemoteHttpJobBean.java
new file mode 100644
index 0000000..1cca089
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/RemoteHttpJobBean.java
@@ -0,0 +1,32 @@
+package com.xxl.job.admin.core.old;//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/xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/XxlJobDynamicScheduler.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/XxlJobDynamicScheduler.java
new file mode 100644
index 0000000..c0080d9
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/XxlJobDynamicScheduler.java
@@ -0,0 +1,413 @@
+package com.xxl.job.admin.core.old;//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 jakarta.servlet.http.HttpServletRequest;
+//import jakarta.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/xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/XxlJobThreadPool.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/XxlJobThreadPool.java
new file mode 100644
index 0000000..74f3f9d
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/XxlJobThreadPool.java
@@ -0,0 +1,58 @@
+package com.xxl.job.admin.core.old;//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/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/ExecutorRouteStrategyEnum.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/ExecutorRouteStrategyEnum.java
new file mode 100644
index 0000000..7fff93a
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/ExecutorRouteStrategyEnum.java
@@ -0,0 +1,48 @@
+package com.xxl.job.admin.core.route;
+
+import com.xxl.job.admin.core.route.strategy.*;
+import com.xxl.job.admin.core.util.I18nUtil;
+
+/**
+ * Created by xuxueli on 17/3/10.
+ */
+public enum ExecutorRouteStrategyEnum {
+
+ FIRST(I18nUtil.getString("jobconf_route_first"), new ExecutorRouteFirst()),
+ LAST(I18nUtil.getString("jobconf_route_last"), new ExecutorRouteLast()),
+ ROUND(I18nUtil.getString("jobconf_route_round"), new ExecutorRouteRound()),
+ RANDOM(I18nUtil.getString("jobconf_route_random"), new ExecutorRouteRandom()),
+ CONSISTENT_HASH(I18nUtil.getString("jobconf_route_consistenthash"), new ExecutorRouteConsistentHash()),
+ LEAST_FREQUENTLY_USED(I18nUtil.getString("jobconf_route_lfu"), new ExecutorRouteLFU()),
+ LEAST_RECENTLY_USED(I18nUtil.getString("jobconf_route_lru"), new ExecutorRouteLRU()),
+ FAILOVER(I18nUtil.getString("jobconf_route_failover"), new ExecutorRouteFailover()),
+ BUSYOVER(I18nUtil.getString("jobconf_route_busyover"), new ExecutorRouteBusyover()),
+ SHARDING_BROADCAST(I18nUtil.getString("jobconf_route_shard"), null);
+
+ ExecutorRouteStrategyEnum(String title, ExecutorRouter router) {
+ this.title = title;
+ this.router = router;
+ }
+
+ private String title;
+ private ExecutorRouter router;
+
+ public String getTitle() {
+ return title;
+ }
+ public ExecutorRouter getRouter() {
+ return router;
+ }
+
+ public static ExecutorRouteStrategyEnum match(String name, ExecutorRouteStrategyEnum defaultItem){
+ if (name != null) {
+ for (ExecutorRouteStrategyEnum item: ExecutorRouteStrategyEnum.values()) {
+ if (item.name().equals(name)) {
+ return item;
+ }
+ }
+ }
+ return defaultItem;
+ }
+
+}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/ExecutorRouter.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/ExecutorRouter.java
new file mode 100644
index 0000000..5de9a1d
--- /dev/null
+++ b/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/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteBusyover.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteBusyover.java
new file mode 100644
index 0000000..d4c3a9f
--- /dev/null
+++ b/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.route.ExecutorRouter;
+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.IdleBeatParam;
+import com.xxl.job.core.biz.model.ReturnT;
+import com.xxl.job.core.biz.model.TriggerParam;
+
+import java.util.List;
+
+/**
+ * Created by xuxueli on 17/3/10.
+ */
+public class ExecutorRouteBusyover extends ExecutorRouter {
+
+ @Override
+ public ReturnT route(TriggerParam triggerParam, List addressList) {
+ StringBuffer idleBeatResultSB = new StringBuffer();
+ for (String address : addressList) {
+ // beat
+ ReturnT idleBeatResult = null;
+ try {
+ ExecutorBiz executorBiz = XxlJobScheduler.getExecutorBiz(address);
+ idleBeatResult = executorBiz.idleBeat(new IdleBeatParam(triggerParam.getJobId()));
+ } catch (Exception e) {
+ logger.error(e.getMessage(), e);
+ idleBeatResult = new ReturnT(ReturnT.FAIL_CODE, "" + e);
+ }
+ idleBeatResultSB.append((idleBeatResultSB.length() > 0) ? "
" : "")
+ .append(I18nUtil.getString("jobconf_idleBeat") + ":")
+ .append("
address:").append(address)
+ .append("
code:").append(idleBeatResult.getCode())
+ .append("
msg:").append(idleBeatResult.getMsg());
+
+ // beat success
+ if (idleBeatResult.getCode() == ReturnT.SUCCESS_CODE) {
+ idleBeatResult.setMsg(idleBeatResultSB.toString());
+ idleBeatResult.setContent(address);
+ return idleBeatResult;
+ }
+ }
+
+ return new ReturnT(ReturnT.FAIL_CODE, idleBeatResultSB.toString());
+ }
+
+}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteConsistentHash.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteConsistentHash.java
new file mode 100644
index 0000000..41ac671
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteConsistentHash.java
@@ -0,0 +1,85 @@
+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/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteFailover.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteFailover.java
new file mode 100644
index 0000000..1576d35
--- /dev/null
+++ b/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.route.ExecutorRouter;
+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 java.util.List;
+
+/**
+ * Created by xuxueli on 17/3/10.
+ */
+public class ExecutorRouteFailover extends ExecutorRouter {
+
+ @Override
+ public ReturnT route(TriggerParam triggerParam, List addressList) {
+
+ StringBuffer beatResultSB = new StringBuffer();
+ for (String address : addressList) {
+ // beat
+ ReturnT beatResult = null;
+ try {
+ ExecutorBiz executorBiz = XxlJobScheduler.getExecutorBiz(address);
+ beatResult = executorBiz.beat();
+ } catch (Exception e) {
+ logger.error(e.getMessage(), e);
+ beatResult = new ReturnT(ReturnT.FAIL_CODE, "" + e);
+ }
+ beatResultSB.append((beatResultSB.length() > 0) ? "
" : "")
+ .append(I18nUtil.getString("jobconf_beat") + ":")
+ .append("
address:").append(address)
+ .append("
code:").append(beatResult.getCode())
+ .append("
msg:").append(beatResult.getMsg());
+
+ // beat success
+ if (beatResult.getCode() == ReturnT.SUCCESS_CODE) {
+
+ beatResult.setMsg(beatResultSB.toString());
+ beatResult.setContent(address);
+ return beatResult;
+ }
+ }
+ return new ReturnT(ReturnT.FAIL_CODE, beatResultSB.toString());
+
+ }
+}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteFirst.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteFirst.java
new file mode 100644
index 0000000..de4d7af
--- /dev/null
+++ b/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/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteLFU.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteLFU.java
new file mode 100644
index 0000000..9df1972
--- /dev/null
+++ b/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/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteLRU.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteLRU.java
new file mode 100644
index 0000000..2d54006
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteLRU.java
@@ -0,0 +1,76 @@
+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/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteLast.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteLast.java
new file mode 100644
index 0000000..4ff3cf6
--- /dev/null
+++ b/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/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteRandom.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteRandom.java
new file mode 100644
index 0000000..5ea4a38
--- /dev/null
+++ b/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/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteRound.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteRound.java
new file mode 100644
index 0000000..d0ea2ba
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteRound.java
@@ -0,0 +1,46 @@
+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/xxl-job-admin/src/main/java/com/xxl/job/admin/core/scheduler/MisfireStrategyEnum.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/scheduler/MisfireStrategyEnum.java
new file mode 100644
index 0000000..0b9b4a9
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/scheduler/MisfireStrategyEnum.java
@@ -0,0 +1,39 @@
+package com.xxl.job.admin.core.scheduler;
+
+import com.xxl.job.admin.core.util.I18nUtil;
+
+/**
+ * @author xuxueli 2020-10-29 21:11:23
+ */
+public enum MisfireStrategyEnum {
+
+ /**
+ * do nothing
+ */
+ DO_NOTHING(I18nUtil.getString("misfire_strategy_do_nothing")),
+
+ /**
+ * fire once now
+ */
+ FIRE_ONCE_NOW(I18nUtil.getString("misfire_strategy_fire_once_now"));
+
+ private String title;
+
+ MisfireStrategyEnum(String title) {
+ this.title = title;
+ }
+
+ public String getTitle() {
+ return title;
+ }
+
+ public static MisfireStrategyEnum match(String name, MisfireStrategyEnum defaultItem){
+ for (MisfireStrategyEnum item: MisfireStrategyEnum.values()) {
+ if (item.name().equals(name)) {
+ return item;
+ }
+ }
+ return defaultItem;
+ }
+
+}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/scheduler/ScheduleTypeEnum.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/scheduler/ScheduleTypeEnum.java
new file mode 100644
index 0000000..aa334fd
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/scheduler/ScheduleTypeEnum.java
@@ -0,0 +1,46 @@
+package com.xxl.job.admin.core.scheduler;
+
+import com.xxl.job.admin.core.util.I18nUtil;
+
+/**
+ * @author xuxueli 2020-10-29 21:11:23
+ */
+public enum ScheduleTypeEnum {
+
+ NONE(I18nUtil.getString("schedule_type_none")),
+
+ /**
+ * schedule by cron
+ */
+ CRON(I18nUtil.getString("schedule_type_cron")),
+
+ /**
+ * schedule by fixed rate (in seconds)
+ */
+ FIX_RATE(I18nUtil.getString("schedule_type_fix_rate")),
+
+ /**
+ * schedule by fix delay (in seconds), after the last time
+ */
+ /*FIX_DELAY(I18nUtil.getString("schedule_type_fix_delay"))*/;
+
+ private String title;
+
+ ScheduleTypeEnum(String title) {
+ this.title = title;
+ }
+
+ public String getTitle() {
+ return title;
+ }
+
+ public static ScheduleTypeEnum match(String name, ScheduleTypeEnum defaultItem){
+ for (ScheduleTypeEnum item: ScheduleTypeEnum.values()) {
+ if (item.name().equals(name)) {
+ return item;
+ }
+ }
+ return defaultItem;
+ }
+
+}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/scheduler/XxlJobScheduler.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/scheduler/XxlJobScheduler.java
new file mode 100644
index 0000000..bb2cda8
--- /dev/null
+++ b/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 trigger pool start
+ JobTriggerPoolHelper.toStart();
+
+ // admin registry monitor run
+ JobRegistryHelper.getInstance().start();
+
+ // admin fail-monitor run
+ JobFailMonitorHelper.getInstance().start();
+
+ // admin lose-monitor run ( depend on JobTriggerPoolHelper )
+ JobCompleteHelper.getInstance().start();
+
+ // admin log report start
+ JobLogReportHelper.getInstance().start();
+
+ // start-schedule ( depend on JobTriggerPoolHelper )
+ JobScheduleHelper.getInstance().start();
+
+ logger.info(">>>>>>>>> init xxl-job admin success.");
+ }
+
+
+ public void destroy() throws Exception {
+
+ // stop-schedule
+ JobScheduleHelper.getInstance().toStop();
+
+ // admin log report stop
+ JobLogReportHelper.getInstance().toStop();
+
+ // admin lose-monitor stop
+ JobCompleteHelper.getInstance().toStop();
+
+ // admin fail-monitor stop
+ JobFailMonitorHelper.getInstance().toStop();
+
+ // admin registry stop
+ JobRegistryHelper.getInstance().toStop();
+
+ // admin trigger pool stop
+ JobTriggerPoolHelper.toStop();
+
+ }
+
+ // ---------------------- I18n ----------------------
+
+ private void initI18n(){
+ for (ExecutorBlockStrategyEnum item:ExecutorBlockStrategyEnum.values()) {
+ item.setTitle(I18nUtil.getString("jobconf_block_".concat(item.name())));
+ }
+ }
+
+ // ---------------------- executor-client ----------------------
+ private static ConcurrentMap executorBizRepository = new ConcurrentHashMap();
+ public static ExecutorBiz getExecutorBiz(String address) throws Exception {
+ // valid
+ if (address==null || address.trim().length()==0) {
+ return null;
+ }
+
+ // load-cache
+ address = address.trim();
+ ExecutorBiz executorBiz = executorBizRepository.get(address);
+ if (executorBiz != null) {
+ return executorBiz;
+ }
+
+ // set-cache
+ executorBiz = new ExecutorBizClient(address, XxlJobAdminConfig.getAdminConfig().getAccessToken());
+
+ executorBizRepository.put(address, executorBiz);
+ return executorBiz;
+ }
+
+}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobCompleteHelper.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobCompleteHelper.java
new file mode 100644
index 0000000..5698926
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobCompleteHelper.java
@@ -0,0 +1,184 @@
+package com.xxl.job.admin.core.thread;
+
+import com.xxl.job.admin.core.complete.XxlJobCompleter;
+import com.xxl.job.admin.core.conf.XxlJobAdminConfig;
+import com.xxl.job.admin.core.model.XxlJobLog;
+import com.xxl.job.admin.core.util.I18nUtil;
+import com.xxl.job.core.biz.model.HandleCallbackParam;
+import com.xxl.job.core.biz.model.ReturnT;
+import com.xxl.job.core.util.DateUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Date;
+import java.util.List;
+import java.util.concurrent.*;
+
+/**
+ * job lose-monitor instance
+ *
+ * @author xuxueli 2015-9-1 18:05:56
+ */
+public class JobCompleteHelper {
+ private static Logger logger = LoggerFactory.getLogger(JobCompleteHelper.class);
+
+ private static JobCompleteHelper instance = new JobCompleteHelper();
+ public static JobCompleteHelper getInstance(){
+ return instance;
+ }
+
+ // ---------------------- monitor ----------------------
+
+ private ThreadPoolExecutor callbackThreadPool = null;
+ private Thread monitorThread;
+ private volatile boolean toStop = false;
+ public void start(){
+
+ // for callback
+ callbackThreadPool = new ThreadPoolExecutor(
+ 2,
+ 20,
+ 30L,
+ TimeUnit.SECONDS,
+ new LinkedBlockingQueue(3000),
+ new ThreadFactory() {
+ @Override
+ public Thread newThread(Runnable r) {
+ return new Thread(r, "xxl-job, admin JobLosedMonitorHelper-callbackThreadPool-" + r.hashCode());
+ }
+ },
+ new RejectedExecutionHandler() {
+ @Override
+ public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
+ r.run();
+ logger.warn(">>>>>>>>>>> xxl-job, callback too fast, match threadpool rejected handler(run now).");
+ }
+ });
+
+
+ // for monitor
+ monitorThread = new Thread(new Runnable() {
+
+ @Override
+ public void run() {
+
+ // wait for JobTriggerPoolHelper-init
+ try {
+ TimeUnit.MILLISECONDS.sleep(50);
+ } catch (InterruptedException e) {
+ if (!toStop) {
+ logger.error(e.getMessage(), e);
+ }
+ }
+
+ // monitor
+ while (!toStop) {
+ try {
+ // 任务结果丢失处理:调度记录停留在 "运行中" 状态超过10min,且对应执行器心跳注册失败不在线,则将本地调度主动标记失败;
+ Date losedTime = DateUtil.addMinutes(new Date(), -10);
+ List losedJobIds = XxlJobAdminConfig.getAdminConfig().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") );
+
+ XxlJobCompleter.updateHandleInfoAndFinish(jobLog);
+ }
+
+ }
+ } catch (Exception e) {
+ if (!toStop) {
+ logger.error(">>>>>>>>>>> xxl-job, job fail monitor thread error:{}", e);
+ }
+ }
+
+ try {
+ TimeUnit.SECONDS.sleep(60);
+ } catch (Exception e) {
+ if (!toStop) {
+ logger.error(e.getMessage(), e);
+ }
+ }
+
+ }
+
+ logger.info(">>>>>>>>>>> xxl-job, JobLosedMonitorHelper stop");
+
+ }
+ });
+ monitorThread.setDaemon(true);
+ monitorThread.setName("xxl-job, admin JobLosedMonitorHelper");
+ monitorThread.start();
+ }
+
+ public void toStop(){
+ toStop = true;
+
+ // stop registryOrRemoveThreadPool
+ callbackThreadPool.shutdownNow();
+
+ // stop monitorThread (interrupt and wait)
+ monitorThread.interrupt();
+ try {
+ monitorThread.join();
+ } catch (InterruptedException e) {
+ logger.error(e.getMessage(), e);
+ }
+ }
+
+
+ // ---------------------- helper ----------------------
+
+ public ReturnT callback(List callbackParamList) {
+
+ callbackThreadPool.execute(new Runnable() {
+ @Override
+ public void run() {
+ for (HandleCallbackParam handleCallbackParam: callbackParamList) {
+ ReturnT callbackResult = callback(handleCallbackParam);
+ logger.debug(">>>>>>>>> JobApiController.callback {}, handleCallbackParam={}, callbackResult={}",
+ (callbackResult.getCode()== ReturnT.SUCCESS_CODE?"success":"fail"), handleCallbackParam, callbackResult);
+ }
+ }
+ });
+
+ return ReturnT.SUCCESS;
+ }
+
+ private ReturnT callback(HandleCallbackParam handleCallbackParam) {
+ // valid log item
+ XxlJobLog log = XxlJobAdminConfig.getAdminConfig().getXxlJobLogDao().load(handleCallbackParam.getLogId());
+ if (log == null) {
+ return new ReturnT(ReturnT.FAIL_CODE, "log item not found.");
+ }
+ if (log.getHandleCode() > 0) {
+ return new ReturnT(ReturnT.FAIL_CODE, "log repeate callback."); // avoid repeat callback, trigger child job etc
+ }
+
+ // handle msg
+ StringBuffer handleMsg = new StringBuffer();
+ if (log.getHandleMsg()!=null) {
+ handleMsg.append(log.getHandleMsg()).append("
");
+ }
+ if (handleCallbackParam.getHandleMsg() != null) {
+ handleMsg.append(handleCallbackParam.getHandleMsg());
+ }
+
+ // success, save log
+ log.setHandleTime(new Date());
+ log.setHandleCode(handleCallbackParam.getHandleCode());
+ log.setHandleMsg(handleMsg.toString());
+ XxlJobCompleter.updateHandleInfoAndFinish(log);
+
+ return ReturnT.SUCCESS;
+ }
+
+
+
+}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobFailMonitorHelper.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobFailMonitorHelper.java
new file mode 100644
index 0000000..8409d7b
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobFailMonitorHelper.java
@@ -0,0 +1,110 @@
+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) {
+ 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/xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobLogReportHelper.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobLogReportHelper.java
new file mode 100644
index 0000000..2387a0c
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobLogReportHelper.java
@@ -0,0 +1,152 @@
+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/xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobRegistryHelper.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobRegistryHelper.java
new file mode 100644
index 0000000..37edfd9
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobRegistryHelper.java
@@ -0,0 +1,204 @@
+package com.xxl.job.admin.core.thread;
+
+import com.xxl.job.admin.core.conf.XxlJobAdminConfig;
+import com.xxl.job.admin.core.model.XxlJobGroup;
+import com.xxl.job.admin.core.model.XxlJobRegistry;
+import com.xxl.job.core.biz.model.RegistryParam;
+import com.xxl.job.core.biz.model.ReturnT;
+import com.xxl.job.core.enums.RegistryConfig;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.util.StringUtils;
+
+import java.util.*;
+import java.util.concurrent.*;
+
+/**
+ * job registry instance
+ * @author xuxueli 2016-10-02 19:10:24
+ */
+public class JobRegistryHelper {
+ private static Logger logger = LoggerFactory.getLogger(JobRegistryHelper.class);
+
+ private static JobRegistryHelper instance = new JobRegistryHelper();
+ public static JobRegistryHelper getInstance(){
+ return instance;
+ }
+
+ private ThreadPoolExecutor registryOrRemoveThreadPool = null;
+ private Thread registryMonitorThread;
+ private volatile boolean toStop = false;
+
+ public void start(){
+
+ // for registry or remove
+ registryOrRemoveThreadPool = new ThreadPoolExecutor(
+ 2,
+ 10,
+ 30L,
+ TimeUnit.SECONDS,
+ new LinkedBlockingQueue(2000),
+ new ThreadFactory() {
+ @Override
+ public Thread newThread(Runnable r) {
+ return new Thread(r, "xxl-job, admin JobRegistryMonitorHelper-registryOrRemoveThreadPool-" + r.hashCode());
+ }
+ },
+ new RejectedExecutionHandler() {
+ @Override
+ public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
+ r.run();
+ logger.warn(">>>>>>>>>>> xxl-job, registry or remove too fast, match threadpool rejected handler(run now).");
+ }
+ });
+
+ // for monitor
+ registryMonitorThread = new Thread(new Runnable() {
+ @Override
+ public void run() {
+ while (!toStop) {
+ try {
+ // auto registry group
+ List groupList = XxlJobAdminConfig.getAdminConfig().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);
+ group.setUpdateTime(new Date());
+
+ 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");
+ }
+ });
+ registryMonitorThread.setDaemon(true);
+ registryMonitorThread.setName("xxl-job, admin JobRegistryMonitorHelper-registryMonitorThread");
+ registryMonitorThread.start();
+ }
+
+ public void toStop(){
+ toStop = true;
+
+ // stop registryOrRemoveThreadPool
+ registryOrRemoveThreadPool.shutdownNow();
+
+ // stop monitir (interrupt and wait)
+ registryMonitorThread.interrupt();
+ try {
+ registryMonitorThread.join();
+ } catch (InterruptedException e) {
+ logger.error(e.getMessage(), e);
+ }
+ }
+
+
+ // ---------------------- helper ----------------------
+
+ public ReturnT registry(RegistryParam registryParam) {
+
+ // valid
+ if (!StringUtils.hasText(registryParam.getRegistryGroup())
+ || !StringUtils.hasText(registryParam.getRegistryKey())
+ || !StringUtils.hasText(registryParam.getRegistryValue())) {
+ return new ReturnT(ReturnT.FAIL_CODE, "Illegal Argument.");
+ }
+
+ // async execute
+ registryOrRemoveThreadPool.execute(new Runnable() {
+ @Override
+ public void run() {
+ int ret = XxlJobAdminConfig.getAdminConfig().getXxlJobRegistryDao().registryUpdate(registryParam.getRegistryGroup(), registryParam.getRegistryKey(), registryParam.getRegistryValue(), new Date());
+ if (ret < 1) {
+ XxlJobAdminConfig.getAdminConfig().getXxlJobRegistryDao().registrySave(registryParam.getRegistryGroup(), registryParam.getRegistryKey(), registryParam.getRegistryValue(), new Date());
+
+ // fresh
+ freshGroupRegistryInfo(registryParam);
+ }
+ }
+ });
+
+ return ReturnT.SUCCESS;
+ }
+
+ public ReturnT registryRemove(RegistryParam registryParam) {
+
+ // valid
+ if (!StringUtils.hasText(registryParam.getRegistryGroup())
+ || !StringUtils.hasText(registryParam.getRegistryKey())
+ || !StringUtils.hasText(registryParam.getRegistryValue())) {
+ return new ReturnT(ReturnT.FAIL_CODE, "Illegal Argument.");
+ }
+
+ // async execute
+ registryOrRemoveThreadPool.execute(new Runnable() {
+ @Override
+ public void run() {
+ int ret = XxlJobAdminConfig.getAdminConfig().getXxlJobRegistryDao().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/xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobScheduleHelper.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobScheduleHelper.java
new file mode 100644
index 0000000..831bcf6
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobScheduleHelper.java
@@ -0,0 +1,369 @@
+package com.xxl.job.admin.core.thread;
+
+import com.xxl.job.admin.core.conf.XxlJobAdminConfig;
+import com.xxl.job.admin.core.cron.CronExpression;
+import com.xxl.job.admin.core.model.XxlJobInfo;
+import com.xxl.job.admin.core.scheduler.MisfireStrategyEnum;
+import com.xxl.job.admin.core.scheduler.ScheduleTypeEnum;
+import com.xxl.job.admin.core.trigger.TriggerTypeEnum;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @author xuxueli 2019-05-21
+ */
+public class JobScheduleHelper {
+ private static Logger logger = LoggerFactory.getLogger(JobScheduleHelper.class);
+
+ private static JobScheduleHelper instance = new JobScheduleHelper();
+ public static JobScheduleHelper getInstance(){
+ return instance;
+ }
+
+ public static final long PRE_READ_MS = 5000; // pre read
+
+ private Thread scheduleThread;
+ private Thread ringThread;
+ private volatile boolean scheduleThreadToStop = false;
+ private volatile boolean ringThreadToStop = false;
+ private volatile static Map> ringData = new ConcurrentHashMap<>();
+
+ public void start(){
+
+ // schedule thread
+ scheduleThread = new Thread(new Runnable() {
+ @Override
+ public void run() {
+
+ try {
+ TimeUnit.MILLISECONDS.sleep(5000 - System.currentTimeMillis()%1000 );
+ } catch (InterruptedException e) {
+ if (!scheduleThreadToStop) {
+ logger.error(e.getMessage(), e);
+ }
+ }
+ logger.info(">>>>>>>>> init xxl-job admin scheduler success.");
+
+ // pre-read count: treadpool-size * trigger-qps (each trigger cost 50ms, qps = 1000/50 = 20)
+ int preReadCount = (XxlJobAdminConfig.getAdminConfig().getTriggerPoolFastMax() + XxlJobAdminConfig.getAdminConfig().getTriggerPoolSlowMax()) * 20;
+
+ while (!scheduleThreadToStop) {
+
+ // Scan Job
+ long start = System.currentTimeMillis();
+
+ Connection conn = null;
+ Boolean connAutoCommit = 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());
+
+ // 1、misfire match
+ MisfireStrategyEnum misfireStrategyEnum = MisfireStrategyEnum.match(jobInfo.getMisfireStrategy(), MisfireStrategyEnum.DO_NOTHING);
+ if (MisfireStrategyEnum.FIRE_ONCE_NOW == misfireStrategyEnum) {
+ // FIRE_ONCE_NOW 》 trigger
+ JobTriggerPoolHelper.trigger(jobInfo.getId(), TriggerTypeEnum.MISFIRE, -1, null, null, null);
+ logger.debug(">>>>>>>>>>> xxl-job, schedule push trigger : jobId = " + jobInfo.getId() );
+ }
+
+ // 2、fresh next
+ refreshNextValidTime(jobInfo, new Date());
+
+ } else if (nowTime > jobInfo.getTriggerNextTime()) {
+ // 2.2、trigger-expire < 5s:direct-trigger && make next-trigger-time
+
+ // 1、trigger
+ JobTriggerPoolHelper.trigger(jobInfo.getId(), TriggerTypeEnum.CRON, -1, null, null, null);
+ logger.debug(">>>>>>>>>>> xxl-job, schedule push trigger : jobId = " + jobInfo.getId() );
+
+ // 2、fresh next
+ refreshNextValidTime(jobInfo, new Date());
+
+ // next-trigger-time in 5s, pre-read again
+ if (jobInfo.getTriggerStatus()==1 && nowTime + PRE_READ_MS > jobInfo.getTriggerNextTime()) {
+
+ // 1、make ring second
+ int ringSecond = (int)((jobInfo.getTriggerNextTime()/1000)%60);
+
+ // 2、push time ring
+ pushTimeRing(ringSecond, jobInfo.getId());
+
+ // 3、fresh next
+ refreshNextValidTime(jobInfo, new Date(jobInfo.getTriggerNextTime()));
+
+ }
+
+ } else {
+ // 2.3、trigger-pre-read:time-ring trigger && make next-trigger-time
+
+ // 1、make ring second
+ int ringSecond = (int)((jobInfo.getTriggerNextTime()/1000)%60);
+
+ // 2、push time ring
+ pushTimeRing(ringSecond, jobInfo.getId());
+
+ // 3、fresh next
+ refreshNextValidTime(jobInfo, new Date(jobInfo.getTriggerNextTime()));
+
+ }
+
+ }
+
+ // 3、update trigger info
+ for (XxlJobInfo jobInfo: scheduleList) {
+ XxlJobAdminConfig.getAdminConfig().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() {
+
+ while (!ringThreadToStop) {
+
+ // align second
+ try {
+ TimeUnit.MILLISECONDS.sleep(1000 - System.currentTimeMillis() % 1000);
+ } catch (InterruptedException e) {
+ if (!ringThreadToStop) {
+ logger.error(e.getMessage(), e);
+ }
+ }
+
+ try {
+ // second data
+ List ringItemData = new ArrayList<>();
+ int nowSecond = Calendar.getInstance().get(Calendar.SECOND); // 避免处理耗时太长,跨过刻度,向前校验一个刻度;
+ for (int i = 0; i < 2; i++) {
+ List tmpData = ringData.remove( (nowSecond+60-i)%60 );
+ if (tmpData != null) {
+ ringItemData.addAll(tmpData);
+ }
+ }
+
+ // ring trigger
+ logger.debug(">>>>>>>>>>> xxl-job, time-ring beat : " + nowSecond + " = " + Arrays.asList(ringItemData) );
+ if (ringItemData.size() > 0) {
+ // do trigger
+ for (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);
+ }
+ }
+ }
+ logger.info(">>>>>>>>>>> xxl-job, JobScheduleHelper#ringThread stop");
+ }
+ });
+ ringThread.setDaemon(true);
+ ringThread.setName("xxl-job, admin JobScheduleHelper#ringThread");
+ ringThread.start();
+ }
+
+ private void refreshNextValidTime(XxlJobInfo jobInfo, Date fromTime) throws Exception {
+ Date nextValidTime = generateNextValidTime(jobInfo, fromTime);
+ if (nextValidTime != null) {
+ jobInfo.setTriggerLastTime(jobInfo.getTriggerNextTime());
+ jobInfo.setTriggerNextTime(nextValidTime.getTime());
+ } else {
+ jobInfo.setTriggerStatus(0);
+ jobInfo.setTriggerLastTime(0);
+ jobInfo.setTriggerNextTime(0);
+ logger.warn(">>>>>>>>>>> xxl-job, refreshNextValidTime fail for job: jobId={}, scheduleType={}, scheduleConf={}",
+ jobInfo.getId(), jobInfo.getScheduleType(), jobInfo.getScheduleConf());
+ }
+ }
+
+ 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");
+ }
+
+
+ // ---------------------- tools ----------------------
+ public static Date generateNextValidTime(XxlJobInfo jobInfo, Date fromTime) throws Exception {
+ ScheduleTypeEnum scheduleTypeEnum = ScheduleTypeEnum.match(jobInfo.getScheduleType(), null);
+ if (ScheduleTypeEnum.CRON == scheduleTypeEnum) {
+ Date nextValidTime = new CronExpression(jobInfo.getScheduleConf()).getNextValidTimeAfter(fromTime);
+ return nextValidTime;
+ } else if (ScheduleTypeEnum.FIX_RATE == scheduleTypeEnum /*|| ScheduleTypeEnum.FIX_DELAY == scheduleTypeEnum*/) {
+ return new Date(fromTime.getTime() + Integer.valueOf(jobInfo.getScheduleConf())*1000 );
+ }
+ return null;
+ }
+
+}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobTriggerPoolHelper.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobTriggerPoolHelper.java
new file mode 100644
index 0000000..398713d
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobTriggerPoolHelper.java
@@ -0,0 +1,150 @@
+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/xxl-job-admin/src/main/java/com/xxl/job/admin/core/trigger/TriggerTypeEnum.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/trigger/TriggerTypeEnum.java
new file mode 100644
index 0000000..446c90e
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/trigger/TriggerTypeEnum.java
@@ -0,0 +1,27 @@
+package com.xxl.job.admin.core.trigger;
+
+import com.xxl.job.admin.core.util.I18nUtil;
+
+/**
+ * trigger type enum
+ *
+ * @author xuxueli 2018-09-16 04:56:41
+ */
+public enum TriggerTypeEnum {
+
+ MANUAL(I18nUtil.getString("jobconf_trigger_type_manual")),
+ CRON(I18nUtil.getString("jobconf_trigger_type_cron")),
+ RETRY(I18nUtil.getString("jobconf_trigger_type_retry")),
+ PARENT(I18nUtil.getString("jobconf_trigger_type_parent")),
+ API(I18nUtil.getString("jobconf_trigger_type_api")),
+ MISFIRE(I18nUtil.getString("jobconf_trigger_type_misfire"));
+
+ private TriggerTypeEnum(String title){
+ this.title = title;
+ }
+ private String title;
+ public String getTitle() {
+ return title;
+ }
+
+}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/trigger/XxlJobTrigger.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/trigger/XxlJobTrigger.java
new file mode 100644
index 0000000..748befc
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/trigger/XxlJobTrigger.java
@@ -0,0 +1,226 @@
+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/xxl-job-admin/src/main/java/com/xxl/job/admin/core/util/CookieUtil.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/util/CookieUtil.java
new file mode 100644
index 0000000..8c2ae7a
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/util/CookieUtil.java
@@ -0,0 +1,98 @@
+package com.xxl.job.admin.core.util;
+
+import jakarta.servlet.http.Cookie;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+
+/**
+ * Cookie.Util
+ *
+ * @author xuxueli 2015-12-12 18:01:06
+ */
+public class CookieUtil {
+
+ // 默认缓存时间,单位/秒, 2H
+ private static final int COOKIE_MAX_AGE = Integer.MAX_VALUE;
+ // 保存路径,根路径
+ private static final String COOKIE_PATH = "/";
+
+ /**
+ * 保存
+ *
+ * @param response
+ * @param key
+ * @param value
+ * @param ifRemember
+ */
+ public static void set(HttpServletResponse response, String key, String value, boolean ifRemember) {
+ int age = ifRemember ? COOKIE_MAX_AGE : -1;
+ set(response, key, value, null, COOKIE_PATH, age, true);
+ }
+
+ /**
+ * 保存
+ *
+ * @param response
+ * @param key
+ * @param value
+ * @param maxAge
+ */
+ private static void set(HttpServletResponse response, String key, String value, String domain, String path, int maxAge, boolean isHttpOnly) {
+ Cookie cookie = new Cookie(key, value);
+ if (domain != null) {
+ cookie.setDomain(domain);
+ }
+ cookie.setPath(path);
+ cookie.setMaxAge(maxAge);
+ cookie.setHttpOnly(isHttpOnly);
+ response.addCookie(cookie);
+ }
+
+ /**
+ * 查询value
+ *
+ * @param request
+ * @param key
+ * @return
+ */
+ public static String getValue(HttpServletRequest request, String key) {
+ Cookie cookie = get(request, key);
+ if (cookie != null) {
+ return cookie.getValue();
+ }
+ return null;
+ }
+
+ /**
+ * 查询Cookie
+ *
+ * @param request
+ * @param key
+ */
+ private static Cookie get(HttpServletRequest request, String key) {
+ Cookie[] arr_cookie = request.getCookies();
+ if (arr_cookie != null && arr_cookie.length > 0) {
+ for (Cookie cookie : arr_cookie) {
+ if (cookie.getName().equals(key)) {
+ return cookie;
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * 删除Cookie
+ *
+ * @param request
+ * @param response
+ * @param key
+ */
+ public static void remove(HttpServletRequest request, HttpServletResponse response, String key) {
+ Cookie cookie = get(request, key);
+ if (cookie != null) {
+ set(response, key, "", null, COOKIE_PATH, 0, true);
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/util/FtlUtil.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/util/FtlUtil.java
new file mode 100644
index 0000000..e90af43
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/util/FtlUtil.java
@@ -0,0 +1,31 @@
+package com.xxl.job.admin.core.util;
+
+import freemarker.ext.beans.BeansWrapper;
+import freemarker.ext.beans.BeansWrapperBuilder;
+import freemarker.template.Configuration;
+import freemarker.template.TemplateHashModel;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * ftl util
+ *
+ * @author xuxueli 2018-01-17 20:37:48
+ */
+public class FtlUtil {
+ private static Logger logger = LoggerFactory.getLogger(FtlUtil.class);
+
+ private static BeansWrapper wrapper = new BeansWrapperBuilder(Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS).build(); //BeansWrapper.getDefaultInstance();
+
+ public static TemplateHashModel generateStaticModel(String packageName) {
+ try {
+ TemplateHashModel staticModels = wrapper.getStaticModels();
+ TemplateHashModel fileStatics = (TemplateHashModel) staticModels.get(packageName);
+ return fileStatics;
+ } catch (Exception e) {
+ logger.error(e.getMessage(), e);
+ }
+ return null;
+ }
+
+}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/util/I18nUtil.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/util/I18nUtil.java
new file mode 100644
index 0000000..772a96e
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/util/I18nUtil.java
@@ -0,0 +1,79 @@
+package com.xxl.job.admin.core.util;
+
+import com.xxl.job.admin.core.conf.XxlJobAdminConfig;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.core.io.Resource;
+import org.springframework.core.io.support.EncodedResource;
+import org.springframework.core.io.support.PropertiesLoaderUtils;
+
+import java.io.IOException;
+import java.text.MessageFormat;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+
+/**
+ * i18n util
+ *
+ * @author xuxueli 2018-01-17 20:39:06
+ */
+public class I18nUtil {
+ private static Logger logger = LoggerFactory.getLogger(I18nUtil.class);
+
+ private static Properties prop = null;
+ public static Properties loadI18nProp(){
+ if (prop != null) {
+ return prop;
+ }
+ try {
+ // build i18n prop
+ String i18n = XxlJobAdminConfig.getAdminConfig().getI18n();
+ String i18nFile = MessageFormat.format("i18n/message_{0}.properties", i18n);
+
+ // load prop
+ Resource resource = new ClassPathResource(i18nFile);
+ EncodedResource encodedResource = new EncodedResource(resource,"UTF-8");
+ prop = PropertiesLoaderUtils.loadProperties(encodedResource);
+ } catch (IOException e) {
+ logger.error(e.getMessage(), e);
+ }
+ return prop;
+ }
+
+ /**
+ * get val of i18n key
+ *
+ * @param key
+ * @return
+ */
+ public static String getString(String key) {
+ return loadI18nProp().getProperty(key);
+ }
+
+ /**
+ * get mult val of i18n mult key, as json
+ *
+ * @param keys
+ * @return
+ */
+ public static String getMultString(String... keys) {
+ Map map = new HashMap();
+
+ Properties prop = loadI18nProp();
+ if (keys!=null && keys.length>0) {
+ for (String key: keys) {
+ map.put(key, prop.getProperty(key));
+ }
+ } else {
+ for (String key: prop.stringPropertyNames()) {
+ map.put(key, prop.getProperty(key));
+ }
+ }
+
+ String json = JacksonUtil.writeValueAsString(map);
+ return json;
+ }
+
+}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/util/JacksonUtil.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/util/JacksonUtil.java
new file mode 100644
index 0000000..4f4ea3c
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/util/JacksonUtil.java
@@ -0,0 +1,92 @@
+package com.xxl.job.admin.core.util;
+
+import com.fasterxml.jackson.core.JsonGenerationException;
+import com.fasterxml.jackson.core.JsonParseException;
+import com.fasterxml.jackson.databind.JavaType;
+import com.fasterxml.jackson.databind.JsonMappingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+
+/**
+ * Jackson util
+ *
+ * 1、obj need private and set/get;
+ * 2、do not support inner class;
+ *
+ * @author xuxueli 2015-9-25 18:02:56
+ */
+public class JacksonUtil {
+ private static Logger logger = LoggerFactory.getLogger(JacksonUtil.class);
+
+ private final static ObjectMapper objectMapper = new ObjectMapper();
+ public static ObjectMapper getInstance() {
+ return objectMapper;
+ }
+
+ /**
+ * bean、array、List、Map --> json
+ *
+ * @param obj
+ * @return json string
+ * @throws Exception
+ */
+ public static String writeValueAsString(Object obj) {
+ try {
+ return getInstance().writeValueAsString(obj);
+ } catch (JsonGenerationException e) {
+ logger.error(e.getMessage(), e);
+ } catch (JsonMappingException e) {
+ logger.error(e.getMessage(), e);
+ } catch (IOException e) {
+ logger.error(e.getMessage(), e);
+ }
+ return null;
+ }
+
+ /**
+ * string --> bean、Map、List(array)
+ *
+ * @param jsonStr
+ * @param clazz
+ * @return obj
+ * @throws Exception
+ */
+ public static T readValue(String jsonStr, Class clazz) {
+ try {
+ return getInstance().readValue(jsonStr, clazz);
+ } catch (JsonParseException e) {
+ logger.error(e.getMessage(), e);
+ } catch (JsonMappingException e) {
+ logger.error(e.getMessage(), e);
+ } catch (IOException e) {
+ logger.error(e.getMessage(), e);
+ }
+ return null;
+ }
+
+ /**
+ * string --> List...
+ *
+ * @param jsonStr
+ * @param parametrized
+ * @param parameterClasses
+ * @param
+ * @return
+ */
+ public static T readValue(String jsonStr, Class> parametrized, Class>... parameterClasses) {
+ try {
+ JavaType javaType = getInstance().getTypeFactory().constructParametricType(parametrized, parameterClasses);
+ return getInstance().readValue(jsonStr, javaType);
+ } catch (JsonParseException e) {
+ logger.error(e.getMessage(), e);
+ } catch (JsonMappingException e) {
+ logger.error(e.getMessage(), e);
+ } catch (IOException e) {
+ logger.error(e.getMessage(), e);
+ }
+ return null;
+ }
+}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/util/LocalCacheUtil.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/util/LocalCacheUtil.java
new file mode 100644
index 0000000..fbab061
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/util/LocalCacheUtil.java
@@ -0,0 +1,133 @@
+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()) {
+ cacheRepository.remove(key);
+ }
+ }
+ }
+ return true;
+ }
+
+}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/dao/XxlJobGroupDao.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/dao/XxlJobGroupDao.java
new file mode 100644
index 0000000..b608d9f
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/dao/XxlJobGroupDao.java
@@ -0,0 +1,37 @@
+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/xxl-job-admin/src/main/java/com/xxl/job/admin/dao/XxlJobInfoDao.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/dao/XxlJobInfoDao.java
new file mode 100644
index 0000000..d640eff
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/dao/XxlJobInfoDao.java
@@ -0,0 +1,49 @@
+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/xxl-job-admin/src/main/java/com/xxl/job/admin/dao/XxlJobLogDao.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/dao/XxlJobLogDao.java
new file mode 100644
index 0000000..62fa3b4
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/dao/XxlJobLogDao.java
@@ -0,0 +1,62 @@
+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/xxl-job-admin/src/main/java/com/xxl/job/admin/dao/XxlJobLogGlueDao.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/dao/XxlJobLogGlueDao.java
new file mode 100644
index 0000000..3028aed
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/dao/XxlJobLogGlueDao.java
@@ -0,0 +1,24 @@
+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/xxl-job-admin/src/main/java/com/xxl/job/admin/dao/XxlJobLogReportDao.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/dao/XxlJobLogReportDao.java
new file mode 100644
index 0000000..f4b3dc8
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/dao/XxlJobLogReportDao.java
@@ -0,0 +1,26 @@
+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/xxl-job-admin/src/main/java/com/xxl/job/admin/dao/XxlJobRegistryDao.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/dao/XxlJobRegistryDao.java
new file mode 100644
index 0000000..1005c46
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/dao/XxlJobRegistryDao.java
@@ -0,0 +1,38 @@
+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/xxl-job-admin/src/main/java/com/xxl/job/admin/dao/XxlJobUserDao.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/dao/XxlJobUserDao.java
new file mode 100644
index 0000000..064ce19
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/dao/XxlJobUserDao.java
@@ -0,0 +1,33 @@
+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/xxl-job-admin/src/main/java/com/xxl/job/admin/service/LoginService.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/service/LoginService.java
new file mode 100644
index 0000000..79ddbde
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/service/LoginService.java
@@ -0,0 +1,108 @@
+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 jakarta.annotation.Resource;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.util.DigestUtils;
+
+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(final XxlJobUser xxlJobUser) {
+ final String tokenJson = JacksonUtil.writeValueAsString(xxlJobUser);
+ final String tokenHex = new BigInteger(tokenJson.getBytes()).toString(16);
+ return tokenHex;
+ }
+
+ private XxlJobUser parseToken(final String tokenHex) {
+ XxlJobUser xxlJobUser = null;
+ if (tokenHex != null) {
+ final String tokenJson = new String(new BigInteger(tokenHex, 16).toByteArray()); // username_password(md5)
+ xxlJobUser = JacksonUtil.readValue(tokenJson, XxlJobUser.class);
+ }
+ return xxlJobUser;
+ }
+
+
+ public ReturnT login(final HttpServletRequest request, final HttpServletResponse response, final String username, final String password, final 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
+ final XxlJobUser xxlJobUser = this.xxlJobUserDao.loadByUserName(username);
+ if (xxlJobUser == null) {
+ return new ReturnT(500, I18nUtil.getString("login_param_unvalid"));
+ }
+ final String passwordMd5 = DigestUtils.md5DigestAsHex(password.getBytes());
+ if (!passwordMd5.equals(xxlJobUser.getPassword())) {
+ return new ReturnT(500, I18nUtil.getString("login_param_unvalid"));
+ }
+
+ final String loginToken = this.makeToken(xxlJobUser);
+
+ // do login
+ CookieUtil.set(response, LoginService.LOGIN_IDENTITY_KEY, loginToken, ifRemember);
+ return ReturnT.SUCCESS;
+ }
+
+ /**
+ * logout
+ *
+ * @param request
+ * @param response
+ */
+ public ReturnT logout(final HttpServletRequest request, final HttpServletResponse response) {
+ CookieUtil.remove(request, response, LoginService.LOGIN_IDENTITY_KEY);
+ return ReturnT.SUCCESS;
+ }
+
+ /**
+ * logout
+ *
+ * @param request
+ * @return
+ */
+ public XxlJobUser ifLogin(final HttpServletRequest request, final HttpServletResponse response) {
+ final String cookieToken = CookieUtil.getValue(request, LoginService.LOGIN_IDENTITY_KEY);
+ if (cookieToken != null) {
+ XxlJobUser cookieUser = null;
+ try {
+ cookieUser = this.parseToken(cookieToken);
+ } catch (final Exception e) {
+ this.logout(request, response);
+ }
+ if (cookieUser != null) {
+ final XxlJobUser dbUser = this.xxlJobUserDao.loadByUserName(cookieUser.getUsername());
+ if (dbUser != null) {
+ if (cookieUser.getPassword().equals(dbUser.getPassword())) {
+ return dbUser;
+ }
+ }
+ }
+ }
+ return null;
+ }
+
+
+}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/service/XxlJobService.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/service/XxlJobService.java
new file mode 100644
index 0000000..60b4bb8
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/service/XxlJobService.java
@@ -0,0 +1,98 @@
+package com.xxl.job.admin.service;
+
+
+import com.xxl.job.admin.core.model.XxlJobInfo;
+import com.xxl.job.admin.core.model.XxlJobUser;
+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);
+
+ /**
+ * trigger
+ *
+ * @param loginUser
+ * @param jobId
+ * @param executorParam
+ * @param addressList
+ * @return
+ */
+ public ReturnT trigger(XxlJobUser loginUser, int jobId, String executorParam, String addressList);
+
+ /**
+ * dashboard info
+ *
+ * @return
+ */
+ public Map dashboardInfo();
+
+ /**
+ * chart info
+ *
+ * @param startDate
+ * @param endDate
+ * @return
+ */
+ public ReturnT> chartInfo(Date startDate, Date endDate);
+
+}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/service/impl/AdminBizImpl.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/service/impl/AdminBizImpl.java
new file mode 100644
index 0000000..3c01e94
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/service/impl/AdminBizImpl.java
@@ -0,0 +1,35 @@
+package com.xxl.job.admin.service.impl;
+
+import com.xxl.job.admin.core.thread.JobCompleteHelper;
+import com.xxl.job.admin.core.thread.JobRegistryHelper;
+import com.xxl.job.core.biz.AdminBiz;
+import com.xxl.job.core.biz.model.HandleCallbackParam;
+import com.xxl.job.core.biz.model.RegistryParam;
+import com.xxl.job.core.biz.model.ReturnT;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+/**
+ * @author xuxueli 2017-07-27 21:54:20
+ */
+@Service
+public class AdminBizImpl implements AdminBiz {
+
+
+ @Override
+ public ReturnT callback(List callbackParamList) {
+ return JobCompleteHelper.getInstance().callback(callbackParamList);
+ }
+
+ @Override
+ public ReturnT registry(RegistryParam registryParam) {
+ return JobRegistryHelper.getInstance().registry(registryParam);
+ }
+
+ @Override
+ public ReturnT registryRemove(RegistryParam registryParam) {
+ return JobRegistryHelper.getInstance().registryRemove(registryParam);
+ }
+
+}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/service/impl/XxlJobServiceImpl.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/service/impl/XxlJobServiceImpl.java
new file mode 100644
index 0000000..a8a9600
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/service/impl/XxlJobServiceImpl.java
@@ -0,0 +1,472 @@
+package com.xxl.job.admin.service.impl;
+
+import com.xxl.job.admin.core.cron.CronExpression;
+import com.xxl.job.admin.core.model.XxlJobGroup;
+import com.xxl.job.admin.core.model.XxlJobInfo;
+import com.xxl.job.admin.core.model.XxlJobLogReport;
+import com.xxl.job.admin.core.model.XxlJobUser;
+import com.xxl.job.admin.core.route.ExecutorRouteStrategyEnum;
+import com.xxl.job.admin.core.scheduler.MisfireStrategyEnum;
+import com.xxl.job.admin.core.scheduler.ScheduleTypeEnum;
+import com.xxl.job.admin.core.thread.JobScheduleHelper;
+import com.xxl.job.admin.core.thread.JobTriggerPoolHelper;
+import com.xxl.job.admin.core.trigger.TriggerTypeEnum;
+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 jakarta.annotation.Resource;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Service;
+
+import java.text.MessageFormat;
+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
+ public XxlJobLogDao xxlJobLogDao;
+ @Resource
+ private XxlJobGroupDao xxlJobGroupDao;
+ @Resource
+ private XxlJobInfoDao xxlJobInfoDao;
+ @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 base
+ 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 (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")));
+ }
+
+ // valid trigger
+ ScheduleTypeEnum scheduleTypeEnum = ScheduleTypeEnum.match(jobInfo.getScheduleType(), null);
+ if (scheduleTypeEnum == null) {
+ return new ReturnT(ReturnT.FAIL_CODE, (I18nUtil.getString("schedule_type") + I18nUtil.getString("system_unvalid")));
+ }
+ if (scheduleTypeEnum == ScheduleTypeEnum.CRON) {
+ if (jobInfo.getScheduleConf() == null || !CronExpression.isValidExpression(jobInfo.getScheduleConf())) {
+ return new ReturnT(ReturnT.FAIL_CODE, "Cron" + I18nUtil.getString("system_unvalid"));
+ }
+ } else if (scheduleTypeEnum == ScheduleTypeEnum.FIX_RATE/* || scheduleTypeEnum == ScheduleTypeEnum.FIX_DELAY*/) {
+ if (jobInfo.getScheduleConf() == null) {
+ return new ReturnT(ReturnT.FAIL_CODE, (I18nUtil.getString("schedule_type")));
+ }
+ try {
+ int fixSecond = Integer.valueOf(jobInfo.getScheduleConf());
+ if (fixSecond < 1) {
+ return new ReturnT(ReturnT.FAIL_CODE, (I18nUtil.getString("schedule_type") + I18nUtil.getString("system_unvalid")));
+ }
+ } catch (Exception e) {
+ return new ReturnT(ReturnT.FAIL_CODE, (I18nUtil.getString("schedule_type") + I18nUtil.getString("system_unvalid")));
+ }
+ }
+
+ // valid job
+ 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", ""));
+ }
+
+ // valid advanced
+ if (ExecutorRouteStrategyEnum.match(jobInfo.getExecutorRouteStrategy(), null) == null) {
+ return new ReturnT(ReturnT.FAIL_CODE, (I18nUtil.getString("jobinfo_field_executorRouteStrategy") + I18nUtil.getString("system_unvalid")));
+ }
+ if (MisfireStrategyEnum.match(jobInfo.getMisfireStrategy(), null) == null) {
+ return new ReturnT(ReturnT.FAIL_CODE, (I18nUtil.getString("misfire_strategy") + 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);
+ }
+
+ // 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 base
+ 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")));
+ }
+
+ // valid trigger
+ ScheduleTypeEnum scheduleTypeEnum = ScheduleTypeEnum.match(jobInfo.getScheduleType(), null);
+ if (scheduleTypeEnum == null) {
+ return new ReturnT(ReturnT.FAIL_CODE, (I18nUtil.getString("schedule_type") + I18nUtil.getString("system_unvalid")));
+ }
+ if (scheduleTypeEnum == ScheduleTypeEnum.CRON) {
+ if (jobInfo.getScheduleConf() == null || !CronExpression.isValidExpression(jobInfo.getScheduleConf())) {
+ return new ReturnT(ReturnT.FAIL_CODE, "Cron" + I18nUtil.getString("system_unvalid"));
+ }
+ } else if (scheduleTypeEnum == ScheduleTypeEnum.FIX_RATE /*|| scheduleTypeEnum == ScheduleTypeEnum.FIX_DELAY*/) {
+ if (jobInfo.getScheduleConf() == null) {
+ return new ReturnT(ReturnT.FAIL_CODE, (I18nUtil.getString("schedule_type") + I18nUtil.getString("system_unvalid")));
+ }
+ try {
+ int fixSecond = Integer.valueOf(jobInfo.getScheduleConf());
+ if (fixSecond < 1) {
+ return new ReturnT(ReturnT.FAIL_CODE, (I18nUtil.getString("schedule_type") + I18nUtil.getString("system_unvalid")));
+ }
+ } catch (Exception e) {
+ return new ReturnT(ReturnT.FAIL_CODE, (I18nUtil.getString("schedule_type") + I18nUtil.getString("system_unvalid")));
+ }
+ }
+
+ // valid advanced
+ if (ExecutorRouteStrategyEnum.match(jobInfo.getExecutorRouteStrategy(), null) == null) {
+ return new ReturnT(ReturnT.FAIL_CODE, (I18nUtil.getString("jobinfo_field_executorRouteStrategy") + I18nUtil.getString("system_unvalid")));
+ }
+ if (MisfireStrategyEnum.match(jobInfo.getMisfireStrategy(), null) == null) {
+ return new ReturnT(ReturnT.FAIL_CODE, (I18nUtil.getString("misfire_strategy") + 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();
+ boolean scheduleDataNotChanged = jobInfo.getScheduleType().equals(exists_jobInfo.getScheduleType()) && jobInfo.getScheduleConf().equals(exists_jobInfo.getScheduleConf());
+ if (exists_jobInfo.getTriggerStatus() == 1 && !scheduleDataNotChanged) {
+ try {
+ Date nextValidTime = JobScheduleHelper.generateNextValidTime(jobInfo, new Date(System.currentTimeMillis() + JobScheduleHelper.PRE_READ_MS));
+ if (nextValidTime == null) {
+ return new ReturnT(ReturnT.FAIL_CODE, (I18nUtil.getString("schedule_type") + I18nUtil.getString("system_unvalid")));
+ }
+ nextTriggerTime = nextValidTime.getTime();
+ } catch (Exception e) {
+ logger.error(e.getMessage(), e);
+ return new ReturnT(ReturnT.FAIL_CODE, (I18nUtil.getString("schedule_type") + I18nUtil.getString("system_unvalid")));
+ }
+ }
+
+ exists_jobInfo.setJobGroup(jobInfo.getJobGroup());
+ exists_jobInfo.setJobDesc(jobInfo.getJobDesc());
+ exists_jobInfo.setAuthor(jobInfo.getAuthor());
+ exists_jobInfo.setAlarmEmail(jobInfo.getAlarmEmail());
+ exists_jobInfo.setScheduleType(jobInfo.getScheduleType());
+ exists_jobInfo.setScheduleConf(jobInfo.getScheduleConf());
+ exists_jobInfo.setMisfireStrategy(jobInfo.getMisfireStrategy());
+ 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);
+
+ // valid
+ ScheduleTypeEnum scheduleTypeEnum = ScheduleTypeEnum.match(xxlJobInfo.getScheduleType(), ScheduleTypeEnum.NONE);
+ if (ScheduleTypeEnum.NONE == scheduleTypeEnum) {
+ return new ReturnT(ReturnT.FAIL_CODE, (I18nUtil.getString("schedule_type_none_limit_start")));
+ }
+
+ // next trigger time (5s后生效,避开预读周期)
+ long nextTriggerTime = 0;
+ try {
+ Date nextValidTime = JobScheduleHelper.generateNextValidTime(xxlJobInfo, new Date(System.currentTimeMillis() + JobScheduleHelper.PRE_READ_MS));
+ if (nextValidTime == null) {
+ return new ReturnT(ReturnT.FAIL_CODE, (I18nUtil.getString("schedule_type") + I18nUtil.getString("system_unvalid")));
+ }
+ nextTriggerTime = nextValidTime.getTime();
+ } catch (Exception e) {
+ logger.error(e.getMessage(), e);
+ return new ReturnT(ReturnT.FAIL_CODE, (I18nUtil.getString("schedule_type") + I18nUtil.getString("system_unvalid")));
+ }
+
+ 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 ReturnT trigger(XxlJobUser loginUser, int jobId, String executorParam, String addressList) {
+ // permission
+ if (loginUser == null) {
+ return new ReturnT(ReturnT.FAIL.getCode(), I18nUtil.getString("system_permission_limit"));
+ }
+ XxlJobInfo xxlJobInfo = xxlJobInfoDao.loadById(jobId);
+ if (xxlJobInfo == null) {
+ return new ReturnT(ReturnT.FAIL.getCode(), I18nUtil.getString("jobinfo_glue_jobid_unvalid"));
+ }
+ if (!hasPermission(loginUser, xxlJobInfo.getJobGroup())) {
+ return new ReturnT(ReturnT.FAIL.getCode(), I18nUtil.getString("system_permission_limit"));
+ }
+
+ // force cover job param
+ if (executorParam == null) {
+ executorParam = "";
+ }
+
+ JobTriggerPoolHelper.trigger(jobId, TriggerTypeEnum.MANUAL, -1, null, executorParam, addressList);
+ return ReturnT.SUCCESS;
+ }
+
+ private boolean hasPermission(XxlJobUser loginUser, int jobGroup) {
+ if (loginUser.getRole() == 1) {
+ return true;
+ }
+ List groupIdStrs = new ArrayList<>();
+ if (loginUser.getPermission() != null && loginUser.getPermission().trim().length() > 0) {
+ groupIdStrs = Arrays.asList(loginUser.getPermission().trim().split(","));
+ }
+ return groupIdStrs.contains(String.valueOf(jobGroup));
+ }
+
+ @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