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