From c1bb3dd69c4d68f3f0787b59881ca258707b8cea Mon Sep 17 00:00:00 2001
From: Administrator <15274802129@163.com>
Date: Thu, 14 Aug 2025 10:34:36 +0800
Subject: [PATCH] feat(websocket): 添加 Netty WebSocket 聊天功能基础版本,URL:http://localhost:8085/febs/pages/websocket/chat.html

---
 src/main/java/cc/mrbird/febs/common/configure/NettyWebSocketConfig.java   |   54 ++++++++++
 src/main/resources/static/febs/pages/websocket/chat.html                  |  134 ++++++++++++++++++++++++++
 src/main/java/cc/mrbird/febs/common/websocket/WebSocketServerHandler.java |   48 +++++++++
 pom.xml                                                                   |    8 +
 src/main/java/cc/mrbird/febs/common/websocket/WebSocketServer.java        |   45 +++++++++
 src/main/resources/application.yml                                        |    4 
 6 files changed, 293 insertions(+), 0 deletions(-)

diff --git a/pom.xml b/pom.xml
index 56ecf7c..9fa5ea9 100644
--- a/pom.xml
+++ b/pom.xml
@@ -29,6 +29,14 @@
 
     <dependencies>
 
+
+
+        <dependency>
+            <groupId>io.netty</groupId>
+            <artifactId>netty-all</artifactId>
+            <version>4.1.92.Final</version> <!-- 示例版本 -->
+        </dependency>
+
         <dependency>
             <groupId>com.volcengine</groupId>
             <artifactId>volcengine-java-sdk-ark-runtime</artifactId>
diff --git a/src/main/java/cc/mrbird/febs/common/configure/NettyWebSocketConfig.java b/src/main/java/cc/mrbird/febs/common/configure/NettyWebSocketConfig.java
new file mode 100644
index 0000000..7065701
--- /dev/null
+++ b/src/main/java/cc/mrbird/febs/common/configure/NettyWebSocketConfig.java
@@ -0,0 +1,54 @@
+package cc.mrbird.febs.common.configure;
+
+import io.netty.bootstrap.ServerBootstrap;
+import io.netty.channel.ChannelOption;
+import io.netty.channel.EventLoopGroup;
+import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.channel.socket.nio.NioServerSocketChannel;
+import io.netty.handler.codec.http.HttpObjectAggregator;
+import io.netty.handler.codec.http.HttpServerCodec;
+import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
+import io.netty.handler.logging.LogLevel;
+import io.netty.handler.logging.LoggingHandler;
+import io.netty.handler.stream.ChunkedWriteHandler;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class NettyWebSocketConfig {
+
+    @Value("${netty.websocket.port:8090}")
+    private int port;
+
+    @Bean(name = "bossGroup", destroyMethod = "shutdownGracefully")
+    public EventLoopGroup bossGroup() {
+        return new NioEventLoopGroup(1);
+    }
+
+    @Bean(name = "workerGroup", destroyMethod = "shutdownGracefully")
+    public EventLoopGroup workerGroup() {
+        return new NioEventLoopGroup();
+    }
+
+    @Bean
+    public ServerBootstrap serverBootstrap(EventLoopGroup bossGroup, EventLoopGroup workerGroup) {
+        ServerBootstrap bootstrap = new ServerBootstrap();
+        bootstrap.group(bossGroup, workerGroup)
+                .channel(NioServerSocketChannel.class)
+                .option(ChannelOption.SO_BACKLOG, 128)
+                .childOption(ChannelOption.SO_KEEPALIVE, true)
+                .handler(new LoggingHandler(LogLevel.INFO))
+                .childHandler(new io.netty.channel.ChannelInitializer<io.netty.channel.socket.SocketChannel>() {
+                    @Override
+                    protected void initChannel(io.netty.channel.socket.SocketChannel ch) throws Exception {
+                        ch.pipeline().addLast(new HttpServerCodec());
+                        ch.pipeline().addLast(new ChunkedWriteHandler());
+                        ch.pipeline().addLast(new HttpObjectAggregator(65536));
+                        ch.pipeline().addLast(new WebSocketServerProtocolHandler("/ws"));
+                        ch.pipeline().addLast(new cc.mrbird.febs.common.websocket.WebSocketServerHandler());
+                    }
+                });
+        return bootstrap;
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/cc/mrbird/febs/common/websocket/WebSocketServer.java b/src/main/java/cc/mrbird/febs/common/websocket/WebSocketServer.java
new file mode 100644
index 0000000..9fdc62a
--- /dev/null
+++ b/src/main/java/cc/mrbird/febs/common/websocket/WebSocketServer.java
@@ -0,0 +1,45 @@
+package cc.mrbird.febs.common.websocket;
+
+import io.netty.bootstrap.ServerBootstrap;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.EventLoopGroup;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.PostConstruct;
+import javax.annotation.PreDestroy;
+
+@Component
+public class WebSocketServer {
+
+    @Value("${netty.websocket.port:8090}")
+    private int port;
+
+    @Autowired
+    private ServerBootstrap serverBootstrap;
+
+    @Autowired
+    private EventLoopGroup bossGroup;
+
+    @Autowired
+    private EventLoopGroup workerGroup;
+
+    private ChannelFuture channelFuture;
+
+    @PostConstruct
+    public void start() throws InterruptedException {
+        channelFuture = serverBootstrap.bind(port).sync();
+        System.out.println("Netty WebSocket服务器启动在端口:" + port);
+    }
+
+    @PreDestroy
+    public void stop() {
+        if (channelFuture != null) {
+            channelFuture.channel().close();
+        }
+        bossGroup.shutdownGracefully();
+        workerGroup.shutdownGracefully();
+        System.out.println("Netty WebSocket服务器已停止");
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/cc/mrbird/febs/common/websocket/WebSocketServerHandler.java b/src/main/java/cc/mrbird/febs/common/websocket/WebSocketServerHandler.java
new file mode 100644
index 0000000..3506dd4
--- /dev/null
+++ b/src/main/java/cc/mrbird/febs/common/websocket/WebSocketServerHandler.java
@@ -0,0 +1,48 @@
+package cc.mrbird.febs.common.websocket;
+
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.SimpleChannelInboundHandler;
+import io.netty.channel.group.ChannelGroup;
+import io.netty.channel.group.DefaultChannelGroup;
+import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
+import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
+import io.netty.util.concurrent.GlobalEventExecutor;
+
+public class WebSocketServerHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {
+
+    public static ChannelGroup channels = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
+
+    @Override
+    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
+        channels.add(ctx.channel());
+    }
+
+    @Override
+    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
+        channels.remove(ctx.channel());
+    }
+
+    @Override
+    public void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {
+        String request = msg.text();
+        System.out.println("服务端收到:" + request);
+        
+        // 广播消息给其他连接的客户端,不包括发送者自己
+        for (io.netty.channel.Channel channel : channels) {
+            if (channel != ctx.channel()) {
+                channel.writeAndFlush(new TextWebSocketFrame("[用户] " + request));
+            }
+        }
+    }
+
+    @Override
+    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
+        ctx.flush();
+    }
+
+    @Override
+    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
+        cause.printStackTrace();
+        ctx.close();
+    }
+}
\ No newline at end of file
diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml
index 8cff713..608f7d0 100644
--- a/src/main/resources/application.yml
+++ b/src/main/resources/application.yml
@@ -41,3 +41,7 @@
             metadata:
               TableInfoHelper: error
     cc.mrbird.febs: info
+
+netty:
+  websocket:
+    port: 8090
diff --git a/src/main/resources/static/febs/pages/websocket/chat.html b/src/main/resources/static/febs/pages/websocket/chat.html
new file mode 100644
index 0000000..148ad8c
--- /dev/null
+++ b/src/main/resources/static/febs/pages/websocket/chat.html
@@ -0,0 +1,134 @@
+<!DOCTYPE html>
+<html lang="zh-CN">
+<head>
+    <meta charset="UTF-8">
+    <title>WebSocket聊天室</title>
+    <link rel="stylesheet" href="/layui/css/layui.css" media="all">
+    <style>
+        .chat-container {
+            width: 100%;
+            max-width: 800px;
+            margin: 20px auto;
+            border: 1px solid #e6e6e6;
+            border-radius: 4px;
+            overflow: hidden;
+        }
+        .chat-messages {
+            height: 400px;
+            padding: 15px;
+            overflow-y: auto;
+            background-color: #f8f8f8;
+        }
+        .chat-input {
+            padding: 15px;
+            background-color: #fff;
+        }
+        .message {
+            margin-bottom: 10px;
+            padding: 8px 12px;
+            border-radius: 4px;
+            background-color: #fff;
+            box-shadow: 0 1px 2px rgba(0,0,0,0.1);
+        }
+        .message.me {
+            background-color: #1E9FFF;
+            color: white;
+            text-align: right;
+        }
+    </style>
+</head>
+<body>
+<div class="layui-container">
+    <div class="chat-container">
+        <div class="chat-messages" id="chatMessages">
+            <!-- 消息将在这里显示 -->
+        </div>
+        <div class="chat-input">
+            <div class="layui-form">
+                <div class="layui-form-item">
+                    <div class="layui-input-block">
+                        <input type="text" id="messageInput" placeholder="请输入消息..." class="layui-input">
+                    </div>
+                </div>
+                <div class="layui-form-item">
+                    <div class="layui-input-block">
+                        <button class="layui-btn" id="sendBtn">发送消息</button>
+                        <button class="layui-btn layui-btn-primary" id="connectBtn">连接</button>
+                        <button class="layui-btn layui-btn-danger" id="disconnectBtn">断开</button>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>
+
+<script src="/layui/layui.js" charset="utf-8"></script>
+<script>
+    layui.use(['jquery'], function() {
+        var $ = layui.jquery;
+        var websocket;
+        
+        function connect() {
+            if ('WebSocket' in window) {
+                // 连接到Netty WebSocket服务器
+                websocket = new WebSocket("ws://localhost:8090/ws");
+                
+                websocket.onopen = function() {
+                    appendMessage("[系统] 连接成功", "system");
+                };
+                
+                websocket.onmessage = function(event) {
+                    appendMessage(event.data, "other");
+                };
+                
+                websocket.onclose = function() {
+                    appendMessage("[系统] 连接已关闭", "system");
+                };
+                
+                websocket.onerror = function() {
+                    appendMessage("[系统] 连接发生错误", "system");
+                };
+            } else {
+                alert('您的浏览器不支持WebSocket');
+            }
+        }
+        
+        function disconnect() {
+            if (websocket) {
+                websocket.close();
+            }
+        }
+        
+        function sendMessage() {
+            var message = $('#messageInput').val().trim();
+            if (message && websocket) {
+                websocket.send(message);
+                appendMessage(message, "me");
+                $('#messageInput').val('');
+            }
+        }
+        
+        function appendMessage(message, type) {
+            var messageClass = "message";
+            if (type === "me") {
+                messageClass += " me";
+            }
+            $('#chatMessages').append('<div class="' + messageClass + '">' + message + '</div>');
+            $('#chatMessages').scrollTop($('#chatMessages')[0].scrollHeight);
+        }
+        
+        // 绑定事件
+        $('#connectBtn').on('click', connect);
+        $('#disconnectBtn').on('click', disconnect);
+        $('#sendBtn').on('click', sendMessage);
+        
+        // 回车发送消息
+        $('#messageInput').on('keypress', function(e) {
+            if (e.which === 13) {
+                sendMessage();
+            }
+        });
+    });
+</script>
+</body>
+</html>
\ No newline at end of file

--
Gitblit v1.9.1