全网最详细!!!Django+Vue3 全栈开发进阶:从后端接口到前端交互实战
引路者👇:
1. 限制 HTTP 方法(require_http_methods系列)
2. 用户认证与权限控制(login_required/permission_required)
2.1.2 聚合查询:统计数据(Count/Sum/Avg)
第三章:Vue3 与 Django 交互:表单提交与异步处理
3.2 Django 接收表单数据:处理 JSON 与表单格式
4.1.1 方法 1:使用django-cors-headers(推荐,功能完善)
4.1.2 方法 2:手动编写自定义中间件(无需第三方依赖)
4.1.3 方法 3:在视图中单独添加 CORS 头(适用于个别接口)
第五章:Promise 与异步处理:Vue3 中的异步请求优化
5.1.2 消费 Promise:then/catch/finally
引言:
在前后端分离开发模式中,Django 凭借强大的后端能力负责数据处理与接口提供,Vue3 则以灵活的组件化开发构建用户交互界面。本文Fly从 Django 的视图路由优化、ORM 高级操作,到 Vue3 的表单交互与异步处理,再到跨域问题解决,完整覆盖全栈开发的核心环节,附带可直接复用的代码示例,助力你快速落地项目。
第一章:Django 视图与路由:构建灵活的后端入口
视图与路由是 Django 接收前端请求、分发业务逻辑的核心。合理的路由设计与视图封装,能让后端接口更易维护、扩展性更强。本节将深入讲解路由的高级配置与视图的实战技巧。
1.1 URL 路由进阶:精准匹配与模块化拆分
1.1.1 路径转换器:捕获动态 URL 参数
Django 内置路径转换器,可快速提取 URL 中的动态参数(如文章 ID、分类别名),无需手动解析字符串,大幅简化代码。
| 转换器 | 功能描述 | 示例代码 |
|---|---|---|
str |
匹配除/外的任意字符(默认类型) |
path('user/<str:username>/', views.user_detail) |
int |
匹配非负整数(常用于 ID、页码等场景) | path('article/<int:pk>/', views.article_detail) |
slug |
匹配字母、数字、_、-组成的 URL 友好字符串 |
path('category/<slug:alias>/', views.category_articles) |
uuid |
匹配 UUID 格式字符串(唯一性强,适合安全场景) | path('order/<uuid:order_id>/', views.order_detail) |
path |
匹配包含/的完整路径(如文件路径) |
path('file/<path:file_path>/', views.file_download) |
实战示例:通过转换器实现文章详情与分类列表
# myapp/urls.py
from django.urls import path
from . import views
urlpatterns = [
# 文章详情:URL格式 /article/1/ → 提取pk=1(文章ID)
path('article/<int:pk>/', views.article_detail, name='article_detail'),
# 分类文章:URL格式 /category/tech/ → 提取alias='tech'(分类别名)
path('category/<slug:alias>/', views.category_articles, name='category_articles'),
# 文章归档:URL格式 /archive/2025/08/ → 提取year=2025、month=08
path('archive/<int:year>/<int:month>/', views.article_archive, name='article_archive'),
]
# myapp/views.py
from django.shortcuts import render, get_object_or_404
from .models import Article, Category
def article_detail(request, pk):
"""通过文章ID获取详情,不存在则返回404"""
# get_object_or_404:查询失败时自动抛出404,替代try-except
article = get_object_or_404(Article, pk=pk)
return render(request, 'article_detail.html', {'article': article})
def category_articles(request, alias):
"""通过分类别名获取文章列表"""
category = get_object_or_404(Category, alias=alias)
articles = Article.objects.filter(category=category).order_by('-pub_date')
return render(request, 'category_list.html', {'category': category, 'articles': articles})
1.1.2 正则表达式:复杂 URL 模式匹配
对于更灵活的 URL 规则(如 “按年月归档”“多条件筛选”),可使用re_path结合正则表达式,实现精准匹配。
示例:匹配 “/archive/2025/08/” 格式的归档 URL
# myapp/urls.py
from django.urls import re_path
from . import views
urlpatterns = [
# 正则表达式:匹配4位年份+2位月份(如2025/08)
re_path(r'^archive/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/$',
views.article_archive,
name='article_archive'),
]
# myapp/views.py
from datetime import datetime
from django.shortcuts import render
from .models import Article
def article_archive(request, year, month):
"""获取指定年月的文章归档"""
# 转换参数类型(URL提取的是字符串,需转为整数)
year = int(year)
month = int(month)
# 筛选该年月的文章
articles = Article.objects.filter(
pub_date__year=year,
pub_date__month=month
).order_by('-pub_date')
return render(request, 'archive.html', {
'articles': articles,
'year': year,
'month': month,
'current_date': datetime.now().strftime('%Y-%m-%d')
})
1.1.3 命名空间与模块化路由:解决多 App 冲突
当项目包含多个 App 时,可能出现路由名称重复(如article_detail)。通过 “命名空间” 可区分不同 App 的路由,通过 “模块化拆分” 可让路由配置更清晰。
1. 根路由配置命名空间(project/urls.py)
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
# 为blog App配置命名空间'blog'
path('blog/', include(('blog.urls', 'blog'), namespace='blog')),
# 为shop App配置命名空间'shop'
path('shop/', include(('shop.urls', 'shop'), namespace='shop')),
]
2. App 内路由定义(blog/urls.py)
# blog/urls.py
from django.urls import path
from . import views
# 必须指定app_name,与根路由的命名空间对应
app_name = 'blog'
urlpatterns = [
path('article/<int:pk>/', views.article_detail, name='article_detail'),
path('category/<slug:alias>/', views.category_articles, name='category_articles'),
]
3. 反向解析路由(视图 / 模板中)
# 视图中反向解析(views.py)
from django.urls import reverse
def test_view(request):
# 解析blog App的article_detail路由:/blog/article/1/
blog_url = reverse('blog:article_detail', args=[1])
# 解析shop App的product_detail路由(假设存在)
shop_url = reverse('shop:product_detail', args=[100])
return render(request, 'test.html', {'blog_url': blog_url, 'shop_url': shop_url})
<!-- 模板中反向解析(test.html) -->
<a href="{% url 'blog:article_detail' 1 %}">博客文章1</a>
<a href="{% url 'shop:product_detail' 100 %}">商城商品100</a>
1.2 视图函数优化:简化代码与增强功能
1.2.1 快捷函数:减少重复逻辑
Django 提供render()、redirect()、get_object_or_404()等快捷函数,可替代冗余的手动代码,提升开发效率。
| 快捷函数 | 功能描述 |
|---|---|
render(request, template, context) |
渲染模板并返回HttpResponse,自动处理模板路径查找 |
redirect(to, *args, **kwargs) |
重定向到指定 URL(支持路由名称、绝对路径、外部链接) |
get_object_or_404(Model, **kwargs) |
根据条件查询单个对象,不存在则返回 404(替代try-except Model.DoesNotExist) |
get_list_or_404(Model, **kwargs) |
根据条件查询对象列表,为空则返回 404 |
示例:使用快捷函数简化视图
# myapp/views.py
from django.shortcuts import render, redirect, get_object_or_404, get_list_or_404
from .models import Article
def article_detail(request, pk):
# 替代:try: article = Article.objects.get(pk=pk) except Article.DoesNotExist: raise Http404
article = get_object_or_404(Article, pk=pk)
return render(request, 'article_detail.html', {'article': article})
def published_articles(request):
# 若没有已发布的文章,直接返回404
articles = get_list_or_404(Article, is_published=True)
return render(request, 'article_list.html', {'articles': articles})
def delete_article(request, pk):
article = get_object_or_404(Article, pk=pk)
article.delete()
# 重定向到文章列表页(使用路由名称,避免硬编码URL)
return redirect('article_list')
1.2.2 视图装饰器:增强视图功能
装饰器可在不修改视图核心逻辑的前提下,为其添加额外功能(如限制 HTTP 方法、要求登录、缓存页面)。
1. 限制 HTTP 方法(require_http_methods系列)
确保视图仅接收指定的 HTTP 方法(如GET/POST),非法方法自动返回 405(Method Not Allowed):
from django.views.decorators.http import require_http_methods, require_GET, require_POST
# 仅允许GET和POST方法
@require_http_methods(['GET', 'POST'])
def article_edit(request, pk):
article = get_object_or_404(Article, pk=pk)
if request.method == 'GET':
# 显示编辑表单
return render(request, 'article_edit.html', {'article': article})
elif request.method == 'POST':
# 处理表单提交
title = request.POST.get('title')
content = request.POST.get('content')
article.title = title
article.content = content
article.save()
return redirect('article_detail', pk=pk)
# 仅允许GET方法(简化版@require_http_methods(['GET']))
@require_GET
def article_list(request):
articles = Article.objects.all().order_by('-pub_date')
return render(request, 'article_list.html', {'articles': articles})
# 仅允许POST方法(简化版@require_http_methods(['POST']))
@require_POST
def article_delete(request, pk):
article = get_object_or_404(Article, pk=pk)
article.delete()
return redirect('article_list')
2. 用户认证与权限控制(login_required/permission_required)
限制视图仅允许已登录用户或拥有特定权限的用户访问:
from django.contrib.auth.decorators import login_required, permission_required
# 仅允许已登录用户访问(未登录自动跳转到登录页)
@login_required
def user_profile(request):
# request.user 为当前登录用户
return render(request, 'profile.html', {'user': request.user})
# 仅允许拥有“修改文章”权限的用户访问
@permission_required('myapp.change_article') # 权限格式:app_label.permission_name
def article_edit(request, pk):
article = get_object_or_404(Article, pk=pk)
# 处理编辑逻辑...
return render(request, 'article_edit.html', {'article': article})
3. 页面缓存(cache_page)
对频繁访问且变化少的页面(如首页、热门文章)进行缓存,减少数据库查询,提升性能:
from django.views.decorators.cache import cache_page
# 缓存15分钟(单位:秒)
@cache_page(60 * 15)
def home_page(request):
# 复杂查询(如热门文章、分类统计)
hot_articles = Article.objects.order_by('-views')[:5]
category_count = Category.objects.annotate(article_count=Count('article'))
return render(request, 'home.html', {
'hot_articles': hot_articles,
'category_count': category_count
})
第二章:Django ORM 高级操作:高效处理数据库数据
ORM(对象关系映射)是 Django 的核心特性,通过 Python 代码即可操作数据库,无需编写 SQL。本节将讲解 ORM 的高级查询、批量操作与关联查询优化,助力处理复杂数据场景。
2.1 高级查询:精准筛选与数据统计
2.1.1 条件查询:复杂筛选逻辑
除基础的filter()/exclude(),Django ORM 支持通过 “双下划线” 语法实现范围、模糊匹配、日期筛选等复杂条件。
| 条件类型 | ORM 语法示例 | 对应 SQL 逻辑 |
|---|---|---|
| 大于 / 小于 | filter(price__gt=100)/filter(price__lt=50) |
WHERE price > 100/WHERE price < 50 |
| 大于等于 / 小于等于 | filter(pub_date__gte='2025-01-01') |
WHERE pub_date >= '2025-01-01' |
| 模糊匹配 | filter(title__contains='Python') |
WHERE title LIKE '%Python%' |
| 开头 / 结尾匹配 | filter(title__startswith='Django')/filter(title__endswith='教程') |
WHERE title LIKE 'Django%'/WHERE title LIKE '%教程' |
| 范围查询 | filter(id__in=[1,2,3])/filter(price__range=(50, 100)) |
WHERE id IN (1,2,3)/WHERE price BETWEEN 50 AND 100 |
| 日期筛选 | filter(pub_date__year=2025)/filter(pub_date__week_day=1) |
WHERE YEAR(pub_date) = 2025/WHERE WEEKDAY(pub_date) = 0(周日) |
示例:多条件组合查询
from django.db.models import Q
from .models import Article
# 1. 基础多条件(AND逻辑):2025年发布且标题含“Python”的已发布文章
articles1 = Article.objects.filter(
pub_date__year=2025,
title__contains='Python',
is_published=True
)
# 2. 复杂逻辑(OR+AND):使用Q对象
# 查询“标题含Python”或“阅读量>1000”,且发布时间在2025年的文章
articles2 = Article.objects.filter(
Q(title__contains='Python') | Q(views__gt=1000), # OR逻辑
pub_date__year=2025 # AND逻辑(与Q对象组合)
)
# 3. 排除条件:非“技术”分类且阅读量>=500的文章
articles3 = Article.objects.filter(
views__gte=500
).exclude(
category__name='技术'
)
2.1.2 聚合查询:统计数据(Count/Sum/Avg)
通过aggregate()结合聚合函数,实现数据统计(如总数、求和、平均值),返回字典格式结果;通过annotate()实现分组聚合(如按分类统计文章数)。
示例:聚合函数使用
from django.db.models import Count, Sum, Avg, Max, Min
from .models import Article, Order
# 1. 基础聚合:统计文章总数、平均阅读量、最高阅读量
article_stats = Article.objects.aggregate(
total=Count('id'),
avg_views=Avg('views'),
max_views=Max('views')
)
print(article_stats)
# 输出:{'total': 100, 'avg_views': 250.5, 'max_views': 1000}
# 2. 分组聚合:按分类统计文章数量(并按数量降序)
category_article_count = Category.objects.annotate(
count=Count('article') # 按分类分组,统计每组的文章数
).order_by('-count')
print(category_article_count)
# 输出:[<Category: 技术 (50)>, <Category: 生活 (30)>, ...]
# 3. 关联聚合:统计每个用户的订单总金额
user_order_stats = User.objects.annotate(
total_amount=Sum('order__amount') # 关联Order模型的amount字段求和
).filter(total_amount__gt=1000) # 筛选总金额>1000的用户
2.2 批量操作:提升大数据处理效率
对于大量数据(如批量导入、批量更新),使用 ORM 的批量操作方法,可减少数据库交互次数,大幅提升性能。
2.2.1 批量创建:bulk_create()
一次性创建多个对象,仅需 1 次数据库交互(替代循环调用create()):
# 批量创建100篇测试文章
articles = [
Article(
title=f"测试文章{i}",
content=f"文章{i}的内容...",
category_id=1,
is_published=True
) for i in range(1, 101)
]
# 批量插入(batch_size控制每次插入数量,避免单次请求过大)
Article.objects.bulk_create(articles, batch_size=50)
2.2.2 批量更新:update()
一次性更新符合条件的所有对象,仅需 1 次数据库交互:
from django.db.models import F
from datetime import timezone
# 1. 批量更新状态:将“待审核”文章改为“已发布”
Article.objects.filter(status='pending').update(status='published')
# 2. 批量更新数值字段:所有文章阅读量+100(F()避免竞态条件)
Article.objects.all().update(views=F('views') + 100)
# 3. 批量更新时间字段:更新所有文章的最后修改时间
Article.objects.all().update(updated_at=timezone.now())
2.2.3 批量删除:delete()
一次性删除符合条件的所有对象,注意谨慎使用(无回收站,删除后不可恢复):
from datetime import datetime, timedelta
# 1. 删除1年前的过期文章
one_year_ago = datetime.now() - timedelta(days=365)
Article.objects.filter(pub_date__lt=one_year_ago).delete()
# 2. 删除“测试”分类下的所有文章
Article.objects.filter(category__name='测试').delete()
2.3 关联查询优化:避免 N+1 查询问题
“N+1 查询问题” 指:查询 N 个对象时,先执行 1 次查询获取主表数据,再执行 N 次查询获取每个对象的关联数据。Django 提供select_related()和prefetch_related()解决该问题。
| 方法 | 适用场景 | 原理 |
|---|---|---|
select_related() |
一对一(OneToOneField)、一对多(ForeignKey,正向查询) | 执行 JOIN 查询,一次性获取主表 + 关联表数据 |
prefetch_related() |
多对多(ManyToManyField)、一对多(反向查询) | 执行 2 次独立查询,内存中关联数据 |
示例:优化关联查询
# 1. 优化一对多正向查询(Article → Category)
# 原写法(N+1问题):获取10篇文章,执行1次Article查询 + 10次Category查询
articles = Article.objects.all()[:10]
for article in articles:
print(article.category.name) # 每次循环执行1次Category查询
# 优化写法(1次查询):使用select_related() JOIN Category表
articles = Article.objects.select_related('category').all()[:10]
for article in articles:
print(article.category.name) # 无额外查询
# 2. 优化多对多查询(Article → Tag)
# 原写法(N+1问题):获取10篇文章,执行1次Article查询 + 10次Tag查询
articles = Article.objects.all()[:10]
for article in articles:
print(article.tags.all()) # 每次循环执行1次Tag查询
# 优化写法(2次查询):使用prefetch_related()
articles = Article.objects.prefetch_related('tags').all()[:10]
for article in articles:
print(article.tags.all()) # 无额外查询
# 3. 组合优化(多关联层级):获取文章的分类+标签+作者
articles = Article.objects.select_related('category', 'author').prefetch_related('tags').all()[:10]
第三章:Vue3 与 Django 交互:表单提交与异步处理
前后端分离架构中,Vue3 通过 Axios 发送异步请求,与 Django 后端交互。本节将讲解 Vue3 的表单设计、异步请求处理,以及 Django 如何接收并处理前端数据。
3.1 Vue3 表单设计:双向绑定与数据验证
Vue3 的v-model实现表单数据双向绑定,结合axios可快速将表单数据发送到 Django 后端。以下为 “文章发布” 表单示例:
<!-- src/components/ArticleForm.vue -->
<template>
<div class="article-form">
<h2>发布文章</h2>
<form @submit.prevent="submitArticle">
<!-- 文章标题 -->
<div class="form-item">
<label>标题:</label>
<input
type="text"
v-model="form.title"
required
placeholder="请输入文章标题(不超过200字)"
@blur="validateTitle"
>
<p class="error" v-if="titleError">{{ titleError }}</p>
</div>
<!-- 文章分类 -->
<div class="form-item">
<label>分类:</label>
<select v-model="form.categoryId" required>
<option value="">请选择分类</option>
<option v-for="category in categories" :key="category.id" :value="category.id">
{{ category.name }}
</option>
</select>
</div>
<!-- 文章内容 -->
<div class="form-item">
<label>内容:</label>
<textarea
v-model="form.content"
required
rows="8"
placeholder="请输入文章内容"
></textarea>
</div>
<!-- 提交按钮(加载状态) -->
<button type="submit" :disabled="loading">
{{ loading ? "发布中..." : "发布文章" }}
</button>
</form>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import axios from 'axios';
// 表单数据
const form = ref({
title: "",
categoryId: "",
content: ""
});
// 分类列表(从Django后端获取)
const categories = ref([]);
// 加载状态
const loading = ref(false);
// 标题验证错误
const titleError = ref("");
// 验证标题长度
const validateTitle = () => {
if (form.value.title.length > 200) {
titleError.value = "标题长度不能超过200字";
} else {
titleError.value = "";
}
};
// 获取分类列表(组件挂载时执行)
const fetchCategories = async () => {
try {
const response = await axios.get("/api/categories/");
categories.value = response.data.data;
} catch (error) {
console.error("获取分类失败:", error);
alert("获取分类失败,请刷新页面重试");
}
};
// 提交文章
const submitArticle = async () => {
// 先验证表单
validateTitle();
if (titleError.value || !form.value.title || !form.value.categoryId || !form.value.content) {
alert("请完善表单信息");
return;
}
loading.value = true;
try {
// 发送POST请求到Django后端
const response = await axios.post("/api/articles/", form.value);
alert(`文章发布成功!文章ID:${response.data.data.id}`);
// 重置表单
form.value = { title: "", categoryId: "", content: "" };
} catch (error) {
console.error("发布失败:", error);
alert(error.response?.data?.message || "发布失败,请稍后再试");
} finally {
loading.value = false;
}
};
// 组件挂载时加载分类
onMounted(() => {
fetchCategories();
});
</script>
<style scoped>
.article-form { max-width: 800px; margin: 20px auto; padding: 0 20px; }
.form-item { margin-bottom: 15px; }
label { display: block; margin-bottom: 5px; font-weight: bold; }
input, select, textarea { width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px; }
.error { color: #dc3545; font-size: 0.9em; margin: 5px 0 0 0; }
button { padding: 10px 20px; background: #007bff; color: white; border: none; border-radius: 4px; cursor: pointer; }
button:disabled { background: #6c757d; cursor: not-allowed; }
</style>
3.2 Django 接收表单数据:处理 JSON 与表单格式
Django 可接收 Vue3 发送的两种数据格式:application/x-www-form-urlencoded(表单格式)和application/json(JSON 格式),以下为两种格式的处理示例。
3.2.1 接收 JSON 格式数据(推荐)
Vue3 的 Axios 默认发送 JSON 格式数据,Django 需通过request.body解析:
# myapp/views.py
import json
from django.http import JsonResponse
from .models import Article, Category
def create_article(request):
"""接收Vue3发送的JSON数据,创建文章"""
if request.method == "POST":
try:
# 解析JSON格式的请求体
data = json.loads(request.body)
title = data.get("title")
category_id = data.get("categoryId")
content = data.get("content")
# 数据验证
if not title or not category_id or not content:
return JsonResponse({
"status": "error",
"message": "标题、分类、内容不能为空"
}, status=400)
# 验证分类是否存在
category = get_object_or_404(Category, id=category_id)
# 存储到数据库
article = Article.objects.create(
title=title,
category=category,
content=content,
is_published=True
)
# 返回成功响应
return JsonResponse({
"status": "success",
"message": "文章创建成功",
"data": {
"id": article.id,
"title": article.title,
"pub_date": article.pub_date.strftime("%Y-%m-%d %H:%M:%S")
}
}, status=201)
except json.JSONDecodeError:
# 数据格式错误(非JSON)
return JsonResponse({
"status": "error",
"message": "数据格式错误,请发送JSON格式"
}, status=400)
# 非POST请求
return JsonResponse({
"status": "error",
"message": "仅支持POST请求"
}, status=405)
3.2.2 接收表单格式数据
若 Vue3 发送表单格式数据(需配置 Axios 的Content-Type),Django 可通过request.POST获取:
# myapp/views.py
from django.http import JsonResponse
from django.shortcuts import get_object_or_404
from .models import Article, Category
def create_article_form(request):
"""接收表单格式数据,创建文章"""
if request.method == "POST":
# 从request.POST获取表单数据
title = request.POST.get("title")
category_id = request.POST.get("categoryId")
content = request.POST.get("content")
# 数据验证(同JSON格式)
if not title or not category_id or not content:
return JsonResponse({
"status": "error",
"message": "标题、分类、内容不能为空"
}, status=400)
category = get_object_or_404(Category, id=category_id)
article = Article.objects.create(
title=title,
category=category,
content=content
)
return JsonResponse({
"status": "success",
"data": {"id": article.id}
}, status=201)
return JsonResponse({"status": "error", "message": "仅支持POST请求"}, status=405)
3.2.3 Vue3 发送表单格式数据的配置
若需发送表单格式数据,需在 Axios 中配置Content-Type:
// Vue3组件中配置Axios
const submitArticleForm = async () => {
try {
const formData = new FormData();
formData.append("title", form.value.title);
formData.append("categoryId", form.value.categoryId);
formData.append("content", form.value.content);
await axios.post("/api/articles/form/", formData, {
headers: {
"Content-Type": "multipart/form-data" // 表单格式(支持文件上传)
}
});
alert("发布成功");
} catch (error) {
console.error(error);
}
};
第四章:跨域问题解决:Django CORS 配置
前后端分离架构中,Vue3(如运行在http://localhost:8080)与 Django(如运行在http://localhost:8000)因域名 / 端口不同,会触发浏览器的 “同源策略”,导致请求被拦截。需通过 CORS(跨域资源共享)配置允许跨域访问。
4.1 三种 CORS 配置方法(推荐第一种)
4.1.1 方法 1:使用django-cors-headers(推荐,功能完善)
django-cors-headers是 Django 官方推荐的跨域解决方案,支持细粒度控制允许的域名、方法、请求头。
1. 安装依赖
pip install django-cors-headers
2. 配置settings.py
需在INSTALLED_APPS中注册应用,并在MIDDLEWARE中添加中间件(顺序必须靠前,建议放在SecurityMiddleware之前):
# settings.py
INSTALLED_APPS = [
# Django内置应用...
'corsheaders', # 注册cors-headers应用
'myapp', # 自定义应用
]
# 中间件配置(corsheaders.middleware.CorsMiddleware必须靠前)
MIDDLEWARE = [
'corsheaders.middleware.CorsMiddleware', # 跨域中间件
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
# 其他中间件...
]
# ------------------- CORS核心配置 -------------------
# 开发环境:允许所有域名(方便调试,生产环境禁用)
# CORS_ALLOW_ALL_ORIGINS = True
# 生产环境:指定允许的域名(推荐,更安全)
CORS_ALLOWED_ORIGINS = [
"http://localhost:8080", # Vue3开发环境默认端口
"http://127.0.0.1:8080",
"https://yourdomain.com", # 生产环境域名
]
# 允许的HTTP方法(默认支持GET/POST,可扩展)
CORS_ALLOW_METHODS = [
"DELETE", "GET", "OPTIONS", "PATCH", "POST", "PUT",
]
# 允许的请求头(如Authorization、Content-Type)
CORS_ALLOW_HEADERS = [
"accept", "authorization", "content-type", "user-agent",
"x-csrftoken", "x-requested-with",
]
# 是否允许携带凭证(如Cookies、Token,需前后端同时配置)
CORS_ALLOW_CREDENTIALS = True
# 预检请求(OPTIONS)的缓存时间(秒),减少重复请求
CORS_PREFLIGHT_MAX_AGE = 86400 # 24小时
3. 前端配合配置(Axios 携带凭证)
若CORS_ALLOW_CREDENTIALS = True,Vue3 的 Axios 需开启withCredentials:
// src/utils/axios.js
import axios from "axios";
const service = axios.create({
baseURL: "http://127.0.0.1:8000/api",
timeout: 5000,
withCredentials: true // 允许携带凭证
});
export default service;
4.1.2 方法 2:手动编写自定义中间件(无需第三方依赖)
若不想安装额外包,可自定义中间件,手动添加 CORS 响应头:
# myapp/middleware.py
class CustomCorsMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
# 处理请求
response = self.get_response(request)
# 添加CORS响应头(允许Vue3开发环境跨域)
response["Access-Control-Allow-Origin"] = "http://localhost:8080"
response["Access-Control-Allow-Methods"] = "GET, POST, PUT, DELETE, OPTIONS"
response["Access-Control-Allow-Headers"] = "Content-Type, Authorization"
response["Access-Control-Allow-Credentials"] = "true"
return response
在settings.py中注册中间件:
# settings.py
MIDDLEWARE = [
'myapp.middleware.CustomCorsMiddleware', # 自定义跨域中间件
# 其他中间件...
]
4.1.3 方法 3:在视图中单独添加 CORS 头(适用于个别接口)
若仅需个别接口支持跨域,可在视图函数中手动添加响应头:
# myapp/views.py
from django.http import JsonResponse
def cross_domain_api(request):
"""仅该接口支持跨域"""
data = {"message": "支持跨域的接口数据"}
response = JsonResponse(data)
# 添加CORS头
response["Access-Control-Allow-Origin"] = "http://localhost:8080"
response["Access-Control-Allow-Methods"] = "GET, POST, OPTIONS"
response["Access-Control-Allow-Headers"] = "Content-Type, Authorization"
return response
4.2 生产环境 CORS 配置注意事项
- 禁止使用
CORS_ALLOW_ALL_ORIGINS = True:生产环境必须明确指定允许的域名,避免恶意网站跨域访问; - 限制允许的方法和头:仅开放业务必需的 HTTP 方法(如
GET/POST/PUT)和请求头,减少攻击面; - 凭证携带需谨慎:若
CORS_ALLOW_CREDENTIALS = True,CORS_ALLOWED_ORIGINS不能使用通配符*,必须指定具体域名; - 处理预检请求(OPTIONS):复杂请求(如带自定义头、
PUT/DELETE方法)会先发送OPTIONS请求,django-cors-headers会自动处理,自定义中间件需手动支持。
第五章:Promise 与异步处理:Vue3 中的异步请求优化
Vue3 中处理异步请求(如 Axios 调用)时,Promise是核心工具,可避免 “回调地狱”;async/await则是Promise的语法糖,让异步代码更易读。本节将讲解Promise的核心用法与async/await的实战应用。
5.1 Promise 基础:管理异步状态
Promise用于管理异步操作的状态(pending→fulfilled/rejected),通过then()/catch()/finally()处理结果。
5.1.1 创建 Promise 对象
模拟 “用户登录” 异步操作,成功返回用户信息,失败返回错误原因:
// 模拟登录接口(异步操作)
const login = (username, password) => {
return new Promise((resolve, reject) => {
// 模拟后端接口延迟(1秒)
setTimeout(() => {
if (username === "admin" && password === "123456") {
// 成功:调用resolve返回结果
resolve({
userId: 1,
username: "admin",
role: "superuser"
});
} else {
// 失败:调用reject返回错误
reject(new Error("用户名或密码错误"));
}
}, 1000);
});
};
5.1.2 消费 Promise:then/catch/finally
// 调用登录接口
login("admin", "123456")
.then((userInfo) => {
console.log("登录成功:", userInfo);
// 后续操作:如存储Token、跳转首页
localStorage.setItem("token", "xxx");
window.location.href = "/home";
})
.catch((error) => {
console.error("登录失败:", error.message);
// 显示错误提示
alert(error.message);
})
.finally(() => {
console.log("登录请求结束(无论成功/失败)");
// 关闭登录按钮的加载状态
document.getElementById("loginBtn").disabled = false;
});
5.2 Promise 链式调用:避免回调嵌套
每个then()都会返回一个新的Promise,支持连续调用,实现 “依次执行多个异步操作”:
// 步骤1:登录 → 步骤2:获取用户权限 → 步骤3:获取用户菜单
login("admin", "123456")
.then((userInfo) => {
// 步骤1成功:携带用户ID请求权限
return axios.get(`/api/permissions?userId=${userInfo.userId}`);
})
.then((permissionRes) => {
const permissions = permissionRes.data;
// 步骤2成功:携带权限请求菜单
return axios.get(`/api/menus?role=${permissions.role}`);
})
.then((menuRes) => {
const menus = menuRes.data;
console.log("用户菜单:", menus);
// 渲染菜单到页面
})
.catch((error) => {
// 任一环节失败,都会进入catch
console.error("流程失败:", error);
});
5.3 async/await:简化异步代码
async/await是 ES8 推出的语法糖,基于Promise实现,将异步代码 “同步化” 书写,更易读、易调试:
// 定义async函数(必须在async函数中使用await)
const initUser = async () => {
// 关闭按钮加载状态
const loginBtn = document.getElementById("loginBtn");
loginBtn.disabled = true;
try {
// 等待登录结果(暂停执行,直到Promise完成)
const userInfo = await login("admin", "123456");
console.log("登录成功:", userInfo);
// 等待权限请求结果
const permissionRes = await axios.get(`/api/permissions?userId=${userInfo.userId}`);
const permissions = permissionRes.data;
// 等待菜单请求结果
const menuRes = await axios.get(`/api/menus?role=${permissions.role}`);
const menus = menuRes.data;
console.log("用户菜单:", menus);
// 渲染菜单
renderMenus(menus);
} catch (error) {
// 所有await的错误,都会被catch捕获
console.error("初始化失败:", error);
alert(error.message);
} finally {
// 无论成功/失败,都会执行
loginBtn.disabled = false;
}
};
// 调用async函数
initUser();
5.4 Promise 静态方法:并行处理多个异步请求
Vue3 中常需同时请求多个接口(如 “用户信息 + 未读消息数”),Promise的静态方法可高效处理并行请求。
| 静态方法 | 功能描述 | 适用场景 |
|---|---|---|
Promise.all() |
接收多个 Promise,全部成功才返回结果数组;任一失败则立即返回错误 | 依赖多个接口数据才能渲染页面 |
Promise.race() |
接收多个 Promise,返回第一个完成的结果(无论成功 / 失败) | 超时控制(如 “接口 5 秒未响应则提示超时”) |
Promise.allSettled() |
接收多个 Promise,所有操作结束后返回每个结果的状态(status+value/reason) |
需知道所有请求的结果(成功 / 失败) |
Promise.any() |
接收多个 Promise,返回第一个成功的结果;全部失败才返回错误 | 多源数据备份(如 “优先请求 CDN,失败则请求本地”) |
示例:使用Promise.all()并行请求
// 并行请求“用户信息”和“未读消息数”
const fetchUserInfo = axios.get("/api/user/1");
const fetchUnreadCount = axios.get("/api/messages/unread");
Promise.all([fetchUserInfo, fetchUnreadCount])
.then(([userRes, countRes]) => {
const userInfo = userRes.data;
const unreadCount = countRes.data;
// 渲染用户信息和未读消息数
renderUserInfo(userInfo);
renderUnreadCount(unreadCount);
})
.catch((error) => {
console.error("任一请求失败:", error);
alert("数据加载失败,请刷新页面");
});
第六章:综合案例:Django+Vue3 实现产品管理系统
结合前文知识点,实现一个简单的 “产品管理系统”,包含 “产品列表展示”“产品添加”“产品删除” 功能,完整覆盖前后端交互、跨域配置、ORM 操作。
6.1 后端 Django 实现
1. 模型定义(models.py)
from django.db import models
class Product(models.Model):
"""产品模型"""
name = models.CharField(max_length=200, verbose_name="产品名称")
price = models.DecimalField(max_digits=10, decimal_places=2, verbose_name="产品价格")
description = models.TextField(blank=True, verbose_name="产品描述")
created_at = models.DateTimeField(auto_now_add=True, verbose_name="创建时间")
is_active = models.BooleanField(default=True, verbose_name="是否激活")
class Meta:
verbose_name = "产品"
verbose_name_plural = "产品"
ordering = ["-created_at"]
2. 视图函数(views.py)
import json
from django.http import JsonResponse
from django.shortcuts import get_object_or_404
from .models import Product
# 1. 获取产品列表
def get_products(request):
if request.method == "GET":
products = Product.objects.filter(is_active=True).values("id", "name", "price", "created_at")
products_list = list(products)
# 格式化日期
for p in products_list:
p["created_at"] = p["created_at"].strftime("%Y-%m-%d %H:%M:%S")
return JsonResponse({"status": "success", "data": products_list})
return JsonResponse({"status": "error", "message": "仅支持GET"}, status=405)
# 2. 添加产品
def add_product(request):
if request.method == "POST":
try:
data = json.loads(request.body)
name = data.get("name")
price = data.get("price")
description = data.get("description", "")
if not name or not price:
return JsonResponse({"status": "error", "message": "名称和价格不能为空"}, status=400)
product = Product.objects.create(
name=name,
price=price,
description=description
)
return JsonResponse({
"status": "success",
"data": {"id": product.id, "name": product.name}
}, status=201)
except json.JSONDecodeError:
return JsonResponse({"status": "error", "message": "数据格式错误"}, status=400)
return JsonResponse({"status": "error", "message": "仅支持POST"}, status=405)
# 3. 删除产品
def delete_product(request, pk):
if request.method == "DELETE":
product = get_object_or_404(Product, pk=pk)
product.delete()
return JsonResponse({"status": "success", "message": "产品已删除"})
return JsonResponse({"status": "error", "message": "仅支持DELETE"}, status=405)
3. URL 配置(urls.py)
from django.urls import path
from . import views
urlpatterns = [
path("products/", views.get_products, name="get_products"),
path("products/add/", views.add_product, name="add_product"),
path("products/delete/<int:pk>/", views.delete_product, name="delete_product"),
]
4. CORS 配置(settings.py)
INSTALLED_APPS = [
# ...其他应用
'corsheaders',
'myapp',
]
MIDDLEWARE = [
'corsheaders.middleware.CorsMiddleware',
# ...其他中间件
]
# 允许Vue3开发环境跨域
CORS_ALLOWED_ORIGINS = ["http://localhost:8080"]
CORS_ALLOW_METHODS = ["GET", "POST", "DELETE"]
CORS_ALLOW_HEADERS = ["Content-Type"]
6.2 前端 Vue3 实现
<template>
<div class="product-manager">
<h2>产品管理系统</h2>
<!-- 添加产品表单 -->
<div class="add-form">
<h3>添加产品</h3>
<form @submit.prevent="addProduct">
<div class="form-item">
<label>产品名称:</label>
<input type="text" v-model="form.name" required placeholder="请输入名称">
</div>
<div class="form-item">
<label>产品价格:</label>
<input type="number" step="0.01" v-model="form.price" required placeholder="请输入价格">
</div>
<div class="form-item">
<label>产品描述:</label>
<textarea v-model="form.description" placeholder="请输入描述"></textarea>
</div>
<button type="submit" :disabled="loading">
{{ loading ? "添加中..." : "添加产品" }}
</button>
</form>
</div>
<!-- 产品列表 -->
<div class="product-list">
<h3>产品列表</h3>
<div v-if="loadingList">加载中...</div>
<div v-else-if="errorList">{{ errorList }}</div>
<table v-else>
<thead>
<tr>
<th>ID</th>
<th>名称</th>
<th>价格</th>
<th>创建时间</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr v-for="product in products" :key="product.id">
<td>{{ product.id }}</td>
<td>{{ product.name }}</td>
<td>¥{{ product.price }}</td>
<td>{{ product.created_at }}</td>
<td>
<button @click="deleteProduct(product.id)" :disabled="loadingDelete === product.id">
{{ loadingDelete === product.id ? "删除中..." : "删除" }}
</button>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import axios from 'axios';
// 表单数据
const form = ref({
name: "",
price: "",
description: ""
});
// 列表数据
const products = ref([]);
const loadingList = ref(false);
const errorList = ref("");
// 加载状态
const loading = ref(false);
const loadingDelete = ref(null);
// 获取产品列表
const fetchProducts = async () => {
loadingList.value = true;
errorList.value = "";
try {
const res = await axios.get("/api/products/");
products.value = res.data.data;
} catch (err) {
errorList.value = "获取产品列表失败";
console.error(err);
} finally {
loadingList.value = false;
}
};
// 添加产品
const addProduct = async () => {
loading.value = true;
try {
await axios.post("/api/products/add/", form.value);
alert("产品添加成功!");
// 重置表单
form.value = { name: "", price: "", description: "" };
// 重新加载列表
fetchProducts();
} catch (err) {
alert(err.response?.data?.message || "添加失败");
console.error(err);
} finally {
loading.value = false;
}
};
// 删除产品
const deleteProduct = async (id) => {
if (!confirm("确定要删除该产品吗?")) return;
loadingDelete.value = id;
try {
await axios.delete(`/api/products/delete/${id}/`);
alert("产品删除成功!");
// 重新加载列表
fetchProducts();
} catch (err) {
alert(err.response?.data?.message || "删除失败");
console.error(err);
} finally {
loadingDelete.value = null;
}
};
// 组件挂载时加载列表
onMounted(() => {
fetchProducts();
});
</script>
<style scoped>
.product-manager { max-width: 1200px; margin: 20px auto; padding: 0 20px; }
.add-form { margin-bottom: 30px; padding: 20px; border: 1px solid #eee; border-radius: 4px; }
.form-item { margin-bottom: 15px; }
label { display: block; margin-bottom: 5px; font-weight: bold; }
input, textarea { width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px; }
button { padding: 8px 16px; border: none; border-radius: 4px; cursor: pointer; }
.add-form button { background: #28a745; color: white; }
.product-list button { background: #dc3545; color: white; }
button:disabled { background: #6c757d; cursor: not-allowed; }
table { width: 100%; border-collapse: collapse; margin-top: 10px; }
th, td { padding: 12px; text-align: left; border-bottom: 1px solid #ddd; }
th { background: #f8f9fa; }
</style>
第七章:总结
本文从 Django 的视图路由、ORM 高级操作,到 Vue3 的表单交互、异步处理,再到跨域问题解决,完整覆盖了全栈开发的核心环节。通过实战案例,展示了如何从 0 到 1 实现前后端交互功能,关键总结如下:
- 视图路由:路径转换器与正则表达式实现灵活 URL 匹配,命名空间解决多 App 路由冲突,装饰器增强视图功能;
- ORM 操作:高级查询、批量操作、关联查询优化是提升后端性能的关键,避免 N+1 查询问题;
- 前后端交互:Vue3 通过 Axios 发送 JSON / 表单数据,Django 通过
request.body/request.POST接收,CORS 配置解决跨域问题; - 异步处理:
Promise管理异步状态,async/await简化异步代码,静态方法高效处理并行请求。
如果实践操作中遇到其他问题,也可以在评论区留言,Fly帮你在线答疑!!!
更多推荐

所有评论(0)