diff --git a/intc-modules/intc-iot/src/main/java/com/intc/iot/controller/IotController.java b/intc-modules/intc-iot/src/main/java/com/intc/iot/controller/IotController.java index 0ed63db..7a2f77b 100644 --- a/intc-modules/intc-iot/src/main/java/com/intc/iot/controller/IotController.java +++ b/intc-modules/intc-iot/src/main/java/com/intc/iot/controller/IotController.java @@ -2634,6 +2634,77 @@ public class IotController extends BaseController { } } + /** + * 强制同步开关定时控制到设备(运维补救接口) + * 用于修复历史上因错误删除逻辑导致设备端槽位残留的问题: + * 将数据库中该开关当前全量定时列表推送给设备,设备端多余的历史槽位将被覆盖为 isValid=0 + * + * @param switchId 开关ID + * @return 操作结果 + */ + @Operation(summary = "强制同步定时控制到设备") + @PostMapping("/timingCtrl/syncToDevice") + public R syncTimingCtrlToDevice(@RequestParam Long switchId) { + try { + // 查询开关信息 + DeviceSwitch deviceSwitch = deviceSwitchMapper.selectOne( + new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper() + .eq(DeviceSwitch::getId, switchId) + .select(DeviceSwitch::getId, + DeviceSwitch::getDeviceId, + DeviceSwitch::getIndex, + DeviceSwitch::getSwitchName) + ); + if (deviceSwitch == null) { + return R.fail("开关不存在"); + } + + // 查询设备信息 + Device device = deviceMapper.selectOne( + new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper() + .eq(Device::getId, deviceSwitch.getDeviceId()) + .select(Device::getId, Device::getUserId, Device::getDeviceName, + Device::getIotId, Device::getSerialNum, Device::getWarnCode, Device::getDeadTime) + ); + if (device == null) { + return R.fail("设备不存在"); + } + + // 查询该开关在数据库中的全量定时控制 + java.util.List allTimingCtrls = timingCtrlMapper.selectList( + new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper() + .eq(TimingCtrl::getSwitchId, switchId) + ); + + // 直接构建属性并推送到设备,传 timeCtrlId=-1(无匹配项,跳过特殊时间重算) + // 数据库中不存在的历史槽位不会出现在 listTimingCtrl 中,设备收到后会用 isValid=0 的空槽覆盖 + Map properties = new java.util.HashMap<>(); + java.util.List listTimingCtrl = new java.util.ArrayList<>(); + for (TimingCtrl tc : allTimingCtrls) { + Map timeCtrl = new java.util.HashMap<>(); + timeCtrl.put("timerMode", tc.getLoopType() != null ? tc.getLoopType() - 1 : 0); + timeCtrl.put("isValid", tc.getIsOpen() != null && tc.getIsOpen() == 1 ? 1 : 0); + timeCtrl.put("onTime", String.valueOf(tc.getOpenTime().getTime() / 1000)); + timeCtrl.put("offTime", String.valueOf(tc.getCloseTime().getTime() / 1000)); + listTimingCtrl.add(timeCtrl); + } + String propertyKey = IOTPropertyName.LOCAL_TIMER_SWITCH + deviceSwitch.getIndex(); + properties.put(propertyKey, listTimingCtrl); + + boolean setOk = iotCloudService.setProperty(device.getIotId(), properties, false, 0); + if (!setOk) { + return R.fail("推送设备失败"); + } + + log.info("[SyncTimingCtrl] 强制同步定时控制完成: switchId={}, device={}, 共{}条", + switchId, device.getDeviceName(), listTimingCtrl.size()); + return R.ok(); + } catch (Exception e) { + log.error("强制同步定时控制失败: {}", e.getMessage(), e); + return R.fail("同步失败: " + e.getMessage()); + } + } + /** * 设置定时控制开关 * @@ -3043,10 +3114,15 @@ public class IotController extends BaseController { .eq(TimingCtrl::getSwitchId, dbTimeCtrl.getSwitchId()) ); - // 从列表中移除要删除的定时控制(删除场景直接移出列表,回查时 targetIdx < 0 直接返回成功) - allTimingCtrls.removeIf(tc -> tc.getId().equals(request.getId())); + // 删除场景:保持槽位数组长度不变,将目标项的 isOpen 置为 0(isValid=0) + // 不能直接移除,否则设备端槽位会错位,被删项数据残留 + allTimingCtrls.forEach(tc -> { + if (tc.getId().equals(request.getId())) { + tc.setIsOpen(0L); + } + }); - // 调用设置属性方法,将更新后的定时控制列表同步到设备 + // 调用设置属性方法,将完整槽位列表(含已置无效的目标项)同步到设备 boolean success = setPropertyTimeCtrl(deviceSwitch, device, allTimingCtrls, request.getId()); if (!success) { return R.fail("设置定时控制失败");