20251229:设备上电触发任务修改

This commit is contained in:
George 2025-12-29 08:19:29 +08:00
parent 9ebb0d0986
commit b35a7b8162
14 changed files with 588 additions and 276 deletions

View File

@ -141,7 +141,7 @@ sip:
# 日志配置 # 日志配置
logging: logging:
level: level:
com.fastbee: debug com.fastbee: info
com.yomahub: warn com.yomahub: warn
org.dromara: warn org.dromara: warn
org.springframework: warn org.springframework: warn

View File

@ -287,6 +287,11 @@ public interface FastBeeConstant {
*/ */
String POWER_ON_TRIGGER_PENDING = "power:on:trigger:pending:"; String POWER_ON_TRIGGER_PENDING = "power:on:trigger:pending:";
/**
* 设备上电触发任务ID列表缓存(用于避免重复查询数据库)
*/
String POWER_ON_TRIGGER_JOB_IDS = "power:on:trigger:jobIds:";
} }
interface PROTOCOL { interface PROTOCOL {

View File

@ -160,7 +160,7 @@ public class RedisKeyBuilder {
/** /**
* 设备上电触发指令缓存key * 设备上电触发指令缓存key
* 用于存储设备上电触发任务下发的指令片段(索引2到11位) * 用于存储设备上电触发任务下发的指令片段(索引2到3位)
*/ */
public static String buildPowerOnTriggerCommandKey(String serialNumber) { public static String buildPowerOnTriggerCommandKey(String serialNumber) {
return FastBeeConstant.REDIS.POWER_ON_TRIGGER_COMMAND + serialNumber.toUpperCase(); return FastBeeConstant.REDIS.POWER_ON_TRIGGER_COMMAND + serialNumber.toUpperCase();
@ -174,5 +174,12 @@ public class RedisKeyBuilder {
return FastBeeConstant.REDIS.POWER_ON_TRIGGER_PENDING + serialNumber.toUpperCase(); return FastBeeConstant.REDIS.POWER_ON_TRIGGER_PENDING + serialNumber.toUpperCase();
} }
/**
* 设备上电触发任务ID列表缓存key
* 用于缓存设备上电触发任务的ID列表,避免重复查询数据库
*/
public static String buildPowerOnTriggerJobIdsKey(String serialNumber) {
return FastBeeConstant.REDIS.POWER_ON_TRIGGER_JOB_IDS + serialNumber.toUpperCase();
}
} }

View File

@ -37,6 +37,7 @@ import java.util.ArrayList;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.function.Function;
/** /**
* 设备调试错误日志AOP切面 * 设备调试错误日志AOP切面
@ -232,7 +233,7 @@ public class DeviceDebugLogAspect {
* 统一异常处理入口 * 统一异常处理入口
*/ */
private void handleException(JoinPoint joinPoint, Throwable e, private void handleException(JoinPoint joinPoint, Throwable e,
java.util.function.Function<Object[], ErrorLogContext> contextExtractor, Function<Object[], ErrorLogContext> contextExtractor,
String errorLogPrefix) { String errorLogPrefix) {
try { try {
Object[] args = joinPoint.getArgs(); Object[] args = joinPoint.getArgs();
@ -378,9 +379,11 @@ public class DeviceDebugLogAspect {
// 构建并保存设备日志 // 构建并保存设备日志
DeviceLog deviceLog = buildDeviceLog(device, context); DeviceLog deviceLog = buildDeviceLog(device, context);
logService.saveDeviceLog(deviceLog); logService.saveDeviceLog(deviceLog);
log.info("已保存错误日志到数据库 - 设备: {}, 错误类型: {}, 错误信息: {}", if (log.isDebugEnabled()) {
serialNumber, context.getIdentity(), log.debug("已保存错误日志到数据库 - 设备: {}, 错误类型: {}, 错误信息: {}",
StringUtils.substring(context.getErrorMsg(), 0, MAX_ERROR_MSG_DISPLAY_LENGTH)); serialNumber, context.getIdentity(),
StringUtils.substring(context.getErrorMsg(), 0, MAX_ERROR_MSG_DISPLAY_LENGTH));
}
// 推送错误信息到前端页面 // 推送错误信息到前端页面
pushErrorToFrontend(serialNumber, productId, context); pushErrorToFrontend(serialNumber, productId, context);
@ -462,8 +465,10 @@ public class DeviceDebugLogAspect {
// 推送消息到MQTT // 推送消息到MQTT
mqttRemoteManager.pushCommon(messageBo); mqttRemoteManager.pushCommon(messageBo);
log.info("已推送错误信息到前端 - 设备: {}, 产品ID: {}, 错误类型: {}", if (log.isDebugEnabled()) {
serialNumber, productId, context.getIdentity()); log.debug("已推送错误信息到前端 - 设备: {}, 产品ID: {}, 错误类型: {}",
serialNumber, productId, context.getIdentity());
}
} catch (Exception ex) { } catch (Exception ex) {
log.error("推送错误信息到前端失败: {}", ex.getMessage(), ex); log.error("推送错误信息到前端失败: {}", ex.getMessage(), ex);
} }

View File

@ -0,0 +1,458 @@
package com.fastbee.iot.data.executor;
import com.alibaba.fastjson2.JSON;
import com.fastbee.base.service.ISessionStore;
import com.fastbee.base.session.Session;
import com.fastbee.common.constant.FastBeeConstant;
import com.fastbee.common.core.protocol.Message;
import com.fastbee.common.core.redis.RedisCache;
import com.fastbee.common.core.redis.RedisKeyBuilder;
import com.fastbee.common.enums.ServerType;
import com.fastbee.common.enums.TopicType;
import com.fastbee.common.utils.DateUtils;
import com.fastbee.common.utils.StringUtils;
import com.fastbee.common.utils.gateway.mq.TopicsUtils;
import com.fastbee.common.utils.gateway.protocol.ByteUtils;
import com.fastbee.iot.data.service.impl.MessageManager;
import com.fastbee.iot.domain.Device;
import com.fastbee.iot.domain.ModbusJob;
import com.fastbee.iot.model.DeviceStatusVO;
import com.fastbee.iot.service.IDeviceService;
import com.fastbee.iot.service.IModbusJobService;
import com.fastbee.mqttclient.PubMqttClient;
import io.netty.buffer.Unpooled;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import javax.annotation.Resource;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.TimeUnit;
/**
* 设备上电触发任务执行器
* 专门用于执行设备上电触发任务,直接下发指令,不通过队列,确保指令及时准确下发
*
* @author Sumuxi
*/
@Slf4j
@Component
public class PowerOnTriggerTaskExecutor {
@Autowired
private PubMqttClient pubMqttClient;
@Autowired
private RedisCache redisCache;
@Resource
private MessageManager messageManager;
@Resource
private TopicsUtils topicsUtils;
@Resource
private IDeviceService deviceService;
@Resource
private ISessionStore sessionStore;
@Resource
private IModbusJobService modbusJobService;
/**
* 标记设备需要触发上电任务(等待第一个数据包)
* 优化:直接查询 ModbusJob,缓存 taskId 列表,避免后续重复查询数据库
* @param device 设备信息
*/
@Async(FastBeeConstant.TASK.TRIGGER_POWER_ON_TASK_ASYNC)
public void markPowerOnTriggerPending(Device device) {
try {
String serialNumber = device.getSerialNumber().toUpperCase();
String taskIdsKey = RedisKeyBuilder.buildPowerOnTriggerJobIdsKey(serialNumber);
String pendingKey = RedisKeyBuilder.buildPowerOnTriggerPendingKey(serialNumber);
// 直接查询该设备的上电触发任务(cycleType=3,即 remark 包含 power_on_trigger)
ModbusJob queryJob = new ModbusJob();
queryJob.setSubSerialNumber(serialNumber);
queryJob.setStatus("0"); // 正常状态
queryJob.setCycleType(3); // 上电触发任务
List<ModbusJob> modbusJobList = modbusJobService.selectModbusJobList(queryJob);
if (CollectionUtils.isEmpty(modbusJobList)) {
// 清除可能存在的缓存
redisCache.deleteObject(taskIdsKey);
return;
}
// 筛选出上电触发任务,并收集 taskId 列表
List<Long> powerOnTriggerTaskIds = new ArrayList<>();
for (ModbusJob job : modbusJobList) {
if (Objects.equals(job.getCycleType(), 3)) {
powerOnTriggerTaskIds.add(job.getTaskId());
}
}
// 如果有上电触发任务,缓存 taskId 列表
if (!powerOnTriggerTaskIds.isEmpty()) {
// 缓存 taskId 列表,过期时间10分钟
redisCache.setCacheObject(taskIdsKey, JSON.toJSONString(powerOnTriggerTaskIds), 600, TimeUnit.SECONDS);
// 关键:设置"等待第一个数据包"标记(简单的标记即可,比如 "1")
redisCache.setCacheObject(pendingKey, "1", 600, TimeUnit.SECONDS);
log.debug("标记设备需要触发上电任务,等待第一个数据包,设备编号:{},任务数量:{}",
serialNumber, powerOnTriggerTaskIds.size());
} else {
// 如果没有上电触发任务,清除可能存在的旧缓存
redisCache.deleteObject(taskIdsKey);
redisCache.deleteObject(pendingKey);
log.debug("清除设备上电触发任务标记,设备编号:{}", serialNumber);
}
} catch (Exception e) {
log.error("标记设备上电触发任务异常,设备编号:{}", device.getSerialNumber(), e);
}
}
/**
* 异步检查并触发设备上电任务(在收到第一个数据包时)
* 优化:使用 taskId 列表作为标志位,列表为空或不存在表示没有待执行任务
* @param serialNumber 设备编号
* @param productId 产品ID
*/
@Async(FastBeeConstant.TASK.TRIGGER_POWER_ON_TASK_ASYNC)
public void checkAndTriggerPowerOnTaskAsync(String serialNumber, Long productId) {
if (StringUtils.isEmpty(serialNumber)) {
log.warn("设备编号为空,无法触发设备上电任务");
return;
}
String pendingKey = RedisKeyBuilder.buildPowerOnTriggerPendingKey(serialNumber);
// 关键:检查是否存在"等待第一个数据包"标记
Object pendingFlag = redisCache.getCacheObject(pendingKey);
if (pendingFlag == null) {
// 标记不存在,说明不是第一个数据包,或者已经处理过了
log.debug("设备上电触发任务标记不存在,不是第一个数据包,设备编号:{}", serialNumber);
return;
}
// 补偿查询设备信息(获取产品ID)
if (productId == null) {
Device device = deviceService.selectDeviceBySerialNumber(serialNumber);
if (device == null) {
log.warn("设备编号:{},产品ID为空,无法触发设备上电任务", serialNumber);
return;
}
productId = device.getProductId();
}
try {
serialNumber = serialNumber.toUpperCase();
String taskIdsKey = RedisKeyBuilder.buildPowerOnTriggerJobIdsKey(serialNumber);
// 从缓存读取 taskId 列表(列表本身作为标志位)
List<Long> taskIds = null;
Object cachedTaskIds = redisCache.getCacheObject(taskIdsKey);
if (cachedTaskIds != null) {
try {
taskIds = JSON.parseArray(cachedTaskIds.toString(), Long.class);
// 如果列表为空,表示没有待执行任务,直接返回(天然标志位)
if (CollectionUtils.isEmpty(taskIds)) {
log.debug("设备上电触发任务列表为空,设备编号:{}", serialNumber);
return;
}
log.debug("从缓存读取上电触发任务ID列表,设备编号:{},任务数量:{}", serialNumber, taskIds.size());
} catch (Exception e) {
log.warn("解析缓存的任务ID列表失败,设备编号:{},将查询数据库", serialNumber, e);
}
} else {
// 缓存不存在,表示没有待执行任务,直接返回
log.debug("设备上电触发任务缓存不存在,设备编号:{}", serialNumber);
return;
}
// 如果缓存中没有或解析失败,则查询数据库(兼容性处理)
if (taskIds == null || taskIds.isEmpty()) {
ModbusJob queryJob = new ModbusJob();
queryJob.setSubSerialNumber(serialNumber);
queryJob.setStatus("0"); // 正常状态
queryJob.setCycleType(3);
List<ModbusJob> modbusJobList = modbusJobService.selectModbusJobList(queryJob);
if (CollectionUtils.isEmpty(modbusJobList)) {
return;
}
// 筛选出上电触发任务的 taskId
taskIds = new ArrayList<>();
for (ModbusJob job : modbusJobList) {
if (Objects.equals(job.getCycleType(), 3) && "0".equals(job.getStatus())) {
taskIds.add(job.getTaskId());
}
}
if (taskIds.isEmpty()) {
return;
}
}
// 根据 taskId 列表查询 ModbusJob 任务
List<ModbusJob> modbusJobList = new ArrayList<>();
for (Long taskId : taskIds) {
ModbusJob job = modbusJobService.selectModbusJobByTaskId(taskId);
if (job != null && "0".equals(job.getStatus()) && Objects.equals(job.getCycleType(), 3)) {
modbusJobList.add(job);
}
}
// 执行所有上电触发任务(传入 taskIdsKey,用于成功后删除 taskId)
if (!modbusJobList.isEmpty()) {
execute(serialNumber, productId, modbusJobList, taskIdsKey);
} else {
// 如果没有有效任务,清除缓存
redisCache.deleteObject(taskIdsKey);
}
// 关键:无论任务是否成功,都删除"等待第一个数据包"标记
// 这样后续数据包就不会再触发任务了
redisCache.deleteObject(pendingKey);
log.info("设备上电触发任务处理完成,已删除第一个数据包标记,设备编号:{}", serialNumber);
} catch (Exception e) {
log.error("处理设备上电触发任务异常,设备编号:{}", serialNumber, e);
// 异常时也删除标记,避免一直阻塞
try {
redisCache.deleteObject(pendingKey);
} catch (Exception ex) {
// 忽略删除异常
}
}
}
/**
* 执行设备上电触发任务
* 直接下发指令,不通过队列,确保指令及时准确下发
* 优化:TCP协议使用block()等待响应,确保上一条指令处理完成后再发送下一条
* 优化:任务执行成功后,从 taskId 列表中删除,列表为空时删除整个 key
*
* @param serialNumber 设备编号
* @param productId 产品ID
* @param modbusJobList Modbus任务列表
* @param taskIdsKey Redis key,用于存储待执行的 taskId 列表
*/
public void execute(String serialNumber, Long productId, List<ModbusJob> modbusJobList, String taskIdsKey) {
if (StringUtils.isEmpty(serialNumber) || productId == null || CollectionUtils.isEmpty(modbusJobList)) {
log.warn("设备上电触发任务执行失败:设备或任务列表为空");
return;
}
// 获取设备状态和传输协议
DeviceStatusVO deviceStatusVO = deviceService.selectDeviceStatusAndTransportStatus(serialNumber);
if (deviceStatusVO == null) {
log.warn("设备上电触发任务执行失败:无法获取设备状态,设备编号:{}", serialNumber);
return;
}
ServerType serverType = ServerType.explain(deviceStatusVO.getTransport());
String topic = topicsUtils.buildTopic(productId, serialNumber, TopicType.FUNCTION_GET);
// 检查设备是否在线(TCP需要检查session)
if (ServerType.TCP.equals(serverType)) {
Session session = sessionStore.getSession(serialNumber);
if (session == null) {
log.warn("设备上电触发任务执行失败:TCP设备不在线,设备编号:{}", serialNumber);
return;
}
}
// 记录设备上电触发任务下发的指令片段(索引2到3位,2个字符)
String cacheKey = RedisKeyBuilder.buildPowerOnTriggerCommandKey(serialNumber);
// 收集所有指令片段(用于支持多个指令的情况)
Set<String> commandSegments = new HashSet<>();
// 执行所有相关的modbus任务
int successCount = 0;
int timeoutCount = 0;
int totalCount = 0;
for (ModbusJob modbusJob : modbusJobList) {
if (!"0".equals(modbusJob.getStatus())) {
// 只执行正常状态的任务
continue;
}
totalCount++;
String command = modbusJob.getCommand();
if (StringUtils.isEmpty(command)) {
log.warn("设备上电触发任务执行失败:指令为空,设备编号:{},任务ID:{}", serialNumber, modbusJob.getTaskId());
continue;
}
try {
// 记录下发的指令片段(索引2到3位,即第3到第4个字符,2个字符)
String cleanCommand = command.replaceAll("\\s+", "").toUpperCase();
if (cleanCommand.length() >= 4) {
String commandSegment = cleanCommand.substring(2, 4);
commandSegments.add(commandSegment);
log.debug("收集设备上电触发任务指令片段,设备编号:{},指令片段:{}", serialNumber, commandSegment);
}
// 转换指令为字节数组
byte[] commandData = ByteUtils.convertToHexByteArray(command);
if (commandData.length == 0) {
log.warn("设备上电触发任务执行失败:指令转换失败,设备编号:{},指令:{}", serialNumber, command);
continue;
}
// 记录指令到Redis缓存(用于轮询任务管理)
String runtimeCacheKey = RedisKeyBuilder.buildModbusRuntimeCacheKey(serialNumber);
redisCache.zSetAdd(runtimeCacheKey, command, DateUtils.getTimestampSeconds());
// 记录发送时间
long sendTime = System.currentTimeMillis();
boolean responseReceived;
// 根据传输协议下发指令
switch (serverType) {
case MQTT:
// 通过MQTT内部客户端下发指令
pubMqttClient.publish(commandData, topic, false, 0);
log.info("=>上电触发-MQTT-线程=[{}],轮询指令:[{}],主题:[{}]", Thread.currentThread().getName(), command, topic);
// MQTT协议无法直接等待响应,等待200ms后继续
try {
Thread.sleep(200);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
log.warn("MQTT指令等待被中断,设备编号:{}", serialNumber);
}
responseReceived = true; // MQTT协议假设发送成功
break;
case TCP:
// 下发TCP客户端,等待200ms内的响应
Message msg = new Message();
msg.setClientId(serialNumber);
msg.setPayload(Unpooled.wrappedBuffer(commandData));
try {
messageManager.requestR(serialNumber, msg, Message.class)
.timeout(Duration.ofMillis(20000)) // 200ms超时
.doOnSuccess(result -> log.info("=>上电触发-TCP-线程=[{}],轮询指令:[{}],响应成功", Thread.currentThread().getName(), command))
.doOnError(error -> log.warn("=>上电触发-TCP-线程=[{}],轮询指令:[{}],响应超时或失败:{}", Thread.currentThread().getName(), command, error.getMessage()))
.block(); // 阻塞等待响应,最多200ms
responseReceived = true;
log.info("=>上电触发-TCP-线程=[{}],轮询指令:[{}],已收到响应", Thread.currentThread().getName(), command);
} catch (Exception e) {
// 200ms内未收到响应,视为超时
long elapsed = System.currentTimeMillis() - sendTime;
log.warn("=>上电触发-TCP-线程=[{}],轮询指令:[{}],200ms内未收到响应,耗时:{}ms", Thread.currentThread().getName(), command, elapsed);
responseReceived = false;
}
break;
default:
log.warn("设备上电触发任务执行失败:不支持的传输协议,设备编号:{},协议:{}", serialNumber, serverType);
continue;
}
// 只有在200ms内收到响应时才更新任务执行时间和从列表中删除 taskId
if (responseReceived) {
successCount++;
// 更新任务执行时间
updateTaskExecuteTime(modbusJob.getTaskId());
// 从 taskId 列表中删除已成功执行的任务
removeTaskIdFromList(taskIdsKey, modbusJob.getTaskId());
} else {
timeoutCount++;
log.warn("设备上电触发任务超时,不更新任务状态,设备编号:{},任务ID:{}", serialNumber, modbusJob.getTaskId());
}
} catch (Exception e) {
log.error("设备上电触发任务执行异常,设备编号:{},任务ID:{},指令:{}", serialNumber, modbusJob.getTaskId(), command, e);
}
}
// 所有指令下发完成后,缓存指令片段集合(用于识别回包)
if (!commandSegments.isEmpty()) {
// 将指令片段集合转换为JSON字符串存储,过期时间5秒(足够设备响应)
String segmentsJson = JSON.toJSONString(new ArrayList<>(commandSegments));
redisCache.setCacheObject(cacheKey, segmentsJson, 5, TimeUnit.SECONDS);
log.debug("缓存设备上电触发任务指令片段集合,设备编号:{},片段数量:{},片段:{}", serialNumber, commandSegments.size(), commandSegments);
}
log.info("设备上电触发任务执行完成,设备编号:{},总任务数:{},成功:{},超时:{}", serialNumber, totalCount, successCount, timeoutCount);
}
/**
* 更新任务执行时间(仅在收到响应时调用)
*
* @param taskId 任务ID
*/
private void updateTaskExecuteTime(Long taskId) {
if (taskId == null) {
return;
}
ModbusJob job = new ModbusJob();
job.setTaskId(taskId);
job.setExecuteTime(new Date());
job.setStatus("1");
modbusJobService.updateModbusJob(job);
}
/**
* 从 taskId 列表中删除已成功执行的任务
* 如果列表为空,则删除整个 key
*
* @param taskIdsKey Redis key
* @param taskId 要删除的任务ID
*/
private void removeTaskIdFromList(String taskIdsKey, Long taskId) {
if (StringUtils.isEmpty(taskIdsKey) || taskId == null) {
return;
}
try {
// 读取当前列表
Object cachedTaskIds = redisCache.getCacheObject(taskIdsKey);
if (cachedTaskIds == null) {
log.debug("taskId 列表不存在,key:{}", taskIdsKey);
return;
}
// 解析为 List
List<Long> taskIds = JSON.parseArray(cachedTaskIds.toString(), Long.class);
if (taskIds == null || CollectionUtils.isEmpty(taskIds)) {
// 列表为空或解析失败,删除整个 key
redisCache.deleteObject(taskIdsKey);
log.debug("taskId 列表为空或解析失败,删除 key:{}", taskIdsKey);
return;
}
// 删除指定的 taskId
boolean removed = taskIds.remove(taskId);
if (!removed) {
log.debug("taskId {} 不在列表中,key:{}", taskId, taskIdsKey);
return;
}
// 如果列表为空,删除整个 key;否则更新列表
if (taskIds.isEmpty()) {
redisCache.deleteObject(taskIdsKey);
log.debug("上电触发任务列表已清空,删除 key:{}", taskIdsKey);
} else {
// 更新列表,保持过期时间
redisCache.setCacheObject(taskIdsKey, JSON.toJSONString(taskIds), 600, TimeUnit.SECONDS);
log.debug("从列表中删除 taskId:{},剩余任务数:{}", taskId, taskIds.size());
}
} catch (Exception e) {
log.error("从 taskId 列表中删除任务失败,key:{},taskId:{}", taskIdsKey, taskId, e);
}
}
}

View File

@ -47,10 +47,4 @@ public interface IDeviceReportMessageService {
*/ */
IProtocol selectedProtocol(Long productId); IProtocol selectedProtocol(Long productId);
/**
* 异步检查并触发设备上电任务(在收到第一个数据包时)
* @param serialNumber 设备编号
*/
void checkAndTriggerPowerOnTaskAsync(String serialNumber);
} }

View File

@ -19,6 +19,7 @@ import com.fastbee.common.utils.DateUtils;
import com.fastbee.common.utils.StringUtils; import com.fastbee.common.utils.StringUtils;
import com.fastbee.common.utils.gateway.mq.TopicsUtils; import com.fastbee.common.utils.gateway.mq.TopicsUtils;
import com.fastbee.iot.cache.IOtaTaskCache; import com.fastbee.iot.cache.IOtaTaskCache;
import com.fastbee.iot.data.executor.PowerOnTriggerTaskExecutor;
import com.fastbee.iot.data.service.IDataHandler; import com.fastbee.iot.data.service.IDataHandler;
import com.fastbee.iot.data.service.IDeviceReportMessageService; import com.fastbee.iot.data.service.IDeviceReportMessageService;
import com.fastbee.iot.data.service.IFirmwareTaskDetailService; import com.fastbee.iot.data.service.IFirmwareTaskDetailService;
@ -83,9 +84,9 @@ public class DeviceReportMessageServiceImpl implements IDeviceReportMessageServi
private TopicsUtils topicsUtils; private TopicsUtils topicsUtils;
@Resource @Resource
private MqttRemoteManager remoteManager; private MqttRemoteManager remoteManager;
@Resource @Resource
private ApplicationContext applicationContext; // self注入,用于异步调用 private PowerOnTriggerTaskExecutor powerOnTriggerTaskExecutor;
/** /**
* 处理设备主动上报数据 * 处理设备主动上报数据
@ -93,17 +94,16 @@ public class DeviceReportMessageServiceImpl implements IDeviceReportMessageServi
@Override @Override
public void parseReportMsg(DeviceReportBo bo) { public void parseReportMsg(DeviceReportBo bo) {
String serialNumber = bo.getSerialNumber(); String serialNumber = bo.getSerialNumber();
Long productId = bo.getProductId();
// 异步检查是否需要触发上电任务(收到第一个数据包时触发) // 异步检查是否需要触发上电任务(收到第一个数据包时触发)
DeviceReportMessageServiceImpl bean = applicationContext.getBean(DeviceReportMessageServiceImpl.class); powerOnTriggerTaskExecutor.checkAndTriggerPowerOnTaskAsync(serialNumber, productId);
bean.checkAndTriggerPowerOnTaskAsync(serialNumber);
switch (bo.getServerType()) { switch (bo.getServerType()) {
case MQTT: case MQTT:
log.debug("=>MQ*收到设备主题[{}],消息:[{}]", bo.getTopicName(), bo.getData()); log.debug("=>MQ*收到设备主题[{}],消息:[{}]", bo.getTopicName(), bo.getData());
//构建消息 //构建消息
DeviceStatusVO deviceStatusVO = buildReport(bo); DeviceStatusVO deviceStatusVO = buildReport(bo);
Long productId = bo.getProductId();
/*获取协议处理器*/ /*获取协议处理器*/
IProtocol protocol = selectedProtocol(productId); IProtocol protocol = selectedProtocol(productId);
DeviceData data = DeviceData.builder() DeviceData data = DeviceData.builder()
@ -245,43 +245,6 @@ public class DeviceReportMessageServiceImpl implements IDeviceReportMessageServi
} }
} }
/**
* 异步检查并触发设备上电任务(在收到第一个数据包时)
* @param serialNumber 设备编号
*/
@Async(FastBeeConstant.TASK.TRIGGER_POWER_ON_TASK_ASYNC)
public void checkAndTriggerPowerOnTaskAsync(String serialNumber) {
try {
if (StringUtils.isEmpty(serialNumber)) {
return;
}
String cacheKey = RedisKeyBuilder.buildPowerOnTriggerPendingKey(serialNumber.toUpperCase());
// 检查是否有待触发的上电任务标记
Object pendingFlag = redisCache.getCacheObject(cacheKey);
if (pendingFlag == null) {
// 没有待触发的任务,直接返回
return;
}
// 有待触发的任务,删除标记(确保只触发一次)
redisCache.deleteObject(cacheKey);
// 获取设备信息
Device device = deviceService.selectDeviceBySerialNumber(serialNumber);
if (device == null) {
log.warn("触发上电任务时设备不存在,设备编号:{}", serialNumber);
return;
}
// 触发上电任务(异步执行)
log.info("收到设备第一个数据包,触发上电任务,设备编号:{}", serialNumber);
deviceService.triggerPowerOnTasks(device);
} catch (Exception e) {
log.error("检查并触发设备上电任务异常,设备编号:{}", serialNumber, e);
} }
/** /**
* 构建消息 * 构建消息
* *

View File

@ -44,7 +44,7 @@ public class MessageDecoderWrapper extends ChannelInboundHandlerAdapter {
/*将当前readerIndex增加指定的长度,已经解码完成*/ /*将当前readerIndex增加指定的长度,已经解码完成*/
input.skipBytes(input.readableBytes()); input.skipBytes(input.readableBytes());
} catch (Exception e) { } catch (Exception e) {
log.error("消息解码异常[" + ByteBufUtil.hexDump(input, 0, input.writerIndex()) + "]", e); log.error("消息解码异常[{}]", ByteBufUtil.hexDump(input, 0, input.writerIndex()), e);
throw new DecoderException(e); throw new DecoderException(e);
} finally { } finally {
input.release(); input.release();

View File

@ -185,18 +185,6 @@ public interface IDeviceService {
*/ */
public int updateDeviceStatusAndLocation(Device device, String ipAddress); public int updateDeviceStatusAndLocation(Device device, String ipAddress);
/**
* 标记设备上电任务待处理
* @param device 设备信息
*/
void markPowerOnTriggerPending(Device device);
/**
* 触发设备上电任务(供外部调用,如收到第一个数据包时)
* @param device 设备信息
*/
public void triggerPowerOnTasks(Device device);
/** /**
* 更新设备状态 * 更新设备状态
* *

View File

@ -149,9 +149,6 @@ public class DeviceServiceImpl implements IDeviceService {
@Resource @Resource
private IModbusJobService modbusJobService; private IModbusJobService modbusJobService;
@Resource @Resource
@Lazy
private IDeviceJobService deviceJobService;
@Resource
private ProductSubGatewayMapper productSubGatewayMapper; private ProductSubGatewayMapper productSubGatewayMapper;
@Resource @Resource
private ModbusParamsMapper modbusParamsMapper; private ModbusParamsMapper modbusParamsMapper;
@ -1373,201 +1370,18 @@ public class DeviceServiceImpl implements IDeviceService {
// 设备上线时,标记需要触发上电任务(等待第一个数据包) // 设备上线时,标记需要触发上电任务(等待第一个数据包)
if (device.getStatus() == 3) { if (device.getStatus() == 3) {
DeviceServiceImpl bean = applicationContext.getBean(DeviceServiceImpl.class); try {
bean.markPowerOnTriggerPending(device); Object executor = applicationContext.getBean("powerOnTriggerTaskExecutor");
Method executeMethod = executor.getClass().getMethod("markPowerOnTriggerPending", Device.class);
executeMethod.invoke(executor, device);
} catch (Exception e) {
log.error("标记需要触发上电任务失败,设备编号:{}", device.getSerialNumber(), e);
}
} }
return result; return result;
} }
/**
* 标记设备需要触发上电任务(等待第一个数据包)
* @param device 设备信息
*/
@Async(FastBeeConstant.TASK.TRIGGER_POWER_ON_TASK_ASYNC)
@Override
public void markPowerOnTriggerPending(Device device) {
try {
// 查询该设备的所有设备上电触发任务
DeviceJob queryJob = new DeviceJob();
queryJob.setDeviceId(device.getDeviceId());
queryJob.setJobType(5); // modbus轮询任务
queryJob.setStatus("0"); // 正常状态
List<DeviceJob> jobList = deviceJobService.selectJobList(queryJob);
if (CollectionUtils.isEmpty(jobList)) {
return;
}
// 检查是否有上电触发任务
boolean hasPowerOnTrigger = false;
for (DeviceJob job : jobList) {
if (isPowerOnTrigger(job.getRemark())) {
hasPowerOnTrigger = true;
break;
}
}
// 如果有上电触发任务,则在Redis中标记,等待第一个数据包
// 无论标记是否存在,都重新设置标记,确保恢复任务后能重新触发
String serialNumber = device.getSerialNumber().toUpperCase();
String cacheKey = RedisKeyBuilder.buildPowerOnTriggerPendingKey(serialNumber);
if (hasPowerOnTrigger) {
// 设置过期时间为10分钟,防止长时间未收到数据包导致标记一直存在
// 无论之前是否有标记,都重新设置,确保恢复任务后能重新触发
redisCache.setCacheObject(cacheKey, "1", 600, TimeUnit.SECONDS);
log.info("标记设备需要触发上电任务,等待第一个数据包,设备编号:{}", serialNumber);
} else {
// 如果没有上电触发任务,清除可能存在的旧标记
if (redisCache.hasKey(cacheKey)) {
redisCache.deleteObject(cacheKey);
log.debug("清除设备上电触发任务标记,设备编号:{}", serialNumber);
}
}
} catch (Exception e) {
log.error("标记设备上电触发任务异常,设备编号:{}", device.getSerialNumber(), e);
}
}
/**
* 触发设备上电任务(收到第一个数据包时)
* @param device 设备信息
*/
public void triggerPowerOnTasks(Device device) {
try {
// 查询该设备的所有设备上电触发任务
DeviceJob queryJob = new DeviceJob();
queryJob.setDeviceId(device.getDeviceId());
queryJob.setJobType(5); // modbus轮询任务
// 只查询正常状态的任务(如果用户手动改成正常,下次上电会再次触发)
queryJob.setStatus("0"); // 正常状态("0"=正常,"1"=暂停)
List<DeviceJob> jobList = deviceJobService.selectJobList(queryJob);
if (CollectionUtils.isEmpty(jobList)) {
return;
}
// 查找设备上电触发任务(状态为正常即可,不需要检查sent状态)
for (DeviceJob job : jobList) {
if (isPowerOnTrigger(job.getRemark())) {
// 执行设备上电触发任务
executePowerOnTriggerTask(job, device);
}
}
} catch (Exception e) {
log.error("处理设备上电触发任务异常,设备编号:{}", device.getSerialNumber(), e);
}
}
/**
* 检查是否是设备上电触发任务
*
* @param remark remark字段
* @return true表示是设备上电触发任务
*/
private boolean isPowerOnTrigger(String remark) {
if (remark == null || remark.isEmpty()) {
return false;
}
return remark.contains("power_on_trigger");
}
/**
* 执行设备上电触发任务
*
* @param deviceJob 设备任务
* @param device 设备信息
*/
private void executePowerOnTriggerTask(DeviceJob deviceJob, Device device) {
try {
// 查询对应的ModbusJob
ModbusJob queryJob = new ModbusJob();
queryJob.setJobId(deviceJob.getJobId());
List<ModbusJob> modbusJobList = modbusJobService.selectModbusJobList(queryJob);
if (CollectionUtils.isEmpty(modbusJobList)) {
return;
}
// 执行所有相关的modbus任务
boolean hasActiveTask = false;
// 记录设备上电触发任务下发的指令片段(索引2到11位)
String serialNumber = device.getSerialNumber().toUpperCase();
String cacheKey = RedisKeyBuilder.buildPowerOnTriggerCommandKey(serialNumber);
for (ModbusJob modbusJob : modbusJobList) {
if ("0".equals(modbusJob.getStatus())) { // 只执行正常状态的任务
hasActiveTask = true;
// 记录下发的指令片段(索引2到11位,即第3到第12个字符)
String command = modbusJob.getCommand();
if (StringUtils.isNotEmpty(command) && command.length() >= 12) {
// 去除空格并转为大写
String cleanCommand = command.replaceAll("\\s+", "").toUpperCase();
if (cleanCommand.length() >= 12) {
// 提取索引2到11位(即第3到第12个字符)
String commandSegment = cleanCommand.substring(2, 12);
// 存储到Redis缓存,设置过期时间为5分钟(设备上报应该在5分钟内完成)
redisCache.setCacheObject(cacheKey, commandSegment, 300, TimeUnit.SECONDS);
log.info("记录设备上电触发任务下发指令片段,设备编号:{},指令片段:{}", serialNumber, commandSegment);
}
}
}
}
if (hasActiveTask) {
// 通过反射调用DeviceJobInvoke.invokeMethod来执行任务
try {
Class<?> clazz = Class.forName("com.fastbee.iot.data.job.DeviceJobInvoke");
Method method = clazz.getMethod("invokeMethod", DeviceJob.class);
method.invoke(null, deviceJob);
log.info("设备上电触发任务执行成功,设备编号:{},任务ID:{}", device.getSerialNumber(), deviceJob.getJobId());
// 执行成功后,将任务状态改为暂停
updatePowerOnTriggerTaskStatus(modbusJobList);
} catch (Exception e) {
log.error("执行设备上电触发任务异常,设备编号:{},任务ID:{}", device.getSerialNumber(), deviceJob.getJobId(), e);
}
}
} catch (Exception e) {
log.error("处理设备上电触发任务异常,设备编号:{},任务ID:{}", device.getSerialNumber(), deviceJob.getJobId(), e);
}
}
/**
* 更新设备上电触发任务状态为暂停
*
* @param modbusJobList 设备任务
*/
private void updatePowerOnTriggerTaskStatus(List<ModbusJob> modbusJobList) {
if (CollectionUtils.isEmpty(modbusJobList)) {
return;
}
log.info("更新设备上电触发任务状态为暂停,任务数量:{}", modbusJobList.size());
java.util.Date executeTime = DateUtils.getNowDate();
modbusJobList.forEach(modbusJob -> {
// 只更新status=0的任务为status=1,如果已经是status=1则跳过
if (!"0".equals(modbusJob.getStatus())) {
log.debug("任务ID:{}的状态已经是{},跳过更新", modbusJob.getTaskId(), modbusJob.getStatus());
return;
}
ModbusJob job = new ModbusJob();
job.setStatus("1");
job.setTaskId(modbusJob.getTaskId());
// 记录执行时间
job.setExecuteTime(executeTime);
try {
// 将任务状态改为暂停("1"=暂停,"0"=正常),并记录执行时间
modbusJobService.updateModbusJob(job);
log.info("设备上电触发任务已设置为暂停状态并记录执行时间,任务ID:{},执行时间:{}", modbusJob.getTaskId(), executeTime);
} catch (Exception e) {
log.error("更新设备上电触发任务状态异常,任务ID:{}", modbusJob.getTaskId(), e);
}
});
}
/** /**
* @param device 设备状态 * @param device 设备状态
* @return 结果 * @return 结果
@ -2130,7 +1944,7 @@ public class DeviceServiceImpl implements IDeviceService {
/** /**
* 判断是否是设备上电触发的回包 * 判断是否是设备上电触发的回包
* 从Redis缓存中获取下发的指令片段(索引2到11位),与上报的指令片段进行比较 * 从Redis缓存中获取下发的指令片段(索引2到3位,2个字符),与上报的指令片段进行比较
* *
* @param sources 原始消息数据包 * @param sources 原始消息数据包
* @param deviceNumber 设备编号 * @param deviceNumber 设备编号
@ -2142,13 +1956,36 @@ public class DeviceServiceImpl implements IDeviceService {
} }
try { try {
// 从Redis缓存中获取下发的指令片段 // 从Redis缓存中获取下发的指令片段(可能是单个片段或片段集合)
String serialNumber = deviceNumber.toUpperCase(); String serialNumber = deviceNumber.toUpperCase();
String cacheKey = RedisKeyBuilder.buildPowerOnTriggerCommandKey(serialNumber); String cacheKey = RedisKeyBuilder.buildPowerOnTriggerCommandKey(serialNumber);
String sentCommandSegment = redisCache.getCacheObject(cacheKey); Object cachedSegments = redisCache.getCacheObject(cacheKey);
// 如果缓存中没有下发的指令片段,说明不是设备上电触发的回包 // 如果缓存中没有下发的指令片段,说明不是设备上电触发的回包
if (StringUtils.isEmpty(sentCommandSegment)) { if (cachedSegments == null) {
return false;
}
// 解析指令片段(可能是单个字符串或JSON数组)
Set<String> sentCommandSegments = new HashSet<>();
try {
if (cachedSegments instanceof String) {
String segmentsStr = (String) cachedSegments;
// 尝试解析为JSON数组
if (segmentsStr.startsWith("[")) {
List<String> segmentsList = JSON.parseArray(segmentsStr, String.class);
sentCommandSegments.addAll(segmentsList);
} else {
// 单个片段(兼容旧数据)
sentCommandSegments.add(segmentsStr);
}
}
} catch (Exception e) {
log.warn("解析指令片段失败,设备编号:{}", serialNumber, e);
return false;
}
if (sentCommandSegments.isEmpty()) {
return false; return false;
} }
@ -2172,15 +2009,16 @@ public class DeviceServiceImpl implements IDeviceService {
hexStr = sources.toString().replaceAll("\\s+", "").toUpperCase(); hexStr = sources.toString().replaceAll("\\s+", "").toUpperCase();
} }
if (hexStr.length() < 12) { // 至少需要4个字符才能提取索引2到3位
if (hexStr.length() < 4) {
return false; return false;
} }
// 提取上报指令的索引2到11位(即第3到第12个字符) // 提取上报指令的索引2到3位(即第3到第4个字符,2个字符)
String actualSegment = hexStr.substring(2, 12); String actualSegment = hexStr.substring(2, 4);
// 与下发的指令片段进行比较 // 与下发的指令片段集合进行比较(支持多个指令)
boolean isMatch = sentCommandSegment.equals(actualSegment); boolean isMatch = sentCommandSegments.contains(actualSegment);
if (isMatch) { if (isMatch) {
// 匹配成功,删除缓存(避免重复匹配) // 匹配成功,删除缓存(避免重复匹配)
@ -2350,3 +2188,4 @@ public class DeviceServiceImpl implements IDeviceService {
} }
} }

View File

@ -106,6 +106,15 @@ public class ModbusJobServiceImpl implements IModbusJobService {
@Override @Override
public int insertModbusJob(ModbusJob modbusJob) { public int insertModbusJob(ModbusJob modbusJob) {
try { try {
// 自动填充 cycleType:如果前端没有传递,则根据 remark 判断
if (modbusJob.getCycleType() == null) {
if (isPowerOnTrigger(modbusJob.getRemark())) {
modbusJob.setCycleType(3); // 设备上电触发
} else {
modbusJob.setCycleType(1); // 周期循环(默认)
}
}
//查询关联的网关设备 //查询关联的网关设备
DeviceAndProtocol deviceAndProtocol = deviceService.selectProtocolBySerialNumber(modbusJob.getSubSerialNumber()); DeviceAndProtocol deviceAndProtocol = deviceService.selectProtocolBySerialNumber(modbusJob.getSubSerialNumber());
//如果是网关子设备 //如果是网关子设备
@ -202,6 +211,15 @@ public class ModbusJobServiceImpl implements IModbusJobService {
*/ */
@Override @Override
public int updateModbusJob(ModbusJob modbusJob) { public int updateModbusJob(ModbusJob modbusJob) {
// 自动填充 cycleType:如果前端没有传递,则根据 remark 判断
if (modbusJob.getCycleType() == null && modbusJob.getRemark() != null) {
if (isPowerOnTrigger(modbusJob.getRemark())) {
modbusJob.setCycleType(3); // 设备上电触发
} else {
modbusJob.setCycleType(1); // 周期循环(默认)
}
}
// 如果状态从1(暂停)改为0(正常),需要清空执行时间 // 如果状态从1(暂停)改为0(正常),需要清空执行时间
if (modbusJob.getTaskId() != null && modbusJob.getStatus() != null && "0".equals(modbusJob.getStatus())) { if (modbusJob.getTaskId() != null && modbusJob.getStatus() != null && "0".equals(modbusJob.getStatus())) {
ModbusJob existingJob = modbusJobMapper.selectModbusJobByTaskId(modbusJob.getTaskId()); ModbusJob existingJob = modbusJobMapper.selectModbusJobByTaskId(modbusJob.getTaskId());

View File

@ -17,10 +17,11 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<result property="remark" column="remark" /> <result property="remark" column="remark" />
<result property="deviceType" column="device_type" /> <result property="deviceType" column="device_type" />
<result property="executeTime" column="execute_time" /> <result property="executeTime" column="execute_time" />
<result property="cycleType" column="cycle_type" />
</resultMap> </resultMap>
<sql id="selectModbusJobVo"> <sql id="selectModbusJobVo">
select task_id,job_name, sub_device_id, sub_serial_number, command,device_type, job_id, status, create_by, create_time, remark, execute_time from iot_modbus_job select task_id,job_name, sub_device_id, sub_serial_number, command,device_type, job_id, status, create_by, create_time, remark, execute_time, cycle_type from iot_modbus_job
</sql> </sql>
<select id="selectModbusJobList" parameterType="com.fastbee.iot.domain.ModbusJob" resultMap="ModbusJobResult"> <select id="selectModbusJobList" parameterType="com.fastbee.iot.domain.ModbusJob" resultMap="ModbusJobResult">
@ -29,6 +30,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="subDeviceId != null "> and sub_device_id = #{subDeviceId}</if> <if test="subDeviceId != null "> and sub_device_id = #{subDeviceId}</if>
<if test="subSerialNumber != null and subSerialNumber != ''"> and sub_serial_number = #{subSerialNumber}</if> <if test="subSerialNumber != null and subSerialNumber != ''"> and sub_serial_number = #{subSerialNumber}</if>
<if test="status != null "> and status = #{status}</if> <if test="status != null "> and status = #{status}</if>
<if test="cycleType != null "> and cycle_type = #{cycleType}</if>
<if test="jobId != null">and job_id = #{jobId}</if> <if test="jobId != null">and job_id = #{jobId}</if>
<if test="subDeviceList != null and subDeviceList != ''"> <if test="subDeviceList != null and subDeviceList != ''">
and sub_serial_number in and sub_serial_number in
@ -58,6 +60,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="remark != null">remark,</if> <if test="remark != null">remark,</if>
<if test="deviceType != null">device_type,</if> <if test="deviceType != null">device_type,</if>
<if test="executeTime != null">execute_time,</if> <if test="executeTime != null">execute_time,</if>
<if test="cycleType != null">cycle_type,</if>
</trim> </trim>
<trim prefix="values (" suffix=")" suffixOverrides=","> <trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="jobName != null">#{jobName},</if> <if test="jobName != null">#{jobName},</if>
@ -71,6 +74,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="remark != null">#{remark},</if> <if test="remark != null">#{remark},</if>
<if test="deviceType != null">#{deviceType},</if> <if test="deviceType != null">#{deviceType},</if>
<if test="executeTime != null">#{executeTime},</if> <if test="executeTime != null">#{executeTime},</if>
<if test="cycleType != null">#{cycleType},</if>
</trim> </trim>
</insert> </insert>
@ -87,6 +91,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="createTime != null">create_time = #{createTime},</if> <if test="createTime != null">create_time = #{createTime},</if>
<if test="remark != null">remark = #{remark},</if> <if test="remark != null">remark = #{remark},</if>
<if test="executeTime != null">execute_time = #{executeTime},</if> <if test="executeTime != null">execute_time = #{executeTime},</if>
<if test="cycleType != null">cycle_type = #{cycleType},</if>
</trim> </trim>
where task_id = #{taskId} where task_id = #{taskId}
</update> </update>

View File

@ -619,12 +619,14 @@ CREATE TABLE `iot_modbus_job`
`sub_device_id` bigint(20) NOT NULL COMMENT '子设备id', `sub_device_id` bigint(20) NOT NULL COMMENT '子设备id',
`sub_serial_number` varchar(64) COLLATE utf8_unicode_ci NOT NULL COMMENT '子设备编号', `sub_serial_number` varchar(64) COLLATE utf8_unicode_ci NOT NULL COMMENT '子设备编号',
`device_type` tinyint(1) DEFAULT NULL COMMENT '设备类型', `device_type` tinyint(1) DEFAULT NULL COMMENT '设备类型',
`cycle_type` int(2) DEFAULT NULL COMMENT '时间周期类型(1-周期循环,2-自定义时间段,3-设备上电触发)',
`command` varchar(255) COLLATE utf8_unicode_ci NOT NULL COMMENT '指令', `command` varchar(255) COLLATE utf8_unicode_ci NOT NULL COMMENT '指令',
`job_id` bigint(20) NOT NULL COMMENT '任务id', `job_id` bigint(20) NOT NULL COMMENT '任务id',
`status` char(1) COLLATE utf8_unicode_ci NOT NULL COMMENT '状态(0正常 1暂停)', `status` char(1) COLLATE utf8_unicode_ci NOT NULL COMMENT '状态(0正常 1暂停)',
`create_by` varchar(64) CHARACTER SET utf8mb4 DEFAULT '' COMMENT '创建者', `create_by` varchar(64) CHARACTER SET utf8mb4 DEFAULT '' COMMENT '创建者',
`create_time` datetime DEFAULT NULL COMMENT '创建时间', `create_time` datetime DEFAULT NULL COMMENT '创建时间',
`remark` varchar(500) CHARACTER SET utf8mb4 DEFAULT '' COMMENT '备注信息', `remark` varchar(500) CHARACTER SET utf8mb4 DEFAULT '' COMMENT '备注信息',
`execute_time` datetime DEFAULT NULL COMMENT '执行时间(仅设备上电触发任务使用,记录任务执行的时间)',
PRIMARY KEY (`task_id`) USING BTREE PRIMARY KEY (`task_id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci ROW_FORMAT=DYNAMIC COMMENT='轮训任务列表'; ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci ROW_FORMAT=DYNAMIC COMMENT='轮训任务列表';

View File

@ -11,7 +11,10 @@
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item :label="$t('dataCenter.history.384934-3')" prop="identifiers"> <el-form-item :label="$t('dataCenter.history.384934-3')" prop="identifiers">
<el-select style="width: 100%" v-model="devQueryParams.identifiers" :placeholder="$t('dataCenter.history.384934-4')" filterable multiple collapse-tags> <el-select style="width: 100%" v-model="devQueryParams.identifiers" :placeholder="$t('dataCenter.history.384934-4')" filterable multiple collapse-tags @change="handleDevIdentifierChange">
<el-option label="全部勾选" value="__selectAll__">
<span style="font-weight: bold; color: #409EFF;">全部勾选</span>
</el-option>
<el-option v-for="(item, index) in devIdentifierList" :key="index" :label="item.modelName" :value="item.identifier"></el-option> <el-option v-for="(item, index) in devIdentifierList" :key="index" :label="item.modelName" :value="item.identifier"></el-option>
</el-select> </el-select>
</el-form-item> </el-form-item>
@ -76,7 +79,10 @@
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item :label="$t('dataCenter.history.384934-3')" prop="identifiers"> <el-form-item :label="$t('dataCenter.history.384934-3')" prop="identifiers">
<el-select style="width: 100%" v-model="sceneQueryParams.identifiers" :placeholder="$t('dataCenter.history.384934-4')" multiple collapse-tags> <el-select style="width: 100%" v-model="sceneQueryParams.identifiers" :placeholder="$t('dataCenter.history.384934-4')" multiple collapse-tags @change="handleSceneIdentifierChange">
<el-option label="全部勾选" value="__selectAll__">
<span style="font-weight: bold; color: #409EFF;">全部勾选</span>
</el-option>
<el-option v-for="(item, index) in sceneIdentifierList" :key="index" :label="item.sourceName" :value="item.identifier"></el-option> <el-option v-for="(item, index) in sceneIdentifierList" :key="index" :label="item.sourceName" :value="item.identifier"></el-option>
</el-select> </el-select>
</el-form-item> </el-form-item>
@ -284,6 +290,17 @@ export default {
this.devQueryParams.identifiers = []; this.devQueryParams.identifiers = [];
this.getDevIdentifierList(val); this.getDevIdentifierList(val);
}, },
// 设备变量选择变化
handleDevIdentifierChange(val) {
// 如果选择了"全部勾选",则选中所有变量
const index = val.indexOf('__selectAll__');
if (index > -1) {
const allIdentifiers = this.devIdentifierList.map(item => item.identifier);
this.$nextTick(() => {
this.devQueryParams.identifiers = [...allIdentifiers];
});
}
},
getDevIdentifierList(deviceId) { getDevIdentifierList(deviceId) {
const params = { const params = {
deviceId: deviceId, deviceId: deviceId,
@ -379,6 +396,17 @@ export default {
this.sceneQueryParams.identifiers = []; this.sceneQueryParams.identifiers = [];
this.getSceneIdentifierList(val); this.getSceneIdentifierList(val);
}, },
// 场景变量选择变化
handleSceneIdentifierChange(val) {
// 如果选择了"全部勾选",则选中所有变量
const index = val.indexOf('__selectAll__');
if (index > -1) {
const allIdentifiers = this.sceneIdentifierList.map(item => item.identifier);
this.$nextTick(() => {
this.sceneQueryParams.identifiers = [...allIdentifiers];
});
}
},
// 获取变量情况列表 // 获取变量情况列表
getSceneIdentifierList(sceneModelDeviceId) { getSceneIdentifierList(sceneModelDeviceId) {
const params = { const params = {