Skip to content

客户端实现建议 ​

本页给出使用公开 API 编写脚本、机器人或第三方客户端时的工程建议。它不增加新的接口,只说明如何可靠地使用已有接口。

基础客户端结构 ​

建议把客户端拆成四层:

  1. 配置层:保存 API 基础地址、请求超时和重试次数;
  2. 认证层:获取、验证、退出 Token;
  3. 资源层:按功能调用配方、缓存、相片、记录等接口;
  4. 展示层:把 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。推荐:

  1. 读取 Content-Type;
  2. JSON 类型走错误解析;
  3. 图片/文件类型使用 blob() 或流式读取;
  4. 由客户端创建临时 URL;
  5. 下载完成后释放临时 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,也不会自动重试写操作。

兼容性检查 ​

启动客户端时建议依次执行:

  1. 验证 Token;
  2. 获取一个只读资源;
  3. 检查响应中的 success/msg;
  4. 记录 API 版本和客户端版本;
  5. 再显示写入或删除按钮。

接口返回新字段时应忽略未知字段;接口缺少可选字段时使用空数组、空对象或“暂无数据”状态,不要直接崩溃。

Released under the CC BY-NC-SA 4.0 License.