feat: add retry configuration and optimization running lifecycle

This commit is contained in:
Wzp-2008 2025-03-26 19:12:56 +08:00
parent d9d3bb2a5b
commit e5788822ed
12 changed files with 177 additions and 22 deletions

View File

@ -8,13 +8,13 @@ package cn.wzpmc.plugins.configuration;
* @since 2024/7/31 上午3:42 * @since 2024/7/31 上午3:42
*/ */
public interface IConfiguration { public interface IConfiguration {
/**
* @return WebSocket连接URL
* @author wzp
* @since 2024/7/31 上午3:48 v0.0.1-dev
*/
String getWebsocket();
/**
* @author wzp
* @since 2025/3/26 17:29 v1.0.7
* @return 网络相关配置
*/
INetworkConfiguration getNetwork();
/** /**
* *
* @author wzp * @author wzp

View File

@ -0,0 +1,38 @@
package cn.wzpmc.plugins.configuration;
/**
* @author wzp
* @since 2025/3/26 17:26
* @version 1.0.7
*/
public interface INetworkConfiguration {
/**
* @return WebSocket连接URL
* @author wzp
* @since 2025/3/26 17:26 v1.0.7
*/
String getWebsocket();
/**
* @author wzp
* @since 2025/3/26 17:27 v1.0.7
* @return 是否启用连接重试
*/
Boolean isRetry();
/**
* @author wzp
* @since 2025/3/26 17:27 v1.0.7
* @return 重试最大次数-1为无限
*/
Integer getMaxRetryCount();
/**
* @author wzp
* @since 2025/3/26 17:28 v1.0.7
* @return 获取重试间隔单位毫秒
*/
Long getRetryInterval();
}

View File

@ -18,6 +18,7 @@ import cn.wzpmc.utils.JsonUtils;
import cn.wzpmc.utils.ReflectionUtils; import cn.wzpmc.utils.ReflectionUtils;
import cn.wzpmc.utils.TemplateFileUtils; import cn.wzpmc.utils.TemplateFileUtils;
import cn.wzpmc.utils.YamlUtils; import cn.wzpmc.utils.YamlUtils;
import com.alibaba.fastjson2.JSONObject;
import lombok.SneakyThrows; import lombok.SneakyThrows;
import lombok.extern.log4j.Log4j2; import lombok.extern.log4j.Log4j2;
@ -52,6 +53,7 @@ public class Main {
} }
log.debug("读取配置文件 {}", configurationFile.getAbsolutePath()); log.debug("读取配置文件 {}", configurationFile.getAbsolutePath());
Configuration configuration = YamlUtils.readYamlFile(configurationFile, Configuration.class); Configuration configuration = YamlUtils.readYamlFile(configurationFile, Configuration.class);
JSONObject fullConfiguration = YamlUtils.readYamlFile(configurationFile, JSONObject.class);
Configuration defaultConfiguration = TemplateFileUtils.readDefaultConfig(classLoader, DEFAULT_CONFIGURATION_FILE_PATH, Configuration.class); Configuration defaultConfiguration = TemplateFileUtils.readDefaultConfig(classLoader, DEFAULT_CONFIGURATION_FILE_PATH, Configuration.class);
// 配置文件自动更新 start // 配置文件自动更新 start
boolean isChanged = false; boolean isChanged = false;
@ -78,6 +80,12 @@ public class Main {
configuration.setCommandPrefix(defaultConfiguration.getCommandPrefix()); configuration.setCommandPrefix(defaultConfiguration.getCommandPrefix());
isChanged = true; isChanged = true;
} }
if (configuration.getNetwork() == null) {
configuration.setNetwork(defaultConfiguration.getNetwork());
configuration.getNetwork().setWebsocket(fullConfiguration.getString("websocket"));
isChanged = true;
}
// end
if (isChanged) { if (isChanged) {
log.warn("已自动升级配置文件请检查config.yml是否有错误的地方有则请修改"); log.warn("已自动升级配置文件请检查config.yml是否有错误的地方有则请修改");
YamlUtils.writeYamlFile(configurationFile, configuration); YamlUtils.writeYamlFile(configurationFile, configuration);
@ -95,7 +103,7 @@ public class Main {
public static URI getUriFromConfiguration(Configuration configuration) { public static URI getUriFromConfiguration(Configuration configuration) {
URI uri; URI uri;
try { try {
uri = new URI(configuration.getWebsocket()); uri = new URI(configuration.getNetwork().getWebsocket());
} catch (URISyntaxException e) { } catch (URISyntaxException e) {
return null; return null;
} }
@ -134,8 +142,8 @@ public class Main {
} }
public static WebSocketConnectionHandler createConnection(MyBot myBot, URI uri) { public static WebSocketConnectionHandler createConnection(MyBot myBot, URI uri) {
WebSocketConnectionHandler webSocketConnectionHandler = new WebSocketConnectionHandler(myBot); WebSocketConnectionHandler webSocketConnectionHandler = new WebSocketConnectionHandler(myBot, uri);
webSocketConnectionHandler.connect(uri); webSocketConnectionHandler.connect();
return webSocketConnectionHandler; return webSocketConnectionHandler;
} }

View File

@ -12,12 +12,13 @@ import lombok.Data;
*/ */
@Data @Data
public class Configuration implements IConfiguration { public class Configuration implements IConfiguration {
/** /**
* WebSocket连接URL * 网络相关配置
* * @since 2025/3/26 17:32 v1.0.7
* @since 2024/7/30 下午11:48 v0.0.1-dev
*/ */
private String websocket; private NetworkConfiguration network;
/** /**
* 命令前缀 * 命令前缀

View File

@ -0,0 +1,38 @@
package cn.wzpmc.configuration;
import cn.wzpmc.plugins.configuration.INetworkConfiguration;
import lombok.Data;
/**
* @author wzp
* @since 2025/3/26 17:29
* @version 1.0.7
*/
@Data
public class NetworkConfiguration implements INetworkConfiguration {
/**
* WebSocket连接URL
* @since 2025/3/26 17:31 v1.0.7
*/
private String websocket;
/**
* 是否启用连接重试
* @since 2025/3/26 17:31 v1.0.7
*/
private Boolean retry;
/**
* 重试最大次数-1为无限
* @since 2025/3/26 17:31 v1.0.7
*/
private Integer maxRetryCount;
/**
* 获取重试间隔单位毫秒
* @since 2025/3/26 17:31 v1.0.7
*/
private Long retryInterval;
@Override
public Boolean isRetry() {
return this.retry;
}
}

View File

@ -1,8 +1,10 @@
package cn.wzpmc.console; package cn.wzpmc.console;
import cn.wzpmc.entities.api.ApiResponseRequired;
import cn.wzpmc.entities.user.bot.MyBot; import cn.wzpmc.entities.user.bot.MyBot;
import cn.wzpmc.network.WebSocketConnectionHandler; import cn.wzpmc.network.WebSocketConnectionHandler;
import cn.wzpmc.plugins.CommandManager; import cn.wzpmc.plugins.CommandManager;
import cn.wzpmc.utils.json.action.ActionReader;
import lombok.Getter; import lombok.Getter;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2; import lombok.extern.log4j.Log4j2;
@ -47,12 +49,16 @@ public class MyBotConsole extends SimpleTerminalConsole {
@Override @Override
public void shutdown() { public void shutdown() {
this.webSocketConnectionHandler.kill(); this.webSocketConnectionHandler.kill();
for (ApiResponseRequired<?, ?> value : ActionReader.tasks.values()) {
value.getFuture().complete(null);
}
running = false; running = false;
} }
@Override @Override
public void start() { public void start() {
this.bot.setConsole(this); this.bot.setConsole(this);
if (this.bot.isShutdown()) return;
super.start(); super.start();
} }
} }

View File

@ -50,6 +50,8 @@ public class MyBot extends IBot {
private final Configuration configuration; private final Configuration configuration;
private final CommandManager commandManager = new CommandManager(this); private final CommandManager commandManager = new CommandManager(this);
private final PluginManager pluginManager = new PluginManager(); private final PluginManager pluginManager = new PluginManager();
@Setter
private boolean shutdown = false;
private final IncreasbleHashMap<Class<? extends Event>, EventHandlerMethod> events = new IncreasbleHashMap<>(); private final IncreasbleHashMap<Class<? extends Event>, EventHandlerMethod> events = new IncreasbleHashMap<>();
@Getter @Getter
private final Ops ops; private final Ops ops;

View File

@ -37,7 +37,6 @@ public class HandshakePacketHandler extends SimpleChannelInboundHandler<FullHttp
handshaker.finishHandshake(channelHandlerContext.channel(), fullHttpResponse); handshaker.finishHandshake(channelHandlerContext.channel(), fullHttpResponse);
this.handshakeFuture.complete(true); this.handshakeFuture.complete(true);
log.debug("握手成功"); log.debug("握手成功");
log.info("连接服务器成功!");
} }
} }
} }

View File

@ -32,6 +32,8 @@ import java.util.concurrent.Executors;
public class PacketHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> { public class PacketHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {
private final IBot bot; private final IBot bot;
private final ExecutorService threadPool = Executors.newFixedThreadPool(4); private final ExecutorService threadPool = Executors.newFixedThreadPool(4);
private final Runnable retryFunction;
@Override @Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, TextWebSocketFrame webSocketFrame) { protected void channelRead0(ChannelHandlerContext channelHandlerContext, TextWebSocketFrame webSocketFrame) {
@ -105,4 +107,10 @@ public class PacketHandler extends SimpleChannelInboundHandler<TextWebSocketFram
public <REQUEST, RESPONSE> void registerResponse(UUID echo, CompletableFuture<ActionResponse<RESPONSE>> responsePromise, Action<REQUEST, RESPONSE> request) { public <REQUEST, RESPONSE> void registerResponse(UUID echo, CompletableFuture<ActionResponse<RESPONSE>> responsePromise, Action<REQUEST, RESPONSE> request) {
ActionReader.tasks.put(echo, new ApiResponseRequired<>(responsePromise, request)); ActionReader.tasks.put(echo, new ApiResponseRequired<>(responsePromise, request));
} }
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
log.info("与服务器断开连接!");
retryFunction.run();
}
} }

View File

@ -2,7 +2,11 @@ package cn.wzpmc.network;
import cn.wzpmc.api.Action; import cn.wzpmc.api.Action;
import cn.wzpmc.api.ActionResponse; import cn.wzpmc.api.ActionResponse;
import cn.wzpmc.user.IBot; import cn.wzpmc.console.MyBotConsole;
import cn.wzpmc.entities.api.ApiResponseRequired;
import cn.wzpmc.entities.user.bot.MyBot;
import cn.wzpmc.plugins.configuration.INetworkConfiguration;
import cn.wzpmc.utils.json.action.ActionReader;
import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSON;
import io.netty.bootstrap.Bootstrap; import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelFuture;
@ -32,26 +36,70 @@ import java.util.concurrent.ExecutionException;
@RequiredArgsConstructor @RequiredArgsConstructor
public class WebSocketConnectionHandler { public class WebSocketConnectionHandler {
private final EventLoopGroup eventLoopGroup = new NioEventLoopGroup(); private final EventLoopGroup eventLoopGroup = new NioEventLoopGroup();
private final IBot bot; private final MyBot bot;
/**
* websocket连接地址
* @since 2025/3/26 17:56 v1.0.7
*/
private final URI websocket;
private ChannelFuture channelFuture; private ChannelFuture channelFuture;
private PacketHandler packetHandler; private PacketHandler packetHandler;
private HandshakePacketHandler handshakePacketHandler; private HandshakePacketHandler handshakePacketHandler;
private int currentRetryCount = 0;
private void tryReconnect() {
INetworkConfiguration network = bot.getConfiguration().getNetwork();
if (!network.isRetry()) {
this.quit();
return;
}
Integer maxRetryCount = network.getMaxRetryCount();
if (maxRetryCount != 0 && currentRetryCount >= maxRetryCount) {
this.quit();
return;
}
this.currentRetryCount++;
log.info("尝试重连第{}次", currentRetryCount);
this.connect();
}
private void quit() {
for (ApiResponseRequired<?, ?> value : ActionReader.tasks.values()) {
value.getFuture().obtrudeException(new InterruptedException());
}
this.handshakePacketHandler.getHandshakeFuture().obtrudeException(new InterruptedException());
MyBotConsole console = bot.getConsole();
bot.setShutdown(true);
if (console == null) {
this.eventLoopGroup.shutdownGracefully();
return;
}
console.shutdown();
}
/** /**
* 建立连接 * 建立连接
*
* @param websocket websocket连接地址
* @author wzp * @author wzp
* @since 2024/7/30 下午11:55 v0.0.1-dev * @since 2024/7/30 下午11:55 v0.0.1-dev
*/ */
public void connect(URI websocket) { public void connect() {
log.info("正在连接websocket"); log.info("正在连接websocket");
Bootstrap bootstrap = new Bootstrap(); Bootstrap bootstrap = new Bootstrap();
WebSocketClientHandshaker clientHandshaker = WebSocketClientHandshakerFactory.newHandshaker(websocket, WebSocketVersion.V13, null, false, new DefaultHttpHeaders(), 65536 * 100); WebSocketClientHandshaker clientHandshaker = WebSocketClientHandshakerFactory.newHandshaker(websocket, WebSocketVersion.V13, null, false, new DefaultHttpHeaders(), 65536 * 100);
this.handshakePacketHandler = new HandshakePacketHandler(clientHandshaker); this.handshakePacketHandler = new HandshakePacketHandler(clientHandshaker);
this.packetHandler = new PacketHandler(this.bot); this.packetHandler = new PacketHandler(this.bot, this::tryReconnect);
bootstrap.group(eventLoopGroup).channel(NioSocketChannel.class).handler(new WebSocketChannelInitializer(this.packetHandler, this.handshakePacketHandler)); bootstrap.group(eventLoopGroup).channel(NioSocketChannel.class).handler(new WebSocketChannelInitializer(this.packetHandler, this.handshakePacketHandler));
this.channelFuture = bootstrap.connect(websocket.getHost(), websocket.getPort()); this.channelFuture = bootstrap.connect(websocket.getHost(), websocket.getPort());
this.channelFuture.addListener(future -> {
if (!future.isSuccess()) {
log.info("连接失败!");
this.tryReconnect();
} else {
log.info("连接成功!");
this.currentRetryCount = 0;
}
});
} }
/** /**
@ -81,6 +129,7 @@ public class WebSocketConnectionHandler {
this.handshakePacketHandler.getHandshakeFuture().get(); this.handshakePacketHandler.getHandshakeFuture().get();
} catch (ExecutionException e) { } catch (ExecutionException e) {
log.error(e); log.error(e);
return null;
} }
CompletableFuture<ActionResponse<RESPONSE>> responsePromise = new CompletableFuture<>(); CompletableFuture<ActionResponse<RESPONSE>> responsePromise = new CompletableFuture<>();
packetHandler.registerResponse(request.getEcho(), responsePromise, request); packetHandler.registerResponse(request.getEcho(), responsePromise, request);

View File

@ -1,6 +1,7 @@
package cn.wzpmc.utils; package cn.wzpmc.utils;
import cn.wzpmc.configuration.Configuration; import cn.wzpmc.configuration.Configuration;
import com.alibaba.fastjson2.JSONObject;
import lombok.extern.log4j.Log4j2; import lombok.extern.log4j.Log4j2;
import org.yaml.snakeyaml.DumperOptions; import org.yaml.snakeyaml.DumperOptions;
import org.yaml.snakeyaml.Yaml; import org.yaml.snakeyaml.Yaml;
@ -30,7 +31,8 @@ public class YamlUtils {
*/ */
public static <T> T readYamlStream(InputStream is, Class<T> clazz) { public static <T> T readYamlStream(InputStream is, Class<T> clazz) {
Yaml yaml = new Yaml(); Yaml yaml = new Yaml();
return yaml.loadAs(is, clazz); JSONObject json = yaml.loadAs(is, JSONObject.class);
return json.to(clazz);
} }
/** /**

View File

@ -1,4 +1,8 @@
websocket: "<Your WebSocket connection link, e.g: ws://127.0.0.1:3001/>" network:
websocket: "<Your WebSocket connection link, e.g: ws://127.0.0.1:3001/>"
retry: true
maxRetryCount: 3
retryInterval: 10000
commandPrefix: "/" commandPrefix: "/"
authorization: authorization:
enable: false enable: false