# Milracle-v2 ## API Docs - 认证 (Auth) [重定向认证操作 (OIDC / GitHub OAuth 回调)](https://open.api.xhsr.org.cn/521572797e0.md): 重定向认证操作,包括 Milkloud OIDC 登录与回调、GitHub OAuth 授权跳转与回调。Milkloud 回调会规范化头像 URL;仅当用户当前头像为空/默认头像或已是 Milkloud 头像时同步 Milkloud 头像,避免覆盖用户上传的本地头像。GitHub OAuth:action=github-login 生成一次性 state(Redis 10 分钟 + HttpOnly Cookie github_state 兜底),302 跳转 https://github.com/login/oauth/authorize(scope=read:user user:email,allow_signup=true);action=github-callback 为 GET 容错分支:当 GitHub App 后台把回调 URL 误配到本 API 端点时,GitHub 会以 GET 302 到此处,服务端校验 state 后转发到前端回调页(携带 code+state,不消费 state),由前端以 POST 继续正常交换流程。 - 认证 (Auth) [认证操作](https://open.api.xhsr.org.cn/521572798e0.md): 根据 action 执行认证与账号生命周期相关操作:login(用户名或邮箱+密码登录)、register(注册并进入待激活)、verify(发送邮箱验证码)、activate(验证码激活账号)、reset-password(单动作密码重置:仅传 email 发送重置码;传 email+code+newPassword 完成重置)、logout、refresh,以及 Milkloud OIDC 登录与 GitHub OAuth 回调(github-callback)。 - 谱面 (Charts) [获取谱面列表或详情](https://open.api.xhsr.org.cn/521572799e0.md): 支持多种 action:list(获取列表),detail(获取详情),history(获取谱面历史事件,返回 data: [ { type, ts, actor_id, actor_name?, detail? } ];type='audit' 为管理操作审计:delete/hide/reject/restore/re-review/vote/publish_approved/publish_rejected,携带 actor_name/action/detail)。moderate / like 仅 POST / DELETE 支持,GET 不支持。list/detail/history 响应均可能命中 Redis 与进程内短期缓存;上传、删除、审核投票、发布审批、管理员状态更新会主动失效对应的列表/详情/历史缓存。history 响应的远端缓存 TTL 约 300 秒。 - 谱面 (Charts) [删除谱面](https://open.api.xhsr.org.cn/521572800e0.md): 软删除指定谱面:不物理移除,仅将 Charts.status 置为 hidden(隐藏),投票/上架申请/成绩/点赞/评论等关联数据全部保留。隐藏谱面仅 admin/moderator/reviewer 可检索和查看(上传者本人也不可见),可随时通过 moderate restore 恢复。成功删除后服务端会失效谱面列表缓存和该谱面的详情缓存,避免后续 GET /charts?action=list/detail 返回已删除谱面的旧数据。 - 谱面 (Charts) [删除谱面 (兼容旧客户端,使用 POST 代替 DELETE)](https://open.api.xhsr.org.cn/521572801e0.md): 兼容旧客户端的删除入口(语义同 DELETE /charts?action=delete&id=...,软删除:不物理移除,仅将 Charts.status 置为 hidden,关联数据全部保留,仅管理角色可见可恢复);并支持 action=moderate 管理操作(body 含 chartId 与 action,权限 admin/reviewer/moderator,上传者不可操作;hide=隐藏/reject=拒绝/restore=恢复/re-review=重新审核,其中 re-review 会清空 ChartVotes 重新进入审核)。成功删除后服务端会失效谱面列表缓存和该谱面的详情缓存。 - 谱面 (Charts) [Studio 管理:获取审核队列(支持待审核 / 上架申请 / 已通过 / 已上架 / 已拒绝等)](https://open.api.xhsr.org.cn/521572802e0.md): action=queue 将返回统一队列(默认 filter=pending_queue)。支持额外 query 参数 filter(pending_queue|pending|publish_requests|approved|published|rejected_review|rejected_publish)、page、limit、q(搜索)。注意:队列结果会被短期缓存(默认 TTL ~5 秒),在投票/审批/上架申请后会立即失效。返回条目会包含 `cover_url` 与 `chart_url`(若有),对于上架申请条目还会包含 `request_id`(PublishRequests 表主键)。若设置 `FILE_CDN_DOMAIN`,URL 会在响应时自动替换为 CDN 域名。 - 谱面 (Charts) [Studio 操作:投票 / 申请上架 / 处理上架](https://open.api.xhsr.org.cn/521572803e0.md): action=vote (body: chartId, vote=approve|reject), action=request-publish (body: chartId), action=approve-publish (body: requestId, approve=true|false)。投票或发布审批成功后,服务端会失效对应谱面的列表/详情/历史缓存,并刷新 Studio 队列缓存。 - 成绩 (Scores) [获取成绩](https://open.api.xhsr.org.cn/521572804e0.md): 按 action 分发成绩查询:recent 返回当前用户的最近成绩列表(含用户名与谱面标题);leaderboard 返回指定谱面的排行榜(需 chartId);user 返回指定用户的历史成绩(需 user_id)。查询实时读库,不做结果缓存。 - 成绩 (Scores) [上传成绩](https://open.api.xhsr.org.cn/521572805e0.md): 上传一次游玩成绩(action=upload)。请求体需包含 chartId(谱面 ID)、score(分数)与 acc(准确率)。需要 JWT 鉴权(Authorization 头)。 - 游玩记录 (Records) [获取游玩记录](https://open.api.xhsr.org.cn/521572806e0.md): action=getsong:按 chartId 获取单个谱面的游玩记录(按 score 从高到低);action=getall:分页获取全部游玩记录(按创建时间从新到旧)。除 download 外均需要 JWT 鉴权。chartId 为纯数字时按谱面 ID 匹配,否则按谱面哈希(chart_hash)匹配。返回 { data: { items, total } };每条记录包含 props(客户端写入的 MilRecord::Properties,服务端原样返回)、file_url(本地 RSA 私钥解密后的明文记录文件)、file_hash(file_url 对应文件的 SHA-512 十六进制摘要,供下载后校验)与 create_time(Unix 时间戳,秒,含小数)。action=download 为兼容用的下载入口:记录本身已存为明文,通常直接访问 file_url 即可;若存储对象仍是加密态,该入口会用本地 RSA 私钥现场解密后返回明文并回写存储。列表结果最多缓存 60 秒,因此新上传的记录可能延后最多 60 秒才出现在列表中。 - 游玩记录 (Records) [上传游玩记录](https://open.api.xhsr.org.cn/521572807e0.md): action=upload。请求体为客户端用公钥加密后的原始二进制(Content-Type: application/octet-stream,无任何表单包装);服务端用本地 RSA 私钥解密,校验 ASAR 结构(meta / hit-objects / input-trace)并将后两者 zstd 解压,全部通过后才写库,避免无效输入留下残留对象或行。解密后的明文记录存入 R2 并作为 file_url 返回,file_hash 为该明文文件的 SHA-512。任何校验失败(无法解密、结构缺失、解压失败)均返回 422「未通过检验,可疑伪造内容」。每个「用户 × 谱面」在云端最多保留 4 条:按 score 从高到低、created_at 从新到旧排序,超出部分及其 R2 对象会被自动删除;若请求既未带 chartId、记录 meta 中也没有 chart_hash,则无法定位谱面,此时跳过裁剪(不会误删该用户其他谱面的记录)。 - 资源包 (Resource Packs) [获取资源包列表/详情或下载资源包](https://open.api.xhsr.org.cn/521572808e0.md): action=list:分页获取资源包,返回 { data: { items, total } },items 中 meta 为 MiluneResourcePack::MetaData(.mrp 服务端解析结果),file_url 为资源包文件地址;sort 可选 popular(默认,下载量降序)/ time-newest(最新上传)/ time-oldest(最早上传),q 为服务端模糊匹配(name / author / description)。action=detail:按 id 获取单个资源包。action=download:200 直接以 application/octet-stream 流式返回资源包文件并使下载计数 +1。默认情况下仅返回 approved;admin/reviewer/moderator 可通过 status=pending 或 all 查看非 approved 资源包。列表结果最多缓存 60 秒,新上传或审核后的变化可能延后最多 60 秒才可见(详情在下载后会立即失效刷新)。 - 资源包 (Resource Packs) [上传资源包或审核资源包](https://open.api.xhsr.org.cn/521572809e0.md): action=upload 上传 .mrp 资源包(multipart/form-data):服务端校验 MRP0 魔数并解析 MetaData(名称/描述/版本/作者/许可证/其他信息/创建与更新时间),所有信息以服务端解析结果为准入库(表单字段不再参与),上传后默认 status='pending',需等待审核,返回 { data: { id } };action=review 审核资源包(application/json),需 admin/reviewer/moderator 权限。 - 用户 (User) [获取用户信息或用户列表](https://open.api.xhsr.org.cn/521572810e0.md): 获取用户资料/列表/关注关系等信息。action=me 需要 JWT 鉴权(匿名返回 401),其余 action 匿名可访问。说明:登录、注册、邮箱验证激活、找回密码等账号认证流程在 `/auth` 接口中(通过 `action` 区分)。 - 用户 (User) [用户操作(某些操作需管理员权限,例如修改其他用户)](https://open.api.xhsr.org.cn/521572811e0.md): 按 action 执行用户相关操作:update 更新当前用户信息(管理员可通过 ?id= 更新其他用户);change-password 修改当前用户密码(管理员可通过 ?id= 代改且无需旧密码);follow 关注或取消关注指定用户(body 需 userId 与 action=follow/unfollow);me 获取当前登录用户信息。均需要 JWT 鉴权,部分操作要求管理员权限。 - 管理员 (Admin) [管理员查询](https://open.api.xhsr.org.cn/521572812e0.md): 管理员专用查询接口,按 action 分发:stats 返回平台统计(用户 / 谱面 / 成绩等计数);users 返回用户列表;charts 返回谱面列表。所有 action 均要求管理员权限(Authorization 携带管理员 JWT 或 ADMIN_SECRET),未通过鉴权时返回 401,无权限时返回 403。 - 管理员 (Admin) [管理员更新](https://open.api.xhsr.org.cn/521572813e0.md): 管理员更新资源。action=charts 用于更新谱面状态;状态变更成功后会失效谱面列表缓存和对应谱面详情缓存。 - 内容 (Content) [获取内容](https://open.api.xhsr.org.cn/521572814e0.md): 获取公告、团队、创作或下载内容。type=announcements 列表会使用 Redis 与进程内短期缓存;若检测到空列表缓存,会忽略并回源数据库刷新,避免临时空缓存导致公告列表异常为空。 - 内容 (Content) [创建内容](https://open.api.xhsr.org.cn/521572815e0.md): 创建内容,按 type 分发:announcements(公告)、team(团队成员)、downloads(下载)需要管理员权限;creations(创作)与 comments(评论)需要登录(JWT)。请求体字段随 type 不同而变化。 - 内容 (Content) [更新内容](https://open.api.xhsr.org.cn/521572816e0.md): 更新内容,按 type 分发:team(团队成员)需要管理员权限;comments(评论)仅内容所有者或管理员可编辑。请求体字段随 type 不同而变化。 - 内容 (Content) [删除内容](https://open.api.xhsr.org.cn/521572817e0.md): 删除内容,按 type 分发,需 id 指定目标:announcements(公告)、team(团队成员)、downloads(下载)需要管理员权限;comments(评论)仅内容所有者或管理员可删除。 - 上传 (Upload) [获取 presigned 上传地址 / 头像代理](https://open.api.xhsr.org.cn/521572818e0.md): 上传模块的只读/代理入口,按 action 或 type 分发:action=avatar 公开代理返回指定用户的头像(userId 必填;无自定义头像时返回内置默认头像,图片流或 302 跳转 CDN);type=presign 请求 presigned 上传地址(需 ADMIN_SECRET 或管理员/reviewer 权限),返回 { url, key, publicUrl } 供客户端直传。上传文件本体请使用 POST /api/v2/upload(服务端不提供 presigned URL 的直接上传场景,客户端直接 POST 本接口)。 - 上传 (Upload) [文件上传](https://open.api.xhsr.org.cn/521572819e0.md): 上传文件、头像或谱面。谱面上传成功后,服务端会失效相关缓存。type=chart-zip:服务端在内存中解包 zip,逐文件上传到 R2 包前缀 charts/{packageId}/... —— meta.json 始终上传;若存在 extra.json,其本体以及它引用的文件都会上传;zip 内其余资源全部保留(零丢失)。服务端**不再**保存/生成原始整包 zip,也不再写 file_url;资源清单写入 assets_manifest。成功响应为 { message, id, packageId, warning? }(zip 内缺 meta.json 时带 warning)。 - 工具 (Utils) [获取谱面元数据(不再服务端打包 zip)](https://open.api.xhsr.org.cn/521572820e0.md): 返回谱面相关元数据:chart/audio/cover URL、meta.json / extra.json URL 等,供客户端自行拉取资源。服务端**不再**打包返回 zip:传 download=1 或 format=zip 一律返回 403(服务端打包已禁用)。chartUrl 现以 Charts.chart_url 为准(不再使用废弃的 file_url)。可通过 inlineCover=1 临时请求内联图片(会增加响应时间,默认关闭)。 - 工单 (Tickets) [工单: 列表 / 详情 / 统计](https://open.api.xhsr.org.cn/521572821e0.md): 按 action 分发:list(工单列表;scope=mine 默认仅自己的,scope=all 仅 admin/reviewer/moderator)、detail(工单详情 + 消息流;内部备注仅版主可见;超过一年无版主动作的工单在此惰性自动关闭)、stats(各状态计数,仅版主及以上)。 - 工单 (Tickets) [工单: 创建 / 回复 / 认领 / 结案 / 驳回](https://open.api.xhsr.org.cn/521572822e0.md): 按 action 分发:create(提交举报或通用工单;举报需 targetType+targetId,通用工单需 subject)、reply(回复;internal=true 为内部备注,仅版主及以上,且视为"响应"并把 open 推进为 in_progress)、claim(认领)、resolve / reject(结案 / 驳回,可带 note)。同一举报人对同一目标在未关闭时不可重复提交(409 duplicate_report);每用户每天最多创建 10 条(429 rate_limited);已结束的工单不可回复/认领(409 ticket_closed)。 ## Schemas - [User](https://open.api.xhsr.org.cn/318312728d0.md): - [Chart](https://open.api.xhsr.org.cn/318312729d0.md): - [ChartAsset](https://open.api.xhsr.org.cn/318312730d0.md): - [Error](https://open.api.xhsr.org.cn/318312731d0.md): - [Score](https://open.api.xhsr.org.cn/318312732d0.md): - [Record](https://open.api.xhsr.org.cn/318312733d0.md): - [ResourcePack](https://open.api.xhsr.org.cn/318312734d0.md): - [RecordProperties](https://open.api.xhsr.org.cn/318312735d0.md):