Commit 8d0caaa16c68e9fe9718f524a2145051c4aed46c

Authored by YunaiV
1 parent 2109449a

【功能新增】IoT:基于 Quartz 实现 IotSchedulerManager!为了兼容 boot 和 cloud!

yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/rule/IotRuleSceneDO.java
@@ -60,8 +60,6 @@ public class IotRuleSceneDO extends TenantBaseDO { @@ -60,8 +60,6 @@ public class IotRuleSceneDO extends TenantBaseDO {
60 @TableField(typeHandler = JacksonTypeHandler.class) 60 @TableField(typeHandler = JacksonTypeHandler.class)
61 private List<TriggerConfig> triggers; 61 private List<TriggerConfig> triggers;
62 62
63 - // TODO @芋艿:需要调研下 https://help.aliyun.com/zh/iot/user-guide/scene-orchestration-1?spm=a2c4g.11186623.help-menu-30520.d_2_4_5_0.45413908fxCSVa  
64 -  
65 /** 63 /**
66 * 执行器数组 64 * 执行器数组
67 */ 65 */
@@ -97,7 +95,7 @@ public class IotRuleSceneDO extends TenantBaseDO { @@ -97,7 +95,7 @@ public class IotRuleSceneDO extends TenantBaseDO {
97 /** 95 /**
98 * 触发条件数组 96 * 触发条件数组
99 * 97 *
100 - * 当 {@link #type} 为 {@link IotRuleSceneTriggerTypeEnum#DEVICE} 时,必填 98 + * 必填:当 {@link #type} 为 {@link IotRuleSceneTriggerTypeEnum#DEVICE} 时
101 * 条件与条件之间,是“或”的关系 99 * 条件与条件之间,是“或”的关系
102 */ 100 */
103 private List<TriggerCondition> conditions; 101 private List<TriggerCondition> conditions;
@@ -105,7 +103,7 @@ public class IotRuleSceneDO extends TenantBaseDO { @@ -105,7 +103,7 @@ public class IotRuleSceneDO extends TenantBaseDO {
105 /** 103 /**
106 * CRON 表达式 104 * CRON 表达式
107 * 105 *
108 - * 当 {@link #type} 为 {@link IotRuleSceneTriggerTypeEnum#TIMER} 时,必填 106 + * 必填:当 {@link #type} 为 {@link IotRuleSceneTriggerTypeEnum#TIMER} 时
109 */ 107 */
110 private String cronExpression; 108 private String cronExpression;
111 109
@@ -185,15 +183,15 @@ public class IotRuleSceneDO extends TenantBaseDO { @@ -185,15 +183,15 @@ public class IotRuleSceneDO extends TenantBaseDO {
185 /** 183 /**
186 * 设备控制 184 * 设备控制
187 * 185 *
188 - * 当 {@link #type} 为 {@link IotRuleSceneActionTypeEnum#DEVICE_CONTROL} 时,必填 186 + * 必填:当 {@link #type} 为 {@link IotRuleSceneActionTypeEnum#DEVICE_CONTROL} 时
189 */ 187 */
190 private ActionDeviceControl deviceControl; 188 private ActionDeviceControl deviceControl;
191 189
192 /** 190 /**
193 * 数据桥接编号 191 * 数据桥接编号
194 * 192 *
195 - * 当 {@link #type} 为 {@link IotRuleSceneActionTypeEnum#DATA_BRIDGE} 时,必填  
196 - * TODO 芋艿:关联 193 + * 必填:当 {@link #type} 为 {@link IotRuleSceneActionTypeEnum#DATA_BRIDGE} 时
  194 + * 关联:{@link IotDataBridgeDO#getId()}
197 */ 195 */
198 private Long dataBridgeId; 196 private Long dataBridgeId;
199 197
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/job/config/IotJobConfiguration.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.framework.job.config;
  2 +
  3 +import cn.iocoder.yudao.module.iot.framework.job.core.IotSchedulerManager;
  4 +import org.springframework.context.ApplicationContext;
  5 +import org.springframework.context.annotation.Bean;
  6 +import org.springframework.context.annotation.Configuration;
  7 +
  8 +import javax.sql.DataSource;
  9 +
  10 +/**
  11 + * IoT 模块的 Job 自动配置类
  12 + *
  13 + * @author 芋道源码
  14 + */
  15 +@Configuration
  16 +public class IotJobConfiguration {
  17 +
  18 + @Bean(initMethod = "start", destroyMethod = "stop")
  19 + public IotSchedulerManager iotSchedulerManager(DataSource dataSource,
  20 + ApplicationContext applicationContext) {
  21 + return new IotSchedulerManager(dataSource, applicationContext);
  22 + }
  23 +
  24 +}
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/job/core/IotSchedulerManager.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.framework.job.core;
  2 +
  3 +import cn.iocoder.yudao.framework.quartz.core.enums.JobDataKeyEnum;
  4 +import lombok.extern.slf4j.Slf4j;
  5 +import org.quartz.*;
  6 +import org.springframework.context.ApplicationContext;
  7 +import org.springframework.scheduling.quartz.SchedulerFactoryBean;
  8 +import org.springframework.scheduling.quartz.SpringBeanJobFactory;
  9 +
  10 +import javax.sql.DataSource;
  11 +import java.util.Map;
  12 +import java.util.Properties;
  13 +
  14 +/**
  15 + * IoT 模块的 Scheduler 管理类,基于 Quartz 实现
  16 + *
  17 + * 疑问:为什么 IoT 模块不复用全局的 SchedulerManager 呢?
  18 + * 回复:yudao-cloud 项目,使用的是 XXL-Job 作为调度中心,无法动态添加任务。
  19 + *
  20 + * @author 芋道源码
  21 + */
  22 +@Slf4j
  23 +public class IotSchedulerManager {
  24 +
  25 + private static final String SCHEDULER_NAME = "iotScheduler";
  26 +
  27 + private final SchedulerFactoryBean schedulerFactoryBean;
  28 +
  29 + private Scheduler scheduler;
  30 +
  31 + public IotSchedulerManager(DataSource dataSource,
  32 + ApplicationContext applicationContext) {
  33 + // 1. 参考 SchedulerFactoryBean 类
  34 + SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean();
  35 + SpringBeanJobFactory jobFactory = new SpringBeanJobFactory();
  36 + jobFactory.setApplicationContext(applicationContext);
  37 + schedulerFactoryBean.setJobFactory(jobFactory);
  38 + schedulerFactoryBean.setAutoStartup(true);
  39 + schedulerFactoryBean.setSchedulerName(SCHEDULER_NAME);
  40 + schedulerFactoryBean.setDataSource(dataSource);
  41 + schedulerFactoryBean.setWaitForJobsToCompleteOnShutdown(true);
  42 + Properties properties = new Properties();
  43 + schedulerFactoryBean.setQuartzProperties(properties);
  44 + // 2. 参考 application-local.yaml 配置文件
  45 + // 2.1 Scheduler 相关配置
  46 + properties.put("org.quartz.scheduler.instanceName", SCHEDULER_NAME);
  47 + properties.put("org.quartz.scheduler.instanceId", "AUTO");
  48 + // 2.2 JobStore 相关配置
  49 + properties.put("org.quartz.jobStore.class", "org.springframework.scheduling.quartz.LocalDataSourceJobStore");
  50 + properties.put("org.quartz.jobStore.isClustered", "true");
  51 + properties.put("org.quartz.jobStore.clusterCheckinInterval", "15000");
  52 + properties.put("org.quartz.jobStore.misfireThreshold", "60000");
  53 + // 2.3 线程池相关配置
  54 + properties.put("org.quartz.threadPool.threadCount", "25");
  55 + properties.put("org.quartz.threadPool.threadPriority", "5");
  56 + properties.put("org.quartz.threadPool.class", "org.quartz.simpl.SimpleThreadPool");
  57 + this.schedulerFactoryBean = schedulerFactoryBean;
  58 + }
  59 +
  60 + public void start() throws Exception {
  61 + log.info("[start][Scheduler 初始化开始]");
  62 + // 初始化
  63 + schedulerFactoryBean.afterPropertiesSet();
  64 + schedulerFactoryBean.start();
  65 + // 获得 Scheduler 对象
  66 + this.scheduler = schedulerFactoryBean.getScheduler();
  67 + log.info("[start][Scheduler 初始化完成]");
  68 + }
  69 +
  70 + public void stop() {
  71 + log.info("[stop][Scheduler 关闭开始]");
  72 + schedulerFactoryBean.stop();
  73 + this.scheduler = null;
  74 + log.info("[stop][Scheduler 关闭完成]");
  75 + }
  76 +
  77 + // ========== 参考 SchedulerManager 实现 ==========
  78 +
  79 + /**
  80 + * 添加或更新 Job 到 Quartz 中
  81 + *
  82 + * @param jobClass 任务处理器的类
  83 + * @param jobHandlerName 任务处理器的名字
  84 + * @param cronExpression CRON 表达式
  85 + * @param jobDataMap 任务数据
  86 + * @throws SchedulerException 添加异常
  87 + */
  88 + public void addOrUpdateJob(Class <? extends Job> jobClass, String jobHandlerName,
  89 + String cronExpression, Map<String, Object> jobDataMap)
  90 + throws SchedulerException {
  91 + if (scheduler.checkExists(new JobKey(jobHandlerName))) {
  92 + this.updateJob(jobHandlerName, cronExpression);
  93 + } else {
  94 + this.addJob(jobClass, jobHandlerName, cronExpression, jobDataMap);
  95 + }
  96 + }
  97 +
  98 + /**
  99 + * 添加 Job 到 Quartz 中
  100 + *
  101 + * @param jobClass 任务处理器的类
  102 + * @param jobHandlerName 任务处理器的名字
  103 + * @param cronExpression CRON 表达式
  104 + * @param jobDataMap 任务数据
  105 + * @throws SchedulerException 添加异常
  106 + */
  107 + public void addJob(Class <? extends Job> jobClass, String jobHandlerName,
  108 + String cronExpression, Map<String, Object> jobDataMap)
  109 + throws SchedulerException {
  110 + // 创建 JobDetail 对象
  111 + JobDetail jobDetail = JobBuilder.newJob(jobClass)
  112 + .usingJobData(new JobDataMap(jobDataMap))
  113 + .withIdentity(jobHandlerName).build();
  114 + // 创建 Trigger 对象
  115 + Trigger trigger = this.buildTrigger(jobHandlerName, cronExpression);
  116 + // 新增 Job 调度
  117 + scheduler.scheduleJob(jobDetail, trigger);
  118 + }
  119 +
  120 + /**
  121 + * 更新 Job 到 Quartz
  122 + *
  123 + * @param jobHandlerName 任务处理器的名字
  124 + * @param cronExpression CRON 表达式
  125 + * @throws SchedulerException 更新异常
  126 + */
  127 + public void updateJob(String jobHandlerName, String cronExpression)
  128 + throws SchedulerException {
  129 + // 创建新 Trigger 对象
  130 + Trigger newTrigger = this.buildTrigger(jobHandlerName, cronExpression);
  131 + // 修改调度
  132 + scheduler.rescheduleJob(new TriggerKey(jobHandlerName), newTrigger);
  133 + }
  134 +
  135 + /**
  136 + * 删除 Quartz 中的 Job
  137 + *
  138 + * @param jobHandlerName 任务处理器的名字
  139 + * @throws SchedulerException 删除异常
  140 + */
  141 + public void deleteJob(String jobHandlerName) throws SchedulerException {
  142 + // 暂停 Trigger 对象
  143 + scheduler.pauseTrigger(new TriggerKey(jobHandlerName));
  144 + // 取消并删除 Job 调度
  145 + scheduler.unscheduleJob(new TriggerKey(jobHandlerName));
  146 + scheduler.deleteJob(new JobKey(jobHandlerName));
  147 + }
  148 +
  149 + /**
  150 + * 暂停 Quartz 中的 Job
  151 + *
  152 + * @param jobHandlerName 任务处理器的名字
  153 + * @throws SchedulerException 暂停异常
  154 + */
  155 + public void pauseJob(String jobHandlerName) throws SchedulerException {
  156 + scheduler.pauseJob(new JobKey(jobHandlerName));
  157 + }
  158 +
  159 + /**
  160 + * 启动 Quartz 中的 Job
  161 + *
  162 + * @param jobHandlerName 任务处理器的名字
  163 + * @throws SchedulerException 启动异常
  164 + */
  165 + public void resumeJob(String jobHandlerName) throws SchedulerException {
  166 + scheduler.resumeJob(new JobKey(jobHandlerName));
  167 + scheduler.resumeTrigger(new TriggerKey(jobHandlerName));
  168 + }
  169 +
  170 + /**
  171 + * 立即触发一次 Quartz 中的 Job
  172 + *
  173 + * @param jobHandlerName 任务处理器的名字
  174 + * @throws SchedulerException 触发异常
  175 + */
  176 + public void triggerJob(String jobHandlerName)
  177 + throws SchedulerException {
  178 + // 触发任务
  179 + JobDataMap data = new JobDataMap();
  180 + data.put(JobDataKeyEnum.JOB_HANDLER_NAME.name(), jobHandlerName);
  181 + scheduler.triggerJob(new JobKey(jobHandlerName), data);
  182 + }
  183 +
  184 + private Trigger buildTrigger(String jobHandlerName, String cronExpression) {
  185 + return TriggerBuilder.newTrigger()
  186 + .withIdentity(jobHandlerName)
  187 + .withSchedule(CronScheduleBuilder.cronSchedule(cronExpression))
  188 + .build();
  189 + }
  190 +
  191 +}
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/IotRuleSceneServiceImpl.java
@@ -23,6 +23,11 @@ import cn.iocoder.yudao.module.iot.mq.message.IotDeviceMessage; @@ -23,6 +23,11 @@ import cn.iocoder.yudao.module.iot.mq.message.IotDeviceMessage;
23 import cn.iocoder.yudao.module.iot.service.rule.action.IotRuleSceneAction; 23 import cn.iocoder.yudao.module.iot.service.rule.action.IotRuleSceneAction;
24 import jakarta.annotation.Resource; 24 import jakarta.annotation.Resource;
25 import lombok.extern.slf4j.Slf4j; 25 import lombok.extern.slf4j.Slf4j;
  26 +import org.quartz.JobKey;
  27 +import org.quartz.Scheduler;
  28 +import org.quartz.SchedulerException;
  29 +import org.quartz.TriggerKey;
  30 +import org.quartz.impl.StdSchedulerFactory;
26 import org.springframework.stereotype.Service; 31 import org.springframework.stereotype.Service;
27 import org.springframework.validation.annotation.Validated; 32 import org.springframework.validation.annotation.Validated;
28 33
@@ -330,4 +335,28 @@ public class IotRuleSceneServiceImpl implements IotRuleSceneService { @@ -330,4 +335,28 @@ public class IotRuleSceneServiceImpl implements IotRuleSceneService {
330 }); 335 });
331 } 336 }
332 337
  338 + // TODO @芋艿:测试思路代码,记得删除!!!
  339 + // 1. Job 类:IotRuleSceneJob
  340 + // 2. 参数:id
  341 + // 3. jobHandlerName:IotRuleSceneJob + id
  342 +
  343 + // 新增:addJob
  344 + // 修改:不存在 addJob、存在 updateJob
  345 + // 有 + 禁用:1)存在、停止;2)不存在:不处理;TODO 测试:直接暂停,是否可以???(结论:可以)
  346 + // 有 + 开启:1)存在,更新;2)不存在,新增;
  347 + // 无 + 禁用、开启:1)存在,删除;TODO 测试:直接删除???(结论:可以)
  348 +
  349 + public static void main2(String[] args) throws SchedulerException {
  350 +// System.out.println(QuartzJobBean.class);
  351 + Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
  352 + scheduler.start();
  353 +
  354 + String jobHandlerName = "123";
  355 + // 暂停 Trigger 对象
  356 + scheduler.pauseTrigger(new TriggerKey(jobHandlerName));
  357 + // 取消并删除 Job 调度
  358 + scheduler.unscheduleJob(new TriggerKey(jobHandlerName));
  359 + scheduler.deleteJob(new JobKey(jobHandlerName));
  360 + }
  361 +
333 } 362 }
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/action/IotRuleSceneAction.java
@@ -4,6 +4,8 @@ import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotRuleSceneDO; @@ -4,6 +4,8 @@ import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotRuleSceneDO;
4 import cn.iocoder.yudao.module.iot.enums.rule.IotRuleSceneActionTypeEnum; 4 import cn.iocoder.yudao.module.iot.enums.rule.IotRuleSceneActionTypeEnum;
5 import cn.iocoder.yudao.module.iot.mq.message.IotDeviceMessage; 5 import cn.iocoder.yudao.module.iot.mq.message.IotDeviceMessage;
6 6
  7 +import javax.annotation.Nullable;
  8 +
7 /** 9 /**
8 * IOT 规则场景的场景执行器接口 10 * IOT 规则场景的场景执行器接口
9 * 11 *
@@ -14,10 +16,12 @@ public interface IotRuleSceneAction { @@ -14,10 +16,12 @@ public interface IotRuleSceneAction {
14 /** 16 /**
15 * 执行场景 17 * 执行场景
16 * 18 *
17 - * @param message 消息 19 + * @param message 消息,允许空
  20 + * 1. 空的情况:定时触发
  21 + * 2. 非空的情况:设备触发
18 * @param config 配置 22 * @param config 配置
19 */ 23 */
20 - void execute(IotDeviceMessage message, IotRuleSceneDO.ActionConfig config); 24 + void execute(@Nullable IotDeviceMessage message, IotRuleSceneDO.ActionConfig config);
21 25
22 /** 26 /**
23 * 获得类型 27 * 获得类型
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/action/IotRuleSceneDataBridgeAction.java
@@ -43,7 +43,11 @@ public class IotRuleSceneDataBridgeAction implements IotRuleSceneAction { @@ -43,7 +43,11 @@ public class IotRuleSceneDataBridgeAction implements IotRuleSceneAction {
43 43
44 @Override 44 @Override
45 public void execute(IotDeviceMessage message, IotRuleSceneDO.ActionConfig config) { 45 public void execute(IotDeviceMessage message, IotRuleSceneDO.ActionConfig config) {
46 - // 1. 获得数据桥梁 46 + // 1.1 如果消息为空,直接返回
  47 + if (message == null) {
  48 + return;
  49 + }
  50 + // 1.2 获得数据桥梁
47 Assert.notNull(config.getDataBridgeId(), "数据桥梁编号不能为空"); 51 Assert.notNull(config.getDataBridgeId(), "数据桥梁编号不能为空");
48 IotDataBridgeDO dataBridge = dataBridgeService.getIotDataBridge(config.getDataBridgeId()); 52 IotDataBridgeDO dataBridge = dataBridgeService.getIotDataBridge(config.getDataBridgeId());
49 if (dataBridge == null || dataBridge.getConfig() == null) { 53 if (dataBridge == null || dataBridge.getConfig() == null) {