前言
我的博客后台用的是 FastAPI + Nuxt 的技术栈,前后端分离运行在不同端口(后端8001,前端4000)。
前几天在分类管理页面测试拖拽排序功能时,突然报了个405错误:Method Not Allowed。但路由代码明明写的是 @router.patch("/sort"),前端调用的也是 PATCH 方法,怎么就405了?
这次排查花了我不少时间,最后发现原因挺离谱的。
一、问题现象
在后台分类管理页面,拖拽文章排序后,浏览器控制台报错:
PATCH http://localhost:4000/api/admin/articles/sort 405 (Method Not Allowed)
{"detail":"Method Not Allowed"}
响应头里还有个关键信息:allow: GET。这说明服务器认为这个路径只支持 GET 方法。
二、排查过程
2.1 确认代码没问题
先检查后端路由代码,backend/routers/admin/articles.py 第22行:
@router.patch("/sort", dependencies=[Depends(get_current_admin)])
def update_article_sort(
order: list[int] = Body(..., embed=True),
db: Session = Depends(get_db),
):
"""批量更新文章排序(拖拽排序)"""
...
路由定义没问题,用的确实是 @router.patch。
2.2 用 TestClient 测试
写了个简单的测试脚本:
from main import app
from starlette.testclient import TestClient
client = TestClient(app)
resp = client.patch('/api/admin/articles/sort', json={'order': [1,2]})
print(f'Status: {resp.status_code}') # 输出: 401 (认证错误)
TestClient 返回401(认证错误),说明路由存在且能正常响应。问题不在代码。
2.3 直接 curl 请求
curl -X PATCH http://localhost:8001/api/admin/articles/sort \
-H "Content-Type: application/json" \
-d '{"order":[1,2]}'
返回 405 Method Not Allowed,响应头 allow: GET。
这就奇怪了——TestClient 能用,curl 不行?两个测试访问的是同一份代码啊。
2.4 检查运行中的进程
查看端口占用:
netstat -ano | grep 8001
发现端口上有3个进程在监听!PID分别是 5180、15052、5692。这是之前多次重启留下的残留进程。
尝试杀掉这些进程:
taskkill /F /PID 5180
# 提示:进程不存在
PID 不存在但端口仍被占用,典型的 Windows 进程残留问题。
三、根本原因
折腾了一圈,最后发现是 Python 字节码缓存的问题。
Python 运行 .py 文件时会自动生成 .pyc 缓存文件,存放在 __pycache__ 目录。当代码修改后,如果缓存没有正确更新,Python 可能会加载旧的 .pyc 文件而不是最新的 .py 源码。
这次的情况是:
- 路由代码确实存在(TestClient 直接加载模块能读到)
- 但运行中的 uvicorn 进程加载的是旧的缓存
- 旧缓存里没有
/sort路由,只有/{article_id}动态路由 - 所以 PATCH 请求被匹配到了 GET 的
/{article_id}路由,返回405
四、解决方案
# 1. 删除 Python 缓存
rm -rf backend/__pycache__
rm -rf backend/routers/__pycache__
rm -rf backend/routers/admin/__pycache__
# 2. 完全关闭后端进程(不是 Ctrl+C,是关掉终端窗口)
# 3. 重新启动后端
cd backend && venv\Scripts\activate && uvicorn main:app --reload --host 0.0.0.0 --port 8001
重启后验证:
curl -X PATCH http://localhost:8001/api/admin/articles/sort \
-H "Content-Type: application/json" \
-d '{"order":[1,2]}'
# 返回: {"detail":"Not authenticated"} ← 401,路由正常了
五、踩坑记录
1. TestClient 和实际运行环境不一致
问题:TestClient 能正常工作,但实际运行的服务器返回405。
原因:TestClient 直接导入模块测试,而 uvicorn 进程可能加载了旧的缓存文件。
经验:排查路由问题时,不能只依赖 TestClient,一定要用 curl 直接请求运行中的服务验证。
2. Windows 进程残留
问题:taskkill 提示进程不存在,但 netstat 显示端口仍被占用。
原因:Windows 下的进程残留问题,可能是因为终端窗口没有正确关闭。
经验:重启服务时,最好完全关闭终端窗口,而不是只按 Ctrl+C。如果还有问题,用任务管理器强制结束所有 python.exe 进程。
3. uvicorn --reload 不总是生效
问题:用了 --reload 参数,但代码修改后没有自动生效。
原因:reload 依赖文件系统监听,有时监听会失效,特别是 Windows 环境下。
经验:修改路由代码后,最好手动重启一次 uvicorn,确保加载最新代码。
总结
- 缓存陷阱:Python 的
.pyc缓存可能导致代码修改不生效,遇到"代码对但运行不对"的情况,先清缓存 - 验证方式:TestClient 通过不代表生产环境正常,一定要用 curl 直接测试运行中的服务
- 进程管理:Windows 下重启服务要彻底,关终端窗口比 Ctrl+C 更可靠
- 调试思路:看到405先查响应头的
allow字段,能快速定位是路由问题还是方法问题
