Создайте локальный API с явным контрактом
Установите Python 3, создайте пустую папку stats-demo и сохраните следующий код как api.py. Он использует только стандартную библиотеку и слушает 127.0.0.1:8088, поэтому предназначен для локального обучения; http.server не следует выставлять как готовый production API.
Единственный профиль содержит учебный UUID и две метрики с единицами в имени: секунды и количество побед. Ответ не читается из Minecraft и не является статистикой реального игрока. Следующий шаг интеграции — заполнить такие поля из документированного публичного источника своего плагина, не отдавая приватные таблицы целиком.
import json
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
PLAYER_ID = "00000000-0000-4000-8000-000000000001"
PROFILE = {"uuid": PLAYER_ID, "name": "Demo",
"parkour_best_seconds": 42.5, "pixel_wins": 3}
class Handler(BaseHTTPRequestHandler):
def do_GET(self):
found = self.path == f"/api/v1/players/{PLAYER_ID}"
data = PROFILE if found else {"error": "player_not_found"}
body = json.dumps(data, ensure_ascii=False).encode("utf-8")
self.send_response(200 if found else 404)
self.send_header("Content-Type", "application/json; charset=utf-8")
self.send_header("Content-Length", str(len(body)))
self.send_header("Cache-Control", "no-store")
self.end_headers()
self.wfile.write(body)
ThreadingHTTPServer(("127.0.0.1", 8088), Handler).serve_forever()Запустите и проверьте ответы
В терминале этой папки выполните в Linux/macOS python3 api.py, в Windows py api.py. Оставьте окно открытым. В другом терминале выполните curl -i http://127.0.0.1:8088/api/v1/players/00000000-0000-4000-8000-000000000001; в Windows используйте curl.exe. Должен прийти HTTP 200, JSON и pixel_wins: 3.
Повторите запрос с окончанием /missing: ожидается HTTP 404 и player_not_found. Откройте точный URL профиля в браузере: JSON виден без Java-плагина и токена. Если порт занят, измените 8088 в коде и во всех запросах. Для остановки локального примера нажмите Ctrl+C.
Подключите чтение к сайту
В приложении сайта запрашивайте свой /api/v1/players/UUID через серверную загрузку или same-origin маршрут. В браузере обработайте response.ok, затем response.json() по Fetch API. Страница должна показать ошибку или отсутствие профиля отдельно от честного нулевого показателя.
Для локального browser fetch от другого порта возникает другой origin: подготовьте reverse proxy либо серверную загрузку, вместо добавления произвольного Access-Control-Allow-Origin: * ко всем приватным ответам. На публичном сайте используйте HTTPS и поддерживаемый веб-фреймворк, а учебный Python обработчик оставьте только для проверки контракта.
Передавайте игровые результаты отдельно от чтения
Запись из Minecraft должна обращаться к защищённому ingest API с секретом, который хранится в серверном окружении. В браузерные переменные, HTML и публичный JSON этот ключ не попадает. Для повторной доставки сохраняйте eventId и подтверждённый результат в устойчивой очереди.
Полный снимок статистики обычно заменяет прежнее представление, а событие добавляет один новый факт. Определите это до первой записи: повтор снимка не должен удваивать победы. Проверяйте путь с настоящим тестовым результатом: игра → хранилище → ingest → публичный JSON → страница, включая отключение API и повтор того же идентификатора.
Опишите публичную метрику до передачи данных
Для каждого показателя задайте ключ, единицу, направление сортировки и смысл отсутствия. Время Dropper меньше — лучше, очки Mining Rush больше — лучше; нулевой результат отличается от отсутствующей записи. Передавайте стабильный идентификатор метрики, а русскую подпись держите в согласованном каталоге. Отдельно определите период, если рейтинг дневной или сезонный.
Игровой UUID и отображаемый ник выполняют разные задачи. UUID связывает записи выбранной системы аккаунтов, ник удобно показывать пользователю. Не раскрывайте через публичный DTO приватные заметки, IP и данные аутентификации. Даже если они доступны в исходной базе, API должен выбирать разрешённые поля, а не возвращать всю строку таблицы.
Пример контракта результата
Публичный ответ может содержать playerUuid, displayName, metric, value и recordedAt. Для точной валюты используйте формат, который не теряет дробные значения при преобразовании клиента. Для времени договоритесь о миллисекундах либо секундах. Запишите пример запроса и ответа рядом с валидацией, чтобы игра, API и сайт не трактовали число по-разному.
Передавать нужно подтверждённый факт, а не промежуточное состояние раунда. Устойчивые игровые записи.
Выберите события или снимки осознанно
Событие сообщает один результат с eventId; API принимает повтор идемпотентно. Снимок сообщает актуальный набор записей с версией или временем, а API заменяет соответствующее представление по контракту. Эти модели нельзя смешивать без правил: повтор снимка не должен суммировать уже суммированную статистику.
Если источник — SQLite или YAML другого плагина, используйте поддерживаемое чтение и проверяйте отсутствие частичной записи. Для SQLite предусмотрена согласованная копия, для файлов — атомарная публикация или документированный формат. Не держите игровой поток в ожидании сетевого ответа. Отправляющий процесс может читать подтверждённые данные по расписанию, если это соответствует допустимой задержке.
У события сначала сохраняется локальная очередь, затем выполняется HTTP. Подтверждение API позволяет отметить доставку. При тайм-ауте неизвестно, принял ли сервер запрос, поэтому нужен повтор с тем же eventId. Уникальность на стороне API предотвращает вторую запись. Ошибка 4xx из-за неверного контракта требует разбора, а временный 5xx — ограниченного повторения.
Таймаут и предел очереди защищают игру от недоступного веб-сервиса. Сеть вне игрового тика.
Авторизация на каждой стороне
Машинный токен хранится только у доверенного отправителя и API. Его права ограничены нужной операцией доставки; публичный браузер не получает этот ключ. Административный интерфейс использует отдельную сессию и проверяет доступ на каждом endpoint. Для cookie-сессии предусмотрите защиту изменяющих запросов от нежелательного внешнего источника.
Отклоняйте слишком большой payload, неизвестную метрику и некорректный UUID до записи. Задайте предел числа элементов и времени обработки. TLS защищает канал, но не заменяет аутентификацию и валидацию. В журнале нужен идентификатор доставки и итог, а не значение Authorization.
Проверка всей цепи
В интерфейсе покажите загрузку, отсутствие результатов и временную ошибку отдельно. «0» при отказе API превращает недоступность в ложную статистику. Если используется кэш, укажите его допустимый срок и проверяйте, что приватные ответы не получают публичное кэширование. Основной текст сайта должен оставаться читаемым даже при ошибке живых счётчиков.
- Создайте один подтверждённый результат тестового игрока.
- Найдите его в устойчивом игровом источнике или очереди.
- Отправьте в отдельную API-базу с тестовым ключом.
- Повторите тот же идентификатор и проверьте отсутствие дубликата.
- Получите публичный ответ и сравните число и единицу.
- Откройте страницу, затем смоделируйте отсутствие API.
Обновление контракта
Новая метрика требует согласованного каталога игры, API и клиента. Добавьте её валидацию, единицу и сортировку до публикации кнопки. При изменении смысла существующего поля используйте явную миграцию или версию контракта, иначе старые записи будут сравниваться с новыми некорректно. Идентификатор названия сам по себе не объясняет такую смену.
Проверьте восстановление очереди после рестарта и наблюдаемость задержки: сколько событий ожидают, как давно последний успешный запрос и какие записи требуют разбора. Большая очередь не должна незаметно заполнять диск. Возможность повторной доставки и строгий публичный DTO делают интеграцию устойчивой к сбоям каждой отдельной части.



