排查了半天才发现,问题根本不在代码
昨天搞了一个下午加晚上,差点没把我心态搞崩
事情是这样的,我们组有个内部的用户管理系统,是用FastAPI写的,之前做的时候比较赶,文档就随便写了写最近产品那边说要做二期功能,需要把现有的接口整理一下文档我一想,这不正好可以让AI帮忙吗,现在Claude这么火,让他帮我把接口代码转换成API文档,岂不是美滋滋
然后我就把整个项目丢给Claude,让它给我生成一份完整的API文档
结果文档是生成了,看起来像模像样的,我拿给产品经理看产品经理看了之后問了我一个问题:“这个登录接口,为什么返回的expires_in是负数token过期时间还能是负的吗”
我当时就愣住了打开文档一看,好家伙,Claude在接口描述里写着:
expires_in: token的剩余有效时间,单位秒为正数时表示未过期,为负数时表示已过期
这他娘的什么鬼token过期时间还能为负数我从业这么多年就没见过负数的过期时间
我第一反应是:完了,代码写错了
开始排查:我以为是我的代码有问题
我赶快回去看代码我们登录接口是这样写的:
@router.post("/login")asyncdeflogin(request: LoginRequest):# 生成token和过期时间 token = generate_jwt_token(user_id=request.username) expire_seconds = 7200# 2小时# 计算过期时间戳 expires_at = time.time() + expire_secondsreturn {"access_token": token,"token_type": "Bearer","expires_in": expire_seconds,"expires_at": int(expires_at), }这代码没问题啊expires_in返回的是7200,也就是2小时,表示token还有多久过期这是标准的OAuth2设计啊
但是文档说是“为负数时表示已过期”,这是什么鬼我完全理解不了
然后我就开始怀疑人生了难道是我代码哪里有bug?expire_seconds算错了?generate_jwt_token里面有什么我没注意到的逻辑?
我拿着这个文档和代码对比了半天,越看越觉得不对劲但又说不上来哪里有问题
产品经理还等着我回复,我压力很大
中间踩的坑:排查过程及其离谱
为了搞清楚到底哪里的问题,我先入为主地认为是代码问题,开始了漫长的排查
第一步:检查JWT生成的代码
defgenerate_jwt_token(user_id: str) -> str: payload = {"sub": user_id,"exp": time.time() + 7200, # 过期时间设置为2小时后"iat": time.time(), } token = jwt.encode(payload, SECRET_KEY, algorithm="HS256")return token也没问题啊,过期时间是2小时后,很正常
第二步:用Python实际调用一下接口,看看返回到底是什么
$ curl -X POST http://localhost:8000/api/v1/login \ -H "Content-Type: application/json" \ -d '{"username": "testuser", "password": "testpass"}'返回结果:
{"access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...","token_type":"Bearer","expires_in":7200,"expires_at":1747824567}返回值完全正确expires_in是7200,正数不是负数
这时候我更懵了代码是对的,返回也是对的,但文档为什么说“为负数时表示已过期”
第三步:我开始怀疑是Claude生成的时候哪里弄错了
我还特意跑回去问Claude:“你为什么说expires_in可以是负数”
Claude给我的回复是这样的:
根据常见的API设计规范,有一些系统会使用负数来表示token已经过期/无效的状态,这是一种常见的约定...
我看到这条回复的时候人都麻了这 ai 在说什么这是它自己编的规范吧
我马上去查了我们系统的其他接口,还问了问组里有经验的同事大家用的都是正数表示剩余有效时间,从来没人用过负数
到这一步我才终于意识到:**这不是代码的问题,这是Claude生成的文档有幻觉**
最后怎么解决的:重新生成一遍,这次加了提示词约束
明确了问题所在之后,我就让Claude重新生成了一遍这次我在prompt 里加了一些约束:
请根据以下FastAPI代码生成OpenAPI格式的JSON文档。注意事项:1. 请严格按照代码中实际的返回值进行描述,不要添加你的推测2. expires_in 表示token的剩余有效时间,单位为秒,始终为正整数3. 只描述代码中实际存在的字段,不要自行添加字段4. 如果某个字段的含义你不确定,请标记为"待确认",不要猜测这次生成的文档就没问题了:
{"/api/v1/login":{"post":{"summary":"用户登录","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"username":{"type":"string"},"password":{"type":"string"}}}}}},"responses":{"200":{"description":"登录成功","content":{"application/json":{"schema":{"type":"object","properties":{"access_token":{"type":"string","description":"访问令牌,用于后续接口认证"},"token_type":{"type":"string","description":"令牌类型,固定为'Bearer'"},"expires_in":{"type":"integer","description":"访问令牌的剩余有效时间,单位为秒"},"expires_at":{"type":"integer","description":"访问令牌的过期时间戳"}}}}}}}}}}这次好多了,至少expires_in的描述是正确的——“访问令牌的剩余有效时间,单位为秒”
不过我还是不太放心,专门做了一个脚本来验证接口返回和文档是否一致:
#!/usr/bin/env python3"""验证API文档与实际接口返回值的一致性"""import requestsimport jsonBASE_URL = "http://localhost:8000"defverify_login_endpoint():"""测试登录接口的返回与文档描述是否一致""" response = requests.post(f"{BASE_URL}/api/v1/login", json={"username": "test_verify", "password": "test123"} ) data = response.json()# 验证必要字段存在 required_fields = ["access_token", "token_type", "expires_in", "expires_at"]for field in required_fields:assert field in data, f"缺少字段: {field}"# 验证expires_in是正数if data["expires_in"] <= 0:print(f"[错误] expires_in应为正数,实际值: {data['expires_in']}")returnFalseprint(f"[验证通过] expires_in={data['expires_in']} (有效剩余时间)")print(f"[验证通过] expires_at={data['expires_at']} (过期时间戳)")returnTrueif __name__ == "__main__": result = verify_login_endpoint()if result:print("\n✓ API文档与实际接口返回一致")else:print("\n✗ 验证失败,请检查API实现") exit(1)运行结果:
$ python verify_api.py[验证通过] expires_in=7200 (有效剩余时间)[验证通过] expires_at=1747827891 (过期时间戳)✓ API文档与实际接口返回一致好了,总算是把这个问题解决了
这件事带给我的思考
这次排查虽然走了弯路,但如果重来一次,我会这样做:
1. 先不管文档,自己跑一遍接口,看实际的返回是什么 2. 用返回的值和文档对比 3. 发现不一致就直接问AI:“为什么你写的描述和实际返回不一样?” 4. 让AI解释它的理由,如果它的理由涉及到它“推测”的规范,就要格外警惕
而不是像我之前那样,先入为主地怀疑代码有问题
现在文档已经修正好了交给了产品经理,虽然耽误了一下午的时间,但至少没有造成更大的问题
回头想想还是很庆幸产品经理多问了那么一句,不然这个错误的文档流出去,后续还不知道会出什么乱子
这件事也算给我提了个醒:AI是个很好的工具,但你得会用用对了效率确实高,用错了就是一个坑
下次再让AI生成重要文档,我一定会先自己验证一遍
类似的问题,欢迎在评论区留言分享,大家一起交流避坑啊!
夜雨聆风