升级xxl-job 3.4.2

This commit is contained in:
JEECG 2026-07-07 13:42:15 +08:00
parent 3a33fc7964
commit dfcf9ebea3
262 changed files with 38443 additions and 9871 deletions

File diff suppressed because it is too large Load Diff

View File

@ -335,11 +335,14 @@
<groupId>com.squareup.okhttp3</groupId> <groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId> <artifactId>okhttp</artifactId>
</dependency> </dependency>
<!-- 2026-07-06 for【SpringBoot4】jackson-module-kotlin Jackson2不兼容Jackson3spring-cloud-function 5.0 JsonMapper初始化时 ClassCastException: KotlinModule cannot be cast to tools.jackson.databind.JacksonModule暂时注释 -->
<!-- 解决okhttp引用了kotlin,应用启动有警告日志问题 --> <!-- 解决okhttp引用了kotlin,应用启动有警告日志问题 -->
<!--
<dependency> <dependency>
<groupId>com.fasterxml.jackson.module</groupId> <groupId>com.fasterxml.jackson.module</groupId>
<artifactId>jackson-module-kotlin</artifactId> <artifactId>jackson-module-kotlin</artifactId>
</dependency> </dependency>
-->
<dependency> <dependency>
<groupId>commons-fileupload</groupId> <groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId> <artifactId>commons-fileupload</artifactId>

View File

@ -39,6 +39,12 @@
<groupId>org.jeecgframework.boot3</groupId> <groupId>org.jeecgframework.boot3</groupId>
<artifactId>jeecg-boot-starter-lock</artifactId> <artifactId>jeecg-boot-starter-lock</artifactId>
</dependency> </dependency>
<!-- spring-cloud-function 5.0 需要 joda-time 但声明为 optional显式引入 -->
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
<version>2.12.7</version>
</dependency>
</dependencies> </dependencies>
</project> </project>

View File

@ -2,9 +2,8 @@
package org.jeecg.modules.test.xxljob; package org.jeecg.modules.test.xxljob;
import com.xxl.job.core.biz.model.ReturnT; import com.xxl.tool.response.Response;
import com.xxl.job.core.context.XxlJobHelper; import com.xxl.job.core.context.XxlJobHelper;
import com.xxl.job.core.handler.IJobHandler;
import com.xxl.job.core.handler.annotation.XxlJob; import com.xxl.job.core.handler.annotation.XxlJob;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@ -15,7 +14,6 @@ import java.io.InputStreamReader;
import java.net.HttpURLConnection; import java.net.HttpURLConnection;
import java.net.URL; import java.net.URL;
import java.util.Arrays; import java.util.Arrays;
import com.xxl.job.core.context.XxlJobHelper;
/** /**
* xxl-job定时任务测试 * xxl-job定时任务测试
@ -34,16 +32,16 @@ public class DemoJobHandler {
* @return * @return
*/ */
@XxlJob(value = "demoJob") @XxlJob(value = "demoJob")
public ReturnT<String> demoJobHandler(String params) { public Response<String> demoJobHandler(String params) {
log.info("我是 jeecg-system 服务里的定时任务 demoJob我执行了..............................."); log.info("我是 jeecg-system 服务里的定时任务 demoJob我执行了...............................");
return ReturnT.SUCCESS; return Response.ofSuccess();
} }
/** /**
* 2分片广播任务 * 2分片广播任务
*/ */
@XxlJob("shardingJobHandler") @XxlJob("shardingJobHandler")
public ReturnT<String> shardingJobHandler(String param) throws Exception { public Response<String> shardingJobHandler(String param) throws Exception {
// 获取分片序号和总分片数 // 获取分片序号和总分片数
int shardIndex = XxlJobHelper.getShardIndex(); int shardIndex = XxlJobHelper.getShardIndex();
@ -59,7 +57,7 @@ public class DemoJobHandler {
} }
} }
return ReturnT.SUCCESS; return Response.ofSuccess();
} }
@ -69,7 +67,7 @@ public class DemoJobHandler {
* 输入参数ipconfig /all * 输入参数ipconfig /all
*/ */
@XxlJob("commandJobHandler") @XxlJob("commandJobHandler")
public ReturnT<String> commandJobHandler(String param) throws Exception { public Response<String> commandJobHandler(String param) throws Exception {
String command = param; String command = param;
int exitValue = -1; int exitValue = -1;
@ -98,9 +96,9 @@ public class DemoJobHandler {
} }
if (exitValue == 0) { if (exitValue == 0) {
return ReturnT.SUCCESS; return Response.ofSuccess();
} else { } else {
return new ReturnT<String>(ReturnT.FAIL_CODE, "command exit value(" + exitValue + ") is failed"); return Response.ofFail("command exit value(" + exitValue + ") is failed");
} }
} }
@ -114,13 +112,13 @@ public class DemoJobHandler {
* data: content * data: content
*/ */
@XxlJob("httpJobHandler") @XxlJob("httpJobHandler")
public ReturnT<String> httpJobHandler(String param) throws Exception { public Response<String> httpJobHandler(String param) throws Exception {
String[] methodArray=new String[]{"GET","POST"}; String[] methodArray=new String[]{"GET","POST"};
int okState=200; int okState=200;
// param parse // param parse
if (param == null || param.trim().length() == 0) { if (param == null || param.trim().length() == 0) {
log.info("param[" + param + "] invalid."); log.info("param[" + param + "] invalid.");
return ReturnT.FAIL; return Response.ofFail();
} }
String[] httpParams = param.split("\n"); String[] httpParams = param.split("\n");
String url = null; String url = null;
@ -141,11 +139,11 @@ public class DemoJobHandler {
// param valid // param valid
if (url == null || url.trim().length() == 0) { if (url == null || url.trim().length() == 0) {
log.info("url[" + url + "] invalid."); log.info("url[" + url + "] invalid.");
return ReturnT.FAIL; return Response.ofFail();
} }
if (method == null || !Arrays.asList(methodArray).contains(method)) { if (method == null || !Arrays.asList(methodArray).contains(method)) {
log.info("method[" + method + "] invalid."); log.info("method[" + method + "] invalid.");
return ReturnT.FAIL; return Response.ofFail();
} }
// request // request
@ -194,10 +192,10 @@ public class DemoJobHandler {
String responseMsg = result.toString(); String responseMsg = result.toString();
log.info(responseMsg); log.info(responseMsg);
return ReturnT.SUCCESS; return Response.ofSuccess();
} catch (Exception e) { } catch (Exception e) {
log.info(e.getMessage(),e); log.info(e.getMessage(),e);
return ReturnT.FAIL; return Response.ofFail();
} finally { } finally {
try { try {
if (bufferedReader != null) { if (bufferedReader != null) {
@ -218,9 +216,9 @@ public class DemoJobHandler {
* 5生命周期任务示例任务初始化与销毁时支持自定义相关逻辑 * 5生命周期任务示例任务初始化与销毁时支持自定义相关逻辑
*/ */
@XxlJob(value = "demoJobHandler2", init = "init", destroy = "destroy") @XxlJob(value = "demoJobHandler2", init = "init", destroy = "destroy")
public ReturnT<String> demoJobHandler2(String param) throws Exception { public Response<String> demoJobHandler2(String param) throws Exception {
log.info("XXL-JOB, Hello World."); log.info("XXL-JOB, Hello World.");
return ReturnT.SUCCESS; return Response.ofSuccess();
} }
public void init() { public void init() {

View File

@ -1,8 +1,8 @@
package org.jeecg.modules.test.xxljob; package org.jeecg.modules.test.xxljob;
import com.xxl.job.core.biz.model.ReturnT;
import com.xxl.job.core.handler.annotation.XxlJob; import com.xxl.job.core.handler.annotation.XxlJob;
import com.xxl.tool.response.Response;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@ -24,9 +24,9 @@ public class XxclJobTest {
*/ */
@XxlJob(value = "xxclJobTest") @XxlJob(value = "xxclJobTest")
public ReturnT<String> demoJobHandler(String params) { public Response<String> demoJobHandler(String params) {
log.info("我是 jeecg-system 服务里的定时任务 xxclJobTest , 我执行了..............................."); log.info("我是 jeecg-system 服务里的定时任务 xxclJobTest , 我执行了...............................");
return ReturnT.SUCCESS; return Response.ofSuccess();
} }
public void init() { public void init() {

View File

@ -1,75 +1,83 @@
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent> <parent>
<artifactId>jeecg-visual</artifactId> <artifactId>jeecg-visual</artifactId>
<groupId>org.jeecgframework.boot3</groupId> <groupId>org.jeecgframework.boot3</groupId>
<version>3.9.2</version> <version>3.9.2</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <packaging>jar</packaging>
<artifactId>jeecg-cloud-xxljob</artifactId> <artifactId>jeecg-cloud-xxljob</artifactId>
<properties>
<maven.deploy.skip>true</maven.deploy.skip>
<xxl-tool.version>2.5.0</xxl-tool.version>
<xxl-sso-core.version>2.4.0</xxl-sso-core.version>
</properties>
<dependencies> <dependencies>
<!-- starter-web -->
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId> <artifactId>spring-boot-starter-web</artifactId>
</dependency> </dependency>
<!-- starter-testjunit + spring-test + mockito --> <!-- starter-test -->
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId> <artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<!-- freemarker-starter --> <!-- freemarker-starter -->
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId> <artifactId>spring-boot-starter-freemarker</artifactId>
</dependency> </dependency>
<!-- mail-starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
<!-- starter-actuator --> <!-- starter-actuator -->
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId> <artifactId>spring-boot-starter-actuator</artifactId>
</dependency> </dependency>
<!-- mybatis-startermybatis + mybatis-spring + hikaridefault --> <!-- mybatis-starter -->
<dependency> <dependency>
<groupId>org.mybatis.spring.boot</groupId> <groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId> <artifactId>mybatis-spring-boot-starter</artifactId>
<version>3.0.3</version> <version>4.0.1</version>
</dependency> </dependency>
<!-- mysql --> <!-- mysql -->
<dependency> <dependency>
<groupId>mysql</groupId> <groupId>com.mysql</groupId>
<artifactId>mysql-connector-java</artifactId> <artifactId>mysql-connector-j</artifactId>
<version>${mysql-connector-java.version}</version>
<scope>runtime</scope>
</dependency>
<!-- mail-starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency> </dependency>
<!-- xxl-job-core -->
<dependency> <dependency>
<groupId>com.xuxueli</groupId> <groupId>com.xuxueli</groupId>
<artifactId>xxl-job-core</artifactId> <artifactId>xxl-job-core</artifactId>
<version>${xxl-job-core.version}</version> <version>${xxl-job-core.version}</version>
</dependency> </dependency>
<!-- xxl-sso -->
<dependency>
<groupId>com.xuxueli</groupId>
<artifactId>xxl-sso-core</artifactId>
<version>${xxl-sso-core.version}</version>
</dependency>
</dependencies> </dependencies>
<!-- update-begin-author:taoyan date:20210226 for:docker部署报错no main manifest attribute -->
<build> <build>
<plugins> <plugins>
<plugin> <plugin>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId> <artifactId>spring-boot-maven-plugin</artifactId>
<executions> <executions>
<execution> <execution>
<phase>package</phase> <phase>package</phase>
@ -98,5 +106,5 @@
</plugin> </plugin>
</plugins> </plugins>
</build> </build>
<!-- update-end-author:taoyan date:20210226 for:docker部署报错no main manifest attribute -->
</project> </project>

View File

@ -17,10 +17,9 @@ public class XxlJobAdminApplication {
ConfigurableApplicationContext application = SpringApplication.run(XxlJobAdminApplication.class, args); ConfigurableApplicationContext application = SpringApplication.run(XxlJobAdminApplication.class, args);
Environment env = application.getEnvironment(); Environment env = application.getEnvironment();
String port = env.getProperty("server.port"); String port = env.getProperty("server.port");
String path = env.getProperty("server.servlet.context-path");
log.info("\n----------------------------------------------------------\n\t" + log.info("\n----------------------------------------------------------\n\t" +
"Application XxlJobAdmin is running! Access URLs:\n\t" + "Application XxlJobAdmin is running! Access URLs:\n\t" +
"Local: \t\thttp://localhost:" + port + path + "/\n\t" + "Local: \t\thttp://localhost:" + port + "\n\t" +
"----------------------------------------------------------"); "----------------------------------------------------------");
} }

View File

@ -0,0 +1,44 @@
package com.xxl.job.admin.business.constant;
/**
* Trigger Status
*
* @author xuxueli 2019-05-04
*/
public enum TriggerStatus {
/**
* Stopped
*/
STOPPED(0, "stopped"),
/**
* Running
*/
RUNNING(1, "running");
private int value;
private String desc;
TriggerStatus(int value, String desc) {
this.value = value;
this.desc = desc;
}
public int getValue() {
return value;
}
public void setValue(int value) {
this.value = value;
}
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,6 +1,6 @@
package com.xxl.job.admin.dao; package com.xxl.job.admin.business.mapper;
import com.xxl.job.admin.core.model.XxlJobInfo; import com.xxl.job.admin.business.model.XxlJobInfo;
import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Param;
@ -12,7 +12,7 @@ import java.util.List;
* @author xuxueli 2016-1-12 18:03:45 * @author xuxueli 2016-1-12 18:03:45
*/ */
@Mapper @Mapper
public interface XxlJobInfoDao { public interface XxlJobInfoMapper {
public List<XxlJobInfo> pageList(@Param("offset") int offset, public List<XxlJobInfo> pageList(@Param("offset") int offset,
@Param("pagesize") int pagesize, @Param("pagesize") int pagesize,
@ -41,9 +41,32 @@ public interface XxlJobInfoDao {
public int findAllCount(); public int findAllCount();
/**
* find schedule job, limit "trigger_status = 1"
*
* @param maxNextTime
* @param pagesize
* @return
*/
public List<XxlJobInfo> scheduleJobQuery(@Param("maxNextTime") long maxNextTime, @Param("pagesize") int pagesize ); public List<XxlJobInfo> scheduleJobQuery(@Param("maxNextTime") long maxNextTime, @Param("pagesize") int pagesize );
/**
* update schedule job
*
* 1can only update "trigger_status = 1", Avoid stopping tasks from being opened
* 2valid "triggerStatus gte 0", filter illegal state
*
* @param xxlJobInfo
* @return
*/
public int scheduleUpdate(XxlJobInfo xxlJobInfo); public int scheduleUpdate(XxlJobInfo xxlJobInfo);
/**
* batch update job info
*
* @param jobInfoList
* @return
*/
public int scheduleBatchUpdate(@Param("list") List<XxlJobInfo> jobInfoList);
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,172 @@
package com.xxl.job.admin.business.model.dto;
import com.xxl.job.admin.business.model.XxlJobLog;
import com.xxl.tool.core.DateTool;
public class XxlJobLogDTO {
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 String triggerTime;
private int triggerCode;
private String triggerMsg;
// handle info
private String handleTime;
private int handleCode;
private String handleMsg;
// alarm info
private int alarmStatus;
public XxlJobLogDTO(XxlJobLog xxlJobLog) {
this.id = xxlJobLog.getId();
this.jobGroup = xxlJobLog.getJobGroup();
this.jobId = xxlJobLog.getJobId();
this.executorAddress = xxlJobLog.getExecutorAddress();
this.executorHandler = xxlJobLog.getExecutorHandler();
this.executorParam = xxlJobLog.getExecutorParam();
this.executorShardingParam = xxlJobLog.getExecutorShardingParam();
this.executorFailRetryCount = xxlJobLog.getExecutorFailRetryCount();
this.triggerTime = xxlJobLog.getTriggerTime() != null ? DateTool.formatDateTime(xxlJobLog.getTriggerTime()) : null;
this.triggerCode = xxlJobLog.getTriggerCode();
this.triggerMsg = xxlJobLog.getTriggerMsg();
this.handleTime = xxlJobLog.getHandleTime() != null ? DateTool.formatDateTime(xxlJobLog.getHandleTime()) : null;
this.handleCode = xxlJobLog.getHandleCode();
this.handleMsg = xxlJobLog.getHandleMsg();
this.alarmStatus = xxlJobLog.getAlarmStatus();
}
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 String getTriggerTime() {
return triggerTime;
}
public void setTriggerTime(String 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 String getHandleTime() {
return handleTime;
}
public void setHandleTime(String 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;
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,5 +1,7 @@
package com.xxl.job.admin.business.scheduler.cron;
/* /*
* All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved.
* Copyright IBM Corp. 2024, 2025
* *
* Licensed under the Apache License, Version 2.0 (the "License"); you may not * Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy * use this file except in compliance with the License. You may obtain a copy
@ -13,10 +15,9 @@
* License for the specific language governing permissions and limitations * License for the specific language governing permissions and limitations
* under the License. * under the License.
* *
* Borrowed from quartz v2.5.2
*/ */
package com.xxl.job.admin.core.cron;
import java.io.Serializable; import java.io.Serializable;
import java.text.ParseException; import java.text.ParseException;
import java.util.Calendar; import java.util.Calendar;
@ -25,6 +26,7 @@ import java.util.HashMap;
import java.util.Iterator; import java.util.Iterator;
import java.util.Locale; import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.Optional;
import java.util.SortedSet; import java.util.SortedSet;
import java.util.StringTokenizer; import java.util.StringTokenizer;
import java.util.TimeZone; import java.util.TimeZone;
@ -35,83 +37,86 @@ import java.util.TreeSet;
* expressions provide the ability to specify complex time combinations such as * expressions provide the ability to specify complex time combinations such as
* &quot;At 8:00am every Monday through Friday&quot; or &quot;At 1:30am every * &quot;At 8:00am every Monday through Friday&quot; or &quot;At 1:30am every
* last Friday of the month&quot;. * last Friday of the month&quot;.
* <P> * <p>
* Cron expressions are comprised of 6 required fields and one optional field * Cron expressions are comprised of 6 required fields and one optional field
* separated by white space. The fields respectively are described as follows: * separated by white space. The fields respectively are described as follows:
* * </p>
* <table cellspacing="8"> * <table>
* <caption>Examples of cron expressions and their meanings.</caption>
* <tr> * <tr>
* <th align="left">Field Name</th> * <th>Field Name</th>
* <th align="left">&nbsp;</th> * <th>&nbsp;</th>
* <th align="left">Allowed Values</th> * <th>Allowed Values</th>
* <th align="left">&nbsp;</th> * <th>&nbsp;</th>
* <th align="left">Allowed Special Characters</th> * <th>Allowed Special Characters</th>
* </tr> * </tr>
* <tr> * <tr>
* <td align="left"><code>Seconds</code></td> * <td><code>Seconds</code></td>
* <td align="left">&nbsp;</th> * <td>&nbsp;</td>
* <td align="left"><code>0-59</code></td> * <td><code>0-59</code></td>
* <td align="left">&nbsp;</th> * <td>&nbsp;</td>
* <td align="left"><code>, - * /</code></td> * <td><code>, - * /</code></td>
* </tr> * </tr>
* <tr> * <tr>
* <td align="left"><code>Minutes</code></td> * <td><code>Minutes</code></td>
* <td align="left">&nbsp;</th> * <td>&nbsp;</td>
* <td align="left"><code>0-59</code></td> * <td><code>0-59</code></td>
* <td align="left">&nbsp;</th> * <td>&nbsp;</td>
* <td align="left"><code>, - * /</code></td> * <td><code>, - * /</code></td>
* </tr> * </tr>
* <tr> * <tr>
* <td align="left"><code>Hours</code></td> * <td><code>Hours</code></td>
* <td align="left">&nbsp;</th> * <td>&nbsp;</td>
* <td align="left"><code>0-23</code></td> * <td><code>0-23</code></td>
* <td align="left">&nbsp;</th> * <td>&nbsp;</td>
* <td align="left"><code>, - * /</code></td> * <td><code>, - * /</code></td>
* </tr> * </tr>
* <tr> * <tr>
* <td align="left"><code>Day-of-month</code></td> * <td><code>Day-of-month</code></td>
* <td align="left">&nbsp;</th> * <td>&nbsp;</td>
* <td align="left"><code>1-31</code></td> * <td><code>1-31</code></td>
* <td align="left">&nbsp;</th> * <td>&nbsp;</td>
* <td align="left"><code>, - * ? / L W</code></td> * <td><code>, - * ? / L W</code></td>
* </tr> * </tr>
* <tr> * <tr>
* <td align="left"><code>Month</code></td> * <td><code>Month</code></td>
* <td align="left">&nbsp;</th> * <td>&nbsp;</td>
* <td align="left"><code>0-11 or JAN-DEC</code></td> * <td><code>1-12 or JAN-DEC</code></td>
* <td align="left">&nbsp;</th> * <td>&nbsp;</td>
* <td align="left"><code>, - * /</code></td> * <td><code>, - * /</code></td>
* </tr> * </tr>
* <tr> * <tr>
* <td align="left"><code>Day-of-Week</code></td> * <td><code>Day-of-Week</code></td>
* <td align="left">&nbsp;</th> * <td>&nbsp;</td>
* <td align="left"><code>1-7 or SUN-SAT</code></td> * <td><code>1-7 or SUN-SAT</code></td>
* <td align="left">&nbsp;</th> * <td>&nbsp;</td>
* <td align="left"><code>, - * ? / L #</code></td> * <td><code>, - * ? / L #</code></td>
* </tr> * </tr>
* <tr> * <tr>
* <td align="left"><code>Year (Optional)</code></td> * <td><code>Year (Optional)</code></td>
* <td align="left">&nbsp;</th> * <td>&nbsp;</td>
* <td align="left"><code>empty, 1970-2199</code></td> * <td><code>empty, 1970-2199</code></td>
* <td align="left">&nbsp;</th> * <td>&nbsp;</td>
* <td align="left"><code>, - * /</code></td> * <td><code>, - * /</code></td>
* </tr> * </tr>
* </table> * </table>
* <P> * <p>
* The '*' character is used to specify all values. For example, &quot;*&quot; * The '*' character is used to specify all values. For example, &quot;*&quot;
* in the minute field means &quot;every minute&quot;. * in the minute field means &quot;every minute&quot;.
* <P> * </p>
* <p>
* The '?' character is allowed for the day-of-month and day-of-week fields. It * The '?' character is allowed for the day-of-month and day-of-week fields. It
* is used to specify 'no specific value'. This is useful when you need to * is used to specify 'no specific value'. This is useful when you need to
* specify something in one of the two fields, but not the other. * specify something in one of the two fields, but not the other.
* <P> * <p>
* The '-' character is used to specify ranges For example &quot;10-12&quot; in * The '-' character is used to specify ranges For example &quot;10-12&quot; in
* the hour field means &quot;the hours 10, 11 and 12&quot;. * the hour field means &quot;the hours 10, 11 and 12&quot;.
* <P> * <p>
* The ',' character is used to specify additional values. For example * The ',' character is used to specify additional values. For example
* &quot;MON,WED,FRI&quot; in the day-of-week field means &quot;the days Monday, * &quot;MON,WED,FRI&quot; in the day-of-week field means &quot;the days Monday,
* Wednesday, and Friday&quot;. * Wednesday, and Friday&quot;.
* <P> * </p>
* <p>
* The '/' character is used to specify increments. For example &quot;0/15&quot; * The '/' character is used to specify increments. For example &quot;0/15&quot;
* in the seconds field means &quot;the seconds 0, 15, 30, and 45&quot;. And * in the seconds field means &quot;the seconds 0, 15, 30, and 45&quot;. And
* &quot;5/15&quot; in the seconds field means &quot;the seconds 5, 20, 35, and * &quot;5/15&quot; in the seconds field means &quot;the seconds 5, 20, 35, and
@ -123,7 +128,8 @@ import java.util.TreeSet;
* on every &quot;nth&quot; value in the given set. Thus &quot;7/6&quot; in the * on every &quot;nth&quot; value in the given set. Thus &quot;7/6&quot; in the
* month field only turns on month &quot;7&quot;, it does NOT mean every 6th * month field only turns on month &quot;7&quot;, it does NOT mean every 6th
* month, please note that subtlety. * month, please note that subtlety.
* <P> * </p>
* <p>
* The 'L' character is allowed for the day-of-month and day-of-week fields. * The 'L' character is allowed for the day-of-month and day-of-week fields.
* This character is short-hand for &quot;last&quot;, but it has different * This character is short-hand for &quot;last&quot;, but it has different
* meaning in each of the two fields. For example, the value &quot;L&quot; in * meaning in each of the two fields. For example, the value &quot;L&quot; in
@ -136,7 +142,8 @@ import java.util.TreeSet;
* from the last day of the month, such as "L-3" which would mean the third-to-last * from the last day of the month, such as "L-3" which would mean the third-to-last
* day of the calendar month. <i>When using the 'L' option, it is important not to * day of the calendar month. <i>When using the 'L' option, it is important not to
* specify lists, or ranges of values, as you'll get confusing/unexpected results.</i> * specify lists, or ranges of values, as you'll get confusing/unexpected results.</i>
* <P> * </p>
* <p>
* The 'W' character is allowed for the day-of-month field. This character * The 'W' character is allowed for the day-of-month field. This character
* is used to specify the weekday (Monday-Friday) nearest the given day. As an * is used to specify the weekday (Monday-Friday) nearest the given day. As an
* example, if you were to specify &quot;15W&quot; as the value for the * example, if you were to specify &quot;15W&quot; as the value for the
@ -148,11 +155,13 @@ import java.util.TreeSet;
* 1st is a Saturday, the trigger will fire on Monday the 3rd, as it will not * 1st is a Saturday, the trigger will fire on Monday the 3rd, as it will not
* 'jump' over the boundary of a month's days. The 'W' character can only be * 'jump' over the boundary of a month's days. The 'W' character can only be
* specified when the day-of-month is a single day, not a range or list of days. * specified when the day-of-month is a single day, not a range or list of days.
* <P> * </p>
* <p>
* The 'L' and 'W' characters can also be combined for the day-of-month * The 'L' and 'W' characters can also be combined for the day-of-month
* expression to yield 'LW', which translates to &quot;last weekday of the * expression to yield 'LW', which translates to &quot;last weekday of the
* month&quot;. * month&quot;.
* <P> * </p>
* <p>
* The '#' character is allowed for the day-of-week field. This character is * The '#' character is allowed for the day-of-week field. This character is
* used to specify &quot;the nth&quot; XXX day of the month. For example, the * used to specify &quot;the nth&quot; XXX day of the month. For example, the
* value of &quot;6#3&quot; in the day-of-week field means the third Friday of * value of &quot;6#3&quot; in the day-of-week field means the third Friday of
@ -163,7 +172,7 @@ import java.util.TreeSet;
* no firing will occur that month. If the '#' character is used, there can * no firing will occur that month. If the '#' character is used, there can
* only be one expression in the day-of-week field (&quot;3#1,6#3&quot; is * only be one expression in the day-of-week field (&quot;3#1,6#3&quot; is
* not valid, since there are two expressions). * not valid, since there are two expressions).
* <P> * </p>
* <!--The 'C' character is allowed for the day-of-month and day-of-week fields. * <!--The 'C' character is allowed for the day-of-month and day-of-week fields.
* This character is short-hand for "calendar". This means values are * This character is short-hand for "calendar". This means values are
* calculated against the associated calendar, if any. If no calendar is * calculated against the associated calendar, if any. If no calendar is
@ -171,12 +180,13 @@ import java.util.TreeSet;
* value of "5C" in the day-of-month field means "the first day included by the * value of "5C" in the day-of-month field means "the first day included by the
* calendar on or after the 5th". A value of "1C" in the day-of-week field * calendar on or after the 5th". A value of "1C" in the day-of-week field
* means "the first day included by the calendar on or after Sunday".--> * means "the first day included by the calendar on or after Sunday".-->
* <P> * <p>
* The legal characters and the names of months and days of the week are not * The legal characters and the names of months and days of the week are not
* case sensitive. * case sensitive.
* *
* <p> * <p>
* <b>NOTES:</b> * <b>NOTES:</b>
* </p>
* <ul> * <ul>
* <li>Support for specifying both a day-of-week and a day-of-month value is * <li>Support for specifying both a day-of-week and a day-of-month value is
* not complete (you'll need to use the '?' character in one of these fields). * not complete (you'll need to use the '?' character in one of these fields).
@ -189,15 +199,11 @@ import java.util.TreeSet;
* interpretation CronExpression chooses. An example would be * interpretation CronExpression chooses. An example would be
* "0 0 14-6 ? * FRI-MON". </li> * "0 0 14-6 ? * FRI-MON". </li>
* </ul> * </ul>
* </p>
* *
* *
* @author Sharada Jambula, James House * @author Sharada Jambula, James House
* @author Contributions from Mads Henderson * @author Contributions from Mads Henderson
* @author Refactoring from CronTrigger to CronExpression by Aaron Craven * @author Refactoring from CronTrigger to CronExpression by Aaron Craven
*
* Borrowed from quartz v2.3.1
*
*/ */
public final class CronExpression implements Serializable, Cloneable { public final class CronExpression implements Serializable, Cloneable {
@ -212,11 +218,14 @@ public final class CronExpression implements Serializable, Cloneable {
protected static final int YEAR = 6; protected static final int YEAR = 6;
protected static final int ALL_SPEC_INT = 99; // '*' protected static final int ALL_SPEC_INT = 99; // '*'
protected static final int NO_SPEC_INT = 98; // '?' protected static final int NO_SPEC_INT = 98; // '?'
protected static final int MAX_LAST_DAY_OFFSET = 30;
protected static final int LAST_DAY_OFFSET_START = 32; // "L-30"
protected static final int LAST_DAY_OFFSET_END = LAST_DAY_OFFSET_START + MAX_LAST_DAY_OFFSET; // 'L'
protected static final Integer ALL_SPEC = ALL_SPEC_INT; protected static final Integer ALL_SPEC = ALL_SPEC_INT;
protected static final Integer NO_SPEC = NO_SPEC_INT; protected static final Integer NO_SPEC = NO_SPEC_INT;
protected static final Map<String, Integer> monthMap = new HashMap<String, Integer>(20); protected static final Map<String, Integer> monthMap = new HashMap<>(20);
protected static final Map<String, Integer> dayMap = new HashMap<String, Integer>(60); protected static final Map<String, Integer> dayMap = new HashMap<>(60);
static { static {
monthMap.put("JAN", 0); monthMap.put("JAN", 0);
monthMap.put("FEB", 1); monthMap.put("FEB", 1);
@ -246,15 +255,13 @@ public final class CronExpression implements Serializable, Cloneable {
protected transient TreeSet<Integer> minutes; protected transient TreeSet<Integer> minutes;
protected transient TreeSet<Integer> hours; protected transient TreeSet<Integer> hours;
protected transient TreeSet<Integer> daysOfMonth; protected transient TreeSet<Integer> daysOfMonth;
protected transient TreeSet<Integer> nearestWeekdays;
protected transient TreeSet<Integer> months; protected transient TreeSet<Integer> months;
protected transient TreeSet<Integer> daysOfWeek; protected transient TreeSet<Integer> daysOfWeek;
protected transient TreeSet<Integer> years; protected transient TreeSet<Integer> years;
protected transient boolean lastdayOfWeek = false; protected transient boolean lastDayOfWeek = false;
protected transient int nthdayOfWeek = 0; 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; protected transient boolean expressionParsed = false;
public static final int MAX_YEAR = Calendar.getInstance().get(Calendar.YEAR) + 100; public static final int MAX_YEAR = Calendar.getInstance().get(Calendar.YEAR) + 100;
@ -265,7 +272,7 @@ public final class CronExpression implements Serializable, Cloneable {
* *
* @param cronExpression String representation of the cron expression the * @param cronExpression String representation of the cron expression the
* new object should represent * new object should represent
* @throws ParseException * @throws java.text.ParseException
* if the string expression cannot be parsed into a valid * if the string expression cannot be parsed into a valid
* <CODE>CronExpression</CODE> * <CODE>CronExpression</CODE>
*/ */
@ -296,7 +303,7 @@ public final class CronExpression implements Serializable, Cloneable {
try { try {
buildExpression(cronExpression); buildExpression(cronExpression);
} catch (ParseException ex) { } catch (ParseException ex) {
throw new AssertionError(); throw new AssertionError("Could not parse expression!", ex);
} }
if (expression.getTimeZone() != null) { if (expression.getTimeZone() != null) {
setTimeZone((TimeZone) expression.getTimeZone().clone()); setTimeZone((TimeZone) expression.getTimeZone().clone());
@ -443,25 +450,28 @@ public final class CronExpression implements Serializable, Cloneable {
try { try {
if (seconds == null) { if (seconds == null) {
seconds = new TreeSet<Integer>(); seconds = new TreeSet<>();
} }
if (minutes == null) { if (minutes == null) {
minutes = new TreeSet<Integer>(); minutes = new TreeSet<>();
} }
if (hours == null) { if (hours == null) {
hours = new TreeSet<Integer>(); hours = new TreeSet<>();
} }
if (daysOfMonth == null) { if (daysOfMonth == null) {
daysOfMonth = new TreeSet<Integer>(); daysOfMonth = new TreeSet<>();
}
if (nearestWeekdays == null) {
nearestWeekdays = new TreeSet<>();
} }
if (months == null) { if (months == null) {
months = new TreeSet<Integer>(); months = new TreeSet<>();
} }
if (daysOfWeek == null) { if (daysOfWeek == null) {
daysOfWeek = new TreeSet<Integer>(); daysOfWeek = new TreeSet<>();
} }
if (years == null) { if (years == null) {
years = new TreeSet<Integer>(); years = new TreeSet<>();
} }
int exprOn = SECOND; int exprOn = SECOND;
@ -469,13 +479,13 @@ public final class CronExpression implements Serializable, Cloneable {
StringTokenizer exprsTok = new StringTokenizer(expression, " \t", StringTokenizer exprsTok = new StringTokenizer(expression, " \t",
false); false);
if(exprsTok.countTokens() > 7) {
throw new ParseException("Invalid expression has too many terms: " + expression, -1);
}
while (exprsTok.hasMoreTokens() && exprOn <= YEAR) { while (exprsTok.hasMoreTokens() && exprOn <= YEAR) {
String expr = exprsTok.nextToken().trim(); 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 // 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(",")) { 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); throw new ParseException("Support for specifying 'L' with other days of the week is not implemented", -1);
@ -519,7 +529,7 @@ public final class CronExpression implements Serializable, Cloneable {
throw pe; throw pe;
} catch (Exception e) { } catch (Exception e) {
throw new ParseException("Illegal cron expression format (" throw new ParseException("Illegal cron expression format ("
+ e.toString() + ")", 0); + e + ")", 0);
} }
} }
@ -572,8 +582,8 @@ public final class CronExpression implements Serializable, Cloneable {
} else if (c == '#') { } else if (c == '#') {
try { try {
i += 4; i += 4;
nthdayOfWeek = Integer.parseInt(s.substring(i)); nthDayOfWeek = Integer.parseInt(s.substring(i));
if (nthdayOfWeek < 1 || nthdayOfWeek > 5) { if (nthDayOfWeek < 1 || nthDayOfWeek > 5) {
throw new Exception(); throw new Exception();
} }
} catch (Exception e) { } catch (Exception e) {
@ -582,7 +592,7 @@ public final class CronExpression implements Serializable, Cloneable {
i); i);
} }
} else if (c == 'L') { } else if (c == 'L') {
lastdayOfWeek = true; lastDayOfWeek = true;
i++; i++;
} }
} }
@ -611,9 +621,8 @@ public final class CronExpression implements Serializable, Cloneable {
"'?' can only be specified for Day-of-Month or Day-of-Week.", "'?' can only be specified for Day-of-Month or Day-of-Week.",
i); i);
} }
if (type == DAY_OF_WEEK && !lastdayOfMonth) { if (type == DAY_OF_WEEK) {
int val = daysOfMonth.last(); if (!daysOfMonth.isEmpty() && daysOfMonth.last() == NO_SPEC_INT) {
if (val == NO_SPEC_INT) {
throw new ParseException( throw new ParseException(
"'?' can only be specified for Day-of-Month -OR- Day-of-Week.", "'?' can only be specified for Day-of-Month -OR- Day-of-Week.",
i); i);
@ -656,20 +665,25 @@ public final class CronExpression implements Serializable, Cloneable {
addToSet(ALL_SPEC_INT, -1, incr, type); addToSet(ALL_SPEC_INT, -1, incr, type);
return i; return i;
} else if (c == 'L') { } else if (c == 'L') {
if(type < DAY_OF_MONTH)
throw new ParseException("'L' not expected in seconds, minutes or hours fields.", i);
i++; i++;
if (type == DAY_OF_MONTH) {
lastdayOfMonth = true;
}
if (type == DAY_OF_WEEK) { if (type == DAY_OF_WEEK) {
addToSet(7, 7, 0, type); addToSet(7, 7, 0, type);
} }
if(type == DAY_OF_MONTH && s.length() > i) { if (type == DAY_OF_MONTH) {
int dom = LAST_DAY_OFFSET_END;
boolean nearestWeekday = false;
if (s.length() > i) {
c = s.charAt(i); c = s.charAt(i);
if (c == '-') { if (c == '-') {
ValueSet vs = getValue(0, s, i + 1); ValueSet vs = getValue(0, s, i + 1);
lastdayOffset = vs.value; int offset = vs.value;
if(lastdayOffset > 30) if (offset > MAX_LAST_DAY_OFFSET)
throw new ParseException("Offset from last day must be <= 30", i+1); throw new ParseException("Offset from last day must be <= " + MAX_LAST_DAY_OFFSET, i + 1);
dom -= offset;
i = vs.pos; i = vs.pos;
} }
if (s.length() > i) { if (s.length() > i) {
@ -680,6 +694,12 @@ public final class CronExpression implements Serializable, Cloneable {
} }
} }
} }
if (nearestWeekday) {
nearestWeekdays.add(dom);
} else {
daysOfMonth.add(dom);
}
}
return i; return i;
} else if (c >= '0' && c <= '9') { } else if (c >= '0' && c <= '9') {
int val = Integer.parseInt(String.valueOf(c)); int val = Integer.parseInt(String.valueOf(c));
@ -705,15 +725,15 @@ public final class CronExpression implements Serializable, Cloneable {
private void checkIncrementRange(int incr, int type, int idxPos) throws ParseException { private void checkIncrementRange(int incr, int type, int idxPos) throws ParseException {
if (incr > 59 && (type == SECOND || type == MINUTE)) { if (incr > 59 && (type == SECOND || type == MINUTE)) {
throw new ParseException("Increment > 60 : " + incr, idxPos); throw new ParseException("Increment >= 60 : " + incr, idxPos);
} else if (incr > 23 && (type == HOUR)) { } else if (incr > 23 && (type == HOUR)) {
throw new ParseException("Increment > 24 : " + incr, idxPos); throw new ParseException("Increment >= 24 : " + incr, idxPos);
} else if (incr > 31 && (type == DAY_OF_MONTH)) { } else if (incr > 31 && (type == DAY_OF_MONTH)) {
throw new ParseException("Increment > 31 : " + incr, idxPos); throw new ParseException("Increment >= 31 : " + incr, idxPos);
} else if (incr > 7 && (type == DAY_OF_WEEK)) { } else if (incr > 7 && (type == DAY_OF_WEEK)) {
throw new ParseException("Increment > 7 : " + incr, idxPos); throw new ParseException("Increment >= 7 : " + incr, idxPos);
} else if (incr > 12 && (type == MONTH)) { } else if (incr > 12 && (type == MONTH)) {
throw new ParseException("Increment > 12 : " + incr, idxPos); throw new ParseException("Increment >= 12 : " + incr, idxPos);
} }
} }
@ -734,7 +754,7 @@ public final class CronExpression implements Serializable, Cloneable {
if (type == DAY_OF_WEEK) { if (type == DAY_OF_WEEK) {
if(val < 1 || val > 7) if(val < 1 || val > 7)
throw new ParseException("Day-of-Week values must be between 1 and 7", -1); throw new ParseException("Day-of-Week values must be between 1 and 7", -1);
lastdayOfWeek = true; lastDayOfWeek = true;
} else { } else {
throw new ParseException("'L' option is not valid here. (pos=" + i + ")", i); throw new ParseException("'L' option is not valid here. (pos=" + i + ")", i);
} }
@ -745,15 +765,12 @@ public final class CronExpression implements Serializable, Cloneable {
} }
if (c == 'W') { if (c == 'W') {
if (type == DAY_OF_MONTH) { if (type != DAY_OF_MONTH) {
nearestWeekday = true;
} else {
throw new ParseException("'W' option is not valid here. (pos=" + i + ")", i); throw new ParseException("'W' option is not valid here. (pos=" + i + ")", i);
} }
if(val > 31) 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); throw new ParseException("The 'W' option does not make sense with values larger than 31 (max number of days in a month)", i);
TreeSet<Integer> set = getSet(type); nearestWeekdays.add(val);
set.add(val);
i++; i++;
return i; return i;
} }
@ -764,8 +781,8 @@ public final class CronExpression implements Serializable, Cloneable {
} }
i++; i++;
try { try {
nthdayOfWeek = Integer.parseInt(s.substring(i)); nthDayOfWeek = Integer.parseInt(s.substring(i));
if (nthdayOfWeek < 1 || nthdayOfWeek > 5) { if (nthDayOfWeek < 1 || nthDayOfWeek > 5) {
throw new Exception(); throw new Exception();
} }
} catch (Exception e) { } catch (Exception e) {
@ -873,23 +890,20 @@ public final class CronExpression implements Serializable, Cloneable {
buf.append("daysOfMonth: "); buf.append("daysOfMonth: ");
buf.append(getExpressionSetSummary(daysOfMonth)); buf.append(getExpressionSetSummary(daysOfMonth));
buf.append("\n"); buf.append("\n");
buf.append("nearestWeekdays: ");
buf.append(getExpressionSetSummary(nearestWeekdays));
buf.append("\n");
buf.append("months: "); buf.append("months: ");
buf.append(getExpressionSetSummary(months)); buf.append(getExpressionSetSummary(months));
buf.append("\n"); buf.append("\n");
buf.append("daysOfWeek: "); buf.append("daysOfWeek: ");
buf.append(getExpressionSetSummary(daysOfWeek)); buf.append(getExpressionSetSummary(daysOfWeek));
buf.append("\n"); buf.append("\n");
buf.append("lastdayOfWeek: "); buf.append("lastDayOfWeek: ");
buf.append(lastdayOfWeek); buf.append(lastDayOfWeek);
buf.append("\n");
buf.append("nearestWeekday: ");
buf.append(nearestWeekday);
buf.append("\n"); buf.append("\n");
buf.append("NthDayOfWeek: "); buf.append("NthDayOfWeek: ");
buf.append(nthdayOfWeek); buf.append(nthDayOfWeek);
buf.append("\n");
buf.append("lastdayOfMonth: ");
buf.append(lastdayOfMonth);
buf.append("\n"); buf.append("\n");
buf.append("years: "); buf.append("years: ");
buf.append(getExpressionSetSummary(years)); buf.append(getExpressionSetSummary(years));
@ -1197,7 +1211,7 @@ public final class CronExpression implements Serializable, Cloneable {
// get second................................................. // get second.................................................
st = seconds.tailSet(sec); st = seconds.tailSet(sec);
if (st != null && st.size() != 0) { if (st != null && !st.isEmpty()) {
sec = st.first(); sec = st.first();
} else { } else {
sec = seconds.first(); sec = seconds.first();
@ -1212,7 +1226,7 @@ public final class CronExpression implements Serializable, Cloneable {
// get minute................................................. // get minute.................................................
st = minutes.tailSet(min); st = minutes.tailSet(min);
if (st != null && st.size() != 0) { if (st != null && !st.isEmpty()) {
t = min; t = min;
min = st.first(); min = st.first();
} else { } else {
@ -1233,7 +1247,7 @@ public final class CronExpression implements Serializable, Cloneable {
// get hour................................................... // get hour...................................................
st = hours.tailSet(hr); st = hours.tailSet(hr);
if (st != null && st.size() != 0) { if (st != null && !st.isEmpty()) {
t = hr; t = hr;
hr = st.first(); hr = st.first();
} else { } else {
@ -1260,63 +1274,14 @@ public final class CronExpression implements Serializable, Cloneable {
boolean dayOfMSpec = !daysOfMonth.contains(NO_SPEC); boolean dayOfMSpec = !daysOfMonth.contains(NO_SPEC);
boolean dayOfWSpec = !daysOfWeek.contains(NO_SPEC); boolean dayOfWSpec = !daysOfWeek.contains(NO_SPEC);
if (dayOfMSpec && !dayOfWSpec) { // get day by day of month rule if (dayOfMSpec && !dayOfWSpec) { // get day by day of month rule
st = daysOfMonth.tailSet(day); Optional<Integer> smallestDay = findSmallestDay(day, mon, cl.get(Calendar.YEAR), daysOfMonth);
if (lastdayOfMonth) { Optional<Integer> smallestDayForWeekday = findSmallestDay(day, mon, cl.get(Calendar.YEAR), nearestWeekdays);
if(!nearestWeekday) {
t = day; t = day;
day = getLastDayOfMonth(mon, cl.get(Calendar.YEAR)); day = -1;
day -= lastdayOffset; if (smallestDayForWeekday.isPresent()) {
if(t > day) { day = smallestDayForWeekday.get();
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()); java.util.Calendar tcal = java.util.Calendar.getInstance(getTimeZone());
tcal.set(Calendar.SECOND, 0);
tcal.set(Calendar.MINUTE, 0);
tcal.set(Calendar.HOUR_OF_DAY, 0);
tcal.set(Calendar.DAY_OF_MONTH, day);
tcal.set(Calendar.MONTH, mon - 1);
tcal.set(Calendar.YEAR, cl.get(Calendar.YEAR));
int ldom = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
int dow = tcal.get(Calendar.DAY_OF_WEEK);
if(dow == Calendar.SATURDAY && day == 1) {
day += 2;
} else if(dow == Calendar.SATURDAY) {
day -= 1;
} else if(dow == Calendar.SUNDAY && day == ldom) {
day -= 2;
} else if(dow == Calendar.SUNDAY) {
day += 1;
}
tcal.set(Calendar.SECOND, sec);
tcal.set(Calendar.MINUTE, min);
tcal.set(Calendar.HOUR_OF_DAY, hr);
tcal.set(Calendar.DAY_OF_MONTH, day);
tcal.set(Calendar.MONTH, mon - 1);
Date nTime = tcal.getTime();
if(nTime.before(afterTime)) {
day = 1;
mon++;
}
}
} else if(nearestWeekday) {
t = day;
day = daysOfMonth.first();
Calendar tcal = Calendar.getInstance(getTimeZone());
tcal.set(Calendar.SECOND, 0); tcal.set(Calendar.SECOND, 0);
tcal.set(Calendar.MINUTE, 0); tcal.set(Calendar.MINUTE, 0);
tcal.set(Calendar.HOUR_OF_DAY, 0); tcal.set(Calendar.HOUR_OF_DAY, 0);
@ -1345,24 +1310,23 @@ public final class CronExpression implements Serializable, Cloneable {
tcal.set(Calendar.MONTH, mon - 1); tcal.set(Calendar.MONTH, mon - 1);
Date nTime = tcal.getTime(); Date nTime = tcal.getTime();
if(nTime.before(afterTime)) { if(nTime.before(afterTime)) {
day = daysOfMonth.first(); day = -1;
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) { boolean needAdvance = false;
if (smallestDay.isPresent()) {
if (day == -1 || smallestDay.get() < day) {
day = smallestDay.get();
needAdvance = true;
}
} else if (day == -1) {
day = 1;
mon++;
needAdvance = true;
}
if (needAdvance && (day != t || mon != tmon)) {
cl.set(Calendar.SECOND, 0); cl.set(Calendar.SECOND, 0);
cl.set(Calendar.MINUTE, 0); cl.set(Calendar.MINUTE, 0);
cl.set(Calendar.HOUR_OF_DAY, 0); cl.set(Calendar.HOUR_OF_DAY, 0);
@ -1372,8 +1336,9 @@ public final class CronExpression implements Serializable, Cloneable {
// are 1-based // are 1-based
continue; continue;
} }
} else if (dayOfWSpec && !dayOfMSpec) { // get day by day of week rule } else if (dayOfWSpec && !dayOfMSpec) { // get day by day of week rule
if (lastdayOfWeek) { // are we looking for the last XXX day of if (lastDayOfWeek) { // are we looking for the last XXX day of
// the month? // the month?
int dow = daysOfWeek.first(); // desired int dow = daysOfWeek.first(); // desired
// d-o-w // d-o-w
@ -1416,7 +1381,7 @@ public final class CronExpression implements Serializable, Cloneable {
continue; continue;
} }
} else if (nthdayOfWeek != 0) { } else if (nthDayOfWeek != 0) {
// are we looking for the Nth XXX day in the month? // are we looking for the Nth XXX day in the month?
int dow = daysOfWeek.first(); // desired int dow = daysOfWeek.first(); // desired
// d-o-w // d-o-w
@ -1428,10 +1393,7 @@ public final class CronExpression implements Serializable, Cloneable {
daysToAdd = dow + (7 - cDow); daysToAdd = dow + (7 - cDow);
} }
boolean dayShifted = false; boolean dayShifted = daysToAdd > 0;
if (daysToAdd > 0) {
dayShifted = true;
}
day += daysToAdd; day += daysToAdd;
int weekOfMonth = day / 7; int weekOfMonth = day / 7;
@ -1439,7 +1401,7 @@ public final class CronExpression implements Serializable, Cloneable {
weekOfMonth++; weekOfMonth++;
} }
daysToAdd = (nthdayOfWeek - weekOfMonth) * 7; daysToAdd = (nthDayOfWeek - weekOfMonth) * 7;
day += daysToAdd; day += daysToAdd;
if (daysToAdd < 0 if (daysToAdd < 0
|| day > getLastDayOfMonth(mon, cl || day > getLastDayOfMonth(mon, cl
@ -1465,7 +1427,7 @@ public final class CronExpression implements Serializable, Cloneable {
int dow = daysOfWeek.first(); // desired int dow = daysOfWeek.first(); // desired
// d-o-w // d-o-w
st = daysOfWeek.tailSet(cDow); st = daysOfWeek.tailSet(cDow);
if (st != null && st.size() > 0) { if (st != null && !st.isEmpty()) {
dow = st.first(); dow = st.first();
} }
@ -1488,7 +1450,7 @@ public final class CronExpression implements Serializable, Cloneable {
cl.set(Calendar.MONTH, mon); cl.set(Calendar.MONTH, mon);
// no '- 1' here because we are promoting the month // no '- 1' here because we are promoting the month
continue; continue;
} else if (daysToAdd > 0) { // are we swithing days? } else if (daysToAdd > 0) { // are we switching days?
cl.set(Calendar.SECOND, 0); cl.set(Calendar.SECOND, 0);
cl.set(Calendar.MINUTE, 0); cl.set(Calendar.MINUTE, 0);
cl.set(Calendar.HOUR_OF_DAY, 0); cl.set(Calendar.HOUR_OF_DAY, 0);
@ -1519,7 +1481,7 @@ public final class CronExpression implements Serializable, Cloneable {
// get month................................................... // get month...................................................
st = months.tailSet(mon); st = months.tailSet(mon);
if (st != null && st.size() != 0) { if (st != null && !st.isEmpty()) {
t = mon; t = mon;
mon = st.first(); mon = st.first();
} else { } else {
@ -1546,7 +1508,7 @@ public final class CronExpression implements Serializable, Cloneable {
// get year................................................... // get year...................................................
st = years.tailSet(year); st = years.tailSet(year);
if (st != null && st.size() != 0) { if (st != null && !st.isEmpty()) {
t = year; t = year;
year = st.first(); year = st.first();
} else { } else {
@ -1580,19 +1542,60 @@ public final class CronExpression implements Serializable, Cloneable {
* @param hour the hour to set * @param hour the hour to set
*/ */
protected void setCalendarHour(Calendar cal, int hour) { protected void setCalendarHour(Calendar cal, int hour) {
cal.set(Calendar.HOUR_OF_DAY, hour); cal.set(java.util.Calendar.HOUR_OF_DAY, hour);
if (cal.get(Calendar.HOUR_OF_DAY) != hour && hour != 24) { if (cal.get(java.util.Calendar.HOUR_OF_DAY) != hour && hour != 24) {
cal.set(Calendar.HOUR_OF_DAY, hour + 1); cal.set(java.util.Calendar.HOUR_OF_DAY, hour + 1);
} }
} }
/** /**
* NOT YET IMPLEMENTED: Returns the time before the given time * Returns the time before the given time
* that the <code>CronExpression</code> matches. * that the <code>CronExpression</code> matches.
*
* @param endTime a time for which the previous
* matching time is returned
* @return the previous matching time before the given end time,
* or null if there are no previous matching times
*/ */
public Date getTimeBefore(Date endTime) { public Date getTimeBefore(Date endTime) {
// FUTURE_TODO: implement QUARTZ-423 // the current implementation is not a direct calculation, but rather
return null; // uses getTimeAfter with a binary search to find the previous match time
long end = endTime.getTime();
long min = 0; // the epoch date is the minimum supported by this class
long max = end;
// check if it's satisfiable at all
Date date = new Date(min);
Date after = getTimeAfter(date);
if (after == null || after.getTime() >= end)
return null; // there are no after-times before end
// from this point forward min's time-after is always less than end,
// and max's time-after is always equal to or greater than end
// so we just need to shrink the interval until they meet.
// optimization - perform inverse binary search to find a tighter lower bound
long interval = 60 * 60 * 1000; // start with a reasonable interval
while (interval < max) {
date.setTime(max - interval);
after = getTimeAfter(date);
if (after != null && after.getTime() < max) {
min = date.getTime(); // found a closer min
break;
}
interval *= 2;
}
// perform a regular binary search to find the earliest moment
// whose time-after is equal to or greater than the end time -
// this moment is the previous match time itself
while (max - min > 1000) { // we can stop at 1 second resolution
long mid = (min + max) >>> 1;
date.setTime(mid);
after = getTimeAfter(date);
if (after != null && after.getTime() < end)
min = mid;
else
max = mid;
}
date.setTime(max - max % 1000); // round to second
return date;
} }
/** /**
@ -1642,6 +1645,31 @@ public final class CronExpression implements Serializable, Cloneable {
} }
private Optional<Integer> findSmallestDay(int day, int mon, int year, TreeSet<Integer> set) {
if (set.isEmpty()) {
return Optional.empty();
}
final int lastDay = getLastDayOfMonth(mon, year);
// For "L", "L-1", etc.
final int smallestDay = Optional.ofNullable(set.ceiling(LAST_DAY_OFFSET_END - (lastDay - day)))
.map(d -> d - LAST_DAY_OFFSET_START + 1)
.orElse(Integer.MAX_VALUE);
// For "1", "2", etc.
SortedSet<Integer> st = set.subSet(day, LAST_DAY_OFFSET_START);
// make sure we don't over-run a short month, such as february
if (!st.isEmpty() && st.first() < smallestDay && st.first() <= lastDay) {
return Optional.of(st.first());
}
if (smallestDay == Integer.MAX_VALUE) {
return Optional.empty();
} else {
return Optional.of(smallestDay + lastDay - LAST_DAY_OFFSET_START + 1);
}
}
private void readObject(java.io.ObjectInputStream stream) private void readObject(java.io.ObjectInputStream stream)
throws java.io.IOException, ClassNotFoundException { throws java.io.IOException, ClassNotFoundException {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,30 +1,32 @@
package com.xxl.job.admin.core.route.strategy; package com.xxl.job.admin.business.scheduler.route.strategy;
import com.xxl.job.admin.core.route.ExecutorRouter; import com.xxl.job.admin.business.scheduler.route.ExecutorRouter;
import com.xxl.job.core.biz.model.ReturnT; import com.xxl.job.core.openapi.model.TriggerRequest;
import com.xxl.job.core.biz.model.TriggerParam; import com.xxl.tool.response.Response;
import java.io.UnsupportedEncodingException; import java.nio.charset.StandardCharsets;
import java.security.MessageDigest; import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.util.List; import java.util.List;
import java.util.SortedMap; import java.util.Map;
import java.util.TreeMap; import java.util.TreeMap;
/** /**
* 分组下机器地址相同不同JOB均匀散列在不同机器上保证分组下机器分配JOB平均且每个JOB固定调度其中一台机器 * 分组下机器地址相同不同JOB均匀散列在不同机器上保证分组下机器分配JOB平均且每个JOB固定调度其中一台机器
* avirtual node解决不均衡问题 * avirtual node解决不均衡问题
* bhash method replace hashCodeString的hashCode可能重复需要进一步扩大hashCode的取值范围 * bhash method replace hashCodeString的hashCode可能重复需要进一步扩大hashCode的取值范围
*
* Created by xuxueli on 17/3/10. * Created by xuxueli on 17/3/10.
*/ */
public class ExecutorRouteConsistentHash extends ExecutorRouter { public class ExecutorRouteConsistentHash extends ExecutorRouter {
private static int VIRTUAL_NODE_NUM = 100; private static final int VIRTUAL_NODE_NUM = 100;
/** /**
* get hash code on 2^32 ring (md5散列的方式计算hash值) * get hash code on 2^32 ring (md5散列的方式计算hash值)
* @param key *
* @return * @param key key
* @return hash code
*/ */
private static long hash(String key) { private static long hash(String key) {
@ -37,11 +39,7 @@ public class ExecutorRouteConsistentHash extends ExecutorRouter {
} }
md5.reset(); md5.reset();
byte[] keyBytes = null; byte[] keyBytes = null;
try { keyBytes = key.getBytes(StandardCharsets.UTF_8);
keyBytes = key.getBytes("UTF-8");
} catch (UnsupportedEncodingException e) {
throw new RuntimeException("Unknown string :" + key, e);
}
md5.update(keyBytes); md5.update(keyBytes);
byte[] digest = md5.digest(); byte[] digest = md5.digest();
@ -52,15 +50,22 @@ public class ExecutorRouteConsistentHash extends ExecutorRouter {
| ((long) (digest[1] & 0xFF) << 8) | ((long) (digest[1] & 0xFF) << 8)
| (digest[0] & 0xFF); | (digest[0] & 0xFF);
long truncateHashCode = hashCode & 0xffffffffL; return hashCode & 0xffffffffL;
return truncateHashCode;
} }
/**
* get address by jobId
*
* @param jobId job id
* @param addressList address list
* @return address
*/
public String hashJob(int jobId, List<String> addressList) { public String hashJob(int jobId, List<String> addressList) {
// 1hash ring
// ------A1------A2-------A3------ // ------A1------A2-------A3------
// -----------J1------------------ // -----------J1------------------
TreeMap<Long, String> addressRing = new TreeMap<Long, String>(); TreeMap<Long, String> addressRing = new TreeMap<>();
for (String address: addressList) { for (String address: addressList) {
for (int i = 0; i < VIRTUAL_NODE_NUM; i++) { for (int i = 0; i < VIRTUAL_NODE_NUM; i++) {
long addressHash = hash("SHARD-" + address + "-NODE-" + i); long addressHash = hash("SHARD-" + address + "-NODE-" + i);
@ -68,18 +73,27 @@ public class ExecutorRouteConsistentHash extends ExecutorRouter {
} }
} }
// 2generate job-hash
long jobHash = hash(String.valueOf(jobId)); long jobHash = hash(String.valueOf(jobId));
SortedMap<Long, String> lastRing = addressRing.tailMap(jobHash);
// 3route job-node
Map.Entry<Long, String> ceilingEntry = addressRing.ceilingEntry(jobHash);
if (ceilingEntry != null) {
return ceilingEntry.getValue();
}
/*SortedMap<Long, String> lastRing = addressRing.tailMap(jobHash);
if (!lastRing.isEmpty()) { if (!lastRing.isEmpty()) {
return lastRing.get(lastRing.firstKey()); return lastRing.get(lastRing.firstKey());
} }*/
// 4default first node
return addressRing.firstEntry().getValue(); return addressRing.firstEntry().getValue();
} }
@Override @Override
public ReturnT<String> route(TriggerParam triggerParam, List<String> addressList) { public Response<String> route(TriggerRequest triggerParam, List<String> addressList) {
String address = hashJob(triggerParam.getJobId(), addressList); String address = hashJob(triggerParam.getJobId(), addressList);
return new ReturnT<String>(address); return Response.ofSuccess(address);
} }
} }

View File

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

View File

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

View File

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

View File

@ -1,8 +1,8 @@
package com.xxl.job.admin.core.route.strategy; package com.xxl.job.admin.business.scheduler.route.strategy;
import com.xxl.job.admin.core.route.ExecutorRouter; import com.xxl.job.admin.business.scheduler.route.ExecutorRouter;
import com.xxl.job.core.biz.model.ReturnT; import com.xxl.job.core.openapi.model.TriggerRequest;
import com.xxl.job.core.biz.model.TriggerParam; import com.xxl.tool.response.Response;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
@ -19,6 +19,11 @@ import java.util.concurrent.ConcurrentMap;
*/ */
public class ExecutorRouteLRU extends ExecutorRouter { public class ExecutorRouteLRU extends ExecutorRouter {
/**
* job lru map
*
* <jobId, <address, address>>
*/
private static ConcurrentMap<Integer, LinkedHashMap<String, String>> jobLRUMap = new ConcurrentHashMap<Integer, LinkedHashMap<String, String>>(); private static ConcurrentMap<Integer, LinkedHashMap<String, String>> jobLRUMap = new ConcurrentHashMap<Integer, LinkedHashMap<String, String>>();
private static long CACHE_VALID_TIME = 0; private static long CACHE_VALID_TIME = 0;
@ -38,7 +43,7 @@ public class ExecutorRouteLRU extends ExecutorRouter {
* aaccessOrdertrue=访问顺序排序get/put时排序false=插入顺序排期 * aaccessOrdertrue=访问顺序排序get/put时排序false=插入顺序排期
* bremoveEldestEntry新增元素时将会调用返回true时会删除最老元素可封装LinkedHashMap并重写该方法比如定义最大容量超出是返回true即可实现固定长度的LRU算法 * bremoveEldestEntry新增元素时将会调用返回true时会删除最老元素可封装LinkedHashMap并重写该方法比如定义最大容量超出是返回true即可实现固定长度的LRU算法
*/ */
lruItem = new LinkedHashMap<String, String>(16, 0.75f, true); lruItem = new LinkedHashMap<>(16, 0.75f, true);
jobLRUMap.putIfAbsent(jobId, lruItem); jobLRUMap.putIfAbsent(jobId, lruItem);
} }
@ -55,22 +60,21 @@ public class ExecutorRouteLRU extends ExecutorRouter {
delKeys.add(existKey); delKeys.add(existKey);
} }
} }
if (delKeys.size() > 0) { if (!delKeys.isEmpty()) {
for (String delKey: delKeys) { for (String delKey: delKeys) {
lruItem.remove(delKey); lruItem.remove(delKey);
} }
} }
// load // load first elment, eldest entry
String eldestKey = lruItem.entrySet().iterator().next().getKey(); String eldestKey = lruItem.entrySet().iterator().next().getKey();
String eldestValue = lruItem.get(eldestKey); return lruItem.get(eldestKey);
return eldestValue;
} }
@Override @Override
public ReturnT<String> route(TriggerParam triggerParam, List<String> addressList) { public Response<String> route(TriggerRequest triggerParam, List<String> addressList) {
String address = route(triggerParam.getJobId(), addressList); String address = route(triggerParam.getJobId(), addressList);
return new ReturnT<String>(address); return Response.ofSuccess(address);
} }
} }

View File

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

View File

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

View File

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

View File

@ -0,0 +1,153 @@
package com.xxl.job.admin.business.scheduler.thread;
import com.xxl.job.admin.business.model.XxlJobLog;
import com.xxl.job.admin.business.scheduler.config.XxlJobAdminBootstrap;
import com.xxl.job.admin.framework.util.I18nUtil;
import com.xxl.job.core.context.XxlJobContext;
import com.xxl.job.core.openapi.model.CallbackRequest;
import com.xxl.tool.concurrent.CyclicThread;
import com.xxl.tool.core.DateTool;
import com.xxl.tool.response.Response;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Date;
import java.util.List;
import java.util.concurrent.*;
/**
* job complate, for callback and result-lost
*
* @author xuxueli 2015-9-1 18:05:56
*/
public class JobCompleteHelper {
private static final Logger logger = LoggerFactory.getLogger(JobCompleteHelper.class);
// ---------------------- monitor ----------------------
private ThreadPoolExecutor callbackThreadPool = null;
private CyclicThread jobMonitorThread;
/**
* start
*/
public void start(){
// 1callbackThreadPool
callbackThreadPool = new ThreadPoolExecutor(
2,
20,
30L,
TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>(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).");
}
});
// 2jobMonitorThread
jobMonitorThread = new CyclicThread("JobCompleteHelper#jobMonitorThread", true, new Runnable() {
@Override
public void run() {
// 任务结果丢失处理调度记录停留在 "运行中" 状态超过10min且对应执行器心跳注册失败不在线则将本地调度主动标记失败
Date losedTime = DateTool.addMinutes(new Date(), -10);
List<Long> losedJobIds = XxlJobAdminBootstrap.getInstance().getXxlJobLogMapper().findLostJobIds(losedTime);
if (losedJobIds!=null && losedJobIds.size()>0) {
for (Long logId: losedJobIds) {
XxlJobLog jobLog = new XxlJobLog();
jobLog.setId(logId);
jobLog.setHandleTime(new Date());
jobLog.setHandleCode(XxlJobContext.HANDLE_CODE_FAIL);
jobLog.setHandleMsg( I18nUtil.getString("joblog_lost_fail") );
XxlJobAdminBootstrap.getInstance().getJobCompleter().complete(jobLog);
}
}
}
}, 60 * 1000L, true);
jobMonitorThread.start();
}
/**
* stop
*/
public void stop(){
// 1callbackThreadPool
callbackThreadPool.shutdownNow();
// 2jobMonitorThread
jobMonitorThread.stop();
}
// ---------------------- helper ----------------------
/**
* callback
*
* @param callbackParamList callback param
* @return callback result
*/
public Response<String> callback(List<CallbackRequest> callbackParamList) {
callbackThreadPool.execute(new Runnable() {
@Override
public void run() {
for (CallbackRequest callbackRequest: callbackParamList) {
Response<String> callbackResult = doCallback(callbackRequest);
logger.debug(">>>>>>>>> JobApiController.callback {}, callbackRequest={}, callbackResult={}",
(callbackResult.isSuccess()?"success":"fail"), callbackRequest, callbackResult);
}
}
});
return Response.ofSuccess();
}
private Response<String> doCallback(CallbackRequest handleCallbackParam) {
// valid log item
XxlJobLog log = XxlJobAdminBootstrap.getInstance().getXxlJobLogMapper().load(handleCallbackParam.getLogId());
if (log == null) {
return Response.ofFail( "log item not found.");
}
if (log.getHandleCode() > 0) {
return Response.ofFail("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("<br>");
}
if (handleCallbackParam.getHandleMsg() != null) {
handleMsg.append(handleCallbackParam.getHandleMsg());
}
// success, save log
log.setHandleTime(new Date());
log.setHandleCode(handleCallbackParam.getHandleCode());
log.setHandleMsg(handleMsg.toString());
XxlJobAdminBootstrap.getInstance().getJobCompleter().complete(log);
return Response.ofSuccess();
}
}

View File

@ -0,0 +1,82 @@
package com.xxl.job.admin.business.scheduler.thread;
import com.xxl.job.admin.business.model.XxlJobInfo;
import com.xxl.job.admin.business.model.XxlJobLog;
import com.xxl.job.admin.business.scheduler.config.XxlJobAdminBootstrap;
import com.xxl.job.admin.business.scheduler.trigger.TriggerTypeEnum;
import com.xxl.job.admin.framework.util.I18nUtil;
import com.xxl.tool.concurrent.CyclicThread;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
/**
* job fail-monitor helper
*
* @author xuxueli 2015-9-1 18:05:56
*/
public class JobFailAlarmMonitorHelper {
private static Logger logger = LoggerFactory.getLogger(JobFailAlarmMonitorHelper.class);
// ---------------------- monitor ----------------------
/**
* monitor thread
*/
private CyclicThread monitorThread;
/**
* start
*/
public void start(){
monitorThread = new CyclicThread("JobFailAlarmMonitorHelper#monitorThread", true, new Runnable() {
@Override
public void run() {
List<Long> failLogIds = XxlJobAdminBootstrap.getInstance().getXxlJobLogMapper().findFailJobLogIds(1000);
if (failLogIds!=null && !failLogIds.isEmpty()) {
for (long failLogId: failLogIds) {
// lock log
int lockRet = XxlJobAdminBootstrap.getInstance().getXxlJobLogMapper().updateAlarmStatus(failLogId, 0, -1);
if (lockRet < 1) {
continue;
}
XxlJobLog log = XxlJobAdminBootstrap.getInstance().getXxlJobLogMapper().load(failLogId);
XxlJobInfo info = XxlJobAdminBootstrap.getInstance().getXxlJobInfoMapper().loadById(log.getJobId());
// 1fail retry monitor
if (log.getExecutorFailRetryCount() > 0) {
XxlJobAdminBootstrap.getInstance().getJobTriggerPoolHelper().trigger(log.getJobId(), TriggerTypeEnum.RETRY, (log.getExecutorFailRetryCount()-1), log.getExecutorShardingParam(), log.getExecutorParam(), null);
String retryMsg = "<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>"+ I18nUtil.getString("jobconf_trigger_type_retry") +"<<<<<<<<<<< </span><br>";
log.setTriggerMsg(log.getTriggerMsg() + retryMsg);
XxlJobAdminBootstrap.getInstance().getXxlJobLogMapper().updateTriggerInfo(log);
}
// 2fail alarm monitor
int newAlarmStatus = 0; // 告警状态0-默认-1=锁定状态1-无需告警2-告警成功3-告警失败
if (info != null) {
boolean alarmResult = XxlJobAdminBootstrap.getInstance().getJobAlarmer().alarm(info, log);
newAlarmStatus = alarmResult?2:3;
} else {
newAlarmStatus = 1;
}
XxlJobAdminBootstrap.getInstance().getXxlJobLogMapper().updateAlarmStatus(failLogId, -1, newAlarmStatus);
}
}
}
}, 10 * 1000L, true);
monitorThread.start();
}
/**
* stop
*/
public void stop(){
monitorThread.stop();
}
}

View File

@ -0,0 +1,128 @@
package com.xxl.job.admin.business.scheduler.thread;
import com.xxl.job.admin.business.model.XxlJobLogReport;
import com.xxl.job.admin.business.scheduler.config.XxlJobAdminBootstrap;
import com.xxl.tool.concurrent.CyclicThread;
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.atomic.AtomicLong;
/**
* job log report helper
*
* @author xuxueli 2019-11-22
*/
public class JobLogReportHelper {
private static final Logger logger = LoggerFactory.getLogger(JobLogReportHelper.class);
private CyclicThread logReportThread;
private AtomicLong lastCleanLogTime;
/**
* start
*/
public void start(){
/**
* last clean log time Thread-safe concurrent reading and writing
*/
lastCleanLogTime = new AtomicLong(0);
// log report thread
logReportThread = new CyclicThread("JobLogReportHelper#logReportThread", true, new Runnable() {
@Override
public void run() {
// 1log-report refresh: refresh log report in 3 days
for (int i = 0; i < 3; i++) {
// today
Calendar itemDay = Calendar.getInstance();
itemDay.add(Calendar.DAY_OF_MONTH, -i);
itemDay.set(Calendar.HOUR_OF_DAY, 0);
itemDay.set(Calendar.MINUTE, 0);
itemDay.set(Calendar.SECOND, 0);
itemDay.set(Calendar.MILLISECOND, 0);
Date todayFrom = itemDay.getTime();
itemDay.set(Calendar.HOUR_OF_DAY, 23);
itemDay.set(Calendar.MINUTE, 59);
itemDay.set(Calendar.SECOND, 59);
itemDay.set(Calendar.MILLISECOND, 999);
Date todayTo = itemDay.getTime();
// refresh log-report every minute
XxlJobLogReport xxlJobLogReport = new XxlJobLogReport();
xxlJobLogReport.setTriggerDay(todayFrom);
xxlJobLogReport.setRunningCount(0);
xxlJobLogReport.setSucCount(0);
xxlJobLogReport.setFailCount(0);
xxlJobLogReport.setUpdateTime(new Date());
// fill count-data
Map<String, Object> triggerCountMap = XxlJobAdminBootstrap.getInstance().getXxlJobLogMapper().findLogReport(todayFrom, todayTo);
if (triggerCountMap!=null && !triggerCountMap.isEmpty()) {
int triggerDayCount = triggerCountMap.containsKey("triggerDayCount")?Integer.parseInt(String.valueOf(triggerCountMap.get("triggerDayCount"))):0;
int triggerDayCountRunning = triggerCountMap.containsKey("triggerDayCountRunning")?Integer.parseInt(String.valueOf(triggerCountMap.get("triggerDayCountRunning"))):0;
int triggerDayCountSuc = triggerCountMap.containsKey("triggerDayCountSuc")?Integer.parseInt(String.valueOf(triggerCountMap.get("triggerDayCountSuc"))):0;
int triggerDayCountFail = triggerDayCount - triggerDayCountRunning - triggerDayCountSuc;
xxlJobLogReport.setRunningCount(triggerDayCountRunning);
xxlJobLogReport.setSucCount(triggerDayCountSuc);
xxlJobLogReport.setFailCount(triggerDayCountFail);
}
// do refresh:
XxlJobAdminBootstrap.getInstance().getXxlJobLogReportMapper().saveOrUpdate(xxlJobLogReport); // 0-fail; 1-save suc; 2-update suc;
/*if (ret < 1) {
XxlJobAdminBootstrap.getInstance().getXxlJobLogReportMapper().save(xxlJobLogReport);
}*/
}
// 2log-clean: switch open & once each day
if (XxlJobAdminBootstrap.getInstance().getLogretentiondays()>0
&& System.currentTimeMillis() - lastCleanLogTime.longValue() > 24*60*60*1000) {
// expire-time
Calendar expiredDay = Calendar.getInstance();
expiredDay.add(Calendar.DAY_OF_MONTH, -1 * XxlJobAdminBootstrap.getInstance().getLogretentiondays());
expiredDay.set(Calendar.HOUR_OF_DAY, 0);
expiredDay.set(Calendar.MINUTE, 0);
expiredDay.set(Calendar.SECOND, 0);
expiredDay.set(Calendar.MILLISECOND, 0);
Date clearBeforeTime = expiredDay.getTime();
// clean expired log
List<Long> logIds = null;
do {
logIds = XxlJobAdminBootstrap.getInstance().getXxlJobLogMapper().findClearLogIds(0, 0, clearBeforeTime, 0, 1000);
if (logIds!=null && !logIds.isEmpty()) {
XxlJobAdminBootstrap.getInstance().getXxlJobLogMapper().clearLog(logIds);
}
} while (logIds!=null && !logIds.isEmpty());
// update clean time
lastCleanLogTime.set(System.currentTimeMillis());
}
}
}, 60 * 1000L, true);
logReportThread.start();
}
/**
* stop
*/
public void stop(){
logReportThread.stop();
}
}

View File

@ -0,0 +1,204 @@
package com.xxl.job.admin.business.scheduler.thread;
import com.xxl.job.admin.business.model.XxlJobGroup;
import com.xxl.job.admin.business.model.XxlJobRegistry;
import com.xxl.job.admin.business.scheduler.config.XxlJobAdminBootstrap;
import com.xxl.job.core.constant.Const;
import com.xxl.job.core.constant.RegistTypeEnum;
import com.xxl.job.core.openapi.model.RegistryRequest;
import com.xxl.tool.concurrent.CyclicThread;
import com.xxl.tool.core.StringTool;
import com.xxl.tool.response.Response;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.*;
import java.util.concurrent.*;
/**
* job registry instance helper
*
* @author xuxueli 2016-10-02 19:10:24
*/
public class JobRegistryHelper {
private static final Logger logger = LoggerFactory.getLogger(JobRegistryHelper.class);
/**
* registry or remove thread pool
*/
private ThreadPoolExecutor registryOrRemoveThreadPool = null;
/**
* registry monitor thread
*/
private CyclicThread registryMonitorThread;
/**
* start
*/
public void start(){
// 1for registry or remove
registryOrRemoveThreadPool = new ThreadPoolExecutor(
2,
10,
30L,
TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>(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).");
}
});
// 2for registry monitor
registryMonitorThread = new CyclicThread("JobRegistryHelper#registryMonitorThread", true, new Runnable() {
@Override
public void run() {
// auto registry group
List<XxlJobGroup> groupList = XxlJobAdminBootstrap.getInstance().getXxlJobGroupMapper().findByAddressType(0);
if (groupList!=null && !groupList.isEmpty()) {
// remove dead address (admin/executor)
List<Integer> ids = XxlJobAdminBootstrap.getInstance().getXxlJobRegistryMapper().findDead(Const.DEAD_TIMEOUT, new Date());
if (ids!=null && !ids.isEmpty()) {
XxlJobAdminBootstrap.getInstance().getXxlJobRegistryMapper().removeDead(ids);
}
// fresh online address (admin/executor)
HashMap<String, List<String>> appAddressMap = new HashMap<String, List<String>>();
List<XxlJobRegistry> list = XxlJobAdminBootstrap.getInstance().getXxlJobRegistryMapper().findAll(Const.DEAD_TIMEOUT, new Date());
if (list != null) {
for (XxlJobRegistry item: list) {
if (RegistTypeEnum.EXECUTOR.name().equals(item.getRegistryGroup())) {
String appname = item.getRegistryKey();
List<String> registryList = appAddressMap.get(appname);
if (registryList == null) {
registryList = new ArrayList<String>();
}
if (!registryList.contains(item.getRegistryValue())) {
registryList.add(item.getRegistryValue());
}
appAddressMap.put(appname, registryList);
}
}
}
// fresh group address
for (XxlJobGroup group: groupList) {
List<String> 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());
XxlJobAdminBootstrap.getInstance().getXxlJobGroupMapper().update(group);
}
}
}
}, Const.BEAT_TIMEOUT * 1000L, true);
registryMonitorThread.start();
}
/**
* stop
*/
public void stop(){
// 1registryOrRemoveThreadPool
registryOrRemoveThreadPool.shutdownNow();
// 2registryMonitorThread
registryMonitorThread.stop();
}
// ---------------------- tool ----------------------
/**
* registry
*/
public Response<String> registry(RegistryRequest registryParam) {
// valid
if (StringTool.isBlank(registryParam.getRegistryGroup())
|| StringTool.isBlank(registryParam.getRegistryKey())
|| StringTool.isBlank(registryParam.getRegistryValue())) {
return Response.ofFail("Illegal Argument.");
}
// async execute
registryOrRemoveThreadPool.execute(new Runnable() {
@Override
public void run() {
// 0-fail; 1-save suc; 2-update suc;
int ret = XxlJobAdminBootstrap.getInstance().getXxlJobRegistryMapper().registrySaveOrUpdate(registryParam.getRegistryGroup(), registryParam.getRegistryKey(), registryParam.getRegistryValue(), new Date());
if (ret == 1) {
// fresh (add)
freshGroupRegistryInfo(registryParam);
}
/*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 Response.ofSuccess();
}
/**
* registry remove
*/
public Response<String> registryRemove(RegistryRequest registryParam) {
// valid
if (StringTool.isBlank(registryParam.getRegistryGroup())
|| StringTool.isBlank(registryParam.getRegistryKey())
|| StringTool.isBlank(registryParam.getRegistryValue())) {
return Response.ofFail("Illegal Argument.");
}
// async execute
registryOrRemoveThreadPool.execute(new Runnable() {
@Override
public void run() {
int ret = XxlJobAdminBootstrap.getInstance().getXxlJobRegistryMapper().registryDelete(registryParam.getRegistryGroup(), registryParam.getRegistryKey(), registryParam.getRegistryValue());
if (ret > 0) {
// fresh (delete)
freshGroupRegistryInfo(registryParam);
}
}
});
return Response.ofSuccess();
}
private void freshGroupRegistryInfo(RegistryRequest registryParam){
// Under consideration, prevent affecting core tables
}
}

View File

@ -0,0 +1,374 @@
package com.xxl.job.admin.business.scheduler.thread;
import com.xxl.job.admin.business.constant.TriggerStatus;
import com.xxl.job.admin.business.model.XxlJobInfo;
import com.xxl.job.admin.business.scheduler.config.XxlJobAdminBootstrap;
import com.xxl.job.admin.business.scheduler.misfire.MisfireStrategyEnum;
import com.xxl.job.admin.business.scheduler.trigger.TriggerTypeEnum;
import com.xxl.job.admin.business.scheduler.type.ScheduleTypeEnum;
import com.xxl.tool.core.CollectionTool;
import com.xxl.tool.core.MapTool;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
/**
* @author xuxueli 2019-05-21
*/
public class JobScheduleHelper {
private static final Logger logger = LoggerFactory.getLogger(JobScheduleHelper.class);
/**
* pre-read time for scheduler, increase efficiency
*/
public static final long PRE_READ_MS = 5000;
/*
* elegant shutdown wait seconds
*/
private static final long ELEGANT_SHUTDOWN_WAITING_SECONDS = 10;
private Thread scheduleThread;
private Thread ringThread;
private volatile boolean scheduleThreadToStop = false;
private volatile boolean ringThreadToStop = false;
private final Map<Integer, List<Integer>> ringData = new ConcurrentHashMap<>();
/**
* start
*/
public void start(){
// init thread flag
scheduleThreadToStop = false;
ringThreadToStop = false;
// 1schedule thread
scheduleThread = new Thread(new Runnable() {
@Override
public void run() {
// align time
try {
TimeUnit.MILLISECONDS.sleep(5000 - System.currentTimeMillis()%1000 );
} catch (Throwable e) {
if (!scheduleThreadToStop) {
logger.error(e.getMessage(), e);
}
}
logger.info(">>>>>>>>> init xxl-job admin scheduler success.");
// pre-read count: treadpool-size * 10 (trigger-qps: 1000ms / 100ms each trigger cost)
int preReadCount = (XxlJobAdminBootstrap.getInstance().getTriggerPoolFastMax() + XxlJobAdminBootstrap.getInstance().getTriggerPoolSlowMax()) * 10;
// do schedule
while (!scheduleThreadToStop) {
// param
long start = System.currentTimeMillis();
boolean preReadSuc = true;
// transaction start
TransactionStatus transactionStatus = null;
try {
transactionStatus = XxlJobAdminBootstrap.getInstance().getTransactionManager().getTransaction(new DefaultTransactionDefinition());
// 1job lock
String lockedRecord = XxlJobAdminBootstrap.getInstance().getXxlJobLockMapper().scheduleLock();
long nowTime = System.currentTimeMillis();
// scan and process job
List<XxlJobInfo> scheduleList = XxlJobAdminBootstrap.getInstance().getXxlJobInfoMapper().scheduleJobQuery(nowTime + PRE_READ_MS, preReadCount);
if (CollectionTool.isNotEmpty(scheduleList)) {
// 2push time-ring
for (XxlJobInfo jobInfo: scheduleList) {
// time-ring jump
if (nowTime > jobInfo.getTriggerNextTime() + PRE_READ_MS) {
// 2.1trigger-expire > 5spass && make next-trigger-time
// 1misfire handle
MisfireStrategyEnum misfireStrategyEnum = MisfireStrategyEnum.match(jobInfo.getMisfireStrategy(), MisfireStrategyEnum.DO_NOTHING);
misfireStrategyEnum.getMisfireHandler().handle(jobInfo.getId());
// 2fresh next
refreshNextTriggerTime(jobInfo, new Date());
} else if (nowTime >= jobInfo.getTriggerNextTime()) {
// 2.2trigger-expire < 5sdirect-trigger && make next-trigger-time
// 1trigger direct
XxlJobAdminBootstrap.getInstance().getJobTriggerPoolHelper().trigger(jobInfo.getId(), TriggerTypeEnum.CRON, -1, null, null, null);
logger.debug(">>>>>>>>>>> xxl-job, schedule expire, direct trigger : jobId = " + jobInfo.getId() );
// 2fresh next
refreshNextTriggerTime(jobInfo, new Date());
// next-trigger-time in 5s, pre-read again
if (jobInfo.getTriggerStatus()== TriggerStatus.RUNNING.getValue() && nowTime + PRE_READ_MS > jobInfo.getTriggerNextTime()) {
// 1make ring second
int ringSecond = (int)((jobInfo.getTriggerNextTime()/1000)%60);
// 2push time ring (pre read)
pushTimeRing(ringSecond, jobInfo.getId());
logger.debug(">>>>>>>>>>> xxl-job, schedule pre-read, push trigger : jobId = " + jobInfo.getId() );
// 3fresh next
refreshNextTriggerTime(jobInfo, new Date(jobInfo.getTriggerNextTime()));
}
} else {
// 2.3trigger-pre-readtime-ring trigger && make next-trigger-time
// 1make ring second
int ringSecond = (int)((jobInfo.getTriggerNextTime()/1000)%60);
// 2push time ring
pushTimeRing(ringSecond, jobInfo.getId());
logger.debug(">>>>>>>>>>> xxl-job, schedule normal, push trigger : jobId = " + jobInfo.getId() );
// 3fresh next
refreshNextTriggerTime(jobInfo, new Date(jobInfo.getTriggerNextTime()));
}
}
// 3update trigger info
/*for (XxlJobInfo jobInfo: scheduleList) {
XxlJobAdminBootstrap.getInstance().getXxlJobInfoMapper().scheduleUpdate(jobInfo);
}*/
int batchSize = XxlJobAdminBootstrap.getInstance().getScheduleBatchSize();
List<List<XxlJobInfo>> scheduleListBatches = CollectionTool.split(scheduleList, batchSize);
for (List<XxlJobInfo> scheduleListBatch : scheduleListBatches) {
int totalAffected = XxlJobAdminBootstrap.getInstance().getXxlJobInfoMapper().scheduleBatchUpdate(scheduleListBatch);
logger.debug(">>>>>>>>>>> xxl-job, JobScheduleHelper scheduleBatchUpdate records:" + totalAffected);
}
} else {
preReadSuc = false;
}
} catch (Throwable e) {
if (!scheduleThreadToStop) {
logger.error(">>>>>>>>>>> xxl-job, JobScheduleHelper#scheduleThread error:{}", e.getMessage(), e);
}
} finally {
// transaction commit
try {
if (transactionStatus != null) {
XxlJobAdminBootstrap.getInstance().getTransactionManager().commit(transactionStatus); // avlid schedule repeat
}
} catch (Throwable e) {
logger.error(">>>>>>>>>>> xxl-job, JobScheduleHelper#scheduleThread transaction commit error:{}", e.getMessage(), e);
}
}
// transaction end
long cost = System.currentTimeMillis()-start;
// 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 (Throwable 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();
// 2ring thread
ringThread = new Thread(new Runnable() {
@Override
public void run() {
while (!ringThreadToStop) {
// align second
try {
TimeUnit.MILLISECONDS.sleep(1000 - System.currentTimeMillis() % 1000);
} catch (Throwable e) {
if (!ringThreadToStop) {
logger.error(e.getMessage(), e);
}
}
try {
// second data
List<Integer> ringItemData = new ArrayList<>();
// collect rind data, by second
int nowSecond = Calendar.getInstance().get(Calendar.SECOND);
for (int i = 0; i <= 2; i++) { // 避免调度遗漏处理耗时太长跨过刻度除当前刻度外 + 向前校验2个刻度
List<Integer> ringItemList = ringData.remove( (nowSecond+60-i)%60 );
if (CollectionTool.isNotEmpty(ringItemList)) {
// distinct for each second
List<Integer> ringItemListDistinct = ringItemList.stream().distinct().toList(); // 避免调度重复重复推送时间轮刻度去重只保留一个
if (ringItemListDistinct.size() < ringItemList.size()) {
logger.warn(">>>>>>>>>>> xxl-job, time-ring found job repeat beat : " + nowSecond + " = " + ringItemData);
}
// collect ring item
ringItemData.addAll(ringItemListDistinct);
}
}
// ring trigger
logger.debug(">>>>>>>>>>> xxl-job, time-ring beat : " + nowSecond + " = " + ringItemData);
if (CollectionTool.isNotEmpty(ringItemData)) {
// do trigger
for (int jobId: ringItemData) {
// do trigger
XxlJobAdminBootstrap.getInstance().getJobTriggerPoolHelper().trigger(jobId, TriggerTypeEnum.CRON, -1, null, null, null);
}
// clear
ringItemData.clear();
}
} catch (Throwable e) {
if (!ringThreadToStop) {
logger.error(">>>>>>>>>>> xxl-job, JobScheduleHelper#ringThread error:{}", e.getMessage(), e);
}
}
}
logger.info(">>>>>>>>>>> xxl-job, JobScheduleHelper#ringThread stop");
}
});
ringThread.setDaemon(true);
ringThread.setName("xxl-job, admin JobScheduleHelper#ringThread");
ringThread.start();
}
/**
* refresh next trigger time of job
*
* @param jobInfo job info
* @param fromTime from time
*/
private void refreshNextTriggerTime(XxlJobInfo jobInfo, Date fromTime) {
try {
// generate next trigger time
ScheduleTypeEnum scheduleTypeEnum = ScheduleTypeEnum.match(jobInfo.getScheduleType(), ScheduleTypeEnum.NONE);
Date nextTriggerTime = scheduleTypeEnum.getScheduleType().generateNextTriggerTime(jobInfo, fromTime);
// refresh next trigger-time + status
if (nextTriggerTime != null) {
// generate success
jobInfo.setTriggerStatus(-1); // pass, may be Inaccurate
jobInfo.setTriggerLastTime(jobInfo.getTriggerNextTime());
jobInfo.setTriggerNextTime(nextTriggerTime.getTime());
} else {
// generate fail, stop job
jobInfo.setTriggerStatus(TriggerStatus.STOPPED.getValue());
jobInfo.setTriggerLastTime(0);
jobInfo.setTriggerNextTime(0);
logger.error(">>>>>>>>>>> xxl-job, refreshNextValidTime fail for job: jobId={}, scheduleType={}, scheduleConf={}",
jobInfo.getId(), jobInfo.getScheduleType(), jobInfo.getScheduleConf());
}
} catch (Throwable e) {
// generate error, stop job
jobInfo.setTriggerStatus(TriggerStatus.STOPPED.getValue());
jobInfo.setTriggerLastTime(0);
jobInfo.setTriggerNextTime(0);
logger.error(">>>>>>>>>>> xxl-job, refreshNextValidTime error for job: jobId={}, scheduleType={}, scheduleConf={}",
jobInfo.getId(), jobInfo.getScheduleType(), jobInfo.getScheduleConf(), e);
}
}
/**
* push time ring
*
* @param ringSecond ring second
* @param jobId job id
*/
private void pushTimeRing(int ringSecond, int jobId){
// get ringItemData, init when not exists
List<Integer> ringItemList = ringData.computeIfAbsent(
ringSecond,
k -> new ArrayList<>());
// push async rind
ringItemList.add(jobId);
logger.debug(">>>>>>>>>>> xxl-job, schedule push time-ring : " + ringSecond + " = " + List.of(ringItemList));
}
/**
* stop
*/
public void stop(){
// 1stop schedule
scheduleThreadToStop = true;
try {
TimeUnit.SECONDS.sleep(1); // wait
} catch (Throwable e) {
logger.error(e.getMessage(), e);
}
if (scheduleThread.getState() != Thread.State.TERMINATED){
// interrupt and wait
scheduleThread.interrupt();
try {
scheduleThread.join();
} catch (Throwable e) {
logger.error(e.getMessage(), e);
}
}
// if has ring data, wait for elegent shutdown
boolean hasRingData = false;
if (MapTool.isNotEmpty(ringData)) {
for (int second : ringData.keySet()) {
List<Integer> ringItemList = ringData.get(second);
if (CollectionTool.isNotEmpty(ringItemList)) {
hasRingData = true;
break;
}
}
}
if (hasRingData) {
try {
TimeUnit.SECONDS.sleep(ELEGANT_SHUTDOWN_WAITING_SECONDS);
} catch (Throwable e) {
logger.error(e.getMessage(), e);
}
}
// 2stop ring (wait job-in-memory stop)
ringThreadToStop = true;
try {
TimeUnit.SECONDS.sleep(1);
} catch (Throwable e) {
logger.error(e.getMessage(), e);
}
if (ringThread.getState() != Thread.State.TERMINATED){
// interrupt and wait
ringThread.interrupt();
try {
ringThread.join();
} catch (Throwable e) {
logger.error(e.getMessage(), e);
}
}
logger.info(">>>>>>>>>>> xxl-job, JobScheduleHelper stop");
}
}

View File

@ -1,8 +1,7 @@
package com.xxl.job.admin.core.thread; package com.xxl.job.admin.business.scheduler.thread;
import com.xxl.job.admin.core.conf.XxlJobAdminConfig; import com.xxl.job.admin.business.scheduler.config.XxlJobAdminBootstrap;
import com.xxl.job.admin.core.trigger.TriggerTypeEnum; import com.xxl.job.admin.business.scheduler.trigger.TriggerTypeEnum;
import com.xxl.job.admin.core.trigger.XxlJobTrigger;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -15,7 +14,7 @@ import java.util.concurrent.atomic.AtomicInteger;
* @author xuxueli 2018-07-03 21:08:07 * @author xuxueli 2018-07-03 21:08:07
*/ */
public class JobTriggerPoolHelper { public class JobTriggerPoolHelper {
private static Logger logger = LoggerFactory.getLogger(JobTriggerPoolHelper.class); private static final Logger logger = LoggerFactory.getLogger(JobTriggerPoolHelper.class);
// ---------------------- trigger pool ---------------------- // ---------------------- trigger pool ----------------------
@ -24,35 +23,52 @@ public class JobTriggerPoolHelper {
private ThreadPoolExecutor fastTriggerPool = null; private ThreadPoolExecutor fastTriggerPool = null;
private ThreadPoolExecutor slowTriggerPool = null; private ThreadPoolExecutor slowTriggerPool = null;
/**
* start
*/
public void start(){ public void start(){
fastTriggerPool = new ThreadPoolExecutor( fastTriggerPool = new ThreadPoolExecutor(
10, 10,
XxlJobAdminConfig.getAdminConfig().getTriggerPoolFastMax(), XxlJobAdminBootstrap.getInstance().getTriggerPoolFastMax(),
60L,
TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>(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, 60L,
TimeUnit.SECONDS, TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>(2000), new LinkedBlockingQueue<Runnable>(2000),
new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
return new Thread(r, "xxl-job, admin JobTriggerPoolHelper-fastTriggerPool-" + r.hashCode());
}
},
new RejectedExecutionHandler() {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
logger.error(">>>>>>>>>>> xxl-job, admin JobTriggerPoolHelper-fastTriggerPool execute too fast, Runnable="+r.toString() );
}
});
slowTriggerPool = new ThreadPoolExecutor(
10,
XxlJobAdminBootstrap.getInstance().getTriggerPoolSlowMax(),
60L,
TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>(5000),
new ThreadFactory() { new ThreadFactory() {
@Override @Override
public Thread newThread(Runnable r) { public Thread newThread(Runnable r) {
return new Thread(r, "xxl-job, admin JobTriggerPoolHelper-slowTriggerPool-" + r.hashCode()); return new Thread(r, "xxl-job, admin JobTriggerPoolHelper-slowTriggerPool-" + r.hashCode());
} }
},
new RejectedExecutionHandler() {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
logger.error(">>>>>>>>>>> xxl-job, admin JobTriggerPoolHelper-slowTriggerPool execute too fast, Runnable="+r.toString() );
}
}); });
} }
/**
* stop
*/
public void stop() { public void stop() {
//triggerPool.shutdown(); //triggerPool.shutdown();
fastTriggerPool.shutdownNow(); fastTriggerPool.shutdownNow();
@ -66,10 +82,22 @@ public class JobTriggerPoolHelper {
private volatile ConcurrentMap<Integer, AtomicInteger> jobTimeoutCountMap = new ConcurrentHashMap<>(); private volatile ConcurrentMap<Integer, AtomicInteger> jobTimeoutCountMap = new ConcurrentHashMap<>();
// ---------------------- tool ----------------------
/** /**
* add trigger * trigger job
*
* @param jobId
* @param triggerType
* @param failRetryCount
* >=0: use this param
* <0: use param from job info config
* @param executorShardingParam
* @param executorParam
* null: use job param
* not null: cover job param
*/ */
public void addTrigger(final int jobId, public void trigger(final int jobId,
final TriggerTypeEnum triggerType, final TriggerTypeEnum triggerType,
final int failRetryCount, final int failRetryCount,
final String executorShardingParam, final String executorShardingParam,
@ -92,8 +120,8 @@ public class JobTriggerPoolHelper {
try { try {
// do trigger // do trigger
XxlJobTrigger.trigger(jobId, triggerType, failRetryCount, executorShardingParam, executorParam, addressList); XxlJobAdminBootstrap.getInstance().getJobTrigger().trigger(jobId, triggerType, failRetryCount, executorShardingParam, executorParam, addressList);
} catch (Exception e) { } catch (Throwable e) {
logger.error(e.getMessage(), e); logger.error(e.getMessage(), e);
} finally { } finally {
@ -116,35 +144,11 @@ public class JobTriggerPoolHelper {
} }
} }
@Override
public String toString() {
return "Job Runnable, jobId:"+jobId;
}
}); });
} }
// ---------------------- 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);
}
} }

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,17 @@
package com.xxl.job.admin.business.scheduler.type.strategy;
import com.xxl.job.admin.business.model.XxlJobInfo;
import com.xxl.job.admin.business.scheduler.cron.CronExpression;
import com.xxl.job.admin.business.scheduler.type.ScheduleType;
import java.util.Date;
public class CronScheduleType extends ScheduleType {
@Override
public Date generateNextTriggerTime(XxlJobInfo jobInfo, Date fromTime) throws Exception {
// generate next trigger time, with cron
return new CronExpression(jobInfo.getScheduleConf()).getNextValidTimeAfter(fromTime);
}
}

View File

@ -0,0 +1,26 @@
package com.xxl.job.admin.business.scheduler.type.strategy;
import com.xxl.job.admin.business.model.XxlJobInfo;
import com.xxl.job.admin.business.scheduler.type.ScheduleType;
import com.xxl.tool.core.DateTool;
import java.util.Date;
public class FixRateScheduleType extends ScheduleType {
@Override
public Date generateNextTriggerTime(XxlJobInfo jobInfo, Date fromTime) throws Exception {
// generate next trigger time, fix rate delay
Date nextTriggerTime = new Date(fromTime.getTime() + Long.parseLong(jobInfo.getScheduleConf()) * 1000L);
// assign second:
if (nextTriggerTime.getTime() % 1000 != 0) {
nextTriggerTime = DateTool.addSeconds(DateTool.setMilliseconds(nextTriggerTime, 0), 1);
}
return nextTriggerTime;
}
}

View File

@ -0,0 +1,16 @@
package com.xxl.job.admin.business.scheduler.type.strategy;
import com.xxl.job.admin.business.model.XxlJobInfo;
import com.xxl.job.admin.business.scheduler.type.ScheduleType;
import java.util.Date;
public class NoneScheduleType extends ScheduleType {
@Override
public Date generateNextTriggerTime(XxlJobInfo jobInfo, Date fromTime) throws Exception {
// generate none trigger-time
return null;
}
}

View File

@ -0,0 +1,63 @@
package com.xxl.job.admin.business.service;
import com.xxl.job.admin.business.model.XxlJobInfo;
import com.xxl.sso.core.model.LoginInfo;
import com.xxl.tool.response.PageModel;
import com.xxl.tool.response.Response;
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
*/
public Response<PageModel<XxlJobInfo>> pageList(int offset, int pagesize, int jobGroup, int triggerStatus, String jobDesc, String executorHandler, String author);
/**
* add job
*/
public Response<String> add(XxlJobInfo jobInfo, LoginInfo loginInfo);
/**
* update job
*/
public Response<String> update(XxlJobInfo jobInfo, LoginInfo loginInfo);
/**
* remove job
*/
public Response<String> remove(int id, LoginInfo loginInfo);
/**
* start job
*/
public Response<String> start(int id, LoginInfo loginInfo);
/**
* stop job
*/
public Response<String> stop(int id, LoginInfo loginInfo);
/**
* trigger
*/
public Response<String> trigger(LoginInfo loginInfo, int jobId, String executorParam, String addressList);
/**
* dashboard info
*/
public Map<String,Object> dashboardInfo();
/**
* chart info
*/
public Response<Map<String,Object>> chartInfo(Date startDate, Date endDate);
}

View File

@ -0,0 +1,33 @@
package com.xxl.job.admin.business.service.impl;
import com.xxl.job.admin.business.scheduler.config.XxlJobAdminBootstrap;
import com.xxl.job.core.openapi.AdminBiz;
import com.xxl.job.core.openapi.model.CallbackRequest;
import com.xxl.job.core.openapi.model.RegistryRequest;
import com.xxl.tool.response.Response;
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 Response<String> callback(List<CallbackRequest> callbackRequestList) {
return XxlJobAdminBootstrap.getInstance().getJobCompleteHelper().callback(callbackRequestList);
}
@Override
public Response<String> registry(RegistryRequest registryRequest) {
return XxlJobAdminBootstrap.getInstance().getJobRegistryHelper().registry(registryRequest);
}
@Override
public Response<String> registryRemove(RegistryRequest registryRequest) {
return XxlJobAdminBootstrap.getInstance().getJobRegistryHelper().registryRemove(registryRequest);
}
}

View File

@ -0,0 +1,531 @@
package com.xxl.job.admin.business.service.impl;
import com.xxl.job.admin.business.constant.TriggerStatus;
import com.xxl.job.admin.business.mapper.*;
import com.xxl.job.admin.business.model.XxlJobGroup;
import com.xxl.job.admin.business.model.XxlJobInfo;
import com.xxl.job.admin.business.model.XxlJobLogReport;
import com.xxl.job.admin.business.scheduler.config.XxlJobAdminBootstrap;
import com.xxl.job.admin.business.scheduler.cron.CronExpression;
import com.xxl.job.admin.business.scheduler.misfire.MisfireStrategyEnum;
import com.xxl.job.admin.business.scheduler.route.ExecutorRouteStrategyEnum;
import com.xxl.job.admin.business.scheduler.thread.JobScheduleHelper;
import com.xxl.job.admin.business.scheduler.trigger.TriggerTypeEnum;
import com.xxl.job.admin.business.scheduler.type.ScheduleTypeEnum;
import com.xxl.job.admin.business.service.XxlJobService;
import com.xxl.job.admin.framework.util.I18nUtil;
import com.xxl.job.admin.framework.util.JobGroupPermissionUtil;
import com.xxl.job.core.constant.ExecutorBlockStrategyEnum;
import com.xxl.job.core.glue.GlueTypeEnum;
import com.xxl.sso.core.model.LoginInfo;
import com.xxl.tool.core.DateTool;
import com.xxl.tool.core.StringTool;
import com.xxl.tool.json.GsonTool;
import com.xxl.tool.response.PageModel;
import com.xxl.tool.response.Response;
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
private XxlJobGroupMapper xxlJobGroupMapper;
@Resource
private XxlJobInfoMapper xxlJobInfoMapper;
@Resource
public XxlJobLogMapper xxlJobLogMapper;
@Resource
private XxlJobLogGlueMapper xxlJobLogGlueMapper;
@Resource
private XxlJobLogReportMapper xxlJobLogReportMapper;
@Override
public Response<PageModel<XxlJobInfo>> pageList(int offset, int pagesize, int jobGroup, int triggerStatus, String jobDesc, String executorHandler, String author) {
// page list
List<XxlJobInfo> list = xxlJobInfoMapper.pageList(offset, pagesize, jobGroup, triggerStatus, jobDesc, executorHandler, author);
int list_count = xxlJobInfoMapper.pageListCount(offset, pagesize, jobGroup, triggerStatus, jobDesc, executorHandler, author);
// package result
PageModel<XxlJobInfo> pageModel = new PageModel<>();
pageModel.setData(list);
pageModel.setTotal(list_count);
return Response.ofSuccess(pageModel);
}
@Override
public Response<String> add(XxlJobInfo jobInfo, LoginInfo loginInfo) {
// valid base
XxlJobGroup group = xxlJobGroupMapper.load(jobInfo.getJobGroup());
if (group == null) {
return Response.ofFail (I18nUtil.getString("system_please_choose")+I18nUtil.getString("jobinfo_field_jobgroup"));
}
if (StringTool.isBlank(jobInfo.getJobDesc())) {
return Response.ofFail ( (I18nUtil.getString("system_please_input")+I18nUtil.getString("jobinfo_field_jobdesc")) );
}
if (StringTool.isBlank(jobInfo.getAuthor())) {
return Response.ofFail ( (I18nUtil.getString("system_please_input")+I18nUtil.getString("jobinfo_field_author")) );
}
// valid trigger
ScheduleTypeEnum scheduleTypeEnum = ScheduleTypeEnum.match(jobInfo.getScheduleType(), null);
if (scheduleTypeEnum == null) {
return Response.ofFail ( (I18nUtil.getString("schedule_type")+I18nUtil.getString("system_invalid")) );
}
if (scheduleTypeEnum == ScheduleTypeEnum.CRON) {
if (jobInfo.getScheduleConf()==null || !CronExpression.isValidExpression(jobInfo.getScheduleConf())) {
return Response.ofFail ( "Cron"+I18nUtil.getString("system_invalid"));
}
} else if (scheduleTypeEnum == ScheduleTypeEnum.FIX_RATE/* || scheduleTypeEnum == ScheduleTypeEnum.FIX_DELAY*/) {
if (jobInfo.getScheduleConf() == null) {
return Response.ofFail ( (I18nUtil.getString("schedule_type")) );
}
try {
int fixSecond = Integer.parseInt(jobInfo.getScheduleConf());
if (fixSecond < 1) {
return Response.ofFail ( (I18nUtil.getString("schedule_type")+I18nUtil.getString("system_invalid")) );
}
} catch (Exception e) {
return Response.ofFail ( (I18nUtil.getString("schedule_type")+I18nUtil.getString("system_invalid")) );
}
}
// valid job
if (GlueTypeEnum.match(jobInfo.getGlueType()) == null) {
return Response.ofFail ( (I18nUtil.getString("jobinfo_field_gluetype")+I18nUtil.getString("system_invalid")) );
}
if (GlueTypeEnum.BEAN==GlueTypeEnum.match(jobInfo.getGlueType()) && StringTool.isBlank(jobInfo.getExecutorHandler()) ) {
return Response.ofFail ( (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 Response.ofFail ( (I18nUtil.getString("jobinfo_field_executorRouteStrategy")+I18nUtil.getString("system_invalid")) );
}
if (MisfireStrategyEnum.match(jobInfo.getMisfireStrategy(), null) == null) {
return Response.ofFail ( (I18nUtil.getString("misfire_strategy")+I18nUtil.getString("system_invalid")) );
}
if (ExecutorBlockStrategyEnum.match(jobInfo.getExecutorBlockStrategy(), null) == null) {
return Response.ofFail ( (I18nUtil.getString("jobinfo_field_executorBlockStrategy")+I18nUtil.getString("system_invalid")) );
}
// ChildJobId valid
if (StringTool.isNotBlank(jobInfo.getChildJobId())) {
String[] childJobIds = jobInfo.getChildJobId().split(",");
for (String childJobIdItem: childJobIds) {
if (StringTool.isNotBlank(childJobIdItem) && StringTool.isNumeric(childJobIdItem)) {
XxlJobInfo childJobInfo = xxlJobInfoMapper.loadById(Integer.parseInt(childJobIdItem));
if (childJobInfo==null) {
return Response.ofFail (
MessageFormat.format((I18nUtil.getString("jobinfo_field_childJobId")+"({0})"+I18nUtil.getString("system_not_found")), childJobIdItem));
}
// valid jobGroup permission
if (!JobGroupPermissionUtil.hasJobGroupPermission(loginInfo, childJobInfo.getJobGroup())) {
return Response.ofFail (
MessageFormat.format((I18nUtil.getString("jobinfo_field_childJobId")+"({0})"+I18nUtil.getString("system_permission_limit")), childJobIdItem));
}
} else {
return Response.ofFail (
MessageFormat.format((I18nUtil.getString("jobinfo_field_childJobId")+"({0})"+I18nUtil.getString("system_invalid")), 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());
// remove the whitespace
jobInfo.setExecutorHandler(jobInfo.getExecutorHandler().trim());
xxlJobInfoMapper.save(jobInfo);
if (jobInfo.getId() < 1) {
return Response.ofFail ( (I18nUtil.getString("jobinfo_field_add")+I18nUtil.getString("system_fail")) );
}
// write operation log
logger.info(">>>>>>>>>>> xxl-job operation log: operator = {}, type = {}, content = {}",
loginInfo.getUserName(), "jobinfo-save", GsonTool.toJson(jobInfo));
return Response.ofSuccess(String.valueOf(jobInfo.getId()));
}
@Override
public Response<String> update(XxlJobInfo jobInfo, LoginInfo loginInfo) {
// valid base
if (StringTool.isBlank(jobInfo.getJobDesc())) {
return Response.ofFail ( (I18nUtil.getString("system_please_input")+I18nUtil.getString("jobinfo_field_jobdesc")) );
}
if (StringTool.isBlank(jobInfo.getAuthor())) {
return Response.ofFail ( (I18nUtil.getString("system_please_input")+I18nUtil.getString("jobinfo_field_author")) );
}
// valid trigger
ScheduleTypeEnum scheduleTypeEnum = ScheduleTypeEnum.match(jobInfo.getScheduleType(), null);
if (scheduleTypeEnum == null) {
return Response.ofFail ( (I18nUtil.getString("schedule_type")+I18nUtil.getString("system_invalid")) );
}
if (scheduleTypeEnum == ScheduleTypeEnum.CRON) {
if (jobInfo.getScheduleConf()==null || !CronExpression.isValidExpression(jobInfo.getScheduleConf())) {
return Response.ofFail ( "Cron"+I18nUtil.getString("system_invalid") );
}
} else if (scheduleTypeEnum == ScheduleTypeEnum.FIX_RATE /*|| scheduleTypeEnum == ScheduleTypeEnum.FIX_DELAY*/) {
if (jobInfo.getScheduleConf() == null) {
return Response.ofFail ( (I18nUtil.getString("schedule_type")+I18nUtil.getString("system_invalid")) );
}
try {
int fixSecond = Integer.parseInt(jobInfo.getScheduleConf());
if (fixSecond < 1) {
return Response.ofFail ( (I18nUtil.getString("schedule_type")+I18nUtil.getString("system_invalid")) );
}
} catch (Exception e) {
return Response.ofFail ( (I18nUtil.getString("schedule_type")+I18nUtil.getString("system_invalid")) );
}
}
// valid advanced
if (ExecutorRouteStrategyEnum.match(jobInfo.getExecutorRouteStrategy(), null) == null) {
return Response.ofFail ( (I18nUtil.getString("jobinfo_field_executorRouteStrategy")+I18nUtil.getString("system_invalid")) );
}
if (MisfireStrategyEnum.match(jobInfo.getMisfireStrategy(), null) == null) {
return Response.ofFail ( (I18nUtil.getString("misfire_strategy")+I18nUtil.getString("system_invalid")) );
}
if (ExecutorBlockStrategyEnum.match(jobInfo.getExecutorBlockStrategy(), null) == null) {
return Response.ofFail ( (I18nUtil.getString("jobinfo_field_executorBlockStrategy")+I18nUtil.getString("system_invalid")) );
}
// ChildJobId valid
if (StringTool.isNotBlank(jobInfo.getChildJobId())) {
String[] childJobIds = jobInfo.getChildJobId().split(",");
for (String childJobIdItem: childJobIds) {
if (StringTool.isNotBlank(childJobIdItem) && StringTool.isNumeric(childJobIdItem)) {
// parse child
int childJobId = Integer.parseInt(childJobIdItem);
if (childJobId == jobInfo.getId()) {
return Response.ofFail ( (I18nUtil.getString("jobinfo_field_childJobId")+"("+childJobId+")"+I18nUtil.getString("system_invalid")) );
}
// valid child
XxlJobInfo childJobInfo = xxlJobInfoMapper.loadById(childJobId);
if (childJobInfo==null) {
return Response.ofFail (
MessageFormat.format((I18nUtil.getString("jobinfo_field_childJobId")+"({0})"+I18nUtil.getString("system_not_found")), childJobIdItem));
}
// valid jobGroup permission
if (!JobGroupPermissionUtil.hasJobGroupPermission(loginInfo, childJobInfo.getJobGroup())) {
return Response.ofFail (
MessageFormat.format((I18nUtil.getString("jobinfo_field_childJobId")+"({0})"+I18nUtil.getString("system_permission_limit")), childJobIdItem));
}
} else {
return Response.ofFail (
MessageFormat.format((I18nUtil.getString("jobinfo_field_childJobId")+"({0})"+I18nUtil.getString("system_invalid")), 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 = xxlJobGroupMapper.load(jobInfo.getJobGroup());
if (jobGroup == null) {
return Response.ofFail ( (I18nUtil.getString("jobinfo_field_jobgroup")+I18nUtil.getString("system_invalid")) );
}
// stage job info
XxlJobInfo exists_jobInfo = xxlJobInfoMapper.loadById(jobInfo.getId());
if (exists_jobInfo == null) {
return Response.ofFail ( (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() == TriggerStatus.RUNNING.getValue() && !scheduleDataNotChanged) {
try {
// generate next trigger time
Date nextValidTime = scheduleTypeEnum.getScheduleType().generateNextTriggerTime(jobInfo, new Date(System.currentTimeMillis() + JobScheduleHelper.PRE_READ_MS));
if (nextValidTime == null) {
return Response.ofFail ( (I18nUtil.getString("schedule_type")+I18nUtil.getString("system_invalid")) );
}
nextTriggerTime = nextValidTime.getTime();
} catch (Exception e) {
logger.error(e.getMessage(), e);
return Response.ofFail ( (I18nUtil.getString("schedule_type")+I18nUtil.getString("system_invalid")) );
}
}
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());
// remove the whitespace
exists_jobInfo.setExecutorHandler(jobInfo.getExecutorHandler().trim());
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());
xxlJobInfoMapper.update(exists_jobInfo);
// write operation log
logger.info(">>>>>>>>>>> xxl-job operation log: operator = {}, type = {}, content = {}",
loginInfo.getUserName(), "jobinfo-update", GsonTool.toJson(exists_jobInfo));
return Response.ofSuccess();
}
@Override
public Response<String> remove(int id, LoginInfo loginInfo) {
// valid job
XxlJobInfo xxlJobInfo = xxlJobInfoMapper.loadById(id);
if (xxlJobInfo == null) {
return Response.ofSuccess();
}
// valid jobGroup permission
if (!JobGroupPermissionUtil.hasJobGroupPermission(loginInfo, xxlJobInfo.getJobGroup())) {
return Response.ofFail(I18nUtil.getString("system_permission_limit"));
}
xxlJobInfoMapper.delete(id);
xxlJobLogMapper.delete(id);
xxlJobLogGlueMapper.deleteByJobId(id);
// write operation log
logger.info(">>>>>>>>>>> xxl-job operation log: operator = {}, type = {}, content = {}",
loginInfo.getUserName(), "jobinfo-remove", id);
return Response.ofSuccess();
}
@Override
public Response<String> start(int id, LoginInfo loginInfo) {
// load and valid
XxlJobInfo xxlJobInfo = xxlJobInfoMapper.loadById(id);
if (xxlJobInfo == null) {
return Response.ofFail(I18nUtil.getString("jobinfo_glue_jobid_invalid"));
}
// valid jobGroup permission
if (!JobGroupPermissionUtil.hasJobGroupPermission(loginInfo, xxlJobInfo.getJobGroup())) {
return Response.ofFail(I18nUtil.getString("system_permission_limit"));
}
// valid ScheduleType: can not be none
ScheduleTypeEnum scheduleTypeEnum = ScheduleTypeEnum.match(xxlJobInfo.getScheduleType(), ScheduleTypeEnum.NONE);
if (ScheduleTypeEnum.NONE == scheduleTypeEnum) {
return Response.ofFail(I18nUtil.getString("schedule_type_none_limit_start"));
}
// next trigger time (5s后生效避开预读周期)
long nextTriggerTime = 0;
try {
// generate next trigger time
Date nextValidTime = scheduleTypeEnum.getScheduleType().generateNextTriggerTime(xxlJobInfo, new Date(System.currentTimeMillis() + JobScheduleHelper.PRE_READ_MS));
if (nextValidTime == null) {
return Response.ofFail ( (I18nUtil.getString("schedule_type")+I18nUtil.getString("system_invalid")) );
}
nextTriggerTime = nextValidTime.getTime();
} catch (Exception e) {
logger.error(e.getMessage(), e);
return Response.ofFail ( (I18nUtil.getString("schedule_type")+I18nUtil.getString("system_invalid")) );
}
xxlJobInfo.setTriggerStatus(TriggerStatus.RUNNING.getValue());
xxlJobInfo.setTriggerLastTime(0);
xxlJobInfo.setTriggerNextTime(nextTriggerTime);
xxlJobInfo.setUpdateTime(new Date());
xxlJobInfoMapper.update(xxlJobInfo);
// write operation log
logger.info(">>>>>>>>>>> xxl-job operation log: operator = {}, type = {}, content = {}",
loginInfo.getUserName(), "jobinfo-start", id);
return Response.ofSuccess();
}
@Override
public Response<String> stop(int id, LoginInfo loginInfo) {
// load and valid
XxlJobInfo xxlJobInfo = xxlJobInfoMapper.loadById(id);
if (xxlJobInfo == null) {
return Response.ofFail(I18nUtil.getString("jobinfo_glue_jobid_invalid"));
}
// valid jobGroup permission
if (!JobGroupPermissionUtil.hasJobGroupPermission(loginInfo, xxlJobInfo.getJobGroup())) {
return Response.ofFail(I18nUtil.getString("system_permission_limit"));
}
// stop
xxlJobInfo.setTriggerStatus(TriggerStatus.STOPPED.getValue());
xxlJobInfo.setTriggerLastTime(0);
xxlJobInfo.setTriggerNextTime(0);
xxlJobInfo.setUpdateTime(new Date());
xxlJobInfoMapper.update(xxlJobInfo);
// write operation log
logger.info(">>>>>>>>>>> xxl-job operation log: operator = {}, type = {}, content = {}",
loginInfo.getUserName(), "jobinfo-stop", id);
return Response.ofSuccess();
}
@Override
public Response<String> trigger(LoginInfo loginInfo, int jobId, String executorParam, String addressList) {
// valid job
XxlJobInfo xxlJobInfo = xxlJobInfoMapper.loadById(jobId);
if (xxlJobInfo == null) {
return Response.ofFail(I18nUtil.getString("jobinfo_glue_jobid_invalid"));
}
// valid jobGroup permission
if (!JobGroupPermissionUtil.hasJobGroupPermission(loginInfo, xxlJobInfo.getJobGroup())) {
return Response.ofFail(I18nUtil.getString("system_permission_limit"));
}
// force cover job param
if (executorParam == null) {
executorParam = "";
}
XxlJobAdminBootstrap.getInstance().getJobTriggerPoolHelper().trigger(jobId, TriggerTypeEnum.MANUAL, -1, null, executorParam, addressList);
// write operation log
logger.info(">>>>>>>>>>> xxl-job operation log: operator = {}, type = {}, content = {}",
loginInfo.getUserName(), "jobinfo-trigger", jobId);
return Response.ofSuccess();
}
@Override
public Map<String, Object> dashboardInfo() {
int jobInfoCount = xxlJobInfoMapper.findAllCount();
int jobLogCount = 0;
int jobLogSuccessCount = 0;
XxlJobLogReport xxlJobLogReport = xxlJobLogReportMapper.queryLogReportTotal();
if (xxlJobLogReport != null) {
jobLogCount = xxlJobLogReport.getRunningCount() + xxlJobLogReport.getSucCount() + xxlJobLogReport.getFailCount();
jobLogSuccessCount = xxlJobLogReport.getSucCount();
}
// executor count
Set<String> executorAddressSet = new HashSet<String>();
List<XxlJobGroup> groupList = xxlJobGroupMapper.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<String, Object> dashboardMap = new HashMap<String, Object>();
dashboardMap.put("jobInfoCount", jobInfoCount);
dashboardMap.put("jobLogCount", jobLogCount);
dashboardMap.put("jobLogSuccessCount", jobLogSuccessCount);
dashboardMap.put("executorCount", executorCount);
return dashboardMap;
}
@Override
public Response<Map<String, Object>> chartInfo(Date startDate, Date endDate) {
// process
List<String> triggerDayList = new ArrayList<String>();
List<Integer> triggerDayCountRunningList = new ArrayList<Integer>();
List<Integer> triggerDayCountSucList = new ArrayList<Integer>();
List<Integer> triggerDayCountFailList = new ArrayList<Integer>();
int triggerCountRunningTotal = 0;
int triggerCountSucTotal = 0;
int triggerCountFailTotal = 0;
List<XxlJobLogReport> logReportList = xxlJobLogReportMapper.queryLogReport(startDate, endDate);
if (logReportList!=null && !logReportList.isEmpty()) {
for (XxlJobLogReport item: logReportList) {
String day = DateTool.formatDate(item.getTriggerDay());
int triggerDayCountRunning = item.getRunningCount();
int triggerDayCountSuc = item.getSucCount();
int triggerDayCountFail = item.getFailCount();
triggerDayList.add(day);
triggerDayCountRunningList.add(triggerDayCountRunning);
triggerDayCountSucList.add(triggerDayCountSuc);
triggerDayCountFailList.add(triggerDayCountFail);
triggerCountRunningTotal += triggerDayCountRunning;
triggerCountSucTotal += triggerDayCountSuc;
triggerCountFailTotal += triggerDayCountFail;
}
} else {
for (int i = -6; i <= 0; i++) {
triggerDayList.add(DateTool.formatDate(DateTool.addDays(new Date(), i)));
triggerDayCountRunningList.add(0);
triggerDayCountSucList.add(0);
triggerDayCountFailList.add(0);
}
}
Map<String, Object> result = new HashMap<String, Object>();
result.put("triggerDayList", triggerDayList);
result.put("triggerDayCountRunningList", triggerDayCountRunningList);
result.put("triggerDayCountSucList", triggerDayCountSucList);
result.put("triggerDayCountFailList", triggerDayCountFailList);
result.put("triggerCountRunningTotal", triggerCountRunningTotal);
result.put("triggerCountSucTotal", triggerCountSucTotal);
result.put("triggerCountFailTotal", triggerCountFailTotal);
return Response.ofSuccess(result);
}
}

View File

@ -1,96 +0,0 @@
package com.xxl.job.admin.controller;
import com.xxl.job.admin.controller.annotation.PermissionLimit;
import com.xxl.job.admin.service.LoginService;
import com.xxl.job.admin.service.XxlJobService;
import com.xxl.job.core.biz.model.ReturnT;
import org.springframework.beans.propertyeditors.CustomDateEditor;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.view.RedirectView;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;
/**
* index controller
* @author xuxueli 2015-12-19 16:13:16
*/
@Controller
public class IndexController {
@Resource
private XxlJobService xxlJobService;
@Resource
private LoginService loginService;
@RequestMapping("/")
public String index(Model model) {
Map<String, Object> dashboardMap = xxlJobService.dashboardInfo();
model.addAllAttributes(dashboardMap);
return "index";
}
@RequestMapping("/chartInfo")
@ResponseBody
public ReturnT<Map<String, Object>> chartInfo(Date startDate, Date endDate) {
ReturnT<Map<String, Object>> chartInfo = xxlJobService.chartInfo(startDate, endDate);
return chartInfo;
}
@RequestMapping("/toLogin")
@PermissionLimit(limit=false)
public ModelAndView toLogin(HttpServletRequest request, HttpServletResponse response,ModelAndView modelAndView) {
if (loginService.ifLogin(request, response) != null) {
modelAndView.setView(new RedirectView("/",true,false));
return modelAndView;
}
return new ModelAndView("login");
}
@RequestMapping(value="login", method=RequestMethod.POST)
@ResponseBody
@PermissionLimit(limit=false)
public ReturnT<String> loginDo(HttpServletRequest request, HttpServletResponse response, String userName, String password, String ifRemember){
boolean ifRem = (ifRemember!=null && ifRemember.trim().length()>0 && "on".equals(ifRemember))?true:false;
return loginService.login(request, response, userName, password, ifRem);
}
@RequestMapping(value="logout", method=RequestMethod.POST)
@ResponseBody
@PermissionLimit(limit=false)
public ReturnT<String> logout(HttpServletRequest request, HttpServletResponse response){
return loginService.logout(request, response);
}
@RequestMapping("/help")
public String help() {
/*if (!PermissionInterceptor.ifLogin(request)) {
return "redirect:/toLogin";
}*/
return "help";
}
@InitBinder
public void initBinder(WebDataBinder binder) {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
dateFormat.setLenient(false);
binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, true));
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,29 +0,0 @@
package com.xxl.job.admin.controller.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 权限限制
* @author xuxueli 2015-12-12 18:29:02
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface PermissionLimit {
/**
* 登录拦截 (默认拦截)
*/
boolean limit() default true;
/**
* 要求管理员权限
*
* @return
*/
boolean adminuser() default false;
}

View File

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

View File

@ -1,59 +0,0 @@
package com.xxl.job.admin.controller.interceptor;
import com.xxl.job.admin.controller.annotation.PermissionLimit;
import com.xxl.job.admin.core.model.XxlJobUser;
import com.xxl.job.admin.core.util.I18nUtil;
import com.xxl.job.admin.service.LoginService;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.AsyncHandlerInterceptor;
/**
* 权限拦截
*
* @author xuxueli 2015-12-12 18:09:04
*/
@Component
public class PermissionInterceptor implements AsyncHandlerInterceptor {
@Resource
private LoginService loginService;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (!(handler instanceof HandlerMethod)) {
return true; // proceed with the next interceptor
}
// if need login
boolean needLogin = true;
boolean needAdminuser = false;
HandlerMethod method = (HandlerMethod)handler;
PermissionLimit permission = method.getMethodAnnotation(PermissionLimit.class);
if (permission!=null) {
needLogin = permission.limit();
needAdminuser = permission.adminuser();
}
if (needLogin) {
XxlJobUser loginUser = loginService.ifLogin(request, response);
if (loginUser == null) {
response.setStatus(302);
response.setHeader("location", request.getContextPath()+"/toLogin");
return false;
}
if (needAdminuser && loginUser.getRole()!=1) {
throw new RuntimeException(I18nUtil.getString("system_permission_limit"));
}
request.setAttribute(LoginService.LOGIN_IDENTITY_KEY, loginUser);
}
return true; // proceed with the next interceptor
}
}

View File

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

View File

@ -1,99 +0,0 @@
package com.xxl.job.admin.core.complete;
import com.xxl.job.admin.core.conf.XxlJobAdminConfig;
import com.xxl.job.admin.core.model.XxlJobInfo;
import com.xxl.job.admin.core.model.XxlJobLog;
import com.xxl.job.admin.core.thread.JobTriggerPoolHelper;
import com.xxl.job.admin.core.trigger.TriggerTypeEnum;
import com.xxl.job.admin.core.util.I18nUtil;
import com.xxl.job.core.biz.model.ReturnT;
import com.xxl.job.core.context.XxlJobContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.text.MessageFormat;
/**
* @author xuxueli 2020-10-30 20:43:10
*/
public class XxlJobCompleter {
private static Logger logger = LoggerFactory.getLogger(XxlJobCompleter.class);
/**
* common fresh handle entrance (limit only once)
*
* @param xxlJobLog
* @return
*/
public static int updateHandleInfoAndFinish(XxlJobLog xxlJobLog) {
// finish
finishJob(xxlJobLog);
// text最大64kb 避免长度过长
if (xxlJobLog.getHandleMsg().length() > 15000) {
xxlJobLog.setHandleMsg( xxlJobLog.getHandleMsg().substring(0, 15000) );
}
// fresh handle
return XxlJobAdminConfig.getAdminConfig().getXxlJobLogDao().updateHandleInfo(xxlJobLog);
}
/**
* do somethind to finish job
*/
private static void finishJob(XxlJobLog xxlJobLog){
// 1handle success, to trigger child job
String triggerChildMsg = null;
if (XxlJobContext.HANDLE_CODE_SUCCESS == xxlJobLog.getHandleCode()) {
XxlJobInfo xxlJobInfo = XxlJobAdminConfig.getAdminConfig().getXxlJobInfoDao().loadById(xxlJobLog.getJobId());
if (xxlJobInfo!=null && xxlJobInfo.getChildJobId()!=null && xxlJobInfo.getChildJobId().trim().length()>0) {
triggerChildMsg = "<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>"+ I18nUtil.getString("jobconf_trigger_child_run") +"<<<<<<<<<<< </span><br>";
String[] childJobIds = xxlJobInfo.getChildJobId().split(",");
for (int i = 0; i < childJobIds.length; i++) {
int childJobId = (childJobIds[i]!=null && childJobIds[i].trim().length()>0 && isNumeric(childJobIds[i]))?Integer.valueOf(childJobIds[i]):-1;
if (childJobId > 0) {
JobTriggerPoolHelper.trigger(childJobId, TriggerTypeEnum.PARENT, -1, null, null, null);
ReturnT<String> triggerChildResult = ReturnT.SUCCESS;
// add msg
triggerChildMsg += MessageFormat.format(I18nUtil.getString("jobconf_callback_child_msg1"),
(i+1),
childJobIds.length,
childJobIds[i],
(triggerChildResult.getCode()==ReturnT.SUCCESS_CODE?I18nUtil.getString("system_success"):I18nUtil.getString("system_fail")),
triggerChildResult.getMsg());
} else {
triggerChildMsg += MessageFormat.format(I18nUtil.getString("jobconf_callback_child_msg2"),
(i+1),
childJobIds.length,
childJobIds[i]);
}
}
}
}
if (triggerChildMsg != null) {
xxlJobLog.setHandleMsg( xxlJobLog.getHandleMsg() + triggerChildMsg );
}
// 2fix_delay trigger next
// on the way
}
private static boolean isNumeric(String str){
try {
int result = Integer.valueOf(str);
return true;
} catch (NumberFormatException e) {
return false;
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,184 +0,0 @@
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<Runnable>(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<Long> 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<String> callback(List<HandleCallbackParam> callbackParamList) {
callbackThreadPool.execute(new Runnable() {
@Override
public void run() {
for (HandleCallbackParam handleCallbackParam: callbackParamList) {
ReturnT<String> callbackResult = callback(handleCallbackParam);
logger.debug(">>>>>>>>> JobApiController.callback {}, handleCallbackParam={}, callbackResult={}",
(callbackResult.getCode()== ReturnT.SUCCESS_CODE?"success":"fail"), handleCallbackParam, callbackResult);
}
}
});
return ReturnT.SUCCESS;
}
private ReturnT<String> callback(HandleCallbackParam handleCallbackParam) {
// valid log item
XxlJobLog log = XxlJobAdminConfig.getAdminConfig().getXxlJobLogDao().load(handleCallbackParam.getLogId());
if (log == null) {
return new ReturnT<String>(ReturnT.FAIL_CODE, "log item not found.");
}
if (log.getHandleCode() > 0) {
return new ReturnT<String>(ReturnT.FAIL_CODE, "log repeate callback."); // avoid repeat callback, trigger child job etc
}
// handle msg
StringBuffer handleMsg = new StringBuffer();
if (log.getHandleMsg()!=null) {
handleMsg.append(log.getHandleMsg()).append("<br>");
}
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;
}
}

View File

@ -1,110 +0,0 @@
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<Long> 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());
// 1fail retry monitor
if (log.getExecutorFailRetryCount() > 0) {
JobTriggerPoolHelper.trigger(log.getJobId(), TriggerTypeEnum.RETRY, (log.getExecutorFailRetryCount()-1), log.getExecutorShardingParam(), log.getExecutorParam(), null);
String retryMsg = "<br><br><span style=\"color:#F39C12;\" > >>>>>>>>>>>"+ I18nUtil.getString("jobconf_trigger_type_retry") +"<<<<<<<<<<< </span><br>";
log.setTriggerMsg(log.getTriggerMsg() + retryMsg);
XxlJobAdminConfig.getAdminConfig().getXxlJobLogDao().updateTriggerInfo(log);
}
// 2fail 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);
}
}
}

View File

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

View File

@ -1,204 +0,0 @@
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<Runnable>(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<XxlJobGroup> groupList = XxlJobAdminConfig.getAdminConfig().getXxlJobGroupDao().findByAddressType(0);
if (groupList!=null && !groupList.isEmpty()) {
// remove dead address (admin/executor)
List<Integer> ids = XxlJobAdminConfig.getAdminConfig().getXxlJobRegistryDao().findDead(RegistryConfig.DEAD_TIMEOUT, new Date());
if (ids!=null && ids.size()>0) {
XxlJobAdminConfig.getAdminConfig().getXxlJobRegistryDao().removeDead(ids);
}
// fresh online address (admin/executor)
HashMap<String, List<String>> appAddressMap = new HashMap<String, List<String>>();
List<XxlJobRegistry> list = XxlJobAdminConfig.getAdminConfig().getXxlJobRegistryDao().findAll(RegistryConfig.DEAD_TIMEOUT, new Date());
if (list != null) {
for (XxlJobRegistry item: list) {
if (RegistryConfig.RegistType.EXECUTOR.name().equals(item.getRegistryGroup())) {
String appname = item.getRegistryKey();
List<String> registryList = appAddressMap.get(appname);
if (registryList == null) {
registryList = new ArrayList<String>();
}
if (!registryList.contains(item.getRegistryValue())) {
registryList.add(item.getRegistryValue());
}
appAddressMap.put(appname, registryList);
}
}
}
// fresh group address
for (XxlJobGroup group: groupList) {
List<String> 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<String> registry(RegistryParam registryParam) {
// valid
if (!StringUtils.hasText(registryParam.getRegistryGroup())
|| !StringUtils.hasText(registryParam.getRegistryKey())
|| !StringUtils.hasText(registryParam.getRegistryValue())) {
return new ReturnT<String>(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<String> registryRemove(RegistryParam registryParam) {
// valid
if (!StringUtils.hasText(registryParam.getRegistryGroup())
|| !StringUtils.hasText(registryParam.getRegistryKey())
|| !StringUtils.hasText(registryParam.getRegistryValue())) {
return new ReturnT<String>(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
}
}

View File

@ -1,369 +0,0 @@
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<Integer, List<Integer>> 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
// 1pre read
long nowTime = System.currentTimeMillis();
List<XxlJobInfo> scheduleList = XxlJobAdminConfig.getAdminConfig().getXxlJobInfoDao().scheduleJobQuery(nowTime + PRE_READ_MS, preReadCount);
if (scheduleList!=null && scheduleList.size()>0) {
// 2push time-ring
for (XxlJobInfo jobInfo: scheduleList) {
// time-ring jump
if (nowTime > jobInfo.getTriggerNextTime() + PRE_READ_MS) {
// 2.1trigger-expire > 5spass && make next-trigger-time
logger.warn(">>>>>>>>>>> xxl-job, schedule misfire, jobId = " + jobInfo.getId());
// 1misfire 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() );
}
// 2fresh next
refreshNextValidTime(jobInfo, new Date());
} else if (nowTime > jobInfo.getTriggerNextTime()) {
// 2.2trigger-expire < 5sdirect-trigger && make next-trigger-time
// 1trigger
JobTriggerPoolHelper.trigger(jobInfo.getId(), TriggerTypeEnum.CRON, -1, null, null, null);
logger.debug(">>>>>>>>>>> xxl-job, schedule push trigger : jobId = " + jobInfo.getId() );
// 2fresh next
refreshNextValidTime(jobInfo, new Date());
// next-trigger-time in 5s, pre-read again
if (jobInfo.getTriggerStatus()==1 && nowTime + PRE_READ_MS > jobInfo.getTriggerNextTime()) {
// 1make ring second
int ringSecond = (int)((jobInfo.getTriggerNextTime()/1000)%60);
// 2push time ring
pushTimeRing(ringSecond, jobInfo.getId());
// 3fresh next
refreshNextValidTime(jobInfo, new Date(jobInfo.getTriggerNextTime()));
}
} else {
// 2.3trigger-pre-readtime-ring trigger && make next-trigger-time
// 1make ring second
int ringSecond = (int)((jobInfo.getTriggerNextTime()/1000)%60);
// 2push time ring
pushTimeRing(ringSecond, jobInfo.getId());
// 3fresh next
refreshNextValidTime(jobInfo, new Date(jobInfo.getTriggerNextTime()));
}
}
// 3update 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<Integer> ringItemData = new ArrayList<>();
int nowSecond = Calendar.getInstance().get(Calendar.SECOND); // 避免处理耗时太长跨过刻度向前校验一个刻度
for (int i = 0; i < 2; i++) {
List<Integer> tmpData = ringData.remove( (nowSecond+60-i)%60 );
if (tmpData != null) {
ringItemData.addAll(tmpData);
}
}
// 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<Integer> ringItemData = ringData.get(ringSecond);
if (ringItemData == null) {
ringItemData = new ArrayList<Integer>();
ringData.put(ringSecond, ringItemData);
}
ringItemData.add(jobId);
logger.debug(">>>>>>>>>>> xxl-job, schedule push time-ring : " + ringSecond + " = " + Arrays.asList(ringItemData) );
}
public void toStop(){
// 1stop 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<Integer> 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;
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,15 @@
package com.xxl.job.admin.framework.constant;
/**
* System Constants
*
* @author xuxueli 2018-08-24 03:43
*/
public class Consts {
/**
* Admin Role
*/
public static final String ADMIN_ROLE = "ADMIN";
}

View File

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

View File

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

View File

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

View File

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

View File

@ -1,8 +1,8 @@
package com.xxl.job.admin.core.model; package com.xxl.job.admin.framework.model;
import org.springframework.util.StringUtils;
/** /**
* xxl job user
*
* @author xuxueli 2019-05-04 16:43:12 * @author xuxueli 2019-05-04 16:43:12
*/ */
public class XxlJobUser { public class XxlJobUser {
@ -10,6 +10,7 @@ public class XxlJobUser {
private int id; private int id;
private String username; // 账号 private String username; // 账号
private String password; // 密码 private String password; // 密码
private String token; // 登录token
private int role; // 角色0-普通用户1-管理员 private int role; // 角色0-普通用户1-管理员
private String permission; // 权限执行器ID列表多个逗号分割 private String permission; // 权限执行器ID列表多个逗号分割
@ -37,6 +38,14 @@ public class XxlJobUser {
this.password = password; this.password = password;
} }
public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
public int getRole() { public int getRole() {
return role; return role;
} }
@ -53,21 +62,4 @@ public class XxlJobUser {
this.permission = 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;
}
}
} }

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