diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 741ad7d9..f2095108 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -12,7 +12,8 @@ "Bash(python3 -c \"import pdfminer; print\\(''pdfminer ok''\\)\")", "Bash(python3 -c \"import fitz; print\\(''fitz ok''\\)\")", "Bash(python3:*)", - "Bash(grep -A 5 \"INSERT INTO \\\\`sys_menu\\\\`\" /Users/admin/IdeaProjects/fastbee/sql/yack.sql)" + "Bash(grep -A 5 \"INSERT INTO \\\\`sys_menu\\\\`\" /Users/admin/IdeaProjects/fastbee/sql/yack.sql)", + "Bash(mvn compile:*)" ] } } diff --git a/fastbee-open-api/src/main/java/com/fastbee/controller/platform/PlatformDeviceMappingController.java b/fastbee-open-api/src/main/java/com/fastbee/controller/platform/PlatformDeviceMappingController.java index e37de413..ed3d3b21 100644 --- a/fastbee-open-api/src/main/java/com/fastbee/controller/platform/PlatformDeviceMappingController.java +++ b/fastbee-open-api/src/main/java/com/fastbee/controller/platform/PlatformDeviceMappingController.java @@ -4,13 +4,17 @@ import com.fastbee.common.core.controller.BaseController; import com.fastbee.common.core.domain.AjaxResult; import com.fastbee.common.core.page.TableDataInfo; import com.fastbee.iot.platform.domain.PlatformDeviceMapping; +import com.fastbee.iot.platform.domain.PlatformDeviceSubMapping; import com.fastbee.iot.platform.service.IPlatformDeviceMappingService; +import com.fastbee.iot.platform.service.IPlatformDeviceSubMappingService; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.*; +import java.util.List; + @RestController @RequestMapping("/platform/device-mapping") @Api(tags = "平台设备绑定") @@ -18,6 +22,8 @@ public class PlatformDeviceMappingController extends BaseController { @Autowired private IPlatformDeviceMappingService mappingService; + @Autowired + private IPlatformDeviceSubMappingService subMappingService; @PreAuthorize("@ss.hasPermi('platform:deviceMapping:list')") @GetMapping("/list") @@ -38,7 +44,8 @@ public class PlatformDeviceMappingController extends BaseController { @PostMapping @ApiOperation("新增设备绑定") public AjaxResult add(@RequestBody PlatformDeviceMapping mapping) { - return toAjax(mappingService.save(mapping)); + mappingService.save(mapping); + return success(mapping.getId()); } @PreAuthorize("@ss.hasPermi('platform:deviceMapping:edit')") @@ -60,9 +67,26 @@ public class PlatformDeviceMappingController extends BaseController { @PreAuthorize("@ss.hasPermi('platform:deviceMapping:edit')") @PostMapping("/{id}/onboard") - @ApiOperation("设备入网(执行入网流程,获取DCP deviceId)") + @ApiOperation("设备入网(执行入网流程,获取平台 deviceId)") public AjaxResult onboard(@PathVariable Long id) { mappingService.onboard(id); return success("入网成功"); } + + // ==================== 子映射管理 ==================== + + @PreAuthorize("@ss.hasPermi('platform:deviceMapping:query')") + @GetMapping("/{id}/sub-mappings") + @ApiOperation("查询子设备映射列表") + public AjaxResult listSubMappings(@PathVariable Long id) { + return success(subMappingService.selectByParentId(id)); + } + + @PreAuthorize("@ss.hasPermi('platform:deviceMapping:edit')") + @PostMapping("/{id}/sub-mappings") + @ApiOperation("批量同步子设备映射(全量替换)") + public AjaxResult syncSubMappings(@PathVariable Long id, @RequestBody List subMappings) { + subMappingService.syncSubMappings(id, subMappings); + return success(); + } } diff --git a/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/platform/adapter/dcp/DcpPlatformAdapter.java b/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/platform/adapter/dcp/DcpPlatformAdapter.java index bc45fb61..95f9a5f3 100644 --- a/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/platform/adapter/dcp/DcpPlatformAdapter.java +++ b/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/platform/adapter/dcp/DcpPlatformAdapter.java @@ -10,6 +10,7 @@ import com.fastbee.common.utils.spring.SpringUtils; import com.fastbee.http.model.HttpClientConfig; import com.fastbee.http.service.HttpClientFactory; import com.fastbee.iot.platform.domain.PlatformDeviceMapping; +import com.fastbee.iot.platform.domain.PlatformDeviceSubMapping; import com.fastbee.iot.platform.domain.PlatformFlow; import com.fastbee.iot.platform.domain.PlatformInterface; import com.fastbee.iot.platform.domain.PlatformInstance; @@ -77,7 +78,7 @@ public class DcpPlatformAdapter { } /** - * 推送设备上报数据到 DCP + * 推送设备上报数据到 DCP(直连设备) */ public void pushData(PlatformInstance platform, PlatformDeviceMapping mapping, String reportSerialNumber, String payload) { @@ -96,16 +97,9 @@ public class DcpPlatformAdapter { } try { - JSONArray arrayFieldsDef = extractArrayFieldsDef(mapping.getExtraMapping()); - if (arrayFieldsDef != null && !arrayFieldsDef.isEmpty()) { - handleArrayFieldPush( - platform, mapping, reportSerialNumber, payload, pushPath, - interfaceCode, requestSchema, arrayFieldsDef); - } else { - handleNormalPush( - platform, mapping, reportSerialNumber, payload, pushPath, - interfaceCode, requestSchema); - } + handleNormalPush( + platform, mapping, reportSerialNumber, payload, pushPath, + interfaceCode, requestSchema); } catch (Exception e) { String serialNumber = StringUtils.hasText(reportSerialNumber) ? reportSerialNumber : mapping.getSerialNumber(); @@ -117,6 +111,134 @@ public class DcpPlatformAdapter { } } + /** + * 推送网关子设备上报数据到 DCP + *

+ * deviceId 直接从子映射行获取,不再解析 JSON。 + * reference 角色仅缓存基准数据,monitor 角色组装 [refItem, monitorItem] 推送。 + */ + public void pushGatewayData(PlatformInstance platform, PlatformDeviceMapping mapping, + PlatformDeviceSubMapping subMapping, + String reportSerialNumber, String payload) { + if (mapping.getNetStatus() == null || mapping.getNetStatus() != 1) { + log.warn("[DCP] 网关设备 {} 未入网,跳过推送", mapping.getSerialNumber()); + return; + } + String sensorType = mapping.getSensorType(); + PlatformInterface pushInterface = loadPushInterface(mapping); + JSONArray requestSchema = loadRequestSchema(pushInterface); + String interfaceCode = resolveInterfaceCode(sensorType, pushInterface); + String pushPath = resolvePushPath(sensorType, pushInterface); + if (pushPath == null) { + log.warn("[DCP] 未知传感器类型: {}", sensorType); + return; + } + + try { + JSONObject reportData = parsePayloadToMap(payload); + if (reportData == null || reportData.isEmpty()) { + return; + } + + JSONArray fieldMappings = parseJsonArray(mapping.getFieldMapping()); + String baseUrl = platform.getBaseUrl() + pushPath; + + // 解析 extra_mapping 中的 itemFieldMappings 和 reportIntervalSec + int reportIntervalSec = 300; + JSONArray itemFieldMappings = null; + String fieldName = "sensorData"; + if (StringUtils.hasText(mapping.getExtraMapping())) { + JSONObject extra = JSON.parseObject(mapping.getExtraMapping()); + reportIntervalSec = extra.getIntValue("reportIntervalSec", 300); + itemFieldMappings = extra.getJSONArray("itemFieldMappings"); + if (StringUtils.hasText(extra.getString("fieldName"))) { + fieldName = extra.getString("fieldName"); + } + } + + // 从 onboard_config 取 sensorId + String sensorId = reportSerialNumber; + if (StringUtils.hasText(subMapping.getOnboardConfig())) { + JSONObject config = JSON.parseObject(subMapping.getOnboardConfig()); + String cfgSensorId = config.getString("sensorId"); + if (StringUtils.hasText(cfgSensorId)) { + sensorId = cfgSensorId; + } + } + + // 构建当前设备的数据项 + JSONObject currentItem = buildArrayItem( + platform, mapping, requestSchema, reportData, fieldName, itemFieldMappings, sensorId); + if (currentItem == null || currentItem.isEmpty()) { + log.warn("[DCP] 网关子设备 {} 数据映射为空,跳过推送", reportSerialNumber); + return; + } + + JSONObject currentBodyFields = buildObjectBySchema(platform, mapping, requestSchema, reportData, fieldMappings); + + if (subMapping.isReference()) { + // 基准点上报:仅缓存,不推送 + bufferService.saveReferenceData( + platform.getId(), fieldName, reportSerialNumber, currentItem, reportIntervalSec); + log.info("[DCP] 基准点 {} 已缓存,等待监测点上报", reportSerialNumber); + return; + } + + // 监测点上报:deviceId 直接从子映射行获取 + String monitorDeviceId = subMapping.getPlatformDeviceId(); + if (!StringUtils.hasText(monitorDeviceId)) { + log.warn("[DCP] 监测点 {} 未分配平台设备ID,跳过推送", reportSerialNumber); + return; + } + + // 获取基准点序列号(从 onboard_config 中取 refSensorId 对应的原始序列号) + String refSerialNumber = resolveRefSerialNumber(subMapping, mapping); + + JSONObject refData = bufferService.getReferenceDataIfFresh( + platform.getId(), fieldName, refSerialNumber, reportIntervalSec); + if (refData != null && refData.getJSONObject("itemData") != null) { + JSONArray sensorData = new JSONArray(); + sensorData.add(refData.getJSONObject("itemData")); + sensorData.add(currentItem); + JSONArray pushBody = buildArrayPushBody(mapping, currentBodyFields, fieldName, sensorData, monitorDeviceId); + + PlatformPushLog logEntry = createLogEntry(platform, mapping, interfaceCode, reportSerialNumber); + doHttpPost(platform, baseUrl, JSON.toJSONString(pushBody), logEntry); + pushLogService.asyncSave(logEntry); + log.info("[DCP] 监测点 {} 基准点就绪,已推送 deviceId={}", reportSerialNumber, monitorDeviceId); + } else { + bufferService.saveMonitorData( + platform.getId(), fieldName, reportSerialNumber, currentItem, currentBodyFields, reportIntervalSec); + log.info("[DCP] 监测点 {} 基准点未就绪,已缓存等待", reportSerialNumber); + } + } catch (Exception e) { + log.error("[DCP] 网关推送失败 device={} sensor={}", reportSerialNumber, sensorType, e); + PlatformPushLog logEntry = createLogEntry(platform, mapping, interfaceCode, reportSerialNumber); + logEntry.setStatus(2); + logEntry.setErrorMsg(e.getMessage()); + pushLogService.asyncSave(logEntry); + } + } + + /** + * 从子映射的 onboard_config 解析基准点序列号 + */ + private String resolveRefSerialNumber(PlatformDeviceSubMapping subMapping, PlatformDeviceMapping mapping) { + // 从 onboard_config 取 refSensorId 对应的子设备序列号 + // 通常基准点的 sensorId == 基准点的 serialNumber + if (StringUtils.hasText(subMapping.getOnboardConfig())) { + try { + JSONObject config = JSON.parseObject(subMapping.getOnboardConfig()); + String refSensorId = config.getString("refSerialNumber"); + if (StringUtils.hasText(refSensorId)) { + return refSensorId; + } + } catch (Exception ignored) {} + } + // 回退:用网关本身的序列号 + return mapping.getSerialNumber(); + } + // ==================== 普通推送 ==================== private void handleNormalPush(PlatformInstance platform, PlatformDeviceMapping mapping, @@ -188,204 +310,15 @@ public class DcpPlatformAdapter { return arr; } - // ==================== 数组字段推送 ==================== - - /** - * 从 extraMapping 中提取 arrayFields 定义 - */ - private JSONArray extractArrayFieldsDef(String extraMapping) { - if (!StringUtils.hasText(extraMapping)) return null; - try { - JSONObject extra = JSON.parseObject(extraMapping); - JSONArray arrayFields = extra.getJSONArray("arrayFields"); - if (arrayFields != null && !arrayFields.isEmpty()) return arrayFields; - JSONObject staticLevel = extra.getJSONObject("staticLevel"); - if (staticLevel != null) { - return convertLegacyStaticLevel(staticLevel); - } - } catch (Exception e) { - log.warn("[DCP] 解析 extraMapping 失败: {}", e.getMessage()); - } - return null; - } - - /** 将旧格式 {staticLevel:{...}} 转换为新 arrayFields 格式 */ - private JSONArray convertLegacyStaticLevel(JSONObject staticLevel) { - JSONArray arrayFields = new JSONArray(); - JSONObject af = new JSONObject(); - af.put("fieldName", "sensorData"); - af.put("reportIntervalSec", 300); - - JSONArray mappings = staticLevel.getJSONArray("sensorIdMappings"); - JSONArray subDeviceMappings = new JSONArray(); - if (mappings != null) { - String refSN = staticLevel.getString("referenceSerialNumber"); - for (int i = 0; i < mappings.size(); i++) { - JSONObject m = mappings.getJSONObject(i); - JSONObject sdm = new JSONObject(); - sdm.put("serialNumber", m.getString("serialNumber")); - sdm.put("sensorId", m.getString("sensorId")); - sdm.put("isReference", m.getString("serialNumber").equals(refSN)); - subDeviceMappings.add(sdm); - } - } - af.put("subDeviceMappings", subDeviceMappings); - - JSONArray itemMappings = new JSONArray(); - if (StringUtils.hasText(staticLevel.getString("heightIdentifier"))) { - JSONObject h = new JSONObject(); - h.put("platformField", "height"); - h.put("identifier", staticLevel.getString("heightIdentifier")); - itemMappings.add(h); - } - if (StringUtils.hasText(staticLevel.getString("temperatureIdentifier"))) { - JSONObject t = new JSONObject(); - t.put("platformField", "temperature"); - t.put("identifier", staticLevel.getString("temperatureIdentifier")); - itemMappings.add(t); - } - af.put("itemFieldMappings", itemMappings); - - arrayFields.add(af); - return arrayFields; - } - - private void handleArrayFieldPush(PlatformInstance platform, PlatformDeviceMapping mapping, - String reportSerialNumber, String payload, - String pushPath, String interfaceCode, - JSONArray requestSchema, JSONArray arrayFieldsDef) { - JSONObject reportData = parsePayloadToMap(payload); - if (reportData == null || reportData.isEmpty()) { - return; - } - - JSONArray fieldMappings = parseJsonArray(mapping.getFieldMapping()); - String currentSerialNumber = StringUtils.hasText(reportSerialNumber) - ? reportSerialNumber : mapping.getSerialNumber(); - String baseUrl = platform.getBaseUrl() + pushPath; - - for (int i = 0; i < arrayFieldsDef.size(); i++) { - JSONObject af = arrayFieldsDef.getJSONObject(i); - String fieldName = af.getString("fieldName"); - int reportIntervalSec = af.getIntValue("reportIntervalSec", 300); - JSONArray subDeviceMappings = af.getJSONArray("subDeviceMappings"); - JSONArray itemFieldMappings = af.getJSONArray("itemFieldMappings"); - - if (subDeviceMappings == null || subDeviceMappings.isEmpty()) { - log.warn("[DCP] arrayField {} 子设备映射为空", fieldName); - continue; - } - - JSONObject currentSubDevice = findSubDevice(subDeviceMappings, currentSerialNumber); - if (currentSubDevice == null) { - log.debug("[DCP] 设备 {} 不属于 arrayField {} 的子设备列表,跳过", currentSerialNumber, fieldName); - continue; - } - - JSONObject refSubDevice = findReferenceDevice(subDeviceMappings); - if (refSubDevice == null) { - log.warn("[DCP] arrayField {} 未指定基准点", fieldName); - continue; - } - - String refSerialNumber = refSubDevice.getString("serialNumber"); - // 子设备 sensorId 优先取页面配置;未显式配置时回退到子设备编号,减少手工维护。 - String currentSensorId = resolveSubDeviceSensorId(currentSubDevice, currentSerialNumber); - boolean isCurrentReference = Boolean.TRUE.equals(currentSubDevice.getBoolean("isReference")); - - JSONObject currentItem = buildArrayItem( - platform, mapping, requestSchema, reportData, fieldName, itemFieldMappings, currentSensorId); - if (currentItem == null || currentItem.isEmpty()) { - log.warn("[DCP] 设备 {} arrayField {} 映射结果为空,跳过", currentSerialNumber, fieldName); - continue; - } - - JSONObject currentBodyFields = buildObjectBySchema(platform, mapping, requestSchema, reportData, fieldMappings); - - if (isCurrentReference) { - bufferService.saveReferenceData( - platform.getId(), fieldName, currentSerialNumber, currentItem, reportIntervalSec); - pushReferenceSelfPair(platform, mapping, baseUrl, interfaceCode, currentSerialNumber, - currentBodyFields, fieldName, currentItem); - flushCachedMonitorData(platform, mapping, baseUrl, interfaceCode, fieldName, - subDeviceMappings, currentSerialNumber, currentItem, reportIntervalSec); - continue; - } - - JSONObject refData = bufferService.getReferenceDataIfFresh( - platform.getId(), fieldName, refSerialNumber, reportIntervalSec); - if (refData != null && refData.getJSONObject("itemData") != null) { - // 监测点上报时,只有在缓存中拿到有效基准点数据,才组装成 [基准点, 监测点]。 - JSONArray monitorSensorData = new JSONArray(); - monitorSensorData.add(refData.getJSONObject("itemData")); - monitorSensorData.add(currentItem); - JSONArray monitorBody = buildArrayPushBody(mapping, currentBodyFields, fieldName, monitorSensorData); - - PlatformPushLog monitorLog = createLogEntry(platform, mapping, interfaceCode, currentSerialNumber); - doHttpPost(platform, baseUrl, JSON.toJSONString(monitorBody), monitorLog); - pushLogService.asyncSave(monitorLog); - log.info("[DCP] 监测点 {} 基准点就绪,已推送", currentSerialNumber); - } else { - bufferService.saveMonitorData( - platform.getId(), fieldName, currentSerialNumber, currentItem, currentBodyFields, reportIntervalSec); - log.info("[DCP] 监测点 {} 基准点未就绪,已缓存等待基准点上报", currentSerialNumber); - } - } - } - - private void pushReferenceSelfPair(PlatformInstance platform, PlatformDeviceMapping mapping, - String baseUrl, String interfaceCode, String refSerialNumber, - JSONObject bodyFields, String fieldName, JSONObject refItem) { - JSONArray sensorData = new JSONArray(); - sensorData.add(refItem); - sensorData.add(refItem); - JSONArray body = buildArrayPushBody(mapping, bodyFields, fieldName, sensorData); - - PlatformPushLog logEntry = createLogEntry(platform, mapping, interfaceCode, refSerialNumber); - doHttpPost(platform, baseUrl, JSON.toJSONString(body), logEntry); - pushLogService.asyncSave(logEntry); - log.info("[DCP] 基准点 {} 已推送自配对数据", refSerialNumber); - } - - private void flushCachedMonitorData(PlatformInstance platform, PlatformDeviceMapping mapping, - String baseUrl, String interfaceCode, String fieldName, - JSONArray subDeviceMappings, String refSerialNumber, - JSONObject refItem, int reportIntervalSec) { - for (int i = 0; i < subDeviceMappings.size(); i++) { - JSONObject subDevice = subDeviceMappings.getJSONObject(i); - String monitorSerialNumber = subDevice.getString("serialNumber"); - if (!StringUtils.hasText(monitorSerialNumber) || refSerialNumber.equals(monitorSerialNumber)) { - continue; - } - JSONObject monitorData = bufferService.getMonitorDataIfFresh( - platform.getId(), fieldName, monitorSerialNumber, reportIntervalSec); - if (monitorData == null || monitorData.getJSONObject("itemData") == null) { - continue; - } - - JSONArray sensorData = new JSONArray(); - sensorData.add(refItem); - sensorData.add(monitorData.getJSONObject("itemData")); - JSONArray body = buildArrayPushBody( - mapping, monitorData.getJSONObject("bodyFields"), fieldName, sensorData); - - PlatformPushLog logEntry = createLogEntry(platform, mapping, interfaceCode, monitorSerialNumber); - doHttpPost(platform, baseUrl, JSON.toJSONString(body), logEntry); - pushLogService.asyncSave(logEntry); - if (Integer.valueOf(1).equals(logEntry.getStatus())) { - bufferService.removeMonitorData(platform.getId(), fieldName, monitorSerialNumber); - } - log.info("[DCP] 基准点 {} 上报后补推监测点 {}", refSerialNumber, monitorSerialNumber); - } - } - // ==================== 构建辅助 ==================== private JSONArray buildArrayPushBody(PlatformDeviceMapping mapping, JSONObject bodyFields, - String fieldName, JSONArray sensorData) { + String fieldName, JSONArray sensorData, String deviceId) { JSONObject body = copyJsonObject(bodyFields); body.put(fieldName, sensorData); - if (!body.containsKey("deviceId")) { + if (StringUtils.hasText(deviceId)) { + body.put("deviceId", deviceId); + } else if (!body.containsKey("deviceId")) { body.put("deviceId", mapping.getPlatformDeviceId()); } if (!body.containsKey("ct")) { @@ -746,42 +679,7 @@ public class DcpPlatformAdapter { return null; } - private JSONObject findSubDevice(JSONArray mappings, String serialNumber) { - if (mappings == null || !StringUtils.hasText(serialNumber)) { - return null; - } - for (int i = 0; i < mappings.size(); i++) { - JSONObject mapping = mappings.getJSONObject(i); - if (serialNumber.equals(mapping.getString("serialNumber"))) { - return mapping; - } - } - return null; - } - - private JSONObject findReferenceDevice(JSONArray mappings) { - if (mappings == null || mappings.isEmpty()) { - return null; - } - for (int i = 0; i < mappings.size(); i++) { - JSONObject mapping = mappings.getJSONObject(i); - if (Boolean.TRUE.equals(mapping.getBoolean("isReference"))) { - return mapping; - } - } - return mappings.getJSONObject(0); - } - - private String resolveSubDeviceSensorId(JSONObject subDevice, String fallbackSerialNumber) { - if (subDevice == null) { - return normalizeText(fallbackSerialNumber); - } - String sensorId = normalizeText(subDevice.getString("sensorId")); - if (StringUtils.hasText(sensorId)) { - return sensorId; - } - return normalizeText(subDevice.getString("serialNumber")); - } + // ==================== HTTP 执行 ==================== // ==================== HTTP 执行 ==================== @@ -938,17 +836,6 @@ public class DcpPlatformAdapter { return value == null ? null : value.trim(); } - private boolean sameSerialNumber(String left, String right) { - String normalizedLeft = normalizeSerialNumber(left); - String normalizedRight = normalizeSerialNumber(right); - return StringUtils.hasText(normalizedLeft) && normalizedLeft.equals(normalizedRight); - } - - private String normalizeSerialNumber(String value) { - String text = normalizeText(value); - return StringUtils.hasText(text) ? text.toUpperCase() : null; - } - // ==================== Token 管理 ==================== private String ensureToken(PlatformInstance platform) { diff --git a/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/platform/domain/PlatformDeviceMapping.java b/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/platform/domain/PlatformDeviceMapping.java index 2b60f6d8..ffa72167 100644 --- a/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/platform/domain/PlatformDeviceMapping.java +++ b/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/platform/domain/PlatformDeviceMapping.java @@ -42,6 +42,9 @@ public class PlatformDeviceMapping implements Serializable { @ApiModelProperty("传感器类型: inclino/stress/hyeto/static_level") private String sensorType; + @ApiModelProperty("设备类型: 0=直连设备 1=网关设备(多子设备)") + private Integer deviceType; + @ApiModelProperty("推送接口ID,用于字段映射参照") private Long pushInterfaceId; diff --git a/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/platform/domain/PlatformDeviceSubMapping.java b/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/platform/domain/PlatformDeviceSubMapping.java new file mode 100644 index 00000000..697225c5 --- /dev/null +++ b/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/platform/domain/PlatformDeviceSubMapping.java @@ -0,0 +1,65 @@ +package com.fastbee.iot.platform.domain; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.fasterxml.jackson.annotation.JsonFormat; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.io.Serializable; +import java.util.Date; + +/** + * 平台设备子映射(网关多子设备通用) + */ +@ApiModel(value = "PlatformDeviceSubMapping", description = "平台设备子映射") +@Data +@TableName("platform_device_sub_mapping") +public class PlatformDeviceSubMapping implements Serializable { + private static final long serialVersionUID = 1L; + + @TableId(value = "id", type = IdType.AUTO) + @ApiModelProperty("id") + private Long id; + + @ApiModelProperty("父映射ID") + private Long parentMappingId; + + @ApiModelProperty("子设备序列号") + private String subSerialNumber; + + @ApiModelProperty("角色: reference=基准 monitor=监测") + private String deviceRole; + + @ApiModelProperty("平台分配的设备ID (入网后回填)") + private String platformDeviceId; + + @ApiModelProperty("平台分配的测点/辅助ID (入网后回填)") + private String platformPointId; + + @ApiModelProperty("入网时传给平台的标识参数(平台相关)") + private String onboardConfig; + + @ApiModelProperty("入网状态(0=未入网,1=已入网)") + private Integer netStatus; + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date createTime; + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date updateTime; + + /** 角色常量 */ + public static final String ROLE_REFERENCE = "reference"; + public static final String ROLE_MONITOR = "monitor"; + + public boolean isReference() { + return ROLE_REFERENCE.equals(this.deviceRole); + } + + public boolean isMonitor() { + return ROLE_MONITOR.equals(this.deviceRole); + } +} diff --git a/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/platform/flow/FlowContext.java b/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/platform/flow/FlowContext.java index 7212c816..9263bbe0 100644 --- a/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/platform/flow/FlowContext.java +++ b/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/platform/flow/FlowContext.java @@ -17,6 +17,10 @@ public class FlowContext { private final String payload; /** 步骤出参存储,key = step.resultKey,value = 解析后的 JSON Map */ private final Map stepResults = new HashMap<>(); + /** 网关入网时,指定当前处理的监测点序列号 */ + private String currentMonitorSerialNumber; + /** 网关子设备入网时,当前子映射行的 onboard_config JSON(平台相关参数) */ + private String subMappingOnboardConfig; public FlowContext(PlatformInstance platform, PlatformDeviceMapping deviceMapping, String payload) { this.platform = platform; @@ -44,6 +48,22 @@ public class FlowContext { stepResults.put(key, value); } + public String getCurrentMonitorSerialNumber() { + return currentMonitorSerialNumber; + } + + public void setCurrentMonitorSerialNumber(String currentMonitorSerialNumber) { + this.currentMonitorSerialNumber = currentMonitorSerialNumber; + } + + public String getSubMappingOnboardConfig() { + return subMappingOnboardConfig; + } + + public void setSubMappingOnboardConfig(String subMappingOnboardConfig) { + this.subMappingOnboardConfig = subMappingOnboardConfig; + } + @SuppressWarnings("unchecked") public Object getFromContext(String contextKey) { // contextKey 格式: "step1.access_token" 或 "step1" diff --git a/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/platform/flow/PlatformFlowExecutor.java b/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/platform/flow/PlatformFlowExecutor.java index e50f377c..f2e05633 100644 --- a/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/platform/flow/PlatformFlowExecutor.java +++ b/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/platform/flow/PlatformFlowExecutor.java @@ -289,17 +289,47 @@ public class PlatformFlowExecutor { String df = fd.getString("field"); if ("platform_device_id".equals(df)) return ctx.getDeviceMapping().getPlatformDeviceId(); if ("platform_point_id".equals(df)) return ctx.getDeviceMapping().getPlatformPointId(); - if ("serial_number".equals(df)) return ctx.getDeviceMapping().getSerialNumber(); + if ("serial_number".equals(df)) { + // 网关入网时优先使用当前监测点序列号(每个 point/device 以监测点命名) + if (StringUtils.hasText(ctx.getCurrentMonitorSerialNumber())) { + return ctx.getCurrentMonitorSerialNumber(); + } + return resolveGatewaySerialNumber(ctx.getDeviceMapping()); + } + if ("gateway_serial_number".equals(df)) { + return resolveGatewaySerialNumber(ctx.getDeviceMapping()); + } + if ("monitor_serial_number".equals(df)) { + return ctx.getCurrentMonitorSerialNumber(); + } return null; + case "onboard_config": + // 从子映射的 onboard_config JSON 中取指定字段 + return resolveOnboardConfigField(ctx, fd.getString("field")); case "subDeviceSensorIds": - return collectSubDeviceSensorIds(ctx.getDeviceMapping()); + return collectSubDeviceSensorIds(ctx); case "referenceSubDeviceSensorIdPairs": - return collectReferenceSubDeviceSensorIdPairs(ctx.getDeviceMapping()); + return collectReferenceSubDeviceSensorIdPairs(ctx); default: return null; } } + /** + * 从子映射 onboard_config 中取字段值 + */ + private Object resolveOnboardConfigField(FlowContext ctx, String field) { + if (!StringUtils.hasText(field) || !StringUtils.hasText(ctx.getSubMappingOnboardConfig())) { + return null; + } + try { + JSONObject config = JSON.parseObject(ctx.getSubMappingOnboardConfig()); + return config.get(field); + } catch (Exception e) { + return null; + } + } + private String newGeneratedId() { return UUID.randomUUID().toString(); } @@ -398,8 +428,21 @@ public class PlatformFlowExecutor { .collect(Collectors.toCollection(LinkedHashSet::new)); } - private List collectSubDeviceSensorIds(PlatformDeviceMapping mapping) { - return collectSubDeviceSensorBindings(mapping).stream() + private List collectSubDeviceSensorIds(FlowContext ctx) { + // 网关入网时,从子表 onboard_config 获取 sensorId + if (StringUtils.hasText(ctx.getSubMappingOnboardConfig())) { + try { + JSONObject config = JSON.parseObject(ctx.getSubMappingOnboardConfig()); + List ids = new ArrayList<>(); + String sensorId = config.getString("sensorId"); + String refSensorId = config.getString("refSensorId"); + if (StringUtils.hasText(refSensorId)) ids.add(refSensorId); + if (StringUtils.hasText(sensorId)) ids.add(sensorId); + if (!ids.isEmpty()) return ids; + } catch (Exception ignored) {} + } + // 回退到从 extra_mapping 解析(直连设备等场景) + return collectSubDeviceSensorBindings(ctx.getDeviceMapping()).stream() .map(item -> item.sensorId) .filter(StringUtils::hasText) .distinct() @@ -407,10 +450,25 @@ public class PlatformFlowExecutor { } /** - * 静力水准类成组设备按“基准点 + 监测点”执行绑定。 - * 返回二维数组后由通用批量参数展开逻辑拆成多次 HTTP 请求。 + * 网关入网时:从当前子映射的 onboard_config 取 [refSensorId, sensorId] 一对。 + * 直连设备/旧逻辑:从 extra_mapping 解析所有配对。 */ - private List> collectReferenceSubDeviceSensorIdPairs(PlatformDeviceMapping mapping) { + private List> collectReferenceSubDeviceSensorIdPairs(FlowContext ctx) { + // 网关入网:onboardConfig 已指定当前监测点 + if (StringUtils.hasText(ctx.getSubMappingOnboardConfig())) { + try { + JSONObject config = JSON.parseObject(ctx.getSubMappingOnboardConfig()); + String sensorId = config.getString("sensorId"); + String refSensorId = config.getString("refSensorId"); + if (StringUtils.hasText(sensorId) && StringUtils.hasText(refSensorId)) { + List> pairs = new ArrayList<>(); + pairs.add(Arrays.asList(refSensorId, sensorId)); + return pairs; + } + } catch (Exception ignored) {} + } + // 回退:从 extra_mapping 解析(兼容直连设备场景) + PlatformDeviceMapping mapping = ctx.getDeviceMapping(); List bindings = collectSubDeviceSensorBindings(mapping); if (bindings.size() < 2) { return Collections.emptyList(); @@ -426,6 +484,11 @@ public class PlatformFlowExecutor { || !StringUtils.hasText(binding.sensorId)) { continue; } + // 若指定了当前监测点,只返回该监测点的配对 + if (StringUtils.hasText(ctx.getCurrentMonitorSerialNumber()) + && !sameText(ctx.getCurrentMonitorSerialNumber(), binding.serialNumber)) { + continue; + } pairs.add(Arrays.asList(reference.sensorId, binding.sensorId)); } return pairs; diff --git a/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/platform/mapper/PlatformDeviceSubMappingMapper.java b/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/platform/mapper/PlatformDeviceSubMappingMapper.java new file mode 100644 index 00000000..588134cb --- /dev/null +++ b/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/platform/mapper/PlatformDeviceSubMappingMapper.java @@ -0,0 +1,17 @@ +package com.fastbee.iot.platform.mapper; + +import com.fastbee.framework.mybatis.mapper.BaseMapperX; +import com.fastbee.iot.platform.domain.PlatformDeviceSubMapping; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +public interface PlatformDeviceSubMappingMapper extends BaseMapperX { + + List selectByParentId(@Param("parentMappingId") Long parentMappingId); + + PlatformDeviceSubMapping selectByParentAndSerial(@Param("parentMappingId") Long parentMappingId, + @Param("subSerialNumber") String subSerialNumber); + + List selectBySubSerialNumber(@Param("subSerialNumber") String subSerialNumber); +} diff --git a/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/platform/service/IPlatformDeviceSubMappingService.java b/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/platform/service/IPlatformDeviceSubMappingService.java new file mode 100644 index 00000000..9eeacffe --- /dev/null +++ b/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/platform/service/IPlatformDeviceSubMappingService.java @@ -0,0 +1,21 @@ +package com.fastbee.iot.platform.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.fastbee.iot.platform.domain.PlatformDeviceSubMapping; + +import java.util.List; + +public interface IPlatformDeviceSubMappingService extends IService { + + /** 查询父映射下的所有子映射 */ + List selectByParentId(Long parentMappingId); + + /** 根据子设备序列号查找子映射(推送时使用) */ + List selectBySubSerialNumber(String subSerialNumber); + + /** 查询父映射下指定角色的子映射 */ + PlatformDeviceSubMapping findReference(Long parentMappingId); + + /** 批量同步子映射(保存时前端传入的子设备列表,全量替换) */ + void syncSubMappings(Long parentMappingId, List subMappings); +} diff --git a/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/platform/service/impl/PlatformDeviceMappingServiceImpl.java b/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/platform/service/impl/PlatformDeviceMappingServiceImpl.java index 24c990be..9998485c 100644 --- a/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/platform/service/impl/PlatformDeviceMappingServiceImpl.java +++ b/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/platform/service/impl/PlatformDeviceMappingServiceImpl.java @@ -4,6 +4,7 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.fastbee.iot.platform.domain.PlatformDeviceMapping; +import com.fastbee.iot.platform.domain.PlatformDeviceSubMapping; import com.fastbee.iot.platform.domain.PlatformFlow; import com.fastbee.iot.platform.domain.PlatformInstance; import com.fastbee.iot.platform.flow.FlowContext; @@ -12,12 +13,15 @@ import com.fastbee.iot.platform.mapper.PlatformDeviceMappingMapper; import com.fastbee.iot.platform.mapper.PlatformFlowMapper; import com.fastbee.iot.platform.mapper.PlatformInstanceMapper; import com.fastbee.iot.platform.service.IPlatformDeviceMappingService; +import com.fastbee.iot.platform.service.IPlatformDeviceSubMappingService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; +import java.util.ArrayList; import java.util.List; +import java.util.stream.Collectors; @Slf4j @Service @@ -32,6 +36,8 @@ public class PlatformDeviceMappingServiceImpl extends ServiceImpl selectList(PlatformDeviceMapping query) { @@ -53,12 +59,15 @@ public class PlatformDeviceMappingServiceImpl extends ServiceImpllambdaQuery() .eq(PlatformFlow::getPlatformId, mapping.getPlatformId()) .eq(PlatformFlow::getFlowType, 2) @@ -69,15 +78,91 @@ public class PlatformDeviceMappingServiceImpl extends ServiceImpl subMappings = subMappingService.selectByParentId(mapping.getId()); + List monitors = subMappings.stream() + .filter(s -> s.isMonitor() && (s.getNetStatus() == null || s.getNetStatus() == 0)) + .collect(Collectors.toList()); + + if (monitors.isEmpty()) { + throw new RuntimeException("子表中无待入网的监测点,请先配置子设备映射"); + } + + int successCount = 0; + List failedMessages = new ArrayList<>(); + + for (PlatformDeviceSubMapping monitor : monitors) { + FlowContext ctx = new FlowContext(platform, mapping, null); + ctx.setCurrentMonitorSerialNumber(monitor.getSubSerialNumber()); + ctx.setSubMappingOnboardConfig(monitor.getOnboardConfig()); + + try { + flowExecutor.execute(onboardFlow, ctx); + } catch (Exception e) { + log.error("[Onboard] 网关子设备 {} 入网失败: {}", monitor.getSubSerialNumber(), e.getMessage(), e); + failedMessages.add(monitor.getSubSerialNumber() + ": " + e.getMessage()); + continue; + } + + // 从流程上下文取回平台分配的 ID + Object pointId = ctx.getFromContext("step1.id"); + Object deviceId = ctx.getFromContext("step2.id"); + + if (deviceId != null) { + monitor.setPlatformDeviceId(deviceId.toString()); + } + if (pointId != null) { + monitor.setPlatformPointId(pointId.toString()); + } + if (StringUtils.hasText(monitor.getPlatformDeviceId())) { + monitor.setNetStatus(1); + subMappingService.updateById(monitor); + successCount++; + } + } + + if (successCount == 0) { + throw new RuntimeException("网关所有监测点入网均失败:\n" + String.join("\n", failedMessages)); + } + + // 检查是否所有 monitor 子行都已入网 + List allSubs = subMappingService.selectByParentId(mapping.getId()); + boolean allMonitorsOnboarded = allSubs.stream() + .filter(PlatformDeviceSubMapping::isMonitor) + .allMatch(s -> s.getNetStatus() != null && s.getNetStatus() == 1); + + if (allMonitorsOnboarded) { + mapping.setNetStatus(1); + this.updateById(mapping); + } else { + // 部分成功部分失败,抛出明确信息让前端知道 + throw new RuntimeException("部分监测点入网成功(" + successCount + "/" + monitors.size() + "),失败:\n" + + String.join("\n", failedMessages)); + } + } } diff --git a/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/platform/service/impl/PlatformDeviceSubMappingServiceImpl.java b/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/platform/service/impl/PlatformDeviceSubMappingServiceImpl.java new file mode 100644 index 00000000..55608d2f --- /dev/null +++ b/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/platform/service/impl/PlatformDeviceSubMappingServiceImpl.java @@ -0,0 +1,77 @@ +package com.fastbee.iot.platform.service.impl; + +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.fastbee.iot.platform.domain.PlatformDeviceSubMapping; +import com.fastbee.iot.platform.mapper.PlatformDeviceSubMappingMapper; +import com.fastbee.iot.platform.service.IPlatformDeviceSubMappingService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +@Service +public class PlatformDeviceSubMappingServiceImpl extends ServiceImpl + implements IPlatformDeviceSubMappingService { + + @Autowired + private PlatformDeviceSubMappingMapper subMappingMapper; + + @Override + public List selectByParentId(Long parentMappingId) { + return subMappingMapper.selectByParentId(parentMappingId); + } + + @Override + public List selectBySubSerialNumber(String subSerialNumber) { + return subMappingMapper.selectBySubSerialNumber(subSerialNumber); + } + + @Override + public PlatformDeviceSubMapping findReference(Long parentMappingId) { + List all = selectByParentId(parentMappingId); + return all.stream() + .filter(PlatformDeviceSubMapping::isReference) + .findFirst() + .orElse(null); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void syncSubMappings(Long parentMappingId, List subMappings) { + List existing = selectByParentId(parentMappingId); + Set incomingSerials = subMappings.stream() + .map(PlatformDeviceSubMapping::getSubSerialNumber) + .collect(Collectors.toSet()); + + // 删除不再存在的子映射 + for (PlatformDeviceSubMapping old : existing) { + if (!incomingSerials.contains(old.getSubSerialNumber())) { + this.removeById(old.getId()); + } + } + + // 新增或更新 + for (PlatformDeviceSubMapping sub : subMappings) { + sub.setParentMappingId(parentMappingId); + PlatformDeviceSubMapping existingRow = subMappingMapper.selectByParentAndSerial( + parentMappingId, sub.getSubSerialNumber()); + if (existingRow != null) { + sub.setId(existingRow.getId()); + // 保留已入网的平台ID + if (existingRow.getNetStatus() != null && existingRow.getNetStatus() == 1) { + sub.setPlatformDeviceId(existingRow.getPlatformDeviceId()); + sub.setPlatformPointId(existingRow.getPlatformPointId()); + sub.setNetStatus(existingRow.getNetStatus()); + } + this.updateById(sub); + } else { + sub.setNetStatus(0); + this.save(sub); + } + } + } +} diff --git a/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/platform/trigger/DeviceReportTrigger.java b/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/platform/trigger/DeviceReportTrigger.java index 07c54163..cc21f22c 100644 --- a/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/platform/trigger/DeviceReportTrigger.java +++ b/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/platform/trigger/DeviceReportTrigger.java @@ -3,8 +3,10 @@ package com.fastbee.iot.platform.trigger; import com.fastbee.common.constant.FastBeeConstant; import com.fastbee.iot.platform.adapter.dcp.DcpPlatformAdapter; import com.fastbee.iot.platform.domain.PlatformDeviceMapping; +import com.fastbee.iot.platform.domain.PlatformDeviceSubMapping; import com.fastbee.iot.platform.domain.PlatformInstance; import com.fastbee.iot.platform.service.IPlatformDeviceMappingService; +import com.fastbee.iot.platform.service.IPlatformDeviceSubMappingService; import com.fastbee.iot.platform.service.IPlatformInstanceService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; @@ -16,9 +18,6 @@ import java.util.List; /** * 设备上报触发器 * 挂接点:在 RuleProcess.processRuleScript(serialNumber, 1, topic, payload) 调用方之后异步调用 - * 示例: - * ruleProcess.processRuleScript(serialNumber, 1, topic, payload); - * deviceReportTrigger.onDeviceReport(serialNumber, payload); // 异步,不阻塞主链路 */ @Slf4j @Component @@ -27,6 +26,8 @@ public class DeviceReportTrigger { @Autowired private IPlatformDeviceMappingService mappingService; @Autowired + private IPlatformDeviceSubMappingService subMappingService; + @Autowired private IPlatformInstanceService instanceService; @Autowired private DcpPlatformAdapter dcpAdapter; @@ -56,13 +57,44 @@ public class DeviceReportTrigger { } /** 根据平台 code 路由到对应 Adapter */ - private void dispatchPush(PlatformInstance platform, PlatformDeviceMapping mapping, String reportSerialNumber, String payload) { + private void dispatchPush(PlatformInstance platform, PlatformDeviceMapping mapping, + String reportSerialNumber, String payload) { switch (platform.getCode()) { case "dcp": - dcpAdapter.pushData(platform, mapping, reportSerialNumber, payload); + if (mapping.getDeviceType() != null && mapping.getDeviceType() == 1) { + // 网关设备:查子表确定该 serialNumber 的角色和 platformDeviceId + dispatchGatewayPush(platform, mapping, reportSerialNumber, payload); + } else { + // 直连设备 + dcpAdapter.pushData(platform, mapping, reportSerialNumber, payload); + } break; default: log.warn("[DeviceReportTrigger] 未知平台 code: {}", platform.getCode()); } } + + /** + * 网关设备推送分发: + * - 上报者是子表中的 monitor → 传入 subMapping,adapter 用其 platformDeviceId 推送 + * - 上报者是子表中的 reference → adapter 仅缓存基准数据 + * - 上报者是网关本身(serial_number 匹配主表) → 判断是否在子表中标记为 reference + */ + private void dispatchGatewayPush(PlatformInstance platform, PlatformDeviceMapping mapping, + String reportSerialNumber, String payload) { + // 查找该序列号在子表中的记录 + List subMappings = subMappingService.selectByParentId(mapping.getId()); + PlatformDeviceSubMapping matchedSub = subMappings.stream() + .filter(s -> reportSerialNumber.equals(s.getSubSerialNumber())) + .findFirst() + .orElse(null); + + if (matchedSub == null) { + // serialNumber 匹配主表但不在子表中(网关本身上报但未配置角色),跳过 + log.debug("[DeviceReportTrigger] 网关设备 {} 上报但不在子映射中,跳过", reportSerialNumber); + return; + } + + dcpAdapter.pushGatewayData(platform, mapping, matchedSub, reportSerialNumber, payload); + } } diff --git a/fastbee-service/fastbee-iot-service/src/main/resources/mapper/iot/platform/PlatformDeviceMappingMapper.xml b/fastbee-service/fastbee-iot-service/src/main/resources/mapper/iot/platform/PlatformDeviceMappingMapper.xml index d28fd0ff..0c6913f3 100644 --- a/fastbee-service/fastbee-iot-service/src/main/resources/mapper/iot/platform/PlatformDeviceMappingMapper.xml +++ b/fastbee-service/fastbee-iot-service/src/main/resources/mapper/iot/platform/PlatformDeviceMappingMapper.xml @@ -10,6 +10,7 @@ + @@ -23,15 +24,14 @@ diff --git a/fastbee-service/fastbee-iot-service/src/main/resources/mapper/iot/platform/PlatformDeviceSubMappingMapper.xml b/fastbee-service/fastbee-iot-service/src/main/resources/mapper/iot/platform/PlatformDeviceSubMappingMapper.xml new file mode 100644 index 00000000..7f34adf9 --- /dev/null +++ b/fastbee-service/fastbee-iot-service/src/main/resources/mapper/iot/platform/PlatformDeviceSubMappingMapper.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + diff --git a/sql/platform-integration.sql b/sql/platform-integration.sql index 03879d81..994a0d1c 100644 --- a/sql/platform-integration.sql +++ b/sql/platform-integration.sql @@ -96,6 +96,7 @@ CREATE TABLE IF NOT EXISTS `platform_device_mapping` ( `platform_device_id` varchar(100) DEFAULT NULL COMMENT 'DCP侧deviceId,入网后自动回填', `platform_point_id` varchar(100) DEFAULT NULL COMMENT 'DCP侧pointId,入网后自动回填', `sensor_type` varchar(50) NOT NULL COMMENT '传感器类型: inclino/stress/hyeto/static_level', + `device_type` tinyint(1) NOT NULL DEFAULT 0 COMMENT '设备类型: 0=直连设备 1=网关设备(多子设备)', `push_interface_id` bigint(20) DEFAULT NULL COMMENT '推送接口ID,用于字段映射参照', `field_mapping` json DEFAULT NULL COMMENT '字段映射 [{identifier,platformField,transform}]', `extra_mapping` json DEFAULT NULL COMMENT '扩展映射配置,如静力水准子设备编号与DCP sensorId映射', @@ -111,6 +112,33 @@ CREATE TABLE IF NOT EXISTS `platform_device_mapping` ( KEY `idx_platform_device` (`platform_id`, `device_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='平台设备绑定关系'; +-- ---------------------------- +-- 5.1 设备绑定关系 - 新增 device_type 字段 +-- ---------------------------- +ALTER TABLE `platform_device_mapping` + ADD COLUMN IF NOT EXISTS `device_type` tinyint(1) NOT NULL DEFAULT 0 + COMMENT '设备类型: 0=直连设备 1=网关设备(多子设备)' AFTER `sensor_type`; + +-- ---------------------------- +-- 5.2 子设备映射(网关多子设备通用) +-- ---------------------------- +CREATE TABLE IF NOT EXISTS `platform_device_sub_mapping` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id', + `parent_mapping_id` bigint(20) NOT NULL COMMENT '父映射ID (platform_device_mapping.id)', + `sub_serial_number` varchar(64) NOT NULL COMMENT '子设备序列号', + `device_role` varchar(20) NOT NULL DEFAULT 'monitor' COMMENT '角色: reference=基准 monitor=监测', + `platform_device_id` varchar(100) DEFAULT NULL COMMENT '平台分配的设备ID (入网后回填)', + `platform_point_id` varchar(100) DEFAULT NULL COMMENT '平台分配的测点/辅助ID (入网后回填)', + `onboard_config` json DEFAULT NULL COMMENT '入网时传给平台的标识参数(平台相关)', + `net_status` tinyint(1) NOT NULL DEFAULT 0 COMMENT '0=未入网 1=已入网', + `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (`id`), + KEY `idx_parent` (`parent_mapping_id`), + KEY `idx_sub_serial` (`sub_serial_number`), + UNIQUE KEY `uk_parent_sub` (`parent_mapping_id`, `sub_serial_number`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='平台设备子映射(网关多子设备通用)'; + -- ---------------------------- -- 6. 推送日志 -- ---------------------------- diff --git a/yack-web/src/api/platform/deviceMapping.js b/yack-web/src/api/platform/deviceMapping.js index 615a718d..380ee19f 100644 --- a/yack-web/src/api/platform/deviceMapping.js +++ b/yack-web/src/api/platform/deviceMapping.js @@ -23,3 +23,11 @@ export function delPlatformDeviceMapping(id) { export function onboardDevice(id) { return request({ url: `/platform/device-mapping/${id}/onboard`, method: 'post' }) } + +export function listSubMappings(parentId) { + return request({ url: `/platform/device-mapping/${parentId}/sub-mappings`, method: 'get' }) +} + +export function syncSubMappings(parentId, data) { + return request({ url: `/platform/device-mapping/${parentId}/sub-mappings`, method: 'post', data }) +} diff --git a/yack-web/src/views/platform/device-mapping/index.vue b/yack-web/src/views/platform/device-mapping/index.vue index c4b9b03c..65f65d50 100644 --- a/yack-web/src/views/platform/device-mapping/index.vue +++ b/yack-web/src/views/platform/device-mapping/index.vue @@ -34,7 +34,29 @@ - + + + + @@ -42,13 +64,24 @@ - + - - + + + + + +