1. 项目概述:为什么选择Django+Vue?
如果你正在寻找一个既能快速构建健壮后端,又能提供现代化、流畅前端体验的全栈解决方案,那么Django + Vue的组合,几乎是一个“开箱即用”的黄金搭档。我这些年带过不少项目,从内部管理系统到对外服务的SaaS平台,这个技术栈的出场率极高。它解决的,本质上是一个“效率”与“体验”如何兼得的问题。
Django,这个基于Python的“全能型”后端框架,以其“自带电池”的哲学闻名。你几乎不用为ORM(对象关系映射)、用户认证、后台管理这些基础又繁琐的事情发愁,它都给你准备好了。这意味着,你的团队可以把更多精力放在核心业务逻辑,而不是重复造轮子上。而Vue.js,作为当前最主流的前端框架之一,其渐进式的特性和响应式数据绑定,让构建复杂交互的单页面应用变得异常清晰和高效。前后端分离后,前端和后端可以并行开发,通过API接口进行通信,职责清晰,也便于后续的独立部署和扩展。
这个组合适合谁呢?如果你是Python后端开发者,想涉足全栈,Vue的学习曲线相对平缓,是绝佳的切入点。如果你是前端开发者,希望有一个稳定、规范的后端提供数据支持,Django的Rest Framework能提供一套非常优雅的API构建方案。对于初创团队或需要快速验证产品的场景,这个组合能极大压缩开发周期。接下来,我就以一个典型的用户文章管理系统为例,拆解从零到一的搭建全过程,并分享那些只有踩过坑才知道的细节。
2. 技术栈选型与项目初始化
2.1 后端基石:Django与Django REST Framework
选择Django,不仅仅是选了一个框架,更是选择了一个完整的生态系统。对于Web开发中80%的通用需求,比如用户模型、会话管理、后台Admin、数据库迁移,Django都提供了成熟、安全的实现。这能避免我们在项目初期陷入各种细节陷阱。
然而,在前后端分离的架构中,Django原生的模板渲染引擎就不再是我们的首选了。这时,Django REST Framework就必须登场。DRF是基于Django的一个强大而灵活的工具包,专门用于构建Web API。它提供了序列化、视图集、权限验证、分页、过滤等一整套工具,能让开发者以极少的代码构建出功能全面、符合RESTful规范的API接口。可以说,DRF是连接Django后端与Vue前端的桥梁。
在项目初始化时,我的习惯是创建一个干净的虚拟环境,这能有效隔离项目依赖。然后,通过pip安装核心包。这里有一个版本搭配的经验之谈:尽量选择经过市场长期检验的稳定版本组合,而不是盲目追新。
# 创建并进入项目目录 mkdir django_vue_project && cd django_vue_project # 创建Python虚拟环境(推荐使用venv) python -m venv venv # 激活虚拟环境 # Windows: venv\Scripts\activate # Linux/Mac: source venv/bin/activate # 安装核心依赖 pip install django==4.2 # 选择一个LTS长期支持版本 pip install djangorestframework==3.14 pip install django-cors-headers==4.2 # 处理跨域请求,前后端分离必备安装完成后,使用Django的命令创建项目和应用。这里我建议将后端API相关的应用放在一个独立的目录下,比如backend,使结构更清晰。
# 创建Django项目(backend作为项目根目录) django-admin startproject backend . # 创建一个名为`api`的应用,专门处理核心业务API python manage.py startapp api接着,需要修改backend/settings.py文件,将我们安装的应用和必要的配置添加进去。跨域是前后端分离的第一个拦路虎,必须优先解决。
# backend/settings.py INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', # 第三方应用 'rest_framework', 'corsheaders', # 本地应用 'api', ] MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'corsheaders.middleware.CorsMiddleware', # 注意:这个要放在CommonMiddleware之前 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', ] # 配置CORS白名单,允许前端域名的请求 # 在开发阶段,可以暂时允许所有来源,但上线前务必收紧! CORS_ALLOWED_ORIGINS = [ "http://localhost:8080", # Vue CLI默认开发服务器端口 "http://127.0.0.1:8080", ] # 或者,简单粗暴地在开发阶段允许所有(不推荐用于生产) # CORS_ALLOW_ALL_ORIGINS = True # 允许携带Cookie等凭证(如果前端请求需要认证信息) CORS_ALLOW_CREDENTIALS = True注意:
CORS_ALLOW_ALL_ORIGINS = True是一个在开发初期图省事的配置,它会让你的后端接受来自任何源的请求。这在生产环境中是极其危险的,相当于敞开了大门。务必在部署前将其注释掉,并严格配置CORS_ALLOWED_ORIGINS列表,只包含你信任的前端域名(如你的生产环境域名)。
2.2 前端利器:Vue CLI与生态圈
前端我们选择Vue 3和其官方的构建工具链Vue CLI(或Vite)。Vue CLI提供了标准化的项目脚手架和开发服务器,集成了热重载、代码检查等现代开发特性。对于状态管理,中型以上项目我推荐使用Pinia(Vuex的官方继承者),它更简洁,且完美支持TypeScript。对于HTTP请求,axios是事实上的标准。
初始化Vue项目时,我习惯将其放在与backend同级的frontend目录中。
# 回到项目根目录(django_vue_project) cd .. # 使用Vue CLI创建项目(确保已全局安装@vue/cli) vue create frontend # 在创建过程中,手动选择特性(Manually select features) # 我通常必选:Babel, Router, Vuex/Pinia, Linter/Formatter # Vue版本选择3.x # 其他配置如历史模式、代码检查规则按需选择。创建完成后,进入frontend目录安装额外的常用依赖。
cd frontend npm install axios pinia # 如果需要UI组件库,也可以一并安装,如Element Plus # npm install element-plus @element-plus/icons-vue至此,一个前后端分离项目的基础骨架就搭建好了。后端在backend目录下运行于http://127.0.0.1:8000,前端在frontend目录下运行于http://localhost:8080。接下来,我们要让它们真正“对话”起来。
3. 核心通信机制:RESTful API设计与实现
前后端分离的核心是API。设计一套清晰、一致、符合规范的API,是项目可持续发展的基石。我们以“文章”这个资源为例,实现一套完整的CRUD(增删改查)API。
3.1 定义数据模型与序列化器
首先,在后端的api/models.py中定义文章模型。
# api/models.py from django.db import models from django.contrib.auth.models import User class Article(models.Model): # 文章标题 title = models.CharField(max_length=200) # 文章内容,使用TextField以存储长文本 content = models.TextField() # 创建时间,自动设置为对象首次创建的时间 created_at = models.DateTimeField(auto_now_add=True) # 更新时间,每次对象保存时自动更新 updated_at = models.DateTimeField(auto_now=True) # 作者,关联到Django内置的User模型 # on_delete=models.CASCADE 表示当用户被删除时,其所有文章也被删除 author = models.ForeignKey(User, on_delete=models.CASCADE, related_name='articles') def __str__(self): return self.title class Meta: # 默认按创建时间倒序排列,让最新的文章排在最前面 ordering = ['-created_at']定义好模型后,需要创建并应用数据库迁移。
python manage.py makemigrations api python manage.py migrate接下来,使用DRF的序列化器(Serializer)将模型实例转换为JSON等格式,反之亦然。它在API视图和数据库模型之间扮演着“翻译官”的角色。
# api/serializers.py from rest_framework import serializers from .models import Article from django.contrib.auth.models import User class UserSerializer(serializers.ModelSerializer): class Meta: model = User # 只暴露id和用户名,避免返回密码等敏感信息 fields = ['id', 'username'] class ArticleSerializer(serializers.ModelSerializer): # 嵌套序列化作者信息,只读(read_only),在创建文章时不需要前端传入作者ID author = UserSerializer(read_only=True) class Meta: model = Article # 指定需要序列化的字段 fields = ['id', 'title', 'content', 'created_at', 'updated_at', 'author'] # 设置created_at和updated_at为只读,由服务器自动维护 read_only_fields = ['created_at', 'updated_at', 'author']这里的关键点在于author字段的处理。我们将其设置为read_only=True,意味着在创建或更新文章时,DRF不会要求前端传入author数据。那么作者信息从哪里来?通常是从当前登录用户的请求上下文中获取。这引出了下一个核心概念:权限认证。
3.2 构建视图集与路由配置
DRF的视图集(ViewSet)和路由器(Router)能极大简化视图和URL配置。一个ModelViewSet就默认提供了列表、创建、检索、更新、部分更新、销毁这六个动作对应的视图。
# api/views.py from rest_framework import viewsets, permissions from rest_framework.response import Response from .models import Article from .serializers import ArticleSerializer class ArticleViewSet(viewsets.ModelViewSet): """ 文章视图集,提供标准的CRUD操作。 """ queryset = Article.objects.all() serializer_class = ArticleSerializer # 设置权限:只有认证用户才能创建、修改、删除文章;未认证用户只能查看列表和详情。 permission_classes = [permissions.IsAuthenticatedOrReadOnly] def perform_create(self, serializer): """ 重写创建方法,在保存序列化器之前,将当前登录用户设置为文章作者。 这是DRF中一个非常常用的钩子函数。 """ serializer.save(author=self.request.user)perform_create是一个重要的钩子方法。当客户端发起POST请求创建文章时,DRF会先验证数据,然后调用这个方法。在这里,我们手动将request.user(即当前登录的用户对象)赋值给序列化器的author字段,再保存到数据库。这样就安全地关联了文章和作者,无需前端传递作者ID。
接下来,使用DRF的DefaultRouter自动生成URL路由。
# api/urls.py from django.urls import path, include from rest_framework.routers import DefaultRouter from .views import ArticleViewSet # 创建路由器并注册我们的视图集 router = DefaultRouter() router.register(r'articles', ArticleViewSet) # urlpatterns将被包含到项目的主URL配置中 urlpatterns = [ path('', include(router.urls)), ]最后,将api应用的路由包含到项目主路由中。
# backend/urls.py from django.contrib import admin from django.urls import path, include urlpatterns = [ path('admin/', admin.site.urls), path('api/', include('api.urls')), # 所有API路径都以/api/开头 ]现在,启动Django开发服务器(python manage.py runserver),访问http://127.0.0.1:8000/api/articles/,你应该能看到DRF提供的可浏览API界面。由于我们设置了IsAuthenticatedOrReadOnly权限,未登录时你可以看到文章列表(GET),但尝试创建(POST)会返回403禁止访问。这证明了我们的基础API已经正常工作。
4. 前端Vue项目与后端API对接
后端API就绪后,前端的工作就是调用这些接口,并将数据渲染到页面上。我们首先需要配置一个全局的HTTP请求客户端。
4.1 配置Axios与全局状态管理
在frontend/src目录下,我习惯创建一个utils或services文件夹来存放与后端通信的模块。
// frontend/src/services/api.js import axios from 'axios'; // 创建一个axios实例,配置基础URL和超时时间 const apiClient = axios.create({ baseURL: 'http://127.0.0.1:8000/api', // 指向Django后端API的地址 timeout: 10000, // 10秒超时 headers: { 'Content-Type': 'application/json', }, }); // 请求拦截器:在发送请求前做些什么 apiClient.interceptors.request.use( (config) => { // 从本地存储(如localStorage)获取token const token = localStorage.getItem('access_token'); if (token) { // 如果存在token,将其添加到请求头中,这是JWT等Token认证的标准方式 config.headers.Authorization = `Bearer ${token}`; } return config; }, (error) => { return Promise.reject(error); } ); // 响应拦截器:在收到响应后做些什么 apiClient.interceptors.response.use( (response) => { // 对响应数据做点什么,比如直接返回data部分 return response.data; }, (error) => { // 对响应错误做点什么,比如统一处理401未授权错误,跳转到登录页 if (error.response && error.response.status === 401) { console.error('未授权,请重新登录'); // 这里可以触发Vue Router跳转到登录页面 // router.push('/login'); } // 将错误继续抛给具体的请求调用处处理 return Promise.reject(error); } ); export default apiClient;接下来,使用Pinia来管理全局状态,比如文章列表和用户登录状态。
// frontend/src/stores/articleStore.js import { defineStore } from 'pinia'; import { ref } from 'vue'; import apiClient from '@/services/api'; export const useArticleStore = defineStore('article', () => { // 状态 const articles = ref([]); const currentArticle = ref(null); const isLoading = ref(false); const error = ref(null); // 操作(Actions) const fetchArticles = async () => { isLoading.value = true; error.value = null; try { const response = await apiClient.get('/articles/'); articles.value = response; // 因为拦截器已经返回了data } catch (err) { error.value = err.message || '获取文章列表失败'; console.error('Fetch articles error:', err); } finally { isLoading.value = false; } }; const createArticle = async (articleData) => { isLoading.value = true; error.value = null; try { const response = await apiClient.post('/articles/', articleData); // 创建成功后,将新文章添加到列表开头 articles.value.unshift(response); return response; // 返回创建的文章,便于后续操作 } catch (err) { error.value = err.message || '创建文章失败'; console.error('Create article error:', err); throw err; // 抛出错误,让组件可以捕获并处理 } finally { isLoading.value = false; } }; // 其他操作:fetchArticleById, updateArticle, deleteArticle... // 计算属性(Getters)如果需要的话 const articleCount = computed(() => articles.value.length); return { // 状态 articles, currentArticle, isLoading, error, // 操作 fetchArticles, createArticle, // 计算属性 articleCount, }; });4.2 实现文章列表与创建页面
有了状态管理,组件就可以变得非常简洁。我们创建一个文章列表页。
<!-- frontend/src/views/ArticleListView.vue --> <template> <div class="article-list"> <h1>文章列表</h1> <div v-if="articleStore.isLoading">加载中...</div> <div v-else-if="articleStore.error" class="error">{{ articleStore.error }}</div> <div v-else> <ul> <li v-for="article in articleStore.articles" :key="article.id"> <router-link :to="`/article/${article.id}`"> <h3>{{ article.title }}</h3> <p>作者:{{ article.author.username }} | 发布于:{{ formatDate(article.created_at) }}</p> </router-link> </li> </ul> <button @click="goToCreate">写新文章</button> </div> </div> </template> <script setup> import { onMounted } from 'vue'; import { useRouter } from 'vue-router'; import { useArticleStore } from '@/stores/articleStore'; const router = useRouter(); const articleStore = useArticleStore(); // 组件挂载时获取文章列表 onMounted(() => { articleStore.fetchArticles(); }); const goToCreate = () => { router.push('/article/create'); }; const formatDate = (dateString) => { // 简单的日期格式化函数 return new Date(dateString).toLocaleDateString(); }; </script>再创建一个文章创建页。
<!-- frontend/src/views/ArticleCreateView.vue --> <template> <div class="article-create"> <h1>创建新文章</h1> <form @submit.prevent="handleSubmit"> <div> <label for="title">标题:</label> <input type="text" id="title" v-model="form.title" required /> </div> <div> <label for="content">内容:</label> <textarea id="content" v-model="form.content" rows="10" required></textarea> </div> <button type="submit" :disabled="articleStore.isLoading">提交</button> <div v-if="articleStore.error" class="error">{{ articleStore.error }}</div> </form> </div> </template> <script setup> import { ref } from 'vue'; import { useRouter } from 'vue-router'; import { useArticleStore } from '@/stores/articleStore'; const router = useRouter(); const articleStore = useArticleStore(); const form = ref({ title: '', content: '', }); const handleSubmit = async () => { try { await articleStore.createArticle(form.value); // 创建成功,跳转回列表页 router.push('/articles'); } catch (error) { // 错误已在store中处理并显示,这里可以做一些额外的UI反馈 console.error('提交失败:', error); } }; </script>实操心得:在表单提交时,使用
@submit.prevent阻止默认的页面刷新行为,这是Vue中的标准做法。将加载状态和错误状态交给Pinia Store管理,可以使组件逻辑更清晰,也便于在不同组件间共享这些状态。v-model的双向绑定让表单处理变得极其简单。
5. 用户认证与权限控制实战
一个完整的系统离不开用户认证。Django REST Framework提供了多种认证方案,这里我们采用最流行的Token认证(或更现代的JWT)结合DRF的权限类。
5.1 后端实现Token认证
首先,安装DRF的Token认证支持。
pip install djangorestframework-simplejwtdjango-rest-framework-simplejwt是一个流行的JWT插件,比DRF自带的Token认证更安全、更灵活。配置settings.py。
# backend/settings.py INSTALLED_APPS = [ # ... 'rest_framework', 'rest_framework_simplejwt', # 添加 # ... ] REST_FRAMEWORK = { 'DEFAULT_AUTHENTICATION_CLASSES': ( # 配置默认的认证类,JWT认证优先 'rest_framework_simplejwt.authentication.JWTAuthentication', 'rest_framework.authentication.SessionAuthentication', # 保留,便于在DRF浏览界面登录 ), 'DEFAULT_PERMISSION_CLASSES': [ # 设置全局默认权限:所有接口默认都需要认证才能访问 'rest_framework.permissions.IsAuthenticated', ], } # JWT配置 from datetime import timedelta SIMPLE_JWT = { 'ACCESS_TOKEN_LIFETIME': timedelta(minutes=60), # Access Token有效期 'REFRESH_TOKEN_LIFETIME': timedelta(days=1), # Refresh Token有效期 'ROTATE_REFRESH_TOKENS': False, 'BLACKLIST_AFTER_ROTATION': True, }注意:这里将全局默认权限设为
IsAuthenticated,意味着所有API默认都需要登录。对于像文章列表、详情这种需要公开访问的接口,我们需要在对应的视图上显式地覆盖这个权限,比如之前用的IsAuthenticatedOrReadOnly。这是一种“默认拒绝,显式允许”的安全策略。
然后,在项目的urls.py中添加JWT的认证端点。
# backend/urls.py from django.urls import path, include from rest_framework_simplejwt.views import TokenObtainPairView, TokenRefreshView urlpatterns = [ # ... path('api/token/', TokenObtainPairView.as_view(), name='token_obtain_pair'), path('api/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'), # ... ]现在,前端可以通过向/api/token/发送POST请求(携带username和password)来获取一对Token(access和refresh)。access token用于访问需要认证的接口,通常放在请求头的Authorization: Bearer <access_token>中。当access token过期后,可以使用refresh token向/api/token/refresh/端点换取新的access token。
5.2 前端登录与Token管理
在前端,我们需要创建登录逻辑,并将获取到的Token妥善存储。
// frontend/src/stores/authStore.js import { defineStore } from 'pinia'; import { ref } from 'vue'; import { useRouter } from 'vue-router'; import apiClient from '@/services/api'; export const useAuthStore = defineStore('auth', () => { const router = useRouter(); const user = ref(null); const isAuthenticated = ref(!!localStorage.getItem('access_token')); const login = async (credentials) => { try { // 注意:这里直接调用axios实例,不使用拦截器,因为登录请求本身不需要token const response = await apiClient.post('/token/', credentials); const { access, refresh } = response; // 将token存储到localStorage localStorage.setItem('access_token', access); localStorage.setItem('refresh_token', refresh); // 更新状态 isAuthenticated.value = true; // 可以再调用一个获取用户信息的接口来填充user.value // await fetchUserProfile(); // 登录成功,跳转到首页或目标页 router.push('/'); return true; } catch (error) { console.error('登录失败:', error); throw error; // 将错误抛给组件处理 } }; const logout = () => { // 清除本地存储的token localStorage.removeItem('access_token'); localStorage.removeItem('refresh_token'); // 重置状态 user.value = null; isAuthenticated.value = false; // 跳转到登录页 router.push('/login'); }; // 可以添加一个自动刷新token的函数,在请求拦截器中调用 const refreshAccessToken = async () => { const refreshToken = localStorage.getItem('refresh_token'); if (!refreshToken) { logout(); throw new Error('No refresh token available'); } try { const response = await apiClient.post('/token/refresh/', { refresh: refreshToken }); const newAccessToken = response.access; localStorage.setItem('access_token', newAccessToken); return newAccessToken; } catch (error) { logout(); throw error; } }; return { user, isAuthenticated, login, logout, refreshAccessToken, }; });然后,我们需要升级之前的api.js中的请求拦截器,加入Token自动刷新的逻辑。
// frontend/src/services/api.js (更新请求拦截器部分) import { useAuthStore } from '@/stores/authStore'; // 注意:在非组件JS文件中使用Pinia store,需要先获取pinia实例 import { createPinia } from 'pinia'; const pinia = createPinia(); const authStore = useAuthStore(pinia); // ... 其他代码 ... // 响应拦截器 apiClient.interceptors.response.use( (response) => response.data, async (error) => { const originalRequest = error.config; // 判断错误是否是401且不是登录请求,并且尚未重试过 if (error.response?.status === 401 && !originalRequest._retry && originalRequest.url !== '/token/') { originalRequest._retry = true; // 标记已重试,防止循环 try { // 尝试刷新token const newAccessToken = await authStore.refreshAccessToken(); // 更新请求头中的token originalRequest.headers.Authorization = `Bearer ${newAccessToken}`; // 重新发起原始请求 return apiClient(originalRequest); } catch (refreshError) { // 刷新token也失败,跳转到登录页 authStore.logout(); return Promise.reject(refreshError); } } // 其他错误,直接抛出 return Promise.reject(error); } );这样,一个具备自动刷新Token机制的认证流程就搭建完成了。用户在登录后,前端会管理Token的生命周期,在Token过期时自动尝试刷新,无需用户重新登录,提供了无缝的体验。
6. 项目部署与生产环境优化
开发完成后的项目,最终需要部署到生产环境。这里涉及前端构建、静态文件收集、数据库和生产服务器配置等多个环节。
6.1 前端构建与静态文件集成
在Vue项目中,运行构建命令会生成一个dist目录,里面是优化、压缩过的静态文件(HTML, JS, CSS)。
cd frontend npm run build对于Django,有两种主流方式处理这些静态文件:
- 使用Django托管:适合小型项目或原型。将
dist目录下的内容复制到Django的静态文件目录,并配置一个通用视图来渲染index.html。 - 使用独立Web服务器(如Nginx)托管:这是生产环境的标准做法。Nginx性能更高,更擅长处理静态文件,同时可以作为反向代理将API请求转发给Django后端(通常通过Gunicorn或uWSGI运行)。
这里简要介绍第一种方式。首先,在Django的settings.py中配置静态文件。
# backend/settings.py import os # 构建前端静态文件的路径 FRONTEND_DIR = os.path.join(BASE_DIR, '../frontend/dist') # 静态文件URL前缀 STATIC_URL = '/static/' # 除了各app的static目录,额外添加前端构建产物的目录 STATICFILES_DIRS = [ os.path.join(FRONTEND_DIR, 'static'), ] # 收集静态文件的目标目录(生产环境使用) STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles') # 模板目录配置,添加前端构建产物的index.html所在目录 TEMPLATES = [ { # ... 'DIRS': [FRONTEND_DIR], # 添加这行 # ... }, ]然后,创建一个视图来服务前端的主页面。
# backend/views.py (新建或在某个app的views.py中) from django.views.generic import TemplateView class HomeView(TemplateView): template_name = 'index.html' # 这会去TEMPLATES['DIRS']里找最后,在urls.py中配置路由,将根路径和其他非API路径指向这个视图。
# backend/urls.py from django.urls import path, include, re_path from .views import HomeView urlpatterns = [ path('admin/', admin.site.urls), path('api/', include('api.urls')), path('api/token/', TokenObtainPairView.as_view(), name='token_obtain_pair'), path('api/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'), # 匹配任何不匹配以上路径的请求,交给前端路由处理(Vue Router的history模式需要) re_path(r'^.*$', HomeView.as_view(), name='home'), ]这样配置后,Django会处理/api/开头的所有API请求,而其他任何请求(如/,/about,/article/1)都会返回前端的index.html,由Vue Router在浏览器端接管路由。
6.2 生产环境部署要点
部署到Linux服务器时,有几个关键步骤和配置:
- 关闭Debug模式:在
settings.py中设置DEBUG = False。这会强制你配置ALLOWED_HOSTS(允许访问的域名/IP列表)。 - 配置数据库:开发用的SQLite不适合生产。换成PostgreSQL或MySQL,并在
settings.py中配置DATABASES。 - 收集静态文件:运行
python manage.py collectstatic,Django会将所有静态文件(包括前端构建的static文件)收集到STATIC_ROOT目录,方便Web服务器(如Nginx)直接提供。 - 使用WSGI服务器:Django自带的开发服务器不能用于生产。使用Gunicorn或uWSGI作为应用服务器。
pip install gunicorn gunicorn backend.wsgi:application -b 0.0.0.0:8000 - 使用Nginx作为反向代理:Nginx监听80/443端口,处理静态文件请求,并将动态请求(
/api/)代理给Gunicorn。# Nginx配置示例片段 server { listen 80; server_name your_domain.com; # 静态文件 location /static/ { alias /path/to/your/project/staticfiles/; } # 前端构建的其他文件(如图片、字体等也在dist/static里) location /media/ { alias /path/to/your/project/media/; # 用户上传的文件目录 } # 将API请求转发给Gunicorn location /api/ { proxy_pass http://127.0.0.1:8000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } # 其他所有请求交给前端处理 location / { root /path/to/your/frontend/dist; try_files $uri $uri/ /index.html; } } - 进程管理:使用Supervisor或systemd来管理Gunicorn进程,确保应用在崩溃后能自动重启。
- 环境变量:敏感信息(如SECRET_KEY、数据库密码)不要写在代码里,使用环境变量管理(如
python-decouple或django-environ库)。
7. 开发与部署中的常见问题排查
在实际开发和部署中,你一定会遇到各种问题。这里记录几个最常见的问题和排查思路。
7.1 跨域问题(CORS)
症状:前端控制台报错:Access-Control-Allow-Originheader missing,或者预检请求(OPTIONS)失败。
原因与解决:
- 开发环境:确保已正确安装并配置
django-cors-headers,且CORS_ALLOWED_ORIGINS包含了前端开发服务器的地址(如http://localhost:8080)。检查MIDDLEWARE顺序,CorsMiddleware应尽量靠前。 - 生产环境:如果前后端部署在不同域名下,必须在Django中正确配置
CORS_ALLOWED_ORIGINS为你的前端生产域名。切勿在生产环境使用CORS_ALLOW_ALL_ORIGINS = True。 - 携带凭证:如果前端请求需要携带Cookies或Authorization头,需要设置
CORS_ALLOW_CREDENTIALS = True,并且CORS_ALLOWED_ORIGINS不能使用通配符*。
7.2 静态文件404
症状:页面可以打开,但CSS、JS、图片等资源加载失败(404)。
原因与解决:
- 开发阶段:Django开发服务器默认只服务各app
static/目录下的文件。确保前端构建的静态文件路径已添加到STATICFILES_DIRS。 - 生产环境:
- 运行
python manage.py collectstatic成功了吗?检查STATIC_ROOT目录下是否有文件。 - Nginx配置中的
location /static/的alias路径是否正确指向了STATIC_ROOT? - 检查文件权限:Nginx进程用户(如
www-data)是否有权读取STATIC_ROOT目录下的文件? - 检查Nginx配置后是否重载了配置:
sudo nginx -s reload。
- 运行
7.3 数据库迁移冲突
症状:在团队协作中,执行python manage.py migrate时出现django.db.utils.OperationalError或迁移依赖错误。
原因与解决:
- 根本原因:不同开发者基于旧的数据库状态创建了新的迁移文件,导致迁移顺序冲突。
- 标准流程:
- 在修改模型前,确保本地数据库是最新的:
python manage.py migrate。 - 修改模型后,生成迁移文件:
python manage.py makemigrations。 - 将生成的迁移文件(
app/migrations/00xx_xxx.py)提交到版本控制系统。 - 其他成员拉取代码后,先运行
python manage.py migrate应用新的迁移。
- 在修改模型前,确保本地数据库是最新的:
- 冲突解决:如果已经产生冲突,可以尝试回滚迁移(
migrate app_name migration_file_name),删除冲突的迁移文件,重新生成。复杂情况可能需要手动干预数据库。
7.4 Vue Router的History模式404
症状:在非根路径下刷新页面,或直接访问一个前端路由(如/article/1),返回Django 404页面。
原因与解决:
- 原因:这个路径在Django的
urlpatterns中没有定义,Django服务器自然返回404。 - 解决:正如我们在6.1节所做的,在Django的
urls.py中,添加一个捕获所有非API请求的正则表达式路由,并将其指向渲染index.html的视图。这样,无论用户访问哪个前端路由,Django都会返回Vue应用,由Vue Router在客户端进行路由匹配。
7.5 性能与优化建议
- 数据库查询优化:使用
select_related或prefetch_related来减少N+1查询问题。在DRF序列化器中,注意嵌套关系的序列化深度,避免无意中加载过多关联数据。 - 缓存:对于不常变动的数据(如文章分类、配置项),使用Django的缓存框架(如Redis)进行缓存。
- 前端资源优化:使用Vue CLI的代码分割、懒加载路由组件。压缩图片,使用CDN托管第三方库。
- 监控与日志:生产环境务必配置日志(Django的
LOGGING),并考虑使用Sentry等工具监控错误。
从技术选型到环境搭建,从API设计到前后端联调,再到最后的部署上线,Django + Vue这套组合拳的每个环节都有其最佳实践和需要避开的“坑”。这套架构的优势在于其清晰的分离、成熟的生态和高效的开发体验。对于大多数Web应用来说,它提供了一个足够稳健、可扩展且易于维护的起点。关键在于理解每个组件扮演的角色,并合理地配置它们协同工作。当遇到问题时,耐心查看日志、理解错误信息,并善用搜索引擎和社区,大部分难题都能找到解决方案。