diff --git a/intc-common/intc-common-json/src/main/java/com/intc/common/json/config/JacksonConfig.java b/intc-common/intc-common-json/src/main/java/com/intc/common/json/config/JacksonConfig.java index 8bbbe0d..32c1017 100644 --- a/intc-common/intc-common-json/src/main/java/com/intc/common/json/config/JacksonConfig.java +++ b/intc-common/intc-common-json/src/main/java/com/intc/common/json/config/JacksonConfig.java @@ -8,6 +8,7 @@ import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer; import lombok.extern.slf4j.Slf4j; import com.intc.common.json.handler.BigNumberSerializer; import com.intc.common.json.handler.CustomDateDeserializer; +import com.intc.common.json.handler.LongDeserializer; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer; import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; @@ -33,8 +34,12 @@ public class JacksonConfig { public Module registerJavaTimeModule() { // 全局配置序列化返回 JSON 处理 JavaTimeModule javaTimeModule = new JavaTimeModule(); + // Long 类型序列化:超出JS安全范围转字符串 javaTimeModule.addSerializer(Long.class, BigNumberSerializer.INSTANCE); javaTimeModule.addSerializer(Long.TYPE, BigNumberSerializer.INSTANCE); + // Long 类型反序列化:支持字符串转 Long + javaTimeModule.addDeserializer(Long.class, new LongDeserializer()); + javaTimeModule.addDeserializer(Long.TYPE, new LongDeserializer()); javaTimeModule.addSerializer(BigInteger.class, BigNumberSerializer.INSTANCE); javaTimeModule.addSerializer(BigDecimal.class, ToStringSerializer.instance); DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); diff --git a/intc-common/intc-common-json/src/main/java/com/intc/common/json/handler/LongDeserializer.java b/intc-common/intc-common-json/src/main/java/com/intc/common/json/handler/LongDeserializer.java new file mode 100644 index 0000000..2b0dd13 --- /dev/null +++ b/intc-common/intc-common-json/src/main/java/com/intc/common/json/handler/LongDeserializer.java @@ -0,0 +1,29 @@ +package com.intc.common.json.handler; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; + +import java.io.IOException; + +/** + * Long类型反序列化器 + * 支持从字符串或数字解析Long,解决前端精度丢失问题 + * + * @author intc + */ +public class LongDeserializer extends JsonDeserializer { + + @Override + public Long deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + String value = p.getValueAsString(); + if (value == null || value.trim().isEmpty()) { + return null; + } + try { + return Long.parseLong(value.trim()); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("Invalid Long value: " + value); + } + } +} diff --git a/intc-modules/intc-fishery/src/main/java/com/intc/fishery/constant/DefineDeviceWarnCode.java b/intc-modules/intc-fishery/src/main/java/com/intc/fishery/constant/DefineDeviceWarnCode.java index 22b1166..81cb358 100644 --- a/intc-modules/intc-fishery/src/main/java/com/intc/fishery/constant/DefineDeviceWarnCode.java +++ b/intc-modules/intc-fishery/src/main/java/com/intc/fishery/constant/DefineDeviceWarnCode.java @@ -51,6 +51,11 @@ public class DefineDeviceWarnCode { */ public static final int DeviceDead = 64; + /** + * 溶解氧探头未校准 + */ + public static final int OxyDetectorNoCorrect = 1; + private DefineDeviceWarnCode() { } diff --git a/intc-modules/intc-fishery/src/main/java/com/intc/fishery/controller/DeviceController.java b/intc-modules/intc-fishery/src/main/java/com/intc/fishery/controller/DeviceController.java index ad8c2c1..e1a9964 100644 --- a/intc-modules/intc-fishery/src/main/java/com/intc/fishery/controller/DeviceController.java +++ b/intc-modules/intc-fishery/src/main/java/com/intc/fishery/controller/DeviceController.java @@ -24,16 +24,36 @@ import com.intc.common.mybatis.core.page.TableDataInfo; import com.intc.fishery.domain.vo.PublicDeviceSimpleVo; import com.intc.fishery.domain.vo.PublicPondIdNameVo; import com.intc.fishery.domain.vo.PublicDeviceSwitchSimpleVo; +import com.intc.fishery.domain.vo.PublicDeviceBaseData; +import com.intc.fishery.domain.vo.PublicDeviceLinkedCtrl; +import com.intc.fishery.domain.vo.PublicDeviceDeadInfo; +import com.intc.fishery.domain.bo.ReqId; +import com.intc.fishery.domain.bo.ReqSetOxyTempWarnCall; +import com.intc.fishery.domain.bo.ReqSetOxyWarnValue; +import com.intc.fishery.domain.bo.ReqSetTempWarnValue; +import com.intc.fishery.domain.bo.ReqDeviceCalibrate; +import com.intc.fishery.domain.bo.ReqDeviceSalinityCompensation; +import com.intc.fishery.domain.bo.ReqChangeName; +import com.intc.fishery.domain.bo.ReqTurnOpen; import com.intc.fishery.domain.Device; import com.intc.fishery.domain.DeviceSwitch; import com.intc.fishery.domain.Pond; +import com.intc.fishery.domain.LinkedCtrl; +import com.intc.fishery.domain.DeviceCorrectRecord; import com.intc.fishery.mapper.DeviceMapper; import com.intc.fishery.mapper.DeviceSwitchMapper; import com.intc.fishery.mapper.PondMapper; +import com.intc.fishery.mapper.LinkedCtrlMapper; +import com.intc.fishery.mapper.DeviceCorrectRecordMapper; +import com.intc.fishery.constant.DefineDeviceWarnCode; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import org.springframework.transaction.annotation.Transactional; import java.util.stream.Collectors; import java.util.Map; import java.util.function.Function; +import java.util.Date; +import java.util.Calendar; /** * 设备管理 @@ -51,6 +71,8 @@ public class DeviceController extends BaseController { private final DeviceMapper deviceMapper; private final DeviceSwitchMapper deviceSwitchMapper; private final PondMapper pondMapper; + private final LinkedCtrlMapper linkedCtrlMapper; + private final DeviceCorrectRecordMapper deviceCorrectRecordMapper; /** * 查询设备管理列表 @@ -77,7 +99,7 @@ public class DeviceController extends BaseController { * * @param id 主键 */ - @SaCheckPermission("fishery:device:query") +// @SaCheckPermission("fishery:device:query") @GetMapping("/{id}") public R getInfo(@NotNull(message = "主键不能为空") @PathVariable Long id) { @@ -260,4 +282,698 @@ public class DeviceController extends BaseController { return R.ok(result); } } + + /** + * 查询单个设备完整信息 + * 包含设备基础信息、塘口信息、开关列表(测控一体机)、联动控制列表 + * + * @param rootUserId 用户ID + * @param request 请求对象(包含设备ID) + * @return 设备完整信息 + */ + @PostMapping("/one_device_info") + public R fetchOneDevice( + @RequestParam("rootUserId") Long rootUserId, + @Validated @RequestBody ReqId request) { + + // 查询设备基础信息 + Device device = deviceMapper.selectById(request.getId()); + if (device == null) { + return R.fail("设备不存在"); + } + + // 构建返回对象 + PublicDeviceBaseData data = new PublicDeviceBaseData(); + data.setId(device.getId()); + data.setDeviceName(device.getDeviceName()); + data.setSerialNum(device.getSerialNum()); + data.setDeviceType(device.getDeviceType()); + data.setBindTime(device.getBindTime()); + data.setDeadTime(device.getDeadTime()); + data.setIsOxygenUsed(device.getIsOxygenUsed()); + data.setOxyWarnTelOpen(device.getOxyWarnCallOpen()); + data.setOxyWarnTelNoDis(device.getOxyWarnCallNoDis()); + data.setOxyWarnLower(device.getOxyWarnLower()); + data.setTempWarnTelOpen(device.getTempWarnCallOpen()); + data.setTempWarnTelNoDis(device.getTempWarnCallNoDis()); + data.setTempWarnUpper(device.getTempWarnUpper()); + data.setTempWarnLower(device.getTempWarnLower()); + data.setSalinityCompensation(device.getSalinityCompensation()); + data.setInputVoltage(device.getInputVoltage()); + data.setVoltageWarnOpen(device.getVoltageWarnOpen()); + + // 设置塘口信息 + if (device.getPondId() != null && device.getPondId() > 0) { + Pond pond = pondMapper.selectById(device.getPondId()); + if (pond != null) { + PublicPondIdNameVo pondInfo = new PublicPondIdNameVo(); + pondInfo.setId(pond.getId()); + pondInfo.setPondName(pond.getPondName()); + data.setPondInfo(pondInfo); + } + } + + // 如果是测控一体机(deviceType=2),查询开关列表 + if (device.getDeviceType() != null && device.getDeviceType() == 2) { + List switches = deviceSwitchMapper.selectList( + new LambdaQueryWrapper() + .eq(DeviceSwitch::getDeviceId, device.getId()) + .orderByAsc(DeviceSwitch::getIndex) + ); + + // 收集开关关联的塘口ID + List switchPondIds = switches.stream() + .map(DeviceSwitch::getPondId) + .filter(id -> id != null && id > 0) + .distinct() + .collect(Collectors.toList()); + + // 批量查询塘口 + Map pondMap = Map.of(); + if (!switchPondIds.isEmpty()) { + List ponds = pondMapper.selectBatchIds(switchPondIds); + pondMap = ponds.stream() + .collect(Collectors.toMap(Pond::getId, Function.identity())); + } + + Map finalPondMap = pondMap; + + List switchVos = switches.stream() + .map(s -> { + PublicDeviceSwitchSimpleVo switchVo = new PublicDeviceSwitchSimpleVo(); + switchVo.setId(s.getId()); + switchVo.setIndex(s.getIndex()); + switchVo.setSwitchName(s.getSwitchName()); + + // 设置开关的塘口信息 + if (s.getPondId() != null && s.getPondId() > 0) { + Pond pond = finalPondMap.get(s.getPondId()); + if (pond != null) { + PublicPondIdNameVo pondInfo = new PublicPondIdNameVo(); + pondInfo.setId(pond.getId()); + pondInfo.setPondName(pond.getPondName()); + switchVo.setPondInfo(pondInfo); + } + } + + return switchVo; + }) + .collect(Collectors.toList()); + + data.setListSwitch(switchVos); + } + + // 查询联动控制列表 + List linkedCtrls = linkedCtrlMapper.selectList( + new LambdaQueryWrapper() + .eq(LinkedCtrl::getDeviceId, device.getId()) + ); + + if (linkedCtrls != null && !linkedCtrls.isEmpty()) { + // 收集所有联动控制ID + List linkedCtrlIds = linkedCtrls.stream() + .map(LinkedCtrl::getId) + .collect(Collectors.toList()); + + // 查询关联的开关 + List linkedSwitches = deviceSwitchMapper.selectList( + new LambdaQueryWrapper() + .in(DeviceSwitch::getLinkedCtrlId, linkedCtrlIds) + ); + + // 按联动控制ID分组开关 + Map> switchesByLinkedCtrl = linkedSwitches.stream() + .collect(Collectors.groupingBy(DeviceSwitch::getLinkedCtrlId)); + + // 构建联动控制VO列表 + List linkedCtrlVos = linkedCtrls.stream() + .map(lc -> { + PublicDeviceLinkedCtrl vo = new PublicDeviceLinkedCtrl(); + vo.setId(lc.getId()); + vo.setDeviceId(lc.getDeviceId()); + vo.setOxyUpperOpen(lc.getOxyUpperOpen()); + vo.setOxyUpperValue(lc.getOxyUpperValue()); + vo.setOxyLowerOpen(lc.getOxyLowerOpen()); + vo.setOxyLowerValue(lc.getOxyLowerValue()); + + // 设置关联的开关列表 + List switches = switchesByLinkedCtrl.getOrDefault(lc.getId(), List.of()); + List switchVos = switches.stream() + .map(s -> { + PublicDeviceSwitchSimpleVo switchVo = new PublicDeviceSwitchSimpleVo(); + switchVo.setId(s.getId()); + switchVo.setSwitchName(s.getSwitchName()); + return switchVo; + }) + .collect(Collectors.toList()); + vo.setListSwitch(switchVos); + + return vo; + }) + .collect(Collectors.toList()); + + data.setListLinkedCtr(linkedCtrlVos); + } else { + data.setListLinkedCtr(List.of()); + } + + return R.ok(data); + } + + /** + * 设置溶解氧/温度告警开关 + * + * @param rootUserId 用户ID + * @param request 请求对象 + * @return 操作结果 + */ + @PostMapping("/set_oxy_warn_call") + public R setOxyWarnCall( + @RequestParam("rootUserId") Long rootUserId, + @Validated @RequestBody ReqSetOxyTempWarnCall request) { + + // 查询设备信息 + Device device = deviceMapper.selectOne( + new LambdaQueryWrapper() + .eq(Device::getId, request.getDeviceId()) + .select(Device::getId, Device::getUserId, Device::getDeviceName, + Device::getSerialNum, Device::getOxyWarnCallOpen, + Device::getOxyWarnCallNoDis, Device::getTempWarnCallOpen, + Device::getTempWarnCallNoDis) + ); + + if (device == null || device.getUserId() == null || !device.getUserId().equals(rootUserId)) { + return R.fail("设备不存在或无权限操作"); + } + + // type=1: 溶解氧告警设置 + if (request.getType() == 1) { + // 检查是否需要更新 + if (device.getOxyWarnCallOpen().equals(request.getIsOpen()) + && device.getOxyWarnCallNoDis().equals(request.getIsNoDisturb())) { + return R.ok(); + } + + // 更新溶解氧告警配置 + boolean updated = deviceMapper.update(null, + new LambdaUpdateWrapper() + .eq(Device::getId, request.getDeviceId()) + .set(Device::getOxyWarnCallOpen, request.getIsOpen()) + .set(Device::getOxyWarnCallNoDis, request.getIsNoDisturb()) + .set(Device::getOxyWarnCallLastTime, new Date(System.currentTimeMillis() - 24 * 60 * 60 * 1000)) + ) > 0; + + if (!updated) { + return R.fail("更新失败"); + } + + // TODO: 记录操作日志 + // CacheData.AddMessageOpRecordUser(rootUserId, userId, "设备操作", + // String.format("%s(%s),溶解氧告警开关:%s, 免打扰开关:%s。", + // device.getDeviceName(), device.getSerialNum(), + // request.getIsOpen() == 1 ? "打开" : "关闭", + // request.getIsNoDisturb() == 1 ? "打开" : "关闭")); + + return R.ok(); + } + // type=2: 温度告警设置 + else if (request.getType() == 2) { + // 检查是否需要更新 + if (device.getTempWarnCallOpen().equals(request.getIsOpen()) + && device.getTempWarnCallNoDis().equals(request.getIsNoDisturb())) { + return R.ok(); + } + + // 更新温度告警配置 + boolean updated = deviceMapper.update(null, + new LambdaUpdateWrapper() + .eq(Device::getId, request.getDeviceId()) + .set(Device::getTempWarnCallOpen, request.getIsOpen()) + .set(Device::getTempWarnCallNoDis, request.getIsNoDisturb()) + .set(Device::getTempWarnCallLastTime, new Date(System.currentTimeMillis() - 24 * 60 * 60 * 1000)) + ) > 0; + + if (!updated) { + return R.fail("更新失败"); + } + + // TODO: 记录操作日志 + // CacheData.AddMessageOpRecordUser(rootUserId, userId, "设备操作", + // String.format("%s(%s),温度告警开关:%s, 免打扰开关:%s。", + // device.getDeviceName(), device.getSerialNum(), + // request.getIsOpen() == 1 ? "打开" : "关闭", + // request.getIsNoDisturb() == 1 ? "打开" : "关闭")); + + return R.ok(); + } + else { + return R.fail("参数错误:类型必须为1或2"); + } + } + + /** + * 设置溶解氧告警值 + * + * @param rootUserId 用户ID + * @param request 请求对象 + * @return 操作结果 + */ + @PutMapping("/set_oxy_warn_value") + public R setOxyWarnValue( + @RequestParam("rootUserId") Long rootUserId, + @Validated @RequestBody ReqSetOxyWarnValue request) { + + // 查询设备信息 + Device device = deviceMapper.selectOne( + new LambdaQueryWrapper() + .eq(Device::getId, request.getDeviceId()) + .select(Device::getDeviceName, Device::getSerialNum, Device::getUserId, + Device::getOxyWarnLower, Device::getDeadTime) + ); + + if (device == null || device.getUserId() == null || !device.getUserId().equals(rootUserId)) { + return R.fail("设备不存在或无权限操作"); + } + + // 检查设备是否过期 + if (device.getDeadTime() != null && device.getDeadTime().before(new Date())) { + return R.fail("设备服务已过期"); + } + + // 保存旧值用于日志记录 + Double oldValue = device.getOxyWarnLower(); + + // 更新溶解氧下限告警值 + boolean updated = deviceMapper.update(null, + new LambdaUpdateWrapper() + .eq(Device::getId, request.getDeviceId()) + .set(Device::getOxyWarnLower, request.getOxyWarnLower()) + ) > 0; + + if (!updated) { + return R.fail("更新失败"); + } + + // 记录操作日志(仅当值发生变化时) + if (oldValue == null || !oldValue.equals(request.getOxyWarnLower())) { + // TODO: 记录操作日志 + // CacheData.AddMessageOpRecordUser(rootUserId, userId, "设备操作", + // String.format("%s(%s),设置溶解氧下限告警值:%smg/L。", + // device.getDeviceName(), device.getSerialNum(), + // request.getOxyWarnLower())); + } + + return R.ok(); + } + + /** + * 设置温度告警值 + * + * @param rootUserId 用户ID + * @param request 请求对象 + * @return 操作结果 + */ + @PutMapping("/set_temp_warn_value") + public R setTempWarnValue( + @RequestParam("rootUserId") Long rootUserId, + @Validated @RequestBody ReqSetTempWarnValue request) { + + // 验证温度上下限 + if (request.getTempWarnUpper() <= request.getTempWarnLower()) { + return R.fail("温度上限必须高于下限值"); + } + + // 查询设备信息 + Device device = deviceMapper.selectOne( + new LambdaQueryWrapper() + .eq(Device::getId, request.getDeviceId()) + .select(Device::getDeviceName, Device::getSerialNum, Device::getUserId, + Device::getTempWarnUpper, Device::getTempWarnLower, Device::getDeadTime) + ); + + if (device == null || device.getUserId() == null || !device.getUserId().equals(rootUserId)) { + return R.fail("设备不存在或无权限操作"); + } + + // 检查设备是否过期 + if (device.getDeadTime() != null && device.getDeadTime().before(new Date())) { + return R.fail("设备服务已过期"); + } + + // 保存旧值用于日志记录 + Double oldUpper = device.getTempWarnUpper(); + Double oldLower = device.getTempWarnLower(); + + // 更新温度告警值 + boolean updated = deviceMapper.update(null, + new LambdaUpdateWrapper() + .eq(Device::getId, request.getDeviceId()) + .set(Device::getTempWarnUpper, request.getTempWarnUpper()) + .set(Device::getTempWarnLower, request.getTempWarnLower()) + ) > 0; + + if (!updated) { + return R.fail("更新失败"); + } + + // 记录操作日志(仅当值发生变化时) + if (oldUpper == null || Math.abs(oldUpper - request.getTempWarnUpper()) > 0.00001) { + // TODO: 记录操作日志 + // CacheData.AddMessageOpRecordUser(rootUserId, userId, "设备操作", + // String.format("%s(%s),设置温度上限告警值:%s℃。", + // device.getDeviceName(), device.getSerialNum(), + // request.getTempWarnUpper())); + } + if (oldLower == null || Math.abs(oldLower - request.getTempWarnLower()) > 0.00001) { + // TODO: 记录操作日志 + // CacheData.AddMessageOpRecordUser(rootUserId, userId, "设备操作", + // String.format("%s(%s),设置温度下限告警值:%s℃。", + // device.getDeviceName(), device.getSerialNum(), + // request.getTempWarnLower())); + } + + return R.ok(); + } + + /** + * 获取设备饱和度 + * + * @param request 请求对象(包含设备ID) + * @return 饱和度值 + */ + @PostMapping("/get_saturability") + public R getSaturability( + @Validated @RequestBody ReqId request) { + + // 查询设备的饱和度值 + Device device = deviceMapper.selectOne( + new LambdaQueryWrapper() + .eq(Device::getId, request.getId()) + .select(Device::getValueSaturability) + ); + + if (device == null) { + return R.fail("设备不存在"); + } + + // 返回饱和度值,如果为null则返回0.0 + Double saturability = device.getValueSaturability(); + return R.ok(saturability != null ? saturability : 0.0); + } + + /** + * 修改设备名称 + * + * @param rootUserId 用户ID + * @param request 请求对象(包含设备ID和新名称) + * @return 操作结果 + */ + @PutMapping("/update_name") + public R changeDeviceName( + @RequestParam("rootUserId") Long rootUserId, + @Validated @RequestBody ReqChangeName request) { + + // 查询设备的用户ID + Device device = deviceMapper.selectOne( + new LambdaQueryWrapper() + .eq(Device::getId, request.getId()) + .select(Device::getId, Device::getUserId) + ); + + // 验证设备存在性和权限 + if (device == null || device.getUserId() == null || !device.getUserId().equals(rootUserId)) { + return R.fail("设备不存在或无权限操作"); + } + + // 更新设备名称 + boolean updated = deviceMapper.update(null, + new LambdaUpdateWrapper() + .eq(Device::getId, request.getId()) + .set(Device::getDeviceName, request.getNewName()) + ) > 0; + + if (!updated) { + return R.fail("更新失败"); + } + + return R.ok(); + } + + /** + * 查询即将到期设备列表 + * 返回当前时间到未来1个月内将要到期的设备 + * + * @param rootUserId 用户ID + * @return 即将到期的设备列表 + */ + @GetMapping("/list_device_dead") + public R> queryListDeviceDead( + @RequestParam("rootUserId") Long rootUserId) { + + // 计算时间范围:当前时间到未来1个月 + Date now = new Date(); + Calendar calendar = Calendar.getInstance(); + calendar.setTime(now); + calendar.add(Calendar.MONTH, 1); + Date oneMonthLater = calendar.getTime(); + + // 查询即将到期的设备 + List devices = deviceMapper.selectList( + new LambdaQueryWrapper() + .eq(Device::getUserId, rootUserId) + .gt(Device::getDeadTime, now) + .le(Device::getDeadTime, oneMonthLater) + .ge(Device::getWarnCode, 0) + .select(Device::getId, Device::getDeviceName, Device::getSerialNum, Device::getDeadTime) + .orderByAsc(Device::getDeadTime) + ); + + // 转换为VO列表 + List list = devices.stream() + .map(device -> { + PublicDeviceDeadInfo vo = new PublicDeviceDeadInfo(); + vo.setId(device.getId()); + vo.setDeviceName(device.getDeviceName()); + vo.setSerialNum(device.getSerialNum()); + vo.setDeadTime(device.getDeadTime()); + return vo; + }) + .collect(Collectors.toList()); + + return R.ok(list); + } + + /** + * 设备从塘口移除 + * 将设备从当前塘口中移除,同时删除关联的联动控制和告警信息 + * + * @param rootUserId 用户ID + * @param request 请求对象(包含设备ID) + * @return 操作结果 + */ + @PostMapping("/break_pond") + @Transactional(rollbackFor = Exception.class) + public R breakPond( + @RequestParam("rootUserId") Long rootUserId, + @Validated @RequestBody ReqId request) { + + // 查询设备信息,包含塘口关联 + Device device = deviceMapper.selectOne( + new LambdaQueryWrapper() + .eq(Device::getId, request.getId()) + .select(Device::getId, Device::getUserId, Device::getDeviceName, + Device::getSerialNum, Device::getDeviceType, Device::getPondId) + ); + + // 验证设备存在性和权限 + if (device == null || device.getUserId() == null || !device.getUserId().equals(rootUserId)) { + return R.fail("设备不存在或无权限操作"); + } + + // 如果设备有关联的塘口,执行移除操作 + if (device.getPondId() != null && device.getPondId() > 0) { + // 查询塘口名称用于日志记录 + Pond pond = pondMapper.selectOne( + new LambdaQueryWrapper() + .eq(Pond::getId, device.getPondId()) + .select(Pond::getId, Pond::getPondName) + ); + + String pondName = pond != null ? pond.getPondName() : "未知塘口"; + + // 删除设备关联的所有联动控制 + linkedCtrlMapper.delete( + new LambdaQueryWrapper() + .eq(LinkedCtrl::getDeviceId, device.getId()) + ); + + // 更新设备:移除塘口关联,清除告警状态 + boolean updated = deviceMapper.update(null, + new LambdaUpdateWrapper() + .eq(Device::getId, request.getId()) + .set(Device::getPondId, null) + .set(Device::getIsTempWarnExist, 0) + .set(Device::getIsOxygenWarnExist, 0) + ) > 0; + + if (!updated) { + return R.fail("更新失败"); + } + + // TODO: 记录操作日志 + // CacheData.AddMessageOpRecordUser(rootUserId, userId, "设备操作", + // String.format("%s(%s) 从 %s 移除。", + // device.getDeviceName(), device.getSerialNum(), pondName)); + + // TODO: 清除设备告警等待和通知 + // await EventHelper.RemoveDeviceWarnWaitAndNotice(request.getId(), DefineDeviceWarnCode.None); + } + + return R.ok(); + } + + /** + * 设置电压告警开关 + * + * @param rootUserId 用户ID + * @param request 请求对象(包含设备ID和开关状态) + * @return 操作结果 + */ + @PutMapping("/voltage_check_open") + public R updateDetectVoltageOpen( + @RequestParam("rootUserId") Long rootUserId, + @Validated @RequestBody ReqTurnOpen request) { + + // 查询设备信息 + Device device = deviceMapper.selectOne( + new LambdaQueryWrapper() + .eq(Device::getId, request.getId()) + .select(Device::getId, Device::getUserId, Device::getSerialNum, + Device::getDeviceName, Device::getDeviceType, + Device::getVoltageWarnOpen, Device::getWarnCode, Device::getDeadTime) + ); + + // 验证设备存在性、权限和设备类型(必须是测控一体机) + if (device == null || device.getUserId() == null || !device.getUserId().equals(rootUserId) || device.getDeviceType() == null || device.getDeviceType() != 2) { + return R.fail("设备不存在或无权限操作"); + } + + // 检查设备是否过期 + if (device.getDeadTime() != null && device.getDeadTime().before(new Date())) { + return R.fail("设备服务已过期"); + } + + // 检查开关状态是否需要更新 + if (device.getVoltageWarnOpen() != null && device.getVoltageWarnOpen().equals(request.getIsOpen())) { + return R.ok(); + } + + // 更新电压告警开关 + boolean updated = deviceMapper.update(null, + new LambdaUpdateWrapper() + .eq(Device::getId, request.getId()) + .set(Device::getVoltageWarnOpen, request.getIsOpen()) + ) > 0; + + if (!updated) { + return R.fail("更新失败"); + } + + // TODO: 记录操作日志 + String op = request.getIsOpen() == 1 ? "开启" : "关闭"; + // CacheData.AddMessageOpRecordUser(rootUserId, userId, "设备操作", + // String.format("%s(%s)设置电压告警开关:%s。", + // device.getDeviceName(), device.getSerialNum(), op)); + + return R.ok(); + } + + /** + * 设置测控一体机溶解氧启用/禁用 + * 启用时:同时启用溶解氧和温度告警及免打扰 + * 禁用时:关闭溶解氧并删除所有联动控制 + * + * @param rootUserId 用户ID + * @param request 请求对象(包含设备ID和开关状态) + * @return 设备完整信息 + */ + @PutMapping("/set_controller_oxy_open") + @Transactional(rollbackFor = Exception.class) + public R setControllerOxyOpen( + @RequestParam("rootUserId") Long rootUserId, + @Validated @RequestBody ReqTurnOpen request) { + + // 查询设备基本信息 + Device device = deviceMapper.selectOne( + new LambdaQueryWrapper() + .eq(Device::getId, request.getId()) + .select(Device::getId, Device::getUserId, Device::getDeviceName, Device::getSerialNum) + ); + + // 验证设备存在性和权限 + if (device == null || device.getUserId() == null || !device.getUserId().equals(rootUserId)) { + return R.fail("设备不存在或无权限操作"); + } + + // 根据开关状态执行不同逻辑 + if (request.getIsOpen() == 1) { + // 启用溶解氧:同时启用相关告警和免打扰 + deviceMapper.update(null, + new LambdaUpdateWrapper() + .eq(Device::getId, request.getId()) + .set(Device::getIsOxygenUsed, 1) + .set(Device::getOxyWarnCallOpen, 1) + .set(Device::getOxyWarnCallNoDis, 1) + .set(Device::getTempWarnCallOpen, 1) + .set(Device::getTempWarnCallNoDis, 1) + ); + } else { + // 禁用溶解氧:关闭溶解氧并删除所有联动控制 + deviceMapper.update(null, + new LambdaUpdateWrapper() + .eq(Device::getId, request.getId()) + .set(Device::getIsOxygenUsed, 0) + ); + + // 删除设备的所有联动控制 + linkedCtrlMapper.delete( + new LambdaQueryWrapper() + .eq(LinkedCtrl::getDeviceId, request.getId()) + ); + } + + // 查询更新后的设备完整信息 + ReqId reqId = new ReqId(); + reqId.setId(request.getId()); + R result = fetchOneDevice(rootUserId, reqId); + + if (!R.isSuccess(result) || result.getData() == null) { + return R.fail("查询设备信息失败"); + } + + PublicDeviceBaseData deviceBaseData = result.getData(); + + // 验证设备类型必须是测控一体机 + if (deviceBaseData.getDeviceType() == null || deviceBaseData.getDeviceType() != 2) { + return R.fail("设备不存在或无权限操作"); + } + + // 如果是禁用溶解氧,需要清除相关告警 + if (request.getIsOpen() == 0) { + // TODO: 清除设备告警等待和通知 + // 清除探头离线相关告警:溶解氧探头、PH探头、盐度探头 + // await EventHelper.RemoveDeviceWarnWaitAndNotice(deviceBaseData.getId(), + // DefineDeviceWarnCode.DetectorOxyOffline | + // DefineDeviceWarnCode.DetectorPHOffline | + // DefineDeviceWarnCode.DetectorSalinityOffline); + } + + // TODO: 记录操作日志 + String operation = request.getIsOpen() == 1 ? "启用溶解氧" : "禁用溶解氧"; + // CacheData.AddMessageOpRecordUser(rootUserId, userId, "设备操作", + // String.format("%s(%s),%s。", + // device.getDeviceName(), device.getSerialNum(), operation)); + + return R.ok(deviceBaseData); + } } diff --git a/intc-modules/intc-fishery/src/main/java/com/intc/fishery/controller/DeviceSwitchController.java b/intc-modules/intc-fishery/src/main/java/com/intc/fishery/controller/DeviceSwitchController.java index 550ceec..e19d6a7 100644 --- a/intc-modules/intc-fishery/src/main/java/com/intc/fishery/controller/DeviceSwitchController.java +++ b/intc-modules/intc-fishery/src/main/java/com/intc/fishery/controller/DeviceSwitchController.java @@ -2,6 +2,10 @@ package com.intc.fishery.controller; import java.util.List; +import com.intc.fishery.domain.bo.ReqId; +import com.intc.fishery.domain.bo.ReqChangeName; +import com.intc.fishery.domain.dto.PublicDeviceSimpleDto; +import com.intc.fishery.domain.dto.PublicDeviceSwitchBaseData; import lombok.RequiredArgsConstructor; import jakarta.servlet.http.HttpServletResponse; import jakarta.validation.constraints.*; @@ -39,7 +43,7 @@ public class DeviceSwitchController extends BaseController { /** * 查询测控一体机开关列表 */ - @SaCheckPermission("fishery:deviceSwitch:list") +// @SaCheckPermission("fishery:deviceSwitch:list") @GetMapping("/list") public TableDataInfo list(DeviceSwitchBo bo, PageQuery pageQuery) { return deviceSwitchService.queryPageList(bo, pageQuery); @@ -102,4 +106,58 @@ public class DeviceSwitchController extends BaseController { @PathVariable Long[] ids) { return toAjax(deviceSwitchService.deleteWithValidByIds(List.of(ids), true)); } + + /** + * 根据塘口ID查询塘口下的设备及其开关信息 + * + * @param request 塘口ID请求对象 + * @return 设备及开关列表 + */ +// @SaCheckPermission("fishery:deviceSwitch:query") + @PostMapping("/get_pond_switch") + public R> getPondSwitch(@Validated @RequestBody ReqId request) { + return R.ok(deviceSwitchService.getPondSwitch(request.getId())); + } + + /** + * 获取单个开关的基础信息 + * + * @param request 开关ID请求对象 + * @return 开关基础信息 + */ +// @SaCheckPermission("fishery:deviceSwitch:query") + @PostMapping("/one_switch_info") + public R getOneSwitchInfo(@Validated @RequestBody ReqId request) { + return R.ok(deviceSwitchService.getOneSwitchInfo(request.getId())); + } + + /** + * 修改开关名称 + * + * @param request 修改名称请求对象 + * @return 操作结果 + */ +// @SaCheckPermission("fishery:deviceSwitch:edit") + @Log(title = "修改开关名称", businessType = BusinessType.UPDATE) + @RepeatSubmit() + @PutMapping("/update_name") + public R changeSwitchName(@Validated @RequestBody ReqChangeName request) { + deviceSwitchService.changeSwitchName(request.getId(), request.getNewName()); + return R.ok(); + } + + /** + * 设置电流告警开关 + * + * @param request 开关状态请求对象 + * @return 操作结果 + */ +// @SaCheckPermission("fishery:deviceSwitch:edit") + @Log(title = "设置电流告警开关", businessType = BusinessType.UPDATE) + @RepeatSubmit() + @PutMapping("/electric_check_open") + public R updateDetectElectricOpen(@Validated @RequestBody com.intc.fishery.domain.bo.ReqTurnOpen request) { + deviceSwitchService.updateElectricWarnOpen(request.getId(), request.getIsOpen()); + return R.ok(); + } } diff --git a/intc-modules/intc-fishery/src/main/java/com/intc/fishery/controller/LinkedCtrlController.java b/intc-modules/intc-fishery/src/main/java/com/intc/fishery/controller/LinkedCtrlController.java index 33990f6..e9aad90 100644 --- a/intc-modules/intc-fishery/src/main/java/com/intc/fishery/controller/LinkedCtrlController.java +++ b/intc-modules/intc-fishery/src/main/java/com/intc/fishery/controller/LinkedCtrlController.java @@ -2,6 +2,11 @@ package com.intc.fishery.controller; import java.util.List; +import com.intc.fishery.domain.bo.ReqAddLinkedCtrl; +import com.intc.fishery.domain.bo.ReqId; +import com.intc.fishery.domain.bo.ReqLinkedCtrlOpen; +import com.intc.fishery.domain.bo.ReqUpdateLinkedCtrl; +import com.intc.fishery.domain.dto.PublicLinkedCtrl; import com.intc.common.excel.utils.ExcelUtil; import lombok.RequiredArgsConstructor; import jakarta.servlet.http.HttpServletResponse; @@ -39,7 +44,7 @@ public class LinkedCtrlController extends BaseController { /** * 查询溶解氧联动控制列表 */ - @SaCheckPermission("fishery:linkedCtrl:list") +// @SaCheckPermission("fishery:linkedCtrl:list") @GetMapping("/list") public TableDataInfo list(LinkedCtrlBo bo, PageQuery pageQuery) { return linkedCtrlService.queryPageList(bo, pageQuery); @@ -61,7 +66,7 @@ public class LinkedCtrlController extends BaseController { * * @param id 主键 */ - @SaCheckPermission("fishery:linkedCtrl:query") +// @SaCheckPermission("fishery:linkedCtrl:query") @GetMapping("/{id}") public R getInfo(@NotNull(message = "主键不能为空") @PathVariable Long id) { @@ -95,11 +100,66 @@ public class LinkedCtrlController extends BaseController { * * @param ids 主键串 */ - @SaCheckPermission("fishery:linkedCtrl:remove") +// @SaCheckPermission("fishery:linkedCtrl:remove") @Log(title = "溶解氧联动控制", businessType = BusinessType.DELETE) @DeleteMapping("/{ids}") public R remove(@NotEmpty(message = "主键不能为空") @PathVariable Long[] ids) { return toAjax(linkedCtrlService.deleteWithValidByIds(List.of(ids), true)); } + + /** + * 新增联动控制 + * + * @param request 新增联动控制请求 + * @return 操作结果 + */ + @Log(title = "溶解氧联动控制", businessType = BusinessType.INSERT) + @RepeatSubmit() + @PostMapping("/add") + public R addLinkedCtrl(@Validated @RequestBody ReqAddLinkedCtrl request) { + linkedCtrlService.addLinkedCtrl(request); + return R.ok(); + } + + /** + * 修改联动控制 + * + * @param request 修改联动控制请求 + * @return 操作结果 + */ + @Log(title = "溶解氧联动控制", businessType = BusinessType.UPDATE) + @RepeatSubmit() + @PutMapping("/update") + public R updateLinkedCtrl(@Validated @RequestBody ReqUpdateLinkedCtrl request) { + linkedCtrlService.updateLinkedCtrl(request); + return R.ok(); + } + + /** + * 根据设备ID查询所有联动控制 + * + * @param request 设备ID请求对象 + * @return 联动控制列表 + */ +// @SaCheckPermission("fishery:linkedCtrl:query") + @PostMapping("/fetch") + public R> queryAllLinkedCtrl(@Validated @RequestBody ReqId request) { + return R.ok(linkedCtrlService.queryAllLinkedCtrl(request.getId())); + } + + /** + * 设置联动控制开关 + * + * @param request 设置开关请求 + * @return 操作结果 + */ +// @SaCheckPermission("fishery:linkedCtrl:edit") + @Log(title = "溶解氧联动控制", businessType = BusinessType.UPDATE) + @RepeatSubmit() + @PutMapping("/set_open") + public R setLinkedOpen(@Validated @RequestBody ReqLinkedCtrlOpen request) { + linkedCtrlService.setLinkedOpen(request); + return R.ok(); + } } diff --git a/intc-modules/intc-fishery/src/main/java/com/intc/fishery/controller/PondController.java b/intc-modules/intc-fishery/src/main/java/com/intc/fishery/controller/PondController.java index 2420b63..58e645c 100644 --- a/intc-modules/intc-fishery/src/main/java/com/intc/fishery/controller/PondController.java +++ b/intc-modules/intc-fishery/src/main/java/com/intc/fishery/controller/PondController.java @@ -2,6 +2,7 @@ package com.intc.fishery.controller; import java.util.List; +import com.intc.fishery.domain.vo.*; import lombok.RequiredArgsConstructor; import jakarta.servlet.http.HttpServletResponse; import jakarta.validation.constraints.*; @@ -17,20 +18,15 @@ import com.intc.common.core.validate.AddGroup; import com.intc.common.core.validate.EditGroup; import com.intc.common.log.enums.BusinessType; import com.intc.common.excel.utils.ExcelUtil; -import com.intc.fishery.domain.vo.PondVo; import com.intc.fishery.domain.bo.PondBo; +import com.intc.fishery.domain.bo.ReqChangePond; import com.intc.fishery.service.IPondService; import com.intc.common.mybatis.core.page.TableDataInfo; import com.intc.common.satoken.utils.LoginHelper; import com.intc.fishery.service.IAquUserService; -import com.intc.fishery.domain.vo.AquUserVo; import com.intc.fishery.domain.bo.AquUserBo; import com.intc.system.service.ISysUserService; import com.intc.system.domain.vo.SysUserVo; -import com.intc.fishery.domain.vo.PondDeviceListVo; -import com.intc.fishery.domain.vo.DeviceVo; -import com.intc.fishery.domain.vo.DeviceWithSwitchVo; -import com.intc.fishery.domain.vo.DeviceSwitchVo; import com.intc.fishery.mapper.DeviceMapper; import com.intc.fishery.mapper.DeviceSwitchMapper; import com.intc.fishery.domain.Device; @@ -49,8 +45,6 @@ import java.util.HashSet; import java.util.Set; import java.util.ArrayList; import com.intc.fishery.domain.LinkedCtrl; -import com.intc.fishery.domain.vo.PublicPondMode1Vo; -import com.intc.fishery.domain.vo.PondMode1WarnCodeInfo; import com.intc.fishery.mapper.DeviceErrorCodeMapper; import com.intc.fishery.domain.DeviceErrorCode; import com.intc.fishery.constant.DefineDeviceWarnCode; @@ -808,4 +802,146 @@ public class PondController extends BaseController { return R.ok(listData); } + + /** + * 查询用户塘口列表 - 模式2(仅返回ID和名称) + * 返回用户所有塘口的ID和名称列表 + * + * @param rootUserId 用户ID + * @return 塘口ID和名称列表 + */ + @GetMapping("/list_mode2") + public R> getPondList2( + @RequestParam("rootUserId") Long rootUserId) { + + // 查询用户的所有塘口,只获取ID和名称 + List ponds = pondMapper.selectList( + new LambdaQueryWrapper() + .eq(Pond::getUserId, rootUserId) + .select(Pond::getId, Pond::getPondName) + .orderByDesc(Pond::getCreateTime) + ); + + // 转换为VO列表 + List list = ponds.stream() + .map(pond -> { + PublicPondIdNameVo vo = new PublicPondIdNameVo(); + vo.setId(pond.getId()); + vo.setPondName(pond.getPondName()); + return vo; + }) + .collect(Collectors.toList()); + + return R.ok(list); + } + + /** + * 修改设备塘口 + * 将设备分配到指定塘口或从塘口移除 + * + * @param rootUserId 用户ID + * @param request 请求对象(包含设备ID和塘口ID) + * @return 操作结果 + */ + @PutMapping("/update_pond") + @Transactional(rollbackFor = Exception.class) + public R changePond( + @RequestParam("rootUserId") Long rootUserId, + @Validated @RequestBody ReqChangePond request) { + + // 查询设备信息 + Device device = deviceMapper.selectOne( + new LambdaQueryWrapper() + .eq(Device::getId, request.getId()) + .select(Device::getId, Device::getUserId, Device::getPondId, + Device::getDeviceName, Device::getSerialNum) + ); + + // 验证设备存在性和权限 + if (device == null || device.getUserId() == null || !device.getUserId().equals(rootUserId)) { + return R.fail("设备不存在或无权限操作"); + } + + // 如果塘口没有变化,直接返回 + Long oldPondId = device.getPondId(); + Long newPondId = (request.getPondId() != null && request.getPondId() > 0) ? request.getPondId() : null; + + if ((oldPondId == null && newPondId == null) || + (oldPondId != null && oldPondId.equals(newPondId))) { + return R.ok(); + } + + // 如果设备原来有塘口,删除关联的联动控制 + if (oldPondId != null) { + linkedCtrlMapper.delete( + new LambdaQueryWrapper() + .eq(LinkedCtrl::getDeviceId, request.getId()) + ); + } + + // 情凵1:分配或转移到新塘口 + if (newPondId != null) { + // 验证塘口存在性和权限 + Pond pond = pondMapper.selectOne( + new LambdaQueryWrapper() + .eq(Pond::getId, newPondId) + .select(Pond::getId, Pond::getUserId, Pond::getPondName) + ); + + if (pond == null || !pond.getUserId().equals(rootUserId)) { + return R.fail("塘口不存在或无权限访问"); + } + + // 更新设备的塘口,并清除告警状态 + deviceMapper.update(null, + new LambdaUpdateWrapper() + .eq(Device::getId, request.getId()) + .set(Device::getPondId, newPondId) + .set(Device::getIsTempWarnExist, 0) + .set(Device::getIsOxygenWarnExist, 0) + ); + + // TODO: 记录操作日志 + // if (oldPondId != null) { + // // 转移到塘口 + // CacheData.AddMessageOpRecordUser(rootUserId, userId, "设备操作", + // String.format("%s(%s),转移到塘口:%s。", + // device.getDeviceName(), device.getSerialNum(), pond.getPondName())); + // } else { + // // 分配塘口 + // CacheData.AddMessageOpRecordUser(rootUserId, userId, "设备操作", + // String.format("%s(%s),分配塘口:%s。", + // device.getDeviceName(), device.getSerialNum(), pond.getPondName())); + // } + } + // 情凵2:从塘口移除 + else { + // 更新设备,移除塘口关联,并清除告警状态 + deviceMapper.update(null, + new LambdaUpdateWrapper() + .eq(Device::getId, request.getId()) + .set(Device::getPondId, null) + .set(Device::getIsTempWarnExist, 0) + .set(Device::getIsOxygenWarnExist, 0) + ); + + // TODO: 记录操作日志 + // if (oldPondId != null) { + // // 需要查询原塘口名称 + // Pond oldPond = pondMapper.selectById(oldPondId); + // if (oldPond != null) { + // CacheData.AddMessageOpRecordUser(rootUserId, userId, "设备操作", + // String.format("%s(%s) 从 %s 移除。", + // device.getDeviceName(), device.getSerialNum(), oldPond.getPondName())); + // } + // } + } + + // TODO: 清除设备报警等待和通知数据 + // await EventHelper.RemoveDeviceWarnWaitAndNotice(request.getId(), + // DefineDeviceWarnCode.DetectorOxyOffline | DefineDeviceWarnCode.DetectorPHOffline | + // DefineDeviceWarnCode.DetectorSalinityOffline); + + return R.ok(); + } } diff --git a/intc-modules/intc-fishery/src/main/java/com/intc/fishery/domain/bo/ReqAddLinkedCtrl.java b/intc-modules/intc-fishery/src/main/java/com/intc/fishery/domain/bo/ReqAddLinkedCtrl.java new file mode 100644 index 0000000..a12b6d1 --- /dev/null +++ b/intc-modules/intc-fishery/src/main/java/com/intc/fishery/domain/bo/ReqAddLinkedCtrl.java @@ -0,0 +1,34 @@ +package com.intc.fishery.domain.bo; + +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.util.List; + +/** + * 新增联动控制请求对象 + * + * @author intc + * @date 2025-01-13 + */ +@Data +public class ReqAddLinkedCtrl implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 设备ID + */ + @NotNull(message = "设备ID不能为空") + private Long deviceId; + + /** + * 开关ID列表 + */ + @NotEmpty(message = "开关ID列表不能为空") + private List listSwitchId; +} diff --git a/intc-modules/intc-fishery/src/main/java/com/intc/fishery/domain/bo/ReqChangeName.java b/intc-modules/intc-fishery/src/main/java/com/intc/fishery/domain/bo/ReqChangeName.java new file mode 100644 index 0000000..ceb7589 --- /dev/null +++ b/intc-modules/intc-fishery/src/main/java/com/intc/fishery/domain/bo/ReqChangeName.java @@ -0,0 +1,39 @@ +package com.intc.fishery.domain.bo; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 修改设备名称请求对象 + * + * @author intc + * @date 2026-01-13 + */ +@Data +@Schema(description = "修改设备名称请求对象") +public class ReqChangeName implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 设备ID + */ + @Schema(description = "设备ID") + @NotNull(message = "设备ID不能为空") + private Long id; + + /** + * 新名称 + */ + @Schema(description = "新名称") + @NotBlank(message = "新名称不能为空") + @Size(max = 10, message = "名称长度不能超过10个字符") + private String newName; +} diff --git a/intc-modules/intc-fishery/src/main/java/com/intc/fishery/domain/bo/ReqChangePond.java b/intc-modules/intc-fishery/src/main/java/com/intc/fishery/domain/bo/ReqChangePond.java new file mode 100644 index 0000000..131a514 --- /dev/null +++ b/intc-modules/intc-fishery/src/main/java/com/intc/fishery/domain/bo/ReqChangePond.java @@ -0,0 +1,35 @@ +package com.intc.fishery.domain.bo; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 修改设备塘口请求对象 + * + * @author intc + * @date 2026-01-13 + */ +@Data +@Schema(description = "修改设备塘口请求对象") +public class ReqChangePond implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 设备ID + */ + @Schema(description = "设备ID") + @NotNull(message = "设备ID不能为空") + private Long id; + + /** + * 塘口ID(为null或0表示移除塘口) + */ + @Schema(description = "塘口ID(为null或0表示移除塘口)") + private Long pondId; +} diff --git a/intc-modules/intc-fishery/src/main/java/com/intc/fishery/domain/bo/ReqDeviceCalibrate.java b/intc-modules/intc-fishery/src/main/java/com/intc/fishery/domain/bo/ReqDeviceCalibrate.java new file mode 100644 index 0000000..a14e871 --- /dev/null +++ b/intc-modules/intc-fishery/src/main/java/com/intc/fishery/domain/bo/ReqDeviceCalibrate.java @@ -0,0 +1,32 @@ +package com.intc.fishery.domain.bo; + +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 设备校准请求对象 + * + * @author intc + * @date 2026-01-13 + */ +@Data +public class ReqDeviceCalibrate implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 设备ID + */ + @NotNull(message = "设备ID不能为空") + private Long id; + + /** + * 校准码 + */ + @NotNull(message = "校准码不能为空") + private String code; +} diff --git a/intc-modules/intc-fishery/src/main/java/com/intc/fishery/domain/bo/ReqDeviceSalinityCompensation.java b/intc-modules/intc-fishery/src/main/java/com/intc/fishery/domain/bo/ReqDeviceSalinityCompensation.java new file mode 100644 index 0000000..c39b4fa --- /dev/null +++ b/intc-modules/intc-fishery/src/main/java/com/intc/fishery/domain/bo/ReqDeviceSalinityCompensation.java @@ -0,0 +1,32 @@ +package com.intc.fishery.domain.bo; + +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 设置盐度补偿请求对象 + * + * @author intc + * @date 2026-01-13 + */ +@Data +public class ReqDeviceSalinityCompensation implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 设备ID + */ + @NotNull(message = "设备ID不能为空") + private Long id; + + /** + * 盐度补偿值 + */ + @NotNull(message = "盐度补偿值不能为空") + private Double salinityCompensation; +} diff --git a/intc-modules/intc-fishery/src/main/java/com/intc/fishery/domain/bo/ReqId.java b/intc-modules/intc-fishery/src/main/java/com/intc/fishery/domain/bo/ReqId.java new file mode 100644 index 0000000..8867553 --- /dev/null +++ b/intc-modules/intc-fishery/src/main/java/com/intc/fishery/domain/bo/ReqId.java @@ -0,0 +1,26 @@ +package com.intc.fishery.domain.bo; + +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * ID请求对象 + * + * @author intc + * @date 2026-01-13 + */ +@Data +public class ReqId implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * ID + */ + @NotNull(message = "ID不能为空") + private Long id; +} diff --git a/intc-modules/intc-fishery/src/main/java/com/intc/fishery/domain/bo/ReqLinkedCtrlOpen.java b/intc-modules/intc-fishery/src/main/java/com/intc/fishery/domain/bo/ReqLinkedCtrlOpen.java new file mode 100644 index 0000000..6a0dda4 --- /dev/null +++ b/intc-modules/intc-fishery/src/main/java/com/intc/fishery/domain/bo/ReqLinkedCtrlOpen.java @@ -0,0 +1,38 @@ +package com.intc.fishery.domain.bo; + +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 设置联动控制开关请求对象 + * + * @author intc + * @date 2025-01-13 + */ +@Data +public class ReqLinkedCtrlOpen implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 联动控制ID + */ + @NotNull(message = "联动控制ID不能为空") + private Long id; + + /** + * 开关类型(1-上限开关,2-下限开关) + */ + @NotNull(message = "开关类型不能为空") + private Integer openType; + + /** + * 是否开启(0-关闭,1-开启) + */ + @NotNull(message = "开关状态不能为空") + private Integer isOpen; +} diff --git a/intc-modules/intc-fishery/src/main/java/com/intc/fishery/domain/bo/ReqSetOxyTempWarnCall.java b/intc-modules/intc-fishery/src/main/java/com/intc/fishery/domain/bo/ReqSetOxyTempWarnCall.java new file mode 100644 index 0000000..fe70160 --- /dev/null +++ b/intc-modules/intc-fishery/src/main/java/com/intc/fishery/domain/bo/ReqSetOxyTempWarnCall.java @@ -0,0 +1,44 @@ +package com.intc.fishery.domain.bo; + +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 设置溶解氧/温度告警开关请求对象 + * + * @author intc + * @date 2026-01-13 + */ +@Data +public class ReqSetOxyTempWarnCall implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 设备ID + */ + @NotNull(message = "设备ID不能为空") + private Long deviceId; + + /** + * 类型(1-溶解氧告警,2-温度告警) + */ + @NotNull(message = "类型不能为空") + private Integer type; + + /** + * 是否打开告警开关 + */ + @NotNull(message = "告警开关不能为空") + private Integer isOpen; + + /** + * 是否开启免打扰 + */ + @NotNull(message = "免打扰开关不能为空") + private Integer isNoDisturb; +} diff --git a/intc-modules/intc-fishery/src/main/java/com/intc/fishery/domain/bo/ReqSetOxyWarnValue.java b/intc-modules/intc-fishery/src/main/java/com/intc/fishery/domain/bo/ReqSetOxyWarnValue.java new file mode 100644 index 0000000..882271d --- /dev/null +++ b/intc-modules/intc-fishery/src/main/java/com/intc/fishery/domain/bo/ReqSetOxyWarnValue.java @@ -0,0 +1,32 @@ +package com.intc.fishery.domain.bo; + +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 设置溶解氧告警值请求对象 + * + * @author intc + * @date 2026-01-13 + */ +@Data +public class ReqSetOxyWarnValue implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 设备ID + */ + @NotNull(message = "设备ID不能为空") + private Long deviceId; + + /** + * 溶解氧下限告警值 + */ + @NotNull(message = "溶解氧下限告警值不能为空") + private Double oxyWarnLower; +} diff --git a/intc-modules/intc-fishery/src/main/java/com/intc/fishery/domain/bo/ReqSetTempWarnValue.java b/intc-modules/intc-fishery/src/main/java/com/intc/fishery/domain/bo/ReqSetTempWarnValue.java new file mode 100644 index 0000000..d80f892 --- /dev/null +++ b/intc-modules/intc-fishery/src/main/java/com/intc/fishery/domain/bo/ReqSetTempWarnValue.java @@ -0,0 +1,38 @@ +package com.intc.fishery.domain.bo; + +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 设置温度告警值请求对象 + * + * @author intc + * @date 2026-01-13 + */ +@Data +public class ReqSetTempWarnValue implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 设备ID + */ + @NotNull(message = "设备ID不能为空") + private Long deviceId; + + /** + * 温度上限告警值 + */ + @NotNull(message = "温度上限告警值不能为空") + private Double tempWarnUpper; + + /** + * 温度下限告警值 + */ + @NotNull(message = "温度下限告警值不能为空") + private Double tempWarnLower; +} diff --git a/intc-modules/intc-fishery/src/main/java/com/intc/fishery/domain/bo/ReqSetVoltageType.java b/intc-modules/intc-fishery/src/main/java/com/intc/fishery/domain/bo/ReqSetVoltageType.java new file mode 100644 index 0000000..55fa705 --- /dev/null +++ b/intc-modules/intc-fishery/src/main/java/com/intc/fishery/domain/bo/ReqSetVoltageType.java @@ -0,0 +1,32 @@ +package com.intc.fishery.domain.bo; + +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 设置开关电压类型请求对象 + * + * @author intc + * @date 2025-01-13 + */ +@Data +public class ReqSetVoltageType implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 开关ID + */ + @NotNull(message = "开关ID不能为空") + private Long id; + + /** + * 电压类型(1-单相220V,4-三相380V四线) + */ + @NotNull(message = "电压类型不能为空") + private Integer voltageType; +} diff --git a/intc-modules/intc-fishery/src/main/java/com/intc/fishery/domain/bo/ReqSwitchElectricSet.java b/intc-modules/intc-fishery/src/main/java/com/intc/fishery/domain/bo/ReqSwitchElectricSet.java new file mode 100644 index 0000000..0b40d11 --- /dev/null +++ b/intc-modules/intc-fishery/src/main/java/com/intc/fishery/domain/bo/ReqSwitchElectricSet.java @@ -0,0 +1,32 @@ +package com.intc.fishery.domain.bo; + +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 设置开关额定电流请求对象 + * + * @author intc + * @date 2025-01-13 + */ +@Data +public class ReqSwitchElectricSet implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 开关ID + */ + @NotNull(message = "开关ID不能为空") + private Long id; + + /** + * 额定电流值(单位:A) + */ + @NotNull(message = "额定电流值不能为空") + private Double electric; +} diff --git a/intc-modules/intc-fishery/src/main/java/com/intc/fishery/domain/bo/ReqTurnOpen.java b/intc-modules/intc-fishery/src/main/java/com/intc/fishery/domain/bo/ReqTurnOpen.java new file mode 100644 index 0000000..254194f --- /dev/null +++ b/intc-modules/intc-fishery/src/main/java/com/intc/fishery/domain/bo/ReqTurnOpen.java @@ -0,0 +1,32 @@ +package com.intc.fishery.domain.bo; + +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 开关设置请求对象 + * + * @author intc + * @date 2025-01-13 + */ +@Data +public class ReqTurnOpen implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 主键ID + */ + @NotNull(message = "ID不能为空") + private Long id; + + /** + * 是否开启(0-关闭,1-开启) + */ + @NotNull(message = "开关状态不能为空") + private Integer isOpen; +} diff --git a/intc-modules/intc-fishery/src/main/java/com/intc/fishery/domain/bo/ReqUpdateLinkedCtrl.java b/intc-modules/intc-fishery/src/main/java/com/intc/fishery/domain/bo/ReqUpdateLinkedCtrl.java new file mode 100644 index 0000000..a876fa8 --- /dev/null +++ b/intc-modules/intc-fishery/src/main/java/com/intc/fishery/domain/bo/ReqUpdateLinkedCtrl.java @@ -0,0 +1,46 @@ +package com.intc.fishery.domain.bo; + +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.util.List; + +/** + * 修改联动控制请求对象 + * + * @author intc + * @date 2025-01-13 + */ +@Data +public class ReqUpdateLinkedCtrl implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 联动控制ID + */ + @NotNull(message = "联动控制ID不能为空") + private Long id; + + /** + * 溶解氧上限值 + */ + @NotNull(message = "溶解氧上限值不能为空") + private Double oxyWarnUpper; + + /** + * 溶解氧下限值 + */ + @NotNull(message = "溶解氧下限值不能为空") + private Double oxyWarnLower; + + /** + * 开关ID列表 + */ + @NotEmpty(message = "开关ID列表不能为空") + private List listSwitchId; +} diff --git a/intc-modules/intc-fishery/src/main/java/com/intc/fishery/domain/dto/PublicDeviceSimpleDto.java b/intc-modules/intc-fishery/src/main/java/com/intc/fishery/domain/dto/PublicDeviceSimpleDto.java new file mode 100644 index 0000000..0d36524 --- /dev/null +++ b/intc-modules/intc-fishery/src/main/java/com/intc/fishery/domain/dto/PublicDeviceSimpleDto.java @@ -0,0 +1,45 @@ +package com.intc.fishery.domain.dto; + +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.util.List; + +/** + * 设备简单信息DTO + * + * @author intc + * @date 2025-01-13 + */ +@Data +public class PublicDeviceSimpleDto implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 设备ID + */ + private Long id; + + /** + * 设备名称 + */ + private String deviceName; + + /** + * 设备类型 + */ + private Integer deviceType; + + /** + * 开关列表 + */ + private List listSwitch; + + /** + * 设备告警状态码 + */ + private Integer warnCode; +} diff --git a/intc-modules/intc-fishery/src/main/java/com/intc/fishery/domain/dto/PublicDeviceSwitchBaseData.java b/intc-modules/intc-fishery/src/main/java/com/intc/fishery/domain/dto/PublicDeviceSwitchBaseData.java new file mode 100644 index 0000000..1ce7076 --- /dev/null +++ b/intc-modules/intc-fishery/src/main/java/com/intc/fishery/domain/dto/PublicDeviceSwitchBaseData.java @@ -0,0 +1,79 @@ +package com.intc.fishery.domain.dto; + +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.intc.common.json.handler.DoubleSerializer; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 开关基础数据DTO + * + * @author intc + * @date 2025-01-13 + */ +@Data +public class PublicDeviceSwitchBaseData implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 开关ID + */ + private Long id; + + /** + * 序号 + */ + private Integer index; + + /** + * 开关名称 + */ + private String switchName; + + /** + * 设备名称 + */ + private String deviceName; + + /** + * 塘口信息 + */ + private PublicPondIdName pondInfo; + + /** + * 当前测定电流 + */ + @JsonSerialize(using = DoubleSerializer.class) + private Double detectElectricValue; + + /** + * 当前测定电压 + */ + @JsonSerialize(using = DoubleSerializer.class) + private Double detectVoltageValue; + + /** + * 输入额定电压 + */ + private Integer inputVoltage; + + /** + * 接线方式 + */ + private Integer connectVoltageType; + + /** + * 电流告警开关 + */ + private Integer electricWarnOpen; + + /** + * 额定电流设置 + */ + @JsonSerialize(using = DoubleSerializer.class) + private Double rateElectricValue; +} diff --git a/intc-modules/intc-fishery/src/main/java/com/intc/fishery/domain/dto/PublicDeviceSwitchSimple.java b/intc-modules/intc-fishery/src/main/java/com/intc/fishery/domain/dto/PublicDeviceSwitchSimple.java new file mode 100644 index 0000000..b4ecfc9 --- /dev/null +++ b/intc-modules/intc-fishery/src/main/java/com/intc/fishery/domain/dto/PublicDeviceSwitchSimple.java @@ -0,0 +1,34 @@ +package com.intc.fishery.domain.dto; + +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 设备开关简单信息DTO(用于联动控制) + * + * @author intc + * @date 2025-01-13 + */ +@Data +public class PublicDeviceSwitchSimple implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 开关ID + */ + private Long id; + + /** + * 序号 + */ + private Integer index; + + /** + * 开关名称 + */ + private String switchName; +} diff --git a/intc-modules/intc-fishery/src/main/java/com/intc/fishery/domain/dto/PublicDeviceSwitchSimpleDto.java b/intc-modules/intc-fishery/src/main/java/com/intc/fishery/domain/dto/PublicDeviceSwitchSimpleDto.java new file mode 100644 index 0000000..c04d3b3 --- /dev/null +++ b/intc-modules/intc-fishery/src/main/java/com/intc/fishery/domain/dto/PublicDeviceSwitchSimpleDto.java @@ -0,0 +1,39 @@ +package com.intc.fishery.domain.dto; + +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 设备开关简单信息DTO + * + * @author intc + * @date 2025-01-13 + */ +@Data +public class PublicDeviceSwitchSimpleDto implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 开关ID + */ + private Long id; + + /** + * 序号 + */ + private Integer index; + + /** + * 开关名称 + */ + private String switchName; + + /** + * 联动控制ID + */ + private Long linkedCtrlId; +} diff --git a/intc-modules/intc-fishery/src/main/java/com/intc/fishery/domain/dto/PublicLinkedCtrl.java b/intc-modules/intc-fishery/src/main/java/com/intc/fishery/domain/dto/PublicLinkedCtrl.java new file mode 100644 index 0000000..8c0db18 --- /dev/null +++ b/intc-modules/intc-fishery/src/main/java/com/intc/fishery/domain/dto/PublicLinkedCtrl.java @@ -0,0 +1,55 @@ +package com.intc.fishery.domain.dto; + +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.util.List; + +/** + * 联动控制信息DTO + * + * @author intc + * @date 2025-01-13 + */ +@Data +public class PublicLinkedCtrl implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 联动控制ID + */ + private Long id; + + /** + * 设备ID + */ + private Long deviceId; + + /** + * 溶解氧上限开关 + */ + private Integer oxyUpperOpen; + + /** + * 溶解氧上限值 + */ + private Double oxyUpperValue; + + /** + * 溶解氧下限开关 + */ + private Integer oxyLowerOpen; + + /** + * 溶解氧下限值 + */ + private Double oxyLowerValue; + + /** + * 开关列表 + */ + private List listSwitch; +} diff --git a/intc-modules/intc-fishery/src/main/java/com/intc/fishery/domain/dto/PublicPondIdName.java b/intc-modules/intc-fishery/src/main/java/com/intc/fishery/domain/dto/PublicPondIdName.java new file mode 100644 index 0000000..b528674 --- /dev/null +++ b/intc-modules/intc-fishery/src/main/java/com/intc/fishery/domain/dto/PublicPondIdName.java @@ -0,0 +1,29 @@ +package com.intc.fishery.domain.dto; + +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 塘口ID和名称DTO + * + * @author intc + * @date 2025-01-13 + */ +@Data +public class PublicPondIdName implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 塘口ID + */ + private Long id; + + /** + * 塘口名称 + */ + private String pondName; +} diff --git a/intc-modules/intc-fishery/src/main/java/com/intc/fishery/domain/vo/DeviceSwitchVo.java b/intc-modules/intc-fishery/src/main/java/com/intc/fishery/domain/vo/DeviceSwitchVo.java index d7d5392..83f19ab 100644 --- a/intc-modules/intc-fishery/src/main/java/com/intc/fishery/domain/vo/DeviceSwitchVo.java +++ b/intc-modules/intc-fishery/src/main/java/com/intc/fishery/domain/vo/DeviceSwitchVo.java @@ -164,4 +164,34 @@ public class DeviceSwitchVo implements Serializable { */ private Boolean hasErrorCode; + /** + * 设备到期时间(关联查询用) + */ + private Date deadTime; + + /** + * 设备告警状态码(关联查询用) + */ + private Integer warnCode; + + /** + * 设备IoT ID(关联查询用) + */ + private String iotId; + + /** + * 设备所属用户ID(关联查询用) + */ + private Long userId; + + /** + * 设备类型(关联查询用) + */ + private Integer deviceType; + + /** + * 设备输入电压类型(关联查询用) + */ + private Integer inputVoltage; + } diff --git a/intc-modules/intc-fishery/src/main/java/com/intc/fishery/domain/vo/LinkedCtrlVo.java b/intc-modules/intc-fishery/src/main/java/com/intc/fishery/domain/vo/LinkedCtrlVo.java index bc04cc4..3028ec1 100644 --- a/intc-modules/intc-fishery/src/main/java/com/intc/fishery/domain/vo/LinkedCtrlVo.java +++ b/intc-modules/intc-fishery/src/main/java/com/intc/fishery/domain/vo/LinkedCtrlVo.java @@ -109,5 +109,24 @@ public class LinkedCtrlVo implements Serializable { @ExcelProperty(value = "用户名") private String userName; + /** + * 用户ID(关联查询用) + */ + private Long userId; + + /** + * 塘口ID(关联查询用) + */ + private Long pondId; + + /** + * 设备告警状态码(关联查询用) + */ + private Integer warnCode; + + /** + * 设备到期时间(关联查询用) + */ + private java.util.Date deadTime; } diff --git a/intc-modules/intc-fishery/src/main/java/com/intc/fishery/domain/vo/PublicDeviceBaseData.java b/intc-modules/intc-fishery/src/main/java/com/intc/fishery/domain/vo/PublicDeviceBaseData.java new file mode 100644 index 0000000..8d1053d --- /dev/null +++ b/intc-modules/intc-fishery/src/main/java/com/intc/fishery/domain/vo/PublicDeviceBaseData.java @@ -0,0 +1,121 @@ +package com.intc.fishery.domain.vo; + +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.util.Date; +import java.util.List; + +/** + * 设备基础数据视图对象 + * + * @author intc + * @date 2026-01-13 + */ +@Data +public class PublicDeviceBaseData implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 设备ID + */ + private Long id; + + /** + * 设备名称 + */ + private String deviceName; + + /** + * 设备编号 + */ + private String serialNum; + + /** + * 设备类型 + */ + private Integer deviceType; + + /** + * 绑定时间 + */ + private Date bindTime; + + /** + * 服务到期时间 + */ + private Date deadTime; + + /** + * 塘口信息 + */ + private PublicPondIdNameVo pondInfo; + + /** + * 溶解氧参数配置开关 + */ + private Integer isOxygenUsed; + + /** + * 溶解氧电话告警开关 + */ + private Integer oxyWarnTelOpen; + + /** + * 低溶氧告警免打扰 + */ + private Integer oxyWarnTelNoDis; + + /** + * 溶解氧电话告警下限 + */ + private Double oxyWarnLower; + + /** + * 温度电话告警开关 + */ + private Integer tempWarnTelOpen; + + /** + * 温度告警免打扰 + */ + private Integer tempWarnTelNoDis; + + /** + * 温度电话告警上限 + */ + private Double tempWarnUpper; + + /** + * 温度电话告警下限 + */ + private Double tempWarnLower; + + /** + * 盐度补偿 + */ + private Double salinityCompensation; + + /** + * 输入额定电压 + */ + private Integer inputVoltage; + + /** + * 电压告警开关 + */ + private Integer voltageWarnOpen; + + /** + * 开关列表(仅测控一体机) + */ + private List listSwitch; + + /** + * 联动控制列表 + */ + private List listLinkedCtr; +} diff --git a/intc-modules/intc-fishery/src/main/java/com/intc/fishery/domain/vo/PublicDeviceDeadInfo.java b/intc-modules/intc-fishery/src/main/java/com/intc/fishery/domain/vo/PublicDeviceDeadInfo.java new file mode 100644 index 0000000..5cb1af8 --- /dev/null +++ b/intc-modules/intc-fishery/src/main/java/com/intc/fishery/domain/vo/PublicDeviceDeadInfo.java @@ -0,0 +1,48 @@ +package com.intc.fishery.domain.vo; + +import com.fasterxml.jackson.annotation.JsonFormat; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.util.Date; + +/** + * 设备到期信息视图对象 + * + * @author intc + * @date 2026-01-13 + */ +@Data +@Schema(description = "设备到期信息") +public class PublicDeviceDeadInfo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 设备ID + */ + @Schema(description = "设备ID") + private Long id; + + /** + * 设备名称 + */ + @Schema(description = "设备名称") + private String deviceName; + + /** + * 设备序列号 + */ + @Schema(description = "设备序列号") + private String serialNum; + + /** + * 服务到期时间 + */ + @Schema(description = "服务到期时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date deadTime; +} diff --git a/intc-modules/intc-fishery/src/main/java/com/intc/fishery/domain/vo/PublicDeviceLinkedCtrl.java b/intc-modules/intc-fishery/src/main/java/com/intc/fishery/domain/vo/PublicDeviceLinkedCtrl.java new file mode 100644 index 0000000..9a5b66e --- /dev/null +++ b/intc-modules/intc-fishery/src/main/java/com/intc/fishery/domain/vo/PublicDeviceLinkedCtrl.java @@ -0,0 +1,55 @@ +package com.intc.fishery.domain.vo; + +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.util.List; + +/** + * 设备联动控制视图对象 + * + * @author intc + * @date 2026-01-13 + */ +@Data +public class PublicDeviceLinkedCtrl implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 联动控制ID + */ + private Long id; + + /** + * 设备ID + */ + private Long deviceId; + + /** + * 溶解氧上限开关 + */ + private Integer oxyUpperOpen; + + /** + * 溶解氧上限值 + */ + private Double oxyUpperValue; + + /** + * 溶解氧下限开关 + */ + private Integer oxyLowerOpen; + + /** + * 溶解氧下限值 + */ + private Double oxyLowerValue; + + /** + * 关联的开关列表 + */ + private List listSwitch; +} diff --git a/intc-modules/intc-fishery/src/main/java/com/intc/fishery/service/IDeviceSwitchService.java b/intc-modules/intc-fishery/src/main/java/com/intc/fishery/service/IDeviceSwitchService.java index c0f0267..8faa5c7 100644 --- a/intc-modules/intc-fishery/src/main/java/com/intc/fishery/service/IDeviceSwitchService.java +++ b/intc-modules/intc-fishery/src/main/java/com/intc/fishery/service/IDeviceSwitchService.java @@ -1,5 +1,7 @@ package com.intc.fishery.service; +import com.intc.fishery.domain.dto.PublicDeviceSimpleDto; +import com.intc.fishery.domain.dto.PublicDeviceSwitchBaseData; import com.intc.fishery.domain.vo.DeviceSwitchVo; import com.intc.fishery.domain.bo.DeviceSwitchBo; import com.intc.common.mybatis.core.page.TableDataInfo; @@ -65,4 +67,36 @@ public interface IDeviceSwitchService { * @return 是否删除成功 */ Boolean deleteWithValidByIds(Collection ids, Boolean isValid); + + /** + * 根据塘口ID查询塘口下的设备及其开关信息 + * + * @param pondId 塘口ID + * @return 设备及开关列表 + */ + List getPondSwitch(Long pondId); + + /** + * 获取单个开关的基础信息 + * + * @param switchId 开关ID + * @return 开关基础信息 + */ + PublicDeviceSwitchBaseData getOneSwitchInfo(Long switchId); + + /** + * 修改开关名称 + * + * @param switchId 开关ID + * @param newName 新名称 + */ + void changeSwitchName(Long switchId, String newName); + + /** + * 设置电流告警开关 + * + * @param switchId 开关ID + * @param isOpen 是否开启(0-关闭,1-开启) + */ + void updateElectricWarnOpen(Long switchId, Integer isOpen); } diff --git a/intc-modules/intc-fishery/src/main/java/com/intc/fishery/service/ILinkedCtrlService.java b/intc-modules/intc-fishery/src/main/java/com/intc/fishery/service/ILinkedCtrlService.java index 6a836de..c48f6f4 100644 --- a/intc-modules/intc-fishery/src/main/java/com/intc/fishery/service/ILinkedCtrlService.java +++ b/intc-modules/intc-fishery/src/main/java/com/intc/fishery/service/ILinkedCtrlService.java @@ -1,5 +1,9 @@ package com.intc.fishery.service; +import com.intc.fishery.domain.bo.ReqAddLinkedCtrl; +import com.intc.fishery.domain.bo.ReqLinkedCtrlOpen; +import com.intc.fishery.domain.bo.ReqUpdateLinkedCtrl; +import com.intc.fishery.domain.dto.PublicLinkedCtrl; import com.intc.fishery.domain.vo.LinkedCtrlVo; import com.intc.fishery.domain.bo.LinkedCtrlBo; import com.intc.common.mybatis.core.page.TableDataInfo; @@ -65,4 +69,33 @@ public interface ILinkedCtrlService { * @return 是否删除成功 */ Boolean deleteWithValidByIds(Collection ids, Boolean isValid); + + /** + * 新增联动控制(业务方法) + * + * @param request 新增联动控制请求 + */ + void addLinkedCtrl(ReqAddLinkedCtrl request); + + /** + * 修改联动控制(业务方法) + * + * @param request 修改联动控制请求 + */ + void updateLinkedCtrl(ReqUpdateLinkedCtrl request); + + /** + * 根据设备ID查询所有联动控制 + * + * @param deviceId 设备ID + * @return 联动控制列表 + */ + List queryAllLinkedCtrl(Long deviceId); + + /** + * 设置联动控制开关 + * + * @param request 设置开关请求 + */ + void setLinkedOpen(ReqLinkedCtrlOpen request); } diff --git a/intc-modules/intc-fishery/src/main/java/com/intc/fishery/service/impl/DeviceSwitchServiceImpl.java b/intc-modules/intc-fishery/src/main/java/com/intc/fishery/service/impl/DeviceSwitchServiceImpl.java index 6afabc0..1f1d714 100644 --- a/intc-modules/intc-fishery/src/main/java/com/intc/fishery/service/impl/DeviceSwitchServiceImpl.java +++ b/intc-modules/intc-fishery/src/main/java/com/intc/fishery/service/impl/DeviceSwitchServiceImpl.java @@ -8,8 +8,13 @@ import com.intc.common.mybatis.core.page.PageQuery; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.intc.fishery.domain.dto.PublicDeviceSimpleDto; +import com.intc.fishery.domain.dto.PublicDeviceSwitchSimpleDto; +import com.intc.fishery.domain.dto.PublicDeviceSwitchBaseData; +import com.intc.fishery.domain.dto.PublicPondIdName; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import com.intc.fishery.domain.bo.DeviceSwitchBo; import com.intc.fishery.domain.vo.DeviceSwitchVo; @@ -17,12 +22,13 @@ import com.intc.fishery.domain.DeviceSwitch; import com.intc.fishery.domain.Device; import com.intc.fishery.domain.Pond; import com.intc.fishery.domain.AquUser; +import com.intc.fishery.domain.TimingCtrl; import com.intc.fishery.mapper.DeviceSwitchMapper; +import com.intc.fishery.mapper.DeviceMapper; import com.intc.fishery.service.IDeviceSwitchService; -import java.util.List; -import java.util.Map; -import java.util.Collection; +import java.util.*; +import java.util.stream.Collectors; /** * 测控一体机开关Service业务层处理 @@ -36,6 +42,7 @@ import java.util.Collection; public class DeviceSwitchServiceImpl implements IDeviceSwitchService { private final DeviceSwitchMapper baseMapper; + private final DeviceMapper deviceMapper; /** * 查询测控一体机开关 @@ -223,4 +230,221 @@ public class DeviceSwitchServiceImpl implements IDeviceSwitchService { } return baseMapper.deleteByIds(ids) > 0; } -} + + /** + * 根据塘口ID查询塘口下的设备及其开关信息 + * + * @param pondId 塘口ID + * @return 设备及开关列表 + */ + @Override + public List getPondSwitch(Long pondId) { + // 使用连表查询获取该塘口下的所有开关及其关联的设备信息 + MPJLambdaWrapper wrapper = new MPJLambdaWrapper() + .selectAll(DeviceSwitch.class) + .selectAs(Device::getDeviceName, DeviceSwitchVo::getDeviceName) + .selectAs(Device::getWarnCode, DeviceSwitchVo::getWarnCode) + .selectAs(Device::getDeadTime, "deadTime") + .leftJoin(Device.class, Device::getId, DeviceSwitch::getDeviceId) + .eq(DeviceSwitch::getPondId, pondId) + .orderByAsc(DeviceSwitch::getIndex); + + List switchList = baseMapper.selectJoinList(DeviceSwitchVo.class, wrapper); + + // 过滤掉已过期的设备,并按设备分组 + Map> deviceSwitchMap = switchList.stream() + .filter(vo -> !isDeviceDead(vo)) + .collect(Collectors.groupingBy(DeviceSwitchVo::getDeviceId)); + + // 构建返回结果 + List result = new ArrayList<>(); + for (Map.Entry> entry : deviceSwitchMap.entrySet()) { + List switches = entry.getValue(); + if (switches.isEmpty()) { + continue; + } + + // 从第一个开关记录中获取设备信息 + DeviceSwitchVo firstSwitch = switches.get(0); + + PublicDeviceSimpleDto deviceDto = new PublicDeviceSimpleDto(); + deviceDto.setId(entry.getKey()); + deviceDto.setDeviceName(firstSwitch.getDeviceName()); + deviceDto.setDeviceType(1); // 测控一体机设备类型设为1(Controller) + deviceDto.setWarnCode(firstSwitch.getWarnCode()); + + // 转换开关列表 + List switchDtoList = switches.stream() + .map(this::convertToSwitchDto) + .collect(Collectors.toList()); + deviceDto.setListSwitch(switchDtoList); + + result.add(deviceDto); + } + + return result; + } + + /** + * 判断设备是否已过期 + * + * @param vo 设备开关信息 + * @return true-已过期,false-未过期 + */ + private boolean isDeviceDead(DeviceSwitchVo vo) { + Date deadTime = vo.getDeadTime(); + if (deadTime == null) { + return false; + } + return deadTime.before(new Date()); + } + + /** + * 将DeviceSwitchVo转换为PublicDeviceSwitchSimpleDto + * + * @param vo 设备开关VO + * @return 设备开关简单DTO + */ + private PublicDeviceSwitchSimpleDto convertToSwitchDto(DeviceSwitchVo vo) { + PublicDeviceSwitchSimpleDto dto = new PublicDeviceSwitchSimpleDto(); + dto.setId(vo.getId()); + dto.setIndex(vo.getIndex()); + dto.setSwitchName(vo.getSwitchName()); + dto.setLinkedCtrlId(vo.getLinkedCtrlId() != null ? vo.getLinkedCtrlId() : 0L); + return dto; + } + + /** + * 获取单个开关的基础信息 + * + * @param switchId 开关ID + * @return 开关基础信息 + */ + @Override + public PublicDeviceSwitchBaseData getOneSwitchInfo(Long switchId) { + // 使用连表查询获取开关及其关联的设备和塘口信息 + MPJLambdaWrapper wrapper = new MPJLambdaWrapper() + .selectAll(DeviceSwitch.class) + .selectAs(Device::getDeviceName, "deviceName") + .selectAs(Device::getInputVoltage, "inputVoltage") + .selectAs(Pond::getPondName, "pondName") + .leftJoin(Device.class, Device::getId, DeviceSwitch::getDeviceId) + .leftJoin(Pond.class, Pond::getId, DeviceSwitch::getPondId) + .eq(DeviceSwitch::getId, switchId); + + DeviceSwitchVo vo = baseMapper.selectJoinOne(DeviceSwitchVo.class, wrapper); + if (vo == null) { + return null; + } + + // 转换为返回DTO + PublicDeviceSwitchBaseData data = new PublicDeviceSwitchBaseData(); + data.setId(vo.getId()); + data.setIndex(vo.getIndex()); + data.setSwitchName(vo.getSwitchName()); + data.setDeviceName(vo.getDeviceName()); + data.setDetectElectricValue(vo.getDetectElectricValue()); + data.setDetectVoltageValue(vo.getDetectVoltageValue()); + data.setConnectVoltageType(vo.getConnectVoltageType()); + data.setElectricWarnOpen(vo.getElectricWarnOpen()); + data.setRateElectricValue(vo.getRateElectricValue()); + + // 从连表查询结果中获取inputVoltage + Integer inputVoltage = null; + if (vo.getDeviceId() != null) { + Device device = deviceMapper.selectById(vo.getDeviceId()); + if (device != null) { + inputVoltage = device.getInputVoltage(); + } + } + data.setInputVoltage(inputVoltage); + + // 设置塘口信息 + if (vo.getPondId() != null && StringUtils.isNotBlank(vo.getPondName())) { + PublicPondIdName pondInfo = new PublicPondIdName(); + pondInfo.setId(vo.getPondId()); + pondInfo.setPondName(vo.getPondName()); + data.setPondInfo(pondInfo); + } + + // 如果接线方式为4,需要将电压值乘以1.732 + if (data.getConnectVoltageType() != null && data.getConnectVoltageType() == 4 && data.getDetectVoltageValue() != null) { + data.setDetectVoltageValue(data.getDetectVoltageValue() * 1.732); + } + + return data; + } + + /** + * 修改开关名称 + * + * @param switchId 开关ID + * @param newName 新名称 + */ + @Override + public void changeSwitchName(Long switchId, String newName) { + // 查询开关信息,包括关联的设备信息 + MPJLambdaWrapper wrapper = new MPJLambdaWrapper() + .selectAll(DeviceSwitch.class) + .selectAs(Device::getDeviceName, "deviceName") + .selectAs(Device::getSerialNum, "serialNum") + .leftJoin(Device.class, Device::getId, DeviceSwitch::getDeviceId) + .eq(DeviceSwitch::getId, switchId); + + DeviceSwitchVo vo = baseMapper.selectJoinOne(DeviceSwitchVo.class, wrapper); + if (vo == null) { + throw new RuntimeException("开关不存在"); + } + + String oldName = vo.getSwitchName(); + + // 更新开关名称 + DeviceSwitch updateEntity = new DeviceSwitch(); + updateEntity.setId(switchId); + updateEntity.setSwitchName(newName); + baseMapper.updateById(updateEntity); + + // 记录操作日志 + log.info("开关操作:{}({})的开关{}修改名称为:{}", + vo.getDeviceName(), vo.getSerialNum(), oldName, newName); + } + + /** + * 设置电流告警开关 + * + * @param switchId 开关ID + * @param isOpen 是否开启(0-关闭,1-开启) + */ + @Override + public void updateElectricWarnOpen(Long switchId, Integer isOpen) { + // 查询开关信息,包括关联的设备信息 + MPJLambdaWrapper wrapper = new MPJLambdaWrapper() + .selectAll(DeviceSwitch.class) + .selectAs(Device::getDeviceName, "deviceName") + .selectAs(Device::getSerialNum, "serialNum") + .selectAs(Device::getUserId, "userId") + .leftJoin(Device.class, Device::getId, DeviceSwitch::getDeviceId) + .eq(DeviceSwitch::getId, switchId); + + DeviceSwitchVo vo = baseMapper.selectJoinOne(DeviceSwitchVo.class, wrapper); + if (vo == null || vo.getUserId() == null) { + throw new RuntimeException("开关不存在"); + } + + // 如果状态未变化,无需更新 + if (vo.getElectricWarnOpen() != null && vo.getElectricWarnOpen().equals(isOpen)) { + return; + } + + // 更新电流告警开关状态 + DeviceSwitch updateEntity = new DeviceSwitch(); + updateEntity.setId(switchId); + updateEntity.setElectricWarnOpen(isOpen); + baseMapper.updateById(updateEntity); + + // 记录操作日志 + String operation = isOpen == 1 ? "开启" : "关闭"; + log.info("开关操作:{}({})的开关{},设置电流告警开关:{}", + vo.getDeviceName(), vo.getSerialNum(), vo.getSwitchName(), operation); + } +} \ No newline at end of file diff --git a/intc-modules/intc-fishery/src/main/java/com/intc/fishery/service/impl/LinkedCtrlServiceImpl.java b/intc-modules/intc-fishery/src/main/java/com/intc/fishery/service/impl/LinkedCtrlServiceImpl.java index 4dd9a8e..448d35d 100644 --- a/intc-modules/intc-fishery/src/main/java/com/intc/fishery/service/impl/LinkedCtrlServiceImpl.java +++ b/intc-modules/intc-fishery/src/main/java/com/intc/fishery/service/impl/LinkedCtrlServiceImpl.java @@ -1,5 +1,7 @@ package com.intc.fishery.service.impl; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import com.intc.common.core.exception.ServiceException; import com.intc.common.core.utils.MapstructUtils; import com.intc.common.core.utils.StringUtils; import com.intc.common.mybatis.core.page.TableDataInfo; @@ -9,7 +11,14 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.github.yulichang.wrapper.MPJLambdaWrapper; import com.intc.fishery.domain.*; +import com.intc.fishery.domain.bo.ReqAddLinkedCtrl; +import com.intc.fishery.domain.bo.ReqLinkedCtrlOpen; +import com.intc.fishery.domain.bo.ReqUpdateLinkedCtrl; +import com.intc.fishery.domain.dto.PublicDeviceSwitchSimple; +import com.intc.fishery.domain.dto.PublicLinkedCtrl; import com.intc.fishery.domain.vo.DeviceSwitchVo; +import com.intc.fishery.mapper.DeviceMapper; +import com.intc.fishery.mapper.DeviceSwitchMapper; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; @@ -17,10 +26,10 @@ import com.intc.fishery.domain.bo.LinkedCtrlBo; import com.intc.fishery.domain.vo.LinkedCtrlVo; import com.intc.fishery.mapper.LinkedCtrlMapper; import com.intc.fishery.service.ILinkedCtrlService; +import org.springframework.transaction.annotation.Transactional; -import java.util.List; -import java.util.Map; -import java.util.Collection; +import java.util.*; +import java.util.stream.Collectors; /** * 溶解氧联动控制Service业务层处理 @@ -34,6 +43,8 @@ import java.util.Collection; public class LinkedCtrlServiceImpl implements ILinkedCtrlService { private final LinkedCtrlMapper baseMapper; + private final DeviceMapper deviceMapper; + private final DeviceSwitchMapper deviceSwitchMapper; /** * 查询溶解氧联动控制 @@ -191,10 +202,351 @@ public class LinkedCtrlServiceImpl implements ILinkedCtrlService { * @return 是否删除成功 */ @Override + @Transactional(rollbackFor = Exception.class) public Boolean deleteWithValidByIds(Collection ids, Boolean isValid) { if(isValid){ //TODO 做一些业务上的校验,判断是否需要校验 } + + // 删除联动控制前,先解除关联的开关绑定 + deviceSwitchMapper.update(null, + Wrappers.lambdaUpdate(DeviceSwitch.class) + .set(DeviceSwitch::getLinkedCtrlId, null) + .in(DeviceSwitch::getLinkedCtrlId, ids)); + + // 删除联动控制记录 return baseMapper.deleteByIds(ids) > 0; } + + /** + * 新增联动控制(业务方法) + * + * @param request 新增联动控制请求 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public void addLinkedCtrl(ReqAddLinkedCtrl request) { + // 1. 检查该设备的联动控制数量是否超过3个 + long count = baseMapper.selectCount(Wrappers.lambdaQuery(LinkedCtrl.class) + .eq(LinkedCtrl::getDeviceId, request.getDeviceId())); + if (count >= 3) { + throw new ServiceException("联动控制数量不得超过3个"); + } + + // 2. 查询设备信息 + Device device = deviceMapper.selectOne(Wrappers.lambdaQuery(Device.class) + .eq(Device::getId, request.getDeviceId()) + .select(Device::getId, Device::getUserId, Device::getPondId, + Device::getDeviceName, Device::getSerialNum, Device::getWarnCode, Device::getDeadTime)); + + if (device == null) { + throw new ServiceException("设备不存在"); + } + + // 3. 检查设备是否过期 + if (isDeviceDead(device)) { + throw new ServiceException("设备已过期"); + } + + // 4. 检查设备是否已绑定塘口 + if (device.getPondId() == null) { + throw new ServiceException("设备未绑定塘口"); + } + + Long pondId = device.getPondId(); + + // 5. 验证开关是否属于该塘口 + long matchCount = deviceSwitchMapper.selectCount(Wrappers.lambdaQuery(DeviceSwitch.class) + .eq(DeviceSwitch::getPondId, pondId) + .in(DeviceSwitch::getId, request.getListSwitchId())); + if (matchCount != request.getListSwitchId().size()) { + throw new ServiceException("开关不属于该塘口"); + } + + // 6. 创建联动控制记录 + LinkedCtrl linkedCtrl = new LinkedCtrl(); + linkedCtrl.setDeviceId(request.getDeviceId()); + linkedCtrl.setOxyUpperOpen(0); // 默认关闭 + linkedCtrl.setOxyUpperValue(10.0); + linkedCtrl.setIsOxyUpperTrigger(0); + linkedCtrl.setOxyLowerOpen(0); // 默认关闭 + linkedCtrl.setOxyLowerValue(5.0); + + int inserted = baseMapper.insert(linkedCtrl); + if (inserted == 0) { + throw new ServiceException("新增联动控制失败"); + } + + Long linkedCtrlId = linkedCtrl.getId(); + + // 7. 更新开关的linkedCtrlId + int updated = deviceSwitchMapper.update(null, + Wrappers.lambdaUpdate(DeviceSwitch.class) + .set(DeviceSwitch::getLinkedCtrlId, linkedCtrlId) + .in(DeviceSwitch::getId, request.getListSwitchId())); + + if (updated == 0) { + throw new ServiceException("请选择联动控制开关"); + } + + log.info("设备{}({})新增联动控制", device.getDeviceName(), device.getSerialNum()); + } + + /** + * 修改联动控制(业务方法) + * + * @param request 修改联动控制请求 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public void updateLinkedCtrl(ReqUpdateLinkedCtrl request) { + // 1. 验证溶解氧上下限值 + if (request.getOxyWarnUpper() < request.getOxyWarnLower() + 1.0) { + throw new ServiceException("溶解氧上限值必须大于下限值1以上"); + } + + // 2. 查询联动控制信息(包括设备信息) + MPJLambdaWrapper wrapper = new MPJLambdaWrapper() + .selectAll(LinkedCtrl.class) + .selectAs(Device::getUserId, LinkedCtrlVo::getUserId) + .selectAs(Device::getPondId, LinkedCtrlVo::getPondId) + .selectAs(Device::getDeviceName, LinkedCtrlVo::getDeviceName) + .selectAs(Device::getSerialNum, LinkedCtrlVo::getSerialNum) + .selectAs(Device::getWarnCode, LinkedCtrlVo::getWarnCode) + .selectAs(Device::getDeadTime, LinkedCtrlVo::getDeadTime) + .leftJoin(Device.class, Device::getId, LinkedCtrl::getDeviceId) + .eq(LinkedCtrl::getId, request.getId()); + + LinkedCtrlVo linkedCtrlVo = baseMapper.selectJoinOne(LinkedCtrlVo.class, wrapper); + + if (linkedCtrlVo == null) { + throw new ServiceException("联动控制不存在"); + } + + // 3. 查询设备信息用于过期检查 + Device device = deviceMapper.selectOne(Wrappers.lambdaQuery(Device.class) + .eq(Device::getId, linkedCtrlVo.getDeviceId()) + .select(Device::getId, Device::getDeadTime, Device::getPondId, + Device::getDeviceName, Device::getSerialNum)); + + if (device == null) { + throw new ServiceException("设备不存在"); + } + + // 4. 检查设备是否过期 + if (isDeviceDead(device)) { + throw new ServiceException("设备已过期"); + } + + // 5. 验证开关是否属于该塘口 + long matchCount = deviceSwitchMapper.selectCount(Wrappers.lambdaQuery(DeviceSwitch.class) + .eq(DeviceSwitch::getPondId, device.getPondId()) + .in(DeviceSwitch::getId, request.getListSwitchId())); + if (matchCount != request.getListSwitchId().size()) { + throw new ServiceException("开关不属于该塘口"); + } + + // 6. 查询当前联动控制已绑定的开关 + List currentSwitches = deviceSwitchMapper.selectList( + Wrappers.lambdaQuery(DeviceSwitch.class) + .eq(DeviceSwitch::getLinkedCtrlId, request.getId()) + .select(DeviceSwitch::getId)); + + List currentSwitchIds = currentSwitches.stream() + .map(DeviceSwitch::getId) + .collect(Collectors.toList()); + + // 找出需要移除绑定的开关(在旧列表中但不在新列表中) + List switchesToRemove = currentSwitchIds.stream() + .filter(id -> !request.getListSwitchId().contains(id)) + .collect(Collectors.toList()); + + // 7. 更新联动控制的溶解氧上下限值 + baseMapper.update(null, + Wrappers.lambdaUpdate(LinkedCtrl.class) + .set(LinkedCtrl::getOxyUpperValue, request.getOxyWarnUpper()) + .set(LinkedCtrl::getOxyLowerValue, request.getOxyWarnLower()) + .eq(LinkedCtrl::getId, request.getId())); + + // 8. 更新新开关的linkedCtrlId + deviceSwitchMapper.update(null, + Wrappers.lambdaUpdate(DeviceSwitch.class) + .set(DeviceSwitch::getLinkedCtrlId, request.getId()) + .in(DeviceSwitch::getId, request.getListSwitchId())); + + // 9. 移除旧开关的linkedCtrlId + if (!switchesToRemove.isEmpty()) { + deviceSwitchMapper.update(null, + Wrappers.lambdaUpdate(DeviceSwitch.class) + .set(DeviceSwitch::getLinkedCtrlId, null) + .in(DeviceSwitch::getId, switchesToRemove)); + } + + log.info("设备{}({})修改联动控制", device.getDeviceName(), device.getSerialNum()); + } + + /** + * 判断设备是否已过期 + * + * @param device 设备信息 + * @return true-已过期,false-未过期 + */ + private boolean isDeviceDead(Device device) { + Date deadTime = device.getDeadTime(); + if (deadTime == null) { + return false; + } + return deadTime.before(new Date()); + } + + /** + * 根据设备ID查询所有联动控制 + * + * @param deviceId 设备ID + * @return 联动控制列表 + */ + @Override + public List queryAllLinkedCtrl(Long deviceId) { + // 1. 查询该设备下的所有联动控制 + List linkedCtrls = baseMapper.selectList( + Wrappers.lambdaQuery(LinkedCtrl.class) + .eq(LinkedCtrl::getDeviceId, deviceId) + .orderByAsc(LinkedCtrl::getId)); + + if (linkedCtrls.isEmpty()) { + return new ArrayList<>(); + } + + // 2. 收集所有联动控制ID + List linkedCtrlIds = linkedCtrls.stream() + .map(LinkedCtrl::getId) + .collect(Collectors.toList()); + + // 3. 查询与这些联动控制相关的开关 + List switches = deviceSwitchMapper.selectList( + Wrappers.lambdaQuery(DeviceSwitch.class) + .in(DeviceSwitch::getLinkedCtrlId, linkedCtrlIds) + .orderByAsc(DeviceSwitch::getIndex)); + + // 4. 按linkedCtrlId分组开关 + Map> switchMap = switches.stream() + .collect(Collectors.groupingBy(DeviceSwitch::getLinkedCtrlId)); + + // 5. 构建返回结果 + List result = new ArrayList<>(); + for (LinkedCtrl linkedCtrl : linkedCtrls) { + PublicLinkedCtrl dto = new PublicLinkedCtrl(); + dto.setId(linkedCtrl.getId()); + dto.setDeviceId(linkedCtrl.getDeviceId()); + dto.setOxyUpperOpen(linkedCtrl.getOxyUpperOpen()); + dto.setOxyUpperValue(linkedCtrl.getOxyUpperValue()); + dto.setOxyLowerOpen(linkedCtrl.getOxyLowerOpen()); + dto.setOxyLowerValue(linkedCtrl.getOxyLowerValue()); + + // 转换开关列表 + List linkedSwitches = switchMap.getOrDefault(linkedCtrl.getId(), new ArrayList<>()); + List switchDtos = linkedSwitches.stream() + .map(this::convertToSwitchSimple) + .collect(Collectors.toList()); + dto.setListSwitch(switchDtos); + + result.add(dto); + } + + return result; + } + + /** + * 将DeviceSwitch转换为PublicDeviceSwitchSimple + * + * @param deviceSwitch 设备开关 + * @return 设备开关简单DTO + */ + private PublicDeviceSwitchSimple convertToSwitchSimple(DeviceSwitch deviceSwitch) { + PublicDeviceSwitchSimple dto = new PublicDeviceSwitchSimple(); + dto.setId(deviceSwitch.getId()); + dto.setIndex(deviceSwitch.getIndex()); + dto.setSwitchName(deviceSwitch.getSwitchName()); + return dto; + } + + /** + * 设置联动控制开关 + * + * @param request 设置开关请求 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public void setLinkedOpen(ReqLinkedCtrlOpen request) { + // 1. 查询联动控制信息(包括设备信息) + MPJLambdaWrapper wrapper = new MPJLambdaWrapper() + .selectAll(LinkedCtrl.class) + .selectAs(Device::getUserId, LinkedCtrlVo::getUserId) + .selectAs(Device::getPondId, LinkedCtrlVo::getPondId) + .selectAs(Device::getDeviceName, LinkedCtrlVo::getDeviceName) + .selectAs(Device::getSerialNum, LinkedCtrlVo::getSerialNum) + .selectAs(Device::getWarnCode, LinkedCtrlVo::getWarnCode) + .selectAs(Device::getDeadTime, LinkedCtrlVo::getDeadTime) + .leftJoin(Device.class, Device::getId, LinkedCtrl::getDeviceId) + .eq(LinkedCtrl::getId, request.getId()); + + LinkedCtrlVo linkedCtrlVo = baseMapper.selectJoinOne(LinkedCtrlVo.class, wrapper); + + if (linkedCtrlVo == null) { + throw new ServiceException("联动控制不存在"); + } + + // 2. 查询设备信息用于过期检查 + Device device = deviceMapper.selectOne(Wrappers.lambdaQuery(Device.class) + .eq(Device::getId, linkedCtrlVo.getDeviceId()) + .select(Device::getId, Device::getDeadTime, Device::getDeviceName, Device::getSerialNum)); + + if (device == null) { + throw new ServiceException("设备不存在"); + } + + // 3. 检查设备是否过期 + if (isDeviceDead(device)) { + throw new ServiceException("设备已过期"); + } + + // 4. 根据开关类型设置对应的开关 + if (request.getOpenType() == 1) { + // 上限开关 + if (linkedCtrlVo.getOxyUpperOpen().equals(request.getIsOpen())) { + // 状态未变化,直接返回 + return; + } + + // 更新上限开关状态和触发标志 + baseMapper.update(null, + Wrappers.lambdaUpdate(LinkedCtrl.class) + .set(LinkedCtrl::getOxyUpperOpen, request.getIsOpen()) + .set(LinkedCtrl::getIsOxyUpperTrigger, 0) // 重置触发标志 + .eq(LinkedCtrl::getId, request.getId())); + + String op = request.getIsOpen() == 1 ? "开启" : "关闭"; + log.info("设备{}({})溶解氧上限联动控制:{}", + device.getDeviceName(), device.getSerialNum(), op); + + } else if (request.getOpenType() == 2) { + // 下限开关 + if (linkedCtrlVo.getOxyLowerOpen().equals(request.getIsOpen())) { + // 状态未变化,直接返回 + return; + } + + // 更新下限开关状态 + baseMapper.update(null, + Wrappers.lambdaUpdate(LinkedCtrl.class) + .set(LinkedCtrl::getOxyLowerOpen, request.getIsOpen()) + .eq(LinkedCtrl::getId, request.getId())); + + String op = request.getIsOpen() == 1 ? "开启" : "关闭"; + log.info("设备{}({})溶解氧下限联动控制:{}", + device.getDeviceName(), device.getSerialNum(), op); + } else { + throw new ServiceException("无效的开关类型"); + } + } } 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 839b6a0..ab2dc06 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 @@ -11,7 +11,10 @@ import com.intc.fishery.mapper.PondMapper; import com.intc.iot.config.AliyunIotProperties; import com.intc.iot.domain.bo.AddDeviceControllerBo; import com.intc.iot.domain.bo.AddDeviceDetectorBo; +import com.intc.iot.domain.bo.DeviceCalibrateBo; import com.intc.iot.domain.bo.DeviceRealtimeDataBo; +import com.intc.iot.domain.bo.DeviceSalinityCompensationBo; +import com.intc.iot.domain.bo.DeviceVoltageTypeBo; import com.intc.iot.domain.vo.DeviceRealtimeDataVo; import com.intc.iot.service.DeviceDataService; import com.intc.iot.service.DeviceRealtimeDataService; @@ -22,6 +25,8 @@ import com.intc.iot.service.VmsMnsConsumerService; import com.intc.iot.service.VmsNoticeService; import com.intc.iot.service.WarnCallNoticeService; import com.intc.iot.utils.AliyunAmqpSignUtil; +import com.intc.iot.utils.ControllerHelper; +import com.intc.iot.service.IotCloudService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; @@ -83,6 +88,15 @@ public class IotController extends BaseController { @Autowired(required = false) private com.intc.fishery.mapper.DeviceSwitchMapper deviceSwitchMapper; + @Autowired(required = false) + private com.intc.fishery.mapper.DeviceCorrectRecordMapper deviceCorrectRecordMapper; + + @Autowired(required = false) + private com.intc.fishery.mapper.TimingCtrlMapper timingCtrlMapper; + + @Autowired(required = false) + private IotCloudService iotCloudService; + @Operation(summary = "测试接口") @GetMapping("/test") public R test() { @@ -348,11 +362,11 @@ public class IotController extends BaseController { try { java.lang.reflect.Method getStatusMethod = detailData.getClass().getMethod("getStatus"); Object statusObj = getStatusMethod.invoke(detailData); - + if (statusObj != null) { String statusStr = statusObj.toString(); log.info("从 SDK 对象获取到的状态: {}", statusStr); - + // 根据物联网平台返回的状态转换为前端状态码 // 0-未激活, 1-在线, 3-离线, 8-禁用 if ("ONLINE".equalsIgnoreCase(statusStr) || "online".equals(statusStr)) { @@ -788,7 +802,7 @@ public class IotController extends BaseController { log.info("查询控制器物模型,ProductKey: {}", productKey); Map thingModel = iotDeviceService.queryThingModel(productKey); log.info("物模型查询结果: {}", thingModel); - + // 先查询现有设备属性,了解 rated_voltage 的正确格式 try { Map currentProps = iotDeviceService.queryDeviceProperties(iotId); @@ -796,10 +810,10 @@ public class IotController extends BaseController { } catch (Exception e) { log.warn("查询当前设备属性失败: {}", e.getMessage()); } - + // 设置设备属性 Map properties = new java.util.HashMap<>(); - + // 设置额定电压 - rated_voltage 是结构体类型 // 根据C#代码,需要调用 ControllerHelper.GetInputVoltageProperty // 该方法返回一个特定结构的对象 @@ -807,17 +821,17 @@ public class IotController extends BaseController { if (voltValue == null) { return R.fail("电压参数错误"); } - + // 暂时跳过 rated_voltage 设置,先设置其他属性 // properties.put("rated_voltage", ratedVoltageStruct); - + // 如果是三相380V四线,设置输出电压 if (bo.getInputVoltage() == 4) { for (int i = 1; i <= 4; i++) { properties.put("Switch" + i + "_volt", 380); } } - + // 清空定时控制数据和设置额定电流 java.util.List emptyTimerList = new java.util.ArrayList<>(); Double defaultRatingSwitch = 10.0; // 默认额定电流10A @@ -825,25 +839,25 @@ public class IotController extends BaseController { properties.put("rating_switch" + i, defaultRatingSwitch); properties.put("localTimer_switch" + i, emptyTimerList); } - + // 如果使用溶解氧功能,设置盐度 if (bo.getIsOxygenUsed()) { properties.put("salinitySet", bo.getSalinityCompensation()); } - + String propertiesJson = cn.hutool.json.JSONUtil.toJsonStr(properties); log.info("准备设置设备属性,iotId: {}, properties: {}", iotId, propertiesJson); - + Map setResult = iotDeviceService.setDeviceProperty(iotId, propertiesJson); log.info("设置设备属性返回结果: {}", setResult); - + if (setResult == null) { log.error("设置设备属性返回null"); return R.fail("设置设备属性失败:返回结果为空"); } - + if (!Boolean.TRUE.equals(setResult.get("success"))) { - String errorMsg = setResult.get("errorMessage") != null ? + String errorMsg = setResult.get("errorMessage") != null ? setResult.get("errorMessage").toString() : "未知错误"; log.error("设置设备属性失败,错误信息: {}", errorMsg); return R.fail("设置设备属性失败: " + errorMsg); @@ -891,7 +905,7 @@ public class IotController extends BaseController { device.setInputVoltage(bo.getInputVoltage()); device.setVoltageWarnOpen(0); device.setIsOxygenUsed(bo.getIsOxygenUsed() ? 1 : 0); - + // 设置必填字段默认值(数据库非空约束) if (device.getValuePh() == null) { device.setValuePh(0.0); @@ -908,15 +922,15 @@ public class IotController extends BaseController { if (device.getPhaseDifference() == null) { device.setPhaseDifference(0.0); } - + if (bo.getIsOxygenUsed()) { device.setSalinityCompensation(bo.getSalinityCompensation()); device.setOxyWarnLower(bo.getOxyWarnLower()); device.setOxyWarnCallOpen(1); device.setOxyWarnCallNoDis(1); - + // 如果有塘口且开关列表包含0(设备自身),则设置设备的塘口ID - if (bo.getPondId() != null && bo.getPondId() > 0 + if (bo.getPondId() != null && bo.getPondId() > 0 && bo.getListPutPondPartId() != null && bo.getListPutPondPartId().contains(0)) { device.setPondId(bo.getPondId()); } else { @@ -925,7 +939,7 @@ public class IotController extends BaseController { } else { device.setPondId(null); } - + device.setTempWarnCallOpen(0); device.setTempWarnCallNoDis(0); @@ -942,13 +956,13 @@ public class IotController extends BaseController { // 初始化电流和电压默认值,避免数据库非空约束错误 deviceSwitch.setDetectElectricValue(0.0); deviceSwitch.setDetectVoltageValue(0.0); - + // 如果有塘口且开关列表包含当前索引,则设置开关的塘口ID - if (bo.getPondId() != null && bo.getPondId() > 0 + if (bo.getPondId() != null && bo.getPondId() > 0 && bo.getListPutPondPartId() != null && bo.getListPutPondPartId().contains(i)) { deviceSwitch.setPondId(bo.getPondId()); } - + switches.add(deviceSwitch); } @@ -956,15 +970,15 @@ public class IotController extends BaseController { int errorCode = 0; if (deviceProperties != null && Boolean.TRUE.equals(deviceProperties.get("success"))) { Object propData = deviceProperties.get("data"); - + if (propData != null) { try { java.lang.reflect.Method getListMethod = propData.getClass().getMethod("getList"); Object listObj = getListMethod.invoke(propData); - + if (listObj instanceof java.util.List) { java.util.List propList = (java.util.List) listObj; - + for (Object item : propList) { if (item != null) { try { @@ -972,7 +986,7 @@ public class IotController extends BaseController { java.lang.reflect.Method getValueMethod = item.getClass().getMethod("getValue"); String attribute = (String) getIdentifierMethod.invoke(item); Object value = getValueMethod.invoke(item); - + if (attribute != null && value != null) { switch (attribute) { case "dissolvedOxygen": // 溶解氧 @@ -1110,7 +1124,7 @@ public class IotController extends BaseController { // 清理JSON字符串 json = json.replace("\"{", "{").replace("}\"", "}").replace("\\", ""); Map content = cn.hutool.json.JSONUtil.toBean(json, Map.class); - + if (content.containsKey("voltage")) { deviceSwitch.setDetectVoltageValue(Double.parseDouble(content.get("voltage").toString())); } @@ -1273,16 +1287,16 @@ public class IotController extends BaseController { // 解析并设置设备属性 if (deviceProperties != null && Boolean.TRUE.equals(deviceProperties.get("success"))) { Object propData = deviceProperties.get("data"); - + if (propData != null) { // 通过反射获取属性列表 try { java.lang.reflect.Method getListMethod = propData.getClass().getMethod("getList"); Object listObj = getListMethod.invoke(propData); - + if (listObj instanceof java.util.List) { java.util.List propList = (java.util.List) listObj; - + for (Object item : propList) { if (item != null) { // 通过反射获取属性标识符和值 @@ -1291,7 +1305,7 @@ public class IotController extends BaseController { java.lang.reflect.Method getValueMethod = item.getClass().getMethod("getValue"); String attribute = (String) getIdentifierMethod.invoke(item); Object value = getValueMethod.invoke(item); - + if (attribute != null && value != null) { switch (attribute) { case "dissolvedOxygen": // 溶解氧 @@ -1377,4 +1391,889 @@ public class IotController extends BaseController { return R.fail("添加设备失败: " + e.getMessage()); } } + + @Operation(summary = "设备校准") + @PostMapping("/device/calibrate") + public R setCalibrate(@RequestBody DeviceCalibrateBo request) { + try { + // 获取当前登录用户ID + Long userId = LoginHelper.getUserId(); + if (userId == null) { + return R.fail("未登录或登录已过期"); + } + + // 验证校准码 + if (!"197346".equals(request.getCode())) { + return R.fail("校准码错误"); + } + + // 查询设备信息 + Device device = deviceMapper.selectOne( + new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper() + .eq(Device::getId, request.getId()) + .select(Device::getId, Device::getUserId, Device::getIotId, Device::getSerialNum, + Device::getDeviceName, Device::getDeviceType, Device::getIsOxygenUsed, + Device::getValueSaturability, Device::getWarnCode, Device::getDeadTime, + Device::getValueDissolvedOxygen, Device::getValueTemperature) + ); + + if (device == null || device.getUserId() == null || !device.getUserId().equals(userId)) { + return R.fail("设备不存在或无权限访问"); + } + + // 如果是控制器且未启用溶解氧功能 + if (device.getDeviceType() == 2 && (device.getIsOxygenUsed() == null || device.getIsOxygenUsed() == 0)) { + return R.fail("未启用溶解氧探头"); + } + + // 检查设备是否到期 + if (device.getDeadTime() != null && new Date().after(device.getDeadTime())) { + return R.fail("设备已到期"); + } + + // 查询设备状态 + if (iotDeviceService == null) { + return R.fail("飞燕平台配置未启用"); + } + + Map deviceDetail = iotDeviceService.queryDeviceInfo(device.getIotId()); + if (deviceDetail != null && deviceDetail.get("data") != null) { + try { + Object detailData = deviceDetail.get("data"); + java.lang.reflect.Method getStatusMethod = detailData.getClass().getMethod("getStatus"); + Object statusObj = getStatusMethod.invoke(detailData); + if (statusObj != null) { + String statusStr = statusObj.toString(); + if ("OFFLINE".equalsIgnoreCase(statusStr) || "offline".equals(statusStr)) { + return R.fail("设备离线或断电"); + } + } + } catch (Exception ex) { + log.warn("获取设备状态失败: {}", ex.getMessage()); + } + } + + // 设置校准属性 + Map properties = new java.util.HashMap<>(); + properties.put("correct", 1); + String propertiesJson = cn.hutool.json.JSONUtil.toJsonStr(properties); + + Map setResult = iotDeviceService.setDeviceProperty(device.getIotId(), propertiesJson); + if (setResult == null || !Boolean.TRUE.equals(setResult.get("success"))) { + return R.fail("校准失败,请重试"); + } + + // 更新设备警告码:清除未校准标记 + int warnCode = device.getWarnCode() != null ? device.getWarnCode() : 0; + if ((warnCode & 0x0001) != 0) { + warnCode &= ~0x0001; // 清除未校准标记 + } + + // 更新设备 + deviceMapper.update(null, + new com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper() + .eq(Device::getId, request.getId()) + .set(Device::getWarnCode, warnCode) + ); + + // 创建校准记录 + if (deviceCorrectRecordMapper != null) { + com.intc.fishery.domain.DeviceCorrectRecord correctRecord = new com.intc.fishery.domain.DeviceCorrectRecord(); + correctRecord.setDeviceId(device.getId()); + correctRecord.setUserId(userId); + correctRecord.setSerialNum(device.getSerialNum()); + correctRecord.setDeviceType(device.getDeviceType()); + correctRecord.setValueDissolvedOxygen(device.getValueDissolvedOxygen()); + correctRecord.setValueTemperature(device.getValueTemperature()); + correctRecord.setValueSaturability(device.getValueSaturability()); + deviceCorrectRecordMapper.insert(correctRecord); + } + + log.info("设备校准成功: userId={}, deviceId={}, deviceName={}, serialNum={}", + userId, device.getId(), device.getDeviceName(), device.getSerialNum()); + + return R.ok(); + } catch (Exception e) { + log.error("设备校准失败: {}", e.getMessage(), e); + return R.fail("设备校准失败: " + e.getMessage()); + } + } + + @Operation(summary = "设置盐度补偿") + @PostMapping("/device/salinity-compensation") + public R setSalinityCompensation(@RequestBody DeviceSalinityCompensationBo request) { + try { + // 获取当前登录用户ID + Long userId = LoginHelper.getUserId(); + if (userId == null) { + return R.fail("未登录或登录已过期"); + } + + // 查询设备信息 + Device device = deviceMapper.selectOne( + new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper() + .eq(Device::getId, request.getId()) + .select(Device::getId, Device::getUserId, Device::getIotId, Device::getSerialNum, + Device::getDeviceName, Device::getDeviceType, Device::getIsOxygenUsed, + Device::getWarnCode, Device::getDeadTime) + ); + + if (device == null || device.getUserId() == null || !device.getUserId().equals(userId)) { + return R.fail("设备不存在或无权限访问"); + } + + // 如果是控制器且未启用溶解氧功能 + if (device.getDeviceType() == 2 && (device.getIsOxygenUsed() == null || device.getIsOxygenUsed() == 0)) { + return R.fail("未启用溶解氧探头"); + } + + // 检查设备是否到期 + if (device.getDeadTime() != null && new Date().after(device.getDeadTime())) { + return R.fail("设备已到期"); + } + + // 查询设备状态 + if (iotDeviceService == null) { + return R.fail("飞燕平台配置未启用"); + } + + Map deviceDetail = iotDeviceService.queryDeviceInfo(device.getIotId()); + if (deviceDetail != null && deviceDetail.get("data") != null) { + try { + Object detailData = deviceDetail.get("data"); + java.lang.reflect.Method getStatusMethod = detailData.getClass().getMethod("getStatus"); + Object statusObj = getStatusMethod.invoke(detailData); + if (statusObj != null) { + String statusStr = statusObj.toString(); + if ("OFFLINE".equalsIgnoreCase(statusStr) || "offline".equals(statusStr)) { + return R.fail("设备离线或断电"); + } + } + } catch (Exception ex) { + log.warn("获取设备状态失败: {}", ex.getMessage()); + } + } + + // 设置盐度补偿属性 + Map properties = new java.util.HashMap<>(); + properties.put("salinitySet", request.getSalinityCompensation()); + String propertiesJson = cn.hutool.json.JSONUtil.toJsonStr(properties); + + Map setResult = iotDeviceService.setDeviceProperty(device.getIotId(), propertiesJson); + if (setResult == null || !Boolean.TRUE.equals(setResult.get("success"))) { + return R.fail("设置盐度补偿失败,请重试"); + } + + // 更新设备盐度补偿值 + deviceMapper.update(null, + new com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper() + .eq(Device::getId, request.getId()) + .set(Device::getSalinityCompensation, request.getSalinityCompensation()) + ); + + log.info("设置盐度补偿成功: userId={}, deviceId={}, deviceName={}, serialNum={}, salinityCompensation={}‰", + userId, device.getId(), device.getDeviceName(), device.getSerialNum(), request.getSalinityCompensation()); + + return R.ok(); + } catch (Exception e) { + log.error("设置盐度补償失败: {}", e.getMessage(), e); + return R.fail("设置盐度补償失败: " + e.getMessage()); + } + } + + /** + * 修改设备输入电压类型 + * + * @param request 请求对象(包含设备ID和电压类型) + * @return 操作结果 + */ + @Operation(summary = "修改设备输入电压类型") + @PutMapping("/device/voltage_type") + public R updateDeviceVoltageType( + @io.swagger.v3.oas.annotations.parameters.RequestBody(description = "设置电压类型请求对象") + @org.springframework.web.bind.annotation.RequestBody + @jakarta.validation.Valid DeviceVoltageTypeBo request) { + + try { + Long userId = LoginHelper.getUserId(); + log.info("修改设备输入电压类型请求: userId={}, deviceId={}, voltageType={}", + userId, request.getId(), request.getVoltageType()); + + // 查询设备信息 + Device device = deviceMapper.selectOne( + new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper() + .eq(Device::getId, request.getId()) + .select(Device::getId, Device::getIotId, Device::getUserId, + Device::getSerialNum, Device::getDeviceName, Device::getDeviceType, + Device::getInputVoltage, Device::getWarnCode, Device::getDeadTime) + ); + + // 验证设备存在性、权限和设备类型(必须是测控一体机) + if (device == null || device.getUserId() == null || !device.getUserId().equals(userId) + || device.getDeviceType() == null || device.getDeviceType() != 2) { + log.warn("设备不存在或无权限操作: userId={}, deviceId={}", userId, request.getId()); + return R.fail("设备不存在或无权限操作"); + } + + // 检查设备是否过期 + if (device.getDeadTime() != null && device.getDeadTime().before(new Date())) { + log.warn("设备服务已过期: deviceId={}, deadTime={}", device.getId(), device.getDeadTime()); + return R.fail("设备服务已过期"); + } + + // 检查电压类型是否需要更新 + if (device.getInputVoltage() != null && device.getInputVoltage().equals(request.getVoltageType())) { + log.info("电压类型未变化,无需更新: deviceId={}, voltageType={}", + device.getId(), request.getVoltageType()); + return R.ok(); + } + + // 检查设备是否在线(warnCode判断) + if (device.getWarnCode() == null || device.getWarnCode() < 0) { + log.warn("设备离线或已关机: deviceId={}, warnCode={}", device.getId(), device.getWarnCode()); + return R.fail("设备离线或已关机"); + } + + // 构建IoT属性字典 + Map properties = new java.util.HashMap<>(); + + // 获取电压属性值 + Map voltData = ControllerHelper.getInputVoltageProperty(request.getVoltageType()); + if (voltData == null) { + log.warn("电压参数错误: voltageType={}", request.getVoltageType()); + return R.fail("电压参数错误"); + } + properties.put("rated_voltage", voltData); + + // 如果是380V四相,需要设置每个开关的电压为380 + if (request.getVoltageType() == 4) { + for (int i = 1; i <= 4; i++) { + String propertyKey = "Switch" + i + "_volt"; + properties.put(propertyKey, 380); + } + } + + // 调用IoT云服务设置属性 + boolean result = iotCloudService.setProperty(device.getIotId(), properties, true, 2); + if (!result) { + log.error("设置IoT属性失败: deviceId=}, iotId={}", device.getId(), device.getIotId()); + return R.fail("设置电压失败"); + } + + // 更新设备的输入电压类型 + deviceMapper.update(null, + new com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper() + .eq(Device::getId, request.getId()) + .set(Device::getInputVoltage, request.getVoltageType()) + ); + + // 更新关联开关的接线方式 + deviceSwitchMapper.update(null, + new com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper() + .eq(com.intc.fishery.domain.DeviceSwitch::getDeviceId, request.getId()) + .set(com.intc.fishery.domain.DeviceSwitch::getConnectVoltageType, request.getVoltageType()) + ); + + String voltageDesc = ControllerHelper.getVoltageDescription(request.getVoltageType()); + log.info("修改设备输入电压类型成功: userId={}, deviceId={}, deviceName={}, serialNum={}, voltageType={}", + userId, device.getId(), device.getDeviceName(), device.getSerialNum(), voltageDesc); + + return R.ok(); + } catch (Exception e) { + log.error("修改设备输入电压类型失败: {}", e.getMessage(), e); + return R.fail("设置电压失败: " + e.getMessage()); + } + } + + /** + * 开关开启/关闭控制 + * + * @param request 开关控制请求对象 + * @return 操作结果 + */ + @Operation(summary = "开关开启/关闭控制") + @PutMapping("/switch/turn_switch") + public R turnSwitch(@RequestBody com.intc.fishery.domain.bo.ReqTurnOpen request) { + try { + if (deviceSwitchMapper == null || deviceMapper == null) { + return R.fail("系统配置未完成"); + } + + Long userId = LoginHelper.getUserId(); + Long switchId = request.getId(); + Integer targetStatus = request.getIsOpen(); + + // 查询开关信息 + com.intc.fishery.domain.DeviceSwitch deviceSwitch = deviceSwitchMapper.selectById(switchId); + 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::getIotId, Device::getSerialNum, + Device::getDeviceName, Device::getWarnCode, Device::getDeadTime) + ); + + if (device == null || device.getUserId() == null || !device.getUserId().equals(userId)) { + return R.fail("开关不存在或无权限访问"); + } + + // 检查设备是否到期 + if (device.getDeadTime() != null && new Date().after(device.getDeadTime())) { + return R.fail("设备服务已过期"); + } + + // 防抖处理:检查距离上次操作是否小于3秒 + if (deviceSwitch.getLastTurnTime() != null) { + long timeDiff = System.currentTimeMillis() - deviceSwitch.getLastTurnTime().getTime(); + if (timeDiff < 3000) { + return R.fail("操作过于频繁,请稍后再试"); + } + } + + // 如果开关状态已经是目标状态,无需操作 + if (deviceSwitch.getIsOpen() != null && deviceSwitch.getIsOpen().equals(targetStatus)) { + return R.ok(); + } + + // 检查设备是否离线(warnCode为99表示离线) + if (device.getWarnCode() == null || device.getWarnCode() == 99) { + return R.fail("设备离线或已关机"); + } + + // 检查IoT云服务是否可用 + if (iotCloudService == null) { + return R.fail("物联网服务未启用"); + } + + // 构造IoT属性 + Map properties = new java.util.HashMap<>(); + properties.put("switchIndex" + deviceSwitch.getIndex(), targetStatus); + + // 调用物联网服务设置属性 + boolean success = iotCloudService.setProperty(device.getIotId(), properties, true, 2); + if (!success) { + return R.fail("设置开关失败"); + } + + // 更新数据库中的开关状态和最后操作时间 + deviceSwitchMapper.update(null, + new com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper() + .eq(com.intc.fishery.domain.DeviceSwitch::getId, switchId) + .set(com.intc.fishery.domain.DeviceSwitch::getIsOpen, targetStatus) + .set(com.intc.fishery.domain.DeviceSwitch::getLastTurnTime, new Date()) + ); + + // 记录操作日志 + String operation = targetStatus == 1 ? "开启" : "关闭"; + log.info("开关操作:{}({})的开关{}:{}", + device.getDeviceName(), device.getSerialNum(), deviceSwitch.getSwitchName(), operation); + + return R.ok(); + } catch (Exception e) { + log.error("开关控制失败: {}", e.getMessage(), e); + return R.fail("开关控制失败: " + e.getMessage()); + } + } + + /** + * 设置塘口所有开关状态 + * + * @param request 开关状态请求对象(id为塘口ID) + * @return 操作结果 + */ + @Operation(summary = "设置塘口所有开关状态") + @PutMapping("/switch/turn_pond_switch") + public R turnPondSwitch(@RequestBody com.intc.fishery.domain.bo.ReqTurnOpen request) { + try { + if (deviceSwitchMapper == null || deviceMapper == null) { + return R.fail("系统配置未完成"); + } + + Long userId = LoginHelper.getUserId(); + Long pondId = request.getId(); + Integer targetStatus = request.getIsOpen(); + + // 查询塘口下所有开关信息(包括关联的设备信息) + com.github.yulichang.wrapper.MPJLambdaWrapper wrapper = + new com.github.yulichang.wrapper.MPJLambdaWrapper() + .selectAll(com.intc.fishery.domain.DeviceSwitch.class) + .selectAs(Device::getUserId, "userId") + .selectAs(Device::getIotId, "iotId") + .selectAs(Device::getSerialNum, "serialNum") + .selectAs(Device::getDeviceName, "deviceName") + .selectAs(Device::getWarnCode, "warnCode") + .selectAs(Device::getDeadTime, "deadTime") + .leftJoin(Device.class, Device::getId, com.intc.fishery.domain.DeviceSwitch::getDeviceId) + .eq(com.intc.fishery.domain.DeviceSwitch::getPondId, pondId); + + java.util.List listSwitch = + deviceSwitchMapper.selectJoinList(com.intc.fishery.domain.vo.DeviceSwitchVo.class, wrapper); + + if (listSwitch == null || listSwitch.isEmpty()) { + return R.fail("该塘口下没有开关"); + } + + // 按设备IotId分组,并过滤不符合条件的开关 + Map> iotIdSwitchMap = + new java.util.HashMap<>(); + + for (com.intc.fishery.domain.vo.DeviceSwitchVo switchVo : listSwitch) { + // 权限验证 + if (switchVo.getUserId() == null || !switchVo.getUserId().equals(userId)) { + return R.fail("开关不存在或无权限访问"); + } + + // 如果状态已经是目标状态,跳过 + if (switchVo.getIsOpen() != null && switchVo.getIsOpen().equals(targetStatus)) { + continue; + } + + // 如果设备已到期,跳过 + if (switchVo.getDeadTime() != null && new Date().after(switchVo.getDeadTime())) { + continue; + } + + // 如果设备离线,跳过 + if (switchVo.getWarnCode() == null || switchVo.getWarnCode() == 99) { + continue; + } + + // 防抖处理:检查距离上次操作是否小于1秒 + if (switchVo.getLastTurnTime() != null) { + long timeDiff = System.currentTimeMillis() - switchVo.getLastTurnTime().getTime(); + if (timeDiff < 1000) { + continue; + } + } + + // 按IotId分组 + String iotId = switchVo.getIotId(); + if (com.intc.common.core.utils.StringUtils.isBlank(iotId)) { + continue; + } + + iotIdSwitchMap.computeIfAbsent(iotId, k -> new java.util.ArrayList<>()).add(switchVo); + } + + // 检查IoT云服务是否可用 + if (iotCloudService == null) { + return R.fail("物联网服务未启用"); + } + + // 按设备批量设置开关状态 + java.util.Set successSwitchIds = new java.util.HashSet<>(); + java.util.List successSwitches = new java.util.ArrayList<>(); + + for (Map.Entry> entry : iotIdSwitchMap.entrySet()) { + String iotId = entry.getKey(); + java.util.List switches = entry.getValue(); + + // 构造该设备的所有开关属性 + Map properties = new java.util.HashMap<>(); + for (com.intc.fishery.domain.vo.DeviceSwitchVo switchVo : switches) { + properties.put("switchIndex" + switchVo.getIndex(), targetStatus); + } + + // 调用物联网服务设置属性 + boolean success = iotCloudService.setProperty(iotId, properties, false, 0); + if (success) { + // 记录成功的开关ID + for (com.intc.fishery.domain.vo.DeviceSwitchVo switchVo : switches) { + successSwitchIds.add(switchVo.getId()); + successSwitches.add(switchVo); + } + } + } + + // 批量更新数据库中成功设置的开关状态 + if (!successSwitchIds.isEmpty()) { + deviceSwitchMapper.update(null, + new com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper() + .in(com.intc.fishery.domain.DeviceSwitch::getId, successSwitchIds) + .set(com.intc.fishery.domain.DeviceSwitch::getIsOpen, targetStatus) + .set(com.intc.fishery.domain.DeviceSwitch::getLastTurnTime, new Date()) + ); + + // 记录操作日志 + String operation = targetStatus == 1 ? "开启" : "关闭"; + for (com.intc.fishery.domain.vo.DeviceSwitchVo switchVo : successSwitches) { + log.info("开关操作:{}({})的开关{}:{}", + switchVo.getDeviceName(), switchVo.getSerialNum(), switchVo.getSwitchName(), operation); + } + } + + return R.ok(); + } catch (Exception e) { + log.error("设置塘口开关失败: {}", e.getMessage(), e); + return R.fail("设置塘口开关失败: " + e.getMessage()); + } + } + + /** + * 设置开关额定电流 + * + * @param request 设置额定电流请求对象 + * @return 操作结果 + */ + @Operation(summary = "设置开关额定电流") + @PutMapping("/switch/electric_set") + public R updateElectricSet(@RequestBody com.intc.fishery.domain.bo.ReqSwitchElectricSet request) { + try { + if (deviceSwitchMapper == null || deviceMapper == null) { + return R.fail("系统配置未完成"); + } + + Long userId = LoginHelper.getUserId(); + Long switchId = request.getId(); + Double electric = request.getElectric(); + + // 查询开关信息 + com.intc.fishery.domain.DeviceSwitch deviceSwitch = deviceSwitchMapper.selectById(switchId); + 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::getIotId, Device::getSerialNum, + Device::getDeviceName, Device::getWarnCode, Device::getDeadTime) + ); + + // 权限验证 + if (device == null || device.getUserId() == null || !device.getUserId().equals(userId)) { + return R.fail("开关不存在或无权限访问"); + } + + // 检查设备是否到期 + if (device.getDeadTime() != null && new Date().after(device.getDeadTime())) { + return R.fail("设备服务已过期"); + } + + // 检查额定电流值是否已改变(精度0.001) + if (deviceSwitch.getRateElectricValue() != null + && Math.abs(deviceSwitch.getRateElectricValue() - electric) < 0.001) { + return R.ok(); + } + + // 检查设备是否离线(warnCode为99表示离线) + if (device.getWarnCode() == null || device.getWarnCode() == 99) { + return R.fail("设备离线或已关机"); + } + + // 检查IoT云服务是否可用 + if (iotCloudService == null) { + return R.fail("物联网服务未启用"); + } + + // 构造IoT属性:rating_switch{Index} + Map properties = new java.util.HashMap<>(); + properties.put("rating_switch" + deviceSwitch.getIndex(), electric); + + // 调用物联网服务设置属性 + boolean success = iotCloudService.setProperty(device.getIotId(), properties, true, 2); + if (!success) { + return R.fail("设置额定电流失败"); + } + + // 更新数据库中的额定电流值 + deviceSwitchMapper.update(null, + new com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper() + .eq(com.intc.fishery.domain.DeviceSwitch::getId, switchId) + .set(com.intc.fishery.domain.DeviceSwitch::getRateElectricValue, electric) + ); + + // 记录操作日志 + log.info("开关操作:{}({})的开关{},额定电流设置:{:00}A", + device.getDeviceName(), device.getSerialNum(), deviceSwitch.getSwitchName(), electric); + + return R.ok(); + } catch (Exception e) { + log.error("设置额定电流失败: {}", e.getMessage(), e); + return R.fail("设置额定电流失败: " + e.getMessage()); + } + } + + /** + * 修改开关输出电压类型 + * + * @param request 电压类型请求对象 + * @return 操作结果 + */ + @Operation(summary = "修改开关输出电压类型") + @PutMapping("/switch/update_voltage_type") + public R updateConnectVoltageType(@RequestBody com.intc.fishery.domain.bo.ReqSetVoltageType request) { + try { + if (deviceSwitchMapper == null || deviceMapper == null) { + return R.fail("系统配置未完成"); + } + + Long userId = LoginHelper.getUserId(); + Long switchId = request.getId(); + Integer voltageType = request.getVoltageType(); + + // 查询开关信息(包括关联的设备信息) + com.github.yulichang.wrapper.MPJLambdaWrapper wrapper = + new com.github.yulichang.wrapper.MPJLambdaWrapper() + .selectAll(com.intc.fishery.domain.DeviceSwitch.class) + .selectAs(Device::getUserId, "userId") + .selectAs(Device::getIotId, "iotId") + .selectAs(Device::getSerialNum, "serialNum") + .selectAs(Device::getDeviceName, "deviceName") + .selectAs(Device::getDeviceType, "deviceType") + .selectAs(Device::getInputVoltage, "inputVoltage") + .selectAs(Device::getWarnCode, "warnCode") + .selectAs(Device::getDeadTime, "deadTime") + .leftJoin(Device.class, Device::getId, com.intc.fishery.domain.DeviceSwitch::getDeviceId) + .eq(com.intc.fishery.domain.DeviceSwitch::getId, switchId); + + com.intc.fishery.domain.vo.DeviceSwitchVo switchVo = + deviceSwitchMapper.selectJoinOne(com.intc.fishery.domain.vo.DeviceSwitchVo.class, wrapper); + + if (switchVo == null) { + return R.fail("开关不存在"); + } + + // 权限验证:检查设备类型为控制器(deviceType=2)且属于当前用户 + if (switchVo.getUserId() == null || !switchVo.getUserId().equals(userId)) { + return R.fail("开关不存在或无权限访问"); + } + + if (switchVo.getDeviceType() == null || switchVo.getDeviceType() != 2) { + return R.fail("仅测控一体机支持该操作"); + } + + // 检查设备是否到期 + if (switchVo.getDeadTime() != null && new Date().after(switchVo.getDeadTime())) { + return R.fail("设备服务已过期"); + } + + // 检查电压类型是否已改变 + if (switchVo.getConnectVoltageType() != null && switchVo.getConnectVoltageType().equals(voltageType)) { + return R.ok(); + } + + // 检查设备是否离线 + if (switchVo.getWarnCode() == null || switchVo.getWarnCode() == 99) { + return R.fail("设备离线或已关机"); + } + + // 检查设备输入电压是否支持设置(必须是三相380V四线,inputVoltage >= 4) + if (switchVo.getInputVoltage() == null || switchVo.getInputVoltage() < 4) { + return R.fail("不可设置"); + } + + // 检查目标电压类型是否合法(1-单相220V,4-三相380V四线) + if (voltageType != 1 && voltageType != 4) { + return R.fail("不可设置"); + } + + // 检查IoT云服务是否可用 + if (iotCloudService == null) { + return R.fail("物联网服务未启用"); + } + + // 构造IoT属性:Switch{Index}_volt + String propertyKey = "Switch" + switchVo.getIndex() + "_volt"; + int voltage = voltageType == 1 ? 220 : 380; + Map properties = new java.util.HashMap<>(); + properties.put(propertyKey, voltage); + + // 调用物联网服务设置属性 + boolean success = iotCloudService.setProperty(switchVo.getIotId(), properties, true, 2); + if (!success) { + return R.fail("设置电压失败"); + } + + // 更新数据库中的电压类型 + deviceSwitchMapper.update(null, + new com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper() + .eq(com.intc.fishery.domain.DeviceSwitch::getId, switchId) + .set(com.intc.fishery.domain.DeviceSwitch::getConnectVoltageType, voltageType) + ); + + // 记录操作日志 + String voltageMsg = getVoltageTypeDesc(voltageType); + log.info("开关操作:{}({})的开关{},修改输出电压接线方式为{}", + switchVo.getDeviceName(), switchVo.getSerialNum(), switchVo.getSwitchName(), voltageMsg); + + return R.ok(); + } catch (Exception e) { + log.error("修改电压类型失败: {}", e.getMessage(), e); + return R.fail("修改电压类型失败: " + e.getMessage()); + } + } + + /** + * 获取电压类型描述 + * + * @param voltageType 电压类型 + * @return 电压类型描述 + */ + private String getVoltageTypeDesc(Integer voltageType) { + if (voltageType == null) { + return "未知"; + } + switch (voltageType) { + case 1: + return "单相220V"; + case 2: + return "单相220V三线"; + case 3: + return "三相380V三线"; + case 4: + return "三相380V四线"; + default: + return "未知"; + } + } + + /** + * 修改开关所属塘口 + * + * @param request 修改塘口请求对象 + * @return 操作结果 + */ + @Operation(summary = "修改开关所属塘口") + @PutMapping("/switch/update_pond") + public R updateSwitchPond(@RequestBody com.intc.fishery.domain.bo.ReqChangePond request) { + try { + if (deviceSwitchMapper == null || pondMapper == null || deviceMapper == null) { + return R.fail("系统配置未完成"); + } + + Long userId = LoginHelper.getUserId(); + Long switchId = request.getId(); + Long pondId = request.getPondId(); + + // 查询开关信息,包括关联的设备和塘口信息 + com.github.yulichang.wrapper.MPJLambdaWrapper wrapper = + new com.github.yulichang.wrapper.MPJLambdaWrapper() + .selectAll(com.intc.fishery.domain.DeviceSwitch.class) + .selectAs(Device::getDeviceName, "deviceName") + .selectAs(Device::getSerialNum, "serialNum") + .selectAs(Device::getIotId, "iotId") + .selectAs(Device::getWarnCode, "warnCode") + .selectAs(com.intc.fishery.domain.Pond::getPondName, "pondName") + .leftJoin(Device.class, Device::getId, com.intc.fishery.domain.DeviceSwitch::getDeviceId) + .leftJoin(com.intc.fishery.domain.Pond.class, com.intc.fishery.domain.Pond::getId, com.intc.fishery.domain.DeviceSwitch::getPondId) + .eq(com.intc.fishery.domain.DeviceSwitch::getId, switchId); + + com.intc.fishery.domain.vo.DeviceSwitchVo vo = deviceSwitchMapper.selectJoinOne( + com.intc.fishery.domain.vo.DeviceSwitchVo.class, wrapper); + + if (vo == null) { + return R.fail("开关不存在"); + } + + Long oldPondId = vo.getPondId(); + String oldPondName = vo.getPondName(); + + // 如果已有塘口且与新塘口相同,无需处理 + if (oldPondId != null && oldPondId.equals(pondId)) { + return R.ok(); + } + + // 如果开关原本有塘口,需要清理定时控制 + if (oldPondId != null) { + // 查询设备的IotId + Device device = deviceMapper.selectById(vo.getDeviceId()); + if (device != null && com.intc.common.core.utils.StringUtils.isNotBlank(device.getIotId())) { + // 检查设备是否在线(通过判断warnCode判断设备状态) + boolean isOnline = device.getWarnCode() != null && device.getWarnCode() != 99; + + if (isOnline && iotCloudService != null) { + try { + // 构造清空定时控制的属性 + Map properties = new java.util.HashMap<>(); + // 清空定时器:localTimer_switch{Index} + properties.put("localTimer_switch" + vo.getIndex(), java.util.Collections.emptyList()); + + // 如果开关是打开状态,需要关闭它 + if (vo.getIsOpen() != null && vo.getIsOpen() == 1) { + // 关闭开关:switchIndex{Index} = 0 + properties.put("switchIndex" + vo.getIndex(), 0); + + // 更新数据库中的开关状态 + deviceSwitchMapper.update(null, + new com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper() + .eq(com.intc.fishery.domain.DeviceSwitch::getId, switchId) + .set(com.intc.fishery.domain.DeviceSwitch::getIsOpen, 0) + ); + } + + // 调用物联网服务设置属性 + boolean success = iotCloudService.setProperty(device.getIotId(), properties, false, 0); + + if (success) { + log.info("成功通过物联网服务清空开关定时控制和关闭开关, switchId={}", switchId); + } else { + log.warn("通过物联网服务清空开关定时控制失败, switchId={}", switchId); + } + } catch (Exception e) { + log.error("调用物联网服务失败, switchId={}", switchId, e); + } + } + } + + // 删除该开关的所有定时控制记录 + if (timingCtrlMapper != null) { + timingCtrlMapper.delete( + new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper() + .eq(com.intc.fishery.domain.TimingCtrl::getSwitchId, switchId) + ); + } + + log.info("已清除开关ID={}的定时控制数据", switchId); + } + + // 处理新塘口分配 + if (pondId != null && pondId > 0) { + // 验证塘口是否存在 + com.intc.fishery.domain.Pond pond = pondMapper.selectById(pondId); + if (pond == null) { + return R.fail("塘口不存在"); + } + + // 更新开关的塘口ID,并清空联动控制ID + deviceSwitchMapper.update(null, + new com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper() + .eq(com.intc.fishery.domain.DeviceSwitch::getId, switchId) + .set(com.intc.fishery.domain.DeviceSwitch::getPondId, pondId) + .set(com.intc.fishery.domain.DeviceSwitch::getLinkedCtrlId, null) + ); + + // 记录操作日志 + if (oldPondId != null) { + log.info("开关操作:{}({})的开关{},转移到塘口:{}", + vo.getDeviceName(), vo.getSerialNum(), vo.getSwitchName(), pond.getPondName()); + } else { + log.info("开关操作:{}({})的开关{},分配到塘口:{}", + vo.getDeviceName(), vo.getSerialNum(), vo.getSwitchName(), pond.getPondName()); + } + } else if (oldPondId != null) { + // 移除塘口分配 + deviceSwitchMapper.update(null, + new com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper() + .eq(com.intc.fishery.domain.DeviceSwitch::getId, switchId) + .set(com.intc.fishery.domain.DeviceSwitch::getPondId, null) + .set(com.intc.fishery.domain.DeviceSwitch::getLinkedCtrlId, null) + ); + + // 记录操作日志 + log.info("开关操作:{}({})的开关{} 从 {} 移除", + vo.getDeviceName(), vo.getSerialNum(), vo.getSwitchName(), oldPondName); + } + + return R.ok(); + } catch (Exception e) { + log.error("修改开关塘口失败: {}", e.getMessage(), e); + return R.fail("修改失败: " + e.getMessage()); + } + } } diff --git a/intc-modules/intc-iot/src/main/java/com/intc/iot/domain/bo/DeviceCalibrateBo.java b/intc-modules/intc-iot/src/main/java/com/intc/iot/domain/bo/DeviceCalibrateBo.java new file mode 100644 index 0000000..b31e55c --- /dev/null +++ b/intc-modules/intc-iot/src/main/java/com/intc/iot/domain/bo/DeviceCalibrateBo.java @@ -0,0 +1,36 @@ +package com.intc.iot.domain.bo; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 设备校准请求业务对象 + * + * @author intc + */ +@Data +@Schema(description = "设备校准请求对象") +public class DeviceCalibrateBo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 设备ID + */ + @Schema(description = "设备ID") + @NotNull(message = "设备ID不能为空") + private Long id; + + /** + * 校准码 + */ + @Schema(description = "校准码") + @NotBlank(message = "校准码不能为空") + private String code; +} diff --git a/intc-modules/intc-iot/src/main/java/com/intc/iot/domain/bo/DeviceSalinityCompensationBo.java b/intc-modules/intc-iot/src/main/java/com/intc/iot/domain/bo/DeviceSalinityCompensationBo.java new file mode 100644 index 0000000..d1f7a2f --- /dev/null +++ b/intc-modules/intc-iot/src/main/java/com/intc/iot/domain/bo/DeviceSalinityCompensationBo.java @@ -0,0 +1,35 @@ +package com.intc.iot.domain.bo; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 设备盐度补偿请求业务对象 + * + * @author intc + */ +@Data +@Schema(description = "设备盐度补偿请求对象") +public class DeviceSalinityCompensationBo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 设备ID + */ + @Schema(description = "设备ID") + @NotNull(message = "设备ID不能为空") + private Long id; + + /** + * 盐度补偿值 + */ + @Schema(description = "盐度补偿值") + @NotNull(message = "盐度补偿值不能为空") + private Double salinityCompensation; +} diff --git a/intc-modules/intc-iot/src/main/java/com/intc/iot/domain/bo/DeviceVoltageTypeBo.java b/intc-modules/intc-iot/src/main/java/com/intc/iot/domain/bo/DeviceVoltageTypeBo.java new file mode 100644 index 0000000..052b778 --- /dev/null +++ b/intc-modules/intc-iot/src/main/java/com/intc/iot/domain/bo/DeviceVoltageTypeBo.java @@ -0,0 +1,35 @@ +package com.intc.iot.domain.bo; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 设置设备输入电压类型请求对象 + * + * @author intc + */ +@Data +@Schema(description = "设置设备输入电压类型请求对象") +public class DeviceVoltageTypeBo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 设备ID + */ + @Schema(description = "设备ID") + @NotNull(message = "设备ID不能为空") + private Long id; + + /** + * 电压类型(1-220V单相,2-220V两相,3-380V三相,4-380V四相) + */ + @Schema(description = "电压类型(1-220V单相,2-220V两相,3-380V三相,4-380V四相)") + @NotNull(message = "电压类型不能为空") + private Integer voltageType; +} diff --git a/intc-modules/intc-iot/src/main/java/com/intc/iot/service/IotDeviceService.java b/intc-modules/intc-iot/src/main/java/com/intc/iot/service/IotDeviceService.java index 7a93d4a..c121479 100644 --- a/intc-modules/intc-iot/src/main/java/com/intc/iot/service/IotDeviceService.java +++ b/intc-modules/intc-iot/src/main/java/com/intc/iot/service/IotDeviceService.java @@ -88,4 +88,23 @@ public interface IotDeviceService { */ Map queryThingModel(String productKey) throws Exception; + /** + * 设置设备校准 + * + * @param iotId 设备ID + * @return 是否成功 + * @throws Exception 异常 + */ + boolean calibrateDevice(String iotId) throws Exception; + + /** + * 设置设备盐度补偿 + * + * @param iotId 设备ID + * @param salinityCompensation 盐度补偿值 + * @return 是否成功 + * @throws Exception 异常 + */ + boolean setSalinityCompensation(String iotId, Double salinityCompensation) throws Exception; + } diff --git a/intc-modules/intc-iot/src/main/java/com/intc/iot/service/impl/IotDeviceServiceImpl.java b/intc-modules/intc-iot/src/main/java/com/intc/iot/service/impl/IotDeviceServiceImpl.java index 1726e76..0d04218 100644 --- a/intc-modules/intc-iot/src/main/java/com/intc/iot/service/impl/IotDeviceServiceImpl.java +++ b/intc-modules/intc-iot/src/main/java/com/intc/iot/service/impl/IotDeviceServiceImpl.java @@ -191,4 +191,44 @@ public class IotDeviceServiceImpl implements IotDeviceService { return result; } + @Override + public boolean calibrateDevice(String iotId) throws Exception { + log.info("设置设备校准,IotId: {}", iotId); + + // 构造属性JSON:{"correct": 1} + String properties = "{\"correct\": 1}"; + + SetDevicePropertyRequest request = new SetDevicePropertyRequest(); + request.setIotId(iotId); + request.setItems(properties); + + SetDevicePropertyResponse response = acsClient.getAcsResponse(request); + + if (!response.getSuccess()) { + log.error("设置设备校准失败,Code: {}, ErrorMessage: {}", response.getCode(), response.getErrorMessage()); + } + + return response.getSuccess(); + } + + @Override + public boolean setSalinityCompensation(String iotId, Double salinityCompensation) throws Exception { + log.info("设置设备盐度补偿,IotId: {}, SalinityCompensation: {}", iotId, salinityCompensation); + + // 构造属性JSON:{"salinitySet": value} + String properties = String.format("{\"salinitySet\": %s}", salinityCompensation); + + SetDevicePropertyRequest request = new SetDevicePropertyRequest(); + request.setIotId(iotId); + request.setItems(properties); + + SetDevicePropertyResponse response = acsClient.getAcsResponse(request); + + if (!response.getSuccess()) { + log.error("设置设备盐度补偿失败,Code: {}, ErrorMessage: {}", response.getCode(), response.getErrorMessage()); + } + + return response.getSuccess(); + } + } diff --git a/intc-modules/intc-iot/src/main/java/com/intc/iot/utils/ControllerHelper.java b/intc-modules/intc-iot/src/main/java/com/intc/iot/utils/ControllerHelper.java new file mode 100644 index 0000000..5021595 --- /dev/null +++ b/intc-modules/intc-iot/src/main/java/com/intc/iot/utils/ControllerHelper.java @@ -0,0 +1,73 @@ +package com.intc.iot.utils; + +import java.util.HashMap; +import java.util.Map; + +/** + * 设备控制辅助工具类 + * + * @author intc + */ +public class ControllerHelper { + + /** + * 获取输入电压属性值 + * 根据电压类型返回对应的IoT平台属性值 + * 注意:rated_voltage是结构体类型,尝试只使用phase字段 + * + * @param voltageType 电压类型(1-220V单相,2-220V两相,3-380V三相,4-380V四相) + * @return IoT平台属性值的Map + */ + public static Map getInputVoltageProperty(Integer voltageType) { + if (voltageType == null) { + return null; + } + + Map result = new HashMap<>(); + + // 尝试只设置phase字段 + switch (voltageType) { + case 1: // 220V单相 + result.put("phase", "1"); + break; + case 2: // 220V两相 + result.put("phase", "2"); + break; + case 3: // 380V三相 + result.put("phase", "3"); + break; + case 4: // 380V四相 + result.put("phase", "4"); + break; + default: + return null; + } + + return result; + } + + /** + * 获取电压类型的中文描述 + * + * @param voltageType 电压类型 + * @return 中文描述 + */ + public static String getVoltageDescription(Integer voltageType) { + if (voltageType == null) { + return "未知"; + } + + switch (voltageType) { + case 1: + return "220V单相"; + case 2: + return "220V两相"; + case 3: + return "380V三相"; + case 4: + return "380V四相"; + default: + return "未知类型"; + } + } +} diff --git a/intc-modules/intc-system/src/main/java/com/intc/system/controller/system/SysNoticeController.java b/intc-modules/intc-system/src/main/java/com/intc/system/controller/system/SysNoticeController.java index 2885d19..4c71d26 100644 --- a/intc-modules/intc-system/src/main/java/com/intc/system/controller/system/SysNoticeController.java +++ b/intc-modules/intc-system/src/main/java/com/intc/system/controller/system/SysNoticeController.java @@ -34,7 +34,7 @@ public class SysNoticeController extends BaseController { /** * 获取通知公告列表 */ - @SaCheckPermission("system:notice:list") +// @SaCheckPermission("system:notice:list") @GetMapping("/list") public TableDataInfo list(SysNoticeBo notice, PageQuery pageQuery) { return noticeService.selectPageNoticeList(notice, pageQuery);