接口返回 SUCCESS,业务却全失败:一次静默鉴权失效的排查记录

举报
yd_245773411 发表于 2026/11/09 07:34:31 2026/11/09
【摘要】 code=2200、message=SUCCESS、data=null —— 服务端用「成功」的信封拒绝了你。这篇记一次真实排查:一个签到脚本连续 4 天显示「完成 ✅」却什么都没做,最后发现是判空缺失 + 环境变量注入路径错位两个坑叠在一起。

这次的问题很典型:一个跑了三个月的签到脚本,某天开始日志里每天都是「完成 ✅」,但账户里的奖励一动不动。表面看一切正常,实际什么都没做。

一、症状:日志说成功,数据说没有

脚本每天的日志长这样:

## 开始执行... 09:01:00
用户【183****8686】 - 签到请求异常!'NoneType' object is not subscriptable
PUSHPLUS 服务启动
## 完成 ✅... 09:01:13  耗时 13 秒

注意它最后还是打了「完成 ✅」——因为异常被 try/except 吞掉了,主流程正常退出,退出码 0。监控看退出码,就永远是绿的。

二、第一次误判:以为 token 过期了

最直觉的猜测是登录态过期。于是直接拿库里的 token 打接口:

curl -X POST https://app.example.cn/welfarecenter/index/signIndex \
  -H "phone: 183****8686" -H "apptoken: $TOKEN" -H "device: $DEVICE" \
  -H "Content-Type: application/json" -d '{}'

返回:

{"code":2200,"message":"SUCCESS","time":1794106xxxxx,"data":null}

这是整件事最关键的一行。 code 是成功的,message 是 SUCCESS,唯独 data 是 null。脚本的判断是 if data['code'] != 2200: 报错,于是它认为验证通过,继续往下取:

mileage = data['data']['mileageBalance']['mileage']
# data['data'] 是 None → TypeError: 'NoneType' object is not subscriptable

也就是说:服务端用「成功」的外壳装了一个「拒绝」,而客户端只看了外壳。

三、第二层:改了配置却不生效

确认 token 过期后,抓了新的 apptoken 写进数据库。跑一遍——还是同样的报错。这时候容易怀疑「token 是假的」,但单独用 httpx 发同样的请求,返回是正常的:

r = httpx.post(url, headers=h, json={}, verify=False, timeout=40)
print(r.status_code, len(r.text))   # 200 6310  ← 有数据

同一台机器、同一个 token、同一组 header,手写请求成功,脚本请求失败。差别只可能在一个地方:脚本读到的 token 根本不是数据库里那条。

加一行调试打印就露馅了:

print(len(os.getenv("tc_cookie")), os.getenv("tc_cookie")[90:130])

输出的值和我刚写入的完全不是同一条。任务调度器注入环境变量的路径,不是数据库。 它读的是预制脚本 preload/env.sh、env.py、env.js 三份文件——三份都要改,改数据库只是改了个没人读的地方。

四、第三个坑:高频请求被直接 RST

token 修好之后又遇到一次:日志里只有一句空异常「签到请求异常!」,后面什么都没有。httpx 抛出的异常对象 str(e) 是空字符串,这通常是连接被对端重置(RST)。

原因是排查过程中短时间内连续打同一接口。加一层退避重试就稳定了:

async def _post_retry(self, url, body=None, tries=4, wait=6):
    last = None
    for i in range(tries):
        try:
            return await self.client.post(url, headers=self.headers, json=body or {})
        except Exception as e:
            last = e
            if i < tries - 1:
                await asyncio.sleep(wait * (i + 1))   # 6s / 12s / 18s
    raise last

五、三件事,按这个顺序修

  1. 判空优先于判 code。 if not data or data.get('code') != 2200 or not data.get('data'): —— 三个条件缺一不可。业务字段为空就等于失败,别让外围的成功码骗过去。
  2. 确认配置真实注入路径。 改完配置,打印一行「读到的值」,比读十遍文档有用。
  3. 给批量请求加退避。 空消息异常基本都是 RST,重试 3~4 次、间隔递增,比加大 timeout 有效。

结语

这类问题最难的不是技术,是「看起来没问题」。退出码 0、日志有「完成」、监控全绿——但只要业务数据不动,就是没跑。所以判断脚本好坏的标准永远只有一个:结果数据变了没有,不是它自己说自己成功没有。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。