Commit 8d0caaa16c68e9fe9718f524a2145051c4aed46c
1 parent
2109449a
【功能新增】IoT:基于 Quartz 实现 IotSchedulerManager!为了兼容 boot 和 cloud!
Showing
6 changed files
with
260 additions
and
10 deletions
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 | 60 | @TableField(typeHandler = JacksonTypeHandler.class) |
| 61 | 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 | 95 | /** |
| 98 | 96 | * 触发条件数组 |
| 99 | 97 | * |
| 100 | - * 当 {@link #type} 为 {@link IotRuleSceneTriggerTypeEnum#DEVICE} 时,必填 | |
| 98 | + * 必填:当 {@link #type} 为 {@link IotRuleSceneTriggerTypeEnum#DEVICE} 时 | |
| 101 | 99 | * 条件与条件之间,是“或”的关系 |
| 102 | 100 | */ |
| 103 | 101 | private List<TriggerCondition> conditions; |
| ... | ... | @@ -105,7 +103,7 @@ public class IotRuleSceneDO extends TenantBaseDO { |
| 105 | 103 | /** |
| 106 | 104 | * CRON 表达式 |
| 107 | 105 | * |
| 108 | - * 当 {@link #type} 为 {@link IotRuleSceneTriggerTypeEnum#TIMER} 时,必填 | |
| 106 | + * 必填:当 {@link #type} 为 {@link IotRuleSceneTriggerTypeEnum#TIMER} 时 | |
| 109 | 107 | */ |
| 110 | 108 | private String cronExpression; |
| 111 | 109 | |
| ... | ... | @@ -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 | 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 | 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 | 23 | import cn.iocoder.yudao.module.iot.service.rule.action.IotRuleSceneAction; |
| 24 | 24 | import jakarta.annotation.Resource; |
| 25 | 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 | 31 | import org.springframework.stereotype.Service; |
| 27 | 32 | import org.springframework.validation.annotation.Validated; |
| 28 | 33 | |
| ... | ... | @@ -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 | 4 | import cn.iocoder.yudao.module.iot.enums.rule.IotRuleSceneActionTypeEnum; |
| 5 | 5 | import cn.iocoder.yudao.module.iot.mq.message.IotDeviceMessage; |
| 6 | 6 | |
| 7 | +import javax.annotation.Nullable; | |
| 8 | + | |
| 7 | 9 | /** |
| 8 | 10 | * IOT 规则场景的场景执行器接口 |
| 9 | 11 | * |
| ... | ... | @@ -14,10 +16,12 @@ public interface IotRuleSceneAction { |
| 14 | 16 | /** |
| 15 | 17 | * 执行场景 |
| 16 | 18 | * |
| 17 | - * @param message 消息 | |
| 19 | + * @param message 消息,允许空 | |
| 20 | + * 1. 空的情况:定时触发 | |
| 21 | + * 2. 非空的情况:设备触发 | |
| 18 | 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 | 43 | |
| 44 | 44 | @Override |
| 45 | 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 | 51 | Assert.notNull(config.getDataBridgeId(), "数据桥梁编号不能为空"); |
| 48 | 52 | IotDataBridgeDO dataBridge = dataBridgeService.getIotDataBridge(config.getDataBridgeId()); |
| 49 | 53 | if (dataBridge == null || dataBridge.getConfig() == null) { | ... | ... |