20260731:平台对接优化,JSON解析

This commit is contained in:
zhugongping 2026-07-31 15:08:35 +08:00
parent 1b5536fc8c
commit 84f681e9d9
15 changed files with 798 additions and 353 deletions

View File

@ -15,6 +15,7 @@ import com.fastbee.iot.domain.Device;
import com.fastbee.iot.model.ThingsModels.ValueItem; import com.fastbee.iot.model.ThingsModels.ValueItem;
import com.fastbee.iot.platform.domain.*; import com.fastbee.iot.platform.domain.*;
import com.fastbee.iot.platform.flow.FlowContext; import com.fastbee.iot.platform.flow.FlowContext;
import com.fastbee.iot.platform.model.json.*;
import com.fastbee.iot.platform.flow.PlatformFlowExecutor; import com.fastbee.iot.platform.flow.PlatformFlowExecutor;
import com.fastbee.iot.platform.mapper.PlatformFlowMapper; import com.fastbee.iot.platform.mapper.PlatformFlowMapper;
import com.fastbee.iot.platform.mapper.PlatformInterfaceMapper; import com.fastbee.iot.platform.mapper.PlatformInterfaceMapper;
@ -94,8 +95,9 @@ public class DcpPlatformAdapter {
return; return;
} }
String sensorType = mapping.getSensorType(); String sensorType = mapping.getSensorType();
PlatformInterface pushInterface = loadPushInterface(mapping); PlatformInterface pushInterface = interfaceMapper.selectById(mapping.getPushInterfaceId());
JSONArray requestSchema = loadRequestSchema(pushInterface);
List<RequestSchemaField> requestSchema = pushInterface.getRequestSchemaParsed();
String interfaceCode = resolveInterfaceCode(sensorType, pushInterface); String interfaceCode = resolveInterfaceCode(sensorType, pushInterface);
try { try {
@ -127,8 +129,8 @@ public class DcpPlatformAdapter {
return; return;
} }
String sensorType = mapping.getSensorType(); String sensorType = mapping.getSensorType();
PlatformInterface pushInterface = loadPushInterface(mapping); PlatformInterface pushInterface = interfaceMapper.selectById(mapping.getPushInterfaceId());
JSONArray requestSchema = loadRequestSchema(pushInterface); List<RequestSchemaField> requestSchema = pushInterface.getRequestSchemaParsed();
String interfaceCode = resolveInterfaceCode(sensorType, pushInterface); String interfaceCode = resolveInterfaceCode(sensorType, pushInterface);
try { try {
@ -151,38 +153,33 @@ public class DcpPlatformAdapter {
return; return;
} }
JSONArray fieldMappings = parseJsonArray(mapping.getFieldMapping()); List<FieldMappingItem> fieldMappings = mapping.getFieldMappingParsed();
String baseUrl = platform.getBaseUrl() + pushPath; String baseUrl = platform.getBaseUrl() + pushPath;
// 解析 extra_mapping 中的 itemFieldMappings 和 reportIntervalSec // 解析 extra_mapping 中的 itemFieldMappings 和 reportIntervalSec
int reportIntervalSec = 300; int reportIntervalSec = 300;
JSONArray itemFieldMappings = null; List<ExtraMappingConfig.ItemFieldMapping> itemFieldMappings = null;
String fieldName = resolveArrayFieldName(requestSchema); String fieldName = resolveArrayFieldName(requestSchema);
JSONObject extra = null; ExtraMappingConfig extra = mapping.getExtraMappingParsed();
if (StringUtils.hasText(mapping.getExtraMapping())) { if (extra != null) {
extra = JSON.parseObject(mapping.getExtraMapping()); reportIntervalSec = extra.getReportIntervalSec() != null ? extra.getReportIntervalSec() : 300;
reportIntervalSec = extra.getIntValue("reportIntervalSec", 300); itemFieldMappings = extra.getItemFieldMappings();
itemFieldMappings = extra.getJSONArray("itemFieldMappings"); if (StringUtils.hasText(extra.getFieldName())) {
if (StringUtils.hasText(extra.getString("fieldName"))) { fieldName = extra.getFieldName();
fieldName = extra.getString("fieldName");
} }
} }
JSONObject arrayFieldSchema = findArrayFieldSchema(requestSchema, fieldName); RequestSchemaField arrayFieldSchema = findArrayFieldSchema(requestSchema, fieldName);
String arrayMode = resolveArrayMode(arrayFieldSchema, extra); String arrayMode = resolveArrayMode(arrayFieldSchema, extra);
// 从 onboard_config 取 sensorId // 从 onboard_config 取 sensorId
String sensorId = reportSerialNumber; String sensorId = reportSerialNumber;
if (StringUtils.hasText(subMapping.getOnboardConfig())) { OnboardConfig onboardCfg = subMapping.getOnboardConfigParsed();
JSONObject config = JSON.parseObject(subMapping.getOnboardConfig()); if (onboardCfg != null && StringUtils.hasText(onboardCfg.getSensorId())) {
String cfgSensorId = config.getString("sensorId"); sensorId = onboardCfg.getSensorId();
if (StringUtils.hasText(cfgSensorId)) {
sensorId = cfgSensorId;
}
} }
// 构建当前设备的数据项 // 构建当前设备的数据项
JSONObject currentItem = buildArrayItem( JSONObject currentItem = buildArrayItem(userAuth, mapping, requestSchema, reportData, fieldName, itemFieldMappings, sensorId);
platform, mapping, requestSchema, reportData, fieldName, itemFieldMappings, sensorId);
if (currentItem == null || currentItem.isEmpty()) { if (currentItem == null || currentItem.isEmpty()) {
log.warn("[DCP] 网关子设备 {} 数据映射为空,跳过推送", reportSerialNumber); log.warn("[DCP] 网关子设备 {} 数据映射为空,跳过推送", reportSerialNumber);
return; return;
@ -261,33 +258,32 @@ public class DcpPlatformAdapter {
/** /**
* 请求 schema 是否包含数组字段 * 请求 schema 是否包含数组字段
*/ */
private boolean hasArrayField(JSONArray requestSchema) { private boolean hasArrayField(List<RequestSchemaField> requestSchema) {
if (requestSchema == null || requestSchema.isEmpty()) { if (requestSchema == null || requestSchema.isEmpty()) {
return false; return false;
} }
for (int i = 0; i < requestSchema.size(); i++) { for (RequestSchemaField field : requestSchema) {
JSONObject field = requestSchema.getJSONObject(i); if ("array".equalsIgnoreCase(field.getType())) {
if ("array".equalsIgnoreCase(field.getString("type"))) {
return true; return true;
} }
} }
return false; return false;
} }
private String resolveArrayFieldName(JSONArray requestSchema) { private String resolveArrayFieldName(List<RequestSchemaField> requestSchema) {
JSONObject firstArrayField = findFirstArrayFieldSchema(requestSchema); RequestSchemaField firstArrayField = findFirstArrayFieldSchema(requestSchema);
if (firstArrayField != null && StringUtils.hasText(firstArrayField.getString("name"))) { if (firstArrayField != null && StringUtils.hasText(firstArrayField.getName())) {
return firstArrayField.getString("name"); return firstArrayField.getName();
} }
return "sensorData"; return "sensorData";
} }
private String resolveArrayMode(JSONObject arrayFieldSchema, JSONObject extra) { private String resolveArrayMode(RequestSchemaField arrayFieldSchema, ExtraMappingConfig extra) {
String mode = null; String mode = null;
if (extra != null && StringUtils.hasText(extra.getString("arrayMode"))) { if (extra != null && StringUtils.hasText(extra.getArrayMode())) {
mode = extra.getString("arrayMode"); mode = extra.getArrayMode();
} else if (arrayFieldSchema != null && StringUtils.hasText(arrayFieldSchema.getString("arrayMode"))) { } else if (arrayFieldSchema != null && StringUtils.hasText(arrayFieldSchema.getArrayMode())) {
mode = arrayFieldSchema.getString("arrayMode"); mode = arrayFieldSchema.getArrayMode();
} }
if (!StringUtils.hasText(mode)) { if (!StringUtils.hasText(mode)) {
return ARRAY_MODE_REFERENCE_PAIR; return ARRAY_MODE_REFERENCE_PAIR;
@ -310,14 +306,9 @@ public class DcpPlatformAdapter {
} }
// 兼容历史配置:从 onboard_config 取基准点原始序列号 // 兼容历史配置:从 onboard_config 取基准点原始序列号
if (StringUtils.hasText(subMapping.getOnboardConfig())) { OnboardConfig config = subMapping.getOnboardConfigParsed();
try { if (config != null && StringUtils.hasText(config.getRefSerialNumber())) {
JSONObject config = JSON.parseObject(subMapping.getOnboardConfig()); return config.getRefSerialNumber();
String refSensorId = config.getString("refSerialNumber");
if (StringUtils.hasText(refSensorId)) {
return refSensorId;
}
} catch (Exception ignored) {}
} }
// 回退:用网关本身的序列号 // 回退:用网关本身的序列号
return mapping.getSerialNumber(); return mapping.getSerialNumber();
@ -385,7 +376,7 @@ public class DcpPlatformAdapter {
private void handleNormalPush(PlatformInstance platform, PlatformUserAuth userAuth, PlatformDeviceMapping mapping, private void handleNormalPush(PlatformInstance platform, PlatformUserAuth userAuth, PlatformDeviceMapping mapping,
String reportSerialNumber, String payload, String pushPath, String reportSerialNumber, String payload, String pushPath,
String interfaceCode, JSONArray requestSchema) { String interfaceCode, List<RequestSchemaField> requestSchema) {
Object body = buildPushBody(platform, userAuth, mapping, payload, requestSchema); Object body = buildPushBody(platform, userAuth, mapping, payload, requestSchema);
if (body == null) { if (body == null) {
log.warn("[DCP] 设备 {} 字段映射结果为空,跳过推送", mapping.getSerialNumber()); log.warn("[DCP] 设备 {} 字段映射结果为空,跳过推送", mapping.getSerialNumber());
@ -397,7 +388,7 @@ public class DcpPlatformAdapter {
} }
private Object buildPushBody(PlatformInstance platform, PlatformUserAuth userAuth,PlatformDeviceMapping mapping, private Object buildPushBody(PlatformInstance platform, PlatformUserAuth userAuth,PlatformDeviceMapping mapping,
String payload, JSONArray requestSchema) { String payload, List<RequestSchemaField> requestSchema) {
if (!StringUtils.hasText(payload)) { if (!StringUtils.hasText(payload)) {
return null; return null;
} }
@ -406,7 +397,7 @@ public class DcpPlatformAdapter {
return null; return null;
} }
JSONArray fieldMappings = parseJsonArray(mapping.getFieldMapping()); List<FieldMappingItem> fieldMappings = mapping.getFieldMappingParsed();
if (requestSchema == null || requestSchema.isEmpty()) { if (requestSchema == null || requestSchema.isEmpty()) {
return buildLegacyPushBody(mapping.getPlatformDeviceId(), fieldMappings, reportData); return buildLegacyPushBody(mapping.getPlatformDeviceId(), fieldMappings, reportData);
} }
@ -421,7 +412,7 @@ public class DcpPlatformAdapter {
return arr; return arr;
} }
private Object buildLegacyPushBody(String platformDeviceId, JSONArray fieldMappings, JSONObject reportData) { private Object buildLegacyPushBody(String platformDeviceId, List<FieldMappingItem> fieldMappings, JSONObject reportData) {
if (fieldMappings == null || fieldMappings.isEmpty()) { if (fieldMappings == null || fieldMappings.isEmpty()) {
return null; return null;
} }
@ -430,10 +421,9 @@ public class DcpPlatformAdapter {
dcpData.put("deviceId", platformDeviceId); dcpData.put("deviceId", platformDeviceId);
dcpData.put("epoch", nowIsoTimestamp()); dcpData.put("epoch", nowIsoTimestamp());
for (int i = 0; i < fieldMappings.size(); i++) { for (FieldMappingItem fm : fieldMappings) {
JSONObject fm = fieldMappings.getJSONObject(i); String identifier = fm.getIdentifier();
String identifier = fm.getString("identifier"); String platformField = fm.getPlatformField();
String platformField = fm.getString("platformField");
if (!StringUtils.hasText(identifier) || !StringUtils.hasText(platformField)) { if (!StringUtils.hasText(identifier) || !StringUtils.hasText(platformField)) {
continue; continue;
} }
@ -459,7 +449,7 @@ public class DcpPlatformAdapter {
*/ */
private void handleSubDeviceSimplePush(PlatformInstance platform, PlatformUserAuth userAuth, PlatformDeviceMapping mapping, private void handleSubDeviceSimplePush(PlatformInstance platform, PlatformUserAuth userAuth, PlatformDeviceMapping mapping,
PlatformDeviceSubMapping subMapping, String reportSerialNumber, String payload, PlatformDeviceSubMapping subMapping, String reportSerialNumber, String payload,
String pushPath, String interfaceCode, JSONArray requestSchema) { String pushPath, String interfaceCode, List<RequestSchemaField> requestSchema) {
Object body = buildSubDevicePushBody(platform, userAuth, mapping, subMapping, payload, requestSchema); Object body = buildSubDevicePushBody(platform, userAuth, mapping, subMapping, payload, requestSchema);
if (body == null) { if (body == null) {
log.warn("[DCP] 网关子设备 {} 字段映射结果为空,跳过推送", reportSerialNumber); log.warn("[DCP] 网关子设备 {} 字段映射结果为空,跳过推送", reportSerialNumber);
@ -477,7 +467,7 @@ public class DcpPlatformAdapter {
*/ */
private Object buildSubDevicePushBody(PlatformInstance platform, PlatformUserAuth userAuth, PlatformDeviceMapping mapping, private Object buildSubDevicePushBody(PlatformInstance platform, PlatformUserAuth userAuth, PlatformDeviceMapping mapping,
PlatformDeviceSubMapping subMapping, String payload, PlatformDeviceSubMapping subMapping, String payload,
JSONArray requestSchema) { List<RequestSchemaField> requestSchema) {
JSONObject reportData = parsePayloadToMap(payload); JSONObject reportData = parsePayloadToMap(payload);
if (reportData == null || reportData.isEmpty()) { if (reportData == null || reportData.isEmpty()) {
return null; return null;
@ -487,7 +477,7 @@ public class DcpPlatformAdapter {
String platformDeviceId = StringUtils.hasText(subMapping.getPlatformDeviceId()) String platformDeviceId = StringUtils.hasText(subMapping.getPlatformDeviceId())
? subMapping.getPlatformDeviceId() : mapping.getPlatformDeviceId(); ? subMapping.getPlatformDeviceId() : mapping.getPlatformDeviceId();
JSONArray fieldMappings = parseJsonArray(mapping.getFieldMapping()); List<FieldMappingItem> fieldMappings = mapping.getFieldMappingParsed();
if (requestSchema == null || requestSchema.isEmpty()) { if (requestSchema == null || requestSchema.isEmpty()) {
return buildLegacyPushBody(platformDeviceId, fieldMappings, reportData); return buildLegacyPushBody(platformDeviceId, fieldMappings, reportData);
} }
@ -528,18 +518,17 @@ public class DcpPlatformAdapter {
} }
private JSONObject buildObjectBySchema(PlatformInstance platform, PlatformUserAuth userAuth, PlatformDeviceMapping mapping, private JSONObject buildObjectBySchema(PlatformInstance platform, PlatformUserAuth userAuth, PlatformDeviceMapping mapping,
JSONArray requestSchema, JSONObject reportData, List<RequestSchemaField> requestSchema, JSONObject reportData,
JSONArray fieldMappings) { List<FieldMappingItem> fieldMappings) {
JSONObject body = new JSONObject(); JSONObject body = new JSONObject();
if (requestSchema == null || requestSchema.isEmpty()) { if (requestSchema == null || requestSchema.isEmpty()) {
return body; return body;
} }
for (int i = 0; i < requestSchema.size(); i++) { for (RequestSchemaField schemaField : requestSchema) {
JSONObject schemaField = requestSchema.getJSONObject(i); if ("array".equalsIgnoreCase(schemaField.getType())) {
if ("array".equalsIgnoreCase(schemaField.getString("type"))) {
continue; continue;
} }
String fieldName = schemaField.getString("name"); String fieldName = schemaField.getName();
if (!StringUtils.hasText(fieldName)) { if (!StringUtils.hasText(fieldName)) {
continue; continue;
} }
@ -552,15 +541,15 @@ public class DcpPlatformAdapter {
} }
private Object resolveSchemaFieldValue(PlatformInstance platform, PlatformUserAuth userAuth, PlatformDeviceMapping mapping, private Object resolveSchemaFieldValue(PlatformInstance platform, PlatformUserAuth userAuth, PlatformDeviceMapping mapping,
JSONObject schemaField, JSONObject reportData, RequestSchemaField schemaField, JSONObject reportData,
JSONArray fieldMappings) { List<FieldMappingItem> fieldMappings) {
String source = schemaField.getString("source"); String source = schemaField.getSource();
String fieldName = schemaField.getString("name"); String fieldName = schemaField.getName();
String configField = schemaField.getString("field"); String configField = schemaField.getField();
String contextKey = schemaField.getString("contextKey"); String contextKey = schemaField.getContextKey();
if ("fixed".equals(source)) { if ("fixed".equals(source)) {
return schemaField.get("value"); return schemaField.getValue();
} }
if ("device".equals(source)) { if ("device".equals(source)) {
return resolveDeviceField(mapping, configField); return resolveDeviceField(mapping, configField);
@ -590,11 +579,11 @@ public class DcpPlatformAdapter {
return null; return null;
} }
private JSONObject buildArrayItem(PlatformInstance platform, PlatformDeviceMapping mapping, private JSONObject buildArrayItem(PlatformUserAuth userAuth, PlatformDeviceMapping mapping,
JSONArray requestSchema, JSONObject reportData, String fieldName, List<RequestSchemaField> requestSchema, JSONObject reportData, String fieldName,
JSONArray itemFieldMappings, String subDeviceId) { List<ExtraMappingConfig.ItemFieldMapping> itemFieldMappings, String subDeviceId) {
JSONObject arrayFieldSchema = findArrayFieldSchema(requestSchema, fieldName); RequestSchemaField arrayFieldSchema = findArrayFieldSchema(requestSchema, fieldName);
JSONArray itemSchema = arrayFieldSchema == null ? null : arrayFieldSchema.getJSONArray("itemSchema"); List<RequestSchemaField> itemSchema = arrayFieldSchema == null ? null : arrayFieldSchema.getItemSchema();
if (itemSchema == null || itemSchema.isEmpty()) { if (itemSchema == null || itemSchema.isEmpty()) {
return buildLegacyArrayItem(reportData, itemFieldMappings, subDeviceId); return buildLegacyArrayItem(reportData, itemFieldMappings, subDeviceId);
} }
@ -602,20 +591,18 @@ public class DcpPlatformAdapter {
JSONObject item = new JSONObject(); JSONObject item = new JSONObject();
boolean hasThingsModelField = false; boolean hasThingsModelField = false;
boolean hasThingsModelValue = false; boolean hasThingsModelValue = false;
for (int i = 0; i < itemSchema.size(); i++) { for (RequestSchemaField schemaField : itemSchema) {
JSONObject schemaField = itemSchema.getJSONObject(i); String itemFieldName = schemaField.getName();
String itemFieldName = schemaField.getString("name");
if (!StringUtils.hasText(itemFieldName)) { if (!StringUtils.hasText(itemFieldName)) {
continue; continue;
} }
if (isThingsModelSource(schemaField.getString("source"))) { if (isThingsModelSource(schemaField.getSource())) {
hasThingsModelField = true; hasThingsModelField = true;
} }
Object value = resolveArrayItemFieldValue( Object value = resolveArrayItemFieldValue(userAuth, mapping, schemaField, reportData, itemFieldMappings, subDeviceId);
platform, mapping, schemaField, reportData, itemFieldMappings, subDeviceId);
if (value != null) { if (value != null) {
item.put(itemFieldName, value); item.put(itemFieldName, value);
if (isThingsModelSource(schemaField.getString("source"))) { if (isThingsModelSource(schemaField.getSource())) {
hasThingsModelValue = true; hasThingsModelValue = true;
} }
} }
@ -626,51 +613,51 @@ public class DcpPlatformAdapter {
return item; return item;
} }
private JSONObject buildLegacyArrayItem(JSONObject reportData, JSONArray itemFieldMappings, String subDeviceId) { private JSONObject buildLegacyArrayItem(JSONObject reportData, List<ExtraMappingConfig.ItemFieldMapping> itemFieldMappings, String subDeviceId) {
Map<String, Object> itemValues = extractItemValues(reportData, itemFieldMappings); Map<String, Object> itemValues = extractItemValues(reportData, itemFieldMappings);
Object height = itemValues.get("height"); Object height = itemValues.get("height");
Object temperature = itemValues.get("temperature"); Object temperature = itemValues.get("temperature");
if (height == null && temperature == null) { if (height == null && temperature == null) {
return null; return null;
} }
String heightIdentifier = findIdentifier(itemFieldMappings, "height"); String heightIdentifier = findIdentifierFromItem(itemFieldMappings, "height");
String epochIdentifier = StringUtils.hasText(heightIdentifier) String epochIdentifier = StringUtils.hasText(heightIdentifier)
? heightIdentifier : findFirstIdentifier(itemFieldMappings); ? heightIdentifier : findFirstIdentifierFromItem(itemFieldMappings);
Object epoch = resolveEpoch(reportData, epochIdentifier); Object epoch = resolveEpoch(reportData, epochIdentifier);
return buildSensorDataItem(epoch, height, temperature, subDeviceId); return buildSensorDataItem(epoch, height, temperature, subDeviceId);
} }
private Object resolveArrayItemFieldValue(PlatformInstance platform, PlatformDeviceMapping mapping, private Object resolveArrayItemFieldValue(PlatformUserAuth userAuth, PlatformDeviceMapping mapping,
JSONObject schemaField, JSONObject reportData, RequestSchemaField schemaField, JSONObject reportData,
JSONArray itemFieldMappings, String subDeviceId) { List<ExtraMappingConfig.ItemFieldMapping> itemFieldMappings, String subDeviceId) {
String source = schemaField.getString("source"); String source = schemaField.getSource();
String fieldName = schemaField.getString("name"); String fieldName = schemaField.getName();
if ("fixed".equals(source)) { if ("fixed".equals(source)) {
return schemaField.get("value"); return schemaField.getValue();
} }
if ("subDeviceId".equals(source)) { if ("subDeviceId".equals(source)) {
return subDeviceId; return subDeviceId;
} }
if ("ts".equals(source)) { if ("ts".equals(source)) {
return resolveTimestampValue(reportData, itemFieldMappings, fieldName, schemaField.getString("contextKey")); return resolveTimestampValueFromItem(reportData, itemFieldMappings, fieldName, schemaField.getContextKey());
} }
if ("device".equals(source)) { if ("device".equals(source)) {
return resolveDeviceField(mapping, schemaField.getString("field")); return resolveDeviceField(mapping, schemaField.getField());
} }
if ("auth".equals(source)) { if ("auth".equals(source)) {
return resolveJsonField(platform.getAuthConfig(), schemaField.getString("field")); return resolveJsonField(userAuth.getAuthConfig(), schemaField.getField());
} }
if ("extra".equals(source)) { if ("extra".equals(source)) {
return resolveJsonField(platform.getExtraConfig(), schemaField.getString("field")); return resolveJsonField(userAuth.getExtraConfig(), schemaField.getField());
} }
if (isThingsModelSource(source)) { if (isThingsModelSource(source)) {
String identifier = findIdentifier(itemFieldMappings, fieldName); String identifier = findIdentifierFromItem(itemFieldMappings, fieldName);
return StringUtils.hasText(identifier) ? reportData.get(identifier) : null; return StringUtils.hasText(identifier) ? reportData.get(identifier) : null;
} }
if ("context".equals(source)) { if ("context".equals(source)) {
String contextKey = schemaField.getString("contextKey"); String contextKey = schemaField.getContextKey();
if ("reportTime".equals(contextKey) || "epoch".equals(fieldName)) { if ("reportTime".equals(contextKey) || "epoch".equals(fieldName)) {
return resolveTimestampValue(reportData, itemFieldMappings, fieldName, contextKey); return resolveTimestampValueFromItem(reportData, itemFieldMappings, fieldName, contextKey);
} }
Object contextValue = reportData.get(contextKey); Object contextValue = reportData.get(contextKey);
if (contextValue != null) { if (contextValue != null) {
@ -680,33 +667,51 @@ public class DcpPlatformAdapter {
return null; return null;
} }
private JSONObject findArrayFieldSchema(JSONArray requestSchema, String fieldName) { private RequestSchemaField findArrayFieldSchema(List<RequestSchemaField> requestSchema, String fieldName) {
if (requestSchema == null || requestSchema.isEmpty() || !StringUtils.hasText(fieldName)) { if (requestSchema == null || requestSchema.isEmpty() || !StringUtils.hasText(fieldName)) {
return null; return null;
} }
for (int i = 0; i < requestSchema.size(); i++) { for (RequestSchemaField schemaField : requestSchema) {
JSONObject schemaField = requestSchema.getJSONObject(i); if ("array".equalsIgnoreCase(schemaField.getType())
if ("array".equalsIgnoreCase(schemaField.getString("type")) && fieldName.equals(schemaField.getName())) {
&& fieldName.equals(schemaField.getString("name"))) {
return schemaField; return schemaField;
} }
} }
return null; return null;
} }
private JSONObject findFirstArrayFieldSchema(JSONArray requestSchema) { private RequestSchemaField findFirstArrayFieldSchema(List<RequestSchemaField> requestSchema) {
if (requestSchema == null || requestSchema.isEmpty()) { if (requestSchema == null || requestSchema.isEmpty()) {
return null; return null;
} }
for (int i = 0; i < requestSchema.size(); i++) { for (RequestSchemaField schemaField : requestSchema) {
JSONObject schemaField = requestSchema.getJSONObject(i); if ("array".equalsIgnoreCase(schemaField.getType())) {
if ("array".equalsIgnoreCase(schemaField.getString("type"))) {
return schemaField; return schemaField;
} }
} }
return null; return null;
} }
private boolean isThingsModelSource(String source) {
return "thingsModel".equals(source) || "identifier".equals(source);
}
private boolean hasPayloadDrivenValue(JSONObject body, List<RequestSchemaField> requestSchema) {
if (body == null || body.isEmpty() || requestSchema == null || requestSchema.isEmpty()) {
return true;
}
for (RequestSchemaField schemaField : requestSchema) {
if ("array".equalsIgnoreCase(schemaField.getType())) {
continue;
}
if (isThingsModelSource(schemaField.getSource()) || "context".equals(schemaField.getSource()) || "ts".equals(schemaField.getSource())
&& body.containsKey(schemaField.getName())) {
return false;
}
}
return true;
}
private JSONObject copyJsonObject(JSONObject source) { private JSONObject copyJsonObject(JSONObject source) {
if (source == null || source.isEmpty()) { if (source == null || source.isEmpty()) {
return new JSONObject(); return new JSONObject();
@ -714,51 +719,6 @@ public class DcpPlatformAdapter {
return JSON.parseObject(source.toJSONString()); return JSON.parseObject(source.toJSONString());
} }
private boolean hasPayloadDrivenValue(JSONObject body, JSONArray requestSchema) {
if (body == null || body.isEmpty() || requestSchema == null || requestSchema.isEmpty()) {
return true;
}
for (int i = 0; i < requestSchema.size(); i++) {
JSONObject schemaField = requestSchema.getJSONObject(i);
if ("array".equalsIgnoreCase(schemaField.getString("type"))) {
continue;
}
if (usesPayload(schemaField.getString("source"))
&& body.containsKey(schemaField.getString("name"))) {
return false;
}
}
return true;
}
private boolean usesPayload(String source) {
return isThingsModelSource(source) || "context".equals(source) || "ts".equals(source);
}
private boolean isThingsModelSource(String source) {
return "thingsModel".equals(source) || "identifier".equals(source);
}
private PlatformInterface loadPushInterface(PlatformDeviceMapping mapping) {
if (mapping.getPushInterfaceId() == null) {
return null;
}
return interfaceMapper.selectById(mapping.getPushInterfaceId());
}
private JSONArray loadRequestSchema(PlatformInterface pushInterface) {
if (pushInterface == null || !StringUtils.hasText(pushInterface.getRequestSchema())) {
return null;
}
try {
return JSON.parseArray(pushInterface.getRequestSchema());
} catch (Exception e) {
log.warn("[DCP] 解析 requestSchema 失败 interfaceId={} err={}",
pushInterface.getId(), e.getMessage());
return null;
}
}
private String resolvePushPath(PlatformDeviceMapping mapping, PlatformInterface pushInterface) { private String resolvePushPath(PlatformDeviceMapping mapping, PlatformInterface pushInterface) {
if (pushInterface != null && StringUtils.hasText(pushInterface.getPath())) { if (pushInterface != null && StringUtils.hasText(pushInterface.getPath())) {
return pushInterface.getPath(); return pushInterface.getPath();
@ -775,19 +735,7 @@ public class DcpPlatformAdapter {
return "dcp_push_" + sensorType; return "dcp_push_" + sensorType;
} }
private JSONArray parseJsonArray(String json) { private Object resolveThingsModelValue(JSONObject reportData, List<FieldMappingItem> fieldMappings,
if (!StringUtils.hasText(json)) {
return null;
}
try {
return JSON.parseArray(json);
} catch (Exception e) {
log.warn("[DCP] 解析 JSON 数组失败: {}", e.getMessage());
return null;
}
}
private Object resolveThingsModelValue(JSONObject reportData, JSONArray fieldMappings,
String platformField, String fallbackKey) { String platformField, String fallbackKey) {
String identifier = findIdentifier(fieldMappings, platformField); String identifier = findIdentifier(fieldMappings, platformField);
if (StringUtils.hasText(identifier)) { if (StringUtils.hasText(identifier)) {
@ -799,7 +747,7 @@ public class DcpPlatformAdapter {
return null; return null;
} }
private Object resolveTimestampValue(JSONObject reportData, JSONArray fieldMappings, private Object resolveTimestampValue(JSONObject reportData, List<FieldMappingItem> fieldMappings,
String platformField, String fallbackKey) { String platformField, String fallbackKey) {
String identifier = findIdentifier(fieldMappings, platformField); String identifier = findIdentifier(fieldMappings, platformField);
if (StringUtils.hasText(identifier)) { if (StringUtils.hasText(identifier)) {
@ -815,6 +763,22 @@ public class DcpPlatformAdapter {
return nowIsoTimestamp(); return nowIsoTimestamp();
} }
private Object resolveTimestampValueFromItem(JSONObject reportData, List<ExtraMappingConfig.ItemFieldMapping> itemFieldMappings,
String platformField, String fallbackKey) {
String identifier = findIdentifierFromItem(itemFieldMappings, platformField);
if (StringUtils.hasText(identifier)) {
return resolveEpoch(reportData, identifier);
}
if (StringUtils.hasText(fallbackKey) && reportData.containsKey(fallbackKey + "__ts")) {
return resolveEpoch(reportData, fallbackKey);
}
String firstIdentifier = findFirstIdentifierFromItem(itemFieldMappings);
if (StringUtils.hasText(firstIdentifier)) {
return resolveEpoch(reportData, firstIdentifier);
}
return nowIsoTimestamp();
}
private Object resolveDeviceField(PlatformDeviceMapping mapping, String field) { private Object resolveDeviceField(PlatformDeviceMapping mapping, String field) {
if (mapping == null || !StringUtils.hasText(field)) { if (mapping == null || !StringUtils.hasText(field)) {
return null; return null;
@ -863,41 +827,61 @@ public class DcpPlatformAdapter {
return item; return item;
} }
private Map<String, Object> extractItemValues(JSONObject reportData, JSONArray itemFieldMappings) { private Map<String, Object> extractItemValues(JSONObject reportData,
List<ExtraMappingConfig.ItemFieldMapping> itemFieldMappings) {
Map<String, Object> values = new HashMap<>(); Map<String, Object> values = new HashMap<>();
if (itemFieldMappings == null) return values; if (itemFieldMappings == null) return values;
for (int i = 0; i < itemFieldMappings.size(); i++) { for (ExtraMappingConfig.ItemFieldMapping im : itemFieldMappings) {
JSONObject im = itemFieldMappings.getJSONObject(i); if (StringUtils.hasText(im.getIdentifier()) && StringUtils.hasText(im.getPlatformField())) {
String platformField = im.getString("platformField"); values.put(im.getPlatformField(), reportData.get(im.getIdentifier()));
String identifier = im.getString("identifier");
if (StringUtils.hasText(identifier) && StringUtils.hasText(platformField)) {
values.put(platformField, reportData.get(identifier));
} }
} }
return values; return values;
} }
private String findIdentifier(JSONArray itemFieldMappings, String platformField) { private String findIdentifier(List<FieldMappingItem> itemFieldMappings, String platformField) {
if (itemFieldMappings == null || !StringUtils.hasText(platformField)) { if (itemFieldMappings == null || !StringUtils.hasText(platformField)) {
return null; return null;
} }
for (int i = 0; i < itemFieldMappings.size(); i++) { for (FieldMappingItem im : itemFieldMappings) {
JSONObject im = itemFieldMappings.getJSONObject(i); if (platformField.equals(im.getPlatformField())) {
if (platformField.equals(im.getString("platformField"))) { return im.getIdentifier();
return im.getString("identifier");
} }
} }
return null; return null;
} }
private String findFirstIdentifier(JSONArray itemFieldMappings) { private String findIdentifierFromItem(List<ExtraMappingConfig.ItemFieldMapping> itemFieldMappings, String platformField) {
if (itemFieldMappings == null || !StringUtils.hasText(platformField)) {
return null;
}
for (ExtraMappingConfig.ItemFieldMapping im : itemFieldMappings) {
if (platformField.equals(im.getPlatformField())) {
return im.getIdentifier();
}
}
return null;
}
private String findFirstIdentifier(List<FieldMappingItem> itemFieldMappings) {
if (itemFieldMappings == null || itemFieldMappings.isEmpty()) { if (itemFieldMappings == null || itemFieldMappings.isEmpty()) {
return null; return null;
} }
for (int i = 0; i < itemFieldMappings.size(); i++) { for (FieldMappingItem im : itemFieldMappings) {
String identifier = itemFieldMappings.getJSONObject(i).getString("identifier"); if (StringUtils.hasText(im.getIdentifier())) {
if (StringUtils.hasText(identifier)) { return im.getIdentifier();
return identifier; }
}
return null;
}
private String findFirstIdentifierFromItem(List<ExtraMappingConfig.ItemFieldMapping> itemFieldMappings) {
if (itemFieldMappings == null || itemFieldMappings.isEmpty()) {
return null;
}
for (ExtraMappingConfig.ItemFieldMapping im : itemFieldMappings) {
if (StringUtils.hasText(im.getIdentifier())) {
return im.getIdentifier();
} }
} }
return null; return null;
@ -1071,7 +1055,7 @@ public class DcpPlatformAdapter {
private void pushBatchSubDevicesData(PlatformInstance platform, PlatformUserAuth userAuth, PlatformDeviceMapping mapping, private void pushBatchSubDevicesData(PlatformInstance platform, PlatformUserAuth userAuth, PlatformDeviceMapping mapping,
PlatformDeviceSubMapping currentSubMapping, String reportSerialNumber, PlatformDeviceSubMapping currentSubMapping, String reportSerialNumber,
String payload, String pushPath, String interfaceCode, String payload, String pushPath, String interfaceCode,
JSONArray requestSchema) { List<RequestSchemaField> requestSchema) {
try { try {
// 1. 查询所有子设备。批量推送只依赖子映射顺序和推送接口 schema, // 1. 查询所有子设备。批量推送只依赖子映射顺序和推送接口 schema,
// 不再从推送接口 request_schema 读取入网用的 sensorDepths。 // 不再从推送接口 request_schema 读取入网用的 sensorDepths。
@ -1104,16 +1088,16 @@ public class DcpPlatformAdapter {
} }
// 3. 准备公共参数(复用已有 fieldMapping + requestSchema) // 3. 准备公共参数(复用已有 fieldMapping + requestSchema)
JSONArray fieldMappings = parseJsonArray(mapping.getFieldMapping()); List<FieldMappingItem> fieldMappings = mapping.getFieldMappingParsed();
String fieldName = resolveArrayFieldName(requestSchema); String fieldName = resolveArrayFieldName(requestSchema);
JSONObject extra = null; List<ExtraMappingConfig.ItemFieldMapping> itemFieldMappings = null;
if (StringUtils.hasText(mapping.getExtraMapping())) { ExtraMappingConfig extra = mapping.getExtraMappingParsed();
extra = JSON.parseObject(mapping.getExtraMapping()); if (extra != null) {
if (StringUtils.hasText(extra.getString("fieldName"))) { if (StringUtils.hasText(extra.getFieldName())) {
fieldName = extra.getString("fieldName"); fieldName = extra.getFieldName();
} }
itemFieldMappings = extra.getItemFieldMappings();
} }
JSONArray itemFieldMappings = extra != null ? extra.getJSONArray("itemFieldMappings") : null;
// 解析当前上报子设备的 payload // 解析当前上报子设备的 payload
JSONObject currentReportData = parsePayloadToMap(payload); JSONObject currentReportData = parsePayloadToMap(payload);
@ -1137,7 +1121,7 @@ public class DcpPlatformAdapter {
} }
String sensorId = resolveSubDeviceSensorId(sub); String sensorId = resolveSubDeviceSensorId(sub);
JSONObject sensorDataItem = buildArrayItem(platform, mapping, requestSchema, reportData, fieldName, itemFieldMappings, sensorId); JSONObject sensorDataItem = buildArrayItem(userAuth, mapping, requestSchema, reportData, fieldName, itemFieldMappings, sensorId);
if (sensorDataItem == null || sensorDataItem.isEmpty()) { if (sensorDataItem == null || sensorDataItem.isEmpty()) {
log.warn("[DCP] 子设备 {} 数据映射为空,跳过其 sensorData", subSn); log.warn("[DCP] 子设备 {} 数据映射为空,跳过其 sensorData", subSn);
continue; continue;
@ -1253,16 +1237,9 @@ public class DcpPlatformAdapter {
* 优先从 onboard_config 中取 sensorId,否则使用子设备序列号。 * 优先从 onboard_config 中取 sensorId,否则使用子设备序列号。
*/ */
private String resolveSubDeviceSensorId(PlatformDeviceSubMapping subMapping) { private String resolveSubDeviceSensorId(PlatformDeviceSubMapping subMapping) {
if (StringUtils.hasText(subMapping.getOnboardConfig())) { OnboardConfig config = subMapping.getOnboardConfigParsed();
try { if (config != null && StringUtils.hasText(config.getSensorId())) {
JSONObject config = JSON.parseObject(subMapping.getOnboardConfig()); return config.getSensorId();
String sensorId = config.getString("sensorId");
if (StringUtils.hasText(sensorId)) {
return sensorId;
}
} catch (Exception e) {
log.debug("[DCP] 解析 onboardConfig 失败 subSn={}", subMapping.getSubSerialNumber());
}
} }
return subMapping.getSubSerialNumber(); return subMapping.getSubSerialNumber();
} }

View File

@ -1,16 +1,22 @@
package com.fastbee.iot.platform.domain; package com.fastbee.iot.platform.domain;
import com.alibaba.fastjson2.JSON;
import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName; import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fastbee.iot.platform.model.json.ExtraMappingConfig;
import com.fastbee.iot.platform.model.json.FieldMappingItem;
import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty; import io.swagger.annotations.ApiModelProperty;
import lombok.Data; import lombok.Data;
import org.springframework.util.StringUtils;
import java.io.Serializable; import java.io.Serializable;
import java.util.Date; import java.util.Date;
import java.util.List;
/** /**
* 平台设备绑定关系 * 平台设备绑定关系
@ -71,6 +77,40 @@ public class PlatformDeviceMapping implements Serializable {
@ApiModelProperty("扩展映射配置,如静力水准子设备编号与DCP sensorId映射") @ApiModelProperty("扩展映射配置,如静力水准子设备编号与DCP sensorId映射")
private String extraMapping; private String extraMapping;
/** fieldMapping 类型安全解析 */
@TableField(exist = false)
@JsonIgnore
private transient List<FieldMappingItem> fieldMappingParsed;
/** extraMapping 类型安全解析 */
@TableField(exist = false)
@JsonIgnore
private transient ExtraMappingConfig extraMappingParsed;
public List<FieldMappingItem> getFieldMappingParsed() {
if (fieldMappingParsed == null && StringUtils.hasText(this.fieldMapping)) {
this.fieldMappingParsed = JSON.parseArray(this.fieldMapping, FieldMappingItem.class);
}
return fieldMappingParsed;
}
public void setFieldMappingParsed(List<FieldMappingItem> parsed) {
this.fieldMappingParsed = parsed;
this.fieldMapping = parsed == null ? null : JSON.toJSONString(parsed);
}
public ExtraMappingConfig getExtraMappingParsed() {
if (extraMappingParsed == null && StringUtils.hasText(this.extraMapping)) {
this.extraMappingParsed = JSON.parseObject(this.extraMapping, ExtraMappingConfig.class);
}
return extraMappingParsed;
}
public void setExtraMappingParsed(ExtraMappingConfig parsed) {
this.extraMappingParsed = parsed;
this.extraMapping = parsed == null ? null : JSON.toJSONString(parsed);
}
@ApiModelProperty("入网状态(0=未入网,1=已入网)") @ApiModelProperty("入网状态(0=未入网,1=已入网)")
private Integer netStatus; private Integer netStatus;

View File

@ -1,12 +1,17 @@
package com.fastbee.iot.platform.domain; package com.fastbee.iot.platform.domain;
import com.alibaba.fastjson2.JSON;
import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName; import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fastbee.iot.platform.model.json.OnboardConfig;
import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty; import io.swagger.annotations.ApiModelProperty;
import lombok.Data; import lombok.Data;
import org.springframework.util.StringUtils;
import java.io.Serializable; import java.io.Serializable;
import java.util.Date; import java.util.Date;
@ -45,6 +50,23 @@ public class PlatformDeviceSubMapping implements Serializable {
@ApiModelProperty("入网时传给平台的标识参数(平台相关)") @ApiModelProperty("入网时传给平台的标识参数(平台相关)")
private String onboardConfig; private String onboardConfig;
/** onboardConfig 类型安全解析 */
@TableField(exist = false)
@JsonIgnore
private transient OnboardConfig onboardConfigParsed;
public OnboardConfig getOnboardConfigParsed() {
if (onboardConfigParsed == null && StringUtils.hasText(this.onboardConfig)) {
this.onboardConfigParsed = JSON.parseObject(this.onboardConfig, OnboardConfig.class);
}
return onboardConfigParsed;
}
public void setOnboardConfigParsed(OnboardConfig parsed) {
this.onboardConfigParsed = parsed;
this.onboardConfig = parsed == null ? null : JSON.toJSONString(parsed);
}
@ApiModelProperty("入网状态(0=未入网,1=已入网)") @ApiModelProperty("入网状态(0=未入网,1=已入网)")
private Integer netStatus; private Integer netStatus;

View File

@ -1,13 +1,22 @@
package com.fastbee.iot.platform.domain; package com.fastbee.iot.platform.domain;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.TypeReference;
import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName; import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fastbee.iot.platform.model.json.ParamOverrideConfig;
import com.fastbee.iot.platform.model.json.ResultSaveField;
import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty; import io.swagger.annotations.ApiModelProperty;
import lombok.Data; import lombok.Data;
import org.springframework.util.StringUtils;
import java.io.Serializable; import java.io.Serializable;
import java.util.List;
import java.util.Map;
/** /**
* 流程步骤 * 流程步骤
@ -40,6 +49,41 @@ public class PlatformFlowStep implements Serializable {
@ApiModelProperty("需持久化的出参字段 [{field,saveTo}]") @ApiModelProperty("需持久化的出参字段 [{field,saveTo}]")
private String resultSave; private String resultSave;
/** paramMapping 类型安全解析 */
@TableField(exist = false)
@JsonIgnore
private transient Map<String, ParamOverrideConfig> paramMappingParsed;
/** resultSave 类型安全解析 */
@TableField(exist = false)
@JsonIgnore
private transient List<ResultSaveField> resultSaveParsed;
public Map<String, ParamOverrideConfig> getParamMappingParsed() {
if (paramMappingParsed == null && StringUtils.hasText(this.paramMapping)) {
this.paramMappingParsed = JSON.parseObject(this.paramMapping,
new TypeReference<Map<String,ParamOverrideConfig>>(){}.getType());
}
return paramMappingParsed;
}
public void setParamMappingParsed(Map<String, ParamOverrideConfig> parsed) {
this.paramMappingParsed = parsed;
this.paramMapping = parsed == null ? null : JSON.toJSONString(parsed);
}
public List<ResultSaveField> getResultSaveParsed() {
if (resultSaveParsed == null && StringUtils.hasText(this.resultSave)) {
this.resultSaveParsed = JSON.parseArray(this.resultSave, ResultSaveField.class);
}
return resultSaveParsed;
}
public void setResultSaveParsed(List<ResultSaveField> parsed) {
this.resultSaveParsed = parsed;
this.resultSave = parsed == null ? null : JSON.toJSONString(parsed);
}
@ApiModelProperty("备注") @ApiModelProperty("备注")
private String remark; private String remark;
} }

View File

@ -1,12 +1,17 @@
package com.fastbee.iot.platform.domain; package com.fastbee.iot.platform.domain;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName; import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonIgnore;
import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty; import io.swagger.annotations.ApiModelProperty;
import lombok.Data; import lombok.Data;
import org.springframework.util.StringUtils;
import java.io.Serializable; import java.io.Serializable;
import java.util.Date; import java.util.Date;
@ -36,6 +41,7 @@ public class PlatformInstance implements Serializable {
@ApiModelProperty("认证类型(1=密码Token,2=ApiKey,3=OAuth2)") @ApiModelProperty("认证类型(1=密码Token,2=ApiKey,3=OAuth2)")
private Integer authType; private Integer authType;
// ========================= 已弃用 =========================
@ApiModelProperty("认证配置模板(新增用户时复制){userName,password,clientId,scope}") @ApiModelProperty("认证配置模板(新增用户时复制){userName,password,clientId,scope}")
private String authConfig; private String authConfig;
@ -51,6 +57,7 @@ public class PlatformInstance implements Serializable {
@ApiModelProperty("初始化状态(0=未初始化,1=已初始化)") @ApiModelProperty("初始化状态(0=未初始化,1=已初始化)")
private Integer initStatus; private Integer initStatus;
// ========================= 已弃用 =========================
@ApiModelProperty("是否启用(0=禁用,1=启用)") @ApiModelProperty("是否启用(0=禁用,1=启用)")
private String enable; private String enable;

View File

@ -1,15 +1,22 @@
package com.fastbee.iot.platform.domain; package com.fastbee.iot.platform.domain;
import com.alibaba.fastjson2.JSON;
import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName; import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fastbee.iot.platform.model.json.ResponseSchemaField;
import com.fastbee.iot.platform.model.json.RequestSchemaField;
import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty; import io.swagger.annotations.ApiModelProperty;
import lombok.Data; import lombok.Data;
import org.springframework.util.StringUtils;
import java.io.Serializable; import java.io.Serializable;
import java.util.Date; import java.util.Date;
import java.util.List;
/** /**
* 平台接口定义 * 平台接口定义
@ -48,6 +55,40 @@ public class PlatformInterface implements Serializable {
@ApiModelProperty("出参字段定义 JSON") @ApiModelProperty("出参字段定义 JSON")
private String responseSchema; private String responseSchema;
/** requestSchema 类型安全解析 */
@TableField(exist = false)
@JsonIgnore
private transient List<RequestSchemaField> requestSchemaParsed;
/** responseSchema 类型安全解析 */
@TableField(exist = false)
@JsonIgnore
private transient List<ResponseSchemaField> responseSchemaParsed;
public List<RequestSchemaField> getRequestSchemaParsed() {
if (requestSchemaParsed == null && StringUtils.hasText(this.requestSchema)) {
this.requestSchemaParsed = JSON.parseArray(this.requestSchema, RequestSchemaField.class);
}
return requestSchemaParsed;
}
public void setRequestSchemaParsed(List<RequestSchemaField> parsed) {
this.requestSchemaParsed = parsed;
this.requestSchema = parsed == null ? null : JSON.toJSONString(parsed);
}
public List<ResponseSchemaField> getResponseSchemaParsed() {
if (responseSchemaParsed == null && StringUtils.hasText(this.responseSchema)) {
this.responseSchemaParsed = JSON.parseArray(this.responseSchema, ResponseSchemaField.class);
}
return responseSchemaParsed;
}
public void setResponseSchemaParsed(List<ResponseSchemaField> parsed) {
this.responseSchemaParsed = parsed;
this.responseSchema = parsed == null ? null : JSON.toJSONString(parsed);
}
@ApiModelProperty("排序") @ApiModelProperty("排序")
private Integer sortOrder; private Integer sortOrder;

View File

@ -1,12 +1,17 @@
package com.fastbee.iot.platform.domain; package com.fastbee.iot.platform.domain;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName; import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonIgnore;
import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty; import io.swagger.annotations.ApiModelProperty;
import lombok.Data; import lombok.Data;
import org.springframework.util.StringUtils;
import java.io.Serializable; import java.io.Serializable;
import java.util.Date; import java.util.Date;
@ -50,6 +55,40 @@ public class PlatformUserAuth implements Serializable {
@ApiModelProperty("用户级配置 {projectId,projectName,...}") @ApiModelProperty("用户级配置 {projectId,projectName,...}")
private String extraConfig; private String extraConfig;
/** authConfig 惰性解析 */
@TableField(exist = false)
@JsonIgnore
private transient JSONObject authConfigParsed;
/** extraConfig 惰性解析 */
@TableField(exist = false)
@JsonIgnore
private transient JSONObject extraConfigParsed;
public JSONObject getAuthConfigParsed() {
if (authConfigParsed == null && StringUtils.hasText(this.authConfig)) {
this.authConfigParsed = JSON.parseObject(this.authConfig);
}
return authConfigParsed;
}
public void setAuthConfigParsed(JSONObject parsed) {
this.authConfigParsed = parsed;
this.authConfig = parsed == null ? null : parsed.toJSONString();
}
public JSONObject getExtraConfigParsed() {
if (extraConfigParsed == null && StringUtils.hasText(this.extraConfig)) {
this.extraConfigParsed = JSON.parseObject(this.extraConfig);
}
return extraConfigParsed;
}
public void setExtraConfigParsed(JSONObject parsed) {
this.extraConfigParsed = parsed;
this.extraConfig = parsed == null ? null : parsed.toJSONString();
}
@ApiModelProperty("是否启用(0=禁用,1=启用)") @ApiModelProperty("是否启用(0=禁用,1=启用)")
private String enable; private String enable;

View File

@ -14,6 +14,7 @@ import com.fastbee.iot.mapper.DeviceMapper;
import com.fastbee.iot.mapper.SubGatewayMapper; import com.fastbee.iot.mapper.SubGatewayMapper;
import com.fastbee.iot.platform.domain.*; import com.fastbee.iot.platform.domain.*;
import com.fastbee.iot.platform.mapper.*; import com.fastbee.iot.platform.mapper.*;
import com.fastbee.iot.platform.model.json.*;
import com.fastbee.iot.platform.service.IPlatformPushLogService; import com.fastbee.iot.platform.service.IPlatformPushLogService;
import com.dtflys.forest.http.ForestRequestType; import com.dtflys.forest.http.ForestRequestType;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@ -227,78 +228,91 @@ public class PlatformFlowExecutor {
* 解析步骤入参 * 解析步骤入参
* source 枚举: fixed / auth / extra / context / device / thingsModel / ts * source 枚举: fixed / auth / extra / context / device / thingsModel / ts
*/ */
@SuppressWarnings("unchecked")
private Map<String, Object> resolveParams(PlatformInterface iface, PlatformFlowStep step, FlowContext ctx) { private Map<String, Object> resolveParams(PlatformInterface iface, PlatformFlowStep step, FlowContext ctx) {
Map<String, Object> result = new LinkedHashMap<>(); Map<String, Object> result = new LinkedHashMap<>();
if (!StringUtils.hasText(iface.getRequestSchema())) { List<RequestSchemaField> schema = iface.getRequestSchemaParsed();
if (schema == null || schema.isEmpty()) {
return result; return result;
} }
// 合并接口默认 schema 和步骤覆盖 mapping // 合并接口默认 schema 和步骤覆盖 mapping
JSONArray schema = JSON.parseArray(iface.getRequestSchema()); Map<String, ParamOverrideConfig> overrides = step.getParamMappingParsed();
Map<String, Object> overrides = StringUtils.hasText(step.getParamMapping()) if (overrides == null) {
? JSON.parseObject(step.getParamMapping(), Map.class) overrides = Collections.emptyMap();
: Collections.emptyMap(); }
// 优先从 userAuth 获取 authConfig,fallback 到 platform.authTemplate // 获取 userAuth authConfig
JSONObject authConfig = new JSONObject(); JSONObject authConfig = new JSONObject();
if (ctx.getPlatformUserAuth() != null && StringUtils.hasText(ctx.getPlatformUserAuth().getAuthConfig())) { if (ctx.getPlatformUserAuth() != null) {
authConfig = JSON.parseObject(ctx.getPlatformUserAuth().getAuthConfig()); JSONObject parsed = ctx.getPlatformUserAuth().getAuthConfigParsed();
} else if (StringUtils.hasText(ctx.getPlatform().getAuthConfig())) { if (parsed != null) {
authConfig = JSON.parseObject(ctx.getPlatform().getAuthConfig()); authConfig = parsed;
}
} }
// 优先从 userAuth 获取 extraConfig,fallback 到 platform.extraConfig // 获取 userAuth extraConfig
JSONObject extraConfig = new JSONObject(); JSONObject extraConfig = new JSONObject();
if (ctx.getPlatformUserAuth() != null && StringUtils.hasText(ctx.getPlatformUserAuth().getExtraConfig())) { if (ctx.getPlatformUserAuth() != null) {
extraConfig = JSON.parseObject(ctx.getPlatformUserAuth().getExtraConfig()); JSONObject parsed = ctx.getPlatformUserAuth().getExtraConfigParsed();
} else if (StringUtils.hasText(ctx.getPlatform().getExtraConfig())) { if (parsed != null) {
extraConfig = JSON.parseObject(ctx.getPlatform().getExtraConfig()); extraConfig = parsed;
}
} }
for (int i = 0; i < schema.size(); i++) { for (RequestSchemaField field : schema) {
JSONObject field = schema.getJSONObject(i); String name = field.getName();
String name = field.getString("name"); if (name == null) continue;
// 步骤覆盖优先 // 步骤覆盖优先
if (overrides.containsKey(name)) { if (overrides.containsKey(name)) {
Object override = overrides.get(name); ParamOverrideConfig override = overrides.get(name);
if (override instanceof Map) { if (override != null) {
Map<String, Object> ov = (Map<String, Object>) override; String src = override.getSource() != null ? override.getSource() : field.getSource();
String src = (String) ov.get("source"); String type = override.getType() != null ? override.getType() : field.getType();
String type = ov.get("type") != null ? String.valueOf(ov.get("type")) : field.getString("type"); result.put(name, normalizeValueByType(type,
result.put(name, normalizeValueByType(type, resolveValue(src, ov, authConfig, extraConfig, ctx))); resolveValue(src, mergeOverride(field, override), authConfig, extraConfig, ctx)));
} else { } else {
result.put(name, normalizeValueByType(field.getString("type"), override)); result.put(name, normalizeValueByType(field.getType(), null));
} }
continue; continue;
} }
String source = field.getString("source"); result.put(name, normalizeValueByType(field.getType(),
result.put(name, normalizeValueByType(field.getString("type"), resolveValue(source, field, authConfig, extraConfig, ctx))); resolveValue(field.getSource(), field, authConfig, extraConfig, ctx)));
} }
return result; return result;
} }
private Object resolveValue(String source, Object fieldDef, JSONObject authConfig, private RequestSchemaField mergeOverride(RequestSchemaField schemaField, ParamOverrideConfig override) {
if (override == null) return schemaField;
RequestSchemaField merged = new RequestSchemaField();
merged.setName(schemaField.getName());
merged.setSource(override.getSource() != null ? override.getSource() : schemaField.getSource());
merged.setType(override.getType() != null ? override.getType() : schemaField.getType());
merged.setValue(override.getValue() != null ? override.getValue() : schemaField.getValue());
merged.setField(override.getField() != null ? override.getField() : schemaField.getField());
merged.setContextKey(override.getContextKey() != null ? override.getContextKey() : schemaField.getContextKey());
merged.setIdentifier(override.getIdentifier() != null ? override.getIdentifier() : schemaField.getIdentifier());
return merged;
}
private Object resolveValue(String source, RequestSchemaField fieldDef, JSONObject authConfig,
JSONObject extraConfig, FlowContext ctx) { JSONObject extraConfig, FlowContext ctx) {
if (source == null) return null; if (source == null) return null;
JSONObject fd = fieldDef instanceof JSONObject ? (JSONObject) fieldDef
: JSON.parseObject(JSON.toJSONString(fieldDef));
switch (source) { switch (source) {
case "fixed": case "fixed":
return fd.get("value"); return fieldDef.getValue();
case "uuid": case "uuid":
return newGeneratedId(); return newGeneratedId();
case "auth": case "auth":
return authConfig.get(fd.getString("field")); return authConfig.get(fieldDef.getField());
case "extra": case "extra":
return extraConfig.get(fd.getString("field")); return extraConfig.get(fieldDef.getField());
case "context": case "context":
return ctx.getFromContext(fd.getString("contextKey")); return ctx.getFromContext(fieldDef.getContextKey());
case "device": case "device":
if (ctx.getDeviceMapping() == null) return null; if (ctx.getDeviceMapping() == null) return null;
String df = fd.getString("field"); String df = fieldDef.getField();
if ("platform_device_id".equals(df)) return ctx.getDeviceMapping().getPlatformDeviceId(); if ("platform_device_id".equals(df)) return ctx.getDeviceMapping().getPlatformDeviceId();
if ("platform_point_id".equals(df)) return ctx.getDeviceMapping().getPlatformPointId(); if ("platform_point_id".equals(df)) return ctx.getDeviceMapping().getPlatformPointId();
// 支持从设备绑定表读取项目信息 // 支持从设备绑定表读取项目信息
@ -310,7 +324,7 @@ public class PlatformFlowExecutor {
if (StringUtils.hasText(ctx.getCurrentMonitorSerialNumber())) { if (StringUtils.hasText(ctx.getCurrentMonitorSerialNumber())) {
return ctx.getCurrentMonitorSerialNumber(); return ctx.getCurrentMonitorSerialNumber();
} }
return normalizeValueByType(fd.getString("type"), resolveGatewaySerialNumber(ctx.getDeviceMapping())); return normalizeValueByType(fieldDef.getType(), resolveGatewaySerialNumber(ctx.getDeviceMapping()));
} }
if ("device_name".equals(df)) { if ("device_name".equals(df)) {
return resolveDeviceName(ctx); return resolveDeviceName(ctx);
@ -323,11 +337,11 @@ public class PlatformFlowExecutor {
} }
return null; return null;
case "thingsModel": case "thingsModel":
return resolveThingsModelValue(ctx, fd); return resolveThingsModelValue(ctx, fieldDef);
case "ts": case "ts":
return resolveThingsModelTs(ctx, fd); return resolveThingsModelTs(ctx, fieldDef);
case "onboard_config": case "onboard_config":
return resolveOnboardConfigValue(ctx, fd); return resolveOnboardConfigValue(ctx, fieldDef);
case "subDeviceSensorIds": case "subDeviceSensorIds":
return collectSubDeviceSensorIds(ctx); return collectSubDeviceSensorIds(ctx);
case "cascadedSubDeviceSensorIds": case "cascadedSubDeviceSensorIds":
@ -341,7 +355,7 @@ public class PlatformFlowExecutor {
} }
} }
private Object resolveThingsModelValue(FlowContext ctx, JSONObject fieldDef) { private Object resolveThingsModelValue(FlowContext ctx, RequestSchemaField fieldDef) {
JSONObject payload = parsePayloadToObject(ctx.getPayload()); JSONObject payload = parsePayloadToObject(ctx.getPayload());
if (payload == null) { if (payload == null) {
return null; return null;
@ -350,7 +364,7 @@ public class PlatformFlowExecutor {
return StringUtils.hasText(identifier) ? payload.get(identifier) : null; return StringUtils.hasText(identifier) ? payload.get(identifier) : null;
} }
private Object resolveThingsModelTs(FlowContext ctx, JSONObject fieldDef) { private Object resolveThingsModelTs(FlowContext ctx, RequestSchemaField fieldDef) {
JSONObject payload = parsePayloadToObject(ctx.getPayload()); JSONObject payload = parsePayloadToObject(ctx.getPayload());
if (payload == null) { if (payload == null) {
return null; return null;
@ -366,13 +380,13 @@ public class PlatformFlowExecutor {
return ts != null ? ts : payload.get("timestamp"); return ts != null ? ts : payload.get("timestamp");
} }
private String resolveThingsModelIdentifier(JSONObject fieldDef) { private String resolveThingsModelIdentifier(RequestSchemaField fieldDef) {
String identifier = fieldDef.getString("identifier"); String identifier = fieldDef.getIdentifier();
if (StringUtils.hasText(identifier)) { if (StringUtils.hasText(identifier)) {
return identifier; return identifier;
} }
String field = fieldDef.getString("field"); String field = fieldDef.getField();
return StringUtils.hasText(field) ? field : fieldDef.getString("contextKey"); return StringUtils.hasText(field) ? field : fieldDef.getContextKey();
} }
private JSONObject parsePayloadToObject(String payload) { private JSONObject parsePayloadToObject(String payload) {
@ -413,15 +427,18 @@ public class PlatformFlowExecutor {
return null; return null;
} }
private Object resolveOnboardConfigValue(FlowContext ctx, JSONObject fieldDef) { private Object resolveOnboardConfigValue(FlowContext ctx, RequestSchemaField fieldDef) {
String name = fieldDef.getString("name"); String name = fieldDef.getName();
List<PlatformDeviceSubMapping> monitorSubMappings = ctx.getMonitorSubMappings(); List<PlatformDeviceSubMapping> monitorSubMappings = ctx.getMonitorSubMappings();
if (ctx.getCurrentMonitorIndex() != null && CollectionUtils.isNotEmpty(monitorSubMappings)) { if (ctx.getCurrentMonitorIndex() != null && CollectionUtils.isNotEmpty(monitorSubMappings)) {
int index = ctx.getCurrentMonitorIndex(); int index = ctx.getCurrentMonitorIndex();
PlatformDeviceSubMapping platformDeviceSubMapping = monitorSubMappings.get(index); PlatformDeviceSubMapping platformDeviceSubMapping = monitorSubMappings.get(index);
JSONObject authConfig = StringUtils.hasText(platformDeviceSubMapping.getOnboardConfig()) OnboardConfig config = platformDeviceSubMapping.getOnboardConfigParsed();
? JSON.parseObject(platformDeviceSubMapping.getOnboardConfig()) : new JSONObject(); if (config == null) {
String value = authConfig.getString(name); throw new RuntimeException("入网失败,子设备" + platformDeviceSubMapping.getSubDeviceName() + "未配置值:" + name);
}
// 从 OnboardConfig 按字段名反射获取值(支持 sensorId/sensorDepth/refSerialNumber/refSensorId)
String value = resolveOnboardConfigField(config, name);
if (value == null) { if (value == null) {
throw new RuntimeException("入网失败,子设备" + platformDeviceSubMapping.getSubDeviceName() + "未配置值:" + name); throw new RuntimeException("入网失败,子设备" + platformDeviceSubMapping.getSubDeviceName() + "未配置值:" + name);
} }
@ -500,21 +517,24 @@ public class PlatformFlowExecutor {
private Set<String> resolveGeneratedFieldNames(PlatformInterface iface, PlatformFlowStep step) { private Set<String> resolveGeneratedFieldNames(PlatformInterface iface, PlatformFlowStep step) {
LinkedHashMap<String, String> sources = new LinkedHashMap<>(); LinkedHashMap<String, String> sources = new LinkedHashMap<>();
try { try {
if (iface != null && StringUtils.hasText(iface.getRequestSchema())) { if (iface != null) {
JSONArray schema = JSON.parseArray(iface.getRequestSchema()); List<RequestSchemaField> schema = iface.getRequestSchemaParsed();
for (int i = 0; i < schema.size(); i++) { if (schema != null) {
JSONObject field = schema.getJSONObject(i); for (RequestSchemaField field : schema) {
if (field != null && StringUtils.hasText(field.getString("name"))) { if (field != null && StringUtils.hasText(field.getName())) {
sources.put(field.getString("name"), field.getString("source")); sources.put(field.getName(), field.getSource());
} }
} }
} }
if (step != null && StringUtils.hasText(step.getParamMapping())) { }
JSONObject overrides = JSON.parseObject(step.getParamMapping()); if (step != null) {
for (String fieldName : overrides.keySet()) { Map<String, ParamOverrideConfig> overrides = step.getParamMappingParsed();
JSONObject override = overrides.getJSONObject(fieldName); if (overrides != null) {
if (override != null && StringUtils.hasText(override.getString("source"))) { for (Map.Entry<String, ParamOverrideConfig> entry : overrides.entrySet()) {
sources.put(fieldName, override.getString("source")); ParamOverrideConfig override = entry.getValue();
if (override != null && StringUtils.hasText(override.getSource())) {
sources.put(entry.getKey(), override.getSource());
}
} }
} }
} }
@ -531,10 +551,8 @@ public class PlatformFlowExecutor {
private List<String> collectSubDeviceSensorIds(FlowContext ctx) { private List<String> collectSubDeviceSensorIds(FlowContext ctx) {
if (StringUtils.hasText(ctx.getCurrentMonitorSerialNumber()) if (StringUtils.hasText(ctx.getCurrentMonitorSerialNumber())
&& StringUtils.hasText(ctx.getCurrentReferenceSerialNumber())) { && StringUtils.hasText(ctx.getCurrentReferenceSerialNumber())) {
String refSensorId = resolveOnboardSensorId( String refSensorId = resolveOnboardSensorIdByCtx(ctx, true);
ctx.getReferenceSubMappingOnboardConfig(), ctx.getCurrentReferenceSerialNumber()); String monitorSensorId = resolveOnboardSensorIdByCtx(ctx, false);
String monitorSensorId = resolveOnboardSensorId(
ctx.getSubMappingOnboardConfig(), ctx.getCurrentMonitorSerialNumber());
List<String> ids = new ArrayList<>(); List<String> ids = new ArrayList<>();
if (StringUtils.hasText(refSensorId)) ids.add(refSensorId); if (StringUtils.hasText(refSensorId)) ids.add(refSensorId);
if (StringUtils.hasText(monitorSensorId)) ids.add(monitorSensorId); if (StringUtils.hasText(monitorSensorId)) ids.add(monitorSensorId);
@ -544,16 +562,14 @@ public class PlatformFlowExecutor {
} }
// 网关入网时,从子表 onboard_config 获取 sensorId // 网关入网时,从子表 onboard_config 获取 sensorId
if (StringUtils.hasText(ctx.getSubMappingOnboardConfig())) { OnboardConfig currentConfig = resolveCurrentOnboardConfig(ctx);
try { if (currentConfig != null) {
JSONObject config = JSON.parseObject(ctx.getSubMappingOnboardConfig());
List<String> ids = new ArrayList<>(); List<String> ids = new ArrayList<>();
String sensorId = config.getString("sensorId"); String sensorId = currentConfig.getSensorId();
String refSensorId = config.getString("refSensorId"); String refSensorId = currentConfig.getRefSensorId();
if (StringUtils.hasText(refSensorId)) ids.add(refSensorId); if (StringUtils.hasText(refSensorId)) ids.add(refSensorId);
if (StringUtils.hasText(sensorId)) ids.add(sensorId); if (StringUtils.hasText(sensorId)) ids.add(sensorId);
if (!ids.isEmpty()) return ids; if (!ids.isEmpty()) return ids;
} catch (Exception ignored) {}
} }
// 回退到从 extra_mapping 解析(直连设备等场景) // 回退到从 extra_mapping 解析(直连设备等场景)
return collectSubDeviceSensorBindings(ctx.getDeviceMapping()).stream() return collectSubDeviceSensorBindings(ctx.getDeviceMapping()).stream()
@ -567,7 +583,13 @@ public class PlatformFlowExecutor {
List<PlatformDeviceSubMapping> suffix = collectCascadedSubDeviceSuffix(ctx); List<PlatformDeviceSubMapping> suffix = collectCascadedSubDeviceSuffix(ctx);
Collections.reverse(suffix); Collections.reverse(suffix);
List<String> sensorIds = suffix.stream() List<String> sensorIds = suffix.stream()
.map(sub -> resolveOnboardSensorId(sub.getOnboardConfig(), sub.getSubSerialNumber())) .map(sub -> {
OnboardConfig cfg = sub.getOnboardConfigParsed();
if (cfg != null && StringUtils.hasText(cfg.getSensorId())) {
return cfg.getSensorId();
}
return normalizeText(sub.getSubSerialNumber());
})
.collect(Collectors.toList()); .collect(Collectors.toList());
if (sensorIds.stream().anyMatch(id -> !StringUtils.hasText(id))) { if (sensorIds.stream().anyMatch(id -> !StringUtils.hasText(id))) {
throw new RuntimeException("深部位移子设备缺少 sensorId"); throw new RuntimeException("深部位移子设备缺少 sensorId");
@ -634,14 +656,14 @@ public class PlatformFlowExecutor {
} }
private Object readSensorDepth(PlatformDeviceSubMapping mapping) { private Object readSensorDepth(PlatformDeviceSubMapping mapping) {
if (mapping == null || !StringUtils.hasText(mapping.getOnboardConfig())) { if (mapping == null) {
return null; return null;
} }
try { OnboardConfig config = mapping.getOnboardConfigParsed();
return JSON.parseObject(mapping.getOnboardConfig()).get("sensorDepth"); if (config == null) {
} catch (Exception e) { return null;
throw new RuntimeException("子设备 " + mapping.getSubSerialNumber() + " 入网参数不是有效JSON");
} }
return config.getSensorDepth();
} }
/** /**
@ -650,12 +672,13 @@ public class PlatformFlowExecutor {
*/ */
private List<List<String>> collectReferenceSubDeviceSensorIdPairs(FlowContext ctx) { private List<List<String>> collectReferenceSubDeviceSensorIdPairs(FlowContext ctx) {
if (StringUtils.hasText(ctx.getCurrentMonitorSerialNumber())) { if (StringUtils.hasText(ctx.getCurrentMonitorSerialNumber())) {
String sensorId = resolveOnboardSensorId( String sensorId = resolveOnboardSensorIdByCtx(ctx, false);
ctx.getSubMappingOnboardConfig(), ctx.getCurrentMonitorSerialNumber()); String refSensorId = resolveOnboardSensorIdByCtx(ctx, true);
String refSensorId = resolveOnboardSensorId(
ctx.getReferenceSubMappingOnboardConfig(), ctx.getCurrentReferenceSerialNumber());
if (!StringUtils.hasText(refSensorId)) { if (!StringUtils.hasText(refSensorId)) {
refSensorId = resolveOnboardConfigText(ctx.getSubMappingOnboardConfig(), "refSensorId"); OnboardConfig currentConfig = resolveCurrentOnboardConfig(ctx);
if (currentConfig != null) {
refSensorId = currentConfig.getRefSensorId();
}
} }
if (StringUtils.hasText(sensorId) && StringUtils.hasText(refSensorId)) { if (StringUtils.hasText(sensorId) && StringUtils.hasText(refSensorId)) {
if (sameText(refSensorId, sensorId)) { if (sameText(refSensorId, sensorId)) {
@ -672,17 +695,15 @@ public class PlatformFlowExecutor {
} }
// 网关入网:onboardConfig 已指定当前监测点 // 网关入网:onboardConfig 已指定当前监测点
if (StringUtils.hasText(ctx.getSubMappingOnboardConfig())) { OnboardConfig currentConfig = resolveCurrentOnboardConfig(ctx);
try { if (currentConfig != null) {
JSONObject config = JSON.parseObject(ctx.getSubMappingOnboardConfig()); String sensorId = currentConfig.getSensorId();
String sensorId = config.getString("sensorId"); String refSensorId = currentConfig.getRefSensorId();
String refSensorId = config.getString("refSensorId");
if (StringUtils.hasText(sensorId) && StringUtils.hasText(refSensorId)) { if (StringUtils.hasText(sensorId) && StringUtils.hasText(refSensorId)) {
List<List<String>> pairs = new ArrayList<>(); List<List<String>> pairs = new ArrayList<>();
pairs.add(Arrays.asList(refSensorId, sensorId)); pairs.add(Arrays.asList(refSensorId, sensorId));
return pairs; return pairs;
} }
} catch (Exception ignored) {}
} }
// 回退:从 extra_mapping 解析(兼容直连设备场景) // 回退:从 extra_mapping 解析(兼容直连设备场景)
PlatformDeviceMapping mapping = ctx.getDeviceMapping(); PlatformDeviceMapping mapping = ctx.getDeviceMapping();
@ -711,20 +732,52 @@ public class PlatformFlowExecutor {
return pairs; return pairs;
} }
private String resolveOnboardSensorId(String onboardConfig, String fallbackSerialNumber) { /** 获取当前监测点子设备的 OnboardConfig POJO */
String sensorId = resolveOnboardConfigText(onboardConfig, "sensorId"); private OnboardConfig resolveCurrentOnboardConfig(FlowContext ctx) {
return StringUtils.hasText(sensorId) ? sensorId : normalizeText(fallbackSerialNumber); if (ctx.getCurrentMonitorIndex() != null && CollectionUtils.isNotEmpty(ctx.getMonitorSubMappings())) {
int index = ctx.getCurrentMonitorIndex();
return ctx.getMonitorSubMappings().get(index).getOnboardConfigParsed();
}
if (StringUtils.hasText(ctx.getSubMappingOnboardConfig())) {
return JSON.parseObject(ctx.getSubMappingOnboardConfig(), OnboardConfig.class);
}
return null;
} }
private String resolveOnboardConfigText(String onboardConfig, String field) { /**
if (!StringUtils.hasText(onboardConfig) || !StringUtils.hasText(field)) { * 解析 sensorId:优先从子映射 OnboardConfig 获取,否则返回序列号
return null; * @param ctx 上下文
* @param isReference true=基准点, false=监测点
*/
private String resolveOnboardSensorIdByCtx(FlowContext ctx, boolean isReference) {
String configStr = isReference ? ctx.getReferenceSubMappingOnboardConfig() : ctx.getSubMappingOnboardConfig();
String fallbackSn = isReference ? ctx.getCurrentReferenceSerialNumber() : ctx.getCurrentMonitorSerialNumber();
if (StringUtils.hasText(configStr)) {
OnboardConfig config = JSON.parseObject(configStr, OnboardConfig.class);
if (config != null && StringUtils.hasText(config.getSensorId())) {
return config.getSensorId();
} }
try { } else if (!isReference && ctx.getCurrentMonitorIndex() != null
JSONObject config = JSON.parseObject(onboardConfig); && CollectionUtils.isNotEmpty(ctx.getMonitorSubMappings())) {
return normalizeText(config.getString(field)); int index = ctx.getCurrentMonitorIndex();
} catch (Exception ignored) { OnboardConfig config = ctx.getMonitorSubMappings().get(index).getOnboardConfigParsed();
return null; if (config != null && StringUtils.hasText(config.getSensorId())) {
return config.getSensorId();
}
}
return normalizeText(fallbackSn);
}
/** 从 OnboardConfig POJO 按字段名取值 */
private String resolveOnboardConfigField(OnboardConfig config, String field) {
if (config == null || !StringUtils.hasText(field)) return null;
switch (field) {
case "sensorId": return config.getSensorId();
case "sensorDepth": return config.getSensorDepth();
case "refSerialNumber": return config.getRefSerialNumber();
case "refSensorId": return config.getRefSensorId();
default: return null;
} }
} }
@ -751,28 +804,35 @@ public class PlatformFlowExecutor {
private String appendConfiguredSubDeviceBindings(LinkedHashMap<String, SubDeviceSensorBinding> bindings, private String appendConfiguredSubDeviceBindings(LinkedHashMap<String, SubDeviceSensorBinding> bindings,
PlatformDeviceMapping mapping) { PlatformDeviceMapping mapping) {
if (!StringUtils.hasText(mapping.getExtraMapping())) { ExtraMappingConfig extra = mapping.getExtraMappingParsed();
if (extra == null) {
return null; return null;
} }
try { try {
JSONObject extra = JSON.parseObject(mapping.getExtraMapping());
String referenceSerialNumber = null; String referenceSerialNumber = null;
JSONArray arrayFields = extra.getJSONArray("arrayFields"); List<ExtraMappingConfig.ArrayFieldConfig> arrayFields = extra.getArrayFields();
if (arrayFields != null) { if (arrayFields != null) {
for (int i = 0; i < arrayFields.size(); i++) { for (ExtraMappingConfig.ArrayFieldConfig arrayField : arrayFields) {
JSONObject arrayField = arrayFields.getJSONObject(i);
referenceSerialNumber = appendConfiguredMappingRows(bindings, referenceSerialNumber = appendConfiguredMappingRows(bindings,
arrayField == null ? null : arrayField.getJSONArray("subDeviceMappings"), arrayField == null ? null : arrayField.getSubDeviceMappings(),
referenceSerialNumber); referenceSerialNumber);
} }
} }
JSONObject staticLevel = extra.getJSONObject("staticLevel"); ExtraMappingConfig.StaticLevelConfig staticLevel = extra.getStaticLevel();
if (staticLevel != null) { if (staticLevel != null) {
referenceSerialNumber = normalizeText(staticLevel.getString("referenceSerialNumber")); referenceSerialNumber = normalizeText(staticLevel.getReferenceSerialNumber());
referenceSerialNumber = appendConfiguredMappingRows( List<ExtraMappingConfig.SensorIdMapping> sensorIdMappings = staticLevel.getSensorIdMappings();
bindings, staticLevel.getJSONArray("sensorIdMappings"), referenceSerialNumber); if (sensorIdMappings != null) {
for (ExtraMappingConfig.SensorIdMapping item : sensorIdMappings) {
if (item == null) continue;
String sn = normalizeText(item.getSerialNumber());
if (!StringUtils.hasText(sn)) continue;
String sid = normalizeText(item.getSensorId());
upsertSubDeviceBinding(bindings, sn, StringUtils.hasText(sid) ? sid : sn);
}
}
} }
return referenceSerialNumber; return referenceSerialNumber;
} catch (Exception e) { } catch (Exception e) {
@ -783,24 +843,20 @@ public class PlatformFlowExecutor {
} }
private String appendConfiguredMappingRows(LinkedHashMap<String, SubDeviceSensorBinding> bindings, private String appendConfiguredMappingRows(LinkedHashMap<String, SubDeviceSensorBinding> bindings,
JSONArray mappings, String referenceSerialNumber) { List<ExtraMappingConfig.SubDeviceMapping> mappings,
String referenceSerialNumber) {
if (mappings == null || mappings.isEmpty()) { if (mappings == null || mappings.isEmpty()) {
return referenceSerialNumber; return referenceSerialNumber;
} }
String ref = referenceSerialNumber; String ref = referenceSerialNumber;
for (int i = 0; i < mappings.size(); i++) { for (ExtraMappingConfig.SubDeviceMapping item : mappings) {
JSONObject item = mappings.getJSONObject(i); if (item == null) continue;
if (item == null) { String serialNumber = normalizeText(item.getSerialNumber());
continue; if (!StringUtils.hasText(serialNumber)) continue;
} String sensorId = normalizeText(item.getSensorId());
String serialNumber = normalizeText(item.getString("serialNumber"));
if (!StringUtils.hasText(serialNumber)) {
continue;
}
String sensorId = normalizeText(item.getString("sensorId"));
upsertSubDeviceBinding(bindings, serialNumber, upsertSubDeviceBinding(bindings, serialNumber,
StringUtils.hasText(sensorId) ? sensorId : serialNumber); StringUtils.hasText(sensorId) ? sensorId : serialNumber);
if (Boolean.TRUE.equals(item.getBoolean("isReference"))) { if (Boolean.TRUE.equals(item.getIsReference())) {
ref = serialNumber; ref = serialNumber;
} }
} }
@ -925,13 +981,7 @@ public class PlatformFlowExecutor {
/** 移除已替换到路径中的变量,避免重复放入 body */ /** 移除已替换到路径中的变量,避免重复放入 body */
private void removePathVars(Map<String, Object> params, String path) { private void removePathVars(Map<String, Object> params, String path) {
Iterator<Map.Entry<String, Object>> it = params.entrySet().iterator(); params.entrySet().removeIf(e -> path.contains("{" + e.getKey() + "}"));
while (it.hasNext()) {
Map.Entry<String, Object> e = it.next();
if (path.contains("{" + e.getKey() + "}")) {
it.remove();
}
}
} }
private Object buildResponseData(PlatformInterface iface, String responseBody) { private Object buildResponseData(PlatformInterface iface, String responseBody) {
@ -973,19 +1023,18 @@ public class PlatformFlowExecutor {
private JSONObject extractResponseBySchema(PlatformInterface iface, Object parsedResponse) { private JSONObject extractResponseBySchema(PlatformInterface iface, Object parsedResponse) {
JSONObject extracted = new JSONObject(); JSONObject extracted = new JSONObject();
if (iface == null || !StringUtils.hasText(iface.getResponseSchema()) || parsedResponse == null) { List<ResponseSchemaField> schema = iface.getResponseSchemaParsed();
if (schema == null || schema.isEmpty() || parsedResponse == null) {
return extracted; return extracted;
} }
try { try {
JSONArray schema = JSON.parseArray(iface.getResponseSchema()); for (ResponseSchemaField field : schema) {
for (int i = 0; i < schema.size(); i++) { if (field == null || !StringUtils.hasText(field.getName())) {
JSONObject field = schema.getJSONObject(i);
if (field == null || !StringUtils.hasText(field.getString("name"))) {
continue; continue;
} }
Object value = readPathValue(parsedResponse, field.getString("path")); Object value = readPathValue(parsedResponse, field.getPath());
if (value != null) { if (value != null) {
extracted.put(field.getString("name"), value); extracted.put(field.getName(), value);
} }
} }
} catch (Exception e) { } catch (Exception e) {
@ -1101,14 +1150,15 @@ public class PlatformFlowExecutor {
*/ */
private void persistResultSave(PlatformFlowStep step, Object responseData, FlowContext context) { private void persistResultSave(PlatformFlowStep step, Object responseData, FlowContext context) {
try { try {
JSONArray saves = JSON.parseArray(step.getResultSave()); List<ResultSaveField> saves = step.getResultSaveParsed();
if (saves == null || saves.isEmpty()) return;
JSONObject response = toJsonObject(responseData); JSONObject response = toJsonObject(responseData);
if (response == null) return; if (response == null) return;
//PlatformInstance platform = context.getPlatform();
PlatformUserAuth userAuth = context.getPlatformUserAuth(); PlatformUserAuth userAuth = context.getPlatformUserAuth();
JSONObject extra = StringUtils.hasText(userAuth.getExtraConfig()) JSONObject extra = userAuth.getExtraConfigParsed();
? JSON.parseObject(userAuth.getExtraConfig()) : new JSONObject(); if (extra == null) extra = new JSONObject();
boolean extraChanged = false; boolean extraChanged = false;
boolean userAuthFieldChanged = false; boolean userAuthFieldChanged = false;
// 平台用户授权信息(token、userId) // 平台用户授权信息(token、userId)
@ -1119,10 +1169,9 @@ public class PlatformFlowExecutor {
// 保存平台映射关系(测点ID、设备ID) // 保存平台映射关系(测点ID、设备ID)
PlatformDeviceMapping mappingUpdate = new PlatformDeviceMapping(); PlatformDeviceMapping mappingUpdate = new PlatformDeviceMapping();
for (int i = 0; i < saves.size(); i++) { for (ResultSaveField save : saves) {
JSONObject save = saves.getJSONObject(i); String field = save.getField();
String field = save.getString("field"); String saveTo = save.getSaveTo();
String saveTo = save.getString("saveTo");
Object value = response.get(field); Object value = response.get(field);
if (value == null || saveTo == null) continue; if (value == null || saveTo == null) continue;
@ -1185,6 +1234,7 @@ public class PlatformFlowExecutor {
} }
} catch (Exception e) { } catch (Exception e) {
log.warn("[PlatformFlow] persistResultSave 失败: {}", e.getMessage()); log.warn("[PlatformFlow] persistResultSave 失败: {}", e.getMessage());
throw new RuntimeException("执行流程❌,保存数据失败: " + e.getMessage());
} }
} }

View File

@ -0,0 +1,62 @@
package com.fastbee.iot.platform.model.json;
import com.fastbee.iot.platform.domain.PlatformDeviceMapping;
import lombok.Data;
import java.io.Serializable;
import java.util.List;
/**
* @see PlatformDeviceMapping#extraMapping POJO
*/
@Data
public class ExtraMappingConfig implements Serializable {
/** 覆盖推送接口中的数组字段名 */
private String fieldName;
/** 数组模式:single / reference_pair */
private String arrayMode;
/** 静力水准缓存超时秒数 */
private Integer reportIntervalSec;
/** 数组项字段映射 */
private List<ItemFieldMapping> itemFieldMappings;
/** 批量推送数组字段子设备映射配置 */
private List<ArrayFieldConfig> arrayFields;
/** 静力水准配置 */
private StaticLevelConfig staticLevel;
@Data
public static class ItemFieldMapping implements Serializable {
private String identifier;
private String platformField;
}
@Data
public static class ArrayFieldConfig implements Serializable {
private List<SubDeviceMapping> subDeviceMappings;
}
@Data
public static class SubDeviceMapping implements Serializable {
private String serialNumber;
private String sensorId;
private Boolean isReference;
}
@Data
public static class StaticLevelConfig implements Serializable {
private String referenceSerialNumber;
private List<SensorIdMapping> sensorIdMappings;
}
@Data
public static class SensorIdMapping implements Serializable {
private String serialNumber;
private String sensorId;
}
}

View File

@ -0,0 +1,22 @@
package com.fastbee.iot.platform.model.json;
import com.fastbee.iot.platform.domain.PlatformDeviceMapping;
import lombok.Data;
import java.io.Serializable;
/**
* @see PlatformDeviceMapping.fieldMapping 字段定义 POJO:{identifier, platformField, transform}
*/
@Data
public class FieldMappingItem implements Serializable {
/** 物模型标识符 */
private String identifier;
/** 平台侧字段名 */
private String platformField;
/** 转换方式(可选) */
private String transform;
}

View File

@ -0,0 +1,25 @@
package com.fastbee.iot.platform.model.json;
import com.fastbee.iot.platform.domain.PlatformDeviceSubMapping;
import lombok.Data;
import java.io.Serializable;
/**
* @see PlatformDeviceSubMapping.onboardConfig POJO
*/
@Data
public class OnboardConfig implements Serializable {
/** 平台传感器 ID */
private String sensorId;
/** 传感器深度 */
private String sensorDepth;
/** 基准点序列号 */
private String refSerialNumber;
/** 基准点传感器 ID */
private String refSensorId;
}

View File

@ -0,0 +1,34 @@
package com.fastbee.iot.platform.model.json;
import com.fastbee.iot.platform.domain.PlatformFlowStep;
import lombok.Data;
import java.io.Serializable;
/**
* @see PlatformFlowStep.paramMapping POJO
* <p>
* JSON 结构为 Map,key 为字段名,value 为参数覆盖定义:
* { "grant_type": {"source": "fixed", "value": "password"}, ... }
*/
@Data
public class ParamOverrideConfig implements Serializable {
/** 值来源:fixed/auth/extra/context 等 */
private String source;
/** 类型:string/number 等 */
private String type;
/** source=auth/extra 时的子字段名 */
private String field;
/** source=fixed 时的固定值 */
private Object value;
/** source=context 时的上下文 key */
private String contextKey;
/** source=thingsModel 时的物模型标识符 */
private String identifier;
}

View File

@ -0,0 +1,44 @@
package com.fastbee.iot.platform.model.json;
import com.alibaba.fastjson2.annotation.JSONField;
import com.fastbee.iot.platform.domain.PlatformInterface;
import lombok.Data;
import java.io.Serializable;
import java.util.List;
/**
* @see PlatformInterface.requestSchema 字段定义 POJO
* 支持 14 种 source 类型和递归嵌套的 itemSchema
*/
@Data
public class RequestSchemaField implements Serializable {
/** 字段名(出参 key) */
private String name;
/** 值来源:fixed/auth/extra/device/thingsModel/ts/context/uuid/onboard_config/等 */
private String source;
/** 字段类型:string/number/boolean/array */
private String type;
/** source=fixed 时的固定值 */
private Object value;
/** source=auth/extra/device 时的子字段名 */
private String field;
/** source=context 时的上下文 key */
private String contextKey;
/** source=thingsModel 时的物模型标识符 */
private String identifier;
/** type=array 时的数组成员 schema */
@JSONField(name = "itemSchema")
private List<RequestSchemaField> itemSchema;
/** type=array 时的数组模式:single/reference_pair */
private String arrayMode;
}

View File

@ -0,0 +1,19 @@
package com.fastbee.iot.platform.model.json;
import com.fastbee.iot.platform.domain.PlatformInterface;
import lombok.Data;
import java.io.Serializable;
/**
* @see PlatformInterface.responseSchema 字段定义 POJO:{name, path}
*/
@Data
public class ResponseSchemaField implements Serializable {
/** 存储到 FlowContext 的 key 名 */
private String name;
/** JSON path 表达式,如 $.data.id、data[0].deviceId */
private String path;
}

View File

@ -0,0 +1,19 @@
package com.fastbee.iot.platform.model.json;
import com.fastbee.iot.platform.domain.PlatformFlowStep;
import lombok.Data;
import java.io.Serializable;
/**
* @see PlatformFlowStep.resultSave 字段定义 POJO:{field, saveTo}
*/
@Data
public class ResultSaveField implements Serializable {
/** 响应中的字段名 */
private String field;
/** 保存目标,如 platform_user_auth.access_token、device_mapping.platform_device_id、extra_config.projectId */
private String saveTo;
}