20260608:静力水准仪接入平台

This commit is contained in:
zhugongping 2026-06-08 09:01:36 +08:00
parent ecc6b1677c
commit e7d19dda6f
17 changed files with 943 additions and 864 deletions

View File

@ -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:*)"
]
}
}

View File

@ -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<PlatformDeviceSubMapping> subMappings) {
subMappingService.syncSubMappings(id, subMappings);
return success();
}
}

View File

@ -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);
}
} catch (Exception e) {
String serialNumber = StringUtils.hasText(reportSerialNumber)
? reportSerialNumber : mapping.getSerialNumber();
@ -117,6 +111,134 @@ public class DcpPlatformAdapter {
}
}
/**
* 推送网关子设备上报数据到 DCP
* <p>
* 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) {

View File

@ -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;

View File

@ -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);
}
}

View File

@ -17,6 +17,10 @@ public class FlowContext {
private final String payload;
/** 步骤出参存储,key = step.resultKey,value = 解析后的 JSON Map */
private final Map<String, Object> 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"

View File

@ -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<String> collectSubDeviceSensorIds(PlatformDeviceMapping mapping) {
return collectSubDeviceSensorBindings(mapping).stream()
private List<String> collectSubDeviceSensorIds(FlowContext ctx) {
// 网关入网时,从子表 onboard_config 获取 sensorId
if (StringUtils.hasText(ctx.getSubMappingOnboardConfig())) {
try {
JSONObject config = JSON.parseObject(ctx.getSubMappingOnboardConfig());
List<String> 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<List<String>> collectReferenceSubDeviceSensorIdPairs(PlatformDeviceMapping mapping) {
private List<List<String>> 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<List<String>> pairs = new ArrayList<>();
pairs.add(Arrays.asList(refSensorId, sensorId));
return pairs;
}
} catch (Exception ignored) {}
}
// 回退:从 extra_mapping 解析(兼容直连设备场景)
PlatformDeviceMapping mapping = ctx.getDeviceMapping();
List<SubDeviceSensorBinding> 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;

View File

@ -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<PlatformDeviceSubMapping> {
List<PlatformDeviceSubMapping> selectByParentId(@Param("parentMappingId") Long parentMappingId);
PlatformDeviceSubMapping selectByParentAndSerial(@Param("parentMappingId") Long parentMappingId,
@Param("subSerialNumber") String subSerialNumber);
List<PlatformDeviceSubMapping> selectBySubSerialNumber(@Param("subSerialNumber") String subSerialNumber);
}

View File

@ -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<PlatformDeviceSubMapping> {
/** 查询父映射下的所有子映射 */
List<PlatformDeviceSubMapping> selectByParentId(Long parentMappingId);
/** 根据子设备序列号查找子映射(推送时使用) */
List<PlatformDeviceSubMapping> selectBySubSerialNumber(String subSerialNumber);
/** 查询父映射下指定角色的子映射 */
PlatformDeviceSubMapping findReference(Long parentMappingId);
/** 批量同步子映射(保存时前端传入的子设备列表,全量替换) */
void syncSubMappings(Long parentMappingId, List<PlatformDeviceSubMapping> subMappings);
}

View File

@ -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<PlatformDevice
private PlatformFlowMapper flowMapper;
@Autowired
private PlatformFlowExecutor flowExecutor;
@Autowired
private IPlatformDeviceSubMappingService subMappingService;
@Override
public List<PlatformDeviceMapping> selectList(PlatformDeviceMapping query) {
@ -53,12 +59,15 @@ public class PlatformDeviceMappingServiceImpl extends ServiceImpl<PlatformDevice
@Override
public void onboard(Long mappingId) {
PlatformDeviceMapping mapping = this.getById(mappingId);
if (mapping == null) throw new RuntimeException("设备绑定关系不存在: " + mappingId);
if (mapping == null) {
throw new RuntimeException("设备绑定关系不存在: " + mappingId);
}
PlatformInstance platform = instanceMapper.selectById(mapping.getPlatformId());
if (platform == null) throw new RuntimeException("平台不存在: " + mapping.getPlatformId());
if (platform == null) {
throw new RuntimeException("平台不存在: " + mapping.getPlatformId());
}
// 查找 flow_type=2 且 sensor_type 匹配的入网流程
PlatformFlow onboardFlow = flowMapper.selectOne(Wrappers.<PlatformFlow>lambdaQuery()
.eq(PlatformFlow::getPlatformId, mapping.getPlatformId())
.eq(PlatformFlow::getFlowType, 2)
@ -69,15 +78,91 @@ public class PlatformDeviceMappingServiceImpl extends ServiceImpl<PlatformDevice
throw new RuntimeException("未找到传感器类型 [" + mapping.getSensorType() + "] 的入网流程");
}
if (mapping.getDeviceType() != null && mapping.getDeviceType() == 1) {
onboardGateway(mapping, platform, onboardFlow);
} else {
onboardDirect(mapping, platform, onboardFlow);
}
}
/**
* 直连设备入网:结果写主表 platform_device_id / platform_point_id
*/
private void onboardDirect(PlatformDeviceMapping mapping, PlatformInstance platform, PlatformFlow onboardFlow) {
FlowContext ctx = new FlowContext(platform, mapping, null);
flowExecutor.execute(onboardFlow, ctx);
// 入网成功后更新状态(platformDeviceId/platformPointId 已在 persistResultSave 中回填)
// 重新读取最新数据
PlatformDeviceMapping updated = this.getById(mappingId);
// persistResultSave 已在流程执行中回填了主表字段,重新读取
PlatformDeviceMapping updated = this.getById(mapping.getId());
if (StringUtils.hasText(updated.getPlatformDeviceId())) {
updated.setNetStatus(1);
this.updateById(updated);
}
}
/**
* 网关设备入网:遍历子表中 device_role=monitor 且未入网的行,逐行执行入网流程
*/
private void onboardGateway(PlatformDeviceMapping mapping, PlatformInstance platform, PlatformFlow onboardFlow) {
List<PlatformDeviceSubMapping> subMappings = subMappingService.selectByParentId(mapping.getId());
List<PlatformDeviceSubMapping> 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<String> 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<PlatformDeviceSubMapping> 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));
}
}
}

View File

@ -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<PlatformDeviceSubMappingMapper, PlatformDeviceSubMapping>
implements IPlatformDeviceSubMappingService {
@Autowired
private PlatformDeviceSubMappingMapper subMappingMapper;
@Override
public List<PlatformDeviceSubMapping> selectByParentId(Long parentMappingId) {
return subMappingMapper.selectByParentId(parentMappingId);
}
@Override
public List<PlatformDeviceSubMapping> selectBySubSerialNumber(String subSerialNumber) {
return subMappingMapper.selectBySubSerialNumber(subSerialNumber);
}
@Override
public PlatformDeviceSubMapping findReference(Long parentMappingId) {
List<PlatformDeviceSubMapping> all = selectByParentId(parentMappingId);
return all.stream()
.filter(PlatformDeviceSubMapping::isReference)
.findFirst()
.orElse(null);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void syncSubMappings(Long parentMappingId, List<PlatformDeviceSubMapping> subMappings) {
List<PlatformDeviceSubMapping> existing = selectByParentId(parentMappingId);
Set<String> 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);
}
}
}
}

View File

@ -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":
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<PlatformDeviceSubMapping> 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);
}
}

View File

@ -10,6 +10,7 @@
<result property="platformDeviceId" column="platform_device_id"/>
<result property="platformPointId" column="platform_point_id"/>
<result property="sensorType" column="sensor_type"/>
<result property="deviceType" column="device_type"/>
<result property="pushInterfaceId" column="push_interface_id"/>
<result property="fieldMapping" column="field_mapping"/>
<result property="extraMapping" column="extra_mapping"/>
@ -23,15 +24,14 @@
</resultMap>
<select id="selectBySerialNumber" resultMap="PlatformDeviceMappingResult">
SELECT * FROM platform_device_mapping
WHERE enable = '1'
AND (
serial_number = #{serialNumber}
OR JSON_SEARCH(extra_mapping, 'one', #{serialNumber}, NULL,
'$.arrayFields[*].subDeviceMappings[*].serialNumber') IS NOT NULL
OR JSON_SEARCH(extra_mapping, 'one', #{serialNumber}, NULL,
'$.staticLevel.sensorIdMappings[*].serialNumber') IS NOT NULL
)
<!-- 直连设备 + 网关主设备(基准点上报) -->
SELECT m.* FROM platform_device_mapping m
WHERE m.serial_number = #{serialNumber} AND m.enable = '1'
UNION
<!-- 网关子设备(监测点上报) — 通过子表关联 -->
SELECT m.* FROM platform_device_mapping m
INNER JOIN platform_device_sub_mapping s ON s.parent_mapping_id = m.id
WHERE s.sub_serial_number = #{serialNumber} AND m.enable = '1'
</select>
</mapper>

View File

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.fastbee.iot.platform.mapper.PlatformDeviceSubMappingMapper">
<select id="selectByParentId" resultType="com.fastbee.iot.platform.domain.PlatformDeviceSubMapping">
SELECT * FROM platform_device_sub_mapping
WHERE parent_mapping_id = #{parentMappingId}
ORDER BY device_role DESC, id ASC
</select>
<select id="selectByParentAndSerial" resultType="com.fastbee.iot.platform.domain.PlatformDeviceSubMapping">
SELECT * FROM platform_device_sub_mapping
WHERE parent_mapping_id = #{parentMappingId}
AND sub_serial_number = #{subSerialNumber}
LIMIT 1
</select>
<select id="selectBySubSerialNumber" resultType="com.fastbee.iot.platform.domain.PlatformDeviceSubMapping">
SELECT * FROM platform_device_sub_mapping
WHERE sub_serial_number = #{subSerialNumber}
</select>
</mapper>

View File

@ -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. 推送日志
-- ----------------------------

View File

@ -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 })
}

File diff suppressed because it is too large Load Diff