踩坑记录: FastAPI 拖拽排序接口返回 405 的排查过程

2026年7月17日 blogTech 7 分钟阅读 3 次阅读
📖 文章摘要

后台文章分类拖拽排序功能突然返回405错误,代码明明有路由却报Method Not Allowed,最终发现是Python字节码缓存导致旧代码被加载的坑

前言

我的博客后台用的是 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 源码。

这次的情况是:

  1. 路由代码确实存在(TestClient 直接加载模块能读到)
  2. 但运行中的 uvicorn 进程加载的是旧的缓存
  3. 旧缓存里没有 /sort 路由,只有 /{article_id} 动态路由
  4. 所以 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,确保加载最新代码。

总结

  1. 缓存陷阱:Python 的 .pyc 缓存可能导致代码修改不生效,遇到"代码对但运行不对"的情况,先清缓存
  2. 验证方式:TestClient 通过不代表生产环境正常,一定要用 curl 直接测试运行中的服务
  3. 进程管理:Windows 下重启服务要彻底,关终端窗口比 Ctrl+C 更可靠
  4. 调试思路:看到405先查响应头的 allow 字段,能快速定位是路由问题还是方法问题
文章创建于:2026年7月17日CC BY-NC-SA 4.0
📡

评论

暂无评论,来写第一条吧

© 2026 My Blog. Built with Nuxt.js + FastAPI.