update xxl-job 2.3.0 => 2.3.1
| | |
| | | <lock4j.version>2.2.1</lock4j.version> |
| | | <dynamic-ds.version>3.5.1</dynamic-ds.version> |
| | | <tlog.version>1.4.0</tlog.version> |
| | | <xxl-job.version>2.3.0</xxl-job.version> |
| | | <xxl-job.version>2.3.1</xxl-job.version> |
| | | |
| | | <!-- jdk11 缺失依赖 jaxb--> |
| | | <jaxb.version>3.0.1</jaxb.version> |
| | |
| | | @PermissionLimit(limit = false) |
| | | public ModelAndView toLogin(HttpServletRequest request, HttpServletResponse response, ModelAndView modelAndView) { |
| | | if (loginService.ifLogin(request, response) != null) { |
| | | modelAndView.setView(new RedirectView("/" , true, false)); |
| | | modelAndView.setView(new RedirectView("/", true, false)); |
| | | return modelAndView; |
| | | } |
| | | return new ModelAndView("login"); |
| | | } |
| | | |
| | | @RequestMapping(value = "login" , method = RequestMethod.POST) |
| | | @RequestMapping(value = "login", method = RequestMethod.POST) |
| | | @ResponseBody |
| | | @PermissionLimit(limit = false) |
| | | public ReturnT<String> loginDo(HttpServletRequest request, HttpServletResponse response, String userName, String password, String ifRemember) { |
| | |
| | | return loginService.login(request, response, userName, password, ifRem); |
| | | } |
| | | |
| | | @RequestMapping(value = "logout" , method = RequestMethod.POST) |
| | | @RequestMapping(value = "logout", method = RequestMethod.POST) |
| | | @ResponseBody |
| | | @PermissionLimit(limit = false) |
| | | public ReturnT<String> logout(HttpServletRequest request, HttpServletResponse response) { |
| | |
| | | */ |
| | | @RequestMapping("/{uri}") |
| | | @ResponseBody |
| | | @PermissionLimit(limit=false) |
| | | @PermissionLimit(limit = false) |
| | | public ReturnT<String> api(HttpServletRequest request, @PathVariable("uri") String uri, @RequestBody(required = false) String data) { |
| | | |
| | | // valid |
| | | if (!"POST".equalsIgnoreCase(request.getMethod())) { |
| | | return new ReturnT<String>(ReturnT.FAIL_CODE, "invalid request, HttpMethod not support."); |
| | | } |
| | | if (uri==null || uri.trim().length()==0) { |
| | | if (uri == null || uri.trim().length() == 0) { |
| | | return new ReturnT<String>(ReturnT.FAIL_CODE, "invalid request, uri-mapping empty."); |
| | | } |
| | | if (XxlJobAdminConfig.getAdminConfig().getAccessToken()!=null |
| | | && XxlJobAdminConfig.getAdminConfig().getAccessToken().trim().length()>0 |
| | | && !XxlJobAdminConfig.getAdminConfig().getAccessToken().equals(request.getHeader(XxlJobRemotingUtil.XXL_JOB_ACCESS_TOKEN))) { |
| | | if (XxlJobAdminConfig.getAdminConfig().getAccessToken() != null |
| | | && XxlJobAdminConfig.getAdminConfig().getAccessToken().trim().length() > 0 |
| | | && !XxlJobAdminConfig.getAdminConfig().getAccessToken().equals(request.getHeader(XxlJobRemotingUtil.XXL_JOB_ACCESS_TOKEN))) { |
| | | return new ReturnT<String>(ReturnT.FAIL_CODE, "The access token is wrong."); |
| | | } |
| | | |
| | |
| | | RegistryParam registryParam = GsonTool.fromJson(data, RegistryParam.class); |
| | | return adminBiz.registryRemove(registryParam); |
| | | } else { |
| | | return new ReturnT<String>(ReturnT.FAIL_CODE, "invalid request, uri-mapping("+ uri +") not found."); |
| | | return new ReturnT<String>(ReturnT.FAIL_CODE, "invalid request, uri-mapping(" + uri + ") not found."); |
| | | } |
| | | |
| | | } |
| | |
| | | JobInfoController.validPermission(request, jobInfo.getJobGroup()); |
| | | |
| | | // Glue类型-字典 |
| | | model.addAttribute("GlueTypeEnum" , GlueTypeEnum.values()); |
| | | model.addAttribute("GlueTypeEnum", GlueTypeEnum.values()); |
| | | |
| | | model.addAttribute("jobInfo" , jobInfo); |
| | | model.addAttribute("jobLogGlues" , jobLogGlues); |
| | | model.addAttribute("jobInfo", jobInfo); |
| | | model.addAttribute("jobLogGlues", jobLogGlues); |
| | | return "jobcode/jobcode.index"; |
| | | } |
| | | |
| | |
| | | |
| | | // package result |
| | | Map<String, Object> maps = new HashMap<String, Object>(); |
| | | maps.put("recordsTotal" , list_count); // 总记录数 |
| | | maps.put("recordsFiltered" , list_count); // 过滤后的总记录数 |
| | | maps.put("data" , list); // 分页列表 |
| | | maps.put("recordsTotal", list_count); // 总记录数 |
| | | maps.put("recordsFiltered", list_count); // 过滤后的总记录数 |
| | | maps.put("data", list); // 分页列表 |
| | | return maps; |
| | | } |
| | | |
| | |
| | | public String index(HttpServletRequest request, Model model, @RequestParam(required = false, defaultValue = "-1") int jobGroup) { |
| | | |
| | | // 枚举-字典 |
| | | model.addAttribute("ExecutorRouteStrategyEnum" , ExecutorRouteStrategyEnum.values()); // 路由策略-列表 |
| | | model.addAttribute("GlueTypeEnum" , GlueTypeEnum.values()); // Glue类型-字典 |
| | | model.addAttribute("ExecutorBlockStrategyEnum" , ExecutorBlockStrategyEnum.values()); // 阻塞处理策略-字典 |
| | | model.addAttribute("ScheduleTypeEnum" , ScheduleTypeEnum.values()); // 调度类型 |
| | | model.addAttribute("MisfireStrategyEnum" , MisfireStrategyEnum.values()); // 调度过期策略 |
| | | 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(); |
| | |
| | | throw new XxlJobException(I18nUtil.getString("jobgroup_empty")); |
| | | } |
| | | |
| | | model.addAttribute("JobGroupList" , jobGroupList); |
| | | model.addAttribute("jobGroup" , jobGroup); |
| | | model.addAttribute("JobGroupList", jobGroupList); |
| | | model.addAttribute("jobGroup", jobGroup); |
| | | |
| | | return "jobinfo/jobinfo.index"; |
| | | } |
| | |
| | | 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.complete.XxlJobCompleter; |
| | | import com.xxl.job.admin.core.model.XxlJobGroup; |
| | | import com.xxl.job.admin.core.model.XxlJobInfo; |
| | | import com.xxl.job.admin.core.model.XxlJobLog; |
| | |
| | | throw new XxlJobException(I18nUtil.getString("jobgroup_empty")); |
| | | } |
| | | |
| | | model.addAttribute("JobGroupList" , jobGroupList); |
| | | model.addAttribute("JobGroupList", jobGroupList); |
| | | |
| | | // 任务 |
| | | if (jobId > 0) { |
| | |
| | | throw new RuntimeException(I18nUtil.getString("jobinfo_field_id") + I18nUtil.getString("system_unvalid")); |
| | | } |
| | | |
| | | model.addAttribute("jobInfo" , jobInfo); |
| | | model.addAttribute("jobInfo", jobInfo); |
| | | |
| | | // valid permission |
| | | JobInfoController.validPermission(request, jobInfo.getJobGroup()); |
| | |
| | | |
| | | // package result |
| | | Map<String, Object> maps = new HashMap<String, Object>(); |
| | | maps.put("recordsTotal" , list_count); // 总记录数 |
| | | maps.put("recordsFiltered" , list_count); // 过滤后的总记录数 |
| | | maps.put("data" , list); // 分页列表 |
| | | maps.put("recordsTotal", list_count); // 总记录数 |
| | | maps.put("recordsFiltered", list_count); // 过滤后的总记录数 |
| | | maps.put("data", list); // 分页列表 |
| | | return maps; |
| | | } |
| | | |
| | |
| | | throw new RuntimeException(I18nUtil.getString("joblog_logid_unvalid")); |
| | | } |
| | | |
| | | 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()); |
| | | 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"; |
| | | } |
| | | |
| | |
| | | |
| | | // 执行器列表 |
| | | List<XxlJobGroup> groupList = xxlJobGroupDao.findAll(); |
| | | model.addAttribute("groupList" , groupList); |
| | | model.addAttribute("groupList", groupList); |
| | | |
| | | return "user/user.index"; |
| | | } |
| | |
| | | |
| | | // package result |
| | | Map<String, Object> maps = new HashMap<String, Object>(); |
| | | maps.put("recordsTotal" , list_count); // 总记录数 |
| | | maps.put("recordsFiltered" , list_count); // 过滤后的总记录数 |
| | | maps.put("data" , list); // 分页列表 |
| | | maps.put("recordsTotal", list_count); // 总记录数 |
| | | maps.put("recordsFiltered", list_count); // 过滤后的总记录数 |
| | | maps.put("data", list); // 分页列表 |
| | | return maps; |
| | | } |
| | | |
| | |
| | | for (Cookie ck : request.getCookies()) { |
| | | cookieMap.put(ck.getName(), ck); |
| | | } |
| | | modelAndView.addObject("cookieMap" , cookieMap); |
| | | modelAndView.addObject("cookieMap", cookieMap); |
| | | } |
| | | |
| | | // static method |
| | | if (modelAndView != null) { |
| | | modelAndView.addObject("I18nUtil" , FtlUtil.generateStaticModel(I18nUtil.class.getName())); |
| | | modelAndView.addObject("I18nUtil", FtlUtil.generateStaticModel(I18nUtil.class.getName())); |
| | | } |
| | | |
| | | AsyncHandlerInterceptor.super.postHandle(request, response, handler, modelAndView); |
| | | } |
| | | |
| | | } |
| | |
| | | public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { |
| | | |
| | | if (!(handler instanceof HandlerMethod)) { |
| | | return AsyncHandlerInterceptor.super.preHandle(request, response, handler); |
| | | return true; // proceed with the next interceptor |
| | | } |
| | | |
| | | // if need login |
| | |
| | | XxlJobUser loginUser = loginService.ifLogin(request, response); |
| | | if (loginUser == null) { |
| | | response.setStatus(302); |
| | | response.setHeader("location" , request.getContextPath() + "/toLogin"); |
| | | response.setHeader("location", request.getContextPath() + "/toLogin"); |
| | | return false; |
| | | } |
| | | if (needAdminuser && loginUser.getRole() != 1) { |
| | |
| | | request.setAttribute(LoginService.LOGIN_IDENTITY_KEY, loginUser); |
| | | } |
| | | |
| | | return AsyncHandlerInterceptor.super.preHandle(request, response, handler); |
| | | return true; // proceed with the next interceptor |
| | | } |
| | | |
| | | } |
| | |
| | | package com.xxl.job.admin.controller.resolver; |
| | | |
| | | import com.xxl.job.admin.core.exception.XxlJobException; |
| | | import com.xxl.job.admin.core.util.JacksonUtil; |
| | | import com.xxl.job.core.biz.model.ReturnT; |
| | | import com.xxl.job.admin.core.util.JacksonUtil; |
| | | import org.slf4j.Logger; |
| | | import org.slf4j.LoggerFactory; |
| | | import org.springframework.stereotype.Component; |
| | |
| | | HttpServletResponse response, Object handler, Exception ex) { |
| | | |
| | | if (!(ex instanceof XxlJobException)) { |
| | | logger.error("WebExceptionResolver:{}" , ex); |
| | | logger.error("WebExceptionResolver:{}", ex); |
| | | } |
| | | |
| | | // if json |
| | |
| | | } |
| | | |
| | | // error result |
| | | ReturnT<String> errorResult = new ReturnT<String>(ReturnT.FAIL_CODE, ex.toString().replaceAll("\n" , "<br/>")); |
| | | ReturnT<String> errorResult = new ReturnT<String>(ReturnT.FAIL_CODE, ex.toString().replaceAll("\n", "<br/>")); |
| | | |
| | | // response |
| | | ModelAndView mv = new ModelAndView(); |
| | |
| | | return mv; |
| | | } else { |
| | | |
| | | mv.addObject("exceptionMsg" , errorResult.getMsg()); |
| | | mv.addObject("exceptionMsg", errorResult.getMsg()); |
| | | mv.setViewName("/common/common.exception"); |
| | | return mv; |
| | | } |
| | |
| | | public boolean alarm(XxlJobInfo info, XxlJobLog jobLog) { |
| | | |
| | | boolean result = false; |
| | | if (jobAlarmList!=null && jobAlarmList.size()>0) { |
| | | if (jobAlarmList != null && jobAlarmList.size() > 0) { |
| | | result = true; // success means all-success |
| | | for (JobAlarm alarm: jobAlarmList) { |
| | | for (JobAlarm alarm : jobAlarmList) { |
| | | boolean resultItem = false; |
| | | try { |
| | | resultItem = alarm.doAlarm(info, jobLog); |
| | |
| | | * |
| | | * @param jobLog |
| | | */ |
| | | public boolean doAlarm(XxlJobInfo info, XxlJobLog jobLog){ |
| | | @Override |
| | | public boolean doAlarm(XxlJobInfo info, XxlJobLog jobLog) { |
| | | boolean alarmResult = true; |
| | | |
| | | // send monitor email |
| | | if (info!=null && info.getAlarmEmail()!=null && info.getAlarmEmail().trim().length()>0) { |
| | | if (info != null && info.getAlarmEmail() != null && info.getAlarmEmail().trim().length() > 0) { |
| | | |
| | | // alarmContent |
| | | String alarmContent = "Alarm Job LogId=" + jobLog.getId(); |
| | | if (jobLog.getTriggerCode() != ReturnT.SUCCESS_CODE) { |
| | | alarmContent += "<br>TriggerMsg=<br>" + jobLog.getTriggerMsg(); |
| | | } |
| | | if (jobLog.getHandleCode()>0 && jobLog.getHandleCode() != ReturnT.SUCCESS_CODE) { |
| | | if (jobLog.getHandleCode() > 0 && jobLog.getHandleCode() != ReturnT.SUCCESS_CODE) { |
| | | alarmContent += "<br>HandleCode=" + jobLog.getHandleMsg(); |
| | | } |
| | | |
| | |
| | | String personal = I18nUtil.getString("admin_name_full"); |
| | | String title = I18nUtil.getString("jobconf_monitor"); |
| | | String content = MessageFormat.format(loadEmailJobAlarmTemplate(), |
| | | group!=null?group.getTitle():"null", |
| | | info.getId(), |
| | | info.getJobDesc(), |
| | | alarmContent); |
| | | group != null ? group.getTitle() : "null", |
| | | info.getId(), |
| | | info.getJobDesc(), |
| | | alarmContent); |
| | | |
| | | Set<String> emailSet = new HashSet<String>(Arrays.asList(info.getAlarmEmail().split(","))); |
| | | for (String email: emailSet) { |
| | | for (String email : emailSet) { |
| | | |
| | | // make mail |
| | | try { |
| | |
| | | * |
| | | * @return |
| | | */ |
| | | private static final String loadEmailJobAlarmTemplate(){ |
| | | private static final String loadEmailJobAlarmTemplate() { |
| | | String mailBodyTemplate = "<h5>" + I18nUtil.getString("jobconf_monitor_detail") + ":</span>" + |
| | | "<table border=\"1\" cellpadding=\"3\" style=\"border-collapse:collapse; width:80%;\" >\n" + |
| | | " <thead style=\"font-weight: bold;color: #ffffff;background-color: #ff8c00;\" >" + |
| | | " <tr>\n" + |
| | | " <td width=\"20%\" >"+ I18nUtil.getString("jobinfo_field_jobgroup") +"</td>\n" + |
| | | " <td width=\"10%\" >"+ I18nUtil.getString("jobinfo_field_id") +"</td>\n" + |
| | | " <td width=\"20%\" >"+ I18nUtil.getString("jobinfo_field_jobdesc") +"</td>\n" + |
| | | " <td width=\"10%\" >"+ I18nUtil.getString("jobconf_monitor_alarm_title") +"</td>\n" + |
| | | " <td width=\"40%\" >"+ I18nUtil.getString("jobconf_monitor_alarm_content") +"</td>\n" + |
| | | " </tr>\n" + |
| | | " </thead>\n" + |
| | | " <tbody>\n" + |
| | | " <tr>\n" + |
| | | " <td>{0}</td>\n" + |
| | | " <td>{1}</td>\n" + |
| | | " <td>{2}</td>\n" + |
| | | " <td>"+ I18nUtil.getString("jobconf_monitor_alarm_type") +"</td>\n" + |
| | | " <td>{3}</td>\n" + |
| | | " </tr>\n" + |
| | | " </tbody>\n" + |
| | | "</table>"; |
| | | "<table border=\"1\" cellpadding=\"3\" style=\"border-collapse:collapse; width:80%;\" >\n" + |
| | | " <thead style=\"font-weight: bold;color: #ffffff;background-color: #ff8c00;\" >" + |
| | | " <tr>\n" + |
| | | " <td width=\"20%\" >" + I18nUtil.getString("jobinfo_field_jobgroup") + "</td>\n" + |
| | | " <td width=\"10%\" >" + I18nUtil.getString("jobinfo_field_id") + "</td>\n" + |
| | | " <td width=\"20%\" >" + I18nUtil.getString("jobinfo_field_jobdesc") + "</td>\n" + |
| | | " <td width=\"10%\" >" + I18nUtil.getString("jobconf_monitor_alarm_title") + "</td>\n" + |
| | | " <td width=\"40%\" >" + I18nUtil.getString("jobconf_monitor_alarm_content") + "</td>\n" + |
| | | " </tr>\n" + |
| | | " </thead>\n" + |
| | | " <tbody>\n" + |
| | | " <tr>\n" + |
| | | " <td>{0}</td>\n" + |
| | | " <td>{1}</td>\n" + |
| | | " <td>{2}</td>\n" + |
| | | " <td>" + I18nUtil.getString("jobconf_monitor_alarm_type") + "</td>\n" + |
| | | " <td>{3}</td>\n" + |
| | | " </tr>\n" + |
| | | " </tbody>\n" + |
| | | "</table>"; |
| | | |
| | | return mailBodyTemplate; |
| | | } |
| | |
| | | |
| | | // text最大64kb 避免长度过长 |
| | | if (xxlJobLog.getHandleMsg().length() > 15000) { |
| | | xxlJobLog.setHandleMsg( xxlJobLog.getHandleMsg().substring(0, 15000) ); |
| | | xxlJobLog.setHandleMsg(xxlJobLog.getHandleMsg().substring(0, 15000)); |
| | | } |
| | | |
| | | // fresh handle |
| | |
| | | /** |
| | | * do somethind to finish job |
| | | */ |
| | | private static void finishJob(XxlJobLog xxlJobLog){ |
| | | private static void finishJob(XxlJobLog xxlJobLog) { |
| | | |
| | | // 1、handle success, to trigger child job |
| | | String triggerChildMsg = null; |
| | | if (XxlJobContext.HANDLE_COCE_SUCCESS == xxlJobLog.getHandleCode()) { |
| | | 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>"; |
| | | 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; |
| | | 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); |
| | |
| | | |
| | | // 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()); |
| | | (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]); |
| | | (i + 1), |
| | | childJobIds.length, |
| | | childJobIds[i]); |
| | | } |
| | | } |
| | | |
| | |
| | | } |
| | | |
| | | if (triggerChildMsg != null) { |
| | | xxlJobLog.setHandleMsg( xxlJobLog.getHandleMsg() + triggerChildMsg ); |
| | | xxlJobLog.setHandleMsg(xxlJobLog.getHandleMsg() + triggerChildMsg); |
| | | } |
| | | |
| | | // 2、fix_delay trigger next |
| | |
| | | |
| | | } |
| | | |
| | | private static boolean isNumeric(String str){ |
| | | private static boolean isNumeric(String str) { |
| | | try { |
| | | int result = Integer.valueOf(str); |
| | | return true; |
| | |
| | | public class XxlJobAdminConfig implements InitializingBean, DisposableBean { |
| | | |
| | | private static XxlJobAdminConfig adminConfig = null; |
| | | |
| | | public static XxlJobAdminConfig getAdminConfig() { |
| | | return adminConfig; |
| | | } |
| | |
| | | /* |
| | | * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. |
| | | * |
| | | * Licensed under the Apache License, Version 2.0 (the "License"); you may not |
| | | * use this file except in compliance with the License. You may obtain a copy |
| | | * of the License at |
| | | * |
| | | * http://www.apache.org/licenses/LICENSE-2.0 |
| | | * |
| | | * Unless required by applicable law or agreed to in writing, software |
| | | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| | | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| | | * License for the specific language governing permissions and limitations |
| | | * |
| | | * Licensed under the Apache License, Version 2.0 (the "License"); you may not |
| | | * use this file except in compliance with the License. You may obtain a copy |
| | | * of the License at |
| | | * |
| | | * http://www.apache.org/licenses/LICENSE-2.0 |
| | | * |
| | | * Unless required by applicable law or agreed to in writing, software |
| | | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| | | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| | | * License for the specific language governing permissions and limitations |
| | | * under the License. |
| | | * |
| | | * |
| | | */ |
| | | |
| | | package com.xxl.job.admin.core.cron; |
| | |
| | | import java.util.TreeSet; |
| | | |
| | | /** |
| | | * Provides a parser and evaluator for unix-like cron expressions. Cron |
| | | * Provides a parser and evaluator for unix-like cron expressions. Cron |
| | | * expressions provide the ability to specify complex time combinations such as |
| | | * "At 8:00am every Monday through Friday" or "At 1:30am every |
| | | * last Friday of the month". |
| | | * "At 8:00am every Monday through Friday" or "At 1:30am every |
| | | * last Friday of the month". |
| | | * <P> |
| | | * Cron expressions are comprised of 6 required fields and one optional field |
| | | * separated by white space. The fields respectively are described as follows: |
| | | * |
| | | * |
| | | * <table cellspacing="8"> |
| | | * <tr> |
| | | * <th align="left">Field Name</th> |
| | |
| | | * </tr> |
| | | * </table> |
| | | * <P> |
| | | * The '*' character is used to specify all values. For example, "*" |
| | | * The '*' character is used to specify all values. For example, "*" |
| | | * in the minute field means "every minute". |
| | | * <P> |
| | | * The '?' character is allowed for the day-of-month and day-of-week fields. It |
| | |
| | | * Wednesday, and Friday". |
| | | * <P> |
| | | * The '/' character is used to specify increments. For example "0/15" |
| | | * in the seconds field means "the seconds 0, 15, 30, and 45". And |
| | | * in the seconds field means "the seconds 0, 15, 30, and 45". And |
| | | * "5/15" in the seconds field means "the seconds 5, 20, 35, and |
| | | * 50". Specifying '*' before the '/' is equivalent to specifying 0 is |
| | | * the value to start with. Essentially, for each field in the expression, there |
| | | * is a set of numbers that can be turned on or off. For seconds and minutes, |
| | | * is a set of numbers that can be turned on or off. For seconds and minutes, |
| | | * the numbers range from 0 to 59. For hours 0 to 23, for days of the month 0 to |
| | | * 31, and for months 0 to 11 (JAN to DEC). The "/" character simply helps you turn |
| | | * on every "nth" value in the given set. Thus "7/6" in the |
| | | * month field only turns on month "7", it does NOT mean every 6th |
| | | * month, please note that subtlety. |
| | | * month field only turns on month "7", it does NOT mean every 6th |
| | | * month, please note that subtlety. |
| | | * <P> |
| | | * The 'L' character is allowed for the day-of-month and day-of-week fields. |
| | | * This character is short-hand for "last", but it has different |
| | | * meaning in each of the two fields. For example, the value "L" in |
| | | * the day-of-month field means "the last day of the month" - day 31 |
| | | * for January, day 28 for February on non-leap years. If used in the |
| | | * day-of-week field by itself, it simply means "7" or |
| | | * This character is short-hand for "last", but it has different |
| | | * meaning in each of the two fields. For example, the value "L" in |
| | | * the day-of-month field means "the last day of the month" - day 31 |
| | | * for January, day 28 for February on non-leap years. If used in the |
| | | * day-of-week field by itself, it simply means "7" or |
| | | * "SAT". But if used in the day-of-week field after another value, it |
| | | * means "the last xxx day of the month" - for example "6L" |
| | | * means "the last friday of the month". You can also specify an offset |
| | | * from the last day of the month, such as "L-3" which would mean the third-to-last |
| | | * day of the calendar month. <i>When using the 'L' option, it is important not to |
| | | * means "the last friday of the month". You can also specify an offset |
| | | * from the last day of the month, such as "L-3" which would mean the third-to-last |
| | | * day of the calendar month. <i>When using the 'L' option, it is important not to |
| | | * specify lists, or ranges of values, as you'll get confusing/unexpected results.</i> |
| | | * <P> |
| | | * The 'W' character is allowed for the day-of-month field. This character |
| | | * is used to specify the weekday (Monday-Friday) nearest the given day. As an |
| | | * example, if you were to specify "15W" as the value for the |
| | | * The 'W' character is allowed for the day-of-month field. This character |
| | | * is used to specify the weekday (Monday-Friday) nearest the given day. As an |
| | | * example, if you were to specify "15W" as the value for the |
| | | * day-of-month field, the meaning is: "the nearest weekday to the 15th of |
| | | * the month". So if the 15th is a Saturday, the trigger will fire on |
| | | * the month". So if the 15th is a Saturday, the trigger will fire on |
| | | * Friday the 14th. If the 15th is a Sunday, the trigger will fire on Monday the |
| | | * 16th. If the 15th is a Tuesday, then it will fire on Tuesday the 15th. |
| | | * 16th. If the 15th is a Tuesday, then it will fire on Tuesday the 15th. |
| | | * However if you specify "1W" as the value for day-of-month, and the |
| | | * 1st is a Saturday, the trigger will fire on Monday the 3rd, as it will not |
| | | * 'jump' over the boundary of a month's days. The 'W' character can only be |
| | | * 1st is a Saturday, the trigger will fire on Monday the 3rd, as it will not |
| | | * 'jump' over the boundary of a month's days. The 'W' character can only be |
| | | * specified when the day-of-month is a single day, not a range or list of days. |
| | | * <P> |
| | | * The 'L' and 'W' characters can also be combined for the day-of-month |
| | | * expression to yield 'LW', which translates to "last weekday of the |
| | | * The 'L' and 'W' characters can also be combined for the day-of-month |
| | | * expression to yield 'LW', which translates to "last weekday of the |
| | | * month". |
| | | * <P> |
| | | * The '#' character is allowed for the day-of-week field. This character is |
| | | * used to specify "the nth" XXX day of the month. For example, the |
| | | * value of "6#3" in the day-of-week field means the third Friday of |
| | | * the month (day 6 = Friday and "#3" = the 3rd one in the month). |
| | | * Other examples: "2#1" = the first Monday of the month and |
| | | * used to specify "the nth" XXX day of the month. For example, the |
| | | * value of "6#3" in the day-of-week field means the third Friday of |
| | | * the month (day 6 = Friday and "#3" = the 3rd one in the month). |
| | | * Other examples: "2#1" = the first Monday of the month and |
| | | * "4#5" = the fifth Wednesday of the month. Note that if you specify |
| | | * "#5" and there is not 5 of the given day-of-week in the month, then |
| | | * no firing will occur that month. If the '#' character is used, there can |
| | | * only be one expression in the day-of-week field ("3#1,6#3" is |
| | | * only be one expression in the day-of-week field ("3#1,6#3" is |
| | | * not valid, since there are two expressions). |
| | | * <P> |
| | | * <!--The 'C' character is allowed for the day-of-month and day-of-week fields. |
| | |
| | | * <P> |
| | | * The legal characters and the names of months and days of the week are not |
| | | * case sensitive. |
| | | * |
| | | * |
| | | * <p> |
| | | * <b>NOTES:</b> |
| | | * <ul> |
| | | * <li>Support for specifying both a day-of-week and a day-of-month value is |
| | | * not complete (you'll need to use the '?' character in one of these fields). |
| | | * </li> |
| | | * <li>Overflowing ranges is supported - that is, having a larger number on |
| | | * the left hand side than the right. You might do 22-2 to catch 10 o'clock |
| | | * at night until 2 o'clock in the morning, or you might have NOV-FEB. It is |
| | | * very important to note that overuse of overflowing ranges creates ranges |
| | | * that don't make sense and no effort has been made to determine which |
| | | * interpretation CronExpression chooses. An example would be |
| | | * <li>Overflowing ranges is supported - that is, having a larger number on |
| | | * the left hand side than the right. You might do 22-2 to catch 10 o'clock |
| | | * at night until 2 o'clock in the morning, or you might have NOV-FEB. It is |
| | | * very important to note that overuse of overflowing ranges creates ranges |
| | | * that don't make sense and no effort has been made to determine which |
| | | * interpretation CronExpression chooses. An example would be |
| | | * "0 0 14-6 ? * FRI-MON". </li> |
| | | * </ul> |
| | | * </p> |
| | | * |
| | | * |
| | | * |
| | | * @author Sharada Jambula, James House |
| | | * @author Contributions from Mads Henderson |
| | | * @author Refactoring from CronTrigger to CronExpression by Aaron Craven |
| | | * |
| | | * <p> |
| | | * Borrowed from quartz v2.3.1 |
| | | * |
| | | */ |
| | | public final class CronExpression implements Serializable, Cloneable { |
| | | |
| | | private static final long serialVersionUID = 12423409423L; |
| | | |
| | | |
| | | protected static final int SECOND = 0; |
| | | protected static final int MINUTE = 1; |
| | | protected static final int HOUR = 2; |
| | |
| | | protected static final int NO_SPEC_INT = 98; // '?' |
| | | protected static final Integer ALL_SPEC = ALL_SPEC_INT; |
| | | protected static final Integer NO_SPEC = NO_SPEC_INT; |
| | | |
| | | |
| | | protected static final Map<String, Integer> monthMap = new HashMap<String, Integer>(20); |
| | | protected static final Map<String, Integer> dayMap = new HashMap<String, Integer>(60); |
| | | |
| | | static { |
| | | monthMap.put("JAN", 0); |
| | | monthMap.put("FEB", 1); |
| | |
| | | protected transient boolean nearestWeekday = false; |
| | | protected transient int lastdayOffset = 0; |
| | | protected transient boolean expressionParsed = false; |
| | | |
| | | |
| | | public static final int MAX_YEAR = Calendar.getInstance().get(Calendar.YEAR) + 100; |
| | | |
| | | /** |
| | | * Constructs a new <CODE>CronExpression</CODE> based on the specified |
| | | * Constructs a new <CODE>CronExpression</CODE> based on the specified |
| | | * parameter. |
| | | * |
| | | * |
| | | * @param cronExpression String representation of the cron expression the |
| | | * new object should represent |
| | | * @throws java.text.ParseException |
| | | * if the string expression cannot be parsed into a valid |
| | | * <CODE>CronExpression</CODE> |
| | | * @throws ParseException if the string expression cannot be parsed into a valid |
| | | * <CODE>CronExpression</CODE> |
| | | */ |
| | | public CronExpression(String cronExpression) throws ParseException { |
| | | if (cronExpression == null) { |
| | | throw new IllegalArgumentException("cronExpression cannot be null"); |
| | | } |
| | | |
| | | |
| | | this.cronExpression = cronExpression.toUpperCase(Locale.US); |
| | | |
| | | |
| | | buildExpression(this.cronExpression); |
| | | } |
| | | |
| | | |
| | | /** |
| | | * Constructs a new {@code CronExpression} as a copy of an existing |
| | | * instance. |
| | | * |
| | | * @param expression |
| | | * The existing cron expression to be copied |
| | | * |
| | | * @param expression The existing cron expression to be copied |
| | | */ |
| | | public CronExpression(CronExpression expression) { |
| | | /* |
| | |
| | | * Indicates whether the given date satisfies the cron expression. Note that |
| | | * milliseconds are ignored, so two Dates falling on different milliseconds |
| | | * of the same second will always have the same result here. |
| | | * |
| | | * |
| | | * @param date the date to evaluate |
| | | * @return a boolean indicating whether the given date satisfies the cron |
| | | * expression |
| | | * expression |
| | | */ |
| | | public boolean isSatisfiedBy(Date date) { |
| | | Calendar testDateCal = Calendar.getInstance(getTimeZone()); |
| | | testDateCal.setTime(date); |
| | | testDateCal.set(Calendar.MILLISECOND, 0); |
| | | Date originalDate = testDateCal.getTime(); |
| | | |
| | | |
| | | testDateCal.add(Calendar.SECOND, -1); |
| | | |
| | | |
| | | Date timeAfter = getTimeAfter(testDateCal.getTime()); |
| | | |
| | | return ((timeAfter != null) && (timeAfter.equals(originalDate))); |
| | | } |
| | | |
| | | |
| | | /** |
| | | * Returns the next date/time <I>after</I> the given date/time which |
| | | * satisfies the cron expression. |
| | | * |
| | | * |
| | | * @param date the date/time at which to begin the search for the next valid |
| | | * date/time |
| | | * @return the next valid date/time |
| | |
| | | public Date getNextValidTimeAfter(Date date) { |
| | | return getTimeAfter(date); |
| | | } |
| | | |
| | | |
| | | /** |
| | | * Returns the next date/time <I>after</I> the given date/time which does |
| | | * <I>not</I> satisfy the expression |
| | | * |
| | | * @param date the date/time at which to begin the search for the next |
| | | * |
| | | * @param date the date/time at which to begin the search for the next |
| | | * invalid date/time |
| | | * @return the next valid date/time |
| | | */ |
| | | public Date getNextInvalidTimeAfter(Date date) { |
| | | long difference = 1000; |
| | | |
| | | |
| | | //move back to the nearest second so differences will be accurate |
| | | Calendar adjustCal = Calendar.getInstance(getTimeZone()); |
| | | adjustCal.setTime(date); |
| | | adjustCal.set(Calendar.MILLISECOND, 0); |
| | | Date lastDate = adjustCal.getTime(); |
| | | |
| | | |
| | | Date newDate; |
| | | |
| | | |
| | | //FUTURE_TODO: (QUARTZ-481) IMPROVE THIS! The following is a BAD solution to this problem. Performance will be very bad here, depending on the cron expression. It is, however A solution. |
| | | |
| | | |
| | | //keep getting the next included time until it's farther than one second |
| | | // apart. At that point, lastDate is the last valid fire time. We return |
| | | // the second immediately following it. |
| | | while (difference == 1000) { |
| | | newDate = getTimeAfter(lastDate); |
| | | if(newDate == null) |
| | | if (newDate == null) |
| | | break; |
| | | |
| | | |
| | | difference = newDate.getTime() - lastDate.getTime(); |
| | | |
| | | |
| | | if (difference == 1000) { |
| | | lastDate = newDate; |
| | | } |
| | | } |
| | | |
| | | |
| | | return new Date(lastDate.getTime() + 1000); |
| | | } |
| | | |
| | | |
| | | /** |
| | | * Returns the time zone for which this <code>CronExpression</code> |
| | | * Returns the time zone for which this <code>CronExpression</code> |
| | | * will be resolved. |
| | | */ |
| | | public TimeZone getTimeZone() { |
| | |
| | | } |
| | | |
| | | /** |
| | | * Sets the time zone for which this <code>CronExpression</code> |
| | | * Sets the time zone for which this <code>CronExpression</code> |
| | | * will be resolved. |
| | | */ |
| | | public void setTimeZone(TimeZone timeZone) { |
| | | this.timeZone = timeZone; |
| | | } |
| | | |
| | | |
| | | /** |
| | | * Returns the string representation of the <CODE>CronExpression</CODE> |
| | | * |
| | | * |
| | | * @return a string representation of the <CODE>CronExpression</CODE> |
| | | */ |
| | | @Override |
| | |
| | | } |
| | | |
| | | /** |
| | | * Indicates whether the specified cron expression can be parsed into a |
| | | * Indicates whether the specified cron expression can be parsed into a |
| | | * valid cron expression |
| | | * |
| | | * |
| | | * @param cronExpression the expression to evaluate |
| | | * @return a boolean indicating whether the given expression is a valid cron |
| | | * expression |
| | | * expression |
| | | */ |
| | | public static boolean isValidExpression(String cronExpression) { |
| | | |
| | | |
| | | try { |
| | | new CronExpression(cronExpression); |
| | | } catch (ParseException pe) { |
| | | return false; |
| | | } |
| | | |
| | | |
| | | return true; |
| | | } |
| | | |
| | | public static void validateExpression(String cronExpression) throws ParseException { |
| | | |
| | | |
| | | new CronExpression(cronExpression); |
| | | } |
| | | |
| | | |
| | | |
| | | |
| | | //////////////////////////////////////////////////////////////////////////// |
| | | // |
| | | // Expression Parsing Functions |
| | |
| | | int exprOn = SECOND; |
| | | |
| | | StringTokenizer exprsTok = new StringTokenizer(expression, " \t", |
| | | false); |
| | | false); |
| | | |
| | | while (exprsTok.hasMoreTokens() && exprOn <= YEAR) { |
| | | String expr = exprsTok.nextToken().trim(); |
| | | |
| | | // throw an exception if L is used with other days of the month |
| | | if(exprOn == DAY_OF_MONTH && expr.indexOf('L') != -1 && expr.length() > 1 && expr.contains(",")) { |
| | | if (exprOn == DAY_OF_MONTH && expr.indexOf('L') != -1 && expr.length() > 1 && expr.contains(",")) { |
| | | throw new ParseException("Support for specifying 'L' and 'LW' with other days of the month is not implemented", -1); |
| | | } |
| | | // throw an exception if L is used with other days of the week |
| | | if(exprOn == DAY_OF_WEEK && expr.indexOf('L') != -1 && expr.length() > 1 && expr.contains(",")) { |
| | | if (exprOn == DAY_OF_WEEK && expr.indexOf('L') != -1 && expr.length() > 1 && expr.contains(",")) { |
| | | throw new ParseException("Support for specifying 'L' with other days of the week is not implemented", -1); |
| | | } |
| | | if(exprOn == DAY_OF_WEEK && expr.indexOf('#') != -1 && expr.indexOf('#', expr.indexOf('#') +1) != -1) { |
| | | if (exprOn == DAY_OF_WEEK && expr.indexOf('#') != -1 && expr.indexOf('#', expr.indexOf('#') + 1) != -1) { |
| | | throw new ParseException("Support for specifying multiple \"nth\" days is not implemented.", -1); |
| | | } |
| | | |
| | | |
| | | StringTokenizer vTok = new StringTokenizer(expr, ","); |
| | | while (vTok.hasMoreTokens()) { |
| | | String v = vTok.nextToken(); |
| | |
| | | |
| | | if (exprOn <= DAY_OF_WEEK) { |
| | | throw new ParseException("Unexpected end of expression.", |
| | | expression.length()); |
| | | expression.length()); |
| | | } |
| | | |
| | | if (exprOn <= YEAR) { |
| | |
| | | if (!dayOfMSpec || dayOfWSpec) { |
| | | if (!dayOfWSpec || dayOfMSpec) { |
| | | throw new ParseException( |
| | | "Support for specifying both a day-of-week AND a day-of-month parameter is not implemented.", 0); |
| | | "Support for specifying both a day-of-week AND a day-of-month parameter is not implemented.", 0); |
| | | } |
| | | } |
| | | } catch (ParseException pe) { |
| | | throw pe; |
| | | } catch (Exception e) { |
| | | throw new ParseException("Illegal cron expression format (" |
| | | + e.toString() + ")", 0); |
| | | + e.toString() + ")", 0); |
| | | } |
| | | } |
| | | |
| | |
| | | sval = getDayOfWeekNumber(sub); |
| | | if (sval < 0) { |
| | | throw new ParseException("Invalid Day-of-Week value: '" |
| | | + sub + "'", i); |
| | | + sub + "'", i); |
| | | } |
| | | if (s.length() > i + 3) { |
| | | c = s.charAt(i + 3); |
| | |
| | | eval = getDayOfWeekNumber(sub); |
| | | if (eval < 0) { |
| | | throw new ParseException( |
| | | "Invalid Day-of-Week value: '" + sub |
| | | + "'", i); |
| | | "Invalid Day-of-Week value: '" + sub |
| | | + "'", i); |
| | | } |
| | | } else if (c == '#') { |
| | | try { |
| | |
| | | } |
| | | } catch (Exception e) { |
| | | throw new ParseException( |
| | | "A numeric value between 1 and 5 must follow the '#' option", |
| | | i); |
| | | "A numeric value between 1 and 5 must follow the '#' option", |
| | | i); |
| | | } |
| | | } else if (c == 'L') { |
| | | lastdayOfWeek = true; |
| | |
| | | |
| | | } else { |
| | | throw new ParseException( |
| | | "Illegal characters for this position: '" + sub + "'", |
| | | i); |
| | | "Illegal characters for this position: '" + sub + "'", |
| | | i); |
| | | } |
| | | if (eval != -1) { |
| | | incr = 1; |
| | |
| | | |
| | | if (c == '?') { |
| | | i++; |
| | | if ((i + 1) < s.length() |
| | | && (s.charAt(i) != ' ' && s.charAt(i + 1) != '\t')) { |
| | | if ((i + 1) < s.length() |
| | | && (s.charAt(i) != ' ' && s.charAt(i + 1) != '\t')) { |
| | | throw new ParseException("Illegal character after '?': " |
| | | + s.charAt(i), i); |
| | | + s.charAt(i), i); |
| | | } |
| | | if (type != DAY_OF_WEEK && type != DAY_OF_MONTH) { |
| | | throw new ParseException( |
| | | "'?' can only be specified for Day-of-Month or Day-of-Week.", |
| | | i); |
| | | "'?' can only be specified for Day-of-Month or Day-of-Week.", |
| | | i); |
| | | } |
| | | if (type == DAY_OF_WEEK && !lastdayOfMonth) { |
| | | int val = daysOfMonth.last(); |
| | | if (val == NO_SPEC_INT) { |
| | | throw new ParseException( |
| | | "'?' can only be specified for Day-of-Month -OR- Day-of-Week.", |
| | | i); |
| | | "'?' can only be specified for Day-of-Month -OR- Day-of-Week.", |
| | | i); |
| | | } |
| | | } |
| | | |
| | |
| | | addToSet(ALL_SPEC_INT, -1, incr, type); |
| | | return i + 1; |
| | | } else if (c == '/' |
| | | && ((i + 1) >= s.length() || s.charAt(i + 1) == ' ' || s |
| | | .charAt(i + 1) == '\t')) { |
| | | && ((i + 1) >= s.length() || s.charAt(i + 1) == ' ' || s |
| | | .charAt(i + 1) == '\t')) { |
| | | throw new ParseException("'/' must be followed by an integer.", i); |
| | | } else if (c == '*') { |
| | | i++; |
| | |
| | | if (type == DAY_OF_WEEK) { |
| | | addToSet(7, 7, 0, type); |
| | | } |
| | | if(type == DAY_OF_MONTH && s.length() > i) { |
| | | if (type == DAY_OF_MONTH && s.length() > i) { |
| | | c = s.charAt(i); |
| | | if(c == '-') { |
| | | ValueSet vs = getValue(0, s, i+1); |
| | | if (c == '-') { |
| | | ValueSet vs = getValue(0, s, i + 1); |
| | | lastdayOffset = vs.value; |
| | | if(lastdayOffset > 30) |
| | | throw new ParseException("Offset from last day must be <= 30", i+1); |
| | | if (lastdayOffset > 30) |
| | | throw new ParseException("Offset from last day must be <= 30", i + 1); |
| | | i = vs.pos; |
| | | } |
| | | if(s.length() > i) { |
| | | } |
| | | if (s.length() > i) { |
| | | c = s.charAt(i); |
| | | if(c == 'W') { |
| | | if (c == 'W') { |
| | | nearestWeekday = true; |
| | | i++; |
| | | } |
| | |
| | | |
| | | protected int checkNext(int pos, String s, int val, int type) |
| | | throws ParseException { |
| | | |
| | | |
| | | int end = -1; |
| | | int i = pos; |
| | | |
| | |
| | | |
| | | 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 { |
| | |
| | | i++; |
| | | return i; |
| | | } |
| | | |
| | | |
| | | if (c == 'W') { |
| | | if (type == DAY_OF_MONTH) { |
| | | nearestWeekday = true; |
| | | } else { |
| | | throw new ParseException("'W' option is not valid here. (pos=" + i + ")", i); |
| | | } |
| | | if(val > 31) |
| | | throw new ParseException("The 'W' option does not make sense with values larger than 31 (max number of days in a month)", i); |
| | | 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++; |
| | |
| | | } |
| | | } catch (Exception e) { |
| | | throw new ParseException( |
| | | "A numeric value between 1 and 5 must follow the '#' option", |
| | | i); |
| | | "A numeric value between 1 and 5 must follow the '#' option", |
| | | i); |
| | | } |
| | | |
| | | TreeSet<Integer> set = getSet(type); |
| | |
| | | public String getCronExpression() { |
| | | return cronExpression; |
| | | } |
| | | |
| | | |
| | | public String getExpressionSummary() { |
| | | StringBuilder buf = new StringBuilder(); |
| | | |
| | |
| | | |
| | | protected void addToSet(int val, int end, int incr, int type) |
| | | throws ParseException { |
| | | |
| | | |
| | | TreeSet<Integer> set = getSet(type); |
| | | |
| | | if (type == SECOND || type == MINUTE) { |
| | | if ((val < 0 || val > 59 || end > 59) && (val != ALL_SPEC_INT)) { |
| | | throw new ParseException( |
| | | "Minute and Second values must be between 0 and 59", |
| | | -1); |
| | | "Minute and Second values must be between 0 and 59", |
| | | -1); |
| | | } |
| | | } else if (type == HOUR) { |
| | | if ((val < 0 || val > 23 || end > 23) && (val != ALL_SPEC_INT)) { |
| | | throw new ParseException( |
| | | "Hour values must be between 0 and 23", -1); |
| | | "Hour values must be between 0 and 23", -1); |
| | | } |
| | | } else if (type == DAY_OF_MONTH) { |
| | | if ((val < 1 || val > 31 || end > 31) && (val != ALL_SPEC_INT) |
| | | && (val != NO_SPEC_INT)) { |
| | | if ((val < 1 || val > 31 || end > 31) && (val != ALL_SPEC_INT) |
| | | && (val != NO_SPEC_INT)) { |
| | | throw new ParseException( |
| | | "Day of month values must be between 1 and 31", -1); |
| | | "Day of month values must be between 1 and 31", -1); |
| | | } |
| | | } else if (type == MONTH) { |
| | | if ((val < 1 || val > 12 || end > 12) && (val != ALL_SPEC_INT)) { |
| | | throw new ParseException( |
| | | "Month values must be between 1 and 12", -1); |
| | | "Month values must be between 1 and 12", -1); |
| | | } |
| | | } else if (type == DAY_OF_WEEK) { |
| | | if ((val == 0 || val > 7 || end > 7) && (val != ALL_SPEC_INT) |
| | | && (val != NO_SPEC_INT)) { |
| | | && (val != NO_SPEC_INT)) { |
| | | throw new ParseException( |
| | | "Day-of-Week values must be between 1 and 7", -1); |
| | | "Day-of-Week values must be between 1 and 7", -1); |
| | | } |
| | | } |
| | | |
| | |
| | | } else { |
| | | set.add(NO_SPEC); |
| | | } |
| | | |
| | | |
| | | return; |
| | | } |
| | | |
| | |
| | | } |
| | | } |
| | | |
| | | // if the end of the range is before the start, then we need to overflow into |
| | | // the next day, month etc. This is done by adding the maximum amount for that |
| | | // if the end of the range is before the start, then we need to overflow into |
| | | // the next day, month etc. This is done by adding the maximum amount for that |
| | | // type, and using modulus max to determine the value being added. |
| | | int max = -1; |
| | | if (stopAt < startAt) { |
| | | switch (type) { |
| | | case SECOND : max = 60; break; |
| | | case MINUTE : max = 60; break; |
| | | case HOUR : max = 24; break; |
| | | case MONTH : max = 12; break; |
| | | case DAY_OF_WEEK : max = 7; break; |
| | | case DAY_OF_MONTH : max = 31; break; |
| | | case YEAR : throw new IllegalArgumentException("Start year must be less than stop year"); |
| | | default : throw new IllegalArgumentException("Unexpected type encountered"); |
| | | case SECOND: |
| | | max = 60; |
| | | break; |
| | | case MINUTE: |
| | | max = 60; |
| | | break; |
| | | case HOUR: |
| | | max = 24; |
| | | break; |
| | | case MONTH: |
| | | max = 12; |
| | | break; |
| | | case DAY_OF_WEEK: |
| | | max = 7; |
| | | break; |
| | | case DAY_OF_MONTH: |
| | | max = 31; |
| | | break; |
| | | case YEAR: |
| | | throw new IllegalArgumentException("Start year must be less than stop year"); |
| | | default: |
| | | throw new IllegalArgumentException("Unexpected type encountered"); |
| | | } |
| | | stopAt += max; |
| | | } |
| | |
| | | int i2 = i % max; |
| | | |
| | | // 1-indexed ranges should not include 0, and should include their max |
| | | if (i2 == 0 && (type == MONTH || type == DAY_OF_WEEK || type == DAY_OF_MONTH) ) { |
| | | if (i2 == 0 && (type == MONTH || type == DAY_OF_WEEK || type == DAY_OF_MONTH)) { |
| | | i2 = max; |
| | | } |
| | | |
| | |
| | | c = s.charAt(i); |
| | | } |
| | | ValueSet val = new ValueSet(); |
| | | |
| | | |
| | | val.pos = (i < s.length()) ? i : i + 1; |
| | | val.value = Integer.parseInt(s1.toString()); |
| | | return val; |
| | |
| | | public Date getTimeAfter(Date afterTime) { |
| | | |
| | | // Computation is based on Gregorian year only. |
| | | Calendar cl = new java.util.GregorianCalendar(getTimeZone()); |
| | | Calendar cl = new java.util.GregorianCalendar(getTimeZone()); |
| | | |
| | | // move ahead one second, since we're computing the time *after* the |
| | | // given time |
| | |
| | | while (!gotOne) { |
| | | |
| | | //if (endTime != null && cl.getTime().after(endTime)) return null; |
| | | if(cl.get(Calendar.YEAR) > 2999) { // prevent endless loop... |
| | | if (cl.get(Calendar.YEAR) > 2999) { // prevent endless loop... |
| | | return null; |
| | | } |
| | | |
| | |
| | | // 1-based |
| | | t = -1; |
| | | int tmon = mon; |
| | | |
| | | |
| | | // get day................................................... |
| | | boolean dayOfMSpec = !daysOfMonth.contains(NO_SPEC); |
| | | boolean dayOfWSpec = !daysOfWeek.contains(NO_SPEC); |
| | | if (dayOfMSpec && !dayOfWSpec) { // get day by day of month rule |
| | | st = daysOfMonth.tailSet(day); |
| | | if (lastdayOfMonth) { |
| | | if(!nearestWeekday) { |
| | | if (!nearestWeekday) { |
| | | t = day; |
| | | day = getLastDayOfMonth(mon, cl.get(Calendar.YEAR)); |
| | | day -= lastdayOffset; |
| | | if(t > day) { |
| | | if (t > day) { |
| | | mon++; |
| | | if(mon > 12) { |
| | | if (mon > 12) { |
| | | mon = 1; |
| | | tmon = 3333; // ensure test of mon != tmon further below fails |
| | | cl.add(Calendar.YEAR, 1); |
| | |
| | | t = day; |
| | | 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); |
| | | tcal.set(Calendar.DAY_OF_MONTH, day); |
| | | tcal.set(Calendar.MONTH, mon - 1); |
| | | tcal.set(Calendar.YEAR, cl.get(Calendar.YEAR)); |
| | | |
| | | |
| | | int ldom = getLastDayOfMonth(mon, cl.get(Calendar.YEAR)); |
| | | int dow = tcal.get(Calendar.DAY_OF_WEEK); |
| | | |
| | | if(dow == Calendar.SATURDAY && day == 1) { |
| | | if (dow == Calendar.SATURDAY && day == 1) { |
| | | day += 2; |
| | | } else if(dow == Calendar.SATURDAY) { |
| | | } else if (dow == Calendar.SATURDAY) { |
| | | day -= 1; |
| | | } else if(dow == Calendar.SUNDAY && day == ldom) { |
| | | } else if (dow == Calendar.SUNDAY && day == ldom) { |
| | | day -= 2; |
| | | } else if(dow == Calendar.SUNDAY) { |
| | | } else if (dow == Calendar.SUNDAY) { |
| | | day += 1; |
| | | } |
| | | |
| | | |
| | | tcal.set(Calendar.SECOND, sec); |
| | | tcal.set(Calendar.MINUTE, min); |
| | | tcal.set(Calendar.HOUR_OF_DAY, hr); |
| | | tcal.set(Calendar.DAY_OF_MONTH, day); |
| | | tcal.set(Calendar.MONTH, mon - 1); |
| | | Date nTime = tcal.getTime(); |
| | | if(nTime.before(afterTime)) { |
| | | if (nTime.before(afterTime)) { |
| | | day = 1; |
| | | mon++; |
| | | } |
| | | } |
| | | } else if(nearestWeekday) { |
| | | } else if (nearestWeekday) { |
| | | 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); |
| | | tcal.set(Calendar.DAY_OF_MONTH, day); |
| | | tcal.set(Calendar.MONTH, mon - 1); |
| | | tcal.set(Calendar.YEAR, cl.get(Calendar.YEAR)); |
| | | |
| | | |
| | | int ldom = getLastDayOfMonth(mon, cl.get(Calendar.YEAR)); |
| | | int dow = tcal.get(Calendar.DAY_OF_WEEK); |
| | | |
| | | if(dow == Calendar.SATURDAY && day == 1) { |
| | | if (dow == Calendar.SATURDAY && day == 1) { |
| | | day += 2; |
| | | } else if(dow == Calendar.SATURDAY) { |
| | | } else if (dow == Calendar.SATURDAY) { |
| | | day -= 1; |
| | | } else if(dow == Calendar.SUNDAY && day == ldom) { |
| | | } else if (dow == Calendar.SUNDAY && day == ldom) { |
| | | day -= 2; |
| | | } else if(dow == Calendar.SUNDAY) { |
| | | } else if (dow == Calendar.SUNDAY) { |
| | | day += 1; |
| | | } |
| | | |
| | | |
| | | |
| | | |
| | | tcal.set(Calendar.SECOND, sec); |
| | | tcal.set(Calendar.MINUTE, min); |
| | | tcal.set(Calendar.HOUR_OF_DAY, hr); |
| | | tcal.set(Calendar.DAY_OF_MONTH, day); |
| | | tcal.set(Calendar.MONTH, mon - 1); |
| | | Date nTime = tcal.getTime(); |
| | | if(nTime.before(afterTime)) { |
| | | if (nTime.before(afterTime)) { |
| | | day = daysOfMonth.first(); |
| | | mon++; |
| | | } |
| | |
| | | day = daysOfMonth.first(); |
| | | mon++; |
| | | } |
| | | |
| | | |
| | | if (day != t || mon != tmon) { |
| | | cl.set(Calendar.SECOND, 0); |
| | | cl.set(Calendar.MINUTE, 0); |
| | |
| | | daysToAdd = (nthdayOfWeek - weekOfMonth) * 7; |
| | | day += daysToAdd; |
| | | if (daysToAdd < 0 |
| | | || day > getLastDayOfMonth(mon, cl |
| | | .get(Calendar.YEAR))) { |
| | | || day > getLastDayOfMonth(mon, cl |
| | | .get(Calendar.YEAR))) { |
| | | cl.set(Calendar.SECOND, 0); |
| | | cl.set(Calendar.MINUTE, 0); |
| | | cl.set(Calendar.HOUR_OF_DAY, 0); |
| | |
| | | } |
| | | } else { // dayOfWSpec && !dayOfMSpec |
| | | throw new UnsupportedOperationException( |
| | | "Support for specifying both a day-of-week AND a day-of-month parameter is not implemented."); |
| | | "Support for specifying both a day-of-week AND a day-of-month parameter is not implemented."); |
| | | } |
| | | cl.set(Calendar.DAY_OF_MONTH, day); |
| | | |
| | |
| | | /** |
| | | * Advance the calendar to the particular hour paying particular attention |
| | | * to daylight saving problems. |
| | | * |
| | | * @param cal the calendar to operate on |
| | | * |
| | | * @param cal the calendar to operate on |
| | | * @param hour the hour to set |
| | | */ |
| | | protected void setCalendarHour(Calendar cal, int hour) { |
| | | cal.set(java.util.Calendar.HOUR_OF_DAY, hour); |
| | | if (cal.get(java.util.Calendar.HOUR_OF_DAY) != hour && hour != 24) { |
| | | cal.set(java.util.Calendar.HOUR_OF_DAY, hour + 1); |
| | | cal.set(Calendar.HOUR_OF_DAY, hour); |
| | | if (cal.get(Calendar.HOUR_OF_DAY) != hour && hour != 24) { |
| | | cal.set(Calendar.HOUR_OF_DAY, hour + 1); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * NOT YET IMPLEMENTED: Returns the time before the given time |
| | | * that the <code>CronExpression</code> matches. |
| | | */ |
| | | public Date getTimeBefore(Date endTime) { |
| | | */ |
| | | public Date getTimeBefore(Date endTime) { |
| | | // FUTURE_TODO: implement QUARTZ-423 |
| | | return null; |
| | | } |
| | | |
| | | /** |
| | | * NOT YET IMPLEMENTED: Returns the final time that the |
| | | * NOT YET IMPLEMENTED: Returns the final time that the |
| | | * <code>CronExpression</code> will match. |
| | | */ |
| | | public Date getFinalFireTime() { |
| | | // FUTURE_TODO: implement QUARTZ-423 |
| | | return null; |
| | | } |
| | | |
| | | |
| | | protected boolean isLeapYear(int year) { |
| | | return ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)); |
| | | } |
| | |
| | | return 31; |
| | | default: |
| | | throw new IllegalArgumentException("Illegal month number: " |
| | | + monthNum); |
| | | + monthNum); |
| | | } |
| | | } |
| | | |
| | | |
| | | |
| | | private void readObject(java.io.ObjectInputStream stream) |
| | | throws java.io.IOException, ClassNotFoundException { |
| | | |
| | | |
| | | stream.defaultReadObject(); |
| | | try { |
| | | buildExpression(cronExpression); |
| | | } catch (Exception ignore) { |
| | | } // never happens |
| | | } |
| | | |
| | | } |
| | | |
| | | @Override |
| | | @Deprecated |
| | | public Object clone() { |
| | |
| | | |
| | | public XxlJobException() { |
| | | } |
| | | |
| | | public XxlJobException(String message) { |
| | | super(message); |
| | | } |
| | |
| | | |
| | | // registry list |
| | | private List<String> registryList; // 执行器地址列表(系统注册) |
| | | |
| | | public List<String> getRegistryList() { |
| | | if (addressList!=null && addressList.trim().length()>0) { |
| | | if (addressList != null && addressList.trim().length() > 0) { |
| | | registryList = new ArrayList<String>(Arrays.asList(addressList.split(","))); |
| | | } |
| | | return registryList; |
| | |
| | | private int executorTimeout; // 任务执行超时时间,单位秒 |
| | | private int executorFailRetryCount; // 失败重试次数 |
| | | |
| | | private String glueType; // GLUE类型 #com.xxl.job.core.glue.GlueTypeEnum |
| | | private String glueType; // GLUE类型 #com.xxl.job.core.glue.GlueTypeEnum |
| | | private String glueSource; // GLUE源代码 |
| | | private String glueRemark; // GLUE备注 |
| | | private Date glueUpdatetime; // GLUE更新时间 |
| | |
| | | //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; |
| | |
| | | // |
| | | // } |
| | | // |
| | | //} |
| | | //} |
| | |
| | | //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; |
| | |
| | | // return jobList; |
| | | // }*/ |
| | | // |
| | | //} |
| | | //} |
| | |
| | | //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; |
| | |
| | | public String getTitle() { |
| | | return title; |
| | | } |
| | | |
| | | public ExecutorRouter getRouter() { |
| | | return router; |
| | | } |
| | | |
| | | public static ExecutorRouteStrategyEnum match(String name, ExecutorRouteStrategyEnum defaultItem){ |
| | | public static ExecutorRouteStrategyEnum match(String name, ExecutorRouteStrategyEnum defaultItem) { |
| | | if (name != null) { |
| | | for (ExecutorRouteStrategyEnum item: ExecutorRouteStrategyEnum.values()) { |
| | | for (ExecutorRouteStrategyEnum item : ExecutorRouteStrategyEnum.values()) { |
| | | if (item.name().equals(name)) { |
| | | return item; |
| | | } |
| | |
| | | * route address |
| | | * |
| | | * @param addressList |
| | | * @return ReturnT.content=address |
| | | * @return ReturnT.content=address |
| | | */ |
| | | public abstract ReturnT<String> route(TriggerParam triggerParam, List<String> addressList); |
| | | |
| | |
| | | idleBeatResult = executorBiz.idleBeat(new IdleBeatParam(triggerParam.getJobId())); |
| | | } catch (Exception e) { |
| | | logger.error(e.getMessage(), e); |
| | | idleBeatResult = new ReturnT<String>(ReturnT.FAIL_CODE, ""+e ); |
| | | idleBeatResult = new ReturnT<String>(ReturnT.FAIL_CODE, "" + e); |
| | | } |
| | | idleBeatResultSB.append( (idleBeatResultSB.length()>0)?"<br><br>":"") |
| | | .append(I18nUtil.getString("jobconf_idleBeat") + ":") |
| | | .append("<br>address:").append(address) |
| | | .append("<br>code:").append(idleBeatResult.getCode()) |
| | | .append("<br>msg:").append(idleBeatResult.getMsg()); |
| | | idleBeatResultSB.append((idleBeatResultSB.length() > 0) ? "<br><br>" : "") |
| | | .append(I18nUtil.getString("jobconf_idleBeat") + ":") |
| | | .append("<br>address:").append(address) |
| | | .append("<br>code:").append(idleBeatResult.getCode()) |
| | | .append("<br>msg:").append(idleBeatResult.getMsg()); |
| | | |
| | | // beat success |
| | | if (idleBeatResult.getCode() == ReturnT.SUCCESS_CODE) { |
| | |
| | | |
| | | /** |
| | | * 分组下机器地址相同,不同JOB均匀散列在不同机器上,保证分组下机器分配JOB平均;且每个JOB固定调度其中一台机器; |
| | | * a、virtual node:解决不均衡问题 |
| | | * b、hash method replace hashCode:String的hashCode可能重复,需要进一步扩大hashCode的取值范围 |
| | | * a、virtual node:解决不均衡问题 |
| | | * b、hash method replace hashCode:String的hashCode可能重复,需要进一步扩大hashCode的取值范围 |
| | | * Created by xuxueli on 17/3/10. |
| | | */ |
| | | public class ExecutorRouteConsistentHash extends ExecutorRouter { |
| | |
| | | |
| | | /** |
| | | * get hash code on 2^32 ring (md5散列的方式计算hash值) |
| | | * |
| | | * @param key |
| | | * @return |
| | | */ |
| | |
| | | |
| | | // hash code, Truncate to 32-bits |
| | | long hashCode = ((long) (digest[3] & 0xFF) << 24) |
| | | | ((long) (digest[2] & 0xFF) << 16) |
| | | | ((long) (digest[1] & 0xFF) << 8) |
| | | | (digest[0] & 0xFF); |
| | | | ((long) (digest[2] & 0xFF) << 16) |
| | | | ((long) (digest[1] & 0xFF) << 8) |
| | | | (digest[0] & 0xFF); |
| | | |
| | | long truncateHashCode = hashCode & 0xffffffffL; |
| | | return truncateHashCode; |
| | |
| | | // ------A1------A2-------A3------ |
| | | // -----------J1------------------ |
| | | TreeMap<Long, String> addressRing = new TreeMap<Long, String>(); |
| | | for (String address: addressList) { |
| | | for (String address : addressList) { |
| | | for (int i = 0; i < VIRTUAL_NODE_NUM; i++) { |
| | | long addressHash = hash("SHARD-" + address + "-NODE-" + i); |
| | | addressRing.put(addressHash, address); |
| | |
| | | beatResult = executorBiz.beat(); |
| | | } catch (Exception e) { |
| | | logger.error(e.getMessage(), e); |
| | | beatResult = new ReturnT<String>(ReturnT.FAIL_CODE, ""+e ); |
| | | beatResult = new ReturnT<String>(ReturnT.FAIL_CODE, "" + e); |
| | | } |
| | | beatResultSB.append( (beatResultSB.length()>0)?"<br><br>":"") |
| | | .append(I18nUtil.getString("jobconf_beat") + ":") |
| | | .append("<br>address:").append(address) |
| | | .append("<br>code:").append(beatResult.getCode()) |
| | | .append("<br>msg:").append(beatResult.getMsg()); |
| | | beatResultSB.append((beatResultSB.length() > 0) ? "<br><br>" : "") |
| | | .append(I18nUtil.getString("jobconf_beat") + ":") |
| | | .append("<br>address:").append(address) |
| | | .append("<br>code:").append(beatResult.getCode()) |
| | | .append("<br>msg:").append(beatResult.getMsg()); |
| | | |
| | | // beat success |
| | | if (beatResult.getCode() == ReturnT.SUCCESS_CODE) { |
| | |
| | | public class ExecutorRouteFirst extends ExecutorRouter { |
| | | |
| | | @Override |
| | | public ReturnT<String> route(TriggerParam triggerParam, List<String> addressList){ |
| | | public ReturnT<String> route(TriggerParam triggerParam, List<String> addressList) { |
| | | return new ReturnT<String>(addressList.get(0)); |
| | | } |
| | | |
| | |
| | | |
| | | /** |
| | | * 单个JOB对应的每个执行器,使用频率最低的优先被选举 |
| | | * a(*)、LFU(Least Frequently Used):最不经常使用,频率/次数 |
| | | * b、LRU(Least Recently Used):最近最久未使用,时间 |
| | | * |
| | | * a(*)、LFU(Least Frequently Used):最不经常使用,频率/次数 |
| | | * b、LRU(Least Recently Used):最近最久未使用,时间 |
| | | * <p> |
| | | * Created by xuxueli on 17/3/10. |
| | | */ |
| | | public class ExecutorRouteLFU extends ExecutorRouter { |
| | |
| | | // cache clear |
| | | if (System.currentTimeMillis() > CACHE_VALID_TIME) { |
| | | jobLfuMap.clear(); |
| | | CACHE_VALID_TIME = System.currentTimeMillis() + 1000*60*60*24; |
| | | CACHE_VALID_TIME = System.currentTimeMillis() + 1000 * 60 * 60 * 24; |
| | | } |
| | | |
| | | // lfu item init |
| | |
| | | } |
| | | |
| | | // put new |
| | | for (String address: addressList) { |
| | | if (!lfuItemMap.containsKey(address) || lfuItemMap.get(address) >1000000 ) { |
| | | for (String address : addressList) { |
| | | if (!lfuItemMap.containsKey(address) || lfuItemMap.get(address) > 1000000) { |
| | | lfuItemMap.put(address, new Random().nextInt(addressList.size())); // 初始化时主动Random一次,缓解首次压力 |
| | | } |
| | | } |
| | | // remove old |
| | | List<String> delKeys = new ArrayList<>(); |
| | | for (String existKey: lfuItemMap.keySet()) { |
| | | for (String existKey : lfuItemMap.keySet()) { |
| | | if (!addressList.contains(existKey)) { |
| | | delKeys.add(existKey); |
| | | } |
| | | } |
| | | if (delKeys.size() > 0) { |
| | | for (String delKey: delKeys) { |
| | | for (String delKey : delKeys) { |
| | | lfuItemMap.remove(delKey); |
| | | } |
| | | } |
| | |
| | | |
| | | /** |
| | | * 单个JOB对应的每个执行器,最久为使用的优先被选举 |
| | | * a、LFU(Least Frequently Used):最不经常使用,频率/次数 |
| | | * b(*)、LRU(Least Recently Used):最近最久未使用,时间 |
| | | * |
| | | * a、LFU(Least Frequently Used):最不经常使用,频率/次数 |
| | | * b(*)、LRU(Least Recently Used):最近最久未使用,时间 |
| | | * <p> |
| | | * Created by xuxueli on 17/3/10. |
| | | */ |
| | | public class ExecutorRouteLRU extends ExecutorRouter { |
| | |
| | | // cache clear |
| | | if (System.currentTimeMillis() > CACHE_VALID_TIME) { |
| | | jobLRUMap.clear(); |
| | | CACHE_VALID_TIME = System.currentTimeMillis() + 1000*60*60*24; |
| | | CACHE_VALID_TIME = System.currentTimeMillis() + 1000 * 60 * 60 * 24; |
| | | } |
| | | |
| | | // init lru |
| | |
| | | } |
| | | |
| | | // put new |
| | | for (String address: addressList) { |
| | | for (String address : addressList) { |
| | | if (!lruItem.containsKey(address)) { |
| | | lruItem.put(address, address); |
| | | } |
| | | } |
| | | // remove old |
| | | List<String> delKeys = new ArrayList<>(); |
| | | for (String existKey: lruItem.keySet()) { |
| | | for (String existKey : lruItem.keySet()) { |
| | | if (!addressList.contains(existKey)) { |
| | | delKeys.add(existKey); |
| | | } |
| | | } |
| | | if (delKeys.size() > 0) { |
| | | for (String delKey: delKeys) { |
| | | for (String delKey : delKeys) { |
| | | lruItem.remove(delKey); |
| | | } |
| | | } |
| | |
| | | |
| | | @Override |
| | | public ReturnT<String> route(TriggerParam triggerParam, List<String> addressList) { |
| | | return new ReturnT<String>(addressList.get(addressList.size()-1)); |
| | | return new ReturnT<String>(addressList.get(addressList.size() - 1)); |
| | | } |
| | | |
| | | } |
| | |
| | | // cache clear |
| | | if (System.currentTimeMillis() > CACHE_VALID_TIME) { |
| | | routeCountEachJob.clear(); |
| | | CACHE_VALID_TIME = System.currentTimeMillis() + 1000*60*60*24; |
| | | CACHE_VALID_TIME = System.currentTimeMillis() + 1000 * 60 * 60 * 24; |
| | | } |
| | | |
| | | AtomicInteger count = routeCountEachJob.get(jobId); |
| | |
| | | |
| | | @Override |
| | | public ReturnT<String> route(TriggerParam triggerParam, List<String> addressList) { |
| | | String address = addressList.get(count(triggerParam.getJobId())%addressList.size()); |
| | | String address = addressList.get(count(triggerParam.getJobId()) % addressList.size()); |
| | | return new ReturnT<String>(address); |
| | | } |
| | | |
| | |
| | | return title; |
| | | } |
| | | |
| | | public static MisfireStrategyEnum match(String name, MisfireStrategyEnum defaultItem){ |
| | | for (MisfireStrategyEnum item: MisfireStrategyEnum.values()) { |
| | | public static MisfireStrategyEnum match(String name, MisfireStrategyEnum defaultItem) { |
| | | for (MisfireStrategyEnum item : MisfireStrategyEnum.values()) { |
| | | if (item.name().equals(name)) { |
| | | return item; |
| | | } |
| | |
| | | return title; |
| | | } |
| | | |
| | | public static ScheduleTypeEnum match(String name, ScheduleTypeEnum defaultItem){ |
| | | for (ScheduleTypeEnum item: ScheduleTypeEnum.values()) { |
| | | public static ScheduleTypeEnum match(String name, ScheduleTypeEnum defaultItem) { |
| | | for (ScheduleTypeEnum item : ScheduleTypeEnum.values()) { |
| | | if (item.name().equals(name)) { |
| | | return item; |
| | | } |
| | |
| | | * @author xuxueli 2018-10-28 00:18:17 |
| | | */ |
| | | |
| | | public class XxlJobScheduler { |
| | | public class XxlJobScheduler { |
| | | private static final Logger logger = LoggerFactory.getLogger(XxlJobScheduler.class); |
| | | |
| | | |
| | |
| | | logger.info(">>>>>>>>> init xxl-job admin success."); |
| | | } |
| | | |
| | | |
| | | |
| | | public void destroy() throws Exception { |
| | | |
| | | // stop-schedule |
| | |
| | | |
| | | // ---------------------- I18n ---------------------- |
| | | |
| | | private void initI18n(){ |
| | | for (ExecutorBlockStrategyEnum item:ExecutorBlockStrategyEnum.values()) { |
| | | private void initI18n() { |
| | | for (ExecutorBlockStrategyEnum item : ExecutorBlockStrategyEnum.values()) { |
| | | item.setTitle(I18nUtil.getString("jobconf_block_".concat(item.name()))); |
| | | } |
| | | } |
| | | |
| | | // ---------------------- executor-client ---------------------- |
| | | private static ConcurrentMap<String, ExecutorBiz> executorBizRepository = new ConcurrentHashMap<String, ExecutorBiz>(); |
| | | |
| | | public static ExecutorBiz getExecutorBiz(String address) throws Exception { |
| | | // valid |
| | | if (address==null || address.trim().length()==0) { |
| | | if (address == null || address.trim().length() == 0) { |
| | | return null; |
| | | } |
| | | |
| | |
| | | } |
| | | } catch (Exception e) { |
| | | if (!toStop) { |
| | | logger.error(">>>>>>>>>>> xxl-job, job fail monitor thread error:{}" , e); |
| | | logger.error(">>>>>>>>>>> xxl-job, job fail monitor thread error:{}", e); |
| | | } |
| | | } |
| | | |
| | |
| | | public void run() { |
| | | for (HandleCallbackParam handleCallbackParam : callbackParamList) { |
| | | ReturnT<String> callbackResult = callback(handleCallbackParam); |
| | | logger.debug(">>>>>>>>> JobApiController.callback {}, handleCallbackParam={}, callbackResult={}" , |
| | | logger.debug(">>>>>>>>> JobApiController.callback {}, handleCallbackParam={}, callbackResult={}", |
| | | (callbackResult.getCode() == ReturnT.SUCCESS_CODE ? "success" : "fail"), handleCallbackParam, callbackResult); |
| | | } |
| | | } |
| | |
| | | |
| | | // 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 { |
| | |
| | | |
| | | } catch (Exception e) { |
| | | if (!toStop) { |
| | | logger.error(">>>>>>>>>>> xxl-job, job fail monitor thread error:{}" , e); |
| | | logger.error(">>>>>>>>>>> xxl-job, job fail monitor thread error:{}", e); |
| | | } |
| | | } |
| | | |
| | |
| | | private static Logger logger = LoggerFactory.getLogger(JobLogReportHelper.class); |
| | | |
| | | private static JobLogReportHelper instance = new JobLogReportHelper(); |
| | | public static JobLogReportHelper getInstance(){ |
| | | |
| | | public static JobLogReportHelper getInstance() { |
| | | return instance; |
| | | } |
| | | |
| | | |
| | | private Thread logrThread; |
| | | private volatile boolean toStop = false; |
| | | public void start(){ |
| | | |
| | | public void start() { |
| | | logrThread = new Thread(new Runnable() { |
| | | |
| | | @Override |
| | |
| | | xxlJobLogReport.setFailCount(0); |
| | | |
| | | Map<String, Object> triggerCountMap = XxlJobAdminConfig.getAdminConfig().getXxlJobLogDao().findLogReport(todayFrom, todayTo); |
| | | if (triggerCountMap!=null && triggerCountMap.size()>0) { |
| | | int triggerDayCount = triggerCountMap.containsKey("triggerDayCount")?Integer.valueOf(String.valueOf(triggerCountMap.get("triggerDayCount"))):0; |
| | | int triggerDayCountRunning = triggerCountMap.containsKey("triggerDayCountRunning")?Integer.valueOf(String.valueOf(triggerCountMap.get("triggerDayCountRunning"))):0; |
| | | int triggerDayCountSuc = triggerCountMap.containsKey("triggerDayCountSuc")?Integer.valueOf(String.valueOf(triggerCountMap.get("triggerDayCountSuc"))):0; |
| | | if (triggerCountMap != null && triggerCountMap.size() > 0) { |
| | | int triggerDayCount = triggerCountMap.containsKey("triggerDayCount") ? Integer.valueOf(String.valueOf(triggerCountMap.get("triggerDayCount"))) : 0; |
| | | int triggerDayCountRunning = triggerCountMap.containsKey("triggerDayCountRunning") ? Integer.valueOf(String.valueOf(triggerCountMap.get("triggerDayCountRunning"))) : 0; |
| | | int triggerDayCountSuc = triggerCountMap.containsKey("triggerDayCountSuc") ? Integer.valueOf(String.valueOf(triggerCountMap.get("triggerDayCountSuc"))) : 0; |
| | | int triggerDayCountFail = triggerDayCount - triggerDayCountRunning - triggerDayCountSuc; |
| | | |
| | | xxlJobLogReport.setRunningCount(triggerDayCountRunning); |
| | |
| | | } |
| | | |
| | | // 2、log-clean: switch open & once each day |
| | | if (XxlJobAdminConfig.getAdminConfig().getLogretentiondays()>0 |
| | | && System.currentTimeMillis() - lastCleanLogTime > 24*60*60*1000) { |
| | | if (XxlJobAdminConfig.getAdminConfig().getLogretentiondays() > 0 |
| | | && System.currentTimeMillis() - lastCleanLogTime > 24 * 60 * 60 * 1000) { |
| | | |
| | | // expire-time |
| | | Calendar expiredDay = Calendar.getInstance(); |
| | |
| | | List<Long> logIds = null; |
| | | do { |
| | | logIds = XxlJobAdminConfig.getAdminConfig().getXxlJobLogDao().findClearLogIds(0, 0, clearBeforeTime, 0, 1000); |
| | | if (logIds!=null && logIds.size()>0) { |
| | | if (logIds != null && logIds.size() > 0) { |
| | | XxlJobAdminConfig.getAdminConfig().getXxlJobLogDao().clearLog(logIds); |
| | | } |
| | | } while (logIds!=null && logIds.size()>0); |
| | | } while (logIds != null && logIds.size() > 0); |
| | | |
| | | // update clean time |
| | | lastCleanLogTime = System.currentTimeMillis(); |
| | |
| | | logrThread.start(); |
| | | } |
| | | |
| | | public void toStop(){ |
| | | public void toStop() { |
| | | toStop = true; |
| | | // interrupt and wait |
| | | logrThread.interrupt(); |
| | |
| | | } |
| | | } catch (Exception e) { |
| | | if (!toStop) { |
| | | logger.error(">>>>>>>>>>> xxl-job, job registry monitor thread error:{}" , e); |
| | | 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.error(">>>>>>>>>>> xxl-job, job registry monitor thread error:{}", e); |
| | | } |
| | | } |
| | | } |
| | |
| | | private static Logger logger = LoggerFactory.getLogger(JobScheduleHelper.class); |
| | | |
| | | private static JobScheduleHelper instance = new JobScheduleHelper(); |
| | | public static JobScheduleHelper getInstance(){ |
| | | |
| | | public static JobScheduleHelper getInstance() { |
| | | return instance; |
| | | } |
| | | |
| | |
| | | private volatile boolean ringThreadToStop = false; |
| | | private volatile static Map<Integer, List<Integer>> ringData = new ConcurrentHashMap<>(); |
| | | |
| | | public void start(){ |
| | | public void start() { |
| | | |
| | | // schedule thread |
| | | scheduleThread = new Thread(new Runnable() { |
| | |
| | | public void run() { |
| | | |
| | | try { |
| | | TimeUnit.MILLISECONDS.sleep(5000 - System.currentTimeMillis()%1000 ); |
| | | TimeUnit.MILLISECONDS.sleep(5000 - System.currentTimeMillis() % 1000); |
| | | } catch (InterruptedException e) { |
| | | if (!scheduleThreadToStop) { |
| | | logger.error(e.getMessage(), e); |
| | |
| | | connAutoCommit = conn.getAutoCommit(); |
| | | conn.setAutoCommit(false); |
| | | |
| | | preparedStatement = conn.prepareStatement( "select * from xxl_job_lock where lock_name = 'schedule_lock' for update" ); |
| | | preparedStatement = conn.prepareStatement("select * from xxl_job_lock where lock_name = 'schedule_lock' for update"); |
| | | preparedStatement.execute(); |
| | | |
| | | // tx start |
| | |
| | | // 1、pre read |
| | | long nowTime = System.currentTimeMillis(); |
| | | List<XxlJobInfo> scheduleList = XxlJobAdminConfig.getAdminConfig().getXxlJobInfoDao().scheduleJobQuery(nowTime + PRE_READ_MS, preReadCount); |
| | | if (scheduleList!=null && scheduleList.size()>0) { |
| | | if (scheduleList != null && scheduleList.size() > 0) { |
| | | // 2、push time-ring |
| | | for (XxlJobInfo jobInfo: scheduleList) { |
| | | for (XxlJobInfo jobInfo : scheduleList) { |
| | | |
| | | // time-ring jump |
| | | if (nowTime > jobInfo.getTriggerNextTime() + PRE_READ_MS) { |
| | |
| | | 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() ); |
| | | logger.debug(">>>>>>>>>>> xxl-job, schedule push trigger : jobId = " + jobInfo.getId()); |
| | | } |
| | | |
| | | // 2、fresh next |
| | |
| | | |
| | | // 1、trigger |
| | | JobTriggerPoolHelper.trigger(jobInfo.getId(), TriggerTypeEnum.CRON, -1, null, null, null); |
| | | logger.debug(">>>>>>>>>>> xxl-job, schedule push trigger : jobId = " + jobInfo.getId() ); |
| | | logger.debug(">>>>>>>>>>> xxl-job, schedule push trigger : jobId = " + jobInfo.getId()); |
| | | |
| | | // 2、fresh next |
| | | refreshNextValidTime(jobInfo, new Date()); |
| | | |
| | | // next-trigger-time in 5s, pre-read again |
| | | if (jobInfo.getTriggerStatus()==1 && nowTime + PRE_READ_MS > jobInfo.getTriggerNextTime()) { |
| | | if (jobInfo.getTriggerStatus() == 1 && nowTime + PRE_READ_MS > jobInfo.getTriggerNextTime()) { |
| | | |
| | | // 1、make ring second |
| | | int ringSecond = (int)((jobInfo.getTriggerNextTime()/1000)%60); |
| | | int ringSecond = (int) ((jobInfo.getTriggerNextTime() / 1000) % 60); |
| | | |
| | | // 2、push time ring |
| | | pushTimeRing(ringSecond, jobInfo.getId()); |
| | |
| | | // 2.3、trigger-pre-read:time-ring trigger && make next-trigger-time |
| | | |
| | | // 1、make ring second |
| | | int ringSecond = (int)((jobInfo.getTriggerNextTime()/1000)%60); |
| | | int ringSecond = (int) ((jobInfo.getTriggerNextTime() / 1000) % 60); |
| | | |
| | | // 2、push time ring |
| | | pushTimeRing(ringSecond, jobInfo.getId()); |
| | |
| | | } |
| | | |
| | | // 3、update trigger info |
| | | for (XxlJobInfo jobInfo: scheduleList) { |
| | | for (XxlJobInfo jobInfo : scheduleList) { |
| | | XxlJobAdminConfig.getAdminConfig().getXxlJobInfoDao().scheduleUpdate(jobInfo); |
| | | } |
| | | |
| | |
| | | } |
| | | } |
| | | } |
| | | long cost = System.currentTimeMillis()-start; |
| | | long cost = System.currentTimeMillis() - start; |
| | | |
| | | |
| | | // Wait seconds, align second |
| | | if (cost < 1000) { // scan-overtime, not wait |
| | | try { |
| | | // pre-read period: success > scan each second; fail > skip this period; |
| | | TimeUnit.MILLISECONDS.sleep((preReadSuc?1000:PRE_READ_MS) - System.currentTimeMillis()%1000); |
| | | TimeUnit.MILLISECONDS.sleep((preReadSuc ? 1000 : PRE_READ_MS) - System.currentTimeMillis() % 1000); |
| | | } catch (InterruptedException e) { |
| | | if (!scheduleThreadToStop) { |
| | | logger.error(e.getMessage(), e); |
| | |
| | | List<Integer> ringItemData = new ArrayList<>(); |
| | | int nowSecond = Calendar.getInstance().get(Calendar.SECOND); // 避免处理耗时太长,跨过刻度,向前校验一个刻度; |
| | | for (int i = 0; i < 2; i++) { |
| | | List<Integer> tmpData = ringData.remove( (nowSecond+60-i)%60 ); |
| | | List<Integer> tmpData = ringData.remove((nowSecond + 60 - i) % 60); |
| | | if (tmpData != null) { |
| | | ringItemData.addAll(tmpData); |
| | | } |
| | | } |
| | | |
| | | // ring trigger |
| | | logger.debug(">>>>>>>>>>> xxl-job, time-ring beat : " + nowSecond + " = " + Arrays.asList(ringItemData) ); |
| | | logger.debug(">>>>>>>>>>> xxl-job, time-ring beat : " + nowSecond + " = " + Arrays.asList(ringItemData)); |
| | | if (ringItemData.size() > 0) { |
| | | // do trigger |
| | | for (int jobId: ringItemData) { |
| | | for (int jobId : ringItemData) { |
| | | // do trigger |
| | | JobTriggerPoolHelper.trigger(jobId, TriggerTypeEnum.CRON, -1, null, null, null); |
| | | } |
| | |
| | | jobInfo.setTriggerLastTime(0); |
| | | jobInfo.setTriggerNextTime(0); |
| | | logger.warn(">>>>>>>>>>> xxl-job, refreshNextValidTime fail for job: jobId={}, scheduleType={}, scheduleConf={}", |
| | | jobInfo.getId(), jobInfo.getScheduleType(), jobInfo.getScheduleConf()); |
| | | jobInfo.getId(), jobInfo.getScheduleType(), jobInfo.getScheduleConf()); |
| | | } |
| | | } |
| | | |
| | | private void pushTimeRing(int ringSecond, int jobId){ |
| | | private void pushTimeRing(int ringSecond, int jobId) { |
| | | // push async ring |
| | | List<Integer> ringItemData = ringData.get(ringSecond); |
| | | if (ringItemData == null) { |
| | |
| | | } |
| | | ringItemData.add(jobId); |
| | | |
| | | logger.debug(">>>>>>>>>>> xxl-job, schedule push time-ring : " + ringSecond + " = " + Arrays.asList(ringItemData) ); |
| | | logger.debug(">>>>>>>>>>> xxl-job, schedule push time-ring : " + ringSecond + " = " + Arrays.asList(ringItemData)); |
| | | } |
| | | |
| | | public void toStop(){ |
| | | public void toStop() { |
| | | |
| | | // 1、stop schedule |
| | | scheduleThreadToStop = true; |
| | |
| | | } catch (InterruptedException e) { |
| | | logger.error(e.getMessage(), e); |
| | | } |
| | | if (scheduleThread.getState() != Thread.State.TERMINATED){ |
| | | if (scheduleThread.getState() != Thread.State.TERMINATED) { |
| | | // interrupt and wait |
| | | scheduleThread.interrupt(); |
| | | try { |
| | |
| | | if (!ringData.isEmpty()) { |
| | | for (int second : ringData.keySet()) { |
| | | List<Integer> tmpData = ringData.get(second); |
| | | if (tmpData!=null && tmpData.size()>0) { |
| | | if (tmpData != null && tmpData.size() > 0) { |
| | | hasRingData = true; |
| | | break; |
| | | } |
| | |
| | | } catch (InterruptedException e) { |
| | | logger.error(e.getMessage(), e); |
| | | } |
| | | if (ringThread.getState() != Thread.State.TERMINATED){ |
| | | if (ringThread.getState() != Thread.State.TERMINATED) { |
| | | // interrupt and wait |
| | | ringThread.interrupt(); |
| | | try { |
| | |
| | | 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 new Date(fromTime.getTime() + Integer.valueOf(jobInfo.getScheduleConf()) * 1000); |
| | | } |
| | | return null; |
| | | } |
| | |
| | | API(I18nUtil.getString("jobconf_trigger_type_api")), |
| | | MISFIRE(I18nUtil.getString("jobconf_trigger_type_misfire")); |
| | | |
| | | private TriggerTypeEnum(String title){ |
| | | private TriggerTypeEnum(String title) { |
| | | this.title = title; |
| | | } |
| | | |
| | | private String title; |
| | | |
| | | public String getTitle() { |
| | | return title; |
| | | } |
| | |
| | | * |
| | | * @param jobId |
| | | * @param triggerType |
| | | * @param failRetryCount |
| | | * >=0: use this param |
| | | * <0: use param from job info config |
| | | * @param failRetryCount >=0: use this param |
| | | * <0: use param from job info config |
| | | * @param executorShardingParam |
| | | * @param executorParam |
| | | * null: use job param |
| | | * not null: cover job param |
| | | * @param addressList |
| | | * null: use executor addressList |
| | | * not null: cover |
| | | * @param executorParam null: use job param |
| | | * not null: cover job param |
| | | * @param addressList null: use executor addressList |
| | | * not null: cover |
| | | */ |
| | | public static void trigger(int jobId, |
| | | TriggerTypeEnum triggerType, |
| | |
| | | if (executorParam != null) { |
| | | jobInfo.setExecutorParam(executorParam); |
| | | } |
| | | int finalFailRetryCount = failRetryCount>=0?failRetryCount:jobInfo.getExecutorFailRetryCount(); |
| | | int finalFailRetryCount = failRetryCount >= 0 ? failRetryCount : jobInfo.getExecutorFailRetryCount(); |
| | | XxlJobGroup group = XxlJobAdminConfig.getAdminConfig().getXxlJobGroupDao().load(jobInfo.getJobGroup()); |
| | | |
| | | // cover addressList |
| | | if (addressList!=null && addressList.trim().length()>0) { |
| | | if (addressList != null && addressList.trim().length() > 0) { |
| | | group.setAddressType(1); |
| | | group.setAddressList(addressList.trim()); |
| | | } |
| | | |
| | | // sharding param |
| | | int[] shardingParam = null; |
| | | if (executorShardingParam!=null){ |
| | | if (executorShardingParam != null) { |
| | | String[] shardingArr = executorShardingParam.split("/"); |
| | | if (shardingArr.length==2 && isNumeric(shardingArr[0]) && isNumeric(shardingArr[1])) { |
| | | if (shardingArr.length == 2 && isNumeric(shardingArr[0]) && isNumeric(shardingArr[1])) { |
| | | shardingParam = new int[2]; |
| | | shardingParam[0] = Integer.valueOf(shardingArr[0]); |
| | | shardingParam[1] = Integer.valueOf(shardingArr[1]); |
| | | } |
| | | } |
| | | if (ExecutorRouteStrategyEnum.SHARDING_BROADCAST==ExecutorRouteStrategyEnum.match(jobInfo.getExecutorRouteStrategy(), null) |
| | | && group.getRegistryList()!=null && !group.getRegistryList().isEmpty() |
| | | && shardingParam==null) { |
| | | if (ExecutorRouteStrategyEnum.SHARDING_BROADCAST == ExecutorRouteStrategyEnum.match(jobInfo.getExecutorRouteStrategy(), null) |
| | | && group.getRegistryList() != null && !group.getRegistryList().isEmpty() |
| | | && shardingParam == null) { |
| | | for (int i = 0; i < group.getRegistryList().size(); i++) { |
| | | processTrigger(group, jobInfo, finalFailRetryCount, triggerType, i, group.getRegistryList().size()); |
| | | } |
| | |
| | | |
| | | } |
| | | |
| | | private static boolean isNumeric(String str){ |
| | | private static boolean isNumeric(String str) { |
| | | try { |
| | | int result = Integer.valueOf(str); |
| | | return true; |
| | |
| | | } |
| | | |
| | | /** |
| | | * @param group job group, registry list may be empty |
| | | * @param group job group, registry list may be empty |
| | | * @param jobInfo |
| | | * @param finalFailRetryCount |
| | | * @param triggerType |
| | | * @param index sharding index |
| | | * @param total sharding index |
| | | * @param index sharding index |
| | | * @param total sharding index |
| | | */ |
| | | private static void processTrigger(XxlJobGroup group, XxlJobInfo jobInfo, int finalFailRetryCount, TriggerTypeEnum triggerType, int index, int total){ |
| | | private static void processTrigger(XxlJobGroup group, XxlJobInfo jobInfo, int finalFailRetryCount, TriggerTypeEnum triggerType, int index, int total) { |
| | | |
| | | // param |
| | | ExecutorBlockStrategyEnum blockStrategy = ExecutorBlockStrategyEnum.match(jobInfo.getExecutorBlockStrategy(), ExecutorBlockStrategyEnum.SERIAL_EXECUTION); // block strategy |
| | | ExecutorRouteStrategyEnum executorRouteStrategyEnum = ExecutorRouteStrategyEnum.match(jobInfo.getExecutorRouteStrategy(), null); // route strategy |
| | | String shardingParam = (ExecutorRouteStrategyEnum.SHARDING_BROADCAST==executorRouteStrategyEnum)?String.valueOf(index).concat("/").concat(String.valueOf(total)):null; |
| | | String shardingParam = (ExecutorRouteStrategyEnum.SHARDING_BROADCAST == executorRouteStrategyEnum) ? String.valueOf(index).concat("/").concat(String.valueOf(total)) : null; |
| | | |
| | | // 1、save log-id |
| | | XxlJobLog jobLog = new XxlJobLog(); |
| | |
| | | // 3、init address |
| | | String address = null; |
| | | ReturnT<String> routeAddressResult = null; |
| | | if (group.getRegistryList()!=null && !group.getRegistryList().isEmpty()) { |
| | | if (group.getRegistryList() != null && !group.getRegistryList().isEmpty()) { |
| | | if (ExecutorRouteStrategyEnum.SHARDING_BROADCAST == executorRouteStrategyEnum) { |
| | | if (index < group.getRegistryList().size()) { |
| | | address = group.getRegistryList().get(index); |
| | |
| | | triggerMsgSb.append(I18nUtil.getString("jobconf_trigger_type")).append(":").append(triggerType.getTitle()); |
| | | triggerMsgSb.append("<br>").append(I18nUtil.getString("jobconf_trigger_admin_adress")).append(":").append(IpUtil.getIp()); |
| | | triggerMsgSb.append("<br>").append(I18nUtil.getString("jobconf_trigger_exe_regtype")).append(":") |
| | | .append( (group.getAddressType() == 0)?I18nUtil.getString("jobgroup_field_addressType_0"):I18nUtil.getString("jobgroup_field_addressType_1") ); |
| | | .append((group.getAddressType() == 0) ? I18nUtil.getString("jobgroup_field_addressType_0") : I18nUtil.getString("jobgroup_field_addressType_1")); |
| | | triggerMsgSb.append("<br>").append(I18nUtil.getString("jobconf_trigger_exe_regaddress")).append(":").append(group.getRegistryList()); |
| | | triggerMsgSb.append("<br>").append(I18nUtil.getString("jobinfo_field_executorRouteStrategy")).append(":").append(executorRouteStrategyEnum.getTitle()); |
| | | if (shardingParam != null) { |
| | | triggerMsgSb.append("("+shardingParam+")"); |
| | | triggerMsgSb.append("(" + shardingParam + ")"); |
| | | } |
| | | triggerMsgSb.append("<br>").append(I18nUtil.getString("jobinfo_field_executorBlockStrategy")).append(":").append(blockStrategy.getTitle()); |
| | | triggerMsgSb.append("<br>").append(I18nUtil.getString("jobinfo_field_timeout")).append(":").append(jobInfo.getExecutorTimeout()); |
| | | triggerMsgSb.append("<br>").append(I18nUtil.getString("jobinfo_field_executorFailRetryCount")).append(":").append(finalFailRetryCount); |
| | | |
| | | triggerMsgSb.append("<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>"+ I18nUtil.getString("jobconf_trigger_run") +"<<<<<<<<<<< </span><br>") |
| | | .append((routeAddressResult!=null&&routeAddressResult.getMsg()!=null)?routeAddressResult.getMsg()+"<br><br>":"").append(triggerResult.getMsg()!=null?triggerResult.getMsg():""); |
| | | triggerMsgSb.append("<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>" + I18nUtil.getString("jobconf_trigger_run") + "<<<<<<<<<<< </span><br>") |
| | | .append((routeAddressResult != null && routeAddressResult.getMsg() != null) ? routeAddressResult.getMsg() + "<br><br>" : "").append(triggerResult.getMsg() != null ? triggerResult.getMsg() : ""); |
| | | |
| | | // 6、save log trigger-info |
| | | jobLog.setExecutorAddress(address); |
| | |
| | | |
| | | /** |
| | | * run executor |
| | | * |
| | | * @param triggerParam |
| | | * @param address |
| | | * @return |
| | | */ |
| | | public static ReturnT<String> runExecutor(TriggerParam triggerParam, String address){ |
| | | public static ReturnT<String> runExecutor(TriggerParam triggerParam, String address) { |
| | | ReturnT<String> runResult = null; |
| | | try { |
| | | ExecutorBiz executorBiz = XxlJobScheduler.getExecutorBiz(address); |
| | |
| | | public static void remove(HttpServletRequest request, HttpServletResponse response, String key) { |
| | | Cookie cookie = get(request, key); |
| | | if (cookie != null) { |
| | | set(response, key, "" , null, COOKIE_PATH, 0, true); |
| | | set(response, key, "", null, COOKIE_PATH, 0, true); |
| | | } |
| | | } |
| | | |
| | |
| | | private static Logger logger = LoggerFactory.getLogger(I18nUtil.class); |
| | | |
| | | private static Properties prop = null; |
| | | public static Properties loadI18nProp(){ |
| | | |
| | | public static Properties loadI18nProp() { |
| | | if (prop != null) { |
| | | return prop; |
| | | } |
| | |
| | | |
| | | // load prop |
| | | Resource resource = new ClassPathResource(i18nFile); |
| | | EncodedResource encodedResource = new EncodedResource(resource,"UTF-8"); |
| | | EncodedResource encodedResource = new EncodedResource(resource, "UTF-8"); |
| | | prop = PropertiesLoaderUtils.loadProperties(encodedResource); |
| | | } catch (IOException e) { |
| | | logger.error(e.getMessage(), e); |
| | |
| | | Map<String, String> map = new HashMap<String, String>(); |
| | | |
| | | Properties prop = loadI18nProp(); |
| | | if (keys!=null && keys.length>0) { |
| | | for (String key: keys) { |
| | | if (keys != null && keys.length > 0) { |
| | | for (String key : keys) { |
| | | map.put(key, prop.getProperty(key)); |
| | | } |
| | | } else { |
| | | for (String key: prop.stringPropertyNames()) { |
| | | for (String key : prop.stringPropertyNames()) { |
| | | map.put(key, prop.getProperty(key)); |
| | | } |
| | | } |
| | |
| | | public class LocalCacheUtil { |
| | | |
| | | private static ConcurrentMap<String, LocalCacheData> cacheRepository = new ConcurrentHashMap<String, LocalCacheData>(); // 类型建议用抽象父类,兼容性更好; |
| | | private static class LocalCacheData{ |
| | | |
| | | private static class LocalCacheData { |
| | | private String key; |
| | | private Object val; |
| | | private long timeoutTime; |
| | |
| | | * @param cacheTime |
| | | * @return |
| | | */ |
| | | public static boolean set(String key, Object val, long cacheTime){ |
| | | public static boolean set(String key, Object val, long cacheTime) { |
| | | |
| | | // clean timeout cache, before set new cache (avoid cache too much) |
| | | cleanTimeoutCache(); |
| | | |
| | | // set new cache |
| | | if (key==null || key.trim().length()==0) { |
| | | if (key == null || key.trim().length() == 0) { |
| | | return false; |
| | | } |
| | | if (val == null) { |
| | |
| | | * @param key |
| | | * @return |
| | | */ |
| | | public static boolean remove(String key){ |
| | | if (key==null || key.trim().length()==0) { |
| | | public static boolean remove(String key) { |
| | | if (key == null || key.trim().length() == 0) { |
| | | return false; |
| | | } |
| | | cacheRepository.remove(key); |
| | |
| | | * @param key |
| | | * @return |
| | | */ |
| | | public static Object get(String key){ |
| | | if (key==null || key.trim().length()==0) { |
| | | public static Object get(String key) { |
| | | if (key == null || key.trim().length() == 0) { |
| | | return null; |
| | | } |
| | | LocalCacheData localCacheData = cacheRepository.get(key); |
| | | if (localCacheData!=null && System.currentTimeMillis()<localCacheData.getTimeoutTime()) { |
| | | if (localCacheData != null && System.currentTimeMillis() < localCacheData.getTimeoutTime()) { |
| | | return localCacheData.getVal(); |
| | | } else { |
| | | remove(key); |
| | |
| | | * |
| | | * @return |
| | | */ |
| | | public static boolean cleanTimeoutCache(){ |
| | | public static boolean cleanTimeoutCache() { |
| | | if (!cacheRepository.keySet().isEmpty()) { |
| | | for (String key: cacheRepository.keySet()) { |
| | | for (String key : cacheRepository.keySet()) { |
| | | LocalCacheData localCacheData = cacheRepository.get(key); |
| | | if (localCacheData!=null && System.currentTimeMillis()>=localCacheData.getTimeoutTime()) { |
| | | if (localCacheData != null && System.currentTimeMillis() >= localCacheData.getTimeoutTime()) { |
| | | cacheRepository.remove(key); |
| | | } |
| | | } |
| | |
| | | @Param("updateTime") Date updateTime); |
| | | |
| | | public int registryDelete(@Param("registryGroup") String registryGroup, |
| | | @Param("registryKey") String registryKey, |
| | | @Param("registryValue") String registryValue); |
| | | @Param("registryKey") String registryKey, |
| | | @Param("registryValue") String registryValue); |
| | | |
| | | } |
| | |
| | | private XxlJobUserDao xxlJobUserDao; |
| | | |
| | | |
| | | private String makeToken(XxlJobUser xxlJobUser){ |
| | | private String makeToken(XxlJobUser xxlJobUser) { |
| | | String tokenJson = JacksonUtil.writeValueAsString(xxlJobUser); |
| | | String tokenHex = new BigInteger(tokenJson.getBytes()).toString(16); |
| | | return tokenHex; |
| | | } |
| | | private XxlJobUser parseToken(String tokenHex){ |
| | | |
| | | private XxlJobUser parseToken(String tokenHex) { |
| | | XxlJobUser xxlJobUser = null; |
| | | if (tokenHex != null) { |
| | | String tokenJson = new String(new BigInteger(tokenHex, 16).toByteArray()); // username_password(md5) |
| | |
| | | } |
| | | |
| | | |
| | | public ReturnT<String> login(HttpServletRequest request, HttpServletResponse response, String username, String password, boolean ifRemember){ |
| | | public ReturnT<String> login(HttpServletRequest request, HttpServletResponse response, String username, String password, boolean ifRemember) { |
| | | |
| | | // param |
| | | if (username==null || username.trim().length()==0 || password==null || password.trim().length()==0){ |
| | | if (username == null || username.trim().length() == 0 || password == null || password.trim().length() == 0) { |
| | | return new ReturnT<String>(500, I18nUtil.getString("login_param_empty")); |
| | | } |
| | | |
| | |
| | | * @param request |
| | | * @param response |
| | | */ |
| | | public ReturnT<String> logout(HttpServletRequest request, HttpServletResponse response){ |
| | | public ReturnT<String> logout(HttpServletRequest request, HttpServletResponse response) { |
| | | CookieUtil.remove(request, response, LOGIN_IDENTITY_KEY); |
| | | return ReturnT.SUCCESS; |
| | | } |
| | |
| | | * @param request |
| | | * @return |
| | | */ |
| | | public XxlJobUser ifLogin(HttpServletRequest request, HttpServletResponse response){ |
| | | public XxlJobUser ifLogin(HttpServletRequest request, HttpServletResponse response) { |
| | | String cookieToken = CookieUtil.getValue(request, LOGIN_IDENTITY_KEY); |
| | | if (cookieToken != null) { |
| | | XxlJobUser cookieUser = null; |
| | |
| | | return null; |
| | | } |
| | | |
| | | public static void main(String[] args) { |
| | | System.out.println("121312"); |
| | | } |
| | | |
| | | } |
| | |
| | | |
| | | // package result |
| | | Map<String, Object> maps = new HashMap<String, Object>(); |
| | | maps.put("recordsTotal" , list_count); // 总记录数 |
| | | maps.put("recordsFiltered" , list_count); // 过滤后的总记录数 |
| | | maps.put("data" , list); // 分页列表 |
| | | maps.put("recordsTotal", list_count); // 总记录数 |
| | | maps.put("recordsFiltered", list_count); // 过滤后的总记录数 |
| | | maps.put("data", list); // 分页列表 |
| | | return maps; |
| | | } |
| | | |
| | |
| | | } |
| | | // 》fix "\r" in shell |
| | | if (GlueTypeEnum.GLUE_SHELL == GlueTypeEnum.match(jobInfo.getGlueType()) && jobInfo.getGlueSource() != null) { |
| | | jobInfo.setGlueSource(jobInfo.getGlueSource().replaceAll("\r" , "")); |
| | | jobInfo.setGlueSource(jobInfo.getGlueSource().replaceAll("\r", "")); |
| | | } |
| | | |
| | | // valid advanced |
| | |
| | | int executorCount = executorAddressSet.size(); |
| | | |
| | | Map<String, Object> dashboardMap = new HashMap<String, Object>(); |
| | | dashboardMap.put("jobInfoCount" , jobInfoCount); |
| | | dashboardMap.put("jobLogCount" , jobLogCount); |
| | | dashboardMap.put("jobLogSuccessCount" , jobLogSuccessCount); |
| | | dashboardMap.put("executorCount" , executorCount); |
| | | dashboardMap.put("jobInfoCount", jobInfoCount); |
| | | dashboardMap.put("jobLogCount", jobLogCount); |
| | | dashboardMap.put("jobLogSuccessCount", jobLogSuccessCount); |
| | | dashboardMap.put("executorCount", executorCount); |
| | | return dashboardMap; |
| | | } |
| | | |
| | |
| | | } |
| | | |
| | | Map<String, Object> result = new HashMap<String, Object>(); |
| | | result.put("triggerDayList" , triggerDayList); |
| | | result.put("triggerDayCountRunningList" , triggerDayCountRunningList); |
| | | result.put("triggerDayCountSucList" , triggerDayCountSucList); |
| | | result.put("triggerDayCountFailList" , triggerDayCountFailList); |
| | | result.put("triggerDayList", triggerDayList); |
| | | result.put("triggerDayCountRunningList", triggerDayCountRunningList); |
| | | result.put("triggerDayCountSucList", triggerDayCountSucList); |
| | | result.put("triggerDayCountFailList", triggerDayCountFailList); |
| | | |
| | | result.put("triggerCountRunningTotal" , triggerCountRunningTotal); |
| | | result.put("triggerCountSucTotal" , triggerCountSucTotal); |
| | | result.put("triggerCountFailTotal" , triggerCountFailTotal); |
| | | result.put("triggerCountRunningTotal", triggerCountRunningTotal); |
| | | result.put("triggerCountSucTotal", triggerCountSucTotal); |
| | | result.put("triggerCountFailTotal", triggerCountFailTotal); |
| | | |
| | | return new ReturnT<Map<String, Object>>(result); |
| | | } |
| | |
| | | admin_name=Scheduling Center |
| | | admin_name_full=Distributed Task Scheduling Platform XXL-JOB |
| | | admin_version=2.3.0 |
| | | admin_version=2.3.1 |
| | | admin_i18n=en |
| | | |
| | | ## system |
| | | system_tips=System message |
| | | system_ok=Confirm |
| | | system_ok=Confirm |
| | | system_close=Close |
| | | system_save=Save |
| | | system_save=Save |
| | | system_cancel=Cancel |
| | | system_search=Search |
| | | system_status=Status |
| | | system_opt=Operate |
| | | system_please_input=please input |
| | | system_please_choose=please choose |
| | | system_please_input=please input |
| | | system_please_choose=please choose |
| | | system_success=success |
| | | system_fail=fail |
| | | system_add_suc=add success |
| | |
| | | joblog_kill_log_byman=Manual operation, kill job |
| | | joblog_lost_fail=Job result lost, marked as failure |
| | | joblog_rolling_log=Rolling log |
| | | joblog_rolling_log_refresh=Refresh |
| | | joblog_rolling_log_refresh=Refresh |
| | | joblog_rolling_log_triggerfail=The job trigger fail, can not view the rolling log |
| | | joblog_rolling_log_failoften=The request for the Rolling log is terminated, the number of failed requests exceeds the limit, Reload the log on the refresh page |
| | | joblog_logid_unvalid=Log ID is illegal |
| | |
| | | admin_name=任务调度中心 |
| | | admin_name_full=分布式任务调度平台XXL-JOB |
| | | admin_version=2.3.0 |
| | | admin_version=2.3.1 |
| | | admin_i18n= |
| | | |
| | | ## system |
| | |
| | | |
| | | ## help |
| | | job_help=使用教程 |
| | | job_help_document=官方文档 |
| | | job_help_document=官方文档 |
| | |
| | | admin_name=任務調度中心 |
| | | admin_name_full=分布式任務調度平臺XXL-JOB |
| | | admin_version=2.3.0 |
| | | admin_version=2.3.1 |
| | | admin_i18n= |
| | | |
| | | ## system |
| | |
| | | |
| | | ## help |
| | | job_help=使用教程 |
| | | job_help_document=官方文件 |
| | | job_help_document=官方文件 |
| | |
| | | $(function() { |
| | | |
| | | // init date tables |
| | | var userListTable = $("#user_list").dataTable({ |
| | | "deferRender": true, |
| | | "processing" : true, |
| | | "serverSide": true, |
| | | "ajax": { |
| | | url: base_url + "/user/pageList", |
| | | type:"post", |
| | | data : function ( d ) { |
| | | var obj = {}; |
| | | // init date tables |
| | | var userListTable = $("#user_list").dataTable({ |
| | | "deferRender": true, |
| | | "processing" : true, |
| | | "serverSide": true, |
| | | "ajax": { |
| | | url: base_url + "/user/pageList", |
| | | type:"post", |
| | | data : function ( d ) { |
| | | var obj = {}; |
| | | obj.username = $('#username').val(); |
| | | obj.role = $('#role').val(); |
| | | obj.start = d.start; |
| | | obj.length = d.length; |
| | | obj.start = d.start; |
| | | obj.length = d.length; |
| | | return obj; |
| | | } |
| | | }, |
| | | "searching": false, |
| | | "ordering": false, |
| | | //"scrollX": true, // scroll x,close self-adaption |
| | | "columns": [ |
| | | { |
| | | "data": 'id', |
| | | "visible" : false, |
| | | "width":'10%' |
| | | }, |
| | | { |
| | | "data": 'username', |
| | | "visible" : true, |
| | | "width":'20%' |
| | | }, |
| | | { |
| | | "data": 'password', |
| | | "visible" : false, |
| | | }, |
| | | "searching": false, |
| | | "ordering": false, |
| | | //"scrollX": true, // scroll x,close self-adaption |
| | | "columns": [ |
| | | { |
| | | "data": 'id', |
| | | "visible" : false, |
| | | "width":'10%' |
| | | }, |
| | | { |
| | | "data": 'username', |
| | | "visible" : true, |
| | | "width":'20%' |
| | | }, |
| | | { |
| | | "data": 'password', |
| | | "visible" : false, |
| | | "width":'20%', |
| | | "render": function ( data, type, row ) { |
| | | return '*********'; |
| | | } |
| | | }, |
| | | { |
| | | "data": 'role', |
| | | "visible" : true, |
| | | "width":'10%', |
| | | }, |
| | | { |
| | | "data": 'role', |
| | | "visible" : true, |
| | | "width":'10%', |
| | | "render": function ( data, type, row ) { |
| | | if (data == 1) { |
| | | return I18n.user_role_admin |
| | |
| | | return I18n.user_role_normal |
| | | } |
| | | } |
| | | }, |
| | | { |
| | | "data": 'permission', |
| | | "width":'10%', |
| | | "visible" : false |
| | | }, |
| | | { |
| | | "data": I18n.system_opt , |
| | | "width":'15%', |
| | | "render": function ( data, type, row ) { |
| | | return function(){ |
| | | // html |
| | | }, |
| | | { |
| | | "data": 'permission', |
| | | "width":'10%', |
| | | "visible" : false |
| | | }, |
| | | { |
| | | "data": I18n.system_opt , |
| | | "width":'15%', |
| | | "render": function ( data, type, row ) { |
| | | return function(){ |
| | | // html |
| | | tableData['key'+row.id] = row; |
| | | var html = '<p id="'+ row.id +'" >'+ |
| | | '<button class="btn btn-warning btn-xs update" type="button">'+ I18n.system_opt_edit +'</button> '+ |
| | | '<button class="btn btn-danger btn-xs delete" type="button">'+ I18n.system_opt_del +'</button> '+ |
| | | '</p>'; |
| | | var html = '<p id="'+ row.id +'" >'+ |
| | | '<button class="btn btn-warning btn-xs update" type="button">'+ I18n.system_opt_edit +'</button> '+ |
| | | '<button class="btn btn-danger btn-xs delete" type="button">'+ I18n.system_opt_del +'</button> '+ |
| | | '</p>'; |
| | | |
| | | return html; |
| | | }; |
| | | } |
| | | } |
| | | ], |
| | | "language" : { |
| | | "sProcessing" : I18n.dataTable_sProcessing , |
| | | "sLengthMenu" : I18n.dataTable_sLengthMenu , |
| | | "sZeroRecords" : I18n.dataTable_sZeroRecords , |
| | | "sInfo" : I18n.dataTable_sInfo , |
| | | "sInfoEmpty" : I18n.dataTable_sInfoEmpty , |
| | | "sInfoFiltered" : I18n.dataTable_sInfoFiltered , |
| | | "sInfoPostFix" : "", |
| | | "sSearch" : I18n.dataTable_sSearch , |
| | | "sUrl" : "", |
| | | "sEmptyTable" : I18n.dataTable_sEmptyTable , |
| | | "sLoadingRecords" : I18n.dataTable_sLoadingRecords , |
| | | "sInfoThousands" : ",", |
| | | "oPaginate" : { |
| | | "sFirst" : I18n.dataTable_sFirst , |
| | | "sPrevious" : I18n.dataTable_sPrevious , |
| | | "sNext" : I18n.dataTable_sNext , |
| | | "sLast" : I18n.dataTable_sLast |
| | | }, |
| | | "oAria" : { |
| | | "sSortAscending" : I18n.dataTable_sSortAscending , |
| | | "sSortDescending" : I18n.dataTable_sSortDescending |
| | | } |
| | | } |
| | | }); |
| | | return html; |
| | | }; |
| | | } |
| | | } |
| | | ], |
| | | "language" : { |
| | | "sProcessing" : I18n.dataTable_sProcessing , |
| | | "sLengthMenu" : I18n.dataTable_sLengthMenu , |
| | | "sZeroRecords" : I18n.dataTable_sZeroRecords , |
| | | "sInfo" : I18n.dataTable_sInfo , |
| | | "sInfoEmpty" : I18n.dataTable_sInfoEmpty , |
| | | "sInfoFiltered" : I18n.dataTable_sInfoFiltered , |
| | | "sInfoPostFix" : "", |
| | | "sSearch" : I18n.dataTable_sSearch , |
| | | "sUrl" : "", |
| | | "sEmptyTable" : I18n.dataTable_sEmptyTable , |
| | | "sLoadingRecords" : I18n.dataTable_sLoadingRecords , |
| | | "sInfoThousands" : ",", |
| | | "oPaginate" : { |
| | | "sFirst" : I18n.dataTable_sFirst , |
| | | "sPrevious" : I18n.dataTable_sPrevious , |
| | | "sNext" : I18n.dataTable_sNext , |
| | | "sLast" : I18n.dataTable_sLast |
| | | }, |
| | | "oAria" : { |
| | | "sSortAscending" : I18n.dataTable_sSortAscending , |
| | | "sSortDescending" : I18n.dataTable_sSortDescending |
| | | } |
| | | } |
| | | }); |
| | | |
| | | // table data |
| | | var tableData = {}; |
| | | |
| | | // search btn |
| | | $('#searchBtn').on('click', function(){ |
| | | // search btn |
| | | $('#searchBtn').on('click', function(){ |
| | | userListTable.fnDraw(); |
| | | }); |
| | | }); |
| | | |
| | | // job operate |
| | | $("#user_list").on('click', '.delete',function() { |
| | | var id = $(this).parent('p').attr("id"); |
| | | |
| | | // job operate |
| | | $("#user_list").on('click', '.delete',function() { |
| | | var id = $(this).parent('p').attr("id"); |
| | | |
| | | layer.confirm( I18n.system_ok + I18n.system_opt_del + '?', { |
| | | icon: 3, |
| | | title: I18n.system_tips , |
| | | layer.confirm( I18n.system_ok + I18n.system_opt_del + '?', { |
| | | icon: 3, |
| | | title: I18n.system_tips , |
| | | btn: [ I18n.system_ok, I18n.system_cancel ] |
| | | }, function(index){ |
| | | layer.close(index); |
| | | }, function(index){ |
| | | layer.close(index); |
| | | |
| | | $.ajax({ |
| | | type : 'POST', |
| | | url : base_url + "/user/remove", |
| | | data : { |
| | | "id" : id |
| | | }, |
| | | dataType : "json", |
| | | success : function(data){ |
| | | if (data.code == 200) { |
| | | $.ajax({ |
| | | type : 'POST', |
| | | url : base_url + "/user/remove", |
| | | data : { |
| | | "id" : id |
| | | }, |
| | | dataType : "json", |
| | | success : function(data){ |
| | | if (data.code == 200) { |
| | | layer.msg( I18n.system_success ); |
| | | userListTable.fnDraw(false); |
| | | } else { |
| | | userListTable.fnDraw(false); |
| | | } else { |
| | | layer.msg( data.msg || I18n.system_opt_del + I18n.system_fail ); |
| | | } |
| | | } |
| | | }); |
| | | }); |
| | | }); |
| | | } |
| | | } |
| | | }); |
| | | }); |
| | | }); |
| | | |
| | | // add role |
| | | // add role |
| | | $("#addModal .form input[name=role]").change(function () { |
| | | var role = $(this).val(); |
| | | if (role == 1) { |
| | | var role = $(this).val(); |
| | | if (role == 1) { |
| | | $("#addModal .form input[name=permission]").parents('.form-group').hide(); |
| | | } else { |
| | | } else { |
| | | $("#addModal .form input[name=permission]").parents('.form-group').show(); |
| | | } |
| | | } |
| | | $("#addModal .form input[name='permission']").prop("checked",false); |
| | | }); |
| | | |
| | |
| | | return this.optional(element) || valid.test(value); |
| | | }, I18n.user_username_valid ); |
| | | |
| | | // add |
| | | $(".add").click(function(){ |
| | | $('#addModal').modal({backdrop: false, keyboard: false}).modal('show'); |
| | | }); |
| | | var addModalValidate = $("#addModal .form").validate({ |
| | | errorElement : 'span', |
| | | // add |
| | | $(".add").click(function(){ |
| | | $('#addModal').modal({backdrop: false, keyboard: false}).modal('show'); |
| | | }); |
| | | var addModalValidate = $("#addModal .form").validate({ |
| | | errorElement : 'span', |
| | | errorClass : 'help-block', |
| | | focusInvalid : true, |
| | | focusInvalid : true, |
| | | rules : { |
| | | username : { |
| | | required : true, |
| | | required : true, |
| | | rangelength:[4, 20], |
| | | myValid01: true |
| | | }, |
| | | }, |
| | | password : { |
| | | required : true, |
| | | rangelength:[4, 20] |
| | | } |
| | | }, |
| | | }, |
| | | messages : { |
| | | username : { |
| | | required : I18n.system_please_input + I18n.user_username, |
| | | required : I18n.system_please_input + I18n.user_username, |
| | | rangelength: I18n.system_lengh_limit + "[4-20]" |
| | | }, |
| | | password : { |
| | |
| | | rangelength: I18n.system_lengh_limit + "[4-20]" |
| | | } |
| | | }, |
| | | highlight : function(element) { |
| | | $(element).closest('.form-group').addClass('has-error'); |
| | | highlight : function(element) { |
| | | $(element).closest('.form-group').addClass('has-error'); |
| | | }, |
| | | success : function(label) { |
| | | label.closest('.form-group').removeClass('has-error'); |
| | | label.remove(); |
| | | success : function(label) { |
| | | label.closest('.form-group').removeClass('has-error'); |
| | | label.remove(); |
| | | }, |
| | | errorPlacement : function(error, element) { |
| | | element.parent('div').append(error); |
| | | errorPlacement : function(error, element) { |
| | | element.parent('div').append(error); |
| | | }, |
| | | submitHandler : function(form) { |
| | | |
| | |
| | | permissionArr.push($(this).val()); |
| | | }); |
| | | |
| | | var paramData = { |
| | | "username": $("#addModal .form input[name=username]").val(), |
| | | var paramData = { |
| | | "username": $("#addModal .form input[name=username]").val(), |
| | | "password": $("#addModal .form input[name=password]").val(), |
| | | "role": $("#addModal .form input[name=role]:checked").val(), |
| | | "permission": permissionArr.join(',') |
| | | }; |
| | | }; |
| | | |
| | | $.post(base_url + "/user/add", paramData, function(data, status) { |
| | | if (data.code == "200") { |
| | | $('#addModal').modal('hide'); |
| | | $.post(base_url + "/user/add", paramData, function(data, status) { |
| | | if (data.code == "200") { |
| | | $('#addModal').modal('hide'); |
| | | |
| | | layer.msg( I18n.system_add_suc ); |
| | | userListTable.fnDraw(); |
| | | } else { |
| | | layer.open({ |
| | | title: I18n.system_tips , |
| | | } else { |
| | | layer.open({ |
| | | title: I18n.system_tips , |
| | | btn: [ I18n.system_ok ], |
| | | content: (data.msg || I18n.system_add_fail), |
| | | icon: '2' |
| | | }); |
| | | } |
| | | }); |
| | | } |
| | | }); |
| | | $("#addModal").on('hide.bs.modal', function () { |
| | | $("#addModal .form")[0].reset(); |
| | | addModalValidate.resetForm(); |
| | | $("#addModal .form .form-group").removeClass("has-error"); |
| | | $(".remote_panel").show(); // remote |
| | | content: (data.msg || I18n.system_add_fail), |
| | | icon: '2' |
| | | }); |
| | | } |
| | | }); |
| | | } |
| | | }); |
| | | $("#addModal").on('hide.bs.modal', function () { |
| | | $("#addModal .form")[0].reset(); |
| | | addModalValidate.resetForm(); |
| | | $("#addModal .form .form-group").removeClass("has-error"); |
| | | $(".remote_panel").show(); // remote |
| | | |
| | | $("#addModal .form input[name=permission]").parents('.form-group').show(); |
| | | }); |
| | | }); |
| | | |
| | | // update role |
| | | $("#updateModal .form input[name=role]").change(function () { |
| | |
| | | $("#updateModal .form input[name='permission']").prop("checked",false); |
| | | }); |
| | | |
| | | // update |
| | | $("#user_list").on('click', '.update',function() { |
| | | // update |
| | | $("#user_list").on('click', '.update',function() { |
| | | |
| | | var id = $(this).parent('p').attr("id"); |
| | | var row = tableData['key'+id]; |
| | | |
| | | // base data |
| | | $("#updateModal .form input[name='id']").val( row.id ); |
| | | $("#updateModal .form input[name='username']").val( row.username ); |
| | | $("#updateModal .form input[name='password']").val( '' ); |
| | | $("#updateModal .form input[name='role'][value='"+ row.role +"']").click(); |
| | | // base data |
| | | $("#updateModal .form input[name='id']").val( row.id ); |
| | | $("#updateModal .form input[name='username']").val( row.username ); |
| | | $("#updateModal .form input[name='password']").val( '' ); |
| | | $("#updateModal .form input[name='role'][value='"+ row.role +"']").click(); |
| | | var permissionArr = []; |
| | | if (row.permission) { |
| | | permissionArr = row.permission.split(","); |
| | | } |
| | | } |
| | | $("#updateModal .form input[name='permission']").each(function () { |
| | | if($.inArray($(this).val(), permissionArr) > -1) { |
| | | $(this).prop("checked",true); |
| | |
| | | } |
| | | }); |
| | | |
| | | // show |
| | | $('#updateModal').modal({backdrop: false, keyboard: false}).modal('show'); |
| | | }); |
| | | var updateModalValidate = $("#updateModal .form").validate({ |
| | | errorElement : 'span', |
| | | // show |
| | | $('#updateModal').modal({backdrop: false, keyboard: false}).modal('show'); |
| | | }); |
| | | var updateModalValidate = $("#updateModal .form").validate({ |
| | | errorElement : 'span', |
| | | errorClass : 'help-block', |
| | | focusInvalid : true, |
| | | highlight : function(element) { |
| | | $(element).closest('.form-group').addClass('has-error'); |
| | | highlight : function(element) { |
| | | $(element).closest('.form-group').addClass('has-error'); |
| | | }, |
| | | success : function(label) { |
| | | label.closest('.form-group').removeClass('has-error'); |
| | | label.remove(); |
| | | success : function(label) { |
| | | label.closest('.form-group').removeClass('has-error'); |
| | | label.remove(); |
| | | }, |
| | | errorPlacement : function(error, element) { |
| | | element.parent('div').append(error); |
| | | errorPlacement : function(error, element) { |
| | | element.parent('div').append(error); |
| | | }, |
| | | submitHandler : function(form) { |
| | | |
| | |
| | | }); |
| | | } |
| | | }); |
| | | } |
| | | }); |
| | | $("#updateModal").on('hide.bs.modal', function () { |
| | | } |
| | | }); |
| | | $("#updateModal").on('hide.bs.modal', function () { |
| | | $("#updateModal .form")[0].reset(); |
| | | updateModalValidate.resetForm(); |
| | | $("#updateModal .form .form-group").removeClass("has-error"); |
| | | $(".remote_panel").show(); // remote |
| | | $(".remote_panel").show(); // remote |
| | | |
| | | $("#updateModal .form input[name=permission]").parents('.form-group').show(); |
| | | }); |
| | | }); |
| | | |
| | | }); |