前情提要:
我参与了一个任务调度系统,在处理任务的冲突校验模块时,项目经理在资质冲突、时间冲突的基础上又新增了一个任务数量冲突校验,
本着写易扩展,已维护的代码,所以我使用了策略模式来实现。
数据介绍:
任务配置:
任务配置会根据 jobCode(任务编码)、jobType(任务类型)、company(保障公司)、safeguardType(保障类型)
作为一个唯一值(confKey)来配置数据。
最大任务数map (jobConfNumMap)
jobConfNumMap的key -> confKey#航班id value -> 最大任务数
业务流程图:
代码实现:
策略模式部分:
定义接口:
@Component
public interface TaskProcessingStrategy {
void processTask(FlightTaskWithStaffVO vo);
}
实现接口(每个类型的任务都有一个实现类来处理业务):
【系统未预排】
@Slf4j
@Component
public class SystemNotWorkerJobStrategy implements TaskProcessingStrategy {
@Override
public void processTask(FlightTaskWithStaffVO vo) {
log.info("处理类型 【系统未预排】 的数据:{}", vo);
// todo 业务
vo.setIsDelete(true);
}
}
【手动未预排】
@Slf4j
@Component
public class UserNotWorkerJobStrategy implements TaskProcessingStrategy {
@Override
public void processTask(FlightTaskWithStaffVO vo) {
log.info("处理类型 【手动未预排】 的数据:{}", vo);
// todo 业务, 看情况删不删除。
vo.setIsDelete(true);
}
}
【手动已预排】
@Slf4j
@Component
public class UserHaveWorkerJobStrategy implements TaskProcessingStrategy {
@Override
public void processTask(FlightTaskWithStaffVO vo) {
log.info("处理类型 【手动已预排】 的数据:{}", vo);
// 设置数量冲突标识
vo.setIsConflict(true)
.setConflictType(new HashSet<>(CollUtil.addAll(vo.getConflictType(), ConflictTypeEnum.JOB_NUM.getCode())))
.setConflictReason(new HashSet<>(CollUtil.addAll(vo.getConflictReason(), ConflictTypeEnum.JOB_NUM.getDesc())));
// 被标记的冲突后续会被记录到冲突日志中
}
}
context上下文(用于 任务类型与具体实现类的绑定)
@Component
@Slf4j
public class TaskProcessingContext {
@Autowired
private ApplicationContext applicationContext;
private Map<String, TaskProcessingStrategy> strategyMap;
@PostConstruct
private void init() {
// 在初始化时获取 strategyMap, key是实现类的类名
strategyMap = applicationContext.getBeansOfType(TaskProcessingStrategy.class);
if (strategyMap == null || strategyMap.isEmpty()) {
log.warn("No TaskProcessingStrategy beans found in the ApplicationContext.");
} else {
for (String strategyKey : strategyMap.keySet()) {
log.info("key: {}" + strategyKey);
}
}
}
// 执行任务处理的方法
public void processTask(String type, FlightTaskWithStaffVO vo) {
TaskProcessingStrategy strategy = strategyMap.get(type);
if (strategy != null) {
strategy.processTask(vo);
} else {
// 如果没有找到对应类型的策略对象,则抛出异常或者进行其他处理
log.info("No strategy found for type: {}", type);
}
}
}
任务类型枚举:code跟实现类的类名相同,方便strategyMap.get() 获取指定的实现类, desc是优先级,用于排序,优先处理优先级低的任务。
@AllArgsConstructor
@Getter
public enum FlightJobTypePriorityEnum {
/**
* 会从低优先级的类型开始执行
*/
// 系统未预排
SYSTEM_NOT_WORKER_JOB("systemNotWorkerJobStrategy", 0),
// 用户新增未预排
USER_NOT_WORKER_JOB("userNotWorkerJobStrategy", 1),
// 用户新增已预排
USER_HAVE_WORKER_JOB("userHaveWorkerJobStrategy", 2),
;
private String code;
private Integer desc;
// 根据code查询优先级
public static Integer getDescByCode(String code){
return Arrays.stream(FlightJobTypePriorityEnum.values()).filter(s-> Objects.equals(code,s.getCode())).map(s -> s.getDesc())
.findFirst().orElse(99);
}
}
业务实现: (数据查询部分使用注释说明,主要突出如何使用策略模式)
@Transactional(rollbackFor = Exception.class)
public void processTasksBusiness(List<FlightTaskWithStaffVO> tasks) {
// 获取全天的数据
List<FlightTaskWithStaffVO> allTasks;
// 获取每个配置的 任务数量上限
Map<String, Long> jobConfNumMap;
// 根据confKey#航班Id来分组
Map<String, List<FlightTaskWithStaffVO>> allTaskMap = allTasks.stream().filter(f -> ObjUtil.isNotEmpty(f.getSafeguardType())).collect(groupingBy(f -> String.format("%s#%s#%s#%s#%s", f.getJobCode(), f.getJobType(), f.getCompany(), f.getSafeguardType(), f.getFlightCombinationId())));
// 这里可以将map里的逻辑整成一个异步方法,使用虚拟线程调用
allTaskMap.forEach((k, v) -> {
// 获取数据库数据任务数量
Long jobNum = Long.valueOf(v.size());
// 获取配置任务数量
Long jobConfNum = ObjUtil.isNotEmpty(jobConfNumMap.get(k)) ? jobConfNumMap.get(k) : 0L;
// 做对比
if (jobNum > jobConfNum) {
log.info("执行业务的key:{}", k);
Long needHandle = jobNum - jobConfNum;
// 任务数量业务处理逻辑,策略模式入口在这
this.processTasks(needHandle.intValue(), v);
} else if (jobNum < jobConfNum) {
// todo 还原被设置为数量冲突的任务
}
});
Map<String, FlightTaskWithStaffVO> allTaskByGuidMap = allTasks.stream().collect(toMap(FlightTaskWithStaffVO::getGuid, task -> task));
// 同步数据,tasks是调度范围的已预排的数据,allTasks包含了未预排的数据
tasks.forEach(task -> {
FlightTaskWithStaffVO correspondingTask = allTaskByGuidMap.get(task.getGuid());
if (correspondingTask != null) {
task.setIsDelete(correspondingTask.getIsDelete());
task.setIsConflict(correspondingTask.getIsConflict());
task.setConflictType(correspondingTask.getConflictType());
task.setConflictReason(correspondingTask.getConflictReason());
}
});
// 执行删除业务
List<String> delIds = allTasks.stream().filter(f -> Boolean.TRUE.equals(f.getIsDelete())).map(WsbFlightJob::getGuid).toList();
if (CollUtil.isNotEmpty(delIds)) {
this.lambdaUpdate().set(WsbFlightJob::getIsDelete, true)
.set(WsbFlightJob::getRemark, "因超过任务数量上限被删除")
.in(WsbFlightJob::getGuid, delIds)
.update();
}
// 被删除的任务不参与进一步的冲突判定
tasks = tasks.stream().filter(f -> !Boolean.TRUE.equals(f.getIsDelete())).toList();
}
processTasks(遍历执行业务):
@Transactional(rollbackFor = Exception.class)
public void processTasks(Integer differenceValue, List<FlightTaskWithStaffVO> vos) {
final Integer[] N = { differenceValue };
// 按照优先级从低到高排序
vos = vos.stream()
.sorted(Comparator.comparing(FlightTaskWithStaffVO::getFlightJobTypePriority)).toList();
// 遍历N次
vos.stream().forEachOrdered(task -> {
if (N[0] <= 0) {
return; // 已经处理了N条数据,退出处理
}
try {
String flightJobType = task.getFlightJobType();
log.info("type: {}",flightJobType);
// 策略模式上下文,寻找对应的实现类,执行业务逻辑
taskProcessingContext.processTask(flightJobType ,task);
N[0]--;
} catch (Exception e) {
throw new RuntimeException(e);
}
});
}
更新 2024年7月24号(补充优先级,任务类型的判定,在实体中补充)
// 先判定类型,再根据类型获取优先级
public Integer getFlightJobTypePriority() {
if (this.flightJobTypePriority == null) {
if (this.flightJobType == null) {
calculateFlightJobType();
}
this.flightJobTypePriority = FlightJobTypePriorityEnum.getDescByCode(this.flightJobType);
return this.flightJobTypePriority;
} else {
return this.flightJobTypePriority;
}
}
private void calculateFlightJobType() {
if (this.getDataSource().equals(FlightJobDataSourceEnum.SYSTEM.getVal()) && ObjUtil.isNull(
this.getWorkerCode())) {
this.flightJobType = FlightJobTypePriorityEnum.SYSTEM_NOT_WORKER_JOB.getCode();
}
if (this.getDataSource().equals(FlightJobDataSourceEnum.USER.getVal()) && ObjUtil.isNull(
this.getWorkerCode())) {
this.flightJobType = FlightJobTypePriorityEnum.USER_NOT_WORKER_JOB.getCode();
}
if (!this.getDataSource().equals(FlightJobDataSourceEnum.SYSTEM.getVal())
&& ObjUtil.isNotEmpty(this.getWorkerCode())) {
this.flightJobType = FlightJobTypePriorityEnum.USER_HAVE_WORKER_JOB.getCode();
}
if (this.flightJobType == null) {
this.flightJobType = "other";
}
}