diff --git a/fastbee-common/src/main/java/com/fastbee/common/utils/CalculateUtils.java b/fastbee-common/src/main/java/com/fastbee/common/utils/CalculateUtils.java index 47caa19d..b43e116b 100644 --- a/fastbee-common/src/main/java/com/fastbee/common/utils/CalculateUtils.java +++ b/fastbee-common/src/main/java/com/fastbee/common/utils/CalculateUtils.java @@ -17,6 +17,12 @@ import java.util.regex.Pattern; */ public class CalculateUtils { + /** + * 公式中跨设备引用正则:{slaveId:N.identifier} + */ + private static final Pattern CROSS_DEVICE_REF = Pattern.compile("\\{slaveId:(\\d+)\\.([^}]+)}"); + + /** * /* * 支持加减乘除、幂运算、取余及括号的应用 @@ -219,6 +225,25 @@ public class CalculateUtils { return isNum.matches(); } + /** + * 解析公式中所有跨设备引用占位符 + * + * @param formula 计算公式,形如 {slaveId:1.Ang_z} + {slaveId:2.Ang_z} + * @return key=slaveId:N.identifier, value=identifier + */ + public static Map parseCrossDeviceRefs(String formula) { + Map refs = new LinkedHashMap<>(); + if (formula == null || formula.isEmpty()) { + return refs; + } + Matcher m = CROSS_DEVICE_REF.matcher(formula); + while (m.find()) { + String key = "slaveId:" + m.group(1) + "." + m.group(2); + refs.put(key, m.group(2)); + } + return refs; + } + public static String calculateReplace(String str, Map map) { for (Map.Entry entry : map.entrySet()) { // 对于包含特殊字符的占位符(如 {identifier}),需要转义正则表达式特殊字符 diff --git a/fastbee-iot-data/src/main/java/com/fastbee/iot/data/push/ThingsModelPushHandler.java b/fastbee-iot-data/src/main/java/com/fastbee/iot/data/push/ThingsModelPushHandler.java index 73e81425..39f040ba 100644 --- a/fastbee-iot-data/src/main/java/com/fastbee/iot/data/push/ThingsModelPushHandler.java +++ b/fastbee-iot-data/src/main/java/com/fastbee/iot/data/push/ThingsModelPushHandler.java @@ -8,7 +8,6 @@ import com.fastbee.common.core.notify.config.WeChatConfigParams; import com.fastbee.common.core.notify.msg.WechatMsgParams; import com.fastbee.common.core.redis.RedisKeyBuilder; import com.fastbee.common.enums.DataLevelEnum; -import com.fastbee.common.utils.CalculateUtils; import com.fastbee.common.utils.StringUtils; import com.fastbee.common.utils.wechat.WechatUtils; import com.fastbee.common.wechat.WeChatAppResult; @@ -58,8 +57,6 @@ public class ThingsModelPushHandler { private static final RestTemplate REST_TEMPLATE = new RestTemplate(); private static final Pattern VAR_PATTERN = Pattern.compile("\\{\\{([^}]+)}}"); - /** 公式中跨设备引用:{slaveId:N.identifier} */ - private static final Pattern CROSS_DEVICE_REF = Pattern.compile("\\{slaveId:(\\d+)\\.([^}]+)}"); private static final SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); @@ -258,6 +255,7 @@ public class ThingsModelPushHandler { /** * 计算网关跨设备计算物模型的当前值(只基于各从机当前缓存值) + * 委托 IThingsModelService.computeCrossDeviceCurrentValue 统一计算 */ private ComputedValue tryComputeGatewayCalculatedValue(ThingsModelPushConfig config, String gwSerialNumber, @@ -286,65 +284,16 @@ public class ThingsModelPushHandler { model.getIsCalculated() != 1 || StringUtils.isEmpty(model.getFormula())) { return null; } - Map refs = parseCrossDeviceRefs(model.getFormula()); - if (refs.isEmpty()) { + List subDeviceList = subDeviceBySlaveId != null + ? new ArrayList<>(subDeviceBySlaveId.values()) + : Collections.emptyList(); + ValueItem result = thingsModelService.computeCrossDeviceCurrentValue(model.getFormula(), subDeviceList); + if (result == null) { + log.warn("[Push] configId={} sn={} identifier={} 跨设备公式计算失败", + config.getId(), gwSerialNumber, identifier); return null; } - - Map replaceMap = new HashMap<>(); - Date maxTs = null; - for (Map.Entry ref : refs.entrySet()) { - String refKey = ref.getKey(); // slaveId:N.identifier - int slaveId; - try { - slaveId = Integer.parseInt(refKey.split("\\.")[0].replace("slaveId:", "")); - } catch (Exception e) { - continue; - } - SubDeviceListVO sub = subDeviceBySlaveId != null ? subDeviceBySlaveId.get(slaveId) : null; - String subSn = sub != null ? sub.getSubDeviceNo() : null; - Long subProductId = sub != null ? sub.getSubProductId() : null; - String subIdentifier = ref.getValue(); - String val = "0"; - if (subProductId != null && StringUtils.isNotEmpty(subSn) && StringUtils.isNotEmpty(subIdentifier)) { - try { - ValueItem v = thingsModelService.getCacheIdentifierValue(subProductId, subSn, subIdentifier); - if (v != null && StringUtils.isNotEmpty(v.getValue())) { - val = v.getValue(); - } - if (v != null && v.getTs() != null) { -// if (maxTs == null || v.getTs().after(maxTs)) { -// maxTs = v.getTs(); -// } - maxTs = v.getTs(); - } - } catch (Exception ignored) { - } - } - replaceMap.put("{" + refKey + "}", val); - } - try { - Object computed = CalculateUtils.execute(model.getFormula(), replaceMap); - String computedStr = computed != null ? computed.toString() : null; - return new ComputedValue(computedStr, maxTs); - } catch (Exception e) { - log.warn("[Push] configId={} sn={} identifier={} 公式计算失败 formula={} err={}", - config.getId(), gwSerialNumber, identifier, model.getFormula(), e.getMessage()); - return null; - } - } - - private Map parseCrossDeviceRefs(String formula) { - Map refs = new LinkedHashMap<>(); - if (StringUtils.isEmpty(formula)) { - return refs; - } - Matcher m = CROSS_DEVICE_REF.matcher(formula); - while (m.find()) { - String key = "slaveId:" + m.group(1) + "." + m.group(2); - refs.put(key, m.group(2)); - } - return refs; + return new ComputedValue(result.getValue(), result.getTs()); } /** diff --git a/fastbee-open-api/src/main/java/com/fastbee/controller/tool/ToolController.java b/fastbee-open-api/src/main/java/com/fastbee/controller/tool/ToolController.java index daf961a6..3c3e1af5 100644 --- a/fastbee-open-api/src/main/java/com/fastbee/controller/tool/ToolController.java +++ b/fastbee-open-api/src/main/java/com/fastbee/controller/tool/ToolController.java @@ -163,7 +163,7 @@ public class ToolController extends BaseController { @ApiOperation("mqtt认证") @PostMapping("/mqtt/authv5") public ResponseEntity mqttAuthv5(@RequestBody JSONObject json) throws Exception { - log.info("-----------auth-json:" + json + "---------------"); + log.info("-----------auth-json:{}---------------", json); String clientid = json.getString("clientid"); String username = json.getString("username"); String password = json.getString("password"); @@ -173,7 +173,7 @@ public class ToolController extends BaseController { if (clientid.startsWith("server")) { // 服务端认证:配置的账号密码认证 if (mqttConfig.getUsername().equals(username) && mqttConfig.getPassword().equals(password)) { - log.info("-----------服务端mqtt认证成功,clientId:" + clientid + "---------------"); + log.info("-----------服务端mqtt认证成功,clientId:{}---------------", clientid); ret.put("result", "allow"); return ResponseEntity.ok().body(ret); } else { @@ -188,7 +188,7 @@ public class ToolController extends BaseController { } try { Claims claims = Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody(); - log.info("-----------移动端/Web端mqtt认证成功,clientId:" + clientid + "---------------"); + log.info("-----------移动端/Web端mqtt认证成功,clientId:{}---------------", clientid); ret.put("result", "allow"); return ResponseEntity.ok().body(ret); } catch (Exception ex) { @@ -255,7 +255,7 @@ public class ToolController extends BaseController { } catch (Exception ex) { ex.printStackTrace(); - log.error("发生错误:" + ex.getMessage()); + log.error("发生错误:{}", ex.getMessage(), ex); } } @@ -270,7 +270,7 @@ public class ToolController extends BaseController { if (peername.indexOf(":") != -1) { ipAddress = peername.substring(0, peername.indexOf(":")); } - log.info("webhook:" + event + ",clientid:" + clientid + ",peername:" + peername); + log.info("webhook:{},clientid:{},peername:{}", event, clientid, peername); // 过滤服务端、web端和手机端 if (clientid.startsWith("server") || clientid.startsWith("web") || clientid.startsWith("phone")) { return; diff --git a/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/dto/ThingsModelDTO.java b/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/dto/ThingsModelDTO.java index 7d4bee0e..8803544e 100644 --- a/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/dto/ThingsModelDTO.java +++ b/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/dto/ThingsModelDTO.java @@ -111,6 +111,11 @@ public class ThingsModelDTO extends BaseEntity { private Integer modelOrder; + /** + * 物模型来源类型(0-设备上报,1-纯计算,2-其他预留) + */ + private Integer isCalculated; + private String value = ""; private String ts; diff --git a/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/IThingsModelService.java b/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/IThingsModelService.java index 8a20e4cb..fdc3dfa0 100644 --- a/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/IThingsModelService.java +++ b/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/IThingsModelService.java @@ -7,6 +7,7 @@ import com.fastbee.iot.model.ThingsModelPerm; import com.fastbee.iot.model.ThingsModelSimVO; import com.fastbee.iot.model.ThingsModels.*; import com.fastbee.iot.model.dto.ThingsModelDTO; +import com.fastbee.iot.model.gateWay.SubDeviceListVO; import com.fastbee.iot.model.modbus.ModbusAndThingsVO; import java.util.List; @@ -202,4 +203,14 @@ public interface IThingsModelService { * @return */ List selectThingsModelBySerialNumber(Long deviceId); + + /** + * 计算跨设备计算物模型当前值 + * 从子设备 Redis 缓存获取各引用变量的值,按公式实时计算 + * + * @param formula 物模型公式,含 {slaveId:N.identifier} 占位符 + * @param subDevices 网关下的子设备列表 + * @return 计算结果(value + ts),公式无跨设备引用或计算失败时返回 null + */ + ValueItem computeCrossDeviceCurrentValue(String formula, List subDevices); } diff --git a/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/impl/DataCenterServiceImpl.java b/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/impl/DataCenterServiceImpl.java index 559af1aa..dcbcae60 100644 --- a/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/impl/DataCenterServiceImpl.java +++ b/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/impl/DataCenterServiceImpl.java @@ -21,8 +21,6 @@ import org.springframework.stereotype.Service; import javax.annotation.Resource; import java.util.*; import java.util.function.Function; -import java.util.regex.Matcher; -import java.util.regex.Pattern; import java.util.stream.Collectors; /** @@ -58,8 +56,6 @@ public class DataCenterServiceImpl implements DataCenterService { @Resource private com.fastbee.iot.cache.ITSLCache itslCache; - /** 公式中跨设备引用:{slaveId:N.identifier} */ - private static final Pattern CROSS_DEVICE_REF = Pattern.compile("\\{slaveId:(\\d+)\\.([^}]+)\\}"); @Override public List deviceHistory(DeviceHistoryParam deviceHistoryParam) { @@ -515,13 +511,7 @@ public class DataCenterServiceImpl implements DataCenterService { } private Map parseCrossDeviceRefs(String formula) { - Map refs = new LinkedHashMap<>(); - Matcher m = CROSS_DEVICE_REF.matcher(formula); - while (m.find()) { - String key = "slaveId:" + m.group(1) + "." + m.group(2); - refs.put(key, m.group(2)); - } - return refs; + return CalculateUtils.parseCrossDeviceRefs(formula); } private List buildMinuteSlots(String beginTime, String endTime) { diff --git a/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/impl/DeviceServiceImpl.java b/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/impl/DeviceServiceImpl.java index 505f2b63..c7c9bb3e 100644 --- a/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/impl/DeviceServiceImpl.java +++ b/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/impl/DeviceServiceImpl.java @@ -2030,11 +2030,38 @@ public class DeviceServiceImpl implements IDeviceService { } list.addAll(subList); } + // 对跨设备计算物模型(isCalculated=1,公式含 slaveId 引用)实时计算当前值 + computeCrossDeviceValuesForList(list, subDeviceListVOList); // 指令控制 jurisdictionThingsModel(list, deviceId); return list; } + /** + * 对列表中所有跨设备计算物模型实时计算当前值(从子设备缓存取值,按公式计算) + */ + private void computeCrossDeviceValuesForList(List list, List subDevices) { + if (org.springframework.util.CollectionUtils.isEmpty(subDevices)) { + return; + } + for (ThingsModelDTO dto : list) { + if (!Integer.valueOf(1).equals(dto.getIsCalculated())) { + continue; + } + String formula = dto.getFormula(); + if (StringUtils.isEmpty(formula) || !formula.contains("slaveId:")) { + continue; + } + ValueItem result = thingsModelService.computeCrossDeviceCurrentValue(formula, subDevices); + if (result != null && StringUtils.isNotEmpty(result.getValue())) { + dto.setValue(result.getValue()); + if (result.getTs() != null) { + dto.setTs(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS, result.getTs())); + } + } + } + } + private void jurisdictionThingsModel(List list, Long deviceId) { Long userId = SecurityUtils.getUserId(); // 查询是否有指令控制 diff --git a/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/impl/ThingsModelServiceImpl.java b/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/impl/ThingsModelServiceImpl.java index 9ae8bb87..60a07a4d 100644 --- a/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/impl/ThingsModelServiceImpl.java +++ b/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/impl/ThingsModelServiceImpl.java @@ -9,9 +9,11 @@ import com.fastbee.common.core.redis.RedisCache; import com.fastbee.common.core.redis.RedisKeyBuilder; import com.fastbee.common.enums.scenemodel.SceneModelVariableTypeEnum; import com.fastbee.common.exception.ServiceException; +import com.fastbee.common.utils.CalculateUtils; import com.fastbee.common.utils.DateUtils; import com.fastbee.common.utils.SecurityUtils; import com.fastbee.common.utils.StringUtils; +import com.fastbee.iot.model.gateWay.SubDeviceListVO; import com.fastbee.iot.cache.ITSLCache; import com.fastbee.iot.domain.*; import com.fastbee.iot.mapper.*; @@ -646,6 +648,8 @@ public class ThingsModelServiceImpl implements IThingsModelService { otherThingsModel.setDatatype(datatype); otherThingsModel.setModelOrder(thingsModel.getModelOrder()); otherThingsModel.setCreateTime(thingsModel.getCreateTime()); + otherThingsModel.setFormula(thingsModel.getFormula()); + otherThingsModel.setIsCalculated(thingsModel.getIsCalculated()); resultList.add(otherThingsModel); } return resultList; @@ -766,6 +770,50 @@ public class ThingsModelServiceImpl implements IThingsModelService { return thingsModelMapper.selectThingsModelBySerialNumber(deviceId, SecurityUtils.getLanguage()); } + @Override + public ValueItem computeCrossDeviceCurrentValue(String formula, List subDevices) { + Map refs = CalculateUtils.parseCrossDeviceRefs(formula); + if (refs.isEmpty()) { + return null; + } + Map replaceMap = new HashMap<>(); + Date maxTs = null; + for (Map.Entry ref : refs.entrySet()) { + String refKey = ref.getKey(); // slaveId:N.identifier + int slaveId; + try { + slaveId = Integer.parseInt(refKey.split("\\.")[0].replace("slaveId:", "")); + } catch (Exception e) { + replaceMap.put("{" + refKey + "}", "0"); + continue; + } + SubDeviceListVO sub = subDevices.stream() + .filter(s -> s.getSlaveId() != null && s.getSlaveId() == slaveId) + .findFirst().orElse(null); + if (sub == null || sub.getSubProductId() == null || StringUtils.isEmpty(sub.getSubDeviceNo())) { + replaceMap.put("{" + refKey + "}", "0"); + continue; + } + String subIdentifier = ref.getValue(); + ValueItem v = this.getCacheIdentifierValue(sub.getSubProductId(), sub.getSubDeviceNo(), subIdentifier); + String val = (v != null && StringUtils.isNotEmpty(v.getValue())) ? v.getValue() : "0"; + replaceMap.put("{" + refKey + "}", val); + if (v != null && v.getTs() != null) { + maxTs = v.getTs(); + } + } + try { + Object computed = CalculateUtils.execute(formula, replaceMap); + ValueItem result = new ValueItem(); + result.setValue(computed != null ? computed.toString() : ""); + result.setTs(maxTs); + return result; + } catch (Exception e) { + log.warn("[ThingsModel] 跨设备公式计算失败 formula={} err={}", formula, e.getMessage()); + return null; + } + } + private ThingsModel propertyDtoChangeThingsModel(ThingsModelValueItem valueItem) { ThingsModel thingsModel = new ThingsModel(); thingsModel.setIdentifier(valueItem.getId()); @@ -779,6 +827,8 @@ public class ThingsModelServiceImpl implements IThingsModelService { thingsModel.setType(valueItem.getType()); thingsModel.setModelId(valueItem.getModelId()); thingsModel.setCreateTime(valueItem.getCreateTime()); + thingsModel.setFormula(valueItem.getFormula()); + thingsModel.setIsCalculated(valueItem.getIsCalculated()); return thingsModel; }