核心思路:
- 在 Windows Server 上创建一个简单的 Web API 服务: 这个服务能够接收请求(例如,根据玩家名查询 UUID,根据 UUID 查询进度),然后去读取本地的
usercache.json
和advancements/<uuid>.json
文件,最后将查询结果以 JSON 格式返回。 - 在 Halo 网站上(后端或前端)调用这个 API: 网站通过 HTTP 请求向 Windows Server 上的 API 服务请求数据。
- 在 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 为例(这是一个比较简单快速的方案):
-
安装 Node.js: 如果你的 Windows Server 上没有安装 Node.js,请先下载并安装 LTS 版本。
-
创建项目:
- 在你方便管理的目录下(例如
C:\minecraft-api
),打开 PowerShell 或 CMD。 - 运行
npm init -y
初始化项目。 - 运行
npm install express
安装 Express 框架。
- 在你方便管理的目录下(例如
-
编写 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}`); });
-
运行 API 服务:
- 在 PowerShell 或 CMD 中,进入项目目录 (
C:\minecraft-api
)。 - 运行
node server.js
。 - 你应该会看到服务启动的日志。
- 配置防火墙: 确保 Windows Server 的防火墙允许外部(至少是你的 Halo Docker 容器所在的机器)访问你设置的端口(例如 3000)。
- 在 PowerShell 或 CMD 中,进入项目目录 (
-
(可选)让 API 服务在后台持续运行: 你可以使用
pm2
(Node.js 进程管理器) 或将脚本设置为 Windows 服务来确保 API 持续运行并在服务器重启后自动启动。
第二步:在 Halo 网站上实现查询功能
Halo 是一个博客系统,你可以通过开发主题或插件来实现这个功能。使用主题模板可能更直接。
-
创建查询页面/组件 (Halo 主题):
- 在你的 Halo 主题中,创建一个新的页面模板 (
.html
或.ft
文件,取决于你的主题引擎,通常是 FreeMarker 或 Thymeleaf)。 - 在这个模板中,添加一个 HTML 表单,让用户输入他们的 Minecraft 玩家名称。
- 使用 JavaScript (最好是异步方式,如
fetch
API) 来处理表单提交。
- 在你的 Halo 主题中,创建一个新的页面模板 (
-
编写前端 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 首先调用你在 Windows Server 上部署的 API 的
示例 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;
}
- 将 JavaScript 集成到 Halo 主题: 将上述 JavaScript 代码放入你的主题模板的
<script>
标签中,或者链接到一个单独的.js
文件。
第三步:网络和安全注意事项
- 网络访问: 确保运行 Halo 的 Docker 容器可以访问到 Windows Server 上 API 服务监听的 IP 地址和端口。这可能涉及到 Docker 网络配置(例如,使用
host
网络模式,或者确保容器网络可以路由到 Windows Server 的 IP)和 Windows Server 的防火墙配置。 - API 安全 (可选但推荐):
- HTTPS: 如果可能,为你的 API 服务配置 HTTPS,以加密传输的数据。可以使用
mkcert
(本地开发) 或 Let's Encrypt (如果需要公网访问)。 - 简单认证: 可以考虑添加一个简单的 API 密钥。API 服务要求请求头中包含一个预设的密钥,Halo 在发送请求时带上这个密钥。这可以防止未经授权的访问。
- IP 限制: 如果 Halo 服务器的 IP 是固定的,可以在 API 服务或 Windows 防火墙层面只允许来自该 IP 的访问。
- HTTPS: 如果可能,为你的 API 服务配置 HTTPS,以加密传输的数据。可以使用
- 错误处理: API 和前端代码都需要健壮的错误处理,例如处理玩家不存在、文件读取失败、网络请求超时等情况,并向用户提供有意义的反馈。
总结:
通过在 Windows Server 上建立一个专门读取游戏数据的 API 服务,你可以安全、有效地将 Minecraft 进度数据暴露给运行在 Docker 中的 Halo 网站,避免了直接文件共享带来的复杂性和安全风险。这种方式也更具扩展性,未来如果需要查询其他游戏数据(如统计信息、玩家在线状态等),只需在 API 服务上添加新的端点即可。