Ver código fonte

add websocket

zhanghaijun 2 anos atrás
pai
commit
8b58855db2

+ 33 - 0
train-server/.gitignore

@@ -0,0 +1,33 @@
+HELP.md
+target/
+!.mvn/wrapper/maven-wrapper.jar
+!**/src/main/**/target/
+!**/src/test/**/target/
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+build/
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### VS Code ###
+.vscode/

+ 10 - 0
train-server/pom.xml

@@ -25,6 +25,16 @@
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter</artifactId>
         </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-websocket</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-thymeleaf</artifactId>
+        </dependency>
+
         <dependency>
             <groupId>com.baomidou</groupId>
             <artifactId>mybatis-plus-boot-starter</artifactId>

+ 14 - 0
train-server/src/main/java/top/haijunit/train/config/WebSocketConfig.java

@@ -0,0 +1,14 @@
+package top.haijunit.train.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.socket.server.standard.ServerEndpointExporter;
+
+@Configuration
+public class WebSocketConfig {
+    @Bean
+    public ServerEndpointExporter serverEndpointExporter() {
+        return new ServerEndpointExporter();
+    }
+
+}

+ 25 - 0
train-server/src/main/java/top/haijunit/train/controller/WebSocketController.java

@@ -0,0 +1,25 @@
+package top.haijunit.train.controller;
+
+import top.haijunit.train.websocket.WebSocketServer;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.servlet.ModelAndView;
+
+@RestController
+public class WebSocketController {
+
+    @GetMapping("/page")
+    public ModelAndView page() {
+        return new ModelAndView("webSocket");
+    }
+
+    @RequestMapping("/push/{toUID}")
+    public ResponseEntity<String> pushToClient(String message, @PathVariable String toUID) throws Exception {
+        WebSocketServer.sendInfo(message, toUID);
+        return ResponseEntity.ok("Send Success!");
+    }
+
+}

+ 160 - 0
train-server/src/main/java/top/haijunit/train/websocket/WebSocketServer.java

@@ -0,0 +1,160 @@
+package top.haijunit.train.websocket;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import jakarta.websocket.*;
+import jakarta.websocket.server.PathParam;
+import jakarta.websocket.server.ServerEndpoint;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.stereotype.Component;
+
+import java.io.IOException;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+@Component
+@ServerEndpoint("/socket/{uid}")
+@Slf4j
+public class WebSocketServer {
+
+    /**
+     * 记录当前在线连接数
+     */
+    private static int onlineCount = 0;
+
+    /**
+     * 使用线程安全的ConcurrentHashMap来存放每个客户端对应的WebSocket对象
+     */
+    private static ConcurrentHashMap<String, WebSocketServer> webSocketMap = new ConcurrentHashMap<>();
+
+    /**
+     * 与某个客户端的连接会话,需要通过它来给客户端发送数据
+     */
+    private Session session;
+
+    /**
+     * 接收客户端消息的uid
+     */
+    private String uid = "";
+
+    /**
+     * 连接建立成功调用的方法
+     * @param session
+     * @param uid
+     */
+    @OnOpen
+    public void onOpen(Session session, @PathParam("uid") String uid) {
+        this.session = session;
+        this.uid = uid;
+        if (webSocketMap.containsKey(uid)) {
+            webSocketMap.remove(uid);
+            //加入到set中
+            webSocketMap.put(uid, this);
+        } else {
+            //加入set中
+            webSocketMap.put(uid, this);
+            //在线数加1
+            addOnlineCount();
+        }
+
+        log.info("用户【" + uid + "】连接成功,当前在线人数为:" + getOnlineCount());
+        try {
+            sendMsg("连接成功");
+        } catch (IOException e) {
+            log.error("用户【" + uid + "】网络异常!", e);
+        }
+    }
+
+    /**
+     * 连接关闭调用的方法
+     */
+    @OnClose
+    public void onClose() {
+        if (webSocketMap.containsKey(uid)) {
+            webSocketMap.remove(uid);
+            //从set中删除
+            subOnlineCount();
+        }
+        log.info("用户【" + uid + "】退出,当前在线人数为:" + getOnlineCount());
+    }
+
+    /**
+     * 收到客户端消息后调用的方法
+     * @param message 客户端发送过来的消息
+     * @param session 会话
+     */
+    @OnMessage
+    public void onMessage(String message, Session session) {
+        log.info("用户【" + uid + "】发送报文:" + message);
+        //群发消息
+        //消息保存到数据库或者redis
+        if (StringUtils.isNotBlank(message)) {
+            try {
+                //解析发送的报文
+                ObjectMapper objectMapper = new ObjectMapper();
+                Map<String, String> map = objectMapper.readValue(message, new TypeReference<Map<String, String>>(){});
+                //追加发送人(防止串改)
+                map.put("fromUID", this.uid);
+                String toUID = map.get("toUID");
+                //传送给对应的toUserId用户的WebSocket
+                if (StringUtils.isNotBlank(toUID) && webSocketMap.containsKey(toUID)) {
+                    webSocketMap.get(toUID).sendMsg(objectMapper.writeValueAsString(map));
+                } else {
+                    //若果不在这个服务器上,可以考虑发送到mysql或者redis
+                    log.error("请求目标用户【" + toUID + "】不在该服务器上");
+                }
+            } catch (Exception e) {
+                log.error("用户【" + uid + "】发送消息异常!", e);
+            }
+        }
+    }
+
+    /**
+     * 处理错误
+     * @param session
+     * @param error
+     */
+    @OnError
+    public void onError(Session session, Throwable error) {
+        log.error("用户【" + this.uid + "】处理消息错误,原因:" + error.getMessage());
+        error.printStackTrace();
+    }
+
+    /**
+     * 实现服务器主动推送
+     * @param msg
+     * @throws IOException
+     */
+    private void sendMsg(String msg) throws IOException {
+        this.session.getBasicRemote().sendText(msg);
+    }
+
+    /**
+     * 发送自定义消息
+     * @param message
+     * @param uid
+     * @throws IOException
+     */
+    public static void sendInfo(String message, @PathParam("uid") String uid) throws IOException {
+        log.info("发送消息到用户【" + uid + "】发送的报文:" + message);
+        if (!StringUtils.isEmpty(uid) && webSocketMap.containsKey(uid)) {
+            webSocketMap.get(uid).sendMsg(message);
+        } else {
+            log.error("用户【" + uid + "】不在线!");
+        }
+    }
+
+    private static synchronized int getOnlineCount() {
+        return onlineCount;
+    }
+
+    private static synchronized void addOnlineCount() {
+        WebSocketServer.onlineCount++;
+    }
+
+    private static synchronized void subOnlineCount() {
+        WebSocketServer.onlineCount--;
+    }
+
+}

+ 19 - 0
train-server/src/main/resources/application.yml

@@ -1,3 +1,10 @@
+server:
+  port: 8080
+  servlet:
+    encoding:
+      charset: UTF-8
+      force: true
+    context-path: /
 spring:
   application:
     name: train-server
@@ -19,6 +26,18 @@ spring:
       maxLifetime: 1800000
       connectionTestQuery: SELECT 1
       keepaliveTime: 30000
+  freemarker:
+    enabled: true
+    request-context-attribute: request
+    prefix: /templates/
+    suffix: .html
+    content-type: text/html
+    cache: false
+    charset: UTF-8
+    allow-request-override: false
+    expose-request-attributes: true
+    expose-session-attributes: true
+    expose-spring-macro-helpers: true
 logging:
   level:
     top.haijunit: debug

+ 10 - 0
train-server/src/main/resources/static/index.html

@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <title>Title</title>
+</head>
+<body>
+
+</body>
+</html>

+ 75 - 0
train-server/src/main/resources/templates/webSocket.html

@@ -0,0 +1,75 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="utf-8">
+    <title>WebSocket消息通知</title>
+</head>
+<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.js"></script>
+<script>
+    var socket;
+    //打开WebSocket
+    function openSocket() {
+        if (typeof (WebSocket) === "undefined") {
+            console.log("您的浏览器不支持WebSocket");
+        } else {
+            console.log("您的浏览器支持WebSocket");
+            //实现化WebSocket对象,指定要连接的服务器地址与端口,建立连接.
+            var socketUrl = "http://localhost:8080/socket/" + $("#uid").val();
+            //将https与http协议替换为ws协议
+            socketUrl = socketUrl.replace("https", "ws").replace("http", "ws");
+            console.log(socketUrl);
+            if (socket != null) {
+                socket.close();
+                socket = null;
+            }
+            socket = new WebSocket(socketUrl);
+            //打开事件
+            socket.onopen = function () {
+                console.log("WebSocket已打开");
+                //socket.send("这是来自客户端的消息" + location.href + new Date());
+            };
+            //获得消息事件
+            socket.onmessage = function (msg) {
+                console.log(msg.data);
+                //发现消息进入,开始处理前端触发逻辑
+            };
+            //关闭事件
+            socket.onclose = function () {
+                console.log("WebSocket已关闭");
+            };
+            //发生了错误事件
+            socket.onerror = function () {
+                console.log("WebSocket发生了错误");
+            }
+        }
+    }
+
+    //发送消息
+    function sendMessage() {
+        if (typeof (WebSocket) === "undefined") {
+            console.log("您的浏览器不支持WebSocket");
+        } else {
+            console.log("您的浏览器支持WebSocket");
+            console.log('{"toUID":"' + $("#toUID").val() + '","Msg":"' + $("#msg").val() + '"}');
+            socket.send('{"toUID":"' + $("#toUID").val() + '","Msg":"' + $("#msg").val() + '"}');
+        }
+    }
+</script>
+<body>
+<p>【uid】:
+<div><input id="uid" name="uid" type="text" value="1"></div>
+<p>【toUID】:
+<div><input id="toUID" name="toUID" type="text" value="2"></div>
+<p>【Msg】:
+<div><input id="msg" name="msg" type="text" value="hello WebSocket2"></div>
+<p>【第一步操作:】:
+<div>
+    <button onclick="openSocket()">开启socket</button>
+</div>
+<p>【第二步操作:】:
+<div>
+    <button onclick="sendMessage()">发送消息</button>
+</div>
+</body>
+
+</html>