Files
MC_Report/docs/PLUGIN-GUIDE.md
canglan 356d7ab5ab chore: remove plugin from repo, add plugin guide, docs + AGPLv3 license
- mc-report-plugin removed from git tracking (kept locally), gitignored;
  plugin code no longer distributed (old x-external-key auth, outdated)
- docs/PLUGIN-GUIDE.md: Java plugin integration guide using new
  ID+Secret -> SESSION auth (session mgmt, submit ticket, track, bans)
- README: updated external API table (SESSION), dynamic sources,
  removed obsolete plugin commands/tech row, new dir structure, doc links
- INSTALL: drop restart requirement, add upgrade-migration/log/sources
  sections, license section
- LICENSE: GNU AGPL v3 full text
- verified: 24 checks (git tracking, docs consistency, license integrity)
2026-08-19 20:23:04 +08:00

6.0 KiB

MC 插件对接指南(外部 API)

本文面向 Bukkit/Paper/Spigot 插件开发者,演示如何用 Java 对接 MC Report 外部 API(提交举报、同步封禁、查询进度)。完整接口定义见 EXTERNAL-API.md

仓库不再内置插件代码 —— 按本指南即可自行实现;凭据(ID+Secret)在站点后台「外部API」页创建。


一、鉴权流程(Java 实现)

外部 API 用 ID + Secret 换取 SESSION,之后所有请求带 Authorization: Bearer <SESSION>

public class McReportApi {
    private final String baseUrl;        // 如 https://mc.example.com/api/external
    private final String clientId;       // 后台创建: 16 位数字
    private final String clientSecret;   // 后台创建: 32 位随机串
    private String sessionToken;
    private long sessionExpiresAt;       // 到期时间戳(ms)

    public McReportApi(String baseUrl, String clientId, String clientSecret) {
        this.baseUrl = baseUrl;
        this.clientId = clientId;
        this.clientSecret = clientSecret;
    }

    /** 获取有效 SESSION, 过期则自动换新 */
    public synchronized String session() throws Exception {
        if (sessionToken != null && System.currentTimeMillis() < sessionExpiresAt) {
            return sessionToken;
        }
        JSONObject body = new JSONObject();
        HttpURLConnection conn = open("POST", "/auth/session", null);
        conn.setRequestProperty("x-api-client-id", clientId);
        conn.setRequestProperty("x-api-secret", clientSecret);
        conn.setDoOutput(true);
        conn.getOutputStream().write(body.toString().getBytes(StandardCharsets.UTF_8));

        JSONObject resp = readJson(conn);
        sessionToken = resp.getString("session_token");
        sessionExpiresAt = System.currentTimeMillis() + resp.getLong("expires_in") * 1000L - 60_000L; // 提前1分钟过期
        return sessionToken;
    }

    private HttpURLConnection open(String method, String path, String token) throws Exception {
        HttpURLConnection conn = (HttpURLConnection) new URL(baseUrl + path).openConnection();
        conn.setRequestMethod(method);
        conn.setConnectTimeout(5000);
        conn.setReadTimeout(10000);
        conn.setRequestProperty("Content-Type", "application/json");
        conn.setRequestProperty("User-Agent", "MCReportPlugin/1.0");
        if (token != null) conn.setRequestProperty("Authorization", "Bearer " + token);
        return conn;
    }

    private JSONObject readJson(HttpURLConnection conn) throws Exception {
        int code = conn.getResponseCode();
        InputStream in = code >= 200 && code < 300 ? conn.getInputStream() : conn.getErrorStream();
        String text = new String(in.readAllBytes(), StandardCharsets.UTF_8);
        JSONObject json = new JSONObject(text);
        if (code >= 400) throw new RuntimeException(json.optString("error", "HTTP " + code));
        return json;
    }
}

二、提交举报工单

/** 玩家在游戏内执行 /report 命令时调用 */
public JSONObject submitReport(String reporterName, String targetName, String reason,
                               String serverName, String description) throws Exception {
    String token = session();
    JSONObject body = new JSONObject();
    body.put("type", "report");
    body.put("title", "游戏内举报: " + targetName);
    body.put("reporter_game_name", reporterName);
    body.put("target_game_name", targetName);
    body.put("reason", reason);
    body.put("description", description);
    body.put("server", serverName);          // 子服别名或「分组/子服」

    HttpURLConnection conn = open("POST", "/tickets", token);
    conn.setDoOutput(true);
    conn.getOutputStream().write(body.toString().getBytes(StandardCharsets.UTF_8));
    JSONObject resp = readJson(conn);
    // resp: {"id": 123, "tracking_token": "..."}
    return resp;
}

返回的 tracking_token 请保存(例如写进玩家持久数据),玩家可用它查询处理进度:

public JSONObject trackTicket(String trackingToken) throws Exception {
    return readJson(open("GET", "/tickets/track?token=" + URLEncoder.encode(trackingToken, "UTF-8"), session()));
}

三、拉取/同步封禁

/** 定时任务: 每 5 分钟同步一次本服生效封禁 */
public JSONArray fetchActiveBans(String serverName) throws Exception {
    String token = session();
    String path = "/bans?status=active&limit=500"
        + (serverName != null && !serverName.isEmpty()
            ? "&server=" + URLEncoder.encode(serverName, "UTF-8") : "");
    return readJson(open("GET", path, token)).getJSONArray("data");
}

/** 服务器内封禁玩家后同步到系统 */
public JSONObject createBan(String playerName, String reason, String type,
                            String duration, String serverName) throws Exception {
    JSONObject body = new JSONObject();
    body.put("player_name", playerName);
    body.put("reason", reason);
    body.put("type", type);                  // ban / mute / warn
    body.put("duration", duration);          // 如 "7d", 空=永久
    body.put("server", serverName);
    return readJson(open("POST", "/bans", session()));  // 需带 body
}

注意:POST /bans 需设置 setDoOutput(true) 并写入 body(与 submitReport 相同写法)。

四、配置

plugins/<你的插件>/config.yml:

# MC Report 外部 API 配置
api_url: "https://mc.example.com/api/external"
client_id: "1829473056482917"      # 后台「外部API」创建
client_secret: "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6"
server_name: "survival"            # 本服别名(后台服务器管理配置)

常见问题

问题 处理
401 ID/Secret 错误或客户端被停用;检查后台「外部API」
401 SESSION 无效或已过期 重新调用 /auth/session 换取(示例代码已自动处理)
400 待处理工单已达上限 同一玩家待处理工单最多 5 个
换服/改名 server 参数按子服隔离数据;玩家身份以 game_name 为准