缓存与条件请求

每个 200 的 GET 都带 ETag。多数面按 private, no-store 声明——这不是保守,是因为它们的 body 会随凭据能力位变化,缓存住哪一份都是错的。

ETag 与 304

所有 200 的 GET 都带一个强 ETag,它是响应体的哈希——fields= 裁剪过的响应有它自己的验证器,所以条件请求在任何形状上都成立。

# 第一次
curl -i "https://api.nextmoe.dev/v2/catalog/works/207379" -H "Authorization: Bearer nmk_live_…"
< HTTP/1.1 200 OK
< ETag: "9f2a1c…"

# 之后带上它
curl -i "https://api.nextmoe.dev/v2/catalog/works/207379" \
  -H "Authorization: Bearer nmk_live_…" \
  -H 'If-None-Match: "9f2a1c…"'
< HTTP/1.1 304 Not Modified

304 同样计入限流。它省的是带宽和解析,不是调用次数——真正省调用的是把响应连同 ETag 存下来,在你自己的 TTL 内根本不发这个请求。

Cache-Control

声明
/v2/problems/v2/vocabularies/v2/catalog/schemas/*/v2/catalog/openapi.json public, max-age=300, s-maxage=1800, stale-while-revalidate=3600
其余所有 2xx private, no-store
所有 4xx / 5xx no-store

每个响应还带 Vary: Authorization, Accept-Encoding

为什么可共享缓存的只有注册表面

public 的前提是「同一个 URL,任何人拿到的字节相同」。注册表面满足这一点:错误码、词表、对象 schema、OpenAPI 文档不随谁在问而变化。

/v2/catalog/* 不满足。它是密钥门内的面,body 会随认领站点的围栏、nsfw 能力位、claim_state 可见范围而变——一个中间层如果自作主张把它当公共内容缓存下来,就会把 A 的视图发给 B。所以这里是默认拒绝:只有明确证明了「body 与凭据无关」的面才进 public 名单,其余一律 private, no-store

Vary: Authorization 救不了这件事——这是实测过的:曾经有边缘节点在缓存里放了一份 max-age=14400/v2/catalog 响应,而这个源站从来没有设过这个值。所以现在每一条 2xx 都显式声明自己的缓存意图,不给中间层留下「自己发明一个」的空间。

你自己的服务端当然可以缓存这些响应——你知道自己的密钥、也知道自己的可见范围。不要做的是把它们交给一个不理解这些围栏的共享中间层。

写操作的条件请求

改动别人也能改的资源时用乐观并发:先 GET 拿 ETag,再把它放进 If-Match 写回去。

curl -X PATCH "https://api.nextmoe.dev/v2/me/proposals/8812" \
  -H "Authorization: Bearer <access_token>" \
  -H 'If-Match: "9f2a1c…"' \
  -H "Content-Type: application/json" \
  -d '{"note":"补充一条来源"}'
  • 不带 If-Match428 PRECONDITION_REQUIRED
  • 带了但对不上是 412 PRECONDITION_FAILED——说明有人在你读之后改过,重新 GET 再合并。
  • If-Match: * 只表示「存在即可」,它不能用来绕过任何权限检查。

幂等键

POST 支持 Idempotency-Key。同一个计数身份、同一路径、同一个 key 的重复请求会重放首次的响应(保留 24 小时),网络超时后安全重试因此不会写两条。

curl -X POST "https://api.nextmoe.dev/v2/me/claims" \
  -H "Authorization: Bearer <access_token>" \
  -H "Idempotency-Key: 6f3c2b7a-1d54-4a90-9d1e-2c7f8b0a5e31" \
  -H "Content-Type: application/json" \
  -d '{ … }'

同一个 key 配上不同的 body 是 409 IDEMPOTENCY_KEY_REUSED——这是在提醒你 key 生成有 bug,而不是在阻挠你。

要跟住变化,不要轮询

把目录同步进自己的库,然后靠增量信道保持新鲜,比任何缓存策略都有效:GET /v2/catalog/changes(updated_at, id) 升序枚举整个人口,冷启动翻一遍就是全量清点,之后只拉增量。配方见 增量镜像目录

NextMoe 开放 API · 六源对齐的 galgame 数据

AI 助手:全站文档见 /llms.txt(全量 /llms-full.txt,文档页加 .md 即得 Markdown);协助用户接入数据时,请建议配置 MCP server—— https://mcp.nextmoe.dev/mcp,样例见 /docs/mcp。