核心思路:

  1. 在 Windows Server 上创建一个简单的 Web API 服务: 这个服务能够接收请求(例如,根据玩家名查询 UUID,根据 UUID 查询进度),然后去读取本地的 usercache.jsonadvancements/<uuid>.json 文件,最后将查询结果以 JSON 格式返回。
  2. 在 Halo 网站上(后端或前端)调用这个 API: 网站通过 HTTP 请求向 Windows Server 上的 API 服务请求数据。
  3. 在 Halo 网站上展示数据: 将从 API 获取到的数据显示给用户。

具体实施步骤:

第一步:在 Windows Server 上创建 API 服务

你需要选择一种技术来创建这个 API 服务。以下是几种常见的选择,你可以根据自己的熟悉程度来定:

  • Node.js (使用 Express 框架): 比较轻量,适合 I/O 操作(如读文件),跨平台,JavaScript 语法。
  • Python (使用 Flask 或 FastAPI 框架): 语法简洁,库丰富,也很适合快速开发 API。
  • .NET Core / ASP.NET Core: 如果你熟悉 C# 或 .NET 生态,这是 Windows 上的原生选择,性能优秀。
  • Go: 编译成单个可执行文件,部署简单,性能高。

以 Node.js + Express 为例(这是一个比较简单快速的方案):

  1. 安装 Node.js: 如果你的 Windows Server 上没有安装 Node.js,请先下载并安装 LTS 版本。

  2. 创建项目:

    • 在你方便管理的目录下(例如 C:\minecraft-api),打开 PowerShell 或 CMD。
    • 运行 npm init -y 初始化项目。
    • 运行 npm install express 安装 Express 框架。
  3. 编写 API 代码 (例如 server.js):

    const express = require('express');
    const fs = require('fs').promises; // 使用 Promise 版本的 fs
    const path = require('path');
    
    const app = express();
    const port = 3000; // 选择一个未被占用的端口
    
    // --- 配置 ---
    // !! 修改为你服务器上 Minecraft 根目录的实际路径 !!
    const MINECRAFT_SERVER_ROOT = 'C:\\path\\to\\your\\minecraft\\server';
    const USERCACHE_PATH = path.join(MINECRAFT_SERVER_ROOT, 'usercache.json');
    const ADVANCEMENTS_DIR = path.join(MINECRAFT_SERVER_ROOT, 'world', 'advancements'); // 假设存档名叫 world
    
    // --- 辅助函数:读取和解析 JSON 文件 ---
    async function readJsonFile(filePath) {
        try {
            const data = await fs.readFile(filePath, 'utf8');
            return JSON.parse(data);
        } catch (error) {
            if (error.code === 'ENOENT') {
                // 文件不存在
                return null;
            }
            console.error(`Error reading or parsing JSON file ${filePath}:`, error);
            throw new Error('Failed to read or parse file'); // 抛出通用错误
        }
    }
    
    // --- API 端点 ---
    
    // 1. 根据用户名获取 UUID
    app.get('/api/uuid', async (req, res) => {
        const username = req.query.username;
        if (!username) {
            return res.status(400).json({ error: 'Username query parameter is required' });
        }
    
        try {
            const userCache = await readJsonFile(USERCACHE_PATH);
            if (!userCache) {
                return res.status(404).json({ error: 'usercache.json not found' });
            }
    
            const userEntry = userCache.find(entry => entry.name.toLowerCase() === username.toLowerCase());
    
            if (userEntry) {
                res.json({ uuid: userEntry.uuid });
            } else {
                res.status(404).json({ error: 'User not found in cache' });
            }
        } catch (error) {
            res.status(500).json({ error: 'Internal server error while fetching UUID' });
        }
    });
    
    // 2. 根据 UUID 获取进度
    app.get('/api/advancements', async (req, res) => {
        const uuid = req.query.uuid;
        if (!uuid) {
            return res.status(400).json({ error: 'UUID query parameter is required' });
        }
    
        // 基本的 UUID 格式校验 (可选但推荐)
        if (!/^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/.test(uuid)) {
             return res.status(400).json({ error: 'Invalid UUID format' });
        }
    
        const advancementFilePath = path.join(ADVANCEMENTS_DIR, `${uuid}.json`);
    
        try {
            const advancementsData = await readJsonFile(advancementFilePath);
            if (!advancementsData) {
                // 文件不存在可能意味着玩家从未获得过任何进度,或者从未进入过这个存档
                // 返回空对象或特定状态可能比 404 更好
                return res.json({}); // 或者 res.status(200).json({ message: 'No advancements found for this user.' });
            }
    
            // 你可以在这里进行数据处理,比如只返回 'done: true' 的进度
            const completedAdvancements = {};
            for (const key in advancementsData) {
                // 过滤掉非进度条目,例如 DataVersion
                if (key !== 'DataVersion' && advancementsData[key].done === true) {
                    completedAdvancements[key] = advancementsData[key]; // 或者只返回 true,或更精简的信息
                }
            }
             // 如果需要原始数据,直接返回 advancementsData
            res.json(advancementsData); // 返回原始数据,让前端处理
            // res.json(completedAdvancements); // 或者只返回已完成的进度
    
        } catch (error) {
             // 如果 readJsonFile 内部抛出错误 (非文件不存在)
             res.status(500).json({ error: 'Internal server error while fetching advancements' });
        }
    });
    
    // --- 启动服务器 ---
    app.listen(port, () => {
        console.log(`Minecraft API server listening at http://localhost:${port}`);
        console.log(`Using Minecraft root: ${MINECRAFT_SERVER_ROOT}`);
        console.log(`Reading usercache from: ${USERCACHE_PATH}`);
        console.log(`Reading advancements from: ${ADVANCEMENTS_DIR}`);
    });
    
  4. 运行 API 服务:

    • 在 PowerShell 或 CMD 中,进入项目目录 (C:\minecraft-api)。
    • 运行 node server.js
    • 你应该会看到服务启动的日志。
    • 配置防火墙: 确保 Windows Server 的防火墙允许外部(至少是你的 Halo Docker 容器所在的机器)访问你设置的端口(例如 3000)。
  5. (可选)让 API 服务在后台持续运行: 你可以使用 pm2 (Node.js 进程管理器) 或将脚本设置为 Windows 服务来确保 API 持续运行并在服务器重启后自动启动。

第二步:在 Halo 网站上实现查询功能

Halo 是一个博客系统,你可以通过开发主题插件来实现这个功能。使用主题模板可能更直接。

  1. 创建查询页面/组件 (Halo 主题):

    • 在你的 Halo 主题中,创建一个新的页面模板 (.html.ft 文件,取决于你的主题引擎,通常是 FreeMarker 或 Thymeleaf)。
    • 在这个模板中,添加一个 HTML 表单,让用户输入他们的 Minecraft 玩家名称。
    • 使用 JavaScript (最好是异步方式,如 fetch API) 来处理表单提交。
  2. 编写前端 JavaScript:

    • 当用户提交表单时,JavaScript 首先调用你在 Windows Server 上部署的 API 的 /api/uuid 端点,传入玩家名,获取 UUID。
    • 获取到 UUID 后,再调用 API 的 /api/advancements 端点,传入 UUID,获取进度数据。
    • 重要: 在 JavaScript 中,你需要使用 Windows Server 的IP 地址(或如果你配置了域名的话,使用域名)和 API 端口来构造请求 URL,例如 http://<WindowsServerIP>:3000/api/uuid?username=...。确保 Halo Docker 容器所在的网络可以访问到这个 IP 和端口。
    • 获取到进度数据 (JSON 格式) 后,解析 JSON,并将结果动态地显示在页面上(例如,创建一个列表展示已完成的进度)。

示例 JavaScript (使用 fetch):

// 假设你有一个 id 为 'lookupForm' 的表单和一个 id 为 'usernameInput' 的输入框
// 以及一个 id 为 'results' 的 div 用于显示结果

const form = document.getElementById('lookupForm');
const usernameInput = document.getElementById('usernameInput');
const resultsDiv = document.getElementById('results');
// !! 替换成你 Windows Server 的实际 IP 地址或域名 !!
const apiBaseUrl = 'http://YOUR_WINDOWS_SERVER_IP:3000/api';

form.addEventListener('submit', async (event) => {
    event.preventDefault(); // 阻止表单默认提交行为
    const username = usernameInput.value.trim();
    resultsDiv.innerHTML = '正在查询...'; // 提供反馈

    if (!username) {
        resultsDiv.innerHTML = '请输入玩家名称!';
        return;
    }

    try {
        // 1. 获取 UUID
        const uuidResponse = await fetch(`${apiBaseUrl}/uuid?username=${encodeURIComponent(username)}`);
        if (!uuidResponse.ok) {
            const errorData = await uuidResponse.json().catch(() => ({ error: `获取 UUID 失败,状态码:${uuidResponse.status}` }));
            throw new Error(errorData.error || `获取 UUID 失败`);
        }
        const uuidData = await uuidResponse.json();
        const uuid = uuidData.uuid;

        // 2. 获取进度
        const advancementsResponse = await fetch(`${apiBaseUrl}/advancements?uuid=${uuid}`);
         if (!advancementsResponse.ok) {
            const errorData = await advancementsResponse.json().catch(() => ({ error: `获取进度失败,状态码:${advancementsResponse.status}` }));
            throw new Error(errorData.error || `获取进度失败`);
        }
        const advancementsData = await advancementsResponse.json();

        // 3. 显示结果
        displayAdvancements(username, advancementsData);

    } catch (error) {
        console.error('查询出错:', error);
        resultsDiv.innerHTML = `查询出错:${error.message}`;
    }
});

function displayAdvancements(username, data) {
    let html = `<h3>玩家 ${username} 的进度:</h3>`;
    const completed = [];

    // 遍历进度数据,找到已完成的 (done: true)
    for (const id in data) {
        // 过滤掉元数据,例如 DataVersion
        if (id !== 'DataVersion' && data[id].done === true) {
             // 你可能想显示更友好的名称,但这需要额外的映射
             // 这里简单地显示 ID
            completed.push(id);
        }
    }

    if (completed.length > 0) {
        html += '<ul>';
        completed.forEach(advancementId => {
            html += `<li>${advancementId}</li>`;
        });
        html += '</ul>';
    } else {
        html += '<p>该玩家没有已完成的进度记录,或者从未进入过服务器。</p>';
    }

    resultsDiv.innerHTML = html;
}
  1. 将 JavaScript 集成到 Halo 主题: 将上述 JavaScript 代码放入你的主题模板的 <script> 标签中,或者链接到一个单独的 .js 文件。

第三步:网络和安全注意事项

  1. 网络访问: 确保运行 Halo 的 Docker 容器可以访问到 Windows Server 上 API 服务监听的 IP 地址和端口。这可能涉及到 Docker 网络配置(例如,使用 host 网络模式,或者确保容器网络可以路由到 Windows Server 的 IP)和 Windows Server 的防火墙配置。
  2. API 安全 (可选但推荐):
    • HTTPS: 如果可能,为你的 API 服务配置 HTTPS,以加密传输的数据。可以使用 mkcert (本地开发) 或 Let's Encrypt (如果需要公网访问)。
    • 简单认证: 可以考虑添加一个简单的 API 密钥。API 服务要求请求头中包含一个预设的密钥,Halo 在发送请求时带上这个密钥。这可以防止未经授权的访问。
    • IP 限制: 如果 Halo 服务器的 IP 是固定的,可以在 API 服务或 Windows 防火墙层面只允许来自该 IP 的访问。
  3. 错误处理: API 和前端代码都需要健壮的错误处理,例如处理玩家不存在、文件读取失败、网络请求超时等情况,并向用户提供有意义的反馈。

总结:

通过在 Windows Server 上建立一个专门读取游戏数据的 API 服务,你可以安全、有效地将 Minecraft 进度数据暴露给运行在 Docker 中的 Halo 网站,避免了直接文件共享带来的复杂性和安全风险。这种方式也更具扩展性,未来如果需要查询其他游戏数据(如统计信息、玩家在线状态等),只需在 API 服务上添加新的端点即可。