【功能新增】IoT:基于 Quartz 实现 IotSchedulerManager!为了兼容 boot 和 cloud!
This commit is contained in:
parent
2109449a89
commit
8d0caaa16c
|
@ -60,8 +60,6 @@ public class IotRuleSceneDO extends TenantBaseDO {
|
||||||
@TableField(typeHandler = JacksonTypeHandler.class)
|
@TableField(typeHandler = JacksonTypeHandler.class)
|
||||||
private List<TriggerConfig> triggers;
|
private List<TriggerConfig> triggers;
|
||||||
|
|
||||||
// TODO @芋艿:需要调研下 https://help.aliyun.com/zh/iot/user-guide/scene-orchestration-1?spm=a2c4g.11186623.help-menu-30520.d_2_4_5_0.45413908fxCSVa
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 执行器数组
|
* 执行器数组
|
||||||
*/
|
*/
|
||||||
|
@ -97,7 +95,7 @@ public class IotRuleSceneDO extends TenantBaseDO {
|
||||||
/**
|
/**
|
||||||
* 触发条件数组
|
* 触发条件数组
|
||||||
*
|
*
|
||||||
* 当 {@link #type} 为 {@link IotRuleSceneTriggerTypeEnum#DEVICE} 时,必填
|
* 必填:当 {@link #type} 为 {@link IotRuleSceneTriggerTypeEnum#DEVICE} 时
|
||||||
* 条件与条件之间,是“或”的关系
|
* 条件与条件之间,是“或”的关系
|
||||||
*/
|
*/
|
||||||
private List<TriggerCondition> conditions;
|
private List<TriggerCondition> conditions;
|
||||||
|
@ -105,7 +103,7 @@ public class IotRuleSceneDO extends TenantBaseDO {
|
||||||
/**
|
/**
|
||||||
* CRON 表达式
|
* CRON 表达式
|
||||||
*
|
*
|
||||||
* 当 {@link #type} 为 {@link IotRuleSceneTriggerTypeEnum#TIMER} 时,必填
|
* 必填:当 {@link #type} 为 {@link IotRuleSceneTriggerTypeEnum#TIMER} 时
|
||||||
*/
|
*/
|
||||||
private String cronExpression;
|
private String cronExpression;
|
||||||
|
|
||||||
|
@ -185,15 +183,15 @@ public class IotRuleSceneDO extends TenantBaseDO {
|
||||||
/**
|
/**
|
||||||
* 设备控制
|
* 设备控制
|
||||||
*
|
*
|
||||||
* 当 {@link #type} 为 {@link IotRuleSceneActionTypeEnum#DEVICE_CONTROL} 时,必填
|
* 必填:当 {@link #type} 为 {@link IotRuleSceneActionTypeEnum#DEVICE_CONTROL} 时
|
||||||
*/
|
*/
|
||||||
private ActionDeviceControl deviceControl;
|
private ActionDeviceControl deviceControl;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 数据桥接编号
|
* 数据桥接编号
|
||||||
*
|
*
|
||||||
* 当 {@link #type} 为 {@link IotRuleSceneActionTypeEnum#DATA_BRIDGE} 时,必填
|
* 必填:当 {@link #type} 为 {@link IotRuleSceneActionTypeEnum#DATA_BRIDGE} 时
|
||||||
* TODO 芋艿:关联
|
* 关联:{@link IotDataBridgeDO#getId()}
|
||||||
*/
|
*/
|
||||||
private Long dataBridgeId;
|
private Long dataBridgeId;
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
package cn.iocoder.yudao.module.iot.framework.job.config;
|
||||||
|
|
||||||
|
import cn.iocoder.yudao.module.iot.framework.job.core.IotSchedulerManager;
|
||||||
|
import org.springframework.context.ApplicationContext;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
import javax.sql.DataSource;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IoT 模块的 Job 自动配置类
|
||||||
|
*
|
||||||
|
* @author 芋道源码
|
||||||
|
*/
|
||||||
|
@Configuration
|
||||||
|
public class IotJobConfiguration {
|
||||||
|
|
||||||
|
@Bean(initMethod = "start", destroyMethod = "stop")
|
||||||
|
public IotSchedulerManager iotSchedulerManager(DataSource dataSource,
|
||||||
|
ApplicationContext applicationContext) {
|
||||||
|
return new IotSchedulerManager(dataSource, applicationContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,191 @@
|
||||||
|
package cn.iocoder.yudao.module.iot.framework.job.core;
|
||||||
|
|
||||||
|
import cn.iocoder.yudao.framework.quartz.core.enums.JobDataKeyEnum;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.quartz.*;
|
||||||
|
import org.springframework.context.ApplicationContext;
|
||||||
|
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
|
||||||
|
import org.springframework.scheduling.quartz.SpringBeanJobFactory;
|
||||||
|
|
||||||
|
import javax.sql.DataSource;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Properties;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IoT 模块的 Scheduler 管理类,基于 Quartz 实现
|
||||||
|
*
|
||||||
|
* 疑问:为什么 IoT 模块不复用全局的 SchedulerManager 呢?
|
||||||
|
* 回复:yudao-cloud 项目,使用的是 XXL-Job 作为调度中心,无法动态添加任务。
|
||||||
|
*
|
||||||
|
* @author 芋道源码
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public class IotSchedulerManager {
|
||||||
|
|
||||||
|
private static final String SCHEDULER_NAME = "iotScheduler";
|
||||||
|
|
||||||
|
private final SchedulerFactoryBean schedulerFactoryBean;
|
||||||
|
|
||||||
|
private Scheduler scheduler;
|
||||||
|
|
||||||
|
public IotSchedulerManager(DataSource dataSource,
|
||||||
|
ApplicationContext applicationContext) {
|
||||||
|
// 1. 参考 SchedulerFactoryBean 类
|
||||||
|
SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean();
|
||||||
|
SpringBeanJobFactory jobFactory = new SpringBeanJobFactory();
|
||||||
|
jobFactory.setApplicationContext(applicationContext);
|
||||||
|
schedulerFactoryBean.setJobFactory(jobFactory);
|
||||||
|
schedulerFactoryBean.setAutoStartup(true);
|
||||||
|
schedulerFactoryBean.setSchedulerName(SCHEDULER_NAME);
|
||||||
|
schedulerFactoryBean.setDataSource(dataSource);
|
||||||
|
schedulerFactoryBean.setWaitForJobsToCompleteOnShutdown(true);
|
||||||
|
Properties properties = new Properties();
|
||||||
|
schedulerFactoryBean.setQuartzProperties(properties);
|
||||||
|
// 2. 参考 application-local.yaml 配置文件
|
||||||
|
// 2.1 Scheduler 相关配置
|
||||||
|
properties.put("org.quartz.scheduler.instanceName", SCHEDULER_NAME);
|
||||||
|
properties.put("org.quartz.scheduler.instanceId", "AUTO");
|
||||||
|
// 2.2 JobStore 相关配置
|
||||||
|
properties.put("org.quartz.jobStore.class", "org.springframework.scheduling.quartz.LocalDataSourceJobStore");
|
||||||
|
properties.put("org.quartz.jobStore.isClustered", "true");
|
||||||
|
properties.put("org.quartz.jobStore.clusterCheckinInterval", "15000");
|
||||||
|
properties.put("org.quartz.jobStore.misfireThreshold", "60000");
|
||||||
|
// 2.3 线程池相关配置
|
||||||
|
properties.put("org.quartz.threadPool.threadCount", "25");
|
||||||
|
properties.put("org.quartz.threadPool.threadPriority", "5");
|
||||||
|
properties.put("org.quartz.threadPool.class", "org.quartz.simpl.SimpleThreadPool");
|
||||||
|
this.schedulerFactoryBean = schedulerFactoryBean;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void start() throws Exception {
|
||||||
|
log.info("[start][Scheduler 初始化开始]");
|
||||||
|
// 初始化
|
||||||
|
schedulerFactoryBean.afterPropertiesSet();
|
||||||
|
schedulerFactoryBean.start();
|
||||||
|
// 获得 Scheduler 对象
|
||||||
|
this.scheduler = schedulerFactoryBean.getScheduler();
|
||||||
|
log.info("[start][Scheduler 初始化完成]");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void stop() {
|
||||||
|
log.info("[stop][Scheduler 关闭开始]");
|
||||||
|
schedulerFactoryBean.stop();
|
||||||
|
this.scheduler = null;
|
||||||
|
log.info("[stop][Scheduler 关闭完成]");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== 参考 SchedulerManager 实现 ==========
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加或更新 Job 到 Quartz 中
|
||||||
|
*
|
||||||
|
* @param jobClass 任务处理器的类
|
||||||
|
* @param jobHandlerName 任务处理器的名字
|
||||||
|
* @param cronExpression CRON 表达式
|
||||||
|
* @param jobDataMap 任务数据
|
||||||
|
* @throws SchedulerException 添加异常
|
||||||
|
*/
|
||||||
|
public void addOrUpdateJob(Class <? extends Job> jobClass, String jobHandlerName,
|
||||||
|
String cronExpression, Map<String, Object> jobDataMap)
|
||||||
|
throws SchedulerException {
|
||||||
|
if (scheduler.checkExists(new JobKey(jobHandlerName))) {
|
||||||
|
this.updateJob(jobHandlerName, cronExpression);
|
||||||
|
} else {
|
||||||
|
this.addJob(jobClass, jobHandlerName, cronExpression, jobDataMap);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加 Job 到 Quartz 中
|
||||||
|
*
|
||||||
|
* @param jobClass 任务处理器的类
|
||||||
|
* @param jobHandlerName 任务处理器的名字
|
||||||
|
* @param cronExpression CRON 表达式
|
||||||
|
* @param jobDataMap 任务数据
|
||||||
|
* @throws SchedulerException 添加异常
|
||||||
|
*/
|
||||||
|
public void addJob(Class <? extends Job> jobClass, String jobHandlerName,
|
||||||
|
String cronExpression, Map<String, Object> jobDataMap)
|
||||||
|
throws SchedulerException {
|
||||||
|
// 创建 JobDetail 对象
|
||||||
|
JobDetail jobDetail = JobBuilder.newJob(jobClass)
|
||||||
|
.usingJobData(new JobDataMap(jobDataMap))
|
||||||
|
.withIdentity(jobHandlerName).build();
|
||||||
|
// 创建 Trigger 对象
|
||||||
|
Trigger trigger = this.buildTrigger(jobHandlerName, cronExpression);
|
||||||
|
// 新增 Job 调度
|
||||||
|
scheduler.scheduleJob(jobDetail, trigger);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新 Job 到 Quartz
|
||||||
|
*
|
||||||
|
* @param jobHandlerName 任务处理器的名字
|
||||||
|
* @param cronExpression CRON 表达式
|
||||||
|
* @throws SchedulerException 更新异常
|
||||||
|
*/
|
||||||
|
public void updateJob(String jobHandlerName, String cronExpression)
|
||||||
|
throws SchedulerException {
|
||||||
|
// 创建新 Trigger 对象
|
||||||
|
Trigger newTrigger = this.buildTrigger(jobHandlerName, cronExpression);
|
||||||
|
// 修改调度
|
||||||
|
scheduler.rescheduleJob(new TriggerKey(jobHandlerName), newTrigger);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除 Quartz 中的 Job
|
||||||
|
*
|
||||||
|
* @param jobHandlerName 任务处理器的名字
|
||||||
|
* @throws SchedulerException 删除异常
|
||||||
|
*/
|
||||||
|
public void deleteJob(String jobHandlerName) throws SchedulerException {
|
||||||
|
// 暂停 Trigger 对象
|
||||||
|
scheduler.pauseTrigger(new TriggerKey(jobHandlerName));
|
||||||
|
// 取消并删除 Job 调度
|
||||||
|
scheduler.unscheduleJob(new TriggerKey(jobHandlerName));
|
||||||
|
scheduler.deleteJob(new JobKey(jobHandlerName));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 暂停 Quartz 中的 Job
|
||||||
|
*
|
||||||
|
* @param jobHandlerName 任务处理器的名字
|
||||||
|
* @throws SchedulerException 暂停异常
|
||||||
|
*/
|
||||||
|
public void pauseJob(String jobHandlerName) throws SchedulerException {
|
||||||
|
scheduler.pauseJob(new JobKey(jobHandlerName));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 启动 Quartz 中的 Job
|
||||||
|
*
|
||||||
|
* @param jobHandlerName 任务处理器的名字
|
||||||
|
* @throws SchedulerException 启动异常
|
||||||
|
*/
|
||||||
|
public void resumeJob(String jobHandlerName) throws SchedulerException {
|
||||||
|
scheduler.resumeJob(new JobKey(jobHandlerName));
|
||||||
|
scheduler.resumeTrigger(new TriggerKey(jobHandlerName));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 立即触发一次 Quartz 中的 Job
|
||||||
|
*
|
||||||
|
* @param jobHandlerName 任务处理器的名字
|
||||||
|
* @throws SchedulerException 触发异常
|
||||||
|
*/
|
||||||
|
public void triggerJob(String jobHandlerName)
|
||||||
|
throws SchedulerException {
|
||||||
|
// 触发任务
|
||||||
|
JobDataMap data = new JobDataMap();
|
||||||
|
data.put(JobDataKeyEnum.JOB_HANDLER_NAME.name(), jobHandlerName);
|
||||||
|
scheduler.triggerJob(new JobKey(jobHandlerName), data);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Trigger buildTrigger(String jobHandlerName, String cronExpression) {
|
||||||
|
return TriggerBuilder.newTrigger()
|
||||||
|
.withIdentity(jobHandlerName)
|
||||||
|
.withSchedule(CronScheduleBuilder.cronSchedule(cronExpression))
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -23,6 +23,11 @@ import cn.iocoder.yudao.module.iot.mq.message.IotDeviceMessage;
|
||||||
import cn.iocoder.yudao.module.iot.service.rule.action.IotRuleSceneAction;
|
import cn.iocoder.yudao.module.iot.service.rule.action.IotRuleSceneAction;
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.quartz.JobKey;
|
||||||
|
import org.quartz.Scheduler;
|
||||||
|
import org.quartz.SchedulerException;
|
||||||
|
import org.quartz.TriggerKey;
|
||||||
|
import org.quartz.impl.StdSchedulerFactory;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.validation.annotation.Validated;
|
import org.springframework.validation.annotation.Validated;
|
||||||
|
|
||||||
|
@ -330,4 +335,28 @@ public class IotRuleSceneServiceImpl implements IotRuleSceneService {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO @芋艿:测试思路代码,记得删除!!!
|
||||||
|
// 1. Job 类:IotRuleSceneJob
|
||||||
|
// 2. 参数:id
|
||||||
|
// 3. jobHandlerName:IotRuleSceneJob + id
|
||||||
|
|
||||||
|
// 新增:addJob
|
||||||
|
// 修改:不存在 addJob、存在 updateJob
|
||||||
|
// 有 + 禁用:1)存在、停止;2)不存在:不处理;TODO 测试:直接暂停,是否可以???(结论:可以)
|
||||||
|
// 有 + 开启:1)存在,更新;2)不存在,新增;
|
||||||
|
// 无 + 禁用、开启:1)存在,删除;TODO 测试:直接删除???(结论:可以)
|
||||||
|
|
||||||
|
public static void main2(String[] args) throws SchedulerException {
|
||||||
|
// System.out.println(QuartzJobBean.class);
|
||||||
|
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
|
||||||
|
scheduler.start();
|
||||||
|
|
||||||
|
String jobHandlerName = "123";
|
||||||
|
// 暂停 Trigger 对象
|
||||||
|
scheduler.pauseTrigger(new TriggerKey(jobHandlerName));
|
||||||
|
// 取消并删除 Job 调度
|
||||||
|
scheduler.unscheduleJob(new TriggerKey(jobHandlerName));
|
||||||
|
scheduler.deleteJob(new JobKey(jobHandlerName));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,8 @@ import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotRuleSceneDO;
|
||||||
import cn.iocoder.yudao.module.iot.enums.rule.IotRuleSceneActionTypeEnum;
|
import cn.iocoder.yudao.module.iot.enums.rule.IotRuleSceneActionTypeEnum;
|
||||||
import cn.iocoder.yudao.module.iot.mq.message.IotDeviceMessage;
|
import cn.iocoder.yudao.module.iot.mq.message.IotDeviceMessage;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* IOT 规则场景的场景执行器接口
|
* IOT 规则场景的场景执行器接口
|
||||||
*
|
*
|
||||||
|
@ -14,10 +16,12 @@ public interface IotRuleSceneAction {
|
||||||
/**
|
/**
|
||||||
* 执行场景
|
* 执行场景
|
||||||
*
|
*
|
||||||
* @param message 消息
|
* @param message 消息,允许空
|
||||||
|
* 1. 空的情况:定时触发
|
||||||
|
* 2. 非空的情况:设备触发
|
||||||
* @param config 配置
|
* @param config 配置
|
||||||
*/
|
*/
|
||||||
void execute(IotDeviceMessage message, IotRuleSceneDO.ActionConfig config);
|
void execute(@Nullable IotDeviceMessage message, IotRuleSceneDO.ActionConfig config);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获得类型
|
* 获得类型
|
||||||
|
|
|
@ -43,7 +43,11 @@ public class IotRuleSceneDataBridgeAction implements IotRuleSceneAction {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void execute(IotDeviceMessage message, IotRuleSceneDO.ActionConfig config) {
|
public void execute(IotDeviceMessage message, IotRuleSceneDO.ActionConfig config) {
|
||||||
// 1. 获得数据桥梁
|
// 1.1 如果消息为空,直接返回
|
||||||
|
if (message == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 1.2 获得数据桥梁
|
||||||
Assert.notNull(config.getDataBridgeId(), "数据桥梁编号不能为空");
|
Assert.notNull(config.getDataBridgeId(), "数据桥梁编号不能为空");
|
||||||
IotDataBridgeDO dataBridge = dataBridgeService.getIotDataBridge(config.getDataBridgeId());
|
IotDataBridgeDO dataBridge = dataBridgeService.getIotDataBridge(config.getDataBridgeId());
|
||||||
if (dataBridge == null || dataBridge.getConfig() == null) {
|
if (dataBridge == null || dataBridge.getConfig() == null) {
|
||||||
|
|
Loading…
Reference in New Issue