package com.vip.saturn.job.console.controller; import com.vip.saturn.job.console.domain.JobBriefInfo; import com.vip.saturn.job.console.domain.JobConfig; import com.vip.saturn.job.console.domain.JobMode; import com.vip.saturn.job.console.domain.RequestResult; import com.vip.saturn.job.console.exception.SaturnJobConsoleException; import com.vip.saturn.job.console.service.ExecutorService; import com.vip.saturn.job.console.service.JobDimensionService; import com.vip.saturn.job.console.utils.CronExpression; import com.vip.saturn.job.console.utils.SaturnConstants; import jxl.Cell; import jxl.CellType; import jxl.Sheet; import jxl.Workbook; import jxl.write.WriteException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartHttpServletRequest; import javax.annotation.Resource; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import java.io.*; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.Iterator; import java.util.List; /** * @author xiaopeng.he */ @RestController @RequestMapping("executor") public class ExecutorController extends AbstractController { protected static Logger LOGGER = LoggerFactory.getLogger(ExecutorController.class); @Resource private ExecutorService executorService; @Resource private JobDimensionService jobDimensionService; @RequestMapping(value = "checkAndAddJobs", method = RequestMethod.POST) public RequestResult checkAndAddJobs(JobConfig jobConfig, HttpServletRequest request) { RequestResult requestResult = new RequestResult(); try { checkJobConfig(jobConfig); requestResult = executorService.addJobs(jobConfig); } catch (SaturnJobConsoleException e) { requestResult.setSuccess(false); requestResult.setMessage(e.getMessage()); } catch (Throwable t) { requestResult.setSuccess(false); requestResult.setMessage(t.toString()); LOGGER.error("checkAndAddJobs exception:", t); } return requestResult; } private void checkJobConfig(JobConfig jobConfig) throws SaturnJobConsoleException { // 作业名必填 if (jobConfig.getJobName() == null || jobConfig.getJobName().trim().isEmpty()) { throw new SaturnJobConsoleException("作业名必填"); } // 作业名只允许包含:数字0-9、小写字符a-z、大写字符A-Z、下划线_ if(!jobConfig.getJobName().matches("[0-9a-zA-Z_]*")) { throw new SaturnJobConsoleException("作业名只允许包含:数字0-9、小写字符a-z、大写字符A-Z、下划线_"); } // 依赖的作业只允许包含:数字0-9、小写字符a-z、大写字符A-Z、下划线_、英文逗号, if(jobConfig.getDependencies() != null && !jobConfig.getDependencies().matches("[0-9a-zA-Z_,]*")) { throw new SaturnJobConsoleException("依赖的作业只允许包含:数字0-9、小写字符a-z、大写字符A-Z、下划线_、英文逗号,"); } // 作业类型必填 if (jobConfig.getJobType() == null || jobConfig.getJobType().trim().isEmpty()) { throw new SaturnJobConsoleException("作业类型必填"); } // 验证作业类型 if (JobBriefInfo.JobType.getJobType(jobConfig.getJobType()).equals(JobBriefInfo.JobType.UNKOWN_JOB)) { throw new SaturnJobConsoleException("作业类型未知"); } // 如果是JAVA/MSG作业 if (jobConfig.getJobType().equals(JobBriefInfo.JobType.JAVA_JOB.name()) || jobConfig.getJobType().equals(JobBriefInfo.JobType.MSG_JOB.name())) { // 作业实现类必填 if (jobConfig.getJobClass() == null || jobConfig.getJobClass().trim().isEmpty()) { throw new SaturnJobConsoleException("对于JAVA/MSG作业,作业实现类必填"); } } // 如果是JAVA/SHELL作业 if (jobConfig.getJobType().equals(JobBriefInfo.JobType.JAVA_JOB.name()) || jobConfig.getJobType().equals(JobBriefInfo.JobType.SHELL_JOB.name())) { // cron表达式必填 if (jobConfig.getCron() == null || jobConfig.getCron().trim().isEmpty()) { throw new SaturnJobConsoleException("对于JAVA/SHELL作业,cron表达式必填"); } // cron表达式语法验证 try { CronExpression.validateExpression(jobConfig.getCron()); } catch (ParseException e) { throw new SaturnJobConsoleException("cron表达式语法有误," + e.toString()); } } else { jobConfig.setCron("");;// 其他类型的不需要持久化保存cron表达式 } if (jobConfig.getLocalMode() != null && jobConfig.getLocalMode()) { if (jobConfig.getShardingItemParameters() == null) { throw new SaturnJobConsoleException("对于本地模式作业,分片参数必填。"); } else { String[] split = jobConfig.getShardingItemParameters().split(","); boolean includeXing = false; for (String tmp : split) { String[] split2 = tmp.split("="); if ("*".equalsIgnoreCase(split2[0].trim())) { includeXing = true; break; } } if (!includeXing) { throw new SaturnJobConsoleException("对于本地模式作业,分片参数必须包含如*=xx。"); } } } else { // 分片参数不能小于分片总数 if (jobConfig.getShardingTotalCount() == null || jobConfig.getShardingTotalCount() < 1) { throw new SaturnJobConsoleException("分片数不能为空,并且不能小于1"); } if (jobConfig.getShardingTotalCount() > 0) { if (jobConfig.getShardingItemParameters() == null || jobConfig.getShardingItemParameters().trim().isEmpty() || jobConfig.getShardingItemParameters().split(",").length < jobConfig .getShardingTotalCount()) { throw new SaturnJobConsoleException("分片参数不能小于分片总数"); } } } // 不能添加系统作业 if(jobConfig.getJobMode() != null && jobConfig.getJobMode().startsWith(JobMode.SYSTEM_PREFIX)) { throw new SaturnJobConsoleException("作业模式有误,不能添加系统作业"); } } @RequestMapping(value = "batchAddJobs", method = RequestMethod.POST) public RequestResult batchAddJobs(MultipartHttpServletRequest request) { RequestResult result = new RequestResult(); int successCount = 0; int failCount = 0; String failMessage = ""; try { Iterator<String> fileNames = request.getFileNames(); MultipartFile file = null; while (fileNames.hasNext()) { if (file != null) { result.setSuccess(false); result.setMessage("仅支持单文件导入"); return result; } file = request.getFile(fileNames.next()); } if (file == null) { result.setSuccess(false); result.setMessage("请选择导入的文件"); return result; } String originalFilename = file.getOriginalFilename(); if(originalFilename == null || !originalFilename.endsWith(".xls")) { result.setSuccess(false); result.setMessage("仅支持.xls文件导入"); return result; } Workbook workbook = Workbook.getWorkbook(file.getInputStream()); Sheet[] sheets = workbook.getSheets(); List<JobConfig> jobConfigList = new ArrayList<>(); // 第一行为配置项提示,从第二行开始为作业配置信息 // 先获取数据并检测内容格式的正确性 for (int i = 0; i < sheets.length; i++) { Sheet sheet = sheets[i]; int rows = sheet.getRows(); for (int row = 1; row < rows; row++) { Cell[] rowCells = sheet.getRow(row); // 如果这一行的表格全为空,则跳过这一行。 if(!isBlankRow(rowCells)) { jobConfigList.add(convertJobConfig(i + 1, row + 1, rowCells)); } } } // 再进行添加 for (JobConfig jobConfig : jobConfigList) { RequestResult addJobResult = executorService.addJobs(jobConfig); if (addJobResult.isSuccess()) { successCount++; } else { failCount++; failMessage += " [" + addJobResult.getMessage() + "]"; } } } catch (SaturnJobConsoleException e) { result.setSuccess(false); result.setMessage("导入失败," + e.getMessage()); return result; } catch (Exception e) { result.setSuccess(false); result.setMessage("导入失败,错误信息:" + e.toString()); return result; } result.setSuccess(true); if (failCount > 0) { result.setMessage("共导入" + successCount + "个作业,忽略" + failCount + "个。错误信息:" + failMessage); } else { result.setMessage("共导入" + successCount + "个作业,忽略0个"); } return result; } private boolean isBlankRow(Cell[] rowCells) { for(int i=0; i<rowCells.length; i++) { if(!CellType.EMPTY.equals(rowCells[i].getType())) { return false; } } return true; } private String createExceptionMessage(int sheetNumber, int rowNumber, int columnNumber, String message) { return "内容格式有误,错误发生在表格页:" + sheetNumber + ",行号:" + rowNumber + ",列号:" + columnNumber + ",错误信息:" + message; } private JobConfig convertJobConfig(int sheetNumber, int rowNumber, Cell[] rowCells) throws SaturnJobConsoleException { JobConfig jobConfig = new JobConfig(); String jobName = getContents(rowCells, 0); if (jobName == null || jobName.trim().isEmpty()) { throw new SaturnJobConsoleException(createExceptionMessage(sheetNumber, rowNumber, 1, "作业名必填。")); } if(!jobName.matches("[0-9a-zA-Z_]*")) { throw new SaturnJobConsoleException(createExceptionMessage(sheetNumber, rowNumber, 1, "作业名只允许包含:数字0-9、小写字符a-z、大写字符A-Z、下划线_。")); } jobConfig.setJobName(jobName); String jobType = getContents(rowCells, 1); if (jobType == null || jobType.trim().isEmpty()) { throw new SaturnJobConsoleException(createExceptionMessage(sheetNumber, rowNumber, 2, "作业类型必填。")); } if (JobBriefInfo.JobType.getJobType(jobType).equals(JobBriefInfo.JobType.UNKOWN_JOB)) { throw new SaturnJobConsoleException(createExceptionMessage(sheetNumber, rowNumber, 2, "作业类型未知。")); } if (JobBriefInfo.JobType.getJobType(jobType).equals(JobBriefInfo.JobType.VSHELL) && jobDimensionService.isNewSaturn("1.1.2") != 2) { throw new SaturnJobConsoleException(createExceptionMessage(sheetNumber, rowNumber, 2, "Shell消息作业不能导入到包含1.1.2以下版本Executor所在的域。")); } jobConfig.setJobType(jobType); String jobClass = getContents(rowCells, 2); if (jobType.equals(JobBriefInfo.JobType.JAVA_JOB.name()) || jobType.equals(JobBriefInfo.JobType.MSG_JOB.name())) { if (jobClass == null || jobClass.trim().isEmpty()) { throw new SaturnJobConsoleException(createExceptionMessage(sheetNumber, rowNumber, 3, "对于JAVA/MSG作业,作业实现类必填。")); } } jobConfig.setJobClass(jobClass); String cron = getContents(rowCells, 3); if (jobType.equals(JobBriefInfo.JobType.JAVA_JOB.name()) || jobType.equals(JobBriefInfo.JobType.SHELL_JOB.name())) { if (cron == null || cron.trim().isEmpty()) { throw new SaturnJobConsoleException(createExceptionMessage(sheetNumber, rowNumber, 4, "对于JAVA/SHELL作业,cron表达式必填。")); } try { CronExpression.validateExpression(cron); } catch (ParseException e) { throw new SaturnJobConsoleException(createExceptionMessage(sheetNumber, rowNumber, 4, "cron表达式语法有误," + e.toString())); } } else { cron = "";// 其他类型的不需要持久化保存cron表达式 } jobConfig.setCron(cron); jobConfig.setDescription(getContents(rowCells, 4)); jobConfig.setLocalMode(Boolean.valueOf(getContents(rowCells, 5))); int shardingTotalCount = 1; if(jobConfig.getLocalMode()) { jobConfig.setShardingTotalCount(shardingTotalCount); } else { String tmp = getContents(rowCells, 6); if (tmp != null) { try { shardingTotalCount = Integer.parseInt(tmp); } catch (NumberFormatException e) { throw new SaturnJobConsoleException(createExceptionMessage(sheetNumber, rowNumber, 7, "分片数有误," + e.toString())); } } else { throw new SaturnJobConsoleException(createExceptionMessage(sheetNumber, rowNumber, 7, "分片数必填")); } if(shardingTotalCount < 1) { throw new SaturnJobConsoleException(createExceptionMessage(sheetNumber, rowNumber, 7, "分片数不能小于1")); } jobConfig.setShardingTotalCount(shardingTotalCount); } int timeoutSeconds = 0; try { String tmp = getContents(rowCells, 7); if (tmp != null && !tmp.trim().isEmpty()) { timeoutSeconds = Integer.parseInt(tmp.trim()); } } catch (NumberFormatException e) { throw new SaturnJobConsoleException(createExceptionMessage(sheetNumber, rowNumber, 8, "超时(Kill线程/进程)时间有误," + e.toString())); } jobConfig.setTimeoutSeconds(timeoutSeconds); jobConfig.setJobParameter(getContents(rowCells, 8)); String shardingItemParameters = getContents(rowCells, 9); if(jobConfig.getLocalMode()) { if(shardingItemParameters == null) { throw new SaturnJobConsoleException(createExceptionMessage(sheetNumber, rowNumber, 10, "对于本地模式作业,分片参数必填。")); } else { String[] split = shardingItemParameters.split(","); boolean includeXing = false; for(String tmp : split) { String[] split2 = tmp.split("="); if("*".equalsIgnoreCase(split2[0].trim())) { includeXing = true; break; } } if(!includeXing) { throw new SaturnJobConsoleException(createExceptionMessage(sheetNumber, rowNumber, 10, "对于本地模式作业,分片参数必须包含如*=xx。")); } } } else if (shardingTotalCount > 0) { if (shardingItemParameters == null || shardingItemParameters.trim().isEmpty() || shardingItemParameters.split(",").length < shardingTotalCount) { throw new SaturnJobConsoleException(createExceptionMessage(sheetNumber, rowNumber, 10, "分片参数不能小于分片总数。")); } } jobConfig.setShardingItemParameters(shardingItemParameters); jobConfig.setQueueName(getContents(rowCells, 10)); jobConfig.setChannelName(getContents(rowCells, 11)); jobConfig.setPreferList(getContents(rowCells, 12)); jobConfig.setUseDispreferList(!Boolean.valueOf(getContents(rowCells, 13))); int processCountIntervalSeconds = 300; try { String tmp = getContents(rowCells, 14); if (tmp != null && !tmp.trim().isEmpty()) { processCountIntervalSeconds = Integer.parseInt(tmp.trim()); } } catch (NumberFormatException e) { throw new SaturnJobConsoleException(createExceptionMessage(sheetNumber, rowNumber, 15, "统计处理数据量的间隔秒数有误," + e.toString())); } jobConfig.setProcessCountIntervalSeconds(processCountIntervalSeconds); int loadLevel = 1; try { String tmp = getContents(rowCells, 15); if (tmp != null && !tmp.trim().isEmpty()) { loadLevel = Integer.parseInt(tmp.trim()); } } catch (NumberFormatException e) { throw new SaturnJobConsoleException(createExceptionMessage(sheetNumber, rowNumber, 16, "负荷有误," + e.toString())); } jobConfig.setLoadLevel(loadLevel); jobConfig.setShowNormalLog(Boolean.valueOf(getContents(rowCells, 16))); jobConfig.setPausePeriodDate(getContents(rowCells, 17)); jobConfig.setPausePeriodTime(getContents(rowCells, 18)); jobConfig.setUseSerial(Boolean.valueOf(getContents(rowCells, 19))); int jobDegree = 0; try { String tmp = getContents(rowCells, 20); if (tmp != null && !tmp.trim().isEmpty()) { jobDegree = Integer.parseInt(tmp.trim()); } } catch (NumberFormatException e) { throw new SaturnJobConsoleException(createExceptionMessage(sheetNumber, rowNumber, 21, "作业重要等级有误," + e.toString())); } jobConfig.setJobDegree(jobDegree); // 对于定时作业,默认为true;对于消息作业,默认为false boolean enabledReport; String enabledReportStr = getContents(rowCells, 21); if(enabledReportStr == null || enabledReportStr.trim().isEmpty()) { if (jobType.equals(JobBriefInfo.JobType.JAVA_JOB.name()) || jobType.equals(JobBriefInfo.JobType.SHELL_JOB.name())) { enabledReport = true; } else { enabledReport = false; } } else { enabledReport = Boolean.valueOf(enabledReportStr); } jobConfig.setEnabledReport(enabledReport); String jobMode = getContents(rowCells, 22);; if(jobMode != null && jobMode.startsWith(JobMode.SYSTEM_PREFIX)) { throw new SaturnJobConsoleException(createExceptionMessage(sheetNumber, rowNumber, 23, "作业模式有误,不能添加系统作业")); } jobConfig.setJobMode(jobMode); String dependencies = getContents(rowCells, 23);; if(dependencies != null && !dependencies.matches("[0-9a-zA-Z_,]*")) { throw new SaturnJobConsoleException(createExceptionMessage(sheetNumber, rowNumber, 24, "依赖的作业只允许包含:数字0-9、小写字符a-z、大写字符A-Z、下划线_、英文逗号,")); } jobConfig.setDependencies(dependencies); jobConfig.setGroups(getContents(rowCells, 24)); int timeout4AlarmSeconds = 0; try { String tmp = getContents(rowCells, 25); if (tmp != null && !tmp.trim().isEmpty()) { timeout4AlarmSeconds = Integer.parseInt(tmp.trim()); } } catch (NumberFormatException e) { throw new SaturnJobConsoleException(createExceptionMessage(sheetNumber, rowNumber, 26, "超时(告警)时间有误," + e.toString())); } jobConfig.setTimeout4AlarmSeconds(timeout4AlarmSeconds); String timeZone = getContents(rowCells, 26); if(timeZone == null || timeZone.trim().length() == 0) { timeZone = SaturnConstants.TIME_ZONE_ID_DEFAULT; } else { timeZone = timeZone.trim(); if(!SaturnConstants.TIME_ZONE_IDS.contains(timeZone)) { throw new SaturnJobConsoleException(createExceptionMessage(sheetNumber, rowNumber, 27, "时区有误")); } } jobConfig.setTimeZone(timeZone); return jobConfig; } private String getContents(Cell[] rowCell, int column) { if (rowCell.length > column) { return rowCell[column].getContents(); } return null; } @RequestMapping(value = "exportJob") public void exportJob(HttpServletRequest request, HttpServletResponse response) throws IOException, WriteException { File exportJobFile = null; try { exportJobFile = executorService.getExportJobFile(); String currentTime = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date()); String fileName = getNamespace() + "_allJobs_" + currentTime + ".xls"; response.setContentType("application/octet-stream"); response.setHeader("Content-disposition", "attachment; filename=" + new String(fileName.getBytes("UTF-8"), "ISO8859-1")); BufferedInputStream bis = new BufferedInputStream(new FileInputStream(exportJobFile)); BufferedOutputStream bos = new BufferedOutputStream(response.getOutputStream()); byte[] buff = new byte[2048]; int bytesRead; while (-1 != (bytesRead = bis.read(buff, 0, buff.length))) { bos.write(buff, 0, bytesRead); } bis.close(); bos.close(); } catch (SaturnJobConsoleException e) { printErrorToResponse("导出全域作业出错:" + e.toString(), response); return; } finally { if(exportJobFile != null) { exportJobFile.delete(); } } } private void printErrorToResponse(String errorMsg, HttpServletResponse response) throws IOException { response.setContentType("text/html; charset=utf-8"); StringBuilder msg = new StringBuilder() .append("<script language='javascript'>") .append("alert(\"") .append(errorMsg.replaceAll("\"", "\\\"")) .append("\");") .append("history.back();") .append("</script>"); response.getOutputStream().print(new String(msg.toString().getBytes("UTF-8"), "ISO8859-1")); } @RequestMapping(value = "shardAllAtOnce", method = RequestMethod.POST) public RequestResult shardAllAtOnce(String nns,HttpServletRequest request,HttpSession httpSession) { RequestResult requestResult = new RequestResult(); LOGGER.info("[tries to sharding all at once.]"); try { requestResult = executorService.shardAllAtOnce(); } catch (SaturnJobConsoleException e) { requestResult.setSuccess(false); requestResult.setMessage(e.getMessage()); } catch (Throwable t) { requestResult.setSuccess(false); requestResult.setMessage(t.toString()); LOGGER.error("shardingAllAtOnce exception:",t); } return requestResult; } }