20260117:纯计算物模型下沉到设备端

This commit is contained in:
George 2026-01-17 23:11:34 +08:00
parent faa11dc37a
commit d12016e04a
30 changed files with 1555 additions and 806 deletions

View File

@ -129,7 +129,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.160 # 本服务启动的 IP ip: 192.168.110.160 # 本服务启动的 IP

View File

@ -239,10 +239,18 @@ public interface FastBeeConstant {
String DEVICE_PRE_KEY = "TSLV:"; String DEVICE_PRE_KEY = "TSLV:";
/** /**
* 物模型缓存 * 物模型缓存(产品级)
* Key格式:TSL:{productId} hKey: identifier value: thingsModel
*/ */
String TSL_PRE_KEY = "TSL:"; String TSL_PRE_KEY = "TSL:";
/**
* 设备级计算物模型缓存
* Key格式:TSLD:{deviceId} hKey: identifier value: thingsModel
* 用于存储设备级别的纯计算物模型,device_id > 0
*/
String TSLD_PRE_KEY = "TSLD:";
String MODBUS_PRE_KEY = "MODBUS:"; String MODBUS_PRE_KEY = "MODBUS:";
/** /**

View File

@ -54,13 +54,21 @@ public class RedisKeyBuilder {
} }
/** /**
* 物模型缓存key * 物模型缓存key(产品级)
* 物模型命名空间:Key:TSL:{productId} hkey: identity value: thingsModel * 物模型命名空间:Key:TSL:{productId} hkey: identity value: thingsModel
*/ */
public static String buildTSLCacheKey(Long productId) { public static String buildTSLCacheKey(Long productId) {
return FastBeeConstant.REDIS.TSL_PRE_KEY + productId; return FastBeeConstant.REDIS.TSL_PRE_KEY + productId;
} }
/**
* 设备级计算物模型缓存key
* 物模型命名空间:Key:TSLD:{SerialNumber} hkey: identity value: thingsModel
*/
public static String buildDeviceTSLCacheKey(String serialNumber) {
return FastBeeConstant.REDIS.TSLD_PRE_KEY + serialNumber;
}
public static String buildModbusKey(Long productId) { public static String buildModbusKey(Long productId) {
return FastBeeConstant.REDIS.MODBUS_PRE_KEY + productId; return FastBeeConstant.REDIS.MODBUS_PRE_KEY + productId;
} }

View File

@ -39,6 +39,11 @@ public class ThingsModelSimpleItem {
**/ **/
private String remark; private String remark;
/**
* 是否纯计算型物模型
*/
private boolean isCalculated;
public ThingsModelSimpleItem(String id, String value, String remark) { public ThingsModelSimpleItem(String id, String value, String remark) {
this.id = id; this.id = id;
@ -88,4 +93,12 @@ public class ThingsModelSimpleItem {
public void setName(String name) { public void setName(String name) {
this.name = name; this.name = name;
} }
public boolean isCalculated() {
return isCalculated;
}
public void setCalculated(boolean calculated) {
isCalculated = calculated;
}
} }

View File

@ -19,11 +19,9 @@ import com.fastbee.common.utils.StringUtils;
import com.fastbee.common.utils.gateway.CRC16Utils; import com.fastbee.common.utils.gateway.CRC16Utils;
import com.fastbee.common.utils.modbus.BitUtils; import com.fastbee.common.utils.modbus.BitUtils;
import com.fastbee.helper.ModbusProtocolHelper; import com.fastbee.helper.ModbusProtocolHelper;
import com.fastbee.iot.cache.ICalculatedThingsModelCache;
import com.fastbee.iot.cache.IModbusConfigCache; import com.fastbee.iot.cache.IModbusConfigCache;
import com.fastbee.iot.domain.Device; import com.fastbee.iot.cache.ITSLCache;
import com.fastbee.iot.domain.ModbusConfig; import com.fastbee.iot.domain.ModbusConfig;
import com.fastbee.iot.mapper.DeviceMapper;
import com.fastbee.iot.mapper.ModbusConfigMapper; import com.fastbee.iot.mapper.ModbusConfigMapper;
import com.fastbee.iot.model.ThingsModels.ThingsModelValueItem; import com.fastbee.iot.model.ThingsModels.ThingsModelValueItem;
import com.fastbee.iot.service.ISubGatewayService; import com.fastbee.iot.service.ISubGatewayService;
@ -40,7 +38,6 @@ import javax.annotation.Resource;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set;
import java.util.function.Function; import java.util.function.Function;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -63,14 +60,12 @@ public class ModbusProtocol implements IProtocol {
private IModbusConfigCache modbusConfigCache; private IModbusConfigCache modbusConfigCache;
@Resource @Resource
private ISubGatewayService subGatewayService; private ISubGatewayService subGatewayService;
@Resource
private DeviceMapper deviceMapper;
@Resource @Resource
private ModbusConfigMapper modbusConfigMapper; private ModbusConfigMapper modbusConfigMapper;
@Resource @Resource
private ICalculatedThingsModelCache calculatedThingsModelCache; private ITSLCache itslCache;
@Override @Override
public DeviceReport decode(DeviceData deviceData, String clientId) { public DeviceReport decode(DeviceData deviceData, String clientId) {
@ -103,16 +98,16 @@ public class ModbusProtocol implements IProtocol {
case Read01: case Read01:
case Read02: case Read02:
//起始地址 //起始地址
String address0102 = getCacheModbusAddress(deviceData.getSerialNumber(), slaveId, code); String address0102 = getCacheModbusAddress(productId, slaveId, code);
//读线圈或读离散型寄存器处理 //读线圈或读离散型寄存器处理
handleRead0102(productId, message, address0102, values); handleRead0102(productId, message, address0102, values);
break; break;
case Read03: case Read03:
case Read04: case Read04:
//起始地址 //起始地址
String address0304 = getCacheModbusAddress(deviceData.getSerialNumber(), slaveId, code); String address0304 = getCacheModbusAddress(productId, slaveId, code);
//读输入、保持寄存器 //读输入、保持寄存器
handleRead0304(productId, message, address0304, values, hexDump); handleRead0304(deviceData.getSerialNumber(), productId, message, address0304, values, hexDump);
break; break;
case Write06: case Write06:
case Write05: case Write05:
@ -191,13 +186,14 @@ public class ModbusProtocol implements IProtocol {
/** /**
* 处理读保持寄存器/输入寄存器 * 处理读保持寄存器/输入寄存器
* *
* @param serialNumber 设备序列号
* @param productId 产品id * @param productId 产品id
* @param message 数据 * @param message 数据
* @param address 起始地址 * @param address 起始地址
* @param simpleItemList 返回的物模型值 * @param simpleItemList 返回的物模型值
* @param sourceStr 原始报文字符串 * @param sourceStr 原始报文字符串
*/ */
private void handleRead0304(Long productId, ModbusRtu message, String address, List<ThingsModelSimpleItem> simpleItemList, String sourceStr) { private void handleRead0304(String serialNumber, Long productId, ModbusRtu message, String address, List<ThingsModelSimpleItem> simpleItemList, String sourceStr) {
short[] data = message.getData(); short[] data = message.getData();
int length = data.length; int length = data.length;
if (length == 1) { if (length == 1) {
@ -318,50 +314,35 @@ public class ModbusProtocol implements IProtocol {
} }
// 处理纯计算物模型:查询产品下所有纯计算物模型,构造 ThingsModelSimpleItem,值为 "0" // 处理纯计算物模型:查询产品下所有纯计算物模型,构造 ThingsModelSimpleItem,值为 "0"
handleCalculatedThingsModels(productId, simpleItemList, isReadOnly); handleCalculatedThingsModels(serialNumber, simpleItemList);
} }
} }
/** /**
* 处理纯计算物模型 * 从redis获取 纯计算物模型(设备级别物模型)
* 从缓存获取纯计算物模型列表,构造 ThingsModelSimpleItem,值为 "0"
* 后续在计算环节会统一处理所有物模型(包括纯计算物模型)
* *
* @param productId 产品ID * @param serialNumber 设备编号
* @param simpleItemList 物模型值列表 * @param simpleItemList 物模型值列表
* @param isReadOnly 是否只读
*/ */
private void handleCalculatedThingsModels(Long productId, List<ThingsModelSimpleItem> simpleItemList, int isReadOnly) { private void handleCalculatedThingsModels(String serialNumber, List<ThingsModelSimpleItem> simpleItemList) {
try { try {
// 从缓存获取纯计算物模型标识符集合(支持产品级别和设备级别) Map<String, ThingsModelValueItem> calculatedModelMap = itslCache.getDeviceCalculatedModelMap(serialNumber);
Set<String> calculatedIdentifiers = calculatedThingsModelCache.getCalculatedIdentifiers(productId, null);
// 构造 ThingsModelSimpleItem,值为 "0"
if (calculatedIdentifiers.isEmpty()) { for (Map.Entry<String, ThingsModelValueItem> entry : calculatedModelMap.entrySet()) {
return; String identifier = entry.getKey();
}
// 获取已上报的物模型标识符集合(避免重复)
Set<String> reportedIdentifiers = simpleItemList.stream()
.map(ThingsModelSimpleItem::getId)
.collect(Collectors.toSet());
// 为纯计算物模型构造 ThingsModelSimpleItem
for (String identifier : calculatedIdentifiers) {
// 跳过已上报的物模型
if (reportedIdentifiers.contains(identifier)) {
continue;
}
// 构造 ThingsModelSimpleItem,值为 "0"
ThingsModelSimpleItem simpleItem = new ThingsModelSimpleItem(); ThingsModelSimpleItem simpleItem = new ThingsModelSimpleItem();
simpleItem.setId(identifier); simpleItem.setId(identifier);
simpleItem.setValue("0"); // 初始值设为0,后续在计算环节会重新计算 simpleItem.setValue("0"); // 初始值设为0,后续在计算环节会重新计算
simpleItem.setTs(DateUtils.getNowDate()); simpleItem.setTs(DateUtils.getNowDate());
simpleItem.setCalculated(true);
simpleItemList.add(simpleItem); simpleItemList.add(simpleItem);
log.debug("=>添加纯计算物模型到上报列表,产品ID:[{}],标识符:[{}]", productId, identifier); log.debug("=>添加纯计算物模型到上报列表,设备:[{}],标识符:[{}]", serialNumber, identifier);
} }
} catch (Exception e) { } catch (Exception e) {
log.error("=>处理纯计算物模型时发生错误,产品ID:[{}],错误:{}", productId, e.getMessage(), e); log.error("=>处理纯计算物模型时发生错误,设备:[{}],错误:{}", serialNumber, e.getMessage(), e);
} }
} }
@ -484,9 +465,9 @@ public class ModbusProtocol implements IProtocol {
} }
/** /**
* 获取modbus地址 * 获取寄存器最小modbus地址,从最小的寄存器地址开始解码
*/ */
private String getCacheModbusAddress(String serialNumber, int salveId, int code) { private String getCacheModbusAddress(Long productId, int salveId, int code) {
// String key = RedisKeyBuilder.buildModbusRuntimeCacheKey(serialNumber); // String key = RedisKeyBuilder.buildModbusRuntimeCacheKey(serialNumber);
// Set<String> commandSet = redisCache.zRange(key, 0, -1); // Set<String> commandSet = redisCache.zRange(key, 0, -1);
// //
@ -502,13 +483,14 @@ public class ModbusProtocol implements IProtocol {
// log.warn("解析指令异常: {}", e.getMessage()); // log.warn("解析指令异常: {}", e.getMessage());
// } // }
// } // }
// todo 拿缓存
// 2. 无匹配时查询数据库 // 2. 无匹配时查询数据库
try { try {
Device device = deviceMapper.selectDeviceBySerialNumber(serialNumber); // Device device = deviceMapper.selectDeviceBySerialNumber(serialNumber);
if (device == null) return "0"; // if (device == null) return "0";
List<ModbusConfig> configs = modbusConfigMapper.selectModbusConfigListByProductId(device.getProductId()); List<ModbusConfig> configs = modbusConfigMapper.selectModbusConfigListByProductId(productId);
if (CollectionUtils.isEmpty(configs)) return "0"; if (CollectionUtils.isEmpty(configs)) return "0";
return configs.stream() return configs.stream()

View File

@ -125,12 +125,13 @@ public class DelimiterBasedFrameDecoder extends ByteToMessageDecoder {
if (minDelim != null) { if (minDelim != null) {
int minDelimLength = minDelim.value.length; int minDelimLength = minDelim.value.length;
ByteBuf frame = null; ByteBuf frame = null;
boolean keepDelimiterForNextFrame = shouldKeepDelimiterForNextFrame(minDelim) && minFrameLength > 0;
if (discardingTooLongFrame) { if (discardingTooLongFrame) {
// 如果true,将长度不符合报文丢弃 // 如果true,将长度不符合报文丢弃
// 初始化原来的值 // 初始化原来的值
discardingTooLongFrame = false; discardingTooLongFrame = false;
buffer.skipBytes(minFrameLength + minDelimLength); buffer.skipBytes(minFrameLength + (keepDelimiterForNextFrame ? 0 : minDelimLength));
int tooLongFrameLength = this.tooLongFrameLength; int tooLongFrameLength = this.tooLongFrameLength;
this.tooLongFrameLength = 0; this.tooLongFrameLength = 0;
@ -142,10 +143,16 @@ public class DelimiterBasedFrameDecoder extends ByteToMessageDecoder {
/*小于最小长度帧处理*/ /*小于最小长度帧处理*/
if (minFrameLength > maxFrameLength) { if (minFrameLength > maxFrameLength) {
//放弃读取帧 //放弃读取帧
buffer.skipBytes(minFrameLength + minDelimLength); buffer.skipBytes(minFrameLength + (keepDelimiterForNextFrame ? 0 : minDelimLength));
fail(minFrameLength); fail(minFrameLength);
return null; return null;
} }
if (keepDelimiterForNextFrame) {
frame = buffer.readRetainedSlice(minFrameLength);
return frame;
}
/*是否需要跳过某字节*/ /*是否需要跳过某字节*/
if (minDelim.strip) { if (minDelim.strip) {
//忽略长度等于0的报文 //忽略长度等于0的报文
@ -182,6 +189,15 @@ public class DelimiterBasedFrameDecoder extends ByteToMessageDecoder {
return null; return null;
} }
} }
/**
* 7E 分隔符需要保留给下一帧作为帧头使用(仅在存在前置数据时)。
*/
private boolean shouldKeepDelimiterForNextFrame(Delimiter delimiter) {
return !delimiter.strip
&& delimiter.value.length == 1
&& (delimiter.value[0] & 0xFF) == 0x7E;
}
/** /**
* 检测7E帧格式(注册包/心跳包) * 检测7E帧格式(注册包/心跳包)

View File

@ -1,56 +0,0 @@
package com.fastbee.iot.cache;
import java.util.Set;
/**
* 纯计算物模型缓存接口
* 支持产品级别和设备级别的缓存
* 支持本地缓存和 Redis 缓存的切换
*
* @author Sumuxi
* @date 2026-01-02
*/
public interface ICalculatedThingsModelCache {
/**
* 获取产品下的纯计算物模型标识符集合
* @param productId 产品ID
* @return 纯计算物模型标识符集合
*/
Set<String> getCalculatedIdentifiersByProduct(Long productId);
/**
* 获取设备下的纯计算物模型标识符集合
* 如果设备有自定义物模型,返回设备级别的;否则返回产品级别的
* @param productId 产品ID
* @param deviceId 设备ID(可选,未来扩展)
* @return 纯计算物模型标识符集合
*/
Set<String> getCalculatedIdentifiers(Long productId, Long deviceId);
/**
* 设置产品下的纯计算物模型标识符集合
* @param productId 产品ID
* @param identifiers 纯计算物模型标识符集合
*/
void setCalculatedIdentifiersByProduct(Long productId, Set<String> identifiers);
/**
* 刷新产品下的纯计算物模型缓存
* @param productId 产品ID
*/
void refreshCalculatedCache(Long productId);
/**
* 清除产品下的纯计算物模型缓存
* @param productId 产品ID
*/
void clearCalculatedCache(Long productId);
/**
* 判断是否使用 Redis 缓存
* @return true-使用Redis,false-使用本地缓存
*/
boolean isRedisCacheEnabled();
}

View File

@ -61,7 +61,7 @@ public interface ITSLCache {
* \"name\":\"光强\",\"order\":0,\"regId\":\"light\",\"type\":1}]} * \"name\":\"光强\",\"order\":0,\"regId\":\"light\",\"type\":1}]}
* </p> * </p>
*/ */
public String setCacheThingsModelByProductId(Long productId); String setCacheThingsModelByProductId(Long productId);
/** /**
* 获取map物模型 --返回 Map<String, PropertyDto> * 获取map物模型 --返回 Map<String, PropertyDto>
@ -90,7 +90,7 @@ public interface ITSLCache {
* value:PropertyDto * value:PropertyDto
* </p> * </p>
*/ */
public Map<String, ThingsModelValueItem> setCacheThMapByProductId(Long productId); Map<String, ThingsModelValueItem> setCacheThMapByProductId(Long productId);
/** /**
* 获取单个物模型缓存 * 获取单个物模型缓存
@ -99,6 +99,40 @@ public interface ITSLCache {
* @param identify 标识符 * @param identify 标识符
* @return PropertyDto * @return PropertyDto
*/ */
public ThingsModelValueItem getSingleThingModels(Long productId, String identify); ThingsModelValueItem getSingleThingModels(Long productId, String identify);
// ==================== 设备级计算物模型缓存方法 ====================
/**
* 获取设备级计算物模型缓存(Map形式)
*
* @param serialNumber 设备ID
* @return Map<标识符, ThingsModelValueItem>
*/
Map<String, ThingsModelValueItem> getDeviceCalculatedModelMap(String serialNumber);
/**
* 刷新设备级计算物模型缓存
*
* @param serialNumber 设备ID
* @return Map<标识符, ThingsModelValueItem>
*/
Map<String, ThingsModelValueItem> refreshDeviceCalculatedModelCache(String serialNumber);
/**
* 清除设备级计算物模型缓存
*
* @param serialNumber 设备ID
*/
void clearDeviceCalculatedModelCache(String serialNumber);
/**
* 获取单个设备级计算物模型
*
* @param serialNumber 设备ID
* @param identifier 标识符
* @return ThingsModelValueItem
*/
ThingsModelValueItem getSingleDeviceCalculatedModel(String serialNumber, String identifier);
} }

View File

@ -1,149 +0,0 @@
package com.fastbee.iot.cache.impl;
import com.fastbee.iot.cache.ICalculatedThingsModelCache;
import com.fastbee.iot.domain.ThingsModel;
import com.fastbee.iot.service.IThingsModelService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import javax.annotation.Resource;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
/**
* 纯计算物模型缓存实现(本地缓存)
* 使用 ConcurrentHashMap 维护本地缓存
* 适用于计算型物模型数量较少、单机部署的场景
*
* @author sumuxi
* @date 2026/01/02
*/
@Service("calculatedThingsModelCache")
@ConditionalOnProperty(name = "fastbee.cache.calculated-things-model.use-redis", havingValue = "false", matchIfMissing = true)
@Slf4j
public class CalculatedThingsModelCacheImpl implements ICalculatedThingsModelCache {
/**
* 本地缓存:productId -> Set<String>(纯计算物模型标识符集合)
*/
private final ConcurrentHashMap<Long, Set<String>> calculatedCache = new ConcurrentHashMap<>();
/**
* 设备级别缓存:deviceId -> Set<String>(未来扩展)
* 当设备有自定义物模型选择时使用
*/
private final ConcurrentHashMap<Long, Set<String>> deviceCalculatedCache = new ConcurrentHashMap<>();
@Resource
private IThingsModelService thingsModelService;
/**
* 是否启用 Redis 缓存(通过配置控制)
* 默认 false,使用本地缓存
* 当计算型物模型数量较多或需要集群部署时,设置为 true
*/
@Value("${fastbee.cache.calculated-things-model.use-redis:false}")
private boolean useRedisCache;
/**
* 本地缓存大小阈值
* 超过此阈值建议切换到 Redis 缓存
*/
@Value("${fastbee.cache.calculated-things-model.local-cache-threshold:1000}")
private int localCacheThreshold;
@Override
public Set<String> getCalculatedIdentifiersByProduct(Long productId) {
if (productId == null) {
return Collections.emptySet();
}
// 从本地缓存获取
Set<String> identifiers = calculatedCache.get(productId);
if (identifiers != null) {
return identifiers;
}
// 缓存未命中,从数据库加载
refreshCalculatedCache(productId);
return calculatedCache.getOrDefault(productId, Collections.emptySet());
}
@Override
public Set<String> getCalculatedIdentifiers(Long productId, Long deviceId) {
// 如果设备有自定义物模型选择,优先返回设备级别的
if (deviceId != null) {
Set<String> deviceIdentifiers = deviceCalculatedCache.get(deviceId);
if (deviceIdentifiers != null) {
return deviceIdentifiers;
}
}
// 否则返回产品级别的
return getCalculatedIdentifiersByProduct(productId);
}
@Override
public void setCalculatedIdentifiersByProduct(Long productId, Set<String> identifiers) {
if (productId == null) {
return;
}
// 检查缓存大小,超过阈值时记录警告
if (calculatedCache.size() > localCacheThreshold) {
log.warn("=>纯计算物模型本地缓存大小超过阈值[{}],建议切换到Redis缓存", localCacheThreshold);
}
calculatedCache.put(productId, identifiers != null ? identifiers : Collections.emptySet());
}
@Override
public void refreshCalculatedCache(Long productId) {
if (productId == null) {
return;
}
try {
// 查询产品下所有物模型
ThingsModel queryModel = new ThingsModel();
queryModel.setProductId(productId);
List<ThingsModel> allThingsModels = thingsModelService.selectThingsModelList(queryModel);
if (CollectionUtils.isEmpty(allThingsModels)) {
calculatedCache.put(productId, Collections.emptySet());
return;
}
// 筛选出纯计算物模型(isCalculated == 1)
Set<String> calculatedIdentifiers = allThingsModels.stream()
.filter(model -> model.getIsCalculated() != null && model.getIsCalculated() == 1)
.map(ThingsModel::getIdentifier)
.collect(Collectors.toSet());
calculatedCache.put(productId, calculatedIdentifiers);
log.debug("=>刷新纯计算物模型本地缓存成功,产品ID:[{}],数量:[{}]", productId, calculatedIdentifiers.size());
} catch (Exception e) {
log.error("=>刷新纯计算物模型本地缓存失败,产品ID:[{}],错误:{}", productId, e.getMessage(), e);
calculatedCache.put(productId, Collections.emptySet());
}
}
@Override
public void clearCalculatedCache(Long productId) {
if (productId != null) {
calculatedCache.remove(productId);
}
}
@Override
public boolean isRedisCacheEnabled() {
return useRedisCache;
}
}

View File

@ -1,163 +0,0 @@
/*
package com.fastbee.iot.cache.impl;
import com.alibaba.fastjson2.JSON;
import com.fastbee.common.core.redis.RedisCache;
import com.fastbee.common.core.redis.RedisKeyBuilder;
import com.fastbee.iot.cache.ICalculatedThingsModelCache;
import com.fastbee.iot.domain.ThingsModel;
import com.fastbee.iot.service.IThingsModelService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import javax.annotation.Resource;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
*/
/**
* 纯计算物模型缓存实现(Redis缓存)
* 使用 Redis 维护缓存,支持集群部署
* 适用于计算型物模型数量较多、集群部署的场景
*
* @author Sumuxi
* @date 2026-01-02
*//*
@Service("calculatedThingsModelRedisCache")
@ConditionalOnProperty(name = "fastbee.cache.calculated-things-model.use-redis", havingValue = "true")
@Slf4j
public class CalculatedThingsModelRedisCacheImpl implements ICalculatedThingsModelCache {
*/
/**
* 缓存过期时间(秒),默认 24 小时
*//*
@Value("${fastbee.cache.calculated-things-model.redis-ttl:86400}")
private long redisTtl;
@Resource
private RedisCache redisCache;
@Resource
private IThingsModelService thingsModelService;
@Override
public Set<String> getCalculatedIdentifiersByProduct(Long productId) {
if (productId == null) {
return Collections.emptySet();
}
String cacheKey = RedisKeyBuilder.buildCalculatedThingsModelKey(productId);
String cacheValue = redisCache.getCacheObject(cacheKey);
if (cacheValue != null) {
try {
@SuppressWarnings("unchecked")
Set<String> result = JSON.parseObject(cacheValue, Set.class);
return result != null ? result : Collections.emptySet();
} catch (Exception e) {
log.warn("=>解析纯计算物模型Redis缓存失败,产品ID:[{}],错误:{}", productId, e.getMessage());
}
}
// 缓存未命中,从数据库加载
refreshCalculatedCache(productId);
cacheValue = redisCache.getCacheObject(cacheKey);
if (cacheValue != null) {
try {
@SuppressWarnings("unchecked")
Set<String> result = JSON.parseObject(cacheValue, Set.class);
return result != null ? result : Collections.emptySet();
} catch (Exception e) {
log.warn("=>解析纯计算物模型Redis缓存失败,产品ID:[{}],错误:{}", productId, e.getMessage());
}
}
return Collections.emptySet();
}
@Override
public Set<String> getCalculatedIdentifiers(Long productId, Long deviceId) {
// 如果设备有自定义物模型选择,优先返回设备级别的
if (deviceId != null) {
String deviceCacheKey = RedisKeyBuilder.buildCalculatedThingsModelDeviceKey(deviceId);
String deviceCacheValue = redisCache.getCacheObject(deviceCacheKey);
if (deviceCacheValue != null) {
try {
@SuppressWarnings("unchecked")
Set<String> result = JSON.parseObject(deviceCacheValue, Set.class);
return result != null ? result : Collections.emptySet();
} catch (Exception e) {
log.warn("=>解析设备纯计算物模型Redis缓存失败,设备ID:[{}],错误:{}", deviceId, e.getMessage());
}
}
}
// 否则返回产品级别的
return getCalculatedIdentifiersByProduct(productId);
}
@Override
public void setCalculatedIdentifiersByProduct(Long productId, Set<String> identifiers) {
if (productId == null) {
return;
}
String cacheKey = RedisKeyBuilder.buildCalculatedThingsModelKey(productId);
String cacheValue = JSON.toJSONString(identifiers != null ? identifiers : Collections.emptySet());
redisCache.setCacheObject(cacheKey, cacheValue, (int) redisTtl, java.util.concurrent.TimeUnit.SECONDS);
}
@Override
public void refreshCalculatedCache(Long productId) {
if (productId == null) {
return;
}
try {
// 查询产品下所有物模型
ThingsModel queryModel = new ThingsModel();
queryModel.setProductId(productId);
List<ThingsModel> allThingsModels = thingsModelService.selectThingsModelList(queryModel);
if (CollectionUtils.isEmpty(allThingsModels)) {
setCalculatedIdentifiersByProduct(productId, Collections.emptySet());
return;
}
// 筛选出纯计算物模型(isCalculated == 1)
Set<String> calculatedIdentifiers = allThingsModels.stream()
.filter(model -> model.getIsCalculated() != null && model.getIsCalculated() == 1)
.map(ThingsModel::getIdentifier)
.collect(Collectors.toSet());
setCalculatedIdentifiersByProduct(productId, calculatedIdentifiers);
log.debug("=>刷新纯计算物模型Redis缓存成功,产品ID:[{}],数量:[{}]", productId, calculatedIdentifiers.size());
} catch (Exception e) {
log.error("=>刷新纯计算物模型Redis缓存失败,产品ID:[{}],错误:{}", productId, e.getMessage(), e);
setCalculatedIdentifiersByProduct(productId, Collections.emptySet());
}
}
@Override
public void clearCalculatedCache(Long productId) {
if (productId != null) {
String cacheKey = RedisKeyBuilder.buildCalculatedThingsModelKey(productId);
redisCache.deleteObject(cacheKey);
}
}
@Override
public boolean isRedisCacheEnabled() {
return true;
}
}
*/

View File

@ -76,6 +76,10 @@ public class ModbusConfigCacheImpl implements IModbusConfigCache {
resultMap.put(entry.getKey() + "", valueStr); resultMap.put(entry.getKey() + "", valueStr);
} }
String modbusKey = RedisKeyBuilder.buildModbusKey(productId); String modbusKey = RedisKeyBuilder.buildModbusKey(productId);
// 先删除缓存,再插入缓存——完成缓存更新
if (redisCache.containsKey(modbusKey)) {
redisCache.deleteObject(modbusKey);
}
redisCache.hashPutAll(modbusKey, resultMap); redisCache.hashPutAll(modbusKey, resultMap);
return listMap; return listMap;
} }

View File

@ -23,10 +23,7 @@ import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils; import org.springframework.util.CollectionUtils;
import javax.annotation.Resource; import javax.annotation.Resource;
import java.util.ArrayList; import java.util.*;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Function; import java.util.function.Function;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -159,11 +156,9 @@ public class TSLCacheImpl implements ITSLCache {
/*组装成原格式数据*/ /*组装成原格式数据*/
Map<String, List<ThingsModelValueItem>> result = thingsModelMap.values().stream().map(x -> JSON.parseObject(x, ThingsModelValueItem.class)) Map<String, List<ThingsModelValueItem>> result = thingsModelMap.values().stream().map(x -> JSON.parseObject(x, ThingsModelValueItem.class))
.collect(Collectors.groupingBy(dto -> ThingsModelType.getName(dto.getType()))); .collect(Collectors.groupingBy(dto -> ThingsModelType.getName(dto.getType())));
if (result != null) { for (List<ThingsModelValueItem> itemList : result.values()) {
for (List<ThingsModelValueItem> itemList : result.values()) { for (ThingsModelValueItem item : itemList) {
for (ThingsModelValueItem item : itemList) { convertModelName(item, SecurityUtils.getLanguage());
convertModelName(item, SecurityUtils.getLanguage());
}
} }
} }
String jsonString = JSON.toJSONString(result); String jsonString = JSON.toJSONString(result);
@ -289,6 +284,7 @@ public class TSLCacheImpl implements ITSLCache {
// 数据库查询物模型集合 // 数据库查询物模型集合
ThingsModel model = new ThingsModel(); ThingsModel model = new ThingsModel();
model.setProductId(productId); model.setProductId(productId);
model.setIsCalculated(0); // 只查询产品级物模型
model.setLimitValue(SecurityUtils.getLanguage()); model.setLimitValue(SecurityUtils.getLanguage());
List<ThingsModel> thingsModels = thingsModelService.selectThingsModelList(model); List<ThingsModel> thingsModels = thingsModelService.selectThingsModelList(model);
//modbus配置参数整合到物模型 //modbus配置参数整合到物模型
@ -325,12 +321,132 @@ public class TSLCacheImpl implements ITSLCache {
/*缓存到redis*/ /*缓存到redis*/
String cacheKey = RedisKeyBuilder.buildTSLCacheKey(productId); String cacheKey = RedisKeyBuilder.buildTSLCacheKey(productId);
//先删除缓存 // 先删除缓存
if (redisCache.containsKey(cacheKey)) { if (redisCache.containsKey(cacheKey)) {
redisCache.deleteObject(cacheKey); redisCache.deleteObject(cacheKey);
} }
// 再插入缓存——完成缓存更新
redisCache.hashPutAll(cacheKey, thingsModelMap); redisCache.hashPutAll(cacheKey, thingsModelMap);
return thingsModelMap; return thingsModelMap;
} }
// ==================== 设备级计算物模型缓存方法 ====================
/**
* 获取设备级计算物模型缓存(Map形式)
*
* @param serialNumber 设备ID
* @return Map<标识符, ThingsModelValueItem>
*/
@Override
public Map<String, ThingsModelValueItem> getDeviceCalculatedModelMap(String serialNumber) {
if (StringUtils.isEmpty(serialNumber)) {
return Collections.emptyMap();
}
String cacheKey = RedisKeyBuilder.buildDeviceTSLCacheKey(serialNumber);
Map<String, Object> map = redisCache.getCacheMap(cacheKey);
if (!CollectionUtils.isEmpty(map)) {
Map<String, ThingsModelValueItem> itemMap = map.values().stream()
.map(v -> JSON.parseObject(v.toString(), ThingsModelValueItem.class))
.collect(Collectors.toMap(ThingsModelValueItem::getId, Function.identity()));
for (ThingsModelValueItem item : itemMap.values()) {
convertModelName(item, SecurityUtils.getLanguage());
}
return itemMap;
}
// 缓存未命中,从数据库加载
return refreshDeviceCalculatedModelCache(serialNumber);
}
/**
* 刷新设备级计算物模型缓存
*
* @param serialNumber 设备ID
* @return Map<标识符, ThingsModelValueItem>
*/
@Override
public Map<String, ThingsModelValueItem> refreshDeviceCalculatedModelCache(String serialNumber) {
if (StringUtils.isEmpty(serialNumber)) {
return Collections.emptyMap();
}
// 从数据库查询设备级计算物模型
ThingsModel model = new ThingsModel();
model.setSerialNumber(serialNumber);
model.setIsCalculated(1); // 查询设备级物模型
List<ThingsModel> models = thingsModelService.selectThingsModelList(model);
if (CollectionUtils.isEmpty(models)) {
// 清除可能存在的旧缓存
clearDeviceCalculatedModelCache(serialNumber);
return Collections.emptyMap();
}
// 转换为缓存格式
Map<String, String> cacheMap = models.stream().collect(Collectors.toMap(
ThingsModel::getIdentifier,
thingsModel -> {
ThingsModelValueItem dto = new ThingsModelValueItem();
BeanUtils.copyProperties(thingsModel, dto);
dto.setDatatype(JSONObject.parseObject(thingsModel.getSpecs(), Datatype.class))
.setId(thingsModel.getIdentifier())
.setName(thingsModel.getModelName())
.setName_zh_CN(thingsModel.getModelName_zh_CN())
.setName_en_US(thingsModel.getModelName_en_US())
.setOrder(thingsModel.getModelOrder())
.setModelId(thingsModel.getModelId())
.setFormula(thingsModel.getFormula())
.setIsCalculated(thingsModel.getIsCalculated());
return JSONObject.toJSONString(dto);
}
));
// 缓存到Redis——先删再写
String cacheKey = RedisKeyBuilder.buildDeviceTSLCacheKey(serialNumber);
if (redisCache.containsKey(cacheKey)) {
redisCache.deleteObject(cacheKey);
}
redisCache.hashPutAll(cacheKey, cacheMap);
// 返回转换后的Map
return cacheMap.values().stream()
.map(v -> JSON.parseObject(v, ThingsModelValueItem.class))
.collect(Collectors.toMap(ThingsModelValueItem::getId, Function.identity()));
}
/**
* 清除设备级计算物模型缓存
*
* @param serialNumber 设备ID
*/
@Override
public void clearDeviceCalculatedModelCache(String serialNumber) {
if (StringUtils.isNotEmpty(serialNumber)) {
String cacheKey = RedisKeyBuilder.buildDeviceTSLCacheKey(serialNumber);
redisCache.deleteObject(cacheKey);
}
}
/**
* 获取单个设备级计算物模型
*
* @param serialNumber 设备ID
* @param identifier 标识符
* @return ThingsModelValueItem
*/
@Override
public ThingsModelValueItem getSingleDeviceCalculatedModel(String serialNumber, String identifier) {
if (StringUtils.isEmpty(serialNumber) || StringUtils.isEmpty(identifier)) {
return null;
}
String cacheKey = RedisKeyBuilder.buildDeviceTSLCacheKey(serialNumber);
String itemStr = redisCache.getCacheMapValue(cacheKey, identifier);
if (StringUtils.isEmpty(itemStr)) {
// 尝试刷新缓存后再获取
Map<String, ThingsModelValueItem> modelMap = getDeviceCalculatedModelMap(serialNumber);
return modelMap.get(identifier);
}
ThingsModelValueItem item = JSONObject.parseObject(itemStr, ThingsModelValueItem.class);
convertModelName(item, SecurityUtils.getLanguage());
return item;
}
} }

View File

@ -43,8 +43,8 @@ public class TSLValueCacheImpl implements ITSLValueCache {
public List<ValueItem> getCacheDeviceStatus(Long productId, String deviceNumber) { public List<ValueItem> getCacheDeviceStatus(Long productId, String deviceNumber) {
String key = RedisKeyBuilder.buildTSLVCacheKey(productId, deviceNumber); String key = RedisKeyBuilder.buildTSLVCacheKey(productId, deviceNumber);
Map<String, String> map = redisCache.hashEntity(key); Map<String, String> map = redisCache.hashEntity(key);
List<ValueItem> valueList = new ArrayList<>(); List<ValueItem> valueList;
if (map == null || map.size() == 0) { if (map == null || map.isEmpty()) {
// 缓存设备状态(物模型值)到redis // 缓存设备状态(物模型值)到redis
valueList = addCacheDeviceStatus(productId, deviceNumber); valueList = addCacheDeviceStatus(productId, deviceNumber);
} else { } else {

View File

@ -63,6 +63,18 @@ public class ThingsModel extends BaseEntity {
@ApiModelProperty("产品名称") @ApiModelProperty("产品名称")
private String productName; private String productName;
/**
* 设备ID(0表示产品级物模型,>0表示设备级计算物模型)
*/
@ApiModelProperty(value = "设备ID", notes = "0表示产品级物模型,>0表示设备级计算物模型")
private Long deviceId;
/**
* 设备编号
*/
@ApiModelProperty("设备编号")
private String serialNumber;
/** /**
* 租户ID * 租户ID
*/ */

View File

@ -156,7 +156,6 @@ public class DeviceServiceImpl implements IDeviceService {
@Value("${server.tcp.port}") @Value("${server.tcp.port}")
private Long tcpPort; private Long tcpPort;
// select cache // select cache
/** /**
@ -165,7 +164,8 @@ public class DeviceServiceImpl implements IDeviceService {
* @param deviceId 设备主键 * @param deviceId 设备主键
* @return 设备 * @return 设备
*/ */
// @Cacheable(cacheNames = "Device", key = "#root.methodName + ':' + #serialNumber", unless = "#result == null") // @Cacheable(cacheNames = "Device", key = "#root.methodName + ':' +
// #serialNumber", unless = "#result == null")
@Override @Override
public Device selectDeviceByDeviceId(Long deviceId) { public Device selectDeviceByDeviceId(Long deviceId) {
Device device = deviceMapper.selectDeviceByDeviceId(deviceId); Device device = deviceMapper.selectDeviceByDeviceId(deviceId);
@ -174,7 +174,7 @@ public class DeviceServiceImpl implements IDeviceService {
// redis中获取设备状态(物模型值) // redis中获取设备状态(物模型值)
device.setThingsModelValue(JSONObject.toJSONString(list)); device.setThingsModelValue(JSONObject.toJSONString(list));
} }
//查询关联的监控设备 // 查询关联的监控设备
SipRelation sipRelation = new SipRelation(); SipRelation sipRelation = new SipRelation();
sipRelation.setReDeviceId(deviceId); sipRelation.setReDeviceId(deviceId);
List<SipRelation> sipRelationList = sipRelationService.selectSipRelationList(sipRelation); List<SipRelation> sipRelationList = sipRelationService.selectSipRelationList(sipRelation);
@ -280,7 +280,8 @@ public class DeviceServiceImpl implements IDeviceService {
*/ */
@Caching(evict = { @Caching(evict = {
@CacheEvict(cacheNames = "Device", key = "'selectDeviceBySerialNumber:' + #device.getSerialNumber()"), @CacheEvict(cacheNames = "Device", key = "'selectDeviceBySerialNumber:' + #device.getSerialNumber()"),
// @CacheEvict(cacheNames = "Device", key = "'selectDeviceByDeviceId:' + #device.getSerialNumber()"), // @CacheEvict(cacheNames = "Device", key = "'selectDeviceByDeviceId:' +
// #device.getSerialNumber()"),
@CacheEvict(cacheNames = "Device", key = "'selectDeviceStatusAndTransportStatus:' + #device.getSerialNumber()"), @CacheEvict(cacheNames = "Device", key = "'selectDeviceStatusAndTransportStatus:' + #device.getSerialNumber()"),
@CacheEvict(cacheNames = "Device", key = "'selectProtocolBySerialNumber:' + #device.getSerialNumber()"), @CacheEvict(cacheNames = "Device", key = "'selectProtocolBySerialNumber:' + #device.getSerialNumber()"),
@CacheEvict(cacheNames = "Device", key = "'selectShortDeviceBySerialNumber:' + #device.getSerialNumber()") @CacheEvict(cacheNames = "Device", key = "'selectShortDeviceBySerialNumber:' + #device.getSerialNumber()")
@ -312,7 +313,8 @@ public class DeviceServiceImpl implements IDeviceService {
@Caching(evict = { @Caching(evict = {
@CacheEvict(cacheNames = "Device", key = "'selectDeviceBySerialNumber:' + #device.getSerialNumber()"), @CacheEvict(cacheNames = "Device", key = "'selectDeviceBySerialNumber:' + #device.getSerialNumber()"),
// @CacheEvict(cacheNames = "Device", key = "'selectDeviceByDeviceId:' + #device.getSerialNumber()"), // @CacheEvict(cacheNames = "Device", key = "'selectDeviceByDeviceId:' +
// #device.getSerialNumber()"),
@CacheEvict(cacheNames = "Device", key = "'selectDeviceStatusAndTransportStatus:' + #device.getSerialNumber()"), @CacheEvict(cacheNames = "Device", key = "'selectDeviceStatusAndTransportStatus:' + #device.getSerialNumber()"),
@CacheEvict(cacheNames = "Device", key = "'selectProtocolBySerialNumber:' + #device.getSerialNumber()"), @CacheEvict(cacheNames = "Device", key = "'selectProtocolBySerialNumber:' + #device.getSerialNumber()"),
@CacheEvict(cacheNames = "Device", key = "'selectShortDeviceBySerialNumber:' + #device.getSerialNumber()") @CacheEvict(cacheNames = "Device", key = "'selectShortDeviceBySerialNumber:' + #device.getSerialNumber()")
@ -331,7 +333,7 @@ public class DeviceServiceImpl implements IDeviceService {
@CacheEvict(cacheNames = "Device", allEntries = true) @CacheEvict(cacheNames = "Device", allEntries = true)
@Override @Override
public AjaxResult deleteDeviceByDeviceId(Long deviceId) { public AjaxResult deleteDeviceByDeviceId(Long deviceId) {
//查询设备 // 查询设备
Device device = deviceMapper.selectDeviceByDeviceId(deviceId); Device device = deviceMapper.selectDeviceByDeviceId(deviceId);
SysUser user = getLoginUser().getUser(); SysUser user = getLoginUser().getUser();
Long sceneUserId; Long sceneUserId;
@ -356,7 +358,8 @@ public class DeviceServiceImpl implements IDeviceService {
} }
// 设备下不能有场景联动 // 设备下不能有场景联动
List<SceneDeviceBindVO> sceneDeviceBindVOList = sceneDeviceMapper.listSceneDeviceBind(device.getSerialNumber()); List<SceneDeviceBindVO> sceneDeviceBindVOList = sceneDeviceMapper.listSceneDeviceBind(device.getSerialNumber());
if (null != sceneDeviceBindVOList.stream().filter(s -> s.getUserId().equals(sceneUserId)).findAny().orElse(null)) { if (null != sceneDeviceBindVOList.stream().filter(s -> s.getUserId().equals(sceneUserId)).findAny()
.orElse(null)) {
return AjaxResult.error("删除失败,请先删除对应设备下的场景联动"); return AjaxResult.error("删除失败,请先删除对应设备下的场景联动");
} }
// 查绑定用户被分享用户配置的场景,需要把场景删掉 // 查绑定用户被分享用户配置的场景,需要把场景删掉
@ -393,7 +396,7 @@ public class DeviceServiceImpl implements IDeviceService {
if (null != shareSceneIds && shareSceneIds.length > 0) { if (null != shareSceneIds && shareSceneIds.length > 0) {
sceneService.deleteSceneBySceneIds(shareSceneIds); sceneService.deleteSceneBySceneIds(shareSceneIds);
} }
// 删除设备分组。 租户、管理员和设备所有者 // 删除设备分组。 租户、管理员和设备所有者
deviceMapper.deleteDeviceGroupByDeviceId(new UserIdDeviceIdModel(null, deviceId)); deviceMapper.deleteDeviceGroupByDeviceId(new UserIdDeviceIdModel(null, deviceId));
// 删除设备用户。 // 删除设备用户。
deviceUserMapper.deleteDeviceUserByDeviceId(new UserIdDeviceIdModel(null, deviceId)); deviceUserMapper.deleteDeviceUserByDeviceId(new UserIdDeviceIdModel(null, deviceId));
@ -402,7 +405,7 @@ public class DeviceServiceImpl implements IDeviceService {
// deviceJobService.deleteJobByDeviceIds(new Long[]{deviceId}); // deviceJobService.deleteJobByDeviceIds(new Long[]{deviceId});
// 批量删除设备监测日志 // 批量删除设备监测日志
logService.deleteDeviceLogByDeviceNumber(device.getSerialNumber()); logService.deleteDeviceLogByDeviceNumber(device.getSerialNumber());
// 批量删除设备事件日志 DeviceNumber // 批量删除设备事件日志 DeviceNumber
eventLogService.deleteEventLogBySerialNumber(device.getSerialNumber()); eventLogService.deleteEventLogBySerialNumber(device.getSerialNumber());
// 批量删除设备功能日志 // 批量删除设备功能日志
functionLogService.deleteFunctionLogByDeviceNumber(device.getSerialNumber()); functionLogService.deleteFunctionLogByDeviceNumber(device.getSerialNumber());
@ -412,12 +415,12 @@ public class DeviceServiceImpl implements IDeviceService {
String key = RedisKeyBuilder.buildTSLVCacheKey(device.getProductId(), device.getSerialNumber()); String key = RedisKeyBuilder.buildTSLVCacheKey(device.getProductId(), device.getSerialNumber());
redisCache.deleteObject(key); redisCache.deleteObject(key);
// 删除设备 // 删除设备
deviceMapper.deleteDeviceByDeviceIds(new Long[]{deviceId}); deviceMapper.deleteDeviceByDeviceIds(new Long[] { deviceId });
// redis中删除规则脚本 // redis中删除规则脚本
String cacheKey = RedisKeyBuilder.buildDeviceMsgCacheKey(device.getSerialNumber()); String cacheKey = RedisKeyBuilder.buildDeviceMsgCacheKey(device.getSerialNumber());
redisCache.deleteObject(cacheKey); redisCache.deleteObject(cacheKey);
// 删除设备 // 删除设备
deviceMapper.deleteDeviceByDeviceIds(new Long[]{deviceId}); deviceMapper.deleteDeviceByDeviceIds(new Long[] { deviceId });
// 删除tcp协议设备缓存 // 删除tcp协议设备缓存
String tcpKey1 = RedisKeyBuilder.buildModbusTcpCacheKey(device.getSerialNumber()); String tcpKey1 = RedisKeyBuilder.buildModbusTcpCacheKey(device.getSerialNumber());
String tcpKey2 = RedisKeyBuilder.buildModbusTcpRuntimeCacheKey(device.getSerialNumber()); String tcpKey2 = RedisKeyBuilder.buildModbusTcpRuntimeCacheKey(device.getSerialNumber());
@ -445,7 +448,6 @@ public class DeviceServiceImpl implements IDeviceService {
return AjaxResult.success(); return AjaxResult.success();
} }
/** /**
* TODO-2.3 查询设备统计信息 * TODO-2.3 查询设备统计信息
* *
@ -486,7 +488,6 @@ public class DeviceServiceImpl implements IDeviceService {
return deviceMapper.selectProductAuthenticate(model); return deviceMapper.selectProductAuthenticate(model);
} }
/** /**
* 更新设备的物模型 * 更新设备的物模型
* *
@ -497,33 +498,47 @@ public class DeviceServiceImpl implements IDeviceService {
*/ */
@Override @Override
@DSTransactional @DSTransactional
public List<ThingsModelSimpleItem> reportDeviceThingsModelValue(ThingsModelValuesInput input, int type, boolean isShadow) { public List<ThingsModelSimpleItem> reportDeviceThingsModelValue(ThingsModelValuesInput input, int type,
String key = RedisKeyBuilder.buildTSLVCacheKey(input.getProductId(), input.getDeviceNumber()); boolean isShadow) {
String serialNumber = input.getDeviceNumber();
String key = RedisKeyBuilder.buildTSLVCacheKey(input.getProductId(), serialNumber);
Map<String, String> maps = new HashMap<>(); Map<String, String> maps = new HashMap<>();
List<ThingsModelSimpleItem> list = new ArrayList<>(); List<ThingsModelSimpleItem> list = new ArrayList<>();
//属性存储集合 // 属性存储集合
List<DeviceLog> deviceLogList = new ArrayList<>(); List<DeviceLog> deviceLogList = new ArrayList<>();
//指令存储集合 // 指令存储集合
List<FunctionLog> functionLogList = new ArrayList<>(); List<FunctionLog> functionLogList = new ArrayList<>();
//事件存储集合 // 事件存储集合
List<EventLog> eventLogList = new ArrayList<>(); List<EventLog> eventLogList = new ArrayList<>();
// 获取当前时间 // 获取当前时间
Date nowDate = DateUtils.getNowDate(); Date nowDate = DateUtils.getNowDate();
for (ThingsModelSimpleItem item : input.getThingsModelValueRemarkItem()) { List<ThingsModelSimpleItem> thingsModelValueRemarkItem = input.getThingsModelValueRemarkItem();
// 判断是否有纯计算物模型 有:需要获取设备级别物模型 无:直接使用产品级别物模型即可
boolean isCalculated = thingsModelValueRemarkItem.stream().anyMatch(ThingsModelSimpleItem::isCalculated);
Map<String, ThingsModelValueItem> calculatedModelMap = new HashMap<>();
if (isCalculated) {
calculatedModelMap = itslCache.getDeviceCalculatedModelMap(serialNumber);
}
for (ThingsModelSimpleItem item : thingsModelValueRemarkItem) {
String identity = item.getId(); String identity = item.getId();
String serialNumber = input.getDeviceNumber();
if (identity.startsWith("array_")) { if (identity.startsWith("array_")) {
identity = identity.substring(9); identity = identity.substring(9);
} }
ThingsModelValueItem thingModels;
ThingsModelValueItem thingModels = thingsModelService.getSingleThingModels(input.getProductId(), identity); if (item.isCalculated()) {
// itslCache.getSingleDeviceCalculatedModel(serialNumber, identity);
thingModels = calculatedModelMap.get(identity);
} else {
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; // 跳过当前物模型的处理 continue; // 跳过当前物模型的处理
} }
// 获取设备实时值 // 获取设备实时值
ValueItem cacheIdentifier = itslValueCache.getCacheIdentifier(input.getProductId(), input.getDeviceNumber(), thingModels.getId()); ValueItem cacheIdentifier = itslValueCache.getCacheIdentifier(input.getProductId(), serialNumber,
thingModels.getId());
if (Objects.isNull(cacheIdentifier)) { if (Objects.isNull(cacheIdentifier)) {
log.warn("=>查询物模型值为空[{}],标识符:[{}]", input.getProductId(), identity); log.warn("=>查询物模型值为空[{}],标识符:[{}]", input.getProductId(), identity);
} }
@ -531,9 +546,9 @@ public class DeviceServiceImpl implements IDeviceService {
String value = item.getValue(); String value = item.getValue();
item.setName(thingModels.getName()); item.setName(thingModels.getName());
/* ★★★★★★★★★★★★★★★★★★★★★★ 数据计算 -开始 ★★★★★★★★★★★★★★★★★★★★★★*/ /* ★★★★★★★★★★★★★★★★★★★★★★ 数据计算 -开始 ★★★★★★★★★★★★★★★★★★★★★★ */
//有计算公式的,经过计算公式 // 有计算公式的,经过计算公式
if (thingModels != null && thingModels.getFormula() != null && !"".equals(thingModels.getFormula())) { if (thingModels.getFormula() != null && StringUtils.isNotEmpty(thingModels.getFormula())) {
Map<String, String> params = new HashMap<>(); Map<String, String> params = new HashMap<>();
// %s 表示当前物模型的值(保持向后兼容) // %s 表示当前物模型的值(保持向后兼容)
params.put("%s", value); params.put("%s", value);
@ -549,7 +564,7 @@ public class DeviceServiceImpl implements IDeviceService {
try { try {
// 优先从当前消息中查找 // 优先从当前消息中查找
String refValue = null; String refValue = null;
for (ThingsModelSimpleItem currentItem : input.getThingsModelValueRemarkItem()) { for (ThingsModelSimpleItem currentItem : thingsModelValueRemarkItem) {
String currentId = currentItem.getId(); String currentId = currentItem.getId();
if (currentId.startsWith("array_")) { if (currentId.startsWith("array_")) {
currentId = currentId.substring(9); currentId = currentId.substring(9);
@ -564,9 +579,8 @@ public class DeviceServiceImpl implements IDeviceService {
if (refValue == null) { if (refValue == null) {
ValueItem refValueItem = itslValueCache.getCacheIdentifier( ValueItem refValueItem = itslValueCache.getCacheIdentifier(
input.getProductId(), input.getProductId(),
input.getDeviceNumber(), serialNumber,
refIdentifier refIdentifier);
);
if (refValueItem != null && StringUtils.isNotEmpty(refValueItem.getValue())) { if (refValueItem != null && StringUtils.isNotEmpty(refValueItem.getValue())) {
refValue = refValueItem.getValue(); refValue = refValueItem.getValue();
} }
@ -590,15 +604,15 @@ public class DeviceServiceImpl implements IDeviceService {
log.info("=>纯计算物模型结果[{}],标识符:[{}],结果:{}", input.getProductId(), identity, value); log.info("=>纯计算物模型结果[{}],标识符:[{}],结果:{}", input.getProductId(), identity, value);
item.setValue(value); item.setValue(value);
} }
/* ★★★★★★★★★★★★★★★★★★★★★★ 数据计算 -结束 ★★★★★★★★★★★★★★★★★★★★★★*/ /* ★★★★★★★★★★★★★★★★★★★★★★ 数据计算 -结束 ★★★★★★★★★★★★★★★★★★★★★★ */
/* ★★★★★★★★★★★★★★★★★★★★★★ 处理数据 - 开始 ★★★★★★★★★★★★★★★★★★★★★★*/ /* ★★★★★★★★★★★★★★★★★★★★★★ 处理数据 - 开始 ★★★★★★★★★★★★★★★★★★★★★★ */
ValueItem valueItem = new ValueItem(); ValueItem valueItem = new ValueItem();
valueItem.setId(identity); valueItem.setId(identity);
if (id.startsWith("array_")) { if (id.startsWith("array_")) {
// 查询是否有缓存,如果没有先进行缓存 // 查询是否有缓存,如果没有先进行缓存
if (!redisCache.containsKey(key)) { if (!redisCache.containsKey(key)) {
Device device = this.selectDeviceBySerialNumber(input.getDeviceNumber()); Device device = this.selectDeviceBySerialNumber(serialNumber);
this.selectDeviceByDeviceId(device.getDeviceId()); this.selectDeviceByDeviceId(device.getDeviceId());
} }
@ -623,7 +637,7 @@ public class DeviceServiceImpl implements IDeviceService {
valueItem.setShadow(String.join(",", shadows)); valueItem.setShadow(String.join(",", shadows));
} }
redisCache.setCacheMapValue(key, identity, JSONObject.toJSONString(valueItem)); redisCache.setCacheMapValue(key, identity, JSONObject.toJSONString(valueItem));
//maps.put(identity, JSONObject.toJSONString(valueItem)); // maps.put(identity, JSONObject.toJSONString(valueItem));
} else { } else {
if (isShadow) { if (isShadow) {
valueItem.setShadow(value); valueItem.setShadow(value);
@ -638,11 +652,11 @@ public class DeviceServiceImpl implements IDeviceService {
} }
maps.put(identity, JSONObject.toJSONString(valueItem)); maps.put(identity, JSONObject.toJSONString(valueItem));
} }
/* ★★★★★★★★★★★★★★★★★★★★★★ 处理数据 - 结束 ★★★★★★★★★★★★★★★★★★★★★★*/ /* ★★★★★★★★★★★★★★★★★★★★★★ 处理数据 - 结束 ★★★★★★★★★★★★★★★★★★★★★★ */
/*★★★★★★★★★★★★★★★★★★★★★★ 存储数据 - 开始 ★★★★★★★★★★★★★★★★★★★★★★*/ /* ★★★★★★★★★★★★★★★★★★★★★★ 存储数据 - 开始 ★★★★★★★★★★★★★★★★★★★★★★ */
ThingsModelType modelType = ThingsModelType.getType(thingModels.getType()); ThingsModelType modelType = ThingsModelType.getType(thingModels.getType());
Device device = this.selectDeviceBySerialNumber(input.getDeviceNumber()); Device device = this.selectDeviceBySerialNumber(serialNumber);
switch (modelType) { switch (modelType) {
case PROP: case PROP:
if (1 == thingModels.getIsHistory()) { if (1 == thingModels.getIsHistory()) {
@ -670,7 +684,7 @@ public class DeviceServiceImpl implements IDeviceService {
FunctionLog function = new FunctionLog(); FunctionLog function = new FunctionLog();
function.setCreateTime(nowDate); function.setCreateTime(nowDate);
function.setFunValue(value); function.setFunValue(value);
function.setSerialNumber(input.getDeviceNumber()); function.setSerialNumber(serialNumber);
function.setIdentify(id); function.setIdentify(id);
function.setShowValue(value); function.setShowValue(value);
// 属性获取 // 属性获取
@ -703,22 +717,20 @@ public class DeviceServiceImpl implements IDeviceService {
redisCache.hashPutAll(key, maps); redisCache.hashPutAll(key, maps);
// 存储历史数据——批量 // 存储历史数据——批量
if (!CollectionUtils.isEmpty(deviceLogList) && !isShadow) { if (!CollectionUtils.isEmpty(deviceLogList) && !isShadow) {
// for (DeviceLog deviceLog : deviceLogList) { // for (DeviceLog deviceLog : deviceLogList) {
// logService.saveDeviceLog(deviceLog); // logService.saveDeviceLog(deviceLog);
// } // }
logService.saveDeviceLogBatch(deviceLogList); logService.saveDeviceLogBatch(deviceLogList);
} }
//指令存储,影子模式不存储 // 指令存储,影子模式不存储
if (!CollectionUtils.isEmpty(functionLogList) && !isShadow) { if (!CollectionUtils.isEmpty(functionLogList) && !isShadow) {
functionLogService.insertBatch(functionLogList); functionLogService.insertBatch(functionLogList);
} }
if (!CollectionUtils.isEmpty(eventLogList)) { if (!CollectionUtils.isEmpty(eventLogList)) {
//事件存储 // 事件存储
eventLogService.saveBatch(eventLogList); eventLogService.saveBatch(eventLogList);
} }
/* ★★★★★★★★★★★★★★★★★★★★★★ 存储数据 - 结束 ★★★★★★★★★★★★★★★★★★★★★★*/ /* ★★★★★★★★★★★★★★★★★★★★★★ 存储数据 - 结束 ★★★★★★★★★★★★★★★★★★★★★★ */
// 注意:纯计算物模型已在 ModbusProtocol 中构造 ThingsModelSimpleItem(值为0)
return list; return list;
} }
@ -751,17 +763,17 @@ public class DeviceServiceImpl implements IDeviceService {
*/ */
@Override @Override
public List<Device> selectDeviceList(Device device) { public List<Device> selectDeviceList(Device device) {
// SysUser user = getLoginUser().getUser(); // SysUser user = getLoginUser().getUser();
// List<SysRole> roles = user.getRoles(); // List<SysRole> roles = user.getRoles();
// for (int i = 0; i < roles.size(); i++) { // for (int i = 0; i < roles.size(); i++) {
// if (roles.get(i).getRoleKey().equals("tenant")) { // if (roles.get(i).getRoleKey().equals("tenant")) {
// // 租户查看产品下所有设备 // // 租户查看产品下所有设备
// device.setTenantId(user.getUserId()); // device.setTenantId(user.getUserId());
// } else if (roles.get(i).getRoleKey().equals("general")) { // } else if (roles.get(i).getRoleKey().equals("general")) {
// // 用户查看自己设备 // // 用户查看自己设备
// device.setUserId(user.getUserId()); // device.setUserId(user.getUserId());
// } // }
// } // }
return deviceMapper.selectDeviceList(device); return deviceMapper.selectDeviceList(device);
} }
@ -773,17 +785,17 @@ public class DeviceServiceImpl implements IDeviceService {
*/ */
@Override @Override
public List<Device> selectUnAuthDeviceList(Device device) { public List<Device> selectUnAuthDeviceList(Device device) {
// SysUser user = getLoginUser().getUser(); // SysUser user = getLoginUser().getUser();
// List<SysRole> roles = user.getRoles(); // List<SysRole> roles = user.getRoles();
// for (int i = 0; i < roles.size(); i++) { // for (int i = 0; i < roles.size(); i++) {
// if (roles.get(i).getRoleKey().equals("tenant")) { // if (roles.get(i).getRoleKey().equals("tenant")) {
// // 租户查看产品下所有设备 // // 租户查看产品下所有设备
// device.setTenantId(user.getUserId()); // device.setTenantId(user.getUserId());
// } else if (roles.get(i).getRoleKey().equals("general")) { // } else if (roles.get(i).getRoleKey().equals("general")) {
// // 用户查看自己设备 // // 用户查看自己设备
// device.setUserId(user.getUserId()); // device.setUserId(user.getUserId());
// } // }
// } // }
return deviceMapper.selectUnAuthDeviceList(device); return deviceMapper.selectUnAuthDeviceList(device);
} }
@ -795,17 +807,17 @@ public class DeviceServiceImpl implements IDeviceService {
*/ */
@Override @Override
public List<Device> selectDeviceListByGroup(Device device) { public List<Device> selectDeviceListByGroup(Device device) {
// SysUser user = getLoginUser().getUser(); // SysUser user = getLoginUser().getUser();
// List<SysRole> roles = user.getRoles(); // List<SysRole> roles = user.getRoles();
// for (int i = 0; i < roles.size(); i++) { // for (int i = 0; i < roles.size(); i++) {
// if (roles.get(i).getRoleKey().equals("tenant")) { // if (roles.get(i).getRoleKey().equals("tenant")) {
// // 租户查看产品下所有设备 // // 租户查看产品下所有设备
// device.setTenantId(user.getUserId()); // device.setTenantId(user.getUserId());
// } else if (roles.get(i).getRoleKey().equals("general")) { // } else if (roles.get(i).getRoleKey().equals("general")) {
// // 用户查看自己设备 // // 用户查看自己设备
// device.setUserId(user.getUserId()); // device.setUserId(user.getUserId());
// } // }
// } // }
return deviceMapper.selectDeviceListByGroup(device); return deviceMapper.selectDeviceListByGroup(device);
} }
@ -845,7 +857,8 @@ public class DeviceServiceImpl implements IDeviceService {
SysUser user = getLoginUser().getUser(); SysUser user = getLoginUser().getUser();
if (null != user.getDept()) { if (null != user.getDept()) {
Long tenantId = user.getDept().getDeptUserId(); Long tenantId = user.getDept().getDeptUserId();
serialNumberList = list.stream().filter(d -> tenantId.equals(d.getTenantId())).map(DeviceShortOutput::getSerialNumber).collect(Collectors.toList()); serialNumberList = list.stream().filter(d -> tenantId.equals(d.getTenantId()))
.map(DeviceShortOutput::getSerialNumber).collect(Collectors.toList());
} else { } else {
serialNumberList = list.stream().map(DeviceShortOutput::getSerialNumber).collect(Collectors.toList()); serialNumberList = list.stream().map(DeviceShortOutput::getSerialNumber).collect(Collectors.toList());
} }
@ -870,19 +883,42 @@ public class DeviceServiceImpl implements IDeviceService {
@Override @Override
public DeviceShortOutput selectDeviceRunningStatusByDeviceId(Long deviceId) { public DeviceShortOutput selectDeviceRunningStatusByDeviceId(Long deviceId) {
DeviceShortOutput device = deviceMapper.selectDeviceRunningStatusByDeviceId(deviceId); DeviceShortOutput device = deviceMapper.selectDeviceRunningStatusByDeviceId(deviceId);
JSONObject thingsModelObject = JSONObject.parseObject(itslCache.getCacheThingsModelByProductId(device.getProductId())); // 1. 获取产品级物模型
JSONObject thingsModelObject = JSONObject
.parseObject(itslCache.getCacheThingsModelByProductId(device.getProductId()));
JSONArray properties = thingsModelObject.getJSONArray("properties"); JSONArray properties = thingsModelObject.getJSONArray("properties");
JSONArray functions = thingsModelObject.getJSONArray("functions"); JSONArray functions = thingsModelObject.getJSONArray("functions");
List<ValueItem> thingsModelValueItems = itslValueCache.getCacheDeviceStatus(device.getProductId(), device.getSerialNumber()); List<ValueItem> thingsModelValueItems = itslValueCache.getCacheDeviceStatus(device.getProductId(),
device.getSerialNumber());
// 物模型转换赋值 // 物模型转换赋值
List<ThingsModelValueItem> thingsList = new ArrayList<>(); List<ThingsModelValueItem> thingsList = new ArrayList<>();
//判断一下properties 和 functions是否为空, 否则报空指针 // 判断一下properties 和 functions是否为空, 否则报空指针
if (!CollectionUtils.isEmpty(properties)) { if (!CollectionUtils.isEmpty(properties)) {
thingsList.addAll(convertJsonToThingsList(properties, thingsModelValueItems, 1)); thingsList.addAll(convertJsonToThingsList(properties, thingsModelValueItems, 1));
} }
if (!CollectionUtils.isEmpty(functions)) { if (!CollectionUtils.isEmpty(functions)) {
thingsList.addAll(convertJsonToThingsList(functions, thingsModelValueItems, 2)); thingsList.addAll(convertJsonToThingsList(functions, thingsModelValueItems, 2));
} }
// 2. 获取设备级计算物模型并合并
Map<String, ThingsModelValueItem> deviceCalculatedMap = itslCache
.getDeviceCalculatedModelMap(device.getSerialNumber());
if (!CollectionUtils.isEmpty(deviceCalculatedMap)) {
for (ThingsModelValueItem item : deviceCalculatedMap.values()) {
// 获取缓存值
ValueItem valueItem = thingsModelValueItems.stream()
.filter(v -> v.getId().equals(item.getId()))
.findFirst()
.orElse(null);
if (valueItem != null) {
item.setValue(valueItem.getValue());
item.setShadow(valueItem.getShadow());
item.setTs(valueItem.getTs());
}
thingsList.add(item);
}
}
device.setThingsModels(thingsList); device.setThingsModels(thingsList);
return device; return device;
@ -897,7 +933,8 @@ public class DeviceServiceImpl implements IDeviceService {
* @return * @return
*/ */
@Async @Async
public List<ThingsModelValueItem> convertJsonToThingsList(JSONArray jsonArray, List<ValueItem> thingsModelValues, Integer type) { public List<ThingsModelValueItem> convertJsonToThingsList(JSONArray jsonArray, List<ValueItem> thingsModelValues,
Integer type) {
List<ThingsModelValueItem> thingsModelList = new ArrayList<>(); List<ThingsModelValueItem> thingsModelList = new ArrayList<>();
for (int i = 0; i < jsonArray.size(); i++) { for (int i = 0; i < jsonArray.size(); i++) {
ThingsModelValueItem thingsModel = new ThingsModelValueItem(); ThingsModelValueItem thingsModel = new ThingsModelValueItem();
@ -905,11 +942,15 @@ public class DeviceServiceImpl implements IDeviceService {
JSONObject datatypeJson = thingsJson.getJSONObject("datatype"); JSONObject datatypeJson = thingsJson.getJSONObject("datatype");
thingsModel.setId(thingsJson.getString("id")); thingsModel.setId(thingsJson.getString("id"));
thingsModel.setName(thingsJson.getString("name")); thingsModel.setName(thingsJson.getString("name"));
thingsModel.setIsMonitor(thingsJson.getInteger("isMonitor") == null ? 0 : thingsJson.getInteger("isMonitor")); thingsModel
thingsModel.setIsReadonly(thingsJson.getInteger("isReadonly") == null ? 0 : thingsJson.getInteger("isReadonly")); .setIsMonitor(thingsJson.getInteger("isMonitor") == null ? 0 : thingsJson.getInteger("isMonitor"));
thingsModel.setIsReadonly(
thingsJson.getInteger("isReadonly") == null ? 0 : thingsJson.getInteger("isReadonly"));
thingsModel.setIsChart(thingsJson.getInteger("isChart") == null ? 0 : thingsJson.getInteger("isChart")); thingsModel.setIsChart(thingsJson.getInteger("isChart") == null ? 0 : thingsJson.getInteger("isChart"));
thingsModel.setIsSharePerm(thingsJson.getInteger("isSharePerm") == null ? 0 : thingsJson.getInteger("isSharePerm")); thingsModel.setIsSharePerm(
thingsModel.setIsHistory(thingsJson.getInteger("isHistory") == null ? 0 : thingsJson.getInteger("isHistory")); thingsJson.getInteger("isSharePerm") == null ? 0 : thingsJson.getInteger("isSharePerm"));
thingsModel
.setIsHistory(thingsJson.getInteger("isHistory") == null ? 0 : thingsJson.getInteger("isHistory"));
thingsModel.setIsApp(thingsJson.getInteger("isApp") == null ? 0 : thingsJson.getInteger("isApp")); thingsModel.setIsApp(thingsJson.getInteger("isApp") == null ? 0 : thingsJson.getInteger("isApp"));
thingsModel.setOrder(thingsJson.getInteger("order") == null ? 0 : thingsJson.getInteger("order")); thingsModel.setOrder(thingsJson.getInteger("order") == null ? 0 : thingsJson.getInteger("order"));
thingsModel.setType(type); thingsModel.setType(type);
@ -957,9 +998,10 @@ public class DeviceServiceImpl implements IDeviceService {
* @param parentIdentifier 上级标识符 * @param parentIdentifier 上级标识符
* @return * @return
*/ */
private Datatype convertJsonToDataType(JSONObject datatypeJson, List<ValueItem> thingsModelValues, Integer type, String parentIdentifier) { private Datatype convertJsonToDataType(JSONObject datatypeJson, List<ValueItem> thingsModelValues, Integer type,
String parentIdentifier) {
Datatype dataType = new Datatype(); Datatype dataType = new Datatype();
//有些物模型数据定义为空的情况兼容 // 有些物模型数据定义为空的情况兼容
if (datatypeJson == null) { if (datatypeJson == null) {
return dataType; return dataType;
} }
@ -986,10 +1028,12 @@ public class DeviceServiceImpl implements IDeviceService {
} else if (dataType.getType().equals("object")) { } else if (dataType.getType().equals("object")) {
JSONArray jsonArray = JSON.parseArray(datatypeJson.getString("params")); JSONArray jsonArray = JSON.parseArray(datatypeJson.getString("params"));
// 物模型值过滤(parentId_开头) // 物模型值过滤(parentId_开头)
thingsModelValues = thingsModelValues.stream().filter(x -> x.getId().startsWith(parentIdentifier)).collect(Collectors.toList()); thingsModelValues = thingsModelValues.stream().filter(x -> x.getId().startsWith(parentIdentifier))
.collect(Collectors.toList());
List<ThingsModelValueItem> thingsList = convertJsonToThingsList(jsonArray, thingsModelValues, type); List<ThingsModelValueItem> thingsList = convertJsonToThingsList(jsonArray, thingsModelValues, type);
// 排序 // 排序
thingsList = thingsList.stream().sorted(Comparator.comparing(ThingsModelValueItem::getOrder).reversed()).collect(Collectors.toList()); thingsList = thingsList.stream().sorted(Comparator.comparing(ThingsModelValueItem::getOrder).reversed())
.collect(Collectors.toList());
dataType.setParams(thingsList); dataType.setParams(thingsList);
} else if (dataType.getType().equals("array")) { } else if (dataType.getType().equals("array")) {
dataType.setArrayType(datatypeJson.getString("arrayType")); dataType.setArrayType(datatypeJson.getString("arrayType"));
@ -998,10 +1042,12 @@ public class DeviceServiceImpl implements IDeviceService {
// 对象数组 // 对象数组
JSONArray jsonArray = datatypeJson.getJSONArray("params"); JSONArray jsonArray = datatypeJson.getJSONArray("params");
// 物模型值过滤(parentId_开头) // 物模型值过滤(parentId_开头)
thingsModelValues = thingsModelValues.stream().filter(x -> x.getId().startsWith(parentIdentifier)).collect(Collectors.toList()); thingsModelValues = thingsModelValues.stream().filter(x -> x.getId().startsWith(parentIdentifier))
.collect(Collectors.toList());
List<ThingsModelValueItem> thingsList = convertJsonToThingsList(jsonArray, thingsModelValues, type); List<ThingsModelValueItem> thingsList = convertJsonToThingsList(jsonArray, thingsModelValues, type);
// 排序 // 排序
thingsList = thingsList.stream().sorted(Comparator.comparing(ThingsModelValueItem::getOrder).reversed()).collect(Collectors.toList()); thingsList = thingsList.stream().sorted(Comparator.comparing(ThingsModelValueItem::getOrder).reversed())
.collect(Collectors.toList());
// 数组类型物模型里面对象赋值 // 数组类型物模型里面对象赋值
List<ThingsModel>[] arrayParams = new List[dataType.getArrayCount()]; List<ThingsModel>[] arrayParams = new List[dataType.getArrayCount()];
for (int i = 0; i < dataType.getArrayCount(); i++) { for (int i = 0; i < dataType.getArrayCount(); i++) {
@ -1053,7 +1099,7 @@ public class DeviceServiceImpl implements IDeviceService {
throw new ServiceException("设备编号:" + device.getSerialNumber() + " 已经存在,新增失败"); throw new ServiceException("设备编号:" + device.getSerialNumber() + " 已经存在,新增失败");
} }
SysUser sysUser = getLoginUser().getUser(); SysUser sysUser = getLoginUser().getUser();
//添加设备 // 添加设备
device.setCreateTime(DateUtils.getNowDate()); device.setCreateTime(DateUtils.getNowDate());
device.setTenantId(sysUser.getDept().getDeptUserId()); device.setTenantId(sysUser.getDept().getDeptUserId());
device.setTenantName(sysUser.getDept().getDeptUserName()); device.setTenantName(sysUser.getDept().getDeptUserName());
@ -1067,7 +1113,8 @@ public class DeviceServiceImpl implements IDeviceService {
setLocation(user.getLoginIp(), device); setLocation(user.getLoginIp(), device);
deviceMapper.insertDevice(device); deviceMapper.insertDevice(device);
// 处理modbus产品 // 处理modbus产品
if (FastBeeConstant.PROTOCOL.ModbusRtu.equals(product.getProtocolCode()) || FastBeeConstant.PROTOCOL.ModbusTcp.equals(product.getProtocolCode())) { if (FastBeeConstant.PROTOCOL.ModbusRtu.equals(product.getProtocolCode())
|| FastBeeConstant.PROTOCOL.ModbusTcp.equals(product.getProtocolCode())) {
this.handleModbusConfig(device, product); this.handleModbusConfig(device, product);
} }
// redis缓存设备默认状态(物模型值) // redis缓存设备默认状态(物模型值)
@ -1104,15 +1151,18 @@ public class DeviceServiceImpl implements IDeviceService {
return; return;
} }
productSubGatewayList.sort(Comparator.comparing(ProductSubGatewayVO::getSlaveId)); productSubGatewayList.sort(Comparator.comparing(ProductSubGatewayVO::getSlaveId));
List<Long> subProductIdList = productSubGatewayList.stream().map(ProductSubGatewayVO::getSubProductId).collect(Collectors.toList()); List<Long> subProductIdList = productSubGatewayList.stream().map(ProductSubGatewayVO::getSubProductId)
.collect(Collectors.toList());
LambdaQueryWrapper<ProductModbusJob> queryWrapper1 = new LambdaQueryWrapper<>(); LambdaQueryWrapper<ProductModbusJob> queryWrapper1 = new LambdaQueryWrapper<>();
queryWrapper1.in(ProductModbusJob::getProductId, subProductIdList); queryWrapper1.in(ProductModbusJob::getProductId, subProductIdList);
List<ProductModbusJob> productModbusJobList = productModbusJobMapper.selectList(queryWrapper1); List<ProductModbusJob> productModbusJobList = productModbusJobMapper.selectList(queryWrapper1);
Map<Long, List<ProductModbusJob>> productModbusJobMap = productModbusJobList.stream().collect(Collectors.groupingBy(ProductModbusJob::getProductId)); Map<Long, List<ProductModbusJob>> productModbusJobMap = productModbusJobList.stream()
.collect(Collectors.groupingBy(ProductModbusJob::getProductId));
// 新增子设备、绑定子设备、子设备生成轮询 // 新增子设备、绑定子设备、子设备生成轮询
for (ProductSubGatewayVO productSubGatewayVO : productSubGatewayList) { for (ProductSubGatewayVO productSubGatewayVO : productSubGatewayList) {
Device addDevice = new Device(); Device addDevice = new Device();
addDevice.setDeviceName(productSubGatewayVO.getSubProductName() + "_" + productSubGatewayVO.getSlaveId()); addDevice.setDeviceName(
productSubGatewayVO.getSubProductName() + "_" + productSubGatewayVO.getSlaveId());
addDevice.setProductId(productSubGatewayVO.getSubProductId()); addDevice.setProductId(productSubGatewayVO.getSubProductId());
addDevice.setProductName(productSubGatewayVO.getSubProductName()); addDevice.setProductName(productSubGatewayVO.getSubProductName());
String serialNumber = this.generationDeviceNum(1); String serialNumber = this.generationDeviceNum(1);
@ -1134,7 +1184,8 @@ public class DeviceServiceImpl implements IDeviceService {
subGateway.setCreateBy(addDevice.getCreateBy()); subGateway.setCreateBy(addDevice.getCreateBy());
subGateway.setCreateTime(new Date()); subGateway.setCreateTime(new Date());
subGatewayMapper.insertGateway(subGateway); subGatewayMapper.insertGateway(subGateway);
List<ProductModbusJob> productModbusList = productModbusJobMap.get(productSubGatewayVO.getSubProductId()); List<ProductModbusJob> productModbusList = productModbusJobMap
.get(productSubGatewayVO.getSubProductId());
if (org.apache.commons.collections4.CollectionUtils.isNotEmpty(productModbusList)) { if (org.apache.commons.collections4.CollectionUtils.isNotEmpty(productModbusList)) {
for (ProductModbusJob productModbusJob : productModbusList) { for (ProductModbusJob productModbusJob : productModbusList) {
ModbusJob modbusJob = new ModbusJob(); ModbusJob modbusJob = new ModbusJob();
@ -1164,12 +1215,12 @@ public class DeviceServiceImpl implements IDeviceService {
@Override @Override
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
public AjaxResult deviceRelateUser(DeviceRelateUserInput params) { public AjaxResult deviceRelateUser(DeviceRelateUserInput params) {
Long userId = params.getUserId(); //终端用户id Long userId = params.getUserId(); // 终端用户id
// 查询用户信息 // 查询用户信息
SysUser sysUser = userService.selectUserById(userId); SysUser sysUser = userService.selectUserById(userId);
for (DeviceNumberAndProductId item : params.getDeviceNumberAndProductIds()) { for (DeviceNumberAndProductId item : params.getDeviceNumberAndProductIds()) {
Device existDevice = deviceMapper.selectDeviceBySerialNumber(item.getDeviceNumber()); Device existDevice = deviceMapper.selectDeviceBySerialNumber(item.getDeviceNumber());
//查看设备是否已经分配 // 查看设备是否已经分配
if (!Objects.isNull(existDevice)) { if (!Objects.isNull(existDevice)) {
if (null != sysUser.getDeptId()) { if (null != sysUser.getDeptId()) {
return AjaxResult.error("租户不允许绑定已存在的设备,设备编号:" + item.getDeviceNumber()); return AjaxResult.error("租户不允许绑定已存在的设备,设备编号:" + item.getDeviceNumber());
@ -1183,7 +1234,8 @@ public class DeviceServiceImpl implements IDeviceService {
} }
} }
// 先删除设备的所有用户 // 先删除设备的所有用户
//deviceUserMapper.deleteDeviceUserByDeviceId(new UserIdDeviceIdModel(null, existDevice.getDeviceId())); // deviceUserMapper.deleteDeviceUserByDeviceId(new UserIdDeviceIdModel(null,
// existDevice.getDeviceId()));
// 添加新的设备用户 // 添加新的设备用户
DeviceUser deviceUser = new DeviceUser(); DeviceUser deviceUser = new DeviceUser();
deviceUser.setUserId(sysUser.getUserId()); deviceUser.setUserId(sysUser.getUserId());
@ -1265,13 +1317,14 @@ public class DeviceServiceImpl implements IDeviceService {
@Override @Override
public ThingsModelShadow getDeviceShadowThingsModel(Device device) { public ThingsModelShadow getDeviceShadowThingsModel(Device device) {
// 物模型值 // 物模型值
List<ValueItem> thingsModelValueItems = itslValueCache.getCacheDeviceStatus(device.getProductId(), device.getSerialNumber()); List<ValueItem> thingsModelValueItems = itslValueCache.getCacheDeviceStatus(device.getProductId(),
device.getSerialNumber());
ThingsModelShadow shadow = new ThingsModelShadow(); ThingsModelShadow shadow = new ThingsModelShadow();
// 查询出设置的影子值 // 查询出设置的影子值
//List<ThingsModelValueItem> shadowList = new ArrayList<>(); // List<ThingsModelValueItem> shadowList = new ArrayList<>();
for (ValueItem item : thingsModelValueItems) { for (ValueItem item : thingsModelValueItems) {
if (!item.getShadow().equals(item.getValue()) && !item.getShadow().isEmpty()) { if (!item.getShadow().equals(item.getValue()) && !item.getShadow().isEmpty()) {
//处理id // 处理id
String id = item.getId(); String id = item.getId();
if (item.getShadow().contains(",")) { if (item.getShadow().contains(",")) {
String[] shadows = item.getShadow().split(","); String[] shadows = item.getShadow().split(",");
@ -1297,7 +1350,6 @@ public class DeviceServiceImpl implements IDeviceService {
return shadow; return shadow;
} }
/** /**
* 生成设备唯一编号 * 生成设备唯一编号
* *
@ -1308,7 +1360,7 @@ public class DeviceServiceImpl implements IDeviceService {
// 设备编号:D + userId + 15位随机字母和数字 // 设备编号:D + userId + 15位随机字母和数字
SysUser user = getLoginUser().getUser(); SysUser user = getLoginUser().getUser();
String number; String number;
//Hex随机字符串 // Hex随机字符串
if (type == 3) { if (type == 3) {
number = toolService.generateRandomHex(12); number = toolService.generateRandomHex(12);
} else { } else {
@ -1433,8 +1485,8 @@ public class DeviceServiceImpl implements IDeviceService {
deviceUserMapper.insertDeviceUser(deviceUser); deviceUserMapper.insertDeviceUser(deviceUser);
} }
// 更新设备用户信息 多租户版本不需要 // 更新设备用户信息 多租户版本不需要
// device.setTenantId(device.getTenantId()); // device.setTenantId(device.getTenantId());
// device.setTenantName(sysUser.getUserName()); // device.setTenantName(sysUser.getUserName());
} }
device.setUpdateTime(DateUtils.getNowDate()); device.setUpdateTime(DateUtils.getNowDate());
if (deviceEntity.getActiveTime() == null || deviceEntity.getActiveTime().equals("")) { if (deviceEntity.getActiveTime() == null || deviceEntity.getActiveTime().equals("")) {
@ -1459,7 +1511,6 @@ public class DeviceServiceImpl implements IDeviceService {
return deviceMapper.selectDevicesByProductId(productId, hasSub); return deviceMapper.selectDevicesByProductId(productId, hasSub);
} }
/** /**
* 批量更新设备状态 * 批量更新设备状态
* *
@ -1472,7 +1523,7 @@ public class DeviceServiceImpl implements IDeviceService {
if (CollectionUtils.isEmpty(serialNumbers)) { if (CollectionUtils.isEmpty(serialNumbers)) {
return; return;
} }
//设备离线 // 设备离线
if (DeviceStatus.OFFLINE.equals(status)) { if (DeviceStatus.OFFLINE.equals(status)) {
deviceMapper.batchChangeOffline(serialNumbers); deviceMapper.batchChangeOffline(serialNumbers);
} else if (DeviceStatus.ONLINE.equals(status)) { } else if (DeviceStatus.ONLINE.equals(status)) {
@ -1514,21 +1565,28 @@ public class DeviceServiceImpl implements IDeviceService {
String password; String password;
if (ProductAuthConstant.AUTHORIZE.equals(deviceMqttVO.getIsAuthorize())) { if (ProductAuthConstant.AUTHORIZE.equals(deviceMqttVO.getIsAuthorize())) {
// 查询产品授权码 // 查询产品授权码
List<ProductAuthorize> productAuthorizeList = productAuthorizeService.listByProductId(deviceMqttVO.getProductId()); List<ProductAuthorize> productAuthorizeList = productAuthorizeService
.listByProductId(deviceMqttVO.getProductId());
if (CollectionUtils.isEmpty(productAuthorizeList)) { if (CollectionUtils.isEmpty(productAuthorizeList)) {
throw new ServiceException("产品已启用授权,获取设备授权码失败,请先配置授权码"); throw new ServiceException("产品已启用授权,获取设备授权码失败,请先配置授权码");
} }
List<ProductAuthorize> collect = productAuthorizeList.stream().filter(p -> p.getProductId().equals(deviceMqttVO.getDeviceId())).collect(Collectors.toList()); List<ProductAuthorize> collect = productAuthorizeList.stream()
ProductAuthorize productAuthorize = CollectionUtils.isEmpty(collect) ? productAuthorizeList.get(0) : collect.get(0); .filter(p -> p.getProductId().equals(deviceMqttVO.getDeviceId())).collect(Collectors.toList());
ProductAuthorize productAuthorize = CollectionUtils.isEmpty(collect) ? productAuthorizeList.get(0)
: collect.get(0);
password = deviceMqttVO.getMqttPassword() + "&" + productAuthorize.getAuthorizeCode(); password = deviceMqttVO.getMqttPassword() + "&" + productAuthorize.getAuthorizeCode();
} else { } else {
password = deviceMqttVO.getMqttPassword(); password = deviceMqttVO.getMqttPassword();
} }
String clientId = ProductAuthConstant.CLIENT_ID_AUTH_TYPE_SIMPLE + "&" + deviceMqttVO.getSerialNumber() + "&" + deviceMqttVO.getProductId() + "&" + deviceMqttVO.getTenantId(); String clientId = ProductAuthConstant.CLIENT_ID_AUTH_TYPE_SIMPLE + "&" + deviceMqttVO.getSerialNumber() + "&"
+ deviceMqttVO.getProductId() + "&" + deviceMqttVO.getTenantId();
// 组装返回结果 // 组装返回结果
connectVO.setClientId(clientId).setUsername(deviceMqttVO.getMqttAccount()).setPasswd(password).setPort(brokerPort); connectVO.setClientId(clientId).setUsername(deviceMqttVO.getMqttAccount()).setPasswd(password)
connectVO.setSubscribeTopic("/" + deviceMqttVO.getProductId() + "/" + deviceMqttVO.getSerialNumber() + "/function/get"); .setPort(brokerPort);
connectVO.setReportTopic("/" + deviceMqttVO.getProductId() + "/" + deviceMqttVO.getSerialNumber() + "/property/post"); connectVO.setSubscribeTopic(
"/" + deviceMqttVO.getProductId() + "/" + deviceMqttVO.getSerialNumber() + "/function/get");
connectVO.setReportTopic(
"/" + deviceMqttVO.getProductId() + "/" + deviceMqttVO.getSerialNumber() + "/property/post");
return connectVO; return connectVO;
} }
@ -1548,7 +1606,7 @@ public class DeviceServiceImpl implements IDeviceService {
*/ */
private void setLocation(String ip, Device device) { private void setLocation(String ip, Device device) {
String IP_URL = "http://whois.pconline.com.cn/ipJson.jsp"; String IP_URL = "http://whois.pconline.com.cn/ipJson.jsp";
//String address = "未知地址"; // String address = "未知地址";
// 内网不查询 // 内网不查询
if (IpUtils.internalIp(ip)) { if (IpUtils.internalIp(ip)) {
device.setNetworkAddress("内网IP"); device.setNetworkAddress("内网IP");
@ -1576,13 +1634,15 @@ public class DeviceServiceImpl implements IDeviceService {
String BAIDU_URL = "http://api.map.baidu.com/geocoding/v3/"; String BAIDU_URL = "http://api.map.baidu.com/geocoding/v3/";
city = StrUtil.isEmpty(city) ? "湖南省长沙市岳麓区" : city; city = StrUtil.isEmpty(city) ? "湖南省长沙市岳麓区" : city;
String encodedAddress = URLEncoder.encode(city, StandardCharsets.UTF_8.toString()); String encodedAddress = URLEncoder.encode(city, StandardCharsets.UTF_8.toString());
String baiduResponse = HttpUtils.sendGet(BAIDU_URL, "address=" + encodedAddress + "&output=json&ak=" + baiduAK, Constants.UTF8); String baiduResponse = HttpUtils.sendGet(BAIDU_URL, "address=" + encodedAddress + "&output=json&ak=" + baiduAK,
Constants.UTF8);
if (!StringUtils.isEmpty(baiduResponse)) { if (!StringUtils.isEmpty(baiduResponse)) {
JSONObject baiduObject = JSONObject.parseObject(baiduResponse); JSONObject baiduObject = JSONObject.parseObject(baiduResponse);
JSONObject location = baiduObject.getJSONObject("result").getJSONObject("location"); JSONObject location = baiduObject.getJSONObject("result").getJSONObject("location");
device.setLongitude(location.getBigDecimal("lng")); device.setLongitude(location.getBigDecimal("lng"));
device.setLatitude(location.getBigDecimal("lat")); device.setLatitude(location.getBigDecimal("lat"));
System.out.println(device.getSerialNumber() + "- 设置经度:" + location.getBigDecimal("lng") + ",设置纬度:" + location.getBigDecimal("lat")); System.out.println(device.getSerialNumber() + "- 设置经度:" + location.getBigDecimal("lng") + ",设置纬度:"
+ location.getBigDecimal("lat"));
} }
} }
@ -1597,7 +1657,6 @@ public class DeviceServiceImpl implements IDeviceService {
return deviceMapper.getDeviceNumsByProductId(productId); return deviceMapper.getDeviceNumsByProductId(productId);
} }
/** /**
* 重置设备状态 * 重置设备状态
* *
@ -1616,7 +1675,8 @@ public class DeviceServiceImpl implements IDeviceService {
if (null == product) { if (null == product) {
return "导入失败,产品信息为空"; return "导入失败,产品信息为空";
} }
List<String> serialNumberList = deviceImportVOList.stream().map(DeviceImportVO::getSerialNumber).collect(Collectors.toList()); List<String> serialNumberList = deviceImportVOList.stream().map(DeviceImportVO::getSerialNumber)
.collect(Collectors.toList());
List<String> oldSerialNumberList = deviceMapper.checkExistBySerialNumbers(serialNumberList); List<String> oldSerialNumberList = deviceMapper.checkExistBySerialNumbers(serialNumberList);
if (org.apache.commons.collections4.CollectionUtils.isNotEmpty(oldSerialNumberList)) { if (org.apache.commons.collections4.CollectionUtils.isNotEmpty(oldSerialNumberList)) {
return "以下设备编号(" + JSON.toJSONString(oldSerialNumberList) + ")已存在,请修改后重试"; return "以下设备编号(" + JSON.toJSONString(oldSerialNumberList) + ")已存在,请修改后重试";
@ -1645,12 +1705,16 @@ public class DeviceServiceImpl implements IDeviceService {
int result = deviceMapper.insertBatchDevice(deviceList); int result = deviceMapper.insertBatchDevice(deviceList);
// 新增导入记录 // 新增导入记录
DeviceRecord deviceRecord = new DeviceRecord(); DeviceRecord deviceRecord = new DeviceRecord();
deviceRecord.setOperateDeptId(loginUser.getDeptId()).setProductId(productId).setType(DeviceRecordTypeEnum.IMPORT.getType()) deviceRecord.setOperateDeptId(loginUser.getDeptId()).setProductId(productId)
.setTotal(deviceList.size()).setTenantId(loginUser.getUser().getDept().getDeptUserId()).setTenantName(loginUser.getUser().getDept().getDeptUserName()); .setType(DeviceRecordTypeEnum.IMPORT.getType())
.setTotal(deviceList.size()).setTenantId(loginUser.getUser().getDept().getDeptUserId())
.setTenantName(loginUser.getUser().getDept().getDeptUserName());
if (result > 0) { if (result > 0) {
deviceRecord.setStatus(StatusEnum.SUCCESS.getStatus()).setSuccessQuantity(deviceList.size()).setFailQuantity(0); deviceRecord.setStatus(StatusEnum.SUCCESS.getStatus()).setSuccessQuantity(deviceList.size())
.setFailQuantity(0);
} else { } else {
deviceRecord.setStatus(StatusEnum.FAIL.getStatus()).setSuccessQuantity(0).setFailQuantity(deviceList.size()); deviceRecord.setStatus(StatusEnum.FAIL.getStatus()).setSuccessQuantity(0)
.setFailQuantity(deviceList.size());
} }
deviceRecord.setCreateBy(loginUser.getUsername()); deviceRecord.setCreateBy(loginUser.getUsername());
deviceRecordMapper.insertDeviceRecord(deviceRecord); deviceRecordMapper.insertDeviceRecord(deviceRecord);
@ -1664,7 +1728,8 @@ public class DeviceServiceImpl implements IDeviceService {
if (null == product) { if (null == product) {
return "导入失败,产品信息为空"; return "导入失败,产品信息为空";
} }
List<String> serialNumberList = deviceAssignmentVOS.stream().map(DeviceAssignmentVO::getSerialNumber).collect(Collectors.toList()); List<String> serialNumberList = deviceAssignmentVOS.stream().map(DeviceAssignmentVO::getSerialNumber)
.collect(Collectors.toList());
List<String> oldSerialNumberList = deviceMapper.checkExistBySerialNumbers(serialNumberList); List<String> oldSerialNumberList = deviceMapper.checkExistBySerialNumbers(serialNumberList);
if (org.apache.commons.collections4.CollectionUtils.isNotEmpty(oldSerialNumberList)) { if (org.apache.commons.collections4.CollectionUtils.isNotEmpty(oldSerialNumberList)) {
return "以下设备编号(" + JSON.toJSONString(oldSerialNumberList) + ")已存在,请修改后重试"; return "以下设备编号(" + JSON.toJSONString(oldSerialNumberList) + ")已存在,请修改后重试";
@ -1701,8 +1766,11 @@ public class DeviceServiceImpl implements IDeviceService {
int result = deviceMapper.insertBatchDevice(deviceList); int result = deviceMapper.insertBatchDevice(deviceList);
// 新增导入记录 // 新增导入记录
DeviceRecord deviceRecord = new DeviceRecord(); DeviceRecord deviceRecord = new DeviceRecord();
deviceRecord.setOperateDeptId(loginUser.getDeptId()).setTargetDeptId(deptId).setTargetDeptId(deptId).setProductId(productId).setDistributeType(DeviceDistributeTypeEnum.IMPORT.getType()) deviceRecord.setOperateDeptId(loginUser.getDeptId()).setTargetDeptId(deptId).setTargetDeptId(deptId)
.setType(DeviceRecordTypeEnum.ASSIGNMENT.getType()).setTotal(deviceList.size()).setTenantId(loginUser.getUser().getDept().getDeptUserId()).setTenantName(loginUser.getUser().getDept().getDeptUserName()); .setProductId(productId).setDistributeType(DeviceDistributeTypeEnum.IMPORT.getType())
.setType(DeviceRecordTypeEnum.ASSIGNMENT.getType()).setTotal(deviceList.size())
.setTenantId(loginUser.getUser().getDept().getDeptUserId())
.setTenantName(loginUser.getUser().getDept().getDeptUserName());
List<DeviceRecord> deviceRecordList = new ArrayList<>(); List<DeviceRecord> deviceRecordList = new ArrayList<>();
if (result > 0) { if (result > 0) {
deviceRecord.setStatus(StatusEnum.SUCCESS.getStatus()); deviceRecord.setStatus(StatusEnum.SUCCESS.getStatus());
@ -1717,10 +1785,15 @@ public class DeviceServiceImpl implements IDeviceService {
int insertRecordStatus = deviceRecordMapper.insertDeviceRecord(deviceRecord); int insertRecordStatus = deviceRecordMapper.insertDeviceRecord(deviceRecord);
for (Device device : deviceList) { for (Device device : deviceList) {
DeviceRecord deviceRecord1 = new DeviceRecord(); DeviceRecord deviceRecord1 = new DeviceRecord();
deviceRecord1.setOperateDeptId(loginUser.getDeptId()).setTargetDeptId(deptId).setProductId(productId).setDeviceId(device.getDeviceId()) deviceRecord1.setOperateDeptId(loginUser.getDeptId()).setTargetDeptId(deptId).setProductId(productId)
.setType(DeviceRecordTypeEnum.ASSIGNMENT_DETAIL.getType()).setParentId(deviceRecord.getId()).setSerialNumber(device.getSerialNumber()) .setDeviceId(device.getDeviceId())
.setDistributeType(DeviceDistributeTypeEnum.IMPORT.getType()).setTenantId(loginUser.getUser().getDept().getDeptUserId()).setTenantName(loginUser.getUser().getDept().getDeptUserName()); .setType(DeviceRecordTypeEnum.ASSIGNMENT_DETAIL.getType()).setParentId(deviceRecord.getId())
deviceRecord1.setStatus(insertRecordStatus > 0 ? StatusEnum.SUCCESS.getStatus() : StatusEnum.FAIL.getStatus()); .setSerialNumber(device.getSerialNumber())
.setDistributeType(DeviceDistributeTypeEnum.IMPORT.getType())
.setTenantId(loginUser.getUser().getDept().getDeptUserId())
.setTenantName(loginUser.getUser().getDept().getDeptUserName());
deviceRecord1
.setStatus(insertRecordStatus > 0 ? StatusEnum.SUCCESS.getStatus() : StatusEnum.FAIL.getStatus());
deviceRecord1.setCreateBy(loginUser.getUsername()); deviceRecord1.setCreateBy(loginUser.getUsername());
deviceRecordList.add(deviceRecord1); deviceRecordList.add(deviceRecord1);
} }
@ -1750,11 +1823,14 @@ public class DeviceServiceImpl implements IDeviceService {
if (null == sysUser) { if (null == sysUser) {
return AjaxResult.error("机构管理员不存在"); return AjaxResult.error("机构管理员不存在");
} }
List<Long> deviceIdList = Arrays.stream(deviceIds.split(",")).map(d -> Long.parseLong(d.trim())).distinct().collect(Collectors.toList()); List<Long> deviceIdList = Arrays.stream(deviceIds.split(",")).map(d -> Long.parseLong(d.trim())).distinct()
.collect(Collectors.toList());
int result = deviceMapper.updateTenantIdByDeptIds(deptUserId, sysUser.getUserName(), deviceIdList); int result = deviceMapper.updateTenantIdByDeptIds(deptUserId, sysUser.getUserName(), deviceIdList);
DeviceRecord deviceRecord = new DeviceRecord(); DeviceRecord deviceRecord = new DeviceRecord();
deviceRecord.setOperateDeptId(user.getDeptId()).setTargetDeptId(deptId).setTargetDeptId(deptId).setDistributeType(DeviceDistributeTypeEnum.SELECT.getType()) deviceRecord.setOperateDeptId(user.getDeptId()).setTargetDeptId(deptId).setTargetDeptId(deptId)
.setType(DeviceRecordTypeEnum.ASSIGNMENT.getType()).setTotal(deviceIdList.size()).setTenantId(user.getDept().getDeptUserId()).setTenantName(user.getDept().getDeptUserName()); .setDistributeType(DeviceDistributeTypeEnum.SELECT.getType())
.setType(DeviceRecordTypeEnum.ASSIGNMENT.getType()).setTotal(deviceIdList.size())
.setTenantId(user.getDept().getDeptUserId()).setTenantName(user.getDept().getDeptUserName());
if (result > 0) { if (result > 0) {
deviceRecord.setStatus(StatusEnum.SUCCESS.getStatus()); deviceRecord.setStatus(StatusEnum.SUCCESS.getStatus());
deviceRecord.setSuccessQuantity(deviceIdList.size()); deviceRecord.setSuccessQuantity(deviceIdList.size());
@ -1773,8 +1849,10 @@ public class DeviceServiceImpl implements IDeviceService {
deviceRecord1.setOperateDeptId(user.getDeptId()).setTargetDeptId(deptId).setDeviceId(device.getDeviceId()) deviceRecord1.setOperateDeptId(user.getDeptId()).setTargetDeptId(deptId).setDeviceId(device.getDeviceId())
.setProductId(device.getProductId()).setSerialNumber(device.getSerialNumber()) .setProductId(device.getProductId()).setSerialNumber(device.getSerialNumber())
.setType(DeviceRecordTypeEnum.ASSIGNMENT_DETAIL.getType()).setParentId(deviceRecord.getId()) .setType(DeviceRecordTypeEnum.ASSIGNMENT_DETAIL.getType()).setParentId(deviceRecord.getId())
.setDistributeType(DeviceDistributeTypeEnum.SELECT.getType()).setTenantId(user.getDept().getDeptUserId()).setTenantName(user.getDept().getDeptUserName()); .setDistributeType(DeviceDistributeTypeEnum.SELECT.getType())
deviceRecord1.setStatus(insertRecordStatus > 0 ? StatusEnum.SUCCESS.getStatus() : StatusEnum.FAIL.getStatus()); .setTenantId(user.getDept().getDeptUserId()).setTenantName(user.getDept().getDeptUserName());
deviceRecord1
.setStatus(insertRecordStatus > 0 ? StatusEnum.SUCCESS.getStatus() : StatusEnum.FAIL.getStatus());
deviceRecord1.setCreateBy(user.getUserName()); deviceRecord1.setCreateBy(user.getUserName());
deviceRecordList.add(deviceRecord1); deviceRecordList.add(deviceRecord1);
} }
@ -1795,7 +1873,8 @@ public class DeviceServiceImpl implements IDeviceService {
if (null == sysUser) { if (null == sysUser) {
return AjaxResult.error("机构管理员不存在"); return AjaxResult.error("机构管理员不存在");
} }
List<Long> deviceIdList = Arrays.stream(deviceIds.split(",")).map(d -> Long.parseLong(d.trim())).distinct().collect(Collectors.toList()); List<Long> deviceIdList = Arrays.stream(deviceIds.split(",")).map(d -> Long.parseLong(d.trim())).distinct()
.collect(Collectors.toList());
int result = deviceMapper.updateTenantIdByDeptIds(deptUserId, sysUser.getUserName(), deviceIdList); int result = deviceMapper.updateTenantIdByDeptIds(deptUserId, sysUser.getUserName(), deviceIdList);
List<DeviceRecord> deviceRecordList = new ArrayList<>(); List<DeviceRecord> deviceRecordList = new ArrayList<>();
List<Device> deviceList = deviceMapper.selectDeviceListByDeviceIds(deviceIdList); List<Device> deviceList = deviceMapper.selectDeviceListByDeviceIds(deviceIdList);
@ -1803,7 +1882,9 @@ public class DeviceServiceImpl implements IDeviceService {
DeviceRecord deviceRecord1 = new DeviceRecord(); DeviceRecord deviceRecord1 = new DeviceRecord();
deviceRecord1.setOperateDeptId(deptId).setTargetDeptId(recoveryDeptId).setDeviceId(device.getDeviceId()) deviceRecord1.setOperateDeptId(deptId).setTargetDeptId(recoveryDeptId).setDeviceId(device.getDeviceId())
.setProductId(device.getProductId()).setSerialNumber(device.getSerialNumber()) .setProductId(device.getProductId()).setSerialNumber(device.getSerialNumber())
.setType(DeviceRecordTypeEnum.RECOVERY.getType()).setTenantId(loginUser.getUser().getDept().getDeptUserId()).setTenantName(loginUser.getUser().getDept().getDeptUserName()); .setType(DeviceRecordTypeEnum.RECOVERY.getType())
.setTenantId(loginUser.getUser().getDept().getDeptUserId())
.setTenantName(loginUser.getUser().getDept().getDeptUserName());
deviceRecord1.setStatus(result > 0 ? StatusEnum.SUCCESS.getStatus() : StatusEnum.FAIL.getStatus()); deviceRecord1.setStatus(result > 0 ? StatusEnum.SUCCESS.getStatus() : StatusEnum.FAIL.getStatus());
deviceRecord1.setCreateBy(loginUser.getUsername()); deviceRecord1.setCreateBy(loginUser.getUsername());
deviceRecordList.add(deviceRecord1); deviceRecordList.add(deviceRecord1);
@ -1833,16 +1914,16 @@ public class DeviceServiceImpl implements IDeviceService {
return deviceMapper.selectDeviceByChannelId(channelId); return deviceMapper.selectDeviceByChannelId(channelId);
} }
@Override @Override
public List<ThingsModelDTO> listThingsModel(Long deviceId) { public List<ThingsModelDTO> listThingsModel(Long deviceId) {
DeviceShortOutput device = deviceMapper.selectDeviceRunningStatusByDeviceId(deviceId); DeviceShortOutput device = deviceMapper.selectDeviceRunningStatusByDeviceId(deviceId);
if (null == device) { if (null == device) {
return new ArrayList<>(); return new ArrayList<>();
} }
//管理员可以下发,其他看指令控制 // 管理员可以下发,其他看指令控制
int canSend = getLoginUser().getUserId().equals(getLoginUser().getDeptUserId()) ? 1 : 0; int canSend = getLoginUser().getUserId().equals(getLoginUser().getDeptUserId()) ? 1 : 0;
List<ThingsModelDTO> list = thingsModelService.getCacheAndHandleArrayByProductId(device.getProductId(), device.getSerialNumber()); List<ThingsModelDTO> list = thingsModelService.getCacheAndHandleArrayByProductId(device.getProductId(),
device.getSerialNumber());
for (ThingsModelDTO thingsModelDTO : list) { for (ThingsModelDTO thingsModelDTO : list) {
thingsModelDTO.setProductId(device.getProductId()); thingsModelDTO.setProductId(device.getProductId());
thingsModelDTO.setSerialNumber(device.getSerialNumber()); thingsModelDTO.setSerialNumber(device.getSerialNumber());
@ -1853,7 +1934,8 @@ public class DeviceServiceImpl implements IDeviceService {
subGateway.setGwDeviceId(deviceId); subGateway.setGwDeviceId(deviceId);
List<SubDeviceListVO> subDeviceListVOList = subGatewayMapper.selectGatewayList(subGateway); List<SubDeviceListVO> subDeviceListVOList = subGatewayMapper.selectGatewayList(subGateway);
for (SubDeviceListVO subDeviceListVO : subDeviceListVOList) { for (SubDeviceListVO subDeviceListVO : subDeviceListVOList) {
List<ThingsModelDTO> subList = thingsModelService.getCacheAndHandleArrayByProductId(subDeviceListVO.getSubProductId(), subDeviceListVO.getSubDeviceNo()); List<ThingsModelDTO> subList = thingsModelService.getCacheAndHandleArrayByProductId(
subDeviceListVO.getSubProductId(), subDeviceListVO.getSubDeviceNo());
for (ThingsModelDTO thingsModelDTO : subList) { for (ThingsModelDTO thingsModelDTO : subList) {
thingsModelDTO.setProductId(subDeviceListVO.getSubProductId()); thingsModelDTO.setProductId(subDeviceListVO.getSubProductId());
thingsModelDTO.setSerialNumber(subDeviceListVO.getSubDeviceNo()); thingsModelDTO.setSerialNumber(subDeviceListVO.getSubDeviceNo());
@ -1863,21 +1945,22 @@ public class DeviceServiceImpl implements IDeviceService {
} }
list.addAll(subList); list.addAll(subList);
} }
//指令控制 // 指令控制
jurisdictionThingsModel(list, deviceId); jurisdictionThingsModel(list, deviceId);
return list; return list;
} }
private void jurisdictionThingsModel(List<ThingsModelDTO> list, Long deviceId) { private void jurisdictionThingsModel(List<ThingsModelDTO> list, Long deviceId) {
Long userId = SecurityUtils.getUserId(); Long userId = SecurityUtils.getUserId();
//查询是否有指令控制 // 查询是否有指令控制
List<OrderControl> orderControls = orderControlService.selectByUserId(userId, deviceId); List<OrderControl> orderControls = orderControlService.selectByUserId(userId, deviceId);
if (CollectionUtils.isEmpty(orderControls)) return; if (CollectionUtils.isEmpty(orderControls))
return;
List<Long> modelIdList = new ArrayList<>(); List<Long> modelIdList = new ArrayList<>();
for (OrderControl control : orderControls) { for (OrderControl control : orderControls) {
String selectOrder = control.getSelectOrder(); String selectOrder = control.getSelectOrder();
String[] split = selectOrder.split(","); String[] split = selectOrder.split(",");
//判断是否生效 // 判断是否生效
boolean just = just(control); boolean just = just(control);
if (just) { if (just) {
List<Long> ids = Arrays.stream(split).map(Long::valueOf).collect(Collectors.toList()); List<Long> ids = Arrays.stream(split).map(Long::valueOf).collect(Collectors.toList());
@ -1906,11 +1989,12 @@ public class DeviceServiceImpl implements IDeviceService {
@Override @Override
public void updateByOrder(Long userId, Long deviceId) { public void updateByOrder(Long userId, Long deviceId) {
//处理指令权限问题 ,非管理员角色 // 处理指令权限问题 ,非管理员角色
List<OrderControl> controlList = orderControlService.selectByUserId(userId, deviceId); List<OrderControl> controlList = orderControlService.selectByUserId(userId, deviceId);
controlList = controlList.stream().filter(control -> Objects.equals(control.getStatus(), "1")).collect(Collectors.toList()); controlList = controlList.stream().filter(control -> Objects.equals(control.getStatus(), "1"))
.collect(Collectors.toList());
if (!CollectionUtils.isEmpty(controlList)) { if (!CollectionUtils.isEmpty(controlList)) {
//更新次数 // 更新次数
OrderControl control = controlList.get(0); OrderControl control = controlList.get(0);
control.setCount(control.getCount() - 1); control.setCount(control.getCount() - 1);
orderControlService.updateOrderControl(control); orderControlService.updateOrderControl(control);
@ -1928,7 +2012,6 @@ public class DeviceServiceImpl implements IDeviceService {
return deviceMapper.listDeviceGroupByGroupIds(groupIds); return deviceMapper.listDeviceGroupByGroupIds(groupIds);
} }
/** /**
* 查询产品下所有设备,返回设备编号 * 查询产品下所有设备,返回设备编号
* *
@ -1940,7 +2023,4 @@ public class DeviceServiceImpl implements IDeviceService {
return deviceMapper.selectSerialNumbersByProductId(productId); return deviceMapper.selectSerialNumbersByProductId(productId);
} }
} }

View File

@ -125,6 +125,7 @@ public class ModbusConfigServiceImpl implements IModbusConfigService {
this.updateModbusConfig(config); this.updateModbusConfig(config);
} }
} }
// 将寄存器配置保存到redis缓存
modbusConfigCache.setModbusConfigCacheByProductId(productId); modbusConfigCache.setModbusConfigCacheByProductId(productId);
} }

View File

@ -12,7 +12,6 @@ import com.fastbee.common.exception.ServiceException;
import com.fastbee.common.utils.DateUtils; import com.fastbee.common.utils.DateUtils;
import com.fastbee.common.utils.SecurityUtils; import com.fastbee.common.utils.SecurityUtils;
import com.fastbee.common.utils.StringUtils; import com.fastbee.common.utils.StringUtils;
import com.fastbee.iot.cache.ICalculatedThingsModelCache;
import com.fastbee.iot.cache.ITSLCache; import com.fastbee.iot.cache.ITSLCache;
import com.fastbee.iot.domain.*; import com.fastbee.iot.domain.*;
import com.fastbee.iot.mapper.*; import com.fastbee.iot.mapper.*;
@ -68,8 +67,6 @@ public class ThingsModelServiceImpl implements IThingsModelService {
private ITSLValueCache thingModelCache; private ITSLValueCache thingModelCache;
@Resource @Resource
private ITSLCache itslCache; private ITSLCache itslCache;
@Resource
private ICalculatedThingsModelCache calculatedThingsModelCache;
private static List<SceneModelData> getSceneModelData(SceneModelDevice sceneModelDevice, List<ThingsModelDTO> thingsModelDTOList) { private static List<SceneModelData> getSceneModelData(SceneModelDevice sceneModelDevice, List<ThingsModelDTO> thingsModelDTOList) {
List<SceneModelData> modelDataList = new ArrayList<>(); List<SceneModelData> modelDataList = new ArrayList<>();
@ -142,11 +139,16 @@ public class ThingsModelServiceImpl implements IThingsModelService {
*/ */
@Override @Override
public int insertThingsModel(ThingsModel thingsModel) { public int insertThingsModel(ThingsModel thingsModel) {
// 去除计算公式中的空格
if (StringUtils.isNotEmpty(thingsModel.getFormula())) {
thingsModel.setFormula(thingsModel.getFormula().replaceAll("\\s+", ""));
}
ThingsModel input = new ThingsModel(); ThingsModel input = new ThingsModel();
input.setProductId(thingsModel.getProductId()); input.setProductId(thingsModel.getProductId());
input.setIsCalculated(0); // 只需要查产品的物模型即可
input.setLanguage(SecurityUtils.getLanguage()); input.setLanguage(SecurityUtils.getLanguage());
List<ThingsModel> list = thingsModelMapper.selectThingsModelList(input); List<ThingsModel> list = thingsModelMapper.selectThingsModelList(input);
Boolean isRepeat = list.stream().anyMatch(x -> x.getIdentifier().equals(thingsModel.getIdentifier())); boolean isRepeat = list.stream().anyMatch(x -> x.getIdentifier().equals(thingsModel.getIdentifier()));
if (!isRepeat) { if (!isRepeat) {
SysUser user = getLoginUser().getUser(); SysUser user = getLoginUser().getUser();
if (null != user.getDeptId()) { if (null != user.getDeptId()) {
@ -157,21 +159,19 @@ public class ThingsModelServiceImpl implements IThingsModelService {
thingsModel.setTenantName(user.getUserName()); thingsModel.setTenantName(user.getUserName());
} }
thingsModel.setCreateTime(DateUtils.getNowDate()); thingsModel.setCreateTime(DateUtils.getNowDate());
// 自动判断是否为纯计算物模型:如果公式包含{},则设置为1(纯计算) // 插入物模型数据库
if (thingsModel.getIsCalculated() == null) {
thingsModel.setIsCalculated(determineIsCalculated(thingsModel.getFormula()));
}
int result = thingsModelMapper.insertThingsModel(thingsModel); int result = thingsModelMapper.insertThingsModel(thingsModel);
// 更新redis缓存 // 更新redis缓存:IsCalculated=1为纯计算物模型
itslCache.setCacheThingsModelByProductId(thingsModel.getProductId()); if (thingsModel.getIsCalculated() != null && thingsModel.getIsCalculated() == 1) {
// 刷新纯计算物模型缓存 itslCache.refreshDeviceCalculatedModelCache(thingsModel.getSerialNumber());
if (result > 0) { } else {
calculatedThingsModelCache.refreshCalculatedCache(thingsModel.getProductId()); itslCache.setCacheThingsModelByProductId(thingsModel.getProductId());
} }
if (result > 0) { if (result > 0) {
// 更新场景管理变量 // 更新场景管理变量
List<SceneModelDevice> sceneModelDeviceList = sceneModelDeviceMapper.listDeviceByProductId(thingsModel.getProductId()); List<SceneModelDevice> sceneModelDeviceList = sceneModelDeviceMapper.listDeviceByProductId(thingsModel.getProductId());
if (org.apache.commons.collections4.CollectionUtils.isEmpty(sceneModelDeviceList)) { if (CollectionUtils.isEmpty(sceneModelDeviceList)) {
return result; return result;
} }
List<ThingsModelDTO> thingsModelDTOList = this.changeObjectOrArray(thingsModel); List<ThingsModelDTO> thingsModelDTOList = this.changeObjectOrArray(thingsModel);
@ -219,15 +219,15 @@ public class ThingsModelServiceImpl implements IThingsModelService {
thingsModel.setIsChart(template.getIsChart()); thingsModel.setIsChart(template.getIsChart());
thingsModel.setIsHistory(template.getIsHistory()); thingsModel.setIsHistory(template.getIsHistory());
thingsModel.setModelOrder(template.getModelOrder()); thingsModel.setModelOrder(template.getModelOrder());
thingsModel.setIsCalculated(determineIsCalculated(thingsModel.getFormula())); thingsModel.setIsCalculated(0);
Boolean isRepeat = dbList.stream().anyMatch(x -> x.getIdentifier().equals(thingsModel.getIdentifier())); boolean isRepeat = dbList.stream().anyMatch(x -> x.getIdentifier().equals(thingsModel.getIdentifier()));
if (isRepeat) { if (isRepeat) {
repeatCount = repeatCount + 1; repeatCount = repeatCount + 1;
} else { } else {
list.add(thingsModel); list.add(thingsModel);
} }
} }
if (list.size() > 0) { if (!list.isEmpty()) {
int result = thingsModelMapper.insertBatchThingsModel(list); int result = thingsModelMapper.insertBatchThingsModel(list);
if (result > 0) { if (result > 0) {
// 更新场景管理变量 // 更新场景管理变量
@ -245,8 +245,6 @@ public class ThingsModelServiceImpl implements IThingsModelService {
} }
//更新redis缓存 //更新redis缓存
itslCache.setCacheThingsModelByProductId(input.getProductId()); itslCache.setCacheThingsModelByProductId(input.getProductId());
// 刷新纯计算物模型缓存
calculatedThingsModelCache.refreshCalculatedCache(input.getProductId());
} }
return repeatCount; return repeatCount;
} }
@ -271,16 +269,12 @@ public class ThingsModelServiceImpl implements IThingsModelService {
Boolean isRepeat = list.stream().anyMatch(x -> x.getIdentifier().equals(thingsModel.getIdentifier()) && x.getModelId().longValue() != thingsModel.getModelId()); Boolean isRepeat = list.stream().anyMatch(x -> x.getIdentifier().equals(thingsModel.getIdentifier()) && x.getModelId().longValue() != thingsModel.getModelId());
if (!isRepeat) { if (!isRepeat) {
thingsModel.setUpdateTime(DateUtils.getNowDate()); thingsModel.setUpdateTime(DateUtils.getNowDate());
// 自动判断是否为纯计算物模型:如果公式包含{},则设置为1(纯计算)
if (thingsModel.getIsCalculated() == null) {
thingsModel.setIsCalculated(determineIsCalculated(thingsModel.getFormula()));
}
int result = thingsModelMapper.updateThingsModel(thingsModel); int result = thingsModelMapper.updateThingsModel(thingsModel);
// 更新redis缓存 // 更新redis缓存
itslCache.setCacheThingsModelByProductId(thingsModel.getProductId()); if (thingsModel.getIsCalculated() != null && thingsModel.getIsCalculated() == 1) {
// 刷新纯计算物模型缓存 itslCache.refreshDeviceCalculatedModelCache(thingsModel.getSerialNumber());
if (result > 0) { } else {
calculatedThingsModelCache.refreshCalculatedCache(thingsModel.getProductId()); itslCache.setCacheThingsModelByProductId(thingsModel.getProductId());
} }
if (result > 0) { if (result > 0) {
List<SceneModelData> sceneModelDataList = sceneModelDataMapper.selectSceneDeviceThingsModelList(thingsModel.getModelId()); List<SceneModelData> sceneModelDataList = sceneModelDataMapper.selectSceneDeviceThingsModelList(thingsModel.getModelId());
@ -320,12 +314,9 @@ public class ThingsModelServiceImpl implements IThingsModelService {
if (result > 0) { if (result > 0) {
sceneModelDataMapper.deleteThingsModelByDatasourceId(modelIds); sceneModelDataMapper.deleteThingsModelByDatasourceId(modelIds);
} }
// 更新redis缓存 // 更新redis缓存 产品和设备物模型都干掉
itslCache.setCacheThingsModelByProductId(thingsModel.getProductId()); itslCache.setCacheThingsModelByProductId(thingsModel.getProductId());
// 刷新纯计算物模型缓存 itslCache.refreshDeviceCalculatedModelCache(thingsModel.getSerialNumber());
if (result > 0 && thingsModel != null && thingsModel.getProductId() != null) {
calculatedThingsModelCache.refreshCalculatedCache(thingsModel.getProductId());
}
return result; return result;
} }
@ -341,12 +332,11 @@ public class ThingsModelServiceImpl implements IThingsModelService {
ThingsModel thingsModel = thingsModelMapper.selectThingsModelByModelId(modelId, SecurityUtils.getLanguage()); ThingsModel thingsModel = thingsModelMapper.selectThingsModelByModelId(modelId, SecurityUtils.getLanguage());
int result = thingsModelMapper.deleteThingsModelByModelId(modelId); int result = thingsModelMapper.deleteThingsModelByModelId(modelId);
// 更新redis缓存 // 更新redis缓存
if (thingsModel != null && thingsModel.getProductId() != null) { if (thingsModel != null) {
itslCache.setCacheThingsModelByProductId(thingsModel.getProductId()); if (thingsModel.getProductId() != null) {
// 刷新纯计算物模型缓存 itslCache.setCacheThingsModelByProductId(thingsModel.getProductId());
if (result > 0) {
calculatedThingsModelCache.refreshCalculatedCache(thingsModel.getProductId());
} }
itslCache.refreshDeviceCalculatedModelCache(thingsModel.getSerialNumber());
} }
return result; return result;
} }
@ -354,8 +344,8 @@ public class ThingsModelServiceImpl implements IThingsModelService {
/** /**
* 获取单个物模型获取 * 获取单个物模型获取
* *
* @param productId * @param productId 产品ID
* @param identify * @param identify 物模型标识符
* @return * @return
*/ */
@Override @Override
@ -695,6 +685,7 @@ public class ThingsModelServiceImpl implements IThingsModelService {
if (null == productId) { if (null == productId) {
return new ArrayList<>(); return new ArrayList<>();
} }
// 1、获取产品物模型
Map<String, ThingsModelValueItem> map = itslCache.getCacheThMapByProductId(productId); Map<String, ThingsModelValueItem> map = itslCache.getCacheThMapByProductId(productId);
List<ThingsModelDTO> thingsModelDTOList = new ArrayList<>(); List<ThingsModelDTO> thingsModelDTOList = new ArrayList<>();
for (Map.Entry<String, ThingsModelValueItem> entry : map.entrySet()) { for (Map.Entry<String, ThingsModelValueItem> entry : map.entrySet()) {
@ -702,6 +693,15 @@ public class ThingsModelServiceImpl implements IThingsModelService {
thingsModelDTOList.addAll(this.changeObjectOrArray(thingsModel)); thingsModelDTOList.addAll(this.changeObjectOrArray(thingsModel));
} }
// 2. 获取设备级计算物模型并合并
if (StringUtils.isNotEmpty(serialNumber)) {
Map<String, ThingsModelValueItem> deviceCalculatedMap = itslCache.getDeviceCalculatedModelMap(serialNumber);
for (Map.Entry<String, ThingsModelValueItem> entry : deviceCalculatedMap.entrySet()) {
ThingsModel thingsModel = this.propertyDtoChangeThingsModel(entry.getValue());
thingsModelDTOList.addAll(this.changeObjectOrArray(thingsModel));
}
}
thingsModelDTOList.sort(Comparator.comparing(ThingsModelDTO::getModelOrder)); thingsModelDTOList.sort(Comparator.comparing(ThingsModelDTO::getModelOrder));
if (StringUtils.isEmpty(serialNumber)) { if (StringUtils.isEmpty(serialNumber)) {
return thingsModelDTOList; return thingsModelDTOList;

View File

@ -163,15 +163,71 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</resultMap> </resultMap>
<sql id="selectDeviceVo"> <sql id="selectDeviceVo">
select device_id, device_name, product_id, product_name, tenant_id, tenant_name, serial_number,gw_dev_code, firmware_version, status, rssi,is_shadow ,is_simulate,location_way,things_model_value,network_address, network_ip, longitude, latitude, active_time, create_time, update_time, img_url,summary,remark,slave_id from iot_device select device_id,
device_name,
product_id,
product_name,
tenant_id,
tenant_name,
serial_number,
gw_dev_code,
firmware_version,
status,
rssi,
is_shadow,
is_simulate,
location_way,
things_model_value,
network_address,
network_ip,
longitude,
latitude,
active_time,
create_time,
update_time,
img_url,
summary,
remark,
slave_id
from iot_device
</sql> </sql>
<sql id="selectDeviceShortVo"> <sql id="selectDeviceShortVo">
select device_id, device_name, product_id, product_name, tenant_id, tenant_name, serial_number, firmware_version, status,rssi,is_shadow ,is_simulate,location_way,things_model_value, active_time,img_url,slave_id from iot_device select device_id,
device_name,
product_id,
product_name,
tenant_id,
tenant_name,
serial_number,
firmware_version,
status,
rssi,
is_shadow,
is_simulate,
location_way,
things_model_value,
active_time,
img_url,
slave_id
from iot_device
</sql> </sql>
<sql id="selectWebhookDeviceVo"> <sql id="selectWebhookDeviceVo">
select device_id, device_name,product_id, serial_number,tenant_id, tenant_name, status,is_shadow,is_simulate, rssi ,location_way,things_model_value, active_time from iot_device select device_id,
device_name,
product_id,
serial_number,
tenant_id,
tenant_name,
status,
is_shadow,
is_simulate,
rssi,
location_way,
things_model_value,
active_time
from iot_device
</sql> </sql>
<select id="selectDeviceList" parameterType="com.fastbee.iot.domain.Device" resultMap="DeviceResult"> <select id="selectDeviceList" parameterType="com.fastbee.iot.domain.Device" resultMap="DeviceResult">

View File

@ -10,6 +10,8 @@
<result property="modelName_en_US" column="model_name_en_us"/> <result property="modelName_en_US" column="model_name_en_us"/>
<result property="productId" column="product_id"/> <result property="productId" column="product_id"/>
<result property="productName" column="product_name"/> <result property="productName" column="product_name"/>
<result property="deviceId" column="device_id"/>
<result property="serialNumber" column="serial_number"/>
<result property="tenantId" column="tenant_id"/> <result property="tenantId" column="tenant_id"/>
<result property="tenantName" column="tenant_name"/> <result property="tenantName" column="tenant_name"/>
<result property="identifier" column="identifier"/> <result property="identifier" column="identifier"/>
@ -52,6 +54,8 @@
model_name, model_name,
product_id, product_id,
product_name, product_name,
device_id,
serial_number,
tenant_id, tenant_id,
tenant_name, tenant_name,
identifier, identifier,
@ -79,72 +83,134 @@
<select id="selectThingsModelList" parameterType="com.fastbee.iot.domain.ThingsModel" resultMap="ThingsModelResult"> <select id="selectThingsModelList" parameterType="com.fastbee.iot.domain.ThingsModel" resultMap="ThingsModelResult">
select m.model_id, select m.model_id,
case case
when #{language} = 'zh-CN' then m.model_name when #{language}
when #{language} = 'en-US' then t.en_us = 'zh-CN' THEN m.model_name WHEN #{language}
else m.model_name = 'en-US' THEN t.en_us ELSE m.model_name END AS model_name,
end as model_name, m.model_name AS model_name_zh_cn,
m.model_name as model_name_zh_cn, t.en_us as model_name_en_us, t.en_us AS model_name_en_us,
m.product_id, m.product_name, m.tenant_id, m.tenant_name, m.identifier, m.type, m.datatype, m.formula, m.product_id,
m.is_calculated, m.specs, m.is_chart, m.is_share_perm, m.is_history, m.is_monitor, m.is_app, m.del_flag, m.create_by, m.product_name,
m.create_time, m.update_by, m.update_time, m.remark, m.is_readonly, m.model_order m.device_id,
from iot_things_model m m.serial_number,
left join iot_things_model_translate t on m.model_id = t.id m.tenant_id,
m.tenant_name,
m.identifier,
m.type,
m.datatype,
m.formula,
m.is_calculated,
m.specs,
m.is_chart,
m.is_share_perm,
m.is_history,
m.is_monitor,
m.is_app,
m.del_flag,
m.create_by,
m.create_time,
m.update_by,
m.update_time,
m.remark,
m.is_readonly,
m.model_order
FROM
iot_things_model m
LEFT JOIN iot_things_model_translate t ON m.model_id = t.id
<where> <where>
<if test="productId != null"> <if test="productId != null">
and m.product_id = #{productId} AND m.product_id = #{productId}
</if>
<if test="serialNumber != null">
AND m.serial_number = #{serialNumber}
</if>
<if test="deviceId != null">
AND m.device_id = #{deviceId}
</if> </if>
<if test="type!=null"> <if test="type!=null">
and type = #{type} AND type = #{type}
</if> </if>
<if test="isChart != null "> <if test="isChart != null ">
and is_chart = #{isChart} AND is_chart = #{isChart}
</if> </if>
<if test="isHistory != null "> <if test="isHistory != null ">
and is_history = #{isHistory} AND is_history = #{isHistory}
</if> </if>
<if test="isMonitor != null "> <if test="isMonitor != null ">
and is_monitor = #{isMonitor} AND is_monitor = #{isMonitor}
</if> </if>
<if test="isReadonly != null "> <if test="isReadonly != null ">
and is_readonly = #{isReadonly} AND is_readonly = #{isReadonly}
</if>
<if test="isCalculated != null ">
AND is_calculated = #{isCalculated}
</if> </if>
<if test="modelIdList != null "> <if test="modelIdList != null ">
model_id in model_id IN
<foreach collection="modelIdList" item="item" separator="," open="(" close=")"> <foreach collection="modelIdList" item="item" separator="," open="(" close=")">
#{item} #{item}
</foreach> </foreach>
</if> </if>
</where> </where>
order by model_order desc,create_time desc ORDER BY
model_order DESC,
create_time DESC
</select> </select>
<select id="selectThingsModelPermList" resultMap="ThingsModelPermResult"> <select id="selectThingsModelPermList" resultMap="ThingsModelPermResult">
select case select case
when #{language} = 'zh-CN' then m.model_name when #{language}
when #{language} = 'en-US' then t.en_us = 'zh-CN' THEN m.model_name WHEN #{language}
else m.model_name = 'en-US' THEN t.en_us ELSE m.model_name END AS model_name,
end as model_name, m.identifier,
m.identifier, m.is_share_perm, m.remark m.is_share_perm,
from iot_things_model m m.remark
left join iot_things_model_translate t on m.model_id = t.id FROM
where m.product_id=#{productId} and is_share_perm=1 iot_things_model m
order by model_order desc,create_time desc LEFT JOIN iot_things_model_translate t ON m.model_id = t.id
WHERE
m.product_id = #{productId}
AND is_share_perm = 1
ORDER BY
model_order DESC,
create_time DESC
</select> </select>
<select id="selectThingsModelListByProductIds" parameterType="String" resultMap="ThingsModelResult"> <select id="selectThingsModelListByProductIds" parameterType="String" resultMap="ThingsModelResult">
select m.model_id, select m.model_id,
case case
when #{language} = 'zh-CN' then m.model_name when #{language}
when #{language} = 'en-US' then t.en_us = 'zh-CN' THEN m.model_name WHEN #{language}
else m.model_name = 'en-US' THEN t.en_us ELSE m.model_name END AS model_name,
end as model_name, m.product_id,
m.product_id, m.product_name, m.tenant_id, m.tenant_name, m.identifier, m.type, m.datatype, m.formula, m.product_name,
m.is_calculated, m.specs, m.is_chart, m.is_share_perm, m.is_history, m.is_monitor, m.is_app, m.del_flag, m.create_by, m.tenant_id,
m.create_time, m.update_by, m.update_time, m.remark, m.is_readonly, m.model_order m.tenant_name,
from iot_things_model m m.identifier,
left join iot_things_model_translate t on m.model_id = t.id m.type,
where is_chart=1 and m.product_id in m.datatype,
m.formula,
m.is_calculated,
m.specs,
m.is_chart,
m.is_share_perm,
m.is_history,
m.is_monitor,
m.is_app,
m.del_flag,
m.create_by,
m.create_time,
m.update_by,
m.update_time,
m.remark,
m.is_readonly,
m.model_order
FROM
iot_things_model m
LEFT JOIN iot_things_model_translate t ON m.model_id = t.id
WHERE
is_chart = 1
AND m.product_id IN
<foreach item="productId" collection="array" open="(" separator="," close=")"> <foreach item="productId" collection="array" open="(" separator="," close=")">
#{productId} #{productId}
</foreach> </foreach>
@ -152,44 +218,86 @@
<select id="selectThingsModelByModelId" resultMap="ThingsModelResult"> <select id="selectThingsModelByModelId" resultMap="ThingsModelResult">
select m.model_id, select m.model_id,
case case
when #{language} = 'zh-CN' then m.model_name when #{language}
when #{language} = 'en-US' then t.en_us = 'zh-CN' THEN m.model_name WHEN #{language}
else m.model_name = 'en-US' THEN t.en_us ELSE m.model_name END AS model_name,
end as model_name, m.product_id,
m.product_id, m.product_name, m.tenant_id, m.tenant_name, m.identifier, m.type, m.datatype, m.formula, m.product_name,
m.specs, m.is_chart, m.is_share_perm, m.is_history, m.is_monitor, m.is_app, m.del_flag, m.create_by, m.serial_number,
m.create_time, m.update_by, m.update_time, m.remark, m.is_readonly, m.model_order m.tenant_id,
from iot_things_model m m.tenant_name,
left join iot_things_model_translate t on m.model_id = t.id m.identifier,
where model_id = #{modelId} m.type,
m.datatype,
m.formula,
m.specs,
m.is_chart,
m.is_share_perm,
m.is_history,
m.is_monitor,
m.is_app,
m.del_flag,
m.create_by,
m.create_time,
m.update_by,
m.update_time,
m.remark,
m.is_readonly,
m.model_order
FROM
iot_things_model m
LEFT JOIN iot_things_model_translate t ON m.model_id = t.id
WHERE
model_id = #{modelId}
</select> </select>
<select id="selectSingleThingsModel" parameterType="com.fastbee.iot.domain.ThingsModel" resultMap="ThingsModelResult"> <select id="selectSingleThingsModel" parameterType="com.fastbee.iot.domain.ThingsModel" resultMap="ThingsModelResult">
select m.model_id, select m.model_id,
case case
when #{language} = 'zh-CN' then m.model_name when #{language}
when #{language} = 'en-US' then t.en_us = 'zh-CN' THEN m.model_name WHEN #{language}
else m.model_name = 'en-US' THEN t.en_us ELSE m.model_name END AS model_name,
end as model_name, m.product_id,
m.product_id, m.product_name, m.tenant_id, m.tenant_name, m.identifier, m.type, m.datatype, m.formula, m.product_name,
m.is_calculated, m.specs, m.is_chart, m.is_share_perm, m.is_history, m.is_monitor, m.is_app, m.del_flag, m.create_by, m.tenant_id,
m.create_time, m.update_by, m.update_time, m.remark, m.is_readonly, m.model_order m.tenant_name,
from iot_things_model m m.identifier,
left join iot_things_model_translate t on m.model_id = t.id m.type,
m.datatype,
m.formula,
m.is_calculated,
m.specs,
m.is_chart,
m.is_share_perm,
m.is_history,
m.is_monitor,
m.is_app,
m.del_flag,
m.create_by,
m.create_time,
m.update_by,
m.update_time,
m.remark,
m.is_readonly,
m.model_order
FROM
iot_things_model m
LEFT JOIN iot_things_model_translate t ON m.model_id = t.id
<where> <where>
<if test="productId != null"> <if test="productId != null">
and m.product_id = #{productId} AND m.product_id = #{productId}
</if> </if>
<if test="identifier !=null and identifier != ''"> <if test="identifier !=null and identifier != ''">
and identifier = #{identifier} AND identifier = #{identifier}
</if> </if>
</where> </where>
</select> </select>
<insert id="insertThingsModel" parameterType="com.fastbee.iot.domain.ThingsModel" useGeneratedKeys="true" <insert id="insertThingsModel" parameterType="com.fastbee.iot.domain.ThingsModel" useGeneratedKeys="true"
keyProperty="modelId"> keyProperty="modelId">
insert into iot_things_model INSERT INTO
iot_things_model
<trim prefix="(" suffix=")" suffixOverrides=","> <trim prefix="(" suffix=")" suffixOverrides=",">
<if test="modelName != null and modelName != ''"> <if test="modelName != null and modelName != ''">
model_name, model_name,
@ -200,6 +308,12 @@
<if test="productName != null and productName != ''"> <if test="productName != null and productName != ''">
product_name, product_name,
</if> </if>
<if test="deviceId != null">
device_id,
</if>
<if test="serialNumber != null and serialNumber != ''">
serial_number,
</if>
<if test="tenantId != null"> <if test="tenantId != null">
tenant_id, tenant_id,
</if> </if>
@ -274,6 +388,12 @@
<if test="productName != null and productName != ''"> <if test="productName != null and productName != ''">
#{productName}, #{productName},
</if> </if>
<if test="deviceId != null">
#{deviceId},
</if>
<if test="serialNumber != null and serialNumber != ''">
#{serialNumber},
</if>
<if test="tenantId != null"> <if test="tenantId != null">
#{tenantId}, #{tenantId},
</if> </if>
@ -342,41 +462,55 @@
<insert id="insertBatchThingsModel" parameterType="com.fastbee.iot.domain.ThingsModel" useGeneratedKeys="true" <insert id="insertBatchThingsModel" parameterType="com.fastbee.iot.domain.ThingsModel" useGeneratedKeys="true"
keyProperty="modelId"> keyProperty="modelId">
INSERT INTO iot_things_model ( INSERT INTO
model_name, iot_things_model (
product_id, model_name,
product_name, product_id,
tenant_id, product_name,
tenant_name, tenant_id,
identifier, tenant_name,
type, identifier,
datatype, type,
specs, datatype,
is_chart, specs,
is_share_perm, is_chart,
is_history, is_share_perm,
is_monitor, is_history,
is_app, is_monitor,
is_readonly, is_app,
create_by, is_readonly,
create_time, create_by,
formula, create_time,
is_calculated formula,
) is_calculated
)
VALUES VALUES
<foreach item="model" collection="list" separator=","> <foreach item="model" collection="list" separator=",">
<trim prefix="(" suffix=")" suffixOverrides=","> <trim prefix="(" suffix=")" suffixOverrides=",">#{model.modelName},
#{model.modelName},#{model.productId},#{model.productName},#{model.tenantId}, #{model.productId},
#{model.tenantName},#{model.identifier},#{model.type},#{model.datatype}, #{model.productName},
#{model.specs},#{model.isChart},#{model.isSharePerm},#{model.isHistory}, #{model.tenantId},
#{model.isMonitor},#{model.isApp},#{model.isReadonly},#{model.createBy},#{model.createTime}, #{model.tenantName},
#{model.formula},#{model.isCalculated} #{model.identifier},
#{model.type},
#{model.datatype},
#{model.specs},
#{model.isChart},
#{model.isSharePerm},
#{model.isHistory},
#{model.isMonitor},
#{model.isApp},
#{model.isReadonly},
#{model.createBy},
#{model.createTime},
#{model.formula},
#{model.isCalculated}
</trim> </trim>
</foreach> </foreach>
</insert> </insert>
<update id="updateThingsModel" parameterType="com.fastbee.iot.domain.ThingsModel"> <update id="updateThingsModel" parameterType="com.fastbee.iot.domain.ThingsModel">
update iot_things_model UPDATE iot_things_model
<trim prefix="SET" suffixOverrides=","> <trim prefix="SET" suffixOverrides=",">
<if test="modelName != null and modelName != ''"> <if test="modelName != null and modelName != ''">
model_name = #{modelName}, model_name = #{modelName},
@ -387,6 +521,12 @@
<if test="productName != null and productName != ''"> <if test="productName != null and productName != ''">
product_name = #{productName}, product_name = #{productName},
</if> </if>
<if test="deviceId != null">
device_id = #{deviceId},
</if>
<if test="serialNumber != null and serialNumber != ''">
serial_number = #{serialNumber},
</if>
<if test="tenantId != null"> <if test="tenantId != null">
tenant_id = #{tenantId}, tenant_id = #{tenantId},
</if> </if>
@ -451,17 +591,20 @@
model_order = #{modelOrder}, model_order = #{modelOrder},
</if> </if>
</trim> </trim>
where model_id = #{modelId} WHERE
model_id = #{modelId}
</update> </update>
<delete id="deleteThingsModelByModelId" parameterType="Long"> <delete id="deleteThingsModelByModelId" parameterType="Long">
delete DELETE FROM iot_things_model
from iot_things_model WHERE
where model_id = #{modelId} model_id = #{modelId}
</delete> </delete>
<delete id="deleteThingsModelByModelIds" parameterType="String"> <delete id="deleteThingsModelByModelIds" parameterType="String">
delete from iot_things_model where model_id in DELETE FROM iot_things_model
WHERE
model_id IN
<foreach item="modelId" collection="array" open="(" separator="," close=")"> <foreach item="modelId" collection="array" open="(" separator="," close=")">
#{modelId} #{modelId}
</foreach> </foreach>
@ -475,16 +618,17 @@
m.is_history as isHistory, m.is_history as isHistory,
m.specs as dataType, m.specs as dataType,
case case
when #{language} = 'zh-CN' then m.model_name when #{language}
when #{language} = 'en-US' then t.en_us = 'zh-CN' THEN m.model_name WHEN #{language}
else m.model_name = 'en-US' THEN t.en_us ELSE m.model_name END AS name,
end as name, m.type AS type,
m.type as type, m.formula AS formula,
m.formula as formula, FROM
from iot_things_model m iot_things_model m
left join iot_things_model_translate t on m.model_id = t.id LEFT JOIN iot_things_model_translate t ON m.model_id = t.id
where m.product_id = #{productId,jdbcType=BIGINT} WHERE
and m.identifier in m.product_id = #{productId,jdbcType=BIGINT}
AND m.identifier IN
<foreach collection="ids" item="item" open="(" separator="," close=")"> <foreach collection="ids" item="item" open="(" separator="," close=")">
#{item} #{item}
</foreach> </foreach>
@ -492,43 +636,50 @@
<delete id="deleteThingsModelByProductId" parameterType="long"> <delete id="deleteThingsModelByProductId" parameterType="long">
delete from iot_things_model where product_id = #{productId} DELETE FROM iot_things_model
WHERE
product_id = #{productId}
</delete> </delete>
<select id="listSimByProductIds" resultType="com.fastbee.iot.model.ThingsModelSimVO"> <select id="listSimByProductIds" resultType="com.fastbee.iot.model.ThingsModelSimVO">
select m.product_id, m.identifier, select m.product_id, m.identifier,
case case
when #{language} = 'zh-CN' then m.model_name when #{language}
when #{language} = 'en-US' then t.en_us = 'zh-CN' THEN m.model_name WHEN #{language}
else m.model_name = 'en-US' THEN t.en_us ELSE m.model_name END AS model_name,
end as model_name, m.type AS thingsModelType,
m.type as thingsModelType,
m.datatype m.datatype
from iot_things_model m FROM
left join iot_things_model_translate t on m.model_id = t.id iot_things_model m
where m.product_id in LEFT JOIN iot_things_model_translate t ON m.model_id = t.id
WHERE
m.product_id IN
<foreach collection="productIdList" item="productId" open="(" separator="," close=")"> <foreach collection="productIdList" item="productId" open="(" separator="," close=")">
#{productId} #{productId}
</foreach> </foreach>
</select> </select>
<select id="getSpecsByProductIdAndIdentifier" resultType="java.lang.String"> <select id="getSpecsByProductIdAndIdentifier" resultType="java.lang.String">
select specs SELECT
from iot_things_model specs
where product_id = #{productId} FROM
and identifier = #{identifier} iot_things_model
WHERE
product_id = #{productId}
AND identifier = #{identifier}
</select> </select>
<select id="listSceneModelDataByModelIds" resultType="com.fastbee.iot.domain.ThingsModel"> <select id="listSceneModelDataByModelIds" resultType="com.fastbee.iot.domain.ThingsModel">
select m.model_id, m.identifier, select m.model_id, m.identifier,
case case
when #{language} = 'zh-CN' then m.model_name when #{language}
when #{language} = 'en-US' then t.en_us = 'zh-CN' THEN m.model_name WHEN #{language}
else m.model_name = 'en-US' THEN t.en_us ELSE m.model_name END AS model_name,
end as model_name, FROM
from iot_things_model m iot_things_model m
left join iot_things_model_translate t on m.model_id = t.id LEFT JOIN iot_things_model_translate t ON m.model_id = t.id
where m.model_id in WHERE
m.model_id IN
<foreach collection="modelIdList" item="modelId" open="(" separator="," close=")"> <foreach collection="modelIdList" item="modelId" open="(" separator="," close=")">
#{modelId} #{modelId}
</foreach> </foreach>
@ -537,41 +688,51 @@
<select id="getModbusConfigUnSelectThingsModel" resultMap="ThingsModelAndModbusResult"> <select id="getModbusConfigUnSelectThingsModel" resultMap="ThingsModelAndModbusResult">
select m.model_id, m.identifier, select m.model_id, m.identifier,
case case
when #{language} = 'zh-CN' then m.model_name when #{language}
when #{language} = 'en-US' then t.en_us = 'zh-CN' THEN m.model_name WHEN #{language}
else m.model_name = 'en-US' then t.en_us
end as model_name, else m.model_name
m.datatype, end as model_name,
case m.datatype,
when (select count(1) from iot_modbus_config c where c.product_id = #{productId} and c.identifier = m.identifier) > 0 then 0 case
else 1 when (select count(1) from iot_modbus_config c where c.product_id = #{productId}
end as is_select and c.identifier = m.identifier) > 0 then 0
else 1
end as is_select
from iot_things_model m from iot_things_model m
left join iot_things_model_translate t on m.model_id = t.id left join iot_things_model_translate t on m.model_id = t.id
where m.product_id = #{productId} where m.product_id = #{productId}
AND m.del_flag = 0
AND m.is_calculated = 0
</select> </select>
<select id="selectThingsModelListByModelIds" resultType="com.fastbee.iot.domain.ThingsModel"> <select id="selectThingsModelListByModelIds" resultType="com.fastbee.iot.domain.ThingsModel">
select model_id, type SELECT
from iot_things_model where model_id in model_id,
type
FROM
iot_things_model
WHERE
model_id IN
<foreach item="modelId" collection="modelIdList" open="(" separator="," close=")"> <foreach item="modelId" collection="modelIdList" open="(" separator="," close=")">
#{modelId} #{modelId}
</foreach> </foreach>
and del_flag = 0 AND del_flag = 0
</select> </select>
<select id="selectThingsModelBySerialNumber" resultMap="ThingsModelResult"> <select id="selectThingsModelBySerialNumber" resultMap="ThingsModelResult">
select m.model_id, m.identifier, select m.model_id, m.identifier,
case case
when #{language} = 'zh-CN' then m.model_name when #{language}
when #{language} = 'en-US' then t.en_us = 'zh-CN' THEN m.model_name WHEN #{language}
else m.model_name = 'en-US' THEN t.en_us ELSE m.model_name END AS model_name
end as model_name FROM
from iot_things_model m iot_things_model m
inner join iot_device d on m.product_id = d.product_id INNER JOIN iot_device d ON m.product_id = d.product_id
left join iot_things_model_translate t on m.model_id = t.id LEFT JOIN iot_things_model_translate t ON m.model_id = t.id
where d.device_id = #{deviceId} WHERE
and m.is_readonly = 0 d.device_id = #{deviceId}
AND m.is_readonly = 0
</select> </select>
</mapper> </mapper>

View File

@ -32,4 +32,15 @@ SET `is_calculated` = 1
WHERE `formula` IS NOT NULL WHERE `formula` IS NOT NULL
AND `formula` != '' AND `formula` != ''
AND `formula` LIKE '%{%}%' AND `formula` LIKE '%{%}%'
AND `del_flag` = '0'; AND `del_flag` = '0';
-- 1. 添加 device_id 字段(默认为0,表示产品级物模型)
ALTER TABLE `iot_things_model`
ADD COLUMN `device_id` bigint(20) NOT NULL DEFAULT 0 COMMENT '设备ID,0表示产品级物模型,>0表示设备级计算物模型' AFTER `product_name`;
-- 2. 添加 serial_number 字段
ALTER TABLE `iot_things_model`
ADD COLUMN `serial_number` varchar(64) NULL DEFAULT NULL COMMENT '设备编号' AFTER `device_id`;
-- 3. 添加索引(设备编号索引)
ALTER TABLE `iot_things_model` ADD INDEX `idx_calculated_serial` (`is_calculated`, `serial_number`);

View File

@ -109,6 +109,7 @@
"device.device-edit.148398-80": "Alert user", "device.device-edit.148398-80": "Alert user",
"device.device-edit.148398-81": "equipment alarm ", "device.device-edit.148398-81": "equipment alarm ",
"device.device-edit.148398-82": "Voice intercom", "device.device-edit.148398-82": "Voice intercom",
"device.device-edit.148398-83": "Calculation Formula",
"device.device-functionlog.399522-0": "Please Select A Device Slave:", "device.device-functionlog.399522-0": "Please Select A Device Slave:",
"device.device-functionlog.399522-1": "Please Select A Device Slave", "device.device-functionlog.399522-1": "Please Select A Device Slave",
"device.device-functionlog.399522-2": "Slave Address:$", "device.device-functionlog.399522-2": "Slave Address:$",

View File

@ -298,7 +298,6 @@
"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": "%s in the formula is a placeholder representing the current thing model value.", "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-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",
@ -307,10 +306,6 @@
"product.product-things-model.142341-59": "Divide: %s/10", "product.product-things-model.142341-59": "Divide: %s/10",
"product.product-things-model.142341-60": "Divide (retain decimals): %s%10.00", "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",
@ -551,5 +546,9 @@
"product.product-modbus-task.894593-67": "Tips", "product.product-modbus-task.894593-67": "Tips",
"product.product-modbus-task.894593-68": "determine", "product.product-modbus-task.894593-68": "determine",
"product.product-modbus-task.894593-69": "cancel", "product.product-modbus-task.894593-69": "cancel",
"product.product-modbus-task.894593-70": "Cancelled" "product.product-modbus-task.894593-70": "Cancelled",
"product.product-things-model.142341-200": "Click symbols to build the formula",
"product.product-things-model.142341-201": "Reference product model identifiers",
"product.product-things-model.142341-202": "No available identifiers",
"product.product-things-model.142341-203": "Add Calculated Model"
} }

View File

@ -109,6 +109,7 @@
"device.device-edit.148398-80": "告警用户", "device.device-edit.148398-80": "告警用户",
"device.device-edit.148398-81": "设备告警", "device.device-edit.148398-81": "设备告警",
"device.device-edit.148398-82": "语音对讲", "device.device-edit.148398-82": "语音对讲",
"device.device-edit.148398-83": "计算公式",
"device.device-functionlog.399522-0": "请选择设备从机:", "device.device-functionlog.399522-0": "请选择设备从机:",
"device.device-functionlog.399522-1": "请选择设备从机", "device.device-functionlog.399522-1": "请选择设备从机",
"device.device-functionlog.399522-2": "从机地址:$", "device.device-functionlog.399522-2": "从机地址:$",

View File

@ -296,9 +296,8 @@
"product.product-things-model.142341-50": "请输入单位,例如:℃", "product.product-things-model.142341-50": "请输入单位,例如:℃",
"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-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",
@ -307,10 +306,6 @@
"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": "例如:关闭",
@ -551,5 +546,9 @@
"product.product-modbus-task.894593-67": "提示", "product.product-modbus-task.894593-67": "提示",
"product.product-modbus-task.894593-68": "确定", "product.product-modbus-task.894593-68": "确定",
"product.product-modbus-task.894593-69": "取消", "product.product-modbus-task.894593-69": "取消",
"product.product-modbus-task.894593-70": "已取消" "product.product-modbus-task.894593-70": "已取消",
} "product.product-things-model.142341-200": "点击符号快速拼接",
"product.product-things-model.142341-201": "引用产品物模型标识",
"product.product-things-model.142341-202": "暂无可用标识",
"product.product-things-model.142341-203": "新增计算模型"
}

View File

@ -135,6 +135,13 @@
<!-- <real-time-status ref="realTimeStatus" :device="form" @statusEvent="getDeviceStatusData($event)" /> --> <!-- <real-time-status ref="realTimeStatus" :device="form" @statusEvent="getDeviceStatusData($event)" /> -->
<running-status ref="runningStatus" :device="form" @statusEvent="getDeviceStatusData($event)" /> <running-status ref="runningStatus" :device="form" @statusEvent="getDeviceStatusData($event)" />
</el-tab-pane> </el-tab-pane>
<!-- 设备级别物模型 -->
<el-tab-pane name="calculatedModel" :disabled="form.deviceId == 0" v-if="form.deviceType !== 3" lazy>
<span slot="label"><span style="color: red">* </span>{{$t('device.device-edit.148398-83')}}</span>
<device-things-model ref="deviceThingsModel" :device="form" />
</el-tab-pane>
<el-tab-pane <el-tab-pane
name="deviceSub" name="deviceSub"
:disabled="form.deviceId == 0" :disabled="form.deviceId == 0"
@ -342,6 +349,7 @@ import deviceVariable from './device-variable';
import deviceInlineVideo from './device-inline-video'; import deviceInlineVideo from './device-inline-video';
import deviceFuncLog from './device-functionlog'; import deviceFuncLog from './device-functionlog';
import deviceSub from './device-sub'; import deviceSub from './device-sub';
import deviceThingsModel from './device-things-model.vue';
import vueQr from 'vue-qr'; import vueQr from 'vue-qr';
import { loadBMap } from '@/utils/map.js'; import { loadBMap } from '@/utils/map.js';
import { deviceSynchronization, getDevice, addDevice, updateDevice, generatorDeviceNum, getMqttConnect } from '@/api/iot/device'; import { deviceSynchronization, getDevice, addDevice, updateDevice, generatorDeviceNum, getMqttConnect } from '@/api/iot/device';
@ -383,6 +391,7 @@ export default {
instructionParsing, instructionParsing,
deviceModbusTask, deviceModbusTask,
deviceInlineVideo, deviceInlineVideo,
deviceThingsModel,
}, },
watch: { watch: {
activeName(val) { activeName(val) {

View File

@ -0,0 +1,606 @@
<template>
<div style="padding-left: 20px">
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" plain icon="el-icon-plus" size="mini" @click="handleAdd" v-hasPermi="['iot:model:add']">
新增
</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="warning" plain icon="el-icon-refresh" size="mini" @click="getList" v-hasPermi="['iot:model:list']">
刷新
</el-button>
</el-col>
<el-col :span="1.5">
<el-link type="danger" style="padding-top: 5px" :underline="false">
设备级物模型,基于产品物模型进行公式计算
</el-link>
</el-col>
</el-row>
<el-table v-loading="loading" :data="modelList" size="mini" :border="false">
<el-table-column label="物模型名称" align="center" prop="modelName" width="200" />
<el-table-column label="标识符" align="center" prop="identifier" />
<el-table-column label="图表展示" align="center" width="80">
<template slot-scope="scope">
<dict-tag :options="dict.type.iot_yes_no" :value="scope.row.isChart" />
</template>
</el-table-column>
<el-table-column label="实时监测" align="center" width="80">
<template slot-scope="scope">
<dict-tag :options="dict.type.iot_yes_no" :value="scope.row.isMonitor" />
</template>
</el-table-column>
<el-table-column label="历史存储" align="center" width="80">
<template slot-scope="scope">
<dict-tag :options="dict.type.iot_yes_no" :value="scope.row.isHistory" />
</template>
</el-table-column>
<el-table-column label="数据类型" align="center" prop="datatype" width="80">
<template slot-scope="scope">
<dict-tag :options="dict.type.iot_data_type" :value="scope.row.datatype" />
</template>
</el-table-column>
<el-table-column label="数据定义" align="left" header-align="center" prop="specs" min-width="150" class-name="specsColor">
<template slot-scope="scope">
<div v-html="formatSpecsDisplay(scope.row.specs)"></div>
</template>
</el-table-column>
<el-table-column label="计算公式" align="left" prop="formula" min-width="180">
<template slot-scope="scope">
<el-tooltip v-if="scope.row.formula" :content="scope.row.formula" placement="top" effect="light">
<span class="formula-tag">{{ scope.row.formula }}</span>
</el-tooltip>
</template>
</el-table-column>
<el-table-column label="排序" align="center" prop="modelOrder" width="80" />
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" fixed="right">
<template slot-scope="scope">
<el-button size="mini" type="text" icon="el-icon-view" @click="handleEdit(scope.row)" v-hasPermi="['iot:model:edit']">
修改
</el-button>
<el-button size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)" v-hasPermi="['iot:model:remove']">
删除
</el-button>
</template>
</el-table-column>
</el-table>
<pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize" @pagination="getList" />
<!-- 添加或修改物模型对话框 -->
<el-dialog :title="dialogTitle" :visible.sync="dialogVisible" width="600px" append-to-body v-dialogDrag>
<el-form ref="form" :model="form" :rules="rules" label-width="100px">
<el-form-item label="物模型名称" prop="modelName">
<el-input v-model="form.modelName" placeholder="请输入物模型名称" style="width: 385px" />
</el-form-item>
<el-form-item label="标识符" prop="identifier">
<el-input v-model="form.identifier" placeholder="请输入标识符" style="width: 385px" :disabled="!!form.modelId" />
</el-form-item>
<el-form-item label="排序" prop="modelOrder">
<el-input v-model="form.modelOrder" placeholder="请输入排序" type="number" style="width: 385px" />
</el-form-item>
<el-form-item label="模型特性" prop="property">
<el-checkbox name="isChart" label="图表展示" v-model="form.isChart" :true-label="1" :false-label="0"></el-checkbox>
<el-checkbox name="isMonitor" label="实时监测" v-model="form.isMonitor" :true-label="1" :false-label="0"></el-checkbox>
<el-checkbox name="isHistory" label="历史存储" v-model="form.isHistory" :true-label="1" :false-label="0"></el-checkbox>
</el-form-item>
<el-divider></el-divider>
<el-form-item label="数据类型" prop="datatype">
<el-select v-model="form.datatype" placeholder="请选择数据类型" style="width: 175px">
<el-option key="integer" label="整数(integer)" value="integer"></el-option>
<el-option key="decimal" label="小数(decimal)" value="decimal"></el-option>
<el-option key="string" label="字符串(string)" value="string"></el-option>
</el-select>
</el-form-item>
<div v-if="form.datatype == 'integer' || form.datatype == 'decimal'">
<el-form-item label="取值范围">
<el-row>
<el-col :span="9">
<el-input v-model="specsForm.min" placeholder="最小值" type="number" />
</el-col>
<el-col :span="2" align="center">~</el-col>
<el-col :span="9">
<el-input v-model="specsForm.max" placeholder="最大值" type="number" />
</el-col>
</el-row>
</el-form-item>
<el-form-item label="单位">
<el-input v-model="specsForm.unit" placeholder="请输入单位,如:℃、%" style="width: 385px" />
</el-form-item>
<el-form-item label="步长">
<el-input v-model="specsForm.step" placeholder="请输入步长" type="number" style="width: 385px" />
</el-form-item>
</div>
<div v-if="form.datatype == 'string'">
<el-form-item label="数据长度">
<el-row>
<el-col :span="9">
<el-input v-model="specsForm.maxLength" placeholder="请输入最大长度" type="number" />
</el-col>
<el-col :span="14" :offset="1">字节(Byte),默认1024</el-col>
</el-row>
</el-form-item>
</div>
<el-form-item label="计算公式" prop="formula">
<template slot="label">
<span slot="label">{{$t('device.device-edit.148398-83')}}</span>
<el-tooltip style="cursor: pointer" effect="light" placement="top">
<div slot="content">
用于对原始值进行计算后存储和显示<br />
公式中使用产品物模型标识符,并用大括号包裹<br />
支持运算符:+ - * / % ^(次方) sqrt(开方)<br />
<br />
<strong>示例:</strong><br />
压力=K*频率平方-常数:1.5*{frequency}^2-100
</div>
<i class="el-icon-question" />
</el-tooltip>
</template>
<div class="formula-editor-container">
<!-- 运算符按钮组 -->
<div class="operator-section">
<span class="section-label">运算符:</span>
<div class="operator-buttons">
<el-button style="color: #f56c6c" size="mini" title="加法" @click="insertOperator('+')">+</el-button>
<el-button style="color: #f56c6c" size="mini" title="减法" @click="insertOperator('-')">−</el-button>
<el-button style="color: #f56c6c" size="mini" title="乘法" @click="insertOperator('*')">×</el-button>
<el-button style="color: #f56c6c" size="mini" title="除法" @click="insertOperator('/')">÷</el-button>
<el-button style="color: #f56c6c" size="mini" title="次方" @click="insertOperator('^')">^</el-button>
<el-button style="color: #f56c6c" size="mini" title="开方" @click="insertOperator('sqrt')">sqrt</el-button>
<el-button style="color: #f56c6c" size="mini" title="取余" @click="insertOperator('%')">%</el-button>
<el-button style="color: #f56c6c" size="mini" title="左括号" @click="insertOperator('(')">(</el-button>
<el-button style="color: #f56c6c" size="mini" title="右括号" @click="insertOperator(')')">)</el-button>
</div>
</div>
<!-- 物模型变量区域 -->
<div class="variable-section" v-loading="loadingModels">
<span class="section-label">产品通用物模型变量:</span>
<div class="variable-tags">
<el-tag
v-for="model in productModels"
:key="model.identifier"
size="small"
type="primary"
effect="plain"
class="var-tag"
@click="insertVariable(model.identifier)"
:title="model.identifier"
>
{{ model.modelName }}
</el-tag>
<span v-if="productModels.length === 0 && !loadingModels" class="no-data">暂无可用物模型</span>
</div>
</div>
<!-- 输入框和清空按钮 -->
<div class="formula-input-section">
<el-input v-model="form.formula" type="textarea" :rows="2" placeholder="输入或点击上方运算符、物模型变量构建公式" class="formula-input" />
<el-button type="danger" plain size="small" title="清空公式" @click="form.formula = ''" class="clear-btn">清空</el-button>
</div>
</div>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitForm" v-show="form.modelId">修 改</el-button>
<el-button type="primary" @click="submitForm" v-show="!form.modelId">新 增</el-button>
<el-button @click="dialogVisible = false">取 消</el-button>
</div>
</el-dialog>
</div>
</template>
<style>
.specsColor {
background-color: #fcfcfc;
}
</style>
<style scoped>
/* 公式编辑器容器 */
.formula-editor-container {
width: 420px;
border: 1px solid #e4e7ed;
border-radius: 8px;
padding: 14px;
background: linear-gradient(to bottom, #fafbfc, #ffffff);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
}
/* 运算符区域 */
.operator-section {
margin-bottom: 12px;
}
.section-label {
display: block;
font-size: 12px;
color: #909399;
margin-bottom: 8px;
font-weight: 500;
}
.operator-buttons {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.operator-buttons .el-button {
min-width: 38px;
height: 32px;
font-weight: 600;
padding: 0 12px;
border-radius: 6px;
font-size: 14px;
}
.operator-buttons .el-button--default {
background: #fff;
border-color: #dcdfe6;
color: #606266;
}
.operator-buttons .el-button--default:hover {
color: #409eff;
border-color: #409eff;
background-color: #ecf5ff;
}
/* 物模型变量区域 */
.variable-section {
margin-bottom: 12px;
padding-top: 12px;
border-top: 1px solid #ebeef5;
}
.variable-tags {
display: flex;
flex-wrap: wrap;
gap: 8px;
max-height: 80px;
overflow-y: auto;
padding: 2px 0;
}
.var-tag {
cursor: pointer;
transition: all 0.2s ease;
border-radius: 4px;
font-size: 12px;
padding: 0 10px;
height: 26px;
line-height: 24px;
}
.var-tag:hover {
background-color: #409eff;
color: #fff;
border-color: #409eff;
transform: translateY(-1px);
box-shadow: 0 2px 6px rgba(64, 158, 255, 0.35);
}
.no-data {
color: #c0c4cc;
font-size: 12px;
font-style: italic;
}
/* 输入框和清空按钮区域 */
.formula-input-section {
display: flex;
align-items: flex-start;
gap: 10px;
padding-top: 12px;
border-top: 1px solid #ebeef5;
}
.formula-input {
flex: 1;
}
.formula-input ::v-deep .el-textarea__inner {
font-family: 'Monaco', 'Menlo', 'Consolas', monospace;
font-size: 13px;
border-radius: 6px;
background: #fff;
border-color: #dcdfe6;
}
.formula-input ::v-deep .el-textarea__inner:focus {
border-color: #409eff;
box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.1);
}
.clear-btn {
height: 54px;
border-radius: 6px;
font-size: 13px;
}
.clear-btn:hover {
background-color: #fef0f0;
}
</style>
<script>
import { listModel, getModel, addModel, updateModel, delModel } from '@/api/iot/model';
export default {
name: 'DeviceThingsModel',
dicts: ['iot_data_type', 'iot_yes_no'],
props: {
device: {
type: Object,
default: () => ({}),
},
},
data() {
return {
loading: false,
loadingModels: false,
dialogVisible: false,
dialogTitle: '',
total: 0,
modelList: [],
productModels: [],
queryParams: {
productId: 0,
deviceId: 0,
isCalculated: 1,
pageNum: 1,
pageSize: 10,
},
form: {
modelId: null,
modelName: '',
identifier: '',
productId: 0,
productName: '',
deviceId: 0,
serialNumber: '',
tenantId: 0,
tenantName: '',
type: 1,
datatype: 'decimal',
formula: '',
isCalculated: 1,
specs: '',
isChart: 1,
isHistory: 1,
isMonitor: 1,
isReadonly: 1,
modelOrder: 0,
remark: '',
},
specsForm: {
type: 'decimal',
max: 100,
min: 0,
step: 1,
unit: '',
maxLength: 1024,
},
rules: {
modelName: [{ required: true, message: '请输入物模型名称', trigger: 'blur' }],
identifier: [
{ required: true, message: '请输入标识符', trigger: 'blur' },
{ pattern: /^[a-zA-Z][a-zA-Z0-9_]*$/, message: '以字母开头,只含字母、数字、下划线', trigger: 'blur' },
],
datatype: [{ required: true, message: '请选择数据类型', trigger: 'change' }],
formula: [{ required: true, message: '请输入计算公式', trigger: 'blur' }],
},
};
},
watch: {
device: {
handler(val) {
if (val && val.deviceId) {
this.queryParams.productId = val.productId || 0;
this.queryParams.deviceId = val.deviceId || 0;
this.getList();
this.loadProductModels();
}
},
immediate: true,
deep: true,
},
},
methods: {
/** 查询设备计算物模型列表 */
getList() {
if (!this.queryParams.deviceId) return;
this.loading = true;
listModel(this.queryParams)
.then((response) => {
this.modelList = response.rows || [];
this.total = response.total || 0;
})
.finally(() => {
this.loading = false;
});
},
/** 加载产品物模型(用于公式变量选择) */
loadProductModels() {
if (!this.device.productId) return;
this.loadingModels = true;
listModel({
productId: this.device.productId,
deviceId: 0,
pageNum: 1,
pageSize: 200,
})
.then((response) => {
// 过滤掉纯计算物模型(只保留设备上报的物模型)
this.productModels = (response.rows || []).filter((m) => m.isCalculated !== 1);
})
.finally(() => {
this.loadingModels = false;
});
},
/** 表单重置 */
resetForm() {
this.form = {
modelId: null,
modelName: '',
identifier: '',
productId: this.device.productId || 0,
productName: this.device.productName || '',
deviceId: this.device.deviceId || 0,
serialNumber: this.device.serialNumber || '',
tenantId: this.device.tenantId || 0,
tenantName: this.device.tenantName || '',
type: 1,
datatype: 'decimal',
formula: '',
isCalculated: 1,
specs: '',
isChart: 1,
isHistory: 1,
isMonitor: 1,
isReadonly: 1,
modelOrder: 0,
remark: '',
};
this.specsForm = {
type: 'decimal',
max: 100,
min: 0,
step: 1,
unit: '',
maxLength: 1024,
};
},
/** 新增按钮操作 */
handleAdd() {
this.resetForm();
this.dialogTitle = '新增计算物模型';
this.dialogVisible = true;
this.$nextTick(() => {
this.$refs.form && this.$refs.form.clearValidate();
});
},
/** 修改按钮操作 */
handleEdit(row) {
this.resetForm();
this.dialogTitle = '修改计算物模型';
getModel(row.modelId).then((response) => {
const data = response.data || {};
this.form = { ...this.form, ...data };
if (data.specs) {
try {
this.specsForm = JSON.parse(data.specs);
} catch (e) {
console.error('解析specs失败', e);
}
}
this.dialogVisible = true;
});
},
/** 删除按钮操作 */
handleDelete(row) {
this.$modal
.confirm(`是否确认删除物模型"${row.modelName}"?`)
.then(() => delModel(row.modelId))
.then(() => {
this.$modal.msgSuccess('删除成功');
this.getList();
})
.catch(() => {});
},
/** 插入运算符 */
insertOperator(op) {
this.form.formula = (this.form.formula || '') + op;
},
/** 插入变量(标识符使用大括号包裹) */
insertVariable(identifier) {
this.form.formula = (this.form.formula || '') + `{${identifier}}`;
},
/** 格式化物模型specs */
formatThingsSpecs() {
var data = {};
data.type = this.form.datatype;
if (this.form.datatype == 'integer' || this.form.datatype == 'decimal') {
data.min = Number(this.specsForm.min ? this.specsForm.min : 0);
data.max = Number(this.specsForm.max ? this.specsForm.max : 100);
data.unit = this.specsForm.unit ? this.specsForm.unit : '';
data.step = Number(this.specsForm.step ? this.specsForm.step : 1);
} else if (this.form.datatype == 'string') {
data.maxLength = Number(this.specsForm.maxLength ? this.specsForm.maxLength : 1024);
}
return JSON.stringify(data);
},
/** 格式化显示数据定义 */
formatSpecsDisplay(json) {
if (json == null || json == undefined) {
return;
}
let specs = JSON.parse(json);
if (specs.type === 'integer' || specs.type === 'decimal') {
return (
`<span style='width:50%;display:inline-block;'>最大值:<span style="color:#F56C6C">` +
specs.max +
`</span></span>最小值:<span style="color:#F56C6C">` +
specs.min +
`</span><br /><span style='width:50%;display:inline-block;'>步长:<span style="color:#F56C6C">` +
specs.step +
`</span></span>单位:<span style="color:#F56C6C">` +
specs.unit
);
} else if (specs.type === 'string') {
return `数据长度:<span style="color:#F56C6C">` + specs.maxLength + '</span>';
}
return '';
},
/** 提交按钮 */
submitForm() {
this.$refs.form.validate((valid) => {
if (!valid) return;
// 格式化specs
this.form.specs = this.formatThingsSpecs();
this.form.isCalculated = 1;
this.form.isReadonly = 1;
this.form.type = 1;
// 去除公式中的空格
if (this.form.formula) {
this.form.formula = this.form.formula.replace(/\s+/g, '');
}
const request = this.form.modelId ? updateModel(this.form) : addModel(this.form);
request.then(() => {
this.$modal.msgSuccess(this.form.modelId ? '修改成功' : '新增成功');
this.dialogVisible = false;
this.getList();
});
});
},
},
};
</script>
<style scoped>
.formula-tag {
display: inline-block;
max-width: 140px;
color: #d85a5a;
font-family: 'SF Mono', Monaco, Menlo, Consolas, monospace;
font-size: 12px;
font-weight: 500;
letter-spacing: 0.3px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
vertical-align: middle;
padding: 4px 10px;
border: 1px solid rgba(216, 90, 90, 0.35);
border-radius: 6px;
background: linear-gradient(135deg, rgba(255, 235, 235, 0.8) 0%, rgba(255, 245, 245, 0.6) 100%);
box-shadow: 0 1px 3px rgba(216, 90, 90, 0.1);
cursor: pointer;
}
</style>

View File

@ -74,6 +74,8 @@ export default {
productId: 0, productId: 0,
// 1-属性,2-功能,3-事件,4-属性和功能 // 1-属性,2-功能,3-事件,4-属性和功能
type: 4, type: 4,
// 产品管理页面只显示设备上报的物模型,不显示纯计算物模型
isCalculated: 0,
}, },
form: {}, form: {},
// 产品 // 产品

View File

@ -195,7 +195,7 @@
<span style="color: red" v-hasPermi="['iot:model:list']">*</span> <span style="color: red" v-hasPermi="['iot:model:list']">*</span>
{{ $t('product.product-edit.473153-38') }} {{ $t('product.product-edit.473153-38') }}
</span> </span>
<product-things-model ref="productThingsModel" :product="form" @updateModel="updateModel" /> <product-things-model ref="productThingsModel" :product="form" :isCalculated="0" @updateModel="updateModel" />
</el-tab-pane> </el-tab-pane>
<!-- 子产品 --> <!-- 子产品 -->
<el-tab-pane name="productSub" :disabled="form.productId == 0" v-if="form.deviceType == 2 && (form.protocolCode == 'MODBUS-RTU' || form.protocolCode == 'MODBUS-TCP')" lazy> <el-tab-pane name="productSub" :disabled="form.productId == 0" v-if="form.deviceType == 2 && (form.protocolCode == 'MODBUS-RTU' || form.protocolCode == 'MODBUS-TCP')" lazy>

View File

@ -401,6 +401,7 @@ export default {
pageNum: 1, pageNum: 1,
pageSize: 1000, pageSize: 1000,
productId: 0, productId: 0,
isCalculated: 0,
}, },
thingsTotal: 0, thingsTotal: 0,
// 表单参数 // 表单参数

View File

@ -196,8 +196,6 @@
<br /> <br />
{{ $t('product.product-things-model.142341-54') }} {{ $t('product.product-things-model.142341-54') }}
<br /> <br />
{{ $t('product.product-things-model.142341-54-1') }}
<br />
{{ $t('product.product-things-model.142341-54-2') }} {{ $t('product.product-things-model.142341-54-2') }}
<br /> <br />
<br /> <br />
@ -211,16 +209,7 @@
<br /> <br />
{{ $t('product.product-things-model.142341-59') }} {{ $t('product.product-things-model.142341-59') }}
<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') }})
<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 /> <br />
</div> </div>
<i class="el-icon-question" /> <i class="el-icon-question" />
@ -417,6 +406,10 @@ export default {
type: Object, type: Object,
default: null, default: null,
}, },
isCalculated: {
type: Number,
default: 0,
},
}, },
watch: { watch: {
// 获取到父组件传递的productId后,刷新列表 // 获取到父组件传递的productId后,刷新列表
@ -425,6 +418,10 @@ export default {
if (this.productInfo && this.productInfo.productId != 0) { if (this.productInfo && this.productInfo.productId != 0) {
this.queryParams.productId = this.productInfo.productId; this.queryParams.productId = this.productInfo.productId;
this.productId = this.productInfo.productId; this.productId = this.productInfo.productId;
// 设置isCalculated查询条件(产品管理页面只显示设备上报的物模型,不显示纯计算物模型)
if (this.isCalculated !== null) {
this.queryParams.isCalculated = this.isCalculated;
}
this.getList(); this.getList();
} }
}, },