diff --git a/intc-modules/intc-fishery/pom.xml b/intc-modules/intc-fishery/pom.xml
index 076046b..022ba7f 100644
--- a/intc-modules/intc-fishery/pom.xml
+++ b/intc-modules/intc-fishery/pom.xml
@@ -107,6 +107,14 @@
intc-system
+
+
+ com.intc
+ intc-tdengine
+ 5.5.0
+ compile
+
+
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 81160ed..eea8720 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
@@ -30,18 +30,25 @@ import com.intc.fishery.domain.vo.PublicDeviceLinkedCtrl;
import com.intc.fishery.domain.vo.PublicDeviceDeadInfo;
import com.intc.fishery.domain.vo.PublicDeviceTypeAndSerialNum;
import com.intc.fishery.domain.Device;
+import com.intc.fishery.domain.DeviceBindRecord;
import com.intc.fishery.domain.DeviceSwitch;
+import com.intc.fishery.domain.DeviceErrorCode;
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.DeviceBindRecordMapper;
+import com.intc.fishery.mapper.DeviceErrorCodeMapper;
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.intc.fishery.utils.MessageOpRecordUtil;
import com.intc.common.core.config.properties.DeviceTypeProperties;
+import com.intc.common.redis.utils.RedisUtils;
+import com.intc.tdengine.service.IDeviceSensorDataService;
+import org.springframework.beans.factory.annotation.Autowired;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import org.springframework.transaction.annotation.Transactional;
@@ -69,8 +76,13 @@ public class DeviceController extends BaseController {
private final PondMapper pondMapper;
private final LinkedCtrlMapper linkedCtrlMapper;
private final DeviceCorrectRecordMapper deviceCorrectRecordMapper;
+ private final DeviceBindRecordMapper deviceBindRecordMapper;
+ private final DeviceErrorCodeMapper deviceErrorCodeMapper;
private final DeviceTypeProperties deviceTypeProperties;
+ @Autowired(required = false)
+ private IDeviceSensorDataService deviceSensorDataService;
+
/**
* 查询设备管理列表
*/
@@ -148,6 +160,106 @@ public class DeviceController extends BaseController {
return toAjax(deviceService.deleteWithValidByIds(List.of(ids), true));
}
+ /**
+ * 清零设备测试数据(出厂测试用)
+ *
清理范围:
+ *
+ * - 有效期(deadTime)、告警状态(warnCode)、实时传感器值、最后上数时间
+ * - TDengine时序数据(删除设备子表,清除所有历史传感器数据)
+ * - 设备绑定/解绑记录
+ * - 设备校准记录
+ * - 设备故障码记录
+ * - Redis告警标记
+ *
+ * 清零后,设备重新绑定时会自动重新计算365天有效期。
+ *
+ * @param id 设备ID
+ * @return 操作结果
+ */
+ @SaCheckPermission("fishery:device:edit")
+ @Log(title = "设备管理", businessType = BusinessType.UPDATE)
+ @RepeatSubmit()
+ @PutMapping("/resetTestData/{id}")
+ public R resetTestData(@NotNull(message = "主键不能为空")
+ @PathVariable Long id) {
+ Device device = deviceMapper.selectById(id);
+ if (device == null) {
+ return R.fail("设备不存在");
+ }
+
+ String serialNum = device.getSerialNum();
+
+ // 1. 清零设备表:有效期、告警状态、实时传感器值、最后上数时间
+ deviceMapper.update(null,
+ new LambdaUpdateWrapper()
+ .eq(Device::getId, id)
+ .set(Device::getDeadTime, null)
+ .set(Device::getWarnCode, DefineDeviceWarnCode.None)
+ .set(Device::getLastReportTime, null)
+ .set(Device::getValueDissolvedOxygen, null)
+ .set(Device::getValueTemperature, null)
+ .set(Device::getValueSaturability, null)
+ .set(Device::getValuePh, null)
+ .set(Device::getValueSalinity, null)
+ .set(Device::getValueBattery, null)
+ .set(Device::getIsOxygenWarnExist, 0)
+ .set(Device::getIsTempWarnExist, 0)
+ );
+
+ // 2. 删除TDengine时序数据(设备子表,含所有历史传感器数据)
+ if (deviceSensorDataService != null && serialNum != null && !serialNum.isBlank()) {
+ try {
+ deviceSensorDataService.dropSubTable(serialNum);
+ } catch (Exception e) {
+ // TDengine清理失败不阻断主流程
+ org.slf4j.LoggerFactory.getLogger(DeviceController.class)
+ .warn("清理TDengine数据失败: deviceId={}, serialNum={}, error={}", id, serialNum, e.getMessage());
+ }
+ }
+
+ // 3. 删除设备绑定/解绑记录
+ deviceBindRecordMapper.delete(
+ new LambdaQueryWrapper()
+ .eq(DeviceBindRecord::getSerialNum, serialNum)
+ );
+
+ // 4. 删除设备校准记录
+ deviceCorrectRecordMapper.delete(
+ new LambdaQueryWrapper()
+ .eq(DeviceCorrectRecord::getDeviceId, id)
+ );
+
+ // 5. 删除设备故障码记录
+ deviceErrorCodeMapper.delete(
+ new LambdaQueryWrapper()
+ .eq(DeviceErrorCode::getDeviceId, id)
+ );
+
+ // 6. 清除Redis告警标记
+ if (serialNum != null && !serialNum.isBlank()) {
+ try {
+ RedisUtils.deleteObject("device:offline:wait:" + serialNum);
+ RedisUtils.deleteObject("device:detector:oxy:offline:wait:" + serialNum);
+ } catch (Exception e) {
+ // Redis清理失败不影响主流程
+ }
+ }
+
+ // 7. 记录操作日志
+ if (device.getUserId() != null) {
+ MessageOpRecordUtil.addMessageOpRecordUser(
+ device.getUserId(),
+ "设备管理",
+ String.format("%s(%s),清零设备测试数据。", device.getDeviceName(), serialNum)
+ );
+ } else {
+ org.slf4j.LoggerFactory.getLogger(DeviceController.class)
+ .info("清零设备测试数据(设备未绑定用户): deviceId={}, serialNum={}", id, serialNum);
+ }
+
+ return R.ok();
+ }
+
/**
* 查询用户所有设备简化列表
* type=1: 返回完整信息(包含塘口信息和开关列表)
diff --git a/intc-modules/intc-fishery/src/main/java/com/intc/fishery/service/impl/DeviceServiceImpl.java b/intc-modules/intc-fishery/src/main/java/com/intc/fishery/service/impl/DeviceServiceImpl.java
index 59d711c..8e38c4d 100644
--- a/intc-modules/intc-fishery/src/main/java/com/intc/fishery/service/impl/DeviceServiceImpl.java
+++ b/intc-modules/intc-fishery/src/main/java/com/intc/fishery/service/impl/DeviceServiceImpl.java
@@ -402,6 +402,9 @@ public class DeviceServiceImpl implements IDeviceService {
try {
AquUser user = aquUserMapper.selectById(device.getUserId());
if (user != null) {
+ // 管理员(出厂测试人员)解绑时清空有效期,普通用户解绑时保留有效期(防止解绑续命)
+ boolean isManager = user.getIsManager() != null && user.getIsManager() == 1L;
+
Device newDevice = new Device();
newDevice.setUserId(null);
newDevice.setIsOxygenUsed(device.getIsOxygenUsed());
@@ -411,7 +414,8 @@ public class DeviceServiceImpl implements IDeviceService {
newDevice.setDeviceType(device.getDeviceType());
newDevice.setIccId(device.getIccId());
newDevice.setBindTime(device.getBindTime());
- newDevice.setDeadTime(device.getDeadTime());
+ // 管理员解绑时清空有效期(出厂测试),普通用户解绑时保留有效期
+ newDevice.setDeadTime(isManager ? null : device.getDeadTime());
newDevice.setPondId(null);
newDevice.setSalinityCompensation(0.0);
newDevice.setOxyWarnLower(device.getOxyWarnLower());
@@ -423,8 +427,8 @@ public class DeviceServiceImpl implements IDeviceService {
newDevice.setWarnCode(DefineDeviceWarnCode.None);
newDevice.setCategory(device.getCategory());
baseMapper.insert(newDevice);
- log.info("已为用户重建无绑定设备: oldDeviceId={}, newDeviceId={}, serialNum={}",
- deviceId, newDevice.getId(), device.getSerialNum());
+ log.info("已为用户重建无绑定设备: oldDeviceId={}, newDeviceId={}, serialNum={}, isManager={}, deadTime={}",
+ deviceId, newDevice.getId(), device.getSerialNum(), isManager, newDevice.getDeadTime());
}
} catch (Exception e) {
log.error("重建无绑定设备失败: deviceId={}, error={}", deviceId, e.getMessage(), e);
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 179fbc4..ae31828 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
@@ -1151,11 +1151,12 @@ public class IotController extends BaseController {
device.setIotId(iotId);
device.setSerialNum(bo.getSerialNum());
device.setDeviceType(2); // 2-测控一体机
- device.setDeadTime(new Date(now.getTime() + 365L * 24 * 60 * 60 * 1000)); // 一年后到期
+ device.setDeadTime(new Date(now.getTime() + 365L * 24 * 60 * 60 * 1000));
} else {
- // 检查是否已过期
- if (device.getDeadTime() != null && now.after(device.getDeadTime())) {
- warnCode |= DefineDeviceWarnCode.DeviceTimeDead;
+ // 管理员解绑会清空有效期(null),普通用户解绑会保留有效期。仅null时才设置365天
+ if (device.getDeadTime() == null) {
+ log.info("测控一体机重新绑定,有效期已被管理员解绑清空,重新设置: serialNum={}", bo.getSerialNum());
+ device.setDeadTime(new Date(now.getTime() + 365L * 24 * 60 * 60 * 1000));
}
}
@@ -1575,12 +1576,13 @@ public class IotController extends BaseController {
device.setIotId(iotId);
device.setSerialNum(bo.getSerialNum());
device.setDeviceType(1); // 1-水质检测仪
- device.setDeadTime(new Date(now.getTime() + 365L * 24 * 60 * 60 * 1000)); // 一年后到期
device.setIsOxygenUsed(1);
+ device.setDeadTime(new Date(now.getTime() + 365L * 24 * 60 * 60 * 1000));
} else {
- // 检查是否已过期
- if (device.getDeadTime() != null && now.after(device.getDeadTime())) {
- warnCode |= DefineDeviceWarnCode.DeviceTimeDead;
+ // 管理员解绑会清空有效期(null),普通用户解绑会保留有效期。仅null时才设置365天
+ if (device.getDeadTime() == null) {
+ log.info("水质检测仪重新绑定,有效期已被管理员解绑清空,重新设置: serialNum={}", bo.getSerialNum());
+ device.setDeadTime(new Date(now.getTime() + 365L * 24 * 60 * 60 * 1000));
}
}
@@ -1832,7 +1834,13 @@ public class IotController extends BaseController {
device.setIotId(iotId);
device.setSerialNum(bo.getSerialNum());
device.setDeviceType(3); // 3-太阳能网控
- device.setDeadTime(new Date(now.getTime() + 365L * 24 * 60 * 60 * 1000)); // 一年后到期
+ device.setDeadTime(new Date(now.getTime() + 365L * 24 * 60 * 60 * 1000));
+ } else {
+ // 管理员解绑会清空有效期(null),普通用户解绑会保留有效期。仅null时才设置365天
+ if (device.getDeadTime() == null) {
+ log.info("太阳能网控重新绑定,有效期已被管理员解绑清空,重新设置: serialNum={}", bo.getSerialNum());
+ device.setDeadTime(new Date(now.getTime() + 365L * 24 * 60 * 60 * 1000));
+ }
}
// 设置基本信息
diff --git a/intc-modules/intc-tdengine/src/main/java/com/intc/tdengine/mapper/DeviceSensorDataMapper.java b/intc-modules/intc-tdengine/src/main/java/com/intc/tdengine/mapper/DeviceSensorDataMapper.java
index f4c3a48..97ded7b 100644
--- a/intc-modules/intc-tdengine/src/main/java/com/intc/tdengine/mapper/DeviceSensorDataMapper.java
+++ b/intc-modules/intc-tdengine/src/main/java/com/intc/tdengine/mapper/DeviceSensorDataMapper.java
@@ -87,4 +87,13 @@ public interface DeviceSensorDataMapper {
@InterceptorIgnore(tenantLine = "true")
DeviceSensorData getLatestReportTime(@Param("serialNum") String serialNum);
+ /**
+ * 删除设备子表(清除该设备的所有时序数据)
+ *
+ * @param serialNum 设备序列号
+ */
+ @DS("taos")
+ @InterceptorIgnore(tenantLine = "true")
+ void dropSubTable(@Param("serialNum") String serialNum);
+
}
diff --git a/intc-modules/intc-tdengine/src/main/java/com/intc/tdengine/service/IDeviceSensorDataService.java b/intc-modules/intc-tdengine/src/main/java/com/intc/tdengine/service/IDeviceSensorDataService.java
index 3fc2210..f6bd46f 100644
--- a/intc-modules/intc-tdengine/src/main/java/com/intc/tdengine/service/IDeviceSensorDataService.java
+++ b/intc-modules/intc-tdengine/src/main/java/com/intc/tdengine/service/IDeviceSensorDataService.java
@@ -46,4 +46,11 @@ public interface IDeviceSensorDataService {
* @return 最新上数时间字符串,格式 yyyy-MM-dd HH:mm:ss;无数据时返回 null
*/
String getLatestReportTime(String serialNum);
+
+ /**
+ * 删除设备子表(清除该设备的所有时序数据)
+ *
+ * @param serialNum 设备序列号
+ */
+ void dropSubTable(String serialNum);
}
diff --git a/intc-modules/intc-tdengine/src/main/java/com/intc/tdengine/service/impl/DeviceSensorDataService.java b/intc-modules/intc-tdengine/src/main/java/com/intc/tdengine/service/impl/DeviceSensorDataService.java
index 698400a..4267301 100644
--- a/intc-modules/intc-tdengine/src/main/java/com/intc/tdengine/service/impl/DeviceSensorDataService.java
+++ b/intc-modules/intc-tdengine/src/main/java/com/intc/tdengine/service/impl/DeviceSensorDataService.java
@@ -296,4 +296,27 @@ public class DeviceSensorDataService implements IDeviceSensorDataService {
default: return "未知";
}
}
+
+ /**
+ * 删除设备子表(清除该设备的所有时序数据)
+ */
+ @Override
+ public void dropSubTable(String serialNum) {
+ if (serialNum == null || serialNum.isBlank()) {
+ return;
+ }
+ try {
+ deviceSensorDataMapper.dropSubTable(serialNum);
+ // 清除建表缓存,下次插入数据时会重新建表
+ CREATED_TABLES.remove(serialNum);
+ log.info("已删除设备TDengine子表: serialNum={}", serialNum);
+ } catch (Exception e) {
+ String errorMsg = e.getMessage();
+ if (errorMsg != null && errorMsg.toLowerCase().contains("not exist")) {
+ log.debug("设备TDengine子表不存在,无需删除: serialNum={}", serialNum);
+ } else {
+ log.warn("删除设备TDengine子表失败: serialNum={}, error={}", serialNum, errorMsg);
+ }
+ }
+ }
}
diff --git a/intc-modules/intc-tdengine/src/main/resources/mapper/tdengine/DeviceSensorDataMapper.xml b/intc-modules/intc-tdengine/src/main/resources/mapper/tdengine/DeviceSensorDataMapper.xml
index c2a811f..a04a6c9 100644
--- a/intc-modules/intc-tdengine/src/main/resources/mapper/tdengine/DeviceSensorDataMapper.xml
+++ b/intc-modules/intc-tdengine/src/main/resources/mapper/tdengine/DeviceSensorDataMapper.xml
@@ -120,4 +120,8 @@
ORDER BY `time` DESC
LIMIT 1
+
+
+ DROP TABLE IF EXISTS `fishery`.`t_${serialNum}`
+