fix: 绑定功能优化完善,新增清零功能接口。

This commit is contained in:
tianyongbao
2026-07-11 19:16:18 +08:00
parent 818b59046d
commit 143dcd8e63
8 changed files with 187 additions and 12 deletions

View File

@@ -107,6 +107,14 @@
<artifactId>intc-system</artifactId> <artifactId>intc-system</artifactId>
</dependency> </dependency>
<!-- TDengine 模块(用于清理设备时序数据) -->
<dependency>
<groupId>com.intc</groupId>
<artifactId>intc-tdengine</artifactId>
<version>5.5.0</version>
<scope>compile</scope>
</dependency>
</dependencies> </dependencies>
</project> </project>

View File

@@ -30,18 +30,25 @@ import com.intc.fishery.domain.vo.PublicDeviceLinkedCtrl;
import com.intc.fishery.domain.vo.PublicDeviceDeadInfo; import com.intc.fishery.domain.vo.PublicDeviceDeadInfo;
import com.intc.fishery.domain.vo.PublicDeviceTypeAndSerialNum; import com.intc.fishery.domain.vo.PublicDeviceTypeAndSerialNum;
import com.intc.fishery.domain.Device; import com.intc.fishery.domain.Device;
import com.intc.fishery.domain.DeviceBindRecord;
import com.intc.fishery.domain.DeviceSwitch; import com.intc.fishery.domain.DeviceSwitch;
import com.intc.fishery.domain.DeviceErrorCode;
import com.intc.fishery.domain.Pond; import com.intc.fishery.domain.Pond;
import com.intc.fishery.domain.LinkedCtrl; import com.intc.fishery.domain.LinkedCtrl;
import com.intc.fishery.domain.DeviceCorrectRecord; import com.intc.fishery.domain.DeviceCorrectRecord;
import com.intc.fishery.mapper.DeviceMapper; import com.intc.fishery.mapper.DeviceMapper;
import com.intc.fishery.mapper.DeviceSwitchMapper; 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.PondMapper;
import com.intc.fishery.mapper.LinkedCtrlMapper; import com.intc.fishery.mapper.LinkedCtrlMapper;
import com.intc.fishery.mapper.DeviceCorrectRecordMapper; import com.intc.fishery.mapper.DeviceCorrectRecordMapper;
import com.intc.fishery.constant.DefineDeviceWarnCode; import com.intc.fishery.constant.DefineDeviceWarnCode;
import com.intc.fishery.utils.MessageOpRecordUtil; import com.intc.fishery.utils.MessageOpRecordUtil;
import com.intc.common.core.config.properties.DeviceTypeProperties; 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.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
@@ -69,8 +76,13 @@ public class DeviceController extends BaseController {
private final PondMapper pondMapper; private final PondMapper pondMapper;
private final LinkedCtrlMapper linkedCtrlMapper; private final LinkedCtrlMapper linkedCtrlMapper;
private final DeviceCorrectRecordMapper deviceCorrectRecordMapper; private final DeviceCorrectRecordMapper deviceCorrectRecordMapper;
private final DeviceBindRecordMapper deviceBindRecordMapper;
private final DeviceErrorCodeMapper deviceErrorCodeMapper;
private final DeviceTypeProperties deviceTypeProperties; 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)); return toAjax(deviceService.deleteWithValidByIds(List.of(ids), true));
} }
/**
* 清零设备测试数据(出厂测试用)
* <p>清理范围:
* <ul>
* <li>有效期(deadTime)、告警状态(warnCode)、实时传感器值、最后上数时间</li>
* <li>TDengine时序数据删除设备子表清除所有历史传感器数据</li>
* <li>设备绑定/解绑记录</li>
* <li>设备校准记录</li>
* <li>设备故障码记录</li>
* <li>Redis告警标记</li>
* </ul>
* 清零后设备重新绑定时会自动重新计算365天有效期。</p>
*
* @param id 设备ID
* @return 操作结果
*/
@SaCheckPermission("fishery:device:edit")
@Log(title = "设备管理", businessType = BusinessType.UPDATE)
@RepeatSubmit()
@PutMapping("/resetTestData/{id}")
public R<Void> 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<Device>()
.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<DeviceBindRecord>()
.eq(DeviceBindRecord::getSerialNum, serialNum)
);
// 4. 删除设备校准记录
deviceCorrectRecordMapper.delete(
new LambdaQueryWrapper<DeviceCorrectRecord>()
.eq(DeviceCorrectRecord::getDeviceId, id)
);
// 5. 删除设备故障码记录
deviceErrorCodeMapper.delete(
new LambdaQueryWrapper<DeviceErrorCode>()
.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: 返回完整信息(包含塘口信息和开关列表) * type=1: 返回完整信息(包含塘口信息和开关列表)

View File

@@ -402,6 +402,9 @@ public class DeviceServiceImpl implements IDeviceService {
try { try {
AquUser user = aquUserMapper.selectById(device.getUserId()); AquUser user = aquUserMapper.selectById(device.getUserId());
if (user != null) { if (user != null) {
// 管理员(出厂测试人员)解绑时清空有效期,普通用户解绑时保留有效期(防止解绑续命)
boolean isManager = user.getIsManager() != null && user.getIsManager() == 1L;
Device newDevice = new Device(); Device newDevice = new Device();
newDevice.setUserId(null); newDevice.setUserId(null);
newDevice.setIsOxygenUsed(device.getIsOxygenUsed()); newDevice.setIsOxygenUsed(device.getIsOxygenUsed());
@@ -411,7 +414,8 @@ public class DeviceServiceImpl implements IDeviceService {
newDevice.setDeviceType(device.getDeviceType()); newDevice.setDeviceType(device.getDeviceType());
newDevice.setIccId(device.getIccId()); newDevice.setIccId(device.getIccId());
newDevice.setBindTime(device.getBindTime()); newDevice.setBindTime(device.getBindTime());
newDevice.setDeadTime(device.getDeadTime()); // 管理员解绑时清空有效期(出厂测试),普通用户解绑时保留有效期
newDevice.setDeadTime(isManager ? null : device.getDeadTime());
newDevice.setPondId(null); newDevice.setPondId(null);
newDevice.setSalinityCompensation(0.0); newDevice.setSalinityCompensation(0.0);
newDevice.setOxyWarnLower(device.getOxyWarnLower()); newDevice.setOxyWarnLower(device.getOxyWarnLower());
@@ -423,8 +427,8 @@ public class DeviceServiceImpl implements IDeviceService {
newDevice.setWarnCode(DefineDeviceWarnCode.None); newDevice.setWarnCode(DefineDeviceWarnCode.None);
newDevice.setCategory(device.getCategory()); newDevice.setCategory(device.getCategory());
baseMapper.insert(newDevice); baseMapper.insert(newDevice);
log.info("已为用户重建无绑定设备: oldDeviceId={}, newDeviceId={}, serialNum={}", log.info("已为用户重建无绑定设备: oldDeviceId={}, newDeviceId={}, serialNum={}, isManager={}, deadTime={}",
deviceId, newDevice.getId(), device.getSerialNum()); deviceId, newDevice.getId(), device.getSerialNum(), isManager, newDevice.getDeadTime());
} }
} catch (Exception e) { } catch (Exception e) {
log.error("重建无绑定设备失败: deviceId={}, error={}", deviceId, e.getMessage(), e); log.error("重建无绑定设备失败: deviceId={}, error={}", deviceId, e.getMessage(), e);

View File

@@ -1151,11 +1151,12 @@ public class IotController extends BaseController {
device.setIotId(iotId); device.setIotId(iotId);
device.setSerialNum(bo.getSerialNum()); device.setSerialNum(bo.getSerialNum());
device.setDeviceType(2); // 2-测控一体机 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 { } else {
// 检查是否已过期 // 管理员解绑会清空有效期(null)普通用户解绑会保留有效期。仅null时才设置365天
if (device.getDeadTime() != null && now.after(device.getDeadTime())) { if (device.getDeadTime() == null) {
warnCode |= DefineDeviceWarnCode.DeviceTimeDead; 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.setIotId(iotId);
device.setSerialNum(bo.getSerialNum()); device.setSerialNum(bo.getSerialNum());
device.setDeviceType(1); // 1-水质检测仪 device.setDeviceType(1); // 1-水质检测仪
device.setDeadTime(new Date(now.getTime() + 365L * 24 * 60 * 60 * 1000)); // 一年后到期
device.setIsOxygenUsed(1); device.setIsOxygenUsed(1);
device.setDeadTime(new Date(now.getTime() + 365L * 24 * 60 * 60 * 1000));
} else { } else {
// 检查是否已过期 // 管理员解绑会清空有效期(null)普通用户解绑会保留有效期。仅null时才设置365天
if (device.getDeadTime() != null && now.after(device.getDeadTime())) { if (device.getDeadTime() == null) {
warnCode |= DefineDeviceWarnCode.DeviceTimeDead; 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.setIotId(iotId);
device.setSerialNum(bo.getSerialNum()); device.setSerialNum(bo.getSerialNum());
device.setDeviceType(3); // 3-太阳能网控 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));
}
} }
// 设置基本信息 // 设置基本信息

View File

@@ -87,4 +87,13 @@ public interface DeviceSensorDataMapper {
@InterceptorIgnore(tenantLine = "true") @InterceptorIgnore(tenantLine = "true")
DeviceSensorData getLatestReportTime(@Param("serialNum") String serialNum); DeviceSensorData getLatestReportTime(@Param("serialNum") String serialNum);
/**
* 删除设备子表(清除该设备的所有时序数据)
*
* @param serialNum 设备序列号
*/
@DS("taos")
@InterceptorIgnore(tenantLine = "true")
void dropSubTable(@Param("serialNum") String serialNum);
} }

View File

@@ -46,4 +46,11 @@ public interface IDeviceSensorDataService {
* @return 最新上数时间字符串,格式 yyyy-MM-dd HH:mm:ss无数据时返回 null * @return 最新上数时间字符串,格式 yyyy-MM-dd HH:mm:ss无数据时返回 null
*/ */
String getLatestReportTime(String serialNum); String getLatestReportTime(String serialNum);
/**
* 删除设备子表(清除该设备的所有时序数据)
*
* @param serialNum 设备序列号
*/
void dropSubTable(String serialNum);
} }

View File

@@ -296,4 +296,27 @@ public class DeviceSensorDataService implements IDeviceSensorDataService {
default: return "未知"; 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);
}
}
}
} }

View File

@@ -120,4 +120,8 @@
ORDER BY `time` DESC ORDER BY `time` DESC
LIMIT 1 LIMIT 1
</select> </select>
<update id="dropSubTable">
DROP TABLE IF EXISTS `fishery`.`t_${serialNum}`
</update>
</mapper> </mapper>