Browse Source

修复#6903,升级xxl-job 至2.4.1版本,规避CVE-2024-24113

EightMonth 8 months ago
parent
commit
1d2b10c2a5
63 changed files with 1486 additions and 740 deletions
  1. 10 8
      jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/doc/db/tables_xxl_job.sql
  2. 1 1
      jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/pom.xml
  3. 6 3
      jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/java/com/xxl/job/admin/controller/IndexController.java
  4. 23 0
      jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/java/com/xxl/job/admin/controller/JobGroupController.java
  5. 24 18
      jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/java/com/xxl/job/admin/controller/JobInfoController.java
  6. 21 7
      jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/java/com/xxl/job/admin/controller/JobLogController.java
  7. 7 0
      jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/java/com/xxl/job/admin/controller/UserController.java
  8. 3 4
      jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/java/com/xxl/job/admin/controller/interceptor/CookieInterceptor.java
  9. 6 6
      jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/java/com/xxl/job/admin/controller/interceptor/PermissionInterceptor.java
  10. 6 4
      jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/java/com/xxl/job/admin/controller/resolver/WebExceptionResolver.java
  11. 1 1
      jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/java/com/xxl/job/admin/core/alarm/impl/EmailJobAlarm.java
  12. 99 0
      jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/java/com/xxl/job/admin/core/complete/XxlJobCompleter.java
  13. 4 4
      jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/java/com/xxl/job/admin/core/conf/XxlJobAdminConfig.java
  14. 12 16
      jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/java/com/xxl/job/admin/core/cron/CronExpression.java
  15. 10 0
      jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/java/com/xxl/job/admin/core/model/XxlJobGroup.java
  16. 28 9
      jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/java/com/xxl/job/admin/core/model/XxlJobInfo.java
  17. 1 1
      jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/java/com/xxl/job/admin/core/old/RemoteHttpJobBean.java
  18. 1 1
      jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/java/com/xxl/job/admin/core/old/XxlJobDynamicScheduler.java
  19. 1 1
      jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/java/com/xxl/job/admin/core/old/XxlJobThreadPool.java
  20. 12 5
      jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteRound.java
  21. 39 0
      jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/java/com/xxl/job/admin/core/scheduler/MisfireStrategyEnum.java
  22. 46 0
      jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/java/com/xxl/job/admin/core/scheduler/ScheduleTypeEnum.java
  23. 12 12
      jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/java/com/xxl/job/admin/core/scheduler/XxlJobScheduler.java
  24. 184 0
      jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/java/com/xxl/job/admin/core/thread/JobCompleteHelper.java
  25. 1 1
      jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/java/com/xxl/job/admin/core/thread/JobFailMonitorHelper.java
  26. 0 95
      jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/java/com/xxl/job/admin/core/thread/JobLosedMonitorHelper.java
  27. 204 0
      jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/java/com/xxl/job/admin/core/thread/JobRegistryHelper.java
  28. 0 111
      jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/java/com/xxl/job/admin/core/thread/JobRegistryMonitorHelper.java
  29. 37 22
      jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/java/com/xxl/job/admin/core/thread/JobScheduleHelper.java
  30. 2 1
      jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/java/com/xxl/job/admin/core/trigger/TriggerTypeEnum.java
  31. 12 0
      jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/java/com/xxl/job/admin/service/XxlJobService.java
  32. 5 145
      jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/java/com/xxl/job/admin/service/impl/AdminBizImpl.java
  33. 131 30
      jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/java/com/xxl/job/admin/service/impl/XxlJobServiceImpl.java
  34. 2 1
      jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/resources/application.yml
  35. 15 3
      jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/resources/i18n/message_en.properties
  36. 15 3
      jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/resources/i18n/message_zh_CN.properties
  37. 15 3
      jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/resources/i18n/message_zh_TC.properties
  38. 1 1
      jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/resources/logback.xml
  39. 7 4
      jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/resources/mybatis-mapper/XxlJobGroupMapper.xml
  40. 16 5
      jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/resources/mybatis-mapper/XxlJobInfoMapper.xml
  41. 21 9
      jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/resources/mybatis-mapper/XxlJobLogMapper.xml
  42. 3 3
      jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/resources/static/adminlte/bower_components/bootstrap/css/bootstrap.min.css
  43. 1 1
      jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/resources/static/adminlte/bower_components/bootstrap/css/bootstrap.min.css.map
  44. 3 4
      jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/resources/static/adminlte/bower_components/bootstrap/js/bootstrap.min.js
  45. 2 2
      jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/resources/static/adminlte/bower_components/jquery/jquery.min.js
  46. 1 1
      jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/resources/static/adminlte/bower_components/moment/moment.min.js
  47. 7 6
      jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/resources/static/adminlte/dist/css/AdminLTE.min.css
  48. 1 1
      jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/resources/static/adminlte/dist/css/skins/_all-skins.min.css
  49. 5 6
      jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/resources/static/adminlte/dist/js/adminlte.min.js
  50. 13 2
      jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/resources/static/js/jobgroup.index.1.js
  51. 130 73
      jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/resources/static/js/jobinfo.index.1.js
  52. 0 2
      jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/resources/static/js/joblog.detail.1.js
  53. 9 4
      jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/resources/static/js/joblog.index.1.js
  54. 1 1
      jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/resources/static/js/login.1.js
  55. 1 1
      jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/resources/static/js/user.index.1.js
  56. 34 7
      jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/resources/static/plugins/cronGen/cronGen.js
  57. 34 7
      jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/resources/static/plugins/cronGen/cronGen_en.js
  58. 2 2
      jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/resources/templates/common/common.macro.ftl
  59. 23 4
      jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/resources/templates/jobgroup/jobgroup.index.ftl
  60. 170 70
      jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/resources/templates/jobinfo/jobinfo.index.ftl
  61. 0 3
      jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/resources/templates/joblog/joblog.detail.ftl
  62. 4 4
      jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/resources/templates/joblog/joblog.index.ftl
  63. 1 1
      jeecg-boot/pom.xml

File diff suppressed because it is too large
+ 10 - 8
jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/doc/db/tables_xxl_job.sql


+ 1 - 1
jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/pom.xml

@@ -39,7 +39,7 @@
         <dependency>
             <groupId>org.mybatis.spring.boot</groupId>
             <artifactId>mybatis-spring-boot-starter</artifactId>
-            <version>2.1.3</version>
+            <version>2.3.2</version>
         </dependency>
         <!-- mysql -->
         <dependency>

+ 6 - 3
jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/java/com/xxl/job/admin/controller/IndexController.java

@@ -12,6 +12,8 @@ import org.springframework.web.bind.annotation.InitBinder;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RequestMethod;
 import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.web.servlet.ModelAndView;
+import org.springframework.web.servlet.view.RedirectView;
 
 import javax.annotation.Resource;
 import javax.servlet.http.HttpServletRequest;
@@ -51,11 +53,12 @@ public class IndexController {
 	
 	@RequestMapping("/toLogin")
 	@PermissionLimit(limit=false)
-	public String toLogin(HttpServletRequest request, HttpServletResponse response) {
+	public ModelAndView toLogin(HttpServletRequest request, HttpServletResponse response,ModelAndView modelAndView) {
 		if (loginService.ifLogin(request, response) != null) {
-			return "redirect:/";
+			modelAndView.setView(new RedirectView("/",true,false));
+			return modelAndView;
 		}
-		return "login";
+		return new ModelAndView("login");
 	}
 	
 	@RequestMapping(value="login", method=RequestMethod.POST)

+ 23 - 0
jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/java/com/xxl/job/admin/controller/JobGroupController.java

@@ -1,5 +1,6 @@
 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;
@@ -34,12 +35,14 @@ public class JobGroupController {
 	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,
@@ -59,6 +62,7 @@ public class JobGroupController {
 
 	@RequestMapping("/save")
 	@ResponseBody
+	@PermissionLimit(adminuser = true)
 	public ReturnT<String> save(XxlJobGroup xxlJobGroup){
 
 		// valid
@@ -68,13 +72,23 @@ public class JobGroupController {
 		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) {
@@ -83,12 +97,16 @@ public class JobGroupController {
 			}
 		}
 
+		// 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) {
@@ -126,6 +144,9 @@ public class JobGroupController {
 			}
 		}
 
+		// process
+		xxlJobGroup.setUpdateTime(new Date());
+
 		int ret = xxlJobGroupDao.update(xxlJobGroup);
 		return (ret>0)?ReturnT.SUCCESS:ReturnT.FAIL;
 	}
@@ -154,6 +175,7 @@ public class JobGroupController {
 
 	@RequestMapping("/remove")
 	@ResponseBody
+	@PermissionLimit(adminuser = true)
 	public ReturnT<String> remove(int id){
 
 		// valid
@@ -173,6 +195,7 @@ public class JobGroupController {
 
 	@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);

+ 24 - 18
jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/java/com/xxl/job/admin/controller/JobInfoController.java

@@ -1,13 +1,13 @@
 package com.xxl.job.admin.controller;
 
-import com.xxl.job.admin.core.cron.CronExpression;
 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.thread.JobTriggerPoolHelper;
-import com.xxl.job.admin.core.trigger.TriggerTypeEnum;
+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;
@@ -16,6 +16,8 @@ import com.xxl.job.core.biz.model.ReturnT;
 import com.xxl.job.core.enums.ExecutorBlockStrategyEnum;
 import com.xxl.job.core.glue.GlueTypeEnum;
 import com.xxl.job.core.util.DateUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.springframework.stereotype.Controller;
 import org.springframework.ui.Model;
 import org.springframework.web.bind.annotation.RequestMapping;
@@ -24,7 +26,6 @@ import org.springframework.web.bind.annotation.ResponseBody;
 
 import javax.annotation.Resource;
 import javax.servlet.http.HttpServletRequest;
-import java.text.ParseException;
 import java.util.*;
 
 /**
@@ -34,6 +35,7 @@ import java.util.*;
 @Controller
 @RequestMapping("/jobinfo")
 public class JobInfoController {
+	private static Logger logger = LoggerFactory.getLogger(JobInfoController.class);
 
 	@Resource
 	private XxlJobGroupDao xxlJobGroupDao;
@@ -47,6 +49,8 @@ public class JobInfoController {
 		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();
@@ -131,36 +135,38 @@ public class JobInfoController {
 	
 	@RequestMapping("/trigger")
 	@ResponseBody
-	//@PermissionLimit(limit = false)
-	public ReturnT<String> triggerJob(int id, String executorParam, String addressList) {
-		// force cover job param
-		if (executorParam == null) {
-			executorParam = "";
-		}
-
-		JobTriggerPoolHelper.trigger(id, TriggerTypeEnum.MANUAL, -1, null, executorParam, addressList);
-		return ReturnT.SUCCESS;
+	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 cron) {
+	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 {
-			CronExpression cronExpression = new CronExpression(cron);
 			Date lastTime = new Date();
 			for (int i = 0; i < 5; i++) {
-				lastTime = cronExpression.getNextValidTimeAfter(lastTime);
+				lastTime = JobScheduleHelper.generateNextValidTime(paramXxlJobInfo, lastTime);
 				if (lastTime != null) {
 					result.add(DateUtil.formatDateTime(lastTime));
 				} else {
 					break;
 				}
 			}
-		} catch (ParseException e) {
-			return new ReturnT<List<String>>(ReturnT.FAIL_CODE, I18nUtil.getString("jobinfo_field_cron_unvalid"));
+		} 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);
+
 	}
 	
 }

+ 21 - 7
jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/java/com/xxl/job/admin/controller/JobLogController.java

@@ -1,5 +1,6 @@
 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;
@@ -19,9 +20,11 @@ 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 javax.annotation.Resource;
 import javax.servlet.http.HttpServletRequest;
@@ -128,27 +131,38 @@ public class JobLogController {
 
         model.addAttribute("triggerCode", jobLog.getTriggerCode());
         model.addAttribute("handleCode", jobLog.getHandleCode());
-        model.addAttribute("executorAddress", jobLog.getExecutorAddress());
-        model.addAttribute("triggerTime", jobLog.getTriggerTime().getTime());
         model.addAttribute("logId", jobLog.getId());
 		return "joblog/joblog.detail";
 	}
 
 	@RequestMapping("/logDetailCat")
 	@ResponseBody
-	public ReturnT<LogResult> logDetailCat(String executorAddress, long triggerTime, long logId, int fromLineNum){
+	public ReturnT<LogResult> logDetailCat(long logId, int fromLineNum){
 		try {
-			ExecutorBiz executorBiz = XxlJobScheduler.getExecutorBiz(executorAddress);
-			ReturnT<LogResult> logResult = executorBiz.log(new LogParam(triggerTime, logId, fromLineNum));
+			// 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()) {
-                XxlJobLog jobLog = xxlJobLogDao.load(logId);
                 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);
@@ -183,7 +197,7 @@ public class JobLogController {
 			log.setHandleCode(ReturnT.FAIL_CODE);
 			log.setHandleMsg( I18nUtil.getString("joblog_kill_log_byman")+":" + (runResult.getMsg()!=null?runResult.getMsg():""));
 			log.setHandleTime(new Date());
-			xxlJobLogDao.updateHandleInfo(log);
+			XxlJobCompleter.updateHandleInfoAndFinish(log);
 			return new ReturnT<String>(runResult.getMsg());
 		} else {
 			return new ReturnT<String>(500, runResult.getMsg());

+ 7 - 0
jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/java/com/xxl/job/admin/controller/UserController.java

@@ -56,6 +56,13 @@ public class UserController {
         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);		// 总记录数

+ 3 - 4
jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/java/com/xxl/job/admin/controller/interceptor/CookieInterceptor.java

@@ -3,8 +3,8 @@ package com.xxl.job.admin.controller.interceptor;
 import com.xxl.job.admin.core.util.FtlUtil;
 import com.xxl.job.admin.core.util.I18nUtil;
 import org.springframework.stereotype.Component;
+import org.springframework.web.servlet.AsyncHandlerInterceptor;
 import org.springframework.web.servlet.ModelAndView;
-import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
 
 import javax.servlet.http.Cookie;
 import javax.servlet.http.HttpServletRequest;
@@ -17,7 +17,7 @@ import java.util.HashMap;
  * @author xuxueli 2015-12-12 18:09:04
  */
 @Component
-public class CookieInterceptor extends HandlerInterceptorAdapter {
+public class CookieInterceptor implements AsyncHandlerInterceptor {
 
 	@Override
 	public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
@@ -36,8 +36,7 @@ public class CookieInterceptor extends HandlerInterceptorAdapter {
 		if (modelAndView != null) {
 			modelAndView.addObject("I18nUtil", FtlUtil.generateStaticModel(I18nUtil.class.getName()));
 		}
-		
-		super.postHandle(request, response, handler, modelAndView);
+
 	}
 	
 }

+ 6 - 6
jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/java/com/xxl/job/admin/controller/interceptor/PermissionInterceptor.java

@@ -6,7 +6,7 @@ import com.xxl.job.admin.core.util.I18nUtil;
 import com.xxl.job.admin.service.LoginService;
 import org.springframework.stereotype.Component;
 import org.springframework.web.method.HandlerMethod;
-import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
+import org.springframework.web.servlet.AsyncHandlerInterceptor;
 
 import javax.annotation.Resource;
 import javax.servlet.http.HttpServletRequest;
@@ -18,7 +18,7 @@ import javax.servlet.http.HttpServletResponse;
  * @author xuxueli 2015-12-12 18:09:04
  */
 @Component
-public class PermissionInterceptor extends HandlerInterceptorAdapter {
+public class PermissionInterceptor implements AsyncHandlerInterceptor {
 
 	@Resource
 	private LoginService loginService;
@@ -27,7 +27,7 @@ public class PermissionInterceptor extends HandlerInterceptorAdapter {
 	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
 		
 		if (!(handler instanceof HandlerMethod)) {
-			return super.preHandle(request, response, handler);
+			return true;	// proceed with the next interceptor
 		}
 
 		// if need login
@@ -43,8 +43,8 @@ public class PermissionInterceptor extends HandlerInterceptorAdapter {
 		if (needLogin) {
 			XxlJobUser loginUser = loginService.ifLogin(request, response);
 			if (loginUser == null) {
-				response.sendRedirect(request.getContextPath() + "/toLogin");
-				//request.getRequestDispatcher("/toLogin").forward(request, response);
+				response.setStatus(302);
+				response.setHeader("location", request.getContextPath()+"/toLogin");
 				return false;
 			}
 			if (needAdminuser && loginUser.getRole()!=1) {
@@ -53,7 +53,7 @@ public class PermissionInterceptor extends HandlerInterceptorAdapter {
 			request.setAttribute(LoginService.LOGIN_IDENTITY_KEY, loginUser);
 		}
 
-		return super.preHandle(request, response, handler);
+		return true;	// proceed with the next interceptor
 	}
 	
 }

+ 6 - 4
jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/java/com/xxl/job/admin/controller/resolver/WebExceptionResolver.java

@@ -34,10 +34,12 @@ public class WebExceptionResolver implements HandlerExceptionResolver {
 
 		// if json
 		boolean isJson = false;
-		HandlerMethod method = (HandlerMethod)handler;
-		ResponseBody responseBody = method.getMethodAnnotation(ResponseBody.class);
-		if (responseBody != null) {
-			isJson = true;
+		if (handler instanceof HandlerMethod) {
+			HandlerMethod method = (HandlerMethod)handler;
+			ResponseBody responseBody = method.getMethodAnnotation(ResponseBody.class);
+			if (responseBody != null) {
+				isJson = true;
+			}
 		}
 
 		// error result

+ 1 - 1
jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/java/com/xxl/job/admin/core/alarm/impl/EmailJobAlarm.java

@@ -66,7 +66,7 @@ public class EmailJobAlarm implements JobAlarm {
                     MimeMessage mimeMessage = XxlJobAdminConfig.getAdminConfig().getMailSender().createMimeMessage();
 
                     MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true);
-                    helper.setFrom(XxlJobAdminConfig.getAdminConfig().getEmailUserName(), personal);
+                    helper.setFrom(XxlJobAdminConfig.getAdminConfig().getEmailFrom(), personal);
                     helper.setTo(email);
                     helper.setSubject(title);
                     helper.setText(content, true);

+ 99 - 0
jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/java/com/xxl/job/admin/core/complete/XxlJobCompleter.java

@@ -0,0 +1,99 @@
+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){
+
+        // 1、handle 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 );
+        }
+
+        // 2、fix_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;
+        }
+    }
+
+}

+ 4 - 4
jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/java/com/xxl/job/admin/core/conf/XxlJobAdminConfig.java

@@ -55,8 +55,8 @@ public class XxlJobAdminConfig implements InitializingBean, DisposableBean {
     @Value("${xxl.job.accessToken}")
     private String accessToken;
 
-    @Value("${spring.mail.username}")
-    private String emailUserName;
+    @Value("${spring.mail.from}")
+    private String emailFrom;
 
     @Value("${xxl.job.triggerpool.fast.max}")
     private int triggerPoolFastMax;
@@ -98,8 +98,8 @@ public class XxlJobAdminConfig implements InitializingBean, DisposableBean {
         return accessToken;
     }
 
-    public String getEmailUserName() {
-        return emailUserName;
+    public String getEmailFrom() {
+        return emailFrom;
     }
 
     public int getTriggerPoolFastMax() {

+ 12 - 16
jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/java/com/xxl/job/admin/core/cron/CronExpression.java

@@ -265,7 +265,7 @@ public final class CronExpression implements Serializable, Cloneable {
      * 
      * @param cronExpression String representation of the cron expression the
      *                       new object should represent
-     * @throws java.text.ParseException
+     * @throws ParseException
      *         if the string expression cannot be parsed into a valid 
      *         <CODE>CronExpression</CODE>
      */
@@ -363,9 +363,8 @@ public final class CronExpression implements Serializable, Cloneable {
         // the second immediately following it.
         while (difference == 1000) {
             newDate = getTimeAfter(lastDate);
-            if(newDate == null) {
+            if(newDate == null)
                 break;
-            }
             
             difference = newDate.getTime() - lastDate.getTime();
             
@@ -533,7 +532,7 @@ public final class CronExpression implements Serializable, Cloneable {
             return i;
         }
         char c = s.charAt(i);
-        if ((c >= 'A') && (c <= 'Z') && (!"L".equals(s)) && (!"LW".equals(s)) && (!s.matches("^L-[0-9]*[W]?"))) {
+        if ((c >= 'A') && (c <= 'Z') && (!s.equals("L")) && (!s.equals("LW")) && (!s.matches("^L-[0-9]*[W]?"))) {
             String sub = s.substring(i, i + 3);
             int sval = -1;
             int eval = -1;
@@ -669,9 +668,8 @@ public final class CronExpression implements Serializable, Cloneable {
                 if(c == '-') {
                     ValueSet vs = getValue(0, s, i+1);
                     lastdayOffset = vs.value;
-                    if(lastdayOffset > 30) {
+                    if(lastdayOffset > 30)
                         throw new ParseException("Offset from last day must be <= 30", i+1);
-                    }
                     i = vs.pos;
                 }                        
                 if(s.length() > i) {
@@ -734,9 +732,8 @@ public final class CronExpression implements Serializable, Cloneable {
 
         if (c == 'L') {
             if (type == DAY_OF_WEEK) {
-                if(val < 1 || val > 7) {
+                if(val < 1 || val > 7)
                     throw new ParseException("Day-of-Week values must be between 1 and 7", -1);
-                }
                 lastdayOfWeek = true;
             } else {
                 throw new ParseException("'L' option is not valid here. (pos=" + i + ")", i);
@@ -753,9 +750,8 @@ public final class CronExpression implements Serializable, Cloneable {
             } else {
                 throw new ParseException("'W' option is not valid here. (pos=" + i + ")", i);
             }
-            if(val > 31) {
-                throw new ParseException("The 'W' option does not make sense with values larger than 31 (max number of days in a month)", i);
-            }
+            if(val > 31)
+                throw new ParseException("The 'W' option does not make sense with values larger than 31 (max number of days in a month)", i); 
             TreeSet<Integer> set = getSet(type);
             set.add(val);
             i++;
@@ -1284,7 +1280,7 @@ public final class CronExpression implements Serializable, Cloneable {
                         day = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
                         day -= lastdayOffset;
                         
-                        java.util.Calendar tcal = java.util.Calendar.getInstance(getTimeZone());
+                        Calendar tcal = Calendar.getInstance(getTimeZone());
                         tcal.set(Calendar.SECOND, 0);
                         tcal.set(Calendar.MINUTE, 0);
                         tcal.set(Calendar.HOUR_OF_DAY, 0);
@@ -1320,7 +1316,7 @@ public final class CronExpression implements Serializable, Cloneable {
                     t = day;
                     day = daysOfMonth.first();
 
-                    java.util.Calendar tcal = java.util.Calendar.getInstance(getTimeZone());
+                    Calendar tcal = Calendar.getInstance(getTimeZone());
                     tcal.set(Calendar.SECOND, 0);
                     tcal.set(Calendar.MINUTE, 0);
                     tcal.set(Calendar.HOUR_OF_DAY, 0);
@@ -1584,9 +1580,9 @@ public final class CronExpression implements Serializable, Cloneable {
      * @param hour the hour to set
      */
     protected void setCalendarHour(Calendar cal, int hour) {
-        cal.set(java.util.Calendar.HOUR_OF_DAY, hour);
-        if (cal.get(java.util.Calendar.HOUR_OF_DAY) != hour && hour != 24) {
-            cal.set(java.util.Calendar.HOUR_OF_DAY, hour + 1);
+        cal.set(Calendar.HOUR_OF_DAY, hour);
+        if (cal.get(Calendar.HOUR_OF_DAY) != hour && hour != 24) {
+            cal.set(Calendar.HOUR_OF_DAY, hour + 1);
         }
     }
 

+ 10 - 0
jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/java/com/xxl/job/admin/core/model/XxlJobGroup.java

@@ -2,6 +2,7 @@ package com.xxl.job.admin.core.model;
 
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Date;
 import java.util.List;
 
 /**
@@ -14,6 +15,7 @@ public class XxlJobGroup {
     private String title;
     private int addressType;        // 执行器地址类型:0=自动注册、1=手动录入
     private String addressList;     // 执行器地址列表,多地址逗号分隔(手动录入)
+    private Date updateTime;
 
     // registry list
     private List<String> registryList;  // 执行器地址列表(系统注册)
@@ -60,6 +62,14 @@ public class XxlJobGroup {
         return addressList;
     }
 
+    public Date getUpdateTime() {
+        return updateTime;
+    }
+
+    public void setUpdateTime(Date updateTime) {
+        this.updateTime = updateTime;
+    }
+
     public void setAddressList(String addressList) {
         this.addressList = addressList;
     }

+ 28 - 9
jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/java/com/xxl/job/admin/core/model/XxlJobInfo.java

@@ -12,7 +12,6 @@ public class XxlJobInfo {
 	private int id;				// 主键ID
 	
 	private int jobGroup;		// 执行器主键ID
-	private String jobCron;		// 任务执行CRON表达式
 	private String jobDesc;
 	
 	private Date addTime;
@@ -21,6 +20,10 @@ public class XxlJobInfo {
 	private String author;		// 负责人
 	private String alarmEmail;	// 报警邮件
 
+	private String scheduleType;			// 调度类型
+	private String scheduleConf;			// 调度配置,值含义取决于调度类型
+	private String misfireStrategy;			// 调度过期策略
+
 	private String executorRouteStrategy;	// 执行器路由策略
 	private String executorHandler;		    // 执行器,任务Handler名称
 	private String executorParam;		    // 执行器,任务参数
@@ -56,14 +59,6 @@ public class XxlJobInfo {
 		this.jobGroup = jobGroup;
 	}
 
-	public String getJobCron() {
-		return jobCron;
-	}
-
-	public void setJobCron(String jobCron) {
-		this.jobCron = jobCron;
-	}
-
 	public String getJobDesc() {
 		return jobDesc;
 	}
@@ -104,6 +99,30 @@ public class XxlJobInfo {
 		this.alarmEmail = alarmEmail;
 	}
 
+	public String getScheduleType() {
+		return scheduleType;
+	}
+
+	public void setScheduleType(String scheduleType) {
+		this.scheduleType = scheduleType;
+	}
+
+	public String getScheduleConf() {
+		return scheduleConf;
+	}
+
+	public void setScheduleConf(String scheduleConf) {
+		this.scheduleConf = scheduleConf;
+	}
+
+	public String getMisfireStrategy() {
+		return misfireStrategy;
+	}
+
+	public void setMisfireStrategy(String misfireStrategy) {
+		this.misfireStrategy = misfireStrategy;
+	}
+
 	public String getExecutorRouteStrategy() {
 		return executorRouteStrategy;
 	}

+ 1 - 1
jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/java/com/xxl/job/admin/core/old/RemoteHttpJobBean.java

@@ -1,4 +1,4 @@
-//package com.xxl.job.admin.core.jobbean;
+package com.xxl.job.admin.core.old;//package com.xxl.job.admin.core.jobbean;
 //
 //import com.xxl.job.admin.core.thread.JobTriggerPoolHelper;
 //import com.xxl.job.admin.core.trigger.TriggerTypeEnum;

+ 1 - 1
jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/java/com/xxl/job/admin/core/old/XxlJobDynamicScheduler.java

@@ -1,4 +1,4 @@
-//package com.xxl.job.admin.core.schedule;
+package com.xxl.job.admin.core.old;//package com.xxl.job.admin.core.schedule;
 //
 //import com.xxl.job.admin.core.conf.XxlJobAdminConfig;
 //import com.xxl.job.admin.core.jobbean.RemoteHttpJobBean;

+ 1 - 1
jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/java/com/xxl/job/admin/core/old/XxlJobThreadPool.java

@@ -1,4 +1,4 @@
-//package com.xxl.job.admin.core.quartz;
+package com.xxl.job.admin.core.old;//package com.xxl.job.admin.core.quartz;
 //
 //import org.quartz.SchedulerConfigException;
 //import org.quartz.spi.ThreadPool;

+ 12 - 5
jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteRound.java

@@ -8,14 +8,16 @@ import java.util.List;
 import java.util.Random;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.atomic.AtomicInteger;
 
 /**
  * Created by xuxueli on 17/3/10.
  */
 public class ExecutorRouteRound extends ExecutorRouter {
 
-    private static ConcurrentMap<Integer, Integer> routeCountEachJob = new ConcurrentHashMap<Integer, Integer>();
+    private static ConcurrentMap<Integer, AtomicInteger> routeCountEachJob = new ConcurrentHashMap<>();
     private static long CACHE_VALID_TIME = 0;
+
     private static int count(int jobId) {
         // cache clear
         if (System.currentTimeMillis() > CACHE_VALID_TIME) {
@@ -23,11 +25,16 @@ public class ExecutorRouteRound extends ExecutorRouter {
             CACHE_VALID_TIME = System.currentTimeMillis() + 1000*60*60*24;
         }
 
-        // count++
-        Integer count = routeCountEachJob.get(jobId);
-        count = (count==null || count>1000000)?(new Random().nextInt(100)):++count;  // 初始化时主动Random一次,缓解首次压力
+        AtomicInteger count = routeCountEachJob.get(jobId);
+        if (count == null || count.get() > 1000000) {
+            // 初始化时主动Random一次,缓解首次压力
+            count = new AtomicInteger(new Random().nextInt(100));
+        } else {
+            // count++
+            count.addAndGet(1);
+        }
         routeCountEachJob.put(jobId, count);
-        return count;
+        return count.get();
     }
 
     @Override

+ 39 - 0
jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/java/com/xxl/job/admin/core/scheduler/MisfireStrategyEnum.java

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

+ 46 - 0
jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/java/com/xxl/job/admin/core/scheduler/ScheduleTypeEnum.java

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

+ 12 - 12
jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/java/com/xxl/job/admin/core/scheduler/XxlJobScheduler.java

@@ -24,22 +24,22 @@ public class XxlJobScheduler  {
         // init i18n
         initI18n();
 
+        // admin trigger pool start
+        JobTriggerPoolHelper.toStart();
+
         // admin registry monitor run
-        JobRegistryMonitorHelper.getInstance().start();
+        JobRegistryHelper.getInstance().start();
 
         // admin fail-monitor run
         JobFailMonitorHelper.getInstance().start();
 
-        // admin lose-monitor run
-        JobLosedMonitorHelper.getInstance().start();
-
-        // admin trigger pool start
-        JobTriggerPoolHelper.toStart();
+        // admin lose-monitor run ( depend on JobTriggerPoolHelper )
+        JobCompleteHelper.getInstance().start();
 
         // admin log report start
         JobLogReportHelper.getInstance().start();
 
-        // start-schedule
+        // start-schedule  ( depend on JobTriggerPoolHelper )
         JobScheduleHelper.getInstance().start();
 
         logger.info(">>>>>>>>> init xxl-job admin success.");
@@ -54,17 +54,17 @@ public class XxlJobScheduler  {
         // admin log report stop
         JobLogReportHelper.getInstance().toStop();
 
-        // admin trigger pool stop
-        JobTriggerPoolHelper.toStop();
-
         // admin lose-monitor stop
-        JobLosedMonitorHelper.getInstance().toStop();
+        JobCompleteHelper.getInstance().toStop();
 
         // admin fail-monitor stop
         JobFailMonitorHelper.getInstance().toStop();
 
         // admin registry stop
-        JobRegistryMonitorHelper.getInstance().toStop();
+        JobRegistryHelper.getInstance().toStop();
+
+        // admin trigger pool stop
+        JobTriggerPoolHelper.toStop();
 
     }
 

+ 184 - 0
jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/java/com/xxl/job/admin/core/thread/JobCompleteHelper.java

@@ -0,0 +1,184 @@
+package com.xxl.job.admin.core.thread;
+
+import com.xxl.job.admin.core.complete.XxlJobCompleter;
+import com.xxl.job.admin.core.conf.XxlJobAdminConfig;
+import com.xxl.job.admin.core.model.XxlJobLog;
+import com.xxl.job.admin.core.util.I18nUtil;
+import com.xxl.job.core.biz.model.HandleCallbackParam;
+import com.xxl.job.core.biz.model.ReturnT;
+import com.xxl.job.core.util.DateUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Date;
+import java.util.List;
+import java.util.concurrent.*;
+
+/**
+ * job lose-monitor instance
+ *
+ * @author xuxueli 2015-9-1 18:05:56
+ */
+public class JobCompleteHelper {
+	private static Logger logger = LoggerFactory.getLogger(JobCompleteHelper.class);
+	
+	private static JobCompleteHelper instance = new JobCompleteHelper();
+	public static JobCompleteHelper getInstance(){
+		return instance;
+	}
+
+	// ---------------------- monitor ----------------------
+
+	private ThreadPoolExecutor callbackThreadPool = null;
+	private Thread monitorThread;
+	private volatile boolean toStop = false;
+	public void start(){
+
+		// for callback
+		callbackThreadPool = new ThreadPoolExecutor(
+				2,
+				20,
+				30L,
+				TimeUnit.SECONDS,
+				new LinkedBlockingQueue<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;
+	}
+
+
+
+}

+ 1 - 1
jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/java/com/xxl/job/admin/core/thread/JobFailMonitorHelper.java

@@ -60,7 +60,7 @@ public class JobFailMonitorHelper {
 
 								// 2、fail alarm monitor
 								int newAlarmStatus = 0;		// 告警状态:0-默认、-1=锁定状态、1-无需告警、2-告警成功、3-告警失败
-								if (info!=null && info.getAlarmEmail()!=null && info.getAlarmEmail().trim().length()>0) {
+								if (info != null) {
 									boolean alarmResult = XxlJobAdminConfig.getAdminConfig().getJobAlarmer().alarm(info, log);
 									newAlarmStatus = alarmResult?2:3;
 								} else {

+ 0 - 95
jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/java/com/xxl/job/admin/core/thread/JobLosedMonitorHelper.java

@@ -1,95 +0,0 @@
-package com.xxl.job.admin.core.thread;
-
-import com.xxl.job.admin.core.conf.XxlJobAdminConfig;
-import com.xxl.job.admin.core.model.XxlJobLog;
-import com.xxl.job.admin.core.util.I18nUtil;
-import com.xxl.job.core.biz.model.ReturnT;
-import com.xxl.job.core.util.DateUtil;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.util.Date;
-import java.util.List;
-import java.util.concurrent.TimeUnit;
-
-/**
- * job lose-monitor instance
- *
- * @author xuxueli 2015-9-1 18:05:56
- */
-public class JobLosedMonitorHelper {
-	private static Logger logger = LoggerFactory.getLogger(JobLosedMonitorHelper.class);
-	
-	private static JobLosedMonitorHelper instance = new JobLosedMonitorHelper();
-	public static JobLosedMonitorHelper getInstance(){
-		return instance;
-	}
-
-	// ---------------------- monitor ----------------------
-
-	private Thread monitorThread;
-	private volatile boolean toStop = false;
-	public void start(){
-		monitorThread = new Thread(new Runnable() {
-
-			@Override
-			public void run() {
-
-				// monitor
-				while (!toStop) {
-					try {
-						// 任务结果丢失处理:调度记录停留在 "运行中" 状态超过10min,且对应执行器心跳注册失败不在线,则将本地调度主动标记失败;
-						Date losedTime = DateUtil.addMinutes(new Date(), -10);
-						List<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") );
-
-								XxlJobAdminConfig.getAdminConfig().getXxlJobLogDao().updateHandleInfo(jobLog);
-							}
-
-						}
-					} catch (Exception e) {
-						if (!toStop) {
-							logger.error(">>>>>>>>>>> xxl-job, job fail monitor thread error:{}", e);
-						}
-					}
-
-                    try {
-                        TimeUnit.SECONDS.sleep(60);
-                    } catch (Exception e) {
-                        if (!toStop) {
-                            logger.error(e.getMessage(), e);
-                        }
-                    }
-
-                }
-
-				logger.info(">>>>>>>>>>> xxl-job, JobLosedMonitorHelper stop");
-
-			}
-		});
-		monitorThread.setDaemon(true);
-		monitorThread.setName("xxl-job, admin JobLosedMonitorHelper");
-		monitorThread.start();
-	}
-
-	public void toStop(){
-		toStop = true;
-		// interrupt and wait
-		monitorThread.interrupt();
-		try {
-			monitorThread.join();
-		} catch (InterruptedException e) {
-			logger.error(e.getMessage(), e);
-		}
-	}
-
-}

+ 204 - 0
jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/java/com/xxl/job/admin/core/thread/JobRegistryHelper.java

@@ -0,0 +1,204 @@
+package com.xxl.job.admin.core.thread;
+
+import com.xxl.job.admin.core.conf.XxlJobAdminConfig;
+import com.xxl.job.admin.core.model.XxlJobGroup;
+import com.xxl.job.admin.core.model.XxlJobRegistry;
+import com.xxl.job.core.biz.model.RegistryParam;
+import com.xxl.job.core.biz.model.ReturnT;
+import com.xxl.job.core.enums.RegistryConfig;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.util.StringUtils;
+
+import java.util.*;
+import java.util.concurrent.*;
+
+/**
+ * job registry instance
+ * @author xuxueli 2016-10-02 19:10:24
+ */
+public class JobRegistryHelper {
+	private static Logger logger = LoggerFactory.getLogger(JobRegistryHelper.class);
+
+	private static JobRegistryHelper instance = new JobRegistryHelper();
+	public static JobRegistryHelper getInstance(){
+		return instance;
+	}
+
+	private ThreadPoolExecutor registryOrRemoveThreadPool = null;
+	private Thread registryMonitorThread;
+	private volatile boolean toStop = false;
+
+	public void start(){
+
+		// for registry or remove
+		registryOrRemoveThreadPool = new ThreadPoolExecutor(
+				2,
+				10,
+				30L,
+				TimeUnit.SECONDS,
+				new LinkedBlockingQueue<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
+	}
+
+
+}

+ 0 - 111
jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/java/com/xxl/job/admin/core/thread/JobRegistryMonitorHelper.java

@@ -1,111 +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.enums.RegistryConfig;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.util.*;
-import java.util.concurrent.TimeUnit;
-
-/**
- * job registry instance
- * @author xuxueli 2016-10-02 19:10:24
- */
-public class JobRegistryMonitorHelper {
-	private static Logger logger = LoggerFactory.getLogger(JobRegistryMonitorHelper.class);
-
-	private static JobRegistryMonitorHelper instance = new JobRegistryMonitorHelper();
-	public static JobRegistryMonitorHelper getInstance(){
-		return instance;
-	}
-
-	private Thread registryThread;
-	private volatile boolean toStop = false;
-	public void start(){
-		registryThread = new Thread(new Runnable() {
-			@Override
-			public void run() {
-				while (!toStop) {
-					try {
-						// auto registry group
-						List<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);
-									addressListStr = "";
-									for (String item:registryList) {
-										addressListStr += item + ",";
-									}
-									addressListStr = addressListStr.substring(0, addressListStr.length()-1);
-								}
-								group.setAddressList(addressListStr);
-								XxlJobAdminConfig.getAdminConfig().getXxlJobGroupDao().update(group);
-							}
-						}
-					} catch (Exception e) {
-						if (!toStop) {
-							logger.error(">>>>>>>>>>> xxl-job, job registry monitor thread error:{}", e);
-						}
-					}
-					try {
-						TimeUnit.SECONDS.sleep(RegistryConfig.BEAT_TIMEOUT);
-					} catch (InterruptedException e) {
-						if (!toStop) {
-							logger.error(">>>>>>>>>>> xxl-job, job registry monitor thread error:{}", e);
-						}
-					}
-				}
-				logger.info(">>>>>>>>>>> xxl-job, job registry monitor thread stop");
-			}
-		});
-		registryThread.setDaemon(true);
-		registryThread.setName("xxl-job, admin JobRegistryMonitorHelper");
-		registryThread.start();
-	}
-
-	public void toStop(){
-		toStop = true;
-		// interrupt and wait
-		registryThread.interrupt();
-		try {
-			registryThread.join();
-		} catch (InterruptedException e) {
-			logger.error(e.getMessage(), e);
-		}
-	}
-
-}

+ 37 - 22
jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/java/com/xxl/job/admin/core/thread/JobScheduleHelper.java

@@ -3,6 +3,8 @@ 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;
@@ -10,7 +12,6 @@ import org.slf4j.LoggerFactory;
 import java.sql.Connection;
 import java.sql.PreparedStatement;
 import java.sql.SQLException;
-import java.text.ParseException;
 import java.util.*;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.TimeUnit;
@@ -86,7 +87,15 @@ public class JobScheduleHelper {
                                     // 2.1、trigger-expire > 5s:pass && make next-trigger-time
                                     logger.warn(">>>>>>>>>>> xxl-job, schedule misfire, jobId = " + jobInfo.getId());
 
-                                    // fresh next
+                                    // 1、misfire match
+                                    MisfireStrategyEnum misfireStrategyEnum = MisfireStrategyEnum.match(jobInfo.getMisfireStrategy(), MisfireStrategyEnum.DO_NOTHING);
+                                    if (MisfireStrategyEnum.FIRE_ONCE_NOW == misfireStrategyEnum) {
+                                        // FIRE_ONCE_NOW 》 trigger
+                                        JobTriggerPoolHelper.trigger(jobInfo.getId(), TriggerTypeEnum.MISFIRE, -1, null, null, null);
+                                        logger.debug(">>>>>>>>>>> xxl-job, schedule push trigger : jobId = " + jobInfo.getId() );
+                                    }
+
+                                    // 2、fresh next
                                     refreshNextValidTime(jobInfo, new Date());
 
                                 } else if (nowTime > jobInfo.getTriggerNextTime()) {
@@ -213,17 +222,17 @@ public class JobScheduleHelper {
             @Override
             public void run() {
 
-                // align second
-                try {
-                    TimeUnit.MILLISECONDS.sleep(1000 - System.currentTimeMillis()%1000 );
-                } catch (InterruptedException e) {
-                    if (!ringThreadToStop) {
-                        logger.error(e.getMessage(), e);
-                    }
-                }
-
                 while (!ringThreadToStop) {
 
+                    // 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<>();
@@ -251,15 +260,6 @@ public class JobScheduleHelper {
                             logger.error(">>>>>>>>>>> xxl-job, JobScheduleHelper#ringThread error:{}", e);
                         }
                     }
-
-                    // next second, align second
-                    try {
-                        TimeUnit.MILLISECONDS.sleep(1000 - System.currentTimeMillis()%1000);
-                    } catch (InterruptedException e) {
-                        if (!ringThreadToStop) {
-                            logger.error(e.getMessage(), e);
-                        }
-                    }
                 }
                 logger.info(">>>>>>>>>>> xxl-job, JobScheduleHelper#ringThread stop");
             }
@@ -269,8 +269,8 @@ public class JobScheduleHelper {
         ringThread.start();
     }
 
-    private void refreshNextValidTime(XxlJobInfo jobInfo, Date fromTime) throws ParseException {
-        Date nextValidTime = new CronExpression(jobInfo.getJobCron()).getNextValidTimeAfter(fromTime);
+    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());
@@ -278,6 +278,8 @@ public class JobScheduleHelper {
             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());
         }
     }
 
@@ -351,4 +353,17 @@ public class JobScheduleHelper {
         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;
+    }
+
 }

+ 2 - 1
jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/java/com/xxl/job/admin/core/trigger/TriggerTypeEnum.java

@@ -13,7 +13,8 @@ public enum TriggerTypeEnum {
     CRON(I18nUtil.getString("jobconf_trigger_type_cron")),
     RETRY(I18nUtil.getString("jobconf_trigger_type_retry")),
     PARENT(I18nUtil.getString("jobconf_trigger_type_parent")),
-    API(I18nUtil.getString("jobconf_trigger_type_api"));
+    API(I18nUtil.getString("jobconf_trigger_type_api")),
+    MISFIRE(I18nUtil.getString("jobconf_trigger_type_misfire"));
 
     private TriggerTypeEnum(String title){
         this.title = title;

+ 12 - 0
jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/java/com/xxl/job/admin/service/XxlJobService.java

@@ -2,6 +2,7 @@ package com.xxl.job.admin.service;
 
 
 import com.xxl.job.admin.core.model.XxlJobInfo;
+import com.xxl.job.admin.core.model.XxlJobUser;
 import com.xxl.job.core.biz.model.ReturnT;
 
 import java.util.Date;
@@ -67,6 +68,17 @@ public interface XxlJobService {
 	 */
 	public ReturnT<String> stop(int id);
 
+	/**
+	 * trigger
+	 *
+	 * @param loginUser
+	 * @param jobId
+	 * @param executorParam
+	 * @param addressList
+	 * @return
+	 */
+	public ReturnT<String> trigger(XxlJobUser loginUser, int jobId, String executorParam, String addressList);
+
 	/**
 	 * dashboard info
 	 *

+ 5 - 145
jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/java/com/xxl/job/admin/service/impl/AdminBizImpl.java

@@ -1,27 +1,13 @@
 package com.xxl.job.admin.service.impl;
 
-import com.xxl.job.admin.core.model.XxlJobInfo;
-import com.xxl.job.admin.core.model.XxlJobLog;
-import com.xxl.job.admin.core.thread.JobTriggerPoolHelper;
-import com.xxl.job.admin.core.trigger.TriggerTypeEnum;
-import com.xxl.job.admin.core.util.I18nUtil;
-import com.xxl.job.admin.dao.XxlJobGroupDao;
-import com.xxl.job.admin.dao.XxlJobInfoDao;
-import com.xxl.job.admin.dao.XxlJobLogDao;
-import com.xxl.job.admin.dao.XxlJobRegistryDao;
+import com.xxl.job.admin.core.thread.JobCompleteHelper;
+import com.xxl.job.admin.core.thread.JobRegistryHelper;
 import com.xxl.job.core.biz.AdminBiz;
 import com.xxl.job.core.biz.model.HandleCallbackParam;
 import com.xxl.job.core.biz.model.RegistryParam;
 import com.xxl.job.core.biz.model.ReturnT;
-import com.xxl.job.core.handler.IJobHandler;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 import org.springframework.stereotype.Service;
-import org.springframework.util.StringUtils;
 
-import javax.annotation.Resource;
-import java.text.MessageFormat;
-import java.util.Date;
 import java.util.List;
 
 /**
@@ -29,147 +15,21 @@ import java.util.List;
  */
 @Service
 public class AdminBizImpl implements AdminBiz {
-    private static Logger logger = LoggerFactory.getLogger(AdminBizImpl.class);
-
-    @Resource
-    public XxlJobLogDao xxlJobLogDao;
-    @Resource
-    private XxlJobInfoDao xxlJobInfoDao;
-    @Resource
-    private XxlJobRegistryDao xxlJobRegistryDao;
-    @Resource
-    private XxlJobGroupDao xxlJobGroupDao;
 
 
     @Override
     public ReturnT<String> callback(List<HandleCallbackParam> callbackParamList) {
-        for (HandleCallbackParam handleCallbackParam: callbackParamList) {
-            ReturnT<String> callbackResult = callback(handleCallbackParam);
-            logger.debug(">>>>>>>>> JobApiController.callback {}, handleCallbackParam={}, callbackResult={}",
-                    (callbackResult.getCode()==IJobHandler.SUCCESS.getCode()?"success":"fail"), handleCallbackParam, callbackResult);
-        }
-
-        return ReturnT.SUCCESS;
-    }
-
-    private ReturnT<String> callback(HandleCallbackParam handleCallbackParam) {
-        // valid log item
-        XxlJobLog log = xxlJobLogDao.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
-        }
-
-        // trigger success, to trigger child job
-        String callbackMsg = null;
-        if (IJobHandler.SUCCESS.getCode() == handleCallbackParam.getExecuteResult().getCode()) {
-            XxlJobInfo xxlJobInfo = xxlJobInfoDao.loadById(log.getJobId());
-            if (xxlJobInfo!=null && xxlJobInfo.getChildJobId()!=null && xxlJobInfo.getChildJobId().trim().length()>0) {
-                callbackMsg = "<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
-                        callbackMsg += MessageFormat.format(I18nUtil.getString("jobconf_callback_child_msg1"),
-                                (i+1),
-                                childJobIds.length,
-                                childJobIds[i],
-                                (triggerChildResult.getCode()==ReturnT.SUCCESS_CODE?I18nUtil.getString("system_success"):I18nUtil.getString("system_fail")),
-                                triggerChildResult.getMsg());
-                    } else {
-                        callbackMsg += MessageFormat.format(I18nUtil.getString("jobconf_callback_child_msg2"),
-                                (i+1),
-                                childJobIds.length,
-                                childJobIds[i]);
-                    }
-                }
-
-            }
-        }
-
-        // handle msg
-        StringBuffer handleMsg = new StringBuffer();
-        if (log.getHandleMsg()!=null) {
-            handleMsg.append(log.getHandleMsg()).append("<br>");
-        }
-        if (handleCallbackParam.getExecuteResult().getMsg() != null) {
-            handleMsg.append(handleCallbackParam.getExecuteResult().getMsg());
-        }
-        if (callbackMsg != null) {
-            handleMsg.append(callbackMsg);
-        }
-
-        if (handleMsg.length() > 15000) {
-            handleMsg = new StringBuffer(handleMsg.substring(0, 15000));  // text最大64kb 避免长度过长
-        }
-
-        // success, save log
-        log.setHandleTime(new Date());
-        log.setHandleCode(handleCallbackParam.getExecuteResult().getCode());
-        log.setHandleMsg(handleMsg.toString());
-        xxlJobLogDao.updateHandleInfo(log);
-
-        return ReturnT.SUCCESS;
-    }
-
-    private boolean isNumeric(String str){
-        try {
-            int result = Integer.valueOf(str);
-            return true;
-        } catch (NumberFormatException e) {
-            return false;
-        }
+        return JobCompleteHelper.getInstance().callback(callbackParamList);
     }
 
     @Override
     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.");
-        }
-
-        int ret = xxlJobRegistryDao.registryUpdate(registryParam.getRegistryGroup(), registryParam.getRegistryKey(), registryParam.getRegistryValue(), new Date());
-        if (ret < 1) {
-            xxlJobRegistryDao.registrySave(registryParam.getRegistryGroup(), registryParam.getRegistryKey(), registryParam.getRegistryValue(), new Date());
-
-            // fresh
-            freshGroupRegistryInfo(registryParam);
-        }
-        return ReturnT.SUCCESS;
+        return JobRegistryHelper.getInstance().registry(registryParam);
     }
 
     @Override
     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.");
-        }
-
-        int ret = xxlJobRegistryDao.registryDelete(registryParam.getRegistryGroup(), registryParam.getRegistryKey(), registryParam.getRegistryValue());
-        if (ret > 0) {
-
-            // fresh
-            freshGroupRegistryInfo(registryParam);
-        }
-        return ReturnT.SUCCESS;
-    }
-
-    private void freshGroupRegistryInfo(RegistryParam registryParam){
-        // Under consideration, prevent affecting core tables
+        return JobRegistryHelper.getInstance().registryRemove(registryParam);
     }
 
 }

+ 131 - 30
jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/java/com/xxl/job/admin/service/impl/XxlJobServiceImpl.java

@@ -1,11 +1,16 @@
 package com.xxl.job.admin.service.impl;
 
+import com.xxl.job.admin.core.cron.CronExpression;
 import com.xxl.job.admin.core.model.XxlJobGroup;
 import com.xxl.job.admin.core.model.XxlJobInfo;
-import com.xxl.job.admin.core.cron.CronExpression;
 import com.xxl.job.admin.core.model.XxlJobLogReport;
+import com.xxl.job.admin.core.model.XxlJobUser;
 import com.xxl.job.admin.core.route.ExecutorRouteStrategyEnum;
+import com.xxl.job.admin.core.scheduler.MisfireStrategyEnum;
+import com.xxl.job.admin.core.scheduler.ScheduleTypeEnum;
 import com.xxl.job.admin.core.thread.JobScheduleHelper;
+import com.xxl.job.admin.core.thread.JobTriggerPoolHelper;
+import com.xxl.job.admin.core.trigger.TriggerTypeEnum;
 import com.xxl.job.admin.core.util.I18nUtil;
 import com.xxl.job.admin.dao.*;
 import com.xxl.job.admin.service.XxlJobService;
@@ -19,7 +24,6 @@ import org.springframework.stereotype.Service;
 
 import javax.annotation.Resource;
 import java.text.MessageFormat;
-import java.text.ParseException;
 import java.util.*;
 
 /**
@@ -58,40 +62,67 @@ public class XxlJobServiceImpl implements XxlJobService {
 
 	@Override
 	public ReturnT<String> add(XxlJobInfo jobInfo) {
-		// valid
+
+		// valid base
 		XxlJobGroup group = xxlJobGroupDao.load(jobInfo.getJobGroup());
 		if (group == null) {
 			return new ReturnT<String>(ReturnT.FAIL_CODE, (I18nUtil.getString("system_please_choose")+I18nUtil.getString("jobinfo_field_jobgroup")) );
 		}
-		if (!CronExpression.isValidExpression(jobInfo.getJobCron())) {
-			return new ReturnT<String>(ReturnT.FAIL_CODE, I18nUtil.getString("jobinfo_field_cron_unvalid") );
-		}
 		if (jobInfo.getJobDesc()==null || jobInfo.getJobDesc().trim().length()==0) {
 			return new ReturnT<String>(ReturnT.FAIL_CODE, (I18nUtil.getString("system_please_input")+I18nUtil.getString("jobinfo_field_jobdesc")) );
 		}
 		if (jobInfo.getAuthor()==null || jobInfo.getAuthor().trim().length()==0) {
 			return new ReturnT<String>(ReturnT.FAIL_CODE, (I18nUtil.getString("system_please_input")+I18nUtil.getString("jobinfo_field_author")) );
 		}
-		if (ExecutorRouteStrategyEnum.match(jobInfo.getExecutorRouteStrategy(), null) == null) {
-			return new ReturnT<String>(ReturnT.FAIL_CODE, (I18nUtil.getString("jobinfo_field_executorRouteStrategy")+I18nUtil.getString("system_unvalid")) );
+
+		// valid trigger
+		ScheduleTypeEnum scheduleTypeEnum = ScheduleTypeEnum.match(jobInfo.getScheduleType(), null);
+		if (scheduleTypeEnum == null) {
+			return new ReturnT<String>(ReturnT.FAIL_CODE, (I18nUtil.getString("schedule_type")+I18nUtil.getString("system_unvalid")) );
 		}
-		if (ExecutorBlockStrategyEnum.match(jobInfo.getExecutorBlockStrategy(), null) == null) {
-			return new ReturnT<String>(ReturnT.FAIL_CODE, (I18nUtil.getString("jobinfo_field_executorBlockStrategy")+I18nUtil.getString("system_unvalid")) );
+		if (scheduleTypeEnum == ScheduleTypeEnum.CRON) {
+			if (jobInfo.getScheduleConf()==null || !CronExpression.isValidExpression(jobInfo.getScheduleConf())) {
+				return new ReturnT<String>(ReturnT.FAIL_CODE, "Cron"+I18nUtil.getString("system_unvalid"));
+			}
+		} else if (scheduleTypeEnum == ScheduleTypeEnum.FIX_RATE/* || scheduleTypeEnum == ScheduleTypeEnum.FIX_DELAY*/) {
+			if (jobInfo.getScheduleConf() == null) {
+				return new ReturnT<String>(ReturnT.FAIL_CODE, (I18nUtil.getString("schedule_type")) );
+			}
+			try {
+				int fixSecond = Integer.valueOf(jobInfo.getScheduleConf());
+				if (fixSecond < 1) {
+					return new ReturnT<String>(ReturnT.FAIL_CODE, (I18nUtil.getString("schedule_type")+I18nUtil.getString("system_unvalid")) );
+				}
+			} catch (Exception e) {
+				return new ReturnT<String>(ReturnT.FAIL_CODE, (I18nUtil.getString("schedule_type")+I18nUtil.getString("system_unvalid")) );
+			}
 		}
+
+		// valid job
 		if (GlueTypeEnum.match(jobInfo.getGlueType()) == null) {
 			return new ReturnT<String>(ReturnT.FAIL_CODE, (I18nUtil.getString("jobinfo_field_gluetype")+I18nUtil.getString("system_unvalid")) );
 		}
 		if (GlueTypeEnum.BEAN==GlueTypeEnum.match(jobInfo.getGlueType()) && (jobInfo.getExecutorHandler()==null || jobInfo.getExecutorHandler().trim().length()==0) ) {
 			return new ReturnT<String>(ReturnT.FAIL_CODE, (I18nUtil.getString("system_please_input")+"JobHandler") );
 		}
-
-		// fix "\r" in shell
+		// 》fix "\r" in shell
 		if (GlueTypeEnum.GLUE_SHELL==GlueTypeEnum.match(jobInfo.getGlueType()) && jobInfo.getGlueSource()!=null) {
 			jobInfo.setGlueSource(jobInfo.getGlueSource().replaceAll("\r", ""));
 		}
 
-		// ChildJobId valid
-        if (jobInfo.getChildJobId()!=null && jobInfo.getChildJobId().trim().length()>0) {
+		// valid advanced
+		if (ExecutorRouteStrategyEnum.match(jobInfo.getExecutorRouteStrategy(), null) == null) {
+			return new ReturnT<String>(ReturnT.FAIL_CODE, (I18nUtil.getString("jobinfo_field_executorRouteStrategy")+I18nUtil.getString("system_unvalid")) );
+		}
+		if (MisfireStrategyEnum.match(jobInfo.getMisfireStrategy(), null) == null) {
+			return new ReturnT<String>(ReturnT.FAIL_CODE, (I18nUtil.getString("misfire_strategy")+I18nUtil.getString("system_unvalid")) );
+		}
+		if (ExecutorBlockStrategyEnum.match(jobInfo.getExecutorBlockStrategy(), null) == null) {
+			return new ReturnT<String>(ReturnT.FAIL_CODE, (I18nUtil.getString("jobinfo_field_executorBlockStrategy")+I18nUtil.getString("system_unvalid")) );
+		}
+
+		// 》ChildJobId valid
+		if (jobInfo.getChildJobId()!=null && jobInfo.getChildJobId().trim().length()>0) {
 			String[] childJobIds = jobInfo.getChildJobId().split(",");
 			for (String childJobIdItem: childJobIds) {
 				if (childJobIdItem!=null && childJobIdItem.trim().length()>0 && isNumeric(childJobIdItem)) {
@@ -140,25 +171,50 @@ public class XxlJobServiceImpl implements XxlJobService {
 	@Override
 	public ReturnT<String> update(XxlJobInfo jobInfo) {
 
-		// valid
-		if (!CronExpression.isValidExpression(jobInfo.getJobCron())) {
-			return new ReturnT<String>(ReturnT.FAIL_CODE, I18nUtil.getString("jobinfo_field_cron_unvalid") );
-		}
+		// valid base
 		if (jobInfo.getJobDesc()==null || jobInfo.getJobDesc().trim().length()==0) {
 			return new ReturnT<String>(ReturnT.FAIL_CODE, (I18nUtil.getString("system_please_input")+I18nUtil.getString("jobinfo_field_jobdesc")) );
 		}
 		if (jobInfo.getAuthor()==null || jobInfo.getAuthor().trim().length()==0) {
 			return new ReturnT<String>(ReturnT.FAIL_CODE, (I18nUtil.getString("system_please_input")+I18nUtil.getString("jobinfo_field_author")) );
 		}
+
+		// valid trigger
+		ScheduleTypeEnum scheduleTypeEnum = ScheduleTypeEnum.match(jobInfo.getScheduleType(), null);
+		if (scheduleTypeEnum == null) {
+			return new ReturnT<String>(ReturnT.FAIL_CODE, (I18nUtil.getString("schedule_type")+I18nUtil.getString("system_unvalid")) );
+		}
+		if (scheduleTypeEnum == ScheduleTypeEnum.CRON) {
+			if (jobInfo.getScheduleConf()==null || !CronExpression.isValidExpression(jobInfo.getScheduleConf())) {
+				return new ReturnT<String>(ReturnT.FAIL_CODE, "Cron"+I18nUtil.getString("system_unvalid") );
+			}
+		} else if (scheduleTypeEnum == ScheduleTypeEnum.FIX_RATE /*|| scheduleTypeEnum == ScheduleTypeEnum.FIX_DELAY*/) {
+			if (jobInfo.getScheduleConf() == null) {
+				return new ReturnT<String>(ReturnT.FAIL_CODE, (I18nUtil.getString("schedule_type")+I18nUtil.getString("system_unvalid")) );
+			}
+			try {
+				int fixSecond = Integer.valueOf(jobInfo.getScheduleConf());
+				if (fixSecond < 1) {
+					return new ReturnT<String>(ReturnT.FAIL_CODE, (I18nUtil.getString("schedule_type")+I18nUtil.getString("system_unvalid")) );
+				}
+			} catch (Exception e) {
+				return new ReturnT<String>(ReturnT.FAIL_CODE, (I18nUtil.getString("schedule_type")+I18nUtil.getString("system_unvalid")) );
+			}
+		}
+
+		// valid advanced
 		if (ExecutorRouteStrategyEnum.match(jobInfo.getExecutorRouteStrategy(), null) == null) {
 			return new ReturnT<String>(ReturnT.FAIL_CODE, (I18nUtil.getString("jobinfo_field_executorRouteStrategy")+I18nUtil.getString("system_unvalid")) );
 		}
+		if (MisfireStrategyEnum.match(jobInfo.getMisfireStrategy(), null) == null) {
+			return new ReturnT<String>(ReturnT.FAIL_CODE, (I18nUtil.getString("misfire_strategy")+I18nUtil.getString("system_unvalid")) );
+		}
 		if (ExecutorBlockStrategyEnum.match(jobInfo.getExecutorBlockStrategy(), null) == null) {
 			return new ReturnT<String>(ReturnT.FAIL_CODE, (I18nUtil.getString("jobinfo_field_executorBlockStrategy")+I18nUtil.getString("system_unvalid")) );
 		}
 
-		// ChildJobId valid
-        if (jobInfo.getChildJobId()!=null && jobInfo.getChildJobId().trim().length()>0) {
+		// ChildJobId valid
+		if (jobInfo.getChildJobId()!=null && jobInfo.getChildJobId().trim().length()>0) {
 			String[] childJobIds = jobInfo.getChildJobId().split(",");
 			for (String childJobIdItem: childJobIds) {
 				if (childJobIdItem!=null && childJobIdItem.trim().length()>0 && isNumeric(childJobIdItem)) {
@@ -197,24 +253,27 @@ public class XxlJobServiceImpl implements XxlJobService {
 
 		// next trigger time (5s后生效,避开预读周期)
 		long nextTriggerTime = exists_jobInfo.getTriggerNextTime();
-		if (exists_jobInfo.getTriggerStatus() == 1 && !jobInfo.getJobCron().equals(exists_jobInfo.getJobCron()) ) {
+		boolean scheduleDataNotChanged = jobInfo.getScheduleType().equals(exists_jobInfo.getScheduleType()) && jobInfo.getScheduleConf().equals(exists_jobInfo.getScheduleConf());
+		if (exists_jobInfo.getTriggerStatus() == 1 && !scheduleDataNotChanged) {
 			try {
-				Date nextValidTime = new CronExpression(jobInfo.getJobCron()).getNextValidTimeAfter(new Date(System.currentTimeMillis() + JobScheduleHelper.PRE_READ_MS));
+				Date nextValidTime = JobScheduleHelper.generateNextValidTime(jobInfo, new Date(System.currentTimeMillis() + JobScheduleHelper.PRE_READ_MS));
 				if (nextValidTime == null) {
-					return new ReturnT<String>(ReturnT.FAIL_CODE, I18nUtil.getString("jobinfo_field_cron_never_fire"));
+					return new ReturnT<String>(ReturnT.FAIL_CODE, (I18nUtil.getString("schedule_type")+I18nUtil.getString("system_unvalid")) );
 				}
 				nextTriggerTime = nextValidTime.getTime();
-			} catch (ParseException e) {
+			} catch (Exception e) {
 				logger.error(e.getMessage(), e);
-				return new ReturnT<String>(ReturnT.FAIL_CODE, I18nUtil.getString("jobinfo_field_cron_unvalid")+" | "+ e.getMessage());
+				return new ReturnT<String>(ReturnT.FAIL_CODE, (I18nUtil.getString("schedule_type")+I18nUtil.getString("system_unvalid")) );
 			}
 		}
 
 		exists_jobInfo.setJobGroup(jobInfo.getJobGroup());
-		exists_jobInfo.setJobCron(jobInfo.getJobCron());
 		exists_jobInfo.setJobDesc(jobInfo.getJobDesc());
 		exists_jobInfo.setAuthor(jobInfo.getAuthor());
 		exists_jobInfo.setAlarmEmail(jobInfo.getAlarmEmail());
+		exists_jobInfo.setScheduleType(jobInfo.getScheduleType());
+		exists_jobInfo.setScheduleConf(jobInfo.getScheduleConf());
+		exists_jobInfo.setMisfireStrategy(jobInfo.getMisfireStrategy());
 		exists_jobInfo.setExecutorRouteStrategy(jobInfo.getExecutorRouteStrategy());
 		exists_jobInfo.setExecutorHandler(jobInfo.getExecutorHandler());
 		exists_jobInfo.setExecutorParam(jobInfo.getExecutorParam());
@@ -248,17 +307,23 @@ public class XxlJobServiceImpl implements XxlJobService {
 	public ReturnT<String> start(int id) {
 		XxlJobInfo xxlJobInfo = xxlJobInfoDao.loadById(id);
 
+		// valid
+		ScheduleTypeEnum scheduleTypeEnum = ScheduleTypeEnum.match(xxlJobInfo.getScheduleType(), ScheduleTypeEnum.NONE);
+		if (ScheduleTypeEnum.NONE == scheduleTypeEnum) {
+			return new ReturnT<String>(ReturnT.FAIL_CODE, (I18nUtil.getString("schedule_type_none_limit_start")) );
+		}
+
 		// next trigger time (5s后生效,避开预读周期)
 		long nextTriggerTime = 0;
 		try {
-			Date nextValidTime = new CronExpression(xxlJobInfo.getJobCron()).getNextValidTimeAfter(new Date(System.currentTimeMillis() + JobScheduleHelper.PRE_READ_MS));
+			Date nextValidTime = JobScheduleHelper.generateNextValidTime(xxlJobInfo, new Date(System.currentTimeMillis() + JobScheduleHelper.PRE_READ_MS));
 			if (nextValidTime == null) {
-				return new ReturnT<String>(ReturnT.FAIL_CODE, I18nUtil.getString("jobinfo_field_cron_never_fire"));
+				return new ReturnT<String>(ReturnT.FAIL_CODE, (I18nUtil.getString("schedule_type")+I18nUtil.getString("system_unvalid")) );
 			}
 			nextTriggerTime = nextValidTime.getTime();
-		} catch (ParseException e) {
+		} catch (Exception e) {
 			logger.error(e.getMessage(), e);
-			return new ReturnT<String>(ReturnT.FAIL_CODE, I18nUtil.getString("jobinfo_field_cron_unvalid")+" | "+ e.getMessage());
+			return new ReturnT<String>(ReturnT.FAIL_CODE, (I18nUtil.getString("schedule_type")+I18nUtil.getString("system_unvalid")) );
 		}
 
 		xxlJobInfo.setTriggerStatus(1);
@@ -283,6 +348,42 @@ public class XxlJobServiceImpl implements XxlJobService {
 		return ReturnT.SUCCESS;
 	}
 
+
+
+	@Override
+	public ReturnT<String> trigger(XxlJobUser loginUser, int jobId, String executorParam, String addressList) {
+		// permission
+		if (loginUser == null) {
+			return new ReturnT<String>(ReturnT.FAIL.getCode(), I18nUtil.getString("system_permission_limit"));
+		}
+		XxlJobInfo xxlJobInfo = xxlJobInfoDao.loadById(jobId);
+		if (xxlJobInfo == null) {
+			return new ReturnT<String>(ReturnT.FAIL.getCode(), I18nUtil.getString("jobinfo_glue_jobid_unvalid"));
+		}
+		if (!hasPermission(loginUser, xxlJobInfo.getJobGroup())) {
+			return new ReturnT<String>(ReturnT.FAIL.getCode(), I18nUtil.getString("system_permission_limit"));
+		}
+
+		// force cover job param
+		if (executorParam == null) {
+			executorParam = "";
+		}
+
+		JobTriggerPoolHelper.trigger(jobId, TriggerTypeEnum.MANUAL, -1, null, executorParam, addressList);
+		return ReturnT.SUCCESS;
+	}
+
+	private boolean hasPermission(XxlJobUser loginUser, int jobGroup){
+		if (loginUser.getRole() == 1) {
+			return true;
+		}
+		List<String> groupIdStrs = new ArrayList<>();
+		if (loginUser.getPermission()!=null && loginUser.getPermission().trim().length()>0) {
+			groupIdStrs = Arrays.asList(loginUser.getPermission().trim().split(","));
+		}
+		return groupIdStrs.contains(String.valueOf(jobGroup));
+	}
+
 	@Override
 	public Map<String, Object> dashboardInfo() {
 

+ 2 - 1
jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/resources/application.yml

@@ -1,7 +1,7 @@
 server:
   port: 9080
   servlet:
-      context-path: /xxl-job-admin
+    context-path: /xxl-job-admin
   #数据源配置
 spring:
   datasource:
@@ -24,6 +24,7 @@ spring:
     host: smtphz.qiye.163.com
     port: 994
     username: zhuwei@aboatedu.com
+    from: zhuwei@aboatedu.com
     password: zwass1314
     properties:
       mail:

+ 15 - 3
jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/resources/i18n/message_en.properties

@@ -1,6 +1,6 @@
 admin_name=Scheduling Center
 admin_name_full=Distributed Task Scheduling Platform XXL-JOB
-admin_version=2.2.0
+admin_version=2.4.2-SNAPSHOT
 admin_i18n=en
 
 ## system
@@ -117,8 +117,6 @@ jobinfo_field_jobdesc=Job description
 jobinfo_field_timeout=Job timeout period
 jobinfo_field_gluetype=GLUE Type
 jobinfo_field_executorparam=Param
-jobinfo_field_cron_unvalid=The Cron is illegal
-jobinfo_field_cron_never_fire=The Cron will never fire
 jobinfo_field_author=Author
 jobinfo_field_alarmemail=Alarm email
 jobinfo_field_alarmemail_placeholder=Please enter alarm mail, if there are more than one comma separated
@@ -144,6 +142,19 @@ jobinfo_glue_rollback=Version Backtrack
 jobinfo_glue_jobid_unvalid=Job ID is illegal
 jobinfo_glue_gluetype_unvalid=The job is not GLUE Type
 jobinfo_field_executorTimeout_placeholder=Job Timeout period,in seconds. effect if greater than zero
+schedule_type=Schedule Type
+schedule_type_none=None
+schedule_type_cron=Cron
+schedule_type_fix_rate=Fix rate
+schedule_type_fix_delay=Fix delay
+schedule_type_none_limit_start=The current schedule type disables startup
+misfire_strategy=Misfire strategy
+misfire_strategy_do_nothing=Do nothing
+misfire_strategy_fire_once_now=Fire once now
+jobinfo_conf_base=Base configuration
+jobinfo_conf_schedule=Schedule configuration
+jobinfo_conf_job=Job configuration
+jobinfo_conf_advanced=Advanced configuration
 
 ## job log
 joblog_name=Trigger Log
@@ -243,6 +254,7 @@ jobconf_trigger_type_manual=Manual trigger
 jobconf_trigger_type_parent=Parent job trigger
 jobconf_trigger_type_api=Api trigger
 jobconf_trigger_type_retry=Fail retry trigger
+jobconf_trigger_type_misfire=Misfire compensation trigger
 
 ## user
 user_manage=User Manage

+ 15 - 3
jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/resources/i18n/message_zh_CN.properties

@@ -1,6 +1,6 @@
 admin_name=任务调度中心
 admin_name_full=分布式任务调度平台XXL-JOB
-admin_version=2.2.0
+admin_version=2.4.2-SNAPSHOT
 admin_i18n=
 
 ## system
@@ -116,8 +116,6 @@ jobinfo_field_jobgroup=执行器
 jobinfo_field_jobdesc=任务描述
 jobinfo_field_gluetype=运行模式
 jobinfo_field_executorparam=任务参数
-jobinfo_field_cron_unvalid=Cron格式非法
-jobinfo_field_cron_never_fire=Cron非法,永远不会触发
 jobinfo_field_author=负责人
 jobinfo_field_timeout=任务超时时间
 jobinfo_field_alarmemail=报警邮件
@@ -144,6 +142,19 @@ jobinfo_glue_rollback=版本回溯
 jobinfo_glue_jobid_unvalid=任务ID非法
 jobinfo_glue_gluetype_unvalid=该任务非GLUE模式
 jobinfo_field_executorTimeout_placeholder=任务超时时间,单位秒,大于零时生效
+schedule_type=调度类型
+schedule_type_none=无
+schedule_type_cron=CRON
+schedule_type_fix_rate=固定速度
+schedule_type_fix_delay=固定延迟
+schedule_type_none_limit_start=当前调度类型禁止启动
+misfire_strategy=调度过期策略
+misfire_strategy_do_nothing=忽略
+misfire_strategy_fire_once_now=立即执行一次
+jobinfo_conf_base=基础配置
+jobinfo_conf_schedule=调度配置
+jobinfo_conf_job=任务配置
+jobinfo_conf_advanced=高级配置
 
 ## job log
 joblog_name=调度日志
@@ -243,6 +254,7 @@ jobconf_trigger_type_manual=手动触发
 jobconf_trigger_type_parent=父任务触发
 jobconf_trigger_type_api=API触发
 jobconf_trigger_type_retry=失败重试触发
+jobconf_trigger_type_misfire=调度过期补偿
 
 ## user
 user_manage=用户管理

+ 15 - 3
jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/resources/i18n/message_zh_TC.properties

@@ -1,6 +1,6 @@
 admin_name=任務調度中心
 admin_name_full=分布式任務調度平臺XXL-JOB
-admin_version=2.2.0
+admin_version=2.4.2-SNAPSHOT
 admin_i18n=
 
 ## system
@@ -116,8 +116,6 @@ jobinfo_field_jobgroup=執行器
 jobinfo_field_jobdesc=任務描述
 jobinfo_field_gluetype=運行模式
 jobinfo_field_executorparam=任務參數
-jobinfo_field_cron_unvalid=Cron 格式非法
-jobinfo_field_cron_never_fire=Cron 格式非法,永遠不會觸發
 jobinfo_field_author=負責人
 jobinfo_field_timeout=任務超時秒數
 jobinfo_field_alarmemail=告警郵件
@@ -144,6 +142,19 @@ jobinfo_glue_rollback=版本回復
 jobinfo_glue_jobid_unvalid=任務ID非法
 jobinfo_glue_gluetype_unvalid=該任務非GLUE模式
 jobinfo_field_executorTimeout_placeholder=任務超時時間,單位秒,大於零時生效
+schedule_type=調度類型
+schedule_type_none=無
+schedule_type_cron=CRON
+schedule_type_fix_rate=固定速度
+schedule_type_fix_delay=固定延遲
+schedule_type_none_limit_start=當前調度類型禁止啟動
+misfire_strategy=調度過期策略
+misfire_strategy_do_nothing=忽略
+misfire_strategy_fire_once_now=立即執行壹次
+jobinfo_conf_base=基礎配置
+jobinfo_conf_schedule=調度配置
+jobinfo_conf_job=任務配置
+jobinfo_conf_advanced=高級配置
 
 ## job log
 joblog_name=調度日誌
@@ -243,6 +254,7 @@ jobconf_trigger_type_manual=手動觸發
 jobconf_trigger_type_parent=父任務觸發
 jobconf_trigger_type_api=API觸發
 jobconf_trigger_type_retry=失敗重試觸發
+jobconf_trigger_type_misfire=調度過期補償
 
 ## user
 user_manage=用户管理

+ 1 - 1
jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/resources/logback.xml

@@ -2,7 +2,7 @@
 <configuration debug="false" scan="true" scanPeriod="1 seconds">
 
     <contextName>logback</contextName>
-    <property name="log.path" value="../xxl-job/xxl-job-admin.log"/>
+    <property name="log.path" value="/data/applogs/xxl-job/xxl-job-admin.log"/>
 
     <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
         <encoder>

+ 7 - 4
jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/resources/mybatis-mapper/XxlJobGroupMapper.xml

@@ -9,6 +9,7 @@
 	    <result column="title" property="title" />
 		<result column="address_type" property="addressType" />
 		<result column="address_list" property="addressList" />
+		<result column="update_time" property="updateTime" />
 	</resultMap>
 
 	<sql id="Base_Column_List">
@@ -16,7 +17,8 @@
 		t.app_name,
 		t.title,
 		t.address_type,
-		t.address_list
+		t.address_list,
+		t.update_time
 	</sql>
 
 	<select id="findAll" resultMap="XxlJobGroup">
@@ -33,8 +35,8 @@
 	</select>
 
 	<insert id="save" parameterType="com.xxl.job.admin.core.model.XxlJobGroup" useGeneratedKeys="true" keyProperty="id" >
-		INSERT INTO xxl_job_group ( `app_name`, `title`, `address_type`, `address_list`)
-		values ( #{appname}, #{title}, #{addressType}, #{addressList});
+		INSERT INTO xxl_job_group ( `app_name`, `title`, `address_type`, `address_list`, `update_time`)
+		values ( #{appname}, #{title}, #{addressType}, #{addressList}, #{updateTime} );
 	</insert>
 
 	<update id="update" parameterType="com.xxl.job.admin.core.model.XxlJobGroup" >
@@ -42,7 +44,8 @@
 		SET `app_name` = #{appname},
 			`title` = #{title},
 			`address_type` = #{addressType},
-			`address_list` = #{addressList}
+			`address_list` = #{addressList},
+			`update_time` = #{updateTime}
 		WHERE id = #{id}
 	</update>
 

+ 16 - 5
jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/resources/mybatis-mapper/XxlJobInfoMapper.xml

@@ -7,7 +7,6 @@
 		<result column="id" property="id" />
 
 		<result column="job_group" property="jobGroup" />
-	    <result column="job_cron" property="jobCron" />
 	    <result column="job_desc" property="jobDesc" />
 
 	    <result column="add_time" property="addTime" />
@@ -16,6 +15,10 @@
 	    <result column="author" property="author" />
 	    <result column="alarm_email" property="alarmEmail" />
 
+		<result column="schedule_type" property="scheduleType" />
+		<result column="schedule_conf" property="scheduleConf" />
+		<result column="misfire_strategy" property="misfireStrategy" />
+
 		<result column="executor_route_strategy" property="executorRouteStrategy" />
 		<result column="executor_handler" property="executorHandler" />
 	    <result column="executor_param" property="executorParam" />
@@ -38,12 +41,14 @@
 	<sql id="Base_Column_List">
 		t.id,
 		t.job_group,
-		t.job_cron,
 		t.job_desc,
 		t.add_time,
 		t.update_time,
 		t.author,
 		t.alarm_email,
+		t.schedule_type,
+		t.schedule_conf,
+		t.misfire_strategy,
 		t.executor_route_strategy,
 		t.executor_handler,
 		t.executor_param,
@@ -109,12 +114,14 @@
 	<insert id="save" parameterType="com.xxl.job.admin.core.model.XxlJobInfo" useGeneratedKeys="true" keyProperty="id" >
 		INSERT INTO xxl_job_info (
 			job_group,
-			job_cron,
 			job_desc,
 			add_time,
 			update_time,
 			author,
 			alarm_email,
+			schedule_type,
+			schedule_conf,
+			misfire_strategy,
             executor_route_strategy,
 			executor_handler,
 			executor_param,
@@ -131,12 +138,14 @@
 			trigger_next_time
 		) VALUES (
 			#{jobGroup},
-			#{jobCron},
 			#{jobDesc},
 			#{addTime},
 			#{updateTime},
 			#{author},
 			#{alarmEmail},
+			#{scheduleType},
+			#{scheduleConf},
+			#{misfireStrategy},
 			#{executorRouteStrategy},
 			#{executorHandler},
 			#{executorParam},
@@ -168,11 +177,13 @@
 		UPDATE xxl_job_info
 		SET
 			job_group = #{jobGroup},
-			job_cron = #{jobCron},
 			job_desc = #{jobDesc},
 			update_time = #{updateTime},
 			author = #{author},
 			alarm_email = #{alarmEmail},
+			schedule_type = #{scheduleType},
+			schedule_conf = #{scheduleConf},
+			misfire_strategy = #{misfireStrategy},
 			executor_route_strategy = #{executorRouteStrategy},
 			executor_handler = #{executorHandler},
 			executor_param = #{executorParam},

+ 21 - 9
jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/resources/mybatis-mapper/XxlJobLogMapper.xml

@@ -247,15 +247,27 @@
 	</update>
 
 	<select id="findLostJobIds" resultType="long" >
-		SELECT t.id
-		FROM xxl_job_log AS t
-		WHERE t.trigger_code = 200
-			and t.handle_code = 0
-			and t.trigger_time <![CDATA[ <= ]]> #{losedTime}
-			and t.executor_address not in (
-				SELECT t2.registry_value
-				FROM xxl_job_registry AS t2
-			)
+		SELECT
+			t.id
+		FROM
+			xxl_job_log t
+			LEFT JOIN xxl_job_registry t2 ON t.executor_address = t2.registry_value
+		WHERE
+			t.trigger_code = 200
+				AND t.handle_code = 0
+				AND t.trigger_time <![CDATA[ <= ]]> #{losedTime}
+				AND t2.id IS NULL;
 	</select>
+	<!--
+	SELECT t.id
+	FROM xxl_job_log AS t
+	WHERE t.trigger_code = 200
+		and t.handle_code = 0
+		and t.trigger_time <![CDATA[ <= ]]> #{losedTime}
+		and t.executor_address not in (
+			SELECT t2.registry_value
+			FROM xxl_job_registry AS t2
+		)
+	-->
 
 </mapper>

File diff suppressed because it is too large
+ 3 - 3
jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/resources/static/adminlte/bower_components/bootstrap/css/bootstrap.min.css


File diff suppressed because it is too large
+ 1 - 1
jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/resources/static/adminlte/bower_components/bootstrap/css/bootstrap.min.css.map


File diff suppressed because it is too large
+ 3 - 4
jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/resources/static/adminlte/bower_components/bootstrap/js/bootstrap.min.js


File diff suppressed because it is too large
+ 2 - 2
jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/resources/static/adminlte/bower_components/jquery/jquery.min.js


File diff suppressed because it is too large
+ 1 - 1
jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/resources/static/adminlte/bower_components/moment/moment.min.js


File diff suppressed because it is too large
+ 7 - 6
jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/resources/static/adminlte/dist/css/AdminLTE.min.css


File diff suppressed because it is too large
+ 1 - 1
jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/resources/static/adminlte/dist/css/skins/_all-skins.min.css


File diff suppressed because it is too large
+ 5 - 6
jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/resources/static/adminlte/dist/js/adminlte.min.js


+ 13 - 2
jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/resources/static/js/jobgroup.index.1.js

@@ -123,7 +123,7 @@ $(function() {
 		var id = $(this).attr("_id");
 		var row = tableData['key'+id];
 
-		var html = '<div>';
+		/*var html = '<div>';
 		if (row.registryList) {
 			for (var index in row.registryList) {
 				html += (parseInt(index)+1) + '. <span class="badge bg-green" >' + row.registryList[index] + '</span><br>';
@@ -135,8 +135,19 @@ $(function() {
 			title: I18n.jobinfo_opt_registryinfo ,
 			btn: [ I18n.system_ok ],
 			content: html
-		});
+		});*/
+
+		var html = '<table class="table table-bordered"><tbody>';
+		if (row.registryList) {
+			for (var index in row.registryList) {
+				html += '<tr><th>' + (parseInt(index)+1) + '</th>';
+				html += '<th><span class="badge bg-green" >' + row.registryList[index] + '</span></th><tr>';
+			}
+		}
+		html += '</tbody></table>';
 
+		$('#showRegistryListModal .data').html(html);
+		$('#showRegistryListModal').modal({backdrop: false, keyboard: false}).modal('show');
 	});
 
 

+ 130 - 73
jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/resources/static/js/jobinfo.index.1.js

@@ -48,6 +48,18 @@ $(function() {
 						"visible" : true,
 						"width":'25%'
 					},
+					{
+						"data": 'scheduleType',
+						"visible" : true,
+						"width":'13%',
+						"render": function ( data, type, row ) {
+							if (row.scheduleConf) {
+								return row.scheduleType + ':'+ row.scheduleConf;
+							} else {
+								return row.scheduleType;
+							}
+						}
+					},
 					{
 						"data": 'glueType',
 						"width":'25%',
@@ -62,11 +74,6 @@ $(function() {
 						}
 					},
 	                { "data": 'executorParam', "visible" : false},
-					{
-						"data": 'jobCron',
-						"visible" : true,
-						"width":'13%'
-					},
 	                {
 	                	"data": 'addTime',
 	                	"visible" : false,
@@ -111,10 +118,16 @@ $(function() {
                                     start_stop_div = '<li><a href="javascript:void(0);" class="job_operate" _type="job_resume" >'+ I18n.jobinfo_opt_start +'</a></li>\n';
                                 }
 
+                                // job_next_time_html
+								var job_next_time_html = '';
+								if (row.scheduleType == 'CRON' || row.scheduleType == 'FIX_RATE') {
+									job_next_time_html = '<li><a href="javascript:void(0);" class="job_next_time" >' + I18n.jobinfo_opt_next_time + '</a></li>\n';
+								}
+
                                 // log url
                                 var logHref = base_url +'/joblog?jobId='+ row.id;
 
-                                // log url
+                                // code url
                                 var codeBtn = "";
                                 if ('BEAN' != row.glueType) {
                                     var codeUrl = base_url +'/jobcode?jobId='+ row.id;
@@ -136,7 +149,7 @@ $(function() {
                                     '       <li><a href="javascript:void(0);" class="job_trigger" >'+ I18n.jobinfo_opt_run +'</a></li>\n' +
                                     '       <li><a href="'+ logHref +'">'+ I18n.jobinfo_opt_log +'</a></li>\n' +
                                     '       <li><a href="javascript:void(0);" class="job_registryinfo" >' + I18n.jobinfo_opt_registryinfo + '</a></li>\n' +
-                                    '       <li><a href="javascript:void(0);" class="job_next_time" >' + I18n.jobinfo_opt_next_time + '</a></li>\n' +
+									job_next_time_html +
                                     '       <li class="divider"></li>\n' +
                                     codeBtn +
                                     start_stop_div +
@@ -322,17 +335,16 @@ $(function() {
         var id = $(this).parents('ul').attr("_id");
         var row = tableData['key'+id];
 
-        var jobCron = row.jobCron;
-
         $.ajax({
             type : 'POST',
             url : base_url + "/jobinfo/nextTriggerTime",
             data : {
-                "cron" : jobCron
+                "scheduleType" : row.scheduleType,
+				"scheduleConf" : row.scheduleConf
             },
             dataType : "json",
             success : function(data){
-            	
+
             	if (data.code != 200) {
                     layer.open({
                         title: I18n.jobinfo_opt_next_time ,
@@ -364,8 +376,14 @@ $(function() {
 	$(".add").click(function(){
 
 		// init-cronGen
-        $("#addModal .form input[name='jobCron']").show().siblings().remove();
-        $("#addModal .form input[name='jobCron']").cronGen({});
+        $("#addModal .form input[name='schedule_conf_CRON']").show().siblings().remove();
+        $("#addModal .form input[name='schedule_conf_CRON']").cronGen({});
+
+		// 》init scheduleType
+		$("#updateModal .form select[name=scheduleType]").change();
+
+		// 》init glueType
+		$("#updateModal .form select[name=glueType]").change();
 
 		$('#addModal').modal({backdrop: false, keyboard: false}).modal('show');
 	});
@@ -378,35 +396,29 @@ $(function() {
 				required : true,
 				maxlength: 50
 			},
-            jobCron : {
-            	required : true
-            },
 			author : {
 				required : true
-			},
+			}/*,
             executorTimeout : {
                 digits:true
             },
             executorFailRetryCount : {
                 digits:true
-            }
+            }*/
         },
         messages : {
             jobDesc : {
             	required : I18n.system_please_input + I18n.jobinfo_field_jobdesc
             },
-            jobCron : {
-            	required : I18n.system_please_input + "Cron"
-            },
             author : {
             	required : I18n.system_please_input + I18n.jobinfo_field_author
-            },
+            }/*,
             executorTimeout : {
                 digits: I18n.system_please_input + I18n.system_digits
             },
             executorFailRetryCount : {
                 digits: I18n.system_please_input + I18n.system_digits
-            }
+            }*/
         },
 		highlight : function(element) {
             $(element).closest('.form-group').addClass('has-error');
@@ -420,7 +432,7 @@ $(function() {
         },
         submitHandler : function(form) {
 
-			// process
+			// process executorTimeout+executorFailRetryCount
             var executorTimeout = $("#addModal .form input[name='executorTimeout']").val();
             if(!/^\d+$/.test(executorTimeout)) {
                 executorTimeout = 0;
@@ -432,8 +444,17 @@ $(function() {
             }
             $("#addModal .form input[name='executorFailRetryCount']").val(executorFailRetryCount);
 
-            // process-cronGen
-            $("#addModal .form input[name='jobCron']").val( $("#addModal .form input[name='cronGen_display']").val() );
+            // process schedule_conf
+			var scheduleType = $("#addModal .form select[name='scheduleType']").val();
+			var scheduleConf;
+			if (scheduleType == 'CRON') {
+				scheduleConf = $("#addModal .form input[name='cronGen_display']").val();
+			} else if (scheduleType == 'FIX_RATE') {
+				scheduleConf = $("#addModal .form input[name='schedule_conf_FIX_RATE']").val();
+			} else if (scheduleType == 'FIX_DELAY') {
+				scheduleConf = $("#addModal .form input[name='schedule_conf_FIX_DELAY']").val();
+			}
+			$("#addModal .form input[name='scheduleConf']").val( scheduleConf );
 
         	$.post(base_url + "/jobinfo/add",  $("#addModal .form").serialize(), function(data, status) {
     			if (data.code == "200") {
@@ -468,6 +489,13 @@ $(function() {
 		$("#addModal .form input[name='executorHandler']").removeAttr("readonly");
 	});
 
+	// scheduleType change
+	$(".scheduleType").change(function(){
+		var scheduleType = $(this).val();
+		$(this).parents("form").find(".schedule_conf").hide();
+		$(this).parents("form").find(".schedule_conf_" + scheduleType).show();
+
+	});
 
     // glueType change
     $(".glueType").change(function(){
@@ -508,27 +536,46 @@ $(function() {
         var id = $(this).parents('ul').attr("_id");
         var row = tableData['key'+id];
 
-		// base data
+		// fill base
 		$("#updateModal .form input[name='id']").val( row.id );
 		$('#updateModal .form select[name=jobGroup] option[value='+ row.jobGroup +']').prop('selected', true);
 		$("#updateModal .form input[name='jobDesc']").val( row.jobDesc );
-		$("#updateModal .form input[name='jobCron']").val( row.jobCron );
 		$("#updateModal .form input[name='author']").val( row.author );
 		$("#updateModal .form input[name='alarmEmail']").val( row.alarmEmail );
-		$("#updateModal .form input[name='executorTimeout']").val( row.executorTimeout );
-        $("#updateModal .form input[name='executorFailRetryCount']").val( row.executorFailRetryCount );
-		$('#updateModal .form select[name=executorRouteStrategy] option[value='+ row.executorRouteStrategy +']').prop('selected', true);
+
+		// fill trigger
+		$('#updateModal .form select[name=scheduleType] option[value='+ row.scheduleType +']').prop('selected', true);
+		$("#updateModal .form input[name='scheduleConf']").val( row.scheduleConf );
+		if (row.scheduleType == 'CRON') {
+			$("#updateModal .form input[name='schedule_conf_CRON']").val( row.scheduleConf );
+		} else if (row.scheduleType == 'FIX_RATE') {
+			$("#updateModal .form input[name='schedule_conf_FIX_RATE']").val( row.scheduleConf );
+		} else if (row.scheduleType == 'FIX_DELAY') {
+			$("#updateModal .form input[name='schedule_conf_FIX_DELAY']").val( row.scheduleConf );
+		}
+
+		// 》init scheduleType
+		$("#updateModal .form select[name=scheduleType]").change();
+
+		// fill job
+		$('#updateModal .form select[name=glueType] option[value='+ row.glueType +']').prop('selected', true);
 		$("#updateModal .form input[name='executorHandler']").val( row.executorHandler );
 		$("#updateModal .form textarea[name='executorParam']").val( row.executorParam );
-        $("#updateModal .form input[name='childJobId']").val( row.childJobId );
-		$('#updateModal .form select[name=executorBlockStrategy] option[value='+ row.executorBlockStrategy +']').prop('selected', true);
-		$('#updateModal .form select[name=glueType] option[value='+ row.glueType +']').prop('selected', true);
 
-        $("#updateModal .form select[name=glueType]").change();
+		// 》init glueType
+		$("#updateModal .form select[name=glueType]").change();
 
-        // init-cronGen
-        $("#updateModal .form input[name='jobCron']").show().siblings().remove();
-        $("#updateModal .form input[name='jobCron']").cronGen({});
+		// 》init-cronGen
+		$("#updateModal .form input[name='schedule_conf_CRON']").show().siblings().remove();
+		$("#updateModal .form input[name='schedule_conf_CRON']").cronGen({});
+
+		// fill advanced
+		$('#updateModal .form select[name=executorRouteStrategy] option[value='+ row.executorRouteStrategy +']').prop('selected', true);
+		$("#updateModal .form input[name='childJobId']").val( row.childJobId );
+		$('#updateModal .form select[name=misfireStrategy] option[value='+ row.misfireStrategy +']').prop('selected', true);
+		$('#updateModal .form select[name=executorBlockStrategy] option[value='+ row.executorBlockStrategy +']').prop('selected', true);
+		$("#updateModal .form input[name='executorTimeout']").val( row.executorTimeout );
+        $("#updateModal .form input[name='executorFailRetryCount']").val( row.executorFailRetryCount );
 
 		// show
 		$('#updateModal').modal({backdrop: false, keyboard: false}).modal('show');
@@ -543,35 +590,17 @@ $(function() {
 				required : true,
 				maxlength: 50
 			},
-			jobCron : {
-				required : true
-			},
 			author : {
 				required : true
-			},
-            executorTimeout : {
-                digits:true
-            },
-            executorFailRetryCount : {
-                digits:true
-            }
+			}
 		},
 		messages : {
 			jobDesc : {
                 required : I18n.system_please_input + I18n.jobinfo_field_jobdesc
 			},
-			jobCron : {
-				required : I18n.system_please_input + "Cron"
-			},
 			author : {
 				required : I18n.system_please_input + I18n.jobinfo_field_author
-			},
-            executorTimeout : {
-                digits: I18n.system_please_input + I18n.system_digits
-            },
-            executorFailRetryCount : {
-                digits: I18n.system_please_input + I18n.system_digits
-            }
+			}
 		},
 		highlight : function(element) {
             $(element).closest('.form-group').addClass('has-error');
@@ -585,7 +614,7 @@ $(function() {
         },
         submitHandler : function(form) {
 
-            // process
+            // process executorTimeout + executorFailRetryCount
             var executorTimeout = $("#updateModal .form input[name='executorTimeout']").val();
             if(!/^\d+$/.test(executorTimeout)) {
                 executorTimeout = 0;
@@ -597,8 +626,18 @@ $(function() {
             }
             $("#updateModal .form input[name='executorFailRetryCount']").val(executorFailRetryCount);
 
-            // process-cronGen
-            $("#updateModal .form input[name='jobCron']").val( $("#updateModal .form input[name='cronGen_display']").val() );
+
+			// process schedule_conf
+			var scheduleType = $("#updateModal .form select[name='scheduleType']").val();
+			var scheduleConf;
+			if (scheduleType == 'CRON') {
+				scheduleConf = $("#updateModal .form input[name='cronGen_display']").val();
+			} else if (scheduleType == 'FIX_RATE') {
+				scheduleConf = $("#updateModal .form input[name='schedule_conf_FIX_RATE']").val();
+			} else if (scheduleType == 'FIX_DELAY') {
+				scheduleConf = $("#updateModal .form input[name='schedule_conf_FIX_DELAY']").val();
+			}
+			$("#updateModal .form input[name='scheduleConf']").val( scheduleConf );
 
 			// post
     		$.post(base_url + "/jobinfo/update", $("#updateModal .form").serialize(), function(data, status) {
@@ -653,27 +692,45 @@ $(function() {
 		var id = $(this).parents('ul').attr("_id");
 		var row = tableData['key'+id];
 
-		// base data
-		//$("#addModal .form input[name='id']").val( row.id );
+		// fill base
 		$('#addModal .form select[name=jobGroup] option[value='+ row.jobGroup +']').prop('selected', true);
 		$("#addModal .form input[name='jobDesc']").val( row.jobDesc );
-		$("#addModal .form input[name='jobCron']").val( row.jobCron );
 		$("#addModal .form input[name='author']").val( row.author );
 		$("#addModal .form input[name='alarmEmail']").val( row.alarmEmail );
-		$("#addModal .form input[name='executorTimeout']").val( row.executorTimeout );
-		$("#addModal .form input[name='executorFailRetryCount']").val( row.executorFailRetryCount );
-		$('#addModal .form select[name=executorRouteStrategy] option[value='+ row.executorRouteStrategy +']').prop('selected', true);
+
+		// fill trigger
+		$('#addModal .form select[name=scheduleType] option[value='+ row.scheduleType +']').prop('selected', true);
+		$("#addModal .form input[name='scheduleConf']").val( row.scheduleConf );
+		if (row.scheduleType == 'CRON') {
+			$("#addModal .form input[name='schedule_conf_CRON']").val( row.scheduleConf );
+		} else if (row.scheduleType == 'FIX_RATE') {
+			$("#addModal .form input[name='schedule_conf_FIX_RATE']").val( row.scheduleConf );
+		} else if (row.scheduleType == 'FIX_DELAY') {
+			$("#addModal .form input[name='schedule_conf_FIX_DELAY']").val( row.scheduleConf );
+		}
+
+		// 》init scheduleType
+		$("#addModal .form select[name=scheduleType]").change();
+
+		// fill job
+		$('#addModal .form select[name=glueType] option[value='+ row.glueType +']').prop('selected', true);
 		$("#addModal .form input[name='executorHandler']").val( row.executorHandler );
 		$("#addModal .form textarea[name='executorParam']").val( row.executorParam );
-		$("#addModal .form input[name='childJobId']").val( row.childJobId );
-		$('#addModal .form select[name=executorBlockStrategy] option[value='+ row.executorBlockStrategy +']').prop('selected', true);
-		$('#addModal .form select[name=glueType] option[value='+ row.glueType +']').prop('selected', true);
 
+		// 》init glueType
 		$("#addModal .form select[name=glueType]").change();
 
-		// init-cronGen
-		$("#addModal .form input[name='jobCron']").show().siblings().remove();
-		$("#addModal .form input[name='jobCron']").cronGen({});
+		// 》init-cronGen
+		$("#addModal .form input[name='schedule_conf_CRON']").show().siblings().remove();
+		$("#addModal .form input[name='schedule_conf_CRON']").cronGen({});
+
+		// fill advanced
+		$('#addModal .form select[name=executorRouteStrategy] option[value='+ row.executorRouteStrategy +']').prop('selected', true);
+		$("#addModal .form input[name='childJobId']").val( row.childJobId );
+		$('#addModal .form select[name=misfireStrategy] option[value='+ row.misfireStrategy +']').prop('selected', true);
+		$('#addModal .form select[name=executorBlockStrategy] option[value='+ row.executorBlockStrategy +']').prop('selected', true);
+		$("#addModal .form input[name='executorTimeout']").val( row.executorTimeout );
+		$("#addModal .form input[name='executorFailRetryCount']").val( row.executorFailRetryCount );
 
 		// show
 		$('#addModal').modal({backdrop: false, keyboard: false}).modal('show');

+ 0 - 2
jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/resources/static/js/joblog.detail.1.js

@@ -25,8 +25,6 @@ $(function() {
             async: false,   // sync, make log ordered
             url : base_url + '/joblog/logDetailCat',
             data : {
-                "executorAddress":executorAddress,
-                "triggerTime":triggerTime,
                 "logId":logId,
                 "fromLineNum":fromLineNum
             },

+ 9 - 4
jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/resources/static/js/joblog.index.1.js

@@ -116,7 +116,7 @@ $(function() {
 						"data": 'triggerTime',
                         "width":'20%',
 						"render": function ( data, type, row ) {
-							return data?moment(new Date(data)).format("YYYY-MM-DD HH:mm:ss"):"";
+							return data?moment(data).format("YYYY-MM-DD HH:mm:ss"):"";
 						}
 					},
 					{
@@ -145,7 +145,7 @@ $(function() {
 	                	"data": 'handleTime',
                         "width":'20%',
 	                	"render": function ( data, type, row ) {
-	                		return data?moment(new Date(data)).format("YYYY-MM-DD HH:mm:ss"):"";
+	                		return data?moment(data).format("YYYY-MM-DD HH:mm:ss"):"";
 	                	}
 	                },
 	                {
@@ -187,6 +187,12 @@ $(function() {
 		                			}*/
 		                			//return temp;
 
+									var logKillDiv = '';
+									if(row.handleCode == 0){
+										logKillDiv = '       <li class="divider"></li>\n' +
+											'       <li><a href="javascript:void(0);" class="logKill" _id="'+ row.id +'" >'+ I18n.joblog_kill_log +'</a></li>\n';
+									}
+
 									var html = '<div class="btn-group">\n' +
 										'     <button type="button" class="btn btn-primary btn-sm">'+ I18n.system_opt +'</button>\n' +
 										'     <button type="button" class="btn btn-primary btn-sm dropdown-toggle" data-toggle="dropdown">\n' +
@@ -195,8 +201,7 @@ $(function() {
 										'     </button>\n' +
 										'     <ul class="dropdown-menu" role="menu" _id="'+ row.id +'" >\n' +
 										'       <li><a href="javascript:void(0);" class="logDetail" _id="'+ row.id +'" >'+ I18n.joblog_rolling_log +'</a></li>\n' +
-										'       <li class="divider"></li>\n' +
-										'       <li><a href="javascript:void(0);" class="logKill" _id="'+ row.id +'" >'+ I18n.joblog_kill_log +'</a></li>\n' +
+										logKillDiv +
 										'     </ul>\n' +
 										'   </div>';
 

+ 1 - 1
jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/resources/static/js/login.1.js

@@ -50,7 +50,7 @@ $(function(){
 				if (data.code == "200") {
                     layer.msg( I18n.login_success );
                     setTimeout(function(){
-                        window.location.href = base_url;
+                        window.location.href = base_url + "/";
                     }, 500);
 				} else {
                     layer.open({

+ 1 - 1
jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/resources/static/js/user.index.1.js

@@ -33,7 +33,7 @@ $(function() {
 					},
 	                {
 	                	"data": 'password',
-						"visible" : true,
+						"visible" : false,
                         "width":'20%',
                         "render": function ( data, type, row ) {
                             return '*********';

+ 34 - 7
jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/resources/static/plugins/cronGen/cronGen.js

@@ -10,7 +10,7 @@
             options = $.extend({}, $.fn.cronGen.defaultOptions, options);
             //create top menu
             var cronContainer = $("<div/>", { id: "CronContainer", style: "display:none;width:300px;height:300px;" });
-            var mainDiv = $("<div/>", { id: "CronGenMainDiv", style: "width:410px;height:300px;" });
+            var mainDiv = $("<div/>", { id: "CronGenMainDiv", style: "width:410px;height:420px;" });
             var topMenu = $("<ul/>", { "class": "nav nav-tabs", id: "CronGenTabs" });
             $('<li/>', { 'class': 'active' }).html($('<a id="SecondlyTab" href="#Secondly">秒</a>')).appendTo(topMenu);
             $('<li/>').html($('<a id="MinutesTab" href="#Minutes">分钟</a>')).appendTo(topMenu);
@@ -318,9 +318,12 @@
             // resultsName = $(this).prop("id");
             // $(this).prop("name", resultsName);
 
+            var runTime = '<br style="padding-top: 10px"><label>最近运行时间: </label></br><textarea id="runTime" rows="6" style="width: 90%;resize: none;background: none;border: none;outline: none;" readonly = readonly></textarea></div>';
+
             $(span12).appendTo(row);
             $(row).appendTo(container);
             $(container).appendTo(mainDiv);
+            $(runTime).appendTo(mainDiv);
             $(cronContainer).append(mainDiv);
 
             var that = $(this);
@@ -351,9 +354,13 @@
                     return $(cronContainer).html();
                 },
                 template: '<div class="popover" style="max-width:500px !important; width:425px;left:-341.656px;"><div class="arrow"></div><div class="popover-inner"><h3 class="popover-title"></h3><div class="popover-content"><p></p></div></div></div>',
+                sanitize:false,
                 placement: options.direction
 
             }).on('click', function (e) {
+                if (inputElement.val().trim() !== '') {
+                    refreshRunTime();
+                }
                 e.preventDefault();
 
                 //fillDataOfMinutesAndHoursSelectOptions();
@@ -374,6 +381,7 @@
                 });
                 $("#CronGenMainDiv select,input").change(function (e) {
                     generate();
+                    refreshRunTime();
                 });
                 $("#CronGenMainDiv input").focus(function (e) {
                     generate();
@@ -628,6 +636,25 @@
         displayElement.val(results);
     };
 
+    var refreshRunTime = function () {
+        $.ajax({
+            type : 'GET',
+            url : base_url + "/jobinfo/nextTriggerTime",
+            data : {
+                "scheduleType" : 'CRON',
+                "scheduleConf" : inputElement.val()
+            },
+            dataType : "json",
+            success : function(data){
+                if (data.code === 200) {
+                    $('#runTime').val(data.content.join("\n"));
+                } else {
+                    $('#runTime').val(data.msg);
+                }
+            }
+        });
+    };
+
 })(jQuery);
 
 (function($) {
@@ -1011,12 +1038,12 @@
             //获取参数中表达式的值
             if (cronExpress) {
                 var regs = cronExpress.split(' ');
-                $("input[name=secondHidden]").val(regs[0]);
-                $("input[name=minHidden]").val(regs[1]);
-                $("input[name=hourHidden]").val(regs[2]);
-                $("input[name=dayHidden]").val(regs[3]);
-                $("input[name=monthHidden]").val(regs[4]);
-                $("input[name=weekHidden]").val(regs[5]);
+                $("#secondHidden").val(regs[0]);
+                $("#minHidden").val(regs[1]);
+                $("#hourHidden").val(regs[2]);
+                $("#dayHidden").val(regs[3]);
+                $("#monthHidden").val(regs[4]);
+                $("#weekHidden").val(regs[5]);
 
                 $.fn.cronGen.tools.initObj(regs[0], "second");
                 $.fn.cronGen.tools.initObj(regs[1], "min");

+ 34 - 7
jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/resources/static/plugins/cronGen/cronGen_en.js

@@ -10,7 +10,7 @@
             options = $.extend({}, $.fn.cronGen.defaultOptions, options);
             //create top menu
             var cronContainer = $("<div/>", { id: "CronContainer", style: "display:none;width:300px;height:300px;" });
-            var mainDiv = $("<div/>", { id: "CronGenMainDiv", style: "width:410px;height:300px;" });
+            var mainDiv = $("<div/>", { id: "CronGenMainDiv", style: "width:410px;height:420px;" });
             var topMenu = $("<ul/>", { "class": "nav nav-tabs", id: "CronGenTabs" });
             $('<li/>', { 'class': 'active' }).html($('<a id="SecondlyTab" href="#Secondly">秒</a>')).appendTo(topMenu);
             $('<li/>').html($('<a id="MinutesTab" href="#Minutes">Minute</a>')).appendTo(topMenu);
@@ -318,9 +318,12 @@
             // resultsName = $(this).prop("id");
             // $(this).prop("name", resultsName);
 
+            var runTime = '<br style="padding-top: 10px"><label>Recent Run Time: </label></br><textarea id="runTime" rows="6" style="width: 90%;resize: none;background: none;border: none;outline: none;" readonly = readonly></textarea></div>';
+
             $(span12).appendTo(row);
             $(row).appendTo(container);
             $(container).appendTo(mainDiv);
+            $(runTime).appendTo(mainDiv);
             $(cronContainer).append(mainDiv);
 
             var that = $(this);
@@ -351,9 +354,13 @@
                     return $(cronContainer).html();
                 },
                 template: '<div class="popover" style="max-width:500px !important; width:425px;left:-341.656px;"><div class="arrow"></div><div class="popover-inner"><h3 class="popover-title"></h3><div class="popover-content"><p></p></div></div></div>',
+                sanitize:false,
                 placement: options.direction
 
             }).on('click', function (e) {
+                if (inputElement.val().trim() !== '') {
+                    refreshRunTime();
+                }
                 e.preventDefault();
 
                 //fillDataOfMinutesAndHoursSelectOptions();
@@ -374,6 +381,7 @@
                 });
                 $("#CronGenMainDiv select,input").change(function (e) {
                     generate();
+                    refreshRunTime();
                 });
                 $("#CronGenMainDiv input").focus(function (e) {
                     generate();
@@ -628,6 +636,25 @@
         displayElement.val(results);
     };
 
+    var refreshRunTime = function () {
+        $.ajax({
+            type : 'GET',
+            url : base_url + "/jobinfo/nextTriggerTime",
+            data : {
+                "scheduleType" : 'CRON',
+                "scheduleConf" : inputElement.val()
+            },
+            dataType : "json",
+            success : function(data){
+                if (data.code === 200) {
+                    $('#runTime').val(data.content.join("\n"));
+                } else {
+                    $('#runTime').val(data.msg);
+                }
+            }
+        });
+    };
+
 })(jQuery);
 
 (function($) {
@@ -1011,12 +1038,12 @@
             //获取参数中表达式的值
             if (cronExpress) {
                 var regs = cronExpress.split(' ');
-                $("input[name=secondHidden]").val(regs[0]);
-                $("input[name=minHidden]").val(regs[1]);
-                $("input[name=hourHidden]").val(regs[2]);
-                $("input[name=dayHidden]").val(regs[3]);
-                $("input[name=monthHidden]").val(regs[4]);
-                $("input[name=weekHidden]").val(regs[5]);
+                $("#secondHidden").val(regs[0]);
+                $("#minHidden").val(regs[1]);
+                $("#hourHidden").val(regs[2]);
+                $("#dayHidden").val(regs[3]);
+                $("#monthHidden").val(regs[4]);
+                $("#weekHidden").val(regs[5]);
 
                 $.fn.cronGen.tools.initObj(regs[0], "second");
                 $.fn.cronGen.tools.initObj(regs[1], "min");

+ 2 - 2
jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/resources/templates/common/common.macro.ftl

@@ -34,9 +34,9 @@
 </#macro>
 
 <#macro commonScript>
-	<!-- jQuery 2.1.4 -->
+	<!-- jQuery -->
 	<script src="${request.contextPath}/static/adminlte/bower_components/jquery/jquery.min.js"></script>
-	<!-- Bootstrap 3.3.5 -->
+	<!-- Bootstrap -->
 	<script src="${request.contextPath}/static/adminlte/bower_components/bootstrap/js/bootstrap.min.js"></script>
 	<!-- FastClick -->
 	<script src="${request.contextPath}/static/adminlte/bower_components/fastclick/fastclick.js"></script>

+ 23 - 4
jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/resources/templates/jobgroup/jobgroup.index.ftl

@@ -28,13 +28,13 @@
                 <div class="col-xs-3">
                     <div class="input-group">
                         <span class="input-group-addon">AppName</span>
-                        <input type="text" class="form-control" id="appname" autocomplete="on" placeholder="${I18n.system_please_input}AppName" >
+                        <input type="text" class="form-control" id="appname" placeholder="${I18n.system_please_input}AppName" >
                     </div>
                 </div>
                 <div class="col-xs-3">
                     <div class="input-group">
                         <span class="input-group-addon">${I18n.jobgroup_field_title}</span>
-                        <input type="text" class="form-control" id="title" autocomplete="on" placeholder="${I18n.jobgroup_field_title}" >
+                        <input type="text" class="form-control" id="title" placeholder="${I18n.jobgroup_field_title}" >
                     </div>
                 </div>
                 <div class="col-xs-2">
@@ -70,6 +70,25 @@
 	    </section>
 	</div>
 
+    <!-- 注册列表查看.模态框 -->
+    <div class="modal fade" id="showRegistryListModal" tabindex="-1" role="dialog"  aria-hidden="true">
+        <div class="modal-dialog modal-sm">
+            <div class="modal-content">
+                <div class="modal-header">
+                    <h4 class="modal-title" >${I18n.jobinfo_opt_registryinfo}</h4>
+                </div>
+                <div class="modal-body">
+                    <div class="data" style="word-wrap: break-word;"></div>
+                </div>
+                <div class="modal-footer">
+                    <div class="text-center" >
+                        <button type="button" class="btn btn-info ok" data-dismiss="modal" >${I18n.system_ok}</button>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+
     <!-- 新增.模态框 -->
     <div class="modal fade" id="addModal" tabindex="-1" role="dialog"  aria-hidden="true">
         <div class="modal-dialog ">
@@ -98,7 +117,7 @@
                         <div class="form-group">
                             <label for="lastname" class="col-sm-2 control-label">${I18n.jobgroup_field_registryList}<font color="red">*</font></label>
                             <div class="col-sm-10">
-                                <textarea class="textarea" name="addressList" maxlength="512" placeholder="${I18n.jobgroup_field_registryList_placeholder}" readonly="readonly" style="background-color:#eee; width: 100%; height: 100px; font-size: 14px; line-height: 15px; border: 1px solid #dddddd; padding: 5px;"></textarea>
+                                <textarea class="textarea" name="addressList" maxlength="20000" placeholder="${I18n.jobgroup_field_registryList_placeholder}" readonly="readonly" style="background-color:#eee; width: 100%; height: 100px; font-size: 14px; line-height: 15px; border: 1px solid #dddddd; padding: 5px;"></textarea>
                             </div>
                         </div>
                         <hr>
@@ -142,7 +161,7 @@
                         <div class="form-group">
                             <label for="lastname" class="col-sm-2 control-label">${I18n.jobgroup_field_registryList}<font color="red">*</font></label>
                             <div class="col-sm-10">
-                                <textarea class="textarea" name="addressList" maxlength="512" placeholder="${I18n.jobgroup_field_registryList_placeholder}" readonly="readonly" style="background-color:#eee; width: 100%; height: 100px; font-size: 14px; line-height: 15px; border: 1px solid #dddddd; padding: 5px;"></textarea>
+                                <textarea class="textarea" name="addressList" maxlength="20000" placeholder="${I18n.jobgroup_field_registryList_placeholder}" readonly="readonly" style="background-color:#eee; width: 100%; height: 100px; font-size: 14px; line-height: 15px; border: 1px solid #dddddd; padding: 5px;"></textarea>
                             </div>
                         </div>
                         <hr>

+ 170 - 70
jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/resources/templates/jobinfo/jobinfo.index.ftl

@@ -46,17 +46,17 @@
                 </div>
                 <div class="col-xs-2">
                     <div class="input-group">
-                        <input type="text" class="form-control" id="jobDesc" autocomplete="on" placeholder="${I18n.system_please_input}${I18n.jobinfo_field_jobdesc}" >
+                        <input type="text" class="form-control" id="jobDesc" placeholder="${I18n.system_please_input}${I18n.jobinfo_field_jobdesc}" >
                     </div>
                 </div>
                 <div class="col-xs-2">
                     <div class="input-group">
-                        <input type="text" class="form-control" id="executorHandler" autocomplete="on" placeholder="${I18n.system_please_input}JobHandler" >
+                        <input type="text" class="form-control" id="executorHandler" placeholder="${I18n.system_please_input}JobHandler" >
                     </div>
                 </div>
                 <div class="col-xs-2">
                     <div class="input-group">
-                        <input type="text" class="form-control" id="author" autocomplete="on" placeholder="${I18n.system_please_input}${I18n.jobinfo_field_author}" >
+                        <input type="text" class="form-control" id="author" placeholder="${I18n.system_please_input}${I18n.jobinfo_field_author}" >
                     </div>
                 </div>
 	            <div class="col-xs-1">
@@ -80,9 +80,9 @@
 					            		<th name="id" >${I18n.jobinfo_field_id}</th>
 					                	<th name="jobGroup" >${I18n.jobinfo_field_jobgroup}</th>
 					                  	<th name="jobDesc" >${I18n.jobinfo_field_jobdesc}</th>
+                                        <th name="scheduleType" >${I18n.schedule_type}</th>
                                         <th name="glueType" >${I18n.jobinfo_field_gluetype}</th>
-					                  	<th name="executorParam" >${I18n.jobinfo_field_executorparam}</th>
-                                        <th name="jobCron" >Cron</th>
+                                        <th name="executorParam" >${I18n.jobinfo_field_executorparam}</th>
 					                  	<th name="addTime" >addTime</th>
 					                  	<th name="updateTime" >updateTime</th>
 					                  	<th name="author" >${I18n.jobinfo_field_author}</th>
@@ -114,6 +114,8 @@
          	</div>
          	<div class="modal-body">
 				<form class="form-horizontal form" role="form" >
+
+                    <p style="margin: 0 0 10px;text-align: left;border-bottom: 1px solid #e5e5e5;color: gray;">${I18n.jobinfo_conf_base}</p>    <#-- 基础信息 -->
 					<div class="form-group">
 						<label for="firstname" class="col-sm-2 control-label">${I18n.jobinfo_field_jobgroup}<font color="red">*</font></label>
 						<div class="col-sm-4">
@@ -123,35 +125,97 @@
 		            			</#list>
 		                  	</select>
 						</div>
+
                         <label for="lastname" class="col-sm-2 control-label">${I18n.jobinfo_field_jobdesc}<font color="red">*</font></label>
                         <div class="col-sm-4"><input type="text" class="form-control" name="jobDesc" placeholder="${I18n.system_please_input}${I18n.jobinfo_field_jobdesc}" maxlength="50" ></div>
 					</div>
                     <div class="form-group">
-                        <label for="firstname" class="col-sm-2 control-label">${I18n.jobinfo_field_executorRouteStrategy}<font color="red">*</font></label>
+                        <label for="lastname" class="col-sm-2 control-label">${I18n.jobinfo_field_author}<font color="red">*</font></label>
+                        <div class="col-sm-4"><input type="text" class="form-control" name="author" placeholder="${I18n.system_please_input}${I18n.jobinfo_field_author}" maxlength="50" ></div>
+                        <label for="lastname" class="col-sm-2 control-label">${I18n.jobinfo_field_alarmemail}<font color="black">*</font></label>
+                        <div class="col-sm-4"><input type="text" class="form-control" name="alarmEmail" placeholder="${I18n.jobinfo_field_alarmemail_placeholder}" maxlength="100" ></div>
+                    </div>
+
+                    <br>
+                    <p style="margin: 0 0 10px;text-align: left;border-bottom: 1px solid #e5e5e5;color: gray;">${I18n.jobinfo_conf_schedule}</p>    <#-- 调度 -->
+                    <div class="form-group">
+                        <label for="firstname" class="col-sm-2 control-label">${I18n.schedule_type}<font color="red">*</font></label>
                         <div class="col-sm-4">
-                            <select class="form-control" name="executorRouteStrategy" >
-							<#list ExecutorRouteStrategyEnum as item>
-                                <option value="${item}" >${item.title}</option>
-							</#list>
+                            <select class="form-control scheduleType" name="scheduleType" >
+                                <#list ScheduleTypeEnum as item>
+                                    <option value="${item}" <#if 'CRON' == item >selected</#if> >${item.title}</option>
+                                </#list>
                             </select>
                         </div>
-                        <label for="lastname" class="col-sm-2 control-label">Cron<font color="red">*</font></label>
-                        <div class="col-sm-4"><input type="text" class="form-control" name="jobCron" placeholder="${I18n.system_please_input}Cron" maxlength="128" ></div>
+
+                        <input type="hidden" name="scheduleConf" />
+                        <div class="schedule_conf schedule_conf_NONE" style="display: none" >
+                        </div>
+                        <div class="schedule_conf schedule_conf_CRON" >
+                            <label for="lastname" class="col-sm-2 control-label">Cron<font color="red">*</font></label>
+                            <div class="col-sm-4"><input type="text" class="form-control" name="schedule_conf_CRON" placeholder="${I18n.system_please_input}Cron" maxlength="128" ></div>
+                        </div>
+                        <div class="schedule_conf schedule_conf_FIX_RATE" style="display: none" >
+                            <label for="lastname" class="col-sm-2 control-label">${I18n.schedule_type_fix_rate}<font color="red">*</font></label>
+                            <div class="col-sm-4"><input type="text" class="form-control" name="schedule_conf_FIX_RATE" placeholder="${I18n.system_please_input} ( Second )" maxlength="10" onkeyup="this.value=this.value.replace(/\D/g,'')" onafterpaste="this.value=this.value.replace(/\D/g,'')" ></div>
+                        </div>
+                        <div class="schedule_conf schedule_conf_FIX_DELAY" style="display: none" >
+                            <label for="lastname" class="col-sm-2 control-label">${I18n.schedule_type_fix_delay}<font color="red">*</font></label>
+                            <div class="col-sm-4"><input type="text" class="form-control" name="schedule_conf_FIX_DELAY" placeholder="${I18n.system_please_input} ( Second )" maxlength="10" onkeyup="this.value=this.value.replace(/\D/g,'')" onafterpaste="this.value=this.value.replace(/\D/g,'')" ></div>
+                        </div>
                     </div>
+
+                    <br>
+                    <p style="margin: 0 0 10px;text-align: left;border-bottom: 1px solid #e5e5e5;color: gray;">${I18n.jobinfo_conf_job}</p>    <#-- 任务配置 -->
+
                     <div class="form-group">
                         <label for="firstname" class="col-sm-2 control-label">${I18n.jobinfo_field_gluetype}<font color="red">*</font></label>
                         <div class="col-sm-4">
                             <select class="form-control glueType" name="glueType" >
-								<#list GlueTypeEnum as item>
-									<option value="${item}" >${item.desc}</option>
-								</#list>
+                                <#list GlueTypeEnum as item>
+                                    <option value="${item}" >${item.desc}</option>
+                                </#list>
                             </select>
                         </div>
                         <label for="firstname" class="col-sm-2 control-label">JobHandler<font color="red">*</font></label>
                         <div class="col-sm-4"><input type="text" class="form-control" name="executorHandler" placeholder="${I18n.system_please_input}JobHandler" maxlength="100" ></div>
                     </div>
+
                     <div class="form-group">
-                        <label for="firstname" class="col-sm-2 control-label">${I18n.jobinfo_field_executorBlockStrategy}<font color="red">*</font></label>
+                        <label for="firstname" class="col-sm-2 control-label">${I18n.jobinfo_field_executorparam}<font color="black">*</font></label>
+                        <div class="col-sm-10">
+                            <textarea class="textarea form-control" name="executorParam" placeholder="${I18n.system_please_input}${I18n.jobinfo_field_executorparam}" maxlength="512" style="height: 63px; line-height: 1.2;"></textarea>
+                        </div>
+                    </div>
+
+                    <br>
+                    <p style="margin: 0 0 10px;text-align: left;border-bottom: 1px solid #e5e5e5;color: gray;">${I18n.jobinfo_conf_advanced}</p>    <#-- 高级配置 -->
+
+                    <div class="form-group">
+                        <label for="firstname" class="col-sm-2 control-label">${I18n.jobinfo_field_executorRouteStrategy}<font color="black">*</font></label>
+                        <div class="col-sm-4">
+                            <select class="form-control" name="executorRouteStrategy" >
+							<#list ExecutorRouteStrategyEnum as item>
+                                <option value="${item}" >${item.title}</option>
+							</#list>
+                            </select>
+                        </div>
+
+                        <label for="lastname" class="col-sm-2 control-label">${I18n.jobinfo_field_childJobId}<font color="black">*</font></label>
+                        <div class="col-sm-4"><input type="text" class="form-control" name="childJobId" placeholder="${I18n.jobinfo_field_childJobId_placeholder}" maxlength="100" ></div>
+                    </div>
+
+                    <div class="form-group">
+                        <label for="firstname" class="col-sm-2 control-label">${I18n.misfire_strategy}<font color="black">*</font></label>
+                        <div class="col-sm-4">
+                            <select class="form-control" name="misfireStrategy" >
+                                <#list MisfireStrategyEnum as item>
+                                    <option value="${item}" <#if 'DO_NOTHING' == item >selected</#if> >${item.title}</option>
+                                </#list>
+                            </select>
+                        </div>
+
+                        <label for="firstname" class="col-sm-2 control-label">${I18n.jobinfo_field_executorBlockStrategy}<font color="black">*</font></label>
                         <div class="col-sm-4">
                             <select class="form-control" name="executorBlockStrategy" >
 								<#list ExecutorBlockStrategyEnum as item>
@@ -159,26 +223,13 @@
                                 </#list>
                             </select>
                         </div>
-                        <label for="lastname" class="col-sm-2 control-label">${I18n.jobinfo_field_childJobId}<font color="black">*</font></label>
-                        <div class="col-sm-4"><input type="text" class="form-control" name="childJobId" placeholder="${I18n.jobinfo_field_childJobId_placeholder}" maxlength="100" ></div>
                     </div>
+
                     <div class="form-group">
                         <label for="lastname" class="col-sm-2 control-label">${I18n.jobinfo_field_timeout}<font color="black">*</font></label>
-                        <div class="col-sm-4"><input type="text" class="form-control" name="executorTimeout" placeholder="${I18n.jobinfo_field_executorTimeout_placeholder}" maxlength="6" ></div>
+                        <div class="col-sm-4"><input type="text" class="form-control" name="executorTimeout" placeholder="${I18n.jobinfo_field_executorTimeout_placeholder}" maxlength="6" onkeyup="this.value=this.value.replace(/\D/g,'')" onafterpaste="this.value=this.value.replace(/\D/g,'')" ></div>
                         <label for="lastname" class="col-sm-2 control-label">${I18n.jobinfo_field_executorFailRetryCount}<font color="black">*</font></label>
-                        <div class="col-sm-4"><input type="text" class="form-control" name="executorFailRetryCount" placeholder="${I18n.jobinfo_field_executorFailRetryCount_placeholder}" maxlength="4" ></div>
-                    </div>
-					<div class="form-group">
-                        <label for="lastname" class="col-sm-2 control-label">${I18n.jobinfo_field_author}<font color="red">*</font></label>
-                        <div class="col-sm-4"><input type="text" class="form-control" name="author" placeholder="${I18n.system_please_input}${I18n.jobinfo_field_author}" maxlength="50" ></div>
-                        <label for="lastname" class="col-sm-2 control-label">${I18n.jobinfo_field_alarmemail}<font color="black">*</font></label>
-                        <div class="col-sm-4"><input type="text" class="form-control" name="alarmEmail" placeholder="${I18n.jobinfo_field_alarmemail_placeholder}" maxlength="100" ></div>
-					</div>
-                    <div class="form-group">
-                        <label for="firstname" class="col-sm-2 control-label">${I18n.jobinfo_field_executorparam}<font color="black">*</font></label>
-                        <div class="col-sm-10">
-                            <textarea class="textarea form-control" name="executorParam" placeholder="${I18n.system_please_input}${I18n.jobinfo_field_executorparam}" maxlength="512" style="height: 63px; line-height: 1.2;"></textarea>
-						</div>
+                        <div class="col-sm-4"><input type="text" class="form-control" name="executorFailRetryCount" placeholder="${I18n.jobinfo_field_executorFailRetryCount_placeholder}" maxlength="4" onkeyup="this.value=this.value.replace(/\D/g,'')" onafterpaste="this.value=this.value.replace(/\D/g,'')" ></div>
                     </div>
 
                     <hr>
@@ -194,16 +245,14 @@
 <textarea class="glueSource_java" style="display:none;" >
 package com.xxl.job.service.handler;
 
-import com.xxl.job.core.log.XxlJobLogger;
-import com.xxl.job.core.biz.model.ReturnT;
+import com.xxl.job.core.context.XxlJobHelper;
 import com.xxl.job.core.handler.IJobHandler;
 
 public class DemoGlueJobHandler extends IJobHandler {
 
 	@Override
-	public ReturnT<String> execute(String param) throws Exception {
-		XxlJobLogger.log("XXL-JOB, Hello World.");
-		return ReturnT.SUCCESS;
+	public void execute() throws Exception {
+		XxlJobHelper.log("XXL-JOB, Hello World.");
 	}
 
 }
@@ -310,71 +359,122 @@ exit 0
          	</div>
          	<div class="modal-body">
 				<form class="form-horizontal form" role="form" >
-					<div class="form-group">
+
+                    <p style="margin: 0 0 10px;text-align: left;border-bottom: 1px solid #e5e5e5;color: gray;">${I18n.jobinfo_conf_base}</p>    <#-- 基础信息 -->
+                    <div class="form-group">
                         <label for="firstname" class="col-sm-2 control-label">${I18n.jobinfo_field_jobgroup}<font color="red">*</font></label>
                         <div class="col-sm-4">
                             <select class="form-control" name="jobGroup" >
-							<#list JobGroupList as group>
-                                <option value="${group.id}" >${group.title}</option>
-							</#list>
+                                <#list JobGroupList as group>
+                                    <option value="${group.id}" >${group.title}</option>
+                                </#list>
                             </select>
                         </div>
+
                         <label for="lastname" class="col-sm-2 control-label">${I18n.jobinfo_field_jobdesc}<font color="red">*</font></label>
                         <div class="col-sm-4"><input type="text" class="form-control" name="jobDesc" placeholder="${I18n.system_please_input}${I18n.jobinfo_field_jobdesc}" maxlength="50" ></div>
                     </div>
                     <div class="form-group">
-                        <label for="firstname" class="col-sm-2 control-label">${I18n.jobinfo_field_executorRouteStrategy}<font color="red">*</font></label>
+                        <label for="lastname" class="col-sm-2 control-label">${I18n.jobinfo_field_author}<font color="red">*</font></label>
+                        <div class="col-sm-4"><input type="text" class="form-control" name="author" placeholder="${I18n.system_please_input}${I18n.jobinfo_field_author}" maxlength="50" ></div>
+                        <label for="lastname" class="col-sm-2 control-label">${I18n.jobinfo_field_alarmemail}<font color="black">*</font></label>
+                        <div class="col-sm-4"><input type="text" class="form-control" name="alarmEmail" placeholder="${I18n.jobinfo_field_alarmemail_placeholder}" maxlength="100" ></div>
+                    </div>
+
+                    <br>
+                    <p style="margin: 0 0 10px;text-align: left;border-bottom: 1px solid #e5e5e5;color: gray;">${I18n.jobinfo_conf_schedule}</p>    <#-- 调度配置 -->
+                    <div class="form-group">
+                        <label for="firstname" class="col-sm-2 control-label">${I18n.schedule_type}<font color="red">*</font></label>
                         <div class="col-sm-4">
-                            <select class="form-control" name="executorRouteStrategy" >
-							<#list ExecutorRouteStrategyEnum as item>
-                                <option value="${item}" >${item.title}</option>
-							</#list>
+                            <select class="form-control scheduleType" name="scheduleType" >
+                                <#list ScheduleTypeEnum as item>
+                                    <option value="${item}" >${item.title}</option>
+                                </#list>
                             </select>
                         </div>
-                        <label for="lastname" class="col-sm-2 control-label">Cron<font color="red">*</font></label>
-                        <div class="col-sm-4"><input type="text" class="form-control" name="jobCron" placeholder="${I18n.system_please_input}Cron" maxlength="128" ></div>
+
+                        <input type="hidden" name="scheduleConf" />
+                        <div class="schedule_conf schedule_conf_NONE" style="display: none" >
+                        </div>
+                        <div class="schedule_conf schedule_conf_CRON" >
+                            <label for="lastname" class="col-sm-2 control-label">Cron<font color="red">*</font></label>
+                            <div class="col-sm-4"><input type="text" class="form-control" name="schedule_conf_CRON" placeholder="${I18n.system_please_input}Cron" maxlength="128" ></div>
+                        </div>
+                        <div class="schedule_conf schedule_conf_FIX_RATE" style="display: none" >
+                            <label for="lastname" class="col-sm-2 control-label">${I18n.schedule_type_fix_rate}<font color="red">*</font></label>
+                            <div class="col-sm-4"><input type="text" class="form-control" name="schedule_conf_FIX_RATE" placeholder="${I18n.system_please_input} ( Second )" maxlength="10" onkeyup="this.value=this.value.replace(/\D/g,'')" onafterpaste="this.value=this.value.replace(/\D/g,'')" ></div>
+                        </div>
+                        <div class="schedule_conf schedule_conf_FIX_DELAY" style="display: none" >
+                            <label for="lastname" class="col-sm-2 control-label">${I18n.schedule_type_fix_delay}<font color="red">*</font></label>
+                            <div class="col-sm-4"><input type="text" class="form-control" name="schedule_conf_FIX_DELAY" placeholder="${I18n.system_please_input} ( Second )" maxlength="10" onkeyup="this.value=this.value.replace(/\D/g,'')" onafterpaste="this.value=this.value.replace(/\D/g,'')" ></div>
+                        </div>
                     </div>
+
+                    <br>
+                    <p style="margin: 0 0 10px;text-align: left;border-bottom: 1px solid #e5e5e5;color: gray;">${I18n.jobinfo_conf_job}</p>    <#-- 任务配置 -->
+
                     <div class="form-group">
                         <label for="firstname" class="col-sm-2 control-label">${I18n.jobinfo_field_gluetype}<font color="red">*</font></label>
                         <div class="col-sm-4">
                             <select class="form-control glueType" name="glueType" disabled >
-							<#list GlueTypeEnum as item>
-                                <option value="${item}" >${item.desc}</option>
-							</#list>
+                                <#list GlueTypeEnum as item>
+                                    <option value="${item}" >${item.desc}</option>
+                                </#list>
                             </select>
                         </div>
                         <label for="firstname" class="col-sm-2 control-label">JobHandler<font color="red">*</font></label>
                         <div class="col-sm-4"><input type="text" class="form-control" name="executorHandler" placeholder="${I18n.system_please_input}JobHandler" maxlength="100" ></div>
                     </div>
+
                     <div class="form-group">
-                        <label for="firstname" class="col-sm-2 control-label">${I18n.jobinfo_field_executorBlockStrategy}<font color="red">*</font></label>
+                        <label for="firstname" class="col-sm-2 control-label">${I18n.jobinfo_field_executorparam}<font color="black">*</font></label>
+                        <div class="col-sm-10">
+                            <textarea class="textarea form-control" name="executorParam" placeholder="${I18n.system_please_input}${I18n.jobinfo_field_executorparam}" maxlength="512" style="height: 63px; line-height: 1.2;"></textarea>
+                        </div>
+                    </div>
+
+                    <br>
+                    <p style="margin: 0 0 10px;text-align: left;border-bottom: 1px solid #e5e5e5;color: gray;">${I18n.jobinfo_conf_advanced}</p>    <#-- 高级配置 -->
+
+                    <div class="form-group">
+                        <label for="firstname" class="col-sm-2 control-label">${I18n.jobinfo_field_executorRouteStrategy}<font color="red">*</font></label>
                         <div class="col-sm-4">
-                            <select class="form-control" name="executorBlockStrategy" >
-							<#list ExecutorBlockStrategyEnum as item>
-                                <option value="${item}" >${item.title}</option>
-							</#list>
+                            <select class="form-control" name="executorRouteStrategy" >
+                                <#list ExecutorRouteStrategyEnum as item>
+                                    <option value="${item}" >${item.title}</option>
+                                </#list>
                             </select>
                         </div>
+
                         <label for="lastname" class="col-sm-2 control-label">${I18n.jobinfo_field_childJobId}<font color="black">*</font></label>
                         <div class="col-sm-4"><input type="text" class="form-control" name="childJobId" placeholder="${I18n.jobinfo_field_childJobId_placeholder}" maxlength="100" ></div>
                     </div>
+
                     <div class="form-group">
-                        <label for="lastname" class="col-sm-2 control-label">${I18n.jobinfo_field_timeout}<font color="black">*</font></label>
-                        <div class="col-sm-4"><input type="text" class="form-control" name="executorTimeout" placeholder="${I18n.jobinfo_field_executorTimeout_placeholder}" maxlength="6" ></div>
-                        <label for="lastname" class="col-sm-2 control-label">${I18n.jobinfo_field_executorFailRetryCount}<font color="black">*</font></label>
-                        <div class="col-sm-4"><input type="text" class="form-control" name="executorFailRetryCount" placeholder="${I18n.jobinfo_field_executorFailRetryCount_placeholder}" maxlength="4" ></div>
-                    </div>
-                    <div class="form-group">
-                        <label for="lastname" class="col-sm-2 control-label">${I18n.jobinfo_field_author}<font color="red">*</font></label>
-                        <div class="col-sm-4"><input type="text" class="form-control" name="author" placeholder="${I18n.system_please_input}${I18n.jobinfo_field_author}" maxlength="50" ></div>
-                        <label for="lastname" class="col-sm-2 control-label">${I18n.jobinfo_field_alarmemail}<font color="black">*</font></label>
-                        <div class="col-sm-4"><input type="text" class="form-control" name="alarmEmail" placeholder="${I18n.jobinfo_field_alarmemail_placeholder}" maxlength="100" ></div>
+                        <label for="firstname" class="col-sm-2 control-label">${I18n.misfire_strategy}<font color="black">*</font></label>
+                        <div class="col-sm-4">
+                            <select class="form-control" name="misfireStrategy" >
+                                <#list MisfireStrategyEnum as item>
+                                    <option value="${item}" <#if 'DO_NOTHING' == item >selected</#if> >${item.title}</option>
+                                </#list>
+                            </select>
+                        </div>
+
+                        <label for="firstname" class="col-sm-2 control-label">${I18n.jobinfo_field_executorBlockStrategy}<font color="red">*</font></label>
+                        <div class="col-sm-4">
+                            <select class="form-control" name="executorBlockStrategy" >
+                                <#list ExecutorBlockStrategyEnum as item>
+                                    <option value="${item}" >${item.title}</option>
+                                </#list>
+                            </select>
+                        </div>
                     </div>
+
                     <div class="form-group">
-                        <label for="firstname" class="col-sm-2 control-label">${I18n.jobinfo_field_executorparam}<font color="black">*</font></label>
-                        <div class="col-sm-10">
-                            <textarea class="textarea form-control" name="executorParam" placeholder="${I18n.system_please_input}${I18n.jobinfo_field_executorparam}" maxlength="512" style="height: 63px; line-height: 1.2;"></textarea>
-						</div>
+                        <label for="lastname" class="col-sm-2 control-label">${I18n.jobinfo_field_timeout}<font color="black">*</font></label>
+                        <div class="col-sm-4"><input type="text" class="form-control" name="executorTimeout" placeholder="${I18n.jobinfo_field_executorTimeout_placeholder}" maxlength="6" onkeyup="this.value=this.value.replace(/\D/g,'')" onafterpaste="this.value=this.value.replace(/\D/g,'')" ></div>
+                        <label for="lastname" class="col-sm-2 control-label">${I18n.jobinfo_field_executorFailRetryCount}<font color="black">*</font></label>
+                        <div class="col-sm-4"><input type="text" class="form-control" name="executorFailRetryCount" placeholder="${I18n.jobinfo_field_executorFailRetryCount_placeholder}" maxlength="4" onkeyup="this.value=this.value.replace(/\D/g,'')" onafterpaste="this.value=this.value.replace(/\D/g,'')" ></div>
                     </div>
 
 					<hr>

+ 0 - 3
jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/resources/templates/joblog/joblog.detail.ftl

@@ -48,7 +48,6 @@
             <pre style="font-size:12px;position:relative;" >
                 <div id="logConsole"></div>
                 <li class="fa fa-refresh fa-spin" style="font-size: 20px;float: left;" id="logConsoleRunning" ></li>
-                <div><hr><hr></div>
             </pre>
         </section>
     </div>
@@ -63,8 +62,6 @@
     // 参数
     var triggerCode = '${triggerCode}';
     var handleCode = '${handleCode}';
-    var executorAddress = '${executorAddress!}';
-    var triggerTime = '${triggerTime?c}';
     var logId = '${logId}';
 </script>
 <script src="${request.contextPath}/static/js/joblog.detail.1.js"></script>

+ 4 - 4
jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/resources/templates/joblog/joblog.index.ftl

@@ -74,7 +74,7 @@
                 </div>
 
 	            <div class="col-xs-1">
-                    <button class="btn btn-block btn-nomal" id="clearLog">${I18n.joblog_clean}</button>
+                    <button class="btn btn-block btn-default" id="clearLog">${I18n.joblog_clean}</button>
 	            </div>
           	</div>
 			
@@ -123,7 +123,7 @@
             <div class="modal-body">
                 <form class="form-horizontal form" role="form" >
                     <div class="form-group">
-                        <label class="col-sm-3 control-label"">${I18n.jobinfo_field_jobgroup}:</label>
+                        <label class="col-sm-3 control-label">${I18n.jobinfo_field_jobgroup}:</label>
                         <div class="col-sm-9">
                             <input type="text" class="form-control jobGroupText" readonly >
 							<input type="hidden" name="jobGroup" >
@@ -131,7 +131,7 @@
                     </div>
 
                     <div class="form-group">
-                        <label class="col-sm-3 control-label"">${I18n.jobinfo_job}:</label>
+                        <label class="col-sm-3 control-label">${I18n.jobinfo_job}:</label>
                         <div class="col-sm-9">
                             <input type="text" class="form-control jobIdText" readonly >
                             <input type="hidden" name="jobId" >
@@ -139,7 +139,7 @@
                     </div>
 
                     <div class="form-group">
-                        <label class="col-sm-3 control-label"">${I18n.joblog_clean_type}:</label>
+                        <label class="col-sm-3 control-label">${I18n.joblog_clean_type}:</label>
                         <div class="col-sm-9">
                             <select class="form-control" name="type" >
                                 <option value="1" >${I18n.joblog_clean_type_1}</option>

+ 1 - 1
jeecg-boot/pom.xml

@@ -38,7 +38,7 @@
 		<alibaba.nacos.version>2.0.4</alibaba.nacos.version>
 		<seata.version>1.5.2</seata.version>
 
-		<xxl-job-core.version>2.2.0</xxl-job-core.version>
+		<xxl-job-core.version>2.4.1</xxl-job-core.version>
         <fastjson.version>1.2.83</fastjson.version>
         <aviator.version>5.2.6</aviator.version>
 		<pegdown.version>1.6.0</pegdown.version>