Django+Vue家教平台全栈开发:从环境搭建到部署打包的完整指南
2026/9/16 5:06:03 网站建设 项目流程

家教平台这类"一体化管理系统",我前后接过好几个,学生端、教师端、管理后台、课程预约、视频回放、账单导出……需求听着不复杂,真做起来全是细节。尤其是技术栈选型和环境搭建,不少人在第一步就卡住了:Django和Vue到底怎么配合?mysqlclient装不上怎么办?打包完布局乱了又是怎么回事?

这篇文章就围绕Django+Vue搭建家教一体化教育平台这条线,把业务拆解、后端设计、前端工程化、前后端联调、部署打包的完整链路捋一遍。适合正在做课程设计、毕业设计,或者刚接手类似全栈项目的开发者参考,里面大部分坑我都实际踩过,能帮你少走不少弯路。

1. 家教一体化平台的业务拆解与选型逻辑

1.1 家教平台到底需要哪些模块

先别急着写代码。家教平台不是简单的"教师列表+预约按钮",真正落地时需要梳理的角色和模块比想象中多。做项目前我习惯先画一张业务地图,把角色、功能、数据流列清楚,后面建模才不会乱。

家教平台的核心角色至少有四类:

  • 家长/学生端:注册登录、浏览教师、按科目/年级筛选、查看教师详情与评价、预约试听课、在线支付、观看课程回放、下载学习资料。
  • 教师端:创建个人主页(简介、教龄、擅长科目)、维护可授课时间段、管理学生预约、上传课程视频与讲义、查看课时收入。
  • 管理员:审核教师入驻、管理用户、处理投诉、统计分析(收入、活跃度)、发布系统公告。
  • 系统支撑功能:教室/授课地点管理、课程表排课、消息通知、账单与订单管理。

这些模块对应的技术需求就浮出来了:需要用户认证与权限控制(教师、学生、管理员三种角色)、课程与订单管理系统、视频文件上传与播放、Excel导出、地图展示授课地点、实时消息通知。这一套业务地图列全了,你才能确定数据模型和接口设计。

1.2 为什么是Django和Vue这对组合

选型这件事没有绝对的对错,但"为什么用"你心里要有数。我选Django做后端,核心原因是它自带的东西足够多:

  • 自带Admin管理后台:教师审核、订单查看、内容管理这些内部功能,不需要从零写页面,Django Admin配置一下就能用,开发效率极高。
  • ORM非常成熟:教师、课程、订单、评价之间的关联关系,用Django ORM表达和查询都非常自然,配合迁移工具可以随时调整表结构。
  • 自带认证体系:User模型、权限组(Group)、会话管理都是现成的,扩展一个教师Profile、学生Profile就是加一张关联表的事。
  • 生态适合做内容型平台:Django处理文件上传、图片缩放、分页查询都很顺手。

前端选Vue,是看中它的工程化体验和上手曲线。Vue的单文件组件(SFC)让页面、样式、脚本聚在一起,组件之间传参通信直观,配合Vue Router和Pinia(或Vuex)就能撑起一个SPA。和React相比,Vue的模板语法更接近HTML思维,对后端出身、不常写前端的开发者更友好。

我见过有人用Django模板直接渲染前端页面,项目初期确实快,但做到预约日历、实时消息提醒、视频播放器交互时,模板语法非常别扭。前后端分离之后,前端只调接口,后端只管数据,开发和部署都能解耦。

1.3 整体架构落地方案

架构上我用的是最常用的前后端分离模式:

Vue SPA(前端,端口8080) ↓ HTTP + JSON Django REST Framework(后端API,端口8000) ↓ MySQL(主数据库)+ Redis(缓存/会话/队列)

后端在Django基础上加djangorestframeworkdjangorestframework-simplejwt,提供RESTful API,JWT做认证,用django-cors-headers解决跨域。前端用Vue3 + Vue Router + Pinia + Axios,UI库用Element Plus,打包后丢给Nginx托管静态文件,API请求反向代理到后端。

这套架构的好处是边界清晰:模板渲染、数据校验、权限控制全在后端;交互体验、路由跳转、状态管理全在前端。后面接部署时,前端不用改代码,后端也不涉及模板页面,各自独立迭代。

2. Django端数据模型设计与Admin后台实操

2.1 用户角色与核心模型的字段设计

Django自带的User模型可以直接用,但家教平台需要扩展角色信息。我的习惯是建两个Profile模型,用OneToOneField关联到User,而不是直接修改User表。

from django.db import models from django.contrib.auth.models import User class TeacherProfile(models.Model): user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='teacher_profile') title = models.CharField('头衔', max_length=50, blank=True) intro = models.TextField('个人简介', blank=True) subjects = models.CharField('擅长科目', max_length=100) hourly_rate = models.DecimalField('每小时费用', max_digits=8, decimal_places=2, default=0) verified = models.BooleanField('审核通过', default=False) created_at = models.DateTimeField(auto_now_add=True) class StudentProfile(models.Model): user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='student_profile') grade = models.CharField('年级', max_length=20, blank=True) points = models.IntegerField('积分', default=0) class Course(models.Model): teacher = models.ForeignKey(TeacherProfile, on_delete=models.CASCADE, related_name='courses') name = models.CharField('课程名称', max_length=100) cover = models.ImageField('封面图', upload_to='course_covers/', blank=True) description = models.TextField('课程描述') start_time = models.DateTimeField('开课时间') end_time = models.DateTimeField('结束时间') price = models.DecimalField('价格', max_digits=8, decimal_places=2) max_students = models.IntegerField('最大人数', default=20) class Order(models.Model): order_no = models.CharField('订单号', max_length=32, unique=True) student = models.ForeignKey(StudentProfile, on_delete=models.CASCADE, related_name='orders') course = models.ForeignKey(Course, on_delete=models.CASCADE, related_name='orders') amount = models.DecimalField('订单金额', max_digits=8, decimal_places=2) status = models.CharField('状态', max_length=20, choices=[ ('pending', '待支付'), ('paid', '已支付'), ('refunded', '已退款') ], default='pending') created_at = models.DateTimeField(auto_now_add=True)

几个设计上的注意点:

  • 用OneToOneField扩展User而不是继承AbstractUser:Django的User模型在项目启动后就不建议改,扩展Profile可以避免后期迁移地狱,而且不影响第三方包对User的引用。
  • 订单号手动生成:时间戳+随机数组合即可,不要用自增id暴露订单量。
  • 金额用DecimalField:FloatField会有精度问题,涉及钱的字段一律用DecimalField。
  • 课程时间用DateTimeField:做排课冲突检测时,直接比较时间范围比存字符串方便一万倍。

除了上面这些,还要考虑预约表(Appointment)、评价表(Review)、课程回放表(LessonVideo)、教室表(Classroom)、课表(Schedule)。尤其是教室管理,很多家教平台会涉及线下授课,Classroom模型里要有地址、经纬度字段,后面才能在Vue端接地图展示。

2.2 连接MySQL时的mysqlclient安装问题

Django默认用SQLite,但家教平台这种多用户系统,生产环境至少要换MySQL。一换MySQL,很多人的第一个坑就是mysqlclient装不上。

在Windows上直接pip install mysqlclient经常会报错,提示缺少Microsoft C++ Build Tools。这是因为mysqlclient需要编译C扩展,Windows上没有完整的编译环境。我的处理办法有两种:

  1. https://pypi.org/project/mysqlclient/#files下载对应Python版本的预编译wheel包,本地安装,省去编译依赖的麻烦。
  2. 改用pymysql兜底:
import pymysql pymysql.install_as_MySQLdb()

在Django项目的__init__.py里加上这两行,然后DATABASES配置保持不变即可。pymysql是纯Python实现,性能不如mysqlclient但开发调试完全够用。

在Linux服务器上安装则要先把系统依赖装好:

sudo apt install python3-dev default-libmysqlclient-dev build-essential pip install mysqlclient

数据库配置时,注意编码和连接参数:

DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'tutor_platform', 'USER': 'your_user', 'PASSWORD': 'your_password', 'HOST': '127.0.0.1', 'PORT': '3306', 'OPTIONS': { 'charset': 'utf8mb4', } } }

utf8mb4一定不能省,不然用户昵称里存个emoji就直接写入报错。新手项目跑不起来,十有八九就是卡在这个字符集和mysqlclient上。

2.3 Admin后台美化的两种路线

Django自带Admin功能是全的,但默认样式非常朴素,而且对中文字段排序、筛选器支持都不够美观。我美化Admin后台时试过两条路线。

路线一:用第三方主题,推荐django-simpleui

pip install django-simpleui

安装后在INSTALLED_APPS里把simpleui放到django.contrib.admin前面,登录后台后主题就生效了。simpleui自带侧边栏菜单、面包屑导航、响应式布局,还支持一键隐藏Django默认的"最近操作"面板,整体观感接近一个现代化的管理后台。

路线二:完全自定义Admin页面

如果后续需要高度定制(比如嵌入数据统计图表),可以在simpleui基础上开发自定义模板页面,覆盖admin/base_site.html,按自己的需求布局。不过对大多数家教平台来说,路线一的性价比最高,先跑通业务,再考虑定制仪表盘。

Admin里还有一个实用配置是list_displaylist_filter。比如课程管理后台,把审核状态、科目、开课时间放进来,管理员筛选操作效率会高很多:

class CourseAdmin(admin.ModelAdmin): list_display = ('name', 'teacher', 'price', 'start_time', 'status') list_filter = ('start_time', 'teacher') search_fields = ('name', 'teacher__user__username')

3. Vue端工程化与路由体系搭建

3.1 从零搭建Vue项目的环境准备

项目环境搭建这一步,很多人觉得简单,实际上翻车率很高。Vue项目要跑起来,Node.js环境、包管理器、脚手架工具缺一不可。

首先是Node.js安装。我建议装LTS版本,不要追新。Vue3和Vite对Node版本有最低要求,但太新的Node版本反而可能和一些依赖包出现兼容性问题。装完后用node -vnpm -v确认版本。

然后创建项目。现在官方推荐用Vite而不是vue-cli,创建速度差好几倍:

npm create vite@latest tutor-web -- --template vue cd tutor-web npm install npm run dev

Vite创建的项目默认目录结构非常干净,src下面按照viewscomponentsrouterstoresapi建子目录管理。如果是接手旧项目用的是vue-cli,也别急着迁移,先把node_modules删掉重新npm install,往往会解决一堆莫名其妙的问题。

npm安装依赖慢是国内开发者最常见的痛点。解决办法是设置镜像源:

npm config set registry https://registry.npmmirror.com

如果你遇到某个包(特别是node-sass这类原生模块)反复安装失败,先看看是不是镜像源和缓存的问题。用npm cache clean --force清掉缓存再重装,能解掉大部分玄学问题。

3.2 路由设计:query、params、props传参怎么选

Vue Router是前端页面的骨架。家教平台的页面结构大概是:

  • 首页/教师列表页(搜索筛选)
  • 教师详情页(看评价、约课)
  • 学生中心(我的课程、订单、回放)
  • 授课教室地图页

路由配置示例:

const routes = [ { path: '/', name: 'Home', component: HomeView }, { path: '/teachers', name: 'TeacherList', component: TeacherList }, { path: '/teachers/:teacherId', name: 'TeacherDetail', component: TeacherDetail, props: true }, { path: '/student/orders', name: 'StudentOrders', component: StudentOrders }, { path: '/classroom/:id', name: 'ClassroomMap', component: ClassroomMap } ]

从教师列表跳详情页时,我推荐路由参数用params传,页面定位用query传。比如:

// 详情页传 teacherId 用路径参数 router.push({ name: 'TeacherDetail', params: { teacherId: 101 } }) // 列表页筛选条件用 query,便于分享和回退 router.push({ path: '/teachers', query: { subject: 'math', grade: 'grade8' } })

params适合传少量业务主键,query适合传筛选条件。有人图省事把所有数据都放state里,刷新页面数据全丢,这就是没分清路由状态和应用状态的区别。页面刷新后地址栏还在,路由参数能恢复数据上下文,这是一个很实用的设计习惯。

props: true这个配置很多人忽略,它能让路由参数直接绑定到组件的props上,组件内部不需要再从route.params里取值,可读性和可测性都好很多。

3.3 依赖安装与版本锁定的那些坑

Vue项目依赖多,版本冲突是家常便饭。我见过一个项目,开发环境一切正常,打包后TotalDistSize直接翻倍,查了半天发现是Element Plus组件库引入了全量样式,没按需加载。

前端依赖安装的经验可以总结成三条:

  1. 用锁文件锁定版本package-lock.json(npm)或pnpm-lock.yaml必须提交到仓库,团队协作时大家的依赖版本一致,才不会再现"我本地好好的,你那里跑不起来"的情况。
  2. 区分dependencies和devDependencies。像Vite、ESLint这类只在开发期用的工具,装到devDependencies;运行时依赖如Vue、Axios、Element Plus,放dependencies。打包体积和部署流程都会干净很多。
  3. 谨慎升级大版本。Vue2升Vue3、Element Plus从Beta到正式版,很多API有breaking change。项目启动阶段选好版本就锁死,不要在开发中期顺手升级。

另外,Vue全家桶目前的状态管理方案,我建议新项目直接用Pinia。Vuex那一套mutation/action的模板代码太繁琐,Pinia的写法接近Composition API,类型推断也更好。如果项目里已经用了Vuex,也不必重构,但新模块用Pinia没有坏处。

4. 前后端联调中的关键功能实现

4.1 登录认证与token处理的正确姿势

前后端分离后,Django自带的session认证不再适用,因为前端和后端不同源,每次请求都带cookie还得处理CSRF,非常麻烦。我用的是JWT认证方案。

后端配置djagnorestframework-simplejwt,在settings.py里注册:

REST_FRAMEWORK = { 'DEFAULT_AUTHENTICATION_CLASSES': ( 'rest_framework_simplejwt.authentication.JWTAuthentication', ), }

然后配置对应的URL路由,获取token和刷新token都会自动生成。

前端这边,Axios需要封装请求拦截器,把token挂到请求头上:

import axios from 'axios' const api = axios.create({ baseURL: '/api', timeout: 10000 }) api.interceptors.request.use(config => { const token = localStorage.getItem('access_token') if (token) { config.headers.Authorization = `Bearer ${token}` } return config }) api.interceptors.response.use( response => response, error => { if (error.response && error.response.status === 401) { // token过期,尝试刷新 const refresh = localStorage.getItem('refresh_token') if (refresh) { axios.post('/api/token/refresh/', { refresh }).then(res => { localStorage.setItem('access_token', res.data.access) // 重新发起原请求 }) } else { router.push('/login') } } return Promise.reject(error) } )

这里有几个细节值得说:

  • token存localStorage而不是sessionStorage:刷新页面不丢登录态,用户体验更好。当然安全性上要接受XSS风险,所以前端一定要避免使用v-html渲染不可信内容。
  • 响应拦截器里处理401:用户登录过期后自动跳转登录页,而不是每个页面都写一遍错误处理。
  • 刷新token的接口要等异步返回,如果同一时间多个接口都401,要避免重复刷新。可以用一个简单的锁变量或者把刷新逻辑前置到请求拦截器中判断token过期时间。

4.2 M3U8课程视频播放与StreamingHttpResponse下载

家教平台经常要上传课程回放视频,存储在服务端的常见格式之一是HLS(M3U8)。前端播放m3u8不能直接用<video>标签,需要借助hls.js。

npm install hls.js

Vue组件里的实现思路:

<template> <video ref="videoEl" controls autoplay></video> </template> <script setup> import Hls from 'hls.js' import { ref, onMounted } from 'vue' const props = defineProps({ src: { type: String, required: true } // /media/courses/101/playlist.m3u8 }) const videoEl = ref(null) onMounted(() => { if (Hls.isSupported()) { const hls = new Hls() hls.loadSource(props.src) hls.attachMedia(videoEl.value) } }) </script>

后端方面要保证m3u8文件及关联的ts文件能被访问。Django处理这类文件,别直接把文件塞到media目录用默认静态服务撑全场,生产环境应该用Nginx直接托管media目录做流媒体分发,Django只负责鉴权和分发URL。

真正有技术含量的是文件下载接口。很多人写下载功能只知道返回一个FileResponse,结果中文文件名乱码、大文件下载占满内存。我推荐用StreamingHttpResponse精确控制响应头:

from django.http import StreamingHttpResponse from urllib.parse import quote def download_material(request, file_id): material = Material.objects.get(id=file_id) file_path = material.file.path def file_iterator(path, chunk_size=8192): with open(path, 'rb') as f: while True: chunk = f.read(chunk_size) if not chunk: break yield chunk filename = quote(material.name + '.pdf') response = StreamingHttpResponse( file_iterator(file_path), content_type='application/octet-stream' ) response['Content-Disposition'] = f"attachment; filename*=UTF-8''{filename}" response['Content-Length'] = material.file.size return response

这里面最容易翻车的是Content-Disposition。直接用中文文件名,在部分浏览器里会乱码或下载失败;用filename*=UTF-8''{filename}格式,配合urllib.parse.quote做URL编码,才能兼容Chrome、Firefox和Safari。

4.3 多表格数据导出Excel与地图组件接入

家教平台的管理员经常要导出各种统计报表:学生名单、教师结算表、课程收入明细。如果这些数据分布在不同表格里,一次导出到一个Excel的多个Sheet里是真实存在的需求。

实现方案有两种:

第一种,后端用openpyxlxlsxwriter生成Excel文件:

from openpyxl import Workbook def export_multi_sheet(request): wb = Workbook() # 第一个Sheet:学生名单 ws1 = wb.active ws1.title = '学生名单' ws1.append(['姓名', '年级']) for s in students: ws1.append([s.user.username, s.grade]) # 第二个Sheet:收入明细 ws2 = wb.create_sheet('收入明细') ws2.append(['订单号', '金额', '状态']) for o in orders: ws2.append([o.order_no, str(o.amount), o.status]) # 写入响应 response = HttpResponse(content_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet') response['Content-Disposition'] = 'attachment; filename=report.xlsx' wb.save(response) return response

第二种,前端用xlsx库(SheetJS)纯前端生成Excel:

npm install xlsx

前端生成Excel适合数据已经在前端页面上展示的场景,导出逻辑比较简单。但如果数据量大或者敏感数据只允许后台导出,还是走后端方案更合理。我通常两套方案配合使用:列表页小批量用前端导出,全量月报走后端导出。

再说到地图组件。家教平台里有"授课地点"展示的需求,我用的是腾讯地图。Vue里集成腾讯地图的思路是:

  1. 去腾讯位置服务控制台申请一个Web端JavaScript API的key。
  2. index.html里引入地图JS脚本。
<script src="https://map.qq.com/api/gljs?v=1.exp&key=YOUR_KEY"></script>
  1. 在组件初始化时创建地图实例,添加标注。
<script setup> import { onMounted } from 'vue' onMounted(() => { const map = new TMap.Map(document.getElementById('map'), { center: { lat: 39.908860, lng: 116.397390 }, zoom: 15 }) new TMap.Marker({ map: map, position: { lat: 39.908860, lng: 116.397390 } }) }) </script>

申请key时有一步容易漏:需要在控制台把域名白名单配上。开发环境的localhost:8080,生产环境的域名,都要加进去,不然地图组件加载出来一片空白。这算是地图类组件接入最典型的坑之一。

4.4 重定向传递数据与WebSocket即时通知

"django重定向传递数据"这个痛点,通常出现在服务端渲染的业务场景。比如课程预约成功后,表单视图重定向到订单详情页,同时要传递一条提示消息。Django的经典做法是用messages框架或session:

from django.contrib import messages from django.shortcuts import redirect def book_course(request, course_id): # 预约逻辑... messages.success(request, '预约成功,请在订单中心查看详情') return redirect('course_detail', course_id=course_id)

但在前后端分离架构里,这个需求变成了前端路由跳转时传递数据。有几种选择:

  • query参数传简单标识,目标页面根据标识重新请求接口获取数据,这是最可靠的做法。
  • 用Pinia临时存消息文本,路由跳转后读取并消费掉。
  • sessionStorage传递一次性数据:
sessionStorage.setItem('booking_success', '预约成功') router.push({ path: `/course/${courseId}` }) // 目标页面 onMounted(() => { const msg = sessionStorage.getItem('booking_success') if (msg) { ElMessage.success(msg) sessionStorage.removeItem('booking_success') } })

这套方案的好处是刷新页面后数据已消费,不会重复弹提示,也不污染URL。

再讲WebSocket。家教平台里,学生预约课程后,教师端最好能实时收到提醒。传统HTTP轮询又慢又费资源,WebSocket是更合适的方案。

后端Django要用Channel:

# asgi.py import os from django.core.asgi import get_asgi_application from channels.routing import ProtocolTypeRouter, URLRouter application = ProtocolTypeRouter({ 'http': get_asgi_application(), 'websocket': URLRouter([ path('ws/notify/', consumers.NotificationConsumer.as_asgi()), ]), })

前端Vue直接用原生WebSocket API:

const ws = new WebSocket('ws://localhost:8000/ws/notify/') ws.onmessage = (e) => { const data = JSON.parse(e.data) if (data.type === 'new_booking') { ElNotification({ title: '新预约提醒', message: `${data.student_name} 预约了你的课程` }) } }

注意WebSocket的URL协议是ws://(生产环境如果没有上TLS就用wss://),跟HTTP不完全一样。如果Nginx做了反向代理,还要单独给WebSocket连接配置Upgrade头,不然连接会被中断。

5. 打包部署阶段的高频故障与排查链路

5.1 打包后布局异常的根因链

"vue 打包后 布局异常"是搜索引擎里的高频词,我自己也遇到过。开发环境页面正常,npm run build完部署到服务器后,CSS失效、图片加载不出来、路由跳转404。

这类问题的排查链路基本是这样的:

第一步,看浏览器控制台网络请求。如果CSS文件、字体、图片的请求地址都是相对路径,而站点部署在子路径下(比如https://example.com/tutor/),就会404。

第二步,检查Vite或Vue CLI的publicPath配置。Vite默认base: '/',如果部署在子目录,需要改成:

// vite.config.js export default { base: '/tutor/' }

第三步,排查Vue Router的history模式。如果用了createWebHistory(),后端必须把所有路由重定向到index.html,否则刷新子路由页面就会404。Nginx配置:

location / { try_files $uri $uri/ /index.html; }

如果不想处理服务端重定向,最简单的是改用createWebHashHistory(),URL会带/#/,但刷新不会404。对内部管理系统来说,hash模式牺牲一点美观,换来部署省心,完全可以接受。

第四步,检查有没有在打包时屏蔽了某些相对路径的资源。Element Plus的字体文件、腾讯地图的引用、背景图片,都是重灾区。

这一整套排查下来,大多数"打包后布局异常"都能定位到base路径hash/history模式这两个核心问题上。

5.2 跨域、静态资源与history路由配置

前端和后端分离部署后,跨域是绕不开的问题。开发阶段用Vite的proxy代理解决,生产阶段最好用Nginx做反向代理,把同源的请求转给后端。

开发环境的Vite配置:

// vite.config.js export default { server: { proxy: { '/api': { target: 'http://localhost:8000', changeOrigin: true }, '/media': { target: 'http://localhost:8000', changeOrigin: true } } } }

生产环境的Nginx配置:

server { listen 80; server_name your-domain.com; # 前端静态文件 location / { root /home/www/tutor-web/dist; try_files $uri $uri/ /index.html; } # Django API接口 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 /media/ { alias /home/www/tutor-platform/media/; } # WebSocket代理 location /ws/ { proxy_pass http://127.0.0.1:8000; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; } }

用这种同源代理方案后,Django端不需要配django-cors-headers,前端请求直接写/api/xxx相对路径,开发生产环境都干净。如果你非要前后端跨域直连,那才需要装django-cors-headers

INSTALLED_APPS = [ 'corsheaders', ] CORS_ALLOWED_ORIGINS = [ 'http://localhost:8080', 'https://your-production-domain.com', ]

有个容易出问题的地方:开发环境配了proxy,生产环境配了Nginx,但前端代码里的baseURL要对应调整。用import.meta.env区分环境是个好习惯:

const api = axios.create({ baseURL: import.meta.env.PROD ? '/api' : '/api' })

只要后端API前缀统一是/api,其实两端可以保持一致,省去环境判断。

5.3 部署后的性能优化清单

部署上线只是起点,性能优化直接影响用户实际体验。基于实际经验,列一份家教平台部署后建议过一遍的优化清单:

  • 前端路由懒加载:把教师详情、订单管理、视频播放等功能性页面改成动态导入,首屏只加载必要的组件代码,首屏渲染速度会有明显提升。
const TeacherDetail = () => import('../views/TeacherDetail.vue')
  • 前端按需引入UI库:Element Plus默认全量引入会打包进大量无用组件。用官方插件做按需引入,打包体积能降一半以上。打包后先看一眼dist目录体积,有个直观心理预期。

  • 后端查询加上select_related和prefetch_related:列表页涉及外键关联查询时,不加这两个会造成N+1查询问题。比如教师列表页关联了TeacherProfile、User、Course,ORM会发大量SQL语句,用select_related一次join出来:

teachers = TeacherProfile.objects.select_related('user', 'user__teacher_profile').all()
  • 数据库索引:经常做筛选的字段(科目、年级、订单状态),加db_index=True,查询效率会提高不少。
class Course(models.Model): subject = models.CharField('科目', max_length=20, db_index=True)
  • 缓存热点数据:教师列表、首页轮播、课程推荐这种更新频率低、读频率高的数据,用Django的cache_page装饰器或者Redis缓存起来。
from django.views.decorators.cache import cache_page @cache_page(60 * 15) # 缓存15分钟 def course_list(request): ...
  • 图片和视频做压缩:Django的ImageField上传后不会自动压缩,需要配合Pillow做图片处理,或者前端上传前压缩。视频文件尽可能用HLS切片并转码成多码率,避免用户手机流量看高码率视频卡顿。

这几项做完,平台的性能和体验基本能达到可上线标准。后面更多是根据业务数据增长逐步调整,比如订单量大了需要做分页优化,视频访问量大需要考虑CDN。


最后说点个人体会。Django+Vue做家教一体化平台,技术上没有特别高深的地方,但真正的功夫全在细节里:数据库字段要不要建索引,文件下载头怎么设置,路由history模式和Nginx怎么配合,每题单看都不难,连在一起就是一堆隐形的坑。我踩过最狠的一次是ajax跨域加cookie带不上,排查了整整一下午,最后发现是Nginx少了proxy_set_header X-Forwarded-Proto $scheme;这一行。做全栈项目,环境配置和部署配置花的时间往往比写业务代码还多,所以别急着撸代码,先把环境链路和部署思路想清楚,后边能省一大半调试时间。

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

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

立即咨询