20260728:平台对接优化

This commit is contained in:
zhugongping 2026-07-28 08:49:41 +08:00
parent be2e868ef5
commit 7877a3ccec
17 changed files with 467 additions and 114 deletions

View File

@ -28,7 +28,9 @@
"Bash(npm run:*)", "Bash(npm run:*)",
"mcp__filesystem__read_text_file", "mcp__filesystem__read_text_file",
"Bash(sips:*)", "Bash(sips:*)",
"Bash(mysql -h localhost -u root -pfastbee fastbee -e \"SELECT id, name, config_json FROM bridge WHERE id = 9;\")" "Bash(mysql -h localhost -u root -pfastbee fastbee -e \"SELECT id, name, config_json FROM bridge WHERE id = 9;\")",
"Bash(mysql:*)",
"Bash(git:*)"
] ]
} }
} }

View File

@ -299,6 +299,11 @@ public interface FastBeeConstant {
*/ */
String DATA_REPAIR_CONFIG_KEY = "data:repair:config"; String DATA_REPAIR_CONFIG_KEY = "data:repair:config";
/**
* 平台对接KEYS
* DCP平台项目列表缓存key
*/
String DCP_PROJECT_INFO_KEY = "platform:dcp:projectInfoKey:";
} }

View File

@ -161,6 +161,10 @@ public class RedisKeyBuilder {
return FastBeeConstant.REDIS.MODBUS_TCP_RUNTIME + serialNumber; return FastBeeConstant.REDIS.MODBUS_TCP_RUNTIME + serialNumber;
} }
public static String buildDCPProjectInfoKey(String platformUserId) {
return FastBeeConstant.REDIS.DCP_PROJECT_INFO_KEY + platformUserId;
}
/** /**
* 设备OTA升级实时数据 * 设备OTA升级实时数据

View File

@ -68,9 +68,16 @@ public class PlatformInstanceController extends BaseController {
@PreAuthorize("@ss.hasPermi('platform:instance:edit')") @PreAuthorize("@ss.hasPermi('platform:instance:edit')")
@PostMapping("/{id}/initialize") @PostMapping("/{id}/initialize")
@ApiOperation("初始化平台(执行初始化流程,获取userId/projectId等)") @ApiOperation("初始化平台(执行初始化流程,获取userId)")
public AjaxResult initialize(@PathVariable Long id) { public AjaxResult initialize(@PathVariable Long id) {
instanceService.initialize(id); instanceService.initialize(id);
return success("初始化成功"); return success("初始化成功");
} }
@PreAuthorize("@ss.hasAnyPermi('platform:instance:list,platform:deviceMapping:list,platform:deviceMapping:add,platform:deviceMapping:edit')")
@GetMapping("/{id}/projects")
@ApiOperation("查询平台项目列表")
public AjaxResult getProjects(@PathVariable Long id) {
return success(instanceService.getProjectsByPlatformId(id));
}
} }

View File

@ -28,6 +28,12 @@ public class PlatformDeviceMapping implements Serializable {
@ApiModelProperty("所属平台ID") @ApiModelProperty("所属平台ID")
private Long platformId; private Long platformId;
@ApiModelProperty("DCP项目ID,绑定页面用户自己选择")
private String platformProjectId;
@ApiModelProperty("DCP项目名称")
private String platformProjectName;
@ApiModelProperty("FastBee设备ID") @ApiModelProperty("FastBee设备ID")
private Long deviceId; private Long deviceId;

View File

@ -1,9 +1,11 @@
package com.fastbee.iot.platform.flow; package com.fastbee.iot.platform.flow;
import com.fastbee.iot.platform.domain.PlatformDeviceMapping; 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.domain.PlatformInstance;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
/** /**
@ -19,6 +21,9 @@ public class FlowContext {
* 绑定推送关系 * 绑定推送关系
*/ */
private final PlatformDeviceMapping deviceMapping; private final PlatformDeviceMapping deviceMapping;
// 绑定推送子关系
private List<PlatformDeviceSubMapping> monitorSubMappings;
/** /**
* 设备上报的原始 payload(实时推送时有值) * 设备上报的原始 payload(实时推送时有值)
*/ */
@ -107,6 +112,14 @@ public class FlowContext {
this.currentMonitorIndex = currentMonitorIndex; this.currentMonitorIndex = currentMonitorIndex;
} }
public List<PlatformDeviceSubMapping> getMonitorSubMappings() {
return monitorSubMappings;
}
public void setMonitorSubMappings(List<PlatformDeviceSubMapping> monitorSubMappings) {
this.monitorSubMappings = monitorSubMappings;
}
public String getSubMappingOnboardConfig() { public String getSubMappingOnboardConfig() {
return subMappingOnboardConfig; return subMappingOnboardConfig;
} }

View File

@ -21,6 +21,7 @@ import com.fastbee.iot.platform.mapper.PlatformInterfaceMapper;
import com.fastbee.iot.platform.service.IPlatformPushLogService; import com.fastbee.iot.platform.service.IPlatformPushLogService;
import com.dtflys.forest.http.ForestRequestType; import com.dtflys.forest.http.ForestRequestType;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
@ -293,6 +294,10 @@ public class PlatformFlowExecutor {
String df = fd.getString("field"); String df = fd.getString("field");
if ("platform_device_id".equals(df)) return ctx.getDeviceMapping().getPlatformDeviceId(); if ("platform_device_id".equals(df)) return ctx.getDeviceMapping().getPlatformDeviceId();
if ("platform_point_id".equals(df)) return ctx.getDeviceMapping().getPlatformPointId(); if ("platform_point_id".equals(df)) return ctx.getDeviceMapping().getPlatformPointId();
// 支持从设备绑定表读取项目信息
if ("platform_project_id".equals(df)) {
return ctx.getDeviceMapping().getPlatformProjectId();
}
if ("serial_number".equals(df)) { if ("serial_number".equals(df)) {
// 网关入网时优先使用当前监测点序列号(每个 point/device 以监测点命名) // 网关入网时优先使用当前监测点序列号(每个 point/device 以监测点命名)
if (StringUtils.hasText(ctx.getCurrentMonitorSerialNumber())) { if (StringUtils.hasText(ctx.getCurrentMonitorSerialNumber())) {
@ -321,7 +326,7 @@ public class PlatformFlowExecutor {
case "cascadedSubDeviceSensorIds": case "cascadedSubDeviceSensorIds":
return collectCascadedSubDeviceSensorIds(ctx); return collectCascadedSubDeviceSensorIds(ctx);
case "cascadedSubDeviceDepths": case "cascadedSubDeviceDepths":
return collectCascadedSubDeviceDepths(ctx, fd); return collectCascadedSubDeviceDepths(ctx);
case "referenceSubDeviceSensorIdPairs": case "referenceSubDeviceSensorIdPairs":
return collectReferenceSubDeviceSensorIdPairs(ctx); return collectReferenceSubDeviceSensorIdPairs(ctx);
default: default:
@ -403,20 +408,19 @@ public class PlatformFlowExecutor {
private Object resolveOnboardConfigValue(FlowContext ctx, JSONObject fieldDef) { private Object resolveOnboardConfigValue(FlowContext ctx, JSONObject fieldDef) {
String name = fieldDef.getString("name"); String name = fieldDef.getString("name");
if (!fieldDef.containsKey("value")) { List<PlatformDeviceSubMapping> monitorSubMappings = ctx.getMonitorSubMappings();
throw new RuntimeException("入网参数 " + name + " 未配置值"); if (ctx.getCurrentMonitorIndex() != null && CollectionUtils.isNotEmpty(monitorSubMappings)) {
}
Object value = fieldDef.get("value");
if (value instanceof JSONArray && ctx.getCurrentMonitorIndex() != null) {
JSONArray values = (JSONArray) value;
int index = ctx.getCurrentMonitorIndex(); int index = ctx.getCurrentMonitorIndex();
if (index >= values.size()) { PlatformDeviceSubMapping platformDeviceSubMapping = monitorSubMappings.get(index);
throw new RuntimeException("入网参数 " + name + " 数组长度不足,当前子设备序号=" JSONObject authConfig = StringUtils.hasText(platformDeviceSubMapping.getOnboardConfig())
+ (index + 1) + ",数组长度=" + values.size()); ? JSON.parseObject(platformDeviceSubMapping.getOnboardConfig()) : new JSONObject();
String value = authConfig.getString(name);
if (value == null) {
throw new RuntimeException("入网失败,子设备" + platformDeviceSubMapping.getSubDeviceName() + "未配置值:" + name);
} }
return values.get(index); return value;
} }
return value; throw new RuntimeException("入网失败," + name + "未配置值");
} }
private String newGeneratedId() { private String newGeneratedId() {
@ -555,21 +559,26 @@ public class PlatformFlowExecutor {
private List<String> collectCascadedSubDeviceSensorIds(FlowContext ctx) { private List<String> collectCascadedSubDeviceSensorIds(FlowContext ctx) {
List<PlatformDeviceSubMapping> suffix = collectCascadedSubDeviceSuffix(ctx); List<PlatformDeviceSubMapping> suffix = collectCascadedSubDeviceSuffix(ctx);
Collections.reverse(suffix); Collections.reverse(suffix);
return suffix.stream() List<String> sensorIds = suffix.stream()
.map(sub -> resolveOnboardSensorId(sub.getOnboardConfig(), sub.getSubSerialNumber())) .map(sub -> resolveOnboardSensorId(sub.getOnboardConfig(), sub.getSubSerialNumber()))
.filter(StringUtils::hasText)
.collect(Collectors.toList()); .collect(Collectors.toList());
if (sensorIds.stream().anyMatch(id -> !StringUtils.hasText(id))) {
throw new RuntimeException("深部位移子设备缺少 sensorId");
}
return sensorIds;
} }
private List<Object> collectCascadedSubDeviceDepths(FlowContext ctx, JSONObject fieldDef) { private List<Object> collectCascadedSubDeviceDepths(FlowContext ctx) {
List<PlatformDeviceSubMapping> monitors = collectCascadedSubDeviceMonitors(ctx); List<PlatformDeviceSubMapping> monitors = collectCascadedSubDeviceMonitors(ctx);
int currentIndex = resolveCurrentMonitorIndex(ctx, monitors); int currentIndex = resolveCurrentMonitorIndex(ctx, monitors);
List<Object> allDepths = resolveConfiguredDepths(fieldDef); List<Object> suffix = new ArrayList<>();
if (allDepths.size() < monitors.size()) { for (PlatformDeviceSubMapping monitor : monitors.subList(currentIndex, monitors.size())) {
throw new RuntimeException("串联子设备深度数组配置数量不足,监测点数量=" Object depth = readSensorDepth(monitor);
+ monitors.size() + ",深度数量=" + allDepths.size()); if (depth == null) {
throw new RuntimeException("深部位移子设备 " + monitor.getSubSerialNumber() + " 缺少传感器深度");
}
suffix.add(depth);
} }
List<Object> suffix = new ArrayList<>(allDepths.subList(currentIndex, monitors.size()));
Collections.reverse(suffix); Collections.reverse(suffix);
return suffix; return suffix;
} }
@ -588,10 +597,13 @@ public class PlatformFlowExecutor {
if (mapping.getId() == null) { if (mapping.getId() == null) {
throw new RuntimeException("串联子设备入网缺少映射ID"); throw new RuntimeException("串联子设备入网缺少映射ID");
} }
List<PlatformDeviceSubMapping> subMappings = subMappingMapper.selectByParentId(mapping.getId()); List<PlatformDeviceSubMapping> monitors = ctx.getMonitorSubMappings();
List<PlatformDeviceSubMapping> monitors = subMappings == null ? Collections.emptyList() : subMappings.stream() if (monitors == null) {
.filter(PlatformDeviceSubMapping::isMonitor) List<PlatformDeviceSubMapping> subMappings = subMappingMapper.selectByParentId(mapping.getId());
.collect(Collectors.toList()); monitors = subMappings == null ? Collections.emptyList() : subMappings.stream()
.filter(PlatformDeviceSubMapping::isMonitor)
.collect(Collectors.toList());
}
if (monitors.isEmpty()) { if (monitors.isEmpty()) {
throw new RuntimeException("串联子设备入网未配置监测点子设备"); throw new RuntimeException("串联子设备入网未配置监测点子设备");
} }
@ -614,23 +626,15 @@ public class PlatformFlowExecutor {
throw new RuntimeException("串联子设备入网无法确定当前监测点序号"); throw new RuntimeException("串联子设备入网无法确定当前监测点序号");
} }
private List<Object> resolveConfiguredDepths(JSONObject fieldDef) { private Object readSensorDepth(PlatformDeviceSubMapping mapping) {
if (!fieldDef.containsKey("value")) { if (mapping == null || !StringUtils.hasText(mapping.getOnboardConfig())) {
throw new RuntimeException("串联子设备未配置深度数组"); return null;
} }
Object value = fieldDef.get("value"); try {
List<?> list = toList(value); return JSON.parseObject(mapping.getOnboardConfig()).get("sensorDepth");
if (list == null && value instanceof String) { } catch (Exception e) {
try { throw new RuntimeException("子设备 " + mapping.getSubSerialNumber() + " 入网参数不是有效JSON");
list = JSON.parseArray(((String) value).trim());
} catch (Exception ignored) {
list = null;
}
} }
if (list == null || list.isEmpty()) {
throw new RuntimeException("串联子设备深度数组为空");
}
return new ArrayList<>(list);
} }
/** /**
@ -1208,7 +1212,7 @@ public class PlatformFlowExecutor {
} }
/** 确保 Token 有效,过期则刷新 */ /** 确保 Token 有效,过期则刷新 */
private String ensureToken(PlatformInstance platform) { public String ensureToken(PlatformInstance platform) {
Date now = new Date(); Date now = new Date();
// 提前 5 分钟视为过期 // 提前 5 分钟视为过期
boolean expired = platform.getTokenExpiresAt() == null boolean expired = platform.getTokenExpiresAt() == null

View File

@ -5,6 +5,7 @@ import com.fastbee.iot.platform.domain.PlatformInstance;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import java.util.Map;
public interface IPlatformInstanceService extends IService<PlatformInstance> { public interface IPlatformInstanceService extends IService<PlatformInstance> {
@ -18,4 +19,9 @@ public interface IPlatformInstanceService extends IService<PlatformInstance> {
/** 更新 extra_config 中的某个字段 */ /** 更新 extra_config 中的某个字段 */
void updateExtraConfig(Long platformId, String key, String value); void updateExtraConfig(Long platformId, String key, String value);
/**
* 根据平台ID查询项目列表
*/
List<Map<String, Object>> getProjectsByPlatformId(Long platformId);
} }

View File

@ -72,6 +72,10 @@ public class PlatformDeviceMappingServiceImpl extends ServiceImpl<PlatformDevice
throw new RuntimeException("设备绑定关系不存在: " + mappingId); throw new RuntimeException("设备绑定关系不存在: " + mappingId);
} }
if (!StringUtils.hasText(mapping.getPlatformProjectId())) {
throw new RuntimeException("请选择所属项目后再执行入网");
}
PlatformInstance platform = instanceMapper.selectById(mapping.getPlatformId()); PlatformInstance platform = instanceMapper.selectById(mapping.getPlatformId());
if (platform == null) { if (platform == null) {
throw new RuntimeException("平台不存在: " + mapping.getPlatformId()); throw new RuntimeException("平台不存在: " + mapping.getPlatformId());
@ -132,6 +136,7 @@ public class PlatformDeviceMappingServiceImpl extends ServiceImpl<PlatformDevice
ctx.setCurrentMonitorSerialNumber(monitor.getSubSerialNumber()); ctx.setCurrentMonitorSerialNumber(monitor.getSubSerialNumber());
ctx.setCurrentMonitorDeviceName(monitor.getSubDeviceName()); ctx.setCurrentMonitorDeviceName(monitor.getSubDeviceName());
ctx.setCurrentMonitorIndex(i); ctx.setCurrentMonitorIndex(i);
ctx.setMonitorSubMappings(monitors);
ctx.setSubMappingOnboardConfig(monitor.getOnboardConfig()); ctx.setSubMappingOnboardConfig(monitor.getOnboardConfig());
if (reference != null) { if (reference != null) {
ctx.setCurrentReferenceSerialNumber(reference.getSubSerialNumber()); ctx.setCurrentReferenceSerialNumber(reference.getSubSerialNumber());

View File

@ -5,6 +5,12 @@ import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.dtflys.forest.http.ForestRequest;
import com.dtflys.forest.http.ForestRequestType;
import com.fastbee.common.core.redis.RedisCache;
import com.fastbee.common.core.redis.RedisKeyBuilder;
import com.fastbee.http.model.HttpClientConfig;
import com.fastbee.http.service.HttpClientFactory;
import com.fastbee.iot.platform.domain.PlatformFlow; import com.fastbee.iot.platform.domain.PlatformFlow;
import com.fastbee.iot.platform.domain.PlatformInstance; import com.fastbee.iot.platform.domain.PlatformInstance;
import com.fastbee.iot.platform.flow.FlowContext; import com.fastbee.iot.platform.flow.FlowContext;
@ -16,8 +22,9 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import java.util.Date; import java.util.*;
import java.util.List; import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
@Service @Service
public class PlatformInstanceServiceImpl extends ServiceImpl<PlatformInstanceMapper, PlatformInstance> public class PlatformInstanceServiceImpl extends ServiceImpl<PlatformInstanceMapper, PlatformInstance>
@ -27,6 +34,8 @@ public class PlatformInstanceServiceImpl extends ServiceImpl<PlatformInstanceMap
private PlatformFlowMapper flowMapper; private PlatformFlowMapper flowMapper;
@Autowired @Autowired
private PlatformFlowExecutor flowExecutor; private PlatformFlowExecutor flowExecutor;
@Autowired
private RedisCache redisCache;
@Override @Override
public List<PlatformInstance> selectList(PlatformInstance query) { public List<PlatformInstance> selectList(PlatformInstance query) {
@ -75,4 +84,73 @@ public class PlatformInstanceServiceImpl extends ServiceImpl<PlatformInstanceMap
update.setExtraConfig(extra.toJSONString()); update.setExtraConfig(extra.toJSONString());
this.updateById(update); this.updateById(update);
} }
@Override
public List<Map<String, Object>> getProjectsByPlatformId(Long platformId) {
PlatformInstance platform = this.getById(platformId);
if (platform == null) {
throw new RuntimeException("平台不存在: " + platformId);
}
// 从extra_config读取userId
JSONObject extraConfig = JSON.parseObject(platform.getExtraConfig());
String userId = extraConfig == null ? null : extraConfig.getString("userId");
if (!StringUtils.hasText(userId)) {
throw new RuntimeException("平台未完成初始化,缺少userId。请先执行平台初始化流程。");
}
String key = RedisKeyBuilder.buildDCPProjectInfoKey(userId);
Object cacheObject = redisCache.getCacheObject(key);
if (cacheObject != null) {
return (List<Map<String, Object>>) cacheObject;
}
// 确保 token 有效(过期自动刷新)
String token = flowExecutor.ensureToken(platform);
String url = platform.getBaseUrl() + "/api/app/project/by-user-id/" + userId;
HttpClientConfig config = HttpClientConfig.builder()
.url(url)
.method(ForestRequestType.GET)
.headers(Collections.singletonMap("Authorization", "Bearer " + token))
.build();
ForestRequest<?> request = HttpClientFactory.instance(config);
String responseBody = request.executeAsString();
if (!StringUtils.hasText(responseBody)) {
return Collections.emptyList();
}
List<Map<String, Object>> result = parseProjectsResponse(responseBody);
redisCache.setCacheObject(key, result, 10, TimeUnit.MINUTES);
return result;
}
@SuppressWarnings("unchecked")
private List<Map<String, Object>> parseProjectsResponse(String responseBody) {
String trimmed = responseBody.trim();
List<Map<String, Object>> rawList;
if (trimmed.startsWith("[")) {
rawList = JSON.parseArray(trimmed, Map.class);
} else {
JSONObject json = JSON.parseObject(trimmed);
Object data = json.get("data");
if (data instanceof List) {
rawList = (List<Map<String, Object>>) data;
} else {
return Collections.emptyList();
}
}
return rawList.stream()
.map(m -> {
Map<String, Object> item = new HashMap<>();
item.put("id", m.get("id"));
item.put("name", m.get("name"));
return item;
})
.collect(Collectors.toList());
}
} }

View File

@ -3,28 +3,30 @@
<mapper namespace="com.fastbee.iot.platform.mapper.PlatformDeviceMappingMapper"> <mapper namespace="com.fastbee.iot.platform.mapper.PlatformDeviceMappingMapper">
<resultMap type="com.fastbee.iot.platform.domain.PlatformDeviceMapping" id="PlatformDeviceMappingResult"> <resultMap type="com.fastbee.iot.platform.domain.PlatformDeviceMapping" id="PlatformDeviceMappingResult">
<result property="id" column="id"/> <result property="id" column="id"/>
<result property="platformId" column="platform_id"/> <result property="platformId" column="platform_id"/>
<result property="deviceId" column="device_id"/> <result property="platformProjectId" column="platform_project_id"/>
<result property="serialNumber" column="serial_number"/> <result property="platformProjectName" column="platform_project_name"/>
<result property="deviceName" column="device_name"/> <result property="deviceId" column="device_id"/>
<result property="platformDeviceId" column="platform_device_id"/> <result property="serialNumber" column="serial_number"/>
<result property="platformPointId" column="platform_point_id"/> <result property="deviceName" column="device_name"/>
<result property="sensorType" column="sensor_type"/> <result property="platformDeviceId" column="platform_device_id"/>
<result property="deviceType" column="device_type"/> <result property="platformPointId" column="platform_point_id"/>
<result property="pushInterfaceId" column="push_interface_id"/> <result property="sensorType" column="sensor_type"/>
<result property="fieldMapping" column="field_mapping"/> <result property="deviceType" column="device_type"/>
<result property="extraMapping" column="extra_mapping"/> <result property="pushInterfaceId" column="push_interface_id"/>
<result property="netStatus" column="net_status"/> <result property="fieldMapping" column="field_mapping"/>
<result property="enable" column="enable"/> <result property="extraMapping" column="extra_mapping"/>
<result property="pushMode" column="push_mode"/> <result property="netStatus" column="net_status"/>
<result property="pushInterval" column="push_interval"/> <result property="enable" column="enable"/>
<result property="lastPushTime" column="last_push_time"/> <result property="pushMode" column="push_mode"/>
<result property="remark" column="remark"/> <result property="pushInterval" column="push_interval"/>
<result property="createBy" column="create_by"/> <result property="lastPushTime" column="last_push_time"/>
<result property="createTime" column="create_time"/> <result property="remark" column="remark"/>
<result property="updateBy" column="update_by"/> <result property="createBy" column="create_by"/>
<result property="updateTime" column="update_time"/> <result property="createTime" column="create_time"/>
<result property="updateBy" column="update_by"/>
<result property="updateTime" column="update_time"/>
</resultMap> </resultMap>
<select id="selectBySerialNumber" resultMap="PlatformDeviceMappingResult"> <select id="selectBySerialNumber" resultMap="PlatformDeviceMappingResult">

View File

@ -8,8 +8,36 @@
SET @dcp_platform_id = (SELECT id FROM platform_instance WHERE code = 'dcp' LIMIT 1); SET @dcp_platform_id = (SELECT id FROM platform_instance WHERE code = 'dcp' LIMIT 1);
-- 重新执行时先清理本脚本维护的流程和接口,避免旧配置被 LIMIT 1 命中。
DELETE s FROM platform_flow_step s
INNER JOIN platform_flow f ON f.id = s.flow_id
WHERE f.platform_id = @dcp_platform_id
AND f.code IN (
'dcp_init', 'dcp_token_refresh', 'dcp_onboard_inclino',
'dcp_onboard_stress', 'dcp_onboard_hyeto',
'dcp_onboard_static_level', 'dcp_onboard_deep_inclino'
);
DELETE FROM platform_flow
WHERE platform_id = @dcp_platform_id
AND code IN (
'dcp_init', 'dcp_token_refresh', 'dcp_onboard_inclino',
'dcp_onboard_stress', 'dcp_onboard_hyeto',
'dcp_onboard_static_level', 'dcp_onboard_deep_inclino'
);
DELETE FROM platform_interface
WHERE platform_id = @dcp_platform_id
AND code IN (
'dcp_auth', 'dcp_get_user', 'dcp_get_projects', 'dcp_create_point',
'dcp_bind_inclino', 'dcp_bind_stress', 'dcp_bind_hyeto',
'dcp_bind_static_level', 'dcp_bind_deep_inclino',
'dcp_push_inclino', 'dcp_push_stress', 'dcp_push_hyeto',
'dcp_push_static_level'
);
-- ============================================= -- =============================================
-- 一、接口定义(8个) -- 一、接口定义(13个)
-- ============================================= -- =============================================
-- 1. 认证接口 -- 1. 认证接口
@ -47,16 +75,16 @@ VALUES (@dcp_platform_id, '获取项目列表', 'dcp_get_projects', 'GET', '/api
{"name":"userId","type":"string","source":"context","contextKey":"step2.id","required":true,"desc":"用户ID,来自上一步"} {"name":"userId","type":"string","source":"context","contextKey":"step2.id","required":true,"desc":"用户ID,来自上一步"}
]', ]',
'[ '[
{"name":"id", "type":"string","path":"[0].id", "desc":"第一个项目ID"}, {"name":"id", "type":"string","path":"id", "desc":"项目ID"},
{"name":"name", "type":"string","path":"[0].name", "desc":"项目名称"} {"name":"name", "type":"string","path":"name", "desc":"项目名称"}
]', ]',
3, '获取用户下的项目列表,取第一个项目', 'system', NOW()); 3, '获取用户下的完整项目列表', 'system', NOW());
-- 4. 创建测点 -- 4. 创建测点
INSERT INTO `platform_interface` (`platform_id`,`name`,`code`,`method`,`path`,`content_type`,`request_schema`,`response_schema`,`sort_order`,`remark`,`create_by`,`create_time`) INSERT INTO `platform_interface` (`platform_id`,`name`,`code`,`method`,`path`,`content_type`,`request_schema`,`response_schema`,`sort_order`,`remark`,`create_by`,`create_time`)
VALUES (@dcp_platform_id, '创建测点', 'dcp_create_point', 'POST', '/api/app/point', 'application/json', VALUES (@dcp_platform_id, '创建测点', 'dcp_create_point', 'POST', '/api/app/point', 'application/json',
'[ '[
{"name":"projectId","type":"string","source":"extra","field":"projectId","required":true,"desc":"项目ID"}, {"name":"projectId","type":"string","source":"device","field":"platform_project_id","required":true,"desc":"项目ID"},
{"name":"name", "type":"string","source":"device","field":"serial_number","required":true,"desc":"测点名称,用设备序列号"}, {"name":"name", "type":"string","source":"device","field":"serial_number","required":true,"desc":"测点名称,用设备序列号"},
{"name":"type", "type":"number","source":"fixed","value":3,"required":true,"desc":"测点类型,固定为3"} {"name":"type", "type":"number","source":"fixed","value":3,"required":true,"desc":"测点类型,固定为3"}
]', ]',
@ -69,7 +97,7 @@ VALUES (@dcp_platform_id, '创建测点', 'dcp_create_point', 'POST', '/api/app/
INSERT INTO `platform_interface` (`platform_id`,`name`,`code`,`method`,`path`,`content_type`,`request_schema`,`response_schema`,`sort_order`,`remark`,`create_by`,`create_time`) INSERT INTO `platform_interface` (`platform_id`,`name`,`code`,`method`,`path`,`content_type`,`request_schema`,`response_schema`,`sort_order`,`remark`,`create_by`,`create_time`)
VALUES (@dcp_platform_id, '绑定倾角传感器', 'dcp_bind_inclino', 'POST', '/api/app/inclino-sensor', 'application/json', VALUES (@dcp_platform_id, '绑定倾角传感器', 'dcp_bind_inclino', 'POST', '/api/app/inclino-sensor', 'application/json',
'[ '[
{"name":"projectId", "type":"string","source":"extra", "field":"projectId", "required":true,"desc":"项目ID"}, {"name":"projectId", "type":"string","source":"device", "field":"platform_project_id", "required":true,"desc":"项目ID"},
{"name":"pointId", "type":"string","source":"context","contextKey":"step1.id","required":true,"desc":"测点ID,来自创建测点步骤"}, {"name":"pointId", "type":"string","source":"context","contextKey":"step1.id","required":true,"desc":"测点ID,来自创建测点步骤"},
{"name":"deviceType", "type":"number","source":"fixed", "value":5, "required":true,"desc":"设备类型,倾角=5"}, {"name":"deviceType", "type":"number","source":"fixed", "value":5, "required":true,"desc":"设备类型,倾角=5"},
{"name":"name", "type":"string","source":"device", "field":"serial_number","required":true,"desc":"传感器名称"}, {"name":"name", "type":"string","source":"device", "field":"serial_number","required":true,"desc":"传感器名称"},
@ -86,7 +114,7 @@ VALUES (@dcp_platform_id, '绑定倾角传感器', 'dcp_bind_inclino', 'POST', '
INSERT INTO `platform_interface` (`platform_id`,`name`,`code`,`method`,`path`,`content_type`,`request_schema`,`response_schema`,`sort_order`,`remark`,`create_by`,`create_time`) INSERT INTO `platform_interface` (`platform_id`,`name`,`code`,`method`,`path`,`content_type`,`request_schema`,`response_schema`,`sort_order`,`remark`,`create_by`,`create_time`)
VALUES (@dcp_platform_id, '绑定轴力计', 'dcp_bind_stress', 'POST', '/api/app/stress-sensor', 'application/json', VALUES (@dcp_platform_id, '绑定轴力计', 'dcp_bind_stress', 'POST', '/api/app/stress-sensor', 'application/json',
'[ '[
{"name":"projectId", "type":"string","source":"extra", "field":"projectId", "required":true,"desc":"项目ID"}, {"name":"projectId", "type":"string","source":"device", "field":"platform_project_id", "required":true,"desc":"项目ID"},
{"name":"pointId", "type":"string","source":"context","contextKey":"step1.id","required":true,"desc":"测点ID"}, {"name":"pointId", "type":"string","source":"context","contextKey":"step1.id","required":true,"desc":"测点ID"},
{"name":"deviceType", "type":"number","source":"fixed", "value":10, "required":true,"desc":"设备类型,轴力计=10"}, {"name":"deviceType", "type":"number","source":"fixed", "value":10, "required":true,"desc":"设备类型,轴力计=10"},
{"name":"name", "type":"string","source":"device", "field":"serial_number","required":true,"desc":"传感器名称"}, {"name":"name", "type":"string","source":"device", "field":"serial_number","required":true,"desc":"传感器名称"},
@ -101,7 +129,7 @@ VALUES (@dcp_platform_id, '绑定轴力计', 'dcp_bind_stress', 'POST', '/api/ap
INSERT INTO `platform_interface` (`platform_id`,`name`,`code`,`method`,`path`,`content_type`,`request_schema`,`response_schema`,`sort_order`,`remark`,`create_by`,`create_time`) INSERT INTO `platform_interface` (`platform_id`,`name`,`code`,`method`,`path`,`content_type`,`request_schema`,`response_schema`,`sort_order`,`remark`,`create_by`,`create_time`)
VALUES (@dcp_platform_id, '绑定雨量计', 'dcp_bind_hyeto', 'POST', '/api/app/hyeto-sensor', 'application/json', VALUES (@dcp_platform_id, '绑定雨量计', 'dcp_bind_hyeto', 'POST', '/api/app/hyeto-sensor', 'application/json',
'[ '[
{"name":"projectId", "type":"string","source":"extra", "field":"projectId", "required":true}, {"name":"projectId", "type":"string","source":"device", "field":"platform_project_id", "required":true},
{"name":"pointId", "type":"string","source":"context","contextKey":"step1.id","required":true}, {"name":"pointId", "type":"string","source":"context","contextKey":"step1.id","required":true},
{"name":"deviceType", "type":"number","source":"fixed", "value":3, "required":true,"desc":"雨量计=3"}, {"name":"deviceType", "type":"number","source":"fixed", "value":3, "required":true,"desc":"雨量计=3"},
{"name":"name", "type":"string","source":"device", "field":"serial_number","required":true}, {"name":"name", "type":"string","source":"device", "field":"serial_number","required":true},
@ -115,7 +143,7 @@ VALUES (@dcp_platform_id, '绑定雨量计', 'dcp_bind_hyeto', 'POST', '/api/app
INSERT INTO `platform_interface` (`platform_id`,`name`,`code`,`method`,`path`,`content_type`,`request_schema`,`response_schema`,`sort_order`,`remark`,`create_by`,`create_time`) INSERT INTO `platform_interface` (`platform_id`,`name`,`code`,`method`,`path`,`content_type`,`request_schema`,`response_schema`,`sort_order`,`remark`,`create_by`,`create_time`)
VALUES (@dcp_platform_id, '绑定静力水准', 'dcp_bind_static_level', 'POST', '/api/app/static-level-device', 'application/json', VALUES (@dcp_platform_id, '绑定静力水准', 'dcp_bind_static_level', 'POST', '/api/app/static-level-device', 'application/json',
'[ '[
{"name":"projectId", "type":"string","source":"extra", "field":"projectId", "required":true}, {"name":"projectId", "type":"string","source":"device", "field":"platform_project_id", "required":true},
{"name":"pointId", "type":"string","source":"context","contextKey":"step1.id","required":true}, {"name":"pointId", "type":"string","source":"context","contextKey":"step1.id","required":true},
{"name":"deviceType", "type":"number","source":"fixed", "value":1002, "required":true,"desc":"静力水准=1002"}, {"name":"deviceType", "type":"number","source":"fixed", "value":1002, "required":true,"desc":"静力水准=1002"},
{"name":"name", "type":"string","source":"device", "field":"serial_number","required":true}, {"name":"name", "type":"string","source":"device", "field":"serial_number","required":true},
@ -126,6 +154,24 @@ VALUES (@dcp_platform_id, '绑定静力水准', 'dcp_bind_static_level', 'POST',
'[{"name":"id","type":"string","path":"id","desc":"DCP设备ID"}]', '[{"name":"id","type":"string","path":"id","desc":"DCP设备ID"}]',
8, '将静力水准传感器绑定到测点', 'system', NOW()); 8, '将静力水准传感器绑定到测点', 'system', NOW());
-- 9. 绑定深部位移
INSERT INTO `platform_interface` (`platform_id`,`name`,`code`,`method`,`path`,`content_type`,`request_schema`,`response_schema`,`sort_order`,`remark`,`create_by`,`create_time`)
VALUES (@dcp_platform_id, '绑定深部位移', 'dcp_bind_deep_inclino', 'POST', '/api/app/deep-inclino-device', 'application/json',
'[
{"name":"projectId", "type":"string","source":"device", "field":"platform_project_id","required":true},
{"name":"pointId", "type":"string","source":"context", "contextKey":"step1.id", "required":true},
{"name":"deviceType", "type":"number","source":"fixed", "value":1001, "required":true},
{"name":"name", "type":"string","source":"device", "field":"serial_number", "required":true},
{"name":"moduleId", "type":"string","source":"uuid", "required":true},
{"name":"virtualId", "type":"string","source":"uuid", "required":true},
{"name":"sensorIds", "type":"array", "source":"cascadedSubDeviceSensorIds", "required":true},
{"name":"sensorDepths", "type":"array", "source":"cascadedSubDeviceDepths", "required":true},
{"name":"holeDepth", "type":"number","source":"onboard_config", "field":"holeDepth", "required":true},
{"name":"holeElevation","type":"number","source":"onboard_config", "field":"holeElevation", "required":true}
]',
'[{"name":"id","type":"string","path":"id","desc":"DCP设备ID"}]',
9, '将深部位移设备绑定到测点', 'system', NOW());
-- ============================================= -- =============================================
-- 1.5 推送数据接口(4个,设备绑定时选择推送接口用) -- 1.5 推送数据接口(4个,设备绑定时选择推送接口用)
@ -184,12 +230,12 @@ VALUES (@dcp_platform_id, '推送静力水准数据', 'dcp_push_static_level', '
-- ============================================= -- =============================================
-- 二、流程定义(6个) -- 二、流程定义(7个)
-- ============================================= -- =============================================
-- 流程1:平台初始化(flow_type=1) -- 流程1:平台初始化(flow_type=1)
INSERT INTO `platform_flow` (`platform_id`,`name`,`code`,`flow_type`,`sensor_type`,`enable`,`remark`,`create_by`,`create_time`) INSERT INTO `platform_flow` (`platform_id`,`name`,`code`,`flow_type`,`sensor_type`,`enable`,`remark`,`create_by`,`create_time`)
VALUES (@dcp_platform_id, 'DCP平台初始化', 'dcp_init', 1, NULL, '1', '获取Token→userId→projectId,一次性执行', 'system', NOW()); VALUES (@dcp_platform_id, 'DCP平台初始化', 'dcp_init', 1, NULL, '1', '获取Token→userId,一次性执行', 'system', NOW());
SET @flow_init_id = LAST_INSERT_ID(); SET @flow_init_id = LAST_INSERT_ID();
@ -208,14 +254,6 @@ VALUES (@flow_init_id, 2,
'[{"field":"id","saveTo":"extra_config.userId"}]', '[{"field":"id","saveTo":"extra_config.userId"}]',
'获取userId并保存'); '获取userId并保存');
INSERT INTO `platform_flow_step` (`flow_id`,`step_order`,`interface_id`,`param_mapping`,`result_key`,`result_save`,`remark`)
VALUES (@flow_init_id, 3,
(SELECT id FROM platform_interface WHERE platform_id=@dcp_platform_id AND code='dcp_get_projects' LIMIT 1),
'{"userId":{"source":"context","contextKey":"step2.id"}}',
'step3',
'[{"field":"id","saveTo":"extra_config.projectId"},{"field":"name","saveTo":"extra_config.projectName"}]',
'获取projectId并保存');
-- 流程2:Token刷新(flow_type=3) -- 流程2:Token刷新(flow_type=3)
INSERT INTO `platform_flow` (`platform_id`,`name`,`code`,`flow_type`,`sensor_type`,`enable`,`remark`,`create_by`,`create_time`) INSERT INTO `platform_flow` (`platform_id`,`name`,`code`,`flow_type`,`sensor_type`,`enable`,`remark`,`create_by`,`create_time`)
@ -314,6 +352,25 @@ VALUES (@flow_static_level_id, 2,
'[{"field":"id","saveTo":"device_mapping.platform_device_id"}]', '[{"field":"id","saveTo":"device_mapping.platform_device_id"}]',
'绑定静力水准'); '绑定静力水准');
-- 流程7:深部位移设备入网(flow_type=2)
INSERT INTO `platform_flow` (`platform_id`,`name`,`code`,`flow_type`,`sensor_type`,`enable`,`remark`,`create_by`,`create_time`)
VALUES (@dcp_platform_id, 'DCP深部位移入网', 'dcp_onboard_deep_inclino', 2, 'deep_inclino', '1', '创建测点→绑定深部位移', 'system', NOW());
SET @flow_deep_inclino_id = LAST_INSERT_ID();
INSERT INTO `platform_flow_step` (`flow_id`,`step_order`,`interface_id`,`result_key`,`result_save`,`remark`)
VALUES (@flow_deep_inclino_id, 1,
(SELECT id FROM platform_interface WHERE platform_id=@dcp_platform_id AND code='dcp_create_point' LIMIT 1),
'step1',
'[{"field":"id","saveTo":"device_mapping.platform_point_id"}]',
'创建测点');
INSERT INTO `platform_flow_step` (`flow_id`,`step_order`,`interface_id`,`result_key`,`result_save`,`remark`)
VALUES (@flow_deep_inclino_id, 2,
(SELECT id FROM platform_interface WHERE platform_id=@dcp_platform_id AND code='dcp_bind_deep_inclino' LIMIT 1),
'step2',
'[{"field":"id","saveTo":"device_mapping.platform_device_id"}]',
'绑定深部位移');
@ -324,7 +381,23 @@ SET @platform_id = (SELECT id FROM platform_instance WHERE code = 'dcp' LIMIT 1)
UPDATE platform_interface UPDATE platform_interface
SET request_schema = '[ SET request_schema = '[
{"name":"projectId", "type":"string","source":"extra", "field":"projectId", "required":true,"desc":"项目ID"}, {"name":"projectId","type":"string","source":"device","field":"platform_project_id","required":true,"desc":"项目ID"},
{"name":"name", "type":"string","source":"device","field":"serial_number","required":true,"desc":"测点名称,用设备序列号"},
{"name":"type", "type":"number","source":"fixed","value":3,"required":true,"desc":"测点类型,固定为3"}
]', update_by = 'system', update_time = NOW()
WHERE platform_id = @platform_id AND code = 'dcp_create_point';
DELETE s FROM platform_flow_step s
INNER JOIN platform_flow f ON f.id = s.flow_id
WHERE f.platform_id = @platform_id AND f.code = 'dcp_init' AND s.step_order > 2;
UPDATE platform_flow
SET remark = '获取Token→userId,一次性执行', update_by = 'system', update_time = NOW()
WHERE platform_id = @platform_id AND code = 'dcp_init';
UPDATE platform_interface
SET request_schema = '[
{"name":"projectId", "type":"string","source":"device", "field":"platform_project_id", "required":true,"desc":"项目ID"},
{"name":"pointId", "type":"string","source":"context","contextKey":"step1.id","required":true,"desc":"测点ID,来自创建测点步骤"}, {"name":"pointId", "type":"string","source":"context","contextKey":"step1.id","required":true,"desc":"测点ID,来自创建测点步骤"},
{"name":"deviceType", "type":"number","source":"fixed", "value":5, "required":true,"desc":"设备类型,倾角=5"}, {"name":"deviceType", "type":"number","source":"fixed", "value":5, "required":true,"desc":"设备类型,倾角=5"},
{"name":"name", "type":"string","source":"device", "field":"serial_number","required":true,"desc":"传感器名称"}, {"name":"name", "type":"string","source":"device", "field":"serial_number","required":true,"desc":"传感器名称"},
@ -339,7 +412,7 @@ WHERE platform_id = @platform_id
UPDATE platform_interface UPDATE platform_interface
SET request_schema = '[ SET request_schema = '[
{"name":"projectId", "type":"string","source":"extra", "field":"projectId", "required":true,"desc":"项目ID"}, {"name":"projectId", "type":"string","source":"device", "field":"platform_project_id", "required":true,"desc":"项目ID"},
{"name":"pointId", "type":"string","source":"context","contextKey":"step1.id","required":true,"desc":"测点ID"}, {"name":"pointId", "type":"string","source":"context","contextKey":"step1.id","required":true,"desc":"测点ID"},
{"name":"deviceType", "type":"number","source":"fixed", "value":10, "required":true,"desc":"设备类型,轴力计=10"}, {"name":"deviceType", "type":"number","source":"fixed", "value":10, "required":true,"desc":"设备类型,轴力计=10"},
{"name":"name", "type":"string","source":"device", "field":"serial_number","required":true,"desc":"传感器名称"}, {"name":"name", "type":"string","source":"device", "field":"serial_number","required":true,"desc":"传感器名称"},
@ -354,7 +427,7 @@ WHERE platform_id = @platform_id
UPDATE platform_interface UPDATE platform_interface
SET request_schema = '[ SET request_schema = '[
{"name":"projectId", "type":"string","source":"extra", "field":"projectId", "required":true}, {"name":"projectId", "type":"string","source":"device", "field":"platform_project_id", "required":true},
{"name":"pointId", "type":"string","source":"context","contextKey":"step1.id","required":true}, {"name":"pointId", "type":"string","source":"context","contextKey":"step1.id","required":true},
{"name":"deviceType", "type":"number","source":"fixed", "value":3, "required":true,"desc":"雨量计=3"}, {"name":"deviceType", "type":"number","source":"fixed", "value":3, "required":true,"desc":"雨量计=3"},
{"name":"name", "type":"string","source":"device", "field":"serial_number","required":true}, {"name":"name", "type":"string","source":"device", "field":"serial_number","required":true},
@ -368,7 +441,7 @@ WHERE platform_id = @platform_id
UPDATE platform_interface UPDATE platform_interface
SET request_schema = '[ SET request_schema = '[
{"name":"projectId", "type":"string","source":"extra", "field":"projectId", "required":true}, {"name":"projectId", "type":"string","source":"device", "field":"platform_project_id", "required":true},
{"name":"pointId", "type":"string","source":"context","contextKey":"step1.id","required":true}, {"name":"pointId", "type":"string","source":"context","contextKey":"step1.id","required":true},
{"name":"deviceType", "type":"number","source":"fixed", "value":1002, "required":true,"desc":"静力水准=1002"}, {"name":"deviceType", "type":"number","source":"fixed", "value":1002, "required":true,"desc":"静力水准=1002"},
{"name":"name", "type":"string","source":"device", "field":"serial_number","required":true}, {"name":"name", "type":"string","source":"device", "field":"serial_number","required":true},

View File

@ -120,6 +120,8 @@ CREATE TABLE IF NOT EXISTS `platform_flow_step` (
CREATE TABLE IF NOT EXISTS `platform_device_mapping` ( CREATE TABLE IF NOT EXISTS `platform_device_mapping` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id', `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',
`platform_id` bigint(20) NOT NULL COMMENT '所属平台ID', `platform_id` bigint(20) NOT NULL COMMENT '所属平台ID',
`platform_project_id` varchar(100) NOT NULL COMMENT 'DCP项目ID',
`platform_project_name` varchar(255) DEFAULT NULL COMMENT 'DCP项目名称',
`device_id` bigint(20) NOT NULL COMMENT 'FastBee设备ID', `device_id` bigint(20) NOT NULL COMMENT 'FastBee设备ID',
`serial_number` varchar(64) NOT NULL COMMENT 'FastBee设备序列号', `serial_number` varchar(64) NOT NULL COMMENT 'FastBee设备序列号',
`device_name` varchar(64) DEFAULT NULL COMMENT '默认为本平台设备名称,可以修改为对方平台需要的名称', `device_name` varchar(64) DEFAULT NULL COMMENT '默认为本平台设备名称,可以修改为对方平台需要的名称',
@ -146,8 +148,12 @@ CREATE TABLE IF NOT EXISTS `platform_device_mapping` (
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='平台设备绑定关系'; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='平台设备绑定关系';
-- ---------------------------- -- ----------------------------
-- 5.1 设备绑定关系 - 新增 device_type 字段 -- 5.1 设备绑定关系 - 补充绑定项目和设备类型字段
-- ---------------------------- -- ----------------------------
ALTER TABLE `platform_device_mapping`
ADD COLUMN IF NOT EXISTS `platform_project_id` varchar(100) DEFAULT NULL COMMENT 'DCP项目ID' AFTER `platform_id`,
ADD COLUMN IF NOT EXISTS `platform_project_name` varchar(255) DEFAULT NULL COMMENT 'DCP项目名称' AFTER `platform_project_id`;
ALTER TABLE `platform_device_mapping` ALTER TABLE `platform_device_mapping`
ADD COLUMN IF NOT EXISTS `device_type` tinyint(1) NOT NULL DEFAULT 0 ADD COLUMN IF NOT EXISTS `device_type` tinyint(1) NOT NULL DEFAULT 0
COMMENT '设备类型: 0=直连设备 1=网关设备(多子设备)' AFTER `sensor_type`; COMMENT '设备类型: 0=直连设备 1=网关设备(多子设备)' AFTER `sensor_type`;

View File

@ -23,3 +23,7 @@ export function delPlatformInstance(ids) {
export function initializePlatform(id) { export function initializePlatform(id) {
return request({ url: `/platform/instance/${id}/initialize`, method: 'post' }) return request({ url: `/platform/instance/${id}/initialize`, method: 'post' })
} }
export function getProjectsByPlatformId(id) {
return request({ url: `/platform/instance/${id}/projects`, method: 'get' })
}

View File

@ -114,7 +114,7 @@
<el-divider content-position="left">入参定义(request_schema)</el-divider> <el-divider content-position="left">入参定义(request_schema)</el-divider>
<div class="schema-tip"> <div class="schema-tip">
定义接口需要的入参字段。参数来源使用业务化名称: 定义接口需要的入参字段。参数来源使用业务化名称:
固定值、认证参数、平台参数、上一步结果、当前设备、自动UUID、子设备传感器列表、串联子设备传感器列表、串联子设备深度数组、基准点成对传感器列表、设备物模型、上报时间。数组字段可配置组包模式。 固定值、认证参数、平台参数、上一步结果、当前设备、自动UUID、子设备传感器列表、串联子设备传感器列表、入网深度数组、基准点成对传感器列表、设备物模型、上报时间。数组字段可配置组包模式。
</div> </div>
<el-table :data="requestSchemaRows" size="mini" border style="width:100%;margin-top:8px"> <el-table :data="requestSchemaRows" size="mini" border style="width:100%;margin-top:8px">
<el-table-column label="字段名" width="130"> <el-table-column label="字段名" width="130">
@ -145,7 +145,7 @@
<el-option label="自动UUID" value="uuid" /> <el-option label="自动UUID" value="uuid" />
<el-option label="子设备传感器列表" value="subDeviceSensorIds" /> <el-option label="子设备传感器列表" value="subDeviceSensorIds" />
<el-option label="串联子设备传感器列表" value="cascadedSubDeviceSensorIds" /> <el-option label="串联子设备传感器列表" value="cascadedSubDeviceSensorIds" />
<el-option label="串联子设备深度数组" value="cascadedSubDeviceDepths" /> <el-option label="入网深度数组" value="cascadedSubDeviceDepths" />
<el-option label="基准点成对传感器列表" value="referenceSubDeviceSensorIdPairs" /> <el-option label="基准点成对传感器列表" value="referenceSubDeviceSensorIdPairs" />
<el-option label="设备物模型" value="thingsModel" /> <el-option label="设备物模型" value="thingsModel" />
<el-option label="上报时间" value="ts" /> <el-option label="上报时间" value="ts" />

View File

@ -64,6 +64,12 @@
<el-table-column label="平台" width="120"> <el-table-column label="平台" width="120">
<template slot-scope="scope">{{ platformName(scope.row.platformId) }}</template> <template slot-scope="scope">{{ platformName(scope.row.platformId) }}</template>
</el-table-column> </el-table-column>
<el-table-column label="项目" min-width="120" show-overflow-tooltip>
<template slot-scope="scope">
<span v-if="scope.row.platformProjectName">{{ scope.row.platformProjectName }}</span>
<span v-else style="color:#909399">-</span>
</template>
</el-table-column>
<el-table-column label="设备名称" min-width="140"> <el-table-column label="设备名称" min-width="140">
<template slot-scope="scope">{{ scope.row.deviceName || deviceName(scope.row.deviceId) }}</template> <template slot-scope="scope">{{ scope.row.deviceName || deviceName(scope.row.deviceId) }}</template>
</el-table-column> </el-table-column>
@ -128,6 +134,11 @@
<el-option v-for="p in platformList" :key="p.id" :label="p.name" :value="p.id" /> <el-option v-for="p in platformList" :key="p.id" :label="p.name" :value="p.id" />
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="所属项目" prop="platformProjectId">
<el-select v-model="form.platformProjectId" placeholder="请选择项目" style="width:100%" @change="onProjectChange" :disabled="!form.platformId || form.netStatus === 1" clearable>
<el-option v-for="proj in projectOptions" :key="proj.id" :label="proj.name" :value="String(proj.id)" />
</el-select>
</el-form-item>
<el-form-item label="系统设备" prop="deviceId"> <el-form-item label="系统设备" prop="deviceId">
<el-select v-model="form.deviceId" placeholder="选择直连或网关设备,支持名称/编号搜索" filterable remote clearable <el-select v-model="form.deviceId" placeholder="选择直连或网关设备,支持名称/编号搜索" filterable remote clearable
:remote-method="searchDevice" :loading="deviceSearchLoading" style="width:100%" :remote-method="searchDevice" :loading="deviceSearchLoading" style="width:100%"
@ -229,6 +240,23 @@
</el-select> </el-select>
</template> </template>
</el-table-column> </el-table-column>
<template v-if="form.sensorType === 'deep_inclino'">
<el-table-column label="传感器深度(m)" min-width="140">
<template slot-scope="scope">
<el-input-number v-model="scope.row.sensorDepth" :min="0" :precision="2" size="mini" style="width:100%" @change="refreshOnboardConfig" />
</template>
</el-table-column>
<el-table-column label="孔深(m)" min-width="120">
<template slot-scope="scope">
<el-input-number v-model="scope.row.holeDepth" :min="0" :precision="2" size="mini" style="width:100%" @change="syncHoleDepthToAllMonitors" />
</template>
</el-table-column>
<el-table-column label="孔口高程(m)" min-width="140">
<template slot-scope="scope">
<el-input-number v-model="scope.row.holeElevation" :min="0" :precision="2" size="mini" style="width:100%" @change="syncHoleElevationToAllMonitors" />
</template>
</el-table-column>
</template>
<el-table-column label="入网参数 (JSON)" min-width="220"> <el-table-column label="入网参数 (JSON)" min-width="220">
<template slot-scope="scope"> <template slot-scope="scope">
<el-input v-model="scope.row.onboardConfig" size="mini" :placeholder="getOnboardConfigPlaceholder(scope.row)" /> <el-input v-model="scope.row.onboardConfig" size="mini" :placeholder="getOnboardConfigPlaceholder(scope.row)" />
@ -270,18 +298,16 @@
<!-- 标量字段映射 --> <!-- 标量字段映射 -->
<el-form-item v-if="fieldMappingRows.length > 0" label="字段映射"> <el-form-item v-if="fieldMappingRows.length > 0" label="字段映射">
<el-table :data="fieldMappingRows" size="mini" border style="width:100%"> <el-table :data="fieldMappingRows" size="mini" border style="width:100%">
<el-table-column label="平台字段" min-width="160">
<template slot-scope="scope">{{ scope.row.platformField }}</template>
</el-table-column>
<el-table-column label="物模型标识" min-width="180"> <el-table-column label="物模型标识" min-width="180">
<template slot-scope="scope"> <template slot-scope="scope">
<el-select v-model="scope.row.identifier" size="mini" placeholder="选择物模型字段" filterable style="width:100%"> <el-select v-model="scope.row.identifier" size="mini" placeholder="选择物模型字段" filterable style="width:100%">
<el-option v-for="tm in uniqueThingsModelOptions" :key="tm.identifier" :label="`${tm.modelName}(${tm.identifier})`" :value="tm.identifier" /> <el-option v-for="tm in uniqueThingsModelOptions" :key="tm.identifier" :label="`${tm.modelName}(${tm.identifier})`" :value="tm.identifier" />
</el-select> </el-select>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="平台字段" min-width="160">
<template slot-scope="scope">
<el-input v-model="scope.row.platformField" size="mini" placeholder="平台字段名" />
</template>
</el-table-column>
<el-table-column label="操作" width="60" align="center"> <el-table-column label="操作" width="60" align="center">
<template slot-scope="scope"> <template slot-scope="scope">
<el-button size="mini" type="text" icon="el-icon-delete" @click="fieldMappingRows.splice(scope.$index,1)" /> <el-button size="mini" type="text" icon="el-icon-delete" @click="fieldMappingRows.splice(scope.$index,1)" />
@ -305,7 +331,7 @@
<script> <script>
import { listPlatformDeviceMapping, addPlatformDeviceMapping, updatePlatformDeviceMapping, delPlatformDeviceMapping, onboardDevice, listSubMappings, syncSubMappings } from '@/api/platform/deviceMapping' import { listPlatformDeviceMapping, addPlatformDeviceMapping, updatePlatformDeviceMapping, delPlatformDeviceMapping, onboardDevice, listSubMappings, syncSubMappings } from '@/api/platform/deviceMapping'
import { listPlatformInstance } from '@/api/platform/instance' import { listPlatformInstance, getProjectsByPlatformId } from '@/api/platform/instance'
import { listPlatformInterface } from '@/api/platform/interface' import { listPlatformInterface } from '@/api/platform/interface'
import { listPlatformFlow, getPlatformFlow } from '@/api/platform/flow' import { listPlatformFlow, getPlatformFlow } from '@/api/platform/flow'
import { listDevice, listThingsModel, getDevice, listDeviceShort } from '@/api/iot/device' import { listDevice, listThingsModel, getDevice, listDeviceShort } from '@/api/iot/device'
@ -323,6 +349,7 @@ export default {
subDeviceOptions: [], subDeviceOptions: [],
deviceSearchLoading: false, thingsModelOptions: [], deviceSearchLoading: false, thingsModelOptions: [],
interfaceOptions: [], interfaceOptions: [],
projectOptions: [],
open: false, title: '', open: false, title: '',
fieldMappingRows: [], fieldMappingRows: [],
// 网关子设备映射行 // 网关子设备映射行
@ -333,9 +360,15 @@ export default {
subMappingCache: {}, subMappingCache: {},
sensorTypeDictOptions: [], sensorTypeDictOptions: [],
queryParams: { pageNum: 1, pageSize: 20, platformId: null, serialNumber: '', sensorType: '', netStatus: null }, queryParams: { pageNum: 1, pageSize: 20, platformId: null, serialNumber: '', sensorType: '', netStatus: null },
form: { enable: '1', deviceType: 0, reportIntervalSec: 300, deviceName: '' }, form: {
enable: '1', deviceType: 0, reportIntervalSec: 300, deviceName: '',
platformId: null, platformProjectId: null, platformProjectName: '',
pushInterfaceId: null, pushMode: 1, pushInterval: 5,
deviceId: null, serialNumber: '', sensorType: '', remark: ''
},
rules: { rules: {
platformId: [{ required: true, message: '请选择平台', trigger: 'change' }], platformId: [{ required: true, message: '请选择平台', trigger: 'change' }],
platformProjectId: [{ required: true, message: '请选择所属项目', trigger: 'change' }],
deviceId: [{ required: true, message: '请选择设备', trigger: 'change' }], deviceId: [{ required: true, message: '请选择设备', trigger: 'change' }],
sensorType: [{ required: true, message: '请选择或输入设备类型', trigger: 'change' }], sensorType: [{ required: true, message: '请选择或输入设备类型', trigger: 'change' }],
pushInterfaceId: [{ required: true, message: '请选择推送接口', trigger: 'change' }] pushInterfaceId: [{ required: true, message: '请选择推送接口', trigger: 'change' }]
@ -433,7 +466,11 @@ export default {
handleQuery() { this.queryParams.pageNum = 1; this.getList() }, handleQuery() { this.queryParams.pageNum = 1; this.getList() },
resetQuery() { this.resetForm('queryForm'); this.handleQuery() }, resetQuery() { this.resetForm('queryForm'); this.handleQuery() },
handleAdd() { handleAdd() {
this.form = { enable: '1', deviceType: 0, pushInterfaceId: null, pushMode: 1, pushInterval: 5, reportIntervalSec: 300, deviceName: '' } this.form = {
enable: '1', deviceType: 0, pushInterfaceId: null, pushMode: 1, pushInterval: 5, reportIntervalSec: 300, deviceName: '',
platformId: null, platformProjectId: null, platformProjectName: '',
deviceId: null, serialNumber: '', sensorType: '', remark: ''
}
this.fieldMappingRows = [] this.fieldMappingRows = []
this.subMappingRows = [] this.subMappingRows = []
this.itemFieldMappingRows = [] this.itemFieldMappingRows = []
@ -441,6 +478,7 @@ export default {
this.subDeviceOptions = [] this.subDeviceOptions = []
this.thingsModelOptions = [] this.thingsModelOptions = []
this.interfaceOptions = [] this.interfaceOptions = []
this.projectOptions = []
this.title = '新增设备绑定'; this.open = true this.title = '新增设备绑定'; this.open = true
}, },
handleUpdate(row) { handleUpdate(row) {
@ -459,11 +497,21 @@ export default {
// 加载该平台的接口列表 // 加载该平台的接口列表
if (row.platformId) { if (row.platformId) {
this.loadInterfaces(row.platformId) this.loadInterfaces(row.platformId)
this.loadProjects(row.platformId)
} }
// 加载子设备映射(网关设备) // 加载子设备映射(网关设备)
if (row.deviceType === 1 && row.id) { if (row.deviceType === 1 && row.id) {
listSubMappings(row.id).then(res => { listSubMappings(row.id).then(res => {
this.subMappingRows = (res.data || []).map(s => ({ ...s, subDeviceName: s.subDeviceName || s.subSerialNumber })) this.subMappingRows = (res.data || []).map(s => {
const config = this.safeParseJson(s.onboardConfig, {})
return {
...s,
subDeviceName: s.subDeviceName || s.subSerialNumber,
sensorDepth: config.sensorDepth != null ? config.sensorDepth : null,
holeDepth: config.holeDepth != null ? config.holeDepth : null,
holeElevation: config.holeElevation != null ? config.holeElevation : null
}
})
}) })
} }
this.loadDeviceContext(row.deviceId) this.loadDeviceContext(row.deviceId)
@ -485,7 +533,27 @@ export default {
onPlatformChange(platformId) { onPlatformChange(platformId) {
this.interfaceOptions = [] this.interfaceOptions = []
this.form.pushInterfaceId = null this.form.pushInterfaceId = null
if (platformId) this.loadInterfaces(platformId) this.projectOptions = []
this.form.platformProjectId = null
this.form.platformProjectName = ''
if (platformId) {
this.loadInterfaces(platformId)
this.loadProjects(platformId)
}
},
loadProjects(platformId) {
if (!platformId) { this.projectOptions = []; return }
getProjectsByPlatformId(platformId).then(res => {
this.projectOptions = res.data || []
}).catch(() => { this.projectOptions = [] })
},
onProjectChange(projectId) {
if (projectId) {
const proj = this.projectOptions.find(p => String(p.id) === String(projectId))
this.form.platformProjectName = proj ? proj.name : ''
} else {
this.form.platformProjectName = ''
}
}, },
onDeviceTypeChange(val) { onDeviceTypeChange(val) {
if (val === 1 && this.subMappingRows.length === 0 && this.subDeviceOptions.length > 0) { if (val === 1 && this.subMappingRows.length === 0 && this.subDeviceOptions.length > 0) {
@ -508,6 +576,15 @@ export default {
}) })
}, },
onPushInterfaceChange(interfaceId) { onPushInterfaceChange(interfaceId) {
// 根据接口的 isBatchPush 自动设置推送模式:0=实时推送(1),1=定时推送(2)
if (interfaceId) {
const iface = this.interfaceOptions.find(x => x.id === interfaceId)
if (iface) {
this.form.pushMode = iface.isBatchPush === 1 ? 2 : 1
}
} else {
this.form.pushMode = 1
}
// 从推送接口的 requestSchema 提取字段作为数据映射模板,切换接口时全量刷新 // 从推送接口的 requestSchema 提取字段作为数据映射模板,切换接口时全量刷新
if (!interfaceId) return if (!interfaceId) return
const iface = this.interfaceOptions.find(x => x.id === interfaceId) const iface = this.interfaceOptions.find(x => x.id === interfaceId)
@ -654,7 +731,7 @@ export default {
}, },
addFieldMapping() { this.fieldMappingRows.push({ identifier: '', platformField: '' }) }, addFieldMapping() { this.fieldMappingRows.push({ identifier: '', platformField: '' }) },
addSubMapping() { addSubMapping() {
this.subMappingRows.push({ subSerialNumber: '', subDeviceName: '', deviceRole: 'monitor', onboardConfig: '', platformDeviceId: '', platformPointId: '' }) this.subMappingRows.push({ subSerialNumber: '', subDeviceName: '', deviceRole: 'monitor', onboardConfig: '', sensorDepth: null, holeDepth: null, holeElevation: null, platformDeviceId: '', platformPointId: '' })
}, },
onSubRoleChange(row) { onSubRoleChange(row) {
if (row.deviceRole === 'reference') { if (row.deviceRole === 'reference') {
@ -665,6 +742,7 @@ export default {
}) })
} }
this.refreshSubMappingOnboardConfigs() this.refreshSubMappingOnboardConfigs()
if (this.form.sensorType === 'deep_inclino') this.refreshOnboardConfig()
}, },
onSubSerialChange(row) { onSubSerialChange(row) {
if (!row.subSerialNumber) return if (!row.subSerialNumber) return
@ -676,6 +754,7 @@ export default {
config.sensorId = config.sensorId || row.subSerialNumber config.sensorId = config.sensorId || row.subSerialNumber
row.onboardConfig = JSON.stringify(config) row.onboardConfig = JSON.stringify(config)
this.refreshSubMappingOnboardConfigs() this.refreshSubMappingOnboardConfigs()
if (this.form.sensorType === 'deep_inclino') this.refreshOnboardConfig()
}, },
findReferenceRow(rows = this.subMappingRows) { findReferenceRow(rows = this.subMappingRows) {
return (rows || []).find(r => r.deviceRole === 'reference' && r.subSerialNumber) return (rows || []).find(r => r.deviceRole === 'reference' && r.subSerialNumber)
@ -706,10 +785,42 @@ export default {
row.onboardConfig = this.buildOnboardConfig(row, refRow) row.onboardConfig = this.buildOnboardConfig(row, refRow)
}) })
}, },
refreshOnboardConfig() {
this.subMappingRows.forEach(row => {
const existing = this.safeParseJson(row.onboardConfig, {})
if (!existing.sensorId) existing.sensorId = row.subSerialNumber
if (this.form.sensorType === 'deep_inclino' && row.deviceRole === 'monitor') {
if (row.sensorDepth != null) existing.sensorDepth = row.sensorDepth
if (row.holeDepth != null) existing.holeDepth = row.holeDepth
if (row.holeElevation != null) existing.holeElevation = row.holeElevation
} else {
delete existing.sensorDepth
delete existing.holeDepth
delete existing.holeElevation
}
row.onboardConfig = JSON.stringify(existing)
})
},
syncHoleDepthToAllMonitors(value) {
this.subMappingRows.forEach(row => {
if (row.deviceRole === 'monitor') row.holeDepth = value
})
this.refreshOnboardConfig()
},
syncHoleElevationToAllMonitors(value) {
this.subMappingRows.forEach(row => {
if (row.deviceRole === 'monitor') row.holeElevation = value
})
this.refreshOnboardConfig()
},
normalizeSubMappingsForSubmit() { normalizeSubMappingsForSubmit() {
const rows = (this.subMappingRows || []) const rows = (this.subMappingRows || [])
.filter(r => r.subSerialNumber) .filter(r => r.subSerialNumber)
.map(r => ({ ...r, deviceRole: r.deviceRole || 'monitor' })) .map(r => ({ ...r, deviceRole: r.deviceRole || 'monitor' }))
if (new Set(rows.map(r => r.subSerialNumber)).size !== rows.length) {
this.$modal.msgError('子设备编号不能重复')
return null
}
const references = rows.filter(r => r.deviceRole === 'reference') const references = rows.filter(r => r.deviceRole === 'reference')
const monitors = rows.filter(r => r.deviceRole === 'monitor') const monitors = rows.filter(r => r.deviceRole === 'monitor')
if (references.length > 1) { if (references.length > 1) {
@ -720,11 +831,27 @@ export default {
this.$modal.msgError('静力水准需配置 1 个基准点和至少 1 个监测点') this.$modal.msgError('静力水准需配置 1 个基准点和至少 1 个监测点')
return null return null
} }
if (this.form.sensorType === 'deep_inclino') {
if (monitors.length === 0) {
this.$modal.msgError('深部位移至少配置 1 个监测点')
return null
}
const invalid = monitors.find(r => r.sensorDepth == null || r.holeDepth == null || r.holeElevation == null || r.sensorDepth > r.holeDepth)
if (invalid) {
this.$modal.msgError(`子设备 ${invalid.subSerialNumber} 的传感器深度、孔深、孔口高程必填,且传感器深度不能大于孔深`)
return null
}
}
const refRow = references[0] || null const refRow = references[0] || null
return rows.map(row => ({ return rows.map(row => {
...row, const onboardConfig = this.safeParseJson(this.buildOnboardConfig(row, refRow), {})
onboardConfig: this.buildOnboardConfig(row, refRow) if (this.form.sensorType === 'deep_inclino' && row.deviceRole === 'monitor') {
})) onboardConfig.sensorDepth = row.sensorDepth
onboardConfig.holeDepth = row.holeDepth
onboardConfig.holeElevation = row.holeElevation
}
return { ...row, onboardConfig: JSON.stringify(onboardConfig) }
})
}, },
safeParseJson(value, defaultValue) { safeParseJson(value, defaultValue) {
if (!value) return defaultValue if (!value) return defaultValue
@ -733,7 +860,18 @@ export default {
cancel() { this.open = false }, cancel() { this.open = false },
submitForm() { submitForm() {
this.$refs.form.validate(valid => { this.$refs.form.validate(valid => {
if (!valid) return if (!valid) {
// 自动定位到第一个校验失败的字段
this.$nextTick(() => {
const firstError = this.$refs.form.$el.querySelector('.el-form-item.is-error')
if (firstError) {
firstError.scrollIntoView({ behavior: 'smooth', block: 'center' })
const input = firstError.querySelector('input, .el-select .el-input__inner, textarea')
if (input) input.focus()
}
})
return
}
this.form.fieldMapping = this.fieldMappingRows.length > 0 this.form.fieldMapping = this.fieldMappingRows.length > 0
? JSON.stringify(this.fieldMappingRows) : null ? JSON.stringify(this.fieldMappingRows) : null
const normalizedSubRows = this.form.deviceType === 1 const normalizedSubRows = this.form.deviceType === 1

View File

@ -119,14 +119,14 @@
</template> </template>
<el-divider content-position="left">平台参数</el-divider> <el-divider content-position="left">平台参数</el-divider>
<div class="schema-tip" style="margin-bottom:8px"> <div class="schema-tip" style="margin-bottom:8px">
这里维护平台运行所需的业务参数,例如 `projectId`、项目名称、站点编码等;初始化流程写回的值也会保存在这里,可直接前端补录或修改。 这里维护平台运行所需的业务参数,例如站点编码等;初始化流程写回的值也会保存在这里,可直接前端补录或修改。
</div> </div>
<key-value-editor <key-value-editor
v-model="platformConfigRows" v-model="platformConfigRows"
add-text="添加平台参数" add-text="添加平台参数"
:show-enabled="false" :show-enabled="false"
:show-description="true" :show-description="true"
key-placeholder="参数名,如 projectId" key-placeholder="参数名"
value-placeholder="参数值" value-placeholder="参数值"
/> />
<el-form-item label="启用"> <el-form-item label="启用">
@ -224,7 +224,7 @@ export default {
}) })
}, },
handleInitialize(row) { handleInitialize(row) {
this.$confirm(`确认初始化平台【${row.name}】?将执行初始化流程获取 userId/projectId 等信息。`).then(() => { this.$confirm(`确认初始化平台【${row.name}】?将执行初始化流程获取 userId。`).then(() => {
initializePlatform(row.id).then(() => { initializePlatform(row.id).then(() => {
this.$modal.msgSuccess('初始化成功') this.$modal.msgSuccess('初始化成功')
this.getList() this.getList()
@ -240,7 +240,7 @@ export default {
if (!value) return [] if (!value) return []
try { try {
const parsed = JSON.parse(value) const parsed = JSON.parse(value)
return Object.keys(parsed).map(key => ({ return Object.keys(parsed).filter(key => !['projectId', 'projectName'].includes(key)).map(key => ({
key, key,
value: parsed[key] == null ? '' : String(parsed[key]), value: parsed[key] == null ? '' : String(parsed[key]),
description: '' description: ''
@ -252,7 +252,7 @@ export default {
buildObjectJson(rows) { buildObjectJson(rows) {
const result = {} const result = {}
;(rows || []).forEach(item => { ;(rows || []).forEach(item => {
if (!item || !item.key) return if (!item || !item.key || ['projectId', 'projectName'].includes(item.key)) return
result[item.key] = item.value result[item.key] = item.value
}) })
return Object.keys(result).length > 0 ? JSON.stringify(result) : null return Object.keys(result).length > 0 ? JSON.stringify(result) : null