fix: 微信小程序接口对接修改,联调测试问题修复。

This commit is contained in:
tianyongbao
2026-01-14 08:31:55 +08:00
parent a41248e405
commit 15af17fb95
44 changed files with 3887 additions and 52 deletions

View File

@@ -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");

View File

@@ -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<Long> {
@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);
}
}
}

View File

@@ -51,6 +51,11 @@ public class DefineDeviceWarnCode {
*/
public static final int DeviceDead = 64;
/**
* 溶解氧探头未校准
*/
public static final int OxyDetectorNoCorrect = 1;
private DefineDeviceWarnCode() {
}

View File

@@ -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<DeviceVo> 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<PublicDeviceBaseData> 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<DeviceSwitch> switches = deviceSwitchMapper.selectList(
new LambdaQueryWrapper<DeviceSwitch>()
.eq(DeviceSwitch::getDeviceId, device.getId())
.orderByAsc(DeviceSwitch::getIndex)
);
// 收集开关关联的塘口ID
List<Long> switchPondIds = switches.stream()
.map(DeviceSwitch::getPondId)
.filter(id -> id != null && id > 0)
.distinct()
.collect(Collectors.toList());
// 批量查询塘口
Map<Long, Pond> pondMap = Map.of();
if (!switchPondIds.isEmpty()) {
List<Pond> ponds = pondMapper.selectBatchIds(switchPondIds);
pondMap = ponds.stream()
.collect(Collectors.toMap(Pond::getId, Function.identity()));
}
Map<Long, Pond> finalPondMap = pondMap;
List<PublicDeviceSwitchSimpleVo> 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<LinkedCtrl> linkedCtrls = linkedCtrlMapper.selectList(
new LambdaQueryWrapper<LinkedCtrl>()
.eq(LinkedCtrl::getDeviceId, device.getId())
);
if (linkedCtrls != null && !linkedCtrls.isEmpty()) {
// 收集所有联动控制ID
List<Long> linkedCtrlIds = linkedCtrls.stream()
.map(LinkedCtrl::getId)
.collect(Collectors.toList());
// 查询关联的开关
List<DeviceSwitch> linkedSwitches = deviceSwitchMapper.selectList(
new LambdaQueryWrapper<DeviceSwitch>()
.in(DeviceSwitch::getLinkedCtrlId, linkedCtrlIds)
);
// 按联动控制ID分组开关
Map<Long, List<DeviceSwitch>> switchesByLinkedCtrl = linkedSwitches.stream()
.collect(Collectors.groupingBy(DeviceSwitch::getLinkedCtrlId));
// 构建联动控制VO列表
List<PublicDeviceLinkedCtrl> 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<DeviceSwitch> switches = switchesByLinkedCtrl.getOrDefault(lc.getId(), List.of());
List<PublicDeviceSwitchSimpleVo> 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<Void> setOxyWarnCall(
@RequestParam("rootUserId") Long rootUserId,
@Validated @RequestBody ReqSetOxyTempWarnCall request) {
// 查询设备信息
Device device = deviceMapper.selectOne(
new LambdaQueryWrapper<Device>()
.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<Device>()
.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<Device>()
.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<Void> setOxyWarnValue(
@RequestParam("rootUserId") Long rootUserId,
@Validated @RequestBody ReqSetOxyWarnValue request) {
// 查询设备信息
Device device = deviceMapper.selectOne(
new LambdaQueryWrapper<Device>()
.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<Device>()
.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<Void> setTempWarnValue(
@RequestParam("rootUserId") Long rootUserId,
@Validated @RequestBody ReqSetTempWarnValue request) {
// 验证温度上下限
if (request.getTempWarnUpper() <= request.getTempWarnLower()) {
return R.fail("温度上限必须高于下限值");
}
// 查询设备信息
Device device = deviceMapper.selectOne(
new LambdaQueryWrapper<Device>()
.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<Device>()
.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<Double> getSaturability(
@Validated @RequestBody ReqId request) {
// 查询设备的饱和度值
Device device = deviceMapper.selectOne(
new LambdaQueryWrapper<Device>()
.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<Void> changeDeviceName(
@RequestParam("rootUserId") Long rootUserId,
@Validated @RequestBody ReqChangeName request) {
// 查询设备的用户ID
Device device = deviceMapper.selectOne(
new LambdaQueryWrapper<Device>()
.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<Device>()
.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<List<PublicDeviceDeadInfo>> 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<Device> devices = deviceMapper.selectList(
new LambdaQueryWrapper<Device>()
.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<PublicDeviceDeadInfo> 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<Void> breakPond(
@RequestParam("rootUserId") Long rootUserId,
@Validated @RequestBody ReqId request) {
// 查询设备信息,包含塘口关联
Device device = deviceMapper.selectOne(
new LambdaQueryWrapper<Device>()
.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<Pond>()
.eq(Pond::getId, device.getPondId())
.select(Pond::getId, Pond::getPondName)
);
String pondName = pond != null ? pond.getPondName() : "未知塘口";
// 删除设备关联的所有联动控制
linkedCtrlMapper.delete(
new LambdaQueryWrapper<LinkedCtrl>()
.eq(LinkedCtrl::getDeviceId, device.getId())
);
// 更新设备:移除塘口关联,清除告警状态
boolean updated = deviceMapper.update(null,
new LambdaUpdateWrapper<Device>()
.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<Void> updateDetectVoltageOpen(
@RequestParam("rootUserId") Long rootUserId,
@Validated @RequestBody ReqTurnOpen request) {
// 查询设备信息
Device device = deviceMapper.selectOne(
new LambdaQueryWrapper<Device>()
.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<Device>()
.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<PublicDeviceBaseData> setControllerOxyOpen(
@RequestParam("rootUserId") Long rootUserId,
@Validated @RequestBody ReqTurnOpen request) {
// 查询设备基本信息
Device device = deviceMapper.selectOne(
new LambdaQueryWrapper<Device>()
.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<Device>()
.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<Device>()
.eq(Device::getId, request.getId())
.set(Device::getIsOxygenUsed, 0)
);
// 删除设备的所有联动控制
linkedCtrlMapper.delete(
new LambdaQueryWrapper<LinkedCtrl>()
.eq(LinkedCtrl::getDeviceId, request.getId())
);
}
// 查询更新后的设备完整信息
ReqId reqId = new ReqId();
reqId.setId(request.getId());
R<PublicDeviceBaseData> 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);
}
}

View File

@@ -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<DeviceSwitchVo> 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<List<PublicDeviceSimpleDto>> 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<PublicDeviceSwitchBaseData> 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<Void> 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<Void> updateDetectElectricOpen(@Validated @RequestBody com.intc.fishery.domain.bo.ReqTurnOpen request) {
deviceSwitchService.updateElectricWarnOpen(request.getId(), request.getIsOpen());
return R.ok();
}
}

View File

@@ -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<LinkedCtrlVo> 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<LinkedCtrlVo> 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<Void> 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<Void> addLinkedCtrl(@Validated @RequestBody ReqAddLinkedCtrl request) {
linkedCtrlService.addLinkedCtrl(request);
return R.ok();
}
/**
* 修改联动控制
*
* @param request 修改联动控制请求
* @return 操作结果
*/
@Log(title = "溶解氧联动控制", businessType = BusinessType.UPDATE)
@RepeatSubmit()
@PutMapping("/update")
public R<Void> updateLinkedCtrl(@Validated @RequestBody ReqUpdateLinkedCtrl request) {
linkedCtrlService.updateLinkedCtrl(request);
return R.ok();
}
/**
* 根据设备ID查询所有联动控制
*
* @param request 设备ID请求对象
* @return 联动控制列表
*/
// @SaCheckPermission("fishery:linkedCtrl:query")
@PostMapping("/fetch")
public R<List<PublicLinkedCtrl>> 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<Void> setLinkedOpen(@Validated @RequestBody ReqLinkedCtrlOpen request) {
linkedCtrlService.setLinkedOpen(request);
return R.ok();
}
}

View File

@@ -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<List<PublicPondIdNameVo>> getPondList2(
@RequestParam("rootUserId") Long rootUserId) {
// 查询用户的所有塘口只获取ID和名称
List<Pond> ponds = pondMapper.selectList(
new LambdaQueryWrapper<Pond>()
.eq(Pond::getUserId, rootUserId)
.select(Pond::getId, Pond::getPondName)
.orderByDesc(Pond::getCreateTime)
);
// 转换为VO列表
List<PublicPondIdNameVo> 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<Void> changePond(
@RequestParam("rootUserId") Long rootUserId,
@Validated @RequestBody ReqChangePond request) {
// 查询设备信息
Device device = deviceMapper.selectOne(
new LambdaQueryWrapper<Device>()
.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<LinkedCtrl>()
.eq(LinkedCtrl::getDeviceId, request.getId())
);
}
// 情凵1分配或转移到新塘口
if (newPondId != null) {
// 验证塘口存在性和权限
Pond pond = pondMapper.selectOne(
new LambdaQueryWrapper<Pond>()
.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<Device>()
.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<Device>()
.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();
}
}

View File

@@ -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<Long> listSwitchId;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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-单相220V4-三相380V四线
*/
@NotNull(message = "电压类型不能为空")
private Integer voltageType;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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<Long> listSwitchId;
}

View File

@@ -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<PublicDeviceSwitchSimpleDto> listSwitch;
/**
* 设备告警状态码
*/
private Integer warnCode;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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<PublicDeviceSwitchSimple> listSwitch;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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<PublicDeviceSwitchSimpleVo> listSwitch;
/**
* 联动控制列表
*/
private List<PublicDeviceLinkedCtrl> listLinkedCtr;
}

View File

@@ -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;
}

View File

@@ -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<PublicDeviceSwitchSimpleVo> listSwitch;
}

View File

@@ -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<Long> ids, Boolean isValid);
/**
* 根据塘口ID查询塘口下的设备及其开关信息
*
* @param pondId 塘口ID
* @return 设备及开关列表
*/
List<PublicDeviceSimpleDto> 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);
}

View File

@@ -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<Long> ids, Boolean isValid);
/**
* 新增联动控制(业务方法)
*
* @param request 新增联动控制请求
*/
void addLinkedCtrl(ReqAddLinkedCtrl request);
/**
* 修改联动控制(业务方法)
*
* @param request 修改联动控制请求
*/
void updateLinkedCtrl(ReqUpdateLinkedCtrl request);
/**
* 根据设备ID查询所有联动控制
*
* @param deviceId 设备ID
* @return 联动控制列表
*/
List<PublicLinkedCtrl> queryAllLinkedCtrl(Long deviceId);
/**
* 设置联动控制开关
*
* @param request 设置开关请求
*/
void setLinkedOpen(ReqLinkedCtrlOpen request);
}

View File

@@ -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<PublicDeviceSimpleDto> getPondSwitch(Long pondId) {
// 使用连表查询获取该塘口下的所有开关及其关联的设备信息
MPJLambdaWrapper<DeviceSwitch> wrapper = new MPJLambdaWrapper<DeviceSwitch>()
.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<DeviceSwitchVo> switchList = baseMapper.selectJoinList(DeviceSwitchVo.class, wrapper);
// 过滤掉已过期的设备,并按设备分组
Map<Long, List<DeviceSwitchVo>> deviceSwitchMap = switchList.stream()
.filter(vo -> !isDeviceDead(vo))
.collect(Collectors.groupingBy(DeviceSwitchVo::getDeviceId));
// 构建返回结果
List<PublicDeviceSimpleDto> result = new ArrayList<>();
for (Map.Entry<Long, List<DeviceSwitchVo>> entry : deviceSwitchMap.entrySet()) {
List<DeviceSwitchVo> 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); // 测控一体机设备类型设为1Controller
deviceDto.setWarnCode(firstSwitch.getWarnCode());
// 转换开关列表
List<PublicDeviceSwitchSimpleDto> 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<DeviceSwitch> wrapper = new MPJLambdaWrapper<DeviceSwitch>()
.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<DeviceSwitch> wrapper = new MPJLambdaWrapper<DeviceSwitch>()
.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<DeviceSwitch> wrapper = new MPJLambdaWrapper<DeviceSwitch>()
.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);
}
}

View File

@@ -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<Long> 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<LinkedCtrl> wrapper = new MPJLambdaWrapper<LinkedCtrl>()
.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<DeviceSwitch> currentSwitches = deviceSwitchMapper.selectList(
Wrappers.lambdaQuery(DeviceSwitch.class)
.eq(DeviceSwitch::getLinkedCtrlId, request.getId())
.select(DeviceSwitch::getId));
List<Long> currentSwitchIds = currentSwitches.stream()
.map(DeviceSwitch::getId)
.collect(Collectors.toList());
// 找出需要移除绑定的开关(在旧列表中但不在新列表中)
List<Long> 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<PublicLinkedCtrl> queryAllLinkedCtrl(Long deviceId) {
// 1. 查询该设备下的所有联动控制
List<LinkedCtrl> linkedCtrls = baseMapper.selectList(
Wrappers.lambdaQuery(LinkedCtrl.class)
.eq(LinkedCtrl::getDeviceId, deviceId)
.orderByAsc(LinkedCtrl::getId));
if (linkedCtrls.isEmpty()) {
return new ArrayList<>();
}
// 2. 收集所有联动控制ID
List<Long> linkedCtrlIds = linkedCtrls.stream()
.map(LinkedCtrl::getId)
.collect(Collectors.toList());
// 3. 查询与这些联动控制相关的开关
List<DeviceSwitch> switches = deviceSwitchMapper.selectList(
Wrappers.lambdaQuery(DeviceSwitch.class)
.in(DeviceSwitch::getLinkedCtrlId, linkedCtrlIds)
.orderByAsc(DeviceSwitch::getIndex));
// 4. 按linkedCtrlId分组开关
Map<Long, List<DeviceSwitch>> switchMap = switches.stream()
.collect(Collectors.groupingBy(DeviceSwitch::getLinkedCtrlId));
// 5. 构建返回结果
List<PublicLinkedCtrl> 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<DeviceSwitch> linkedSwitches = switchMap.getOrDefault(linkedCtrl.getId(), new ArrayList<>());
List<PublicDeviceSwitchSimple> 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<LinkedCtrl> wrapper = new MPJLambdaWrapper<LinkedCtrl>()
.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("无效的开关类型");
}
}
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -88,4 +88,23 @@ public interface IotDeviceService {
*/
Map<String, Object> 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;
}

View File

@@ -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();
}
}

View File

@@ -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<String, Object> getInputVoltageProperty(Integer voltageType) {
if (voltageType == null) {
return null;
}
Map<String, Object> 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 "未知类型";
}
}
}

View File

@@ -34,7 +34,7 @@ public class SysNoticeController extends BaseController {
/**
* 获取通知公告列表
*/
@SaCheckPermission("system:notice:list")
// @SaCheckPermission("system:notice:list")
@GetMapping("/list")
public TableDataInfo<SysNoticeVo> list(SysNoticeBo notice, PageQuery pageQuery) {
return noticeService.selectPageNoticeList(notice, pageQuery);