mirror of
https://github.com/jie65535/gc-opencommand-plugin.git
synced 2025-06-02 17:49:12 +08:00
Impl token persistence (#19)
This commit is contained in:
parent
b70b73667d
commit
ef885af137
@ -0,0 +1,42 @@
|
|||||||
|
package com.github.jie65535.opencommand;
|
||||||
|
|
||||||
|
import com.github.jie65535.opencommand.model.Client;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.Vector;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 插件持久化数据
|
||||||
|
*/
|
||||||
|
public class OpenCommandData {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 连接的客户端列表
|
||||||
|
*/
|
||||||
|
public Vector<Client> clients = new Vector<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通过令牌获取客户端
|
||||||
|
* @param token 令牌
|
||||||
|
* @return 客户端对象,若未找到返回null
|
||||||
|
*/
|
||||||
|
public Client getClientByToken(String token) {
|
||||||
|
for (var c : clients) {
|
||||||
|
if (c.token.equals(token))
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 移除所有过期的客户端
|
||||||
|
*/
|
||||||
|
public void removeExpiredClients() {
|
||||||
|
var now = new Date();
|
||||||
|
clients.removeIf(client -> client.tokenExpireTime.before(now));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addClient(Client client) {
|
||||||
|
clients.add(client);
|
||||||
|
}
|
||||||
|
}
|
@ -19,6 +19,7 @@ package com.github.jie65535.opencommand;
|
|||||||
|
|
||||||
import com.github.jie65535.opencommand.json.JsonRequest;
|
import com.github.jie65535.opencommand.json.JsonRequest;
|
||||||
import com.github.jie65535.opencommand.json.JsonResponse;
|
import com.github.jie65535.opencommand.json.JsonResponse;
|
||||||
|
import com.github.jie65535.opencommand.model.Client;
|
||||||
import com.github.jie65535.opencommand.socket.SocketData;
|
import com.github.jie65535.opencommand.socket.SocketData;
|
||||||
import emu.grasscutter.command.CommandMap;
|
import emu.grasscutter.command.CommandMap;
|
||||||
import emu.grasscutter.server.http.Router;
|
import emu.grasscutter.server.http.Router;
|
||||||
@ -42,17 +43,17 @@ public final class OpenCommandHandler implements Router {
|
|||||||
javalin.post("/opencommand/api", OpenCommandHandler::handle);
|
javalin.post("/opencommand/api", OpenCommandHandler::handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final Map<String, Integer> clients = new HashMap<>();
|
|
||||||
private static final Map<String, Date> tokenExpireTime = new HashMap<>();
|
|
||||||
private static final Map<String, Integer> codes = new HashMap<>();
|
private static final Map<String, Integer> codes = new HashMap<>();
|
||||||
private static final Int2ObjectMap<Date> codeExpireTime = new Int2ObjectOpenHashMap<>();
|
private static final Int2ObjectMap<Date> codeExpireTime = new Int2ObjectOpenHashMap<>();
|
||||||
|
|
||||||
public static void handle(Context context) {
|
public static void handle(Context context) {
|
||||||
// Trigger cleanup action
|
|
||||||
cleanupExpiredData();
|
|
||||||
var plugin = OpenCommandPlugin.getInstance();
|
var plugin = OpenCommandPlugin.getInstance();
|
||||||
var config = plugin.getConfig();
|
var config = plugin.getConfig();
|
||||||
|
var data = plugin.getData();
|
||||||
var now = new Date();
|
var now = new Date();
|
||||||
|
// Trigger cleanup action
|
||||||
|
cleanupExpiredCodes();
|
||||||
|
data.removeExpiredClients();
|
||||||
|
|
||||||
var req = context.bodyAsClass(JsonRequest.class);
|
var req = context.bodyAsClass(JsonRequest.class);
|
||||||
if (req.action.equals("sendCode")) {
|
if (req.action.equals("sendCode")) {
|
||||||
@ -74,9 +75,8 @@ public final class OpenCommandHandler implements Router {
|
|||||||
token = Utils.bytesToHex(Crypto.createSessionKey(32));
|
token = Utils.bytesToHex(Crypto.createSessionKey(32));
|
||||||
int code = Utils.randomRange(1000, 9999);
|
int code = Utils.randomRange(1000, 9999);
|
||||||
codeExpireTime.put(playerId, new Date(now.getTime() + config.codeExpirationTime_S * 1000L));
|
codeExpireTime.put(playerId, new Date(now.getTime() + config.codeExpirationTime_S * 1000L));
|
||||||
tokenExpireTime.put(token, new Date(now.getTime() + config.tempTokenExpirationTime_S * 1000L));
|
|
||||||
codes.put(token, code);
|
codes.put(token, code);
|
||||||
clients.put(token, playerId);
|
data.addClient(new Client(token, playerId, new Date(now.getTime() + config.tempTokenExpirationTime_S * 1000L)));
|
||||||
player.dropMessage("[Open Command] Verification code: " + code);
|
player.dropMessage("[Open Command] Verification code: " + code);
|
||||||
context.json(new JsonResponse(token));
|
context.json(new JsonResponse(token));
|
||||||
}
|
}
|
||||||
@ -97,7 +97,8 @@ public final class OpenCommandHandler implements Router {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var isConsole = req.token.equals(config.consoleToken);
|
var isConsole = req.token.equals(config.consoleToken);
|
||||||
if (!isConsole && !clients.containsKey(req.token)) {
|
var client = data.getClientByToken(req.token);
|
||||||
|
if (!isConsole && client == null) {
|
||||||
context.json(new JsonResponse(401, "Unauthorized"));
|
context.json(new JsonResponse(401, "Unauthorized"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -131,9 +132,10 @@ public final class OpenCommandHandler implements Router {
|
|||||||
if (codes.get(req.token).equals(req.data)) {
|
if (codes.get(req.token).equals(req.data)) {
|
||||||
codes.remove(req.token);
|
codes.remove(req.token);
|
||||||
// update token expire time
|
// update token expire time
|
||||||
tokenExpireTime.put(req.token, new Date(now.getTime() + config.tokenLastUseExpirationTime_H * 60L * 60L * 1000L));
|
client.tokenExpireTime = new Date(now.getTime() + config.tokenLastUseExpirationTime_H * 60L * 60L * 1000L);
|
||||||
context.json(new JsonResponse());
|
context.json(new JsonResponse());
|
||||||
plugin.getLogger().info(String.format("Player %d has passed the verification, ip: %s", clients.get(req.token), context.ip()));
|
plugin.getLogger().info(String.format("Player %d has passed the verification, ip: %s", client.playerId, context.ip()));
|
||||||
|
plugin.saveData();
|
||||||
} else {
|
} else {
|
||||||
context.json(new JsonResponse(400, "Verification failed"));
|
context.json(new JsonResponse(400, "Verification failed"));
|
||||||
}
|
}
|
||||||
@ -142,9 +144,8 @@ public final class OpenCommandHandler implements Router {
|
|||||||
} else {
|
} else {
|
||||||
if (req.action.equals("command")) {
|
if (req.action.equals("command")) {
|
||||||
// update token expire time
|
// update token expire time
|
||||||
tokenExpireTime.put(req.token, new Date(now.getTime() + config.tokenLastUseExpirationTime_H * 60L * 60L * 1000L));
|
client.tokenExpireTime = new Date(now.getTime() + config.tokenLastUseExpirationTime_H * 60L * 60L * 1000L);
|
||||||
var playerId = clients.get(req.token);
|
var player = plugin.getServer().getPlayerByUid(client.playerId);
|
||||||
var player = plugin.getServer().getPlayerByUid(playerId);
|
|
||||||
var command = req.data.toString();
|
var command = req.data.toString();
|
||||||
if (player == null) {
|
if (player == null) {
|
||||||
context.json(new JsonResponse(404, "Player not found"));
|
context.json(new JsonResponse(404, "Player not found"));
|
||||||
@ -171,18 +172,10 @@ public final class OpenCommandHandler implements Router {
|
|||||||
context.json(new JsonResponse(403, "forbidden"));
|
context.json(new JsonResponse(403, "forbidden"));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void cleanupExpiredData() {
|
private static void cleanupExpiredCodes() {
|
||||||
var now = new Date();
|
var now = new Date();
|
||||||
codeExpireTime.int2ObjectEntrySet().removeIf(entry -> entry.getValue().before(now));
|
codeExpireTime.int2ObjectEntrySet().removeIf(entry -> entry.getValue().before(now));
|
||||||
|
if (codeExpireTime.isEmpty())
|
||||||
var it = tokenExpireTime.entrySet().iterator();
|
codes.clear();
|
||||||
while (it.hasNext()) {
|
|
||||||
var entry = it.next();
|
|
||||||
if (entry.getValue().before(now)) {
|
|
||||||
it.remove();
|
|
||||||
// remove expired token
|
|
||||||
clients.remove(entry.getKey());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,7 @@ package com.github.jie65535.opencommand;
|
|||||||
|
|
||||||
import com.github.jie65535.opencommand.json.JsonRequest;
|
import com.github.jie65535.opencommand.json.JsonRequest;
|
||||||
import com.github.jie65535.opencommand.json.JsonResponse;
|
import com.github.jie65535.opencommand.json.JsonResponse;
|
||||||
|
import com.github.jie65535.opencommand.model.Client;
|
||||||
import com.github.jie65535.opencommand.socket.SocketData;
|
import com.github.jie65535.opencommand.socket.SocketData;
|
||||||
import com.github.jie65535.opencommand.socket.SocketDataWait;
|
import com.github.jie65535.opencommand.socket.SocketDataWait;
|
||||||
import com.github.jie65535.opencommand.socket.SocketServer;
|
import com.github.jie65535.opencommand.socket.SocketServer;
|
||||||
@ -45,17 +46,17 @@ public final class OpenCommandOnlyHttpHandler implements Router {
|
|||||||
javalin.post("/opencommand/api", OpenCommandOnlyHttpHandler::handle);
|
javalin.post("/opencommand/api", OpenCommandOnlyHttpHandler::handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final Map<String, Integer> clients = new HashMap<>();
|
|
||||||
private static final Map<String, Date> tokenExpireTime = new HashMap<>();
|
|
||||||
private static final Map<String, Integer> codes = new HashMap<>();
|
private static final Map<String, Integer> codes = new HashMap<>();
|
||||||
private static final Int2ObjectMap<Date> codeExpireTime = new Int2ObjectOpenHashMap<>();
|
private static final Int2ObjectMap<Date> codeExpireTime = new Int2ObjectOpenHashMap<>();
|
||||||
|
|
||||||
public static void handle(Context context) {
|
public static void handle(Context context) {
|
||||||
// Trigger cleanup action
|
|
||||||
cleanupExpiredData();
|
|
||||||
var plugin = OpenCommandPlugin.getInstance();
|
var plugin = OpenCommandPlugin.getInstance();
|
||||||
var config = plugin.getConfig();
|
var config = plugin.getConfig();
|
||||||
|
var data = plugin.getData();
|
||||||
var now = new Date();
|
var now = new Date();
|
||||||
|
// Trigger cleanup action
|
||||||
|
cleanupExpiredCodes();
|
||||||
|
data.removeExpiredClients();
|
||||||
|
|
||||||
var req = context.bodyAsClass(JsonRequest.class);
|
var req = context.bodyAsClass(JsonRequest.class);
|
||||||
if (req.action.equals("sendCode")) {
|
if (req.action.equals("sendCode")) {
|
||||||
@ -77,9 +78,8 @@ public final class OpenCommandOnlyHttpHandler implements Router {
|
|||||||
token = Utils.bytesToHex(Crypto.createSessionKey(32));
|
token = Utils.bytesToHex(Crypto.createSessionKey(32));
|
||||||
int code = Utils.randomRange(1000, 9999);
|
int code = Utils.randomRange(1000, 9999);
|
||||||
codeExpireTime.put(playerId, new Date(now.getTime() + config.codeExpirationTime_S * 1000L));
|
codeExpireTime.put(playerId, new Date(now.getTime() + config.codeExpirationTime_S * 1000L));
|
||||||
tokenExpireTime.put(token, new Date(now.getTime() + config.tempTokenExpirationTime_S * 1000L));
|
|
||||||
codes.put(token, code);
|
codes.put(token, code);
|
||||||
clients.put(token, playerId);
|
data.addClient(new Client(token, playerId, new Date(now.getTime() + config.tempTokenExpirationTime_S * 1000L)));
|
||||||
Player.dropMessage(playerId, "[Open Command] Verification code: " + code);
|
Player.dropMessage(playerId, "[Open Command] Verification code: " + code);
|
||||||
context.json(new JsonResponse(token));
|
context.json(new JsonResponse(token));
|
||||||
}
|
}
|
||||||
@ -98,7 +98,8 @@ public final class OpenCommandOnlyHttpHandler implements Router {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var isConsole = req.token.equals(config.consoleToken);
|
var isConsole = req.token.equals(config.consoleToken);
|
||||||
if (!isConsole && !clients.containsKey(req.token)) {
|
var client = data.getClientByToken(req.token);
|
||||||
|
if (!isConsole && client == null) {
|
||||||
context.json(new JsonResponse(401, "Unauthorized"));
|
context.json(new JsonResponse(401, "Unauthorized"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -130,12 +131,12 @@ public final class OpenCommandOnlyHttpHandler implements Router {
|
|||||||
};
|
};
|
||||||
|
|
||||||
SocketServer.sendPacketAndWait(server.ip, new RunConsoleCommand(req.data.toString()), wait);
|
SocketServer.sendPacketAndWait(server.ip, new RunConsoleCommand(req.data.toString()), wait);
|
||||||
var data = wait.getData();
|
var packet = wait.getData();
|
||||||
if (data == null) {
|
if (packet == null) {
|
||||||
context.json(new JsonResponse(408, "Timeout"));
|
context.json(new JsonResponse(408, "Timeout"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
context.json(new JsonResponse(data.code, data.message, data.data));
|
context.json(new JsonResponse(packet.code, packet.message, packet.data));
|
||||||
return;
|
return;
|
||||||
} else if (req.action.equals("server")) {
|
} else if (req.action.equals("server")) {
|
||||||
context.json(new JsonResponse(200, "Success", SocketServer.getOnlineClient()));
|
context.json(new JsonResponse(200, "Success", SocketServer.getOnlineClient()));
|
||||||
@ -149,9 +150,10 @@ public final class OpenCommandOnlyHttpHandler implements Router {
|
|||||||
if (codes.get(req.token).equals(req.data)) {
|
if (codes.get(req.token).equals(req.data)) {
|
||||||
codes.remove(req.token);
|
codes.remove(req.token);
|
||||||
// update token expire time
|
// update token expire time
|
||||||
tokenExpireTime.put(req.token, new Date(now.getTime() + config.tokenLastUseExpirationTime_H * 60L * 60L * 1000L));
|
client.tokenExpireTime = new Date(now.getTime() + config.tokenLastUseExpirationTime_H * 60L * 60L * 1000L);
|
||||||
context.json(new JsonResponse());
|
context.json(new JsonResponse());
|
||||||
plugin.getLogger().info(String.format("Player %d has passed the verification, ip: %s", clients.get(req.token), context.ip()));
|
plugin.getLogger().info(String.format("Player %d has passed the verification, ip: %s", client.playerId, context.ip()));
|
||||||
|
plugin.saveData();
|
||||||
} else {
|
} else {
|
||||||
context.json(new JsonResponse(400, "Verification failed"));
|
context.json(new JsonResponse(400, "Verification failed"));
|
||||||
}
|
}
|
||||||
@ -175,15 +177,14 @@ public final class OpenCommandOnlyHttpHandler implements Router {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// update token expire time
|
// update token expire time
|
||||||
tokenExpireTime.put(req.token, new Date(now.getTime() + config.tokenLastUseExpirationTime_H * 60L * 60L * 1000L));
|
client.tokenExpireTime = new Date(now.getTime() + config.tokenLastUseExpirationTime_H * 60L * 60L * 1000L);
|
||||||
var playerId = clients.get(req.token);
|
|
||||||
var command = req.data.toString();
|
var command = req.data.toString();
|
||||||
var player = new Player();
|
var player = new Player();
|
||||||
player.uid = playerId;
|
player.uid = client.playerId;
|
||||||
player.type = PlayerEnum.RunCommand;
|
player.type = PlayerEnum.RunCommand;
|
||||||
player.data = command;
|
player.data = command;
|
||||||
|
|
||||||
if (!SocketServer.sendUidPacketAndWait(playerId, player, socketDataWait)) {
|
if (!SocketServer.sendUidPacketAndWait(client.playerId, player, socketDataWait)) {
|
||||||
context.json(new JsonResponse(404, "Player Not Found."));
|
context.json(new JsonResponse(404, "Player Not Found."));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -201,18 +202,10 @@ public final class OpenCommandOnlyHttpHandler implements Router {
|
|||||||
context.json(new JsonResponse(403, "forbidden"));
|
context.json(new JsonResponse(403, "forbidden"));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void cleanupExpiredData() {
|
private static void cleanupExpiredCodes() {
|
||||||
var now = new Date();
|
var now = new Date();
|
||||||
codeExpireTime.int2ObjectEntrySet().removeIf(entry -> entry.getValue().before(now));
|
codeExpireTime.int2ObjectEntrySet().removeIf(entry -> entry.getValue().before(now));
|
||||||
|
if (codeExpireTime.isEmpty())
|
||||||
var it = tokenExpireTime.entrySet().iterator();
|
codes.clear();
|
||||||
while (it.hasNext()) {
|
|
||||||
var entry = it.next();
|
|
||||||
if (entry.getValue().before(now)) {
|
|
||||||
it.remove();
|
|
||||||
// remove expired token
|
|
||||||
clients.remove(entry.getKey());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -40,6 +40,8 @@ public final class OpenCommandPlugin extends Plugin {
|
|||||||
|
|
||||||
private OpenCommandConfig config;
|
private OpenCommandConfig config;
|
||||||
|
|
||||||
|
private OpenCommandData data;
|
||||||
|
|
||||||
private Grasscutter.ServerRunMode runMode = Grasscutter.ServerRunMode.HYBRID;
|
private Grasscutter.ServerRunMode runMode = Grasscutter.ServerRunMode.HYBRID;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -47,6 +49,8 @@ public final class OpenCommandPlugin extends Plugin {
|
|||||||
instance = this;
|
instance = this;
|
||||||
// 加载配置
|
// 加载配置
|
||||||
loadConfig();
|
loadConfig();
|
||||||
|
// 加载数据
|
||||||
|
loadData();
|
||||||
// 启动Socket
|
// 启动Socket
|
||||||
startSocket();
|
startSocket();
|
||||||
}
|
}
|
||||||
@ -77,6 +81,7 @@ public final class OpenCommandPlugin extends Plugin {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDisable() {
|
public void onDisable() {
|
||||||
|
saveData();
|
||||||
getLogger().info("[OpenCommand] Disabled");
|
getLogger().info("[OpenCommand] Disabled");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -84,6 +89,10 @@ public final class OpenCommandPlugin extends Plugin {
|
|||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public OpenCommandData getData() {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
private void loadConfig() {
|
private void loadConfig() {
|
||||||
var configFile = new File(getDataFolder(), "config.json");
|
var configFile = new File(getDataFolder(), "config.json");
|
||||||
if (!configFile.exists()) {
|
if (!configFile.exists()) {
|
||||||
@ -110,6 +119,31 @@ public final class OpenCommandPlugin extends Plugin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void loadData() {
|
||||||
|
var dataFile = new File(getDataFolder(), "data.json");
|
||||||
|
if (!dataFile.exists()) {
|
||||||
|
data = new OpenCommandData();
|
||||||
|
saveData();
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
data = JsonUtils.loadToClass(dataFile.getAbsolutePath(), OpenCommandData.class);
|
||||||
|
} catch (Exception exception) {
|
||||||
|
data = new OpenCommandData();
|
||||||
|
getLogger().error("[OpenCommand] There was an error while trying to load the data from data.json. Please make sure that there are no syntax errors. If you want to start with a default data, delete your existing data.json.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void saveData() {
|
||||||
|
try (var file = new FileWriter(new File(getDataFolder(), "data.json"))) {
|
||||||
|
file.write(JsonUtils.encode(data));
|
||||||
|
} catch (IOException e) {
|
||||||
|
getLogger().error("[OpenCommand] Unable to write to data file.");
|
||||||
|
} catch (Exception e) {
|
||||||
|
getLogger().error("[OpenCommand] Unable to save data file.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void startSocket() {
|
private void startSocket() {
|
||||||
if (runMode == Grasscutter.ServerRunMode.GAME_ONLY) {
|
if (runMode == Grasscutter.ServerRunMode.GAME_ONLY) {
|
||||||
getLogger().info("[OpenCommand] Starting socket client...");
|
getLogger().info("[OpenCommand] Starting socket client...");
|
||||||
|
@ -0,0 +1,16 @@
|
|||||||
|
package com.github.jie65535.opencommand.model;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
public final class Client {
|
||||||
|
|
||||||
|
public String token;
|
||||||
|
public Integer playerId;
|
||||||
|
public Date tokenExpireTime;
|
||||||
|
|
||||||
|
public Client(String token, Integer playerId, Date tokenExpireTime) {
|
||||||
|
this.token = token;
|
||||||
|
this.playerId = playerId;
|
||||||
|
this.tokenExpireTime = tokenExpireTime;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user