From e8445bb286c104a74134d94d3c3f725d33593aea Mon Sep 17 00:00:00 2001 From: liyongde <1419499670@qq.com> Date: Mon, 9 Mar 2026 11:29:48 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20da=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../org/nl/iot/core/driver/entity/RValue.java | 11 +- .../opcda/OpcDaProtocolDriverImpl.java | 3 +- .../driver/service/DriverCustomFactory.java | 9 + nl-web-app/src/test/java/org/nl/ApiTest.java | 36 ++- .../test/java/org/nl/OpcDaConnectionTest.java | 261 ++++++++++++++++++ 5 files changed, 303 insertions(+), 17 deletions(-) create mode 100644 nl-iot/src/main/java/org/nl/iot/core/driver/service/DriverCustomFactory.java create mode 100644 nl-web-app/src/test/java/org/nl/OpcDaConnectionTest.java diff --git a/nl-iot/src/main/java/org/nl/iot/core/driver/entity/RValue.java b/nl-iot/src/main/java/org/nl/iot/core/driver/entity/RValue.java index 26043da..138598c 100644 --- a/nl-iot/src/main/java/org/nl/iot/core/driver/entity/RValue.java +++ b/nl-iot/src/main/java/org/nl/iot/core/driver/entity/RValue.java @@ -16,15 +16,8 @@ import org.nl.iot.modular.iot.entity.IotConnect; @NoArgsConstructor @AllArgsConstructor public class RValue { - /** - * 设备 - */ -// private DeviceBO device; - - /** - * 位号 - 子设备 - */ -// private PointBO point; +// private String connectId; +// private String deviceCode; /** * 配置 */ diff --git a/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/OpcDaProtocolDriverImpl.java b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/OpcDaProtocolDriverImpl.java index 816129f..9e06101 100644 --- a/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/OpcDaProtocolDriverImpl.java +++ b/nl-iot/src/main/java/org/nl/iot/core/driver/protocol/opcda/OpcDaProtocolDriverImpl.java @@ -1,5 +1,6 @@ package org.nl.iot.core.driver.protocol.opcda; +import cn.hutool.core.util.ObjectUtil; import jakarta.annotation.PostConstruct; import lombok.extern.slf4j.Slf4j; import org.jinterop.dcom.common.JIException; @@ -78,7 +79,7 @@ public class OpcDaProtocolDriverImpl implements DriverCustomService { String host = driverConfig.get("host").getValueByClass(String.class); String clsId = driverConfig.get("clsId").getValueByClass(String.class); String user = driverConfig.get("username").getValueByClass(String.class); - String password = driverConfig.get("password").getValueByClass(String.class); + String password = ObjectUtil.isEmpty(driverConfig.get("password").getValue()) ? "" : driverConfig.get("password").getValueByClass(String.class); ConnectionInformation connectionInformation = new ConnectionInformation(host, clsId, user, password); server = new Server(connectionInformation, Executors.newSingleThreadScheduledExecutor()); try { diff --git a/nl-iot/src/main/java/org/nl/iot/core/driver/service/DriverCustomFactory.java b/nl-iot/src/main/java/org/nl/iot/core/driver/service/DriverCustomFactory.java new file mode 100644 index 0000000..9205416 --- /dev/null +++ b/nl-iot/src/main/java/org/nl/iot/core/driver/service/DriverCustomFactory.java @@ -0,0 +1,9 @@ +package org.nl.iot.core.driver.service; + +/** + * 自定义通信协议驱动工厂 + * @author: lyd + * @date: 2026/3/2 + */ +public class DriverCustomFactory { +} diff --git a/nl-web-app/src/test/java/org/nl/ApiTest.java b/nl-web-app/src/test/java/org/nl/ApiTest.java index a415dcc..8c8a876 100644 --- a/nl-web-app/src/test/java/org/nl/ApiTest.java +++ b/nl-web-app/src/test/java/org/nl/ApiTest.java @@ -497,16 +497,38 @@ public class ApiTest { opcDaProtocolDriver.initial(); // 构建驱动配置(连接配置) + // 重要提示: + // 1. 本地连接使用 "127.0.0.1" 或 "localhost" + // 2. username 格式: + // - 本地用户:直接使用用户名,如 "Administrator" 或 "YourUsername" + // - 域用户:使用 "DOMAIN\\username" 格式 + // - 如果 OPC 服务器配置为允许匿名访问,可以尝试空字符串 + // 3. password 必须是 Windows 用户的实际密码 + // 4. 确保 Windows DCOM 配置正确(参考 Matrikon OPC 文档) + Map driverConfig = new HashMap<>(); - driverConfig.put("host", AttributeBO.builder().value("localhost").build()); - driverConfig.put("clsId", AttributeBO.builder().value("F8582CF2-88FB-11D0-B850-00C0F0104305").build()); // OPC DA Server CLSID - driverConfig.put("username", AttributeBO.builder().value("Administrator").build()); - driverConfig.put("password", AttributeBO.builder().value("password").build()); + driverConfig.put("host", AttributeBO.builder().value("127.0.0.1").build()); + driverConfig.put("clsId", AttributeBO.builder().value("F8582CF2-88FB-11D0-B850-00C0F0104305").build()); + + // 方案1:使用当前登录用户(推荐本地测试) + // 获取当前 Windows 用户名:在 CMD 中运行 "echo %USERNAME%" + String currentUser = System.getProperty("user.name"); // 获取当前 Java 进程的用户名 +// driverConfig.put("username", AttributeBO.builder().value(currentUser).build()); +// driverConfig.put("password", AttributeBO.builder().value("6614").build()); // 替换为你的 Windows 密码 + + // 方案2:如果模拟器配置了特定用户,使用该用户 + driverConfig.put("username", AttributeBO.builder().value("Administrator").build()); + driverConfig.put("password", AttributeBO.builder().value("12356").build()); + + // 方案3:尝试匿名访问(某些 OPC 服务器支持) + // driverConfig.put("username", AttributeBO.builder().value("").build()); + // driverConfig.put("password", AttributeBO.builder().value("").build()); // 构建点位配置 + // 根据你的模拟器截图,Item ID 是 "Random.Int1" Map pointConfig = new HashMap<>(); - pointConfig.put("group", AttributeBO.builder().value("Group1").build()); // 组名 - pointConfig.put("tag", AttributeBO.builder().value("Channel1.Device1.Tag1").build()); // 标签名 + pointConfig.put("group", AttributeBO.builder().value("Group0").build()); // 组名 + pointConfig.put("tag", AttributeBO.builder().value("Random.Int1").build()); // 标签名,与模拟器中的 Item ID 一致 // 构建连接对象 IotConnect connect = IotConnect.builder() @@ -524,7 +546,7 @@ public class ApiTest { .connectId(1) .alias("sensor1") .aliasName("传感器1") - .registerAddress("Channel1.Device1.Tag1") + .registerAddress("Random.Int1") .dataType("int") .readonly(true) .enabled(true) diff --git a/nl-web-app/src/test/java/org/nl/OpcDaConnectionTest.java b/nl-web-app/src/test/java/org/nl/OpcDaConnectionTest.java new file mode 100644 index 0000000..a714024 --- /dev/null +++ b/nl-web-app/src/test/java/org/nl/OpcDaConnectionTest.java @@ -0,0 +1,261 @@ +package org.nl; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.nl.iot.core.driver.bo.AttributeBO; +import org.nl.iot.core.driver.entity.RValue; +import org.nl.iot.core.driver.protocol.opcda.OpcDaProtocolDriverImpl; +import org.nl.iot.modular.iot.entity.IotConfig; +import org.nl.iot.modular.iot.entity.IotConnect; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +import java.util.HashMap; +import java.util.Map; + +/** + * OPC DA 连接测试 - 多种认证方案 + * + * 使用说明: + * 1. 确保 Matrikon OPC 模拟器正在运行 + * 2. 确保已配置 DCOM 权限(参考 OPC_DA_DCOM_完整配置指南.md) + * 3. 依次尝试不同的测试方法,直到连接成功 + */ +@RunWith(SpringRunner.class) +@SpringBootTest(classes = Application.class) +public class OpcDaConnectionTest { + + @Autowired + private OpcDaProtocolDriverImpl opcDaProtocolDriver; + + /** + * 方案 1:空用户名和密码(本地连接最简单) + * 适用场景:本地测试,DCOM 配置为允许匿名访问 + */ + @Test + public void testWithEmptyCredentials() { + System.out.println("\n========================================"); + System.out.println("测试方案 1:空用户名和密码"); + System.out.println("========================================"); + + testConnection("", ""); + } + + /** + * 方案 2:使用当前 Java 进程用户 + * 适用场景:使用当前登录的 Windows 用户 + * 注意:需要输入正确的 Windows 密码 + */ + @Test + public void testWithCurrentUser() { + System.out.println("\n========================================"); + System.out.println("测试方案 2:当前用户"); + System.out.println("========================================"); + + String currentUser = System.getProperty("user.name"); + System.out.println("当前用户: " + currentUser); + System.out.println("请在代码中填入该用户的 Windows 密码!"); + + // TODO: 替换为你的 Windows 密码 + String password = "YOUR_PASSWORD_HERE"; + + if ("YOUR_PASSWORD_HERE".equals(password)) { + System.err.println("错误:请先在代码中设置正确的密码!"); + return; + } + + testConnection(currentUser, password); + } + + /** + * 方案 3:使用 计算机名\用户名 格式 + * 适用场景:明确指定本地计算机的用户 + */ + @Test + public void testWithComputerNameUser() { + System.out.println("\n========================================"); + System.out.println("测试方案 3:计算机名\\用户名"); + System.out.println("========================================"); + + String computerName = System.getenv("COMPUTERNAME"); + String userName = System.getProperty("user.name"); + String fullUserName = computerName + "\\" + userName; + + System.out.println("计算机名: " + computerName); + System.out.println("用户名: " + userName); + System.out.println("完整用户名: " + fullUserName); + System.out.println("请在代码中填入该用户的 Windows 密码!"); + + // TODO: 替换为你的 Windows 密码 + String password = "YOUR_PASSWORD_HERE"; + + if ("YOUR_PASSWORD_HERE".equals(password)) { + System.err.println("错误:请先在代码中设置正确的密码!"); + return; + } + + testConnection(fullUserName, password); + } + + /** + * 方案 4:使用 Administrator 账户 + * 适用场景:使用管理员账户 + * 注意:确保 Administrator 账户已启用 + */ + @Test + public void testWithAdministrator() { + System.out.println("\n========================================"); + System.out.println("测试方案 4:Administrator 账户"); + System.out.println("========================================"); + + System.out.println("注意:确保 Administrator 账户已启用"); + System.out.println("启用命令:net user Administrator /active:yes"); + System.out.println("请在代码中填入 Administrator 的密码!"); + + // TODO: 替换为 Administrator 的密码 + String password = "YOUR_ADMIN_PASSWORD_HERE"; + + if ("YOUR_ADMIN_PASSWORD_HERE".equals(password)) { + System.err.println("错误:请先在代码中设置 Administrator 的密码!"); + return; + } + + testConnection("Administrator", password); + } + + /** + * 方案 5:使用 .\用户名 格式(本地用户) + * 适用场景:明确指定本地用户 + */ + @Test + public void testWithDotUser() { + System.out.println("\n========================================"); + System.out.println("测试方案 5:.\\用户名 格式"); + System.out.println("========================================"); + + String userName = System.getProperty("user.name"); + String fullUserName = ".\\" + userName; + + System.out.println("用户名: " + fullUserName); + System.out.println("请在代码中填入该用户的 Windows 密码!"); + + // TODO: 替换为你的 Windows 密码 + String password = "YOUR_PASSWORD_HERE"; + + if ("YOUR_PASSWORD_HERE".equals(password)) { + System.err.println("错误:请先在代码中设置正确的密码!"); + return; + } + + testConnection(fullUserName, password); + } + + /** + * 核心测试方法 + */ + private void testConnection(String username, String password) { + opcDaProtocolDriver.initial(); + + Map driverConfig = new HashMap<>(); + driverConfig.put("host", AttributeBO.builder().value("127.0.0.1").build()); + driverConfig.put("clsId", AttributeBO.builder().value("F8582CF2-88FB-11D0-B850-00C0F0104305").build()); + driverConfig.put("username", AttributeBO.builder().value(username).build()); + driverConfig.put("password", AttributeBO.builder().value(password).build()); + + Map pointConfig = new HashMap<>(); + pointConfig.put("group", AttributeBO.builder().value("Group0").build()); + pointConfig.put("tag", AttributeBO.builder().value("Random.Int1").build()); + + IotConnect connect = IotConnect.builder() + .id(1) + .code("OPC_DA_TEST") + .host("127.0.0.1") + .protocol("opc-da") + .enabled(true) + .description("OPC DA 连接测试") + .build(); + + IotConfig config = IotConfig.builder() + .id(1) + .connectId(1) + .alias("test_sensor") + .aliasName("测试传感器") + .registerAddress("Random.Int1") + .dataType("int") + .readonly(true) + .enabled(true) + .description("OPC DA 测试点位") + .build(); + + try { + System.out.println("\n--- 连接参数 ---"); + System.out.println("主机: " + driverConfig.get("host").getValue()); + System.out.println("CLSID: " + driverConfig.get("clsid").getValue()); + System.out.println("用户名: " + (username.isEmpty() ? "(空)" : username)); + System.out.println("密码: " + (password.isEmpty() ? "(空)" : "******")); + System.out.println("组名: " + pointConfig.get("group").getValue()); + System.out.println("标签: " + pointConfig.get("tag").getValue()); + + System.out.println("\n正在连接..."); + RValue result = opcDaProtocolDriver.read(driverConfig, pointConfig, connect, config); + + if (result != null) { + System.out.println("\n✅ 连接成功!"); + System.out.println("读取值: " + result.getValue()); + System.out.println("\n========================================"); + System.out.println("成功配置:"); + System.out.println("用户名: " + username); + System.out.println("请在正式代码中使用此配置!"); + System.out.println("========================================"); + } else { + System.err.println("\n❌ 连接失败:返回结果为 null"); + } + + } catch (Exception e) { + System.err.println("\n❌ 连接失败"); + System.err.println("错误信息: " + e.getMessage()); + + // 分析错误并给出建议 + String errorMsg = e.getMessage(); + if (errorMsg != null) { + if (errorMsg.contains("0x00000005") || errorMsg.contains("Access is denied")) { + System.err.println("\n可能原因:"); + System.err.println("1. 用户名或密码错误"); + System.err.println("2. DCOM 权限未正确配置"); + System.err.println("3. 用户未在 'Distributed COM Users' 组中"); + System.err.println("\n建议:"); + System.err.println("- 检查密码是否正确"); + System.err.println("- 运行 dcomcnfg 配置权限"); + System.err.println("- 参考 OPC_DA_DCOM_完整配置指南.md"); + } else if (errorMsg.contains("0x00000533")) { + System.err.println("\n可能原因:"); + System.err.println("- 用户账户被禁用"); + System.err.println("\n建议:"); + System.err.println("- 运行: net user " + username + " /active:yes"); + } + } + } + } + + /** + * 显示系统信息 + */ + @Test + public void showSystemInfo() { + System.out.println("\n========================================"); + System.out.println("系统信息"); + System.out.println("========================================"); + System.out.println("计算机名: " + System.getenv("COMPUTERNAME")); + System.out.println("当前用户: " + System.getProperty("user.name")); + System.out.println("用户主目录: " + System.getProperty("user.home")); + System.out.println("操作系统: " + System.getProperty("os.name")); + System.out.println("========================================"); + System.out.println("\n建议测试顺序:"); + System.out.println("1. testWithEmptyCredentials() - 最简单"); + System.out.println("2. testWithCurrentUser() - 使用当前用户"); + System.out.println("3. testWithComputerNameUser() - 完整格式"); + System.out.println("4. testWithAdministrator() - 管理员账户"); + System.out.println("5. testWithDotUser() - 本地用户格式"); + } +}