跳到主要内容

RhythMC-Reborn HTTP API 规范文档

目录

  1. API 概述
  2. 通用规范
  3. 服务器 API
  4. 玩家 API
  5. 资源 API
  6. 数据上传 API
  7. 请求类详解

API 概述

基础信息

项目
基础URL{game-server} (配置文件指定)
协议HTTPS (TLSv1.3)
认证方式/server/login 使用 Authorization: Bearer <serverBootstrapToken> 建立服务器身份;其余受保护后端接口和 WebSocket 都使用短期 Bearer access token
数据格式JSON
字符编码UTF-8

请求架构


通用规范

请求头

所有请求自动添加以下请求头:

User-Agent: RhythMC/{version} Java/{java-version}
Content-Type: application/json

服务器登录成功后,受保护的后端请求额外附加:

Authorization: Bearer <serverAccessToken>

serverAccessToken 是服务器登录响应中返回的短期 opaque Bearer 令牌,由 ServerSession 持有。客户端仅对受保护的后端业务接口自动注入 Authorization: Bearer <serverAccessToken>,不会对 Mojang API、CDN 下载地址或资源直链附带该头。WebSocket 握手也使用同一令牌作为 Bearer 头。

/server/login 现在优先使用请求头中的服务器 bootstrap Bearer 令牌建立服务器身份;登录成功后的 /server/*/player/* 等受保护后端接口使用短期 Bearer access token 继续鉴权。历史请求体字段 serverAuthToken 仅保留兼容读取。

serverAccessToken 当前有效期默认 10 分钟。若 WebSocket 意外断开,客户端会先调用 /server/refresh 刷新令牌;刷新失败则使本地令牌失效,并重新执行完整的 /server/login 登录流程。

响应格式

成功响应:

{
"field1": "value1",
"field2": "value2"
}

错误响应:

{
"error": "ERROR_CODE",
"message": "Error description"
}

HTTP 状态码

状态码含义处理方式
200成功正常处理
400请求格式错误记录日志,提示用户
401认证失败重新登录
403权限不足检查服务器登录 token 或后端授权配置
429请求过于频繁等待后重试
430账号已封禁踢出玩家,显示封禁原因和解封时间
500服务器错误记录日志,注册资源包 fallback 后进入离线模式

服务器 API

POST /server/login

描述: 服务器登录,获取服务器信息和资源服务器地址

请求类: LoginRequest

请求体: 请求头:

Authorization: Bearer <serverBootstrapToken>

请求体:

{
"pluginVersion": 1,
"pluginSHA": "4a8c1e..."
}

请求字段:

字段类型必需描述
pluginVersionint插件版本号/版本编码
pluginSHAString当前插件构建 SHA,用于服务端校验版本来源
Authorization BearerString长期 bootstrap 凭证,仅允许用于 /server/login

成功响应 (200):

{
"serverID": 12345,
"serverName": "CN-East-01",
"assetServer": "https://assets.rhythmc.example.com",
"serverIP": "192.168.1.100",
"country": "CN",
"wsServer": "wss://ws.rhythmc.example.com",
"accessToken": "opaque-server-access-token",
"expiresIn": 600
}

响应字段:

字段类型描述
serverIDint服务器在远程系统的唯一ID
serverNameString服务器名称
assetServerString资源服务器基础URL
serverIPString服务器公网IP
countryString服务器所在国家/地区
wsServerStringWebSocket服务器地址
accessTokenString短期 opaque Bearer 令牌,后续所有 HTTP/WebSocket 请求携带此令牌
expiresInintaccessToken 的 TTL 秒数

失败响应:

状态码含义说明
401登录失败典型场景为后端维护模式;响应 message 会被插件打印到控制台
426插件异常版本或 SHA 校验失败,响应会带 toVersiondownUrl
429速率限制后端拒绝当前请求频率

会话覆盖规则:

  • 若同一 serverID 在旧会话尚未主动 /server/logout 时再次调用 /server/login,后端会先强制下线旧服务器会话,再为新请求签发新的 accessToken
  • 旧服务器会话绑定的玩家会话也会一并失效,避免同一物理服残留多个活动 serverSession

流程图:


GET /server/refresh

描述: 刷新服务器当前使用的 Bearer Auth Token。

请求类: RefreshAuthTokenRequest

认证: 需要 Authorization: Bearer <serverAccessToken>

说明: 当前客户端在 WebSocket 非预期断开后优先调用此接口。若刷新成功,则更新内存中的 serverAccessToken 并继续使用新令牌进行重连;若刷新失败,则本地令牌失效并触发完整的 /server/login 重登录。

成功响应 (200):

{
"accessToken": "opaque-server-access-token",
"expiresIn": 600
}

响应字段:

字段类型描述
accessTokenString新的短期 Bearer 令牌
expiresInintaccessToken 的 TTL 秒数

GET /server/collections

描述: 获取服务器定义的收藏品列表,登录后与资源同步、资源包列表加载并行执行

请求类: GetCollectionsRequest

URL: {game-server}/server/collections

认证: 需要 Authorization: Bearer <serverAccessToken>。请求体中的 player.accessToken 会优先按玩家会话令牌校验;如果该令牌已经过期,但该玩家在同一个 serverSession 下仍存在有效在线会话,后端会回退到该会话继续接收成绩上传,避免长局结算时因玩家令牌过期丢成绩。

响应 (200):

[
{
"key": "rhythmc:title.beta",
"type": "title",
"unlockMethod": {
"type": "ACHIEVEMENT",
"name": "封测成就",
"mode": "ALL",
"events": [
"player.login"
],
"player": {
"betaTester": {
"operator": "EQUALS",
"value": true
}
}
},
"properties": {
"title": "Beta测试员",
"position": true
}
},
{
"key": "rhythmc:title.dev",
"type": "title",
"unlockMethod": {
"type": "MANUAL"
},
"properties": {
"title": "开发者",
"position": false
}
}
]

响应字段:

字段类型描述
keyString收藏品唯一键,格式 namespace:category.id
typeString收藏品类型,当前客户端使用 title / tag
unlockMethodObject解锁方式对象,至少包含 type,并可选携带完整规则字段:namedescriptionmodeeventsinfovaluesplayerconditionsmethodsgrantsdefaultUnlockedhideCondition
propertiesObject类型相关属性;对 title / tag 至少包含 titleposition

处理逻辑: 响应数据转换为客户端收藏品对象后批量存入 CollectionsManager,与资源完整性校验、ResourcePackManager.init() 并行执行。客户端会直接使用 unlockMethod 的完整规则对象参与 UnlockService 判定。任一失败将导致注册本地资源包 fallback 并进入离线模式。


GET /server/baltop

描述: 获取当前 KWh 财富排行榜,供插件侧 /baltop 命令展示。

请求类: GetBaltopRequest

URL: {game-server}/server/baltop?limit=10

认证: 需要 Authorization: Bearer <serverAccessToken>

查询参数:

参数类型必需描述
limitint返回条数,默认 10,范围 1-100

响应 (200):

[
{
"rank": 1,
"uid": 10001,
"username": "Player_1234",
"displayName": "Frkovo",
"wattHour": 952700
}
]

响应字段:

字段类型描述
rankint排行名次,当前按 watt_hour DESC, id ASC 计算
uidlong平台玩家 UID,对应 players.id
usernameString后端保存的账号名
displayNameString展示名;为空时后端会回退为 username
wattHourlong当前 KWh 余额

错误响应:

状态码含义说明
400参数错误limit 不是整数,或不在 1-100 范围内
401认证失败缺少或携带了无效的 serverAccessToken
500服务器错误后端读取排行榜失败

说明:

  • 当前排行榜仍以数据库 players.watt_hour 为唯一来源。
  • 当前经济模式改为插件本地计算 wattHour,每次本地余额变化后通过 POST /player/economy 上传最新余额到后端。

GET /server/ping

描述: 测试服务器连通性

返回: CompletableFuture<Boolean>

使用场景:

  • 插件启动时检测网络
  • 管理员手动测试连接
  • 定期健康检查

玩家 API

POST /player/login

描述: 玩家登录,支持三种登录模式

离线例外: 当服务器自身尚未完成 /server/login 或已进入离线模式时,客户端不会调用本接口,而是直接在本地创建游客档案供玩家进入游戏。

请求类: PlayerLoginRequest

登录模式:

模式1: ONLINE (正版自动登录)

请求体:

{
"mode": "ONLINE",
"uuid": "550e8400-e29b-41d4-a716-446655440000",
"version": 1,
"authToken": "optional-token"
}

验证流程:

  1. 通过 Mojang API 验证 UUID
  2. 检查玩家是否为正版用户
  3. 返回玩家档案

模式2: TOKEN (Net Token手动登录)

请求体:

{
"mode": "TOKEN",
"version": 1,
"authToken": "user-provided-token"
}

使用场景: 玩家手动输入令牌进行登录

模式3: OFFLINE_REFRESH (盗版自动续登)

请求体:

{
"mode": "OFFLINE_REFRESH",
"version": 1,
"resumeToken": "locally-cached-token"
}

使用场景: 使用插件本地缓存的 token 自动续期。插件重载后,只要 token 未过期,玩家仍可自动续登;整服/JVM 关闭后本地缓存会被清空。

前置条件: 后端只会在玩家选项 options.continueEnabled = true 时下发 resumeToken。开发阶段默认值为 false,因此未显式开启续关的玩家不会收到该 token,也不会触发 OFFLINE_REFRESH

成功响应 (200) 附加字段:

字段类型描述
versionint客户端当前玩家数据/插件版本,会在服务端保存为 players.profile_version
banstateint?可选。> 0 时表示账号存在警告,登录后显示红色 Title 提示
accessTokenstring短期玩家 access token;用于 WebSocket resume、选项同步、登出、上传归属校验
resumeTokenstring?仅当玩家已开启 options.continueEnabled 时返回;用于后续 OFFLINE_REFRESH
expiresInintaccessToken 的 TTL 秒数
resumeExpiresInint?resumeToken 的 TTL 秒数
options.continueEnabledboolean玩家是否允许服务端签发 resumeToken;默认 false
data.collectionsstring[]玩家当前已持久化解锁的收藏品 Key 列表;包含称号/标签以及 rhythmc:assets.* 资产类解锁

POST /player/options

描述: 更新玩家选项,并返回刷新后的玩家档案快照。

请求体: uidaccessTokenoptions,可选 playerPrefixplayerSuffix

options 当前还包含 songSelectionState 对象,用于持久化选歌界面状态:

  • sortMode: 选歌排序模式
  • difficulty: 选歌难度
  • collection: 当前精选集 Key
  • songId: 当前高亮歌曲 ID
  • autoPlay: 当前自动播放开关

成功响应 (200): 返回最新 PlayerProfileResponse

  • options.continueEnabledfalse 切换为 true 时,响应会立即包含新的 resumeToken
  • 当该字段切回 false 时,响应中的 resumeToken 为空,服务端同时撤销该玩家已有的 resume token
  • 响应里的 data.collections 会随最新玩家档案一起返回,便于客户端在设置同步后继续保留本地解锁快照
  • 后端会将 options.soundoptions.songSelectionState 持久化到 JSON 列,其余核心选项继续使用结构化列。

POST /player/collections

描述: 同步玩家当前已解锁的收藏品 Key 集合,用于持久化本地触发的解锁逻辑。

认证: 需要 Authorization: Bearer <serverAccessToken>,且请求体中的玩家 accessToken 必须有效。

请求体:

{
"uid": 12345,
"accessToken": "player-access-token",
"collections": [
"rhythmc:title.beta",
"rhythmc:assets.song.100",
"rhythmc:assets.chart.100.wd"
]
}

字段说明:

字段类型必需描述
uidlong玩家 UID
accessTokenstring玩家 access token
collectionsstring[]需要确保后端已持久化的收藏品 Key 列表;后端按增量 grant 处理,不会删除缺失项

成功响应:

状态码描述
204同步成功,无响应体

错误响应:

状态码含义说明
400请求错误缺少 uidaccessToken
401认证失败玩家 access token 无效、过期,或与当前 server session 不匹配
500服务器错误后端持久化收藏品失败

说明:

  • 插件侧 UnlockService 在获得新的持久化解锁后,会异步调用本接口。
  • 若后端尚不存在某个资产类 Key,对应记录会自动创建为 asset 类型收藏品,再写入 player_collection_unlocks
  • GET /server/collections 默认不会返回这些自动创建的 asset 记录;它们只用于玩家持久化解锁状态。

version 兼容性说明:

  • 请求中的 version 表示当前运行插件声明的玩家数据版本。
  • 成功响应中的 version 仍表示该玩家登录前已保存的档案版本,客户端据此决定是否提示升级或拒绝旧插件继续加载。
  • 若响应 version 高于本地 plugin-version-code,客户端会直接踢出玩家并提示需要先升级服务器插件。
  • 若响应 version 低于本地 plugin-version-code,客户端允许登录,但会提示当前档案较旧且可能需要数据升级。

错误响应 (430) 字段:

字段类型描述
reasonString封禁原因
untilString解封时间(ISO 格式或可读字符串)

响应状态码:

状态码含义处理方式
200登录成功保存令牌,更新状态;若有 banstate > 0 则显示账号警告
401认证失败提示用户重新输入 Token(TOKEN 模式连续失败 login.max-failures 次则踢出)
409账号已在线保持在手动 Token 登录流程,提示“当前帐号已登录,如已登出请等一分钟后再试。”
429请求频繁等待后重试
430账号已封禁踢出玩家,在踢出消息中显示 reasonuntil

客户端补充说明:

  • 在线模式下,玩家加入后按 ONLINE -> OFFLINE_REFRESH -> TOKEN 的顺序尝试认证。
  • 离线模式下,玩家登录流程短路为本地游客模式,不创建远端会话,也不要求输入 Net Token

POST /player/economy

描述: 上传插件本地计算后的最新 wattHour 余额,用于持久化与排行榜刷新。

请求类: SyncEconomyRequest

认证: 需要 Authorization: Bearer <serverAccessToken>,且请求体中的玩家 accessToken 必须有效。

请求体:

{
"uid": 12345,
"accessToken": "player-access-token",
"wattHour": 5600
}

响应 (200):

{
"uid": 12345,
"wattHour": 5600
}

错误响应:

状态码含义说明
400请求错误缺少 uid / accessToken / wattHour,或 wattHour < 0
401认证失败玩家会话无效或过期
404玩家不存在uid 无法匹配数据库玩家
500服务器错误后端保存余额失败

说明:

  • 插件侧经济变更当前以本地内存档案 PlayerData.wattHour 为准。
  • 每次调用本地 Economy 服务修改余额后,都会异步调用本接口上传完整最新余额,而不是上传增量。
  • 后端不会在本接口中自行做加减计算,只负责认证、覆盖保存和排行榜持久化。

POST /player/logout

描述: 玩家登出。

请求类: PlayerLogoutRequest

认证: 需要 Authorization: Bearer <serverAccessToken>

请求体:

{
"accessToken": "player-access-token",
"uid": 12345
}

说明: 当前客户端在 PlayerQuitEvent 中做 best-effort 调用此接口。请求使用 POST /player/logout 并携带 accessTokenuid;若调用失败,仅记录日志,不阻塞玩家退出流程。

成功响应:

状态码描述
204登出成功,无响应体

常见错误响应:

状态码含义
401Bearer 令牌无效或已过期
403服务器登录 token 无权限或已被拒绝
429速率限制

资源 API

GET /resources/manifest

描述: 获取资源清单,包含所有资源文件的哈希值。客户端优先使用 sha256 校验,缺失时回退到 sha1

请求类: GetManifestRequest

认证: 需要 Authorization: Bearer <serverAccessToken>

URL: {assetServer}/resources/manifest?lang={checkLang}

查询参数:

参数类型描述
langboolean是否包含语言资源

响应:

{
"Data": [
{
"path": "songs/song1.rsm",
"sha256": "9f86d081884c7d659a2feaa0c55ad015..."
},
{
"path": "levels/level1.json",
"sha256": "b7d00764f43b6358669a3d0107692faf..."
}
],
"Lang": [
{
"path": "lang/zh_cn.json",
"sha256": "5df6e0e2761358c2c4cb7bb135b2f3af..."
}
]
}

字段说明:

字段类型描述
pathString资源相对路径
sha256String必填。资源文件 SHA256,用于本地完整性校验

POST /resources/down

描述: 获取资源下载URL列表

请求类: GetDownloadUrlsRequest

认证: 需要 Authorization: Bearer <serverAccessToken>

URL: {assetServer}/resources/down

请求体:

[
"songs/song1.rsm",
"levels/level1.json"
]

响应:

[
{
"path": "songs/song1.rsm",
"url": "https://cdn.example.com/songs/song1.rsm",
"bytes": 1048576,
"sha256": "9f86d081884c7d659a2feaa0c55ad015..."
},
{
"path": "levels/level1.json",
"url": "https://cdn.example.com/levels/level1.json",
"bytes": 2048,
"sha256": "b7d00764f43b6358669a3d0107692faf..."
}
]

下载流程:


GET /resources/packmanifest

描述: 获取资源包清单。插件账户设置中的资源包菜单会使用这里返回的 size 字段,将大小格式化显示为 10.29MiB 这类人类可读文本。

请求类: GetRspListRequest

认证: 需要 Authorization: Bearer <serverAccessToken>

URL: {assetServer}/resources/packmanifest

返回字段补充:

字段类型描述
nameString资源包名称
displayNameString显示名称
typeStringFULL / CHAPTER / SONG
availableSongsint[]资源包覆盖的歌曲 ID 列表
sizelong资源包字节大小,客户端可格式化为 KiB / MiB
sha1String资源包 SHA1
urlString资源包直链下载地址

数据上传 API

POST /player/upload

描述: 上传单条游戏记录数据

请求类: UploadRecordsRequest

认证: 需要 Authorization: Bearer <serverAccessToken>。请求体中的 player.accessToken 会优先按玩家会话令牌校验;如果该令牌已经过期,但该玩家在同一个 serverSession 下仍存在有效在线会话,后端会回退到该会话继续接收成绩上传,避免长局结算时因玩家令牌过期丢成绩。

请求体: 单个记录对象(非数组)

{
"player": {
"playerUID": 12345,
"accessToken": "player-access-token",
"timestamp": 1700000000000
},
"server": {
"serverID": 100,
"serverName": "CN-East-01",
"country": "CN"
},
"payload": {
"type": "gameplay",
"levelID": 1,
"acc": 98,
"isRival": false,
"isWin": true,
"rivalUID": 0,
"rivalAcc": 0,
"tapStats": {"perfect": 100, "great": 10, "miss": 0},
"holdStats": {"perfect": 50, "great": 5, "miss": 0},
"dodgeStats": {"perfect": 30, "great": 2, "miss": 0},
"lookStats": {"perfect": 20, "great": 1, "miss": 0},
"maxCombo": 150,
"pass": true,
"fullCombo": false,
"allPerfect": false,
"isNewRecord": true,
"player_arena": "arena_1",
"time": 1700000000000
}
}

响应说明:

  • 200: 上传成功,返回新的 ATP、全站排名和曲目排名。
  • 400: 请求体缺失必要字段或记录内容非法。
  • 401: 服务器令牌无效,或玩家既没有可用的 player.accessToken,也没有与当前服务器会话绑定的有效在线会话。
  • 500: 服务端处理上传失败。

数据结构图:


请求类详解

ApiRequest<T> (抽象基类)

public abstract class ApiRequest<T> {
public abstract CompletableFuture<T> execute(HttpClient httpClient);
}

设计模式: 命令模式 + 异步回调

优点:

  • 统一的请求接口
  • 支持异步执行
  • 类型安全的响应

请求类汇总表

类名端点方法返回类型用途
LoginRequest/server/loginPOSTVoid服务器登录
RefreshAuthTokenRequest/server/refreshGETString刷新服务器 Bearer 令牌
GetCollectionsRequest/server/collectionsGETList<CollectionsInfo>获取收藏品列表
GetBaltopRequest/server/baltopGETList<BaltopEntry>获取 KWh 财富排行榜
PlayerLoginRequest/player/loginPOSTHttpResult玩家登录
PlayerLogoutRequest/player/logoutPOSTString玩家登出
UploadRecordsRequest/player/uploadPOSTString上传单条记录
GetManifestRequest/resources/manifestGETManifestResult获取清单(sha256/sha1)
GetDownloadUrlsRequest/resources/downPOSTList<DownloadInfo>获取下载URL
GetRspListRequest/resources/packmanifestGETString获取资源包清单

2026-03-16 Update: Charts Endpoint

GET /server/charts

描述: 获取当前启用的谱面目录。客户端在服务器登录成功后将此请求加入初始化链,用于决定本地 charts 的反序列化范围。

请求类: GetChartsRequest

URL: {game-server}/server/charts

认证: 需要 Authorization: Bearer <serverAccessToken>

响应 (200):

[
{
"levelId": 100091,
"name": "Song Title",
"difficultyConstant": 10.5,
"version": "1.0",
"createdAt": "2026-03-16T20:30:00"
}
]

说明:

  • 仅返回 is_active = 1 的标准谱面槽位
  • difficultySlot 当前约定为 WORLD / NETHER / END / VOID
  • 客户端收到后会构建 ActiveChartCatalog,交给 RawSongDeserializer 判断是否加载本地 chart

请求类补充:

  • GetChartsRequest | /server/charts | GET | List<ChartInfo> | 获取当前启用的谱面目录

2026-03-18 Update: Ban Enforcement And Immediate Kick

/player/login

  • Active bans are now enforced during login.
  • If the player has an active record in player_bans, the backend returns HTTP 430 instead of allowing login to continue.
  • Response payload:
{
"error": "BANNED",
"message": "Player is banned",
"reason": "abusive chat",
"until": "2026-03-20T22:30:00"
}

Field notes:

  • reason: active ban reason, empty string when absent.
  • until: active ban expiry time as string, empty string for permanent bans.

Online ban push

  • Admin ban now immediately pushes a WebSocket kick_player packet to the owning game server.
  • Backend also revokes player session token(s) and marks runtime player sessions as logged out.
  • Client receives the packet and kicks the target Bukkit player with the provided message.

Session binding note

  • The client now sends resume_session immediately after successful player state sync.
  • This makes backend-side online actions reliable even if the player has not entered matchmaking yet.

2026-03-18 Update: Collection Metadata For Cross-Server Chat Hover

The cross-server chat feature does not add a new HTTP endpoint, but it relies on richer collection metadata that is already returned by:

  • GET /server/collections

properties additions

For title / tag style collections, properties may now include:

FieldTypeDescription
titleStringrendered title/tag text
descriptionString or String[]hover description used by the client chat UI
rarityStringhover rarity label
positionbooleantrue prefix, false suffix

Example:

[
{
"key": "rhythmc:title.beta",
"type": "title",
"unlockMethod": {
"type": "ACHIEVEMENT",
"mode": "ALL",
"events": [
"player.login"
],
"player": {
"betaTester": {
"operator": "EQUALS",
"value": true
}
}
},
"properties": {
"title": "&6[Beta]",
"description": [
"参与封测的玩家",
"感谢你的陪伴"
],
"rarity": "Legendary",
"position": true
}
}
]

Notes:

  • description may be a single string or a string array.

  • The client stores description in its local collection cache.

  • unlockMethod now accepts generic rule payloads so the client can cache and evaluate event/info/value/player based unlock definitions without hard-coding specific business classes.

  • When a local unlock rule matches, the plugin now adds that collection key into player.data.collections in the in-memory profile snapshot.

  • CommonUnlockMethod is target-agnostic: the same rule payload can be reused by arenas, collections, titles, songs, charts, or future unlockable entities.

  • The backend also reuses the same metadata when building ChatDecorationInfo for WebSocket chat delivery. 上传鉴权严格以 token 反查 session 为准:

  • 服务器身份来自 Authorization: Bearer <serverAccessToken>

  • 玩家身份来自 player.accessToken