客户端实现建议
本页给出使用公开 API 编写脚本、机器人或第三方客户端时的工程建议。它不增加新的接口,只说明如何可靠地使用已有接口。
基础客户端结构
建议把客户端拆成四层:
- 配置层:保存 API 基础地址、请求超时和重试次数;
- 认证层:获取、验证、退出 Token;
- 资源层:按功能调用配方、缓存、相片、记录等接口;
- 展示层:把
msg、加载状态、分页和错误显示给用户。
基础地址不应散落在代码中:
ts
export const config = {
apiBase: 'https://xn--9er778cb3e.xn--igrr70arr3c.vip/zundalink/api/v0',
timeoutMs: 20_000,
maxRetries: 2,
};统一请求函数
ts
type ApiEnvelope = {
success?: boolean;
msg?: string;
[key: string]: unknown;
};
export async function apiRequest<T extends ApiEnvelope>(
path: string,
body: Record<string, unknown>,
): Promise<T> {
const response = await fetch(`${config.apiBase}${path}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body),
});
const data = (await response.json()) as T;
if (!response.ok || data.success === false) {
throw new Error(data.msg || `HTTP ${response.status}`);
}
return data;
}文件接口需要先检查 Content-Type,不能无条件调用 response.json()。
重试策略
只对网络中断、连接重置或 5xx 做有限重试:
- 第一次等待约 1 秒;
- 第二次等待约 2 秒;
- 加入少量随机抖动;
- 超过上限后显示错误并保存上下文。
不要自动重试以下操作:
- 删除相片;
- 删除上传缓存;
- 恢复缓存;
- 修改配方;
- 发布评论或反馈。
这些操作可能已经成功但响应丢失,盲目重试会造成重复或不可逆变化。
认证状态机
建议按以下状态处理:
| 状态 | 行为 |
|---|---|
| 未登录 | 只显示公开页面和登录入口 |
| Token 未验证 | 先调用验证接口,不调用写入接口 |
| Token 有效 | 允许读取当前账号可见资源 |
| Token 过期 | 停止后台轮询,要求重新登录 |
| 权限不足 | 显示服务消息,不尝试升级权限 |
| 主动退出 | 清除内存 Token,按用户选择保留或删除本地账号条目 |
Token 不应出现在日志、URL、异常堆栈、截图或遥测数据中。
分页读取
游玩记录使用 [start, end) 范围:
ts
async function* allPlaylogs(token: string, pageSize = 50) {
let start = 0;
while (true) {
const data = await apiRequest<{
success: boolean;
total: number;
playlogList: unknown[];
}>('/playlog/get', { token, start, end: start + pageSize });
yield* data.playlogList;
start += data.playlogList.length;
if (start >= data.total || data.playlogList.length === 0) break;
}
}如果返回列表长度小于请求范围,应使用实际返回长度推进游标,避免跳过记录。
二进制响应
相片文件成功时可能返回二进制,错误时返回 JSON。推荐:
- 读取
Content-Type; - JSON 类型走错误解析;
- 图片/文件类型使用
blob()或流式读取; - 由客户端创建临时 URL;
- 下载完成后释放临时 URL。
不要把大文件完整转换成 base64 再长期保存在 localStorage。
数据单位
- 达成率使用放大整数,显示前除以
10000; - 文件
size使用字节; - 限速字段使用 MB/s;
- 时间字符串通常为 RFC3339 或
YYYY-MM-DDTHH:mm:ss,处理时明确时区; - 百分比字段可能已经是 0–100 数值,不要重复乘 100。
删除和恢复的交互设计
任何不可逆按钮都应:
- 显示目标名称;
- 说明是否影响全部内容;
- 要求用户二次确认;
- 在请求期间禁用重复点击;
- 成功后刷新列表;
- 失败时保留原列表和错误消息。
恢复缓存还应显示文件名、大小、生成时间和目标账号,避免误恢复。
本地缓存
可以缓存公开索引、缩略图和最近一次成功读取的非敏感结果,但应:
- 给每个账号和机台隔离缓存键;
- Token 不放入缓存键;
- 标记生成时间和版本;
- 在退出账号时清理敏感数据;
- 网络请求失败时明确标记“缓存数据”,不要伪装成实时数据。
Python 示例
python
import requests
API = "https://xn--9er778cb3e.xn--igrr70arr3c.vip/zundalink/api/v0"
def call(path: str, payload: dict) -> dict:
response = requests.post(
API + path,
json=payload,
timeout=20,
)
response.raise_for_status()
data = response.json()
if data.get("success") is False:
raise RuntimeError(data.get("msg", "request failed"))
return data
token_data = call("/token/validate", {"token": "<TOKEN>"})生产客户端应补充超时、日志脱敏、退避和可取消请求;示例不会保存 Token,也不会自动重试写操作。
兼容性检查
启动客户端时建议依次执行:
- 验证 Token;
- 获取一个只读资源;
- 检查响应中的
success/msg; - 记录 API 版本和客户端版本;
- 再显示写入或删除按钮。
接口返回新字段时应忽略未知字段;接口缺少可选字段时使用空数组、空对象或“暂无数据”状态,不要直接崩溃。
