From 487b2f2a353b89ab46cd9b784226b600b7b915b8 Mon Sep 17 00:00:00 2001
From: yantian yue <306727702@qq.com>
Date: 星期二, 17 十月 2023 15:33:05 +0800
Subject: [PATCH] 新增OPCUA模块

---
 guns-vip-main/pom.xml                                                                      |   26 
 guns-vip-main/src/main/java/cn/stylefeng/guns/opcua/controller/CommonController.java       |  131 +++
 guns-vip-main/src/main/java/cn/stylefeng/guns/opcua/cert/MethodName.java                   |   16 
 guns-vip-main/src/main/java/cn/stylefeng/guns/opcua/controller/OpcuaConfController.java    |  137 ++++
 guns-vip-main/src/main/java/cn/stylefeng/guns/opcua/cert/KeyStoreLoader.java               |  131 +++
 guns-vip-main/src/main/java/cn/stylefeng/guns/opcua/entity/NodeEntity.java                 |   19 
 guns-vip-main/src/main/java/cn/stylefeng/guns/opcua/client/ClientHandler.java              |  223 ++++++
 guns-vip-main/src/main/resources/opcua.properties                                          |   19 
 guns-vip-main/src/main/webapp/assets/modular/bs/opcuaConf/opcuaConf_add.js                 |   43 +
 guns-vip-main/src/main/webapp/pages/modular/bs/opcuaConf/opcuaConf.html                    |   32 
 guns-vip-main/src/main/java/cn/stylefeng/guns/opcua/mapper/OpcuaConfMapper.java            |   56 +
 guns-vip-main/src/main/resources/opcua.yml                                                 |   27 
 guns-vip-main/src/main/webapp/assets/modular/bs/opcuaConf/opcuaConf_edit.js                |   75 ++
 guns-vip-main/src/main/java/cn/stylefeng/guns/opcua/model/result/OpcuaConfResult.java      |   72 ++
 guns-vip-main/src/main/java/cn/stylefeng/guns/opcua/client/ClientRunner.java               |  150 ++++
 guns-vip-main/src/main/java/cn/stylefeng/guns/opcua/model/params/OpcuaConfParam.java       |   78 ++
 guns-vip-main/src/main/webapp/pages/modular/bs/opcuaConf/opcuaConf_add.html                |   86 ++
 guns-vip-main/src/main/java/cn/stylefeng/guns/opcua/mapper/mapping/OpcuaConfMapper.xml     |   49 +
 guns-vip-main/src/main/java/cn/stylefeng/guns/opcua/service/impl/OpcuaConfServiceImpl.java |   84 ++
 guns-vip-main/src/main/java/cn/stylefeng/guns/opcua/config/Properties.java                 |   58 +
 guns-vip-main/src/main/java/cn/stylefeng/guns/opcua/service/OpcuaConfService.java          |   69 ++
 guns-vip-main/src/main/webapp/assets/modular/bs/opcuaConf/opcuaConf.js                     |  132 ++++
 guns-vip-main/src/main/webapp/pages/modular/bs/opcuaConf/opcuaConf_edit.html               |   86 ++
 guns-vip-main/src/main/java/cn/stylefeng/guns/opcua/entity/OpcuaConf.java                  |  176 +++++
 24 files changed, 1,968 insertions(+), 7 deletions(-)

diff --git a/guns-vip-main/pom.xml b/guns-vip-main/pom.xml
index 08122bb..2f1c91b 100644
--- a/guns-vip-main/pom.xml
+++ b/guns-vip-main/pom.xml
@@ -73,6 +73,12 @@
             <groupId>cn.stylefeng</groupId>
             <artifactId>guns-sys</artifactId>
             <version>1.0.0</version>
+            <exclusions>
+                <exclusion>
+                    <artifactId>guava</artifactId>
+                    <groupId>com.google.guava</groupId>
+                </exclusion>
+            </exclusions>
         </dependency>
 
         <!-- 宸ヤ綔娴� -->
@@ -134,33 +140,33 @@
         <dependency>
             <groupId>org.eclipse.milo</groupId>
             <artifactId>sdk-client</artifactId>
-            <version>0.2.4</version>
+            <version>0.3.6</version>
         </dependency>
 
         <!--Server SDK渚濊禆-->
         <dependency>
             <groupId>org.eclipse.milo</groupId>
             <artifactId>sdk-server</artifactId>
-            <version>0.2.4</version>
+            <version>0.3.6</version>
         </dependency>
 
         <!-- Stack渚濊禆-->
         <dependency>
             <groupId>org.eclipse.milo</groupId>
             <artifactId>stack-client</artifactId>
-            <version>0.2.4</version>
+            <version>0.3.6</version>
         </dependency>
+
         <dependency>
             <groupId>org.eclipse.milo</groupId>
             <artifactId>stack-server</artifactId>
-            <version>0.2.4</version>
+            <version>0.3.6</version>
         </dependency>
 
-        <!--Milo瀹㈡埛绔殑渚濊禆-->
         <dependency>
             <groupId>org.eclipse.milo</groupId>
-            <artifactId>sdk-client</artifactId>
-            <version>0.2.4</version>
+            <artifactId>stack-client</artifactId>
+            <version>0.3.6</version>
         </dependency>
 
         <dependency>
@@ -183,6 +189,12 @@
             <groupId>org.apache.hadoop</groupId>
             <artifactId>hadoop-client</artifactId>
             <version>3.1.3</version>
+            <exclusions>
+                <exclusion>
+                    <artifactId>guava</artifactId>
+                    <groupId>com.google.guava</groupId>
+                </exclusion>
+            </exclusions>
         </dependency>
 
         <dependency>
diff --git a/guns-vip-main/src/main/java/cn/stylefeng/guns/opcua/cert/KeyStoreLoader.java b/guns-vip-main/src/main/java/cn/stylefeng/guns/opcua/cert/KeyStoreLoader.java
new file mode 100644
index 0000000..11bdc74
--- /dev/null
+++ b/guns-vip-main/src/main/java/cn/stylefeng/guns/opcua/cert/KeyStoreLoader.java
@@ -0,0 +1,131 @@
+package cn.stylefeng.guns.opcua.cert;
+
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.security.Key;
+import java.security.KeyPair;
+import java.security.KeyStore;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.cert.X509Certificate;
+import java.util.regex.Pattern;
+
+import org.eclipse.milo.opcua.sdk.server.util.HostnameUtil;
+import org.eclipse.milo.opcua.stack.core.util.SelfSignedCertificateBuilder;
+import org.eclipse.milo.opcua.stack.core.util.SelfSignedCertificateGenerator;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Component;
+
+/**
+ * @ClassName: KeyStoreLoader
+ * @Description: KeyStoreLoader
+ * @author yyt
+ * @date 2023骞�10鏈�13鏃�
+ */
+@Component
+public class KeyStoreLoader {
+
+	private static final Pattern IP_ADDR_PATTERN = Pattern
+			.compile("^(([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.){3}([01]?\\d\\d?|2[0-4]\\d|25[0-5])$");
+
+	// 璇佷功鍒悕
+	private static final String CLIENT_ALIAS = "jlclient-ai";
+	// 鑾峰彇绉侀挜鐨勫瘑鐮�
+	private static final char[] PASSWORD = "yyt@8888888888".toCharArray();
+
+	private final Logger logger = LoggerFactory.getLogger(getClass());
+
+	// 璇佷功瀵硅薄
+	private X509Certificate clientCertificate;
+	// 瀵嗛挜瀵瑰璞�
+	private KeyPair clientKeyPair;
+
+	/**
+	 * @MethodName: load
+	 * @Description: load
+	 * @param baseDir
+	 * @return
+	 * @throws Exception
+	 * @CreateTime 2023骞�10鏈�13鏃�
+	 */
+	public KeyStoreLoader load(Path baseDir) throws Exception {
+		// 鍒涘缓涓�涓娇鐢╜PKCS12`鍔犲瘑鏍囧噯鐨凨eyStore銆侹eyStore鍦ㄥ悗闈㈠皢浣滀负璇诲彇鍜岀敓鎴愯瘉涔︾殑瀵硅薄銆�
+		KeyStore keyStore = KeyStore.getInstance("PKCS12");
+
+		// PKCS12鐨勫姞瀵嗘爣鍑嗙殑鏂囦欢鍚庣紑鏄�.pfx锛屽叾涓寘鍚簡鍏挜鍜岀閽ャ��
+		// 鑰屽叾浠栧.der绛夌殑鏍煎紡鍙寘鍚叕閽ワ紝绉侀挜鍦ㄥ彟澶栫殑鏂囦欢涓��
+		Path serverKeyStore = baseDir.resolve("OPCUA-client.pfx");
+
+		logger.info("Loading KeyStore at {}", serverKeyStore);
+
+		// 濡傛灉鏂囦欢涓嶅瓨鍦ㄥ垯鍒涘缓.pfx璇佷功鏂囦欢銆�
+		if (!Files.exists(serverKeyStore)) {
+			keyStore.load(null, PASSWORD);
+
+			// 鐢�2048浣嶇殑RAS绠楁硶銆俙SelfSignedCertificateGenerator`涓篗ilo搴撶殑瀵硅薄銆�
+			KeyPair keyPair = SelfSignedCertificateGenerator.generateRsaKeyPair(2048);
+
+			// `SelfSignedCertificateBuilder`涔熸槸Milo搴撶殑瀵硅薄锛岀敤鏉ョ敓鎴愯瘉涔︺��
+			// 涓棿鎵�璁剧疆鐨勮瘉涔﹀睘鎬у彲浠ヨ嚜琛屼慨鏀广��
+			SelfSignedCertificateBuilder builder = new SelfSignedCertificateBuilder(keyPair)
+					.setCommonName("UaClient@Jellyleo")
+					.setOrganization("JL")
+					.setOrganizationalUnit("per")
+					.setLocalityName("jl")
+					.setStateName("JiangSu")
+					.setCountryCode("CN")
+					.setApplicationUri("urn:Yyt_PC:UnifiedAutomation:UaExpert")
+					.addDnsName("Yyt_PC")
+					.addIpAddress("127.0.0.1");
+
+			// Get as many hostnames and IP addresses as we can listed in the certificate.
+			for (String hostname : HostnameUtil.getHostnames("0.0.0.0")) {
+				if (IP_ADDR_PATTERN.matcher(hostname).matches()) {
+					builder.addIpAddress(hostname);
+				} else {
+					builder.addDnsName(hostname);
+				}
+			}
+			// 鍒涘缓璇佷功
+			X509Certificate certificate = builder.build();
+
+			// 璁剧疆瀵瑰簲绉侀挜鐨勫埆鍚嶏紝瀵嗙爜锛岃瘉涔﹂摼
+			keyStore.setKeyEntry(CLIENT_ALIAS, keyPair.getPrivate(), PASSWORD, new X509Certificate[] { certificate });
+			try (OutputStream out = Files.newOutputStream(serverKeyStore)) {
+				// 淇濆瓨璇佷功鍒拌緭鍑烘祦
+				keyStore.store(out, PASSWORD);
+			}
+		} else {
+			try (InputStream in = Files.newInputStream(serverKeyStore)) {
+				// 濡傛灉鏂囦欢瀛樺湪鍒欒鍙�
+				keyStore.load(in, PASSWORD);
+			}
+		}
+
+		// 鐢ㄥ瘑鐮佽幏鍙栧搴斿埆鍚嶇殑绉侀挜銆�
+		Key serverPrivateKey = keyStore.getKey(CLIENT_ALIAS, PASSWORD);
+		if (serverPrivateKey instanceof PrivateKey) {
+			// 鑾峰彇瀵瑰簲鍒悕鐨勮瘉涔﹀璞°��
+			clientCertificate = (X509Certificate) keyStore.getCertificate(CLIENT_ALIAS);
+			// 鑾峰彇鍏挜
+			PublicKey serverPublicKey = clientCertificate.getPublicKey();
+			// 鍒涘缓Keypair瀵硅薄銆�
+			clientKeyPair = new KeyPair(serverPublicKey, (PrivateKey) serverPrivateKey);
+		}
+
+		return this;
+	}
+
+	// 杩斿洖璇佷功
+	public X509Certificate getClientCertificate() {
+		return clientCertificate;
+	}
+
+	// 杩斿洖瀵嗛挜瀵�
+	public KeyPair getClientKeyPair() {
+		return clientKeyPair;
+	}
+}
diff --git a/guns-vip-main/src/main/java/cn/stylefeng/guns/opcua/cert/MethodName.java b/guns-vip-main/src/main/java/cn/stylefeng/guns/opcua/cert/MethodName.java
new file mode 100644
index 0000000..1c9738f
--- /dev/null
+++ b/guns-vip-main/src/main/java/cn/stylefeng/guns/opcua/cert/MethodName.java
@@ -0,0 +1,16 @@
+package cn.stylefeng.guns.opcua.cert;
+
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+public class MethodName {
+    public void a(String name){
+        log.info("str={}", name);
+    }
+    public void b(String name,String value){
+        log.info("item={}, value={},{}", name, value,"BBBBBB");
+    }
+    public void c(String name,String value){
+        log.info("item={}, value={},{}", name, value,"CCCCCC");
+        }
+}
diff --git a/guns-vip-main/src/main/java/cn/stylefeng/guns/opcua/client/ClientHandler.java b/guns-vip-main/src/main/java/cn/stylefeng/guns/opcua/client/ClientHandler.java
new file mode 100644
index 0000000..008716b
--- /dev/null
+++ b/guns-vip-main/src/main/java/cn/stylefeng/guns/opcua/client/ClientHandler.java
@@ -0,0 +1,223 @@
+package cn.stylefeng.guns.opcua.client;
+
+import com.google.common.collect.ImmutableList;
+import cn.stylefeng.guns.opcua.entity.NodeEntity;
+import lombok.extern.slf4j.Slf4j;
+import org.eclipse.milo.opcua.sdk.client.OpcUaClient;
+import org.eclipse.milo.opcua.sdk.client.api.nodes.VariableNode;
+import org.eclipse.milo.opcua.sdk.client.api.subscriptions.UaSubscription;
+import org.eclipse.milo.opcua.stack.core.AttributeId;
+import org.eclipse.milo.opcua.stack.core.BuiltinDataType;
+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.StatusCode;
+import org.eclipse.milo.opcua.stack.core.types.builtin.Variant;
+import org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.Unsigned;
+import org.eclipse.milo.opcua.stack.core.types.enumerated.MonitoringMode;
+import org.eclipse.milo.opcua.stack.core.types.enumerated.TimestampsToReturn;
+import org.eclipse.milo.opcua.stack.core.types.structured.MonitoredItemCreateRequest;
+import org.eclipse.milo.opcua.stack.core.types.structured.MonitoringParameters;
+import org.eclipse.milo.opcua.stack.core.types.structured.ReadValueId;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.util.CollectionUtils;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @ClassName: ClientHandler
+ * @Description: 瀹㈡埛绔鐞�
+ * @author Jellyleo
+ * @date 2019骞�12鏈�12鏃�
+ */
+@Slf4j
+@Service
+public class ClientHandler {
+
+	// 瀹㈡埛绔疄渚�
+	private OpcUaClient client = null;
+
+	@Autowired
+	private ClientRunner clientRunner;
+
+	/**
+	 * 
+	 * @MethodName: connect
+	 * @Description: connect
+	 * @throws Exception
+	 * @CreateTime 2019骞�12鏈�18鏃� 涓婂崍10:41:09
+	 */
+	public String connect() throws Exception {
+
+		if (client != null) {
+			return "瀹㈡埛绔凡鍒涘缓";
+		}
+
+		client = clientRunner.run();
+
+		if (client == null) {
+			return "瀹㈡埛绔厤缃疄渚嬪寲澶辫触";
+		}
+
+		// 鍒涘缓杩炴帴
+		client.connect().get();
+		return "鍒涘缓杩炴帴鎴愬姛";
+	}
+
+	/**
+	 * @MethodName: disconnect
+	 * @Description: 鏂紑杩炴帴
+	 * @return
+	 * @throws Exception
+	 * @CreateTime 2019骞�12鏈�18鏃� 涓婂崍10:45:21
+	 */
+	public String disconnect() throws Exception {
+
+		if (client == null) {
+			return "杩炴帴宸叉柇寮�";
+		}
+
+		// 鏂紑杩炴帴
+		clientRunner.getFuture().complete(client);
+		client = null;
+		return "鏂紑杩炴帴鎴愬姛";
+	}
+
+	/**
+	 * @MethodName: subscribe
+	 * @Description: 璁㈤槄鑺傜偣鍙橀噺
+	 * @throws Exception
+	 * @CreateTime 2019骞�12鏈�18鏃� 涓婂崍10:38:11
+	 */
+	public String subscribe(List<NodeEntity> nodes) throws Exception {
+
+		if (client == null) {
+			return "鎵句笉鍒板鎴风锛屾搷浣滃け璐�";
+		}
+
+//		List<Node> ns = client.getAddressSpace().browse(new NodeId(2, "妯℃嫙閫氶亾涓�.妯℃嫙璁惧涓�")).get();
+
+		// 鏌ヨ璁㈤槄瀵硅薄锛屾病鏈夊垯鍒涘缓
+		UaSubscription subscription = null;
+		ImmutableList<UaSubscription> subscriptionList = client.getSubscriptionManager().getSubscriptions();
+		if (CollectionUtils.isEmpty(subscriptionList)) {
+			subscription = client.getSubscriptionManager().createSubscription(1000.0).get();
+		} else {
+			subscription = subscriptionList.get(0);
+		}
+
+		// 鐩戞帶椤硅姹傚垪琛�
+		List<MonitoredItemCreateRequest> requests = new ArrayList<>();
+
+		if (!CollectionUtils.isEmpty(nodes)) {
+			for (NodeEntity node : nodes) {
+				// 鍒涘缓鐩戞帶鐨勫弬鏁�
+				MonitoringParameters parameters = new MonitoringParameters(subscription.nextClientHandle(), 1000.0, // sampling
+						// interval
+						null, // filter, null means use default
+						Unsigned.uint(10), // queue size
+						true // discard oldest
+				);
+				// 鍒涘缓璁㈤槄鐨勫彉閲忥紝 鍒涘缓鐩戞帶椤硅 姹�
+				MonitoredItemCreateRequest request = new MonitoredItemCreateRequest(
+						new ReadValueId(new NodeId(node.getIndex(), node.getIdentifier()), AttributeId.Value.uid(),
+								null, null),
+						MonitoringMode.Reporting, parameters);
+				requests.add(request);
+			}
+		}
+
+		// 鍒涘缓鐩戞帶椤癸紝骞朵笖娉ㄥ唽鍙橀噺鍊兼敼鍙樻椂鍊欑殑鍥炶皟鍑芥暟
+		subscription.createMonitoredItems(TimestampsToReturn.Both, requests, (item, id) -> {
+			item.setValueConsumer((i, v) -> {
+				handle(i.getReadValueId().getNodeId(), v.getValue());
+			});
+		}).get();
+
+		return "璁㈤槄鎴愬姛";
+	}
+
+	/**
+	 * * @MethodName: write
+	 * @Description: 鍥炶皟鍑芥暟
+	 * @CreateTime 2023骞�10鏈�13鏃�
+	 */
+
+	public void handle(NodeId id, Variant value){
+		String className = "cn.stylefeng.guns.opcua.cert.MethodName";
+		String methodName = "a";
+		String str1 = id.getIdentifier().toString()+":"+value.getValue().toString();
+		try {
+			Class<?> clazz = Class.forName(className);
+			Method method = clazz.getMethod(methodName, String.class);
+
+			method.invoke(clazz.newInstance(), str1);
+		} catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException | InstantiationException |
+				 InvocationTargetException e) {
+			e.printStackTrace();
+		}
+		//if(id.getIdentifier().toString().equals("my.device.x1")){
+		//	log.info("item={}, value={},{}", id.getIdentifier().toString(), value,"杩斿洖涓�涓猄N鍙�");
+		//}
+	}
+
+	/**
+	 * @MethodName: write
+	 * @Description: 鍙樿妭鐐归噺鍐欏叆
+	 * @param node
+	 * @throws Exception
+	 * @CreateTime 2019骞�12鏈�18鏃� 涓婂崍9:51:40
+	 */
+	public String write(NodeEntity node) throws Exception {
+
+		if (client == null) {
+			return "鎵句笉鍒板鎴风锛屾搷浣滃け璐�";
+		}
+
+		NodeId nodeId = new NodeId(node.getIndex(), node.getIdentifier());
+		Variant value = null;
+		switch (node.getType()) {
+		case "int":
+			value = new Variant(Integer.parseInt(node.getValue().toString()));
+			break;
+		case "boolean":
+			value = new Variant(Boolean.parseBoolean(node.getValue().toString()));
+			break;
+		}
+		DataValue dataValue = new DataValue(value, null, null);
+
+		StatusCode statusCode = client.writeValue(nodeId, dataValue).get();
+
+		return "鑺傜偣銆�" + node.getIdentifier() + "銆戝啓鍏ョ姸鎬侊細" + statusCode.isGood();
+	}
+
+	/**
+	 * @MethodName: read
+	 * @Description: 璇诲彇
+	 * @param node
+	 * @return
+	 * @throws Exception
+	 * @CreateTime 2019骞�12鏈�19鏃� 涓嬪崍2:40:34
+	 */
+	public String read(NodeEntity node) throws Exception {
+
+		if (client == null) {
+			return "鎵句笉鍒板鎴风锛屾搷浣滃け璐�";
+		}
+
+		NodeId nodeId = new NodeId(node.getIndex(), node.getIdentifier());
+		VariableNode vnode = client.getAddressSpace().createVariableNode(nodeId);
+		DataValue value = vnode.readValue().get();
+		log.info("Value={}", value);
+
+		Variant variant = value.getValue();
+		log.info("Variant={}", variant.getValue());
+
+		log.info("BackingClass={}", BuiltinDataType.getBackingClass(variant.getDataType().get()));
+
+		return "鑺傜偣銆�" + node.getIdentifier() + "銆戯細" + variant.getValue();
+	}
+}
diff --git a/guns-vip-main/src/main/java/cn/stylefeng/guns/opcua/client/ClientRunner.java b/guns-vip-main/src/main/java/cn/stylefeng/guns/opcua/client/ClientRunner.java
new file mode 100644
index 0000000..eb3fc05
--- /dev/null
+++ b/guns-vip-main/src/main/java/cn/stylefeng/guns/opcua/client/ClientRunner.java
@@ -0,0 +1,150 @@
+package cn.stylefeng.guns.opcua.client;
+
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.function.Predicate;
+
+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.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.Stack;
+import org.eclipse.milo.opcua.stack.core.security.SecurityPolicy;
+import org.eclipse.milo.opcua.stack.core.types.builtin.LocalizedText;
+
+import cn.stylefeng.guns.opcua.cert.KeyStoreLoader;
+import cn.stylefeng.guns.opcua.config.Properties;
+
+import lombok.extern.slf4j.Slf4j;
+
+import org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.Unsigned;
+import org.eclipse.milo.opcua.stack.core.types.structured.EndpointDescription;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+/**
+ * @ClassName: ClientRunner
+ * @Description: 瀹㈡埛绔惎鍔ㄧ被
+ * @author yyt
+ * @date 2023骞�10鏈�13鏃�
+ */
+@Slf4j
+@Component
+public class ClientRunner {
+
+	private final CompletableFuture<OpcUaClient> future = new CompletableFuture<>();
+
+	@Autowired
+	private Properties properties;
+
+	@Autowired
+	private KeyStoreLoader keyStoreLoader;
+
+	/**
+	 * @MethodName: run
+	 * @Description: 鍚姩
+	 * @return
+	 * @throws Exception
+	 * @CreateTime 2023骞�10鏈�13鏃�
+	 */
+	public OpcUaClient run() throws Exception {
+
+		OpcUaClient client = createClient();
+
+		future.whenCompleteAsync((c, ex) -> {
+			if (ex != null) {
+				log.error("Error running example: {}", ex.getMessage(), ex);
+			}
+
+			try {
+				c.disconnect().get();
+				Stack.releaseSharedResources();
+			} catch (InterruptedException | ExecutionException e) {
+				log.error("Error disconnecting:", e.getMessage(), e);
+			}
+		});
+
+		return client;
+	}
+
+	/**
+	 * @MethodName: createClient
+	 * @Description: 鍒涘缓瀹㈡埛绔�
+	 * @return
+	 * @throws Exception
+	 * @CreateTime 2023骞�10鏈�13鏃�
+	 */
+	private OpcUaClient createClient() throws Exception {
+
+		Path securityTempDir = Paths.get(properties.getCertPath(), "security");
+		Files.createDirectories(securityTempDir);
+		if (!Files.exists(securityTempDir)) {
+			log.error("unable to create security dir: " + securityTempDir);
+			return null;
+		}
+
+		KeyStoreLoader loader = keyStoreLoader.load(securityTempDir);
+
+		// 鎼滅储OPC鑺傜偣
+		List<EndpointDescription> endpoints = null;
+		try {
+			//鑾峰彇瀹夊叏绛栫暐
+			endpoints = DiscoveryClient.getEndpoints(properties.getEndpointUrl()).get();
+		} catch (Throwable e) {
+			// try the explicit discovery endpoint as well
+			String discoveryUrl = properties.getEndpointUrl();
+
+			if (!discoveryUrl.endsWith("/")) {
+				discoveryUrl += "/";
+			}
+			discoveryUrl += "discovery";
+
+			log.info("Trying explicit discovery URL: {}", discoveryUrl);
+			endpoints = DiscoveryClient.getEndpoints(discoveryUrl).get();
+		}
+
+		EndpointDescription endpoint = endpoints.stream()
+				.filter(e -> e.getSecurityPolicyUri().equals(SecurityPolicy.None.getUri())).filter(endpointFilter())
+				.findFirst().orElseThrow(() -> new Exception("no desired endpoints returned"));
+
+		OpcUaClientConfig config = OpcUaClientConfig.builder()
+				// opc ua鑷畾涔夌殑鍚嶇О
+				.setApplicationName(LocalizedText.english("plc"))
+				// 鍦板潃
+				.setApplicationUri("opc.tcp://127.0.0.1:49320")
+				.setCertificate(loader.getClientCertificate()).setKeyPair(loader.getClientKeyPair())
+				// 瀹夊叏绛栫暐绛夐厤缃�
+				//.setEndpoint(endpoint).setIdentityProvider(new UsernameProvider("OPCUA", "yyt@8888888888"))
+				.setEndpoint(endpoint)
+				// 鍖垮悕楠岃瘉
+				.setIdentityProvider(new AnonymousProvider())
+				//绛夊緟鏃堕棿
+				.setRequestTimeout(Unsigned.uint(5000)).build();
+
+		return OpcUaClient.create(config);
+
+	}
+
+	/**
+	 * @MethodName: endpointFilter
+	 * @Description: endpointFilter
+	 * @return
+	 * @CreateTime 2023骞�10鏈�13鏃�
+	 */
+	private Predicate<EndpointDescription> endpointFilter() {
+		return e -> true;
+	}
+
+	/**
+	 * @return the future
+	 */
+	public CompletableFuture<OpcUaClient> getFuture() {
+		return future;
+	}
+
+}
diff --git a/guns-vip-main/src/main/java/cn/stylefeng/guns/opcua/config/Properties.java b/guns-vip-main/src/main/java/cn/stylefeng/guns/opcua/config/Properties.java
new file mode 100644
index 0000000..51d59a9
--- /dev/null
+++ b/guns-vip-main/src/main/java/cn/stylefeng/guns/opcua/config/Properties.java
@@ -0,0 +1,58 @@
+/**
+ * Created by Jellyleo on 2019骞�12鏈�19鏃�
+ * Copyright 漏 2019 jellyleo.com 
+ * All rights reserved. 
+ */
+package cn.stylefeng.guns.opcua.config;
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.PropertySource;
+
+import lombok.Getter;
+
+/**
+ * @ClassName: Properties
+ * @Description: OpcUa鍙傛暟
+ * @author yyt
+ * @date 2023骞�10鏈�13鏃�
+ */
+@Getter
+@Configuration
+@PropertySource("classpath:opcua.properties")
+public class Properties {
+	@Value("${opcua.server.endpoint.url}")
+	private String endpointUrl;
+	@Value("${opcua.server.idp.username}")
+	private String idpUsername;
+	@Value("${opcua.server.idp.password}")
+	private String idpPassword;
+	@Value("${opcua.client.app.name}")
+	private String appName;
+	@Value("${opcua.client.app.uri}")
+	private String appUri;
+	@Value("${opcua.client.cert.path}")
+	private String certPath;
+	@Value("${opcua.client.cert.file}")
+	private String certFile;
+	@Value("${opcua.client.cert.alias}")
+	private String certAlias;
+	@Value("${opcua.client.cert.common.name}")
+	private String commonName;
+	@Value("${opcua.client.cert.organization}")
+	private String organization;
+	@Value("${opcua.client.cert.organization.unit}")
+	private String orgUnit;
+	@Value("${opcua.client.cert.locality.name}")
+	private String localityName;
+	@Value("${opcua.client.cert.state.name}")
+	private String stateName;
+	@Value("${opcua.client.cert.country.code}")
+	private String countryCode;
+	@Value("${opcua.client.cert.dns.name}")
+	private String dnsName;
+	@Value("${opcua.client.cert.ip.address}")
+	private String ipAddress;
+	@Value("${opcua.client.cert.keystore.password}")
+	private String keyPassword;
+}
diff --git a/guns-vip-main/src/main/java/cn/stylefeng/guns/opcua/controller/CommonController.java b/guns-vip-main/src/main/java/cn/stylefeng/guns/opcua/controller/CommonController.java
new file mode 100644
index 0000000..8040e83
--- /dev/null
+++ b/guns-vip-main/src/main/java/cn/stylefeng/guns/opcua/controller/CommonController.java
@@ -0,0 +1,131 @@
+package cn.stylefeng.guns.opcua.controller;
+
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.ResponseBody;
+import cn.stylefeng.guns.opcua.client.ClientHandler;
+import cn.stylefeng.guns.opcua.entity.NodeEntity;
+
+//import com.google.common.collect.Lists;
+
+/**
+ * @ClassName: OpcUaController
+ * @Description: OpcUa鎺у埗鍣�
+ * @author yyt
+ * @date 2023骞�10鏈�13鏃�
+ */
+@Controller
+public class CommonController {
+
+	@Autowired
+	private ClientHandler clientHandler;
+
+	/**
+	 * @MethodName: connect
+	 * @Description: opcua杩炴帴骞惰闃呭彉閲�
+	 * @param request
+	 * @param response
+	 * @return
+	 * @CreateTime 2023骞�10鏈�13鏃�
+	 */
+	@RequestMapping("/connect")
+	@ResponseBody
+	public String connect() {
+
+		try {
+			return clientHandler.connect();
+		} catch (Exception e) {
+			e.printStackTrace();
+			return "fail";
+		}
+	}
+
+	/**
+	 * @MethodName: disconnect
+	 * @Description: disconnect
+	 * @return
+	 * @CreateTime 2023骞�10鏈�13鏃�
+	 */
+	@RequestMapping("/disconnect")
+	@ResponseBody
+	public String disconnect() {
+
+		try {
+			return clientHandler.disconnect();
+		} catch (Exception e) {
+			e.printStackTrace();
+			return "fail";
+		}
+	}
+
+	/**
+	 * @MethodName: subscribe
+	 * @Description: subscribe
+	 * @return
+	 * @CreateTime 2023骞�10鏈�13鏃�
+	 */
+	@RequestMapping("/subscribe")
+	@ResponseBody
+	public String subscribe(HttpServletRequest request) {
+
+		try {
+			List<NodeEntity> nodes = Stream.of(request.getParameter("id").split(","))
+					.map(id -> NodeEntity.builder().index(2).identifier(id).build()).collect(Collectors.toList());
+
+			return clientHandler.subscribe(nodes);
+		} catch (Exception e) {
+			e.printStackTrace();
+			return "fail";
+		}
+	}
+
+	/**
+	 * @MethodName: write
+	 * @Description: 鑺傜偣鍐欏叆
+	 * @param request
+	 * @return
+	 * @CreateTime 2023骞�10鏈�13鏃�
+	 */
+	@RequestMapping("/write")
+	@ResponseBody
+	public String write(HttpServletRequest request) {
+
+		NodeEntity node = NodeEntity.builder().index(2).identifier(request.getParameter("id"))
+				.value(request.getParameter("value")).type(request.getParameter("type")).build();
+
+		try {
+			return clientHandler.write(node);
+		} catch (Exception e) {
+			e.printStackTrace();
+			return "fail";
+		}
+	}
+
+	/**
+	 * @MethodName: read
+	 * @Description: read
+	 * @param request
+	 * @return
+	 * @CreateTime 2023骞�10鏈�13鏃�
+	 */
+	@RequestMapping("/read")
+	@ResponseBody
+	public String read(HttpServletRequest request) {
+
+		NodeEntity node = NodeEntity.builder().index(2).identifier(request.getParameter("id")).build();
+
+		try {
+			return clientHandler.read(node);
+		} catch (Exception e) {
+			e.printStackTrace();
+			return "fail";
+		}
+	}
+}
diff --git a/guns-vip-main/src/main/java/cn/stylefeng/guns/opcua/controller/OpcuaConfController.java b/guns-vip-main/src/main/java/cn/stylefeng/guns/opcua/controller/OpcuaConfController.java
new file mode 100644
index 0000000..a9d154d
--- /dev/null
+++ b/guns-vip-main/src/main/java/cn/stylefeng/guns/opcua/controller/OpcuaConfController.java
@@ -0,0 +1,137 @@
+package cn.stylefeng.guns.opcua.controller;
+
+import cn.stylefeng.guns.base.pojo.page.LayuiPageInfo;
+import cn.stylefeng.guns.opcua.entity.OpcuaConf;
+import cn.stylefeng.guns.opcua.model.params.OpcuaConfParam;
+import cn.stylefeng.guns.opcua.service.OpcuaConfService;
+import cn.stylefeng.roses.core.base.controller.BaseController;
+import cn.stylefeng.roses.kernel.model.response.ResponseData;
+import cn.stylefeng.roses.core.mutidatasource.annotion.DataSource;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.ResponseBody;
+
+
+
+/**
+ * 鎺у埗鍣�
+ *
+ * @author yyt
+ * @Date 2023-10-17 09:29:17
+ */
+@Controller
+@RequestMapping("/opcuaConf")
+public class OpcuaConfController extends BaseController {
+
+    private String PREFIX = "/modular/bs/opcuaConf";
+
+    @Autowired
+    private OpcuaConfService opcuaConfService;
+
+    /**
+     * 璺宠浆鍒颁富椤甸潰
+     *
+     * @author yyt
+     * @Date 2023-10-17
+     */
+    @RequestMapping("")
+    public String index() {
+
+        return PREFIX + "/opcuaConf.html";
+    }
+
+    /**
+     * 鏂板椤甸潰
+     *
+     * @author yyt
+     * @Date 2023-10-17
+     */
+    @RequestMapping("/add")
+    public String add() {
+        return PREFIX + "/opcuaConf_add.html";
+    }
+
+    /**
+     * 缂栬緫椤甸潰
+     *
+     * @author yyt
+     * @Date 2023-10-17
+     */
+    @RequestMapping("/edit")
+    public String edit() {
+        return PREFIX + "/opcuaConf_edit.html";
+    }
+
+    /**
+     * 鏂板鎺ュ彛
+     *
+     * @author yyt
+     * @Date 2023-10-17
+     */
+    @RequestMapping("/addItem")
+    @ResponseBody
+    @DataSource(name = "self")
+    public ResponseData addItem(OpcuaConfParam opcuaConfParam) {
+        this.opcuaConfService.add(opcuaConfParam);
+        return ResponseData.success();
+    }
+
+    /**
+     * 缂栬緫鎺ュ彛
+     *
+     * @author yyt
+     * @Date 2023-10-17
+     */
+    @RequestMapping("/editItem")
+    @ResponseBody
+    @DataSource(name = "self")
+    public ResponseData editItem(OpcuaConfParam opcuaConfParam) {
+        this.opcuaConfService.update(opcuaConfParam);
+        return ResponseData.success();
+    }
+
+    /**
+     * 鍒犻櫎鎺ュ彛
+     *
+     * @author yyt
+     * @Date 2023-10-17
+     */
+    @RequestMapping("/delete")
+    @ResponseBody
+    @DataSource(name = "self")
+    public ResponseData delete(OpcuaConfParam opcuaConfParam) {
+        this.opcuaConfService.delete(opcuaConfParam);
+        return ResponseData.success();
+    }
+
+    /**
+     * 鏌ョ湅璇︽儏鎺ュ彛
+     *
+     * @author yyt
+     * @Date 2023-10-17
+     */
+    @RequestMapping("/detail")
+    @ResponseBody
+    @DataSource(name = "self")
+    public ResponseData detail(OpcuaConfParam opcuaConfParam) {
+        OpcuaConf detail = this.opcuaConfService.getById(opcuaConfParam.getId());
+        return ResponseData.success(detail);
+    }
+
+    /**
+     * 鏌ヨ鍒楄〃
+     *
+     * @author yyt
+     * @Date 2023-10-17
+     */
+    @ResponseBody
+    @RequestMapping("/list")
+    @DataSource(name = "self")
+    public LayuiPageInfo list(OpcuaConfParam opcuaConfParam) {
+        return this.opcuaConfService.findPageBySpec(opcuaConfParam);
+    }
+
+}
+
+
diff --git a/guns-vip-main/src/main/java/cn/stylefeng/guns/opcua/entity/NodeEntity.java b/guns-vip-main/src/main/java/cn/stylefeng/guns/opcua/entity/NodeEntity.java
new file mode 100644
index 0000000..0254dbb
--- /dev/null
+++ b/guns-vip-main/src/main/java/cn/stylefeng/guns/opcua/entity/NodeEntity.java
@@ -0,0 +1,19 @@
+package cn.stylefeng.guns.opcua.entity;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@Builder(toBuilder = true)
+@AllArgsConstructor
+@NoArgsConstructor
+public class NodeEntity {
+
+	private Integer index;
+	private String identifier;
+	private Object value;
+	private String type;
+	private Integer clientHandle;
+}
diff --git a/guns-vip-main/src/main/java/cn/stylefeng/guns/opcua/entity/OpcuaConf.java b/guns-vip-main/src/main/java/cn/stylefeng/guns/opcua/entity/OpcuaConf.java
new file mode 100644
index 0000000..4401d59
--- /dev/null
+++ b/guns-vip-main/src/main/java/cn/stylefeng/guns/opcua/entity/OpcuaConf.java
@@ -0,0 +1,176 @@
+package cn.stylefeng.guns.opcua.entity;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableField;
+import java.io.Serializable;
+
+/**
+ * <p>
+ * 
+ * </p>
+ *
+ * @author yyt
+ * @since 2023-10-17
+ */
+@TableName("sys_opcua_conf")
+public class OpcuaConf implements Serializable {
+    private static final long serialVersionUID=1L;
+
+    /**
+     * ID
+     */
+    @TableId(value = "id", type = IdType.ID_WORKER)
+    private Long id;
+
+    /**
+     * 鎵�灞炴ā鍧�
+     */
+    @TableField("module")
+    private String module;
+
+    /**
+     * 鑺傜偣
+     */
+    @TableField("node")
+    private String node;
+
+    /**
+     * 闀垮害
+     */
+    @TableField("length")
+    private Integer length;
+
+    /**
+     * 绫诲瀷
+     */
+    @TableField("sys_types")
+    private String sys_types;
+
+    /**
+     * 鍔熻兘璇存槑
+     */
+    @TableField("functionality")
+    private String functionality;
+
+    /**
+     * 鏄惁璁㈤槄
+     */
+    @TableField("subscribe")
+    private Integer subscribe;
+
+    /**
+     * 璁㈤槄鍝嶅簲妯″潡
+     */
+    @TableField("r_module")
+    private String rModule;
+
+    /**
+     * 璁㈤槄鍝嶅簲鍑芥暟
+     */
+    @TableField("r_function")
+    private String rFunction;
+
+    /**
+     * 澶囨敞
+     */
+    @TableField("remarks")
+    private String remarks;
+
+
+    public Long getId() {
+        return id;
+    }
+
+    public void setId(Long id) {
+        this.id = id;
+    }
+
+    public String getModule() {
+        return module;
+    }
+
+    public void setModule(String module) {
+        this.module = module;
+    }
+
+    public String getNode() {
+        return node;
+    }
+
+    public void setNode(String node) {
+        this.node = node;
+    }
+
+    public Integer getLength() {
+        return length;
+    }
+
+    public void setLength(Integer length) {
+        this.length = length;
+    }
+
+    public String getSys_types() {
+        return sys_types;
+    }
+
+    public void setSys_types(String sys_types) {
+        this.sys_types = sys_types;
+    }
+
+    public String getFunctionality() {
+        return functionality;
+    }
+
+    public void setFunctionality(String functionality) {
+        this.functionality = functionality;
+    }
+
+    public Integer getSubscribe() {
+        return subscribe;
+    }
+
+    public void setSubscribe(Integer subscribe) {
+        this.subscribe = subscribe;
+    }
+
+    public String getrModule() {
+        return rModule;
+    }
+
+    public void setrModule(String rModule) {
+        this.rModule = rModule;
+    }
+
+    public String getrFunction() {
+        return rFunction;
+    }
+
+    public void setrFunction(String rFunction) {
+        this.rFunction = rFunction;
+    }
+
+    public String getRemarks() {
+        return remarks;
+    }
+
+    public void setRemarks(String remarks) {
+        this.remarks = remarks;
+    }
+
+    @Override
+    public String toString() {
+        return "OpcuaConf{" +
+        "id=" + id +
+        ", module=" + module +
+        ", node=" + node +
+        ", length=" + length +
+        ", sys_types=" + sys_types + ", " + "functionality=" + functionality +
+        ", subscribe=" + subscribe +
+        ", rModule=" + rModule +
+        ", rFunction=" + rFunction +
+        ", remarks=" + remarks +
+        "}";
+    }
+}
diff --git a/guns-vip-main/src/main/java/cn/stylefeng/guns/opcua/mapper/OpcuaConfMapper.java b/guns-vip-main/src/main/java/cn/stylefeng/guns/opcua/mapper/OpcuaConfMapper.java
new file mode 100644
index 0000000..d5ce050
--- /dev/null
+++ b/guns-vip-main/src/main/java/cn/stylefeng/guns/opcua/mapper/OpcuaConfMapper.java
@@ -0,0 +1,56 @@
+package cn.stylefeng.guns.opcua.mapper;
+
+import cn.stylefeng.guns.opcua.entity.OpcuaConf;
+import cn.stylefeng.guns.opcua.model.params.OpcuaConfParam;
+import cn.stylefeng.guns.opcua.model.result.OpcuaConfResult;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * <p>
+ *  Mapper 鎺ュ彛
+ * </p>
+ *
+ * @author yyt
+ * @since 2023-10-17
+ */
+public interface OpcuaConfMapper extends BaseMapper<OpcuaConf> {
+
+    /**
+     * 鑾峰彇鍒楄〃
+     *
+     * @author yyt
+     * @Date 2023-10-17
+     */
+    List<OpcuaConfResult> customList(@Param("paramCondition") OpcuaConfParam paramCondition);
+
+    /**
+     * 鑾峰彇map鍒楄〃
+     *
+     * @author yyt
+     * @Date 2023-10-17
+     */
+    List<Map<String, Object>> customMapList(@Param("paramCondition") OpcuaConfParam paramCondition);
+
+    /**
+     * 鑾峰彇鍒嗛〉瀹炰綋鍒楄〃
+     *
+     * @author yyt
+     * @Date 2023-10-17
+     */
+    Page<OpcuaConfResult> customPageList(@Param("page") Page page, @Param("paramCondition") OpcuaConfParam paramCondition);
+
+    /**
+     * 鑾峰彇鍒嗛〉map鍒楄〃
+     *
+     * @author yyt
+     * @Date 2023-10-17
+     */
+    Page<Map<String, Object>> customPageMapList(@Param("page") Page page, @Param("paramCondition") OpcuaConfParam paramCondition);
+
+}
diff --git a/guns-vip-main/src/main/java/cn/stylefeng/guns/opcua/mapper/mapping/OpcuaConfMapper.xml b/guns-vip-main/src/main/java/cn/stylefeng/guns/opcua/mapper/mapping/OpcuaConfMapper.xml
new file mode 100644
index 0000000..7643377
--- /dev/null
+++ b/guns-vip-main/src/main/java/cn/stylefeng/guns/opcua/mapper/mapping/OpcuaConfMapper.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="cn.stylefeng.guns.opcua.mapper.OpcuaConfMapper">
+
+    <!-- 閫氱敤鏌ヨ鏄犲皠缁撴灉 -->
+    <resultMap id="BaseResultMap" type="cn.stylefeng.guns.opcua.entity.OpcuaConf">
+        <id column="id" property="id" />
+        <result column="module" property="module" />
+        <result column="node" property="node" />
+        <result column="length" property="length" />
+        <result column="sys_types" property="sys_types" />
+        <result column="functionality" property="functionality" />
+        <result column="subscribe" property="subscribe" />
+        <result column="r_module" property="rModule" />
+        <result column="r_function" property="rFunction" />
+        <result column="remarks" property="remarks" />
+    </resultMap>
+
+    <!-- 閫氱敤鏌ヨ缁撴灉鍒� -->
+    <sql id="Base_Column_List">
+        id AS "id", module AS "module", node AS "node", length AS "length",sys_types AS "sys_types",functionality AS "functionality", subscribe AS "subscribe", r_module AS "rModule", r_function AS "rFunction", remarks AS "remarks"
+    </sql>
+
+
+    <select id="customList" resultType="cn.stylefeng.guns.opcua.model.result.OpcuaConfResult" parameterType="cn.stylefeng.guns.opcua.model.params.OpcuaConfParam">
+        select
+        <include refid="Base_Column_List"/>
+        from sys_opcua_conf where 1 = 1
+    </select>
+
+    <select id="customMapList" resultType="map" parameterType="cn.stylefeng.guns.opcua.model.params.OpcuaConfParam">
+        select
+        <include refid="Base_Column_List"/>
+        from sys_opcua_conf where 1 = 1
+    </select>
+
+    <select id="customPageList" resultType="cn.stylefeng.guns.opcua.model.result.OpcuaConfResult" parameterType="cn.stylefeng.guns.opcua.model.params.OpcuaConfParam">
+        select
+        <include refid="Base_Column_List"/>
+        from sys_opcua_conf where 1 = 1
+    </select>
+
+    <select id="customPageMapList" resultType="map" parameterType="cn.stylefeng.guns.opcua.model.params.OpcuaConfParam">
+        select
+        <include refid="Base_Column_List"/>
+        from sys_opcua_conf where 1 = 1
+    </select>
+
+</mapper>
diff --git a/guns-vip-main/src/main/java/cn/stylefeng/guns/opcua/model/params/OpcuaConfParam.java b/guns-vip-main/src/main/java/cn/stylefeng/guns/opcua/model/params/OpcuaConfParam.java
new file mode 100644
index 0000000..abbdcfa
--- /dev/null
+++ b/guns-vip-main/src/main/java/cn/stylefeng/guns/opcua/model/params/OpcuaConfParam.java
@@ -0,0 +1,78 @@
+package cn.stylefeng.guns.opcua.model.params;
+
+import lombok.Data;
+import cn.stylefeng.roses.kernel.model.validator.BaseValidatingParam;
+import java.util.Date;
+import java.io.Serializable;
+import java.math.BigDecimal;
+
+/**
+ * <p>
+ * 
+ * </p>
+ *
+ * @author yyt
+ * @since 2023-10-17
+ */
+@Data
+public class OpcuaConfParam implements Serializable, BaseValidatingParam {
+
+    private static final long serialVersionUID = 1L;
+
+
+    /**
+     * ID
+     */
+    private Long id;
+
+    /**
+     * 鎵�灞炴ā鍧�
+     */
+    private String module;
+
+    /**
+     * 鑺傜偣
+     */
+    private String node;
+
+    /**
+     * 闀垮害
+     */
+    private Integer length;
+
+    /**
+     * 绫诲瀷
+     */
+    private String sys_types;
+
+    /**
+     * 鍔熻兘璇存槑
+     */
+    private String functionality;
+
+    /**
+     * 鏄惁璁㈤槄
+     */
+    private Integer subscribe;
+
+    /**
+     * 璁㈤槄鍝嶅簲妯″潡
+     */
+    private String rModule;
+
+    /**
+     * 璁㈤槄鍝嶅簲鍑芥暟
+     */
+    private String rFunction;
+
+    /**
+     * 澶囨敞
+     */
+    private String remarks;
+
+    @Override
+    public String checkParam() {
+        return null;
+    }
+
+}
diff --git a/guns-vip-main/src/main/java/cn/stylefeng/guns/opcua/model/result/OpcuaConfResult.java b/guns-vip-main/src/main/java/cn/stylefeng/guns/opcua/model/result/OpcuaConfResult.java
new file mode 100644
index 0000000..4237c80
--- /dev/null
+++ b/guns-vip-main/src/main/java/cn/stylefeng/guns/opcua/model/result/OpcuaConfResult.java
@@ -0,0 +1,72 @@
+package cn.stylefeng.guns.opcua.model.result;
+
+import lombok.Data;
+import java.util.Date;
+import java.io.Serializable;
+import java.math.BigDecimal;
+
+/**
+ * <p>
+ * 
+ * </p>
+ *
+ * @author yyt
+ * @since 2023-10-17
+ */
+@Data
+public class OpcuaConfResult implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+
+    /**
+     * ID
+     */
+    private Long id;
+
+    /**
+     * 鎵�灞炴ā鍧�
+     */
+    private String module;
+
+    /**
+     * 鑺傜偣
+     */
+    private String node;
+
+    /**
+     * 闀垮害
+     */
+    private Integer length;
+
+    /**
+     * 绫诲瀷
+     */
+    private String sys_types;
+
+    /**
+     * 鍔熻兘璇存槑
+     */
+    private String functionality;
+
+    /**
+     * 鏄惁璁㈤槄
+     */
+    private Integer subscribe;
+
+    /**
+     * 璁㈤槄鍝嶅簲妯″潡
+     */
+    private String rModule;
+
+    /**
+     * 璁㈤槄鍝嶅簲鍑芥暟
+     */
+    private String rFunction;
+
+    /**
+     * 澶囨敞
+     */
+    private String remarks;
+
+}
diff --git a/guns-vip-main/src/main/java/cn/stylefeng/guns/opcua/service/OpcuaConfService.java b/guns-vip-main/src/main/java/cn/stylefeng/guns/opcua/service/OpcuaConfService.java
new file mode 100644
index 0000000..b14e435
--- /dev/null
+++ b/guns-vip-main/src/main/java/cn/stylefeng/guns/opcua/service/OpcuaConfService.java
@@ -0,0 +1,69 @@
+package cn.stylefeng.guns.opcua.service;
+
+import cn.stylefeng.guns.base.pojo.page.LayuiPageInfo;
+import cn.stylefeng.guns.opcua.entity.OpcuaConf;
+import cn.stylefeng.guns.opcua.model.params.OpcuaConfParam;
+import cn.stylefeng.guns.opcua.model.result.OpcuaConfResult;
+import com.baomidou.mybatisplus.extension.service.IService;
+
+import java.util.List;
+
+/**
+ * <p>
+ *  鏈嶅姟绫�
+ * </p>
+ *
+ * @author yyt
+ * @since 2023-10-17
+ */
+public interface OpcuaConfService extends IService<OpcuaConf> {
+
+    /**
+     * 鏂板
+     *
+     * @author yyt
+     * @Date 2023-10-17
+     */
+    void add(OpcuaConfParam param);
+
+    /**
+     * 鍒犻櫎
+     *
+     * @author yyt
+     * @Date 2023-10-17
+     */
+    void delete(OpcuaConfParam param);
+
+    /**
+     * 鏇存柊
+     *
+     * @author yyt
+     * @Date 2023-10-17
+     */
+    void update(OpcuaConfParam param);
+
+    /**
+     * 鏌ヨ鍗曟潯鏁版嵁锛孲pecification妯″紡
+     *
+     * @author yyt
+     * @Date 2023-10-17
+     */
+    OpcuaConfResult findBySpec(OpcuaConfParam param);
+
+    /**
+     * 鏌ヨ鍒楄〃锛孲pecification妯″紡
+     *
+     * @author yyt
+     * @Date 2023-10-17
+     */
+    List<OpcuaConfResult> findListBySpec(OpcuaConfParam param);
+
+    /**
+     * 鏌ヨ鍒嗛〉鏁版嵁锛孲pecification妯″紡
+     *
+     * @author yyt
+     * @Date 2023-10-17
+     */
+     LayuiPageInfo findPageBySpec(OpcuaConfParam param);
+
+}
diff --git a/guns-vip-main/src/main/java/cn/stylefeng/guns/opcua/service/impl/OpcuaConfServiceImpl.java b/guns-vip-main/src/main/java/cn/stylefeng/guns/opcua/service/impl/OpcuaConfServiceImpl.java
new file mode 100644
index 0000000..d5ee5b2
--- /dev/null
+++ b/guns-vip-main/src/main/java/cn/stylefeng/guns/opcua/service/impl/OpcuaConfServiceImpl.java
@@ -0,0 +1,84 @@
+package cn.stylefeng.guns.opcua.service.impl;
+
+import cn.stylefeng.guns.base.pojo.page.LayuiPageFactory;
+import cn.stylefeng.guns.base.pojo.page.LayuiPageInfo;
+import cn.stylefeng.guns.opcua.entity.OpcuaConf;
+import cn.stylefeng.guns.opcua.mapper.OpcuaConfMapper;
+import cn.stylefeng.guns.opcua.model.params.OpcuaConfParam;
+import cn.stylefeng.guns.opcua.model.result.OpcuaConfResult;
+import  cn.stylefeng.guns.opcua.service.OpcuaConfService;
+import cn.stylefeng.roses.core.util.ToolUtil;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import org.springframework.stereotype.Service;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * <p>
+ *  鏈嶅姟瀹炵幇绫�
+ * </p>
+ *
+ * @author yyt
+ * @since 2023-10-17
+ */
+@Service
+public class OpcuaConfServiceImpl extends ServiceImpl<OpcuaConfMapper, OpcuaConf> implements OpcuaConfService {
+
+    @Override
+    public void add(OpcuaConfParam param){
+        OpcuaConf entity = getEntity(param);
+        this.save(entity);
+    }
+
+    @Override
+    public void delete(OpcuaConfParam param){
+        this.removeById(getKey(param));
+    }
+
+    @Override
+    public void update(OpcuaConfParam param){
+        OpcuaConf oldEntity = getOldEntity(param);
+        OpcuaConf newEntity = getEntity(param);
+        ToolUtil.copyProperties(newEntity, oldEntity);
+        this.updateById(newEntity);
+    }
+
+    @Override
+    public OpcuaConfResult findBySpec(OpcuaConfParam param){
+        return null;
+    }
+
+    @Override
+    public List<OpcuaConfResult> findListBySpec(OpcuaConfParam param){
+        return null;
+    }
+
+    @Override
+    public LayuiPageInfo findPageBySpec(OpcuaConfParam param){
+        Page pageContext = getPageContext();
+        IPage page = this.baseMapper.customPageList(pageContext, param);
+        return LayuiPageFactory.createPageInfo(page);
+    }
+
+    private Serializable getKey(OpcuaConfParam param){
+        return param.getId();
+    }
+
+    private Page getPageContext() {
+        return LayuiPageFactory.defaultPage();
+    }
+
+    private OpcuaConf getOldEntity(OpcuaConfParam param) {
+        return this.getById(getKey(param));
+    }
+
+    private OpcuaConf getEntity(OpcuaConfParam param) {
+        OpcuaConf entity = new OpcuaConf();
+        ToolUtil.copyProperties(param, entity);
+        return entity;
+    }
+
+}
diff --git a/guns-vip-main/src/main/resources/opcua.properties b/guns-vip-main/src/main/resources/opcua.properties
new file mode 100644
index 0000000..e7ce955
--- /dev/null
+++ b/guns-vip-main/src/main/resources/opcua.properties
@@ -0,0 +1,19 @@
+#opcua?????
+opcua.server.endpoint.url=opc.tcp://127.0.0.1:49320
+opcua.server.idp.username=administrator
+opcua.server.idp.password=yyt@8888888888
+#opcua?????
+opcua.client.app.name=plc
+opcua.client.app.uri=urn:Yyt_PC:UnifiedAutomation:UaExpert
+opcua.client.cert.path=C:/Users/30672/Desktop
+opcua.client.cert.file=Yyt_PC-client.pfx
+opcua.client.cert.alias=jlclient-ai
+opcua.client.cert.common.name=UaClient@Jellyleo
+opcua.client.cert.organization=JL
+opcua.client.cert.organization.unit=per
+opcua.client.cert.locality.name=jl
+opcua.client.cert.state.name=JiangSu
+opcua.client.cert.country.code=CN
+opcua.client.cert.dns.name=Jellyleo
+opcua.client.cert.ip.address=administrator
+opcua.client.cert.keystore.password=yyt@8888888888
diff --git a/guns-vip-main/src/main/resources/opcua.yml b/guns-vip-main/src/main/resources/opcua.yml
new file mode 100644
index 0000000..9702b0b
--- /dev/null
+++ b/guns-vip-main/src/main/resources/opcua.yml
@@ -0,0 +1,27 @@
+#opcua鏈嶅姟绔缃�
+opcua:
+  server:
+      endpoint:
+          url: opc.tcp://127.0.0.1:49320
+      idp:
+          username: administrator
+          password: yyt@8888888888
+  #opcua瀹㈡埛绔缃�
+  client:
+      app:
+          name: plc
+          uri: urn:Yyt_PC:UnifiedAutomation:UaExpert
+      cert:
+          path: C:/Users/30672/Desktop
+          file: Yyt_PC-client.pfx
+          alias: jlclient-ai
+          common:
+              name: UaClient@Jellyleo
+#opcua.client.cert.organization=JL
+#opcua.client.cert.organization.unit=per
+#opcua.client.cert.locality.name=jl
+#opcua.client.cert.state.name=JiangSu
+#opcua.client.cert.country.code=CN
+#opcua.client.cert.dns.name=Jellyleo
+#opcua.client.cert.ip.address=administrator
+#opcua.client.cert.keystore.password=yyt@8888888888
\ No newline at end of file
diff --git a/guns-vip-main/src/main/webapp/assets/modular/bs/opcuaConf/opcuaConf.js b/guns-vip-main/src/main/webapp/assets/modular/bs/opcuaConf/opcuaConf.js
new file mode 100644
index 0000000..350b60a
--- /dev/null
+++ b/guns-vip-main/src/main/webapp/assets/modular/bs/opcuaConf/opcuaConf.js
@@ -0,0 +1,132 @@
+layui.use(['table', 'admin', 'ax', 'func'], function () {
+    var $ = layui.$;
+    var table = layui.table;
+    var $ax = layui.ax;
+    var admin = layui.admin;
+    var func = layui.func;
+
+    /**
+     * 绠$悊
+     */
+    var OpcuaConf = {
+        tableId: "opcuaConfTable"
+    };
+
+    /**
+     * 鍒濆鍖栬〃鏍肩殑鍒�
+     */
+    OpcuaConf.initColumn = function () {
+        return [[
+            {type: 'checkbox'},
+            {field: 'id', hide: true, title: 'ID'},
+            {field: 'module', sort: true, title: '鎵�灞炴ā鍧�'},
+            {field: 'node', sort: true, title: '鑺傜偣'},
+            {field: 'length', sort: true, title: '闀垮害'},
+            {field: 'types', sort: true, title: '绫诲瀷'},
+            {field: 'functionality', sort: true, title: '鍔熻兘璇存槑'},
+            {field: 'subscribe', sort: true, title: '鏄惁璁㈤槄'},
+            {field: 'rModule', sort: true, title: '璁㈤槄鍝嶅簲妯″潡'},
+            {field: 'rFunction', sort: true, title: '璁㈤槄鍝嶅簲鍑芥暟'},
+            {field: 'remarks', sort: true, title: '澶囨敞'},
+            {fixed: 'right',width: 125, minWidth: 125, align: 'center', toolbar: '#tableBar', title: '鎿嶄綔'}
+        ]];
+    };
+
+    /**
+     * 鐐瑰嚮鏌ヨ鎸夐挳
+     */
+    OpcuaConf.search = function () {
+        var queryData = {};
+
+
+        table.reload(OpcuaConf.tableId, {
+            where: queryData, page: {curr: 1}
+        });
+    };
+
+    /**
+     * 璺宠浆鍒版坊鍔犻〉闈�
+     */
+    OpcuaConf.jumpAddPage = function () {
+        window.location.href = Feng.ctxPath + '/opcuaConf/add'
+    };
+
+    /**
+    * 璺宠浆鍒扮紪杈戦〉闈�
+    *
+    * @param data 鐐瑰嚮鎸夐挳鏃跺�欑殑琛屾暟鎹�
+    */
+    OpcuaConf.jumpEditPage = function (data) {
+        window.location.href = Feng.ctxPath + '/opcuaConf/edit?id=' + data.id
+    };
+
+    /**
+     * 瀵煎嚭excel鎸夐挳
+     */
+    OpcuaConf.exportExcel = function () {
+        var checkRows = table.checkStatus(OpcuaConf.tableId);
+        if (checkRows.data.length === 0) {
+            Feng.error("璇烽�夋嫨瑕佸鍑虹殑鏁版嵁");
+        } else {
+            table.exportFile(tableResult.config.id, checkRows.data, 'xls');
+        }
+    };
+
+    /**
+     * 鐐瑰嚮鍒犻櫎
+     *
+     * @param data 鐐瑰嚮鎸夐挳鏃跺�欑殑琛屾暟鎹�
+     */
+    OpcuaConf.onDeleteItem = function (data) {
+        var operation = function () {
+            var ajax = new $ax(Feng.ctxPath + "/opcuaConf/delete", function (data) {
+                Feng.success("鍒犻櫎鎴愬姛!");
+                table.reload(OpcuaConf.tableId);
+            }, function (data) {
+                Feng.error("鍒犻櫎澶辫触!" + data.responseJSON.message + "!");
+            });
+            ajax.set("id", data.id);
+            ajax.start();
+        };
+        Feng.confirm("鏄惁鍒犻櫎?", operation);
+    };
+
+    // 娓叉煋琛ㄦ牸
+    var tableResult = table.render({
+        elem: '#' + OpcuaConf.tableId,
+        url: Feng.ctxPath + '/opcuaConf/list',
+        page: true,
+        height: "full-158",
+        cellMinWidth: 100,
+        cols: OpcuaConf.initColumn()
+    });
+
+    // 鎼滅储鎸夐挳鐐瑰嚮浜嬩欢
+    $('#btnSearch').click(function () {
+        OpcuaConf.search();
+    });
+
+    // 娣诲姞鎸夐挳鐐瑰嚮浜嬩欢
+    $('#btnAdd').click(function () {
+
+    OpcuaConf.jumpAddPage();
+
+    });
+
+    // 瀵煎嚭excel
+    $('#btnExp').click(function () {
+        OpcuaConf.exportExcel();
+    });
+
+    // 宸ュ叿鏉$偣鍑讳簨浠�
+    table.on('tool(' + OpcuaConf.tableId + ')', function (obj) {
+        var data = obj.data;
+        var layEvent = obj.event;
+
+        if (layEvent === 'edit') {
+            OpcuaConf.jumpEditPage(data);
+        } else if (layEvent === 'delete') {
+            OpcuaConf.onDeleteItem(data);
+        }
+    });
+});
diff --git a/guns-vip-main/src/main/webapp/assets/modular/bs/opcuaConf/opcuaConf_add.js b/guns-vip-main/src/main/webapp/assets/modular/bs/opcuaConf/opcuaConf_add.js
new file mode 100644
index 0000000..37b2520
--- /dev/null
+++ b/guns-vip-main/src/main/webapp/assets/modular/bs/opcuaConf/opcuaConf_add.js
@@ -0,0 +1,43 @@
+/**
+ * 娣诲姞鎴栬�呬慨鏀归〉闈�
+ */
+var OpcuaConfInfoDlg = {
+    data: {
+        id: "",
+        module: "",
+        node: "",
+        length: "",
+        types: "",
+        functionality: "",
+        subscribe: "",
+        rModule: "",
+        rFunction: "",
+        remarks: ""
+    }
+};
+
+layui.use(['form', 'admin', 'ax','laydate','upload','formSelects'], function () {
+    var $ = layui.jquery;
+    var $ax = layui.ax;
+    var form = layui.form;
+    var admin = layui.admin;
+
+    //琛ㄥ崟鎻愪氦浜嬩欢
+    form.on('submit(btnSubmit)', function (data) {
+        var ajax = new $ax(Feng.ctxPath + "/opcuaConf/addItem", function (data) {
+            Feng.success("娣诲姞鎴愬姛锛�");
+            window.location.href = Feng.ctxPath + '/opcuaConf'
+        }, function (data) {
+            Feng.error("娣诲姞澶辫触锛�" + data.responseJSON.message)
+        });
+        ajax.set(data.field);
+        ajax.start();
+
+        return false;
+    });
+
+    $('#cancel').click(function(){
+        window.location.href = Feng.ctxPath + '/opcuaConf'
+    });
+
+});
\ No newline at end of file
diff --git a/guns-vip-main/src/main/webapp/assets/modular/bs/opcuaConf/opcuaConf_edit.js b/guns-vip-main/src/main/webapp/assets/modular/bs/opcuaConf/opcuaConf_edit.js
new file mode 100644
index 0000000..3d96d7c
--- /dev/null
+++ b/guns-vip-main/src/main/webapp/assets/modular/bs/opcuaConf/opcuaConf_edit.js
@@ -0,0 +1,75 @@
+/**
+ * 璇︽儏瀵硅瘽妗�
+ */
+var OpcuaConfInfoDlg = {
+    data: {
+        id: "",
+        module: "",
+        node: "",
+        length: "",
+        types: "",
+        functionality: "",
+        subscribe: "",
+        rModule: "",
+        rFunction: "",
+        remarks: ""
+    }
+};
+
+layui.use(['form', 'admin', 'ax','laydate','upload','formSelects'], function () {
+    var $ = layui.jquery;
+    var $ax = layui.ax;
+    var form = layui.form;
+    var admin = layui.admin;
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+    //鑾峰彇璇︽儏淇℃伅锛屽~鍏呰〃鍗�
+    var ajax = new $ax(Feng.ctxPath + "/opcuaConf/detail?id=" + Feng.getUrlParam("id"));
+    var result = ajax.start();
+    form.val('opcuaConfForm', result.data);
+
+    //琛ㄥ崟鎻愪氦浜嬩欢
+    form.on('submit(btnSubmit)', function (data) {
+        var ajax = new $ax(Feng.ctxPath + "/opcuaConf/editItem", function (data) {
+            Feng.success("鏇存柊鎴愬姛锛�");
+            window.location.href = Feng.ctxPath + '/opcuaConf'
+        }, function (data) {
+            Feng.error("鏇存柊澶辫触锛�" + data.responseJSON.message)
+        });
+        ajax.set(data.field);
+        ajax.start();
+
+        return false;
+    });
+
+    $('#cancel').click(function(){
+        window.location.href = Feng.ctxPath + '/opcuaConf'
+    });
+});
\ No newline at end of file
diff --git a/guns-vip-main/src/main/webapp/pages/modular/bs/opcuaConf/opcuaConf.html b/guns-vip-main/src/main/webapp/pages/modular/bs/opcuaConf/opcuaConf.html
new file mode 100644
index 0000000..56eba99
--- /dev/null
+++ b/guns-vip-main/src/main/webapp/pages/modular/bs/opcuaConf/opcuaConf.html
@@ -0,0 +1,32 @@
+@layout("/common/_container.html",{js:["/assets/modular/bs/opcuaConf/opcuaConf.js"]}){
+
+<div class="layui-body-header">
+    <span class="layui-body-header-title">绠$悊</span>
+</div>
+
+<div class="layui-fluid">
+    <div class="layui-row layui-col-space15">
+        <div class="layui-col-sm12 layui-col-md12 layui-col-lg12">
+            <div class="layui-card">
+                <div class="layui-card-body">
+                    <div class="layui-form toolbar">
+                        <div class="layui-form-item">
+                            <div class="layui-inline">
+                                <button id="btnSearch" class="layui-btn icon-btn"><i class="layui-icon">&#xe615;</i>鎼滅储</button>
+                                <button id="btnAdd" class="layui-btn icon-btn"><i class="layui-icon">&#xe654;</i>娣诲姞</button>
+                                <button id="btnExp" class="layui-btn icon-btn"><i class="layui-icon">&#xe67d;</i>瀵煎嚭</button>
+                            </div>
+                        </div>
+                    </div>
+                    <table class="layui-table" id="opcuaConfTable" lay-filter="opcuaConfTable"></table>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>
+
+<script type="text/html" id="tableBar">
+    <a class="layui-btn layui-btn-primary layui-btn-xs" lay-event="edit">淇敼</a>
+    <a class="layui-btn layui-btn-danger layui-btn-xs" lay-event="delete">鍒犻櫎</a>
+</script>
+@}
\ No newline at end of file
diff --git a/guns-vip-main/src/main/webapp/pages/modular/bs/opcuaConf/opcuaConf_add.html b/guns-vip-main/src/main/webapp/pages/modular/bs/opcuaConf/opcuaConf_add.html
new file mode 100644
index 0000000..49dd4fc
--- /dev/null
+++ b/guns-vip-main/src/main/webapp/pages/modular/bs/opcuaConf/opcuaConf_add.html
@@ -0,0 +1,86 @@
+@layout("/common/_form.html",{js:["/assets/modular/bs/opcuaConf/opcuaConf_add.js"],css:["/assets/expand/module/formSelects/formSelects-v4.css"]}){
+
+<form class="layui-form" id="opcuaConfForm" lay-filter="opcuaConfForm">
+    <div class="layui-fluid" style="padding-bottom: 75px;">
+        <div class="layui-card">
+            <div class="layui-card-header">鍩烘湰淇℃伅</div>
+            <div class="layui-card-body">
+                <div class="layui-form-item layui-row">
+                    <input name="id" type="hidden"/>
+
+                    <div class="layui-inline layui-col-md12">
+                        <label class="layui-form-label">鎵�灞炴ā鍧�<span style="color: red;">*</span></label>
+                        <div class="layui-input-block">
+                            <input id="module" name="module" placeholder="璇疯緭鍏ユ墍灞炴ā鍧�" type="text" class="layui-input" lay-verify="required" required/>
+                        </div>
+                    </div>
+                    <div class="layui-inline layui-col-md12">
+                        <label class="layui-form-label">鑺傜偣<span style="color: red;">*</span></label>
+                        <div class="layui-input-block">
+                            <input id="node" name="node" placeholder="璇疯緭鍏ヨ妭鐐�" type="text" class="layui-input" lay-verify="required" required/>
+                        </div>
+                    </div>
+                    <div class="layui-inline layui-col-md12">
+                        <label class="layui-form-label">闀垮害<span style="color: red;">*</span></label>
+                        <div class="layui-input-block">
+                            <input id="length" name="length" placeholder="璇疯緭鍏ラ暱搴�" type="text" class="layui-input" lay-verify="required" required/>
+                        </div>
+                    </div>
+                    <div class="layui-inline layui-col-md12">
+                        <label class="layui-form-label">绫诲瀷<span style="color: red;">*</span></label>
+                        <div class="layui-input-block">
+                            <input id="
+
+
+
+types" name="
+
+
+
+types" placeholder="璇疯緭鍏ョ被鍨�" type="text" class="layui-input" lay-verify="required" required/>
+                        </div>
+                    </div>
+                    <div class="layui-inline layui-col-md12">
+                        <label class="layui-form-label">鍔熻兘璇存槑<span style="color: red;">*</span></label>
+                        <div class="layui-input-block">
+                            <input id="
+functionality" name="
+functionality" placeholder="璇疯緭鍏ュ姛鑳借鏄�" type="text" class="layui-input" lay-verify="required" required/>
+                        </div>
+                    </div>
+                    <div class="layui-inline layui-col-md12">
+                        <label class="layui-form-label">鏄惁璁㈤槄<span style="color: red;">*</span></label>
+                        <div class="layui-input-block">
+                            <input id="subscribe" name="subscribe" placeholder="璇疯緭鍏ユ槸鍚﹁闃�" type="text" class="layui-input" lay-verify="required" required/>
+                        </div>
+                    </div>
+                    <div class="layui-inline layui-col-md12">
+                        <label class="layui-form-label">璁㈤槄鍝嶅簲妯″潡<span style="color: red;">*</span></label>
+                        <div class="layui-input-block">
+                            <input id="rModule" name="rModule" placeholder="璇疯緭鍏ヨ闃呭搷搴旀ā鍧�" type="text" class="layui-input" lay-verify="required" required/>
+                        </div>
+                    </div>
+                    <div class="layui-inline layui-col-md12">
+                        <label class="layui-form-label">璁㈤槄鍝嶅簲鍑芥暟<span style="color: red;">*</span></label>
+                        <div class="layui-input-block">
+                            <input id="rFunction" name="rFunction" placeholder="璇疯緭鍏ヨ闃呭搷搴斿嚱鏁�" type="text" class="layui-input" lay-verify="required" required/>
+                        </div>
+                    </div>
+                    <div class="layui-inline layui-col-md12">
+                        <label class="layui-form-label">澶囨敞<span style="color: red;">*</span></label>
+                        <div class="layui-input-block">
+                            <input id="remarks" name="remarks" placeholder="璇疯緭鍏ュ娉�" type="text" class="layui-input" lay-verify="required" required/>
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+    <div class="form-group-bottom text-center">
+        <button class="layui-btn" lay-filter="btnSubmit" lay-submit>&emsp;鎻愪氦&emsp;</button>
+        <button type="reset" class="layui-btn layui-btn-primary" id="cancel">&emsp;鍙栨秷&emsp;</button>
+    </div>
+
+</form>
+
+@}
\ No newline at end of file
diff --git a/guns-vip-main/src/main/webapp/pages/modular/bs/opcuaConf/opcuaConf_edit.html b/guns-vip-main/src/main/webapp/pages/modular/bs/opcuaConf/opcuaConf_edit.html
new file mode 100644
index 0000000..509648e
--- /dev/null
+++ b/guns-vip-main/src/main/webapp/pages/modular/bs/opcuaConf/opcuaConf_edit.html
@@ -0,0 +1,86 @@
+@layout("/common/_form.html",{js:["/assets/modular/bs/opcuaConf/opcuaConf_edit.js"]}){
+
+<form class="layui-form" id="opcuaConfForm" lay-filter="opcuaConfForm">
+    <div class="layui-fluid" style="padding-bottom: 75px;">
+        <div class="layui-card">
+            <div class="layui-card-header">鍩烘湰淇℃伅</div>
+            <div class="layui-card-body">
+                <div class="layui-form-item layui-row">
+                    <input name="id" type="hidden"/>
+
+                    <div class="layui-inline layui-col-md12">
+                        <label class="layui-form-label">鎵�灞炴ā鍧�<span style="color: red;">*</span></label>
+                        <div class="layui-input-block">
+                            <input id="module" name="module" placeholder="璇疯緭鍏ユ墍灞炴ā鍧�" type="text" class="layui-input" lay-verify="required" required/>
+                        </div>
+                    </div>
+                    <div class="layui-inline layui-col-md12">
+                        <label class="layui-form-label">鑺傜偣<span style="color: red;">*</span></label>
+                        <div class="layui-input-block">
+                            <input id="node" name="node" placeholder="璇疯緭鍏ヨ妭鐐�" type="text" class="layui-input" lay-verify="required" required/>
+                        </div>
+                    </div>
+                    <div class="layui-inline layui-col-md12">
+                        <label class="layui-form-label">闀垮害<span style="color: red;">*</span></label>
+                        <div class="layui-input-block">
+                            <input id="length" name="length" placeholder="璇疯緭鍏ラ暱搴�" type="text" class="layui-input" lay-verify="required" required/>
+                        </div>
+                    </div>
+                    <div class="layui-inline layui-col-md12">
+                        <label class="layui-form-label">绫诲瀷<span style="color: red;">*</span></label>
+                        <div class="layui-input-block">
+                            <input id="
+
+
+
+types" name="
+
+
+
+types" placeholder="璇疯緭鍏ョ被鍨�" type="text" class="layui-input" lay-verify="required" required/>
+                        </div>
+                    </div>
+                    <div class="layui-inline layui-col-md12">
+                        <label class="layui-form-label">鍔熻兘璇存槑<span style="color: red;">*</span></label>
+                        <div class="layui-input-block">
+                            <input id="
+functionality" name="
+functionality" placeholder="璇疯緭鍏ュ姛鑳借鏄�" type="text" class="layui-input" lay-verify="required" required/>
+                        </div>
+                    </div>
+                    <div class="layui-inline layui-col-md12">
+                        <label class="layui-form-label">鏄惁璁㈤槄<span style="color: red;">*</span></label>
+                        <div class="layui-input-block">
+                            <input id="subscribe" name="subscribe" placeholder="璇疯緭鍏ユ槸鍚﹁闃�" type="text" class="layui-input" lay-verify="required" required/>
+                        </div>
+                    </div>
+                    <div class="layui-inline layui-col-md12">
+                        <label class="layui-form-label">璁㈤槄鍝嶅簲妯″潡<span style="color: red;">*</span></label>
+                        <div class="layui-input-block">
+                            <input id="rModule" name="rModule" placeholder="璇疯緭鍏ヨ闃呭搷搴旀ā鍧�" type="text" class="layui-input" lay-verify="required" required/>
+                        </div>
+                    </div>
+                    <div class="layui-inline layui-col-md12">
+                        <label class="layui-form-label">璁㈤槄鍝嶅簲鍑芥暟<span style="color: red;">*</span></label>
+                        <div class="layui-input-block">
+                            <input id="rFunction" name="rFunction" placeholder="璇疯緭鍏ヨ闃呭搷搴斿嚱鏁�" type="text" class="layui-input" lay-verify="required" required/>
+                        </div>
+                    </div>
+                    <div class="layui-inline layui-col-md12">
+                        <label class="layui-form-label">澶囨敞<span style="color: red;">*</span></label>
+                        <div class="layui-input-block">
+                            <input id="remarks" name="remarks" placeholder="璇疯緭鍏ュ娉�" type="text" class="layui-input" lay-verify="required" required/>
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+    <div class="form-group-bottom text-center">
+        <button class="layui-btn" lay-filter="btnSubmit" lay-submit>&emsp;鎻愪氦&emsp;</button>
+        <button type="reset" class="layui-btn layui-btn-primary" id="cancel">&emsp;鍙栨秷&emsp;</button>
+    </div>
+
+</form>
+
+@}
\ No newline at end of file

--
Gitblit v1.9.3