第58批续:AI聊天@厂家修复+信誉积分双向同步+错误日志恢复
Check Source / check (push) Successful in 5s
Auto Release / build (push) Successful in 6m24s
Simple Test / test (push) Successful in 1s
Test Env / test (push) Successful in 2s

AIChatManager:
- 修复@//顶层分组名报「未知厂家」bug:parseModelConfig 之前跳过第1段(如「非国产」)
  不注册为厂家别名,导致 @//非国产/ 无法调用。改为注册所有路径段。
- 重读配置前清理 modelEndpoints/providerToModel,避免旧配置残留
- getAvailableModels 改为返回别名+厂家名+模型全名,不再误报「未配置模型」
- checkAutoChat 失败分支恢复详细错误日志:完整请求URL、请求体、响应头、响应体
  (与 sendToModel 对齐,搞明白上游真寄了还是本地问题)

WebManager:
- 新增 pushCreditToPhp:Java→PHP 推送信誉积分(非默认积分玩家+最近200条记录)
- 新增 pullCreditFromPhp:PHP→Java 拉回管理员手动调整的记录(source=admin)
- 两个方法接入 Timer C 批处理队列错峰执行
- 新增 JsonElement/JsonParser import
This commit is contained in:
youpaishidifu
2026-10-28 18:40:04 +08:00
parent 13ee2eb515
commit 6a5af09ed3
2 changed files with 230 additions and 16 deletions
+33 -15
View File
@@ -260,6 +260,10 @@ public class AIChatManager {
} }
private void parseModelConfig(JsonObject modelConfig) { private void parseModelConfig(JsonObject modelConfig) {
// ★ 重读前清理,避免旧配置残留(putIfAbsent 不会覆盖已存在的键)
modelEndpoints.clear();
providerToModel.clear();
// 遍历所有供应商配置 // 遍历所有供应商配置
for (Map.Entry<String, JsonElement> entry : modelConfig.entrySet()) { for (Map.Entry<String, JsonElement> entry : modelConfig.entrySet()) {
String key = entry.getKey(); String key = entry.getKey();
@@ -327,16 +331,11 @@ public class AIChatManager {
} }
} }
if (firstModel != null) { if (firstModel != null) {
// 第1段如果是供应商.json顶层分组名(如「国产」「非国产」)→ 屏蔽不注册 // 注册所有路径段为厂家名别名(包括顶层分组名)
// 从第2段开始才是厂家名/子分组名,注册为 @// 参数 // 如 "非国产/agnes/中国版" → 非国产、agnes、中国版 都能 @// 调用
// ★ 第58批修复:顶层分组名(如"非国产")也要注册,否则 @//非国产/ 报"未知厂家"
String[] segs = providerName.split("/"); String[] segs = providerName.split("/");
int startIdx = 0; for (int i = 0; i < segs.length; i++) {
if (segs.length >= 1 && providersConfig != null
&& providersConfig.has(segs[0].trim())) {
// 第1段是分组名,跳过
startIdx = 1;
}
for (int i = startIdx; i < segs.length; i++) {
String name = segs[i].trim(); String name = segs[i].trim();
if (!name.isEmpty()) { if (!name.isEmpty()) {
providerToModel.putIfAbsent(name, firstModel); providerToModel.putIfAbsent(name, firstModel);
@@ -420,10 +419,15 @@ public class AIChatManager {
} }
/** /**
* 获取可用模型简称列表 * 获取可用模型列表(别名 + 厂家名 + 模型全名)
*/ */
public List<String> getAvailableModels() { public List<String> getAvailableModels() {
return new ArrayList<>(aliasToFullModel.keySet()); // 合并别名、厂家名、模型全名,去重
LinkedHashSet<String> all = new LinkedHashSet<>();
all.addAll(aliasToFullModel.keySet());
all.addAll(providerToModel.keySet());
all.addAll(modelEndpoints.keySet());
return new ArrayList<>(all);
} }
// ==================== HTTP 请求 ==================== // ==================== HTTP 请求 ====================
@@ -838,19 +842,33 @@ public class AIChatManager {
return httpClient.send(request, HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8)); return httpClient.send(request, HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8));
} catch (Exception e) { } catch (Exception e) {
// ★ 完整异常详情:搞明白是上游真寄了还是本地问题
plugin.getLogger().warning("[AI聊天-调试] 自动互动请求异常: " + e.getClass().getSimpleName() + ": " + e.getMessage());
return null; return null;
} }
}).thenAccept(response -> { }).thenAccept(response -> {
if (response == null) { if (response == null) {
// ★ 日志静默:异常只记一行,不刷屏 plugin.getLogger().warning("[AI聊天-调试] 自动互动请求失败: 网络异常/超时");
plugin.getLogger().warning("[AI聊天] 自动互动请求异常(网络超时)"); plugin.getLogger().warning("[AI聊天-调试] 模型全名: " + defaultModelFull);
plugin.getLogger().warning("[AI聊天-调试] 请求URL: " + requestUrl);
plugin.getLogger().warning("[AI聊天-调试] 请求体JSON: " + jsonBody);
handleRequestFailure("[自动] " + prompt.substring(0, Math.min(100, prompt.length())), "自动互动失败: 请求异常"); handleRequestFailure("[自动] " + prompt.substring(0, Math.min(100, prompt.length())), "自动互动失败: 请求异常");
prevAutoChatFailed = true; prevAutoChatFailed = true;
return; return;
} }
if (response.statusCode() != 200) { if (response.statusCode() != 200) {
// ★ 日志静默:非200只记一行状态码,不打印完整响应体 // ★ 完整错误日志:请求头 + HTTP 返回一起打出来,定位上游是否真寄了
plugin.getLogger().warning("[AI聊天] 自动互动失败: HTTP " + response.statusCode()); String errBody = response.body();
plugin.getLogger().warning("[AI聊天-调试] === 自动互动请求失败 ===");
plugin.getLogger().warning("[AI聊天-调试] HTTP状态码: " + response.statusCode());
plugin.getLogger().warning("[AI聊天-调试] 模型全名: " + defaultModelFull);
plugin.getLogger().warning("[AI聊天-调试] 请求URL: " + requestUrl);
plugin.getLogger().warning("[AI聊天-调试] 请求体JSON: " + jsonBody);
response.headers().map().forEach((name, values) -> {
plugin.getLogger().warning("[AI聊天-调试] 响应头 " + name + ": " + String.join(", ", values));
});
plugin.getLogger().warning("[AI聊天-调试] 响应体: " + errBody);
plugin.getLogger().warning("[AI聊天-调试] === 自动互动请求失败结束 ===");
handleRequestFailure("[自动] " + prompt.substring(0, Math.min(100, prompt.length())), handleRequestFailure("[自动] " + prompt.substring(0, Math.min(100, prompt.length())),
"自动互动失败: HTTP " + response.statusCode()); "自动互动失败: HTTP " + response.statusCode());
prevAutoChatFailed = true; prevAutoChatFailed = true;
+197 -1
View File
@@ -14,6 +14,8 @@ import org.slf4j.LoggerFactory;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import com.google.gson.JsonArray; import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonParser;
import javax.net.ssl.*; import javax.net.ssl.*;
import java.io.*; import java.io.*;
import java.net.*; import java.net.*;
@@ -2401,6 +2403,10 @@ public class WebManager {
awaitDbQueueIdle(14000); // 等队列排空(原为固定6~14秒错峰) awaitDbQueueIdle(14000); // 等队列排空(原为固定6~14秒错峰)
// ★ syncLandData已由Timer D独立定时器处理(15~25秒),不再放批处理队列防SQL锁 // ★ syncLandData已由Timer D独立定时器处理(15~25秒),不再放批处理队列防SQL锁
submitNormalDbTask("周期-pollAdminChanges", () -> pollAdminChanges()); submitNormalDbTask("周期-pollAdminChanges", () -> pollAdminChanges());
// ★ 信誉积分双向同步:Java推送到PHP + 从PHP拉回管理员手动调整
submitNormalDbTask("周期-pushCreditToPhp", () -> pushCreditToPhp());
awaitDbQueueIdle(14000);
submitNormalDbTask("周期-pullCreditFromPhp", () -> pullCreditFromPhp());
checkSyncNotify(); checkSyncNotify();
} catch (Exception e) { } catch (Exception e) {
plugin.getLogger().warning("[合并C] 全量同步异常: " + e.getMessage()); plugin.getLogger().warning("[合并C] 全量同步异常: " + e.getMessage());
@@ -11890,10 +11896,200 @@ public class WebManager {
} }
} }
// ==================== 信誉积分双向同步 ====================
/** /**
* Java → PHP 推送信誉积分数据(PHP 端作为副本镜像)
* 推送内容:所有非默认积分的玩家 + 最近200条积分记录
* 在 Timer C 批处理中调用,错峰执行
*/
public void pushCreditToPhp() {
if (!isEnabled()) return;
try {
CreditManager cm = plugin.getCreditManager();
if (cm == null) return;
DatabaseManager db = plugin.getDb();
if (db == null) return;
// 收集所有非默认积分的玩家(默认100分不推,减少数据量)
Gson gson = new Gson();
JsonObject playersObj = new JsonObject();
try (Connection conn = db.getConnection();
Statement st = conn.createStatement();
ResultSet rs = st.executeQuery(
"SELECT player_name, credit_score FROM users WHERE credit_score IS NOT NULL AND credit_score != 100 ORDER BY player_name")) {
while (rs.next()) {
playersObj.addProperty(rs.getString("player_name"), rs.getInt("credit_score"));
}
} catch (Exception e) {
plugin.getLogger().warning("[信誉同步] 查询玩家积分失败: " + e.getMessage());
return;
}
// 收集最近200条积分记录
JsonArray recordsArr = new JsonArray();
try (Connection conn = db.getConnection();
PreparedStatement ps = conn.prepareStatement(
"SELECT target_player, reporter_player, type, content, status, score_change, created_at, approved_at, approve_reason, source "
+ "FROM credit_records ORDER BY created_at DESC LIMIT 200")) {
ResultSet rs = ps.executeQuery();
while (rs.next()) {
JsonObject rec = new JsonObject();
rec.addProperty("target_player", rs.getString("target_player"));
rec.addProperty("reporter_player", rs.getString("reporter_player"));
rec.addProperty("type", rs.getString("type"));
rec.addProperty("content", rs.getString("content"));
rec.addProperty("status", rs.getString("status"));
rec.addProperty("score_change", rs.getInt("score_change"));
rec.addProperty("created_at", rs.getLong("created_at"));
rec.addProperty("approved_at", rs.getLong("approved_at"));
rec.addProperty("approve_reason", rs.getString("approve_reason"));
rec.addProperty("source", rs.getString("source"));
recordsArr.add(rec);
}
} catch (Exception e) {
plugin.getLogger().warning("[信誉同步] 查询积分记录失败: " + e.getMessage());
return;
}
if (playersObj.entrySet().isEmpty() && recordsArr.isEmpty()) return;
// POST 到 PHP sync_push
String url = webBaseUrl + "/api/credit.php?action=sync_push&secret="
+ java.net.URLEncoder.encode(secretKey, "UTF-8");
JsonObject body = new JsonObject();
body.add("players", playersObj);
body.add("records", recordsArr);
String jsonBody = gson.toJson(body);
String result = doPost(url, jsonBody);
if (result != null) {
try {
JsonObject root = JsonParser.parseString(result).getAsJsonObject();
if (root.has("success") && root.get("success").getAsBoolean()) {
plugin.getLogger().info("[信誉同步] 推送PHP成功: " + playersObj.size() + "玩家, " + recordsArr.size() + "记录");
} else {
plugin.getLogger().warning("[信誉同步] 推送PHP失败: " + (root.has("message") ? root.get("message").getAsString() : "未知"));
}
} catch (Exception e) {
plugin.getLogger().warning("[信誉同步] 推送PHP响应解析失败: " + e.getMessage());
}
} else {
plugin.getLogger().warning("[信誉同步] 推送PHP请求失败(网络超时)");
}
} catch (Exception e) {
plugin.getLogger().warning("[信誉同步] 推送PHP异常: " + e.getMessage());
}
}
/**
* PHP → Java 拉回管理员手动调整的信誉积分
* 拉回内容:PHP端 source='admin' 的积分记录 → 应用到 Java DB
* 在 Timer C 批处理中调用,紧跟 pushCreditToPhp 之后
*/
public void pullCreditFromPhp() {
if (!isEnabled()) return;
try {
String url = webBaseUrl + "/api/credit.php?action=sync_pull&secret="
+ java.net.URLEncoder.encode(secretKey, "UTF-8");
String result = doGet(url);
if (result == null) {
plugin.getLogger().warning("[信誉同步] 拉回PHP失败(网络超时)");
return;
}
JsonObject root = JsonParser.parseString(result).getAsJsonObject();
if (!root.has("success") || !root.get("success").getAsBoolean()) {
plugin.getLogger().warning("[信誉同步] 拉回PHP失败: " + (root.has("message") ? root.get("message").getAsString() : "未知"));
return;
}
if (!root.has("data") || !root.get("data").isJsonObject()) return;
JsonObject data = root.getAsJsonObject("data");
// 处理管理员手动调整的记录(source='admin')
if (data.has("records") && data.get("records").isJsonArray()) {
JsonArray records = data.getAsJsonArray("records");
DatabaseManager db = plugin.getDb();
if (db == null) return;
int appliedCount = 0;
for (JsonElement el : records) {
JsonObject rec = el.getAsJsonObject();
String source = rec.has("source") ? rec.get("source").getAsString() : "";
// 只处理管理员手动调整的记录
if (!"admin".equals(source)) continue;
String target = rec.get("target_player").getAsString();
int scoreChange = rec.has("score_change") ? rec.get("score_change").getAsInt() : 0;
long createdAt = rec.has("created_at") ? rec.get("created_at").getAsLong() : 0;
// 去重:Java端已有同 target+source+created_at 的记录则跳过
try (Connection conn = db.getConnection();
PreparedStatement check = conn.prepareStatement(
"SELECT 1 FROM credit_records WHERE target_player = ? AND source = 'php' AND created_at = ?")) {
check.setString(1, target);
check.setLong(2, createdAt);
ResultSet checkRs = check.executeQuery();
if (checkRs.next()) {
checkRs.close();
continue; // 已同步过,跳过
}
checkRs.close();
} catch (Exception e) {
continue;
}
// 插入记录 + 更新积分
try (Connection conn = db.getConnection()) {
conn.setAutoCommit(false);
// 插入 credit_record
String type = scoreChange > 0 ? "good" : "bad";
String reason = rec.has("approve_reason") ? rec.get("approve_reason").getAsString() : "管理员手动调整";
try (PreparedStatement ps = conn.prepareStatement(
"INSERT INTO credit_records (target_player, reporter_player, type, content, status, score_change, created_at, approved_at, approve_reason, source) "
+ "VALUES (?, ?, ?, ?, 'approved', ?, ?, ?, ?, 'php')")) {
ps.setString(1, target);
ps.setString(2, rec.has("reporter_player") ? rec.get("reporter_player").getAsString() : "admin");
ps.setString(3, type);
ps.setString(4, rec.has("content") ? rec.get("content").getAsString() : reason);
ps.setInt(5, scoreChange);
ps.setLong(6, createdAt);
ps.setLong(7, rec.has("approved_at") ? rec.get("approved_at").getAsLong() : createdAt);
ps.setString(8, reason);
ps.executeUpdate();
}
// 更新玩家积分(直接设值,PHP端是副本但管理员调整需回写Java)
try (PreparedStatement ps = conn.prepareStatement(
"UPDATE users SET credit_score = credit_score + ? WHERE player_name = ?")) {
ps.setInt(1, scoreChange);
ps.setString(2, target);
ps.executeUpdate();
}
conn.commit();
appliedCount++;
} catch (Exception e) {
plugin.getLogger().warning("[信誉同步] 拉回记录应用失败 " + target + ": " + e.getMessage());
}
}
if (appliedCount > 0) {
plugin.getLogger().info("[信誉同步] 从PHP拉回" + appliedCount + "条管理员调整记录");
}
}
} catch (Exception e) {
plugin.getLogger().warning("[信誉同步] 拉回PHP异常: " + e.getMessage());
}
}
/**
* 检查同步通知文件 * 检查同步通知文件
*/ */
private void checkSyncNotify() { private void checkSyncNotify() {