20260608:静力水准仪接入平台
This commit is contained in:
parent
ecc6b1677c
commit
e7d19dda6f
@ -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:*)"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
* <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) {
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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"
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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);
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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<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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
@ -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. 推送日志
|
||||
-- ----------------------------
|
||||
|
||||
@ -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
Loading…
Reference in New Issue
Block a user