从零搭建 RESTful API 服务:DRF 开发指南与 JWT 认证实战
2026/8/28 15:51:18 网站建设 项目流程

从零搭建 RESTful API 服务:DRF 开发指南与 JWT 认证实战

【免费下载链接】Python-100-DaysPython - 100天从新手到大师项目地址: https://gitcode.com/GitHub_Trending/py/Python-100-Days

在 Python-100-Days 项目里,DRF 开发指南相关的章节覆盖了 RESTful API 从零搭建的完整路径:资源怎么规划、DRF 序列化器配置怎么写、JWT 认证流程如何跑通,直到接口文档的规范交付。你跟着本文走一遍,就能亲手做出一个带认证、分页、过滤和文档的博客 API,并且知道每一步"为什么这么做"。

一条博客 API 的完整旅程

假设你要给前端提供"查看某篇文章及其评论"的能力。用户点击页面的瞬间,发生的事是这样的:

  • 浏览器发出GET /api/blogs/1/请求;
  • 请求穿过 Django 的中间件,被 URL 分发器转给对应的视图;
  • DRF 在这里接手:先做认证(你是谁)、再做权限判断(你能不能干这事);
  • 视图取出模型数据,交给序列化器转成 JSON;
  • 响应原路返回,前端拿到结果渲染页面。

理解这条链路非常重要:以后接口出任何问题——401、403、404、字段缺失——你都能快速定位到是"认证、权限、路由、视图还是序列化"哪一环出的事,而不是两眼一抹黑地猜。

安装 DRF 并配置全局默认值

在终端执行安装命令,把rest_framework注册进INSTALLED_APPS

pip install djangorestframework
INSTALLED_APPS = [ # ...其他应用 'rest_framework', ]

然后在配置文件的REST_FRAMEWORK字典里设默认值。💡 这些默认值是全项目的"底线":单个视图随时可以用permission_classespagination_class等属性覆盖,但覆盖是少数情况,绝大多数接口应该沿用统一策略。

配置项作用
DEFAULT_AUTHENTICATION_CLASSES默认认证方式,含SessionAuthentication(基于 session)、TokenAuthenticationBasicAuthentication
DEFAULT_PERMISSION_CLASSES默认权限,如AllowAnyIsAuthenticatedIsAdminUser
DEFAULT_PAGINATION_CLASSPAGE_SIZE列表接口的默认分页器与每页条数
EXCEPTION_HANDLER统一异常出口,定制错误响应格式在这里改

一个适合博客场景的起步配置:

REST_FRAMEWORK = { 'DEFAULT_AUTHENTICATION_CLASSES': [ 'rest_framework.authentication.SessionAuthentication', ], 'DEFAULT_PERMISSION_CLASSES': [ 'rest_framework.permissions.AllowAny', ], 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination', 'PAGE_SIZE': 10, }

序列化器:给前端"翻译"模型字段

前端、App、其他服务消费的都是 JSON,而你的数据躺在 Django 模型里。两者之间需要一个"翻译官"——这就是 DRF 序列化器(serializer):它负责把模型实例转成可 JSON 化的字典,同时反过来做输入校验。

绝大多数情况下,继承ModelSerializer并声明Meta就够了。注意fields显式列出'__all__'会把模型里每个字段都暴露出去,密码、内部计数这类字段一旦漏出去就是事故。

class BlogSerializer(serializers.ModelSerializer): class Meta: model = Blog fields = ('id', 'title', 'summary', 'content', 'pub_date')

序列化时机是在视图里:单个对象直接传,列表则加many=True。返回结构也固定:.data拿到字典,交给Response就变成 JSON 响应。

函数视图:博客详情接口

DRF 支持两种写法:函数视图(FBV)和类视图(CBV)。函数视图最直观,@api_view装饰器声明允许的 HTTP 方法,函数体想怎么写就怎么写,适合逻辑个性化的接口。

下面是一个博客详情接口,同时演示了 404 的正确处理方式:

@api_view(['GET']) def blog_detail(request, blog_id): try: blog = Blog.objects.get(pk=blog_id) except Blog.DoesNotExist: return Response(status=status.HTTP_404_NOT_FOUND) return Response(BlogSerializer(blog).data)

⚠️ 注意一个常见误区:查不到对象就继续往下走、返回一串None,前端会很难受。资源不存在就该返回 404,这是 HTTP 语义的基本约定。

类视图与嵌套路由:评论接口一次配齐

当接口是标准的"列表 / 详情 / 创建"套路时,用类视图更省。ModelViewSet一次性给你listretrievecreateupdatedestroy全套动作,你只需声明querysetserializer_class;如果只要读接口,用ReadOnlyModelViewSet更克制。

评论是博客的子资源,放在blog/{blog_pk}/comments/下面。用DefaultRouter注册嵌套路由,评论的"挂在哪个博客下"由路径参数直接给出:

class BlogViewSet(ReadOnlyModelViewSet): queryset = Blog.objects.all() serializer_class = BlogSerializer permission_classes = [permissions.IsAuthenticatedOrReadOnly] class CommentSerializer(serializers.ModelSerializer): class Meta: model = Comment fields = ('id', 'content', 'pub_date') def get_queryset(self): return Comment.objects.filter(blog_id=self.kwargs['blog_pk']) @action(detail=False, methods=['get', 'post']) def comments(self, request, blog_pk=None): # GET 返回该博客的评论,POST 创建新评论(此处省略分支细节) ... class CommentViewSet(ModelViewSet): serializer_class = BlogViewSet.CommentSerializer queryset = Comment.objects.none() def get_queryset(self): return Comment.objects.filter(blog_id=self.kwargs['blog_pk']) def perform_create(self, serializer): serializer.save(blog_id=self.kwargs['blog_pk'])
router = DefaultRouter() router.register('blogs', BlogViewSet) router.register(r'blogs/(?P<blog_pk>\d+)/comments', CommentViewSet, basename='blog-comment') urlpatterns += router.urls

几个要点值得记住:

  • 权限写在视集上:IsAuthenticatedOrReadOnly的语义是——GET等安全方法人人可读,POST/PUT/DELETE必须登录。这正符合"内容公开、评论需登录"的业务诉求。
  • 评论挂在博客路径下,创建时无需用户再传一次blog_id,从路径取,杜绝了"评论写错博客"这种 bug。
  • ModelViewSet必须配合路由器注册,basename在存在歧义时指定一下更稳。

注册完成后,用浏览器访问http://127.0.0.1:8000/api/,DRF 自带一套可交互的接口页面,能直接看到每个端点并手动发请求。📌 开发早期用它联调非常方便,前端同学也能在上面试数据:

JWT 令牌怎么签发和校验

session 认证有个扩展难题:服务端必须保存会话对象,多机部署时还要靠 Redis 之类共享存储。而 token 方案把状态挪到了客户端——用户登录后拿到令牌,之后每次请求都带着它,服务端只验签、不存状态,加机器就能扩容。

JWT(JSON Web Token,开放标准 RFC 7519)由三段组成,段与段之间用.连接:

部分内容用途
头部{"alg": "HS256", "typ": "JWT"}声明签名算法与类型
载荷用户标识、exp过期时间,可加自定义字段承载实际数据
签名用服务器端密钥对前两段做 HMAC 摘要防伪造、防篡改

完整 JWT 认证流程在 Python-100-Days 项目的 54.RESTful架构和DRF入门 中有系统讲解,落地时用pip install pyjwt装上 PyJWT 即可:

  • 签发:登录成功后把用户 ID、过期时间等放进载荷,用settings.SECRET_KEY编码成令牌返回给前端;
  • 存储:前端存入localStoragesessionStorage,之后每个请求都通过自定义请求头(约定为Authorization)携带;
  • 校验:服务端解码验签,令牌过期或无效统一返回 401,前端收到 401 就跳回登录页。

两个提醒:密钥只能留在服务端,绝不能下发给客户端;令牌一旦签发、在过期前无法作废,所以有效期要设得短一些,敏感操作另加二次验证。

接口文档规范:联调之前先把契约写清楚

接口写好了,前端怎么知道怎么调?靠一份清晰的接口文档。项目里 94.网络API接口设计 推荐用 RAP2、YAPI 这类工具管理文档,核心是一份"契约"。以博客详情接口为例,文档应包含:

项目约定
URL 与方法GET /api/blogs/{id}/
路径参数id:整数,必填
权限无需登录
成功响应200,正文为博客对象(字段与序列化器一致)
失败响应404:博客不存在;401:token 过期(仅需认证接口)

分页接口的响应结构也值得写进文档,DRF 的PageNumberPagination默认返回count(总数)、next/previous(翻页地址)和results(当前页数据)四个键。💡 实际项目里建议再约定一个统一信封:成功时业务字段外附code: 0,失败时code非零并配message,让前端用一套逻辑处理所有异常。契约一旦双方确认,后端改字段就必须同步改文档——这是避免"联调扯皮"最便宜的办法。

新手最容易踩的 5 个坑

  1. fields = '__all__'顺手一写:敏感字段直接裸露。永远显式列字段。
  2. 查不到就返回 None:应该返回 404,让客户端明确知道资源不存在。
  3. 部分更新用了 PUT:PUT 语义是全量替换,只改几个字段请用 PATCH。
  4. 默认权限没想清楚:全局AllowAny意味着所有写接口也是公开的,创建类接口记得收紧。
  5. 跨域忘了配:前后端分离必然跨域,接入django-cors-headers并按来源白名单配置,别图省事写*

下一步往哪走

  • API 版本控制:用 URL 前缀或请求头版本策略,让老客户端不被破坏
  • 限流与节流:给匿名/认证用户配置不同的频率上限,防刷接口
  • 异步任务:导出、通知这类慢操作交给 Celery 处理
  • 性能监控与日志:记录慢查询,给热点接口加缓存并盯紧命中率

RESTful 架构与 DRF 的更多细节,可以回看项目中的 55.RESTful架构和DRF进阶,里面还有数据筛选(django-filter)和游标分页的完整写法。

【免费下载链接】Python-100-DaysPython - 100天从新手到大师项目地址: https://gitcode.com/GitHub_Trending/py/Python-100-Days

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询