Merge branch 'hhk' into main

main
HuangHuiKang 4 weeks ago
commit 404794c86f

@ -1,8 +1,11 @@
package cn.iocoder.yudao.framework.common.util.opc;
import org.eclipse.milo.opcua.sdk.client.OpcUaClient;
import org.eclipse.milo.opcua.sdk.client.api.config.OpcUaClientConfig;
import org.eclipse.milo.opcua.sdk.client.api.config.OpcUaClientConfigBuilder;
import org.eclipse.milo.opcua.sdk.client.api.identity.AnonymousProvider;
import org.eclipse.milo.opcua.sdk.client.api.identity.UsernameProvider;
import org.eclipse.milo.opcua.stack.client.DiscoveryClient;
import org.eclipse.milo.opcua.stack.core.types.builtin.DataValue;
import org.eclipse.milo.opcua.stack.core.types.builtin.NodeId;
import org.eclipse.milo.opcua.stack.core.types.builtin.Variant;
@ -10,7 +13,11 @@ import org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.UInteger;
import org.eclipse.milo.opcua.stack.core.types.enumerated.TimestampsToReturn;
import org.eclipse.milo.opcua.stack.core.types.structured.EndpointDescription;
import java.net.InetAddress;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.function.Predicate;
/**
@ -37,9 +44,6 @@ public class OpcUtils {
return connect(url, null, null, timeoutSeconds);
}
/**
* OPC UA
*/
public static boolean connect(String url, String username, String password, int timeoutSeconds) {
if (isConnected) {
System.out.println(LOG_PREFIX + "客户端已连接,无需重复连接");
@ -51,24 +55,105 @@ public class OpcUtils {
try {
System.out.println(LOG_PREFIX + "正在连接到OPC UA服务器: " + url);
// 提取主机和端口
final String targetHost = extractHostFromUrl(url);
final int targetPort = extractPortFromUrl(url);
final String path = extractPathFromUrl(url);
System.out.println(LOG_PREFIX + "目标主机: " + targetHost + ", 端口: " + targetPort + ", 路径: " + path);
// 将主机名解析为IP地址
final String ipAddress = resolveToIpAddress(targetHost);
System.out.println(LOG_PREFIX + "解析为IP地址: " + ipAddress);
if (username != null && password != null && !username.isEmpty()) {
System.out.println(LOG_PREFIX + "使用用户名密码认证: " + username);
Predicate<EndpointDescription> endpointPredicate = e ->
"http://opcfoundation.org/UA/SecurityPolicy#None".equals(e.getSecurityPolicyUri());
client = OpcUaClient.create(url, endpoints ->
endpoints.stream()
.filter(endpointPredicate)
.findFirst(),
configBuilder -> configBuilder
.setIdentityProvider(new UsernameProvider(username, password))
.setRequestTimeout(UInteger.valueOf(timeoutSeconds * 1000L))
.build()
);
// 用户名密码认证
client = OpcUaClient.create(url, endpoints -> {
if (endpoints == null || endpoints.isEmpty()) {
System.err.println(LOG_PREFIX + "服务器未返回任何端点");
return Optional.empty();
}
System.out.println(LOG_PREFIX + "发现端点数量: " + endpoints.size());
// 查找无安全策略的端点
for (EndpointDescription endpoint : endpoints) {
String endpointUrl = endpoint.getEndpointUrl();
System.out.println(LOG_PREFIX + "检查端点: " + endpointUrl +
" | 安全策略: " + endpoint.getSecurityPolicyUri());
if ("http://opcfoundation.org/UA/SecurityPolicy#None".equals(endpoint.getSecurityPolicyUri())) {
// 修正端点URL强制使用IP地址
String correctedUrl = forceIpAddressEndpoint(endpointUrl, ipAddress, targetPort, path);
System.out.println(LOG_PREFIX + "强制使用IP地址: " + endpointUrl + " -> " + correctedUrl);
return Optional.of(new EndpointDescription(
correctedUrl,
endpoint.getServer(),
endpoint.getServerCertificate(),
endpoint.getSecurityMode(),
endpoint.getSecurityPolicyUri(),
endpoint.getUserIdentityTokens(),
endpoint.getTransportProfileUri(),
endpoint.getSecurityLevel()
));
}
}
System.err.println(LOG_PREFIX + "未找到无安全策略的端点");
return Optional.empty();
}, configBuilder -> configBuilder
.setIdentityProvider(new UsernameProvider(username, password))
.setRequestTimeout(UInteger.valueOf(timeoutSeconds * 1000L))
.build());
} else {
System.out.println(LOG_PREFIX + "使用匿名认证");
client = OpcUaClient.create(url);
// 对于匿名认证,手动发现端点并修正
List<EndpointDescription> endpoints = DiscoveryClient.getEndpoints(url).get(timeoutSeconds, TimeUnit.SECONDS);
if (endpoints == null || endpoints.isEmpty()) {
System.err.println(LOG_PREFIX + "服务器未返回任何端点");
return false;
}
// 查找无安全策略的端点
Optional<EndpointDescription> selectedEndpoint = Optional.empty();
for (EndpointDescription endpoint : endpoints) {
if ("http://opcfoundation.org/UA/SecurityPolicy#None".equals(endpoint.getSecurityPolicyUri())) {
selectedEndpoint = Optional.of(endpoint);
break;
}
}
if (!selectedEndpoint.isPresent()) {
System.err.println(LOG_PREFIX + "未找到无安全策略的端点");
return false;
}
EndpointDescription endpoint = selectedEndpoint.get();
String correctedUrl = forceIpAddressEndpoint(endpoint.getEndpointUrl(), ipAddress, targetPort, path);
System.out.println(LOG_PREFIX + "强制使用IP地址: " + endpoint.getEndpointUrl() + " -> " + correctedUrl);
EndpointDescription correctedEndpoint = new EndpointDescription(
correctedUrl,
endpoint.getServer(),
endpoint.getServerCertificate(),
endpoint.getSecurityMode(),
endpoint.getSecurityPolicyUri(),
endpoint.getUserIdentityTokens(),
endpoint.getTransportProfileUri(),
endpoint.getSecurityLevel()
);
OpcUaClientConfigBuilder configBuilder = OpcUaClientConfig.builder()
.setEndpoint(correctedEndpoint)
.setIdentityProvider(new AnonymousProvider())
.setRequestTimeout(UInteger.valueOf(timeoutSeconds * 1000L));
client = OpcUaClient.create(configBuilder.build());
}
client.connect().get(timeoutSeconds, TimeUnit.SECONDS);
@ -84,10 +169,321 @@ public class OpcUtils {
} catch (Exception e) {
System.err.println(LOG_PREFIX + "连接失败: " + e.getMessage());
e.printStackTrace();
return false;
}
}
/**
* IP
*/
private static String resolveToIpAddress(String host) {
try {
// 如果已经是IP地址直接返回
if (host.matches("\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}")) {
return host;
}
// 尝试解析主机名
InetAddress address = InetAddress.getByName(host);
return address.getHostAddress();
} catch (Exception e) {
System.err.println(LOG_PREFIX + "解析主机名失败: " + host + ", 错误: " + e.getMessage());
return host; // 返回原始值
}
}
/**
* 使IPURL
*/
private static String forceIpAddressEndpoint(String endpointUrl, String ipAddress, int defaultPort, String defaultPath) {
try {
// 提取端口和路径
int port = extractPortFromUrl(endpointUrl);
if (port <= 0) {
port = defaultPort;
}
String path = extractPathFromUrl(endpointUrl);
if (path.isEmpty()) {
path = defaultPath;
}
return String.format("opc.tcp://%s:%d%s", ipAddress, port, path);
} catch (Exception e) {
System.err.println(LOG_PREFIX + "构建IP端点URL失败: " + e.getMessage());
return endpointUrl;
}
}
/**
* URL
*/
private static String extractHostFromUrl(String url) {
try {
// 格式: opc.tcp://host:port/path
if (url == null || url.trim().isEmpty()) {
return "localhost";
}
String withoutProtocol = url.replace("opc.tcp://", "");
// 处理IPv6地址
if (withoutProtocol.startsWith("[")) {
// IPv6地址格式: [2001:db8::1]:4840/path
int closeBracket = withoutProtocol.indexOf("]");
if (closeBracket > 0) {
String host = withoutProtocol.substring(1, closeBracket);
return host;
}
}
// 普通IPv4或主机名
String[] parts = withoutProtocol.split("[:/]");
if (parts.length > 0 && !parts[0].isEmpty()) {
return parts[0];
}
} catch (Exception e) {
System.err.println(LOG_PREFIX + "提取主机地址失败: " + e.getMessage());
}
return "localhost";
}
/**
* URL
*/
private static int extractPortFromUrl(String url) {
try {
if (url == null || url.trim().isEmpty()) {
return 4840; // 默认OPC UA端口
}
String withoutProtocol = url.replace("opc.tcp://", "");
// 查找端口号
int portStart = -1;
int portEnd = -1;
if (withoutProtocol.startsWith("[")) {
// IPv6地址: [2001:db8::1]:4840/path
int closeBracket = withoutProtocol.indexOf("]");
if (closeBracket > 0) {
portStart = withoutProtocol.indexOf(":", closeBracket);
}
} else {
// IPv4或主机名: host:4840/path
int firstColon = withoutProtocol.indexOf(":");
if (firstColon > 0) {
portStart = firstColon;
}
}
if (portStart > 0) {
portStart++; // 跳过冒号
portEnd = withoutProtocol.indexOf("/", portStart);
if (portEnd < 0) {
portEnd = withoutProtocol.length();
}
String portStr = withoutProtocol.substring(portStart, portEnd);
if (portStr.matches("\\d+")) {
return Integer.parseInt(portStr);
}
}
} catch (Exception e) {
System.err.println(LOG_PREFIX + "提取端口失败: " + e.getMessage());
}
return 4840; // 默认OPC UA端口
}
/**
* URL
*/
private static String extractPathFromUrl(String url) {
try {
if (url == null || url.trim().isEmpty()) {
return "";
}
String withoutProtocol = url.replace("opc.tcp://", "");
// 查找路径开始位置
int pathStart = -1;
if (withoutProtocol.startsWith("[")) {
// IPv6地址: [2001:db8::1]:4840/path
int closeBracket = withoutProtocol.indexOf("]");
if (closeBracket > 0) {
// 检查是否有端口
int colonAfterBracket = withoutProtocol.indexOf(":", closeBracket);
if (colonAfterBracket > 0) {
// 有端口: [2001:db8::1]:4840/path
int slashAfterPort = withoutProtocol.indexOf("/", colonAfterBracket);
if (slashAfterPort > 0) {
return withoutProtocol.substring(slashAfterPort);
}
} else {
// 无端口: [2001:db8::1]/path
int slashAfterBracket = withoutProtocol.indexOf("/", closeBracket);
if (slashAfterBracket > 0) {
return withoutProtocol.substring(slashAfterBracket);
}
}
}
} else {
// IPv4或主机名
// 先找主机名结束位置
int hostEnd = withoutProtocol.indexOf(":");
if (hostEnd < 0) {
// 无端口: host/path
hostEnd = withoutProtocol.indexOf("/");
if (hostEnd > 0) {
return withoutProtocol.substring(hostEnd);
}
} else {
// 有端口: host:port/path
int portEnd = withoutProtocol.indexOf("/", hostEnd);
if (portEnd > 0) {
return withoutProtocol.substring(portEnd);
}
}
}
} catch (Exception e) {
System.err.println(LOG_PREFIX + "提取路径失败: " + e.getMessage());
}
return "";
}
/**
* URL
*/
private static String extractProtocolFromUrl(String url) {
try {
if (url == null || url.trim().isEmpty()) {
return "opc.tcp";
}
int protocolEnd = url.indexOf("://");
if (protocolEnd > 0) {
return url.substring(0, protocolEnd);
}
} catch (Exception e) {
System.err.println(LOG_PREFIX + "提取协议失败: " + e.getMessage());
}
return "opc.tcp";
}
/**
* URL
*/
private static String buildUrl(String protocol, String host, int port, String path) {
StringBuilder url = new StringBuilder();
if (protocol == null || protocol.isEmpty()) {
protocol = "opc.tcp";
}
url.append(protocol).append("://");
// 处理IPv6地址
if (host.contains(":")) {
url.append("[").append(host).append("]");
} else {
url.append(host);
}
if (port > 0) {
url.append(":").append(port);
}
if (path != null && !path.isEmpty()) {
if (!path.startsWith("/")) {
url.append("/");
}
url.append(path);
}
return url.toString();
}
/**
* URL使
*/
private static String correctEndpointUrl(String endpointUrl, String targetHost, int targetPort) {
if (endpointUrl == null || endpointUrl.trim().isEmpty()) {
return endpointUrl;
}
try {
// 提取原URL的各个部分
String protocol = extractProtocolFromUrl(endpointUrl);
String originalHost = extractHostFromUrl(endpointUrl);
int originalPort = extractPortFromUrl(endpointUrl);
String path = extractPathFromUrl(endpointUrl);
// 决定使用哪个主机
String useHost = targetHost;
int usePort = (targetPort > 0) ? targetPort : originalPort;
// 如果原端口无效,使用默认端口
if (usePort <= 0) {
usePort = 4840;
}
// 构建修正后的URL
return buildUrl(protocol, useHost, usePort, path);
} catch (Exception e) {
System.err.println(LOG_PREFIX + "修正端点URL失败: " + e.getMessage());
return endpointUrl;
}
}
/**
*
*/
private static boolean isLocalAddress(String url) {
if (url == null) return false;
return url.contains("127.0.0.1") ||
url.contains("localhost") ||
url.contains("127.0.1.1") ||
url.contains("[::1]") ||
url.startsWith("opc.tcp://localhost") ||
url.startsWith("opc.tcp://127.");
}
/**
* URL
*/
private static String correctEndpointUrl(String endpointUrl, String targetHost) {
if (endpointUrl == null) return endpointUrl;
// 替换各种本地地址表示形式
String corrected = endpointUrl
.replace("127.0.0.1", targetHost)
.replace("localhost", targetHost)
.replace("127.0.1.1", targetHost)
.replace("[::1]", targetHost);
// 如果还有localhost:端口的形式
if (corrected.contains("localhost:")) {
corrected = corrected.replace("localhost", targetHost);
}
return corrected;
}
/**
*
*/

@ -42,15 +42,15 @@ public class SchedulerManager {
Integer retryCount, Integer retryInterval)
throws SchedulerException {
validateScheduler();
String jobKeyName = jobHandlerName+ "_"+ jobId;
JobKey jobKey = JobKey.jobKey(jobKeyName, DEFAULT_GROUP);
// String jobKeyName = jobHandlerName+ "_"+ jobId;
// JobKey jobKey = JobKey.jobKey(jobKeyName, DEFAULT_GROUP);
// 创建 JobDetail 对象
JobDetail jobDetail = JobBuilder.newJob(JobHandlerInvoker.class)
.usingJobData(JobDataKeyEnum.JOB_ID.name(), jobId)
.usingJobData(JobDataKeyEnum.JOB_HANDLER_NAME.name(), jobHandlerName)
.withIdentity(jobKey).build();
.withIdentity(jobHandlerName).build();
// 创建 Trigger 对象
Trigger trigger = this.buildTrigger(jobKey, jobHandlerParam, cronExpression, retryCount, retryInterval);
Trigger trigger = this.buildTrigger(jobHandlerName, jobHandlerParam, cronExpression, retryCount, retryInterval);
// 新增 Job 调度
scheduler.scheduleJob(jobDetail, trigger);
}

@ -48,9 +48,9 @@ public class JobServiceImpl implements JobService {
public Long createJob(JobSaveReqVO createReqVO) throws SchedulerException {
validateCronExpression(createReqVO.getCronExpression());
// 1.1 校验唯一性
// if (jobMapper.selectByHandlerName(createReqVO.getHandlerName()) != null) {
// throw exception(JOB_HANDLER_EXISTS);
// }
if (jobMapper.selectByHandlerName(createReqVO.getHandlerName()) != null) {
throw exception(JOB_HANDLER_EXISTS);
}
// 1.2 校验 JobHandler 是否存在
validateJobHandlerExists(createReqVO.getHandlerName());
@ -64,7 +64,7 @@ public class JobServiceImpl implements JobService {
schedulerManager.addJob(job.getId(), job.getHandlerName(), job.getHandlerParam(), job.getCronExpression(),
createReqVO.getRetryCount(), createReqVO.getRetryInterval());
// 3.2 更新 JobDO
JobDO updateObj = JobDO.builder().id(job.getId()).status(JobStatusEnum.STOP.getStatus()).build();
JobDO updateObj = JobDO.builder().id(job.getId()).status(JobStatusEnum.NORMAL.getStatus()).build();
jobMapper.updateById(updateObj);
return job.getId();
}

@ -36,6 +36,7 @@ public interface ErrorCodeConstants {
//======================================数据采集相关=================================================
ErrorCode DEVICE_MODEL_NOT_EXISTS = new ErrorCode(1_003_000_000, "采集设备模型不存在");
ErrorCode DEVICE_ID_MODEL_NOT_EXISTS = new ErrorCode(1_003_000_000, "该设备模型ID不能为空");
ErrorCode POINT_ID_MODEL_NOT_EXISTS = new ErrorCode(1_003_000_000, "该点位参数ID不能为空");
ErrorCode DEVICE_MODEL_ATTRIBUTE_NOT_EXISTS = new ErrorCode(1_003_000_000, "采集设备模型点位不存在");
ErrorCode DEVICE_ATTRIBUTE_NOT_EXISTS = new ErrorCode(1_003_000_000, "设备属性不存在");
@ -49,6 +50,7 @@ public interface ErrorCodeConstants {
ErrorCode OPC_CLOSE_CONNECT_FAILURE= new ErrorCode(1_003_000_000, "OPC断开连接失败");
ErrorCode OPC_PARAMETER_DOES_NOT_EXIST= new ErrorCode(1_003_000_000, "连接关闭参数不存在");
ErrorCode CREATE_TDENGINE_FAILURE= new ErrorCode(1_003_000_000, "创建Tdengine子表失败");
ErrorCode RECIPE_NOT_EXISTS = new ErrorCode(1_003_000_000, "配方主不存在");
ErrorCode RECIPE_POINT_NOT_EXISTS = new ErrorCode(1_003_000_000, "配方点位配置表(配方与设备点位关联)不存在");
ErrorCode RECIPE_TYPE_NOT_EXISTS = new ErrorCode(1_003_000_000, "配方类型表(基础字典)不存在");

@ -5,22 +5,16 @@ import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.common.util.opc.OpcUtils;
import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
import cn.iocoder.yudao.module.infra.controller.admin.job.vo.job.JobSaveReqVO;
import cn.iocoder.yudao.module.infra.dal.dataobject.job.JobDO;
import cn.iocoder.yudao.module.infra.service.job.JobService;
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.*;
import cn.iocoder.yudao.module.iot.controller.admin.devicecontactmodel.vo.DeviceContactModelPageReqVO;
import cn.iocoder.yudao.module.iot.controller.admin.devicemodelattribute.vo.DeviceModelAttributePageReqVO;
import cn.iocoder.yudao.module.iot.dal.dataobject.device.DeviceAttributeDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.device.DeviceDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.devicemodelattribute.DeviceModelAttributeDO;
import cn.iocoder.yudao.module.iot.dal.devicecontactmodel.DeviceContactModelDO;
import cn.iocoder.yudao.module.iot.service.device.DeviceService;
import cn.iocoder.yudao.module.iot.service.device.TDengineService;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.fasterxml.jackson.core.JsonProcessingException;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
@ -194,7 +188,6 @@ public class DeviceController {
}
// ==================== 子表(设备属性) ====================
@GetMapping("/device-attribute/page")

@ -98,4 +98,16 @@ public class DeviceModelAttributeController {
BeanUtils.toBean(list, DeviceModelAttributeRespVO.class));
}
@GetMapping("/operationAnalysisDetails")
@Operation(summary = "设备运行参数分析-详情")
// @Parameter(name = "deviceId,modelId", description = "设备id点位参数id", required = true)
@PreAuthorize("@ss.hasPermission('iot:device-model-attribute:query')")
public CommonResult<List<Map<String, Object>>> operationAnalysisDetails(@RequestParam("deviceId") Long deviceId,
@RequestParam("modelId") Long modelId,
@RequestParam(name = "collectionStartTime", required = false ) String collectionStartTime,
@RequestParam(name = "collectionEndTime", required = false ) String collectionEndTime) {
List<Map<String, Object>> deviceModelAttribute = deviceModelAttributeService.operationAnalysisDetails(deviceId,modelId,collectionStartTime,collectionEndTime);
return success(deviceModelAttribute);
}
}

@ -4,18 +4,12 @@ import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.DevicePageReqVO;
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.DeviceSaveReqVO;
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.LineDeviceRequestVO;
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.LineDeviceRespVO;
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.*;
import cn.iocoder.yudao.module.iot.controller.admin.devicecontactmodel.vo.DeviceContactModelPageReqVO;
import cn.iocoder.yudao.module.iot.controller.admin.devicemodelattribute.vo.DeviceModelAttributePageReqVO;
import cn.iocoder.yudao.module.iot.dal.dataobject.device.DeviceDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.device.DeviceAttributeDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.devicemodelattribute.DeviceModelAttributeDO;
import cn.iocoder.yudao.module.iot.dal.devicecontactmodel.DeviceContactModelDO;
import com.fasterxml.jackson.core.JsonProcessingException;
import org.springframework.web.bind.annotation.RequestParam;
import javax.validation.Valid;
import java.util.Collection;
@ -128,4 +122,5 @@ public interface DeviceService {
Map<String, List<Map<String, Object>>> singleDevice(Long deviceId) throws JsonProcessingException;
List<Map<String, Object>> historyRecord(Long deviceId,String collectionStartTime, String collectionEndTime);
}

@ -6,12 +6,8 @@ import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.common.util.opc.OpcUtils;
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.DevicePageReqVO;
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.DeviceSaveReqVO;
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.LineDeviceRequestVO;
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.LineDeviceRespVO;
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.*;
import cn.iocoder.yudao.module.iot.controller.admin.devicecontactmodel.vo.DeviceContactModelPageReqVO;
import cn.iocoder.yudao.module.iot.controller.admin.devicemodelattribute.vo.DeviceModelAttributePageReqVO;
import cn.iocoder.yudao.module.iot.controller.admin.mqttdatarecord.vo.MqttDataRecordPageReqVO;
import cn.iocoder.yudao.module.iot.dal.dataobject.device.DeviceDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.deviceattributetype.DeviceAttributeTypeDO;
@ -25,22 +21,16 @@ import cn.iocoder.yudao.module.iot.dal.mysql.devicecontactmodel.DeviceContactMod
import cn.iocoder.yudao.module.iot.dal.mysql.devicemodel.DeviceModelMapper;
import cn.iocoder.yudao.module.iot.dal.mysql.devicemodelattribute.DeviceModelAttributeMapper;
import cn.iocoder.yudao.module.iot.dal.mysql.mqttdatarecord.MqttDataRecordMapper;
import cn.iocoder.yudao.module.iot.dal.dataobject.device.DeviceDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.device.DeviceAttributeDO;
import cn.iocoder.yudao.module.iot.dal.mysql.device.DeviceMapper;
import cn.iocoder.yudao.module.iot.dal.mysql.device.DeviceAttributeMapper;
import com.alibaba.fastjson.JSON;
import com.baomidou.dynamic.datasource.annotation.DS;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.jdbc.core.JdbcTemplate;
@ -52,7 +42,6 @@ import javax.annotation.Resource;
import java.sql.Timestamp;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Collectors;
@ -95,6 +84,7 @@ public class DeviceServiceImpl implements DeviceService {
@Resource
private DeviceAttributeTypeMapper deviceAttributeTypeMapper;
@Override
@Transactional(rollbackFor = Exception.class)
public DeviceDO createDevice(DeviceSaveReqVO createReqVO) {
@ -220,7 +210,8 @@ public class DeviceServiceImpl implements DeviceService {
public PageResult<DeviceContactModelDO> getDeviceAttributePage(PageParam pageReqVO, DeviceContactModelPageReqVO deviceModelAttributePageReqVO) {
// 参数校验
if (deviceModelAttributePageReqVO.getDeviceId() == null) {
throw new IllegalArgumentException("关联设备模型ID不能为空");
throw exception(DEVICE_ID_MODEL_NOT_EXISTS);
}
// 查询设备信息,只查询一次
@ -378,21 +369,19 @@ public class DeviceServiceImpl implements DeviceService {
deviceModelAttributeLambdaQueryWrapper.eq(DeviceContactModelDO::getDeviceId,createReqVO.getId());
List<DeviceContactModelDO> deviceContactModelDOS = deviceContactModelMapper.selectList(deviceModelAttributeLambdaQueryWrapper);
//连接后查询5次保存到数据库
for (int i = 0; i < 3; i++) {
if (deviceContactModelDOS != null && deviceContactModelDOS.size() > 0){
for (DeviceContactModelDO deviceContactModelDO : deviceContactModelDOS) {
Object addressValue = OpcUtils.readValue(deviceContactModelDO.getAddress() != null ? deviceContactModelDO.getAddress() : "");
deviceContactModelDO.setAddressValue(addressValue);
}
String json = JSON.toJSONString(deviceContactModelDOS);
tdengineService.insertDeviceData(createReqVO.getId(),json);
if (deviceContactModelDOS != null && deviceContactModelDOS.size() > 0){
for (DeviceContactModelDO deviceContactModelDO : deviceContactModelDOS) {
Object addressValue = OpcUtils.readValue(deviceContactModelDO.getAddress() != null ? deviceContactModelDO.getAddress() : "");
deviceContactModelDO.setAddressValue(addressValue);
}
String json = JSON.toJSONString(deviceContactModelDOS);
tdengineService.insertDeviceData(createReqVO.getId(),json);
}
}else {
throw exception(OPC_CONNECT_FAILURE_DOES_NOT_EXIST);
}
@ -632,6 +621,7 @@ public class DeviceServiceImpl implements DeviceService {
}
private void validateDeviceAttributeExists(Long id) {
if (deviceAttributeMapper.selectById(id) == null) {
throw exception(DEVICE_ATTRIBUTE_NOT_EXISTS);

@ -52,4 +52,5 @@ public interface DeviceModelAttributeService {
*/
PageResult<DeviceModelAttributeDO> getDeviceModelAttributePage(DeviceModelAttributePageReqVO pageReqVO);
List<Map<String, Object>> operationAnalysisDetails(Long deviceId, Long modelId,String collectionStartTime, String collectionEndTime);
}

@ -1,13 +1,23 @@
package cn.iocoder.yudao.module.iot.service.devicemodelattribute;
import cn.iocoder.yudao.framework.common.exception.ErrorCode;
import cn.iocoder.yudao.module.iot.dal.dataobject.deviceattributetype.DeviceAttributeTypeDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.devicemodel.DeviceModelDO;
import cn.iocoder.yudao.module.iot.dal.mysql.deviceattributetype.DeviceAttributeTypeMapper;
import cn.iocoder.yudao.module.iot.service.device.TDengineService;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import org.springframework.validation.annotation.Validated;
import org.springframework.transaction.annotation.Transactional;
import java.sql.Timestamp;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.stream.Collectors;
import cn.iocoder.yudao.module.iot.controller.admin.devicemodelattribute.vo.*;
import cn.iocoder.yudao.module.iot.dal.dataobject.devicemodelattribute.DeviceModelAttributeDO;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
@ -31,6 +41,12 @@ public class DeviceModelAttributeServiceImpl implements DeviceModelAttributeServ
@Resource
private DeviceModelAttributeMapper deviceModelAttributeMapper;
@Resource
private TDengineService tdengineService;
@Resource
private DeviceAttributeTypeMapper deviceAttributeTypeMapper;
@Override
public Long createDeviceModelAttribute(DeviceModelAttributeSaveReqVO createReqVO) {
// 插入
@ -76,5 +92,90 @@ public class DeviceModelAttributeServiceImpl implements DeviceModelAttributeServ
return deviceModelAttributeMapper.selectPage(pageReqVO);
}
@Override
public List<Map<String, Object>> operationAnalysisDetails(Long deviceId, Long modelId,String collectionStartTime, String collectionEndTime) {
if(deviceId == null){
throw exception(DEVICE_ID_DOES_NOT_EXIST);
}
if(modelId == null){
throw exception(POINT_ID_MODEL_NOT_EXISTS);
}
List<Map<String, Object>> resultList = new ArrayList<>();
try {
// 1. 获取设备数据列表
List<Map<String, Object>> deviceDataList = tdengineService.getstDeviceDataOrderByTimeDesc(deviceId,collectionStartTime,collectionEndTime);
// 2. 获取属性类型映射
Map<Long, String> idToNameMap = deviceAttributeTypeMapper.selectList()
.stream()
.collect(Collectors.toMap(
DeviceAttributeTypeDO::getId,
DeviceAttributeTypeDO::getName
));
// 3. 遍历每个时间点的数据
for (Map<String, Object> deviceData : deviceDataList) {
String queryDataJson = (String) deviceData.get("queryData");
Timestamp timestamp = (Timestamp) deviceData.get("timestamp");
SimpleDateFormat sdf = new SimpleDateFormat("yy-MM-dd HH:mm:ss");
String formattedTime = sdf.format(timestamp); // 例如 "26-01-06 14:30:45"
if (StringUtils.isNotBlank(queryDataJson) && timestamp != null) {
List<Map<String, Object>> dataList = new ObjectMapper().readValue(
queryDataJson,
new TypeReference<List<Map<String, Object>>>() {}
);
// 按属性类型分组
Map<String, List<Map<String, Object>>> groupedData = new LinkedHashMap<>();
for (Map<String, Object> data : dataList) {
if (((Integer) data.get("id")).longValue() == modelId){
String attributeTypeName = "其他";
String typeStr = (String) data.get("attributeType");
if (typeStr != null) {
try {
attributeTypeName = typeStr;
} catch (Exception e) {
attributeTypeName = "未知";
}
}
Map<String, Object> simplifiedData = new HashMap<>();
simplifiedData.put("addressValue", data.get("addressValue"));
simplifiedData.put("attributeName", data.get("attributeName"));
groupedData
.computeIfAbsent(attributeTypeName, k -> new ArrayList<>())
.add(simplifiedData);
}
}
// 创建当前时间点的Map
Map<String, Object> timePointData = new LinkedHashMap<>();
// 添加属性分组
for (Map.Entry<String, List<Map<String, Object>>> entry : groupedData.entrySet()) {
timePointData.put(entry.getKey(), entry.getValue());
}
// 添加收集时间
timePointData.put("collectTime", formattedTime);
resultList.add(timePointData);
}
}
} catch (Exception e) {
throw new RuntimeException("处理设备数据时发生异常", e);
}
return resultList;
}
}

@ -4,6 +4,7 @@ import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
import cn.iocoder.yudao.module.mes.controller.admin.organization.vo.LineAnalysisTreeDTO;
import cn.iocoder.yudao.module.mes.controller.admin.organization.vo.OrganizationListReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.organization.vo.OrganizationRespVO;
import cn.iocoder.yudao.module.mes.controller.admin.organization.vo.OrganizationSaveReqVO;
@ -12,6 +13,7 @@ import cn.iocoder.yudao.module.mes.service.organization.OrganizationService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
@ -92,4 +94,14 @@ public class OrganizationController {
organizationService.buildVOList(list));
}
@GetMapping("/deviceParameterAnalysis")
@Operation(summary = "设备运行参数分析")
@PreAuthorize("@ss.hasPermission('iot:device:query')")
public CommonResult<List<LineAnalysisTreeDTO.LineNode>> deviceParameterAnalysis(@RequestParam(value = "keyword", required = false) String keyword) {
List<LineAnalysisTreeDTO.LineNode> list = organizationService.deviceParameterAnalysis(keyword);
return success(list);
}
}

@ -0,0 +1,58 @@
package cn.iocoder.yudao.module.mes.controller.admin.organization.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.ArrayList;
import java.util.List;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Schema(description = "管理后台 - 产线数据运行分析 树形结构")
public class LineAnalysisTreeDTO {
@Schema(description = "产线节点列表")
private List<LineNode> lines = new ArrayList<>();
// 产线节点
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public static class LineNode {
private Long id; // 产线ID
private String name; // 产线名称
private List<EquipmentNode> equipments = new ArrayList<>();
}
// 设备节点
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public static class EquipmentNode {
private Long id; // 设备ID
private String name; // 设备名称
private List<ParameterNode> parameters = new ArrayList<>();
}
// 参数节点
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public static class ParameterNode {
private Long id; // 参数ID
private String name; // 参数名称 (对应attributeName)
private String code; // 参数编码 (对应attributeCode)
private String type; // 参数类型 (对应attributeType)
private String dataType;// 数据类型
private String unit; // 单位
private Double ratio; // 倍率
}
}

@ -12,7 +12,7 @@ import java.time.LocalDateTime;
@Schema(description = "管理后台 - 产线工位 Response VO")
@Data
@ExcelIgnoreUnannotated
public class OrganizationRespVO {
public class OrganizationRespVO {
@Schema(description = "组织id", requiredMode = Schema.RequiredMode.REQUIRED, example = "9560")
@ExcelProperty("组织id")

@ -2,9 +2,7 @@ package cn.iocoder.yudao.module.mes.service.organization;
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.module.erp.controller.admin.product.vo.product.ErpProductRespVO;
import cn.iocoder.yudao.module.erp.dal.dataobject.stock.ErpStockOutItemDO;
import cn.iocoder.yudao.module.mes.controller.admin.feedingrecord.vo.FeedingRecordRespVO;
import cn.iocoder.yudao.module.mes.controller.admin.organization.vo.LineAnalysisTreeDTO;
import cn.iocoder.yudao.module.mes.controller.admin.organization.vo.OrganizationListReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.organization.vo.OrganizationRespVO;
import cn.iocoder.yudao.module.mes.controller.admin.organization.vo.OrganizationSaveReqVO;
@ -81,4 +79,6 @@ public interface OrganizationService {
if(ids.isEmpty())return new HashMap<>();
return convertMap(getOrganizationVOList(ids), OrganizationDO::getId);
}
List<LineAnalysisTreeDTO.LineNode> deviceParameterAnalysis(String keyword);
}

@ -4,18 +4,13 @@ import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.erp.controller.admin.product.vo.product.ErpProductRespVO;
import cn.iocoder.yudao.module.erp.dal.dataobject.product.ErpProductDO;
import cn.iocoder.yudao.module.erp.dal.dataobject.stock.ErpStockOutItemDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.device.DeviceDO;
import cn.iocoder.yudao.module.iot.dal.devicecontactmodel.DeviceContactModelDO;
import cn.iocoder.yudao.module.iot.dal.mysql.device.DeviceMapper;
import cn.iocoder.yudao.module.iot.dal.mysql.devicecontactmodel.DeviceContactModelMapper;
import cn.iocoder.yudao.module.iot.service.device.DeviceService;
import cn.iocoder.yudao.module.mes.controller.admin.feedingrecord.vo.FeedingRecordRespVO;
import cn.iocoder.yudao.module.mes.controller.admin.organization.vo.OrgClassEnum;
import cn.iocoder.yudao.module.mes.controller.admin.organization.vo.OrganizationListReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.organization.vo.OrganizationRespVO;
import cn.iocoder.yudao.module.mes.controller.admin.organization.vo.OrganizationSaveReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.organization.vo.*;
import cn.iocoder.yudao.module.mes.controller.admin.orgworker.vo.OrgWorkerPageReqVO;
import cn.iocoder.yudao.module.mes.dal.dataobject.machine.MachineComponentDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.organization.OrganizationDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.orgworker.OrgWorkerDO;
import cn.iocoder.yudao.module.mes.dal.mysql.organization.OrganizationMapper;
@ -24,6 +19,7 @@ import cn.iocoder.yudao.module.mes.dal.redis.no.MesNoRedisDAO;
import cn.iocoder.yudao.module.mes.service.machine.MachineComponentService;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
@ -31,6 +27,7 @@ import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
@ -55,7 +52,10 @@ public class OrganizationServiceImpl implements OrganizationService {
private MesNoRedisDAO noRedisDAO;
@Resource
private AdminUserApi adminUserApi;
@Resource
private DeviceContactModelMapper deviceContactModelMapper;
@Resource
private DeviceMapper deviceMapper;
@Override
public Long createOrganization(OrganizationSaveReqVO createReqVO) {
// 校验父组织id的有效性
@ -255,4 +255,320 @@ public class OrganizationServiceImpl implements OrganizationService {
List<OrganizationDO> list = organizationMapper.selectBatchIds(ids);
return list;
}
@Override
public List<LineAnalysisTreeDTO.LineNode> deviceParameterAnalysis(String keyword) {
//1. 获取产线集合
OrganizationListReqVO organizationListReqVO = new OrganizationListReqVO();
List<OrganizationDO> organizationDOS = getOrganizationList(organizationListReqVO);
List<OrganizationRespVO> organizationRespVOS = buildVOList(organizationDOS);
if (com.baomidou.mybatisplus.core.toolkit.CollectionUtils.isEmpty(organizationRespVOS)) {
return Collections.emptyList();
}
// 2. 获取所有有machineId的产线
List<Long> machineIds = organizationRespVOS.stream()
.map(OrganizationRespVO::getMachineId)
.filter(Objects::nonNull)
.collect(Collectors.toList());
// 3. 根据machineId查询设备
List<DeviceDO> allDevices = Collections.emptyList();
if (!machineIds.isEmpty()) {
LambdaQueryWrapper<DeviceDO> deviceWrapper = new LambdaQueryWrapper<>();
deviceWrapper.in(DeviceDO::getId, machineIds)
.eq(DeviceDO::getIsEnable, true);
allDevices = deviceMapper.selectList(deviceWrapper);
}
// 4. 获取所有参数
List<DeviceContactModelDO> allParameters = getAllParameters(allDevices);
// 5. 构建映射关系
Map<Long, DeviceDO> deviceById = allDevices.stream()
.collect(Collectors.toMap(DeviceDO::getId, Function.identity()));
// 6. 构建设备按产线分组的映射
Map<Long, List<DeviceDO>> devicesByLineId = new HashMap<>();
for (OrganizationRespVO line : organizationRespVOS) {
if (line.getMachineId() != null) {
DeviceDO device = deviceById.get(line.getMachineId());
if (device != null) {
devicesByLineId.computeIfAbsent(line.getId(), k -> new ArrayList<>())
.add(device);
}
}
}
Map<Long, List<DeviceContactModelDO>> paramsByDeviceId = allParameters.stream()
.filter(param -> param.getDeviceId() != null)
.collect(Collectors.groupingBy(DeviceContactModelDO::getDeviceId));
// 7. 构建树结构
return buildTreeStructureWithKeyword(organizationRespVOS, devicesByLineId, paramsByDeviceId,keyword);
}
private List<LineAnalysisTreeDTO.LineNode> buildTreeStructureWithKeyword(List<OrganizationRespVO> lines,
Map<Long, List<DeviceDO>> devicesByLineId,
Map<Long, List<DeviceContactModelDO>> paramsByDeviceId,
String keyword) {
// 统一处理关键词
boolean hasKeyword = StringUtils.isNotBlank(keyword);
String lowerKeyword = hasKeyword ? keyword.toLowerCase() : "";
// 记录匹配情况
Map<Long, Boolean> lineMatchedMap = new HashMap<>();
Map<Long, Boolean> deviceMatchedMap = new HashMap<>();
// 第一遍:分析匹配情况
for (OrganizationRespVO line : lines) {
// 产线是否匹配
boolean lineMatch = !hasKeyword ||
(line.getName() != null && line.getName().toLowerCase().contains(lowerKeyword));
lineMatchedMap.put(line.getId(), lineMatch);
// 检查该产线的设备
List<DeviceDO> lineDevices = devicesByLineId.getOrDefault(line.getId(), new ArrayList<>());
for (DeviceDO device : lineDevices) {
// 设备是否匹配
boolean deviceMatch = !hasKeyword ||
(device.getDeviceName() != null &&
device.getDeviceName().toLowerCase().contains(lowerKeyword));
deviceMatchedMap.put(device.getId(), deviceMatch);
}
}
// 第二遍:构建树结构
return lines.stream().map(line -> {
boolean lineMatch = lineMatchedMap.getOrDefault(line.getId(), false);
// 关键修改:如果产线匹配,跳过后续的设备过滤逻辑
if (hasKeyword && !lineMatch) {
// 检查产线下是否有匹配的设备或参数
boolean hasMatchingDeviceOrParam = checkIfLineHasMatchingDeviceOrParam(
line, devicesByLineId, paramsByDeviceId, lowerKeyword, deviceMatchedMap);
if (!hasMatchingDeviceOrParam) {
return null;
}
}
// 获取该产线的设备
List<DeviceDO> lineDevices = devicesByLineId.getOrDefault(line.getId(), new ArrayList<>());
// 构建设备节点
List<LineAnalysisTreeDTO.EquipmentNode> equipmentNodes = lineDevices.stream()
.map(device -> {
boolean deviceMatch = deviceMatchedMap.getOrDefault(device.getId(), false);
// 关键修改:如果产线匹配,保留所有设备
if (lineMatch) {
// 产线匹配时,保留该设备
List<DeviceContactModelDO> deviceParams = paramsByDeviceId.getOrDefault(device.getId(), new ArrayList<>());
// 产线匹配时,显示所有参数
List<LineAnalysisTreeDTO.ParameterNode> parameterNodes = deviceParams.stream()
.map(this::buildParameterNode)
.collect(Collectors.toList());
return LineAnalysisTreeDTO.EquipmentNode.builder()
.id(device.getId())
.name(device.getDeviceName())
.parameters(parameterNodes)
.build();
}
// 以下为原有逻辑:产线不匹配时的处理
if (hasKeyword && !deviceMatch) {
boolean hasMatchingParam = checkIfDeviceHasMatchingParam(
device, paramsByDeviceId, lowerKeyword);
if (!hasMatchingParam) {
return null;
}
}
List<DeviceContactModelDO> deviceParams = paramsByDeviceId.getOrDefault(device.getId(), new ArrayList<>());
// 构建参数节点
List<LineAnalysisTreeDTO.ParameterNode> parameterNodes = deviceParams.stream()
.filter(param -> {
if (!hasKeyword) {
return true; // 没有关键词,显示所有
}
// 有关键词时:
// 如果设备匹配,显示所有参数
if (deviceMatch) {
return true;
}
// 否则,只显示匹配的参数
return isParameterMatch(param, lowerKeyword);
})
.map(this::buildParameterNode)
.collect(Collectors.toList());
return LineAnalysisTreeDTO.EquipmentNode.builder()
.id(device.getId())
.name(device.getDeviceName())
.parameters(parameterNodes)
.build();
})
.filter(Objects::nonNull)
.filter(equipmentNode -> !lineMatch || !equipmentNode.getParameters().isEmpty()) // 修改过滤条件
.collect(Collectors.toList());
// 构建设备节点
return LineAnalysisTreeDTO.LineNode.builder()
.id(line.getId())
.name(line.getName())
.equipments(equipmentNodes)
.build();
})
.filter(Objects::nonNull)
.filter(lineNode -> !hasKeyword || lineMatchedMap.getOrDefault(lineNode.getId(), false) || !lineNode.getEquipments().isEmpty())
.collect(Collectors.toList());
}
/**
* 线
*/
private boolean checkIfLineHasMatchingDeviceOrParam(OrganizationRespVO line,
Map<Long, List<DeviceDO>> devicesByLineId,
Map<Long, List<DeviceContactModelDO>> paramsByDeviceId,
String lowerKeyword,
Map<Long, Boolean> deviceMatchedMap) {
List<DeviceDO> lineDevices = devicesByLineId.getOrDefault(line.getId(), new ArrayList<>());
for (DeviceDO device : lineDevices) {
// 检查设备是否匹配
boolean deviceMatch = deviceMatchedMap.getOrDefault(device.getId(), false);
if (deviceMatch) {
return true;
}
// 检查设备下的参数是否匹配
List<DeviceContactModelDO> deviceParams = paramsByDeviceId.getOrDefault(device.getId(), new ArrayList<>());
boolean hasMatchingParam = deviceParams.stream()
.anyMatch(param -> isParameterMatch(param, lowerKeyword));
if (hasMatchingParam) {
return true;
}
}
return false;
}
/**
*
*/
private boolean checkIfDeviceHasMatchingParam(DeviceDO device,
Map<Long, List<DeviceContactModelDO>> paramsByDeviceId,
String lowerKeyword) {
List<DeviceContactModelDO> deviceParams = paramsByDeviceId.getOrDefault(device.getId(), new ArrayList<>());
return deviceParams.stream()
.anyMatch(param -> isParameterMatch(param, lowerKeyword));
}
private List<LineAnalysisTreeDTO.LineNode> buildTreeStructure(List<OrganizationRespVO> lines,
Map<Long, List<DeviceDO>> devicesByLineId,
Map<Long, List<DeviceContactModelDO>> paramsByDeviceId) {
return lines.stream().map(line -> {
LineAnalysisTreeDTO.LineNode lineNode = LineAnalysisTreeDTO.LineNode.builder()
.id(line.getId())
.name(line.getName())
.build();
// 获取该产线的设备
List<DeviceDO> lineDevices = devicesByLineId.getOrDefault(line.getId(), new ArrayList<>());
// 构建设备节点
List<LineAnalysisTreeDTO.EquipmentNode> equipmentNodes = lineDevices.stream()
.map(device -> buildEquipmentNode(device, paramsByDeviceId))
.collect(Collectors.toList());
lineNode.setEquipments(equipmentNodes);
return lineNode;
}).collect(Collectors.toList());
}
/**
*
*/
private boolean isParameterMatch(DeviceContactModelDO param, String lowerKeyword) {
if (StringUtils.isBlank(lowerKeyword)) {
return true;
}
return (param.getAttributeName() != null && param.getAttributeName().toLowerCase().contains(lowerKeyword)) ||
(param.getAttributeCode() != null && param.getAttributeCode().toLowerCase().contains(lowerKeyword));
}
/**
*
*/
private LineAnalysisTreeDTO.EquipmentNode buildEquipmentNode(DeviceDO device,
Map<Long, List<DeviceContactModelDO>> paramsByDeviceId) {
LineAnalysisTreeDTO.EquipmentNode equipmentNode = LineAnalysisTreeDTO.EquipmentNode.builder()
.id(device.getId())
.name(device.getDeviceName())
.build();
// 获取设备参数
List<DeviceContactModelDO> deviceParams =
paramsByDeviceId.getOrDefault(device.getId(), new ArrayList<>());
// 构建参数节点
List<LineAnalysisTreeDTO.ParameterNode> parameterNodes = deviceParams.stream()
.map(this::buildParameterNode)
.collect(Collectors.toList());
equipmentNode.setParameters(parameterNodes);
return equipmentNode;
}
/**
*
*/
private LineAnalysisTreeDTO.ParameterNode buildParameterNode(DeviceContactModelDO param) {
return LineAnalysisTreeDTO.ParameterNode.builder()
.id(param.getId())
.name(param.getAttributeName())
.code(param.getAttributeCode())
.type(param.getAttributeType())
.dataType(param.getDataType())
.unit(param.getDataUnit())
.ratio(param.getRatio())
.build();
}
private List<DeviceContactModelDO> getAllParameters(List<DeviceDO> devices) {
if (com.baomidou.mybatisplus.core.toolkit.CollectionUtils.isEmpty(devices)) {
return Collections.emptyList();
}
List<Long> deviceIds = devices.stream()
.map(DeviceDO::getId)
.collect(Collectors.toList());
LambdaQueryWrapper<DeviceContactModelDO> wrapper = new LambdaQueryWrapper<>();
wrapper.in(DeviceContactModelDO::getDeviceId, deviceIds)
.orderByAsc(DeviceContactModelDO::getSort); // 按排序字段排序
List<DeviceContactModelDO> result = deviceContactModelMapper.selectList(wrapper);
// 调试日志
System.out.println("查询条件: 设备IDs=" + deviceIds);
System.out.println("查询结果数量: " + result.size());
return result;
}
}
Loading…
Cancel
Save