20260102:
1、合并纯计算物模型与设备上报物模型数据存储(方法在解析数据之后将纯计算物模型填数据0,用到ConcurrentHashMap key:productId value:纯计算物模型标识符集合)
This commit is contained in:
parent
44df912fc2
commit
d4403f5d42
@ -8,6 +8,12 @@ fastbee:
|
|||||||
profile: /uploadPath
|
profile: /uploadPath
|
||||||
addressEnabled: true # 获取ip地址开关
|
addressEnabled: true # 获取ip地址开关
|
||||||
captchaType: math # 验证码类型 math 数组计算 char 字符验证
|
captchaType: math # 验证码类型 math 数组计算 char 字符验证
|
||||||
|
# 纯计算物模型缓存配置
|
||||||
|
# cache:
|
||||||
|
# calculated-things-model:
|
||||||
|
# use-redis: false # 是否使用 Redis 缓存(false=本地缓存,true=Redis缓存)
|
||||||
|
# local-cache-threshold: 1000 # 本地缓存大小阈值(超过此值建议切换到Redis)
|
||||||
|
# redis-ttl: 86400 # Redis 缓存过期时间(秒),默认 24 小时
|
||||||
|
|
||||||
# 开发环境配置
|
# 开发环境配置
|
||||||
server:
|
server:
|
||||||
|
|||||||
@ -182,4 +182,20 @@ public class RedisKeyBuilder {
|
|||||||
return FastBeeConstant.REDIS.POWER_ON_TRIGGER_JOB_IDS + serialNumber.toUpperCase();
|
return FastBeeConstant.REDIS.POWER_ON_TRIGGER_JOB_IDS + serialNumber.toUpperCase();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 纯计算物模型缓存key(产品级别)
|
||||||
|
* 用于缓存产品下的纯计算物模型标识符集合
|
||||||
|
*/
|
||||||
|
public static String buildCalculatedThingsModelKey(Long productId) {
|
||||||
|
return "CALCULATED_TM:PRODUCT:" + productId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 纯计算物模型缓存key(设备级别,未来扩展)
|
||||||
|
* 用于缓存设备下的纯计算物模型标识符集合(当设备有自定义物模型选择时使用)
|
||||||
|
*/
|
||||||
|
public static String buildCalculatedThingsModelDeviceKey(Long deviceId) {
|
||||||
|
return "CALCULATED_TM:DEVICE:" + deviceId;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -101,7 +101,7 @@ public class DeviceReportMessageServiceImpl implements IDeviceReportMessageServi
|
|||||||
|
|
||||||
switch (bo.getServerType()) {
|
switch (bo.getServerType()) {
|
||||||
case MQTT:
|
case MQTT:
|
||||||
log.debug("=>MQ*收到设备主题[{}],消息:[{}]", bo.getTopicName(), bo.getData());
|
log.debug("{}=>MQ*收到设备主题[{}],消息:[{}]", serialNumber, bo.getTopicName(), bo.getData());
|
||||||
//构建消息
|
//构建消息
|
||||||
DeviceStatusVO deviceStatusVO = buildReport(bo);
|
DeviceStatusVO deviceStatusVO = buildReport(bo);
|
||||||
/*获取协议处理器*/
|
/*获取协议处理器*/
|
||||||
@ -131,7 +131,7 @@ public class DeviceReportMessageServiceImpl implements IDeviceReportMessageServi
|
|||||||
processNoSub(reportMessage, bo.getTopicName());
|
processNoSub(reportMessage, bo.getTopicName());
|
||||||
break;
|
break;
|
||||||
case TCP:
|
case TCP:
|
||||||
log.debug("*MQ收到TCP推送消息[{}]", JSON.toJSON(bo.getThingsModelSimpleItem()));
|
log.debug("{}*MQ收到TCP推送消息[{}]", serialNumber, JSON.toJSON(bo.getThingsModelSimpleItem()));
|
||||||
DeviceStatusVO deviceStatusVO1 = deviceService.selectDeviceStatusAndTransportStatus(serialNumber);
|
DeviceStatusVO deviceStatusVO1 = deviceService.selectDeviceStatusAndTransportStatus(serialNumber);
|
||||||
Optional.ofNullable(deviceStatusVO1).orElseThrow(() -> new ServiceException("设备不存在"));
|
Optional.ofNullable(deviceStatusVO1).orElseThrow(() -> new ServiceException("设备不存在"));
|
||||||
|
|
||||||
|
|||||||
@ -19,6 +19,7 @@ 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.domain.Device;
|
||||||
import com.fastbee.iot.domain.ModbusConfig;
|
import com.fastbee.iot.domain.ModbusConfig;
|
||||||
@ -39,6 +40,7 @@ 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;
|
||||||
|
|
||||||
@ -67,6 +69,9 @@ public class ModbusProtocol implements IProtocol {
|
|||||||
@Resource
|
@Resource
|
||||||
private ModbusConfigMapper modbusConfigMapper;
|
private ModbusConfigMapper modbusConfigMapper;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private ICalculatedThingsModelCache calculatedThingsModelCache;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public DeviceReport decode(DeviceData deviceData, String clientId) {
|
public DeviceReport decode(DeviceData deviceData, String clientId) {
|
||||||
try {
|
try {
|
||||||
@ -311,6 +316,52 @@ public class ModbusProtocol implements IProtocol {
|
|||||||
|
|
||||||
simpleItemList.add(simpleItem);
|
simpleItemList.add(simpleItem);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 处理纯计算物模型:查询产品下所有纯计算物模型,构造 ThingsModelSimpleItem,值为 "0"
|
||||||
|
handleCalculatedThingsModels(productId, simpleItemList, isReadOnly);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理纯计算物模型
|
||||||
|
* 从缓存获取纯计算物模型列表,构造 ThingsModelSimpleItem,值为 "0"
|
||||||
|
* 后续在计算环节会统一处理所有物模型(包括纯计算物模型)
|
||||||
|
*
|
||||||
|
* @param productId 产品ID
|
||||||
|
* @param simpleItemList 物模型值列表
|
||||||
|
* @param isReadOnly 是否只读
|
||||||
|
*/
|
||||||
|
private void handleCalculatedThingsModels(Long productId, List<ThingsModelSimpleItem> simpleItemList, int isReadOnly) {
|
||||||
|
try {
|
||||||
|
// 从缓存获取纯计算物模型标识符集合(支持产品级别和设备级别)
|
||||||
|
Set<String> calculatedIdentifiers = calculatedThingsModelCache.getCalculatedIdentifiers(productId, null);
|
||||||
|
|
||||||
|
if (calculatedIdentifiers.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取已上报的物模型标识符集合(避免重复)
|
||||||
|
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();
|
||||||
|
simpleItem.setId(identifier);
|
||||||
|
simpleItem.setValue("0"); // 初始值设为0,后续在计算环节会重新计算
|
||||||
|
simpleItem.setTs(DateUtils.getNowDate());
|
||||||
|
simpleItemList.add(simpleItem);
|
||||||
|
log.debug("=>添加纯计算物模型到上报列表,产品ID:[{}],标识符:[{}]", productId, identifier);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("=>处理纯计算物模型时发生错误,产品ID:[{}],错误:{}", productId, e.getMessage(), e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,56 @@
|
|||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
@ -0,0 +1,149 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@ -0,0 +1,163 @@
|
|||||||
|
/*
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*/
|
||||||
@ -317,6 +317,8 @@ public class TSLCacheImpl implements ITSLCache {
|
|||||||
.setName_en_US(thingsModel.getModelName_en_US())
|
.setName_en_US(thingsModel.getModelName_en_US())
|
||||||
.setOrder(thingsModel.getModelOrder())
|
.setOrder(thingsModel.getModelOrder())
|
||||||
.setModelId(thingsModel.getModelId())
|
.setModelId(thingsModel.getModelId())
|
||||||
|
.setFormula(thingsModel.getFormula())
|
||||||
|
.setIsCalculated(thingsModel.getIsCalculated())
|
||||||
.setConfig(thingsModel.getModbusConfig());
|
.setConfig(thingsModel.getModbusConfig());
|
||||||
return JSONObject.toJSONString(dto);
|
return JSONObject.toJSONString(dto);
|
||||||
}));
|
}));
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
package com.fastbee.iot.domain;
|
package com.fastbee.iot.domain;
|
||||||
|
|
||||||
import com.fastbee.common.annotation.Excel;
|
|
||||||
import com.fastbee.common.core.domain.BaseEntity;
|
import com.fastbee.common.core.domain.BaseEntity;
|
||||||
import com.fastbee.common.core.protocol.modbus.ModbusCode;
|
import com.fastbee.common.core.protocol.modbus.ModbusCode;
|
||||||
import io.swagger.annotations.ApiModelProperty;
|
import io.swagger.annotations.ApiModelProperty;
|
||||||
|
|||||||
@ -96,6 +96,13 @@ public class ThingsModel extends BaseEntity {
|
|||||||
@Excel(name = "计算公式", prompt = "选填,例如:%s*10,%s是占位符")
|
@Excel(name = "计算公式", prompt = "选填,例如:%s*10,%s是占位符")
|
||||||
private String formula;
|
private String formula;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 物模型来源类型(0-设备上报,1-纯计算,2-其他预留)
|
||||||
|
* 纯计算物模型:计算公式包含{},依赖其他物模型计算值
|
||||||
|
*/
|
||||||
|
@ApiModelProperty(value = "物模型来源类型", notes = "(0-设备上报,1-纯计算,2-其他预留)")
|
||||||
|
private Integer isCalculated;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 数据定义
|
* 数据定义
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -79,6 +79,12 @@ public class ThingsModelValueItem {
|
|||||||
* 计算公式
|
* 计算公式
|
||||||
*/
|
*/
|
||||||
private String formula;
|
private String formula;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 物模型来源类型(0-设备上报,1-纯计算,2-其他预留)
|
||||||
|
*/
|
||||||
|
private Integer isCalculated;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* modbus参数配置
|
* modbus参数配置
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -570,6 +570,7 @@ public class DeviceServiceImpl implements IDeviceService {
|
|||||||
|
|
||||||
// 执行计算
|
// 执行计算
|
||||||
value = String.valueOf(CaculateUtils.execute(formula, params));
|
value = String.valueOf(CaculateUtils.execute(formula, params));
|
||||||
|
log.info("=>纯计算物模型结果[{}],标识符:[{}],结果:{}", input.getProductId(), identity, value);
|
||||||
item.setValue(value);
|
item.setValue(value);
|
||||||
}
|
}
|
||||||
/* ★★★★★★★★★★★★★★★★★★★★★★ 数据计算 -结束 ★★★★★★★★★★★★★★★★★★★★★★*/
|
/* ★★★★★★★★★★★★★★★★★★★★★★ 数据计算 -结束 ★★★★★★★★★★★★★★★★★★★★★★*/
|
||||||
@ -700,40 +701,7 @@ public class DeviceServiceImpl implements IDeviceService {
|
|||||||
}
|
}
|
||||||
/* ★★★★★★★★★★★★★★★★★★★★★★ 存储数据 - 结束 ★★★★★★★★★★★★★★★★★★★★★★*/
|
/* ★★★★★★★★★★★★★★★★★★★★★★ 存储数据 - 结束 ★★★★★★★★★★★★★★★★★★★★★★*/
|
||||||
|
|
||||||
/* ★★★★★★★★★★★★★★★★★★★★★★ 自动计算依赖物模型 - 开始 ★★★★★★★★★★★★★★★★★★★★★★*/
|
// 注意:纯计算物模型已在 ModbusProtocol 中构造 ThingsModelSimpleItem(值为0)
|
||||||
// 记录所有已更新的物模型标识符(设备上报的)
|
|
||||||
Set<String> updatedIdentifiers = new HashSet<>();
|
|
||||||
for (ThingsModelSimpleItem item : input.getThingsModelValueRemarkItem()) {
|
|
||||||
String identity = item.getId();
|
|
||||||
if (identity.startsWith("array_")) {
|
|
||||||
identity = identity.substring(9);
|
|
||||||
}
|
|
||||||
updatedIdentifiers.add(identity);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 自动计算依赖已更新物模型的其他物模型(纯计算型物模型)
|
|
||||||
if (!updatedIdentifiers.isEmpty() && !isShadow) {
|
|
||||||
// 用于存储计算型物模型的数据
|
|
||||||
Map<String, String> calculatedMaps = new HashMap<>();
|
|
||||||
List<DeviceLog> calculatedDeviceLogList = new ArrayList<>();
|
|
||||||
|
|
||||||
calculateDependentThingsModels(input, updatedIdentifiers, nowDate, calculatedMaps, calculatedDeviceLogList, type);
|
|
||||||
|
|
||||||
// 将计算后的值更新到缓存
|
|
||||||
if (!calculatedMaps.isEmpty()) {
|
|
||||||
redisCache.hashPutAll(key, calculatedMaps);
|
|
||||||
log.debug("=>自动计算依赖物模型,更新缓存成功,设备编号:[{}],计算型物模型数量:[{}]",
|
|
||||||
input.getDeviceNumber(), calculatedMaps.size());
|
|
||||||
}
|
|
||||||
|
|
||||||
// 保存计算型物模型的历史数据
|
|
||||||
if (!CollectionUtils.isEmpty(calculatedDeviceLogList)) {
|
|
||||||
logService.saveDeviceLogBatch(calculatedDeviceLogList);
|
|
||||||
log.debug("=>自动计算依赖物模型,保存历史数据成功,设备编号:[{}],历史数据数量:[{}]",
|
|
||||||
input.getDeviceNumber(), calculatedDeviceLogList.size());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/* ★★★★★★★★★★★★★★★★★★★★★★ 自动计算依赖物模型 - 结束 ★★★★★★★★★★★★★★★★★★★★★★*/
|
|
||||||
|
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
@ -1956,151 +1924,6 @@ public class DeviceServiceImpl implements IDeviceService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 自动计算依赖已更新物模型的其他物模型(纯计算型物模型)
|
|
||||||
* 当设备只上报频率时,自动计算依赖频率的压力等物模型
|
|
||||||
*
|
|
||||||
* @param input 设备上报数据输入
|
|
||||||
* @param updatedIdentifiers 已更新的物模型标识符集合(设备上报的)
|
|
||||||
* @param nowDate 数据记录时间
|
|
||||||
* @param maps 物模型值映射(用于批量更新缓存)
|
|
||||||
* @param deviceLogList 设备日志列表(用于存储历史数据)
|
|
||||||
* @param type 物模型类型(1=属性,2=功能,3=事件)
|
|
||||||
*/
|
|
||||||
private void calculateDependentThingsModels(ThingsModelValuesInput input,
|
|
||||||
Set<String> updatedIdentifiers,
|
|
||||||
Date nowDate,
|
|
||||||
Map<String, String> maps,
|
|
||||||
List<DeviceLog> deviceLogList,
|
|
||||||
int type) {
|
|
||||||
try {
|
|
||||||
// 查询产品下所有物模型
|
|
||||||
com.fastbee.iot.domain.ThingsModel queryModel = new com.fastbee.iot.domain.ThingsModel();
|
|
||||||
queryModel.setProductId(input.getProductId());
|
|
||||||
List<com.fastbee.iot.domain.ThingsModel> allThingsModels = thingsModelService.selectThingsModelList(queryModel);
|
|
||||||
|
|
||||||
if (CollectionUtils.isEmpty(allThingsModels)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取设备信息(用于存储历史数据)
|
|
||||||
Device device = this.selectDeviceBySerialNumber(input.getDeviceNumber());
|
|
||||||
if (device == null) {
|
|
||||||
log.warn("=>自动计算依赖物模型时,设备不存在[{}]", input.getDeviceNumber());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 遍历所有物模型,找出依赖已更新物模型的计算型物模型
|
|
||||||
for (com.fastbee.iot.domain.ThingsModel thingsModel : allThingsModels) {
|
|
||||||
String dependentIdentifier = thingsModel.getIdentifier();
|
|
||||||
|
|
||||||
// 跳过已更新的物模型(设备已上报的)
|
|
||||||
if (updatedIdentifiers.contains(dependentIdentifier)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 只处理有计算公式的物模型
|
|
||||||
String formula = thingsModel.getFormula();
|
|
||||||
if (StringUtils.isEmpty(formula)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查计算公式是否引用了已更新的物模型
|
|
||||||
Set<String> referencedIdentifiers = parseFormulaPlaceholders(formula);
|
|
||||||
|
|
||||||
// 检查是否至少引用了一个已更新的物模型
|
|
||||||
boolean hasDependency = false;
|
|
||||||
for (String refIdentifier : referencedIdentifiers) {
|
|
||||||
if (updatedIdentifiers.contains(refIdentifier)) {
|
|
||||||
hasDependency = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果没有依赖已更新的物模型,跳过
|
|
||||||
if (!hasDependency) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查所有引用的物模型是否都有值(从缓存中获取)
|
|
||||||
Map<String, String> params = new HashMap<>();
|
|
||||||
|
|
||||||
for (String refIdentifier : referencedIdentifiers) {
|
|
||||||
try {
|
|
||||||
ValueItem refValueItem = itslValueCache.getCacheIdentifier(
|
|
||||||
input.getProductId(),
|
|
||||||
input.getDeviceNumber(),
|
|
||||||
refIdentifier
|
|
||||||
);
|
|
||||||
if (refValueItem != null && StringUtils.isNotEmpty(refValueItem.getValue())) {
|
|
||||||
params.put("{" + refIdentifier + "}", refValueItem.getValue());
|
|
||||||
} else {
|
|
||||||
// 如果引用的物模型值不存在,使用0作为默认值
|
|
||||||
params.put("{" + refIdentifier + "}", "0");
|
|
||||||
log.warn("=>自动计算依赖物模型时,引用的物模型值不存在[{}],标识符:[{}],使用默认值0",
|
|
||||||
input.getProductId(), refIdentifier);
|
|
||||||
// 如果依赖的物模型没有值,仍然尝试计算(使用默认值0)
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.error("=>自动计算依赖物模型时,获取引用的物模型值失败[{}],标识符:[{}],错误:{}",
|
|
||||||
input.getProductId(), refIdentifier, e.getMessage());
|
|
||||||
params.put("{" + refIdentifier + "}", "0");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果公式中有 %s,使用0作为默认值(因为这是纯计算型物模型,没有设备上报的原始值)
|
|
||||||
if (formula.contains("%s")) {
|
|
||||||
params.put("%s", "0");
|
|
||||||
}
|
|
||||||
|
|
||||||
// 执行计算
|
|
||||||
String calculatedValue;
|
|
||||||
try {
|
|
||||||
calculatedValue = String.valueOf(CaculateUtils.execute(formula, params));
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.error("=>自动计算依赖物模型时,计算公式执行失败[{}],标识符:[{}],公式:[{}],错误:{}",
|
|
||||||
input.getProductId(), dependentIdentifier, formula, e.getMessage());
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 更新缓存
|
|
||||||
ValueItem valueItem = new ValueItem();
|
|
||||||
valueItem.setId(dependentIdentifier);
|
|
||||||
valueItem.setValue(calculatedValue);
|
|
||||||
valueItem.setShadow(calculatedValue);
|
|
||||||
valueItem.setTs(DateUtils.getNowDate());
|
|
||||||
maps.put(dependentIdentifier, JSONObject.toJSONString(valueItem));
|
|
||||||
|
|
||||||
// 获取物模型详细信息(用于存储历史数据)
|
|
||||||
ThingsModelValueItem thingModelValue = thingsModelService.getSingleThingModels(
|
|
||||||
input.getProductId(), dependentIdentifier);
|
|
||||||
if (thingModelValue != null && thingModelValue.getType() == 1 && thingModelValue.getIsHistory() == 1) {
|
|
||||||
// 存储历史数据(仅属性类型且启用了历史存储)
|
|
||||||
DeviceLog deviceLog = new DeviceLog();
|
|
||||||
deviceLog.setSerialNumber(input.getDeviceNumber());
|
|
||||||
deviceLog.setLogType(type);
|
|
||||||
deviceLog.setMode(2); // 在线模式
|
|
||||||
deviceLog.setLogValue(calculatedValue);
|
|
||||||
deviceLog.setRemark("自动计算"); // 标记为自动计算
|
|
||||||
deviceLog.setIdentity(dependentIdentifier);
|
|
||||||
deviceLog.setCreateTime(nowDate);
|
|
||||||
deviceLog.setUserId(device.getTenantId());
|
|
||||||
deviceLog.setUserName(device.getTenantName());
|
|
||||||
deviceLog.setTenantId(device.getTenantId());
|
|
||||||
deviceLog.setTenantName(device.getTenantName());
|
|
||||||
deviceLog.setModelName(thingModelValue.getName());
|
|
||||||
deviceLog.setIsMonitor(thingModelValue.getIsMonitor());
|
|
||||||
deviceLogList.add(deviceLog);
|
|
||||||
}
|
|
||||||
|
|
||||||
log.debug("=>自动计算依赖物模型成功[{}],标识符:[{}],计算公式:[{}],计算结果:[{}]",
|
|
||||||
input.getProductId(), dependentIdentifier, formula, calculatedValue);
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.error("=>自动计算依赖物模型时发生异常[{}],设备编号:[{}],错误:{}",
|
|
||||||
input.getProductId(), input.getDeviceNumber(), e.getMessage(), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -12,6 +12,7 @@ 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.*;
|
||||||
@ -67,6 +68,8 @@ 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<>();
|
||||||
@ -154,9 +157,17 @@ 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缓存
|
||||||
itslCache.setCacheThingsModelByProductId(thingsModel.getProductId());
|
itslCache.setCacheThingsModelByProductId(thingsModel.getProductId());
|
||||||
|
// 刷新纯计算物模型缓存
|
||||||
|
if (result > 0) {
|
||||||
|
calculatedThingsModelCache.refreshCalculatedCache(thingsModel.getProductId());
|
||||||
|
}
|
||||||
if (result > 0) {
|
if (result > 0) {
|
||||||
// 更新场景管理变量
|
// 更新场景管理变量
|
||||||
List<SceneModelDevice> sceneModelDeviceList = sceneModelDeviceMapper.listDeviceByProductId(thingsModel.getProductId());
|
List<SceneModelDevice> sceneModelDeviceList = sceneModelDeviceMapper.listDeviceByProductId(thingsModel.getProductId());
|
||||||
@ -233,6 +244,8 @@ public class ThingsModelServiceImpl implements IThingsModelService {
|
|||||||
}
|
}
|
||||||
//更新redis缓存
|
//更新redis缓存
|
||||||
itslCache.setCacheThingsModelByProductId(input.getProductId());
|
itslCache.setCacheThingsModelByProductId(input.getProductId());
|
||||||
|
// 刷新纯计算物模型缓存
|
||||||
|
calculatedThingsModelCache.refreshCalculatedCache(input.getProductId());
|
||||||
}
|
}
|
||||||
return repeatCount;
|
return repeatCount;
|
||||||
}
|
}
|
||||||
@ -257,9 +270,17 @@ 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());
|
itslCache.setCacheThingsModelByProductId(thingsModel.getProductId());
|
||||||
|
// 刷新纯计算物模型缓存
|
||||||
|
if (result > 0) {
|
||||||
|
calculatedThingsModelCache.refreshCalculatedCache(thingsModel.getProductId());
|
||||||
|
}
|
||||||
if (result > 0) {
|
if (result > 0) {
|
||||||
List<SceneModelData> sceneModelDataList = sceneModelDataMapper.selectSceneDeviceThingsModelList(thingsModel.getModelId());
|
List<SceneModelData> sceneModelDataList = sceneModelDataMapper.selectSceneDeviceThingsModelList(thingsModel.getModelId());
|
||||||
if (org.apache.commons.collections4.CollectionUtils.isEmpty(sceneModelDataList)) {
|
if (org.apache.commons.collections4.CollectionUtils.isEmpty(sceneModelDataList)) {
|
||||||
@ -300,6 +321,10 @@ public class ThingsModelServiceImpl implements IThingsModelService {
|
|||||||
}
|
}
|
||||||
// 更新redis缓存
|
// 更新redis缓存
|
||||||
itslCache.setCacheThingsModelByProductId(thingsModel.getProductId());
|
itslCache.setCacheThingsModelByProductId(thingsModel.getProductId());
|
||||||
|
// 刷新纯计算物模型缓存
|
||||||
|
if (result > 0 && thingsModel != null && thingsModel.getProductId() != null) {
|
||||||
|
calculatedThingsModelCache.refreshCalculatedCache(thingsModel.getProductId());
|
||||||
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@ -315,7 +340,13 @@ 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) {
|
||||||
itslCache.setCacheThingsModelByProductId(thingsModel.getProductId());
|
itslCache.setCacheThingsModelByProductId(thingsModel.getProductId());
|
||||||
|
// 刷新纯计算物模型缓存
|
||||||
|
if (result > 0) {
|
||||||
|
calculatedThingsModelCache.refreshCalculatedCache(thingsModel.getProductId());
|
||||||
|
}
|
||||||
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -343,6 +374,7 @@ public class ThingsModelServiceImpl implements IThingsModelService {
|
|||||||
item.setDatatype(JSONObject.parseObject(selectModel.getSpecs(), Datatype.class));
|
item.setDatatype(JSONObject.parseObject(selectModel.getSpecs(), Datatype.class));
|
||||||
item.setOrder(selectModel.getModelOrder());
|
item.setOrder(selectModel.getModelOrder());
|
||||||
item.setFormula(selectModel.getFormula());
|
item.setFormula(selectModel.getFormula());
|
||||||
|
item.setIsCalculated(selectModel.getIsCalculated());
|
||||||
itslCache.setCacheThingsModelByProductId(productId);
|
itslCache.setCacheThingsModelByProductId(productId);
|
||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
@ -736,4 +768,22 @@ public class ThingsModelServiceImpl implements IThingsModelService {
|
|||||||
return thingsModel;
|
return thingsModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断是否为纯计算物模型
|
||||||
|
* 如果公式包含{},则认为是纯计算物模型(依赖其他物模型计算值)
|
||||||
|
*
|
||||||
|
* @param formula 计算公式
|
||||||
|
* @return 0=设备上报(默认),1=纯计算,2=其他(预留)
|
||||||
|
*/
|
||||||
|
private Integer determineIsCalculated(String formula) {
|
||||||
|
if (StringUtils.isEmpty(formula)) {
|
||||||
|
return 0; // 默认设备上报
|
||||||
|
}
|
||||||
|
// 如果公式包含{},则认为是纯计算物模型
|
||||||
|
if (formula.contains("{") && formula.contains("}")) {
|
||||||
|
return 1; // 纯计算
|
||||||
|
}
|
||||||
|
return 0; // 设备上报
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -16,6 +16,7 @@
|
|||||||
<result property="type" column="type"/>
|
<result property="type" column="type"/>
|
||||||
<result property="datatype" column="datatype"/>
|
<result property="datatype" column="datatype"/>
|
||||||
<result property="formula" column="formula"/>
|
<result property="formula" column="formula"/>
|
||||||
|
<result property="isCalculated" column="is_calculated"/>
|
||||||
<result property="specs" column="specs"/>
|
<result property="specs" column="specs"/>
|
||||||
<result property="isChart" column="is_chart"/>
|
<result property="isChart" column="is_chart"/>
|
||||||
<result property="isSharePerm" column="is_share_perm"/>
|
<result property="isSharePerm" column="is_share_perm"/>
|
||||||
@ -57,6 +58,7 @@
|
|||||||
type,
|
type,
|
||||||
datatype,
|
datatype,
|
||||||
formula,
|
formula,
|
||||||
|
is_calculated,
|
||||||
specs,
|
specs,
|
||||||
is_chart,
|
is_chart,
|
||||||
is_share_perm,
|
is_share_perm,
|
||||||
@ -83,7 +85,7 @@
|
|||||||
end as model_name,
|
end as model_name,
|
||||||
m.model_name as model_name_zh_cn, t.en_us as model_name_en_us,
|
m.model_name as model_name_zh_cn, 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.product_name, m.tenant_id, m.tenant_name, m.identifier, 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.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
|
m.create_time, m.update_by, m.update_time, m.remark, m.is_readonly, m.model_order
|
||||||
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
|
||||||
@ -138,7 +140,7 @@
|
|||||||
else m.model_name
|
else m.model_name
|
||||||
end as model_name,
|
end as model_name,
|
||||||
m.product_id, m.product_name, m.tenant_id, m.tenant_name, m.identifier, m.type, m.datatype, m.formula,
|
m.product_id, m.product_name, m.tenant_id, m.tenant_name, m.identifier, 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.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
|
m.create_time, m.update_by, m.update_time, m.remark, m.is_readonly, m.model_order
|
||||||
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
|
||||||
@ -171,7 +173,7 @@
|
|||||||
else m.model_name
|
else m.model_name
|
||||||
end as model_name,
|
end as model_name,
|
||||||
m.product_id, m.product_name, m.tenant_id, m.tenant_name, m.identifier, m.type, m.datatype, m.formula,
|
m.product_id, m.product_name, m.tenant_id, m.tenant_name, m.identifier, 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.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
|
m.create_time, m.update_by, m.update_time, m.remark, m.is_readonly, m.model_order
|
||||||
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
|
||||||
@ -216,6 +218,9 @@
|
|||||||
<if test="formula != null">
|
<if test="formula != null">
|
||||||
formula,
|
formula,
|
||||||
</if>
|
</if>
|
||||||
|
<if test="isCalculated != null">
|
||||||
|
is_calculated,
|
||||||
|
</if>
|
||||||
<if test="specs != null and specs != ''">
|
<if test="specs != null and specs != ''">
|
||||||
specs,
|
specs,
|
||||||
</if>
|
</if>
|
||||||
@ -287,6 +292,9 @@
|
|||||||
<if test="formula != null">
|
<if test="formula != null">
|
||||||
#{formula},
|
#{formula},
|
||||||
</if>
|
</if>
|
||||||
|
<if test="isCalculated != null">
|
||||||
|
#{isCalculated},
|
||||||
|
</if>
|
||||||
<if test="specs != null and specs != ''">
|
<if test="specs != null and specs != ''">
|
||||||
#{specs},
|
#{specs},
|
||||||
</if>
|
</if>
|
||||||
@ -352,7 +360,8 @@
|
|||||||
is_readonly,
|
is_readonly,
|
||||||
create_by,
|
create_by,
|
||||||
create_time,
|
create_time,
|
||||||
formula
|
formula,
|
||||||
|
is_calculated
|
||||||
)
|
)
|
||||||
VALUES
|
VALUES
|
||||||
<foreach item="model" collection="list" separator=",">
|
<foreach item="model" collection="list" separator=",">
|
||||||
@ -361,7 +370,7 @@
|
|||||||
#{model.tenantName},#{model.identifier},#{model.type},#{model.datatype},
|
#{model.tenantName},#{model.identifier},#{model.type},#{model.datatype},
|
||||||
#{model.specs},#{model.isChart},#{model.isSharePerm},#{model.isHistory},
|
#{model.specs},#{model.isChart},#{model.isSharePerm},#{model.isHistory},
|
||||||
#{model.isMonitor},#{model.isApp},#{model.isReadonly},#{model.createBy},#{model.createTime},
|
#{model.isMonitor},#{model.isApp},#{model.isReadonly},#{model.createBy},#{model.createTime},
|
||||||
#{model.formula}
|
#{model.formula},#{model.isCalculated}
|
||||||
</trim>
|
</trim>
|
||||||
</foreach>
|
</foreach>
|
||||||
</insert>
|
</insert>
|
||||||
@ -396,6 +405,9 @@
|
|||||||
<if test="formula != null">
|
<if test="formula != null">
|
||||||
formula = #{formula},
|
formula = #{formula},
|
||||||
</if>
|
</if>
|
||||||
|
<if test="isCalculated != null">
|
||||||
|
is_calculated = #{isCalculated},
|
||||||
|
</if>
|
||||||
<if test="specs != null and specs != ''">
|
<if test="specs != null and specs != ''">
|
||||||
specs = #{specs},
|
specs = #{specs},
|
||||||
</if>
|
</if>
|
||||||
|
|||||||
@ -1,6 +0,0 @@
|
|||||||
-- 为iot_modbus_job表添加execute_time字段
|
|
||||||
-- 用于记录设备上电触发任务的执行时间
|
|
||||||
|
|
||||||
ALTER TABLE `iot_modbus_job`
|
|
||||||
ADD COLUMN `execute_time` datetime NULL DEFAULT NULL COMMENT '执行时间(仅设备上电触发任务使用,记录任务执行的时间)' AFTER `remark`;
|
|
||||||
|
|
||||||
35
sql/add_field_extension_file.sql
Normal file
35
sql/add_field_extension_file.sql
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
-- 一、为iot_modbus_job表添加execute_time字段
|
||||||
|
-- 用于记录设备上电触发任务的执行时间
|
||||||
|
|
||||||
|
ALTER TABLE `iot_modbus_job`
|
||||||
|
ADD COLUMN `execute_time` datetime NULL DEFAULT NULL COMMENT '执行时间(仅设备上电触发任务使用,记录任务执行的时间)' AFTER `remark`;
|
||||||
|
|
||||||
|
-- 二. 在字典表中添加"文档"类型,用于首页公告下载pdf等文档
|
||||||
|
INSERT INTO `sys_dict_data` VALUES (NULL, 3, '文档', '3', 'sys_notice_type', '', 'info', 'N', '0', 'admin', NOW(), '', NULL, '文档类型');
|
||||||
|
|
||||||
|
-- 2. 在 sys_notice 表中添加文件路径字段
|
||||||
|
ALTER TABLE `sys_notice` ADD COLUMN `file_path` VARCHAR(500) NULL COMMENT '文件路径(文档类型使用)' AFTER `notice_content`;
|
||||||
|
|
||||||
|
-- 检查表结构
|
||||||
|
SELECT * FROM sys_dict_data_translate WHERE id = 738;
|
||||||
|
|
||||||
|
-- 如果没有记录,插入翻译数据
|
||||||
|
INSERT INTO sys_dict_data_translate (id, zh_cn, en_us)
|
||||||
|
VALUES (738, '文档', 'Document')
|
||||||
|
ON DUPLICATE KEY UPDATE zh_cn = '文档', en_us = 'Document';
|
||||||
|
|
||||||
|
|
||||||
|
-- 三、物模型改造:添加纯计算物模型标识字段
|
||||||
|
-- is_calculated: 0=设备上报(默认),1=纯计算(计算公式包含{}),2=其他(预留扩展)
|
||||||
|
ALTER TABLE `iot_things_model`
|
||||||
|
ADD COLUMN `is_calculated` tinyint(1) NOT NULL DEFAULT 0
|
||||||
|
COMMENT '物模型来源类型(0-设备上报,1-纯计算,2-其他预留)'
|
||||||
|
AFTER `formula`;
|
||||||
|
|
||||||
|
-- 为已有数据设置 is_calculated:如果公式包含{},则设置为1(纯计算)
|
||||||
|
UPDATE `iot_things_model`
|
||||||
|
SET `is_calculated` = 1
|
||||||
|
WHERE `formula` IS NOT NULL
|
||||||
|
AND `formula` != ''
|
||||||
|
AND `formula` LIKE '%{%}%'
|
||||||
|
AND `del_flag` = '0';
|
||||||
@ -175,14 +175,21 @@
|
|||||||
</div>
|
</div>
|
||||||
</el-col>
|
</el-col>
|
||||||
|
|
||||||
<el-col :xs="24" :sm="24" :md="24" :lg="8" :xl="8">
|
|
||||||
<el-card shadow="hover" class="message-card">
|
|
||||||
<!-- 信息栏 -->
|
<!-- 信息栏 -->
|
||||||
|
<el-col :xs="24" :sm="24" :md="24" :lg="8" :xl="8">
|
||||||
|
<el-card shadow="hover" class="message-card message-card-expanded">
|
||||||
<h3 style="font-weight: bold; margin-top: 10px">
|
<h3 style="font-weight: bold; margin-top: 10px">
|
||||||
<i class="el-icon-s-order"></i>
|
<i class="el-icon-s-order"></i>
|
||||||
{{ $t('home.information') }}
|
{{ $t('home.information') }}
|
||||||
</h3>
|
</h3>
|
||||||
<div class="notice-bar" :class="{ animating: animate }">
|
<div
|
||||||
|
class="notice-bar"
|
||||||
|
:class="{ animating: animate && !isScrolling }"
|
||||||
|
ref="noticeBar"
|
||||||
|
@wheel="handleWheel"
|
||||||
|
@mouseenter="handleMouseEnter"
|
||||||
|
@mouseleave="handleMouseLeave"
|
||||||
|
>
|
||||||
<div
|
<div
|
||||||
style="cursor: pointer; display: table; width: 100%; line-height: 36px"
|
style="cursor: pointer; display: table; width: 100%; line-height: 36px"
|
||||||
v-for="item in noticeList"
|
v-for="item in noticeList"
|
||||||
@ -205,12 +212,13 @@
|
|||||||
</el-card>
|
</el-card>
|
||||||
</el-col>
|
</el-col>
|
||||||
|
|
||||||
<el-col :xs="24" :sm="24" :md="24" :lg="8" :xl="8">
|
<!-- 应飞哥要求隐藏 2026-01-01 -->
|
||||||
<!-- mqtt状态数据 -->
|
<!-- <el-col :xs="24" :sm="24" :md="24" :lg="8" :xl="8">
|
||||||
|
<!- mqtt状态数据 ->
|
||||||
<div class="card-container">
|
<div class="card-container">
|
||||||
<div ref="statsChart" style="height: 240px; width: 100%"></div>
|
<div ref="statsChart" style="height: 240px; width: 100%"></div>
|
||||||
</div>
|
</div>
|
||||||
</el-col>
|
</el-col> -->
|
||||||
</el-row>
|
</el-row>
|
||||||
|
|
||||||
<el-card class="rate" shadow="hover" style="margin: 10px 10px 20px 10px">
|
<el-card class="rate" shadow="hover" style="margin: 10px 10px 20px 10px">
|
||||||
@ -407,7 +415,9 @@ export default {
|
|||||||
version: '3.8.0',
|
version: '3.8.0',
|
||||||
animate: true,
|
animate: true,
|
||||||
duration: 10, // 公告滚动一次的时长(单位:秒)
|
duration: 10, // 公告滚动一次的时长(单位:秒)
|
||||||
interval: 5000, // 公告间隔时间(单位:毫秒)
|
interval: 4000, // 公告间隔时间(单位:毫秒)
|
||||||
|
isScrolling: false, // 是否正在使用鼠标滚轮滚动
|
||||||
|
scrollResetTimer: null, // 滚动重置定时器
|
||||||
// 服务器信息
|
// 服务器信息
|
||||||
server: {
|
server: {
|
||||||
jvm: {
|
jvm: {
|
||||||
@ -458,6 +468,11 @@ export default {
|
|||||||
clearInterval(this.weatherTimer);
|
clearInterval(this.weatherTimer);
|
||||||
this.weatherTimer = null;
|
this.weatherTimer = null;
|
||||||
}
|
}
|
||||||
|
// 清除滚动重置定时器
|
||||||
|
if (this.scrollResetTimer) {
|
||||||
|
clearTimeout(this.scrollResetTimer);
|
||||||
|
this.scrollResetTimer = null;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
this.init();
|
this.init();
|
||||||
@ -467,8 +482,8 @@ export default {
|
|||||||
// 先尝试获取浏览器位置,然后获取天气
|
// 先尝试获取浏览器位置,然后获取天气
|
||||||
this.getLocationAndWeather();
|
this.getLocationAndWeather();
|
||||||
|
|
||||||
this.getMqttStats();
|
// this.getMqttStats();
|
||||||
this.statisticMqtt();
|
// this.statisticMqtt();
|
||||||
|
|
||||||
// 设置天气自动刷新(每30分钟刷新一次)
|
// 设置天气自动刷新(每30分钟刷新一次)
|
||||||
this.weatherTimer = setInterval(() => {
|
this.weatherTimer = setInterval(() => {
|
||||||
@ -478,16 +493,57 @@ export default {
|
|||||||
methods: {
|
methods: {
|
||||||
// 公告栏滚动
|
// 公告栏滚动
|
||||||
startScroll() {
|
startScroll() {
|
||||||
|
// 只有在没有手动滚动且没有鼠标悬停时才启动自动滚动
|
||||||
|
if (!this.isScrolling) {
|
||||||
|
this.stopScroll(); // 先清除可能存在的定时器
|
||||||
this.intervalId = setInterval(() => {
|
this.intervalId = setInterval(() => {
|
||||||
const firstNotice = this.noticeList.shift();
|
const firstNotice = this.noticeList.shift();
|
||||||
this.noticeList.push(firstNotice);
|
this.noticeList.push(firstNotice);
|
||||||
}, this.interval);
|
}, this.interval);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
stopScroll() {
|
stopScroll() {
|
||||||
if (this.intervalId) {
|
if (this.intervalId) {
|
||||||
clearInterval(this.intervalId);
|
clearInterval(this.intervalId);
|
||||||
|
this.intervalId = null;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
// 处理鼠标滚轮事件
|
||||||
|
handleWheel(event) {
|
||||||
|
const noticeBar = this.$refs.noticeBar;
|
||||||
|
if (noticeBar) {
|
||||||
|
// 标记用户正在手动滚动
|
||||||
|
if (!this.isScrolling) {
|
||||||
|
this.isScrolling = true;
|
||||||
|
this.stopScroll();
|
||||||
|
}
|
||||||
|
// 重置滚动标志的定时器
|
||||||
|
if (this.scrollResetTimer) {
|
||||||
|
clearTimeout(this.scrollResetTimer);
|
||||||
|
}
|
||||||
|
// 滚动停止后2秒,重置标志
|
||||||
|
this.scrollResetTimer = setTimeout(() => {
|
||||||
|
this.isScrolling = false;
|
||||||
|
}, 2000);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// 鼠标进入信息栏
|
||||||
|
handleMouseEnter() {
|
||||||
|
// 鼠标悬停时暂停自动滚动
|
||||||
|
this.stopScroll();
|
||||||
|
},
|
||||||
|
// 鼠标离开信息栏
|
||||||
|
handleMouseLeave() {
|
||||||
|
// 清除滚动重置定时器
|
||||||
|
if (this.scrollResetTimer) {
|
||||||
|
clearTimeout(this.scrollResetTimer);
|
||||||
|
this.scrollResetTimer = null;
|
||||||
|
}
|
||||||
|
// 重置滚动标志
|
||||||
|
this.isScrolling = false;
|
||||||
|
// 鼠标离开后,恢复自动滚动
|
||||||
|
this.startScroll();
|
||||||
|
},
|
||||||
// 折线图
|
// 折线图
|
||||||
getChartData() {
|
getChartData() {
|
||||||
searchUserCount().then((response) => {
|
searchUserCount().then((response) => {
|
||||||
@ -1058,10 +1114,11 @@ export default {
|
|||||||
getNoticeList() {
|
getNoticeList() {
|
||||||
let queryParams = {
|
let queryParams = {
|
||||||
pageNum: 1,
|
pageNum: 1,
|
||||||
pageSize: 6,
|
pageSize: 20,
|
||||||
};
|
};
|
||||||
listNotice(queryParams).then((response) => {
|
listNotice(queryParams).then((response) => {
|
||||||
this.noticeList = response.rows.splice(0, 6);
|
// 显示所有查询到的数据,不限制数量
|
||||||
|
this.noticeList = response.rows || [];
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
// 下载文件
|
// 下载文件
|
||||||
@ -2841,6 +2898,21 @@ $aqi-unhealthy-color: #f44336;
|
|||||||
box-shadow: 0 8px 30px $hover-shadow-color;
|
box-shadow: 0 8px 30px $hover-shadow-color;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// 扩大后的信息栏
|
||||||
|
.message-card-expanded {
|
||||||
|
height: 500px; // 与地图高度一致
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
:deep(.el-card__body) {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 16px;
|
||||||
|
overflow: hidden;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
// 柱状图
|
// 柱状图
|
||||||
.card-container {
|
.card-container {
|
||||||
height: 240px;
|
height: 240px;
|
||||||
@ -2988,8 +3060,30 @@ $aqi-unhealthy-color: #f44336;
|
|||||||
|
|
||||||
// 公告栏容器样式调整
|
// 公告栏容器样式调整
|
||||||
.notice-bar {
|
.notice-bar {
|
||||||
overflow: hidden;
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
max-height: 450px;
|
||||||
|
padding-right: 5px;
|
||||||
|
|
||||||
|
// 自定义滚动条样式
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
width: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-webkit-scrollbar-track {
|
||||||
|
background: #f1f1f1;
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-webkit-scrollbar-thumb {
|
||||||
|
background: #888;
|
||||||
|
border-radius: 10px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: #555;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 确保每个公告项有适当间距
|
// 确保每个公告项有适当间距
|
||||||
& > div {
|
& > div {
|
||||||
|
|||||||
@ -91,19 +91,19 @@
|
|||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
<!-- 文档类型时显示文件上传 -->
|
<!-- 文档类型时显示文件上传 -->
|
||||||
<el-col :span="24" v-if="isDocumentType">
|
<el-col :span="24" v-if="form.noticeType == '3'">
|
||||||
<el-form-item label="文档上传" prop="filePath">
|
<el-form-item label="文档上传" prop="filePath">
|
||||||
<file-upload
|
<file-upload
|
||||||
v-model="form.filePath"
|
v-model="form.filePath"
|
||||||
:file-type="['pdf', 'doc', 'docx']"
|
:file-type="['pdf', 'doc', 'docx']"
|
||||||
:file-size="50"
|
:file-size="50"
|
||||||
:limit="1"
|
:limit="1"
|
||||||
:upload-file-url="getUploadUrl()"
|
:upload-file-url="process.env.VUE_APP_BASE_API + '/common/upload'"
|
||||||
></file-upload>
|
></file-upload>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
<!-- 非文档类型时显示内容编辑器 -->
|
<!-- 非文档类型时显示内容编辑器 -->
|
||||||
<el-col :span="24" v-if="!isDocumentType">
|
<el-col :span="24" v-if="form.noticeType != '3'">
|
||||||
<el-form-item :label="$t('system.notice.670989-7')">
|
<el-form-item :label="$t('system.notice.670989-7')">
|
||||||
<editor v-model="form.noticeContent" :min-height="192" />
|
<editor v-model="form.noticeContent" :min-height="192" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
@ -166,7 +166,7 @@ export default {
|
|||||||
filePath: [
|
filePath: [
|
||||||
{
|
{
|
||||||
validator: (rule, value, callback) => {
|
validator: (rule, value, callback) => {
|
||||||
if (this.isDocumentType && !value) {
|
if (this.form.noticeType == '3' && !value) {
|
||||||
callback(new Error('文档类型必须上传文件'));
|
callback(new Error('文档类型必须上传文件'));
|
||||||
} else {
|
} else {
|
||||||
callback();
|
callback();
|
||||||
@ -178,16 +178,6 @@ export default {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
|
||||||
// 判断是否为文档类型
|
|
||||||
isDocumentType() {
|
|
||||||
if (!this.form || this.form.noticeType === undefined || this.form.noticeType === null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// 兼容字符串和数字类型
|
|
||||||
return this.form.noticeType == '3';
|
|
||||||
}
|
|
||||||
},
|
|
||||||
created() {
|
created() {
|
||||||
this.getList();
|
this.getList();
|
||||||
},
|
},
|
||||||
@ -221,14 +211,9 @@ export default {
|
|||||||
// 处理公告类型变化
|
// 处理公告类型变化
|
||||||
handleNoticeTypeChange(value) {
|
handleNoticeTypeChange(value) {
|
||||||
// 切换类型时清空不相关的字段
|
// 切换类型时清空不相关的字段
|
||||||
const noticeType = String(value || '');
|
if (value == '3') {
|
||||||
if (noticeType === '3') {
|
|
||||||
// 文档类型,清空内容
|
// 文档类型,清空内容
|
||||||
this.form.noticeContent = undefined;
|
this.form.noticeContent = undefined;
|
||||||
// 触发文件路径验证
|
|
||||||
this.$nextTick(() => {
|
|
||||||
this.$refs.form && this.$refs.form.validateField('filePath');
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
// 非文档类型,清空文件路径
|
// 非文档类型,清空文件路径
|
||||||
this.form.filePath = undefined;
|
this.form.filePath = undefined;
|
||||||
@ -300,12 +285,6 @@ export default {
|
|||||||
})
|
})
|
||||||
.catch(() => {});
|
.catch(() => {});
|
||||||
},
|
},
|
||||||
/** 获取上传URL */
|
|
||||||
getUploadUrl() {
|
|
||||||
// 使用 window.location.origin 作为备用方案
|
|
||||||
const baseUrl = process.env.VUE_APP_BASE_API || '/prod-api';
|
|
||||||
return baseUrl + '/common/upload';
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user