# 多联动规则支持 - 修改总结 ## 一、修改概述 与定时规则类似,现在支持**一个任务绑定多个传感器联动规则**: - ✅ 一个任务可以同时配置多个联动规则 - ✅ 每个规则可以绑定不同的传感器设备 - ✅ 同一传感器可以配置多个不同阈值条件的规则 - ✅ 每个规则可独立启用/禁用 - ✅ 规则之间互不干扰,独立触发 ## 二、修改内容 ### 1. 数据库层 #### 现有表: `wfauto_v2_link_rule` 联动规则表已经存在,支持多规则设计: - 通过 `task_id` 字段关联任务(一对多关系) - 每个规则有独立的 `id`、`rule_name`、`rule_code` - 支持冷却时间机制(`cooldown_minutes`) - 记录触发次数(`trigger_count`)和最后触发时间(`last_trigger_at`) ### 2. 实体类 #### 2.1 LinkageRule.java (已存在) 已具备多规则支持的完整结构: - 包含所有联动规则相关字段 - 提供业务方法:`matchCondition()`, `getCooldownSeconds()`, `incrementTriggerCount()` - 支持多种比较运算符:> / >= / < / <= / == ### 3. 数据访问层 #### 3.1 LinkageRuleMapper.java (已修改) **新增方法:** ```java /** * 根据任务ID逻辑删除所有规则 */ int deleteByTaskId(@Param("taskId") Long taskId); ``` **已有方法:** - `insert(LinkageRule)` - 插入规则 - `selectByTaskId(Long)` - 查询任务的所有规则 - `selectEnabledBySensorId(String)` - 查询传感器的启用规则 - `selectBySensorId(String)` - 查询传感器的所有规则 - `updateById(LinkageRule)` - 更新规则 - `deleteById(Long)` - 删除单个规则 ### 4. API层修改 #### 4.1 CreateTaskRequest DTO **新增字段:** ```java /** * 传感器联动规则列表(可选,为空则不配置联动触发) * 支持配置多个联动规则,可以绑定不同传感器或同一传感器的不同阈值条件 */ private List linkageRules; @Data public static class LinkageRuleDTO { @NotBlank(message = "规则名称不能为空") private String ruleName; private String ruleCode; @NotBlank(message = "传感器设备ID不能为空") private String sensorDeviceId; @NotBlank(message = "传感器数据项编码不能为空") private String dataItemCode; // 如: temperature, humidity, soilMoisture @NotBlank(message = "比较运算符不能为空") private String operator; // GT(>), GTE(>=), LT(<), LTE(<=), EQ(=), NEQ(!=) @NotNull(message = "阈值不能为空") private Double threshold; private Integer cooldownMinutes; // 默认30分钟 private Boolean enabled; // 默认true } ``` ### 5. 业务逻辑层修改 #### 5.1 IrrigationTaskController.java **修改的方法:** 1. **createTask()** - 支持创建多个联动规则 ```java // 5. 保存联动规则(如果配置了) if (request.getLinkageRules() != null && !request.getLinkageRules().isEmpty()) { for (LinkageRuleDTO linkageDTO : request.getLinkageRules()) { LinkageRule linkageRule = LinkageRule.builder() .taskId(taskId) .ruleName(linkageDTO.getRuleName()) .ruleCode(linkageDTO.getRuleCode()) .sensorDeviceId(linkageDTO.getSensorDeviceId()) .sensorDataType(linkageDTO.getDataItemCode()) .operator(linkageDTO.getOperator()) .threshold(BigDecimal.valueOf(linkageDTO.getThreshold())) .cooldownMinutes(linkageDTO.getCooldownMinutes() != null ? linkageDTO.getCooldownMinutes() : 30) .enabled(linkageDTO.getEnabled() != null ? linkageDTO.getEnabled() : true) .triggerCount(0) .tenantId(request.getTenantId()) .createdAt(LocalDateTime.now()) .deleted(false) .build(); linkageRuleMapper.insert(linkageRule); } } ``` 2. **deleteTask()** - 删除所有联动规则 ```java // 逻辑删除所有联动规则 linkageRuleMapper.deleteByTaskId(id); ``` ## 三、API 使用示例 ### 3.1 创建任务(多个联动规则) ```http POST /api/tasks Content-Type: application/json { "taskName": "农场A区智能轮灌", "triggerType": "LINKAGE", "linkageRules": [ { "ruleName": "土壤湿度过低触发", "ruleCode": "SOIL_MOISTURE_LOW", "sensorDeviceId": "sensor-soil-001", "dataItemCode": "soilMoisture", "operator": "<", "threshold": 30.0, "cooldownMinutes": 60, "enabled": true }, { "ruleName": "温度过高触发", "ruleCode": "TEMP_HIGH", "sensorDeviceId": "sensor-temp-001", "dataItemCode": "temperature", "operator": ">", "threshold": 35.0, "cooldownMinutes": 30, "enabled": true }, { "ruleName": "土壤湿度传感器备用规则", "ruleCode": "SOIL_MOISTURE_LOW_BACKUP", "sensorDeviceId": "sensor-soil-002", "dataItemCode": "soilMoisture", "operator": "<=", "threshold": 25.0, "cooldownMinutes": 60, "enabled": true } ], "pumpId": "pump-001", "pressureMode": "PUMP_UNIFIED", "targetPressureKpa": 300, "groupConfigs": [ { "groupId": 101, "sortOrder": 1, "irrigationDurationMinutes": 30 } ], "tenantId": 1 } ``` ### 3.2 定时+联动混合模式 ```json { "taskName": "农场B区混合触发灌溉", "triggerType": "SCHEDULED", "scheduleRules": [ { "ruleName": "每天凌晨2点", "scheduleType": "CRON", "cronExpression": "0 0 2 * * ?", "enabled": true } ], "linkageRules": [ { "ruleName": "土壤湿度应急补水", "sensorDeviceId": "sensor-soil-003", "dataItemCode": "soilMoisture", "operator": "<", "threshold": 20.0, "cooldownMinutes": 120, "enabled": true } ], "pumpId": "pump-002", "pressureMode": "PUMP_ZONE", "groupConfigs": [ { "groupId": 201, "sortOrder": 1, "irrigationDurationMinutes": 25 } ], "tenantId": 1 } ``` ### 3.3 查询任务的联动规则 ```sql SELECT * FROM wfauto_v2_link_rule WHERE task_id = 1 AND deleted = 0 ORDER BY id; ``` ### 3.4 查询规则触发状态 ```sql SELECT r.id, r.rule_name, r.sensor_device_id, r.sensor_data_type, r.operator, r.threshold, r.cooldown_minutes, r.enabled, r.trigger_count, r.last_trigger_at, TIMESTAMPDIFF(MINUTE, r.last_trigger_at, NOW()) AS minutes_since_last_trigger FROM wfauto_v2_link_rule r WHERE r.task_id = 1 AND r.deleted = 0 ORDER BY r.id; ``` ### 3.5 查询传感器关联的所有规则 ```sql SELECT r.id, r.rule_name, r.task_id, t.task_name, r.operator, r.threshold, r.enabled FROM wfauto_v2_link_rule r LEFT JOIN wfauto_v2_irrigation_task t ON t.id = r.task_id WHERE r.sensor_device_id = 'sensor-soil-001' AND r.deleted = 0 AND r.enabled = 1; ``` ## 四、技术优势 ### 4.1 灵活性 - ✅ 支持同时配置多个传感器的联动规则 - ✅ 同一传感器可配置多个不同阈值的规则 - ✅ 每个规则可独立启用/禁用 - ✅ 规则可动态增删改 ### 4.2 可靠性 - ✅ 冷却时间机制防止频繁触发 - ✅ 触发次数统计便于分析规则效率 - ✅ 多规则冗余设计提高系统容错性 ### 4.3 可维护性 - ✅ 联动规则独立管理,职责清晰 - ✅ 符合关系型数据库设计规范 - ✅ 便于后续扩展(如增加规则优先级、复合条件等) ### 4.4 性能 - ✅ 通过传感器ID索引快速查询相关规则 - ✅ 冷却时间机制减少不必要的任务触发 - ✅ 规则触发状态独立记录,互不影响 ### 4.5 安全性 - ✅ 每个规则独立的冷却时间控制 - ✅ 触发次数限制防止恶意触发 - ✅ 规则故障不影响其他规则 ## 五、与定时规则的对比 | 特性 | 定时规则 | 联动规则 | |------|---------|---------| | 触发方式 | 基于时间调度 | 基于传感器数据 | | 调度引擎 | Quartz | 事件驱动 | | 防重复机制 | Quartz自带 | 冷却时间 | | 执行次数控制 | SIMPLE模式支持 | 无限制,统计触发次数 | | 状态管理 | ACTIVE/COMPLETED/DISABLED | enabled字段 | | 独立性 | 每个规则独立Job | 每个规则独立判断 | | 共同点 | 支持多规则,互不干扰,独立配置 | 支持多规则,互不干扰,独立配置 | ## 六、注意事项 ### 6.1 规则设计 - ⚠️ 合理设置冷却时间,避免频繁触发导致设备损耗 - ⚠️ 同一传感器的多个规则应设置不同阈值,避免重复触发 - ⚠️ 建议为关键传感器配置备用规则,提高可靠性 ### 6.2 触发机制 - ⚠️ 传感器数据需要通过消息队列或API推送到系统 - ⚠️ 系统根据 `sensorDeviceId` 查询启用的规则并匹配条件 - ⚠️ 冷却时间内的触发请求会被忽略,不会执行任务 ### 6.3 并发控制 - ⚠️ 多个联动规则同时满足条件时,需要任务级别的并发防护 - ⚠️ 建议在 `TaskTriggerService.linkageTrigger()` 中实现分布式锁 - ⚠️ 避免同一任务因多个规则同时触发导致重复执行 ### 6.4 数据精度 - ⚠️ 阈值比较使用 `BigDecimal` 确保精度 - ⚠️ 相等比较(`==`)使用0.001误差容忍度 - ⚠️ 传感器数据应做异常值过滤 ### 6.5 规则数量 - ⚠️ 单个任务建议不超过10个联动规则 - ⚠️ 同一传感器建议不超过5个规则 - ⚠️ 规则过多会增加匹配开销 ## 七、后续优化建议 ### 7.1 功能扩展 - [ ] 支持复合条件(AND/OR逻辑) - [ ] 支持规则优先级 - [ ] 支持时间窗口限制(仅在特定时间段生效) - [ ] 支持规则触发历史查询 - [ ] 支持规则有效期设置 ### 7.2 性能优化 - [ ] 规则匹配缓存 - [ ] 传感器数据变化订阅机制 - [ ] 批量规则匹配 - [ ] 规则热数据缓存(Redis) ### 7.3 智能化 - [ ] 基于历史数据的阈值自适应调整 - [ ] 规则效果分析(触发次数、成功率等) - [ ] 异常规则自动禁用 - [ ] 规则冲突检测 ## 八、文件清单 ### 8.1 修改文件 - `src/main/java/cn/sciento/farm/automationv2/api/dto/CreateTaskRequest.java` - 新增 LinkageRuleDTO - `src/main/java/cn/sciento/farm/automationv2/api/controller/IrrigationTaskController.java` - 支持多联动规则 - `src/main/java/cn/sciento/farm/automationv2/infra/mapper/LinkageRuleMapper.java` - 新增 deleteByTaskId 方法 ### 8.2 已存在(无需修改) - `src/main/java/cn/sciento/farm/automationv2/domain/entity/LinkageRule.java` - 已支持多规则设计 - `src/main/java/cn/sciento/farm/automationv2/domain/repository/LinkageRuleRepository.java` - `src/main/java/cn/sciento/farm/automationv2/infra/repository/impl/LinkageRuleRepositoryImpl.java` ## 九、测试建议 ### 9.1 单元测试 - [ ] LinkageRule 实体类业务方法测试(matchCondition等) - [ ] LinkageRuleMapper 数据访问测试 - [ ] 冷却时间机制测试 ### 9.2 集成测试 - [ ] 创建任务(多个联动规则)测试 - [ ] 删除任务(级联删除规则)测试 - [ ] 规则独立触发测试 - [ ] 冷却时间有效性测试 - [ ] 并发触发防护测试 ### 9.3 端到端测试 - [ ] 多规则同时满足条件场景 - [ ] 同一传感器多个阈值规则 - [ ] 定时+联动混合触发 - [ ] 规则故障不影响其他规则 - [ ] 传感器数据异常处理 ## 十、与传感器服务集成建议 ### 10.1 数据推送方式 ```java // 方式1: 消息队列(推荐) @KafkaListener(topics = "sensor-data") public void handleSensorData(SensorDataEvent event) { // 查询该传感器的所有启用规则 List rules = linkageRuleMapper .selectEnabledBySensorId(event.getSensorDeviceId()); for (LinkageRule rule : rules) { // 检查是否在冷却期 if (isInCooldown(rule)) continue; // 匹配条件 if (rule.matchCondition(event.getValue())) { // 触发任务 taskTriggerService.linkageTrigger(rule.getTaskId(), rule.getId()); } } } // 方式2: HTTP API @PostMapping("/api/sensors/{sensorId}/data") public Result receiveSensorData( @PathVariable String sensorId, @RequestBody SensorDataDTO data) { // 同上逻辑 } ``` ### 10.2 冷却期检查 ```java private boolean isInCooldown(LinkageRule rule) { if (rule.getLastTriggerAt() == null) { return false; } long minutesSinceLastTrigger = ChronoUnit.MINUTES.between( rule.getLastTriggerAt(), LocalDateTime.now() ); return minutesSinceLastTrigger < rule.getCooldownMinutes(); } ``` --- **修改完成时间:** 2026-03-02 **修改人:** Claude Sonnet 4.5 **版本:** v2.0