From 432ba2b81293cda3dd23cafb959bee16ccfcb20a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=A8=8B=E5=BA=8F=E5=91=98=E5=B0=8F=E5=A2=A8?= <2291200076@qq.com> Date: Mon, 28 Nov 2022 23:29:03 +0800 Subject: [PATCH] =?UTF-8?q?[=E5=90=8E=E7=AB=AF]=20webSocket=20demo?= =?UTF-8?q?=E8=B7=91=E9=80=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 10 +- .../microservice-provider-access-8002/pom.xml | 6 + .../epp/access/config/WebSocketConfig.java | 28 +++++ .../access/controller/WebSocketServer.java | 112 ++++++++++++++++++ .../epp/access/pojo/WebSocketData.java | 19 +++ backend/pom.xml | 7 ++ .../epp.postman_collection.json | 98 +++++++++++++++ test-code/.gitignore | 1 + test-code/package-lock.json | 44 +++++++ test-code/package.json | 14 +++ test-code/websocketTest.js | 45 +++++++ 11 files changed, 382 insertions(+), 2 deletions(-) create mode 100644 backend/microservice-provider-access-8002/src/main/java/com/cxyxiaomo/epp/access/config/WebSocketConfig.java create mode 100644 backend/microservice-provider-access-8002/src/main/java/com/cxyxiaomo/epp/access/controller/WebSocketServer.java create mode 100644 backend/microservice-provider-access-8002/src/main/java/com/cxyxiaomo/epp/access/pojo/WebSocketData.java create mode 100644 test-code/.gitignore create mode 100644 test-code/package-lock.json create mode 100644 test-code/package.json create mode 100644 test-code/websocketTest.js diff --git a/README.md b/README.md index 01d7aa7..17093f7 100644 --- a/README.md +++ b/README.md @@ -413,10 +413,10 @@ cnpm install ```bash npm run package -npm run make +# npm run make ``` - +`npm run make` 等于先执行 `npm run package`(即 *Import project into Forge*),再 *Create distributable* @@ -468,6 +468,12 @@ npm run serve +### Step3. 启动项目门禁端 + +编译并运行client-entrance-guard目录下electron项目即可 + + + ## 停止项目 ### Step1. 停止各个微服务 diff --git a/backend/microservice-provider-access-8002/pom.xml b/backend/microservice-provider-access-8002/pom.xml index 5cd9249..79c3553 100644 --- a/backend/microservice-provider-access-8002/pom.xml +++ b/backend/microservice-provider-access-8002/pom.xml @@ -103,6 +103,12 @@ com.squareup.okhttp3 okhttp + + + + org.springframework.boot + spring-boot-starter-websocket + diff --git a/backend/microservice-provider-access-8002/src/main/java/com/cxyxiaomo/epp/access/config/WebSocketConfig.java b/backend/microservice-provider-access-8002/src/main/java/com/cxyxiaomo/epp/access/config/WebSocketConfig.java new file mode 100644 index 0000000..6cef8c1 --- /dev/null +++ b/backend/microservice-provider-access-8002/src/main/java/com/cxyxiaomo/epp/access/config/WebSocketConfig.java @@ -0,0 +1,28 @@ +package com.cxyxiaomo.epp.access.config; + +import org.springframework.boot.web.servlet.ServletContextInitializer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.socket.server.standard.ServerEndpointExporter; + +import javax.servlet.ServletContext; +import javax.servlet.ServletException; + +/** + * 开启WebSocket支持 + */ +@Configuration +public class WebSocketConfig implements ServletContextInitializer { + /** + * 这个bean的注册,用于扫描带有@ServerEndpoint的注解成为websocket,如果你使用外置的tomcat就不需要该配置文件 + */ + @Bean + public ServerEndpointExporter serverEndpointExporter() { + return new ServerEndpointExporter(); + } + + @Override + public void onStartup(ServletContext servletContext) throws ServletException { + + } +} diff --git a/backend/microservice-provider-access-8002/src/main/java/com/cxyxiaomo/epp/access/controller/WebSocketServer.java b/backend/microservice-provider-access-8002/src/main/java/com/cxyxiaomo/epp/access/controller/WebSocketServer.java new file mode 100644 index 0000000..51dbd61 --- /dev/null +++ b/backend/microservice-provider-access-8002/src/main/java/com/cxyxiaomo/epp/access/controller/WebSocketServer.java @@ -0,0 +1,112 @@ +package com.cxyxiaomo.epp.access.controller; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import javax.websocket.*; +import javax.websocket.server.PathParam; +import javax.websocket.server.ServerEndpoint; +import java.io.IOException; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArraySet; + +@ServerEndpoint("/websocket/{userId}") +@Component +@Slf4j +public class WebSocketServer { + // 与某个客户端的连接会话,需要通过它来给客户端发送数据 + private Session session; + + // session集合,存放对应的session + private static ConcurrentHashMap sessionPool = new ConcurrentHashMap<>(); + + // concurrent包的线程安全Set,用来存放每个客户端对应的WebSocket对象。 + private static CopyOnWriteArraySet webSocketSet = new CopyOnWriteArraySet<>(); + + /** + * 建立WebSocket连接 + * + * @param session + * @param userId 用户ID + */ + @OnOpen + public void onOpen(Session session, @PathParam(value = "userId") Integer userId) { + log.info("WebSocket建立连接中,连接用户ID:{}", userId); + try { + Session historySession = sessionPool.get(userId); + // historySession不为空,说明已经有人登陆账号,应该删除登陆的WebSocket对象 + if (historySession != null) { + webSocketSet.remove(historySession); + historySession.close(); + } + } catch (IOException e) { + log.error("重复登录异常,错误信息:" + e.getMessage(), e); + } + // 建立连接 + this.session = session; + webSocketSet.add(this); + sessionPool.put(userId, session); + log.info("建立连接完成,当前在线人数为:{}", webSocketSet.size()); + } + + /** + * 发生错误 + * + * @param throwable e + */ + @OnError + public void onError(Throwable throwable) { + throwable.printStackTrace(); + } + + /** + * 连接关闭 + */ + @OnClose + public void onClose() { + webSocketSet.remove(this); + log.info("连接断开,当前在线人数为:{}", webSocketSet.size()); + } + + /** + * 接收客户端消息 + * + * @param message 接收的消息 + */ + @OnMessage + public void onMessage(String message) { + log.info("收到客户端发来的消息:{}", message); + } + + /** + * 推送消息到指定用户 + * + * @param userId 用户ID + * @param message 发送的消息 + */ + public static void sendMessageByUser(Integer userId, String message) { + log.info("用户ID:" + userId + ",推送内容:" + message); + Session session = sessionPool.get(userId); + try { + session.getBasicRemote().sendText(message); + } catch (IOException e) { + log.error("推送消息到指定用户发生错误:" + e.getMessage(), e); + } + } + + /** + * 群发消息 + * + * @param message 发送的消息 + */ + public static void sendAllMessage(String message) { + log.info("发送消息:{}", message); + for (WebSocketServer webSocket : webSocketSet) { + try { + webSocket.session.getBasicRemote().sendText(message); + } catch (IOException e) { + log.error("群发消息发生错误:" + e.getMessage(), e); + } + } + } +} diff --git a/backend/microservice-provider-access-8002/src/main/java/com/cxyxiaomo/epp/access/pojo/WebSocketData.java b/backend/microservice-provider-access-8002/src/main/java/com/cxyxiaomo/epp/access/pojo/WebSocketData.java new file mode 100644 index 0000000..6051cc1 --- /dev/null +++ b/backend/microservice-provider-access-8002/src/main/java/com/cxyxiaomo/epp/access/pojo/WebSocketData.java @@ -0,0 +1,19 @@ +package com.cxyxiaomo.epp.access.pojo; + +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; + +@Data +@NoArgsConstructor +@Accessors(chain = true) // 链式写法 +public class WebSocketData { + // 小程序: "miniprogram" 门禁: "guard" + private String type; + + // 需要执行的动作 + private String action; + + // 传输的数据(JSON字符串) + private String json; +} diff --git a/backend/pom.xml b/backend/pom.xml index 4ad23f7..361e908 100644 --- a/backend/pom.xml +++ b/backend/pom.xml @@ -127,6 +127,13 @@ 4.10.0 + + + org.springframework.boot + spring-boot-starter-websocket + 2.7.6 + + com.alibaba.fastjson2 diff --git a/postman-collection/epp.postman_collection.json b/postman-collection/epp.postman_collection.json index 8954dfe..7896b7b 100644 --- a/postman-collection/epp.postman_collection.json +++ b/postman-collection/epp.postman_collection.json @@ -118,6 +118,104 @@ } }, "response": [] + }, + { + "name": "[微服务] 获取不限制的小程序码", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "http://localhost:8002/access/wechat/getUnlimitedQRCode?scene=t=1&page=pages/index/index", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8002", + "path": [ + "access", + "wechat", + "getUnlimitedQRCode" + ], + "query": [ + { + "key": "envVersion", + "value": "develop", + "disabled": true + }, + { + "key": "scene", + "value": "t=1" + }, + { + "key": "page", + "value": "pages/index/index" + } + ] + } + }, + "response": [] + }, + { + "name": "[线上] 获取不限制的小程序码", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "https://epp.only4.work/access/wechat/getUnlimitedQRCode?scene=t=1&page=pages/index/index", + "protocol": "https", + "host": [ + "epp", + "only4", + "work" + ], + "path": [ + "access", + "wechat", + "getUnlimitedQRCode" + ], + "query": [ + { + "key": "envVersion", + "value": "develop", + "disabled": true + }, + { + "key": "scene", + "value": "t=1" + }, + { + "key": "page", + "value": "pages/index/index" + } + ] + } + }, + "response": [] + } + ] + }, + { + "name": "TestProvider", + "item": [ + { + "name": "http://localhost:8011/hi/1", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "http://localhost:8011/hi/1", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8011", + "path": [ + "hi", + "1" + ] + } + }, + "response": [] } ] } diff --git a/test-code/.gitignore b/test-code/.gitignore new file mode 100644 index 0000000..3c3629e --- /dev/null +++ b/test-code/.gitignore @@ -0,0 +1 @@ +node_modules diff --git a/test-code/package-lock.json b/test-code/package-lock.json new file mode 100644 index 0000000..91a3a44 --- /dev/null +++ b/test-code/package-lock.json @@ -0,0 +1,44 @@ +{ + "name": "test-code", + "version": "1.0.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "test-code", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "ws": "^8.11.0" + } + }, + "node_modules/ws": { + "version": "8.11.0", + "resolved": "https://registry.npmmirror.com/ws/-/ws-8.11.0.tgz", + "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + } + }, + "dependencies": { + "ws": { + "version": "8.11.0", + "resolved": "https://registry.npmmirror.com/ws/-/ws-8.11.0.tgz", + "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", + "requires": {} + } + } +} diff --git a/test-code/package.json b/test-code/package.json new file mode 100644 index 0000000..6f95043 --- /dev/null +++ b/test-code/package.json @@ -0,0 +1,14 @@ +{ + "name": "test-code", + "version": "1.0.0", + "description": "", + "main": "websocketTest.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "ISC", + "dependencies": { + "ws": "^8.11.0" + } +} diff --git a/test-code/websocketTest.js b/test-code/websocketTest.js new file mode 100644 index 0000000..8ce01a7 --- /dev/null +++ b/test-code/websocketTest.js @@ -0,0 +1,45 @@ +'use strict'; + +const WebSocket = require('ws'); + +const webSocketUrl = 'ws://localhost:8002/websocket/1'; + +global.ws = null; // WebSocket 实例对象 + +/** + * 启动完毕,输出配置信息 + */ +console.log("Start running ...", "process.env", process.env); + +function createWebSocket() { + //申请一个WebSocket对象,参数是服务端地址,同http协议使用http://开头一样,WebSocket协议的url使用ws://开头,另外安全的WebSocket协议使用wss://开头 + global.ws = new WebSocket(webSocketUrl); + + // 当WebSocket创建成功时,触发onopen事件 + global.ws.onopen = function () { + console.log("webhook is open."); + } + + // 当客户端收到服务端发送的关闭连接请求时,触发onclose事件 + global.ws.onclose = function (e) { + console.log("webhook is close."); + console.log("未知错误被关闭,等待 1s 尝试重新建立连接..."); + setTimeout(function () { + createWebSocket(); + }, 1000); + } + + // 如果出现连接、处理、接收、发送数据失败的时候触发onerror事件 + global.ws.onerror = function (e) { + console.log(e.error); + } + + // 当客户端收到服务端发来的消息时,触发onmessage事件,参数e.data包含server传递过来的数据 + global.ws.onmessage = function (e) { + var data = JSON.parse(e.data); + console.log(data); + } +} + +// 创建连接 +createWebSocket();