diff --git a/.claude/settings.local.json b/.claude/settings.local.json index fa977bd3..683dd7bd 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -28,7 +28,9 @@ "Bash(npm run:*)", "mcp__filesystem__read_text_file", "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:*)" ] } } diff --git a/fastbee-common/src/main/java/com/fastbee/common/constant/FastBeeConstant.java b/fastbee-common/src/main/java/com/fastbee/common/constant/FastBeeConstant.java index 48d88c51..d60c0b3a 100644 --- a/fastbee-common/src/main/java/com/fastbee/common/constant/FastBeeConstant.java +++ b/fastbee-common/src/main/java/com/fastbee/common/constant/FastBeeConstant.java @@ -299,6 +299,11 @@ public interface FastBeeConstant { */ String DATA_REPAIR_CONFIG_KEY = "data:repair:config"; + /** + * 平台对接KEYS + * DCP平台项目列表缓存key + */ + String DCP_PROJECT_INFO_KEY = "platform:dcp:projectInfoKey:"; } diff --git a/fastbee-common/src/main/java/com/fastbee/common/core/redis/RedisKeyBuilder.java b/fastbee-common/src/main/java/com/fastbee/common/core/redis/RedisKeyBuilder.java index 91114794..a5756857 100644 --- a/fastbee-common/src/main/java/com/fastbee/common/core/redis/RedisKeyBuilder.java +++ b/fastbee-common/src/main/java/com/fastbee/common/core/redis/RedisKeyBuilder.java @@ -161,6 +161,10 @@ public class RedisKeyBuilder { return FastBeeConstant.REDIS.MODBUS_TCP_RUNTIME + serialNumber; } + public static String buildDCPProjectInfoKey(String platformUserId) { + return FastBeeConstant.REDIS.DCP_PROJECT_INFO_KEY + platformUserId; + } + /** * 设备OTA升级实时数据 diff --git a/fastbee-open-api/src/main/java/com/fastbee/controller/platform/PlatformInstanceController.java b/fastbee-open-api/src/main/java/com/fastbee/controller/platform/PlatformInstanceController.java index 55923fa8..9fdf5bc7 100644 --- a/fastbee-open-api/src/main/java/com/fastbee/controller/platform/PlatformInstanceController.java +++ b/fastbee-open-api/src/main/java/com/fastbee/controller/platform/PlatformInstanceController.java @@ -68,9 +68,16 @@ public class PlatformInstanceController extends BaseController { @PreAuthorize("@ss.hasPermi('platform:instance:edit')") @PostMapping("/{id}/initialize") - @ApiOperation("初始化平台(执行初始化流程,获取userId/projectId等)") + @ApiOperation("初始化平台(执行初始化流程,获取userId)") public AjaxResult initialize(@PathVariable Long id) { instanceService.initialize(id); 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)); + } } diff --git a/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/platform/domain/PlatformDeviceMapping.java b/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/platform/domain/PlatformDeviceMapping.java index fc3d64ab..7b592d15 100644 --- a/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/platform/domain/PlatformDeviceMapping.java +++ b/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/platform/domain/PlatformDeviceMapping.java @@ -28,6 +28,12 @@ public class PlatformDeviceMapping implements Serializable { @ApiModelProperty("所属平台ID") private Long platformId; + @ApiModelProperty("DCP项目ID,绑定页面用户自己选择") + private String platformProjectId; + + @ApiModelProperty("DCP项目名称") + private String platformProjectName; + @ApiModelProperty("FastBee设备ID") private Long deviceId; diff --git a/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/platform/flow/FlowContext.java b/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/platform/flow/FlowContext.java index 087a25a2..0a992b79 100644 --- a/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/platform/flow/FlowContext.java +++ b/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/platform/flow/FlowContext.java @@ -1,9 +1,11 @@ package com.fastbee.iot.platform.flow; import com.fastbee.iot.platform.domain.PlatformDeviceMapping; +import com.fastbee.iot.platform.domain.PlatformDeviceSubMapping; import com.fastbee.iot.platform.domain.PlatformInstance; import java.util.HashMap; +import java.util.List; import java.util.Map; /** @@ -19,6 +21,9 @@ public class FlowContext { * 绑定推送关系 */ private final PlatformDeviceMapping deviceMapping; + // 绑定推送子关系 + private List monitorSubMappings; + /** * 设备上报的原始 payload(实时推送时有值) */ @@ -107,6 +112,14 @@ public class FlowContext { this.currentMonitorIndex = currentMonitorIndex; } + public List getMonitorSubMappings() { + return monitorSubMappings; + } + + public void setMonitorSubMappings(List monitorSubMappings) { + this.monitorSubMappings = monitorSubMappings; + } + public String getSubMappingOnboardConfig() { return subMappingOnboardConfig; } diff --git a/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/platform/flow/PlatformFlowExecutor.java b/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/platform/flow/PlatformFlowExecutor.java index ff17374d..447a0e86 100644 --- a/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/platform/flow/PlatformFlowExecutor.java +++ b/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/platform/flow/PlatformFlowExecutor.java @@ -21,6 +21,7 @@ import com.fastbee.iot.platform.mapper.PlatformInterfaceMapper; import com.fastbee.iot.platform.service.IPlatformPushLogService; import com.dtflys.forest.http.ForestRequestType; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.collections4.CollectionUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; @@ -293,6 +294,10 @@ 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 ("platform_project_id".equals(df)) { + return ctx.getDeviceMapping().getPlatformProjectId(); + } if ("serial_number".equals(df)) { // 网关入网时优先使用当前监测点序列号(每个 point/device 以监测点命名) if (StringUtils.hasText(ctx.getCurrentMonitorSerialNumber())) { @@ -321,7 +326,7 @@ public class PlatformFlowExecutor { case "cascadedSubDeviceSensorIds": return collectCascadedSubDeviceSensorIds(ctx); case "cascadedSubDeviceDepths": - return collectCascadedSubDeviceDepths(ctx, fd); + return collectCascadedSubDeviceDepths(ctx); case "referenceSubDeviceSensorIdPairs": return collectReferenceSubDeviceSensorIdPairs(ctx); default: @@ -403,20 +408,19 @@ public class PlatformFlowExecutor { private Object resolveOnboardConfigValue(FlowContext ctx, JSONObject fieldDef) { String name = fieldDef.getString("name"); - if (!fieldDef.containsKey("value")) { - throw new RuntimeException("入网参数 " + name + " 未配置值"); - } - Object value = fieldDef.get("value"); - if (value instanceof JSONArray && ctx.getCurrentMonitorIndex() != null) { - JSONArray values = (JSONArray) value; + List monitorSubMappings = ctx.getMonitorSubMappings(); + if (ctx.getCurrentMonitorIndex() != null && CollectionUtils.isNotEmpty(monitorSubMappings)) { int index = ctx.getCurrentMonitorIndex(); - if (index >= values.size()) { - throw new RuntimeException("入网参数 " + name + " 数组长度不足,当前子设备序号=" - + (index + 1) + ",数组长度=" + values.size()); + PlatformDeviceSubMapping platformDeviceSubMapping = monitorSubMappings.get(index); + JSONObject authConfig = StringUtils.hasText(platformDeviceSubMapping.getOnboardConfig()) + ? 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() { @@ -555,21 +559,26 @@ public class PlatformFlowExecutor { private List collectCascadedSubDeviceSensorIds(FlowContext ctx) { List suffix = collectCascadedSubDeviceSuffix(ctx); Collections.reverse(suffix); - return suffix.stream() + List sensorIds = suffix.stream() .map(sub -> resolveOnboardSensorId(sub.getOnboardConfig(), sub.getSubSerialNumber())) - .filter(StringUtils::hasText) .collect(Collectors.toList()); + if (sensorIds.stream().anyMatch(id -> !StringUtils.hasText(id))) { + throw new RuntimeException("深部位移子设备缺少 sensorId"); + } + return sensorIds; } - private List collectCascadedSubDeviceDepths(FlowContext ctx, JSONObject fieldDef) { + private List collectCascadedSubDeviceDepths(FlowContext ctx) { List monitors = collectCascadedSubDeviceMonitors(ctx); int currentIndex = resolveCurrentMonitorIndex(ctx, monitors); - List allDepths = resolveConfiguredDepths(fieldDef); - if (allDepths.size() < monitors.size()) { - throw new RuntimeException("串联子设备深度数组配置数量不足,监测点数量=" - + monitors.size() + ",深度数量=" + allDepths.size()); + List suffix = new ArrayList<>(); + for (PlatformDeviceSubMapping monitor : monitors.subList(currentIndex, monitors.size())) { + Object depth = readSensorDepth(monitor); + if (depth == null) { + throw new RuntimeException("深部位移子设备 " + monitor.getSubSerialNumber() + " 缺少传感器深度"); + } + suffix.add(depth); } - List suffix = new ArrayList<>(allDepths.subList(currentIndex, monitors.size())); Collections.reverse(suffix); return suffix; } @@ -588,10 +597,13 @@ public class PlatformFlowExecutor { if (mapping.getId() == null) { throw new RuntimeException("串联子设备入网缺少映射ID"); } - List subMappings = subMappingMapper.selectByParentId(mapping.getId()); - List monitors = subMappings == null ? Collections.emptyList() : subMappings.stream() - .filter(PlatformDeviceSubMapping::isMonitor) - .collect(Collectors.toList()); + List monitors = ctx.getMonitorSubMappings(); + if (monitors == null) { + List subMappings = subMappingMapper.selectByParentId(mapping.getId()); + monitors = subMappings == null ? Collections.emptyList() : subMappings.stream() + .filter(PlatformDeviceSubMapping::isMonitor) + .collect(Collectors.toList()); + } if (monitors.isEmpty()) { throw new RuntimeException("串联子设备入网未配置监测点子设备"); } @@ -614,23 +626,15 @@ public class PlatformFlowExecutor { throw new RuntimeException("串联子设备入网无法确定当前监测点序号"); } - private List resolveConfiguredDepths(JSONObject fieldDef) { - if (!fieldDef.containsKey("value")) { - throw new RuntimeException("串联子设备未配置深度数组"); + private Object readSensorDepth(PlatformDeviceSubMapping mapping) { + if (mapping == null || !StringUtils.hasText(mapping.getOnboardConfig())) { + return null; } - Object value = fieldDef.get("value"); - List list = toList(value); - if (list == null && value instanceof String) { - try { - list = JSON.parseArray(((String) value).trim()); - } catch (Exception ignored) { - list = null; - } + try { + return JSON.parseObject(mapping.getOnboardConfig()).get("sensorDepth"); + } catch (Exception e) { + throw new RuntimeException("子设备 " + mapping.getSubSerialNumber() + " 入网参数不是有效JSON"); } - if (list == null || list.isEmpty()) { - throw new RuntimeException("串联子设备深度数组为空"); - } - return new ArrayList<>(list); } /** @@ -1208,7 +1212,7 @@ public class PlatformFlowExecutor { } /** 确保 Token 有效,过期则刷新 */ - private String ensureToken(PlatformInstance platform) { + public String ensureToken(PlatformInstance platform) { Date now = new Date(); // 提前 5 分钟视为过期 boolean expired = platform.getTokenExpiresAt() == null diff --git a/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/platform/service/IPlatformInstanceService.java b/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/platform/service/IPlatformInstanceService.java index e4682cde..4d95fa82 100644 --- a/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/platform/service/IPlatformInstanceService.java +++ b/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/platform/service/IPlatformInstanceService.java @@ -5,6 +5,7 @@ import com.fastbee.iot.platform.domain.PlatformInstance; import java.util.Date; import java.util.List; +import java.util.Map; public interface IPlatformInstanceService extends IService { @@ -18,4 +19,9 @@ public interface IPlatformInstanceService extends IService { /** 更新 extra_config 中的某个字段 */ void updateExtraConfig(Long platformId, String key, String value); + + /** + * 根据平台ID查询项目列表 + */ + List> getProjectsByPlatformId(Long platformId); } diff --git a/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/platform/service/impl/PlatformDeviceMappingServiceImpl.java b/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/platform/service/impl/PlatformDeviceMappingServiceImpl.java index 3a17f13e..41816ccc 100644 --- a/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/platform/service/impl/PlatformDeviceMappingServiceImpl.java +++ b/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/platform/service/impl/PlatformDeviceMappingServiceImpl.java @@ -72,6 +72,10 @@ public class PlatformDeviceMappingServiceImpl extends ServiceImpl @@ -27,6 +34,8 @@ public class PlatformInstanceServiceImpl extends ServiceImpl selectList(PlatformInstance query) { @@ -75,4 +84,73 @@ public class PlatformInstanceServiceImpl extends ServiceImpl> 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>) 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> result = parseProjectsResponse(responseBody); + redisCache.setCacheObject(key, result, 10, TimeUnit.MINUTES); + return result; + } + + @SuppressWarnings("unchecked") + private List> parseProjectsResponse(String responseBody) { + String trimmed = responseBody.trim(); + List> 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>) data; + } else { + return Collections.emptyList(); + } + } + + return rawList.stream() + .map(m -> { + Map item = new HashMap<>(); + item.put("id", m.get("id")); + item.put("name", m.get("name")); + return item; + }) + .collect(Collectors.toList()); + } } diff --git a/fastbee-service/fastbee-iot-service/src/main/resources/mapper/iot/platform/PlatformDeviceMappingMapper.xml b/fastbee-service/fastbee-iot-service/src/main/resources/mapper/iot/platform/PlatformDeviceMappingMapper.xml index e17e37f9..50a774a6 100644 --- a/fastbee-service/fastbee-iot-service/src/main/resources/mapper/iot/platform/PlatformDeviceMappingMapper.xml +++ b/fastbee-service/fastbee-iot-service/src/main/resources/mapper/iot/platform/PlatformDeviceMappingMapper.xml @@ -3,28 +3,30 @@ - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + +