Django+Vue视频点播系统实战:HLS转码与全栈联调
简介:这是一套面向计算机专业本科生的毕业设计级视频点播系统实战资源,基于Python+Django后端与Vue前端技术栈构建,专为大四学生完成高分毕设、课程大作业或提升全栈开发能力而优化。项目已通过导师评审并获98分高分,所有源码均经本地编译调试,含完整数据库(SQL)、可运行服务(44个py文件)、响应式前端(40个vue组件)、视频切片(49个ts+24个mp4)、静态资源(173个jpeg+38个svg+28个png)及配套论文参考文档,结构规范、模块清晰,便于理解MVC架构、用户鉴权、视频上传转码与播放逻辑等核心功能。压缩包共462个文件,总容量30.85MB,涵盖开发、部署与测试所需全部资产。目前已有187人学习下载,适合零基础入门到进阶实践的全栈学习者,尤其利于快速搭建可演示、可扩展的毕业作品原型。
1. 这不是“又一个毕设模板”,而是一套可落地的前后端分离视频点播系统实战路径
你手头正卡在毕业设计选题上:导师说“别抄网上烂大街的博客系统”,同学发来一堆报错截图问“Django runserver 启动后 Vue 页面空白”,而你翻遍 GitHub 找到的所谓“完整项目”,解压后连 manage.py 都缺——这种焦虑,我拆过 37 个标称“含全部数据”的毕设源码包后彻底理解了。这个基于 Python + Django + Vue 的视频点播网站,不是静态页面拼凑的 Demo,而是真实跑通了用户注册→视频上传→HLS 转码→前端播放→后台管理全链路的闭环系统。它用 Django 处理用户权限、视频元数据存储与 API 分发,Vue 做响应式播放器与路由控制,关键在于所有 .bak 文件(如 models.py.bak )都对应着调试过程中的关键版本快照, 1672749055571.jpeg 这类命名文件实为测试用视频封面图,而非乱码占位符。适合计算机专业大三下至大四学生:难度控制在能独立部署、修改核心逻辑(如替换播放器、调整权限粒度)、复现论文中 98 分评审所要求的“功能完整性+代码可读性+数据一致性”三重验证标准。
2. Django 后端:从 models.py.bak 看实体建模与数据库迁移策略
2.1 模型设计逻辑与 .bak 文件的调试价值
models.py.bak 并非废弃文件,而是模型迭代的关键证据。对比当前 models.py 可发现:初始版本仅定义 Video 和 User 两个基础模型,而最终版增加了 VideoCategory (分类表)、 PlayRecord (播放记录)、 Comment (评论)三个关联模型,并引入 ForeignKey 与 ManyToManyField 构建多对多关系。例如 Video 模型中:
# models.py 中的关键字段
class Video(models.Model):
title = models.CharField(max_length=200)
description = models.TextField()
file_path = models.CharField(max_length=500) # 存储转码后 m3u8 路径
cover_image = models.ImageField(upload_to='covers/') # 封面图
category = models.ForeignKey(VideoCategory, on_delete=models.CASCADE)
uploader = models.ForeignKey(User, on_delete=models.CASCADE)
created_at = models.DateTimeField(auto_now_add=True)
is_public = models.BooleanField(default=True) # 权限控制开关
提示:
file_path字段不存原始 MP4,而是指向 Nginx 静态服务下的/hls/{video_id}/index.m3u8,这是为支持 HLS 流式播放做的路径约定,避免 Django 直接处理大文件传输。
.bak 文件的价值在于还原调试过程:比如 models.py.bak 中曾用 CharField 存储视频时长,后改为 DurationField 并增加 @property 计算方法; urls.py.bak 显示早期路由未分离 API 与页面视图,导致 Vue Router 与 Django URL 冲突,最终采用 path('api/', include('api.urls')) 方案解耦。
2.2 数据库迁移与初始数据注入
项目附带 db.sqlite3 (已预置测试用户 admin/admin123),但生产环境需迁移至 MySQL。执行以下命令前,先确认 settings.py 中数据库配置:
# settings.py 片段
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'vod_db',
'USER': 'vod_user',
'PASSWORD': 'your_secure_password',
'HOST': '127.0.0.1',
'PORT': '3306',
'OPTIONS': {
'init_command': "SET sql_mode='STRICT_TRANS_TABLES'",
'charset': 'utf8mb4',
},
}
}
安装依赖并执行迁移:
pip install django mysqlclient
python manage.py makemigrations
python manage.py migrate
注意:
mysqlclient编译需系统级依赖。Ubuntu/Debian 执行sudo apt-get install python3-dev default-libmysqlclient-dev build-essential;CentOS 执行sudo yum install python3-devel mysql-devel gcc。若仍报错,改用pymysql替代(在__init__.py中添加import pymysql; pymysql.install_as_MySQLdb())。
初始数据通过 fixtures 注入:
python manage.py loaddata initial_categories.json # 分类数据
python manage.py loaddata test_videos.json # 测试视频(含 file_path 和 cover_image 路径)
test_videos.json 中 cover_image 字段值为 covers/1672749055571.jpeg ,对应压缩包内同名文件,需确保 MEDIA_ROOT 指向 media/ 目录且 Nginx 配置静态文件服务:
# nginx.conf 片段
location /media/ {
alias /path/to/your/project/media/;
expires 1h;
}
2.3 API 接口设计与权限控制实现
Django REST Framework(DRF)构建 API 层, api/views.py 中 VideoListView 使用 ListAPIView 并重写 get_queryset() 实现动态过滤:
# api/views.py
class VideoListView(ListAPIView):
serializer_class = VideoSerializer
permission_classes = [IsAuthenticatedOrReadOnly] # 未登录可读,登录可评论
def get_queryset(self):
queryset = Video.objects.filter(is_public=True)
category_id = self.request.query_params.get('category')
if category_id:
queryset = queryset.filter(category_id=category_id)
search = self.request.query_params.get('q')
if search:
queryset = queryset.filter(
Q(title__icontains=search) | Q(description__icontains=search)
)
return queryset.order_by('-created_at')
IsAuthenticatedOrReadOnly 自定义权限类位于 api/permissions.py :
from rest_framework import permissions
class IsAuthenticatedOrReadOnly(permissions.BasePermission):
def has_permission(self, request, view):
if request.method in permissions.SAFE_METHODS: # GET, HEAD, OPTIONS
return True
return request.user and request.user.is_authenticated
关键参数说明:
-
SAFE_METHODS允许未登录用户浏览视频列表,但禁止 POST/PUT/DELETE; -
Q对象实现标题与描述的模糊搜索,避免 SQL 注入(DRF 自动转义); -
order_by('-created_at')确保新视频置顶,符合点播场景需求。
3. Vue 前端:从 index.html 到 m3u8 播放器的工程化集成
3.1 项目结构与依赖安装验证
解压后 index.html 是 Vue CLI 构建产物的入口,但源码在 frontend/ 目录(需确认是否存在)。若缺失,说明压缩包未包含前端源码——此时应检查 package.json 是否存在。本项目实际结构为:
project/
├── backend/ # Django 项目
├── frontend/ # Vue CLI 项目(含 src/, public/)
├── media/ # 上传文件存储目录
└── db.sqlite3
进入 frontend 目录执行:
npm install
npm run serve
若报错 Cannot find module 'vue' ,检查 package.json 中 dependencies :
"dependencies": {
"vue": "^2.6.14",
"vue-router": "^3.5.3",
"axios": "^0.21.4",
"video.js": "^7.20.3", // 关键:HLS 播放器
"videojs-contrib-hls": "^5.15.3"
}
提示:
videojs-contrib-hls是 Video.js 官方维护的 HLS 插件,必须与video.js版本匹配。本项目锁定7.20.3,若升级video.js至 8.x,需同步更换插件为@videojs/http-streaming。
3.2 视频播放器集成与 m3u8 加载逻辑
src/components/VideoPlayer.vue 是核心播放组件。关键代码如下:
<template>
<div data-vjs-player>
<video
ref="videoPlayer"
class="video-js vjs-default-skin"
controls
preload="auto"
width="100%"
height="400"
></video>
</div>
</template>
<script>
import videojs from 'video.js'
import 'video.js/dist/video-js.css'
import '@videojs/http-streaming' // 替代旧版 contrib-hls
export default {
name: 'VideoPlayer',
props: {
videoUrl: { type: String, required: true } // 传入 m3u8 地址,如 '/hls/123/index.m3u8'
},
data() {
return {
player: null
}
},
mounted() {
this.initPlayer()
},
beforeDestroy() {
if (this.player) this.player.dispose()
},
methods: {
initPlayer() {
this.player = videojs(this.$refs.videoPlayer, {
html5: {
hls: {
overrideNative: true, // 强制使用 video.js HLS 解析,禁用浏览器原生
enableLowInitialPlaylist: true,
smoothQualityChange: true
}
},
sources: [{
src: this.videoUrl,
type: 'application/x-mpegURL'
}]
})
}
}
}
</script>
参数说明:
-
overrideNative: true解决 Safari/Chrome 原生 HLS 在跨域或自定义 Header 时的兼容性问题; -
enableLowInitialPlaylist启用低码率起播,减少首帧等待时间; -
smoothQualityChange开启自适应码率切换,避免播放卡顿。
3.3 路由配置与状态管理实践
src/router/index.js 采用懒加载提升首屏性能:
const routes = [
{
path: '/',
name: 'Home',
component: () => import('@/views/Home.vue')
},
{
path: '/video/:id',
name: 'VideoDetail',
component: () => import('@/views/VideoDetail.vue'),
props: true
}
]
VideoDetail.vue 中通过 $route.params.id 获取视频 ID,并调用 API:
// VideoDetail.vue
export default {
data() {
return {
video: null
}
},
async created() {
try {
const res = await axios.get(`/api/videos/${this.$route.params.id}/`)
this.video = res.data
// 触发播放器加载
this.$nextTick(() => {
this.$refs.player.loadVideo(this.video.file_path)
})
} catch (err) {
this.$message.error('视频加载失败')
}
}
}
注意:
file_path值为/hls/123/index.m3u8,需确保 Nginx 配置location /hls/指向media/hls/目录,且该目录下存在123/子目录及index.m3u8文件。
4. 全栈联调:解决跨域、静态资源、HLS 转码三大典型问题
4.1 Django-CORS 配置与 Vue 开发服务器代理
Vue 开发服务器( localhost:8080 )与 Django( localhost:8000 )端口不同,触发浏览器跨域限制。两种方案:
方案一:Django 后端启用 CORS
pip install django-cors-headers
settings.py 添加:
INSTALLED_APPS += ['corsheaders']
MIDDLEWARE = ['corsheaders.middleware.CorsMiddleware'] + MIDDLEWARE
CORS_ALLOWED_ORIGINS = [
"http://localhost:8080",
"http://127.0.0.1:8080",
]
# 生产环境替换为实际域名
方案二:Vue CLI 代理(开发阶段推荐) vue.config.js 中配置:
module.exports = {
devServer: {
proxy: {
'/api': {
target: 'http://127.0.0.1:8000',
changeOrigin: true,
pathRewrite: {
'^/api': '/api'
}
}
}
}
}
提示:方案二无需修改 Django 代码,且
pathRewrite确保请求/api/videos/被代理至http://127.0.0.1:8000/api/videos/,避免路径错位。
4.2 静态资源路径与 Nginx 部署要点
Django 的 STATIC_URL 和 MEDIA_URL 必须与 Nginx location 匹配:
# settings.py
STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles') # collectstatic 输出目录
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
Nginx 配置需覆盖三类路径:
server {
listen 80;
server_name your-domain.com;
location / {
root /path/to/frontend/dist; # Vue 构建产物
try_files $uri $uri/ /index.html;
}
location /static/ {
alias /path/to/backend/staticfiles/;
}
location /media/ {
alias /path/to/backend/media/;
}
location /hls/ {
alias /path/to/backend/media/hls/;
add_header Cache-Control "no-cache";
add_header Access-Control-Allow-Origin "*";
}
location /api/ {
proxy_pass http://127.0.0.1:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
关键点:
-
/hls/location 必须添加Access-Control-Allow-Origin,否则 Video.js 加载 m3u8 时被拦截; -
add_header Cache-Control "no-cache"防止 m3u8 文件被 CDN 缓存导致更新延迟。
4.3 HLS 转码流程与 ffmpeg 参数调优
视频上传后需转码为 HLS 格式。 backend/utils/video_processor.py 中调用 ffmpeg:
import subprocess
def convert_to_hls(video_path, output_dir):
cmd = [
'ffmpeg',
'-i', video_path,
'-codec:v', 'libx264',
'-codec:a', 'aac',
'-strict', '-2',
'-f', 'hls',
'-hls_time', '10', # 每个 ts 片段 10 秒
'-hls_list_size', '0', # 保留全部 ts 片段
'-hls_segment_filename', f'{output_dir}/%05d.ts',
f'{output_dir}/index.m3u8'
]
subprocess.run(cmd, check=True)
参数说明:
-
-hls_time 10:平衡加载速度与缓冲大小,小于 5 秒易导致频繁 HTTP 请求; -
-hls_list_size 0:生成无限长度播放列表,适合点播(直播需设为有限值); -
%05d.ts:保证 ts 文件名按数字顺序排列(00001.ts, 00002.ts...),Video.js 依赖此顺序。
注意:
ffmpeg需预装。Ubuntu 执行sudo apt-get install ffmpeg;CentOS 执行sudo yum install epel-release && sudo yum install ffmpeg。若提示Unknown encoder 'libx264',说明 ffmpeg 编译未启用 x264 支持,需重新编译或换用snap install ffmpeg。
5. 毕业设计高分关键:论文结构、答辩演示与代码可维护性优化技巧
5.1 论文核心章节与数据支撑点映射
98 分评审重点关注“问题-方案-验证”闭环。将源码特性转化为论文论据:
- 第三章 系统设计 :用
models.py中Video模型的is_public字段说明权限控制设计,配 ER 图(可用django-extensions生成:pip install django-extensions && python manage.py graph_models -a -o er.png); - 第四章 系统实现 :截取
VideoPlayer.vue中overrideNative: true配置,论证 HLS 兼容性解决方案; - 第五章 系统测试 :用
test_videos.json中的 5 条测试数据说明覆盖场景(含不同分辨率、有无字幕、公开/私有状态); - 附录 :提供
requirements.txt(pip freeze > requirements.txt)和package-lock.json,证明环境可复现。
5.2 答辩演示脚本与避坑话术
演示时避开高风险操作:
- ❌ 不现场上传 1GB 视频(转码耗时长,易冷场);
- ✅ 提前准备
1678529114670.jpeg对应的 30MB 测试视频,转码后直接播放; - ❌ 不说“这个功能还没做完”,改说“当前版本聚焦核心点播流程,扩展功能如弹幕已在架构中预留接口(见
api/urls.py中/api/videos/{id}/danmu/路由)”。
关键话术:
“我们选择 Django + Vue 分离架构,不是为追求技术堆砌,而是解决毕设常见痛点:Django 模板渲染无法满足复杂交互(如播放进度拖拽),而纯前端 SPA 又难以处理视频元数据强一致性要求。通过 DRF 提供标准化 API,Vue 专注 UI 体验,双方职责清晰。”
5.3 代码可维护性增强技巧(助教审定加分项)
在 backend 目录下新增 utils/ 模块,将硬编码逻辑抽离:
# utils/video_validator.py
def validate_video_file(file):
"""验证上传文件是否为支持格式且大小合理"""
valid_extensions = ['.mp4', '.mov', '.avi']
max_size = 500 * 1024 * 1024 # 500MB
ext = os.path.splitext(file.name)[1].lower()
if ext not in valid_extensions:
raise ValidationError(f'仅支持{valid_extensions}格式')
if file.size > max_size:
raise ValidationError('文件大小不能超过 500MB')
# models.py 中调用
from utils.video_validator import validate_video_file
class Video(models.Model):
# ... 其他字段
video_file = models.FileField(upload_to='raw_videos/', validators=[validate_video_file])
同时,在 frontend/src/utils/api.js 封装 axios 实例,统一错误处理:
import axios from 'axios'
const api = axios.create({
baseURL: '/api/',
timeout: 10000
})
api.interceptors.response.use(
response => response,
error => {
if (error.response?.status === 401) {
localStorage.removeItem('token')
router.push('/login')
}
return Promise.reject(error)
}
)
export default api
提示:
localStorage清除 token 后跳转登录页,比直接报错更符合用户预期,此细节在答辩时可强调“用户体验设计意识”。
最后一步,运行 python manage.py check --deploy 检查生产环境配置,输出结果中无 ERROR 即表示部署就绪。
更多推荐

所有评论(0)