业务场景:猜谜答题模块需要什么数据
在内容型应用里,谜语通常不是独立功能,而是附着在某个互动场景中。常见的两种形态:
- 首页信息流中随机展示一条谜语,用户点击“换一个”刷新谜面;
- “猜谜答题”玩法,服务端每次给出一条谜面,用户提交答案后判定对错。
无论哪种形态,后端都需要三个基础数据能力:
- 随机取一条谜语,支撑首页展示和换一换;
- 按类型分页拉谜语列表,支持分类浏览;
- 获取类型集合,用于前端筛选器或兴趣标签。
本文记录的案例是一个社区 App 的“每日猜谜”签到页。后端在首次进入页面时,通过 list 模式拉取当前类型的整页谜语,写入 Redis 缓存;后续用户每次点击“下一题”,都从缓存中随机挑一条未回答过的谜语。这样上游接口只需在缓存过期时被调用一次,可以大幅降低 QPS 压力。
接口能力边界
谜语大全接口的基本信息如下:
- 请求方式:POST
- 请求地址:
https://v1.apizero.cn/api/riddle - URL 参数:无,所有参数均在请求体中
- 请求头:
X-API-Key与Content-Type: application/json - 接口 QPS:5 次/秒
接口支持三种动作,由请求体中的action字段控制:
| action 值 | 行为 | 可选参数 |
|---|---|---|
| random | 随机返回一条谜语(默认值) | 无 |
| list | 分页返回谜语列表 | type、page |
| types | 返回谜语类型列表 | 无 |
从工程角度看,5 QPS 是一个需要认真对待的约束。如果每次用户点击都直接透传到上游,一个几十人的在线活动就可能触发限流。所以接入方需要建立“上游只拿数据、业务自己做分发”的思路。
请求体参数与鉴权
请求体是 JSON 对象,支持以下字段:
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| action | string | 否 | random / list / types,默认 random |
| type | string | 否 | 仅 list 模式有效;谜语类型,使用小写字母 |
| page | string | 否 | 仅 list 模式有效;页码,正整数,默认 1 |
需要特别留意:page的类型定义是 string,不是 number。在 Node.js 或 Python 中传参时,如果不加转换,JSON 序列化后会出现"page":1而不是"page":"1",某些网关可能因此拒绝请求。
鉴权方式:在每个 POST 请求头中携带X-API-Key。密钥应当存放在服务端的环境变量或配置中心,不能写死在客户端代码里,否则一旦打包发布,密钥就会泄露。
curl 接入示例
先设置密钥环境变量:
export APIZERO_API_KEY=your_key_here请求随机一条谜语:
curl -sS \ -X POST \ -H "X-API-Key: $APIZERO_API_KEY" \ -H "Content-Type: application/json" \ -d '{"action": "random"}' \ "https://v1.apizero.cn/api/riddle"请求类型列表:
curl -sS \ -X POST \ -H "X-API-Key: $APIZERO_API_KEY" \ -H "Content-Type: application/json" \ -d '{"action": "types"}' \ "https://v1.apizero.cn/api/riddle"请求分页列表:
curl -sS \ -X POST \ -H "X-API-Key: $APIZERO_API_KEY" \ -H "Content-Type: application/json" \ -d '{"action": "list", "type": "<type_from_types_api>", "page": "1"}' \ "https://v1.apizero.cn/api/riddle"type来源不应当硬编码。建议先请求types拿到合法值,再拼接到 list 请求中,避免因类型名写错而返回空列表。
返回值解读
接口成功时的响应结构如下:
{ "code": 200, "data": {}, "message": "success" }调用方的解析逻辑应该围绕三层展开:
- 校验 code:只有
code为 200 时才继续处理业务。不能只看 HTTP 状态码,因为某些代理或网关在业务异常时仍会返回 HTTP 200。 - 校验 data:
data是核心载荷。在 random 模式下通常包含谜面、谜底、类型等字段;在 list 模式下通常包含谜语数组或分页信息。具体字段名和结构以原始文档为准,这里不展开。 - 空值降级:如果
data为null、空对象或空数组,业务侧应返回“暂无谜语”或读取本地缓存,而不是直接抛异常。
下面是一个 Node.js 的解析示例,使用 Node 18+ 全局 fetch,不依赖第三方库:
const API_URL = 'https://v1.apizero.cn/api/riddle'; async function fetchRandomRiddle() { const response = await fetch(API_URL, { method: 'POST', headers: { 'X-API-Key': process.env.APIZERO_API_KEY, 'Content-Type': 'application/json' }, body: JSON.stringify({ action: 'random' }) }); const body = await response.json(); if (body.code !== 200) { throw new Error(`riddle api error: ${body.message}`); } if (!body.data || Object.keys(body.data).length === 0) { // 降级逻辑:返回缓存中的备选谜语 return getCachedRiddle(); } return body.data; }这段代码展示了两个关键点:失败时抛出业务错误,空数据时走降级分支。实际项目中,可以把降级逻辑替换成读取 Redis 或返回固定文案。
常见错误与排查思路
401 Unauthorized
X-API-Key缺失或填写错误。检查密钥是否从服务端配置读取,环境变量是否注入到当前 shell 或进程。常见问题是在本地调试时把密钥写进了 curl,然后误提交到代码仓库。
400 Bad Request
请求体格式不正确。按以下顺序排查:
- JSON 是否合法,有没有尾逗号或单引号未闭合;
action是否误写成大写;page是否传成了数字而非字符串;type是否包含大写字母或空格。
429 Too Many Requests
触发 QPS 限制。接口限制 5 次/秒,如果业务侧有突发流量,就需要把请求收口到一层带缓存的 service,而不是让客户端直接调用。
200 但 data 为空
可能是 list 模式下page超出总页数,或者type不合法。建议先用types动作拉取合法类型集合,再观察空数据场景是否集中在某个类型上。
工程化注意事项
- 缓存策略:随机模式一次只返回一条,高频场景下应当改用 list 拉取一页数据,缓存到 Redis 或本地内存。设置合理的 TTL(如 6 到 12 小时),过期后再回源。
- 密钥隔离:
X-API-Key只能存在于服务端。如果有客户端直连需求,必须通过后端代理转发,避免密钥暴露。 - 超时控制:外部接口会有慢响应风险。HTTP 客户端应设置 3 秒左右的超时,超时后返回降级内容,而不是让用户长时间等待。
- 优雅降级:当接口不可用或数据为空时,业务页面应展示静态谜语列表或友好提示,避免整个模块白屏。
- 重试机制:POST 请求不保证幂等,重试前要确认请求只是只读操作。拉取谜语属于只读场景,可以在超时后最多重试一次,但需要加抖动退避。
- 日志监控:记录每次请求的耗时、code、data 是否为空。当失败率超过阈值时触发告警,便于快速定位是网络问题、密钥问题还是参数问题。
参考文档
- 谜语大全接口文档:https://apizero.cn/aidocs/riddle
- 原始文档:https://apizero.cn/aidocs/riddle/raw.md