20251223:物模型支持计算(以另一个物模型上报数据计算得出)
This commit is contained in:
parent
2c44850c38
commit
898687ea09
@ -127,7 +127,7 @@ emqx:
|
|||||||
ApiSecret: emqx.api.123
|
ApiSecret: emqx.api.123
|
||||||
# sip 配置
|
# sip 配置
|
||||||
sip:
|
sip:
|
||||||
enabled: true # 是否启用视频监控SIP,true为启用
|
enabled: false # 是否启用视频监控SIP,true为启用
|
||||||
## 本地调试时,绑定网卡局域网IP,设备在同一局域网,设备接入IP填写绑定IP
|
## 本地调试时,绑定网卡局域网IP,设备在同一局域网,设备接入IP填写绑定IP
|
||||||
## 部署服务端时,默认绑定容器IP,设备接入IP填写服务器公网IP
|
## 部署服务端时,默认绑定容器IP,设备接入IP填写服务器公网IP
|
||||||
ip: 192.168.110.78 # 本服务启动的 IP
|
ip: 192.168.110.78 # 本服务启动的 IP
|
||||||
|
|||||||
@ -275,6 +275,11 @@ public interface FastBeeConstant {
|
|||||||
*/
|
*/
|
||||||
String DEVICE_OTA_DATA = "device:ota:";
|
String DEVICE_OTA_DATA = "device:ota:";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设备上电触发指令缓存
|
||||||
|
*/
|
||||||
|
String POWER_ON_TRIGGER_COMMAND = "power:on:trigger:command:";
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface PROTOCOL {
|
interface PROTOCOL {
|
||||||
|
|||||||
@ -134,5 +134,13 @@ public class RedisKeyBuilder {
|
|||||||
return CacheConstants.DEVICE_OTA_DATA + serialNumber;
|
return CacheConstants.DEVICE_OTA_DATA + serialNumber;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设备上电触发指令缓存key
|
||||||
|
* 用于存储设备上电触发任务下发的指令片段(索引2到11位)
|
||||||
|
*/
|
||||||
|
public static String buildPowerOnTriggerCommandKey(String serialNumber){
|
||||||
|
return FastBeeConstant.REDIS.POWER_ON_TRIGGER_COMMAND + serialNumber.toUpperCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -22,6 +22,9 @@ public class ThingsModelValuesInput
|
|||||||
/** 设备物模型值的集合 **/
|
/** 设备物模型值的集合 **/
|
||||||
private List<ThingsModelSimpleItem> thingsModelSimpleItem;
|
private List<ThingsModelSimpleItem> thingsModelSimpleItem;
|
||||||
|
|
||||||
|
/** 原始消息数据包(用于判断设备上电触发回包等) **/
|
||||||
|
private Object sources;
|
||||||
|
|
||||||
public Long getDeviceId() {
|
public Long getDeviceId() {
|
||||||
return deviceId;
|
return deviceId;
|
||||||
}
|
}
|
||||||
@ -53,4 +56,12 @@ public class ThingsModelValuesInput
|
|||||||
public void setThingsModelValueRemarkItem(List<ThingsModelSimpleItem> thingsModelSimpleItem) {
|
public void setThingsModelValueRemarkItem(List<ThingsModelSimpleItem> thingsModelSimpleItem) {
|
||||||
this.thingsModelSimpleItem = thingsModelSimpleItem;
|
this.thingsModelSimpleItem = thingsModelSimpleItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Object getSources() {
|
||||||
|
return sources;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSources(Object sources) {
|
||||||
|
this.sources = sources;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -19,9 +19,9 @@ public class CaculateUtils {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* /*
|
* /*
|
||||||
* 暂时只支持加减乘除及括号的应用
|
* 支持加减乘除、幂运算、取余及括号的应用
|
||||||
*/
|
*/
|
||||||
private static final String symbol = "+-,*/,(),%";
|
private static final String symbol = "+-,*/,(),%^";
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -105,6 +105,10 @@ public class CaculateUtils {
|
|||||||
}
|
}
|
||||||
int length = b.toString().split("\\.")[1].length();
|
int length = b.toString().split("\\.")[1].length();
|
||||||
return a.divide(b, length, BigDecimal.ROUND_HALF_UP);
|
return a.divide(b, length, BigDecimal.ROUND_HALF_UP);
|
||||||
|
case '^':
|
||||||
|
// 幂运算:a的b次方
|
||||||
|
double result = Math.pow(a.doubleValue(), b.doubleValue());
|
||||||
|
return BigDecimal.valueOf(result).stripTrailingZeros();
|
||||||
default:
|
default:
|
||||||
throw new RuntimeException("操作符号异常!");
|
throw new RuntimeException("操作符号异常!");
|
||||||
}
|
}
|
||||||
@ -212,7 +216,19 @@ public class CaculateUtils {
|
|||||||
|
|
||||||
public static String caculateReplace(String str, Map<String, String> map) {
|
public static String caculateReplace(String str, Map<String, String> map) {
|
||||||
for (Map.Entry<String, String> entry : map.entrySet()) {
|
for (Map.Entry<String, String> entry : map.entrySet()) {
|
||||||
str = str.replaceAll(entry.getKey(), entry.getValue()==null ? "1" : entry.getValue());
|
// 对于包含特殊字符的占位符(如 {identifier}),需要转义正则表达式特殊字符
|
||||||
|
String key = entry.getKey();
|
||||||
|
String value = entry.getValue() == null ? "1" : entry.getValue();
|
||||||
|
// 如果占位符包含正则表达式特殊字符,使用 Pattern.quote() 转义
|
||||||
|
if (key.contains("{") || key.contains("}") || key.contains("(") || key.contains(")")
|
||||||
|
|| key.contains("[") || key.contains("]") || key.contains("*") || key.contains("+")
|
||||||
|
|| key.contains("?") || key.contains("^") || key.contains("$") || key.contains("|")) {
|
||||||
|
// 转义正则表达式特殊字符
|
||||||
|
String escapedKey = java.util.regex.Pattern.quote(key);
|
||||||
|
str = str.replaceAll(escapedKey, value);
|
||||||
|
} else {
|
||||||
|
str = str.replaceAll(key, value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return str;
|
return str;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -53,7 +53,10 @@ public class TraceIdUtils {
|
|||||||
public static long generateTraceId() {
|
public static long generateTraceId() {
|
||||||
long id = getIdWorker().nextId();
|
long id = getIdWorker().nextId();
|
||||||
// 使用绝对值确保TraceId为正数,避免负数显示
|
// 使用绝对值确保TraceId为正数,避免负数显示
|
||||||
|
// 注意:Math.abs(id) * 1000 可能会溢出,所以需要对最终结果再次使用Math.abs()
|
||||||
long traceId = Math.abs(id) * 1000 + ThreadLocalRandom.current().nextInt(1000);
|
long traceId = Math.abs(id) * 1000 + ThreadLocalRandom.current().nextInt(1000);
|
||||||
|
// 确保最终结果为非负数(处理溢出情况)
|
||||||
|
traceId = Math.abs(traceId);
|
||||||
MDC.put(TRACE_ID_KEY, String.valueOf(traceId));
|
MDC.put(TRACE_ID_KEY, String.valueOf(traceId));
|
||||||
return traceId;
|
return traceId;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -54,7 +54,8 @@ public class IdUtils
|
|||||||
}
|
}
|
||||||
|
|
||||||
if(lastTimestamp == timestamp) {
|
if(lastTimestamp == timestamp) {
|
||||||
this.sequence = this.sequence + 1L & 4095L;
|
// 修复:使用括号确保正确的运算顺序
|
||||||
|
this.sequence = (this.sequence + 1L) & 4095L;
|
||||||
if(this.sequence == 0L) {
|
if(this.sequence == 0L) {
|
||||||
timestamp = this.tilNextMillis(lastTimestamp);
|
timestamp = this.tilNextMillis(lastTimestamp);
|
||||||
}
|
}
|
||||||
@ -63,7 +64,18 @@ public class IdUtils
|
|||||||
}
|
}
|
||||||
|
|
||||||
lastTimestamp = timestamp;
|
lastTimestamp = timestamp;
|
||||||
return timestamp - 1288834974657L << 22 | this.datacenterId << 17 | this.workerId << 12 | this.sequence;
|
|
||||||
|
// 修复:使用括号确保正确的运算符优先级,避免负数
|
||||||
|
// 原代码:timestamp - 1288834974657L << 22 会被解析为 timestamp - (1288834974657L << 22)
|
||||||
|
// 修复后:((timestamp - 1288834974657L) << 22) 确保先减法后位移
|
||||||
|
long id = ((timestamp - 1288834974657L) << 22)
|
||||||
|
| (this.datacenterId << 17)
|
||||||
|
| (this.workerId << 12)
|
||||||
|
| this.sequence;
|
||||||
|
|
||||||
|
// 确保返回的ID为正数(通过清除符号位)
|
||||||
|
// 如果id为负数,说明计算结果超出了预期,使用绝对值
|
||||||
|
return id < 0 ? Math.abs(id) : id;
|
||||||
}
|
}
|
||||||
|
|
||||||
private long tilNextMillis(long lastTimestamp) {
|
private long tilNextMillis(long lastTimestamp) {
|
||||||
|
|||||||
@ -0,0 +1,122 @@
|
|||||||
|
package com.fastbee.common.utils.uuid;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 系统编码生成器(改进版)
|
||||||
|
* 修复了原版本中可能导致负数的问题
|
||||||
|
*
|
||||||
|
* @author fastbee
|
||||||
|
* @date 2025-01-20
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public class IdWorker {
|
||||||
|
|
||||||
|
private final long workerId;
|
||||||
|
/** 起始时间戳:2015-01-20 00:00:00 */
|
||||||
|
private final static long twepoch = 1421976968066L;
|
||||||
|
private long sequence = 0L;
|
||||||
|
/** 机器ID占用的位数 */
|
||||||
|
private final static long workerIdBits = 2L;
|
||||||
|
/** 支持的最大机器ID */
|
||||||
|
public final static long maxWorkerId = -1L ^ (-1L << workerIdBits);
|
||||||
|
/** 序列号占用的位数 */
|
||||||
|
private final static long sequenceBits = 9L;
|
||||||
|
|
||||||
|
/** 机器ID向左移的位数 */
|
||||||
|
private final static long workerIdShift = sequenceBits;
|
||||||
|
/** 时间戳向左移的位数 */
|
||||||
|
private final static long timestampLeftShift = sequenceBits + workerIdBits;
|
||||||
|
/** 生成序列的掩码 */
|
||||||
|
public final static long sequenceMask = -1L ^ (-1L << sequenceBits);
|
||||||
|
|
||||||
|
private long lastTimestamp = -1L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构造函数
|
||||||
|
*
|
||||||
|
* @param workerId 机器ID (0-3)
|
||||||
|
*/
|
||||||
|
public IdWorker(final long workerId) {
|
||||||
|
super();
|
||||||
|
if (workerId > IdWorker.maxWorkerId || workerId < 0) {
|
||||||
|
throw new IllegalArgumentException(String.format(
|
||||||
|
"worker Id can't be greater than %d or less than 0",
|
||||||
|
IdWorker.maxWorkerId));
|
||||||
|
}
|
||||||
|
this.workerId = workerId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成下一个ID
|
||||||
|
* 修复了原代码中的运算符优先级问题,确保不会产生负数
|
||||||
|
*
|
||||||
|
* @return 生成的ID(始终为正数)
|
||||||
|
*/
|
||||||
|
public synchronized long nextId() {
|
||||||
|
long timestamp = this.timeGen();
|
||||||
|
|
||||||
|
// 时钟回拨检查
|
||||||
|
if (timestamp < this.lastTimestamp) {
|
||||||
|
long offset = this.lastTimestamp - timestamp;
|
||||||
|
log.warn("Clock moved backwards. Refusing to generate id for {} milliseconds", offset);
|
||||||
|
// 时钟回拨时,等待时钟追上
|
||||||
|
timestamp = this.tilNextMillis(this.lastTimestamp);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.lastTimestamp == timestamp) {
|
||||||
|
// 修复:使用括号确保正确的运算顺序
|
||||||
|
// 原代码:this.sequence + 1 & IdWorker.sequenceMask 会被解析为 this.sequence + (1 & IdWorker.sequenceMask)
|
||||||
|
// 修复后:(this.sequence + 1) & IdWorker.sequenceMask 确保先加法后按位与
|
||||||
|
this.sequence = (this.sequence + 1) & IdWorker.sequenceMask;
|
||||||
|
if (this.sequence == 0) {
|
||||||
|
// 序列号溢出,等待下一毫秒
|
||||||
|
timestamp = this.tilNextMillis(this.lastTimestamp);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.sequence = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.lastTimestamp = timestamp;
|
||||||
|
|
||||||
|
// 修复:使用括号确保正确的运算符优先级,避免负数
|
||||||
|
// 原代码:timestamp - twepoch << timestampLeftShift 会被解析为 timestamp - (twepoch << timestampLeftShift)
|
||||||
|
// 修复后:((timestamp - twepoch) << timestampLeftShift) 确保先减法后位移
|
||||||
|
long id = ((timestamp - twepoch) << timestampLeftShift)
|
||||||
|
| (this.workerId << IdWorker.workerIdShift)
|
||||||
|
| (this.sequence);
|
||||||
|
|
||||||
|
// 确保返回的ID为正数
|
||||||
|
// 正常情况下id应该为正数,但如果计算结果为负数,使用绝对值
|
||||||
|
if (id < 0) {
|
||||||
|
log.warn("Generated negative ID: {}, using absolute value", id);
|
||||||
|
return Math.abs(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 等待下一毫秒
|
||||||
|
*
|
||||||
|
* @param lastTimestamp 上次生成ID的时间戳
|
||||||
|
* @return 新的时间戳
|
||||||
|
*/
|
||||||
|
private long tilNextMillis(final long lastTimestamp) {
|
||||||
|
long timestamp = this.timeGen();
|
||||||
|
while (timestamp <= lastTimestamp) {
|
||||||
|
timestamp = this.timeGen();
|
||||||
|
}
|
||||||
|
return timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前时间戳(毫秒)
|
||||||
|
*
|
||||||
|
* @return 当前时间戳
|
||||||
|
*/
|
||||||
|
private long timeGen() {
|
||||||
|
return System.currentTimeMillis();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@ -58,6 +58,15 @@ import java.util.Objects;
|
|||||||
@Slf4j
|
@Slf4j
|
||||||
public class DeviceDebugLogAspect {
|
public class DeviceDebugLogAspect {
|
||||||
|
|
||||||
|
/** 日志值最大长度 */
|
||||||
|
private static final int MAX_LOG_VALUE_LENGTH = 64;
|
||||||
|
/** 备注最大长度 */
|
||||||
|
private static final int MAX_REMARK_LENGTH = 200;
|
||||||
|
/** 错误信息显示最大长度 */
|
||||||
|
private static final int MAX_ERROR_MSG_DISPLAY_LENGTH = 50;
|
||||||
|
/** 其他模式 */
|
||||||
|
private static final int MODE_OTHER = 3;
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
private ILogService logService;
|
private ILogService logService;
|
||||||
|
|
||||||
@ -102,43 +111,21 @@ public class DeviceDebugLogAspect {
|
|||||||
*/
|
*/
|
||||||
@AfterThrowing(pointcut = "parseReportMsgPointcut()", throwing = "e")
|
@AfterThrowing(pointcut = "parseReportMsgPointcut()", throwing = "e")
|
||||||
public void afterThrowingParseReportMsg(JoinPoint joinPoint, Throwable e) {
|
public void afterThrowingParseReportMsg(JoinPoint joinPoint, Throwable e) {
|
||||||
try {
|
handleException(joinPoint, e, (args) -> {
|
||||||
Object[] args = joinPoint.getArgs();
|
if (args[0] instanceof DeviceReportBo) {
|
||||||
if (args != null && args.length > 0 && args[0] instanceof DeviceReportBo) {
|
|
||||||
DeviceReportBo bo = (DeviceReportBo) args[0];
|
DeviceReportBo bo = (DeviceReportBo) args[0];
|
||||||
String serialNumber = bo.getSerialNumber();
|
String serialNumber = bo.getSerialNumber();
|
||||||
if (StringUtils.isEmpty(serialNumber)) {
|
if (StringUtils.isEmpty(serialNumber)) {
|
||||||
log.warn("设备编号为空,跳过错误日志记录");
|
return null;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
String sourceData = extractSourceDataFromReportBo(bo);
|
||||||
// 处理原始数据,可能是byte[]或String
|
String errorMsg = getErrorMessage(e);
|
||||||
String sourceData = "";
|
|
||||||
try {
|
|
||||||
if (bo.getData() != null && bo.getData().length > 0) {
|
|
||||||
sourceData = ByteBufUtil.hexDump(bo.getData());
|
|
||||||
} else if (StringUtils.isNotEmpty(bo.getSources())) {
|
|
||||||
sourceData = bo.getSources();
|
|
||||||
}
|
|
||||||
} catch (Exception ex) {
|
|
||||||
log.warn("处理原始数据失败: {}", ex.getMessage());
|
|
||||||
sourceData = bo.getSources() != null ? bo.getSources() : "";
|
|
||||||
}
|
|
||||||
|
|
||||||
String errorMsg = e.getMessage();
|
|
||||||
if (StringUtils.isEmpty(errorMsg)) {
|
|
||||||
errorMsg = e.getClass().getName();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 直接从bo中获取协议编码,避免额外的查询
|
|
||||||
String protocolCode = bo.getProtocolCode();
|
|
||||||
Long productId = bo.getProductId();
|
Long productId = bo.getProductId();
|
||||||
|
return new ErrorLogContext(serialNumber, sourceData, errorMsg,
|
||||||
saveErrorLog(serialNumber, sourceData, errorMsg, DeviceLogTypeEnum.DATA_PARSE_ERROR.getType(), "DEBUG_ERROR", productId);
|
DeviceLogTypeEnum.DATA_PARSE_ERROR.getType(), "DEBUG_ERROR", productId);
|
||||||
}
|
}
|
||||||
} catch (Exception ex) {
|
return null;
|
||||||
log.error("记录数据解析错误日志失败: {}", ex.getMessage(), ex);
|
}, "记录数据解析错误日志失败");
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -146,58 +133,21 @@ public class DeviceDebugLogAspect {
|
|||||||
*/
|
*/
|
||||||
@AfterThrowing(pointcut = "messagePostPointcut()", throwing = "e")
|
@AfterThrowing(pointcut = "messagePostPointcut()", throwing = "e")
|
||||||
public void afterThrowingMessagePost(JoinPoint joinPoint, Throwable e) {
|
public void afterThrowingMessagePost(JoinPoint joinPoint, Throwable e) {
|
||||||
try {
|
handleException(joinPoint, e, (args) -> {
|
||||||
Object[] args = joinPoint.getArgs();
|
if (args[0] instanceof DeviceMessage) {
|
||||||
if (args != null && args.length > 0 && args[0] instanceof DeviceMessage) {
|
|
||||||
DeviceMessage deviceMessage = (DeviceMessage) args[0];
|
DeviceMessage deviceMessage = (DeviceMessage) args[0];
|
||||||
String serialNumber = deviceMessage.getSerialNumber();
|
String serialNumber = deviceMessage.getSerialNumber();
|
||||||
if (StringUtils.isEmpty(serialNumber)) {
|
if (StringUtils.isEmpty(serialNumber)) {
|
||||||
log.warn("设备编号为空,跳过错误日志记录");
|
return null;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
String sourceData = extractSourceDataFromDeviceMessage(deviceMessage);
|
||||||
// 处理原始数据
|
String errorMsg = getErrorMessage(e);
|
||||||
String sourceData = "";
|
|
||||||
try {
|
|
||||||
if (deviceMessage.getMessage() != null) {
|
|
||||||
Object msg = deviceMessage.getMessage();
|
|
||||||
if (msg instanceof byte[]) {
|
|
||||||
sourceData = ByteBufUtil.hexDump((byte[]) msg);
|
|
||||||
} else {
|
|
||||||
String msgStr = msg.toString();
|
|
||||||
// 如果dataType是ascii,需要将ASCII字符串转换为HEX格式用于显示
|
|
||||||
if ("ascii".equalsIgnoreCase(deviceMessage.getDataType())) {
|
|
||||||
// ASCII字符串转HEX
|
|
||||||
sourceData = asciiToHex(msgStr);
|
|
||||||
} else {
|
|
||||||
// 其他情况直接使用原字符串(可能是HEX格式)
|
|
||||||
sourceData = msgStr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (Exception ex) {
|
|
||||||
log.warn("处理原始数据失败: {}", ex.getMessage());
|
|
||||||
// 如果转换失败,至少保存原始字符串
|
|
||||||
if (deviceMessage.getMessage() != null) {
|
|
||||||
sourceData = deviceMessage.getMessage().toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
String errorMsg = e.getMessage();
|
|
||||||
if (StringUtils.isEmpty(errorMsg)) {
|
|
||||||
errorMsg = e.getClass().getName();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取协议编码和产品ID(通过设备编号查询,有缓存)
|
|
||||||
String protocolCode = getProtocolCodeBySerialNumber(serialNumber);
|
|
||||||
Long productId = getProductIdBySerialNumber(serialNumber);
|
Long productId = getProductIdBySerialNumber(serialNumber);
|
||||||
|
return new ErrorLogContext(serialNumber, sourceData, errorMsg,
|
||||||
saveErrorLog(serialNumber, sourceData, errorMsg,
|
|
||||||
DeviceLogTypeEnum.COMMAND_SEND_ERROR.getType(), "SEND_ERROR", productId);
|
DeviceLogTypeEnum.COMMAND_SEND_ERROR.getType(), "SEND_ERROR", productId);
|
||||||
}
|
}
|
||||||
} catch (Exception ex) {
|
return null;
|
||||||
log.error("记录指令发送错误日志失败: {}", ex.getMessage(), ex);
|
}, "记录指令发送错误日志失败");
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -205,55 +155,24 @@ public class DeviceDebugLogAspect {
|
|||||||
*/
|
*/
|
||||||
@AfterThrowing(pointcut = "protocolDecodePointcut()", throwing = "e")
|
@AfterThrowing(pointcut = "protocolDecodePointcut()", throwing = "e")
|
||||||
public void afterThrowingProtocolDecode(JoinPoint joinPoint, Throwable e) {
|
public void afterThrowingProtocolDecode(JoinPoint joinPoint, Throwable e) {
|
||||||
try {
|
handleException(joinPoint, e, (args) -> {
|
||||||
Object[] args = joinPoint.getArgs();
|
if (args[0] instanceof DeviceData) {
|
||||||
if (args != null && args.length > 0 && args[0] instanceof DeviceData) {
|
DeviceData deviceData = (DeviceData) args[0];
|
||||||
DeviceData deviceData =
|
|
||||||
(DeviceData) args[0];
|
|
||||||
String serialNumber = deviceData.getSerialNumber();
|
String serialNumber = deviceData.getSerialNumber();
|
||||||
if (StringUtils.isEmpty(serialNumber)) {
|
if (StringUtils.isEmpty(serialNumber)) {
|
||||||
log.warn("设备编号为空,跳过错误日志记录");
|
return null;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
String sourceData = extractSourceDataFromDeviceData(deviceData);
|
||||||
// 处理原始数据
|
String errorMsg = getErrorMessage(e);
|
||||||
String sourceData = "";
|
|
||||||
try {
|
|
||||||
ByteBuf buf = deviceData.getBuf();
|
|
||||||
if (buf != null) {
|
|
||||||
sourceData = ByteBufUtil.hexDump(buf);
|
|
||||||
} else if (deviceData.getData() != null && deviceData.getData().length > 0) {
|
|
||||||
sourceData = ByteBufUtil.hexDump(deviceData.getData());
|
|
||||||
}
|
|
||||||
} catch (Exception ex) {
|
|
||||||
log.warn("处理原始数据失败: {}", ex.getMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
String errorMsg = e.getMessage();
|
|
||||||
if (StringUtils.isEmpty(errorMsg)) {
|
|
||||||
errorMsg = e.getClass().getName();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取协议编码和产品ID:优先从deviceData获取,否则通过设备编号查询
|
|
||||||
String protocolCode = null;
|
|
||||||
Long productId = deviceData.getProductId();
|
Long productId = deviceData.getProductId();
|
||||||
if (productId != null) {
|
if (productId == null) {
|
||||||
protocolCode = getProtocolCodeByProductId(productId);
|
productId = getProductIdBySerialNumber(serialNumber);
|
||||||
}
|
}
|
||||||
if (StringUtils.isEmpty(protocolCode) && StringUtils.isNotEmpty(serialNumber)) {
|
return new ErrorLogContext(serialNumber, sourceData, errorMsg,
|
||||||
protocolCode = getProtocolCodeBySerialNumber(serialNumber);
|
|
||||||
if (productId == null) {
|
|
||||||
productId = getProductIdBySerialNumber(serialNumber);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 协议解析错误也归类为数据解析错误
|
|
||||||
saveErrorLog(serialNumber, sourceData, errorMsg,
|
|
||||||
DeviceLogTypeEnum.DATA_PARSE_ERROR.getType(), "PARSE_ERROR", productId);
|
DeviceLogTypeEnum.DATA_PARSE_ERROR.getType(), "PARSE_ERROR", productId);
|
||||||
}
|
}
|
||||||
} catch (Exception ex) {
|
return null;
|
||||||
log.error("记录协议解析错误日志失败: {}", ex.getMessage(), ex);
|
}, "记录协议解析错误日志失败");
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -301,31 +220,140 @@ public class DeviceDebugLogAspect {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 统一异常处理入口
|
||||||
|
*/
|
||||||
|
private void handleException(JoinPoint joinPoint, Throwable e,
|
||||||
|
java.util.function.Function<Object[], ErrorLogContext> contextExtractor,
|
||||||
|
String errorLogPrefix) {
|
||||||
|
try {
|
||||||
|
Object[] args = joinPoint.getArgs();
|
||||||
|
if (args == null || args.length == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ErrorLogContext context = contextExtractor.apply(args);
|
||||||
|
if (context == null || StringUtils.isEmpty(context.getSerialNumber())) {
|
||||||
|
log.warn("设备编号为空,跳过错误日志记录");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
saveErrorLog(context);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
log.error("{}: {}", errorLogPrefix, ex.getMessage(), ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从DeviceReportBo提取原始数据
|
||||||
|
*/
|
||||||
|
private String extractSourceDataFromReportBo(DeviceReportBo bo) {
|
||||||
|
try {
|
||||||
|
if (bo.getData() != null && bo.getData().length > 0) {
|
||||||
|
return ByteBufUtil.hexDump(bo.getData());
|
||||||
|
} else if (StringUtils.isNotEmpty(bo.getSources())) {
|
||||||
|
return bo.getSources();
|
||||||
|
}
|
||||||
|
} catch (Exception ex) {
|
||||||
|
log.warn("处理原始数据失败: {}", ex.getMessage());
|
||||||
|
}
|
||||||
|
return bo.getSources() != null ? bo.getSources() : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从DeviceMessage提取原始数据
|
||||||
|
*/
|
||||||
|
private String extractSourceDataFromDeviceMessage(DeviceMessage deviceMessage) {
|
||||||
|
try {
|
||||||
|
if (deviceMessage.getMessage() != null) {
|
||||||
|
Object msg = deviceMessage.getMessage();
|
||||||
|
if (msg instanceof byte[]) {
|
||||||
|
return ByteBufUtil.hexDump((byte[]) msg);
|
||||||
|
} else {
|
||||||
|
String msgStr = msg.toString();
|
||||||
|
if ("ascii".equalsIgnoreCase(deviceMessage.getDataType())) {
|
||||||
|
return asciiToHex(msgStr);
|
||||||
|
} else {
|
||||||
|
return msgStr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception ex) {
|
||||||
|
log.warn("处理原始数据失败: {}", ex.getMessage());
|
||||||
|
if (deviceMessage.getMessage() != null) {
|
||||||
|
return deviceMessage.getMessage().toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从DeviceData提取原始数据
|
||||||
|
*/
|
||||||
|
private String extractSourceDataFromDeviceData(DeviceData deviceData) {
|
||||||
|
try {
|
||||||
|
ByteBuf buf = deviceData.getBuf();
|
||||||
|
if (buf != null) {
|
||||||
|
return ByteBufUtil.hexDump(buf);
|
||||||
|
} else if (deviceData.getData() != null && deviceData.getData().length > 0) {
|
||||||
|
return ByteBufUtil.hexDump(deviceData.getData());
|
||||||
|
}
|
||||||
|
} catch (Exception ex) {
|
||||||
|
log.warn("处理原始数据失败: {}", ex.getMessage());
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取错误信息
|
||||||
|
*/
|
||||||
|
private String getErrorMessage(Throwable e) {
|
||||||
|
String errorMsg = e.getMessage();
|
||||||
|
return StringUtils.isEmpty(errorMsg) ? e.getClass().getName() : errorMsg;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查设备是否已授权
|
||||||
|
*/
|
||||||
|
private boolean isDeviceAuthorized(String serialNumber, Long productId) {
|
||||||
|
if (productId == null) {
|
||||||
|
return true; // 产品ID为空,不进行授权校验
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
Product product = productService.selectProductByProductId(productId);
|
||||||
|
if (product == null || product.getIsAuthorize() == null || product.getIsAuthorize() != 1) {
|
||||||
|
return true; // 产品未开启授权码,允许记录
|
||||||
|
}
|
||||||
|
// 产品开启了授权码,检查设备是否绑定了授权码
|
||||||
|
ProductAuthorize queryParam = new ProductAuthorize();
|
||||||
|
queryParam.setProductId(productId);
|
||||||
|
queryParam.setSerialNumber(serialNumber);
|
||||||
|
List<ProductAuthorize> authorizeList = productAuthorizeService.selectProductAuthorizeList(queryParam);
|
||||||
|
return authorizeList != null && !authorizeList.isEmpty();
|
||||||
|
} catch (Exception ex) {
|
||||||
|
log.warn("授权码校验失败: {}, 设备编号: {}, 继续记录日志", ex.getMessage(), serialNumber);
|
||||||
|
return true; // 校验失败时继续记录日志,避免影响正常功能
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 保存错误日志到iot_device_log表,并推送到前端页面
|
* 保存错误日志到iot_device_log表,并推送到前端页面
|
||||||
*/
|
*/
|
||||||
private void saveErrorLog(String serialNumber, String sourceData, String errorMsg,
|
private void saveErrorLog(ErrorLogContext context) {
|
||||||
Integer logType, String identity, Long productId) {
|
|
||||||
try {
|
try {
|
||||||
|
String serialNumber = context.getSerialNumber();
|
||||||
if (StringUtils.isEmpty(serialNumber)) {
|
if (StringUtils.isEmpty(serialNumber)) {
|
||||||
log.warn("设备编号为空,无法保存错误日志");
|
log.warn("设备编号为空,无法保存错误日志");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取设备信息
|
// 获取设备信息
|
||||||
Device device = null;
|
Device device = getDeviceBySerialNumber(serialNumber);
|
||||||
try {
|
|
||||||
device = deviceService.selectDeviceBySerialNumber(serialNumber);
|
|
||||||
} catch (Exception ex) {
|
|
||||||
log.warn("获取设备信息失败: {}, 设备编号: {}", ex.getMessage(), serialNumber);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (device == null) {
|
if (device == null) {
|
||||||
log.warn("设备不存在,无法保存错误日志: {}", serialNumber);
|
log.warn("设备不存在,无法保存错误日志: {}", serialNumber);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果productId为空,尝试从设备信息中获取
|
// 获取产品ID
|
||||||
|
Long productId = context.getProductId();
|
||||||
if (productId == null) {
|
if (productId == null) {
|
||||||
productId = device.getProductId();
|
productId = device.getProductId();
|
||||||
}
|
}
|
||||||
@ -333,64 +361,66 @@ public class DeviceDebugLogAspect {
|
|||||||
productId = getProductIdBySerialNumber(serialNumber);
|
productId = getProductIdBySerialNumber(serialNumber);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 授权码校验:如果产品开启了授权码,但设备未绑定授权码,不记录日志也不推送消息
|
// 授权码校验
|
||||||
if (productId != null) {
|
if (!isDeviceAuthorized(serialNumber, productId)) {
|
||||||
try {
|
log.debug("设备[{}]未绑定授权码,跳过错误日志记录和消息推送", serialNumber);
|
||||||
Product product = productService.selectProductByProductId(productId);
|
return;
|
||||||
if (product != null && product.getIsAuthorize() != null && product.getIsAuthorize() == 1) {
|
|
||||||
// 产品开启了授权码,检查设备是否绑定了授权码
|
|
||||||
ProductAuthorize queryParam = new ProductAuthorize();
|
|
||||||
queryParam.setProductId(productId);
|
|
||||||
queryParam.setSerialNumber(serialNumber);
|
|
||||||
java.util.List<ProductAuthorize> authorizeList = productAuthorizeService.selectProductAuthorizeList(queryParam);
|
|
||||||
if (authorizeList == null || authorizeList.isEmpty()) {
|
|
||||||
log.debug("设备[{}]未绑定授权码,跳过错误日志记录和消息推送", serialNumber);
|
|
||||||
return; // 不记录日志,也不推送消息
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (Exception ex) {
|
|
||||||
log.warn("授权码校验失败: {}, 设备编号: {}, 继续记录日志", ex.getMessage(), serialNumber);
|
|
||||||
// 校验失败时继续记录日志,避免影响正常功能
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
DeviceLog deviceLog = new DeviceLog();
|
// 构建并保存设备日志
|
||||||
deviceLog.setSerialNumber(serialNumber);
|
DeviceLog deviceLog = buildDeviceLog(device, context);
|
||||||
deviceLog.setDeviceId(device.getDeviceId());
|
|
||||||
deviceLog.setDeviceName(device.getDeviceName());
|
|
||||||
deviceLog.setLogType(logType);
|
|
||||||
// 存储原始数据包(限制64字符)
|
|
||||||
deviceLog.setLogValue(StringUtils.substring(sourceData, 0, 64));
|
|
||||||
// 存储错误信息(限制500字符)
|
|
||||||
deviceLog.setRemark(StringUtils.substring(errorMsg, 0, 500));
|
|
||||||
deviceLog.setIdentity(identity);
|
|
||||||
deviceLog.setModelName(Objects.equals(logType, DeviceLogTypeEnum.DATA_PARSE_ERROR.getType()) ?
|
|
||||||
"数据解析错误" : "指令发送错误");
|
|
||||||
deviceLog.setCreateTime(DateUtils.getNowDate());
|
|
||||||
deviceLog.setUserId(device.getTenantId());
|
|
||||||
deviceLog.setUserName(device.getTenantName());
|
|
||||||
deviceLog.setTenantId(device.getTenantId());
|
|
||||||
deviceLog.setTenantName(device.getTenantName());
|
|
||||||
deviceLog.setIsMonitor(0);
|
|
||||||
deviceLog.setMode(3); // 3=其他
|
|
||||||
|
|
||||||
// 保存错误日志到数据库
|
|
||||||
logService.saveDeviceLog(deviceLog);
|
logService.saveDeviceLog(deviceLog);
|
||||||
log.info("已保存错误日志到数据库 - 设备: {}, 错误类型: {}, 错误信息: {}",
|
log.info("已保存错误日志到数据库 - 设备: {}, 错误类型: {}, 错误信息: {}",
|
||||||
serialNumber, identity, StringUtils.substring(errorMsg, 0, 50));
|
serialNumber, context.getIdentity(),
|
||||||
|
StringUtils.substring(context.getErrorMsg(), 0, MAX_ERROR_MSG_DISPLAY_LENGTH));
|
||||||
|
|
||||||
// 推送错误信息到前端页面
|
// 推送错误信息到前端页面
|
||||||
pushErrorToFrontend(serialNumber, productId, sourceData, errorMsg, logType, identity);
|
pushErrorToFrontend(serialNumber, productId, context);
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
log.error("保存错误日志失败: {}", ex.getMessage(), ex);
|
log.error("保存错误日志失败: {}", ex.getMessage(), ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取设备信息
|
||||||
|
*/
|
||||||
|
private Device getDeviceBySerialNumber(String serialNumber) {
|
||||||
|
try {
|
||||||
|
return deviceService.selectDeviceBySerialNumber(serialNumber);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
log.warn("获取设备信息失败: {}, 设备编号: {}", ex.getMessage(), serialNumber);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建设备日志对象
|
||||||
|
*/
|
||||||
|
private DeviceLog buildDeviceLog(Device device, ErrorLogContext context) {
|
||||||
|
DeviceLog deviceLog = new DeviceLog();
|
||||||
|
deviceLog.setSerialNumber(context.getSerialNumber());
|
||||||
|
deviceLog.setDeviceId(device.getDeviceId());
|
||||||
|
deviceLog.setDeviceName(device.getDeviceName());
|
||||||
|
deviceLog.setLogType(context.getLogType());
|
||||||
|
deviceLog.setLogValue(StringUtils.substring(context.getSourceData(), 0, MAX_LOG_VALUE_LENGTH));
|
||||||
|
deviceLog.setRemark(StringUtils.substring(context.getErrorMsg(), 0, MAX_REMARK_LENGTH));
|
||||||
|
deviceLog.setIdentity(context.getIdentity());
|
||||||
|
deviceLog.setModelName(Objects.equals(context.getLogType(), DeviceLogTypeEnum.DATA_PARSE_ERROR.getType()) ?
|
||||||
|
"数据解析错误" : "指令发送错误");
|
||||||
|
deviceLog.setCreateTime(DateUtils.getNowDate());
|
||||||
|
deviceLog.setUserId(device.getTenantId());
|
||||||
|
deviceLog.setUserName(device.getTenantName());
|
||||||
|
deviceLog.setTenantId(device.getTenantId());
|
||||||
|
deviceLog.setTenantName(device.getTenantName());
|
||||||
|
deviceLog.setIsMonitor(0);
|
||||||
|
deviceLog.setMode(MODE_OTHER);
|
||||||
|
return deviceLog;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 推送错误信息到前端页面
|
* 推送错误信息到前端页面
|
||||||
*/
|
*/
|
||||||
private void pushErrorToFrontend(String serialNumber, Long productId, String sourceData,
|
private void pushErrorToFrontend(String serialNumber, Long productId, ErrorLogContext context) {
|
||||||
String errorMsg, Integer logType, String identity) {
|
|
||||||
try {
|
try {
|
||||||
if (productId == null) {
|
if (productId == null) {
|
||||||
log.warn("产品ID为空,无法推送错误信息到前端: {}", serialNumber);
|
log.warn("产品ID为空,无法推送错误信息到前端: {}", serialNumber);
|
||||||
@ -398,42 +428,68 @@ public class DeviceDebugLogAspect {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 构建错误消息的物模型数据
|
// 构建错误消息的物模型数据
|
||||||
List<ThingsModelSimpleItem> errorItems = new ArrayList<>();
|
|
||||||
ThingsModelSimpleItem errorItem = new ThingsModelSimpleItem();
|
ThingsModelSimpleItem errorItem = new ThingsModelSimpleItem();
|
||||||
errorItem.setId("ERROR_" + identity);
|
errorItem.setId("ERROR_" + context.getIdentity());
|
||||||
errorItem.setName(Objects.equals(logType, DeviceLogTypeEnum.DATA_PARSE_ERROR.getType()) ? "数据解析错误" : "指令发送错误");
|
errorItem.setName(Objects.equals(context.getLogType(), DeviceLogTypeEnum.DATA_PARSE_ERROR.getType())
|
||||||
errorItem.setValue(errorMsg);
|
? "数据解析错误" : "指令发送错误");
|
||||||
errorItem.setRemark(errorMsg);
|
errorItem.setValue(context.getErrorMsg());
|
||||||
|
errorItem.setRemark(context.getErrorMsg());
|
||||||
errorItem.setTs(new Date());
|
errorItem.setTs(new Date());
|
||||||
|
List<ThingsModelSimpleItem> errorItems = new ArrayList<>();
|
||||||
errorItems.add(errorItem);
|
errorItems.add(errorItem);
|
||||||
|
|
||||||
// 构建推送消息
|
// 构建推送消息
|
||||||
PushMessageBo messageBo = new PushMessageBo();
|
PushMessageBo messageBo = new PushMessageBo();
|
||||||
messageBo.setTopic(topicsUtils.buildTopic(productId, serialNumber.toUpperCase(), TopicType.WS_SERVICE_INVOKE));
|
messageBo.setTopic(topicsUtils.buildTopic(productId, serialNumber.toUpperCase(), TopicType.WS_SERVICE_INVOKE));
|
||||||
|
|
||||||
// 将hex转换为ASCII
|
String sourceAscii = hexToAscii(context.getSourceData());
|
||||||
String sourceAscii = hexToAscii(sourceData);
|
|
||||||
|
|
||||||
JSONObject pushObj = new JSONObject();
|
JSONObject pushObj = new JSONObject();
|
||||||
pushObj.put("message", errorItems);
|
pushObj.put("message", errorItems);
|
||||||
pushObj.put("sources", StringUtils.isNotEmpty(sourceData) ? sourceData : ""); // HEX格式
|
pushObj.put("sources", StringUtils.isNotEmpty(context.getSourceData()) ? context.getSourceData() : "");
|
||||||
pushObj.put("sourcesAscii", sourceAscii); // ASCII格式
|
pushObj.put("sourcesAscii", sourceAscii);
|
||||||
pushObj.put("errorType", identity); // 错误类型:PARSE_ERROR 或 SEND_ERROR
|
pushObj.put("errorType", context.getIdentity());
|
||||||
pushObj.put("isError", true); // 标记为错误消息
|
pushObj.put("isError", true);
|
||||||
|
|
||||||
messageBo.setMessage(JSON.toJSONString(pushObj));
|
messageBo.setMessage(JSON.toJSONString(pushObj));
|
||||||
|
|
||||||
// 推送消息到MQTT
|
// 推送消息到MQTT
|
||||||
mqttRemoteManager.pushCommon(messageBo);
|
mqttRemoteManager.pushCommon(messageBo);
|
||||||
log.info("已推送错误信息到前端 - 设备: {}, 产品ID: {}, 错误类型: {}, HEX: {}, ASCII: {}",
|
log.info("已推送错误信息到前端 - 设备: {}, 产品ID: {}, 错误类型: {}",
|
||||||
serialNumber, productId, identity,
|
serialNumber, productId, context.getIdentity());
|
||||||
StringUtils.isNotEmpty(sourceData) ? StringUtils.substring(sourceData, 0, 20) + "..." : "",
|
|
||||||
StringUtils.isNotEmpty(sourceAscii) ? StringUtils.substring(sourceAscii, 0, 20) + "..." : "");
|
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
log.error("推送错误信息到前端失败: {}", ex.getMessage(), ex);
|
log.error("推送错误信息到前端失败: {}", ex.getMessage(), ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 错误日志上下文对象
|
||||||
|
*/
|
||||||
|
private static class ErrorLogContext {
|
||||||
|
private final String serialNumber;
|
||||||
|
private final String sourceData;
|
||||||
|
private final String errorMsg;
|
||||||
|
private final Integer logType;
|
||||||
|
private final String identity;
|
||||||
|
private final Long productId;
|
||||||
|
|
||||||
|
public ErrorLogContext(String serialNumber, String sourceData, String errorMsg,
|
||||||
|
Integer logType, String identity, Long productId) {
|
||||||
|
this.serialNumber = serialNumber;
|
||||||
|
this.sourceData = sourceData;
|
||||||
|
this.errorMsg = errorMsg;
|
||||||
|
this.logType = logType;
|
||||||
|
this.identity = identity;
|
||||||
|
this.productId = productId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSerialNumber() { return serialNumber; }
|
||||||
|
public String getSourceData() { return sourceData; }
|
||||||
|
public String getErrorMsg() { return errorMsg; }
|
||||||
|
public Integer getLogType() { return logType; }
|
||||||
|
public String getIdentity() { return identity; }
|
||||||
|
public Long getProductId() { return productId; }
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 将ASCII字符串转换为HEX字符串
|
* 将ASCII字符串转换为HEX字符串
|
||||||
* @param asciiStr ASCII字符串
|
* @param asciiStr ASCII字符串
|
||||||
|
|||||||
@ -95,6 +95,8 @@ public class DataHandlerImpl implements IDataHandler {
|
|||||||
// 这里上报设备编号是转的大写,后面存缓存也是使用大写的,所以在查询物模型的值时添加把设备编号转大写后取值
|
// 这里上报设备编号是转的大写,后面存缓存也是使用大写的,所以在查询物模型的值时添加把设备编号转大写后取值
|
||||||
input.setDeviceNumber(bo.getSerialNumber().toUpperCase());
|
input.setDeviceNumber(bo.getSerialNumber().toUpperCase());
|
||||||
input.setThingsModelValueRemarkItem(thingsModelSimpleItems);
|
input.setThingsModelValueRemarkItem(thingsModelSimpleItems);
|
||||||
|
// 传递原始消息数据包,用于判断设备上电触发回包等
|
||||||
|
input.setSources(bo.getSources());
|
||||||
List<ThingsModelSimpleItem> result = deviceService.reportDeviceThingsModelValue(input, bo.getType(), bo.isShadow());
|
List<ThingsModelSimpleItem> result = deviceService.reportDeviceThingsModelValue(input, bo.getType(), bo.isShadow());
|
||||||
// 只有设备上报进入规则引擎流程
|
// 只有设备上报进入规则引擎流程
|
||||||
if (bo.isRuleEngine() && !bo.getSerialNumber().startsWith("server-")){
|
if (bo.isRuleEngine() && !bo.getSerialNumber().startsWith("server-")){
|
||||||
@ -120,7 +122,7 @@ public class DataHandlerImpl implements IDataHandler {
|
|||||||
// log.error("=>上报属性信息给小度音箱异常", e);
|
// log.error("=>上报属性信息给小度音箱异常", e);
|
||||||
// }
|
// }
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("接收属性数据,解析数据时异常 message={},e={}", e.getMessage(),e);
|
log.error("接收属性数据,解析数据时异常 message={}", e.getMessage(),e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -55,7 +55,6 @@ public class MessageAdapter implements MessageDecoder, MessageEncoder{
|
|||||||
//这里兼容一下TCP整包发送的数据(整包==设备编号,数据一起发送)
|
//这里兼容一下TCP整包发送的数据(整包==设备编号,数据一起发送)
|
||||||
if (clientId == null){
|
if (clientId == null){
|
||||||
if (StringUtils.isNotEmpty(dump)) {
|
if (StringUtils.isNotEmpty(dump)) {
|
||||||
assert dump != null;
|
|
||||||
if (dump.startsWith("68") && dump.endsWith("16")) {
|
if (dump.startsWith("68") && dump.endsWith("16")) {
|
||||||
protocol = managerService.getProtocolByProtocolCode(FastBeeConstant.PROTOCOL.FlowMeter);
|
protocol = managerService.getProtocolByProtocolCode(FastBeeConstant.PROTOCOL.FlowMeter);
|
||||||
}else if (dump.startsWith("7e")&& dump.endsWith("7e")){
|
}else if (dump.startsWith("7e")&& dump.endsWith("7e")){
|
||||||
|
|||||||
@ -130,7 +130,7 @@ public class ModbusProtocol implements IProtocol {
|
|||||||
report.setSources(hexDump);
|
report.setSources(hexDump);
|
||||||
return report;
|
return report;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("=>解码异常[{}]", e, e.getMessage());
|
log.error("=>解码异常[{}]", e.getMessage(), e);
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -80,4 +80,10 @@ public class ModbusJob extends BaseEntity {
|
|||||||
@ApiModelProperty("时间周期类型")
|
@ApiModelProperty("时间周期类型")
|
||||||
private Integer cycleType;
|
private Integer cycleType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行时间(仅设备上电触发任务使用,记录任务执行的时间)
|
||||||
|
*/
|
||||||
|
@ApiModelProperty("执行时间")
|
||||||
|
private java.util.Date executeTime;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -45,6 +45,14 @@ public interface ModbusJobMapper
|
|||||||
*/
|
*/
|
||||||
public int updateModbusJob(ModbusJob modbusJob);
|
public int updateModbusJob(ModbusJob modbusJob);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清空执行时间
|
||||||
|
*
|
||||||
|
* @param taskId 任务ID
|
||||||
|
* @return 结果
|
||||||
|
*/
|
||||||
|
public int clearExecuteTime(Long taskId);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 删除轮训任务列
|
* 删除轮训任务列
|
||||||
*
|
*
|
||||||
|
|||||||
@ -68,6 +68,7 @@ import java.math.BigDecimal;
|
|||||||
import java.net.URLEncoder;
|
import java.net.URLEncoder;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import static com.fastbee.common.utils.SecurityUtils.getLoginUser;
|
import static com.fastbee.common.utils.SecurityUtils.getLoginUser;
|
||||||
@ -513,6 +514,7 @@ public class DeviceServiceImpl implements IDeviceService {
|
|||||||
ThingsModelValueItem thingModels = thingsModelService.getSingleThingModels(input.getProductId(), identity);
|
ThingsModelValueItem thingModels = thingsModelService.getSingleThingModels(input.getProductId(), identity);
|
||||||
if (Objects.isNull(thingModels)) {
|
if (Objects.isNull(thingModels)) {
|
||||||
log.warn("=>查询物模型为空[{}],标识符:[{}]", input.getProductId(), identity);
|
log.warn("=>查询物模型为空[{}],标识符:[{}]", input.getProductId(), identity);
|
||||||
|
continue; // 跳过当前物模型的处理
|
||||||
}
|
}
|
||||||
// 获取设备实时值
|
// 获取设备实时值
|
||||||
ValueItem cacheIdentifier = itslValueCache.getCacheIdentifier(input.getProductId(), input.getDeviceNumber(), thingModels.getId());
|
ValueItem cacheIdentifier = itslValueCache.getCacheIdentifier(input.getProductId(), input.getDeviceNumber(), thingModels.getId());
|
||||||
@ -525,10 +527,50 @@ public class DeviceServiceImpl implements IDeviceService {
|
|||||||
|
|
||||||
/* ★★★★★★★★★★★★★★★★★★★★★★ 数据计算 -开始 ★★★★★★★★★★★★★★★★★★★★★★*/
|
/* ★★★★★★★★★★★★★★★★★★★★★★ 数据计算 -开始 ★★★★★★★★★★★★★★★★★★★★★★*/
|
||||||
//有计算公式的,经过计算公式
|
//有计算公式的,经过计算公式
|
||||||
if (thingModels.getFormula() != null && !"".equals(thingModels.getFormula())) {
|
if (thingModels != null && thingModels.getFormula() != null && !"".equals(thingModels.getFormula())) {
|
||||||
Map<String, String> params = new HashMap<>();
|
Map<String, String> params = new HashMap<>();
|
||||||
|
// %s 表示当前物模型的值(保持向后兼容)
|
||||||
params.put("%s", value);
|
params.put("%s", value);
|
||||||
value = String.valueOf(CaculateUtils.execute(thingModels.getFormula(), params));
|
|
||||||
|
// 解析公式中的其他物模型标识符(格式:{identifier} 或直接使用标识符)
|
||||||
|
String formula = thingModels.getFormula();
|
||||||
|
// 匹配 {identifier} 格式的占位符
|
||||||
|
java.util.regex.Pattern pattern = java.util.regex.Pattern.compile("\\{([^}]+)\\}");
|
||||||
|
java.util.regex.Matcher matcher = pattern.matcher(formula);
|
||||||
|
Set<String> referencedIdentifiers = new HashSet<>();
|
||||||
|
while (matcher.find()) {
|
||||||
|
String identifier = matcher.group(1);
|
||||||
|
referencedIdentifiers.add(identifier);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取引用的其他物模型的值
|
||||||
|
for (String refIdentifier : referencedIdentifiers) {
|
||||||
|
if (!refIdentifier.equals(identity)) { // 避免循环引用自己
|
||||||
|
try {
|
||||||
|
ValueItem refValueItem = itslValueCache.getCacheIdentifier(
|
||||||
|
input.getProductId(),
|
||||||
|
input.getDeviceNumber(),
|
||||||
|
refIdentifier
|
||||||
|
);
|
||||||
|
if (refValueItem != null && StringUtils.isNotEmpty(refValueItem.getValue())) {
|
||||||
|
// 使用 {identifier} 作为占位符
|
||||||
|
params.put("{" + refIdentifier + "}", refValueItem.getValue());
|
||||||
|
} else {
|
||||||
|
// 如果引用的物模型值不存在,使用0作为默认值
|
||||||
|
params.put("{" + refIdentifier + "}", "0");
|
||||||
|
log.warn("=>计算公式引用的物模型值不存在[{}],标识符:[{}],使用默认值0",
|
||||||
|
input.getProductId(), refIdentifier);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("=>获取计算公式引用的物模型值失败[{}],标识符:[{}],错误:{}",
|
||||||
|
input.getProductId(), refIdentifier, e.getMessage());
|
||||||
|
params.put("{" + refIdentifier + "}", "0");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 执行计算
|
||||||
|
value = String.valueOf(CaculateUtils.execute(formula, params));
|
||||||
item.setValue(value);
|
item.setValue(value);
|
||||||
}
|
}
|
||||||
/* ★★★★★★★★★★★★★★★★★★★★★★ 数据计算 -结束 ★★★★★★★★★★★★★★★★★★★★★★*/
|
/* ★★★★★★★★★★★★★★★★★★★★★★ 数据计算 -结束 ★★★★★★★★★★★★★★★★★★★★★★*/
|
||||||
@ -642,8 +684,10 @@ public class DeviceServiceImpl implements IDeviceService {
|
|||||||
}
|
}
|
||||||
// 缓存最新一条数据到redis
|
// 缓存最新一条数据到redis
|
||||||
redisCache.hashPutAll(key, maps);
|
redisCache.hashPutAll(key, maps);
|
||||||
|
// 判断是否是设备上电触发的回包,如果是则跳过保存历史数据
|
||||||
|
boolean isPowerOnTriggerReply = isPowerOnTriggerReply(input.getSources(), input.getDeviceNumber());
|
||||||
//存储历史数据
|
//存储历史数据
|
||||||
if (!CollectionUtils.isEmpty(deviceLogList) && !isShadow) {
|
if (!CollectionUtils.isEmpty(deviceLogList) && !isShadow && !isPowerOnTriggerReply) {
|
||||||
for (DeviceLog deviceLog : deviceLogList) {
|
for (DeviceLog deviceLog : deviceLogList) {
|
||||||
logService.saveDeviceLog(deviceLog);
|
logService.saveDeviceLog(deviceLog);
|
||||||
}
|
}
|
||||||
@ -1355,10 +1399,26 @@ public class DeviceServiceImpl implements IDeviceService {
|
|||||||
|
|
||||||
// 执行所有相关的modbus任务
|
// 执行所有相关的modbus任务
|
||||||
boolean hasActiveTask = false;
|
boolean hasActiveTask = false;
|
||||||
|
// 记录设备上电触发任务下发的指令片段(索引2到11位)
|
||||||
|
String serialNumber = device.getSerialNumber().toUpperCase();
|
||||||
|
String cacheKey = RedisKeyBuilder.buildPowerOnTriggerCommandKey(serialNumber);
|
||||||
|
|
||||||
for (ModbusJob modbusJob : modbusJobList) {
|
for (ModbusJob modbusJob : modbusJobList) {
|
||||||
if ("0".equals(modbusJob.getStatus())) { // 只执行正常状态的任务
|
if ("0".equals(modbusJob.getStatus())) { // 只执行正常状态的任务
|
||||||
hasActiveTask = true;
|
hasActiveTask = true;
|
||||||
// log.info("发现设备上电触发任务,设备编号:{},任务ID:{},指令:{}", device.getSerialNumber(), deviceJob.getJobId(), modbusJob.getCommand());
|
// 记录下发的指令片段(索引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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1391,6 +1451,7 @@ public class DeviceServiceImpl implements IDeviceService {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
log.info("更新设备上电触发任务状态为暂停,任务数量:{}", modbusJobList.size());
|
log.info("更新设备上电触发任务状态为暂停,任务数量:{}", modbusJobList.size());
|
||||||
|
java.util.Date executeTime = DateUtils.getNowDate();
|
||||||
modbusJobList.forEach(modbusJob -> {
|
modbusJobList.forEach(modbusJob -> {
|
||||||
// 只更新status=0的任务为status=1,如果已经是status=1则跳过
|
// 只更新status=0的任务为status=1,如果已经是status=1则跳过
|
||||||
if (!"0".equals(modbusJob.getStatus())) {
|
if (!"0".equals(modbusJob.getStatus())) {
|
||||||
@ -1400,10 +1461,12 @@ public class DeviceServiceImpl implements IDeviceService {
|
|||||||
ModbusJob job = new ModbusJob();
|
ModbusJob job = new ModbusJob();
|
||||||
job.setStatus("1");
|
job.setStatus("1");
|
||||||
job.setTaskId(modbusJob.getTaskId());
|
job.setTaskId(modbusJob.getTaskId());
|
||||||
|
// 记录执行时间
|
||||||
|
job.setExecuteTime(executeTime);
|
||||||
try {
|
try {
|
||||||
// 将任务状态改为暂停("1"=暂停,"0"=正常)
|
// 将任务状态改为暂停("1"=暂停,"0"=正常),并记录执行时间
|
||||||
modbusJobService.updateModbusJob(job);
|
modbusJobService.updateModbusJob(job);
|
||||||
log.info("设备上电触发任务已设置为暂停状态,任务ID:{}", modbusJob.getTaskId());
|
log.info("设备上电触发任务已设置为暂停状态并记录执行时间,任务ID:{},执行时间:{}", modbusJob.getTaskId(), executeTime);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("更新设备上电触发任务状态异常,任务ID:{}", modbusJob.getTaskId(), e);
|
log.error("更新设备上电触发任务状态异常,任务ID:{}", modbusJob.getTaskId(), e);
|
||||||
}
|
}
|
||||||
@ -1972,4 +2035,71 @@ public class DeviceServiceImpl implements IDeviceService {
|
|||||||
return deviceMapper.selectSerialNumbersByProductId(productId);
|
return deviceMapper.selectSerialNumbersByProductId(productId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断是否是设备上电触发的回包
|
||||||
|
* 从Redis缓存中获取下发的指令片段(索引2到11位),与上报的指令片段进行比较
|
||||||
|
*
|
||||||
|
* @param sources 原始消息数据包
|
||||||
|
* @param deviceNumber 设备编号
|
||||||
|
* @return true表示是设备上电触发的回包
|
||||||
|
*/
|
||||||
|
private boolean isPowerOnTriggerReply(Object sources, String deviceNumber) {
|
||||||
|
if (sources == null || StringUtils.isEmpty(deviceNumber)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 从Redis缓存中获取下发的指令片段
|
||||||
|
String serialNumber = deviceNumber.toUpperCase();
|
||||||
|
String cacheKey = RedisKeyBuilder.buildPowerOnTriggerCommandKey(serialNumber);
|
||||||
|
String sentCommandSegment = redisCache.getCacheObject(cacheKey);
|
||||||
|
|
||||||
|
// 如果缓存中没有下发的指令片段,说明不是设备上电触发的回包
|
||||||
|
if (StringUtils.isEmpty(sentCommandSegment)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
String hexStr = null;
|
||||||
|
|
||||||
|
// 将 sources 转换为十六进制字符串
|
||||||
|
if (sources instanceof String) {
|
||||||
|
hexStr = (String) sources;
|
||||||
|
// 如果是字符串,可能需要去除空格
|
||||||
|
hexStr = hexStr.replaceAll("\\s+", "").toUpperCase();
|
||||||
|
} else if (sources instanceof byte[]) {
|
||||||
|
// 如果是字节数组,转换为十六进制字符串
|
||||||
|
byte[] bytes = (byte[]) sources;
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
for (byte b : bytes) {
|
||||||
|
sb.append(String.format("%02X", b & 0xFF));
|
||||||
|
}
|
||||||
|
hexStr = sb.toString();
|
||||||
|
} else {
|
||||||
|
// 其他类型,尝试转换为字符串
|
||||||
|
hexStr = sources.toString().replaceAll("\\s+", "").toUpperCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hexStr.length() < 12) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提取上报指令的索引2到11位(即第3到第12个字符)
|
||||||
|
String actualSegment = hexStr.substring(2, 12);
|
||||||
|
|
||||||
|
// 与下发的指令片段进行比较
|
||||||
|
boolean isMatch = sentCommandSegment.equals(actualSegment);
|
||||||
|
|
||||||
|
if (isMatch) {
|
||||||
|
// 匹配成功,删除缓存(避免重复匹配)
|
||||||
|
redisCache.deleteObject(cacheKey);
|
||||||
|
log.info("检测到设备上电触发回包,设备编号:{},指令片段:{}", serialNumber, actualSegment);
|
||||||
|
}
|
||||||
|
|
||||||
|
return isMatch;
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("判断设备上电触发回包时发生异常: {}", e.getMessage());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -201,6 +201,14 @@ public class ModbusJobServiceImpl implements IModbusJobService {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public int updateModbusJob(ModbusJob modbusJob) {
|
public int updateModbusJob(ModbusJob modbusJob) {
|
||||||
|
// 如果状态从1(暂停)改为0(正常),需要清空执行时间
|
||||||
|
if (modbusJob.getTaskId() != null && modbusJob.getStatus() != null && "0".equals(modbusJob.getStatus())) {
|
||||||
|
ModbusJob existingJob = modbusJobMapper.selectModbusJobByTaskId(modbusJob.getTaskId());
|
||||||
|
if (existingJob != null && "1".equals(existingJob.getStatus())) {
|
||||||
|
// 状态从1改为0,清空执行时间
|
||||||
|
modbusJobMapper.clearExecuteTime(modbusJob.getTaskId());
|
||||||
|
}
|
||||||
|
}
|
||||||
return modbusJobMapper.updateModbusJob(modbusJob);
|
return modbusJobMapper.updateModbusJob(modbusJob);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -16,10 +16,11 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||||||
<result property="createTime" column="create_time" />
|
<result property="createTime" column="create_time" />
|
||||||
<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" />
|
||||||
</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 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 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">
|
||||||
@ -56,6 +57,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||||||
<if test="createTime != null">create_time,</if>
|
<if test="createTime != null">create_time,</if>
|
||||||
<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>
|
||||||
</trim>
|
</trim>
|
||||||
<trim prefix="values (" suffix=")" suffixOverrides=",">
|
<trim prefix="values (" suffix=")" suffixOverrides=",">
|
||||||
<if test="jobName != null">#{jobName},</if>
|
<if test="jobName != null">#{jobName},</if>
|
||||||
@ -68,6 +70,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||||||
<if test="createTime != null">#{createTime},</if>
|
<if test="createTime != null">#{createTime},</if>
|
||||||
<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>
|
||||||
</trim>
|
</trim>
|
||||||
</insert>
|
</insert>
|
||||||
|
|
||||||
@ -83,9 +86,15 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||||||
<if test="createBy != null">create_by = #{createBy},</if>
|
<if test="createBy != null">create_by = #{createBy},</if>
|
||||||
<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>
|
||||||
</trim>
|
</trim>
|
||||||
where task_id = #{taskId}
|
where task_id = #{taskId}
|
||||||
</update>
|
</update>
|
||||||
|
|
||||||
|
<!-- 清空执行时间 -->
|
||||||
|
<update id="clearExecuteTime" parameterType="Long">
|
||||||
|
update iot_modbus_job set execute_time = null where task_id = #{taskId}
|
||||||
|
</update>
|
||||||
|
|
||||||
<delete id="deleteModbusJobByTaskId" parameterType="Long">
|
<delete id="deleteModbusJobByTaskId" parameterType="Long">
|
||||||
delete from iot_modbus_job where task_id = #{taskId}
|
delete from iot_modbus_job where task_id = #{taskId}
|
||||||
|
|||||||
3538
sql/yack.sql
Normal file
3538
sql/yack.sql
Normal file
File diff suppressed because it is too large
Load Diff
@ -15,7 +15,7 @@ VUE_APP_SERVER_API_URL = http://localhost:8080
|
|||||||
VUE_APP_MQTT_SERVER_URL = ws://localhost:8083/mqtt
|
VUE_APP_MQTT_SERVER_URL = ws://localhost:8083/mqtt
|
||||||
|
|
||||||
# 百度地图 API Key
|
# 百度地图 API Key
|
||||||
VUE_APP_BAI_DU_AK = 0SD4KrYeMXVu8Ox2pxQGDOw3NmDYiKIw
|
VUE_APP_BAI_DU_AK = hxAMWdceoYQ2laSOzL1OEddHj4fx3Ah9
|
||||||
|
|
||||||
# OSS API 地址(生产环境使用)
|
# OSS API 地址(生产环境使用)
|
||||||
VUE_APP_OSS_API =
|
VUE_APP_OSS_API =
|
||||||
|
|||||||
@ -297,14 +297,20 @@
|
|||||||
"product.product-things-model.142341-51": "step",
|
"product.product-things-model.142341-51": "step",
|
||||||
"product.product-things-model.142341-52": "Please enter the step size, for example: 1",
|
"product.product-things-model.142341-52": "Please enter the step size, for example: 1",
|
||||||
"product.product-things-model.142341-53": "The upstream data of the device is displayed after being calculated using a formula.",
|
"product.product-things-model.142341-53": "The upstream data of the device is displayed after being calculated using a formula.",
|
||||||
"product.product-things-model.142341-54": "The% s in the formula is a placeholder and a fixed field.",
|
"product.product-things-model.142341-54": "%s in the formula is a placeholder representing the current thing model value.",
|
||||||
|
"product.product-things-model.142341-54-1": "Use {identifier} to reference values from other thing models, supporting multiple thing models in calculations.",
|
||||||
|
"product.product-things-model.142341-54-2": "Supported operators: +, -, *, /, % (modulo), ^ (power), parentheses ().",
|
||||||
"product.product-things-model.142341-55": "For example:",
|
"product.product-things-model.142341-55": "For example:",
|
||||||
"product.product-things-model.142341-56": "Add:% s+10",
|
"product.product-things-model.142341-56": "Add: %s+10",
|
||||||
"product.product-things-model.142341-57": "Subtract:% s-10",
|
"product.product-things-model.142341-57": "Subtract: %s-10",
|
||||||
"product.product-things-model.142341-58": "Multiply:% s * 10",
|
"product.product-things-model.142341-58": "Multiply: %s*10",
|
||||||
"product.product-things-model.142341-59": "Except for:% s/10",
|
"product.product-things-model.142341-59": "Divide: %s/10",
|
||||||
"product.product-things-model.142341-60": "except(Retain decimals)for:% s/10",
|
"product.product-things-model.142341-60": "Divide (retain decimals): %s%10.00",
|
||||||
"product.product-things-model.142341-61": "Retain decimals",
|
"product.product-things-model.142341-61": "Retain decimals",
|
||||||
|
"product.product-things-model.142341-61-1": "Examples of referencing other thing models:",
|
||||||
|
"product.product-things-model.142341-61-2": "Pressure = K * frequency^2 - constant: 1.5*{frequency}^2-100",
|
||||||
|
"product.product-things-model.142341-61-3": "Speed = distance / time: {distance}/{time}",
|
||||||
|
"product.product-things-model.142341-61-4": "Mixed calculation: %s*{factor}+{offset}",
|
||||||
"product.product-things-model.142341-62": "Control Formula",
|
"product.product-things-model.142341-62": "Control Formula",
|
||||||
"product.product-things-model.142341-63": "Boolean value",
|
"product.product-things-model.142341-63": "Boolean value",
|
||||||
"product.product-things-model.142341-64": "For example: Close",
|
"product.product-things-model.142341-64": "For example: Close",
|
||||||
|
|||||||
@ -297,7 +297,9 @@
|
|||||||
"product.product-things-model.142341-51": "步长",
|
"product.product-things-model.142341-51": "步长",
|
||||||
"product.product-things-model.142341-52": "请输入步长,例如:1",
|
"product.product-things-model.142341-52": "请输入步长,例如:1",
|
||||||
"product.product-things-model.142341-53": "设备上行数据经计算公式计算后显示 。",
|
"product.product-things-model.142341-53": "设备上行数据经计算公式计算后显示 。",
|
||||||
"product.product-things-model.142341-54": "公式中的%s为占位符,是固定字段。",
|
"product.product-things-model.142341-54": "公式中的%s为占位符,表示当前物模型的值。",
|
||||||
|
"product.product-things-model.142341-54-1": "使用{标识符}可以引用其他物模型的值,支持多个物模型参与计算。",
|
||||||
|
"product.product-things-model.142341-54-2": "支持运算符:+、-、*、/、%、^(幂运算)、括号()。",
|
||||||
"product.product-things-model.142341-55": "如:",
|
"product.product-things-model.142341-55": "如:",
|
||||||
"product.product-things-model.142341-56": "加:%s+10",
|
"product.product-things-model.142341-56": "加:%s+10",
|
||||||
"product.product-things-model.142341-57": "减:%s-10",
|
"product.product-things-model.142341-57": "减:%s-10",
|
||||||
@ -305,6 +307,10 @@
|
|||||||
"product.product-things-model.142341-59": "除:%s/10",
|
"product.product-things-model.142341-59": "除:%s/10",
|
||||||
"product.product-things-model.142341-60": "除(保留小数):%s%10.00",
|
"product.product-things-model.142341-60": "除(保留小数):%s%10.00",
|
||||||
"product.product-things-model.142341-61": "保留小数",
|
"product.product-things-model.142341-61": "保留小数",
|
||||||
|
"product.product-things-model.142341-61-1": "引用其他物模型示例:",
|
||||||
|
"product.product-things-model.142341-61-2": "压力=K*频率平方-常数:1.5*{frequency}^2-100",
|
||||||
|
"product.product-things-model.142341-61-3": "速度=路程/时间:{distance}/{time}",
|
||||||
|
"product.product-things-model.142341-61-4": "混合计算:%s*{factor}+{offset}",
|
||||||
"product.product-things-model.142341-62": "控制公式",
|
"product.product-things-model.142341-62": "控制公式",
|
||||||
"product.product-things-model.142341-63": "布尔值",
|
"product.product-things-model.142341-63": "布尔值",
|
||||||
"product.product-things-model.142341-64": "例如:关闭",
|
"product.product-things-model.142341-64": "例如:关闭",
|
||||||
|
|||||||
@ -241,7 +241,8 @@ export default {
|
|||||||
return {
|
return {
|
||||||
name: this.identifierList.find((chil) => chil.identifier === item).modelName,
|
name: this.identifierList.find((chil) => chil.identifier === item).modelName,
|
||||||
type: 'line',
|
type: 'line',
|
||||||
stack: this.$t('dataCenter.analysis.349202-12'),
|
//stack: this.$t('dataCenter.analysis.349202-12'),
|
||||||
|
smooth: true,
|
||||||
data: this.deviceLineList.map((d) => {
|
data: this.deviceLineList.map((d) => {
|
||||||
const ide = Object.values(d)[0].find((f) => Object.keys(f)[0] === item);
|
const ide = Object.values(d)[0].find((f) => Object.keys(f)[0] === item);
|
||||||
return Object.values(ide)[0];
|
return Object.values(ide)[0];
|
||||||
|
|||||||
@ -515,7 +515,7 @@ export default {
|
|||||||
return {
|
return {
|
||||||
name: this.devIdentifierList.find((chil) => chil.identifier === item).modelName,
|
name: this.devIdentifierList.find((chil) => chil.identifier === item).modelName,
|
||||||
type: 'line',
|
type: 'line',
|
||||||
stack: this.$t('dataCenter.history.384934-23'),
|
smooth: true,
|
||||||
data: this.devDatas.map((d) => {
|
data: this.devDatas.map((d) => {
|
||||||
const ide = Object.values(d)[0].find((f) => Object.keys(f)[0] === item);
|
const ide = Object.values(d)[0].find((f) => Object.keys(f)[0] === item);
|
||||||
return Object.values(ide)[0];
|
return Object.values(ide)[0];
|
||||||
@ -665,7 +665,7 @@ export default {
|
|||||||
return {
|
return {
|
||||||
name: this.sceneIdentifierList.find((chil) => chil.identifier === item).sourceName,
|
name: this.sceneIdentifierList.find((chil) => chil.identifier === item).sourceName,
|
||||||
type: 'line',
|
type: 'line',
|
||||||
stack: this.$t('dataCenter.history.384934-23'),
|
smooth: true,
|
||||||
data: this.sceneDatas.map((d) => {
|
data: this.sceneDatas.map((d) => {
|
||||||
const ide = Object.values(d)[0].find((f) => Object.keys(f)[0] === item);
|
const ide = Object.values(d)[0].find((f) => Object.keys(f)[0] === item);
|
||||||
return Object.values(ide)[0];
|
return Object.values(ide)[0];
|
||||||
|
|||||||
@ -35,6 +35,14 @@
|
|||||||
<el-switch v-model="scope.row.status" active-value="0" inactive-value="1" @change="handleStatusChange(scope.row)"></el-switch>
|
<el-switch v-model="scope.row.status" active-value="0" inactive-value="1" @change="handleStatusChange(scope.row)"></el-switch>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
|
<el-table-column label="执行时间" align="center" prop="executeTime" width="180">
|
||||||
|
<template slot-scope="scope">
|
||||||
|
<span v-if="scope.row.cycleType === 3 && scope.row.status === '1' && scope.row.executeTime">
|
||||||
|
{{ parseTime(scope.row.executeTime, '{y}-{m}-{d} {h}:{i}:{s}') }}
|
||||||
|
</span>
|
||||||
|
<span v-else>-</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
<el-table-column :label="$t('device.device-modbus-task.384302-59')" align="center" prop="remarkStr"></el-table-column>
|
<el-table-column :label="$t('device.device-modbus-task.384302-59')" align="center" prop="remarkStr"></el-table-column>
|
||||||
<el-table-column :label="$t('device.device-modbus-task.384302-60')" align="center" class-name="small-padding fixed-width">
|
<el-table-column :label="$t('device.device-modbus-task.384302-60')" align="center" class-name="small-padding fixed-width">
|
||||||
<template slot-scope="scope">
|
<template slot-scope="scope">
|
||||||
@ -55,7 +63,7 @@
|
|||||||
<!-- 从机地址 -->
|
<!-- 从机地址 -->
|
||||||
<el-col :span="12">
|
<el-col :span="12">
|
||||||
<el-form-item :label="$t('device.device-modbus-task.384302-14')" prop="path">
|
<el-form-item :label="$t('device.device-modbus-task.384302-14')" prop="path">
|
||||||
<el-input v-model="createForm.path" class="input-item" disabled></el-input>
|
<el-input v-model="createForm.path" class="input-item"></el-input>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
<!-- 功能码 -->
|
<!-- 功能码 -->
|
||||||
@ -307,6 +315,7 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { hex2int, int2hex, copyText } from '@/utils/common';
|
import { hex2int, int2hex, copyText } from '@/utils/common';
|
||||||
|
import { parseTime } from '@/utils/ruoyi';
|
||||||
import { encode } from '@/api/iot/mqttTest';
|
import { encode } from '@/api/iot/mqttTest';
|
||||||
import { listJob, getJob, delJob, addJob, updateJob, getSlaveId } from '@/api/iot/modbusJob';
|
import { listJob, getJob, delJob, addJob, updateJob, getSlaveId } from '@/api/iot/modbusJob';
|
||||||
|
|
||||||
|
|||||||
@ -93,7 +93,7 @@
|
|||||||
<!-- 从机地址 -->
|
<!-- 从机地址 -->
|
||||||
<el-col :span="24">
|
<el-col :span="24">
|
||||||
<el-form-item :label="$t('device.instruction-parsing.830424-19')" prop="path">
|
<el-form-item :label="$t('device.instruction-parsing.830424-19')" prop="path">
|
||||||
<el-input v-model="createForm.path" :disabled="true"></el-input>
|
<el-input v-model="createForm.path"></el-input>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
<!-- 指令类型 -->
|
<!-- 指令类型 -->
|
||||||
|
|||||||
@ -190,13 +190,18 @@
|
|||||||
<el-form-item :label="$t('product.product-things-model.142341-19')" prop="formula">
|
<el-form-item :label="$t('product.product-things-model.142341-19')" prop="formula">
|
||||||
<template slot="label">
|
<template slot="label">
|
||||||
<span>{{ $t('product.product-things-model.142341-19') }}</span>
|
<span>{{ $t('product.product-things-model.142341-19') }}</span>
|
||||||
<el-tooltip style="cursor: pointer" effect="light" placement="top">
|
<el-tooltip style="cursor: pointer" effect="light" placement="top" max-width="500px">
|
||||||
<div slot="content">
|
<div slot="content">
|
||||||
{{ $t('product.product-things-model.142341-53') }}
|
{{ $t('product.product-things-model.142341-53') }}
|
||||||
<br />
|
<br />
|
||||||
{{ $t('product.product-things-model.142341-54') }}
|
{{ $t('product.product-things-model.142341-54') }}
|
||||||
<br />
|
<br />
|
||||||
{{ $t('product.product-things-model.142341-55') }}
|
{{ $t('product.product-things-model.142341-54-1') }}
|
||||||
|
<br />
|
||||||
|
{{ $t('product.product-things-model.142341-54-2') }}
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
<strong>{{ $t('product.product-things-model.142341-55') }}</strong>
|
||||||
<br />
|
<br />
|
||||||
{{ $t('product.product-things-model.142341-56') }}
|
{{ $t('product.product-things-model.142341-56') }}
|
||||||
<br />
|
<br />
|
||||||
@ -208,6 +213,15 @@
|
|||||||
<br />
|
<br />
|
||||||
{{ $t('product.product-things-model.142341-60') }}({{ $t('product.product-things-model.142341-61') }}):%s%10.00
|
{{ $t('product.product-things-model.142341-60') }}({{ $t('product.product-things-model.142341-61') }}):%s%10.00
|
||||||
<br />
|
<br />
|
||||||
|
<br />
|
||||||
|
<strong>{{ $t('product.product-things-model.142341-61-1') }}</strong>
|
||||||
|
<br />
|
||||||
|
{{ $t('product.product-things-model.142341-61-2') }}
|
||||||
|
<br />
|
||||||
|
{{ $t('product.product-things-model.142341-61-3') }}
|
||||||
|
<br />
|
||||||
|
{{ $t('product.product-things-model.142341-61-4') }}
|
||||||
|
<br />
|
||||||
</div>
|
</div>
|
||||||
<i class="el-icon-question" />
|
<i class="el-icon-question" />
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user