20260323:优化设备上线之后一秒内发送IMEI/ICCID

This commit is contained in:
George 2026-03-23 22:46:09 +08:00
parent 479c0aab6c
commit 25abae984d
8 changed files with 159 additions and 7 deletions

View File

@ -0,0 +1,67 @@
package com.fastbee.web.controller.backend;
import com.fastbee.common.core.domain.AjaxResult;
import com.fastbee.common.utils.StringUtils;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;
/**
* @description: 调试/后台接口
*
* @author Sumuxi
* @date 2026/3/23
*/
@Api(tags = "后台接口")
@RestController
@RequestMapping("/api/backend")
public class BackendController {
@Resource
private RedisTemplate<String, Object> redisTemplate;
@ApiOperation("设置缓存值(调试/运维)")
@PreAuthorize("@ss.hasPermi('monitor:cache:remove')")
@PostMapping("/set")
public AjaxResult setCacheValue(@RequestParam("key") String key, @RequestParam("value") String value, @RequestParam(required = false) Long expireSeconds) {
if (StringUtils.isEmpty(key)) {
return AjaxResult.error("key不能为空");
}
if (value == null || value.isEmpty()) {
return AjaxResult.error("value不能为空");
}
if (expireSeconds == null) {
redisTemplate.opsForValue().set(key, value);
return AjaxResult.success("ok");
}
if (expireSeconds > 0) {
redisTemplate.opsForValue().set(key, value, expireSeconds, TimeUnit.SECONDS);
} else {
// expireSeconds <= 0 视为不设置过期时间
redisTemplate.opsForValue().set(key, value);
}
return AjaxResult.success("ok");
}
// 删除缓存值
@ApiOperation("删除缓存值(调试/运维)")
@PreAuthorize("@ss.hasPermi('monitor:cache:remove')")
@PostMapping("/remove")
public AjaxResult removeCacheValue(@RequestParam("key") String key) {
if (StringUtils.isEmpty(key)) {
return AjaxResult.error("key不能为空");
}
redisTemplate.delete(key);
return AjaxResult.success("ok");
}
}

View File

@ -3,12 +3,17 @@ package com.fastbee.iot.data.executor;
import com.fastbee.base.service.ISessionStore;
import com.fastbee.base.session.Session;
import com.fastbee.common.constant.FastBeeConstant;
import com.fastbee.common.core.mq.message.DeviceMessage;
import com.fastbee.common.core.protocol.Message;
import com.fastbee.common.core.redis.RedisKeyBuilder;
import com.fastbee.common.utils.StringUtils;
import com.fastbee.common.utils.gateway.protocol.ByteUtils;
import com.fastbee.iot.data.service.IDeviceMessageService;
import com.fastbee.iot.data.service.impl.MessageManager;
import com.fastbee.iot.domain.Device;
import com.fastbee.iot.domain.ModbusJob;
import com.fastbee.iot.enums.DeviceType;
import com.fastbee.iot.service.IDeviceService;
import com.fastbee.iot.service.IModbusJobService;
import io.netty.buffer.Unpooled;
import lombok.extern.slf4j.Slf4j;
@ -57,22 +62,62 @@ public class PowerOnTriggerTaskExecutor {
@Resource
private RedisTemplate<String, Object> redisTemplate;
@Resource
private IDeviceMessageService deviceMessageService;
@Resource
private IDeviceService deviceService;
/**
* 每台设备同一时刻仅允许一个"上电触发轮询"流水线运行,避免重复触发/并发下发导致应答错配
*/
private final ConcurrentHashMap<String, AtomicBoolean> running = new ConcurrentHashMap<>();
/**
* 设置上电触发标识(设备注册/上线成功后调用)
* 注册之后处理
* 1、设置上电触发标识(设备注册/上线成功后调用)
* 2、下发IMEI/ICCID读取指令
* @param serialNumber 设备编号
*/
public void setPowerOnTriggerFlag(String serialNumber) {
@Async(FastBeeConstant.TASK.TRIGGER_POWER_ON_TASK_ASYNC)
public void registerAfterHandle(String serialNumber) {
if (StringUtils.isEmpty(serialNumber)) {
return;
}
// 1、设置上电触发redis标识
String key = RedisKeyBuilder.buildPowerOnTriggerFlagKey(serialNumber);
redisTemplate.opsForValue().set(key, "1", TRIGGER_FLAG_EXPIRE_SECONDS, TimeUnit.SECONDS);
log.debug("设置上电触发标识,设备:{}", serialNumber);
//log.debug("设置上电触发标识,设备:{}", serialNumber);
// 2、下发IMEI/ICCID读取指令
if (redisTemplate.opsForValue().get("get_imei_iccid") == null){
log.warn("无缓存标识,无法下发IMEI/ICCID指令");
return;
}
Device device = deviceService.selectDeviceInfoBySerialNumber(serialNumber);
if (device == null) {
log.warn("设备不存在,无法下发指令,设备编号:{}", serialNumber);
return;
}
if (!(device.getDeviceType() == DeviceType.DIRECT_DEVICE.getCode() || device.getDeviceType() == DeviceType.GATEWAY.getCode())) {
log.warn("设备类型不为直连或网关,无需下发指令,设备编号:{}", serialNumber);
return;
}
// 等设备上线
try { Thread.sleep(1000); } catch (InterruptedException e) { log.error("Thread sleep interrupted", e); }
DeviceMessage deviceMessage = new DeviceMessage();
deviceMessage.setSerialNumber(serialNumber);
deviceMessage.setTopicName("/function/get");
deviceMessage.setDataType("hex");
if (StringUtils.isEmpty(device.getImei())) {
deviceMessage.setMessage("41542b494d45495c725c6e");
deviceMessageService.messagePost(deviceMessage);
}
if (StringUtils.isEmpty(device.getIccid())) {
deviceMessage.setMessage("41542b49434349445c725c6e");
deviceMessageService.messagePost(deviceMessage);
}
}
/**

View File

@ -56,7 +56,7 @@ public class TcpHandlerInterceptor implements HandlerInterceptor<Message> {
// 注册报文处理完成后,设置上电触发标识
int messageId = Integer.parseInt(request.getMessageId());
if (messageId == TcpDtu.注册报文 && session.isRegistered()) {
powerOnTriggerTaskExecutor.setPowerOnTriggerFlag(session.getClientId());
powerOnTriggerTaskExecutor.registerAfterHandle(session.getClientId());
log.debug("设备注册成功,设置上电触发标识,设备:{}", session.getClientId());
}
}

View File

@ -1,6 +1,5 @@
package com.fastbee.iot.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.fastbee.common.core.device.DeviceAndProtocol;
import com.fastbee.common.core.thingsModel.ThingsModelValuesInput;
import com.fastbee.framework.mybatis.mapper.BaseMapperX;
@ -10,7 +9,6 @@ import com.fastbee.iot.model.*;
import com.fastbee.iot.model.ThingsModels.ThingsModelValuesOutput;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Set;
@ -38,6 +36,16 @@ public interface DeviceMapper extends BaseMapperX<Device> {
*/
public DeviceStatistic selectDeviceProductAlertCount(Long deptId);
/**
* 根据设备编号查询轻量信息:deviceType/imei/iccid
*
* <p>用于只需要判断/下发 IMEI/ICCID 的场景,避免查询 DeviceResult 中的大量无关字段。</p>
*
* @param serialNumber 设备编号
* @return 设备轻量对象(只保证 deviceType/imei/iccid 字段可用)
*/
Device selectDeviceInfoBySerialNumber(String serialNumber);
/**
* 根据设备编号查询设备
*

View File

@ -266,6 +266,13 @@ public interface IDeviceService {
*/
public void batchChangeStatus(List<String> serialNumbers, DeviceStatus status);
/**
* 根据设备编号查询轻量信息:deviceType/imei/iccid
*
* @param serialNumber 设备编号
* @return 设备轻量对象(只保证 deviceType/imei/iccid 字段可用)
*/
Device selectDeviceInfoBySerialNumber(String serialNumber);
/**
* 根据设备编号查询设备信息 -不带缓存物模型值

View File

@ -1555,6 +1555,10 @@ public class DeviceServiceImpl implements IDeviceService {
deviceCache.updateBatchDeviceStatusCache(serialNumbers, status);
}
public Device selectDeviceInfoBySerialNumber(String serialNumber) {
return deviceMapper.selectDeviceInfoBySerialNumber(serialNumber);
}
/**
* 根据设备编号查询设备信息 -不带缓存物模型值
*

View File

@ -40,6 +40,16 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<result property="iccid" column="iccid" />
</resultMap>
<!--
轻量查询结果映射:仅用于 IMEI/ICCID 读取场景。
目的:避免 selectDeviceResultBySerialNumber 拉取过多无关字段。
-->
<resultMap type="com.fastbee.iot.domain.Device" id="DeviceImeiIccidTypeResult">
<result property="deviceType" column="device_type" />
<result property="imei" column="imei" />
<result property="iccid" column="iccid" />
</resultMap>
<resultMap type="com.fastbee.iot.model.DeviceShortOutput" id="DeviceShortResult">
<result property="deviceId" column="device_id" />
<result property="deviceName" column="device_name" />
@ -449,6 +459,17 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
where device_id = #{deviceId}
</select>
<select id="selectDeviceInfoBySerialNumber" parameterType="String" resultMap="DeviceImeiIccidTypeResult">
select
p.device_type as device_type,
d.imei as imei,
d.iccid as iccid
from iot_device d
left join iot_product p on p.product_id = d.product_id
where d.serial_number = #{serialNumber}
limit 1
</select>
<select id="selectDeviceBySerialNumber" parameterType="String" resultMap="DeviceResult">
<include refid="selectDeviceVo"/>
where serial_number = #{serialNumber}