HTTP API 文档
项目:对面是AI吗
协议:HTTP/1.1
Content-Type:application/json(除标注外)
基础URL:https://game.xfcode.top
更新日期:2026-10-06
时间格式约定
全站时间唯一格式为 Unix 秒级时间戳(整数):
- 存储:MySQL / SQLite 时间列一律
BIGINT/INTEGER(Redis、消息 JSON 同理)。 - API 返回:所有时间字段(
created_at、updated_at、last_played_at、saved_at、favorited_at、generated_at、period_start、period_end等)均为秒级整数,展示格式由消费端自行解析(前端统一用shared.js的全局XQFTime)。 - 例外:
duration/timeout/seconds/expires_in/cooldown等是相对时长(秒),不是时间戳;日历业务键(week、date)、HTTPLast-Modified/ETag等按格式要求保留原样。
下文各示例中的时间字段均为整数;若为相对时长会在字段说明中标注。
认证机制
大部分需要身份的 API 使用 Bearer Token 认证:
- 请求头:
Authorization: Bearer <token> - Token 类型:自签 JWT(HMAC-SHA256,密钥为玩家的
password_hash,有效期 365 天) - 获取方式:
POST /api/email/auth、POST /api/oauth/confirm-create、GET /api/oauth/complete、/api/generate-player-id?action=recover等身份入口下发 - 特点:修改密码后旧 token 自动失效(签名密钥变化),此时会下发新 token
需要认证的 API(共 45 个):
/api/chat-history、/api/chat-history/detail、/api/player-messages、/api/player/tags、/api/sticker/list、 /api/soup/my-puzzles(GET 列表)、/api/account/overview、/api/account/bot-access、/api/bot/panel、/api/oauth/bindings、/api/community/my、 /api/player-message/hide、/api/player-message/settings、/api/player/worn-tags、/api/chat-history/collect、/api/collection/like、 /api/sticker/upload、/api/image/upload、/api/sticker/delete、/api/sticker/add-to-mine、 /api/macros(POST 保存)、/api/macros/delete、/api/soup/my-puzzles(POST 新建)、/api/soup/my-puzzles/{id}/share、/api/soup/public-puzzles/{id}/copy、 /api/temp/invite、/api/temp/invite/decline、/api/bot/apply、 /api/account/nickname、/api/account/avatar、/api/account/password、/api/account/email、 /api/bot/panel/nickname、/api/bot/panel/key、 /api/community/post/save、/api/community/post/delete、/api/community/like、/api/community/favorite、 /api/community/comment、/api/community/comment/delete、/api/community/report、 /api/oauth/unbind、/api/oauth/sync-avatar、 /api/soup/my-puzzles/{id}(PUT)、/api/soup/my-puzzles/{id}(DELETE)
条件认证:
/api/generate-player-id:仅action=change_password(以及带Authorization头查询战绩时)需要 token/api/email/send-code:scene=bind_email时需要 token,scene=auth不需要
可选认证(带 token 时行为不同,不带也能访问):
/api/macros:带 token 时返回的宏带mine标记/api/temp/users:带 token 时搜索结果排除自己- 社区读接口
/api/community/posts、/api/community/search、/api/community/post、/api/community/comments:带 token 时返回liked/favorited/is_own等互动状态
BOT KEY 认证:
/api/bot/stickers:不走玩家 token,采用player_id查询参数 +Authorization: Bearer <bot_key>(BOT 的独立密钥,见 BOT 面板)
免认证:其余公开接口、页面路由与静态资源;部分接口通过昵称+密码手动校验(如 /api/player-stats)。
通用错误响应
需要玩家 token 的接口(requirePlayerId)认证失败时返回:
{"error": "缺少 token"}
{"error": "token 无效或已过期"}交流社区写接口未登录时返回 HTTP 401:
{"error": "请先登录后再操作"}OAuth 相关接口(requirePlayerId)认证失败时返回:
{"ok": false, "error": "缺少 token"}
{"ok": false, "error": "token 无效或已过期"}1. 身份与账号
源码位置:GameController.php(generate-player-id / player-stats / player-profile) · 路由定义见 Router.php
1.1 获取/恢复玩家身份
GET /api/generate-player-id说明:通过 Token 查询战绩、通过 IP+指纹恢复、密码恢复身份、修改密码的复合接口。
「代号 + 密码」注册入口已下线:
action=register会直接返回引导文案,注册请改用邮箱验证码(POST /api/email/auth)。
请求参数(Query):
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
action | string | 否 | recover(恢复)、change_password(改密码)、register(已下线)、空(查询) |
nickname | string | 条件 | 恢复时必填,最长 16 字符 |
password | string | 条件 | 恢复时的原密码 |
old_password | string | 条件 | 改密码时的旧密码 |
new_password | string | 条件 | 改密码时的新密码(走 PasswordPolicy,见 7.4) |
fp | string | 否 | 设备指纹,用于 IP+指纹联合查找 |
请求头:改密码时(及通过 Token 查询时)必须带 Authorization: Bearer <token>
场景一:通过 Token 查询战绩
curl "https://game.xfcode.top/api/generate-player-id" \
-H "Authorization: Bearer <token>"响应 200:
{
"stats": {
"total_games": 42,
"wins": 18,
"losses": 20,
"draws": 4,
"last_played_at": 1751000000
}
}场景二:通过 IP+指纹恢复战绩
curl "https://game.xfcode.top/api/generate-player-id?fp=<设备指纹>"响应 200:
{
"stats": { "total_games": 42, "wins": 18, "losses": 20, "draws": 4 }
}场景三:密码恢复身份
curl "https://game.xfcode.top/api/generate-player-id?action=recover&nickname=小明&password=Abc12345"成功 200:
{
"token": "eyJ...",
"nickname": "小明",
"stats": { "total_games": 42, "wins": 18, "losses": 20, "draws": 4 }
}昵称不存在或密码错误 200(不区分,防枚举昵称):
{"error": "用户名或密码错误"}场景四:修改密码
curl "https://game.xfcode.top/api/generate-player-id?action=change_password&old_password=Abc12345&new_password=Xyz98765" \
-H "Authorization: Bearer <token>"成功 200(密码已改,旧 token 失效,下发新 token):
{"ok": true, "token": "eyJ..."}新密码强度不足 200(文案来自 PasswordPolicy,见 7.4):
{"error": "密码至少 8 位"}旧密码错误 200:
{"error": "旧密码不正确"}场景五:注册入口已下线 / 无匹配
{"error": "注册方式已升级,请使用邮箱验证码注册"}{"error": "未找到玩家"}1.2 查询玩家战绩
GET /api/player-stats说明:通过昵称+密码查询战绩(不依赖 Token,适用于换了设备/浏览器的情况)。
请求参数(Query):
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
nickname | string | 是 | 昵称,最长 16 字符 |
password | string | 是 | 密码 |
请求示例:
curl "https://game.xfcode.top/api/player-stats?nickname=小明&password=Abc12345"成功 200:
{
"player_id": "abc123...",
"stats": { "total_games": 42, "wins": 18, "losses": 20, "draws": 4 }
}错误 200:
{"error": "昵称不能为空"}
{"error": "密码不能为空"}
{"error": "用户名或密码错误"}1.3 玩家公开档案
GET /api/player-profile说明:公开接口,无需认证。返回玩家公开身份档案(风格画像 + 称号等)。
请求参数(Query):
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
nickname | string | 是 | 昵称,最长 16 字符 |
请求示例:
curl "https://game.xfcode.top/api/player-profile?nickname=小明"成功 200:返回档案对象(字段由 PlayerStatsRepository::getPlayerProfileByNickname 组装)。
错误 200:
{"error": "昵称不能为空"}
{"error": "玩家不存在"}2. 聊天记录
2.1 聊天记录列表
GET /api/chat-history认证:需要 Authorization 请求头。
请求参数(Query):
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
page | int | 否 | 页码,默认 1 |
请求示例:
curl "https://game.xfcode.top/api/chat-history?page=1" \
-H "Authorization: Bearer <token>"成功 200(created_at 为 Unix 秒级整数):
{
"list": [
{
"id": 1,
"opponent_name": "小红",
"truth": "ai",
"guess": "human",
"result": "lose",
"duration": 600,
"created_at": 1751000000
}
],
"total": 42,
"page": 1,
"per_page": 10
}2.2 聊天记录详情
GET /api/chat-history/detail认证:需要 Authorization 请求头。
请求参数(Query):
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
id | int | 是 | 聊天记录 ID |
请求示例:
curl "https://game.xfcode.top/api/chat-history/detail?id=1" \
-H "Authorization: Bearer <token>"成功 200:
{
"id": 1,
"opponent_name": "小红",
"messages": [...],
"truth": "ai",
"guess": "human"
}参数错误 200:
{"error": "参数错误"}未找到 200:
{"error": "未找到该记录"}2.3 收藏聊天记录(设置标题 / 公开状态)
POST /api/chat-history/collect认证:需要 Authorization 请求头。
请求体(JSON):
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
id | int | 是 | 聊天记录 ID |
title | string | 否 | 收藏标题,最多 100 字符 |
is_public | bool | 否 | 是否公开分享 |
请求示例:
curl -X POST "https://game.xfcode.top/api/chat-history/collect" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <token>" \
-d '{"id":1,"title":"精彩对局","is_public":true}'成功 200:
{"ok": true, "token": "public_token_xxx"}参数错误 200:
{"error": "参数错误"}3. 收藏
3.1 通过 Token 获取收藏
GET /api/collection/by-token说明:无需登录,通过公开令牌查看收藏。
请求参数(Query):
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
token | string | 是 | 公开令牌 |
成功 200:
{ "id": 1, "title": "精彩对局", "messages": [...] }错误 200:
{"error": "参数错误"}
{"error": "该链接已失效或不存在"}3.2 玩家公开收藏列表
GET /api/player-collections说明:公开接口,无需认证。
请求参数(Query):
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
nickname | string | 是 | 玩家昵称 |
page | int | 否 | 页码,默认 1 |
错误 200:
{"error": "昵称不能为空"}
{"error": "玩家不存在"}3.3 收藏详情
GET /api/collection/detail说明:公开接口,无需认证。仅返回公开的收藏。
请求参数(Query):
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
id | int | 是 | 收藏 ID |
错误 200:
{"error": "参数错误"}
{"error": "该收藏不存在或未公开"}3.4 点赞收藏
POST /api/collection/like认证:需要 Authorization 请求头。
请求体(JSON):
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
id | int | 是 | 收藏 ID |
错误 200:
{"error": "参数错误"}4. 留言
4.1 我的留言列表
GET /api/player-messages认证:需要 Authorization 请求头。
说明:获取其他玩家给自己的留言列表(含隐藏状态)。
错误 200:
{"error": "玩家不存在"}4.2 隐藏/显示留言
POST /api/player-message/hide认证:需要 Authorization 请求头。
请求体(JSON):
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
message_id | string | 是 | 留言 ID |
hidden | bool | 是 | true=隐藏,false=显示 |
错误 200:
{"error": "参数不完整"}4.3 留言设置
POST /api/player-message/settings认证:需要 Authorization 请求头。
请求体(JSON):
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
allow_messages | bool | 否 | 是否允许他人留言,缺省视为 true |
成功 200:
{"success": true, "message": "设置已更新"}5. 表情
5.1 表情列表
GET /api/sticker/list认证:需要 Authorization 请求头。
说明:返回默认表情 + 当前用户自定义表情。
成功 200:
{
"stickers": [
{ "id": "s1", "name": "大笑", "url": "/stickers/xxx.png" }
]
}5.2 上传表情
POST /api/sticker/upload认证:需要 Authorization 请求头。
限制:
image_data字符串长度不超过约 3MB(解码后 ≤ 约 2MB)- 每个用户最多 100 个自定义表情
请求体(JSON):
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
image_data | string | 是 | Base64 图片数据(可带 data:image/xxx;base64, 前缀) |
file_ext | string | 否 | 文件扩展名,默认 png |
成功 200:
{"success": true, "sticker": {"id": "us_xxx", "name": "", "url": "https://..."}}错误 200:
{"error": "图片数据不能为空"}
{"error": "图片大小不能超过 2MB"}
{"error": "自定义表情已达上限(100个),请先删除旧表情"}
{"error": "图床未配置"}
{"error": "图片数据 base64 解码失败"}
{"error": "图床上传失败,未获取到有效URL"}5.3 删除表情
POST /api/sticker/delete认证:需要 Authorization 请求头。
请求体(JSON):
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
sticker_id | string | 是 | 表情 ID |
成功 200:
{"success": true}错误 200:
{"error": "参数不完整"}5.4 添加到我的表情
POST /api/sticker/add-to-mine认证:需要 Authorization 请求头。
说明:把默认表情 / 其他用户已审核的表情添加到自己的列表,无需审核,直接置为 approved。
请求体(JSON):
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
sticker_id | string | 是 | 表情 ID |
成功 200:
{"success": true}错误 200:
{"error": "缺少表情ID"}
{"error": "表情不存在"}
{"error": "该表情已在你的列表中"}
{"error": "自定义表情已达上限(100个),请先删除旧表情"}5.5 图片上传(聊天室图床代理)
POST /api/image/upload认证:需要 Authorization 请求头。
Content-Type:multipart/form-data
说明:把图片代理上传到图床,图床 URL 不下发,仅返回一次性 key;由 lobby_image 消息内部消费 key 换取图片地址,客户端无法伪造图片地址。不入库。
请求表单字段:
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
file | file | 是 | 图片文件 |
file_ext | string | 否 | 扩展名,缺省从文件名后缀推断,再缺省 png |
fp | string | 否 | 设备指纹(用于封禁检查) |
限制:
- 同一玩家 60 秒内最多 10 次,超出返回
{"error": "上传太频繁,请稍后再试"} - 返回的
key为一次性凭证,有效期 120 秒,且与提交玩家绑定
成功 200:
{"success": true, "key": "a1b2c3d4e5f6..."}错误 200:
{"error": "您已被管理员封禁"}
{"error": "上传太频繁,请稍后再试"}
{"error": "图片上传失败,请重试"}
{"error": "图片读取失败"}
{"error": "图床未配置"}
{"error": "图床上传失败,未获取到有效URL"}6. 头像
6.1 获取玩家头像
GET /api/avatar/{player_id}说明:公开接口,无需认证。返回玩家头像图片(WebP)。
响应头:
Content-Type: image/webpCache-Control: public, no-cacheETag/Last-Modified(支持 304 协商缓存)
响应:
200:头像二进制内容304:If-None-Match/If-Modified-Since命中时404:玩家无头像(返回纯文本Not Found,前端降级为首字符渲染)
说明:上传入口见「7.3 上传头像」POST /api/account/avatar。
7. 账号中心
源码位置:GameController.php(账号中心一区) · 密码策略见 PasswordPolicy.php
页面:GET /account
7.1 账号总览
GET /api/account/overview认证:需要 Authorization 请求头(player_token,只读)。
成功 200(时间字段为 Unix 秒级整数):
{
"ok": true,
"player_id": "abc123...",
"nickname": "小明",
"email": "a@b.com",
"discriminator": 1234,
"created_at": 1700000000,
"last_played_at": 1751000000,
"avatar": "/api/avatar/abc123...?v=1751000000",
"worn_tags": ["理性"],
"worn_special_tags": [],
"can_rename": true,
"rename_hint": "本月可修改一次",
"password_set": true,
"bindings": [
{ "provider": "github", "provider_id": "12345", "email": "a@b.com", "created_at": 1700000000 }
],
"stats": {
"total_games": 42,
"win_rate": 60,
"avg_msgs": 8
}
}| 字段 | 类型 | 说明 |
|---|---|---|
can_rename | bool | 本月是否还可改名(每月限改一次) |
password_set | bool | 是否已自行设置密码;false 时前端只展示「新密码」输入 |
bindings | array | OAuth 绑定列表(provider / provider_id / email / created_at) |
错误 200:
{"error": "账号不存在"}7.2 修改昵称
POST /api/account/nickname认证:需要 Authorization 请求头。
请求体(JSON):
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
nickname | string | 是 | 新昵称,最长 16 字符 |
fp | string | 否 | 设备指纹 |
成功 200:
{"ok": true, "nickname": "新名字", "player_id": "abc123..."}错误 200:
{"error": "昵称不能为空"}
{"error": "该昵称不可使用"}
{"error": "昵称已被占用"}
{"error": "本月已修改过昵称,每月仅可修改一次"}7.3 上传头像
POST /api/account/avatar认证:需要 Authorization 请求头。
Content-Type:multipart/form-data
请求表单字段:
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
file | file | 是 | 头像图片,大小 ≤ 2MB |
限制:同一玩家 60 秒内最多 5 次。落盘路径 Storage/Avatars/{player_id}.webp,直接覆盖。
成功 200:
{"ok": true, "avatar": "/api/avatar/abc123...?v=1751000000"}错误 200:
{"error": "操作太频繁,请稍后再试"}
{"error": "头像上传失败,请重试"}
{"error": "图片大小不能超过 2MB"}
{"error": "图片读取失败"}
{"error": "仅支持常见图片格式(PNG/JPG/GIF/WebP)"}7.4 修改 / 首次设置密码
POST /api/account/password认证:需要 Authorization 请求头。
说明:语义与 WS 通道 change_password / set_password 一致:
password_set=1(已设置过密码):必须提供正确的old_password;password_set=0(OAuth / 邮箱注册的随机密码):免旧密码直接设置。
密码变更后旧 token 失效,故返回新 token。
请求体(JSON):
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
new_password | string | 是 | 新密码,走 PasswordPolicy |
old_password | string | 条件 | password_set=1 时必填 |
密码策略(App\Services\Auth\PasswordPolicy,仅约束新设置的密码):至少 8 位;拒绝纯数字、6 位以上连续字符(123456 / abcdef / qwerty)、单字符重复与常见弱口令;需包含字母、数字、符号中的两类(纯中文口令 ≥2 个汉字可豁免)。
成功 200:
{
"ok": true,
"token": "eyJ...",
"player_id": "abc123...",
"nickname": "小明",
"password_set": true
}错误 200:
{"error": "密码不能为空或只包含空白字符"}
{"error": "密码不能包含换行、制表符等控制字符"}
{"error": "密码至少 8 位"}
{"error": "密码过长,请控制在 72 字节以内"}
{"error": "密码不能是同一个字符的重复"}
{"error": "密码不能包含 6 位以上连续字符(如 123456、abcdef、qwerty)"}
{"error": "密码太常见了,容易被猜到,请换一个"}
{"error": "密码需至少包含字母、数字、符号中的两类"}
{"error": "请输入当前密码"}
{"error": "旧密码不正确"}
{"error": "你已设置过密码,请刷新后重试"}7.5 绑定 / 更换邮箱
POST /api/account/email认证:需要 Authorization 请求头。
请求体(JSON):
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
email | string | 是 | 新邮箱 |
code | string | 是 | 该邮箱收到的验证码(scene=bind_email) |
成功 200:
{"ok": true, "email": "a@b.com"}错误 200:
{"error": "请先登录"}
{"error": "邮箱格式不正确"}
{"error": "验证码错误或已过期"}
{"error": "该邮箱已被其他账号使用"}
{"error": "操作过于频繁,请稍后再试"}7.6 BOT 权限状态
GET /api/account/bot-access认证:需要 Authorization 请求头。
说明:供聊天室内 BOT 弹窗动态决定展示「面板」还是「申请表单」。
成功 200(state 四态之一):
{
"state": "approved",
"nickname": "我的机器人",
"bot_key": "bot_key_xxx",
"account_id": "abc123...",
"status_text": "启用",
"created_at": 1700000000
}{"state": "applied", "message": "您的申请已提交,请等待管理员审核"}{"state": "disabled", "message": "该账号的 BOT 已被禁用,请联系管理员"}{"state": "none"}| 字段 | 类型 | 说明 |
|---|---|---|
state | string | approved(已启用)/ applied(已申请待审)/ disabled(被禁用)/ none(无 BOT) |
8. 邮箱验证码登录 / 注册
源码位置:GameController.php(sendEmailCode / emailAuth) · 业务逻辑见 EmailAuthService.php
/account 未登录页支持「邮箱 + 验证码」一键登录 / 注册。
8.1 发送邮箱验证码
POST /api/email/send-code认证:scene=auth 无需认证;scene=bind_email 需要 Authorization 请求头。
说明:限流 + 签发验证码并入队邮件队列(验证码只存哈希,Redis 自动过期)。
请求体(JSON):
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
email | string | 是 | 收件邮箱 |
scene | string | 否 | auth(登录/注册,默认)或 bind_email(绑定/更换邮箱) |
成功 200(cooldown 为冷却秒数,默认 60):
{"ok": true, "cooldown": 60}错误 200:
{"error": "邮箱格式不正确"}
{"error": "请先登录"}
{"error": "该邮箱已被其他账号使用"}
{"error": "该邮箱已绑定到当前账号"}
{"error": "不支持的验证码用途"}
{"error": "验证码发送失败"}8.2 邮箱验证码登录 / 注册
POST /api/email/auth认证:无需认证。
说明:邮箱未注册 → 自动建号,昵称由邮箱前缀生成({前缀}_{4位随机串},前缀最长 11 位、总长 ≤ 16,例:q1432777209@126.com → q1432777209_a1b2),password_set=0,可后续免旧密码首次设置密码;邮箱已注册 → 直接登录。
请求体(JSON):
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
email | string | 是 | 邮箱 |
code | string | 是 | 邮箱验证码 |
fp | string | 否 | 设备指纹(用于注册限制) |
成功 200:
{
"ok": true,
"is_new": true,
"token": "eyJ...",
"nickname": "q1432777209_a1b2",
"player_id": "abc123...",
"email": "q1432777209@126.com"
}错误 200:
{"error": "邮箱格式不正确"}
{"error": "请输入验证码"}
{"error": "验证码错误或已过期"}
{"error": "操作过于频繁,请稍后再试"}
{"error": "不允许创建多个账号"}
{"error": "账号创建过于频繁,请稍后再试"}
{"error": "账号不存在"}9. BOT 面板
BOT 系统允许玩家申请一个 AI 机器人,BOT 可以以独立身份加入聊天室并发言。每个 BOT 拥有独立的 bot_key 用于 WebSocket 鉴权。
源码位置:GameController.php(BOT 面板/申请/表情接口) · BotGatewayWebSocketHandler.php(BOT 网关)
入口变更:BOT 面板已不再有独立页面,改为在
/lobby聊天室内的弹窗中展示(由GET /api/account/bot-access决定展示面板还是申请表单)。
9.1 获取 BOT 信息
GET /api/bot/panel认证:需要 Authorization 请求头(玩家 token)。
说明:获取当前玩家绑定的 BOT 信息(需 BOT 存在且 status=1)。
请求示例:
curl "https://game.xfcode.top/api/bot/panel" \
-H "Authorization: Bearer <token>"成功 200:
{
"success": true,
"nickname": "我的机器人",
"bot_key": "bot_key_xxx",
"account_id": "abc123...",
"status": 1,
"status_text": "启用",
"created_at": 1700000000
}未绑定 / 被禁用 200:
{"error": "该账号未绑定 BOT 或 BOT 已被禁用"}9.2 更新 BOT 昵称
POST /api/bot/panel/nickname认证:需要 Authorization 请求头。
请求体(JSON):
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
nickname | string | 是 | 新昵称,1~12 个字符 |
请求示例:
curl -X POST "https://game.xfcode.top/api/bot/panel/nickname" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <token>" \
-d '{"nickname":"新机器人"}'成功 200:
{"success": true, "nickname": "新机器人"}错误 200:
{"error": "该账号未绑定 BOT 或 BOT 已被禁用"}
{"error": "昵称需 1~12 个字符"}9.3 轮换 BOT KEY
POST /api/bot/panel/key认证:需要 Authorization 请求头。
说明:重新生成 BOT 的 bot_key,旧 KEY 立即失效。
成功 200:
{"success": true, "bot_key": "新的_bot_key_xxx"}错误 200:
{"error": "该账号未绑定 BOT 或 BOT 已被禁用"}9.4 申请 BOT
POST /api/bot/apply认证:需要 Authorization 请求头。
说明:玩家提交 BOT 申请(邮箱 + 理由;一人仅可申请一次)。
请求体(JSON):
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
email | string | 是 | 申请邮箱 |
reason | string | 是 | 申请理由,至少 10 个字,最长 500 字 |
请求示例:
curl -X POST "https://game.xfcode.top/api/bot/apply" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <token>" \
-d '{"email":"me@example.com","reason":"我想做一个陪聊机器人,为大家提供陪伴"}'成功 200:
{"success": true, "message": "申请已提交,请等待管理员审核", "id": 12}错误 200:
{"error": "邮箱格式不正确"}
{"error": "申请理由至少 10 个字"}
{"error": "您已提交过申请,不可重复申请"}
{"error": "申请失败"}9.5 BOT 表情列表(BOT 网关)
GET /api/bot/stickers?player_id=xxx认证:特殊 BOT KEY 认证 —— player_id 放在查询参数中,bot_key 放在 Authorization: Bearer <bot_key> 请求头中(非玩家 token)。
说明:返回全站表情列表(StickerService::list(),即默认表情表)。
请求示例:
curl "https://game.xfcode.top/api/bot/stickers?player_id=abc123..." \
-H "Authorization: Bearer <bot_key>"成功 200:
{
"stickers": [
{"id": "st_xxx", "name": "大笑", "url": "https://..."}
]
}鉴权失败 200:
{"error": "鉴权失败:玩家ID或KEY不正确"}10. 临时聊天 TempChat
临时聊天(1v1 私密房)系统,支持搜索在线用户、邀请进行私密对话、断线重连等功能。
源码位置:GameController.php(在线/邀请 HTTP 接口) · TempChatWebSocketHandler.php(WS 处理器)
页面:GET /temp-chat
10.1 搜索在线用户
GET /api/temp/users认证:可选 Authorization 请求头(带 token 时结果排除自己)。
说明:仅搜索在线玩家(内存在线索引 OnlineRegistry,不查全量注册用户);昵称实时回源 player_data。
请求参数(Query):
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
keyword | string | 否 | 搜索关键词,最长 20 字符 |
请求示例:
curl "https://game.xfcode.top/api/temp/users?keyword=小明" \
-H "Authorization: Bearer <token>"成功 200:
{
"success": true,
"users": [
{"player_id": "abc...", "nickname": "小明", "status": "online"}
]
}| 字段 | 类型 | 说明 |
|---|---|---|
player_id | string | 玩家 ID |
nickname | string | 玩家昵称 |
status | string | online(在线)、busy(临时聊天中)、ingame(对局中) |
10.2 发起邀请
POST /api/temp/invite认证:需要 Authorization 请求头。
说明:向在线用户发起临时聊天邀请。邀请方必须保持在线(有活跃的 WebSocket 连接)。
请求体(JSON):
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
target_player_id | string | 是 | 目标玩家 ID |
from_name | string | 否 | 邀请方昵称,缺省回源账号昵称 |
请求示例:
curl -X POST "https://game.xfcode.top/api/temp/invite" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <token>" \
-d '{"target_player_id":"abc123..."}'成功 200:
{"success": true, "invite_id": "iv_xxx"}错误 200:
{"error": "缺少目标用户"}
{"error": "不能邀请自己"}
{"error": "服务未就绪"}
{"error": "邀请失败"}10.3 拒绝邀请
POST /api/temp/invite/decline认证:需要 Authorization 请求头。
说明:原地拒绝收到的临时聊天邀请(不跳转临时聊天页)。
请求体(JSON):
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
invite_id | string | 是 | 邀请 ID |
成功 200:
{"success": true}错误 200:
{"error": "缺少邀请ID"}
{"error": "服务未就绪"}
{"error": "拒绝失败"}11. 宏命令
宏命令是聊天室自定义 MD 组件模板。玩家可以创建、管理、删除自己的宏,宏在聊天室中通过 [!触发宏:名称] 语法调用。
源码位置:MacroRepository.php(存取层) · GameController.php(列表/保存/删除接口) · MarkdownMessageParser.php(宏解析/展开)
11.1 宏列表
GET /api/macros认证:可选 Authorization 请求头(带 token 时返回的宏带 mine 标记,区分「我的宏」)。
说明:返回房间全部宏。
成功 200:
{
"success": true,
"macros": [
{
"name": "hello",
"nick": "",
"params": "",
"template": "大家好!",
"creator_id": "abc...",
"creator": "小明",
"mine": false,
"updated_at": 1751000000
}
]
}| 字段 | 类型 | 说明 |
|---|---|---|
mine | bool | 当前 token 对应的玩家是否为该宏创建者 |
updated_at | int | Unix 秒级时间戳 |
11.2 保存宏(新建/更新)
POST /api/macros认证:需要 Authorization 请求头。
说明:创建新宏或更新已有宏(仅创建者可更新)。创建者昵称回源 DB,不信任请求体。
请求体(JSON):
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
name | string | 是 | 宏名称,最长 20 字符 |
nick | string | 否 | 宏昵称(触发别名),最长 32 字符 |
params | string | 否 | 参数名列表,最长 128 字符 |
template | string | 否 | 模板内容 |
成功 200:
{"success": true, "macro": { "name": "greet", "nick": "", "params": "name", "template": "你好,{name}!", "creator_id": "abc...", "creator": "小明" }}错误 200:
{"error": "缺少宏名称"}
{"error": "保存失败"}11.3 删除宏
POST /api/macros/delete认证:需要 Authorization 请求头。
说明:删除自己注册的宏(仅创建者可删除)。
请求体(JSON):
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
name | string | 是 | 要删除的宏名称 |
成功 200:
{"success": true}错误 200:
{"error": "缺少宏名称"}
{"error": "删除失败"}11.4 宏触发语法
宏在聊天室消息中通过以下语法触发:
[!触发宏:名称]
[!触发宏:名称:参数值1|参数值2]
[!触发宏:昵称]
[!触发宏:昵称:参数值1|参数值2]- 名称优先匹配昵称(nick),再匹配名称(name)
- 参数值用
|分隔,按顺序对应宏定义的params - 模板内
{参数名}会被替换为对应参数值 - 展开深度上限 3 层,防止递归死循环
12. 玩家标签
源码位置:PlayerStatsRepository.php(标签存取) · GameController.php(getMyTags/setWornTags 接口)
12.1 获取我的标签
GET /api/player/tags认证:需要 Authorization 请求头。
说明:获取自己的标签库 + 当前佩戴(设置页展示用)。
成功 200:
{
"tags": ["理性", "幽默", "老玩家"],
"worn": ["理性", "幽默"],
"special": [{"name": "新年快乐", "icon": "🎉"}],
"worn_special": [],
"max": 3
}| 字段 | 类型 | 说明 |
|---|---|---|
tags | array | 玩家拥有的所有普通标签 |
worn | array | 当前佩戴的普通标签 |
special | array | 获得的特殊/节日标签 |
worn_special | array | 当前佩戴的特殊标签 |
max | int | 最大可佩戴标签数(PlayerStatsRepository::MAX_WORN_TAGS) |
12.2 设置佩戴标签
POST /api/player/worn-tags认证:需要 Authorization 请求头。
说明:服务端校验 ≤ 上限且必须存在于标签库。
请求体(JSON):
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
tags | array | 否 | 普通标签名数组 |
special_tags | array | 否 | 特殊标签名数组 |
成功 200:返回 PlayerStatsRepository::setWornTags() 的结果对象(含 ok 等字段)。
13. 海龟汤汤面库
源码位置:GameController.php(soup* 接口) · 业务逻辑 SoupPuzzleService.php · 存储 SoupPuzzleRepository.php
页面:GET /soup
汤面表字段:id / owner_id / source(official|member) / scope(private|public) / origin_author_id / title / surface(汤面) / truth(汤底) / key_points(数组) / hints(数组) / difficulty(1~3) / tags / status(active|banned) / used_count / created_at / updated_at。
13.1 我的汤面列表
GET /api/soup/my-puzzles认证:需要 Authorization 请求头。
说明:含汤底,仅本人可见。
成功 200:
{
"success": true,
"puzzles": [
{
"id": 7,
"source": "member",
"scope": "private",
"origin_author_id": "",
"title": "雨夜的车站",
"surface": "...",
"truth": "...",
"key_points": ["..."],
"hints": ["..."],
"difficulty": 2,
"tags": "推理,悬疑",
"status": "active",
"used_count": 3,
"created_at": 1751000000,
"updated_at": 1751000000
}
]
}13.2 新建汤面
POST /api/soup/my-puzzles认证:需要 Authorization 请求头。
说明:新建一律私有。数量上限由 Soup.PublicMaxMyPuzzles 控制(默认 100)。
请求体(JSON):
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
surface | string | 是 | 汤面(谜面),最长 1000 字符 |
truth | string | 是 | 汤底(真相),最长 2000 字符 |
title | string | 否 | 标题,最长 100 字符;缺省取汤面前 20 字 |
key_points | array | 否 | 关键点,每项最长 100 字符 |
hints | array | 否 | 逐级提示,每项最长 200 字符 |
difficulty | int | 否 | 难度 1~3,默认 1 |
tags | string | 否 | 标签,逗号分隔,最长 255 字符 |
成功 200:
{"success": true, "id": 7}错误 200:
{"error": "汤面和汤底均不能为空"}
{"error": "汤面数量已达上限(100)"}
{"error": "保存失败,请稍后再试"}13.3 编辑汤面
PUT /api/soup/my-puzzles/{id}认证:需要 Authorization 请求头。
说明:仅本人、且仅私有汤面可编辑(已公开不可编辑)。请求体字段同 13.2。
成功 200:
{"success": true}错误 200:
{"error": "参数不完整"}
{"error": "汤面不存在或无权编辑"}
{"error": "已公开的汤面不可编辑"}
{"error": "汤面和汤底均不能为空"}
{"error": "保存失败"}13.4 删除汤面
DELETE /api/soup/my-puzzles/{id}认证:需要 Authorization 请求头。
说明:仅本人可删。公开汤面同样可删除(删除即终态,仅移除原汤,已复制出去的衍生汤不受影响)。
成功 200:
{"success": true}错误 200:
{"error": "参数不完整"}
{"error": "汤面不存在或无权删除"}
{"error": "删除失败"}13.5 公开汤面
POST /api/soup/my-puzzles/{id}/share认证:需要 Authorization 请求头。
说明:单向不可逆 —— 公开后汤底对所有用户可见、退出可游玩池、不可编辑、不可撤回;仍可删除。
成功 200:
{"success": true, "scope": "public"}错误 200:
{"error": "参数不完整"}
{"error": "汤面不存在或无权操作"}
{"error": "该汤面已公开,且不可撤回"}
{"error": "操作失败,请稍后再试"}13.6 公开汤池
GET /api/soup/public-puzzles说明:公开接口,无需认证。官方题 + 玩家公开题,公开即作品,汤底一并展示,附原作者昵称。
成功 200:
{
"success": true,
"puzzles": [
{
"id": 3,
"source": "official",
"scope": "public",
"title": "雨夜的车站",
"surface": "...",
"truth": "...",
"author": "官方",
"is_derivative": false,
"difficulty": 2,
"tags": "推理",
"used_count": 12
}
]
}13.7 复制公开汤面
POST /api/soup/public-puzzles/{id}/copy认证:需要 Authorization 请求头。
说明:复制公开汤池 / 官方题库中的汤面到「我的汤面」(含汤底,复制后转为私有)。不能复制自己的汤面。
成功 200:
{"success": true, "id": 8}错误 200:
{"error": "参数不完整"}
{"error": "汤面不存在或已下架"}
{"error": "该汤面不在公开汤池中"}
{"error": "这是你自己的汤面,无需复制"}
{"error": "汤面数量已达上限(100)"}14. 点歌历史
源码位置:GameController.php(songHistory*) · SongService.php
页面:GET /song-history(展示每日 00:00 自动归档的历史歌单,支持选择日期、试听与查看票数)
14.1 歌单快照列表
GET /api/song-history说明:公开接口,无需认证。返回每日歌单备份列表(按日期倒序,仅摘要不含歌曲明细)。
成功 200:
{
"count": 2,
"history": [
{
"date": "2026-10-05",
"saved_at": 1759593600,
"pool_count": 5,
"queue_count": 3,
"has_playing": true,
"total_songs": 9
}
]
}| 字段 | 类型 | 说明 |
|---|---|---|
date | string | 归档日期 YYYY-MM-DD |
saved_at | int | 保存时间(Unix 秒级时间戳;历史快照缺失时回退到该归属日 0 点) |
pool_count | int | 投票池歌曲数 |
queue_count | int | 播放队列歌曲数 |
has_playing | bool | 是否有当前播放 |
total_songs | int | 合计歌曲数 |
14.2 指定日期歌单详情
GET /api/song-history/detail?date=YYYY-MM-DD说明:公开接口,无需认证。返回指定日期歌单快照的完整内容(投票池 + 播放队列 + 当前播放)。
请求参数(Query):
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
date | string | 是 | 日期,格式 YYYY-MM-DD |
成功 200:返回快照对象。
参数格式错误 400:
{"error": "date 参数格式应为 YYYY-MM-DD"}未找到 404:
{"error": "未找到 2026-10-05 的歌单备份"}15. 周报
源码位置:GameController.php(weeklyReport / weeklyReportIndex)
页面:GET /weekly-report
15.1 获取周报数据
GET /api/weekly-report说明:公开接口,无需认证。查询指定周的全部玩家榜单(SQLite Storage/weekly_reports.db)。
请求参数(Query):
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
week | string | 否 | 周标识(如 2026-W33),缺省取最新周 |
sort | string | 否 | 排序字段,默认 total_games;白名单:total_games、total_wins、win_rate、turing_games、turing_guess_accuracy、turing_best_streak、board_gomoku_games、board_go_games,非法值回退为 total_games |
page | int | 否 | 页码,默认 1 |
limit | int | 否 | 每页数量,1~100,默认 20 |
min_games | int | 否 | 最低总局数过滤,默认 0 |
成功 200(时间字段为 Unix 秒级整数):
{
"week": "2026-W33",
"overview": {
"week": "2026-W33",
"generated_at": 1751000000,
"period_start": 1750000000,
"period_end": 1751000000,
"total_players": 120,
"active_players": 80
},
"players": [
{
"player_id": "abc...",
"discriminator": 1234,
"total_games": 20,
"total_wins": 12,
"win_rate": 60,
"turing_games": 10,
"turing_guess_accuracy": 70,
"turing_best_streak": 5,
"board_gomoku_games": 6,
"board_go_games": 4,
"peak_hours": [20, 21],
"created_at": 1700000000,
"last_played_at": 1751000000
}
],
"pagination": {
"page": 1,
"limit": 20,
"total": 80,
"total_pages": 4
},
"available_weeks": [
{"week": "2026-W33", "generated_at": 1751000000, "total_players": 120, "active_players": 80}
]
}错误 200:
{"error": "暂无周榜数据"}
{"error": "无法读取周榜数据"}
{"error": "周榜 2026-W33 不存在"}16. 交流社区
交流社区(帖子 / 评论 / 点赞 / 收藏 / 举报)。公开读接口无需登录;写接口需 Authorization: Bearer <player_token>,未登录返回 HTTP 401 {"error": "请先登录后再操作"}。
源码位置:CommunityController.php · CommunityService.php · CommunityRepository.php
页面:GET /community(列表)、GET /community/post/{id}(详情)
帖子状态(status):pending(待审)/ approved(已通过)/ rejected(被拒)/ deleted(软删除)。列表仅返回 approved,作者本人额外可见自己的 pending/rejected。 帖子类型(type):common(纯文本)/ markdown(Markdown 原文)。审核模式由 Community.ReviewMode 决定。
统一分页结构(success/data):
| 字段 | 类型 | 说明 |
|---|---|---|
list | array | 数据列表 |
total | int | 总数 |
page | int | 当前页 |
page_size | int | 每页数量(默认 Community.PageSize,20;上限 100) |
has_more | bool | 是否还有下一页 |
分页帖子对象字段(list[]):
| 字段 | 类型 | 说明 |
|---|---|---|
id | int | 帖子 ID |
title | string | 标题 |
author_id | string | 作者 player_id |
author_name | string | 作者昵称(实时回源) |
type | string | common / markdown |
description | string | 摘要 |
created_at | int | 发布时间(Unix 秒级时间戳) |
like_count / favorite_count / comment_count | int | 计数 |
status | string | 帖子状态 |
is_pinned | int | 是否置顶(1/0) |
liked / favorited | bool | 当前浏览者是否已赞 / 已收藏(未登录为 false) |
is_own | bool | 是否本人帖子 |
reject_reason | string | 退回理由,仅作者本人可见(他人不返回该字段) |
favorited_at | int | 收藏时间(仅「我的收藏」列表返回) |
author_ip/author_fp属隐私字段,前台接口一律不返回。
16.1 帖子列表
GET /api/community/posts认证:可选 Authorization 请求头(带 token 时返回互动状态与自己的待审帖)。
请求参数(Query):
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
page | int | 否 | 页码,默认 1 |
page_size | int | 否 | 每页数量,默认 Community.PageSize(20),上限 100 |
type | string | 否 | 帖子类型 common / markdown |
sort | string | 否 | 排序:new(默认,置顶优先+时间倒序)/ hot(置顶优先+热度)/ like(置顶优先+点赞数) |
author | string | 否 | 作者 player_id,最长 64 字符 |
成功 200:
{
"success": true,
"data": {
"list": [
{
"id": 12,
"title": "大家好",
"author_id": "abc...",
"author_name": "小明",
"type": "markdown",
"description": "第一次发帖",
"created_at": 1751000000,
"like_count": 3,
"favorite_count": 1,
"comment_count": 2,
"status": "approved",
"is_pinned": 0,
"liked": false,
"favorited": false,
"is_own": false
}
],
"total": 42,
"page": 1,
"page_size": 20,
"has_more": true
}
}16.2 搜索帖子
GET /api/community/search认证:可选 Authorization 请求头。
说明:全文检索(ngram 短语搜索,索引不可用时自动降级 LIKE)。关键词只保留中英文与数字,长度须在 Community.SearchMinLen(默认 2)~ Community.SearchMaxLen(默认 50)之间。
请求参数(Query):
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
q | string | 是 | 搜索关键词 |
page | int | 否 | 页码,默认 1 |
type | string | 否 | 帖子类型过滤 |
成功 200(data 额外含 keyword、driver):
{
"success": true,
"data": {
"list": [],
"total": 0,
"page": 1,
"page_size": 20,
"has_more": false,
"driver": "ngram",
"keyword": "数据库"
}
}错误 400:
{"error": "请输入搜索关键词"}
{"error": "搜索关键词至少 2 个字"}
{"error": "搜索功能未开放"}16.3 帖子详情
GET /api/community/post?id={id}认证:可选 Authorization 请求头。
说明:返回帖子详情 + 评论首页(未通过审核的帖子不展示评论)。
请求参数(Query):
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
id | int | 是 | 帖子 ID |
comment_page | int | 否 | 评论页码,默认 1 |
成功 200:
{
"success": true,
"post": { "id": 12, "title": "大家好", "status": "approved" },
"comments": { "list": [], "total": 0, "page": 1, "page_size": 20, "has_more": false }
}错误:
{"error": "参数不完整"} // 400
{"error": "帖子不存在或不可见"} // 40416.4 评论列表
GET /api/community/comments认证:可选 Authorization 请求头。
说明:分页只对顶级评论(parent_id=0)进行,每条顶级评论附带其全部已通过回复 replies(楼中楼,仅一层)。
请求参数(Query):
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
post_id | int | 是 | 帖子 ID |
page | int | 否 | 页码,默认 1 |
成功 200:
{
"success": true,
"data": {
"list": [
{
"id": 5,
"post_id": 12,
"parent_id": 0,
"author_id": "abc...",
"author_name": "小红",
"content": "写得好",
"created_at": 1751000000,
"is_own": false,
"replies": []
}
],
"total": 1,
"page": 1,
"page_size": 20,
"has_more": false
}
}错误:
{"error": "参数不完整"} // 400
{"error": "帖子不存在或不可见"} // 40416.5 评论定位
GET /api/community/comment-locate说明:公开接口,无需认证。定位评论所在页码(支持 /community/post/{id}#comment-{cid} 跨页直达)。
请求参数(Query):
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
comment_id | int | 是 | 评论 ID |
成功 200:
{"success": true, "post_id": 12, "comment_id": 5, "page": 1}错误:
{"error": "参数不完整"} // 400
{"error": "评论不存在"} // 40416.6 我的帖子 / 我的收藏
GET /api/community/my认证:需要 Authorization 请求头。
请求参数(Query):
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
tab | string | 否 | posts(我的帖子,默认)或 favorites(我的收藏) |
page | int | 否 | 页码,默认 1 |
成功 200(我的帖子,status='all' 会过滤掉已删除):
{"success": true, "tab": "posts", "data": {"list": [], "total": 0, "page": 1, "page_size": 20, "has_more": false}}{"success": true, "tab": "favorites", "data": {"list": [], "total": 0, "page": 1, "page_size": 20, "has_more": false}}16.7 发布 / 编辑帖子
POST /api/community/post/save认证:需要 Authorization 请求头。
说明:id 为空即新建,>0 为编辑(仅作者本人)。发帖限流:每分钟 1 条、每天 20 条。编辑会重置审核状态。
请求体(JSON):
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
id | int | 否 | 帖子 ID(编辑时传) |
title | string | 是 | 标题,最长 Community.MaxTitleLen(120) |
content | string | 是 | 正文,最长 Community.MaxContentLen(40000) |
description | string | 否 | 摘要,最长 Community.MaxDescLen(500) |
type | string | 否 | common / markdown,默认 markdown,非法值回退 markdown |
fp | string | 否 | 设备指纹(也可放在表单字段) |
成功 200:
{
"success": true,
"id": 12,
"status": "pending",
"message": "发布成功,已提交审核,通过后即可公开展示"
}错误 400:
{"error": "标题不能为空"}
{"error": "正文不能为空"}
{"error": "您已被管理员封禁,无法发帖"}
{"error": "无效的玩家身份"}
{"error": "帖子不存在"}
{"error": "只能编辑自己的帖子"}
{"error": "帖子已删除,无法编辑"}
{"error": "编辑失败,请重试"}
{"error": "发布失败,请重试"}
{"error": "发帖太频繁,请稍后再试"}
{"error": "今日发帖太频繁,请稍后再试"}16.8 删除帖子
POST /api/community/post/delete认证:需要 Authorization 请求头。
请求体(JSON):
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
id | int | 是 | 帖子 ID |
成功 200:
{"success": true, "message": "已删除"}错误:
{"error": "参数不完整"} // 400
{"error": "帖子不存在"} // 404
{"error": "只能删除自己的帖子"} // 403
{"error": "删除失败,请重试"} // 40016.9 点赞帖子
POST /api/community/like认证:需要 Authorization 请求头。
说明:幂等切换(赞 / 取消赞)。每秒频次受限(每分钟 30 次)。
请求体(JSON):
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
id | int | 是 | 帖子 ID |
成功 200:
{"success": true, "active": true, "count": 4, "message": "已点赞"}错误 400:
{"error": "参数不完整"}
{"error": "帖子不存在或不可见"}
{"error": "您已被管理员封禁"}
{"error": "操作太频繁,请稍后再试"}
{"error": "点赞失败,请重试"}16.10 收藏帖子
POST /api/community/favorite认证:需要 Authorization 请求头。
说明:幂等切换(收藏 / 取消收藏)。
请求体(JSON):
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
id | int | 是 | 帖子 ID |
成功 200:
{"success": true, "active": true, "count": 2, "message": "已收藏"}错误 400:
{"error": "参数不完整"}
{"error": "帖子不存在或不可见"}
{"error": "您已被管理员封禁"}
{"error": "操作太频繁,请稍后再试"}
{"error": "收藏失败,请重试"}16.11 发表评论
POST /api/community/comment认证:需要 Authorization 请求头。
说明:评论免审(始终即时 approved)。支持楼中楼:parent_id > 0 时为该顶级评论的回复(仅允许一层)。限流:每分钟 5 条。
请求体(JSON):
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
post_id | int | 是 | 帖子 ID |
content | string | 是 | 评论内容,最长 Community.MaxCommentLen(1000) |
parent_id | int | 否 | 回复的顶级评论 ID,0=顶级评论 |
fp | string | 否 | 设备指纹(也可放在表单字段) |
成功 200:
{"success": true, "id": 9, "status": "approved", "message": "评论成功"}错误 400:
{"error": "参数不完整"}
{"error": "评论内容不能为空"}
{"error": "您已被管理员封禁,无法评论"}
{"error": "无效的玩家身份"}
{"error": "帖子不存在或不可见"}
{"error": "回复的评论不存在"}
{"error": "评论太频繁,请稍后再试"}
{"error": "评论失败,请重试"}16.12 删除评论
POST /api/community/comment/delete认证:需要 Authorization 请求头。
说明:仅作者本人。删除顶级评论时级联软删其全部回复并扣除计数。
请求体(JSON):
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
id | int | 是 | 评论 ID |
成功 200:
{"success": true, "message": "已删除"}错误:
{"error": "参数不完整"} // 400
{"error": "评论不存在"} // 404
{"error": "只能删除自己的评论"} // 403
{"error": "删除失败,请重试"} // 40016.13 举报帖子
POST /api/community/report认证:需要 Authorization 请求头。
说明:复用既有举报体系(source=community,与对局/聊天室举报同一张 reports 表)。仅允许举报「已通过」且非本人的帖子;同一用户对同一帖子只能举报一次。
请求体(JSON):
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
id | int | 是 | 帖子 ID |
reason | string | 是 | 举报原因,非空且不超过 100 字 |
fp | string | 否 | 设备指纹(也可放在表单字段) |
成功 200:
{"success": true, "message": "举报已提交,管理员将尽快处理"}错误 400:
{"error": "参数不完整"}
{"error": "举报原因不能为空且不超过100字"}
{"error": "您已被管理员封禁,无法举报"}
{"error": "无效的玩家身份"}
{"error": "帖子不存在"}
{"error": "该帖子不可举报"}
{"error": "不能举报自己的帖子"}
{"error": "举报失败,请稍后再试"}17. OAuth 快捷登录
OAuth 2.0 快捷登录系统支持通过第三方平台(如 GitHub)快速登录/注册,使用 PKCE 授权码流程。登录结果产出现有的 player token(localStorage 存储),不引入 session/cookie。
源码位置:OAuthController.php(PKCE 授权码流程) · GenericOAuthProvider.php(提供商抽象) · OAuthBindingRepository.php(绑定存取)
17.1 支持的提供商
GET /api/oauth/providers说明:公开接口,无需认证。未配置 ClientId 或端点不完整的 provider 不会返回。
请求示例:
curl "https://game.xfcode.top/api/oauth/providers"成功 200(直接返回数组):
[
{"key": "github", "name": "GitHub"}
]17.2 发起 OAuth 登录
GET /oauth/login/{provider}
POST /oauth/login/{provider}说明:跳转到第三方 OAuth 授权页面。{provider} 为提供商 ID(如 github)。未知 / 不可用的 provider 返回 404 Unknown OAuth provider。
服务端处理:
- 生成 PKCE
code_verifier和code_challenge - 生成随机
state参数防 CSRF - 302 跳转到第三方 OAuth 授权 URL
- 临时数据(state、code_verifier、redirect、可选 bind_player_id)存入 Redis,600 秒过期
请求参数(Query 或 form body,POST 场景用 form):
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
redirect | string | 否 | 登录成功后的站内跳转路径(仅允许以 / 开头的同站路径) |
bind | int | 否 | 1=绑定模式(需已登录),缺省为登录模式 |
token | string | 条件 | 绑定模式的玩家 Token(form POST 时用,无法带 Authorization 头) |
绑定模式:已登录玩家通过 bind=1 将第三方账号绑定到现有账号,须经 Authorization: Bearer 或 form token 解析出玩家;失效时返回 {"ok": false, "error": "登录状态已失效,请重新登录后再绑定"}。
登录模式:未登录玩家通过 OAuth 创建新账号或登录已有绑定账号。
17.3 OAuth 回调
GET /oauth/callback/{provider}说明:第三方 OAuth 授权后的回调地址。{provider} 为提供商 ID。
请求参数(Query):
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
code | string | 是 | 授权码(provider 回调携带) |
state | string | 是 | 状态参数(校验 CSRF) |
处理流程:
- 校验
state(一次性,取后即删)与 Redis 中的一致、provider 匹配 - 用
code+code_verifier向 provider 换取access_token - 用
access_token获取第三方用户信息 - 分支处理:
- 已绑定 → 生成一次性 exchange code,302 回到
redirect(携带oauth_code) - 绑定模式 → 关联到已登录玩家,
bind失败则 302 携带oauth_error=该 OAuth 账号已绑定其他玩家 - 按邮箱匹配到已有玩家 → 自动绑定并登录
- 邮箱未匹配 → 302 回
redirect携带pending_code,引导确认建号
- 已绑定 → 生成一次性 exchange code,302 回到
错误响应:400 Missing code or state / 400 Invalid or expired state / 400 Provider mismatch;业务失败时 302 携带 oauth_error=<消息>。
17.4 完成登录(一次性换取 token)
GET /oauth/complete说明:公开接口,无需认证。用回调下发的一次性 exchange code 换取 player token(前端存 localStorage)。exchange code 有效期 60 秒,且一次性。
请求参数(Query):
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
code | string | 是 | 一次性 exchange code |
成功 200:
{"ok": true, "token": "eyJ...", "nickname": "小明", "player_id": "abc123..."}错误 200:
{"ok": false, "error": "缺少凭证"}
{"ok": false, "error": "登录凭证无效或已过期"}
{"ok": false, "error": "登录凭证数据异常"}17.5 获取待绑定信息
GET /api/oauth/pending-info说明:公开接口,无需认证。读取 pending 建号确认信息(供前端弹窗展示邮箱 + 预填昵称),只读不消费,不返回任何 token 等敏感数据。
请求参数(Query):
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
code | string | 是 | pending 建号凭证 |
成功 200:
{"ok": true, "provider": "github", "email": "a@b.com", "nickname": "octocat"}错误 200:
{"ok": false, "error": "缺少凭证"}
{"ok": false, "error": "确认凭证无效或已过期,请重新登录"}17.6 确认创建账号
POST /api/oauth/confirm-create说明:公开接口,无需认证。参数从 Query 读取。与普通注册同一套防线:昵称唯一、IP/FP 3 账号上限、Redis 频率限制。
请求参数(Query):
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
code | string | 是 | pending 建号凭证 |
nickname | string | 是 | 昵称,最长 12 个字符 |
fp | string | 否 | 设备指纹 |
成功 200:
{"ok": true, "token": "eyJ...", "nickname": "小明", "player_id": "abc123..."}错误 200:
{"ok": false, "error": "缺少确认凭证"}
{"ok": false, "error": "昵称不能为空"}
{"ok": false, "error": "昵称最长 12 个字符"}
{"ok": false, "error": "确认凭证无效或已过期,请重新登录"}
{"ok": false, "error": "该昵称已被占用,请换一个"}
{"ok": false, "error": "确认凭证已使用,请重新登录"}
{"ok": false, "error": "不允许创建多个账号!"}
{"ok": false, "error": "账号创建过于频繁,请稍后再试"}
{"ok": false, "error": "该 OAuth 账号已被其他玩家绑定"}17.7 取消建号
POST /api/oauth/cancel说明:公开接口,无需认证。用户选择不创建账户时清理 pending(不创建任何数据)。code 从 Query 读取(可选)。
请求参数(Query):
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
code | string | 否 | pending 建号凭证 |
成功 200:
{"ok": true}17.8 获取绑定列表
GET /api/oauth/bindings认证:需要 Authorization 请求头。
成功 200(时间字段为 Unix 秒级整数):
{
"ok": true,
"bindings": [
{"provider": "github", "provider_id": "12345", "email": "a@b.com", "created_at": 1700000000}
],
"avatar": "/api/avatar/abc123...?v=1751000000"
}17.9 解绑 OAuth
POST /api/oauth/unbind认证:需要 Authorization 请求头。
说明:provider 从 form body 读取(Content-Type: application/x-www-form-urlencoded 或 multipart/form-data)。
请求表单字段:
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
provider | string | 是 | 提供商 ID |
成功 200:
{"ok": true}错误 200:
{"ok": false, "error": "缺少 provider"}17.10 手动同步头像
POST /api/oauth/sync-avatar认证:需要 Authorization 请求头。
说明:用指定平台存储的 access_token 重新拉取最新头像并下载覆盖本地文件。provider 从 form body 读取。
请求表单字段:
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
provider | string | 是 | 提供商 ID |
成功 200:
{"ok": true, "player_id": "abc123..."}错误 200:
{"ok": false, "error": "缺少 provider"}
{"ok": false, "error": "未找到可同步的 OAuth 绑定"}
{"ok": false, "error": "该平台授权已过期,请重新登录后同步"}
{"ok": false, "error": "该平台未返回头像"}
{"ok": false, "error": "头像同步失败,请稍后再试"}18. 页面路由
以下为服务端渲染的 HTML 页面(GET,均免认证):
| 路径 | 说明 |
|---|---|
/ | 游戏中心首页(XQFGameHub:玩法入口、在线人数、账号便签) |
/turing | 图灵测试 1v1 玩法页(同时承载玩家档案 / 公开收藏视图) |
/lobby | 公共聊天室大厅 |
/temp-chat | 临时聊天私密房间 |
/board/gomoku | 五子棋对局页 |
/board/go | 围棋对局页 |
/soup | 海龟汤页 |
/blockworld | 方块世界(BlockWorld.Enabled=false 时返回 503 维护页) |
/agreement | 用户协议页 |
/privacy | 隐私政策页 |
/weekly-report | 全服周报页 |
/account | 完整账号中心页 |
/community | 交流社区帖子列表页 |
/community/post/{id} | 交流社区帖子详情页 |
/song-history | 点歌回顾页(历史歌单) |
/player/{nickname} | 玩家档案视图(由 1v1 SPA 渲染) |
/collection/{token} | 公开收藏视图(由 1v1 SPA 渲染) |
/{adminPath} | 管理后台页面(默认 /admin,由 Admin.Path 配置) |
棋类页面已统一到
/board/...命名空间,早期的旧版短路径已下线(不做 302 兼容)。
静态资源路由
由 GameController::STATIC_RESOURCES 批量注册(GET,免认证,带强缓存 Cache-Control: public, max-age=31536000, immutable 与 ETag / Last-Modified 协商):
| 路径 | 文件 |
|---|---|
/script.js | Public/script.js |
/style.css | Public/style.css |
/favicon.svg | Public/favicon.svg |
/shared.js | Public/shared.js |
/shared/md-renderer.js | Public/shared/md-renderer.js |
/shared/md-renderer.css | Public/shared/md-renderer.css |
/hub.css | Public/hub.css |
/hub.js | Public/hub.js |
/hub-nav.js | Public/hub-nav.js |
/comment-widget.js | Public/comment-widget.js |
/temp-invite.js | Public/temp-invite.js |
/turing/index.html | Public/turing/index.html |
/soup/index.html | Public/soup/index.html |
/soup/soup.css | Public/soup/soup.css |
/soup/soup.js | Public/soup/soup.js |
/agreement/index.html | Public/agreement/index.html |
/privacy/index.html | Public/privacy/index.html |
/lobby/lobby.css | Public/lobby/lobby.css |
/lobby/lobby.js | Public/lobby/lobby.js |
/lobby/lobby-bot.js | Public/lobby/lobby-bot.js |
/board/board-game.css | Public/board/board-game.css |
/board/gomoku/gomoku.css | Public/board/gomoku/gomoku.css |
/board/gomoku/gomoku.js | Public/board/gomoku/gomoku.js |
/board/go/go.css | Public/board/go/go.css |
/board/go/go.js | Public/board/go/go.js |
/board/go/go-chat.js | Public/board/go/go-chat.js |
/weekly-report/weekly-report.css | Public/weekly-report/weekly-report.css |
/weekly-report/weekly-report.js | Public/weekly-report/weekly-report.js |
/temp-chat/temp-chat.css | Public/temp-chat/temp-chat.css |
/temp-chat/temp-chat.js | Public/temp-chat/temp-chat.js |
/account/index.html | Public/account/index.html |
/account/account.css | Public/account/account.css |
/account/account.js | Public/account/account.js |
/account/sticker-manager.js | Public/account/sticker-manager.js |
/blockworld/index.html | Public/blockworld/index.html |
/blockworld/blockworld.css | Public/blockworld/blockworld.css |
/blockworld/blockworld.js | Public/blockworld/blockworld.js |
/community/index.html | Public/community/index.html |
/community/post.html | Public/community/post.html |
/community/community.css | Public/community/community.css |
/community/community.js | Public/community/community.js |
/community/post.js | Public/community/post.js |
/song-history/song-history.css | Public/song-history/song-history.css |
/song-history/song-history.js | Public/song-history/song-history.js |
/admin/admin.js | Public/admin/admin.js |
/admin/admin_mdv3.js | Public/admin/admin_mdv3.js |
/admin/admin.css | Public/admin/admin.css |
/admin/favicon.svg | Public/admin/favicon.svg |
19. 管理员
19.1 管理员登录
POST /{adminPath}/api/login说明:{adminPath} 默认值为 /admin,可在配置中修改。注意此接口不走 Bearer Token,使用用户名+密码。
请求体(JSON):
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
username | string | 是 | 管理员用户名 |
password | string | 是 | 管理员密码 |
请求示例:
curl -X POST "https://你的域名/admin/api/login" \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"xxx"}'成功 200:
{
"ok": true,
"token": "eyJ...",
"username": "admin",
"role": "super_admin"
}失败 200:
{"ok": false, "error": "用户名和密码不能为空"}
{"ok": false, "error": "用户名或密码错误"}
{"ok": false, "error": "该账号已被禁用"}Token 说明:
- 管理员 Token 有效期 24 小时
- 签名密钥为
Admin.Password配置项 - Payload 包含
admin_id、username、role
附录:完整 API 索引
API 路由
| 方法 | 路径 | 认证 | 说明 |
|---|---|---|---|
| GET | /api/generate-player-id | 条件 | 获取/恢复身份、改密码、查战绩 |
| GET | /api/player-stats | 密码 | 通过昵称+密码查战绩 |
| GET | /api/player-profile | 无 | 玩家公开档案 |
| GET | /api/chat-history | Bearer | 聊天记录列表 |
| GET | /api/chat-history/detail | Bearer | 聊天记录详情 |
| POST | /api/chat-history/collect | Bearer | 设置收藏标题/公开状态 |
| GET | /api/collection/by-token | 无 | 通过 Token 获取收藏 |
| GET | /api/player-collections | 无 | 玩家公开收藏列表 |
| GET | /api/collection/detail | 无 | 收藏详情 |
| POST | /api/collection/like | Bearer | 点赞收藏 |
| GET | /api/player-messages | Bearer | 我的留言列表 |
| POST | /api/player-message/hide | Bearer | 隐藏/显示留言 |
| POST | /api/player-message/settings | Bearer | 留言设置 |
| GET | /api/sticker/list | Bearer | 表情列表 |
| POST | /api/sticker/upload | Bearer | 上传表情 |
| POST | /api/sticker/delete | Bearer | 删除表情 |
| POST | /api/sticker/add-to-mine | Bearer | 添加到我的表情 |
| POST | /api/image/upload | Bearer | 图片上传(返回一次性 key) |
| GET | /api/avatar/{player_id} | 无 | 玩家头像图片 |
| GET | /api/account/overview | Bearer | 账号总览 |
| POST | /api/account/nickname | Bearer | 修改昵称 |
| POST | /api/account/avatar | Bearer | 上传头像 |
| POST | /api/account/password | Bearer | 修改/首次设置密码 |
| POST | /api/account/email | Bearer | 绑定/更换邮箱 |
| GET | /api/account/bot-access | Bearer | BOT 权限状态 |
| POST | /api/email/send-code | 条件 | 发送邮箱验证码 |
| POST | /api/email/auth | 无 | 邮箱验证码登录/注册 |
| GET | /api/bot/panel | Bearer | 获取 BOT 信息 |
| POST | /api/bot/panel/nickname | Bearer | 更新 BOT 昵称 |
| POST | /api/bot/panel/key | Bearer | 轮换 BOT KEY |
| POST | /api/bot/apply | Bearer | 申请 BOT |
| GET | /api/bot/stickers | BOT KEY | BOT 表情列表(player_id + bot_key) |
| GET | /api/temp/users | 可选 | 搜索在线用户 |
| POST | /api/temp/invite | Bearer | 发起临时聊天邀请 |
| POST | /api/temp/invite/decline | Bearer | 拒绝邀请 |
| GET | /api/macros | 可选 | 宏列表 |
| POST | /api/macros | Bearer | 保存宏(新建/更新) |
| POST | /api/macros/delete | Bearer | 删除宏 |
| GET | /api/player/tags | Bearer | 获取我的标签 |
| POST | /api/player/worn-tags | Bearer | 设置佩戴标签 |
| GET | /api/soup/my-puzzles | Bearer | 我的汤面列表 |
| POST | /api/soup/my-puzzles | Bearer | 新建汤面 |
| PUT | /api/soup/my-puzzles/{id} | Bearer | 编辑汤面 |
| DELETE | /api/soup/my-puzzles/{id} | Bearer | 删除汤面 |
| POST | /api/soup/my-puzzles/{id}/share | Bearer | 公开汤面 |
| GET | /api/soup/public-puzzles | 无 | 公开汤池 |
| POST | /api/soup/public-puzzles/{id}/copy | Bearer | 复制公开汤面 |
| GET | /api/song-history | 无 | 歌单快照列表 |
| GET | /api/song-history/detail | 无 | 指定日期歌单详情 |
| GET | /api/weekly-report | 无 | 周榜数据 |
| GET | /api/community/posts | 可选 | 社区帖子列表 |
| GET | /api/community/search | 可选 | 社区搜索 |
| GET | /api/community/post | 可选 | 社区帖子详情 |
| GET | /api/community/comments | 可选 | 社区评论列表 |
| GET | /api/community/comment-locate | 无 | 定位评论所在页码 |
| GET | /api/community/my | Bearer | 我的帖子/我的收藏 |
| POST | /api/community/post/save | Bearer | 发布/编辑帖子 |
| POST | /api/community/post/delete | Bearer | 删除帖子 |
| POST | /api/community/like | Bearer | 点赞帖子 |
| POST | /api/community/favorite | Bearer | 收藏帖子 |
| POST | /api/community/comment | Bearer | 发表评论 |
| POST | /api/community/comment/delete | Bearer | 删除评论 |
| POST | /api/community/report | Bearer | 举报帖子 |
| GET | /api/oauth/providers | 无 | OAuth 提供商列表 |
| GET | /api/oauth/bindings | Bearer | OAuth 绑定列表 |
| GET | /api/oauth/pending-info | 无 | 获取待建号信息 |
| POST | /api/oauth/confirm-create | 无 | 确认创建 OAuth 账号(参数在 Query) |
| POST | /api/oauth/cancel | 无 | 取消 OAuth 建号 |
| POST | /api/oauth/unbind | Bearer | 解绑 OAuth(provider 为 form 字段) |
| POST | /api/oauth/sync-avatar | Bearer | 手动同步头像(provider 为 form 字段) |
| GET/POST | /oauth/login/{provider} | 条件 | 发起 OAuth 授权(bind=1 时需登录) |
| GET | /oauth/callback/{provider} | 无 | OAuth 回调 |
| GET | /oauth/complete | 无 | 一次性 exchange code 换 token |
| POST | /{adminPath}/api/login | 密码 | 管理员登录 |
页面路由
| 方法 | 路径 | 认证 | 说明 |
|---|---|---|---|
| GET | / | 无 | 游戏中心首页 |
| GET | /turing | 无 | 图灵测试 1v1 |
| GET | /lobby | 无 | 公共聊天室 |
| GET | /temp-chat | 无 | 临时聊天 |
| GET | /board/gomoku | 无 | 五子棋 |
| GET | /board/go | 无 | 围棋 |
| GET | /soup | 无 | 海龟汤 |
| GET | /blockworld | 无 | 方块世界(可 503 维护) |
| GET | /agreement | 无 | 用户协议 |
| GET | /privacy | 无 | 隐私政策 |
| GET | /weekly-report | 无 | 全服周报 |
| GET | /account | 无 | 账号中心 |
| GET | /community | 无 | 交流社区列表 |
| GET | /community/post/{id} | 无 | 交流社区详情 |
| GET | /song-history | 无 | 点歌回顾 |
| GET | /player/{nickname} | 无 | 玩家档案 |
| GET | /collection/{token} | 无 | 公开收藏 |
| GET | /{adminPath} | 无 | 管理后台页面 |
| GET | 静态资源路径(见 18 节) | 无 | 静态资源 |