URL路径前缀法是最稳的硬隔离方案,需在urls.py中显式分发v1/v2路由、各版本独立目录与Schema,避免混用视图和共享模块状态。

URL路径前缀法:最稳的硬隔离方案
直接在URL里写/api/v1/和/api/v2/是最不容易出错的做法。Django本身不内置版本路由,靠中间件或装饰器动态解析版本号,容易漏判、误判,尤其在并发或缓存场景下。
实操建议:
- 在主
urls.py中显式分发:path('api/v1/', include('myapp.urls.v1'))和path('api/v2/', include('myapp.urls.v2')) - 每个版本目录(如
myapp/urls/v1.py)只挂载该版本的视图,不复用views.py里的通用函数 - 别把v1和v2的视图混在一个文件里用
if version == 'v2'分支——这会让测试难覆盖、上线易漏改 - 静态资源路径、OpenAPI文档入口也得按前缀隔离,否则Swagger UI可能加载错schema
Django Ninja:多NinjaAPI实例并行管理
NinjaAPI实例天然支持版本字段,但关键不是加version='1.0.0'这个参数,而是让每个实例绑定独立路由、独立Schema、独立认证逻辑。
常见错误现象:
立即学习“Python免费学习笔记(深入)”;
- 两个
NinjaAPI实例共用同一个urls.py入口,结果/api/users/被v2逻辑悄悄接管,v1客户端收到KeyError: 'updated_at' - Schema类(如
UserV1和UserV2)定义在同一个模块,导入时未加版本后缀,导致类型检查失效 - 没重写
get_openapi_schema(),v1文档里混入了v2的字段说明
正确做法:每个版本新建独立app(如api_v1、api_v2),asgi.py里分别挂载,避免共享任何模块级状态。
请求头版本法:Accept头不能当唯一依据
用Accept: application/json; version=2看起来优雅,但实际落地时问题集中于三点:客户端不一致、CDN缓存污染、调试困难。
使用场景有限:
- 内部微服务间调用,且双方协议严格约定头格式
- 前端完全可控(如React App统一封装fetch拦截器)
- 不适用于浏览器直接访问或curl测试——你得手动加
-H "Accept: ...",一漏就走默认逻辑
如果坚持用,务必配合fallback:没带Accept或版本非法时,明确返回406 Not Acceptable,而不是静默降级到v1——后者会让问题延迟暴露。
迁移期必须守住的底线
v2上线后,v1不能只是“暂时保留”。真正麻烦的是那些隐性依赖:数据库迁移脚本是否兼容旧字段?Celery任务里调用的序列化器有没有偷偷升级?日志埋点字段名变了没通知监控系统?
最容易被忽略的地方:
-
settings.py里全局配置(如REST_FRAMEWORK['DEFAULT_VERSIONING_CLASS'])对非路径法版本控制有副作用,哪怕你没用它 - 第三方库(如
djangorestframework-simplejwt)的token payload结构变更,会导致v1客户端刷新token失败 - API网关层做了路径重写,却没同步更新v1/v2路由规则,请求被错误转发
版本切换不是改几行代码的事,是整条调用链路的契约重签。每次发布前,拿真实v1客户端跑一遍核心路径,比看文档更可靠。


















