Merge remote-tracking branch 'origin/dev' into dev

This commit is contained in:
lijiahangmax
2024-04-06 23:12:38 +08:00
21 changed files with 495 additions and 143 deletions

View File

@@ -13,6 +13,8 @@ public interface ErrorMessage {
String PARAM_MISSING = "参数不能为空"; String PARAM_MISSING = "参数不能为空";
String PARAM_ERROR = "参数错误";
String ID_MISSING = "id 不能为空"; String ID_MISSING = "id 不能为空";
String INVALID_PARAM = "参数验证失败"; String INVALID_PARAM = "参数验证失败";
@@ -91,4 +93,6 @@ public interface ErrorMessage {
String FILE_READ_ERROR = "文件读取失败"; String FILE_READ_ERROR = "文件读取失败";
String PLEASE_CHECK_HOST_SSH = "请检查主机 {} 是否存在/权限/SSH配置";
} }

View File

@@ -1,9 +1,10 @@
package com.orion.ops.framework.job.core.utils; package com.orion.ops.framework.job.core.utils;
import com.orion.lang.utils.Exceptions; import com.orion.lang.utils.Exceptions;
import com.orion.lang.utils.Objects1;
import com.orion.lang.utils.collect.Maps; import com.orion.lang.utils.collect.Maps;
import com.orion.ops.framework.common.constant.FieldConst;
import org.quartz.*; import org.quartz.*;
import org.quartz.spi.MutableTrigger;
import java.util.Map; import java.util.Map;
@@ -16,6 +17,10 @@ import java.util.Map;
*/ */
public class QuartzUtils { public class QuartzUtils {
private static final String JOB_PREFIX = "Job_";
private static final String TRIGGER_PREFIX = "Trigger_";
private static Scheduler scheduler; private static Scheduler scheduler;
private QuartzUtils() { private QuartzUtils() {
@@ -24,39 +29,48 @@ public class QuartzUtils {
/** /**
* 添加任务 * 添加任务
* *
* @param name name * @param type type
* @param group group * @param key key
* @param cron cron * @param cron cron
* @param desc desc
* @param jobClass jobClass * @param jobClass jobClass
*/ */
public static void addJob(String name, String group, public static void addJob(String type, Object key,
String cron, Class<? extends Job> jobClass) { String cron, String desc,
QuartzUtils.addJob(name, group, Class<? extends Job> jobClass) {
cron, jobClass, QuartzUtils.addJob(type, key,
Maps.empty()); cron, desc,
jobClass, Maps.newMap());
} }
/** /**
* 添加任务 * 添加任务
* *
* @param name name * @param type type
* @param group group * @param key key
* @param cron cron * @param cron cron
* @param desc desc
* @param jobClass jobClass * @param jobClass jobClass
* @param params params * @param params params
*/ */
public static void addJob(String name, String group, public static void addJob(String type, Object key,
String cron, Class<? extends Job> jobClass, String cron, String desc,
Map<Object, Object> params) { Class<? extends Job> jobClass,
Map<String, Object> params) {
params.put(FieldConst.KEY, key);
// 生成 job // 生成 job
JobDetail jobDetail = JobBuilder.newJob(jobClass) JobDetail jobDetail = JobBuilder.newJob(jobClass)
.withIdentity(name, group) .withDescription(desc)
.withIdentity(getJobKey(type, key))
.usingJobData(new JobDataMap(params)) .usingJobData(new JobDataMap(params))
.storeDurably() .storeDurably()
.build(); .build();
// 生成触发器 // 生成触发器
MutableTrigger trigger = CronScheduleBuilder.cronSchedule(cron) Trigger trigger = TriggerBuilder.newTrigger()
.withMisfireHandlingInstructionDoNothing() .withIdentity(getTriggerKey(type, key))
.withSchedule(CronScheduleBuilder
.cronSchedule(cron)
.withMisfireHandlingInstructionIgnoreMisfires())
.build(); .build();
QuartzUtils.addJob(jobDetail, trigger); QuartzUtils.addJob(jobDetail, trigger);
} }
@@ -71,66 +85,100 @@ public class QuartzUtils {
try { try {
scheduler.scheduleJob(jobDetail, trigger); scheduler.scheduleJob(jobDetail, trigger);
} catch (Exception e) { } catch (Exception e) {
throw Exceptions.task(); throw Exceptions.task(e);
} }
} }
/** /**
* 删除任务 * 删除任务
* *
* @param name name * @param type type
* @param group group * @param key key
*/ */
public static void deleteJob(String name, String group) { public static void deleteJob(String type, Object key) {
try { try {
scheduler.deleteJob(new JobKey(name, group)); scheduler.deleteJob(getJobKey(type, key));
} catch (Exception e) { } catch (Exception e) {
throw Exceptions.task(); throw Exceptions.task(e);
} }
} }
/** /**
* 暂停任务 * 暂停任务
* *
* @param name name * @param type type
* @param group group * @param key key
*/ */
public static void pauseJob(String name, String group) { public static void pauseJob(String type, Object key) {
try { try {
scheduler.pauseJob(new JobKey(name, group)); scheduler.pauseJob(getJobKey(type, key));
} catch (Exception e) { } catch (Exception e) {
throw Exceptions.task(); throw Exceptions.task(e);
} }
} }
/** /**
* 恢复任务 * 恢复任务
* *
* @param name name * @param type type
* @param group group * @param key key
*/ */
public static void resumeJob(String name, String group) { public static void resumeJob(String type, Object key) {
try { try {
scheduler.resumeJob(new JobKey(name, group)); scheduler.resumeJob(getJobKey(type, key));
} catch (Exception e) { } catch (Exception e) {
throw Exceptions.task(); throw Exceptions.task(e);
} }
} }
/** /**
* 立即执行任务 * 立即执行任务
* *
* @param name name * @param type type
* @param group group * @param key key
*/ */
public static void triggerJob(String name, String group) { public static void triggerJob(String type, Object key) {
triggerJob(type, key, Maps.newMap());
}
/**
* 立即执行任务
*
* @param type type
* @param key key
* @param params params
*/
public static void triggerJob(String type, Object key, Map<String, Object> params) {
try { try {
scheduler.triggerJob(new JobKey(name, group)); params.put(FieldConst.KEY, key);
scheduler.triggerJob(getJobKey(type, key), new JobDataMap(params));
} catch (Exception e) { } catch (Exception e) {
throw Exceptions.task(); throw Exceptions.task(e);
} }
} }
/**
* 获取 jobKey
*
* @param type type
* @param key key
* @return jobKey
*/
private static JobKey getJobKey(String type, Object key) {
return new JobKey(JOB_PREFIX + type + "_" + Objects1.toString(key), JOB_PREFIX + type);
}
/**
* 获取 triggerKey
*
* @param type type
* @param key key
* @return triggerKey
*/
private static TriggerKey getTriggerKey(String type, Object key) {
return new TriggerKey(TRIGGER_PREFIX + type + "_" + Objects1.toString(key), TRIGGER_PREFIX + type);
}
/** /**
* 设置调度器 * 设置调度器
* *

View File

@@ -243,6 +243,7 @@ public class GlobalExceptionHandler {
return ErrorCode.INTERNAL_SERVER_ERROR.wrapper(); return ErrorCode.INTERNAL_SERVER_ERROR.wrapper();
} }
// TODO kit
@ExceptionHandler(value = ParseCronException.class) @ExceptionHandler(value = ParseCronException.class)
public HttpWrapper<?> parseCronExceptionHandler(ParseCronException ex) { public HttpWrapper<?> parseCronExceptionHandler(ParseCronException ex) {
log.error("parseCronExceptionHandler", ex); log.error("parseCronExceptionHandler", ex);

View File

@@ -68,7 +68,7 @@ spring:
job-store-type: JDBC job-store-type: JDBC
overwrite-existing-jobs: false overwrite-existing-jobs: false
jdbc: jdbc:
initialize-schema: ALWAYS initialize-schema: NEVER
properties: properties:
org: org:
quartz: quartz:

View File

@@ -4,13 +4,12 @@ Content-Type: application/json
Authorization: {{token}} Authorization: {{token}}
{ {
"name": "", "name": "测试 1",
"expression": "", "expression": "0 */3 * * * ?",
"timeout": "", "timeout": "0",
"command": "", "command": "echo 123",
"parameterSchema": "", "parameterSchema": "[]",
"status": "", "hostIdList": [1]
"recentLogId": ""
} }
@@ -19,15 +18,27 @@ PUT {{baseUrl}}/asset/exec-job/update
Content-Type: application/json Content-Type: application/json
Authorization: {{token}} Authorization: {{token}}
{
"id": 5,
"name": "测试 1",
"expression": "0 */1 * * * ?",
"timeout": "0",
"command": "echo 123",
"parameterSchema": "[]",
"hostIdList": [
1
]
}
### 更新计划执行任务状态
PUT {{baseUrl}}/asset/exec-job/update-status
Content-Type: application/json
Authorization: {{token}}
{ {
"id": "", "id": "",
"name": "", "status": ""
"expression": "",
"timeout": "",
"command": "",
"parameterSchema": "",
"status": "",
"recentLogId": ""
} }
@@ -36,28 +47,6 @@ GET {{baseUrl}}/asset/exec-job/get?id=1
Authorization: {{token}} Authorization: {{token}}
### 批量查询计划执行任务
GET {{baseUrl}}/asset/exec-job/batch-get?idList=1,2,3
Authorization: {{token}}
### 查询全部计划执行任务
POST {{baseUrl}}/asset/exec-job/list
Content-Type: application/json
Authorization: {{token}}
{
"id": "",
"name": "",
"expression": "",
"timeout": "",
"command": "",
"parameterSchema": "",
"status": "",
"recentLogId": ""
}
### 分页查询计划执行任务 ### 分页查询计划执行任务
POST {{baseUrl}}/asset/exec-job/query POST {{baseUrl}}/asset/exec-job/query
Content-Type: application/json Content-Type: application/json
@@ -68,12 +57,8 @@ Authorization: {{token}}
"limit": 10, "limit": 10,
"id": "", "id": "",
"name": "", "name": "",
"expression": "",
"timeout": "",
"command": "", "command": "",
"parameterSchema": "", "status": ""
"status": "",
"recentLogId": ""
} }
@@ -82,8 +67,4 @@ DELETE {{baseUrl}}/asset/exec-job/delete?id=1
Authorization: {{token}} Authorization: {{token}}
### 批量删除计划执行任务
DELETE {{baseUrl}}/asset/exec-job/batch-delete?idList=1,2,3
Authorization: {{token}}
### ###

View File

@@ -39,13 +39,6 @@ import javax.annotation.Resource;
@SuppressWarnings({"ELValidationInJSP", "SpringElInspection"}) @SuppressWarnings({"ELValidationInJSP", "SpringElInspection"})
public class ExecJobController { public class ExecJobController {
// TODO EXEC_seq
// todo 测试一下添加失败 操作日志是怎么
// TODO 操作日志 菜单
// TODO 手动执行
// TODO NEXT time
@Resource @Resource
private ExecJobService execJobService; private ExecJobService execJobService;

View File

@@ -1,9 +1,9 @@
package com.orion.ops.module.asset.dao; package com.orion.ops.module.asset.dao;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.orion.ops.framework.mybatis.core.mapper.IMapper; import com.orion.ops.framework.mybatis.core.mapper.IMapper;
import com.orion.ops.module.asset.entity.domain.ExecJobDO; import com.orion.ops.module.asset.entity.domain.ExecJobDO;
import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
/** /**
* 计划执行任务 Mapper 接口 * 计划执行任务 Mapper 接口
@@ -16,21 +16,10 @@ import org.apache.ibatis.annotations.Mapper;
public interface ExecJobDAO extends IMapper<ExecJobDO> { public interface ExecJobDAO extends IMapper<ExecJobDO> {
/** /**
* 获取查询条件 * 自增 exec_seq
* *
* @param entity entity * @param id id
* @return 查询条件
*/ */
default LambdaQueryWrapper<ExecJobDO> queryCondition(ExecJobDO entity) { void incrExecSeq(@Param("id") Long id);
return this.wrapper()
.eq(ExecJobDO::getId, entity.getId())
.eq(ExecJobDO::getName, entity.getName())
.eq(ExecJobDO::getExpression, entity.getExpression())
.eq(ExecJobDO::getTimeout, entity.getTimeout())
.eq(ExecJobDO::getCommand, entity.getCommand())
.eq(ExecJobDO::getParameterSchema, entity.getParameterSchema())
.eq(ExecJobDO::getStatus, entity.getStatus())
.eq(ExecJobDO::getRecentLogId, entity.getRecentLogId());
}
} }

View File

@@ -1,4 +1,4 @@
package com.orion.ops.module.asset.define.operator; package com.orion.ops.module.asset.define.operator;
import com.orion.ops.framework.biz.operator.log.core.annotation.Module; import com.orion.ops.framework.biz.operator.log.core.annotation.Module;
import com.orion.ops.framework.biz.operator.log.core.factory.InitializingOperatorTypes; import com.orion.ops.framework.biz.operator.log.core.factory.InitializingOperatorTypes;
@@ -30,9 +30,9 @@ public class ExecJobOperatorType extends InitializingOperatorTypes {
public OperatorType[] types() { public OperatorType[] types() {
return new OperatorType[]{ return new OperatorType[]{
new OperatorType(L, CREATE, "创建计划任务 <sb>${name}</sb>"), new OperatorType(L, CREATE, "创建计划任务 <sb>${name}</sb>"),
new OperatorType(M, UPDATE, "更新计划任务 <sb>${before}</sb>"),
new OperatorType(M, UPDATE_STATUS, "<sb>${statusName}</sb>计划任务 <sb>${name}</sb>"),
new OperatorType(M, EXEC, "手动执行计划任务 <sb>${name}</sb>"), new OperatorType(M, EXEC, "手动执行计划任务 <sb>${name}</sb>"),
new OperatorType(M, UPDATE, "更新计划任务 <sb>${name}</sb>"),
new OperatorType(M, UPDATE_STATUS, "更新计划任务状态 <sb>${name}</sb> <sb>${statusName}</sb>"),
new OperatorType(H, DELETE, "删除计划任务 <sb>${name}</sb>"), new OperatorType(H, DELETE, "删除计划任务 <sb>${name}</sb>"),
}; };
} }

View File

@@ -34,4 +34,7 @@ public class ExecJobQueryRequest extends PageRequest {
@Schema(description = "启用状态 0禁用 1启用") @Schema(description = "启用状态 0禁用 1启用")
private Integer status; private Integer status;
@Schema(description = "是否查询最近执行任务")
private Boolean queryRecentLog;
} }

View File

@@ -8,6 +8,7 @@ import lombok.NoArgsConstructor;
import java.io.Serializable; import java.io.Serializable;
import java.util.Date; import java.util.Date;
import java.util.List;
/** /**
* 计划执行任务 视图响应对象 * 计划执行任务 视图响应对象
@@ -31,9 +32,6 @@ public class ExecJobVO implements Serializable {
@Schema(description = "任务名称") @Schema(description = "任务名称")
private String name; private String name;
@Schema(description = "执行序列")
private Integer execSeq;
@Schema(description = "cron 表达式") @Schema(description = "cron 表达式")
private String expression; private String expression;
@@ -52,16 +50,19 @@ public class ExecJobVO implements Serializable {
@Schema(description = "最近执行id") @Schema(description = "最近执行id")
private Long recentLogId; private Long recentLogId;
@Schema(description = "最近执行状态")
private String recentExecStatus;
@Schema(description = "最近执行时间")
private Date recentExecTime;
@Schema(description = "创建时间") @Schema(description = "创建时间")
private Date createTime; private Date createTime;
@Schema(description = "修改时间") @Schema(description = "修改时间")
private Date updateTime; private Date updateTime;
@Schema(description = "创建人") @Schema(description = "执行主机")
private String creator; private List<Long> hostIdList;
@Schema(description = "修改人")
private String updater;
} }

View File

@@ -17,17 +17,19 @@ public enum ExecJobStatusEnum {
/** /**
* 停用 * 停用
*/ */
DISABLED(0), DISABLED(0, "停用"),
/** /**
* 启用 * 启用
*/ */
ENABLED(1), ENABLED(1, "启用"),
; ;
private final Integer status; private final Integer status;
private final String statusName;
public static ExecJobStatusEnum of(Integer status) { public static ExecJobStatusEnum of(Integer status) {
if (status == null) { if (status == null) {
return null; return null;

View File

@@ -56,6 +56,14 @@ public interface ExecJobService {
*/ */
DataGrid<ExecJobVO> getExecJobPage(ExecJobQueryRequest request); DataGrid<ExecJobVO> getExecJobPage(ExecJobQueryRequest request);
/**
* 获取下一个执行序列
*
* @param id id
* @return seq
*/
Integer getNextExecSeq(Long id);
/** /**
* 删除计划执行任务 * 删除计划执行任务
* *

View File

@@ -1,6 +1,5 @@
package com.orion.ops.module.asset.service.impl; package com.orion.ops.module.asset.service.impl;
import com.orion.lang.utils.collect.Lists;
import com.orion.ops.module.asset.dao.ExecJobHostDAO; import com.orion.ops.module.asset.dao.ExecJobHostDAO;
import com.orion.ops.module.asset.entity.domain.ExecJobHostDO; import com.orion.ops.module.asset.entity.domain.ExecJobHostDO;
import com.orion.ops.module.asset.service.ExecJobHostService; import com.orion.ops.module.asset.service.ExecJobHostService;
@@ -30,16 +29,14 @@ public class ExecJobHostServiceImpl implements ExecJobHostService {
log.info("ExecJobHostService.setHostIdByJobId jobId: {}, hostIdList: {}", jobId, hostIdList); log.info("ExecJobHostService.setHostIdByJobId jobId: {}, hostIdList: {}", jobId, hostIdList);
// 删除 // 删除
execJobHostDAO.deleteByJobId(jobId); execJobHostDAO.deleteByJobId(jobId);
// 如果不为空则重新插入 // 重新插入
if (!Lists.isEmpty(hostIdList)) { List<ExecJobHostDO> rows = hostIdList.stream()
List<ExecJobHostDO> rows = hostIdList.stream() .map(s -> ExecJobHostDO.builder()
.map(s -> ExecJobHostDO.builder() .hostId(s)
.hostId(s) .jobId(jobId)
.jobId(jobId) .build())
.build()) .collect(Collectors.toList());
.collect(Collectors.toList()); execJobHostDAO.insertBatch(rows);
execJobHostDAO.insertBatch(rows);
}
} }
@Override @Override

View File

@@ -3,21 +3,43 @@ package com.orion.ops.module.asset.service.impl;
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.orion.lang.define.wrapper.DataGrid; import com.orion.lang.define.wrapper.DataGrid;
import com.orion.lang.utils.Booleans;
import com.orion.lang.utils.Strings;
import com.orion.lang.utils.time.Dates;
import com.orion.lang.utils.time.cron.Cron;
import com.orion.ops.framework.biz.operator.log.core.utils.OperatorLogs;
import com.orion.ops.framework.common.constant.ErrorMessage; import com.orion.ops.framework.common.constant.ErrorMessage;
import com.orion.ops.framework.common.constant.FieldConst;
import com.orion.ops.framework.common.utils.Valid; import com.orion.ops.framework.common.utils.Valid;
import com.orion.ops.framework.job.core.utils.QuartzUtils;
import com.orion.ops.framework.security.core.utils.SecurityUtils;
import com.orion.ops.module.asset.convert.ExecJobConvert; import com.orion.ops.module.asset.convert.ExecJobConvert;
import com.orion.ops.module.asset.dao.ExecJobDAO; import com.orion.ops.module.asset.dao.ExecJobDAO;
import com.orion.ops.module.asset.dao.ExecLogDAO;
import com.orion.ops.module.asset.entity.domain.ExecJobDO; import com.orion.ops.module.asset.entity.domain.ExecJobDO;
import com.orion.ops.module.asset.entity.domain.ExecLogDO;
import com.orion.ops.module.asset.entity.request.exec.ExecJobCreateRequest; import com.orion.ops.module.asset.entity.request.exec.ExecJobCreateRequest;
import com.orion.ops.module.asset.entity.request.exec.ExecJobQueryRequest; import com.orion.ops.module.asset.entity.request.exec.ExecJobQueryRequest;
import com.orion.ops.module.asset.entity.request.exec.ExecJobUpdateRequest; import com.orion.ops.module.asset.entity.request.exec.ExecJobUpdateRequest;
import com.orion.ops.module.asset.entity.request.exec.ExecJobUpdateStatusRequest; import com.orion.ops.module.asset.entity.request.exec.ExecJobUpdateStatusRequest;
import com.orion.ops.module.asset.entity.vo.ExecJobVO; import com.orion.ops.module.asset.entity.vo.ExecJobVO;
import com.orion.ops.module.asset.enums.ExecJobStatusEnum;
import com.orion.ops.module.asset.enums.HostConfigTypeEnum;
import com.orion.ops.module.asset.service.AssetAuthorizedDataService;
import com.orion.ops.module.asset.service.ExecJobHostService;
import com.orion.ops.module.asset.service.ExecJobService; import com.orion.ops.module.asset.service.ExecJobService;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource; import javax.annotation.Resource;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Function;
import java.util.stream.Collectors;
/** /**
* 计划执行任务 服务实现类 * 计划执行任务 服务实现类
@@ -30,30 +52,57 @@ import javax.annotation.Resource;
@Service @Service
public class ExecJobServiceImpl implements ExecJobService { public class ExecJobServiceImpl implements ExecJobService {
// TODO 测试 SSH 禁用后是什么样子的
// TODO 操作日志 菜单
// TODO 执行日志抽象
// TODO 任务分组
// TODO 手动执行 测试 quartz
private static final String QUARTZ_TYPE = "Exec";
@Resource @Resource
private ExecJobDAO execJobDAO; private ExecJobDAO execJobDAO;
@Resource
private ExecJobHostService execJobHostService;
@Resource
private ExecLogDAO execLogDAO;
@Resource
private AssetAuthorizedDataService assetAuthorizedDataService;
@Override @Override
@Transactional(rollbackFor = Exception.class)
public Long createExecJob(ExecJobCreateRequest request) { public Long createExecJob(ExecJobCreateRequest request) {
log.info("ExecJobService-createExecJob request: {}", JSON.toJSONString(request)); log.info("ExecJobService-createExecJob request: {}", JSON.toJSONString(request));
// 验证表达式是否正确
Cron.of(request.getExpression());
// 转换 // 转换
ExecJobDO record = ExecJobConvert.MAPPER.to(request); ExecJobDO record = ExecJobConvert.MAPPER.to(request);
// 查询数据是否冲突 // 查询数据是否冲突
this.checkExecJobPresent(record); this.checkExecJobPresent(record);
// TODO 查询主机是否存在 // 查询主机是否有权限
// 查询是否有主机权限 this.checkHostPermission(request.getHostIdList());
// 插入 // 插入任务
record.setStatus(ExecJobStatusEnum.ENABLED.getStatus());
int effect = execJobDAO.insert(record); int effect = execJobDAO.insert(record);
Long id = record.getId(); Long id = record.getId();
// TODO 插入主机 // 设置任务主机
execJobHostService.setHostIdByJobId(id, request.getHostIdList());
// 设置 quartz 状态
this.setQuartzJobStatus(record, false, true);
log.info("ExecJobService-createExecJob id: {}, effect: {}", id, effect); log.info("ExecJobService-createExecJob id: {}, effect: {}", id, effect);
return id; return id;
} }
@Override @Override
@Transactional(rollbackFor = Exception.class)
public Integer updateExecJobById(ExecJobUpdateRequest request) { public Integer updateExecJobById(ExecJobUpdateRequest request) {
Long id = Valid.notNull(request.getId(), ErrorMessage.ID_MISSING); Long id = Valid.notNull(request.getId(), ErrorMessage.ID_MISSING);
log.info("ExecJobService-updateExecJobById id: {}, request: {}", id, JSON.toJSONString(request)); log.info("ExecJobService-updateExecJobById id: {}, request: {}", id, JSON.toJSONString(request));
// 验证表达式是否正确
Cron.of(request.getExpression());
// 查询 // 查询
ExecJobDO record = execJobDAO.selectById(id); ExecJobDO record = execJobDAO.selectById(id);
Valid.notNull(record, ErrorMessage.DATA_ABSENT); Valid.notNull(record, ErrorMessage.DATA_ABSENT);
@@ -61,44 +110,120 @@ public class ExecJobServiceImpl implements ExecJobService {
ExecJobDO updateRecord = ExecJobConvert.MAPPER.to(request); ExecJobDO updateRecord = ExecJobConvert.MAPPER.to(request);
// 查询数据是否冲突 // 查询数据是否冲突
this.checkExecJobPresent(updateRecord); this.checkExecJobPresent(updateRecord);
// 更新 // 查询主机是否有权限
this.checkHostPermission(request.getHostIdList());
// 更新任务
int effect = execJobDAO.updateById(updateRecord); int effect = execJobDAO.updateById(updateRecord);
// 设置任务主机
execJobHostService.setHostIdByJobId(id, request.getHostIdList());
// 设置日志参数
OperatorLogs.add(OperatorLogs.BEFORE, record.getName());
// 设置 quartz 状态
this.setQuartzJobStatus(updateRecord, true, ExecJobStatusEnum.ENABLED.getStatus().equals(record.getStatus()));
log.info("ExecJobService-updateExecJobById effect: {}", effect); log.info("ExecJobService-updateExecJobById effect: {}", effect);
return effect; return effect;
} }
@Override @Override
@Transactional(rollbackFor = Exception.class)
public Integer updateExecJobStatus(ExecJobUpdateStatusRequest request) { public Integer updateExecJobStatus(ExecJobUpdateStatusRequest request) {
return null; Long id = request.getId();
ExecJobStatusEnum status = ExecJobStatusEnum.of(request.getStatus());
Valid.notNull(status, ErrorMessage.PARAM_ERROR);
log.info("ExecJobService-updateExecJobStatus id: {}, status: {}", id, status);
// 查询任务
ExecJobDO record = execJobDAO.selectById(id);
Valid.notNull(record, ErrorMessage.DATA_ABSENT);
// 更新状态
ExecJobDO update = new ExecJobDO();
update.setId(id);
update.setStatus(status.getStatus());
int effect = execJobDAO.updateById(update);
// 设置日志参数
OperatorLogs.add(OperatorLogs.NAME, record.getName());
OperatorLogs.add(OperatorLogs.STATUS_NAME, status.getStatusName());
// 设置 quartz 状态
this.setQuartzJobStatus(record, true, ExecJobStatusEnum.ENABLED.equals(status));
return effect;
} }
@Override @Override
public ExecJobVO getExecJobById(Long id) { public ExecJobVO getExecJobById(Long id) {
// 查询 // 查询任务
ExecJobDO record = execJobDAO.selectById(id); ExecJobDO record = execJobDAO.selectById(id);
Valid.notNull(record, ErrorMessage.DATA_ABSENT); Valid.notNull(record, ErrorMessage.DATA_ABSENT);
// 转换 // 转换
return ExecJobConvert.MAPPER.to(record); ExecJobVO vo = ExecJobConvert.MAPPER.to(record);
// 查询任务主机
List<Long> hostIdList = execJobHostService.getHostIdByJobId(id);
vo.setHostIdList(hostIdList);
return vo;
} }
@Override @Override
public DataGrid<ExecJobVO> getExecJobPage(ExecJobQueryRequest request) { public DataGrid<ExecJobVO> getExecJobPage(ExecJobQueryRequest request) {
// 条件 // 条件
LambdaQueryWrapper<ExecJobDO> wrapper = this.buildQueryWrapper(request); LambdaQueryWrapper<ExecJobDO> wrapper = this.buildQueryWrapper(request);
// 查询 // 查询任务
return execJobDAO.of(wrapper) DataGrid<ExecJobVO> dataGrid = execJobDAO.of(wrapper)
.page(request) .page(request)
.dataGrid(ExecJobConvert.MAPPER::to); .dataGrid(ExecJobConvert.MAPPER::to);
if (!Booleans.isTrue(request.getQueryRecentLog())) {
return dataGrid;
}
// 查询最近执行任务
List<Long> logIdList = dataGrid.stream()
.map(ExecJobVO::getRecentLogId)
.filter(Objects::nonNull)
.collect(Collectors.toList());
if (!logIdList.isEmpty()) {
List<ExecLogDO> logList = execLogDAO.selectBatchIds(logIdList);
Map<Long, ExecLogDO> logMap = logList.stream()
.collect(Collectors.toMap(ExecLogDO::getId, Function.identity()));
dataGrid.forEach(s -> {
Long logId = s.getRecentLogId();
if (logId == null) {
return;
}
ExecLogDO execLog = logMap.get(logId);
if (execLog == null) {
return;
}
s.setRecentExecTime(execLog.getStartTime());
s.setRecentExecStatus(execLog.getStatus());
});
}
return dataGrid;
} }
@Override @Override
public Integer getNextExecSeq(Long id) {
// 自增
execJobDAO.incrExecSeq(id);
// 获取
return execJobDAO.of()
.createWrapper()
.select(ExecJobDO::getExecSeq)
.eq(ExecJobDO::getId, id)
.then()
.getOne(ExecJobDO::getExecSeq);
}
@Override
@Transactional(rollbackFor = Exception.class)
public Integer deleteExecJobById(Long id) { public Integer deleteExecJobById(Long id) {
log.info("ExecJobService-deleteExecJobById id: {}", id); log.info("ExecJobService-deleteExecJobById id: {}", id);
// 检查数据是否存在 // 检查数据是否存在
ExecJobDO record = execJobDAO.selectById(id); ExecJobDO record = execJobDAO.selectById(id);
Valid.notNull(record, ErrorMessage.DATA_ABSENT); Valid.notNull(record, ErrorMessage.DATA_ABSENT);
// 删除 // 删除任务
int effect = execJobDAO.deleteById(id); int effect = execJobDAO.deleteById(id);
// 删除任务主机
effect += execJobHostService.deleteByJobId(id);
// 设置日志参数
OperatorLogs.add(OperatorLogs.NAME, record.getName());
// 设置 quartz 状态
this.setQuartzJobStatus(record, true, false);
log.info("ExecJobService-deleteExecJobById id: {}, effect: {}", id, effect); log.info("ExecJobService-deleteExecJobById id: {}, effect: {}", id, effect);
return effect; return effect;
} }
@@ -135,4 +260,48 @@ public class ExecJobServiceImpl implements ExecJobService {
.orderByDesc(ExecJobDO::getId); .orderByDesc(ExecJobDO::getId);
} }
/**
* 设置 quartz 任务状态
*
* @param record record
* @param delete 是否删除
* @param add 是否新增
*/
private void setQuartzJobStatus(ExecJobDO record, boolean delete, boolean add) {
Long id = record.getId();
// 删除 quartz job
if (delete) {
QuartzUtils.deleteJob(QUARTZ_TYPE, id);
}
// FIXME
// 启动 quartz job
if (add) {
QuartzUtils.addJob(QUARTZ_TYPE, id, record.getExpression(), record.getName(), TestJob.class);
}
}
/**
* 检查主机权限
*
* @param hostIdList hostIdList
*/
private void checkHostPermission(List<Long> hostIdList) {
// 查询有权限的主机
List<Long> authorizedHostIdList = assetAuthorizedDataService.getUserAuthorizedHostId(SecurityUtils.getLoginUserId(), HostConfigTypeEnum.SSH);
for (Long hostId : hostIdList) {
Valid.isTrue(authorizedHostIdList.contains(hostId), Strings.format(ErrorMessage.PLEASE_CHECK_HOST_SSH, hostId));
}
}
// FIXME
static class TestJob implements Job {
@Override
public void execute(JobExecutionContext context) {
System.out.println("----------------------");
System.out.println(Dates.current());
System.out.println(context.getMergedJobDataMap().getLong(FieldConst.KEY));
}
}
} }

View File

@@ -21,6 +21,7 @@ import com.orion.ops.module.asset.entity.request.host.HostCreateRequest;
import com.orion.ops.module.asset.entity.request.host.HostQueryRequest; import com.orion.ops.module.asset.entity.request.host.HostQueryRequest;
import com.orion.ops.module.asset.entity.request.host.HostUpdateRequest; import com.orion.ops.module.asset.entity.request.host.HostUpdateRequest;
import com.orion.ops.module.asset.entity.vo.HostVO; import com.orion.ops.module.asset.entity.vo.HostVO;
import com.orion.ops.module.asset.service.ExecJobHostService;
import com.orion.ops.module.asset.service.HostConfigService; import com.orion.ops.module.asset.service.HostConfigService;
import com.orion.ops.module.asset.service.HostService; import com.orion.ops.module.asset.service.HostService;
import com.orion.ops.module.infra.api.DataExtraApi; import com.orion.ops.module.infra.api.DataExtraApi;
@@ -66,6 +67,9 @@ public class HostServiceImpl implements HostService {
@Resource @Resource
private HostConfigService hostConfigService; private HostConfigService hostConfigService;
@Resource
private ExecJobHostService execJobHostService;
@Resource @Resource
private TagRelApi tagRelApi; private TagRelApi tagRelApi;
@@ -210,8 +214,11 @@ public class HostServiceImpl implements HostService {
@Override @Override
@Async("asyncExecutor") @Async("asyncExecutor")
public void deleteHostRelByIdAsync(Long id) { public void deleteHostRelByIdAsync(Long id) {
// 删除配置 log.info("HostService-deleteHostRelByIdAsync id: {}", id);
// 删除主机配置
hostConfigDAO.deleteByHostId(id); hostConfigDAO.deleteByHostId(id);
// 删除计划执行任务主机
execJobHostService.deleteByHostId(id);
// 删除分组 // 删除分组
dataGroupRelApi.deleteByRelId(DataGroupTypeEnum.HOST, id); dataGroupRelApi.deleteByRelId(DataGroupTypeEnum.HOST, id);
// 删除 tag 引用 // 删除 tag 引用

View File

@@ -6,6 +6,7 @@
<resultMap id="BaseResultMap" type="com.orion.ops.module.asset.entity.domain.ExecJobDO"> <resultMap id="BaseResultMap" type="com.orion.ops.module.asset.entity.domain.ExecJobDO">
<id column="id" property="id"/> <id column="id" property="id"/>
<result column="name" property="name"/> <result column="name" property="name"/>
<result column="exec_seq" property="execSeq"/>
<result column="expression" property="expression"/> <result column="expression" property="expression"/>
<result column="timeout" property="timeout"/> <result column="timeout" property="timeout"/>
<result column="command" property="command"/> <result column="command" property="command"/>
@@ -21,7 +22,13 @@
<!-- 通用查询结果列 --> <!-- 通用查询结果列 -->
<sql id="Base_Column_List"> <sql id="Base_Column_List">
id, name, expression, timeout, command, parameter_schema, status, recent_log_id, create_time, update_time, creator, updater, deleted id, name, exec_seq, expression, timeout, command, parameter_schema, status, recent_log_id, create_time, update_time, creator, updater, deleted
</sql> </sql>
<update id="incrExecSeq">
UPDATE exec_job
SET exec_seq = exec_seq + 1
WHERE id = #{id}
</update>
</mapper> </mapper>

View File

@@ -10,6 +10,7 @@
<result column="source" property="source"/> <result column="source" property="source"/>
<result column="source_id" property="sourceId"/> <result column="source_id" property="sourceId"/>
<result column="description" property="description"/> <result column="description" property="description"/>
<result column="exec_seq" property="exec_seq"/>
<result column="command" property="command"/> <result column="command" property="command"/>
<result column="parameter_schema" property="parameterSchema"/> <result column="parameter_schema" property="parameterSchema"/>
<result column="timeout" property="timeout"/> <result column="timeout" property="timeout"/>
@@ -25,7 +26,7 @@
<!-- 通用查询结果列 --> <!-- 通用查询结果列 -->
<sql id="Base_Column_List"> <sql id="Base_Column_List">
id, user_id, username, source, source_id, description, command, parameter_schema, timeout, status, start_time, finish_time, create_time, update_time, creator, updater, deleted id, user_id, username, source, source_id, description, exec_seq, command, parameter_schema, timeout, status, start_time, finish_time, create_time, update_time, creator, updater, deleted
</sql> </sql>
<select id="getExecHistory" resultMap="BaseResultMap"> <select id="getExecHistory" resultMap="BaseResultMap">

View File

@@ -0,0 +1,11 @@
### 获取 cron 下次执行时间
POST {{baseUrl}}/infra/expression/cron-next
Content-Type: application/json
{
"expression": "5 */3 * * * ?",
"times": 2
}
###

View File

@@ -0,0 +1,59 @@
package com.orion.ops.module.infra.controller;
import com.orion.lang.utils.collect.Lists;
import com.orion.lang.utils.time.Dates;
import com.orion.lang.utils.time.cron.Cron;
import com.orion.lang.utils.time.cron.CronSupport;
import com.orion.ops.framework.web.core.annotation.RestWrapper;
import com.orion.ops.module.infra.entity.request.exoression.CronNextRequest;
import com.orion.ops.module.infra.entity.vo.CronNextVO;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.security.PermitAll;
import java.util.List;
import java.util.stream.Collectors;
/**
* 表达式服务
*
* @author Jiahang Li
* @version 1.0.0
* @since 2024/4/2 16:33
*/
@Tag(name = "infra - 表达式服务")
@Slf4j
@Validated
@RestWrapper
@RestController
@RequestMapping("/infra/expression")
@SuppressWarnings({"ELValidationInJSP", "SpringElInspection"})
public class ExpressionController {
@PermitAll
@PostMapping("/cron-next")
@Operation(summary = "获取 cron 下次执行时间")
public CronNextVO getCronNextTime(@Validated @RequestBody CronNextRequest request) {
CronNextVO next = new CronNextVO();
try {
Cron cron = Cron.of(request.getExpression());
List<String> nextTime = CronSupport.getNextTime(cron, request.getTimes())
.stream()
.map(Dates::format)
.collect(Collectors.toList());
next.setNext(nextTime);
next.setValid(true);
} catch (Exception e) {
next.setNext(Lists.empty());
next.setValid(false);
}
return next;
}
}

View File

@@ -0,0 +1,37 @@
package com.orion.ops.module.infra.entity.request.exoression;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.hibernate.validator.constraints.Range;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.io.Serializable;
/**
* cron 下次执行时间请求对象
*
* @author Jiahang Li
* @version 1.0.0
* @since 2024/4/2 16:42
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(name = "CronNextRequest", description = "cron 下次执行时间请求对象")
public class CronNextRequest implements Serializable {
@NotBlank
@Schema(description = "cron 表达式")
private String expression;
@NotNull
@Range(min = 1, max = 100)
@Schema(description = "次数")
private Integer times;
}

View File

@@ -0,0 +1,34 @@
package com.orion.ops.module.infra.entity.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.util.List;
/**
* cron 下次执行时间响应对象
*
* @author Jiahang Li
* @version 1.0.0
* @since 2024/4/2 16:35
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(name = "CronNextVO", description = "cron 下次执行时间响应对象")
public class CronNextVO implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "表达式是否正确")
private Boolean valid;
@Schema(description = "下次执行时间")
private List<String> next;
}