From f4644873804165e9db88375f242421f00b0903f2 Mon Sep 17 00:00:00 2001
From: zhoushihao <zsh19950802@163.com>
Date: 星期五, 11 十月 2024 08:36:01 +0800
Subject: [PATCH] 1、opc订阅断电重连功能已实现 2、移除lang包下定义字典重复字段

---
 hangzhoumesParent/common/opcuaClient/src/main/java/com/mes/milo/service/MiloService.java                                                 |  553 ++++++++++++++++++
 hangzhoumesParent/common/opcuaClient/src/main/java/com/mes/milo/pool/MiloConnectFactory.java                                             |  155 +++++
 hangzhoumesParent/common/opcuaClient/src/main/resources/META-INF/spring.factories                                                        |    3 
 hangzhoumesParent/common/opcuaClient/src/main/java/com/mes/milo/utils/CustomUtil.java                                                    |  114 +++
 hangzhoumesParent/common/opcuaClient/src/main/java/com/mes/milo/runner/ReadValuesRunner.java                                             |   60 ++
 hangzhoumesParent/moduleService/CacheGlassModule/src/main/java/com/mes/opccallback/CacheGlassSubscriptionCallback.java                   |   35 +
 hangzhoumesParent/moduleService/CacheGlassModule/src/main/java/com/mes/job/OpcCacheGlassTask.java                                        |   31 +
 hangzhoumesParent/common/opcuaClient/src/main/java/com/mes/milo/runner/BrowseRunner.java                                                 |   36 +
 hangzhoumesParent/common/opcuaClient/src/main/java/com/mes/milo/runner/WriteValuesRunner.java                                            |   50 +
 hangzhoumesParent/moduleService/CacheGlassModule/src/main/resources/application.yml                                                      |    8 
 hangzhoumesParent/common/opcuaClient/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports |    1 
 hangzhoumesParent/common/opcuaClient/src/main/java/com/mes/milo/exception/EndPointNotFoundException.java                                 |   18 
 hangzhoumesParent/moduleService/CacheGlassModule/src/main/java/com/mes/job/entity/CacheGlassInfo.java                                    |   46 +
 hangzhoumesParent/common/pom.xml                                                                                                         |    1 
 hangzhoumesParent/common/opcuaClient/src/main/java/com/mes/milo/pool/MiloConnectPool.java                                                |   25 
 hangzhoumesParent/common/opcuaClient/src/main/java/com/mes/milo/model/ReadWriteEntity.java                                               |   26 
 UI-Project/src/lang/en.js                                                                                                                |    2 
 UI-Project/src/lang/zh.js                                                                                                                |   10 
 hangzhoumesParent/common/opcuaClient/src/main/java/com/mes/milo/runner/subscription/SubscriptionRunner.java                              |  111 +++
 hangzhoumesParent/common/opcuaClient/src/main/java/com/mes/milo/utils/KeyStoreLoader.java                                                |  123 ++++
 hangzhoumesParent/common/opcuaClient/src/main/java/com/mes/milo/exception/IdentityNotFoundException.java                                 |   14 
 hangzhoumesParent/common/opcuaClient/src/main/java/com/mes/milo/configuration/MiloProperties.java                                        |   85 ++
 hangzhoumesParent/common/opcuaClient/pom.xml                                                                                             |   40 +
 hangzhoumesParent/common/opcuaClient/src/main/java/com/mes/milo/model/WriteEntity.java                                                   |   23 
 hangzhoumesParent/moduleService/yiwumes/src/main/resources/application.yml                                                               |   10 
 hangzhoumesParent/common/opcuaClient/src/main/resources/application.yml                                                                  |    9 
 hangzhoumesParent/common/opcuaClient/src/main/java/com/mes/milo/configuration/MiloAutoConfiguration.java                                 |  117 +++
 hangzhoumesParent/moduleService/pom.xml                                                                                                  |    5 
 hangzhoumesParent/common/opcuaClient/src/main/java/com/mes/milo/runner/subscription/SubscriptionCallback.java                            |   16 
 hangzhoumesParent/common/opcuaClient/src/main/java/com/mes/milo/runner/BrowseNodeRunner.java                                             |   59 +
 30 files changed, 1,775 insertions(+), 11 deletions(-)

diff --git a/UI-Project/src/lang/en.js b/UI-Project/src/lang/en.js
index d80a9be..869758b 100644
--- a/UI-Project/src/lang/en.js
+++ b/UI-Project/src/lang/en.js
@@ -158,7 +158,6 @@
         inglassgaps:'袙胁械写懈褌械 褋褌械泻谢芯 蟹邪蟹芯褉',
         sure:'锌芯写褌胁械褉写',
         cancel:'袨褌屑械薪邪',
-        glassID:'小褌械泻谢褟薪薪褘泄 ID',
         operate:'袨锌械褉邪褑懈褟',
         breakage:'袩芯胁褉械卸写械薪懈械',
         delete:'校写邪谢',
@@ -224,7 +223,6 @@
         bindingshelves:'袧邪蟹薪邪褔械薪薪邪褟 褋褌械谢谢邪卸泻邪',
         clear:'效懈褋褌芯.',
         workstation:'小褌邪薪褑懈褟 薪芯屑械褉',
-        shelfnumber:'袧芯屑械褉 褋褌芯泄泻懈 褉械谐懈褋褌褉邪褑懈懈锛�',
         cardnumbera:'袧芯屑械褉 泻邪褉褌芯褔泻懈 锌褉芯褑械褋褋邪锛�',
         incardnumber:'袩芯卸邪谢褍泄褋褌邪, 胁褘斜械褉懈褌械 薪芯屑械褉 锌褉芯谐褉邪屑屑褘',
         clearglass:'袨褔懈褋褌懈褌褜 褋褌械泻谢芯 褋 锌芯谢泻懈',
diff --git a/UI-Project/src/lang/zh.js b/UI-Project/src/lang/zh.js
index 4f2483f..d1de9eb 100644
--- a/UI-Project/src/lang/zh.js
+++ b/UI-Project/src/lang/zh.js
@@ -216,12 +216,11 @@
         specifytemperinga:'鏄惁鎸囧畾閽㈠寲璇ユ潯淇℃伅锛�',
         temperedswitch:'閽㈠寲寮�鍏�',
         dutyinformation:'鍊肩彮淇℃伅',
-        line:'绾胯矾',
         process:'宸ュ簭',
         team:'鐝粍',
         basic:'璁惧',
         makesure:'纭淇濆瓨',
-        cancel:'鍙栨秷',
+        // cancel:'鍙栨秷',
         temperingtotal:'閽㈠寲鐐夋暟锛�',
         glasstotal:'鐜荤拑鎬绘暟锛�',
     },
@@ -452,11 +451,9 @@
         allstatus: '鍏ㄩ儴鍏ュ簱鐘舵��',
         completedquantity: '瀹屾垚鏁伴噺',
         scrapquantity: '鎶ュ簾鏁伴噺',
-        number: '鏁伴噺',
         method: '鍔犲伐鏂瑰紡',
         innumber: '宸插叆鏁伴噺',
         productstatus: '鐢熶骇鐘舵��',
-        projectnumber: '宸ョ▼鍙�',
         right: '姝e父',
         stop: '缁堟',
         inquire: '鏌ヨ',
@@ -490,7 +487,6 @@
         glassID :'鐜荤拑ID',
         projectnumber :'宸ョ▼鍙�',
         layoutID :'閽㈠寲鐗堝浘ID',
-        productiontime :'鐢熶骇鏃堕棿',
         type :'绫诲瀷',
         state :'鐘舵��',
         processcards :'娴佺▼鍗�',
@@ -525,7 +521,6 @@
         startslot:'寮�濮嬪伐浣�',
         endslot:'鐩爣宸ヤ綅',
         slotid:'鏍煎瓙ID',
-        slot:'鏍煎瓙鍙�',
         width:'鍘熺墖瀹�',
         widtha:'鍘熺墖瀹斤細',
         inwidth:'璇疯緭鍏ュ師鐗囧',
@@ -545,7 +540,6 @@
         quantitya:'鏁伴噺锛�',
         enableid:'浠诲姟ID',
         originateslot:'璧峰鏍煎瓙',
-        endslot:'鐩爣鏍煎瓙',
         patternquantity:'鍘熺墖鏁伴噺',
         enabletype:'浠诲姟绫诲瀷',
         enablestate:'宸ヤ綅鐘舵��',
@@ -563,10 +557,8 @@
         taskstatus :'浠诲姟鐘舵��',
         built :'鏂板缓',
         execution :'鎵ц涓�',
-        finish :'瀹屾垚',
         tasktype :'浠诲姟绫诲瀷',
         stocke :'鍏ュ簱',
-        outbound :'鍑哄簱',
         dispatch :'璋冨害',
         inquire :'鏌ヨ',
         station :'宸ヤ綅',
diff --git a/hangzhoumesParent/common/opcuaClient/pom.xml b/hangzhoumesParent/common/opcuaClient/pom.xml
new file mode 100644
index 0000000..cf5d20f
--- /dev/null
+++ b/hangzhoumesParent/common/opcuaClient/pom.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>common</artifactId>
+        <groupId>com.mes</groupId>
+        <version>1.0-SNAPSHOT</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>opcuaClient</artifactId>
+
+    <properties>
+        <maven.compiler.source>8</maven.compiler.source>
+        <maven.compiler.target>8</maven.compiler.target>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-autoconfigure</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-configuration-processor</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.eclipse.milo</groupId>
+            <artifactId>sdk-client</artifactId>
+            <version>0.6.8</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-pool2</artifactId>
+            <version>2.6.2</version>
+        </dependency>
+    </dependencies>
+
+</project>
\ No newline at end of file
diff --git a/hangzhoumesParent/common/opcuaClient/src/main/java/com/mes/milo/configuration/MiloAutoConfiguration.java b/hangzhoumesParent/common/opcuaClient/src/main/java/com/mes/milo/configuration/MiloAutoConfiguration.java
new file mode 100644
index 0000000..5e56b39
--- /dev/null
+++ b/hangzhoumesParent/common/opcuaClient/src/main/java/com/mes/milo/configuration/MiloAutoConfiguration.java
@@ -0,0 +1,117 @@
+package com.mes.milo.configuration;
+
+import com.mes.milo.pool.MiloConnectFactory;
+import com.mes.milo.pool.MiloConnectPool;
+import com.mes.milo.service.MiloService;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.pool2.impl.GenericKeyedObjectPoolConfig;
+import org.eclipse.milo.opcua.sdk.client.OpcUaClient;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.DependsOn;
+
+import javax.annotation.PreDestroy;
+
+/**
+ * @author mes
+ * @date 2020/4/25
+ * @desc milo-spring-boot-starter
+ * @since 0.0.1
+ */
+@Configuration
+@EnableConfigurationProperties(MiloProperties.class)
+@ConditionalOnClass({MiloService.class, MiloConnectPool.class})
+@ConditionalOnProperty(prefix = MiloProperties.PREFIX, value = "enabled", havingValue = "true", matchIfMissing = true)
+@Slf4j
+public class MiloAutoConfiguration {
+    private final MiloProperties properties;
+
+    private MiloConnectPool connectPool;
+
+    public MiloAutoConfiguration(MiloProperties properties) {
+        this.properties = properties;
+    }
+
+    @Bean(name = "miloConnectPool")
+    @ConditionalOnMissingBean({MiloConnectPool.class})
+    protected MiloConnectPool miloConnectPool() {
+        MiloConnectFactory objectFactory = new MiloConnectFactory(this.properties);
+        //璁剧疆瀵硅薄姹犵殑鐩稿叧鍙傛暟
+        GenericKeyedObjectPoolConfig<OpcUaClient> poolConfig = new GenericKeyedObjectPoolConfig<>();
+
+        MiloProperties.Pool pool = properties.getPool();
+        // 鏈�澶х┖闂叉暟
+        poolConfig.setMaxIdlePerKey(pool.getMaxIdle());
+        //鏈�灏忕┖闂�,璁剧疆涓�2琛ㄧず姹犲唴鑷冲皯瀛樻斁2涓┖闂插璞�(褰撴睜鍐呮湁2涓┖闂插璞℃椂璋冪敤borrowObject鍘诲璞℃椂浼氱珛鍗宠皟鐢ㄥ垱寤哄璞$殑鏂规硶淇濊瘉姹犲唴鏈�2涓┖闂插璞�)
+        poolConfig.setMinIdlePerKey(pool.getMinIdle());
+        //鏈�澶ф�绘暟 10
+        poolConfig.setMaxTotal(pool.getMaxTotal());
+        // 澶氫箙鎵ц涓�娆″璞℃壂鎻忥紝灏嗘棤鐢ㄧ殑瀵硅薄閿�姣侊紝榛樿-1涓嶆壂鎻�
+        // poolConfig.setTimeBetweenEvictionRuns(Duration.ofMinutes(1));
+        // 鍦ㄨ幏鍙栧璞$殑鏃跺�欐鏌ユ湁鏁堟��, 榛樿false
+        poolConfig.setTestOnBorrow(true);
+        // 鍦ㄥ綊杩樺璞$殑鏃跺�欐鏌ユ湁鏁堟��, 榛樿false
+        poolConfig.setTestOnReturn(false);
+        // 鍦ㄧ┖闂叉椂妫�鏌ユ湁鏁堟��, 榛樿false
+        poolConfig.setTestWhileIdle(false);
+        // 鏈�澶х瓑寰呮椂闂达紝 榛樿鐨勫�间负-1锛岃〃绀烘棤闄愮瓑寰呫��
+//        poolConfig.setMaxWait(Duration.ofSeconds(1));
+        // 鏄惁鍚敤鍚庤繘鍏堝嚭, 榛樿true
+        poolConfig.setLifo(true);
+        // 杩炴帴鑰楀敖鏃舵槸鍚﹂樆濉�, false绔嬪嵆鎶涘紓甯�,true闃诲鐩村埌瓒呮椂, 榛樿true
+        poolConfig.setBlockWhenExhausted(true);
+        // 姣忔閫愬嚭妫�鏌ユ椂 閫愬嚭鐨勬渶澶ф暟鐩� 榛樿3
+        poolConfig.setNumTestsPerEvictionRun(3);
+
+        //涓�瀹氳鍏抽棴jmx锛屼笉鐒秙pringboot鍚姩浼氭姤宸茬粡娉ㄥ唽浜嗘煇涓猨mx鐨勯敊璇�
+        poolConfig.setJmxEnabled(false);
+
+        //鏂板缓涓�涓璞℃睜,浼犲叆瀵硅薄宸ュ巶鍜岄厤缃�
+        connectPool = new MiloConnectPool(objectFactory, poolConfig);
+
+        initPool(pool.getInitialSize(), pool.getMaxIdle());
+        return connectPool;
+    }
+
+    @Bean
+    @ConditionalOnMissingBean(MiloService.class)
+    @DependsOn("miloConnectPool")
+    public MiloService miloService(MiloConnectPool miloConnectPool) {
+        return new MiloService(miloConnectPool, properties);
+    }
+
+    /**
+     * 棰勫厛鍔犺浇testObject瀵硅薄鍒板璞℃睜涓�
+     *
+     * @param initialSize 鍒濆鍖栬繛鎺ユ暟
+     * @param maxIdle     鏈�澶х┖闂茶繛鎺ユ暟
+     */
+    private void initPool(int initialSize, int maxIdle) {
+        if (initialSize <= 0) {
+            return;
+        }
+
+        properties.getConfig().forEach((key, config) -> {
+            for (int i = 0; i < Math.min(initialSize, maxIdle); i++) {
+                try {
+                    connectPool.addObject(config);
+                } catch (Exception e) {
+                    throw new RuntimeException(e);
+                }
+            }
+        });
+
+    }
+
+    @PreDestroy
+    public void destroy() {
+        if (connectPool != null) {
+            connectPool.close();
+            log.info("all opcUaClients are closed");
+        }
+    }
+}
diff --git a/hangzhoumesParent/common/opcuaClient/src/main/java/com/mes/milo/configuration/MiloProperties.java b/hangzhoumesParent/common/opcuaClient/src/main/java/com/mes/milo/configuration/MiloProperties.java
new file mode 100644
index 0000000..1dac285
--- /dev/null
+++ b/hangzhoumesParent/common/opcuaClient/src/main/java/com/mes/milo/configuration/MiloProperties.java
@@ -0,0 +1,85 @@
+package com.mes.milo.configuration;
+
+import lombok.Data;
+import org.eclipse.milo.opcua.stack.core.security.SecurityPolicy;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * @author mes
+ * @date 2020/4/25
+ * @desc milo-spring-boot-starter
+ * @since 0.0.1
+ */
+@Data
+@ConfigurationProperties(prefix = MiloProperties.PREFIX)
+public class MiloProperties {
+    public static final String PREFIX = "mes.milo";
+
+    /**
+     * 鏄惁鍚敤缁勪欢
+     */
+    private Boolean enabled = true;
+
+    /**
+     * server 榛樿璇锋眰閰嶇疆锛屼笉鎸囧畾锛屽垯榛樿鍙� config涓涓�涓�
+     */
+    private String primary;
+
+    /**
+     * server 鍒楄〃
+     */
+    private Map<String, Config> config = new LinkedHashMap<>();
+
+    /**
+     * 杩炴帴姹犻厤缃�
+     */
+    private Pool pool = new Pool();
+
+    @Data
+    public static class Config {
+
+        /**
+         * OPC UA鍦板潃
+         */
+        private String endpoint;
+
+        /**
+         * 瀹夊叏绛栫暐
+         */
+        private SecurityPolicy securityPolicy = SecurityPolicy.None;
+
+        /**
+         * 鐢ㄦ埛鍚�
+         */
+        private String username;
+
+        /**
+         * 瀵嗙爜
+         */
+        private String password;
+    }
+
+    @Data
+    public static class Pool {
+        /**
+         * 鏈�澶х┖闂�
+         */
+        private int maxIdle = 5;
+        /**
+         * 鏈�澶ф�绘暟
+         */
+        private int maxTotal = 20;
+        /**
+         * 鏈�灏忕┖闂�
+         */
+        private int minIdle = 2;
+
+        /**
+         * 鍒濆鍖栬繛鎺ユ暟
+         */
+        private int initialSize = 3;
+    }
+}
diff --git a/hangzhoumesParent/common/opcuaClient/src/main/java/com/mes/milo/exception/EndPointNotFoundException.java b/hangzhoumesParent/common/opcuaClient/src/main/java/com/mes/milo/exception/EndPointNotFoundException.java
new file mode 100644
index 0000000..0388c2f
--- /dev/null
+++ b/hangzhoumesParent/common/opcuaClient/src/main/java/com/mes/milo/exception/EndPointNotFoundException.java
@@ -0,0 +1,18 @@
+package com.mes.milo.exception;
+
+/**
+ * 绫� EndPointNotFoundException 鍔熻兘鎻忚堪锛�
+ *
+ * @author mes
+ * @version 0.0.1
+ * @date 2021/09/04 17:03
+ */
+public class EndPointNotFoundException extends RuntimeException {
+    public EndPointNotFoundException() {
+        super();
+    }
+
+    public EndPointNotFoundException(String message) {
+        super(message);
+    }
+}
diff --git a/hangzhoumesParent/common/opcuaClient/src/main/java/com/mes/milo/exception/IdentityNotFoundException.java b/hangzhoumesParent/common/opcuaClient/src/main/java/com/mes/milo/exception/IdentityNotFoundException.java
new file mode 100644
index 0000000..afd69f2
--- /dev/null
+++ b/hangzhoumesParent/common/opcuaClient/src/main/java/com/mes/milo/exception/IdentityNotFoundException.java
@@ -0,0 +1,14 @@
+package com.mes.milo.exception;
+
+/**
+ * 绫� IdentityNotFoundException 鍔熻兘鎻忚堪锛�
+ *
+ * @author mes
+ * @version 0.0.1
+ * @date 2021/09/15 09:35
+ */
+public class IdentityNotFoundException extends RuntimeException {
+    public IdentityNotFoundException(String message) {
+        super(message);
+    }
+}
diff --git a/hangzhoumesParent/common/opcuaClient/src/main/java/com/mes/milo/model/ReadWriteEntity.java b/hangzhoumesParent/common/opcuaClient/src/main/java/com/mes/milo/model/ReadWriteEntity.java
new file mode 100644
index 0000000..5276365
--- /dev/null
+++ b/hangzhoumesParent/common/opcuaClient/src/main/java/com/mes/milo/model/ReadWriteEntity.java
@@ -0,0 +1,26 @@
+package com.mes.milo.model;
+
+import lombok.*;
+import lombok.experimental.Accessors;
+import org.eclipse.milo.opcua.stack.core.types.builtin.DataValue;
+
+/**
+ * @author mes
+ * @version 0.0.1
+ * @desc
+ * @since 2020/4/13
+ */
+@Data
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+@Accessors(chain = true)
+@ToString
+public class ReadWriteEntity {
+
+    private String identifier;
+
+    private Object value;
+
+    private DataValue dataValue;
+}
diff --git a/hangzhoumesParent/common/opcuaClient/src/main/java/com/mes/milo/model/WriteEntity.java b/hangzhoumesParent/common/opcuaClient/src/main/java/com/mes/milo/model/WriteEntity.java
new file mode 100644
index 0000000..748781c
--- /dev/null
+++ b/hangzhoumesParent/common/opcuaClient/src/main/java/com/mes/milo/model/WriteEntity.java
@@ -0,0 +1,23 @@
+package com.mes.milo.model;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.experimental.Accessors;
+import org.eclipse.milo.opcua.stack.core.types.builtin.Variant;
+
+/**
+ * @author mes
+ * @version 0.0.1
+ * @since 2020/4/13
+ */
+@Data
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+@Accessors(chain = true)
+public class WriteEntity {
+    private String identifier;
+    private Variant variant;
+}
diff --git a/hangzhoumesParent/common/opcuaClient/src/main/java/com/mes/milo/pool/MiloConnectFactory.java b/hangzhoumesParent/common/opcuaClient/src/main/java/com/mes/milo/pool/MiloConnectFactory.java
new file mode 100644
index 0000000..f99202a
--- /dev/null
+++ b/hangzhoumesParent/common/opcuaClient/src/main/java/com/mes/milo/pool/MiloConnectFactory.java
@@ -0,0 +1,155 @@
+package com.mes.milo.pool;
+
+import com.mes.milo.configuration.MiloProperties;
+import com.mes.milo.exception.EndPointNotFoundException;
+import com.mes.milo.exception.IdentityNotFoundException;
+import com.mes.milo.utils.CustomUtil;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.pool2.KeyedPooledObjectFactory;
+import org.apache.commons.pool2.PooledObject;
+import org.apache.commons.pool2.impl.DefaultPooledObject;
+import org.eclipse.milo.opcua.sdk.client.OpcUaClient;
+import org.eclipse.milo.opcua.sdk.client.api.identity.AnonymousProvider;
+import org.eclipse.milo.opcua.sdk.client.api.identity.IdentityProvider;
+import org.eclipse.milo.opcua.sdk.client.api.identity.UsernameProvider;
+import org.eclipse.milo.opcua.stack.core.security.SecurityPolicy;
+import org.eclipse.milo.opcua.stack.core.types.builtin.LocalizedText;
+import org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.UInteger;
+import org.eclipse.milo.opcua.stack.core.types.structured.EndpointDescription;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Optional;
+
+/**
+ * 绫� MiloConnectFactory 鍔熻兘鎻忚堪锛�<br/>
+ *
+ * @author mes
+ * @version 0.0.1
+ * @date 2023/5/4 18:56
+ */
+@Slf4j
+public class MiloConnectFactory implements KeyedPooledObjectFactory<MiloProperties.Config, OpcUaClient> {
+
+    public MiloConnectFactory(MiloProperties properties) {
+        CustomUtil.verifyProperties(properties);
+    }
+
+    /**
+     * 鍒涘缓瀵硅薄
+     *
+     * @return
+     * @throws Exception
+     */
+    @Override
+    public PooledObject<OpcUaClient> makeObject(MiloProperties.Config key) throws Exception {
+        OpcUaClient client = null;
+        try {
+            client = createClient(key);
+            client.connect().get();
+            return new DefaultPooledObject<>(client);
+        } catch (Exception e) {
+            if (client != null) {
+                client.disconnect().get();
+            }
+            throw new InterruptedException(e.getMessage());
+        }
+    }
+
+    /**
+     * 瀵硅薄瑕佽閿�姣佹椂(validateObject鏂规硶杩斿洖false鎴栬�呰秴鏃�)鍚庤璋冪敤
+     *
+     * @param pooledObject
+     * @throws Exception
+     */
+    @Override
+    public void destroyObject(MiloProperties.Config key, PooledObject<OpcUaClient> pooledObject) throws Exception {
+        OpcUaClient opcUaClient = pooledObject.getObject();
+        log.info("disconnect opcUaClient {}", opcUaClient.getConfig().getApplicationName().getText());
+        opcUaClient.disconnect().get();
+    }
+
+    /**
+     * 姣忔鑾峰彇瀵硅薄鍜岃繕鍥炲璞℃椂浼氳璋冪敤锛屽鏋滆繑鍥瀎alse浼氶攢姣佸璞�
+     */
+    @Override
+    public boolean validateObject(MiloProperties.Config key, PooledObject<OpcUaClient> pooledObject) {
+        return true;
+    }
+
+    /**
+     * 璋冪敤鑾峰彇瀵硅薄鏂规硶鍓嶈璋冪敤
+     * 姝ゆ柟娉曚竴鑸繘琛屼竴浜涘墠缃搷浣�
+     */
+    @Override
+    public void activateObject(MiloProperties.Config key, PooledObject<OpcUaClient> pooledObject) throws Exception {
+
+    }
+
+    /**
+     * 褰撹繕鍥炲璞″苟涓攙alidateObject鏂规硶杩斿洖true鍚庤璋冪敤
+     * 涓�鑸湪姝ゆ柟娉曚腑瀵瑰垰鍒氫娇鐢ㄥ畬鎴愮殑瀵硅薄杩涜閲嶇疆
+     */
+    @Override
+    public void passivateObject(MiloProperties.Config key, PooledObject<OpcUaClient> pooledObject) throws Exception {
+
+    }
+
+    private OpcUaClient createClient(MiloProperties.Config key) throws Exception {
+        Path securityTempDir = Paths.get(System.getProperty("java.io.tmpdir"), "security");
+        Files.createDirectories(securityTempDir);
+        if (!Files.exists(securityTempDir)) {
+            throw new Exception("unable to create security dir: " + securityTempDir);
+        }
+        return OpcUaClient.create(key.getEndpoint(),
+                endpoints -> {
+                    final Optional<EndpointDescription> endpoint = endpoints
+                            .stream()
+//                            .filter(e -> e.getSecurityPolicyUri().equals(SecurityPolicy.None.getUri()))
+                            .findFirst();
+                    EndpointDescription newEndpoint = new EndpointDescription(key.getEndpoint(), endpoint.get().getServer(), endpoint.get().getServerCertificate(),
+                            endpoint.get().getSecurityMode(), endpoint.get().getSecurityPolicyUri(), endpoint.get().getUserIdentityTokens(),
+                            endpoint.get().getTransportProfileUri(), endpoint.get().getSecurityLevel());
+                    return Optional.of(newEndpoint);
+                },
+                configBuilder ->
+                        configBuilder
+                                .setApplicationName(LocalizedText.english("eclipse milo opc-ua client"))
+                                .setApplicationUri("urn:eclipse:milo:examples:client")
+                                //璁块棶鏂瑰紡
+                                .setIdentityProvider(new UsernameProvider(key.getUsername(), key.getPassword()))
+                                .setRequestTimeout(UInteger.valueOf(5000))
+                                .build()
+        );
+    }
+
+    private URI getUri(MiloProperties.Config key) {
+        try {
+            return new URI(endpointUrl(key));
+        } catch (URISyntaxException e) {
+            throw new EndPointNotFoundException("endpoint 閰嶇疆寮傚父");
+        }
+    }
+
+    private String endpointUrl(MiloProperties.Config key) {
+        return key.getEndpoint();
+    }
+
+    private SecurityPolicy securityPolicy(MiloProperties.Config key) {
+        return key.getSecurityPolicy();
+    }
+
+    private IdentityProvider identityProvider(MiloProperties.Config key) {
+        if (securityPolicy(key).equals(SecurityPolicy.None)) {
+            return new AnonymousProvider();
+        }
+        if (key.getUsername() == null || key.getPassword() == null) {
+            throw new IdentityNotFoundException("杩炴帴淇℃伅鏈畬鍠�");
+        } else {
+            return new UsernameProvider(key.getUsername(), key.getPassword());
+        }
+    }
+}
diff --git a/hangzhoumesParent/common/opcuaClient/src/main/java/com/mes/milo/pool/MiloConnectPool.java b/hangzhoumesParent/common/opcuaClient/src/main/java/com/mes/milo/pool/MiloConnectPool.java
new file mode 100644
index 0000000..aa13b70
--- /dev/null
+++ b/hangzhoumesParent/common/opcuaClient/src/main/java/com/mes/milo/pool/MiloConnectPool.java
@@ -0,0 +1,25 @@
+package com.mes.milo.pool;
+
+import com.mes.milo.configuration.MiloProperties;
+import org.apache.commons.pool2.KeyedPooledObjectFactory;
+import org.apache.commons.pool2.impl.GenericKeyedObjectPool;
+import org.apache.commons.pool2.impl.GenericKeyedObjectPoolConfig;
+import org.eclipse.milo.opcua.sdk.client.OpcUaClient;
+
+/**
+ * 绫� MiloConnectPool 鍔熻兘鎻忚堪锛�<br/>
+ *
+ * @author mes
+ * @version 0.0.1
+ * @date 2023/5/4 19:17
+ */
+public class MiloConnectPool extends GenericKeyedObjectPool<MiloProperties.Config, OpcUaClient> {
+
+    public MiloConnectPool(KeyedPooledObjectFactory<MiloProperties.Config, OpcUaClient> factory) {
+        super(factory);
+    }
+
+    public MiloConnectPool(KeyedPooledObjectFactory<MiloProperties.Config, OpcUaClient> factory, GenericKeyedObjectPoolConfig<OpcUaClient> config) {
+        super(factory, config);
+    }
+}
diff --git a/hangzhoumesParent/common/opcuaClient/src/main/java/com/mes/milo/runner/BrowseNodeRunner.java b/hangzhoumesParent/common/opcuaClient/src/main/java/com/mes/milo/runner/BrowseNodeRunner.java
new file mode 100644
index 0000000..6b3ba57
--- /dev/null
+++ b/hangzhoumesParent/common/opcuaClient/src/main/java/com/mes/milo/runner/BrowseNodeRunner.java
@@ -0,0 +1,59 @@
+package com.mes.milo.runner;
+
+import com.mes.milo.utils.CustomUtil;
+import lombok.extern.slf4j.Slf4j;
+import org.eclipse.milo.opcua.sdk.client.OpcUaClient;
+import org.eclipse.milo.opcua.sdk.client.nodes.UaNode;
+import org.eclipse.milo.opcua.stack.core.UaException;
+import org.eclipse.milo.opcua.stack.core.types.builtin.NodeId;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+/**
+ * @author mes
+ * @version 0.0.1
+ * @since 2020/4/14
+ */
+@Slf4j
+public class BrowseNodeRunner {
+    /**
+     * 瑕佽鐨勮妭鐐�
+     */
+    private final String browseRoot;
+
+    public BrowseNodeRunner(String browseRoot) {
+        this.browseRoot = browseRoot;
+    }
+
+    public List<String> run(OpcUaClient opcUaClient) {
+        NodeId nodeId = CustomUtil.parseNodeId(browseRoot);
+        return browseNode(browseRoot, opcUaClient, nodeId);
+    }
+
+    private List<String> browseNode(String prefix, OpcUaClient client, NodeId browseRoot) {
+        List<String> nodesList = new ArrayList<>();
+        try {
+            List<? extends UaNode> nodes = client.getAddressSpace().browseNodes(browseRoot);
+
+            nodes = nodes.stream().filter(item -> !Objects.requireNonNull(item.getBrowseName().getName()).startsWith("_")).collect(Collectors.toList());
+
+            for (UaNode node : nodes) {
+                String sub = prefix + "." + node.getBrowseName().getName();
+
+                // recursively browse to children
+                List<String> browseNode = browseNode(sub, client, node.getNodeId());
+                if (browseNode.isEmpty()) {
+                    nodesList.add(sub);
+                } else {
+                    nodesList.addAll(browseNode(sub, client, node.getNodeId()));
+                }
+            }
+        } catch (UaException e) {
+            log.error("Browsing nodeId={} failed: {}", browseRoot, e.getMessage(), e);
+        }
+        return nodesList;
+    }
+}
diff --git a/hangzhoumesParent/common/opcuaClient/src/main/java/com/mes/milo/runner/BrowseRunner.java b/hangzhoumesParent/common/opcuaClient/src/main/java/com/mes/milo/runner/BrowseRunner.java
new file mode 100644
index 0000000..2f93c84
--- /dev/null
+++ b/hangzhoumesParent/common/opcuaClient/src/main/java/com/mes/milo/runner/BrowseRunner.java
@@ -0,0 +1,36 @@
+package com.mes.milo.runner;
+
+import lombok.extern.slf4j.Slf4j;
+import org.eclipse.milo.opcua.sdk.client.OpcUaClient;
+import org.eclipse.milo.opcua.sdk.client.nodes.UaNode;
+import org.eclipse.milo.opcua.stack.core.Identifiers;
+import org.eclipse.milo.opcua.stack.core.UaException;
+import org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.UShort;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+/**
+ * @author mes
+ * @version 0.0.1
+ * @since 2020/4/14
+ */
+@Slf4j
+public class BrowseRunner {
+
+    public List<String> run(OpcUaClient opcUaClient) {
+        List<String> nodesList = new ArrayList<>();
+        try {
+            List<? extends UaNode> nodes = opcUaClient.getAddressSpace().browseNodes(Identifiers.ObjectsFolder);
+
+            nodesList.addAll(nodes.stream().filter(item -> !Objects.requireNonNull(item.getBrowseName().getName()).startsWith("_")
+                    && Objects.equals(item.getBrowseName().getNamespaceIndex(), UShort.valueOf(2)))
+                    .map(item -> item.getBrowseName().getName()).collect(Collectors.toList()));
+        } catch (UaException e) {
+            log.error("閬嶅巻鏍硅妭鐐瑰紓甯革細{}", e.getMessage(), e);
+        }
+        return nodesList;
+    }
+}
diff --git a/hangzhoumesParent/common/opcuaClient/src/main/java/com/mes/milo/runner/ReadValuesRunner.java b/hangzhoumesParent/common/opcuaClient/src/main/java/com/mes/milo/runner/ReadValuesRunner.java
new file mode 100644
index 0000000..74772d5
--- /dev/null
+++ b/hangzhoumesParent/common/opcuaClient/src/main/java/com/mes/milo/runner/ReadValuesRunner.java
@@ -0,0 +1,60 @@
+package com.mes.milo.runner;
+
+import com.mes.milo.model.ReadWriteEntity;
+import com.mes.milo.utils.CustomUtil;
+import lombok.extern.slf4j.Slf4j;
+import org.eclipse.milo.opcua.sdk.client.OpcUaClient;
+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.enumerated.TimestampsToReturn;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author mes
+ * @version 0.0.1
+ * @desc
+ * @since 2020/4/14
+ */
+@Slf4j
+public class ReadValuesRunner {
+    /**
+     * 瑕佽鐨勭偣浣峫ist
+     */
+    private final List<String> identifiers;
+
+    public ReadValuesRunner(List<String> identifiers) {
+        this.identifiers = identifiers;
+    }
+
+    public List<ReadWriteEntity> run(OpcUaClient opcUaClient) {
+        List<ReadWriteEntity> entityList = new ArrayList<>();
+        try {
+            List<NodeId> nodeIds = new ArrayList<>();
+            identifiers.forEach(identifier -> nodeIds.add(CustomUtil.parseNodeId(identifier)));
+            // 璇诲彇鎸囧畾鐐逛綅鐨勫�硷紝10s瓒呮椂
+            List<DataValue> dataValues = opcUaClient.readValues(10000, TimestampsToReturn.Both, nodeIds).get();
+            if (dataValues.size() == identifiers.size()) {
+                for (int i = 0; i < identifiers.size(); i++) {
+                    String id = identifiers.get(i);
+                    Object value = dataValues.get(i).getValue().getValue();
+                    StatusCode status = dataValues.get(i).getStatusCode();
+                    assert status != null;
+                    if (status.isGood()) {
+                        log.info("璇诲彇鐐逛綅 '{}' 鐨勫�间负 {}", id, value);
+                    }
+                    entityList.add(ReadWriteEntity.builder()
+                            .identifier(id)
+                            .value(value)
+                            .dataValue(dataValues.get(i))
+                            .build());
+                }
+            }
+        } catch (Exception e) {
+            log.error("璇诲�兼椂鍑虹幇浜嗗紓甯革細{}", e.getMessage(), e);
+        }
+        return entityList;
+    }
+}
diff --git a/hangzhoumesParent/common/opcuaClient/src/main/java/com/mes/milo/runner/WriteValuesRunner.java b/hangzhoumesParent/common/opcuaClient/src/main/java/com/mes/milo/runner/WriteValuesRunner.java
new file mode 100644
index 0000000..9ff7e71
--- /dev/null
+++ b/hangzhoumesParent/common/opcuaClient/src/main/java/com/mes/milo/runner/WriteValuesRunner.java
@@ -0,0 +1,50 @@
+package com.mes.milo.runner;
+
+import com.mes.milo.model.WriteEntity;
+import com.mes.milo.utils.CustomUtil;
+import lombok.extern.slf4j.Slf4j;
+import org.eclipse.milo.opcua.sdk.client.OpcUaClient;
+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 java.util.LinkedList;
+import java.util.List;
+
+/**
+ * @author mes
+ * @version 0.0.1
+ * @since 2020/4/14
+ */
+@Slf4j
+public class WriteValuesRunner {
+    private final List<WriteEntity> entities;
+
+    public WriteValuesRunner(List<WriteEntity> entities) {
+        this.entities = entities;
+    }
+
+    public void run(OpcUaClient opcUaClient) {
+        try {
+            if (!entities.isEmpty()) {
+                List<NodeId> nodeIds = new LinkedList<>();
+                List<DataValue> dataValues = new LinkedList<>();
+                for (WriteEntity entity : entities) {
+                    nodeIds.add(CustomUtil.parseNodeId(entity.getIdentifier()));
+                    dataValues.add(new DataValue(entity.getVariant(), null, null));
+                }
+
+                List<StatusCode> statusCodeList = opcUaClient.writeValues(nodeIds, dataValues).join();
+                for (int i = 0; i < statusCodeList.size(); i++) {
+                    if (statusCodeList.get(i).isGood()) {
+                        log.info("灏嗗�� '{}' 鍐欏叆鍒扮偣浣嶏細{} 鎴愬姛", dataValues.get(i).getValue(), nodeIds.get(i));
+                    } else {
+                        log.error("鐐逛綅锛歿} 鍐欏叆鏃跺嚭鐜颁簡寮傚父锛歿}", nodeIds.get(i), statusCodeList.get(i));
+                    }
+                }
+            }
+        } catch (Exception e) {
+            log.error("鎵归噺鍐欏�煎嚭鐜板紓甯稿嚭鐜颁簡寮傚父锛歿}", e.getMessage(), e);
+        }
+    }
+}
diff --git a/hangzhoumesParent/common/opcuaClient/src/main/java/com/mes/milo/runner/subscription/SubscriptionCallback.java b/hangzhoumesParent/common/opcuaClient/src/main/java/com/mes/milo/runner/subscription/SubscriptionCallback.java
new file mode 100644
index 0000000..a31cee0
--- /dev/null
+++ b/hangzhoumesParent/common/opcuaClient/src/main/java/com/mes/milo/runner/subscription/SubscriptionCallback.java
@@ -0,0 +1,16 @@
+package com.mes.milo.runner.subscription;
+
+import org.eclipse.milo.opcua.sdk.client.subscriptions.ManagedDataItem;
+import org.eclipse.milo.opcua.stack.core.types.builtin.DataValue;
+
+/**
+ * 绫� SubscriptionCallback 鍔熻兘鎻忚堪锛�<br/>
+ *
+ * @author mes
+ * @version 0.0.1
+ * @date 2023/5/8 22:14
+ */
+public interface SubscriptionCallback {
+
+    void onSubscribe(ManagedDataItem dataItem, DataValue value) throws Exception;
+}
diff --git a/hangzhoumesParent/common/opcuaClient/src/main/java/com/mes/milo/runner/subscription/SubscriptionRunner.java b/hangzhoumesParent/common/opcuaClient/src/main/java/com/mes/milo/runner/subscription/SubscriptionRunner.java
new file mode 100644
index 0000000..3cc0db3
--- /dev/null
+++ b/hangzhoumesParent/common/opcuaClient/src/main/java/com/mes/milo/runner/subscription/SubscriptionRunner.java
@@ -0,0 +1,111 @@
+package com.mes.milo.runner.subscription;
+
+import com.mes.milo.utils.CustomUtil;
+import lombok.extern.slf4j.Slf4j;
+import org.eclipse.milo.opcua.sdk.client.OpcUaClient;
+import org.eclipse.milo.opcua.sdk.client.api.subscriptions.UaSubscription;
+import org.eclipse.milo.opcua.sdk.client.api.subscriptions.UaSubscriptionManager;
+import org.eclipse.milo.opcua.sdk.client.subscriptions.ManagedDataItem;
+import org.eclipse.milo.opcua.sdk.client.subscriptions.ManagedSubscription;
+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.unsigned.UInteger;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+
+/**
+ * 绫� SubscriptionRunner 鍔熻兘鎻忚堪锛�
+ *
+ * @author mes
+ * @version 0.0.1
+ * @date 2022/01/01 23:49
+ */
+@Slf4j
+public class SubscriptionRunner {
+    /**
+     * 鐐逛綅list
+     */
+    private final List<String> identifiers;
+
+    private final double samplingInterval;
+
+    public SubscriptionRunner(List<String> identifiers) {
+        this.identifiers = identifiers;
+        this.samplingInterval = 1000.0D;
+    }
+
+    public SubscriptionRunner(List<String> identifiers, double samplingInterval) {
+        this.identifiers = identifiers;
+        this.samplingInterval = samplingInterval;
+    }
+
+    public void run(OpcUaClient opcUaClient, SubscriptionCallback callback) {
+
+        final CountDownLatch downLatch = new CountDownLatch(1);
+
+        //娣诲姞璁㈤槄鐩戝惉鍣紝鐢ㄤ簬澶勭悊鏂嚎閲嶈繛鍚庣殑璁㈤槄闂
+        opcUaClient.getSubscriptionManager().addSubscriptionListener(new CustomSubscriptionListener(opcUaClient, callback));
+
+        //澶勭悊璁㈤槄閫昏緫
+        handler(opcUaClient, callback);
+
+        try {
+            //鎸佺画鐩戝惉
+            downLatch.await();
+        } catch (Exception e) {
+            log.error("璁㈤槄鏃跺嚭鐜颁簡寮傚父锛歿}", e.getMessage(), e);
+        }
+    }
+
+    private void handler(OpcUaClient opcUaClient, SubscriptionCallback callback) {
+        try {
+            //鍒涘缓璁㈤槄
+            ManagedSubscription subscription = ManagedSubscription.create(opcUaClient, samplingInterval);
+            subscription.setDefaultSamplingInterval(samplingInterval);
+            subscription.setDefaultQueueSize(UInteger.valueOf(10));
+
+            List<NodeId> nodeIdList = new ArrayList<>();
+            for (String identifier : identifiers) {
+                nodeIdList.add(CustomUtil.parseNodeId(identifier));
+            }
+            List<ManagedDataItem> dataItemList = subscription.createDataItems(nodeIdList);
+            for (ManagedDataItem dataItem : dataItemList) {
+                dataItem.addDataValueListener((item) -> {
+                    try {
+                        callback.onSubscribe
+                                (dataItem, item);
+                    } catch (Exception e) {
+                        e.printStackTrace();
+                    }
+                });
+            }
+        } catch (Exception e) {
+            log.error("璁㈤槄鏃跺嚭鐜颁簡寮傚父锛歿}", e.getMessage(), e);
+        }
+    }
+
+    private class CustomSubscriptionListener implements UaSubscriptionManager.SubscriptionListener {
+        private final OpcUaClient client;
+        private final SubscriptionCallback callback;
+
+        public CustomSubscriptionListener(OpcUaClient client, SubscriptionCallback callback) {
+            this.client = client;
+            this.callback = callback;
+        }
+
+        /**
+         * 閲嶈繛鏃� 灏濊瘯鎭㈠涔嬪墠鐨勮闃呭け璐ユ椂 浼氳皟鐢ㄦ鏂规硶
+         *
+         * @param uaSubscription 璁㈤槄
+         * @param statusCode     鐘舵��
+         */
+        @Override
+        public void onSubscriptionTransferFailed(UaSubscription uaSubscription, StatusCode statusCode) {
+            log.debug("鎭㈠璁㈤槄澶辫触 闇�瑕侀噸鏂拌闃�");
+            //鍦ㄥ洖璋冩柟娉曚腑閲嶆柊璁㈤槄
+            handler(client, callback);
+        }
+    }
+}
diff --git a/hangzhoumesParent/common/opcuaClient/src/main/java/com/mes/milo/service/MiloService.java b/hangzhoumesParent/common/opcuaClient/src/main/java/com/mes/milo/service/MiloService.java
new file mode 100644
index 0000000..45c5658
--- /dev/null
+++ b/hangzhoumesParent/common/opcuaClient/src/main/java/com/mes/milo/service/MiloService.java
@@ -0,0 +1,553 @@
+package com.mes.milo.service;
+
+import com.mes.milo.configuration.MiloProperties;
+import com.mes.milo.model.ReadWriteEntity;
+import com.mes.milo.model.WriteEntity;
+import com.mes.milo.pool.MiloConnectPool;
+import com.mes.milo.runner.BrowseNodeRunner;
+import com.mes.milo.runner.BrowseRunner;
+import com.mes.milo.runner.ReadValuesRunner;
+import com.mes.milo.runner.WriteValuesRunner;
+import com.mes.milo.runner.subscription.SubscriptionCallback;
+import com.mes.milo.runner.subscription.SubscriptionRunner;
+import com.mes.milo.utils.CustomUtil;
+import lombok.extern.slf4j.Slf4j;
+import org.eclipse.milo.opcua.sdk.client.OpcUaClient;
+import org.eclipse.milo.opcua.stack.core.types.builtin.Variant;
+import org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.Unsigned;
+import org.springframework.stereotype.Service;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * @author mes
+ * @date 2020/4/25
+ * @desc milo-spring-boot-starter
+ * @since 0.0.1
+ */
+@Service
+@Slf4j
+public class MiloService {
+    private final MiloConnectPool connectPool;
+    private final MiloProperties properties;
+
+    public MiloService(MiloConnectPool connectPool, MiloProperties properties) {
+        this.connectPool = connectPool;
+        this.properties = properties;
+    }
+
+    /**
+     * 閬嶅巻OPC UA鏈嶅姟鍣ㄦ牴鑺傜偣
+     *
+     * @return 鏍硅妭鐐瑰垪琛�
+     */
+    public List<String> browseRoot() throws Exception {
+        return browseRoot(null);
+    }
+
+    /**
+     * 閬嶅巻OPC UA鏈嶅姟鍣ㄦ牴鑺傜偣
+     *
+     * @param clientName 閰嶇疆key
+     * @return 鏍硅妭鐐瑰垪琛�
+     */
+    public List<String> browseRoot(String clientName) throws Exception {
+        MiloProperties.Config config = CustomUtil.getConfig(properties, clientName);
+        BrowseRunner runner = new BrowseRunner();
+        OpcUaClient client = connectPool.borrowObject(config);
+        if (client != null) {
+            try {
+                return runner.run(client);
+            } finally {
+                connectPool.returnObject(config, client);
+            }
+        }
+        return Collections.emptyList();
+    }
+
+    /**
+     * 閬嶅巻OPC UA鏈嶅姟鍣ㄦ寚瀹氳妭鐐�
+     *
+     * @param browseRoot 鑺傜偣鍚嶇О
+     * @return 鎸囧畾鑺傜偣 tag鍒楄〃
+     */
+    public List<String> browseNode(String browseRoot) throws Exception {
+        return browseNode(browseRoot, null);
+    }
+
+    /**
+     * 閬嶅巻OPC UA鏈嶅姟鍣ㄦ寚瀹氳妭鐐�
+     *
+     * @param browseRoot 鑺傜偣鍚嶇О
+     * @param clientName 閰嶇疆key
+     * @return 鎸囧畾鑺傜偣 tag鍒楄〃
+     */
+    public List<String> browseNode(String browseRoot, String clientName) throws Exception {
+        MiloProperties.Config config = CustomUtil.getConfig(properties, clientName);
+        BrowseNodeRunner runner = new BrowseNodeRunner(browseRoot);
+        OpcUaClient client = connectPool.borrowObject(config);
+        if (client != null) {
+            try {
+                return runner.run(client);
+            } finally {
+                connectPool.returnObject(config, client);
+            }
+        }
+        return Collections.emptyList();
+    }
+
+    /**
+     * 鎸囧畾绫诲瀷 鍐欏叆kep鐐逛綅鍊�
+     *
+     * @param entity 寰呭啓鍏ユ暟鎹�
+     */
+    public void writeSpecifyType(WriteEntity entity) throws Exception {
+        writeSpecifyType(Collections.singletonList(entity));
+    }
+
+    /**
+     * 鎸囧畾绫诲瀷 鍐欏叆kep鐐逛綅鍊�
+     *
+     * @param entity     寰呭啓鍏ユ暟鎹�
+     * @param clientName 閰嶇疆key
+     */
+    public void writeSpecifyType(WriteEntity entity, String clientName) throws Exception {
+        writeSpecifyType(Collections.singletonList(entity), clientName);
+    }
+
+    /**
+     * 鎸囧畾绫诲瀷 鍐欏叆kep鐐逛綅鍊硷紝鍙壒閲忓啓鍏ヤ笉鍚岀被鍨嬬殑鍊�
+     *
+     * @param entities 寰呭啓鍏ユ暟鎹�
+     */
+    public void writeSpecifyType(List<WriteEntity> entities) throws Exception {
+        writeSpecifyType(entities, null);
+    }
+
+    /**
+     * 鎸囧畾绫诲瀷 鍐欏叆kep鐐逛綅鍊硷紝鍙壒閲忓啓鍏ヤ笉鍚岀被鍨嬬殑鍊�
+     *
+     * @param entities   寰呭啓鍏ユ暟鎹�
+     * @param clientName 閰嶇疆key
+     */
+    public void writeSpecifyType(List<WriteEntity> entities, String clientName) throws Exception {
+        MiloProperties.Config config = CustomUtil.getConfig(properties, clientName);
+        WriteValuesRunner runner = new WriteValuesRunner(entities);
+        OpcUaClient client = connectPool.borrowObject(config);
+        if (client != null) {
+            try {
+                runner.run(client);
+            } finally {
+                connectPool.returnObject(config, client);
+            }
+        }
+    }
+
+    /**
+     * 鍐欏叆kep鐐逛綅鍊�
+     *
+     * @param entity 寰呭啓鍏ユ暟鎹�
+     */
+    public void writeToOpcUa(ReadWriteEntity entity) throws Exception {
+        writeToOpcUa(Collections.singletonList(entity));
+    }
+
+    /**
+     * 鍐欏叆kep鐐逛綅鍊�
+     *
+     * @param entity     寰呭啓鍏ユ暟鎹�
+     * @param clientName 閰嶇疆key
+     */
+    public void writeToOpcUa(ReadWriteEntity entity, String clientName) throws Exception {
+        writeToOpcUa(Collections.singletonList(entity), clientName);
+    }
+
+    /**
+     * 鍐欏叆kep鐐逛綅鍊�
+     *
+     * @param entities 寰呭啓鍏ユ暟鎹�
+     */
+    public void writeToOpcUa(List<ReadWriteEntity> entities) throws Exception {
+        writeToOpcUa(entities, null);
+    }
+
+    /**
+     * 鍐欏叆kep鐐逛綅鍊�
+     *
+     * @param entities   寰呭啓鍏ユ暟鎹�
+     * @param clientName 閰嶇疆key
+     */
+    public void writeToOpcUa(List<ReadWriteEntity> entities, String clientName) throws Exception {
+        MiloProperties.Config config = CustomUtil.getConfig(properties, clientName);
+        List<WriteEntity> writeEntityList = new ArrayList<>();
+        if (!entities.isEmpty()) {
+            for (ReadWriteEntity entity : entities) {
+                writeEntityList.add(WriteEntity.builder()
+                        .identifier(entity.getIdentifier())
+                        .variant(new Variant(entity.getValue()))
+                        .build());
+            }
+        }
+        WriteValuesRunner runner = new WriteValuesRunner(writeEntityList);
+        OpcUaClient client = connectPool.borrowObject(config);
+        if (client != null) {
+            try {
+                runner.run(client);
+            } finally {
+                connectPool.returnObject(config, client);
+            }
+        }
+    }
+
+    /**
+     * kepware 鏁版嵁绫诲瀷涓猴細Char<br/>
+     * 8浣嶅甫绗﹀彿鏁存暟
+     *
+     * @param entity 寰呭啓鍏ユ暟鎹�
+     */
+    public void writeToOpcChar(ReadWriteEntity entity) throws Exception {
+        writeToOpcChar(Collections.singletonList(entity));
+    }
+
+    /**
+     * kepware 鏁版嵁绫诲瀷涓猴細Char<br/>
+     * 8浣嶅甫绗﹀彿鏁存暟
+     *
+     * @param entity     寰呭啓鍏ユ暟鎹�
+     * @param clientName 閰嶇疆key
+     */
+    public void writeToOpcChar(ReadWriteEntity entity, String clientName) throws Exception {
+        writeToOpcChar(Collections.singletonList(entity), clientName);
+    }
+
+    /**
+     * kepware 鏁版嵁绫诲瀷涓猴細Char<br/>
+     * 8浣嶅甫绗﹀彿鏁存暟
+     *
+     * @param entities 寰呭啓鍏ユ暟鎹�
+     */
+    public void writeToOpcChar(List<ReadWriteEntity> entities) throws Exception {
+        writeToOpcChar(entities, null);
+    }
+
+    /**
+     * kepware 鏁版嵁绫诲瀷涓猴細Char<br/>
+     * 8浣嶅甫绗﹀彿鏁存暟
+     *
+     * @param entities   寰呭啓鍏ユ暟鎹�
+     * @param clientName 閰嶇疆key
+     */
+    public void writeToOpcChar(List<ReadWriteEntity> entities, String clientName) throws Exception {
+        MiloProperties.Config config = CustomUtil.getConfig(properties, clientName);
+        List<WriteEntity> writeEntityList = new ArrayList<>();
+        if (!entities.isEmpty()) {
+            for (ReadWriteEntity entity : entities) {
+                writeEntityList.add(WriteEntity.builder()
+                        .identifier(entity.getIdentifier())
+                        .variant(new Variant(((Integer) entity.getValue()).byteValue()))
+                        .build());
+            }
+        }
+        WriteValuesRunner runner = new WriteValuesRunner(writeEntityList);
+        OpcUaClient client = connectPool.borrowObject(config);
+        if (client != null) {
+            try {
+                runner.run(client);
+            } finally {
+                connectPool.returnObject(config, client);
+            }
+        }
+    }
+
+    /**
+     * kepware 鏁版嵁绫诲瀷涓猴細Byte<br/>
+     * 8浣嶆棤绗﹀彿鏁存暟
+     *
+     * @param entity 寰呭啓鍏ユ暟鎹�
+     */
+    public void writeToOpcByte(ReadWriteEntity entity) throws Exception {
+        writeToOpcByte(Collections.singletonList(entity));
+    }
+
+    /**
+     * kepware 鏁版嵁绫诲瀷涓猴細Byte<br/>
+     * 8浣嶆棤绗﹀彿鏁存暟
+     *
+     * @param entity     寰呭啓鍏ユ暟鎹�
+     * @param clientName 閰嶇疆key
+     */
+    public void writeToOpcByte(ReadWriteEntity entity, String clientName) throws Exception {
+        writeToOpcByte(Collections.singletonList(entity), clientName);
+    }
+
+    /**
+     * kepware 鏁版嵁绫诲瀷涓猴細Byte<br/>
+     * 8浣嶆棤绗﹀彿鏁存暟
+     *
+     * @param entities 寰呭啓鍏ユ暟鎹�
+     */
+    public void writeToOpcByte(List<ReadWriteEntity> entities) throws Exception {
+        writeToOpcByte(entities, null);
+    }
+
+    /**
+     * kepware 鏁版嵁绫诲瀷涓猴細Byte<br/>
+     * 8浣嶆棤绗﹀彿鏁存暟
+     *
+     * @param entities   寰呭啓鍏ユ暟鎹�
+     * @param clientName 閰嶇疆key
+     */
+    public void writeToOpcByte(List<ReadWriteEntity> entities, String clientName) throws Exception {
+        MiloProperties.Config config = CustomUtil.getConfig(properties, clientName);
+        List<WriteEntity> writeEntityList = new ArrayList<>();
+        if (!entities.isEmpty()) {
+            for (ReadWriteEntity entity : entities) {
+                writeEntityList.add(WriteEntity.builder()
+                        .identifier(entity.getIdentifier())
+                        .variant(new Variant(Unsigned.ubyte((Integer) entity.getValue())))
+                        .build());
+            }
+        }
+        WriteValuesRunner runner = new WriteValuesRunner(writeEntityList);
+        OpcUaClient client = connectPool.borrowObject(config);
+        if (client != null) {
+            try {
+                runner.run(client);
+            } finally {
+                connectPool.returnObject(config, client);
+            }
+        }
+    }
+
+    /**
+     * kepware 鏁版嵁绫诲瀷涓猴細Short<br/>
+     * 16浣嶅甫绗﹀彿鏁存暟
+     *
+     * @param entity 寰呭啓鍏ユ暟鎹�
+     */
+    public void writeToOpcShort(ReadWriteEntity entity) throws Exception {
+        writeToOpcShort(Collections.singletonList(entity));
+    }
+
+    /**
+     * kepware 鏁版嵁绫诲瀷涓猴細Short<br/>
+     * 16浣嶅甫绗﹀彿鏁存暟
+     *
+     * @param entity     寰呭啓鍏ユ暟鎹�
+     * @param clientName 閰嶇疆key
+     */
+    public void writeToOpcShort(ReadWriteEntity entity, String clientName) throws Exception {
+        writeToOpcShort(Collections.singletonList(entity), clientName);
+    }
+
+    /**
+     * kepware 鏁版嵁绫诲瀷涓猴細Short<br/>
+     * 16浣嶅甫绗﹀彿鏁存暟
+     *
+     * @param entities 寰呭啓鍏ユ暟鎹�
+     */
+    public void writeToOpcShort(List<ReadWriteEntity> entities) throws Exception {
+        writeToOpcShort(entities, null);
+    }
+
+    /**
+     * kepware 鏁版嵁绫诲瀷涓猴細Short<br/>
+     * 16浣嶅甫绗﹀彿鏁存暟
+     *
+     * @param entities   寰呭啓鍏ユ暟鎹�
+     * @param clientName 閰嶇疆key
+     */
+    public void writeToOpcShort(List<ReadWriteEntity> entities, String clientName) throws Exception {
+        MiloProperties.Config config = CustomUtil.getConfig(properties, clientName);
+        List<WriteEntity> writeEntityList = new ArrayList<>();
+        if (!entities.isEmpty()) {
+            for (ReadWriteEntity entity : entities) {
+                writeEntityList.add(WriteEntity.builder()
+                        .identifier(entity.getIdentifier())
+                        .variant(new Variant(((Integer) entity.getValue()).shortValue()))
+                        .build());
+            }
+        }
+        WriteValuesRunner runner = new WriteValuesRunner(writeEntityList);
+        OpcUaClient client = connectPool.borrowObject(config);
+        if (client != null) {
+            try {
+                runner.run(client);
+            } finally {
+                connectPool.returnObject(config, client);
+            }
+        }
+    }
+
+    /**
+     * kepware 鏁版嵁绫诲瀷涓猴細Word<br/>
+     * 16浣嶆棤绗﹀彿鏁存暟
+     *
+     * @param entity 寰呭啓鍏ユ暟鎹�
+     */
+    public void writeToOpcWord(ReadWriteEntity entity) throws Exception {
+        writeToOpcWord(Collections.singletonList(entity));
+    }
+
+    /**
+     * kepware 鏁版嵁绫诲瀷涓猴細Word<br/>
+     * 16浣嶆棤绗﹀彿鏁存暟
+     *
+     * @param entity     寰呭啓鍏ユ暟鎹�
+     * @param clientName 閰嶇疆key
+     */
+    public void writeToOpcWord(ReadWriteEntity entity, String clientName) throws Exception {
+        writeToOpcWord(Collections.singletonList(entity), clientName);
+    }
+
+    /**
+     * kepware 鏁版嵁绫诲瀷涓猴細Word<br/>
+     * 16浣嶆棤绗﹀彿鏁存暟
+     *
+     * @param entities 寰呭啓鍏ユ暟鎹�
+     */
+    public void writeToOpcWord(List<ReadWriteEntity> entities) throws Exception {
+        writeToOpcWord(entities, null);
+    }
+
+    /**
+     * kepware 鏁版嵁绫诲瀷涓猴細Word<br/>
+     * 16浣嶆棤绗﹀彿鏁存暟
+     *
+     * @param entities   寰呭啓鍏ユ暟鎹�
+     * @param clientName 閰嶇疆key
+     */
+    public void writeToOpcWord(List<ReadWriteEntity> entities, String clientName) throws Exception {
+        MiloProperties.Config config = CustomUtil.getConfig(properties, clientName);
+        List<WriteEntity> writeEntityList = new ArrayList<>();
+        if (!entities.isEmpty()) {
+            for (ReadWriteEntity entity : entities) {
+                writeEntityList.add(WriteEntity.builder()
+                        .identifier(entity.getIdentifier())
+                        .variant(new Variant(Unsigned.ushort((Integer) entity.getValue())))
+                        .build());
+            }
+        }
+        WriteValuesRunner runner = new WriteValuesRunner(writeEntityList);
+        OpcUaClient client = connectPool.borrowObject(config);
+        if (client != null) {
+            try {
+                runner.run(client);
+            } finally {
+                connectPool.returnObject(config, client);
+            }
+        }
+    }
+
+    /**
+     * 璇诲彇kep鐐逛綅鍊�
+     *
+     * @param id 鐐逛綅id
+     * @return
+     */
+    public ReadWriteEntity readFromOpcUa(String id) throws Exception {
+        return readFromOpcUa(id, null);
+    }
+
+    /**
+     * 璇诲彇kep鐐逛綅鍊�
+     *
+     * @param id         鐐逛綅id
+     * @param clientName 閰嶇疆key
+     * @return
+     */
+    public ReadWriteEntity readFromOpcUa(String id, String clientName) throws Exception {
+        List<ReadWriteEntity> entityList = readFromOpcUa(Collections.singletonList(id), clientName);
+        if (!entityList.isEmpty()) {
+            return entityList.get(0);
+        }
+        return null;
+    }
+
+    /**
+     * 璇诲彇kep鐐逛綅鍊�
+     *
+     * @param ids 鐐逛綅id鏁扮粍
+     * @return
+     */
+    public List<ReadWriteEntity> readFromOpcUa(List<String> ids) throws Exception {
+        return readFromOpcUa(ids, null);
+    }
+
+    /**
+     * 璇诲彇kep鐐逛綅鍊�
+     *
+     * @param ids        鐐逛綅id鏁扮粍
+     * @param clientName 閰嶇疆key
+     * @return
+     */
+    public List<ReadWriteEntity> readFromOpcUa(List<String> ids, String clientName) throws Exception {
+        MiloProperties.Config config = CustomUtil.getConfig(properties, clientName);
+        ReadValuesRunner runner = new ReadValuesRunner(ids);
+        OpcUaClient client = connectPool.borrowObject(config);
+        if (client != null) {
+            try {
+                return runner.run(client);
+            } finally {
+                connectPool.returnObject(config, client);
+            }
+        }
+        return new ArrayList<>();
+    }
+
+    /**
+     * 璁㈤槄kep鐐逛綅鍊�
+     *
+     * @param ids 鐐逛綅id鏁扮粍
+     * @return
+     */
+    public void subscriptionFromOpcUa(List<String> ids, SubscriptionCallback callback) throws Exception {
+        subscriptionFromOpcUa(ids, 1000.0, callback);
+    }
+
+    /**
+     * 璁㈤槄kep鐐逛綅鍊�
+     *
+     * @param ids        鐐逛綅id鏁扮粍
+     * @param clientName 閰嶇疆key
+     * @return
+     */
+    public void subscriptionFromOpcUa(List<String> ids, String clientName, SubscriptionCallback callback) throws Exception {
+        subscriptionFromOpcUa(ids, 1000.0, clientName, callback);
+    }
+
+    /**
+     * 璁㈤槄kep鐐逛綅鍊�
+     *
+     * @param ids              鐐逛綅id鏁扮粍
+     * @param samplingInterval 璁㈤槄鏃堕棿闂撮殧 榛樿1000 ms
+     * @return
+     */
+    public void subscriptionFromOpcUa(List<String> ids, double samplingInterval, SubscriptionCallback callback) throws Exception {
+        subscriptionFromOpcUa(ids, samplingInterval, null, callback);
+    }
+
+    /**
+     * 璁㈤槄kep鐐逛綅鍊�
+     *
+     * @param ids              鐐逛綅id鏁扮粍
+     * @param samplingInterval 璁㈤槄鏃堕棿闂撮殧 榛樿1000 ms
+     * @param clientName       閰嶇疆key
+     * @return
+     */
+    public void subscriptionFromOpcUa(List<String> ids, double samplingInterval, String clientName, SubscriptionCallback callback) throws Exception {
+        MiloProperties.Config config = CustomUtil.getConfig(properties, clientName);
+        SubscriptionRunner runner = new SubscriptionRunner(ids, samplingInterval);
+        OpcUaClient client = connectPool.borrowObject(config);
+        if (client != null) {
+            try {
+                runner.run(client, callback);
+            } finally {
+                connectPool.returnObject(config, client);
+            }
+        }
+    }
+
+}
diff --git a/hangzhoumesParent/common/opcuaClient/src/main/java/com/mes/milo/utils/CustomUtil.java b/hangzhoumesParent/common/opcuaClient/src/main/java/com/mes/milo/utils/CustomUtil.java
new file mode 100644
index 0000000..37df820
--- /dev/null
+++ b/hangzhoumesParent/common/opcuaClient/src/main/java/com/mes/milo/utils/CustomUtil.java
@@ -0,0 +1,114 @@
+package com.mes.milo.utils;
+
+import com.google.common.collect.Sets;
+import com.mes.milo.configuration.MiloProperties;
+import com.mes.milo.exception.EndPointNotFoundException;
+import com.mes.milo.exception.IdentityNotFoundException;
+import lombok.extern.slf4j.Slf4j;
+import org.eclipse.milo.opcua.stack.core.types.builtin.NodeId;
+import org.springframework.util.StringUtils;
+
+import java.net.*;
+import java.util.*;
+
+/**
+ * @author mes
+ * @version 0.0.1
+ * @desc
+ * @since 2020/4/13
+ */
+@Slf4j
+public class CustomUtil {
+
+    private static final String OPC_UA_NOT_CONFIG = "璇烽厤缃甇PC UA鍦板潃淇℃伅";
+
+    private CustomUtil() {
+    }
+
+    public static String getHostname() {
+        try {
+            return InetAddress.getLocalHost().getHostName();
+        } catch (UnknownHostException var1) {
+            return "localhost";
+        }
+    }
+
+    public static Set<String> getHostnames(String address) {
+        return getHostnames(address, true);
+    }
+
+    public static Set<String> getHostnames(String address, boolean includeLoopback) {
+        HashSet<String> hostnames = Sets.newHashSet();
+
+        try {
+            InetAddress inetAddress = InetAddress.getByName(address);
+            if (inetAddress.isAnyLocalAddress()) {
+                try {
+                    Enumeration<NetworkInterface> nis = NetworkInterface.getNetworkInterfaces();
+
+                    for (NetworkInterface ni : Collections.list(nis)) {
+                        Collections.list(ni.getInetAddresses()).forEach((ia) -> {
+                            if (ia instanceof Inet4Address) {
+                                boolean loopback = ia.isLoopbackAddress();
+                                if (!loopback || includeLoopback) {
+                                    hostnames.add(ia.getHostName());
+                                    hostnames.add(ia.getHostAddress());
+                                    hostnames.add(ia.getCanonicalHostName());
+                                }
+                            }
+
+                        });
+                    }
+                } catch (SocketException var7) {
+                    log.warn("Failed to NetworkInterfaces for bind address: {}", address, var7);
+                }
+            } else {
+                boolean loopback = inetAddress.isLoopbackAddress();
+                if (!loopback || includeLoopback) {
+                    hostnames.add(inetAddress.getHostName());
+                    hostnames.add(inetAddress.getHostAddress());
+                    hostnames.add(inetAddress.getCanonicalHostName());
+                }
+            }
+        } catch (UnknownHostException var8) {
+            log.warn("Failed to get InetAddress for bind address: {}", address, var8);
+        }
+
+        return hostnames;
+    }
+
+    public static NodeId parseNodeId(String identifier) {
+        NodeId nodeId = new NodeId(2, identifier);
+        if (identifier.startsWith("ns=") && identifier.contains(";")) {
+            nodeId = NodeId.parseOrNull(identifier);
+        }
+        if (nodeId == null) {
+            throw new IdentityNotFoundException("NodeId 瑙f瀽澶辫触锛岃妫�鏌�");
+        }
+        return nodeId;
+    }
+
+    public static void verifyProperties(MiloProperties properties) {
+        if (properties.getConfig().isEmpty()) {
+            throw new EndPointNotFoundException(OPC_UA_NOT_CONFIG);
+        }
+        if (!StringUtils.hasText(properties.getPrimary())) {
+            Set<String> keySet = properties.getConfig().keySet();
+            properties.setPrimary(keySet.stream().findFirst().orElseThrow(() -> new EndPointNotFoundException(OPC_UA_NOT_CONFIG)));
+        }
+        properties.getConfig().forEach((key, config) -> {
+            if (!StringUtils.hasText(config.getEndpoint())) {
+                throw new EndPointNotFoundException(OPC_UA_NOT_CONFIG + ": " + key);
+            }
+        });
+    }
+
+    public static MiloProperties.Config getConfig(MiloProperties properties) {
+        return getConfig(properties, null);
+    }
+
+    public static MiloProperties.Config getConfig(MiloProperties properties, String clientName) {
+        Map<String, MiloProperties.Config> config = properties.getConfig();
+        return StringUtils.hasText(clientName) ? config.get(clientName) : config.get(properties.getPrimary());
+    }
+}
diff --git a/hangzhoumesParent/common/opcuaClient/src/main/java/com/mes/milo/utils/KeyStoreLoader.java b/hangzhoumesParent/common/opcuaClient/src/main/java/com/mes/milo/utils/KeyStoreLoader.java
new file mode 100644
index 0000000..ad4c6bb
--- /dev/null
+++ b/hangzhoumesParent/common/opcuaClient/src/main/java/com/mes/milo/utils/KeyStoreLoader.java
@@ -0,0 +1,123 @@
+package com.mes.milo.utils;
+
+import lombok.extern.slf4j.Slf4j;
+import org.eclipse.milo.opcua.stack.client.security.DefaultClientCertificateValidator;
+import org.eclipse.milo.opcua.stack.core.security.DefaultTrustListManager;
+import org.eclipse.milo.opcua.stack.core.util.SelfSignedCertificateBuilder;
+import org.eclipse.milo.opcua.stack.core.util.SelfSignedCertificateGenerator;
+
+import java.io.File;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.security.*;
+import java.security.cert.X509Certificate;
+import java.util.Arrays;
+import java.util.regex.Pattern;
+
+@Slf4j
+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 = "client-ai";
+    private static final char[] PASSWORD = "password".toCharArray();
+
+    private X509Certificate clientCertificate;
+    private X509Certificate[] clientCertificateChain;
+    private KeyPair clientKeyPair;
+    private DefaultClientCertificateValidator certificateValidator;
+
+    public KeyStoreLoader load() throws Exception {
+        Path securityTempDir = Paths.get(System.getProperty("java.io.tmpdir"), "security");
+        Files.createDirectories(securityTempDir);
+        if (!Files.exists(securityTempDir)) {
+            throw new Exception("unable to create security dir: " + securityTempDir);
+        }
+
+        File pkiDir = securityTempDir.resolve("pki").toFile();
+
+        log.info("security temp dir: {}", securityTempDir.toAbsolutePath());
+
+        KeyStore keyStore = KeyStore.getInstance("PKCS12");
+
+        Path serverKeyStore = securityTempDir.resolve("milo-client.pfx");
+
+        DefaultTrustListManager trustListManager = new DefaultTrustListManager(pkiDir);
+
+        certificateValidator = new DefaultClientCertificateValidator(trustListManager);
+
+        log.info("Loading KeyStore at {}", serverKeyStore);
+
+        if (!Files.exists(serverKeyStore)) {
+            keyStore.load(null, PASSWORD);
+
+            KeyPair keyPair = SelfSignedCertificateGenerator.generateRsaKeyPair(2048);
+
+            SelfSignedCertificateBuilder builder = new SelfSignedCertificateBuilder(keyPair)
+                    .setCommonName("Milo Client")
+                    .setOrganization("kangaroohy")
+                    .setOrganizationalUnit("dev")
+                    .setLocalityName("Folsom")
+                    .setStateName("CA")
+                    .setCountryCode("US")
+                    .setApplicationUri("urn:kangaroohy:milo:client")
+                    .addDnsName("localhost")
+                    .addIpAddress("127.0.0.1");
+
+            // Get as many hostnames and IP addresses as we can listed in the certificate.
+            for (String hostname : CustomUtil.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 clientPrivateKey = keyStore.getKey(CLIENT_ALIAS, PASSWORD);
+        if (clientPrivateKey instanceof PrivateKey) {
+            clientCertificate = (X509Certificate) keyStore.getCertificate(CLIENT_ALIAS);
+
+            clientCertificateChain = Arrays.stream(keyStore.getCertificateChain(CLIENT_ALIAS))
+                    .map(X509Certificate.class::cast)
+                    .toArray(X509Certificate[]::new);
+
+            PublicKey clientPublicKey = clientCertificate.getPublicKey();
+            clientKeyPair = new KeyPair(clientPublicKey, (PrivateKey) clientPrivateKey);
+        }
+
+        return this;
+    }
+
+    public X509Certificate getClientCertificate() {
+        return clientCertificate;
+    }
+
+    public X509Certificate[] getClientCertificateChain() {
+        return clientCertificateChain;
+    }
+
+    public DefaultClientCertificateValidator getCertificateValidator() {
+        return certificateValidator;
+    }
+
+    public KeyPair getClientKeyPair() {
+        return clientKeyPair;
+    }
+
+}
diff --git a/hangzhoumesParent/common/opcuaClient/src/main/resources/META-INF/spring.factories b/hangzhoumesParent/common/opcuaClient/src/main/resources/META-INF/spring.factories
new file mode 100644
index 0000000..dfcfa97
--- /dev/null
+++ b/hangzhoumesParent/common/opcuaClient/src/main/resources/META-INF/spring.factories
@@ -0,0 +1,3 @@
+# Auto Configure
+org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
+  com.mes.milo.configuration.MiloAutoConfiguration
diff --git a/hangzhoumesParent/common/opcuaClient/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/hangzhoumesParent/common/opcuaClient/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
new file mode 100644
index 0000000..eda2fe7
--- /dev/null
+++ b/hangzhoumesParent/common/opcuaClient/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
@@ -0,0 +1 @@
+com.mes.milo.configuration.MiloAutoConfiguration
\ No newline at end of file
diff --git a/hangzhoumesParent/common/opcuaClient/src/main/resources/application.yml b/hangzhoumesParent/common/opcuaClient/src/main/resources/application.yml
new file mode 100644
index 0000000..1ea573c
--- /dev/null
+++ b/hangzhoumesParent/common/opcuaClient/src/main/resources/application.yml
@@ -0,0 +1,9 @@
+mes:
+  milo:
+    primary: default
+    config:
+      default:
+        endpoint: opc.tcp://zidonghua:49320
+        security-policy: basic256sha256
+        username: zsh
+        password: 1qaz2wsx3edc4rfv
\ No newline at end of file
diff --git a/hangzhoumesParent/common/pom.xml b/hangzhoumesParent/common/pom.xml
index c8bb428..affa009 100644
--- a/hangzhoumesParent/common/pom.xml
+++ b/hangzhoumesParent/common/pom.xml
@@ -15,6 +15,7 @@
     <modules>
         <module>servicebase</module>
         <module>springsecurity</module>
+        <module>opcuaClient</module>
     </modules>
     <properties>
         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
diff --git a/hangzhoumesParent/moduleService/CacheGlassModule/src/main/java/com/mes/job/OpcCacheGlassTask.java b/hangzhoumesParent/moduleService/CacheGlassModule/src/main/java/com/mes/job/OpcCacheGlassTask.java
new file mode 100644
index 0000000..47b2fd0
--- /dev/null
+++ b/hangzhoumesParent/moduleService/CacheGlassModule/src/main/java/com/mes/job/OpcCacheGlassTask.java
@@ -0,0 +1,31 @@
+package com.mes.job;
+
+import com.mes.milo.runner.subscription.SubscriptionCallback;
+import com.mes.milo.service.MiloService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Component;
+
+import java.util.Arrays;
+
+/**
+ * @Author : zhoush
+ * @Date: 2024/10/10 8:05
+ * @Description:
+ */
+@Component
+@Slf4j
+public class OpcCacheGlassTask {
+
+    @Autowired
+    MiloService miloService;
+
+    @Autowired
+    SubscriptionCallback cacheGlassSubscriptionCallback;
+
+    @Scheduled(fixedDelay = Long.MAX_VALUE)
+    public void startOpcTask() throws Exception {
+        miloService.subscriptionFromOpcUa(Arrays.asList("my.device.x1", "my.device.x2"), cacheGlassSubscriptionCallback);
+    }
+}
diff --git a/hangzhoumesParent/moduleService/CacheGlassModule/src/main/java/com/mes/job/entity/CacheGlassInfo.java b/hangzhoumesParent/moduleService/CacheGlassModule/src/main/java/com/mes/job/entity/CacheGlassInfo.java
new file mode 100644
index 0000000..1a5b591
--- /dev/null
+++ b/hangzhoumesParent/moduleService/CacheGlassModule/src/main/java/com/mes/job/entity/CacheGlassInfo.java
@@ -0,0 +1,46 @@
+package com.mes.job.entity;
+
+import lombok.Data;
+
+/**
+ * @Author : zhoush
+ * @Date: 2024/9/20 10:56
+ * @Description:
+ */
+@Data
+public class CacheGlassInfo {
+
+    /**
+     * 璇锋眰瀛�
+     */
+    private String requestWord;
+    /**
+     * 璇锋眰瀛�
+     */
+    private String confirmationWrod;
+
+    /**
+     * 鐜荤拑id
+     */
+    private String glassId;
+
+    /**
+     * 鏈�1鐘舵��
+     */
+    private String oneMachineState;
+
+    /**
+     * 鏈�2鐘舵��
+     */
+    private String twoMachineState;
+
+    /**
+     * 涓�鍙峰崸寮忓綋鍓嶆牸瀛�
+     */
+    private int oneCurrentSlot;
+
+    /**
+     * 浜屽彿鍗у紡褰撳墠鏍煎瓙
+     */
+    private int twoCurrentSlot;
+}
diff --git a/hangzhoumesParent/moduleService/CacheGlassModule/src/main/java/com/mes/opccallback/CacheGlassSubscriptionCallback.java b/hangzhoumesParent/moduleService/CacheGlassModule/src/main/java/com/mes/opccallback/CacheGlassSubscriptionCallback.java
new file mode 100644
index 0000000..eaf7695
--- /dev/null
+++ b/hangzhoumesParent/moduleService/CacheGlassModule/src/main/java/com/mes/opccallback/CacheGlassSubscriptionCallback.java
@@ -0,0 +1,35 @@
+package com.mes.opccallback;
+
+import com.mes.milo.model.ReadWriteEntity;
+import com.mes.milo.runner.subscription.SubscriptionCallback;
+import com.mes.milo.service.MiloService;
+import lombok.extern.slf4j.Slf4j;
+import org.eclipse.milo.opcua.sdk.client.subscriptions.ManagedDataItem;
+import org.eclipse.milo.opcua.stack.core.types.builtin.DataValue;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+/**
+ * @Author : zhoush
+ * @Date: 2024/10/10 14:13
+ * @Description:
+ */
+@Service
+@Slf4j
+public class CacheGlassSubscriptionCallback implements SubscriptionCallback {
+
+    @Autowired
+    MiloService miloService;
+
+    @Override
+    public void onSubscribe(ManagedDataItem dataItem, DataValue value) {
+        try {
+            log.info("{}___________{}" + dataItem, value);
+            ReadWriteEntity readWriteEntity = miloService.readFromOpcUa("my.device.x1");
+            log.info("{}___________{}" + readWriteEntity);
+        } catch (Exception e) {
+
+        }
+
+    }
+}
diff --git a/hangzhoumesParent/moduleService/CacheGlassModule/src/main/resources/application.yml b/hangzhoumesParent/moduleService/CacheGlassModule/src/main/resources/application.yml
index b872b63..eaa6417 100644
--- a/hangzhoumesParent/moduleService/CacheGlassModule/src/main/resources/application.yml
+++ b/hangzhoumesParent/moduleService/CacheGlassModule/src/main/resources/application.yml
@@ -27,4 +27,12 @@
       secondLength: 400
   sequence:
     order: false
+  milo:
+    primary: default
+    config:
+      default:
+        endpoint: opc.tcp://127.0.0.1:49320
+        security-policy: basic256sha256
+        username: zsh
+        password: 1qaz2wsx3edc4rfv
 
diff --git a/hangzhoumesParent/moduleService/pom.xml b/hangzhoumesParent/moduleService/pom.xml
index 55ea5f6..b043adf 100644
--- a/hangzhoumesParent/moduleService/pom.xml
+++ b/hangzhoumesParent/moduleService/pom.xml
@@ -55,6 +55,11 @@
             <groupId>com.mes</groupId>
             <version>1.0-SNAPSHOT</version>
         </dependency>
+        <dependency>
+            <artifactId>opcuaClient</artifactId>
+            <groupId>com.mes</groupId>
+            <version>1.0-SNAPSHOT</version>
+        </dependency>
 
         <!--寮�鍙戣�呭伐鍏�-->
         <!--        <dependency>-->
diff --git a/hangzhoumesParent/moduleService/yiwumes/src/main/resources/application.yml b/hangzhoumesParent/moduleService/yiwumes/src/main/resources/application.yml
index 3b841eb..1bcc515 100644
--- a/hangzhoumesParent/moduleService/yiwumes/src/main/resources/application.yml
+++ b/hangzhoumesParent/moduleService/yiwumes/src/main/resources/application.yml
@@ -15,4 +15,14 @@
   configuration:
     log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
 
+mes:
+  milo:
+    primary: default
+    config:
+      default:
+        endpoint: opc.tcp://127.0.0.1:49320
+        security-policy: basic256sha256
+        username: zsh
+        password: 1qaz2wsx3edc4rfv
+
 

--
Gitblit v1.8.0