Skip to content

Commit 5247580

Browse files
feat(api): 实现管理员令牌生成功能并优化白名单接口
新增 `/api/v1/admin/generate-token` 路由,支持通过管理员密码验证后生成注册令牌。 允许自定义令牌过期时间(1-168小时),默认24小时。 增强白名单添加接口,支持可选 UUID 和时间戳参数。 引入 `UuidUtils` 工具类用于生成或验证玩家 UUID。 重构代码导入顺序,提升代码整洁度。 更新 API 文档中 notes 字段示例值。 移除未使用的数据库索引定义。 配置管理器增加管理员密码读取与保存方法。 初始化时优先使用配置文件中的管理员密码,避免重复生成。
1 parent b01f092 commit 5247580

12 files changed

Lines changed: 357 additions & 75 deletions

File tree

API.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ X-Admin-Password: your-admin-password
113113
"created_at": "2024-01-01T00:00:00Z",
114114
"updated_at": "2024-01-01T00:00:00Z",
115115
"source": "manual",
116-
"notes": "VIP玩家"
116+
"notes": "op"
117117
}
118118
],
119119
"pagination": {

src/main/java/com/xaoxiao/convenientaccess/api/ApiRouter.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
package com.xaoxiao.convenientaccess.api;
22

3+
import java.io.IOException;
4+
5+
import org.slf4j.Logger;
6+
import org.slf4j.LoggerFactory;
7+
38
import jakarta.servlet.ServletException;
49
import jakarta.servlet.http.HttpServlet;
510
import jakarta.servlet.http.HttpServletRequest;
611
import jakarta.servlet.http.HttpServletResponse;
7-
import org.slf4j.Logger;
8-
import org.slf4j.LoggerFactory;
9-
10-
import java.io.IOException;
1112

1213
/**
1314
* API路由器(简化版)
@@ -101,8 +102,7 @@ else if (path.equals("/api/v1/register")) {
101102
}
102103
// 令牌生成路由(简化版,需要管理员密码验证)
103104
else if (path.equals("/api/v1/admin/generate-token")) {
104-
// TODO: 需要在UserApiController中添加handleGenerateToken方法
105-
send404Response(response, "Token generation not implemented yet");
105+
userController.handleGenerateToken(request, response);
106106
}
107107
else {
108108
send404Response(response, "API endpoint not found");

src/main/java/com/xaoxiao/convenientaccess/api/UserApiController.java

Lines changed: 85 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,22 @@
11
package com.xaoxiao.convenientaccess.api;
22

3+
import java.io.BufferedReader;
4+
import java.io.IOException;
5+
import java.util.UUID;
6+
import java.util.regex.Pattern;
7+
8+
import org.slf4j.Logger;
9+
import org.slf4j.LoggerFactory;
10+
311
import com.google.gson.Gson;
412
import com.google.gson.JsonObject;
513
import com.google.gson.JsonParser;
614
import com.xaoxiao.convenientaccess.auth.RegistrationTokenManager;
715
import com.xaoxiao.convenientaccess.whitelist.WhitelistEntry;
816
import com.xaoxiao.convenientaccess.whitelist.WhitelistManager;
17+
918
import jakarta.servlet.http.HttpServletRequest;
1019
import jakarta.servlet.http.HttpServletResponse;
11-
import org.slf4j.Logger;
12-
import org.slf4j.LoggerFactory;
13-
14-
import java.io.BufferedReader;
15-
import java.io.IOException;
16-
import java.util.UUID;
17-
import java.util.regex.Pattern;
1820

1921
/**
2022
* 用户API控制器
@@ -30,12 +32,88 @@ public class UserApiController {
3032
// 玩家名称验证正则
3133
private static final Pattern PLAYER_NAME_PATTERN = Pattern.compile("^[a-zA-Z0-9_]{3,16}$");
3234

35+
// 管理员密码(从WhitelistSystem获取)
36+
private String adminPassword;
37+
3338
public UserApiController(RegistrationTokenManager tokenManager, WhitelistManager whitelistManager) {
3439
this.tokenManager = tokenManager;
3540
this.whitelistManager = whitelistManager;
3641
this.gson = new Gson();
3742
}
3843

44+
/**
45+
* 设置管理员密码
46+
*/
47+
public void setAdminPassword(String adminPassword) {
48+
this.adminPassword = adminPassword;
49+
}
50+
51+
/**
52+
* 处理POST /api/v1/admin/generate-token - 生成注册令牌
53+
*/
54+
public void handleGenerateToken(HttpServletRequest request, HttpServletResponse response) throws IOException {
55+
try {
56+
// 验证管理员密码
57+
String providedPassword = request.getHeader("X-Admin-Password");
58+
if (providedPassword == null || providedPassword.trim().isEmpty()) {
59+
sendJsonResponse(response, 401, ApiResponse.unauthorized("缺少管理员密码"));
60+
return;
61+
}
62+
63+
if (adminPassword == null || !adminPassword.equals(providedPassword.trim())) {
64+
sendJsonResponse(response, 401, ApiResponse.unauthorized("管理员密码错误"));
65+
return;
66+
}
67+
68+
// 读取请求体(可选参数)
69+
String requestBody = readRequestBody(request);
70+
JsonObject json = null;
71+
int expiryHours = 24; // 默认24小时
72+
73+
if (!requestBody.isEmpty()) {
74+
try {
75+
json = JsonParser.parseString(requestBody).getAsJsonObject();
76+
if (json.has("expiryHours")) {
77+
expiryHours = json.get("expiryHours").getAsInt();
78+
if (expiryHours <= 0 || expiryHours > 168) { // 最大7天
79+
sendJsonResponse(response, 400, ApiResponse.badRequest("过期时间必须在1-168小时之间"));
80+
return;
81+
}
82+
}
83+
} catch (Exception e) {
84+
logger.warn("解析请求体失败,使用默认参数", e);
85+
}
86+
}
87+
88+
final int finalExpiryHours = expiryHours; // 为lambda表达式创建final变量
89+
90+
// 生成注册令牌
91+
tokenManager.generateRegistrationToken(finalExpiryHours)
92+
.thenAccept(token -> {
93+
if (token != null) {
94+
JsonObject responseData = new JsonObject();
95+
responseData.addProperty("token", token);
96+
responseData.addProperty("expiryHours", finalExpiryHours);
97+
responseData.addProperty("message", "令牌生成成功");
98+
99+
sendJsonResponse(response, 200, ApiResponse.success(responseData, "令牌生成成功"));
100+
logger.info("管理员生成注册令牌成功,过期时间: {}小时", finalExpiryHours);
101+
} else {
102+
sendJsonResponse(response, 500, ApiResponse.error("令牌生成失败"));
103+
}
104+
})
105+
.exceptionally(throwable -> {
106+
logger.error("生成注册令牌失败", throwable);
107+
sendJsonResponse(response, 500, ApiResponse.error("令牌生成服务异常"));
108+
return null;
109+
});
110+
111+
} catch (Exception e) {
112+
logger.error("处理令牌生成请求失败", e);
113+
sendJsonResponse(response, 500, ApiResponse.error("服务器内部错误"));
114+
}
115+
}
116+
39117
/**
40118
* 处理POST /api/v1/register - 用户注册
41119
*/

src/main/java/com/xaoxiao/convenientaccess/api/WhitelistApiController.java

Lines changed: 69 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import com.xaoxiao.convenientaccess.sync.SyncTask;
1212
import com.xaoxiao.convenientaccess.sync.SyncTaskManager;
1313
import com.xaoxiao.convenientaccess.whitelist.BatchOperation;
14+
import com.xaoxiao.convenientaccess.utils.UuidUtils;
1415
import com.xaoxiao.convenientaccess.whitelist.WhitelistEntry;
1516
import com.xaoxiao.convenientaccess.whitelist.WhitelistManager;
1617
import com.xaoxiao.convenientaccess.whitelist.WhitelistStats;
@@ -112,27 +113,44 @@ public void handleAddPlayer(HttpServletRequest request, HttpServletResponse resp
112113
String requestBody = readRequestBody(request);
113114
JsonObject json = JsonParser.parseString(requestBody).getAsJsonObject();
114115

115-
// 参数验证
116-
if (!json.has("name") || !json.has("uuid") ||
117-
!json.has("added_by_name") || !json.has("added_by_uuid")) {
118-
sendJsonResponse(response, 400, ApiResponse.badRequest("缺少必需参数"));
116+
// 参数验证 - 要求name、added_by_name、added_by_uuid、source,uuid变为可选
117+
if (!json.has("name") || !json.has("added_by_name") || !json.has("added_by_uuid") || !json.has("source")) {
118+
sendJsonResponse(response, 400, ApiResponse.badRequest("缺少必需参数: name, added_by_name, added_by_uuid, source"));
119119
return;
120120
}
121121

122122
String name = json.get("name").getAsString();
123-
String uuid = json.get("uuid").getAsString();
123+
String providedUuid = json.has("uuid") ? json.get("uuid").getAsString() : null;
124124
String addedByName = json.get("added_by_name").getAsString();
125125
String addedByUuid = json.get("added_by_uuid").getAsString();
126-
String sourceStr = json.has("source") ? json.get("source").getAsString() : "ADMIN";
126+
String sourceStr = json.get("source").getAsString();
127+
128+
// 处理时间戳 - 如果前端提供则使用,否则使用当前时间
129+
LocalDateTime addedAt;
130+
if (json.has("added_at")) {
131+
try {
132+
String addedAtStr = json.get("added_at").getAsString();
133+
addedAt = LocalDateTime.parse(addedAtStr, DateTimeFormatter.ISO_LOCAL_DATE_TIME);
134+
} catch (Exception e) {
135+
sendJsonResponse(response, 400, ApiResponse.badRequest("时间格式无效,请使用ISO格式: yyyy-MM-ddTHH:mm:ss"));
136+
return;
137+
}
138+
} else {
139+
addedAt = LocalDateTime.now();
140+
}
127141

128142
// 验证参数格式
129143
if (!isValidPlayerName(name)) {
130144
sendJsonResponse(response, 400, ApiResponse.badRequest("玩家名称格式无效"));
131145
return;
132146
}
133147

134-
if (!isValidUuid(uuid)) {
135-
sendJsonResponse(response, 400, ApiResponse.badRequest("UUID格式无效"));
148+
// 生成或验证UUID
149+
String uuid;
150+
try {
151+
uuid = UuidUtils.getOrGenerateUuid(name, providedUuid);
152+
} catch (IllegalArgumentException e) {
153+
sendJsonResponse(response, 400, ApiResponse.badRequest("玩家名不能为空"));
136154
return;
137155
}
138156

@@ -145,7 +163,7 @@ public void handleAddPlayer(HttpServletRequest request, HttpServletResponse resp
145163
}
146164

147165
// 添加玩家
148-
whitelistManager.addPlayer(name, uuid, addedByName, addedByUuid, source)
166+
whitelistManager.addPlayer(name, uuid, addedByName, addedByUuid, source, addedAt)
149167
.thenAccept(success -> {
150168
if (success) {
151169
// 创建同步任务
@@ -155,6 +173,7 @@ public void handleAddPlayer(HttpServletRequest request, HttpServletResponse resp
155173
result.addProperty("uuid", uuid);
156174
result.addProperty("name", name);
157175
result.addProperty("added", true);
176+
result.addProperty("uuid_generated", providedUuid == null || providedUuid.trim().isEmpty());
158177

159178
sendJsonResponse(response, 201, ApiResponse.success(result, "玩家添加成功"));
160179
} else {
@@ -359,13 +378,37 @@ public void handleBatchOperation(HttpServletRequest request, HttpServletResponse
359378
JsonObject json = JsonParser.parseString(requestBody).getAsJsonObject();
360379

361380
// 参数验证
362-
if (!json.has("operation") || !json.has("players")) {
363-
sendJsonResponse(response, 400, ApiResponse.badRequest("缺少必需参数: operation, players"));
381+
if (!json.has("operation") || !json.has("players") || !json.has("source")) {
382+
sendJsonResponse(response, 400, ApiResponse.badRequest("缺少必需参数: operation, players, source"));
364383
return;
365384
}
366385

367386
String operation = json.get("operation").getAsString();
368387
JsonArray playersArray = json.getAsJsonArray("players");
388+
String sourceStr = json.get("source").getAsString();
389+
390+
// 验证source参数
391+
WhitelistEntry.Source source;
392+
try {
393+
source = WhitelistEntry.Source.fromString(sourceStr);
394+
} catch (IllegalArgumentException e) {
395+
sendJsonResponse(response, 400, ApiResponse.badRequest("无效的来源类型: " + sourceStr));
396+
return;
397+
}
398+
399+
// 解析时间戳(可选)
400+
LocalDateTime addedAt;
401+
if (json.has("added_at")) {
402+
try {
403+
String addedAtStr = json.get("added_at").getAsString();
404+
addedAt = LocalDateTime.parse(addedAtStr, DateTimeFormatter.ISO_LOCAL_DATE_TIME);
405+
} catch (Exception e) {
406+
sendJsonResponse(response, 400, ApiResponse.badRequest("无效的时间格式,请使用ISO格式: " + e.getMessage()));
407+
return;
408+
}
409+
} else {
410+
addedAt = LocalDateTime.now();
411+
}
369412

370413
if (playersArray.size() == 0) {
371414
sendJsonResponse(response, 400, ApiResponse.badRequest("玩家列表不能为空"));
@@ -380,15 +423,6 @@ public void handleBatchOperation(HttpServletRequest request, HttpServletResponse
380423
// 获取操作者信息
381424
String addedByName = json.has("added_by_name") ? json.get("added_by_name").getAsString() : "API";
382425
String addedByUuid = json.has("added_by_uuid") ? json.get("added_by_uuid").getAsString() : "00000000-0000-0000-0000-000000000000";
383-
String sourceStr = json.has("source") ? json.get("source").getAsString() : "ADMIN";
384-
385-
WhitelistEntry.Source source;
386-
try {
387-
source = WhitelistEntry.Source.fromString(sourceStr);
388-
} catch (IllegalArgumentException e) {
389-
sendJsonResponse(response, 400, ApiResponse.badRequest("无效的来源类型"));
390-
return;
391-
}
392426

393427
if ("add".equalsIgnoreCase(operation)) {
394428
// 批量添加
@@ -400,20 +434,29 @@ public void handleBatchOperation(HttpServletRequest request, HttpServletResponse
400434
for (int i = 0; i < playersArray.size(); i++) {
401435
JsonObject playerObj = playersArray.get(i).getAsJsonObject();
402436

403-
if (!playerObj.has("name") || !playerObj.has("uuid")) {
404-
sendJsonResponse(response, 400, ApiResponse.badRequest("玩家信息缺少name或uuid字段"));
437+
if (!playerObj.has("name")) {
438+
sendJsonResponse(response, 400, ApiResponse.badRequest("玩家信息缺少name字段"));
405439
return;
406440
}
407441

408442
String name = playerObj.get("name").getAsString();
409-
String uuid = playerObj.get("uuid").getAsString();
443+
String providedUuid = playerObj.has("uuid") ? playerObj.get("uuid").getAsString() : null;
444+
445+
if (!isValidPlayerName(name)) {
446+
sendJsonResponse(response, 400, ApiResponse.badRequest("无效的玩家名: " + name));
447+
return;
448+
}
410449

411-
if (!isValidPlayerName(name) || !isValidUuid(uuid)) {
412-
sendJsonResponse(response, 400, ApiResponse.badRequest("无效的玩家数据: " + name + " (" + uuid + ")"));
450+
// 生成或验证UUID
451+
String uuid;
452+
try {
453+
uuid = UuidUtils.getOrGenerateUuid(name, providedUuid);
454+
} catch (IllegalArgumentException e) {
455+
sendJsonResponse(response, 400, ApiResponse.badRequest("玩家名不能为空: " + name));
413456
return;
414457
}
415458

416-
batchOperation.addEntry(name, uuid, source);
459+
batchOperation.addEntry(name, uuid, source, addedAt);
417460
}
418461

419462
// 执行批量添加

src/main/java/com/xaoxiao/convenientaccess/config/ConfigManager.java

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
package com.xaoxiao.convenientaccess.config;
22

3-
import com.xaoxiao.convenientaccess.ConvenientAccessPlugin;
3+
import java.util.List;
4+
45
import org.bukkit.configuration.file.FileConfiguration;
56

6-
import java.util.List;
7+
import com.xaoxiao.convenientaccess.ConvenientAccessPlugin;
78

89
/**
910
* 配置管理器
@@ -70,6 +71,21 @@ public String getApiKey() {
7071
return config.getString("api.auth.api-key", "");
7172
}
7273

74+
/**
75+
* 获取管理员密码
76+
*/
77+
public String getAdminPassword() {
78+
return config.getString("api.auth.admin-password", "");
79+
}
80+
81+
/**
82+
* 设置管理员密码到配置文件
83+
*/
84+
public void setAdminPassword(String password) {
85+
config.set("api.auth.admin-password", password);
86+
plugin.saveConfig();
87+
}
88+
7389
public boolean isRateLimitEnabled() {
7490
return config.getBoolean("api.rate-limit.enabled", true);
7591
}

src/main/java/com/xaoxiao/convenientaccess/sync/SyncTaskManager.java

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -773,10 +773,30 @@ private List<WhitelistEntry> readWhitelistFromJson() {
773773
entry.setSource("manual");
774774
}
775775

776-
// 设置默认的添加者信息
777-
entry.setAddedByName("System");
778-
entry.setAddedByUuid("00000000-0000-0000-0000-000000000000");
779-
entry.setAddedAt(LocalDateTime.now());
776+
// 设置添加者信息 - 优先使用JSON中的值,否则使用默认值
777+
if (obj.has("addedByName")) {
778+
entry.setAddedByName(obj.get("addedByName").getAsString());
779+
} else {
780+
entry.setAddedByName("System");
781+
}
782+
783+
if (obj.has("addedByUUID")) {
784+
entry.setAddedByUuid(obj.get("addedByUUID").getAsString());
785+
} else {
786+
entry.setAddedByUuid("00000000-0000-0000-0000-000000000000");
787+
}
788+
789+
if (obj.has("addedAt")) {
790+
try {
791+
entry.setAddedAt(LocalDateTime.parse(obj.get("addedAt").getAsString()));
792+
} catch (Exception e) {
793+
logger.debug("解析addedAt失败,使用当前时间: {}", e.getMessage());
794+
entry.setAddedAt(LocalDateTime.now());
795+
}
796+
} else {
797+
entry.setAddedAt(LocalDateTime.now());
798+
}
799+
780800
entry.setActive(true);
781801

782802
entries.add(entry);

0 commit comments

Comments
 (0)