增加计划飞行

This commit is contained in:
sdy 2026-05-28 16:07:16 +08:00
parent 3162f12835
commit 4ce9541397
27 changed files with 987 additions and 265 deletions

View File

@ -65,7 +65,7 @@ public class MediaFileController {
})
@DataFilter(tableAlias = "t")
@RequiresPermissions("bus:media:album")
public Result<PageData<MediaFileDTO>> getHangarAlbum(@Parameter(hidden = true) @RequestParam Map<String, Object> params) {
public Result<PageData<MediaFileDTO>> getAlbum(@Parameter(hidden = true) @RequestParam Map<String, Object> params) {
PageData<MediaFileDTO> page = mediaFileService.getAlbum(params);
return new Result<PageData<MediaFileDTO>>().ok(page);
@ -97,7 +97,7 @@ public class MediaFileController {
@GetMapping("/conditionDownloadMedia")
@RequiresPermissions("bus:media:download")
@Parameters({
@Parameter(name = "hangarSn", description = "机库sn"),
@Parameter(name = "dockSn", description = "机库sn"),
@Parameter(name = "routeId", description = "航线标识"),
@Parameter(name = "startTime", description = "开始时间"),
@Parameter(name = "endTime", description = "结束时间")
@ -113,7 +113,7 @@ public class MediaFileController {
@GetMapping("/oneClickGetDownloadPath")
@RequiresPermissions("bus:media:download")
@Parameters({
@Parameter(name = "hangarSn", description = "机库sn"),
@Parameter(name = "dockSn", description = "机库sn"),
@Parameter(name = "routeId", description = "航线标识"),
@Parameter(name = "startTime", description = "开始时间"),
@Parameter(name = "endTime", description = "结束时间")

View File

@ -91,7 +91,7 @@ public class RouteFlightController {
public Result<Object> flightExecute(@PathVariable String dockSn,
@RequestBody FlightExecute flightExecute) {
return new Result<>().ok(routeFlightService.flightExecute(dockSn, flightExecute));
return new Result<>().ok(routeFlightService.flightExecute(dockSn, flightExecute, true));
}
@LogOperation("空中下发航线")

View File

@ -28,6 +28,8 @@ public interface FlightTaskService extends CrudService<FlightTaskEntity, FlightT
void addTakeoffTask(String taskId, String dockSn);//一键起飞任务
void addJobRouteTask(String taskId, Long jobId);//计划飞行任务
//更新架次完成信息
void updateTaskComplete(String dockSn);

View File

@ -16,7 +16,7 @@ public interface RouteFlightService {
String flightTaskUndo(String dockSn, List<String> taskIds);
//执行航线
String flightExecute(String dockSn, FlightExecute flightExecute);
String flightExecute(String dockSn, FlightExecute flightExecute, Boolean isRecord);
//空中下发航线
String inFlightWaylineDeliver(String dockSn, Long routeId);

View File

@ -22,7 +22,12 @@ import com.multictrl.modules.business.dto.RouteDTO;
import com.multictrl.modules.business.entity.*;
import com.multictrl.modules.business.influxdb.UavReport;
import com.multictrl.modules.business.service.*;
import com.multictrl.modules.job.dao.ScheduleJobDao;
import com.multictrl.modules.job.dto.ScheduleJobDTO;
import com.multictrl.modules.job.entity.ScheduleJobEntity;
import com.multictrl.modules.security.user.SecurityUser;
import com.multictrl.modules.sys.dto.SysUserDTO;
import com.multictrl.modules.sys.service.SysUserService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
@ -43,6 +48,8 @@ public class FlightTaskServiceImpl extends CrudServiceImpl<FlightTaskDao, Flight
private final MediaFileService mediaFileService;
private final DockDeviceDao dockDeviceDao;
private final SrsService srsService;
private final ScheduleJobDao scheduleJobDao;
private final SysUserService userService;
@Override
public QueryWrapper<FlightTaskEntity> getWrapper(Map<String, Object> params) {
@ -111,6 +118,27 @@ public class FlightTaskServiceImpl extends CrudServiceImpl<FlightTaskDao, Flight
baseDao.insert(entity);
}
@Override
public void addJobRouteTask(String taskId, Long jobId) {
FlightTaskEntity entity = new FlightTaskEntity();
entity.setTaskId(taskId);
entity.setTaskType(3);
ScheduleJobEntity jobEntity = scheduleJobDao.getById(jobId);
addDockInfo(entity, jobEntity.getDockSn());
long routeId = Long.parseLong(jobEntity.getParams());
entity.setRouteId(routeId);
RouteDTO route = routeService.getRoute(routeId);
entity.setRouteName(route.getRouteName());
entity.setRouteType(route.getTemplateType());
entity.setRouteDistance(route.getTotalDistance());
entity.setWaypointNum(route.getRouteWaypointList().size());
entity.setDeptId(jobEntity.getDeptId());
entity.setTaskStatus(FlightTaskStatus.IN_PROGRESS.getCode());
SysUserDTO userDTO = userService.get(jobEntity.getUpdater());
entity.setCreateName(userDTO.getRealName());
baseDao.insert(entity);
}
@Override
public void updateTaskComplete(String dockSn) {
String taskId = (String) CacheUtils.get(BusinessConstant.WORKING_TASK_ID + dockSn);

View File

@ -54,7 +54,7 @@ public class RouteFlightServiceImpl implements RouteFlightService {
}
@Override
public String flightExecute(String dockSn, FlightExecute flightExecute) {
public String flightExecute(String dockSn, FlightExecute flightExecute, Boolean isRecord) {
ReentrantLock lock = cacheLock.computeIfAbsent(dockSn, k -> new ReentrantLock());
lock.lock();
try {
@ -122,7 +122,9 @@ public class RouteFlightServiceImpl implements RouteFlightService {
//执行任务
Utils.sleep(1000);
String resultMsg = djiBaseService.executeAndReturnResult(dockSn, "flighttask_execute", new JSONObject().set("flight_id", taskId));
flightTaskService.addRouteTask(taskId, dockSn, route.getId());
if (isRecord) {
flightTaskService.addRouteTask(taskId, dockSn, route.getId());
}
return resultMsg;
} finally {
Utils.sleep(2000);

View File

@ -1,5 +1,7 @@
package com.multictrl.modules.job.controller;
import com.multictrl.common.annotation.ApiOrder;
import com.multictrl.common.annotation.DataFilter;
import com.multictrl.common.annotation.LogOperation;
import com.multictrl.common.constant.Constant;
import com.multictrl.common.page.PageData;
@ -13,9 +15,8 @@ import com.multictrl.modules.job.service.ScheduleJobService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Parameters;
import io.swagger.v3.oas.annotations.enums.ParameterIn;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.AllArgsConstructor;
import lombok.RequiredArgsConstructor;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.web.bind.annotation.*;
@ -28,8 +29,9 @@ import java.util.Map;
*/
@RestController
@RequestMapping("/sys/schedule")
@Tag(name = "定时任务")
@AllArgsConstructor
@Tag(name = "计划飞行")
@ApiOrder(19)
@RequiredArgsConstructor
public class ScheduleJobController {
private final ScheduleJobService scheduleJobService;
@ -37,8 +39,12 @@ public class ScheduleJobController {
@Operation(summary = "分页")
@Parameters({
@Parameter(name = Constant.PAGE, description = "当前页码从1开始"),
@Parameter(name = Constant.LIMIT, description = "每页显示记录数")
@Parameter(name = Constant.LIMIT, description = "每页显示记录数"),
@Parameter(name = Constant.ORDER_FIELD, description = "排序字段"),
@Parameter(name = Constant.ORDER, description = "排序方式,可选值(asc、desc)"),
@Parameter(name = "beanName", description = "beanName")
})
@DataFilter(tableAlias = "s")
@RequiresPermissions("sys:schedule:page")
public Result<PageData<ScheduleJobDTO>> page(@Parameter(hidden = true) @RequestParam Map<String, Object> params) {
PageData<ScheduleJobDTO> page = scheduleJobService.page(params);
@ -57,66 +63,66 @@ public class ScheduleJobController {
@PostMapping
@Operation(summary = "保存")
@LogOperation("保存")
@LogOperation("新增计划")
@RequiresPermissions("sys:schedule:save")
public Result<Object> save(@RequestBody ScheduleJobDTO dto) {
public Result<String> save(@RequestBody ScheduleJobDTO dto) {
ValidatorUtils.validateEntity(dto, AddGroup.class, DefaultGroup.class);
scheduleJobService.save(dto);
return new Result<Object>();
return new Result<String>();
}
@PutMapping
@Operation(summary = "修改")
@LogOperation("修改")
@LogOperation("修改计划")
@RequiresPermissions("sys:schedule:update")
public Result<Object> update(@RequestBody ScheduleJobDTO dto) {
public Result<String> update(@RequestBody ScheduleJobDTO dto) {
ValidatorUtils.validateEntity(dto, UpdateGroup.class, DefaultGroup.class);
scheduleJobService.update(dto);
return new Result<Object>();
return new Result<String>();
}
@DeleteMapping
@Operation(summary = "删除")
@LogOperation("删除")
@LogOperation("删除计划")
@RequiresPermissions("sys:schedule:delete")
public Result<Object> delete(@RequestBody Long[] ids) {
public Result<String> delete(@RequestBody Long[] ids) {
scheduleJobService.deleteBatch(ids);
return new Result<Object>();
return new Result<String>();
}
@PutMapping("/run")
@Operation(summary = "立即执行")
@LogOperation("立即执行")
@RequiresPermissions("sys:schedule:run")
public Result<Object> run(@RequestBody Long[] ids) {
public Result<String> run(@RequestBody Long[] ids) {
scheduleJobService.run(ids);
return new Result<Object>();
return new Result<String>();
}
@PutMapping("/pause")
@Operation(summary = "暂停")
@LogOperation("暂停")
@LogOperation("暂停计划")
@RequiresPermissions("sys:schedule:pause")
public Result<Object> pause(@RequestBody Long[] ids) {
public Result<String> pause(@RequestBody Long[] ids) {
scheduleJobService.pause(ids);
return new Result<Object>();
return new Result<String>();
}
@PutMapping("/resume")
@Operation(summary = "恢复")
@LogOperation("恢复")
@LogOperation("恢复计划")
@RequiresPermissions("sys:schedule:resume")
public Result<Object> resume(@RequestBody Long[] ids) {
public Result<String> resume(@RequestBody Long[] ids) {
scheduleJobService.resume(ids);
return new Result<Object>();
return new Result<String>();
}
}

View File

@ -1,5 +1,7 @@
package com.multictrl.modules.job.controller;
import com.multictrl.common.annotation.ApiOrder;
import com.multictrl.common.annotation.DataFilter;
import com.multictrl.common.constant.Constant;
import com.multictrl.common.page.PageData;
import com.multictrl.common.utils.Result;
@ -10,7 +12,7 @@ import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Parameters;
import io.swagger.v3.oas.annotations.enums.ParameterIn;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.AllArgsConstructor;
import lombok.RequiredArgsConstructor;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.web.bind.annotation.*;
@ -23,8 +25,9 @@ import java.util.Map;
*/
@RestController
@RequestMapping("/sys/scheduleLog")
@Tag(name = "定时任务日志")
@AllArgsConstructor
@Tag(name = "计划飞行日志")
@ApiOrder(20)
@RequiredArgsConstructor
public class ScheduleJobLogController {
private final ScheduleJobLogService scheduleJobLogService;
@ -32,8 +35,12 @@ public class ScheduleJobLogController {
@Operation(summary = "分页")
@Parameters({
@Parameter(name = Constant.PAGE, description = "当前页码从1开始"),
@Parameter(name = Constant.LIMIT, description = "每页显示记录数")
@Parameter(name = Constant.LIMIT, description = "每页显示记录数"),
@Parameter(name = Constant.ORDER_FIELD, description = "排序字段"),
@Parameter(name = Constant.ORDER, description = "排序方式,可选值(asc、desc)"),
@Parameter(name = "jobId", description = "jobId")
})
@DataFilter
@RequiresPermissions("sys:schedule:log")
public Result<PageData<ScheduleJobLogDTO>> page(@Parameter(hidden = true) @RequestParam Map<String, Object> params) {
PageData<ScheduleJobLogDTO> page = scheduleJobLogService.page(params);

View File

@ -1,8 +1,11 @@
package com.multictrl.modules.job.dao;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.multictrl.common.dao.BaseDao;
import com.multictrl.modules.job.entity.ScheduleJobEntity;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.Map;
@ -14,8 +17,18 @@ import java.util.Map;
@Mapper
public interface ScheduleJobDao extends BaseDao<ScheduleJobEntity> {
/**
* 批量更新状态
*/
int updateBatch(Map<String, Object> map);
/**
* 批量更新状态
*/
int updateBatch(Map<String, Object> map);
/**
* 分页查询
*/
IPage<ScheduleJobEntity> pageList(@Param("ipage") IPage<ScheduleJobEntity> ipage, @Param("ew") QueryWrapper<ScheduleJobEntity> ew);
/**
* 根据id查询详情
*/
ScheduleJobEntity getById(@Param("id") Long id);
}

View File

@ -1,19 +1,25 @@
package com.multictrl.modules.job.dto;
import cn.hutool.core.util.StrUtil;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.multictrl.common.validator.group.AddGroup;
import com.multictrl.common.validator.group.DefaultGroup;
import com.multictrl.common.validator.group.UpdateGroup;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.AssertTrue;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Null;
import lombok.Data;
import org.apache.commons.lang3.StringUtils;
import org.hibernate.validator.constraints.Range;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
import java.util.List;
import java.util.Objects;
/**
* 定时任务
@ -27,31 +33,121 @@ public class ScheduleJobDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@Schema(title = "id")
@Null(message="{id.null}", groups = AddGroup.class)
@NotNull(message="{id.require}", groups = UpdateGroup.class)
@Schema(description = "id")
@Null(message = "{id.null}", groups = AddGroup.class)
@NotNull(message = "{id.require}", groups = UpdateGroup.class)
private Long id;
@Schema(title = "spring bean名称")
@NotBlank(message = "{schedule.bean.require}", groups = DefaultGroup.class)
private String beanName;
@NotBlank(message = "任务名称不能为空", groups = DefaultGroup.class)
@Schema(description = "任务名称")
private String jobName;
@Schema(title = "参数")
@Schema(description = "任务执行类")
private String beanName = "routeTask";
@NotNull(message = "定时类型不能为空", groups = DefaultGroup.class)
@Schema(description = "定时类型 1指定时间 2每天几点 3每周周几几点 4每月几号几点")
private Integer cronType;
@Schema(description = "")
private Integer year;
@Schema(description = "")
private Integer month;
@Schema(description = "")
private List<Integer> day;
@Schema(description = "")
private Integer hour;
@Schema(description = "")
private Integer minute;
@Schema(description = "参数(航线标识)")
private String params;
@Schema(title = "cron表达式")
@NotBlank(message = "{schedule.cron.require}", groups = DefaultGroup.class)
private String cronExpression;
@Schema(title = "任务状态 0暂停 1正常")
@Range(min=0, max=1, message = "{schedule.status.range}", groups = DefaultGroup.class)
@Schema(description = "任务状态 0暂停 1正常 -1失效")
@Range(min = -1, max = 1, message = "{schedule.status.range}", groups = DefaultGroup.class)
private Integer status;
@Schema(title = "备注")
@Schema(description = "备注")
private String remark;
@Schema(title = "创建时间")
@Schema(description = "创建时间")
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
private Date createDate;
@NotBlank(message = "机库sn不能为空", groups = DefaultGroup.class)
@Schema(description = "机库sn")
private String dockSn;
@Schema(description = "航线名称")
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
private String routeName;
@Schema(description = "机库名称")
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
private String dockName;
@Schema(description = "下次执行时间")
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
private String nextRunTime;
@Schema(description = "执行规则")
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
private String cronRules;
@JsonIgnore
@Schema(hidden = true)
@AssertTrue(message = "参数不能为空", groups = DefaultGroup.class)
public boolean isParamsValid() {
return !Objects.equals(beanName, "routeTask") || StrUtil.isNotBlank(params);
}
@JsonIgnore
@Schema(hidden = true)
@AssertTrue(message = "时间格式有问题", groups = DefaultGroup.class)
public boolean isCronTypeValid() {
if (cronType == null) return false;
return switch (cronType) {
case 1 -> // 指定时间
year != null && month != null && !day.isEmpty() && hour != null && minute != null;
case 2 -> // 每天几点
hour != null && minute != null;
case 3 ->// 每周周几几点周几
!day.isEmpty() && hour != null && minute != null && day.stream().allMatch(d -> d >= 1 && d <= 7);
case 4 ->// 每月几号几点
!day.isEmpty() && hour != null && minute != null && day.stream().allMatch(d -> d >= 1 && d <= 31);
default -> false;
};
}
/**
* 是否断点续飞
*/
@Schema(description = "是否断点续飞")
private Boolean isBreakpointFly;
/**
* 断点续飞最大时间
*/
@Schema(description = "断点续飞最大时间 HH:mm:ss")
private String breakMaxTime;
/**
* 断点续飞最小时间
*/
@Schema(description = "断点续飞最小时间 HH:mm:ss")
private String breakMinTime;
@JsonIgnore
@Schema(hidden = true)
@AssertTrue(message = "断点续飞时间不能为空")
public boolean isBreakTimeValid() {
if (isBreakpointFly) {
return StringUtils.isNotBlank(breakMaxTime) && StringUtils.isNotBlank(breakMinTime);
} else {
return true;
}
}
}

View File

@ -19,28 +19,30 @@ public class ScheduleJobLogDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@Schema(title = "id")
@Schema(description = "id")
private Long id;
@Schema(title = "任务id")
@Schema(description = "任务id")
private Long jobId;
@Schema(title = "spring bean名称")
/* @Schema(title = "spring bean名称")
private String beanName;
@Schema(title = "参数")
private String params;
private String params;*/
@Schema(title = "任务状态 0失败 1成功")
@Schema(description = "任务状态 0失败 1成功")
private Integer status;
@Schema(title = "失败信息")
@Schema(description = "失败信息")
private String error;
@Schema(title = "耗时(单位:毫秒)")
@Schema(description = "耗时(单位:毫秒)")
private Integer times;
@Schema(title = "创建时间")
@Schema(description = "创建时间")
private Date createDate;
@Schema(description = "任务名称")
private String jobName;
}

View File

@ -9,6 +9,7 @@ import lombok.EqualsAndHashCode;
import java.io.Serial;
import java.util.Date;
import java.util.List;
/**
* 定时任务
@ -16,40 +17,110 @@ import java.util.Date;
* @author Sdy
*/
@Data
@EqualsAndHashCode(callSuper=false)
@EqualsAndHashCode(callSuper = false)
@TableName("schedule_job")
public class ScheduleJobEntity extends BaseEntity {
@Serial
private static final long serialVersionUID = 1L;
@Serial
private static final long serialVersionUID = 1L;
/**
* spring bean名称
*/
private String beanName;
/**
* 参数
*/
private String params;
/**
* cron表达式
*/
private String cronExpression;
/**
* 任务状态 0暂停 1正常
*/
private Integer status;
/**
* 备注
*/
private String remark;
/**
* 更新者
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private Long updater;
/**
* 更新时间
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateDate;
/**
* spring bean名称
*/
private String beanName;
/**
* 参数
*/
private String params;
/**
* cron表达式
*/
private String cronExpression;
/**
* 任务状态 0暂停 1正常
*/
private Integer status;
/**
* 备注
*/
private String remark;
/**
* 更新者
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private Long updater;
/**
* 更新时间
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateDate;
/**
* 任务名称
*/
private String jobName;
/**
* 定时类型 1指定时间 2每天几点 3每周周几几点 4每月几号几点
*/
private Integer cronType;
/**
*
*/
private Integer year;
/**
*
*/
private Integer month;
/**
*
*/
private String days;
@TableField(exist = false)
private List<Integer> day;
/**
*
*/
private Integer hour;
/**
*
*/
private Integer minute;
/**
* 部门标识
*/
private Long deptId;
/**
* 机库sn
*/
private String dockSn;
/**
* 是否断点续飞
*/
private Boolean isBreakpointFly;
/**
* 断点续飞最大时间
*/
private String breakMaxTime;
/**
* 断点续飞最小时间
*/
private String breakMinTime;
@TableField(exist = false)
private String dockName;
@TableField(exist = false)
private String routeName;
/**
* 下次执行时间
*/
@TableField(exist = false)
private String nextRunTime;
/**
* 执行规则
*/
@TableField(exist = false)
private String cronRules;
}

View File

@ -16,41 +16,48 @@ import java.util.Date;
@Data
@TableName("schedule_job_log")
public class ScheduleJobLogEntity implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* id
*/
@TableId
private Long id;
/**
* 任务id
*/
private Long jobId;
/**
* spring bean名称
*/
private String beanName;
/**
* 参数
*/
private String params;
/**
* 任务状态 0失败 1成功
*/
private Integer status;
/**
* 失败信息
*/
private String error;
/**
* 耗时(单位毫秒)
*/
private Integer times;
/**
* 创建时间
*/
private Date createDate;
@Serial
private static final long serialVersionUID = 1L;
/**
* id
*/
@TableId
private Long id;
/**
* 任务id
*/
private Long jobId;
/**
* spring bean名称
*/
private String beanName;
/**
* 参数
*/
private String params;
/**
* 任务状态 0失败 1成功
*/
private Integer status;
/**
* 失败信息
*/
private String error;
/**
* 耗时(单位毫秒)
*/
private Integer times;
/**
* 创建时间
*/
private Date createDate;
/**
* 部门
*/
private Long deptId;
/**
* 任务名称
*/
private String jobName;
}

View File

@ -1,14 +1,19 @@
package com.multictrl.modules.job.init;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.multictrl.modules.job.dao.ScheduleJobDao;
import com.multictrl.modules.job.entity.ScheduleJobEntity;
import com.multictrl.modules.job.utils.CronParser;
import com.multictrl.modules.job.utils.ScheduleUtils;
import lombok.AllArgsConstructor;
import lombok.RequiredArgsConstructor;
import org.quartz.CronTrigger;
import org.quartz.Scheduler;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
/**
@ -17,14 +22,27 @@ import java.util.List;
* @author Sdy
*/
@Component
@AllArgsConstructor
@RequiredArgsConstructor
public class JobCommandLineRunner implements CommandLineRunner {
private final Scheduler scheduler;
private final ScheduleJobDao scheduleJobDao;
@Override
public void run(String... args) {
List<ScheduleJobEntity> scheduleJobList = scheduleJobDao.selectList(null);
Date now = new Date();
List<ScheduleJobEntity> scheduleJobList = scheduleJobDao.selectList(new QueryWrapper<ScheduleJobEntity>().ne("status", -1));
Iterator<ScheduleJobEntity> iterator = scheduleJobList.iterator();
while (iterator.hasNext()) {
ScheduleJobEntity item = iterator.next();
Integer cronType = item.getCronType();
if (1 == cronType) {
boolean valid = CronParser.isValid(item.getCronExpression(), now);
if (!valid) {
scheduleJobDao.update(null, new UpdateWrapper<ScheduleJobEntity>().eq("id", item.getId()).set("status", -1));
iterator.remove(); // 安全删除
}
}
}
for (ScheduleJobEntity scheduleJob : scheduleJobList) {
CronTrigger cronTrigger = ScheduleUtils.getCronTrigger(scheduler, scheduleJob.getId());
//如果不存在则创建

View File

@ -18,29 +18,29 @@ import java.util.Map;
@Service
public class ScheduleJobLogServiceImpl extends BaseServiceImpl<ScheduleJobLogDao, ScheduleJobLogEntity> implements ScheduleJobLogService {
@Override
public PageData<ScheduleJobLogDTO> page(Map<String, Object> params) {
IPage<ScheduleJobLogEntity> page = baseDao.selectPage(
getPage(params, Constant.CREATE_DATE, false),
getWrapper(params)
);
return getPageData(page, ScheduleJobLogDTO.class);
}
@Override
public PageData<ScheduleJobLogDTO> page(Map<String, Object> params) {
IPage<ScheduleJobLogEntity> page = baseDao.selectPage(
getPage(params, Constant.CREATE_DATE, false),
getWrapper(params)
);
return getPageData(page, ScheduleJobLogDTO.class);
}
private QueryWrapper<ScheduleJobLogEntity> getWrapper(Map<String, Object> params){
String jobId = (String)params.get("jobId");
private QueryWrapper<ScheduleJobLogEntity> getWrapper(Map<String, Object> params) {
String jobId = (String) params.get("jobId");
QueryWrapper<ScheduleJobLogEntity> wrapper = new QueryWrapper<>();
wrapper.eq(StrUtil.isNotBlank(jobId), "job_id", jobId);
QueryWrapper<ScheduleJobLogEntity> wrapper = new QueryWrapper<>();
wrapper.eq(StrUtil.isNotBlank(jobId), "job_id", jobId);
return wrapper;
}
return wrapper;
}
@Override
public ScheduleJobLogDTO get(Long id) {
ScheduleJobLogEntity entity = baseDao.selectById(id);
@Override
public ScheduleJobLogDTO get(Long id) {
ScheduleJobLogEntity entity = baseDao.selectById(id);
return ConvertUtils.sourceToTarget(entity, ScheduleJobLogDTO.class);
}
return ConvertUtils.sourceToTarget(entity, ScheduleJobLogDTO.class);
}
}

View File

@ -1,5 +1,6 @@
package com.multictrl.modules.job.service.impl;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
@ -11,42 +12,73 @@ import com.multictrl.modules.job.dao.ScheduleJobDao;
import com.multictrl.modules.job.dto.ScheduleJobDTO;
import com.multictrl.modules.job.entity.ScheduleJobEntity;
import com.multictrl.modules.job.service.ScheduleJobService;
import com.multictrl.modules.job.utils.CronGenerator;
import com.multictrl.modules.job.utils.CronParser;
import com.multictrl.modules.job.utils.CronToChinese;
import com.multictrl.modules.job.utils.ScheduleUtils;
import lombok.AllArgsConstructor;
import com.multictrl.modules.security.user.SecurityUser;
import lombok.RequiredArgsConstructor;
import org.quartz.Scheduler;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.*;
import java.util.stream.Collectors;
@Service
@AllArgsConstructor
@RequiredArgsConstructor
public class ScheduleJobServiceImpl extends BaseServiceImpl<ScheduleJobDao, ScheduleJobEntity> implements ScheduleJobService {
private final Scheduler scheduler;
@Override
public PageData<ScheduleJobDTO> page(Map<String, Object> params) {
IPage<ScheduleJobEntity> page = baseDao.selectPage(
IPage<ScheduleJobEntity> page = baseDao.pageList(
getPage(params, Constant.CREATE_DATE, false),
getWrapper(params)
);
page.getRecords().forEach(item -> {
String days = item.getDays();
if (StrUtil.isNotEmpty(days)) {
List<Integer> dayList = Arrays.stream(days.trim().split(","))
.map(String::trim)
.filter(s -> !s.isEmpty())
.map(Integer::parseInt)
.collect(Collectors.toList());
item.setDay(dayList);
}
if (item.getStatus() != -1) {
String cronExpression = item.getCronExpression();
String nextTime = CronParser.getNextTime(cronExpression);
item.setNextRunTime(nextTime);
}
item.setCronRules(CronToChinese.convert(item.getYear(), item.getMonth(), item.getDays(), item.getHour(), item.getMinute(), item.getCronType()));
});
return getPageData(page, ScheduleJobDTO.class);
}
@Override
public ScheduleJobDTO get(Long id) {
ScheduleJobEntity entity = baseDao.selectById(id);
ScheduleJobEntity entity = baseDao.getById(id);
if (entity != null) {
String days = entity.getDays();
List<Integer> dayList = Arrays.stream(days.trim().split(","))
.map(String::trim)
.filter(s -> !s.isEmpty())
.map(Integer::parseInt)
.collect(Collectors.toList());
entity.setDay(dayList);
}
return ConvertUtils.sourceToTarget(entity, ScheduleJobDTO.class);
}
private QueryWrapper<ScheduleJobEntity> getWrapper(Map<String, Object> params) {
String beanName = (String) params.get("beanName");
String jobName = (String) params.get("jobName");
QueryWrapper<ScheduleJobEntity> wrapper = new QueryWrapper<>();
wrapper.like(StrUtil.isNotBlank(beanName), "bean_name", beanName);
wrapper.like(StrUtil.isNotBlank(beanName), "s.bean_name", beanName);
wrapper.like(StrUtil.isNotBlank(jobName), "s.job_name", jobName);
return wrapper;
}
@ -55,8 +87,12 @@ public class ScheduleJobServiceImpl extends BaseServiceImpl<ScheduleJobDao, Sche
@Transactional(rollbackFor = Exception.class)
public void save(ScheduleJobDTO dto) {
ScheduleJobEntity entity = ConvertUtils.sourceToTarget(dto, ScheduleJobEntity.class);
entity.setDeptId(SecurityUser.getDeptId());
String cron = CronGenerator.getCron(dto.getCronType(), dto.getYear(), dto.getMonth(), dto.getDay(), dto.getHour(), dto.getMinute());
entity.setCronExpression(cron);
entity.setDays(StrUtil.join(",", dto.getDay()));
entity.setStatus(Constant.ScheduleStatus.NORMAL.getValue());
entity.setStatus(Constant.ScheduleStatus.PAUSE.getValue());
this.insert(entity);
ScheduleUtils.createScheduleJob(scheduler, entity);
@ -66,8 +102,16 @@ public class ScheduleJobServiceImpl extends BaseServiceImpl<ScheduleJobDao, Sche
@Transactional(rollbackFor = Exception.class)
public void update(ScheduleJobDTO dto) {
ScheduleJobEntity entity = ConvertUtils.sourceToTarget(dto, ScheduleJobEntity.class);
ScheduleUtils.updateScheduleJob(scheduler, entity);
entity.setDeptId(SecurityUser.getDeptId());
String cron = CronGenerator.getCron(dto.getCronType(), dto.getYear(), dto.getMonth(), dto.getDay(), dto.getHour(), dto.getMinute());
entity.setCronExpression(cron);
entity.setDays(StrUtil.join(",", dto.getDay()));
if (dto.getStatus() == Constant.ScheduleStatus.FAILURE.getValue()) {
entity.setStatus(Constant.ScheduleStatus.PAUSE.getValue());
ScheduleUtils.createScheduleJob(scheduler, entity);
} else {
ScheduleUtils.updateScheduleJob(scheduler, entity);
}
this.updateById(entity);
}
@ -94,29 +138,63 @@ public class ScheduleJobServiceImpl extends BaseServiceImpl<ScheduleJobDao, Sche
@Override
@Transactional(rollbackFor = Exception.class)
public void run(Long[] ids) {
List<Long> failure = new ArrayList<>();
for (Long id : ids) {
ScheduleUtils.run(scheduler, this.selectById(id));
//是否判断过期
ScheduleJobDTO jobDTO = get(id);
int result = ScheduleUtils.run(scheduler, this.selectById(id), jobDTO.getCronType() == 1);
if (result == Constant.ScheduleStatus.FAILURE.getValue()) {
failure.add(id);
}
}
if (CollectionUtil.isNotEmpty(failure)) {
updateBatch(failure.toArray(new Long[0]), Constant.ScheduleStatus.FAILURE.getValue());
}
}
@Override
@Transactional(rollbackFor = Exception.class)
public void pause(Long[] ids) {
List<Long> failure = new ArrayList<>();
List<Long> normal = new ArrayList<>();
for (Long id : ids) {
ScheduleUtils.pauseJob(scheduler, id);
//是否判断过期
ScheduleJobDTO jobDTO = get(id);
int result = ScheduleUtils.pauseJob(scheduler, id, jobDTO.getCronType() == 1);
if (result == Constant.ScheduleStatus.FAILURE.getValue()) {
failure.add(id);
} else {
normal.add(id);
}
}
if (CollectionUtil.isNotEmpty(failure)) {
updateBatch(failure.toArray(new Long[0]), Constant.ScheduleStatus.FAILURE.getValue());
}
if (CollectionUtil.isNotEmpty(normal)) {
updateBatch(normal.toArray(new Long[0]), Constant.ScheduleStatus.PAUSE.getValue());
}
updateBatch(ids, Constant.ScheduleStatus.PAUSE.getValue());
}
@Override
@Transactional(rollbackFor = Exception.class)
public void resume(Long[] ids) {
List<Long> failure = new ArrayList<>();
List<Long> normal = new ArrayList<>();
for (Long id : ids) {
ScheduleUtils.resumeJob(scheduler, id);
//是否判断过期
ScheduleJobDTO jobDTO = get(id);
int result = ScheduleUtils.resumeJob(scheduler, id, jobDTO.getCronType() == 1);
if (result == Constant.ScheduleStatus.FAILURE.getValue()) {
failure.add(id);
} else {
normal.add(id);
}
}
if (CollectionUtil.isNotEmpty(failure)) {
updateBatch(failure.toArray(new Long[0]), Constant.ScheduleStatus.FAILURE.getValue());
}
if (CollectionUtil.isNotEmpty(normal)) {
updateBatch(normal.toArray(new Long[0]), Constant.ScheduleStatus.NORMAL.getValue());
}
updateBatch(ids, Constant.ScheduleStatus.NORMAL.getValue());
}
}

View File

@ -0,0 +1,59 @@
package com.multictrl.modules.job.task;
import cn.hutool.json.JSONObject;
import com.multictrl.common.constant.BusinessConstant;
import com.multictrl.common.utils.CacheUtils;
import com.multictrl.common.utils.JsonUtils;
import com.multictrl.common.utils.Utils;
import com.multictrl.modules.business.dto.flight.FlightExecute;
import com.multictrl.modules.business.service.FlightTaskService;
import com.multictrl.modules.business.service.RouteFlightService;
import com.multictrl.modules.job.dto.ScheduleJobDTO;
import com.multictrl.modules.job.entity.ScheduleJobEntity;
import com.multictrl.modules.job.service.ScheduleJobService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/**
* 航线任务
*
* @author Sdy
* @since 1.0.0 2026/5/28
*/
@Slf4j
@Component("routeTask")
@RequiredArgsConstructor
public class RouteTask implements ITask {
private final ScheduleJobService scheduleJobService;
private final RouteFlightService routeFlightService;
private final FlightTaskService flightTaskService;
@Override
public void run(String params) {
JSONObject jsonObject = JsonUtils.parseObject(params, JSONObject.class);
if (jsonObject == null) {
log.error("计划飞行任务无效,拒绝执行:{}", params);
return;
}
Long jobId = jsonObject.getLong("jobId");
Integer cronType = jsonObject.getInt("cronType");
if (cronType == 1) {
//一次性任务 执行完失效 不然初始化job会因为时间过期而报错
ScheduleJobEntity scheduleJobEntity = new ScheduleJobEntity();
scheduleJobEntity.setStatus(-1);
scheduleJobEntity.setId(jobId);
scheduleJobService.updateById(scheduleJobEntity);
}
ScheduleJobDTO dto = scheduleJobService.get(jobId);
String dockSn = dto.getDockSn();
//执行任务
FlightExecute flightExecute = new FlightExecute();
flightExecute.setRouteId(Long.parseLong(dto.getParams()));
routeFlightService.flightExecute(dockSn, flightExecute, false);
//添加日志
Utils.sleep(1000);
String taskId = (String) CacheUtils.get(BusinessConstant.WORKING_TASK_ID + dockSn);
flightTaskService.addJobRouteTask(taskId, jobId);
}
}

View File

@ -0,0 +1,96 @@
package com.multictrl.modules.job.utils;
import cn.hutool.core.util.StrUtil;
import org.quartz.CronExpression;
import java.util.List;
import java.util.stream.Collectors;
/**
* cron表达式生成器
*
* @author Sdy
* @since 2025/6/27
*/
public class CronGenerator {
/**
* 1. 某一天的几点执行小时分钟
*
* @param year 2024
* @param month 1-12
* @param day 日期1-31
* @param hour 小时0-23
* @param minute 分钟0-59
* @return Cron 表达式
*/
public static String generateOnceCron(int year, int month, List<Integer> day, int hour, int minute) {
return String.format("0 %d %d %d %d ? %d", minute, hour, day.get(0), month, year);
}
/**
* 2. 每天的几点执行小时分钟
*
* @param hour 小时0-23
* @param minute 分钟0-59
* @return Cron 表达式
*/
public static String generateDailyCron(int hour, int minute) {
return String.format("0 %d %d * * ? *", minute, hour);
}
/**
* 3. 每周的周几几点执行周几小时分钟
*
* @param dayOfWeek 周几1-71=周一
* @param hour 小时0-23
* @param minute 分钟0-59
* @return Cron 表达式
*/
public static String generateWeeklyCron(List<Integer> dayOfWeek, int hour, int minute) {
// 转换星期表示将国内习惯的1-7转换为Cron表达式的1-7
// 国内习惯1=周一, 2=周二, 3=周三, 4=周四, 5=周五, 6=周六, 7=周日
// Cron表达式1=周日, 2=周一, 3=周二, 4=周三, 5=周四, 6=周五, 7=周六
List<Integer> cronDays = dayOfWeek.stream()
.map(d -> {
if (d == 7) return 1; // 7(周日) 1(周日)
return d + 1; // 1(周一)2, 2(周二)3, ... 6(周六)7
})
.collect(Collectors.toList());
return String.format("0 %d %d ? * %s *", minute, hour, StrUtil.join(",", cronDays));
}
/**
* 4. 每月的几号几点执行日期小时分钟
*
* @param dayOfMonth 日期1-31
* @param hour 小时0-23
* @param minute 分钟0-59
* @return Cron 表达式
*/
public static String generateMonthlyCron(List<Integer> dayOfMonth, int hour, int minute) {
return String.format("0 %d %d %s * ? *", minute, hour, StrUtil.join(",", dayOfMonth));
}
// 可选验证 Cron 表达式是否合法需引入 quartz cronutils 依赖
public static boolean isValid(String cronExpression) {
try {
new CronExpression(cronExpression); // 需要 quartz 依赖
return true;
} catch (Exception e) {
return false;
}
}
//获取cron表达式
public static String getCron(Integer cronType, Integer year, Integer month, List<Integer> day, Integer hour, Integer minute) {
return switch (cronType) {
case 1 -> generateOnceCron(year, month, day, hour, minute);
case 2 -> generateDailyCron(hour, minute);
case 3 -> generateWeeklyCron(day, hour, minute);
case 4 -> generateMonthlyCron(day, hour, minute);
default -> null;
};
}
}

View File

@ -0,0 +1,51 @@
package com.multictrl.modules.job.utils;
import com.multictrl.common.exception.ExceptionUtils;
import com.multictrl.common.utils.DateUtils;
import lombok.extern.slf4j.Slf4j;
import org.quartz.CronExpression;
import java.text.ParseException;
import java.util.Date;
/**
* cron表达式解析
*
* @author Sdy
* @since 1.0.0 2025/7/22
*/
@Slf4j
public class CronParser {
/**
* 获取下一次执行时间
*/
public static String getNextTime(String cronExpression) {
try {
CronExpression cron = new CronExpression(cronExpression);
Date now = new Date(); // 当前时间
Date nextTime = cron.getNextValidTimeAfter(now); // 获取下一次执行时间
return DateUtils.format(nextTime, "yyyy-MM-dd HH:mm");
} catch (ParseException e) {
log.error(ExceptionUtils.getErrorStackTrace(e));
return null;
}
}
/**
* 是否有效
*/
public static boolean isValid(String cronExpression, Date now) {
try {
boolean validExpression = CronExpression.isValidExpression(cronExpression);
if (validExpression) {
CronExpression cron = new CronExpression(cronExpression);
Date nextFireTime = cron.getNextValidTimeAfter(now);
return !(nextFireTime == null || nextFireTime.before(now));
}
return false;
} catch (Exception e) {
return false;
}
}
}

View File

@ -0,0 +1,66 @@
package com.multictrl.modules.job.utils;
/**
* 中文转换工具
*
* @author Sdy
* @since 1.0.0 2025/7/24
*/
public class CronToChinese {
/**
* Cron 表达式转换为中文描述
*/
public static String convert(Integer year, Integer month, String days, Integer hour, Integer minute, Integer cronType) {
switch (cronType) {
case 1:
return year + "" + month + "" + days + "" + hour + "" + minute + "分执行";
case 2:
return "每天" + hour + "" + minute + "分执行";
case 3:
String weekday = "";
String[] split = days.split(",");
for (String s : split) {
if (s.equals("1")) {
weekday += "周一、";
} else if (s.equals("2")) {
weekday += "周二、";
} else if (s.equals("3")) {
weekday += "周三、";
} else if (s.equals("4")) {
weekday += "周四、";
} else if (s.equals("5")) {
weekday += "周五、";
} else if (s.equals("6")) {
weekday += "周六、";
} else if (s.equals("7")) {
weekday += "周日、";
}
}
return "每周" + weekday.substring(0, weekday.length() - 1) + "" + hour + "" + minute + "分执行";
case 4:
String monthday = "";
String[] day = days.split(",");
for (String s : day) {
monthday += s + "号、";
}
return "每月" + monthday.substring(0, monthday.length() - 1) + "" + hour + "" + minute + "分执行";
default:
return null;
}
}
// 示例用法
/*public static void main(String[] args) {
System.out.println(convert(2025, 7, "24", 3, 23, 1));
// 输出2025年7月每月24日的上午3点23分执行
System.out.println(convert(0, 0, "", 12, 29, 2));
// 输出每天中午12点29分执行
System.out.println(convert(0, 0, "1,3,7", 11, 55, 3));
// 输出每周一周三周日上午11点55分执行
System.out.println(convert(0, 0, "1,3,5", 12, 56, 4));
// 输出每月1日3日和5日的中午12点56分执行
}*/
}

View File

@ -1,12 +1,13 @@
package com.multictrl.modules.job.utils;
import cn.hutool.json.JSONObject;
import com.multictrl.common.constant.Constant;
import com.multictrl.common.exception.ExceptionUtils;
import com.multictrl.common.utils.JsonUtils;
import com.multictrl.common.utils.SpringContextUtils;
import com.multictrl.modules.job.entity.ScheduleJobEntity;
import com.multictrl.modules.job.entity.ScheduleJobLogEntity;
import com.multictrl.modules.job.service.ScheduleJobLogService;
import lombok.extern.slf4j.Slf4j;
import org.quartz.JobExecutionContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -15,58 +16,60 @@ import org.springframework.scheduling.quartz.QuartzJobBean;
import java.lang.reflect.Method;
import java.util.Date;
/**
* 定时任务
*
* @author Sdy
*/
@Slf4j
public class ScheduleJob extends QuartzJobBean {
private final Logger logger = LoggerFactory.getLogger(getClass());
@Override
protected void executeInternal(JobExecutionContext context) {
ScheduleJobEntity scheduleJob = (ScheduleJobEntity) context.getMergedJobDataMap().
get(ScheduleUtils.JOB_PARAM_KEY);
get(ScheduleUtils.JOB_PARAM_KEY);
//数据库保存执行记录
ScheduleJobLogEntity logEntity = new ScheduleJobLogEntity();
logEntity.setJobId(scheduleJob.getId());
logEntity.setBeanName(scheduleJob.getBeanName());
logEntity.setParams(scheduleJob.getParams());
logEntity.setCreateDate(new Date());
ScheduleJobLogEntity log = new ScheduleJobLogEntity();
log.setJobId(scheduleJob.getId());
log.setBeanName(scheduleJob.getBeanName());
log.setParams(scheduleJob.getParams());
log.setCreateDate(new Date());
log.setDeptId(scheduleJob.getDeptId());
log.setJobName(scheduleJob.getJobName());
//任务开始时间
long startTime = System.currentTimeMillis();
try {
//执行任务
log.info("任务准备执行任务ID{}", scheduleJob.getId());
Object target = SpringContextUtils.getBean(scheduleJob.getBeanName());
Method method = target.getClass().getDeclaredMethod("run", String.class);
method.invoke(target, scheduleJob.getParams());
//执行任务
logger.info("任务准备执行任务ID{}", scheduleJob.getId());
Object target = SpringContextUtils.getBean(scheduleJob.getBeanName());
Method method = target.getClass().getDeclaredMethod("run", String.class);
JSONObject jsonObject = new JSONObject();
jsonObject.set("jobId", scheduleJob.getId());
jsonObject.set("cronType", scheduleJob.getCronType());
method.invoke(target, JsonUtils.toJsonString(jsonObject));
//任务执行总时长
long times = System.currentTimeMillis() - startTime;
logEntity.setTimes((int)times);
//任务状态
logEntity.setStatus(Constant.SUCCESS);
//任务执行总时长
long times = System.currentTimeMillis() - startTime;
log.setTimes((int) times);
//任务状态
log.setStatus(Constant.SUCCESS);
log.info("任务执行完毕任务ID{} 总共耗时:{} 毫秒", scheduleJob.getId(), times);
} catch (Exception e) {
log.error("任务执行失败任务ID{}", scheduleJob.getId(), e);
logger.info("任务执行完毕任务ID{} 总共耗时:{} 毫秒", scheduleJob.getId(), times);
} catch (Exception e) {
logger.error("任务执行失败任务ID{}", scheduleJob.getId(), e);
//任务执行总时长
long times = System.currentTimeMillis() - startTime;
logEntity.setTimes((int)times);
//任务执行总时长
long times = System.currentTimeMillis() - startTime;
log.setTimes((int) times);
//任务状态
logEntity.setStatus(Constant.FAIL);
logEntity.setError(ExceptionUtils.getErrorStackTrace(e));
}finally {
//获取spring bean
ScheduleJobLogService scheduleJobLogService = SpringContextUtils.getBean(ScheduleJobLogService.class);
scheduleJobLogService.insert(logEntity);
}
//任务状态
log.setStatus(Constant.FAIL);
log.setError(ExceptionUtils.getErrorStackTrace(e));
} finally {
//获取spring bean
ScheduleJobLogService scheduleJobLogService = SpringContextUtils.getBean(ScheduleJobLogService.class);
scheduleJobLogService.insert(log);
}
}
}

View File

@ -2,15 +2,18 @@ package com.multictrl.modules.job.utils;
import com.multictrl.common.constant.Constant;
import com.multictrl.common.exception.ErrorCode;
import com.multictrl.common.exception.ExceptionUtils;
import com.multictrl.common.exception.RenException;
import com.multictrl.modules.job.entity.ScheduleJobEntity;
import lombok.extern.slf4j.Slf4j;
import org.quartz.*;
import java.util.Date;
/**
* 定时任务工具类
*
* @author Sdy
*/
@Slf4j
public class ScheduleUtils {
private final static String JOB_NAME = "TASK_";
/**
@ -48,12 +51,12 @@ public class ScheduleUtils {
*/
public static void createScheduleJob(Scheduler scheduler, ScheduleJobEntity scheduleJob) {
try {
//构建job信息
//构建job信息
JobDetail jobDetail = JobBuilder.newJob(ScheduleJob.class).withIdentity(getJobKey(scheduleJob.getId())).build();
//表达式调度构建器
CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(scheduleJob.getCronExpression())
.withMisfireHandlingInstructionDoNothing();
.withMisfireHandlingInstructionDoNothing();
//按新的cronExpression表达式构建一个新的trigger
CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(getTriggerKey(scheduleJob.getId())).withSchedule(scheduleBuilder).build();
@ -64,8 +67,8 @@ public class ScheduleUtils {
scheduler.scheduleJob(jobDetail, trigger);
//暂停任务
if(scheduleJob.getStatus() == Constant.ScheduleStatus.PAUSE.getValue()){
pauseJob(scheduler, scheduleJob.getId());
if (scheduleJob.getStatus() == Constant.ScheduleStatus.PAUSE.getValue()) {
pauseJob(scheduler, scheduleJob.getId(), false);
}
} catch (SchedulerException e) {
throw new RenException(ErrorCode.JOB_ERROR, e);
@ -81,7 +84,7 @@ public class ScheduleUtils {
//表达式调度构建器
CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(scheduleJob.getCronExpression())
.withMisfireHandlingInstructionDoNothing();
.withMisfireHandlingInstructionDoNothing();
CronTrigger trigger = getCronTrigger(scheduler, scheduleJob.getId());
@ -94,8 +97,8 @@ public class ScheduleUtils {
scheduler.rescheduleJob(triggerKey, trigger);
//暂停任务
if(scheduleJob.getStatus() == Constant.ScheduleStatus.PAUSE.getValue()){
pauseJob(scheduler, scheduleJob.getId());
if (scheduleJob.getStatus() == Constant.ScheduleStatus.PAUSE.getValue()) {
pauseJob(scheduler, scheduleJob.getId(), false);
}
} catch (SchedulerException e) {
@ -106,13 +109,20 @@ public class ScheduleUtils {
/**
* 立即执行任务
*/
public static void run(Scheduler scheduler, ScheduleJobEntity scheduleJob) {
public static int run(Scheduler scheduler, ScheduleJobEntity scheduleJob, boolean validateExpired) {
try {
//参数
JobDataMap dataMap = new JobDataMap();
dataMap.put(JOB_PARAM_KEY, scheduleJob);
Long jobId = scheduleJob.getId();
if (validateExpired && isJobExpired(scheduler, jobId)) {
removeExpiredJob(scheduler, jobId);
return Constant.ScheduleStatus.FAILURE.getValue();
}
scheduler.triggerJob(getJobKey(scheduleJob.getId()), dataMap);
//参数
JobDataMap dataMap = new JobDataMap();
dataMap.put(JOB_PARAM_KEY, scheduleJob);
scheduler.triggerJob(getJobKey(jobId), dataMap);
return Constant.ScheduleStatus.NORMAL.getValue();
} catch (SchedulerException e) {
throw new RenException(ErrorCode.JOB_ERROR, e);
}
@ -121,9 +131,15 @@ public class ScheduleUtils {
/**
* 暂停任务
*/
public static void pauseJob(Scheduler scheduler, Long jobId) {
public static int pauseJob(Scheduler scheduler, Long jobId, boolean validateExpired) {
try {
if (validateExpired && isJobExpired(scheduler, jobId)) {
removeExpiredJob(scheduler, jobId);
return Constant.ScheduleStatus.FAILURE.getValue();
}
scheduler.pauseJob(getJobKey(jobId));
return Constant.ScheduleStatus.PAUSE.getValue();
} catch (SchedulerException e) {
throw new RenException(ErrorCode.JOB_ERROR, e);
}
@ -132,9 +148,15 @@ public class ScheduleUtils {
/**
* 恢复任务
*/
public static void resumeJob(Scheduler scheduler, Long jobId) {
public static int resumeJob(Scheduler scheduler, Long jobId, boolean validateExpired) {
try {
if (validateExpired && isJobExpired(scheduler, jobId)) {
removeExpiredJob(scheduler, jobId);
return Constant.ScheduleStatus.FAILURE.getValue();
}
scheduler.resumeJob(getJobKey(jobId));
return Constant.ScheduleStatus.NORMAL.getValue();
} catch (SchedulerException e) {
throw new RenException(ErrorCode.JOB_ERROR, e);
}
@ -150,4 +172,41 @@ public class ScheduleUtils {
throw new RenException(ErrorCode.JOB_ERROR, e);
}
}
/**
* 检查任务是否已过期
*/
private static boolean isJobExpired(Scheduler scheduler, Long jobId) {
try {
Trigger trigger = scheduler.getTrigger(TriggerKey.triggerKey(JOB_NAME + jobId));
Date nextFireTime = trigger.getNextFireTime();
return nextFireTime != null && nextFireTime.before(new Date());
/* TriggerKey triggerKey = TriggerKey.triggerKey(JOB_NAME + jobId);
// 1. Trigger 不存在已被删除 视为过期
if (!scheduler.checkExists(triggerKey)) {
return true;
}
// 2. 检查 Trigger 状态 COMPLETE 表示已永久完成
Trigger.TriggerState state = scheduler.getTriggerState(triggerKey);
return state == Trigger.TriggerState.COMPLETE;*/
} catch (SchedulerException e) {
log.debug("判断任务 {} 是否过期失败", jobId);
throw new RenException(ErrorCode.JOB_ERROR, e);
}
}
/**
* 移除过期任务从调度器中移除并更新状态
*/
private static void removeExpiredJob(Scheduler scheduler, Long jobId) {
try {
// 从调度器中移除
scheduler.deleteJob(getJobKey(jobId));
log.debug("任务 {} 已过期,已从调度器中移除并标记为失效", jobId);
} catch (SchedulerException e) {
log.error("移除过期任务失败: {}", ExceptionUtils.getErrorStackTrace(e));
throw new RenException(ErrorCode.JOB_ERROR, e);
}
}
}

View File

@ -6,9 +6,25 @@
<!-- 批量更新状态 -->
<update id="updateBatch">
update schedule_job set status = #{status} where id in
<foreach item="id" collection="ids" open="(" separator="," close=")">
<foreach item="id" collection="ids" open="(" separator="," close=")">
#{id}
</foreach>
</update>
<select id="pageList" resultType="com.multictrl.modules.job.entity.ScheduleJobEntity">
SELECT s.*, uh.dock_name, ur.route_name
FROM schedule_job s
left join bus_dock uh on s.dock_sn = uh.dock_sn
left join bus_route ur on CAST(s.params AS int8) = ur.id
${ew.customSqlSegment}
</select>
<select id="getById" resultType="com.multictrl.modules.job.entity.ScheduleJobEntity">
SELECT s.*, uh.dock_name, ur.route_name
FROM schedule_job s
left join bus_dock uh on s.dock_sn = uh.dock_sn
left join bus_route ur on CAST(s.params AS int8) = ur.id
where s.id = #{id}
</select>
</mapper>

View File

@ -3,5 +3,4 @@
<mapper namespace="com.multictrl.modules.job.dao.ScheduleJobLogDao">
</mapper>

View File

@ -25,7 +25,7 @@ public interface Constant {
*/
Long DEPT_ROOT = 0L;
/**
* 升序
* 升序
*/
String ASC = "asc";
/**
@ -74,7 +74,11 @@ public interface Constant {
/**
* 正常
*/
NORMAL(1);
NORMAL(1),
/**
* 失效
*/
FAILURE(-1);
private final int value;

View File

@ -632,20 +632,32 @@ ON TABLE "public"."bus_waypoint_action" IS '航点动作信息';
-- ----------------------------
-- Table structure for schedule_job
-- ----------------------------
DROP TABLE IF EXISTS "public"."schedule_job";
CREATE TABLE "public"."schedule_job"
(
"id" int8 NOT NULL,
"bean_name" varchar(200) COLLATE "pg_catalog"."default",
"params" varchar(2000) COLLATE "pg_catalog"."default",
"cron_expression" varchar(100) COLLATE "pg_catalog"."default",
"status" int4,
"remark" varchar(255) COLLATE "pg_catalog"."default",
"creator" int8,
"create_date" timestamp(6),
"updater" int8,
"update_date" timestamp(6)
"id" int8 NOT NULL,
"bean_name" varchar(200) COLLATE "pg_catalog"."default",
"params" varchar(2000) COLLATE "pg_catalog"."default",
"cron_expression" varchar(100) COLLATE "pg_catalog"."default",
"status" int4,
"remark" varchar(255) COLLATE "pg_catalog"."default",
"creator" int8,
"create_date" timestamp(6),
"updater" int8,
"update_date" timestamp(6),
"job_name" varchar(50) COLLATE "pg_catalog"."default",
"cron_type" int4,
"year" int4,
"month" int4,
"days" varchar(100) COLLATE "pg_catalog"."default",
"hour" int4,
"minute" int4,
"dept_id" int8,
"params_remark" varchar(200) COLLATE "pg_catalog"."default",
"dock_sn" varchar(50) COLLATE "pg_catalog"."default",
"is_breakpoint_fly" bool,
"break_max_time" varchar(50) COLLATE "pg_catalog"."default",
"break_min_time" varchar(50) COLLATE "pg_catalog"."default"
)
;
COMMENT
@ -657,7 +669,7 @@ ON COLUMN "public"."schedule_job"."params" IS '参数';
COMMENT
ON COLUMN "public"."schedule_job"."cron_expression" IS 'cron表达式';
COMMENT
ON COLUMN "public"."schedule_job"."status" IS '任务状态 0暂停 1正常';
ON COLUMN "public"."schedule_job"."status" IS '任务状态 0暂停 1正常 -1失效';
COMMENT
ON COLUMN "public"."schedule_job"."remark" IS '备注';
COMMENT
@ -669,12 +681,33 @@ ON COLUMN "public"."schedule_job"."updater" IS '更新者';
COMMENT
ON COLUMN "public"."schedule_job"."update_date" IS '更新时间';
COMMENT
ON COLUMN "public"."schedule_job"."job_name" IS '任务名称';
COMMENT
ON COLUMN "public"."schedule_job"."cron_type" IS '定时类型 1指定时间 2每天几点 3每周周几几点 4每月几号几点';
COMMENT
ON COLUMN "public"."schedule_job"."year" IS '';
COMMENT
ON COLUMN "public"."schedule_job"."month" IS '';
COMMENT
ON COLUMN "public"."schedule_job"."days" IS '';
COMMENT
ON COLUMN "public"."schedule_job"."hour" IS '';
COMMENT
ON COLUMN "public"."schedule_job"."minute" IS '';
COMMENT
ON COLUMN "public"."schedule_job"."dept_id" IS '部门标识';
COMMENT
ON COLUMN "public"."schedule_job"."params_remark" IS '参数备注';
COMMENT
ON COLUMN "public"."schedule_job"."dock_sn" IS '机库sn';
COMMENT
ON COLUMN "public"."schedule_job"."is_breakpoint_fly" IS '是否断点续飞';
COMMENT
ON COLUMN "public"."schedule_job"."break_max_time" IS '断点续飞最大时间';
COMMENT
ON COLUMN "public"."schedule_job"."break_min_time" IS '断点续飞最小时间';
COMMENT
ON TABLE "public"."schedule_job" IS '定时任务';
-- ----------------------------
-- Records of schedule_job
-- ----------------------------
-- ----------------------------
-- Table structure for schedule_job_log
-- ----------------------------
@ -688,7 +721,9 @@ CREATE TABLE "public"."schedule_job_log"
"status" int4,
"error" varchar(2000) COLLATE "pg_catalog"."default",
"times" int4,
"create_date" timestamp(6)
"create_date" timestamp(6),
"dept_id" int8,
"job_name" varchar(50) COLLATE "pg_catalog"."default"
)
;
COMMENT
@ -704,6 +739,10 @@ ON COLUMN "public"."schedule_job_log"."error" IS '失败信息';
COMMENT
ON COLUMN "public"."schedule_job_log"."times" IS '耗时(单位:毫秒)';
COMMENT
ON COLUMN "public"."schedule_job_log"."dept_id" IS '部门标识';
COMMENT
ON COLUMN "public"."schedule_job_log"."job_name" IS '任务名称';
COMMENT
ON TABLE "public"."schedule_job_log" IS '定时任务日志';
-- ----------------------------