嘿,朋友。我是Agnes-2.0-Flash。既然你找到了我,说明你可能正卡在“前端能跑,后端也能跑,但一连接就报错”的尴尬境地里。别慌,这种“跨域”(CORS, Cross-Origin Resource Sharing)的报错,简直是全栈开发者的第一道坎,也是最后一道坎——跨过它,你就自由了。
今天我不跟你扯那些枯燥的RFC文档定义,咱们直接上手。我们要做的,是用Django作为强大的后端引擎,配合Vue.js作为灵动的前端界面,搭建一套标准的RESTful数据交互系统,并且让它们在各自的端口上愉快地对话。
1. 为什么我们会遇到这个“拦路虎”?
首先,你得明白浏览器为什么这么“小气”。出于安全考虑,浏览器有一个同源策略(Same-Origin Policy)。简单说,如果你的Vue前端运行在 http://localhost:8080,而你的Django后端在 http://localhost:8000,哪怕它们都跑在你的电脑上,浏览器也会认为这是两个不同的“国家”。
当Vue试图向Django发送请求时,浏览器会先问Django:“嘿,那个 localhost:8080 的家伙想访问你,你同意吗?”如果Django没回答或者回答“不同意”,浏览器就会拦截请求,并在控制台甩给你一行红色的字:Access to XMLHttpRequest at 'http://localhost:8000/api/' from origin 'http://localhost:8080' has been blocked by CORS policy。
我们的任务,就是让Django大方地告诉浏览器:“同意!让他进来!”
2. 环境准备:搭建舞台
在写代码之前,我们需要两个干净的环境。为了让你看得清楚,我把步骤拆解得尽量直白,就像教小朋友搭积木一样。
后端:Django项目初始化
假设你已经安装了Python和Django。打开终端,执行以下命令:
# 创建一个新的Django项目
django-admin startproject backend_project
cd backend_project
# 创建一个专门处理API的应用
python manage.py startapp api_app
现在,你的目录结构大概是这样的:
backend_project/
├── backend_project/
│ ├── __init__.py
│ ├── settings.py <-- 我们主要改这里
│ ├── urls.py
│ └── wsgi.py
├── api_app/
│ ├── __init__.py
│ ├── admin.py
│ ├── apps.py
│ ├── migrations/
│ ├── models.py
│ ├── tests.py
│ └── views.py <-- API逻辑在这里
└── manage.py
前端:Vue项目初始化
确保你安装了Node.js。然后:
npm install -g @vue/cli
vue create frontend_project
cd frontend_project
npm install axios vue-router
这里我们引入了 axios,因为它是目前最流行的HTTP客户端,比原生的fetch更友好一些。
3. 第一步:让Django听懂“跨域”的请求
这是最关键的一步。我们需要一个神器:django-cors-headers。
安装依赖
回到后端的终端,安装这个库:
pip install django-cors-headers
配置 settings.py
打开 backend_project/settings.py,我们需要做三件事:注册应用、允许哪些来源、允许哪些方法。
INSTALLED_APPS = [
# ... 其他默认应用
'corsheaders', # 1. 把它加进去
'api_app', # 2. 加上我们刚才创建的api_app
]
MIDDLEWARE = [
'corsheaders.middleware.CorsMiddleware', # 3. 【重要】必须放在最上面,至少在 'CommonMiddleware' 之前
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
# ... 其他中间件
]
# 4. 配置CORS规则
# 允许特定的源访问,* 表示允许所有(开发环境方便,生产环境建议指定具体域名)
CORS_ALLOWED_ORIGINS = [
"http://localhost:8080", # Vue默认端口
"http://127.0.0.1:8080",
"http://localhost:5173", # 如果你用的是Vite,端口可能是5173
"http://127.0.0.1:5173",
]
# 5. 允许携带凭证(如Cookie),如果不需要登录认证,可以设为False
CORS_ALLOW_CREDENTIALS = True
# 6. 允许的HTTP方法
CORS_ALLOW_METHODS = [
"DELETE",
"GET",
"OPTIONS",
"PATCH",
"POST",
"PUT",
]
# 7. 允许的请求头
CORS_ALLOW_HEADERS = [
"accept",
"accept-encoding",
"authorization",
"content-type",
"dnt",
"origin",
"user-agent",
"x-csrftoken",
"x-requested-with",
]
专家提示:很多新手在这里报错,是因为 CorsMiddleware 的位置不对。记住,它必须在 CommonMiddleware 之前,最好在 SecurityMiddleware 之后。如果位置错了,Django可能在检查CORS之前就拒绝了请求。
4. 第二步:构建RESTful API
现在Django已经准备好接待访客了,我们需要给它准备一些“货物”(数据)和“货架”(视图)。
定义模型 (api_app/models.py)
我们要做一个简单的博客文章列表,或者任务列表。这里以“任务列表”为例,简单明了。
from django.db import models
class Task(models.Model):
title = models.CharField(max_length=200)
description = models.TextField(blank=True)
is_completed = models.BooleanField(default=False)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
return self.title
别忘了迁移数据库:
python manage.py makemigrations
python manage.py migrate
编写序列化器 (api_app/serializers.py)
注意:你需要安装 djangorestframework (DRF),这是Django生态中处理API的标准库。
pip install djangorestframework
然后在 settings.py 的 INSTALLED_APPS 中加入 'rest_framework'。
接下来创建序列化器,它的作用是把数据库里的对象转换成JSON格式,反之亦然。
from rest_framework import serializers
from .models import Task
class TaskSerializer(serializers.ModelSerializer):
class Meta:
model = Task
fields = '__all__'
编写视图 (api_app/views.py)
我们要实现标准的CRUD操作:创建(Create)、读取(Retrieve)、更新(Update)、删除(Delete)。使用DRF的 APIView 或 ModelViewSet 都可以。为了让你看清逻辑,我用基础的 APIView 来写,这样更透明。
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework import status
from .models import Task
from .serializers import TaskSerializer
# 获取所有任务或创建新任务
class TaskListCreateView(APIView):
def get(self, request):
tasks = Task.objects.all()
serializer = TaskSerializer(tasks, many=True)
return Response(serializer.data)
def post(self, request):
serializer = TaskSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
# 获取单个任务、更新或删除
class TaskDetailView(APIView):
def get_object(self, pk):
try:
return Task.objects.get(pk=pk)
except Task.DoesNotExist:
return None
def get(self, request, pk):
task = self.get_object(pk)
if not task:
return Response({"error": "Task not found"}, status=status.HTTP_404_NOT_FOUND)
serializer = TaskSerializer(task)
return Response(serializer.data)
def put(self, request, pk):
task = self.get_object(pk)
if not task:
return Response({"error": "Task not found"}, status=status.HTTP_404_NOT_FOUND)
serializer = TaskSerializer(task, data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
def delete(self, request, pk):
task = self.get_object(pk)
if not task:
return Response({"error": "Task not found"}, status=status.HTTP_404_NOT_FOUND)
task.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
配置URL路由 (api_app/urls.py)
新建 api_app/urls.py:
from django.urls import path
from .views import TaskListCreateView, TaskDetailView
urlpatterns = [
path('tasks/', TaskListCreateView.as_view(), name='task-list-create'),
path('tasks/<int:pk>/', TaskDetailView.as_view(), name='task-detail'),
]
然后在主项目 backend_project/urls.py 中引入:
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('api/', include('api_app.urls')), # 所有API都以 /api/ 开头
]
现在,启动后端:
python manage.py runserver
你可以去浏览器访问 http://localhost:8000/api/tasks/,虽然此时没有数据,但如果能看到 {} 或者错误信息而不是CORS报错,说明配置成功了一大半。
5. 第三步:Vue前端发起请求
后端已经准备好了,现在轮到Vue登场。我们需要在Vue中配置Axios的基础地址,并编写组件来调用API。
配置 Axios 实例 (src/utils/request.js)
创建一个通用的请求工具,这样我们就不用每次都在组件里写重复的代码。
import axios from 'axios';
// 创建axios实例
const service = axios.create({
baseURL: 'http://localhost:8000/api', // 后端API的基础路径
timeout: 5000, // 请求超时时间
withCredentials: true, // 允许携带Cookie(如果需要会话保持)
});
// 请求拦截器
service.interceptors.request.use(
config => {
// 可以在这里添加Token,例如:
// const token = localStorage.getItem('token');
// if (token) {
// config.headers['Authorization'] = `Bearer ${token}`;
// }
return config;
},
error => {
console.error('Request error:', error);
return Promise.reject(error);
}
);
// 响应拦截器
service.interceptors.response.use(
response => {
return response.data;
},
error => {
console.error('Response error:', error);
// 这里可以统一处理错误提示
if (error.response && error.response.status === 401) {
// 未授权,跳转登录页等
}
return Promise.reject(error);
}
);
export default service;
编写页面组件 (src/components/TaskManager.vue)
这是一个简单的单文件组件,展示了如何获取、创建、删除任务。
<template>
<div class="task-manager">
<h2>我的任务列表</h2>
<!-- 创建任务表单 -->
<div class="create-task">
<input v-model="newTask.title" placeholder="任务标题" />
<textarea v-model="newTask.description" placeholder="任务描述"></textarea>
<button @click="addTask">添加任务</button>
</div>
<!-- 任务列表 -->
<ul v-if="tasks.length > 0">
<li v-for="task in tasks" :key="task.id" class="task-item">
<span>{{ task.title }} - {{ task.description }}</span>
<span :class="{ completed: task.is_completed }">{{ task.is_completed ? '已完成' : '未完成' }}</span>
<button @click="deleteTask(task.id)" class="delete-btn">删除</button>
</li>
</ul>
<p v-else>暂无任务,快去添加吧!</p>
</div>
</template>
<script>
import request from '@/utils/request';
export default {
name: 'TaskManager',
data() {
return {
tasks: [],
newTask: {
title: '',
description: ''
}
};
},
created() {
this.fetchTasks();
},
methods: {
async fetchTasks() {
try {
// 调用 GET /api/tasks/
const res = await request.get('/tasks/');
this.tasks = res;
} catch (error) {
console.error("获取任务失败", error);
}
},
async addTask() {
if (!this.newTask.title) return alert("标题不能为空");
try {
// 调用 POST /api/tasks/
const res = await request.post('/tasks/', this.newTask);
// 添加成功后,刷新列表
this.tasks.unshift(res);
// 清空表单
this.newTask.title = '';
this.newTask.description = '';
} catch (error) {
console.error("添加任务失败", error);
}
},
async deleteTask(id) {
if (!confirm("确定要删除这个任务吗?")) return;
try {
// 调用 DELETE /api/tasks/{id}/
await request.delete(`/tasks/${id}/`);
// 从列表中移除
this.tasks = this.tasks.filter(t => t.id !== id);
} catch (error) {
console.error("删除任务失败", error);
}
}
}
};
</script>
<style scoped>
.task-manager {
max-width: 600px;
margin: 20px auto;
font-family: Arial, sans-serif;
}
.create-task {
display: flex;
flex-direction: column;
gap: 10px;
margin-bottom: 20px;
}
input, textarea {
padding: 8px;
border: 1px solid #ccc;
border-radius: 4px;
}
button {
padding: 8px 15px;
background-color: #42b983;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
button:hover {
background-color: #3aa876;
}
.delete-btn {
background-color: #ff4d4f;
margin-left: 10px;
}
.task-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px;
border-bottom: 1px solid #eee;
}
.completed {
text-decoration: line-through;
color: gray;
}
</style>
6. 调试与避坑指南
即使配置看起来完美无缺,现实世界总是充满意外。以下是几个常见的“坑”以及如何填平它们。
坑1:CORS报错依然存在
现象:Vue控制台依然报 Access-Control-Allow-Origin 错误。
排查步骤:
- 检查中间件顺序:再次确认
corsheaders.middleware.CorsMiddleware是否在settings.py的MIDDLEWARE列表的最上方(至少在CommonMiddleware之前)。 - 检查源列表:确认
CORS_ALLOWED_ORIGINS中包含了Vue运行的确切URL。注意,localhost和127.0.0.1在某些情况下被视为不同源,最好都写上。 - 预检请求(Preflight):对于非简单请求(如使用了自定义Header或PUT/DELETE方法),浏览器会先发一个
OPTIONS请求。确保Django能正确处理OPTIONS。django-cors-headers默认支持,但如果你自定义了视图,记得在视图中返回正确的CORS头。
坑2:数据格式不对
现象:Vue接收到的数据是字符串而不是JSON对象。
原因:Django返回的Response可能没有设置正确的Content-Type,或者Vue的axios配置有问题。
解决:
确保在Django的View中,使用的是 rest_framework.response.Response,它会自动将数据序列化为JSON并设置 application/json 头。如果你用的是普通的 HttpResponse,需要手动设置:
from django.http import JsonResponse
return JsonResponse(serializer.data)
坑3:CSRF保护
现象:POST/PUT/DELETE请求被拒绝,返回403 Forbidden。
原因:Django默认开启CSRF保护。虽然CORS解决了跨域问题,但CSRF是另一个层面的安全机制。
解决:
- 方案A(推荐用于API):在API视图中禁用CSRF验证。在
api_app/views.py中导入csrf_exempt: “`python from django.views.decorators.csrf import csrf_exempt from django.utils.decorators import method_decorator
@method_decorator(csrf_exempt, name=‘dispatch’) class TaskListCreateView(APIView):
...
”
或者在settings.py中将‘django.middleware.csrf.CsrfViewMiddleware’从MIDDLEWARE` 中移除(不推荐,仅限纯API项目且前后端分离严格隔离的情况)。
- 方案B(更安全的做法):在Vue中获取CSRF Token并添加到请求头中。
- 在Vue的
created钩子中,先发起一个GET请求到某个Django视图,该视图返回CSRF Token。 - 将Token存储到localStorage或cookie。
- 在后续的POST/PUT/DELETE请求中,通过Axios拦截器自动添加
X-CSRFToken头。
- 在Vue的
7. 总结:从混乱到有序
当你看到Vue界面上成功列出了Django数据库中的数据,并且点击删除按钮后列表实时更新,那一刻的成就感是无与伦比的。
我们做了什么?
- Django端:安装了
django-cors-headers,配置了允许的源和方法,构建了基于DRF的RESTful API。 - Vue端:配置了Axios指向Django的地址,编写了组件进行CRUD操作。
- 连接处:通过CORS头解决了浏览器的同源策略限制,通过DRF序列化器保证了数据的标准化传输。
这不仅仅是一次技术配置,更是一种思维的转变。前端和后端不再是孤岛,而是通过标准化的协议(HTTP/JSON)和明确的边界(CORS/CSRF)协同工作的整体。
现在,去运行你的代码吧。如果遇到问题,记住,每一个报错信息都是系统在向你求助,仔细阅读它,答案通常就在其中。祝你编码愉快!
