DRF实战指南:从序列化器到认证限流的Django API开发全链路解析

发布时间:2026/10/7 10:41:50
DRF实战指南:从序列化器到认证限流的Django API开发全链路解析 上个月被朋友拉去救火帮一个快上线的项目补接口。前端同事把页面都写好了只差后端联调可我一打开代码就有点上头接口全是JsonResponse手拼 JSON每个函数里都在重复做参数解析、权限判断、错误码封装列表接口的分页逻辑自己写了一遍又一遍最离谱的是两个接口的返回格式还不一样前端拿到几千行代码光适配就炸了。那时候我就跟朋友说如果一开始用 Django Rest Framework简称 DRF来构建 API这些事情根本不用花这么大代价去补。DRF 不是 Django 生态里唯一的 API 方案但它是在“用 Django 写业务”这条路上投入产出比最稳的选择。本文不讲广告直接从实战出发从环境搭建到序列化器、查询过滤、删除对象、认证限流、测试部署完整过一遍用 DRF 搭接口的链路。里面写的那些坑基本都是我真实踩过的。1. 为什么我坚持用DRF写API而不是手写JSONResponse1.1 从一次加班说起手写API的痛回到那次救火经历。老项目里的接口其实也就十来个但每个视图函数平均都有几十行用来处理请求参数。比如 GET 接口要解析request.GET里的page、page_size、keyword得先写一大段try...except去转 int默认值、空值、异常值全都要考虑。这还不算完查询完数据库之后要让 ORM 对象变成字典如果嵌套了外键还得手动循环遍历把关联对象的字段一层层拼进去。更头疼的是写接口的人多了每个人的风格都不一样。A 写的错误返回是{code: 1, msg: xxx}B 写的是{status: failed, message: xxx}。前端同事联调的时候每接一个接口就要重新看一遍文档还经常遇到某个字段时有时无的情况。这种“手写 API”的方式在小项目里确实能跑但只要接口一多、参与的人一多规范就会失控。DRF 的出现本质上不是给你省几行代码而是把“写 API”这件事的通用环节抽象成了标准组件序列化、校验、认证、权限、限流、分页、路由、文档。你不需要再在视图里重复发明轮子只需要把业务相关的部分写出来剩下的交给框架。1.2 DRF到底帮你解决了哪几件事我带团队从手写切到 DRF 之后最大的感受是以下四件事变化最明显序列化与反序列化ORM 对象、QuerySet 变成 JSON 只是其中一个方向反过来前端传进来的 JSON 会自动变成经过校验的 Python 数据再写入数据库。模型、接口文档、前端字段能保持同一套定义。视图与路由的标准化一个ModelViewSet加一个DefaultRouter几分钟就能生成一整套符合 REST 风格的增删改查接口URL 规则统一方法语义也统一。认证和权限的组件化内置了 SessionAuthentication、TokenAuthentication、BasicAuthentication配合自带的IsAuthenticated、IsAdminUser、AllowAny这些权限类可以直接声明在视图或全局配置里。可浏览的 API 页面开发模式下浏览器直接打开接口 URL能看到一个类似表单页的调试界面不用额外装 Postman自己就能 POST 数据试接口非常方便。我经常跟别人说DRF 不是魔法它只是把你原本就该做好的事用一套成熟方案帮你做了。关键是你得理解这套方案背后的规则才不会在配置时一头雾水。1.3 什么时候别用DRF任何工具都有边界DRF 也不是所有场景都合适。我个人的判断是项目本身就只是几个简单页面用 Django 模板渲染就够了没必要额外引入 API 层。接口的消费方只有自己的内部脚本且数据结构高度定制用 Django 原生的HttpResponse直接返回序列化后的 JSON 反而更轻量。项目核心是高并发低延迟的异步场景比如大量长连接或流式响应Django 同步 WSGI 的模型并不合适这时候 FastAPI 或 Starlette 会更好。如果团队对 REST 本身不熟悉又需要极强灵活性的动态查询整个 GraphQL 方案可能比强行套 DRF 更合适。DRF 适合的是“业务以 Django ORM 为核心需要对外提供 REST 风格 API”的典型场景。下面就从项目初始化开始把链路完整走一遍。2. 从零到一个能跑的接口项目骨架与第一个序列化器2.1 环境准备与项目初始化踩坑假设你已经装好了 Python 3.10 以上版本先建虚拟环境python -m venv venv source venv/bin/activate pip install django djangorestframework我用的是 Django 4.2 和 DRF 3.14 这套组合在 2025 年看依然很稳。安装完成后创建项目和应用django-admin startproject myapi python manage.py startapp blog这里有个经常被忽略的点记得去myapi/settings.py的INSTALLED_APPS里加上rest_framework。如果你后面要用 DRF 自带的 Token 认证还要加rest_framework.authtoken否则执行迁移的时候会少一张表。我习惯先定义一个简单的文章模型来做演示# blog/models.py from django.db import models from django.conf import settings class Article(models.Model): title models.CharField(max_length100) content models.TextField() author models.ForeignKey( settings.AUTH_USER_MODEL, on_deletemodels.CASCADE, related_namearticles, ) created_at models.DateTimeField(auto_now_addTrue) updated_at models.DateTimeField(auto_nowTrue)然后执行迁移python manage.py makemigrations blog python manage.py migrate这里要提醒一下auto_now_add和auto_now这俩字段在序列化时默认是只读的前端不需要传框架会自动生成。新手容易犯的错误是把它们写进前端表单导致新增接口报“字段不能为空”或者“非法提交”。2.2 用ModelSerializer把模型变成JSON模型写好之后最核心的一步就是写序列化器。新建blog/serializers.py# blog/serializers.py from rest_framework import serializers from .models import Article class ArticleSerializer(serializers.ModelSerializer): class Meta: model Article fields [id, title, content, author, created_at, updated_at]ModelSerializer会自动根据模型字段生成对应的序列化字段包含字段类型、必填性、只读性这些信息。你不需要手动去声明title serializers.CharField(max_length100)除非你有额外定制。我建议在Meta里尽量显式写fields而不是用exclude。显式字段的好处是以后给模型加了个敏感字段比如用户的手机号它不会因为没写进fields就悄悄暴露到接口里。使用exclude会埋下安全隐患。id字段要不要暴露很多人纠结。我个人的观点是只要前端确实需要这个 ID 来操作资源比如“编辑文章”“删除文章”那就应该返回。不要为了所谓“隐藏实现细节”去删掉它结果反而让前端多一次多余查询。2.3 视图与路由的三种写法DRF 里写视图大概有三种层级第一种是函数视图from rest_framework.decorators import api_view from rest_framework.response import Response from rest_framework import status api_view([GET]) def article_list(request): articles Article.objects.all() serializer ArticleSerializer(articles, manyTrue) return Response(serializer.data)第二种是APIView类视图适合每个接口逻辑差异较大的场景。第三种是我最常用的ModelViewSet适合标准的增删改查# blog/views.py from rest_framework.viewsets import ModelViewSet from .models import Article from .serializers import ArticleSerializer class ArticleViewSet(ModelViewSet): queryset Article.objects.all() serializer_class ArticleSerializer路由的写法也有讲究用DefaultRouter能省掉一堆手动path# myapi/urls.py from django.contrib import admin from django.urls import path, include from rest_framework.routers import DefaultRouter from blog.views import ArticleViewSet router DefaultRouter() router.register(articles, ArticleViewSet, basenamearticle) urlpatterns [ path(admin/, admin.site.urls), path(api/, include(router.urls)), ]这时候跑起来python manage.py runserver你就能访问到GET /api/articles/列表支持 POST 新增GET /api/articles/{id}/详情同时支持 PUT/PATCH/DELETE浏览器直接打开/api/articles/会看到 DRF 的可浏览 API 页面这比手写接口时对着命令行调方便太多了。3. 序列化器的进阶玩法嵌套、校验和自定义逻辑3.1 嵌套序列化与字段展平接口联调中最常见的需求是返回文章的同时把作者的名字也带出来。最简单的方式是定义嵌套序列化器# blog/serializers.py from django.contrib.auth import get_user_model from rest_framework import serializers from .models import Article User get_user_model() class AuthorSerializer(serializers.ModelSerializer): class Meta: model User fields [id, username] class ArticleSerializer(serializers.ModelSerializer): author AuthorSerializer(read_onlyTrue) class Meta: model Article fields [id, title, content, author, created_at, updated_at]这样前端拿到的author就是一个对象{id: 1, username: alice}。但要注意这里如果直接使用这个序列化器做 POST 新增前端传author对象默认会报错因为外键字段期望的是主键值而不是嵌套对象。所以在生产环境我一般会拆成两个字段class ArticleSerializer(serializers.ModelSerializer): author AuthorSerializer(read_onlyTrue) author_id serializers.PrimaryKeyRelatedField( sourceauthor, querysetUser.objects.all(), write_onlyTrue ) class Meta: model Article fields [id, title, content, author, author_id, created_at, updated_at]这里用author_id作为写字段前端只管传作者主键 ID返回结果里的author是完整的作者信息。这种做法既安全又直观是我在项目里很常用的套路。如果你的接口消费方只想要一个字段值不想要嵌套对象还可以用source做字段展平author_name serializers.CharField(sourceauthor.username, read_onlyTrue)然后用fields [id, title, author_name, ...]返回的就是扁平 JSON。3.2 自定义字段和validate方法序列化的最核心价值是“校验”。比如文章标题不能重复直接加一个字段级校验方法class ArticleSerializer(serializers.ModelSerializer): class Meta: model Article fields [id, title, content, author_id] def validate_title(self, value): if Article.objects.filter(titlevalue).exists(): raise serializers.ValidationError(标题已存在) return value字段级方法名为validate_字段名。DRF 会在调用serializer.is_valid()时自动执行这些方法。如果需要跨字段校验就重写validate方法def validate(self, attrs): title attrs.get(title) content attrs.get(content) if title and content and title.casefold() content.casefold(): raise serializers.ValidationError(标题和正文不能完全相同) return attrs这里有个容易踩的坑校验失败时前端拿到的错误信息结构是{title: [标题已存在]}这样的 list不是简单字符串。前端同学不习惯的话会觉得很奇怪。我一般会要求后端在异常处理里统一转换一下错误结构但如果没有特殊需求DRF 默认格式也是合理的关键是前后端要提前对齐。3.3 ModelSerializer的create/update不是万能的ModelSerializer默认的create方法是def create(self, validated_data): instance Model.objects.create(**validated_data) return instance默认的update方法会遍历所有validated_data然后逐个setattr最后save()。这些在“前端提交的字段和模型字段一一对应”的场景下够用。但一旦出现嵌套字段、多对多关系或者你需要在写入时填充当前用户就必须重写这些方法。最典型的例子是需要把当前登录用户作为作者写进文章。在视图里我们通常会在perform_create中处理而不是在序列化器里处理class ArticleViewSet(ModelViewSet): queryset Article.objects.all() serializer_class ArticleSerializer def perform_create(self, serializer): serializer.save(authorself.request.user)这样前端不需要传author_id后端直接拿认证信息赋值。很多新手喜欢在前端传author_id然后接口里直接写死这个在安全性上很不友好等于任何人都能冒充作者。如果是真正的“可写嵌套序列化”比如创建一个文章的同时创建作者那create里就要拆数据def create(self, validated_data): author_data validated_data.pop(author) user User.objects.create(**author_data) article Article.objects.create(authoruser, **validated_data) return article但这种设计我建议尽量少用因为一旦作者信息在业务里被多个接口共用嵌套创建的校验和事务边界会变得很复杂。拆成两步接口各管各的反而清晰。4. 查得爽也要删得对过滤、排序、分页以及删除对象的正确姿势4.1 使用django-filter组合查询条件后端接口如果不支持过滤前端调列表页根本没法用。DRF 官方推荐的过滤方案是配合django-filter使用。安装依赖pip install django-filter在settings.py里配置默认过滤后端REST_FRAMEWORK { DEFAULT_FILTER_BACKENDS: [ django_filters.rest_framework.DjangoFilterBackend, rest_framework.filters.SearchFilter, rest_framework.filters.OrderingFilter, ] }然后在视图里指定过滤字段class ArticleViewSet(ModelViewSet): queryset Article.objects.all() serializer_class ArticleSerializer filterset_fields [author] search_fields [title, content] ordering_fields [created_at, updated_at]此时客户端请求/api/articles/?author1只返回作者 1 的文章/api/articles/?searchdjango会搜索标题和正文里包含 django 的数据/api/articles/?ordering-created_at按创建时间倒序这里有个需要留意的点SearchFilter的实现原理是 ORM 的icontains在 MySQL 里默认大小写不敏感在 PostgreSQL 里可能有差异。如果搜索量很大考虑换用 PostgreSQL 的全文检索或者引入搜索引擎而不是把流量都压在一句LIKE %xxx%上。4.2 分页和排序的用户体验设计没有分页的列表接口早晚会被数据量压垮。DRF 的配置很简单REST_FRAMEWORK { DEFAULT_PAGINATION_CLASS: rest_framework.pagination.PageNumberPagination, PAGE_SIZE: 20, }PageNumberPagination返回的默认结构是count/next/previous/results。如果你希望返回{items: [], total: 100}这种结构可以自定义分页类# myapi/pagination.py from rest_framework.response import Response from rest_framework.pagination import PageNumberPagination class CustomPageNumberPagination(PageNumberPagination): page_size 20 page_size_query_param page_size max_page_size 100 def get_paginated_response(self, data): return Response( { items: data, total: self.page.paginator.count, page: self.page.number, pages: self.page.paginator.num_pages, } )然后视图里class ArticleViewSet(ModelViewSet): pagination_class CustomPageNumberPagination三种内置分页类怎么选分页类特点适合场景PageNumberPagination用页码分页可以任意跳页管理后台列表、通用列表LimitOffsetPagination用 limit 和 offset 控制位置客户端自己控制偏移量的场景CursorPagination游标分页只能向前/向后翻页实时更新的 Feed避免数据变动导致重复要注意的是CursorPagination要求排序字段是唯一的否则容易出现重复或丢失数据。如果业务排序里有并列值建议排序字段加上id作为 tiebreaker。4.3 删除对象的两种典型方案物理删除与软删除标题里专门提了“django执行查询-删除对象”这确实是被问得最多的问题。DRF 默认的DELETE /api/articles/{id}/做的是物理删除也就是直接调用queryset.delete()数据库里一行记录就没了。问题来了业务上很多数据不能真删。比如订单、文章、用户评论删了以后审计、统计、恢复全都没法做。我的建议是按业务类型决定删除策略。日志、临时文件、缓存数据物理删除没问题。核心业务数据一定要软删除。软删除最简单的做法是在模型里加is_activeclass Article(models.Model): # ...原有字段 is_active models.BooleanField(defaultTrue)在视图里把查询范围限制为is_activeTrueclass ArticleViewSet(ModelViewSet): def get_queryset(self): return Article.objects.filter(is_activeTrue) def perform_destroy(self, instance): instance.is_active False instance.save(update_fields[is_active, updated_at])这样默认列表和详情都看不到已删除数据也不会真的删库。但有个边界情况要注意如果你想让管理员能看到已删除数据并恢复需要在同一个视图里额外提供 actionfrom rest_framework.decorators import action from rest_framework.response import Response from rest_framework import status class ArticleViewSet(ModelViewSet): queryset Article.objects.all() serializer_class ArticleSerializer action(detailTrue, methods[post], permission_classes[IsAdminUser]) def restore(self, request, pkNone): article Article.objects.filter(pkpk, is_activeFalse).first() if not article: return Response({detail: 文章不存在或未删除}, statusstatus.HTTP_404_NOT_FOUND) article.is_active True article.save(update_fields[is_active, updated_at]) return Response(ArticleSerializer(article).data)一个隐性坑是软删除和数据库唯一约束会冲突。比如文章标题有唯一约束A 文章被软删除后你再创建一篇同名文章数据库会直接报唯一约束冲突。解决方案是给唯一约束加上condition条件只对is_activeTrue的行生效from django.db.models import Q class Article(models.Model): ... class Meta: constraints [ models.UniqueConstraint( fields[title], conditionQ(is_activeTrue), nameunique_active_title, ) ]这个坑我见过两次每次都是线上报“标题重复”才发现。5. 给API加上门禁认证、权限和调用频率控制5.1 Token认证与JWT怎么选接口一旦上线不可能让所有人随便调。DRF 内置认证方案里最常用的是TokenAuthentication和 JWT。Token 方式配置很简单INSTALLED_APPS [ ... rest_framework.authtoken, ] REST_FRAMEWORK { DEFAULT_AUTHENTICATION_CLASSES: [ rest_framework.authentication.TokenAuthentication, ], DEFAULT_PERMISSION_CLASSES: [ rest_framework.permissions.IsAuthenticated, ], }创建 token 可以这样python manage.py drf_create_token alice也可以在项目的urls.py里加一个获取 token 的接口from rest_framework.authtoken.views import obtain_auth_token urlpatterns [ path(api/token/, obtain_auth_token), ]JWT 我用的是djangorestframework-simplejwt配置和 Token 不太一样pip install djangorestframework-simplejwtREST_FRAMEWORK { DEFAULT_AUTHENTICATION_CLASSES: [ rest_framework_simplejwt.authentication.JWTAuthentication, ], }from rest_framework_simplejwt.views import TokenObtainPairView, TokenRefreshView urlpatterns [ path(api/token/, TokenObtainPairView.as_view()), path(api/token/refresh/, TokenRefreshView.as_view()), ]两者区别我整理一下维度TokenJWT存储服务端数据库存 tokentoken 里自带用户信息服务端注销删掉 token 记录即可需要额外维护黑名单过期时间默认长期有效可自定义自定义 access/refresh 过期时间多设备每设备一个 token方便踢人无状态场景复杂时需要更多设计适合场景内部服务、管理后台、第一方客户端前后端分离、移动端、第三方开放平台我的实际建议是如果你做的是公司内部系统、后台管理优先用内置 Token省心如果你是开放平台或面向公众的 App用 JWT 会更符合无状态水平扩展的需求但一定要仔细处理 refresh token 的存储和轮换。5.2 自定义权限类的常见场景DRF 内置的IsAuthenticated只能判断“是否登录”没法判断“这篇文是不是你写的”。所以你需要自定义权限。比如“文章作者本人才能修改和删除”from rest_framework.permissions import BasePermission, SAFE_METHODS class IsAuthorOrReadOnly(BasePermission): def has_object_permission(self, request, view, obj): if request.method in SAFE_METHODS: return True return obj.author request.user在视图里class ArticleViewSet(ModelViewSet): permission_classes [IsAuthenticated, IsAuthorOrReadOnly]这里有个细节has_object_permission只在调用get_object()的时候生效。列表接口不会走这个方法所以列表里要过滤当前用户的文章仍然需要重写get_querysetdef get_queryset(self): return Article.objects.filter(authorself.request.user)但如果你同时用IsAuthorOrReadOnly别的用户修改文章时应该返回 403 还是 404这取决于get_queryset是否包含目标对象。如果get_queryset已经过滤为当前用户那非作者的请求会直接 404权限检查根本没有机会执行。这个行为在语义上是说得通的但要记得跟前端说清楚你们看到的 404 可能不是“文章不存在”而是“你没权限访问”。5.3 别让接口被刷限流配置细节接口不想被爬虫或恶意脚本刷爆限流是必须的。DRF 内置限流类全局配置如下REST_FRAMEWORK { DEFAULT_THROTTLE_CLASSES: [ rest_framework.throttling.AnonRateThrottle, rest_framework.throttling.UserRateThrottle, ], DEFAULT_THROTTLE_RATES: { anon: 100/day, user: 1000/hour, }, }如果你希望对某个特定接口设置更高频的限流可以用ScopedRateThrottleclass ArticleViewSet(ModelViewSet): throttle_scope articlesREST_FRAMEWORK { DEFAULT_THROTTLE_RATES: { articles: 60/min, }, }关于限流有个非常实际的坑如果你的服务跑在反向代理后面DRF 默认拿REMOTE_ADDR来识别客户端 IP而代理服务器传过来的都是内网 IP结果就是所有用户共享一个 IP限流一下子就把所有人挡在外面了。这时候要配置NUM_PROXIESREST_FRAMEWORK { NUM_PROXIES: 1, }这个值表示反向代理的层数DRF 会根据X-Forwarded-For往前推对应位数。如果代理层数不确定建议在中间件或 Nginx 侧统一处理客户端 IP别等上线以后被限流误伤再到处排查。6. 测试与调试让接口在交付前就稳定下来6.1 APITestCase的实用姿势没有自动化测试的接口重构起来心里没底。DRF 提供了APITestCase用起来比 Django 原生TestCase顺手得多。第一个测试可以先验权限# blog/tests.py from django.contrib.auth import get_user_model from django.urls import reverse from rest_framework import status from rest_framework.test import APITestCase class ArticleAPITests(APITestCase): def setUp(self): self.user get_user_model().objects.create_user( usernamealice, passwordsecret, ) def test_create_article_requires_login(self): url reverse(article-list) resp self.client.post(url, {title: x, content: y}, formatjson) self.assertEqual(resp.status_code, status.HTTP_401_UNAUTHORIZED) def test_create_article_after_login(self): url reverse(article-list) self.client.force_authenticate(userself.user) resp self.client.post(url, {title: x, content: y}, formatjson) self.assertEqual(resp.status_code, status.HTTP_201_CREATED)用force_authenticate可以绕过真实登录过程直接模拟已认证用户。如果想要完整模拟 Token 流程还可以from rest_framework.authtoken.models import Token token Token.objects.create(userself.user) self.client.credentials(HTTP_AUTHORIZATIONfToken {token.key})我推荐日常写接口时至少覆盖三条路径未认证请求返回 401/403正常请求返回成功非法参数返回 400。这三条路走顺了接口的基本稳定性就有保障了。6.2 调试接口的常用工具和日志技巧开发阶段如果想快速看请求日志可以配置 LOGGINGLOGGING { version: 1, disable_existing_loggers: False, handlers: { console: { class: logging.StreamHandler, }, }, loggers: { django.request: { handlers: [console], level: DEBUG, }, django.db.backends: { handlers: [console], level: DEBUG, }, }, }开了django.db.backends的日志后每个 ORM 查询都会被打印出来这对于发现 N1 问题非常直接。但生产环境一定不要开 DEBUG 级别否则日志量会爆炸而且会把 SQL 参数泄露进去。另一个我经常用的库是django-silk。安装后它会记录每个请求的耗时、执行的 SQL 语句、调用栈对新接口做性能体检非常方便。pip install django-silk配置好以后打开/silk/就能看到请求明细。不过这是侵入式工具只建议在开发环境启用生产环境一定要关闭。还有一件事Docker 里跑数据库连接报permission denied while trying to connect to the docker api这类问题通常不是 Django 代码问题而是容器间网络或 socket 权限问题。遇到这种错误先把视角转到 Docker 网络配置和数据库容器的端口映射上别在 DRF 配置里翻半天。6.3 我踩过的典型坑权限、时区、Docker连接这里把实际项目里遇到频率比较高的几个坑集中说一下。第一个是时区坑。Django 默认USE_TZTrue数据库里存的是 UTC 时间DRF 返回给前端的时候默认带2025-06-01T10:00:00Z这种格式。很多前端直接用new Date()解析没问题但如果你内部有一些对比时间的逻辑字符串里的Z和本地时间差很容易造成偏差。解决方案是在 settings 里统一REST_FRAMEWORK { DATETIME_FORMAT: %Y-%m-%d %H:%M:%S, }但这样返回的字符串就没有时区标记了前后端必须约定好都按东八区展示。我一般会保留带时区的 ISO 格式让前端自己处理转换除非团队里有明确约定。第二个坑是序列化器的 write_only 和 read_only 没分清。比如新增文章时前端传了created_at字段而你没有在fields里去掉它DRF 默认会认为是可写的导致用户可以伪造创建时间。安全做法是显式标记read_onlyTrue或者在序列化器里排除掉由后端控制的字段class ArticleSerializer(serializers.ModelSerializer): created_at serializers.DateTimeField(read_onlyTrue) updated_at serializers.DateTimeField(read_onlyTrue)第三个坑是 Docker 环境下数据库连接属性写错。很多人在runserver里好好的放进 Docker 就连不上数据库报 permission denied 或者 connection refused。多半是因为数据库容器名字和服务名不一致或者没有设置正确的HOST。这个跟 DRF 没关系但遇到接口 500大家第一时间都会怀疑代码容易在上面浪费不少时间。7. 性能优化与部署接口上线前最后一步7.1 先处理N1查询问题select_related和prefetch_related接口卡顿十有八九是 SQL 查询太多。典型场景是序列化器里写了嵌套作者信息列表接口返回 20 篇文章结果执行了 1 条查文章 20 条查作者的 SQL这就是 N1 查询。解决方案很简单在视图的 queryset 里提前把外键查询出来class ArticleViewSet(ModelViewSet): def get_queryset(self): return Article.objects.select_related(author).all()select_related适用于 ForeignKey 和 OneToOne 字段它内部用 JOIN 的方式把关联对象一次性查出来。如果是多对多字段比如文章的标签就要用prefetch_relatedfrom django.db.models import Prefetch class ArticleViewSet(ModelViewSet): def get_queryset(self): return ( Article.objects .select_related(author) .prefetch_related(tags) .all() )判断什么时候用哪个记住一句话单个对象的外键用select_related集合型关联用prefetch_related。prefetch_related会额外再发一条 IN 查询然后把结果映射到对应对象而不是用 JOIN。7.2 缓存和数据库索引列表接口如果数据变更不频繁可以加一层缓存。DRF 里最简单的做法是直接对 list 方法加缓存from django.utils.decorators import method_decorator from django.views.decorators.cache import cache_page method_decorator(cache_page(60 * 5), namelist) class ArticleViewSet(ModelViewSet): ...但这里有个重要的坑cache_page默认按完整 URL 做缓存 key不会区分请求头里的认证信息。如果你的接口返回的是登录用户私有数据千万别用这种粗粒度的缓存否则用户 A 的数据可能会被用户 B 看到。公开的资讯列表、文章列表可以用一旦涉及用户维度还是要改用基于用户身份的缓存方案。数据库索引同样重要。比如文章列表经常按author过滤外键字段默认有索引按created_at排序如果这个字段没有索引数据量大了以后排序会越来越慢。可以在模型里添加class Article(models.Model): created_at models.DateTimeField(auto_now_addTrue, db_indexTrue)索引不是越多越好每个索引都会影响写入性能。我的习惯是只给查询频率高、区分度高的字段加索引同时定期用EXPLAIN看执行计划。7.3 用Gunicorn反向代理跑起来开发环境用runserver没问题生产环境千万别这么干。我常用的部署命令是pip install gunicorn gunicorn myapi.wsgi:application -w 4 -b 0.0.0.0:8000-w 4是 worker 进程数建议根据 CPU 核数调整不是越大越好。worker 太多反而会因为上下文切换带来额外开销。更完整的生产部署我一般会再加一层反向代理比如 Nginx统一负责静态文件、HTTPS 终止和端口转发。Django 侧至少要设置ALLOWED_HOSTS [api.example.com] DEBUG False如果使用了 HTTPS还需要配置安全相关的 header。这里我建议直接让云平台的负载均衡或反向代理去处理证书Django 侧只需要保证SECURE_PROXY_SSL_HEADER配置正确即可不然在 HTTP header 里没有正确标记时Django 可能把请求当成非安全请求导致重定向循环。最后提一个我在项目里实测很有效的经验不要把“加缓存”“上部署”放在最后才做。新接口一接手先把get_queryset的关联查询写好把分页配好把权限限制好再进入业务逻辑开发。DRF 的能力很透明前面几步做得扎实后面几乎不需要回头补坑。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询