起因:一句「找不到雨云域名」
狸花云的 DDNS 托管支持四家服务商,雨云是最后接入的一家。配置界面上填好 API Key、填好域名,点同步,红字弹回来:
找不到雨云域名:xuanwork.top(请到雨云控制台确认域名已添加,且 API Key 有 domain 权限)
第一反应当然是怀疑权限。去雨云控制台确认:域名在、Key 也在、权限没问题。于是打开 Chrome DevTools,直接手打接口。
三次返工就从这里开始。
坑一:端点路径不是文档里写的那样
早期按网上流传的写法,记录接口是 /dns/domain/。实测这个路径一律返回 404,连鉴权都过不去。
雨云的 API 文档(api.v2.rainyun.com/openapi.json)给出的真实路径是 /product/domain/——域名和记录都挂在产品体系下,不是独立的 DNS 模块。
| 操作 | 错误写法 | 正确写法 |
|---|---|---|
| 域名列表 | /dns/domain/ |
/product/domain/ |
| 记录列表 | /dns/domain/{id}/record/ |
/product/domain/{id}/dns |
| 鉴权头 | Authorization: Bearer |
x-api-key: |
顺带一个细节:域名列表的 options 参数是必填的。不带参数直接 GET,返回的是:
{"code":10002,"message":"输入参数无效"}
带上 options={}(空对象)才正常返回。这种”空参数也是参数”的设计,不试一次根本不会知道。
坑二:返回结构里套了一层
端点改对之后,域名列表能拿到了。但代码里的解析是这么写的:
const list = r.data?.data || r.data?.list || r.data?.rows
按常见约定猜的,数组直接在 data 下。而雨云实际返回的是:
{
"code": 200,
"data": {
"TotalRecords": 1,
"Records": [
{ "id": 1222, "domain": "xuanwork.top", "status": "ok" }
]
}
}
data 是个对象,不是数组。data.find() 直接抛错,被 try 接住,然后一路走到”找不到域名”——错误信息还是我上一版刚改的那句,看起来像权限问题,实际是解析问题。
修正就一行:
const raw = r.data?.data
const list = Array.isArray(raw) ? raw : (raw?.Records || raw?.records || raw?.list || [])
加上 Records 这一路兜底,TotalRecords / Records 这种”总数字段在前、数据在后”的分页风格就跑通了。
坑三:尾斜杠会 301
记录接口带不带尾斜杠,结果完全不同:
# 301 Moved Permanently,body 是一段 HTML
curl -H "x-api-key: $K" \
"https://api.v2.rainyun.com/product/domain/1222/dns/?limit=5&page_no=1"
# 200,正常 JSON
curl -H "x-api-key: $K" \
"https://api.v2.rainyun.com/product/domain/1222/dns?limit=5&page_no=1"
301 的响应体是 Moved Permanently,JSON.parse 一挂,又被 catch 吞掉。这类”解析失败被静默”的写法很危险——建议所有 provider 的 error 都至少 console.warn 一次,不然出问题全靠肉眼猜。
验证:幂等比”成功”更有信息量
三处改完,跑一次真实调用:
zoneId = 1222
upsert = {"ok":true,"unchanged":true,"id":151040519}
unchanged: true 比 ok: true 更让人放心——说明它不只知道”我能写”,还知道”这条记录已经是这个值,不用动”。DDNS 每 2 分钟跑一次,幂等做不对,就是每 2 分钟刷一次接口。
附带:容器信息不再靠手动补报
这版还顺手改了一件运维上的事:容器清单改成后端每 10 分钟自动拉取,启动 3 分钟后先跑一次。以前新开的容器要等用户点「一键补报」,节点上多了几台机器,面板里就是一堆空白。
顺带修掉一个转义 bug:生成上报脚本时,tools 数组被拼成了 [curl,wget](缺引号),服务端按 JSON 解析直接 400。
| 项目 | 改前 | 改后 |
|---|---|---|
| 容器信息来源 | 手动一键补报 | 后端每 10 分钟自动拉 |
| 新容器 | 等用户点一次 | 初始化脚本装 systemd timer / cron |
| 上报 JSON | tools: [curl,wget] 400 |
正确转义 |
小结
这三个坑有个共同点:都不在文档里,而且失败时看起来都像权限问题。
对接第三方 API,能省时间的三件事:直接读 openapi.json 而不是博客教程;先手打一次 curl 看原始响应,再写解析;任何被 catch 吞掉的错误,至少留一行日志。