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"></i>鎼滅储</button> + <button id="btnAdd" class="layui-btn icon-btn"><i class="layui-icon"></i>娣诲姞</button> + <button id="btnExp" class="layui-btn icon-btn"><i class="layui-icon"></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> 鎻愪氦 </button> + <button type="reset" class="layui-btn layui-btn-primary" id="cancel"> 鍙栨秷 </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> 鎻愪氦 </button> + <button type="reset" class="layui-btn layui-btn-primary" id="cancel"> 鍙栨秷 </button> + </div> + +</form> + +@} \ No newline at end of file -- Gitblit v1.9.3