升级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,102 +1,110 @@
<?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" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" <modelVersion>4.0.0</modelVersion>
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <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> <packaging>jar</packaging>
<modelVersion>4.0.0</modelVersion> <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>
<artifactId>jeecg-cloud-xxljob</artifactId> <dependencies>
<dependencies> <!-- starter-web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- starter-test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency> <!-- freemarker-starter -->
<groupId>org.springframework.boot</groupId> <dependency>
<artifactId>spring-boot-starter-web</artifactId> <groupId>org.springframework.boot</groupId>
</dependency> <artifactId>spring-boot-starter-freemarker</artifactId>
<!-- starter-testjunit + spring-test + mockito --> </dependency>
<dependency> <!-- mail-starter -->
<groupId>org.springframework.boot</groupId> <dependency>
<artifactId>spring-boot-starter-test</artifactId> <groupId>org.springframework.boot</groupId>
<scope>test</scope> <artifactId>spring-boot-starter-mail</artifactId>
</dependency> </dependency>
<!-- freemarker-starter --> <!-- starter-actuator -->
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId> <artifactId>spring-boot-starter-actuator</artifactId>
</dependency> </dependency>
<!-- starter-actuator --> <!-- mybatis-starter -->
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.mybatis.spring.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId> <artifactId>mybatis-spring-boot-starter</artifactId>
</dependency> <version>4.0.1</version>
</dependency>
<!-- mysql -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
</dependency>
<!-- mybatis-startermybatis + mybatis-spring + hikaridefault --> <!-- xxl-job-core -->
<dependency> <dependency>
<groupId>org.mybatis.spring.boot</groupId> <groupId>com.xuxueli</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId> <artifactId>xxl-job-core</artifactId>
<version>3.0.3</version> <version>${xxl-job-core.version}</version>
</dependency> </dependency>
<!-- mysql -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql-connector-java.version}</version>
<scope>runtime</scope>
</dependency>
<!-- xxl-sso -->
<dependency>
<groupId>com.xuxueli</groupId>
<artifactId>xxl-sso-core</artifactId>
<version>${xxl-sso-core.version}</version>
</dependency>
<!-- mail-starter --> </dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
<dependency> <build>
<groupId>com.xuxueli</groupId> <plugins>
<artifactId>xxl-job-core</artifactId> <plugin>
<version>${xxl-job-core.version}</version> <groupId>org.springframework.boot</groupId>
</dependency> <artifactId>spring-boot-maven-plugin</artifactId>
</dependencies> <executions>
<execution>
<!-- update-begin-author:taoyan date:20210226 for:docker部署报错no main manifest attribute -->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<phase>package</phase> <phase>package</phase>
<goals> <goals>
<goal>repackage</goal> <goal>repackage</goal>
</goals> </goals>
</execution> </execution>
</executions> </executions>
<configuration> <configuration>
<includeSystemScope>true</includeSystemScope> <includeSystemScope>true</includeSystemScope>
<mainClass>com.xxl.job.admin.XxlJobAdminApplication</mainClass> <mainClass>com.xxl.job.admin.XxlJobAdminApplication</mainClass>
</configuration> </configuration>
</plugin> </plugin>
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId> <artifactId>maven-resources-plugin</artifactId>
<configuration> <configuration>
<nonFilteredFileExtensions> <nonFilteredFileExtensions>
<nonFilteredFileExtension>otf</nonFilteredFileExtension> <nonFilteredFileExtension>otf</nonFilteredFileExtension>
<nonFilteredFileExtension>ttf</nonFilteredFileExtension> <nonFilteredFileExtension>ttf</nonFilteredFileExtension>
<nonFilteredFileExtension>woff</nonFilteredFileExtension> <nonFilteredFileExtension>woff</nonFilteredFileExtension>
<nonFilteredFileExtension>woff2</nonFilteredFileExtension> <nonFilteredFileExtension>woff2</nonFilteredFileExtension>
<nonFilteredFileExtension>eot</nonFilteredFileExtension> <nonFilteredFileExtension>eot</nonFilteredFileExtension>
</nonFilteredFileExtensions> </nonFilteredFileExtensions>
</configuration> </configuration>
</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,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,15 +82,27 @@ 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,
final String executorParam, final String executorParam,
final String addressList) { final String addressList) {
// choose thread pool // choose thread pool
ThreadPoolExecutor triggerPool_ = fastTriggerPool; ThreadPoolExecutor triggerPool_ = fastTriggerPool;
@ -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);
// invoke
Response<String> runResult = executorBiz.run(triggerParam);
// build result
StringBuffer runResultSB = new StringBuffer(I18nUtil.getString("jobconf_trigger_run") + "");
runResultSB.append("<br>address").append(address);
runResultSB.append("<br>code").append(runResult.getCode());
runResultSB.append("<br>msg").append(runResult.getMsg());
// return
runResult.setMsg(runResultSB.toString());
return runResult;
} catch (Exception e) { } catch (Exception e) {
logger.error(">>>>>>>>>>> xxl-job trigger error, please check if the executor[{}] is running.", address, e); logger.error(">>>>>>>>>>> xxl-job trigger error, please check if the executor[{}] is running.", address, e);
runResult = new ReturnT<String>(ReturnT.FAIL_CODE, ThrowableUtil.toString(e)); return Response.of(XxlJobContext.HANDLE_CODE_FAIL, ThrowableTool.toString(e));
} }
StringBuffer runResultSB = new StringBuffer(I18nUtil.getString("jobconf_trigger_run") + "");
runResultSB.append("<br>address").append(address);
runResultSB.append("<br>code").append(runResult.getCode());
runResultSB.append("<br>msg").append(runResult.getMsg());
runResult.setMsg(runResultSB.toString());
return runResult;
} }
} }

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,8 +10,9 @@ 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列表多个逗号分割
public int getId() { public int getId() {
return id; return 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