Add multi server run console command

This commit is contained in:
方块君 2022-07-26 19:14:49 +08:00
parent 37dcd0f1a7
commit 4d08acd084
19 changed files with 509 additions and 149 deletions

195
README.md
View File

@ -5,8 +5,10 @@
一个为第三方客户端开放GC命令执行接口的插件
## 服务端安装
1. 在 [Release](https://github.com/jie65535/gc-opencommand-plugin/releases) 下载 `jar`
2. 放入 `plugins` 文件夹即可
> 注意,如果出现以下错误:
> ```log
> INFO:PluginManager Enabling plugin: opencommand-plugin
@ -17,24 +19,28 @@
> 请使用v1.2.1版本插件,因为该报错表示你的服务端是旧版!
## 控制台连接
1. 首次启动时,会在 `plugins` 目录下生成一个 `opencommand-plugin` 目录,打开并编辑 `config.json`
2. 设置 `consoleToken` 的值为你的连接秘钥建议使用至少32字符的长随机字符串。
3. 重新启动服务端即可生效配置
4. 在客户端中选择控制台身份,并填写你的 `consoleToken` 即可以控制台身份运行指令
## 构建说明
1. 克隆仓库
2. 在目录下新建 `lib` 目录
3. 将 `grasscutter-1.1.x-dev.jar` 放入 `lib` 目录
4. `gradle build`
## 玩家使用流程
1. 在客户端中填写服务地址,确认是否支持
2. 填写UID发送验证码
3. 将游戏内收到的**4位整数验证码**填入客户端校验
4. 享受便利!
## 客户端请求流程
1. `ping` 确认是否支持 `opencommand` 插件
2. `sendCode` 向指定玩家发送验证码1分钟内不允许重发保存返回的 `token`
3. 使用 `token` 和**4位整数验证码**发送 `verify` 校验
@ -43,6 +49,7 @@
---
## `config.json`
```json
{
// 控制台连接令牌
@ -58,114 +65,210 @@
// 多服务器通信密钥
"socketToken": "",
// 多服务器Dispatch地址
"socketHost": "127.0.0.1"
"socketHost": "127.0.0.1",
// 多服务器显示名称
"socketDisplayName": ""
}
```
## API `/opencommand/api`
示例
```
https://127.0.0.1/opencommand/api
```
### Request 请求
```java
public final class JsonRequest {
public String token = "";
public String action = "";
public String server = "";
public Object data = null;
}
```
### Response 响应
```java
public final class JsonResponse {
public int retcode = 200;
public String message = "success";
public String message = "Success";
public Object data;
}
```
### Actions 动作
#### `测试连接`
##### Request
| 请求参数 | 请求数据 | 类型 |
| ------- | --------- | ------ |
| action | `ping` |`String`|
| 请求参数 | 请求数据 | 类型 |
|--------|--------|----------|
| action | `ping` | `String` |
##### Response
| 返回参数 | 返回数据 | 类型 |
| ------- | --------- | ------ |
| retcode | `200` |`String`|
| message | `success` |`String`|
| data | `null` |`null` |
| 返回参数 | 返回数据 | 类型 |
|---------|-----------|----------|
| retcode | `200` | `Int` |
| message | `Success` | `String` |
| data | `null` | `null` |
#### `获取在线玩家`
##### Request
| 请求参数 | 请求数据 | 类型 |
|--------|----------|----------|
| action | `online` | `String` |
##### Response
| 返回参数 | 返回数据 | 类型 |
|---------|---------------------------------|--------------|
| retcode | `200` | `Int` |
| message | `Success` | `String` |
| data | `{"count": 0, playerList": []}` | `JsonObject` |
#### `发送验证码`
##### Request
| 请求参数 | 请求数据 | 类型 |
| ------- | --------- | ------ |
| action | `sendCode`|`String`|
| data | `uid` |`Int` |
| 请求参数 | 请求数据 | 类型 |
|--------|------------|----------|
| action | `sendCode` | `String` |
| data | `uid` | `Int` |
##### Response
| 返回参数 | 返回数据 | 类型 |
| ------- | --------- | ------ |
| retcode | `200` |`String`|
| message | `success` |`String`|
| data | `token` |`String`|
| 返回参数 | 返回数据 | 类型 |
|---------|-----------|----------|
| retcode | `200` | `Int` |
| message | `Success` | `String` |
| data | `token` | `String` |
#### `验证验证码`
##### Request
| 请求参数 | 请求数据 | 类型 |
| ------- | --------- | ------ |
| action | `verify` |`String`|
| token | `token` |`String`|
| data | `code` |`Int` |
| 请求参数 | 请求数据 | 类型 |
|--------|----------|----------|
| action | `verify` | `String` |
| token | `token` | `String` |
| data | `code` | `Int` |
##### Response
成功
| 返回参数 | 返回数据 | 类型 |
| ------- | --------- | ------ |
| retcode | `200` |`String`|
| message | `success` |`String`|
| data | `null` | `null` |
| 返回参数 | 返回数据 | 类型 |
|---------|-----------|----------|
| retcode | `200` | `Int` |
| message | `Success` | `String` |
| data | `null` | `null` |
失败
| 返回参数 | 返回数据 | 类型 |
| ------- | -------------------- | ------ |
| retcode | `400` |`String`|
| message | `Verification failed`|`String`|
| data | `null` |`null` |
| 返回参数 | 返回数据 | 类型 |
|---------|-----------------------|----------|
| retcode | `400` | `Int` |
| message | `Verification failed` | `String` |
| data | `null` | `null` |
#### `执行命令`
##### Request
| 请求参数 | 请求数据 | 类型 |
| ------- | ----------- | ------ |
| action | `command` |`String`|
| token | `token` |`String`|
| data | `command` |`String`|
| 请求参数 | 请求数据 | 类型 |
|--------|-----------|----------|
| action | `command` | `String` |
| token | `token` | `String` |
| data | `command` | `String` |
##### Response
成功
| 返回参数 | 返回数据 | 类型 |
| ------- | ---------------- | ------ |
| retcode | `200` |`String`|
| message | `success` |`String`|
| data | `Command return` |`String`|
| 返回参数 | 返回数据 | 类型 |
|---------|------------------|----------|
| retcode | `200` | `Int` |
| message | `Success` | `String` |
| data | `Command return` | `String` |
### 执行控制台命令
#### `获取运行模式`
##### Request
| 请求参数 | 请求数据 | 类型 |
|--------|-----------|----------|
| action | `runmode` | `String` |
| token | `token` | `String` |
##### Response
成功
| 返回参数 | 返回数据 | 类型 |
|---------|-----------------------|----------|
| retcode | `200` | `Int` |
| message | `Success` | `String` |
| data | `1 (多服务器) / 0 (单服务器)` | `Int` |
#### `获取多服务器列表`
##### Request
| 请求参数 | 请求数据 | 类型 |
|--------|----------|----------|
| action | `server` | `String` |
| token | `token` | `String` |
##### Response
成功
| 返回参数 | 返回数据 | 类型 |
|---------|-----------|--------------|
| retcode | `200` | `Int` |
| message | `Success` | `String` |
| data | `{}` | `JsonObject` |
```json
{
"retcode": 200,
"message": "success",
"data": {
// 服务器 UUID
"13d82d0d-c7d9-47dd-830c-76588006ef6e": "2.8.0 服务器",
"e6b83224-a761-4023-be57-e054c5bb823a": "2.8.0 开发服务器"
}
}
```
#### `执行命令`
##### Request
| 请求参数 | 请求数据 | 类型 |
|--------|-----------|----------|
| action | `command` | `String` |
| token | `token` | `String` |
| server | `UUID` | `String` |
| data | `command` | `String` |
##### Response
成功
| 返回参数 | 返回数据 | 类型 |
|---------|------------------|----------|
| retcode | `200` | `Int` |
| message | `Success` | `String` |
| data | `Command return` | `String` |

View File

@ -60,6 +60,7 @@ https://127.0.0.1/opencommand/api
public final class JsonRequest {
public String token = "";
public String action = "";
public Seting server = "";
public Object data = null;
}
```
@ -68,7 +69,7 @@ public final class JsonRequest {
```java
public final class JsonResponse {
public int retcode = 200;
public String message = "success";
public String message = "Success";
public Object data;
}
```
@ -78,79 +79,168 @@ public final class JsonResponse {
##### Request
| Request | Request data | type |
| ------- | -------------- | ------ |
| action | `ping` |`String`|
| Request | Request data | type |
|---------|--------------|----------|
| action | `ping` | `String` |
##### Response
| Response | Response data | type |
| -------- | --------------- | ------ |
| retcode | `200` |`String`|
| message | `success` |`String`|
| data | `null` |`null` |
| Response | Response data | type |
|----------|---------------|----------|
| retcode | `200` | `String` |
| message | `Success` | `String` |
| data | `null` | `null` |
#### `Get online players`
##### Request
| Request | Request data | type |
|---------|--------------|----------|
| action | `online` | `String` |
##### Response
| Response | Response data | type |
|----------|---------------------------------|--------------|
| retcode | `200` | `String` |
| message | `Success` | `String` |
| data | `{"count": 0, playerList": []}` | `JsonObject` |
#### `Send code`
##### Request
| Request | Request data | type |
| ------- | -------------- | ------ |
| action | `sendCode` |`String`|
| data | `uid` |`Int` |
| Request | Request data | type |
|---------|--------------|----------|
| action | `sendCode` | `String` |
| data | `uid` | `Int` |
##### Response
| Response | Response data | type |
| -------- | --------------- | ------ |
| retcode | `200` |`String`|
| message | `success` |`String`|
| data | `token` |`String`|
| Response | Response data | type |
|----------|---------------|----------|
| retcode | `200` | `String` |
| message | `Success` | `String` |
| data | `token` | `String` |
#### `Verify code`
##### Request
| Request | Request data | type |
| ------- | -------------- | ------ |
| action | `verify` |`String`|
| token | `token` |`String`|
| data | `code` |`Int` |
| Request | Request data | type |
|---------|--------------|----------|
| action | `verify` | `String` |
| token | `token` | `String` |
| data | `code` | `Int` |
##### Response
Success
| Response | Response data | type |
| -------- | -------------- | ------ |
| retcode | `200` |`String`|
| message | `success` |`String`|
| data | `null` | `null` |
| Response | Response data | type |
|----------|---------------|----------|
| retcode | `200` | `String` |
| message | `Success` | `String` |
| data | `null` | `null` |
Failed
| Response | Response data | type |
| -------- | -------------------- | ------ |
| retcode | `400` |`String`|
| message | `Verification failed`|`String`|
| data | `null` |`null` |
| Response | Response data | type |
|----------|-----------------------|----------|
| retcode | `400` | `String` |
| message | `Verification failed` | `String` |
| data | `null` | `null` |
#### `Run command`
##### Request
| Request | Request data | type |
| ------- | -------------- | ------ |
| action | `command` |`String`|
| token | `token` |`String`|
| data | `command` |`String`|
| Request | Request data | type |
|---------|--------------|----------|
| action | `command` | `String` |
| token | `token` | `String` |
| data | `command` | `String` |
##### Response
Success
| Response | Response data | type |
| -------- | ------------------ | ------ |
| retcode | `200` |`String`|
| message | `success` |`String`|
| data | `Command return` |`String`|
| Response | Response data | type |
|----------|------------------|----------|
| retcode | `200` | `String` |
| message | `Success` | `String` |
| data | `Command return` | `String` |
### Run console command
#### `Get run mode`
##### Request
| Request | Request data | Type |
|---------|--------------|----------|
| action | `runmode` | `String` |
| token | `token` | `String` |
##### Response
Success
| Request | Response data | Type |
|---------|----------------------------------------|----------|
| retcode | `200` | `Int` |
| message | `Success` | `String` |
| data | `1 (Multi server) / 0 (Single server)` | `Int` |
#### `Get mulit server list`
##### Request
| Request | Request data | Type |
|---------|--------------|----------|
| action | `server` | `String` |
| token | `token` | `String` |
##### Response
Success
| Request | Response data | Type |
|---------|---------------|--------------|
| retcode | `200` | `Int` |
| message | `Success` | `String` |
| data | `{}` | `JsonObject` |
```json
{
"retcode": 200,
"message": "success",
"data": {
// Server UUID
"13d82d0d-c7d9-47dd-830c-76588006ef6e": "2.8.0 Server",
"e6b83224-a761-4023-be57-e054c5bb823a": "2.8.0 Dev server"
}
}
```
#### `Run command`
##### Request
| Request | Request data | Type |
|---------|--------------|----------|
| action | `command` | `String` |
| token | `token` | `String` |
| server | `UUID` | `String` |
| data | `command` | `String` |
##### Response
Success
| Request | Response data | Type |
|---------|------------------|----------|
| retcode | `200` | `Int` |
| message | `Success` | `String` |
| data | `Command return` | `String` |

View File

@ -15,9 +15,11 @@ import java.util.ArrayList;
public final class EventListeners {
private static MessageHandler consoleMessageHandler;
public static void setConsoleMessageHandler(MessageHandler handler) {
consoleMessageHandler = handler;
}
public static void onCommandResponse(ReceiveCommandFeedbackEvent event) {
if (consoleMessageHandler != null && event.getPlayer() == null) {
consoleMessageHandler.setMessage(event.getMessage());

View File

@ -25,4 +25,5 @@ public class OpenCommandConfig {
public int socketPort = 5746;
public String socketToken = "";
public String socketHost = "127.0.0.1";
public String socketDisplayName = "";
}

View File

@ -19,6 +19,7 @@ package com.github.jie65535.opencommand;
import com.github.jie65535.opencommand.json.JsonRequest;
import com.github.jie65535.opencommand.json.JsonResponse;
import com.github.jie65535.opencommand.socket.SocketData;
import emu.grasscutter.command.CommandMap;
import emu.grasscutter.server.http.Router;
import emu.grasscutter.utils.Crypto;
@ -31,6 +32,7 @@ import io.javalin.Javalin;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
@ -85,6 +87,11 @@ public final class OpenCommandHandler implements Router {
} else if (req.action.equals("ping")) {
response.json(new JsonResponse());
return;
} else if (req.action.equals("online")) {
var p = new ArrayList<String>();
plugin.getServer().getPlayers().forEach((uid, player) -> p.add(player.getNickname()));
response.json(new JsonResponse(200, "Success", new SocketData.OnlinePlayer(p)));
return;
}
// token is required
@ -118,6 +125,9 @@ public final class OpenCommandHandler implements Router {
}
}
return;
} else if (req.action.equals("runmode")) {
response.json(new JsonResponse(200, "Success", 0));
return;
}
} else if (codes.containsKey(req.token)) {
if (req.action.equals("verify")) {

View File

@ -23,6 +23,7 @@ import com.github.jie65535.opencommand.socket.SocketData;
import com.github.jie65535.opencommand.socket.SocketDataWait;
import com.github.jie65535.opencommand.socket.SocketServer;
import com.github.jie65535.opencommand.socket.packet.HttpPacket;
import com.github.jie65535.opencommand.socket.packet.RunConsoleCommand;
import com.github.jie65535.opencommand.socket.packet.player.Player;
import com.github.jie65535.opencommand.socket.packet.player.PlayerEnum;
import emu.grasscutter.command.CommandMap;
@ -92,6 +93,9 @@ public final class OpenCommandOnlyHttpHandler implements Router {
} else if (req.action.equals("ping")) {
response.json(new JsonResponse());
return;
} else if (req.action.equals("online")) {
response.json(new JsonResponse(200, "Success", SocketData.getOnlinePlayer()));
return;
}
// token is required
@ -110,20 +114,40 @@ public final class OpenCommandOnlyHttpHandler implements Router {
response.json(new JsonResponse());
return;
} else if (req.action.equals("command")) {
//noinspection SynchronizationOnLocalVariableOrMethodParameter
synchronized (plugin) {
try {
plugin.getLogger().info(String.format("IP: %s run command in console > %s", request.ip(), req.data));
var resultCollector = new MessageHandler();
EventListeners.setConsoleMessageHandler(resultCollector);
CommandMap.getInstance().invoke(null, null, req.data.toString());
response.json(new JsonResponse(resultCollector.getMessage()));
} catch (Exception e) {
plugin.getLogger().warn("Run command failed.", e);
EventListeners.setConsoleMessageHandler(null);
response.json(new JsonResponse(500, "error", e.getLocalizedMessage()));
}
var server = SocketServer.getClientInfoByUuid(req.server);
if (server == null) {
response.json(new JsonResponse(404, "Server Not Found."));
return;
}
plugin.getLogger().info(String.format("IP: %s run command in console > %s", request.ip(), req.data));
var wait = new SocketDataWait<HttpPacket>(2000L) {
@Override
public void run() {
}
@Override
public HttpPacket initData(HttpPacket data) {
return data;
}
@Override
public void timeout() {
}
};
SocketServer.sendPacketAndWait(server.ip, new RunConsoleCommand(req.data.toString()), wait);
var data = wait.getData();
if (data == null) {
response.json(new JsonResponse(408, "Timeout"));
return;
}
response.json(new JsonResponse(data.code, data.message, data.data));
return;
} else if (req.action.equals("server")) {
response.json(new JsonResponse(200, "Success", SocketServer.getOnlineClient()));
return;
} else if (req.action.equals("runmode")) {
response.json(new JsonResponse(200, "Success", 1));
return;
}
} else if (codes.containsKey(req.token)) {
@ -143,7 +167,8 @@ public final class OpenCommandOnlyHttpHandler implements Router {
if (req.action.equals("command")) {
SocketDataWait<HttpPacket> socketDataWait = new SocketDataWait<>(1000L * 10L) {
@Override
public void run() {}
public void run() {
}
@Override
public HttpPacket initData(HttpPacket data) {
@ -152,7 +177,6 @@ public final class OpenCommandOnlyHttpHandler implements Router {
@Override
public void timeout() {
response.json(new JsonResponse(408, "Wait server timeout"));
}
};
@ -173,7 +197,7 @@ public final class OpenCommandOnlyHttpHandler implements Router {
HttpPacket httpPacket = socketDataWait.getData();
if (httpPacket == null) {
response.json(new JsonResponse(500, "error", "Server connect failed."));
response.json(new JsonResponse(500, "error", "Wait timeout"));
return;
}
response.json(new JsonResponse(httpPacket.code, httpPacket.message));

View File

@ -35,7 +35,10 @@ import java.io.IOException;
public final class OpenCommandPlugin extends Plugin {
private static OpenCommandPlugin instance;
public static OpenCommandPlugin getInstance() { return instance; }
public static OpenCommandPlugin getInstance() {
return instance;
}
private OpenCommandConfig config;
@ -82,19 +85,19 @@ public final class OpenCommandPlugin extends Plugin {
var configFile = new File(getDataFolder(), "config.json");
if (!configFile.exists()) {
config = new OpenCommandConfig();
try (var file = new FileWriter(configFile)){
try (var file = new FileWriter(configFile)) {
file.write(Grasscutter.getGsonFactory().toJson(config));
} catch (IOException e) {
getLogger().error("Unable to write to config file.");
getLogger().error("[OpenCommand] Unable to write to config file.");
} catch (Exception e) {
getLogger().error("Unable to save config file.");
getLogger().error("[OpenCommand] Unable to save config file.");
}
} else {
try (var file = new FileReader(configFile)) {
config = Grasscutter.getGsonFactory().fromJson(file, OpenCommandConfig.class);
} catch (Exception exception) {
config = new OpenCommandConfig();
getLogger().error("There was an error while trying to load the configuration from config.json. Please make sure that there are no syntax errors. If you want to start with a default configuration, delete your existing config.json.");
getLogger().error("[OpenCommand] There was an error while trying to load the configuration from config.json. Please make sure that there are no syntax errors. If you want to start with a default configuration, delete your existing config.json.");
}
}
// 启动Socket
@ -110,7 +113,7 @@ public final class OpenCommandPlugin extends Plugin {
try {
SocketServer.startServer();
} catch (IOException e) {
getLogger().error("Unable to start socket server.", e);
getLogger().error("[OpenCommand] Unable to start socket server.", e);
}
}
}

View File

@ -20,5 +20,6 @@ package com.github.jie65535.opencommand.json;
public final class JsonRequest {
public String token = "";
public String action = "";
public String server = "";
public Object data = null;
}

View File

@ -19,7 +19,7 @@ package com.github.jie65535.opencommand.json;
public final class JsonResponse {
public int retcode = 200;
public String message = "success";
public String message = "Success";
public Object data;
public JsonResponse() {

View File

@ -0,0 +1,14 @@
package com.github.jie65535.opencommand.socket;
public class ClientInfo {
public final String uuid;
public final SocketServer.ClientThread clientThread;
public final String ip;
public ClientInfo(String uuid, String ip, SocketServer.ClientThread clientThread) {
this.uuid = uuid;
this.clientThread = clientThread;
this.ip = ip;
}
}

View File

@ -1,5 +1,6 @@
package com.github.jie65535.opencommand.socket;
import com.github.jie65535.opencommand.EventListeners;
import com.github.jie65535.opencommand.OpenCommandConfig;
import com.github.jie65535.opencommand.OpenCommandPlugin;
import com.github.jie65535.opencommand.socket.packet.*;
@ -33,6 +34,14 @@ public class SocketClient {
// 连接服务器
public static void connectServer() {
if (connect) return;
if (clientThread != null) {
mLogger.warn("[OpenCommand] Retry connecting to the server after 15 seconds");
try {
Thread.sleep(15000);
} catch (InterruptedException ex) {
throw new RuntimeException(ex);
}
}
OpenCommandConfig config = OpenCommandPlugin.getInstance().getConfig();
mLogger = OpenCommandPlugin.getInstance().getLogger();
clientThread = new ClientThread(config.socketHost, config.socketPort);
@ -160,6 +169,24 @@ public class SocketClient {
}
}
break;
case RunConsoleCommand:
var consoleCommand = Grasscutter.getGsonFactory().fromJson(packet.data, RunConsoleCommand.class);
var plugin = OpenCommandPlugin.getInstance();
//noinspection SynchronizationOnLocalVariableOrMethodParameter
synchronized (plugin) {
try {
var resultCollector = new MessageHandler();
EventListeners.setConsoleMessageHandler(resultCollector);
CommandMap.getInstance().invoke(null, null, consoleCommand.command);
sendPacket(new HttpPacket(resultCollector.getMessage()), packet.packetID);
} catch (Exception e) {
mLogger.warn("[OpenCommand] Run command failed.", e);
EventListeners.setConsoleMessageHandler(null);
sendPacket(new HttpPacket(500, "error", e.getLocalizedMessage()), packet.packetID);
} finally {
EventListeners.setConsoleMessageHandler(null);
}
}
}
} catch (Throwable e) {
e.printStackTrace();
@ -206,18 +233,12 @@ public class SocketClient {
socket = new Socket(ip, port);
os = socket.getOutputStream();
mLogger.info("[OpenCommand]Connect to server: " + ip + ":" + port);
SocketClient.sendPacket(new AuthPacket(OpenCommandPlugin.getInstance().getConfig().socketToken));
mLogger.info("[OpenCommand] Connect to server: " + ip + ":" + port);
SocketClient.sendPacket(new AuthPacket(OpenCommandPlugin.getInstance().getConfig().socketToken, OpenCommandPlugin.getInstance().getConfig().socketDisplayName));
receiveThread = new ReceiveThread(socket);
} catch (IOException e) {
connect = false;
mLogger.warn("[OpenCommand]Connect to server failed: " + ip + ":" + port);
mLogger.warn("[OpenCommand] Retry connecting to the server after 15 seconds");
try {
Thread.sleep(15000);
} catch (InterruptedException ex) {
throw new RuntimeException(ex);
}
mLogger.warn("[OpenCommand] Connect to server failed: " + ip + ":" + port);
connectServer();
}
}

View File

@ -2,6 +2,7 @@ package com.github.jie65535.opencommand.socket;
import com.github.jie65535.opencommand.socket.packet.player.PlayerList;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.concurrent.atomic.AtomicReference;
@ -27,4 +28,21 @@ public class SocketData {
});
return ret.get();
}
public static OnlinePlayer getOnlinePlayer() {
ArrayList<String> player = new ArrayList<>();
playerList.forEach((address, playerMap) -> playerMap.playerMap.forEach((uid, name) -> player.add(name)));
return new OnlinePlayer(player);
}
public static class OnlinePlayer {
public int count;
public ArrayList<String> playerList;
public OnlinePlayer(ArrayList<String> playerList) {
this.playerList = playerList;
this.count = playerList.size();
}
}
}

View File

@ -14,16 +14,34 @@ import java.net.Socket;
import java.util.HashMap;
import java.util.Timer;
import java.util.TimerTask;
import java.util.UUID;
// Socket 服务器
public class SocketServer {
// 客户端超时时间
private static final int TIMEOUT = 5000;
private static final HashMap<String, ClientThread> clientList = new HashMap<>();
private static final HashMap<String, ClientInfo> clientList = new HashMap<>();
private static final HashMap<String, Integer> clientTimeout = new HashMap<>();
private static Logger mLogger;
public static HashMap<String, String> getOnlineClient() {
HashMap<String, String> onlineClient = new HashMap<>();
for (var key : clientList.entrySet()) {
onlineClient.put(key.getValue().uuid, key.getValue().clientThread.getDisplayName());
}
return onlineClient;
}
public static ClientInfo getClientInfoByUuid(String uuid) {
for (var key : clientList.entrySet()) {
if (key.getValue().uuid.equals(uuid)) {
return key.getValue();
}
}
return null;
}
public static void startServer() throws IOException {
int port = OpenCommandPlugin.getInstance().getConfig().socketPort;
mLogger = OpenCommandPlugin.getInstance().getLogger();
@ -34,7 +52,7 @@ public class SocketServer {
// 向全部客户端发送数据
public static boolean sendAllPacket(BasePacket packet) {
var p = SocketUtils.getPacket(packet);
HashMap<String, ClientThread> old = (HashMap<String, ClientThread>) clientList.clone();
HashMap<String, ClientThread> old = (HashMap<String, ClientThread>) clientList.clone();
for (var client : old.entrySet()) {
if (!client.getValue().sendPacket(p)) {
mLogger.warn("[OpenCommand] Send packet to client {} failed", client.getKey());
@ -49,7 +67,21 @@ public class SocketServer {
var p = SocketUtils.getPacket(packet);
var client = clientList.get(address);
if (client != null) {
if (client.sendPacket(p)) {
if (client.clientThread.sendPacket(p)) {
return true;
}
mLogger.warn("[OpenCommand] Send packet to client {} failed", address);
clientList.remove(address);
}
return false;
}
public static boolean sendPacketAndWait(String address, BasePacket packet, SocketDataWait<?> wait) {
var p = SocketUtils.getPacketAndPackID(packet);
var client = clientList.get(address);
if (client != null) {
wait.uid = p.get(0);
if (client.clientThread.sendPacket(p.get(1), wait)) {
return true;
}
mLogger.warn("[OpenCommand] Send packet to client {} failed", address);
@ -66,7 +98,7 @@ public class SocketServer {
var client = clientList.get(clientID);
if (client != null) {
socketDataWait.uid = p.get(0);
if (!client.sendPacket(p.get(1), socketDataWait)) {
if (!client.clientThread.sendPacket(p.get(1), socketDataWait)) {
mLogger.warn("[OpenCommand] Send packet to client {} failed", clientID);
clientList.remove(clientID);
return false;
@ -97,13 +129,14 @@ public class SocketServer {
}
// 客户端数据包处理
private static class ClientThread extends Thread {
static class ClientThread extends Thread {
private final Socket socket;
private InputStream is;
private OutputStream os;
private final String address;
private final String token;
private boolean auth = false;
private String displayName = "";
private final HashMap<String, SocketDataWait<?>> socketDataWaitList = new HashMap<>();
@ -147,11 +180,12 @@ public class SocketServer {
String data = SocketUtils.readString(is);
Packet packet = Grasscutter.getGsonFactory().fromJson(data, Packet.class);
if (packet.type == PacketEnum.AuthPacket) {
AuthPacket authPacket = Grasscutter.getGsonFactory().fromJson(data, AuthPacket.class);
AuthPacket authPacket = Grasscutter.getGsonFactory().fromJson(packet.data, AuthPacket.class);
if (authPacket.token.equals(token)) {
auth = true;
mLogger.info("[OpenCommand] Client {} auth success", address);
clientList.put(address, this);
displayName = authPacket.displayName;
mLogger.info("[OpenCommand] Client {} auth success, name: {}", address, displayName);
clientList.put(address, new ClientInfo(UUID.randomUUID().toString(), address, this));
clientTimeout.put(address, 0);
} else {
mLogger.warn("[OpenCommand] Client {} auth failed", address);
@ -196,6 +230,10 @@ public class SocketServer {
}
}
}
public String getDisplayName() {
return displayName;
}
}
// 等待客户端连接

View File

@ -17,12 +17,12 @@ public class SocketUtils {
/**
* 获取打包后的数据包
*
* @param bPacket 数据包
* @return 打包后的数据包
*/
public static String getPacket(BasePacket bPacket) {
Packet packet = new Packet();
packet.token = OpenCommandPlugin.getInstance().getConfig().socketToken;
packet.type = bPacket.getType();
packet.data = bPacket.getPacket();
packet.packetID = UUID.randomUUID().toString();
@ -31,12 +31,12 @@ public class SocketUtils {
/**
* 获取打包后的数据包
*
* @param bPacket BasePacket
* @return list[0] 是包ID, list[1] 是数据包
*/
public static List<String> getPacketAndPackID(BasePacket bPacket) {
Packet packet = new Packet();
packet.token = OpenCommandPlugin.getInstance().getConfig().socketToken;
packet.type = bPacket.getType();
packet.data = bPacket.getPacket();
packet.packetID = UUID.randomUUID().toString();
@ -49,13 +49,13 @@ public class SocketUtils {
/**
* 获取打包后的数据包
* @param bPacket 数据包
*
* @param bPacket 数据包
* @param packetID 数据包ID
* @return 打包后的数据包
*/
public static String getPacketAndPackID(BasePacket bPacket, String packetID) {
Packet packet = new Packet();
packet.token = OpenCommandPlugin.getInstance().getConfig().socketToken;
packet.type = bPacket.getType();
packet.data = bPacket.getPacket();
packet.packetID = packetID;
@ -64,6 +64,7 @@ public class SocketUtils {
/**
* 读整数
*
* @param is 输入流
* @return 整数
*/
@ -77,32 +78,34 @@ public class SocketUtils {
e.printStackTrace();
}
return values[0]<<24 | values[1]<<16 | values[2]<<8 | values[3];
return values[0] << 24 | values[1] << 16 | values[2] << 8 | values[3];
}
/**
* 写整数
* @param os 输出流
*
* @param os 输出流
* @param value 整数
*/
public static void writeInt(OutputStream os, int value) {
int[] values = new int[4];
values[0] = (value>>24)&0xFF;
values[1] = (value>>16)&0xFF;
values[2] = (value>>8)&0xFF;
values[3] = (value)&0xFF;
values[0] = (value >> 24) & 0xFF;
values[1] = (value >> 16) & 0xFF;
values[2] = (value >> 8) & 0xFF;
values[3] = (value) & 0xFF;
try{
try {
for (int i = 0; i < 4; i++) {
os.write(values[i]);
}
}catch (IOException e){
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 读字符串
*
* @param is 输入流
* @return 字符串
*/
@ -120,15 +123,16 @@ public class SocketUtils {
/**
* 写字符串
*
* @param os 输出流
* @param s 字符串
* @param s 字符串
* @return 是否成功
*/
public static boolean writeString(OutputStream os,String s) {
public static boolean writeString(OutputStream os, String s) {
try {
byte[] bytes = s.getBytes();
int len = bytes.length;
writeInt(os,len);
writeInt(os, len);
os.write(bytes);
return true;
} catch (IOException e) {

View File

@ -4,8 +4,10 @@ import emu.grasscutter.Grasscutter;
public class AuthPacket extends BasePacket {
public String token;
public String displayName;
public AuthPacket(String token) {
public AuthPacket(String token, String displayName) {
this.displayName = displayName;
this.token = token;
}
@ -18,4 +20,9 @@ public class AuthPacket extends BasePacket {
public PacketEnum getType() {
return PacketEnum.AuthPacket;
}
@Override
public String toString() {
return "AuthPacket [token=" + token + ", displayName=" + displayName + "]";
}
}

View File

@ -4,8 +4,8 @@ import emu.grasscutter.Grasscutter;
// http返回数据
public class HttpPacket extends BasePacket {
public int code;
public String message;
public int code = 200;
public String message = "Success";
public String data;
public HttpPacket(int code, String message, String data) {
@ -18,6 +18,9 @@ public class HttpPacket extends BasePacket {
this.code = code;
this.message = message;
}
public HttpPacket(String data) {
this.data = data;
}
@Override
public String getPacket() {

View File

@ -2,13 +2,12 @@ package com.github.jie65535.opencommand.socket.packet;
// 数据包结构
public class Packet {
public String token;
public PacketEnum type;
public String data;
public String packetID;
@Override
public String toString() {
return "Packet [token=" + token + ", type=" + type + ", data=" + data + ", packetID=" + packetID + "]";
return "Packet [type=" + type + ", data=" + data + ", packetID=" + packetID + "]";
}
}

View File

@ -6,5 +6,6 @@ public enum PacketEnum {
Player,
HttpPacket,
AuthPacket,
RunConsoleCommand,
HeartBeat
}

View File

@ -0,0 +1,21 @@
package com.github.jie65535.opencommand.socket.packet;
import emu.grasscutter.Grasscutter;
public class RunConsoleCommand extends BasePacket {
public String command;
public RunConsoleCommand(String command) {
this.command = command;
}
@Override
public String getPacket() {
return Grasscutter.getGsonFactory().toJson(this);
}
@Override
public PacketEnum getType() {
return PacketEnum.RunConsoleCommand;
}
}