This commit is contained in:
XSWL1018 2024-10-19 15:20:49 +08:00
parent 59ccedce9b
commit e6736e99b1
6 changed files with 0 additions and 419 deletions

View File

@ -1,22 +0,0 @@
package com.ruoyi.websocket;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.web.embedded.netty.NettyWebServer;
import org.springframework.stereotype.Component;
import com.ruoyi.websocket.nettyServer.NettyWebSocketServer;
@Component
public class NettyServerRunner implements ApplicationRunner {
@Autowired
private NettyWebSocketServer server;
@Override
public void run(ApplicationArguments args) throws Exception {
server.start();
}
}

View File

@ -1,12 +0,0 @@
package com.ruoyi.websocket.annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface NettyWebSocketEndpoint {
String path();
}

View File

@ -1,74 +0,0 @@
package com.ruoyi.websocket.nettyServer;
import java.nio.channels.Channel;
import java.util.Map;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.handler.codec.http.FullHttpMessage;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.util.concurrent.GlobalEventExecutor;
public abstract class NettyWebSocketEndpointHandler {
private final ChannelGroup group = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
private Map<String, String> pathParam;
private Map<String, String> urlParam;
public void sendAll(String msg) {
group.writeAndFlush(msg);
}
public static void sendMsg(ChannelHandlerContext context, String msg) {
TextWebSocketFrame textWebSocketFrame = new TextWebSocketFrame(msg);
context.channel().writeAndFlush(textWebSocketFrame);
}
public abstract void onMessage(ChannelHandlerContext channelHandlerContext, TextWebSocketFrame textWebSocketFrame);
public abstract void onOpen(ChannelHandlerContext channelHandlerContext, FullHttpMessage fullHttpMessage);
public abstract void onClose(ChannelHandlerContext channelHandlerContext);
public abstract void onError(ChannelHandlerContext channelHandlerContext, Throwable throwable);
public ChannelGroup getGroup() {
return group;
}
public Map<String, String> getPathParam() {
return pathParam;
}
public void setPathParam(Map<String, String> pathParam) {
this.pathParam = pathParam;
}
public Map<String, String> getUrlParam() {
return urlParam;
}
public void setUrlParam(Map<String, String> urlParam) {
this.urlParam = urlParam;
}
public Long getLongPathParam(String key) {
return Long.valueOf(pathParam.get(key));
}
public String getPathParam(String key) {
return pathParam.get(key);
}
public Double getDoublePathParam(String key) {
return Double.parseDouble(pathParam.get(key));
}
public void closeChannel(ChannelHandlerContext channelHandlerContext) {
channelHandlerContext.close().addListener(ChannelFutureListener.CLOSE);
}
}

View File

@ -1,64 +0,0 @@
package com.ruoyi.websocket.nettyServer;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import com.ruoyi.websocket.nettyServer.handler.WebSocketHandler;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
@Component
public class NettyWebSocketServer {
private static ServerBootstrap serverBootstrap;
@Value("${netty.websocket.maxMessageSize}")
private Long messageSize;
@Value("${netty.websocket.bossThreads}")
private Long bossThreads;
@Value("${netty.websocket.workerThreads}")
private Long workerThreads;
@Value("${netty.websocket.port}")
private Long port;
@Value("${netty.websocket.enable}")
private Boolean enable;
public ServerBootstrap start() throws InterruptedException {
if (!enable) {
return null;
}
ServerBootstrap serverBootstrap = new ServerBootstrap();
NioEventLoopGroup boss = new NioEventLoopGroup(4);
NioEventLoopGroup worker = new NioEventLoopGroup(workerThreads.intValue());
serverBootstrap.group(boss, worker);
serverBootstrap.channel(NioServerSocketChannel.class);
serverBootstrap.childOption(ChannelOption.SO_KEEPALIVE, true);
serverBootstrap.childHandler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel channel) throws Exception {
ChannelPipeline pipeline = channel.pipeline();
pipeline.addLast(new HttpServerCodec());
pipeline.addLast(new HttpObjectAggregator(messageSize.intValue()));
pipeline.addLast(new WebSocketHandler());
pipeline.addLast(new WebSocketServerProtocolHandler("/", true));
}
});
serverBootstrap.bind(port.intValue()).sync();
System.out.println(
"----------------------------------------------------------------------------------- \n Arknights!");
NettyWebSocketServer.serverBootstrap = serverBootstrap;
return serverBootstrap;
}
}

View File

@ -1,168 +0,0 @@
package com.ruoyi.websocket.nettyServer.handler;
import java.util.concurrent.ConcurrentHashMap;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.websocket.annotations.NettyWebSocketEndpoint;
import com.ruoyi.websocket.nettyServer.NettyWebSocketEndpointHandler;
import com.ruoyi.websocket.utils.CommonUtil;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelId;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpVersion;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.util.concurrent.GlobalEventExecutor;
import jakarta.annotation.PostConstruct;
import java.lang.reflect.Constructor;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.*;
@Component
public class WebSocketHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {
@Autowired
private List<NettyWebSocketEndpointHandler> handlers;
private static final Map<String, PathMatchModel> uriHandlerMapper = new ConcurrentHashMap<>();
public static final ChannelGroup channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
public static final Map<ChannelId, NettyWebSocketEndpointHandler> channelHandlerMap = new ConcurrentHashMap<>();
@PostConstruct
private void init() throws URISyntaxException, NoSuchMethodException, SecurityException {
for (NettyWebSocketEndpointHandler handler : handlers) {
Class<?> handlerClass = handler.getClass();
NettyWebSocketEndpoint annotation = handlerClass.getAnnotation(NettyWebSocketEndpoint.class);
if (annotation == null || StringUtils.isEmpty(annotation.path())) {
throw new RuntimeException("未配置路径的 netty websocket endpoint ");
}
// uriHandlerMap.put(uri.getPath(), handler);
PathMatchModel pathMachModel = parseHandler(annotation.path(), handlerClass);
uriHandlerMapper.put(pathMachModel.path, pathMachModel);
}
}
@Override
protected void channelRead0(ChannelHandlerContext context, TextWebSocketFrame webSocketFrame) throws Exception {
NettyWebSocketEndpointHandler handler = channelHandlerMap.get(context.channel().id());
handler.onMessage(context, webSocketFrame);
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
channelGroup.add(ctx.channel());
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
NettyWebSocketEndpointHandler handler = channelHandlerMap.get(ctx.channel().id());
if (handler != null) {
handler.onClose(ctx);
handler.getGroup().remove(ctx.channel());
}
channelHandlerMap.remove(ctx.channel().id());
channelGroup.remove(ctx.channel());
}
@Override
public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
channelHandlerMap.get(ctx.channel().id()).onError(ctx, cause);
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
if (msg instanceof FullHttpRequest) {
FullHttpRequest fullHttpRequest = (FullHttpRequest) msg;
if (channelHandlerMap.get(ctx.channel().id()) != null) {
super.channelRead(ctx, fullHttpRequest);
return;
}
URI uri = new URI(fullHttpRequest.uri());
PathMatchModel mathPathMachModel = mathPathMachModel(uri.getPath());
if (mathPathMachModel == null) {
ctx.channel()
.writeAndFlush(new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.NOT_FOUND));
ctx.close().addListener(ChannelFutureListener.CLOSE);
return;
}
NettyWebSocketEndpointHandler newInstance = (NettyWebSocketEndpointHandler) mathPathMachModel.handlerConstructor
.newInstance();
if (!(mathPathMachModel.pathParams == null || mathPathMachModel.pathParams.isEmpty())) {
newInstance.setPathParam(
CommonUtil.parsePathParam(uri.getPath(), mathPathMachModel.pathParams, mathPathMachModel.path));
super.channelRead(ctx, msg);
}
newInstance.setUrlParam(CommonUtil.parseQueryParameters(uri.getQuery()));
channelHandlerMap.put(ctx.channel().id(), newInstance);
newInstance.onOpen(ctx, fullHttpRequest);
} else if (msg instanceof TextWebSocketFrame) {
super.channelRead(ctx, msg);
}
}
private static PathMatchModel parseHandler(String path, Class<?> handlerClass)
throws NoSuchMethodException, SecurityException {
List<String> paramName = new ArrayList<>();
String[] split = path.split("/");
for (int index = 1; index < split.length; index++) {
String item = split[index];
if (item.startsWith("{") && item.endsWith("}")) {
paramName.add(item.substring(1, item.length() - 1).trim());
split[index] = "?";
}
}
StringBuilder finalPath = new StringBuilder("");
for (int index = 1; index < split.length; index++) {
finalPath.append("/").append(split[index]);
}
return new PathMatchModel(paramName, finalPath.toString(), handlerClass.getDeclaredConstructor());
}
private static PathMatchModel mathPathMachModel(String uri) {
Map<Integer, PathMatchModel> map = new HashMap<>();
for (String key : uriHandlerMapper.keySet()) {
int mathUri = CommonUtil.mathUri(uri, key);
if (mathUri > 0) {
map.put(mathUri, uriHandlerMapper.get(key));
}
}
if (map.keySet() == null || map.keySet().isEmpty()) {
return null;
}
Integer max = CommonUtil.getMax(map.keySet());
return map.get(max);
}
private static final class PathMatchModel {
private final List<String> pathParams;
private final String path;
private final Constructor<?> handlerConstructor;
public PathMatchModel(List<String> pathParams, String path, Constructor<?> handlerConstructor) {
this.pathParams = pathParams;
this.path = path;
this.handlerConstructor = handlerConstructor;
}
}
}

View File

@ -1,79 +0,0 @@
package com.ruoyi.websocket.utils;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
public class CommonUtil {
/**
* @param uri
* @param uriTemplates
* @return
*/
public static int mathUri(String uri, String uriTemplate) {
String[] uriSplit = uri.split("/");
String[] tempalteSplit = uriTemplate.split("/");
if (uriSplit.length != tempalteSplit.length) {
return -1;
}
int mathLevel = 0;
for (int index = 1; index < tempalteSplit.length; index++) {
if (tempalteSplit[index].equals("?")) {
mathLevel = mathLevel + index;
continue;
}
if (!tempalteSplit[index].equals(uriSplit[index])) {
return -1;
} else {
mathLevel = mathLevel + tempalteSplit.length + 1;
}
}
return mathLevel;
}
public static Map<String, String> parseQueryParameters(String query) {
if (query == null || query.isEmpty()) {
return Map.of();
}
Map<String, String> params = new HashMap<>();
String[] pairs = query.split("&");
for (String pair : pairs) {
String[] keyValue = pair.split("=");
if (keyValue.length > 1) {
params.put(keyValue[0], keyValue[1]);
} else {
params.put(keyValue[0], "");
}
}
return params;
}
public static Map<String, String> parsePathParam(String uri, List<String> pathParams, String uriTemplate) {
int index = 0;
String[] split = uriTemplate.split("/");
String[] split2 = uri.split("/");
Map<String, String> map = new HashMap<>();
for (int i = 1; i < split.length; i++) {
if (split[i].equals("?")) {
map.put(pathParams.get(index), split2[i]);
index++;
}
}
return map;
}
public static Integer getMax(Set<Integer> set) {
Optional<Integer> maxNumber = set.stream().max(Integer::compare);
if (maxNumber.isPresent()) {
System.out.println("Max number: " + maxNumber.get());
} else {
System.out.println("The list is empty");
}
return maxNumber.get();
}
}