Django视频点播网站搭建:从ORM建模到HLS播放与Nginx部署

发布时间:2026/9/16 17:12:59
Django视频点播网站搭建:从ORM建模到HLS播放与Nginx部署 简介一套基于Django框架开发的视频点播网站完整源码面向计算机、数学、电子信息等专业学生适合作为课程设计、期末大作业或毕业设计参考项目。项目已实现视频播放、收藏、后台管理等功能模块代码结构清晰可直接下载运行同时留有二次开发空间便于理解Django MTV架构、路由映射、模型操作与前后端交互逻辑。压缩包共178个文件体积14.56MB主要包含49个Python源码文件、47个HTML模板、40个编译后的pyc文件以及JS、CSS、图片等静态资源覆盖视图、模板、静态文件等完整站点组成还包含少量演示视频与图标素材。已有125人学习下载可作为同类项目起步参考。通过源码可学习用户登录、视频列表与播放页渲染、收藏功能实现、admin后台配置等关键知识适合有一定Python基础、希望快速搭建功能完整视频站点的学习者参考借鉴。1. 用 Django 搭视频点播站播放链路比后台管理更值得拆拿到“基于django框架搭建的视频点播网站源码”这类压缩包时大部分人第一反应是解压、配环境、跑起来但真正值得看的是播放链路Django 负责业务编排视频文件本身并不适合直接塞给模板渲染。一个能用于生产的视频点播站至少要拆成数据模型、文件处理、播放分发、后台管理四层而 Django 在第四层的 admin 和第二层的数据建模上优势明显第三层要交给 ffmpeg 与 Nginx 补位。下面按这条主线把播放、收藏、后台管理三个功能逐个落到可复现代码上适合正在做内部培训系统、课程站或私有媒体库的 Django 开发者。2. 视频模型与收藏关系Django ORM 的核心建模2.1 三张表Video、Category、Favorite 的字段取舍视频点播站的业务实体不算多但字段设计直接影响后续转码和播放。常见的做法是拆出 Category、Video、Favorite 三张表其中 Video 表要把“业务字段”和“文件字段”分开title、summary、status 属于业务video_file、cover_image、duration 属于文件。status 字段用 IntegerField 配合 choices比直接用 CharField 更省空间也方便在 Admin 里做筛选。from django.db import models from django.contrib.auth.models import User class Category(models.Model): name models.CharField(max_length50, uniqueTrue) sort models.IntegerField(default0) class Meta: ordering [sort, id] def __str__(self): return self.name class Video(models.Model): STATUS_CHOICES ( (0, 草稿), (1, 转码中), (2, 已上架), (3, 已下架), ) category models.ForeignKey(Category, on_deletemodels.PROTECT) title models.CharField(max_length120, db_indexTrue) summary models.TextField(blankTrue) video_file models.FileField(upload_tovideos/%Y/%m/) cover_image models.ImageField(upload_tocovers/%Y/%m/, blankTrue) duration models.IntegerField(default0, help_text秒) status models.IntegerField(choicesSTATUS_CHOICES, default0, db_indexTrue) views_count models.PositiveIntegerField(default0) created_at models.DateTimeField(auto_now_addTrue) class Meta: ordering [-created_at] def __str__(self): return self.title class Favorite(models.Model): user models.ForeignKey(User, on_deletemodels.CASCADE) video models.ForeignKey(Video, on_deletemodels.CASCADE, related_namefavorites) created_at models.DateTimeField(auto_now_addTrue) class Meta: unique_together (user, video)代码逻辑说明category 用 PROTECT 防止误删分类把视频一起删掉video_file 的 upload_to 按年月分目录避免单目录文件过多Favorite 的 unique_together 是收藏业务的关键它把“重复收藏”从数据库层面挡掉接口层就不用再做一次 exists 判断。2.2 存储选型本地 MEDIA_ROOT 与云存储的关键参数models 里的 FileField 只保存路径文件实际落盘位置由 MEDIA_ROOT 决定。开发阶段直接指向项目内 media 目录最简单但生产环境建议用云存储或独立文件服务器原因在表格里看得更清楚。对比项本地磁盘 MEDIA_ROOT云存储 OSS/COS/S3读取速度快无网络开销依赖地域节点需配 CDN扩容需手动加盘按量付费自动扩展转码集成本机 ffmpeg 直接读路径需先下载或绑定文件目录成本磁盘固定成本流量费用偏高如果打算用云存储FileField 要换成 django-storages 对应的 storage 后端字段本身不用动。我一般会把 MEDIA_URL 和 MEDIA_ROOT 单独抽到 local_settings 或环境变量里避免源码里写死路径。转码机与 Web 机分离时本地磁盘方案要求共享存储NFS 或对象存储否则 ffmpeg 找不到源文件。2.3 迁移与查询优化select_related 与收藏状态集合模型建好后先 makemigrations 再 migrate这一步人人都会但查询优化常被忽略。视频列表页如果直接Video.objects.all()模板里每访问一次 category.name 就会多发一条 SQL这就是教科书级的 N1 问题。from django.db.models import Prefetch def video_list(request): videos Video.objects.select_related(category).filter(status2) favorites Favorite.objects.filter(userrequest.user.id) if request.user.is_authenticated else Favorite.objects.none() fav_video_ids set(favorites.values_list(video_id, flatTrue)) return render(request, video/list.html, { videos: videos, fav_video_ids: fav_video_ids, })select_related 适用于 ForeignKey会用一次 JOIN 把 category 带出来收藏的查询用 values_list 取 video_id 集合是为了在模板里快速判断“当前用户是否已收藏”比循环里逐个判断省太多。注意 select_related 不适用于 ManyToMany 和反向外键那些场景要换 Prefetch。3. 播放链路ffmpeg 转 HLS、流媒体视图与防盗链3.1 为什么不能直接放 mp4HLS 切片的价值本地网络里直接点 mp4 确实能播但公网场景问题很明显单文件 mp4 无法做到“边下边播”的精细控制拖动进度条要服务端支持 Range 请求不同带宽用户只能共用一个码率。HLS 把视频切成 6-10 秒的 ts 分片再生成 m3u8 索引播放器按需拉片配合多码率就能做自适应。Django 本身不做转码ffmpeg 是这一层的主角。常见的工作流是后台上传原片 → 任务队列触发 ffmpeg → 输出 HLS 目录 → 更新 Video.status。很多人直接把 ffmpeg 放进视图同步跑小文件还好一个 2GB 的视频会把 worker 阻塞十几分钟必须拆出去。没有 Celery 时最简单的是丢给后台线程或直接 systemd 定时扫描。3.2 ffmpeg 转码命令与码率档位转码前先约定目录结构我常用的约定是media/hls/{video_id}/index.m3u8切完片之后把 video_file 更新成相对路径。核心命令如下ffmpeg -i input.mp4 \ -profile:v baseline -level 3.0 \ -hls_time 10 -hls_list_size 0 \ -hls_segment_filename media/hls/12/seg_%04d.ts \ -f hls media/hls/12/index.m3u8参数说明-profile:v baseline是为了兼容旧手机和低端播放器-hls_time 10控制每个分片时长太短会增加请求数太长拖动延迟变大-hls_list_size 0表示在 m3u8 里保留所有分片默认只保留最近几个分片点播场景必须写成 0。码率档位按清晰度调整清晰度视频码率音频码率建议 hls_time360P800k64k10720P2500k128k101080P5000k192k8转码完成后把 duration 也写上ffprobe -show_format可以拿到精确时长。ffprobe -v quiet -print_format json -show_format input.mp4 | jq .format.duration3.3 播放视图与 Range 请求Django 的流式响应播放页视图要同时做两件事模板渲染播放器、提供 m3u8 和 ts 文件的流式响应。m3u8 文件很小用普通 HttpResponse 即可ts 分片和 mp4 这类媒体文件要支持断点续传Django 3 内置的 FileResponse 已经实现了 Range 处理直接用它比自己写 StreamingHttpResponse 更稳。import os from django.http import FileResponse, Http404 from django.views.decorators.http import require_GET require_GET def stream_video(request, video_id): video get_object_or_404(Video, pkvideo_id, status2) file_path video.video_file.path if not os.path.exists(file_path): raise Http404 response FileResponse(open(file_path, rb), content_typevideo/mp4) response[Content-Disposition] inline return response播放器模板里直接指向视图的 URL。要注意video.video_file.path 依赖 MEDIA_ROOT 配置云存储场景下 path 不可用到那时要么先用脚本同步到本地要么换重定向方案。3.4 防盗链时间戳签名 URL 的生成与校验HLS 的 m3u8 和 ts 文件如果直接放在 media 目录下别人拿到域名就能刷流量。常见的做法是在 URL 带上过期时间和签名服务端先校验再决定是否放行。签名逻辑很简单expire 时间戳 视频路径 密钥做 MD5。import hashlib, time from django.conf import settings def sign_url(path, expire_secs3600): expire int(time.time()) expire_secs sign hashlib.md5(f{path}{expire}{settings.MEDIA_SIGN_KEY}.encode()).hexdigest() return f{path}?expire{expire}sign{sign}校验视图和播放视图合在一起先验证 expire 是否过期、sign 是否匹配再调用 FileResponse。注意签名过期时间不要设成 10 年否则防盗链形同虚设转码后的 HLS 分片建议也走同一套签名逻辑否则播放器请求 ts 分片时会绕过校验。4. 收藏接口与后台管理Django Admin 的定制实践4.1 收藏接口的幂等设计POST 切换状态前端按钮要做“收藏/取消收藏”切换接口设计成 POST /api/favorite/{video_id}/toggle 最简单返回当前状态。幂等性是这类接口的要点快速连点两次不能出现两条记录。数据库层的 unique_together 已经挡住重复视图里再用 get_or_create 兜底。from django.http import JsonResponse from django.views.decorators.http import require_POST from django.views.decorators.csrf import csrf_exempt from django.shortcuts import get_object_or_404 require_POST def toggle_favorite(request, video_id): video get_object_or_404(Video, pkvideo_id) fav, created Favorite.objects.get_or_create(userrequest.user, videovideo) if not created: fav.delete() return JsonResponse({favorited: created, video_id: video_id})前端需要带 csrftoken否则 Django 会拒绝 POST。简单的办法是在模板里加隐藏字段或者从 cookie 里用 JavaScript 取值发 fetch 请求时加上 X-CSRFToken 头。上面的视图为了演示没写登录校验真实项目里记得加 login_required。4.2 后台管理把 Admin 变成运营台而不是默认列表Django Admin 默认生成的表单很朴素但对视频站点来说真正要解决的是“运营同学快速找到视频并改状态”。list_display、list_filter、search_fields 这三个属性最常用list_display 决定列显示list_filter 让状态、分类在侧边栏直接筛search_fields 对标题做模糊搜索。from django.contrib import admin from .models import Video, Favorite class VideoAdmin(admin.ModelAdmin): list_display (title, category, status, views_count, created_at) list_filter (status, category) search_fields (title,) readonly_fields (views_count, created_at) actions [make_published] admin.action(description上架选中视频) def make_published(self, request, queryset): queryset.update(status2) admin.site.register(Video, VideoAdmin) admin.site.register(Favorite)readonly_fields 把容易误改的手工字段锁死views_count 只允许通过代码累加actions 是批量操作的人口批量上架、下架在这种站点里比逐个编辑表单好使。收藏表注册进 Admin 的作用是排障用户说“我收藏的视频不见了”运营可以直接按用户查。配置项作用适用场景list_display控制列表列展示标题、状态、播放量list_filter侧边栏筛选按状态、分类过滤search_fields模糊搜索按标题找视频readonly_fields只读字段播放量、创建时间actions批量操作批量上下架4.3 从 Admin 到转码任务入口status 的四个状态里转码中是异步任务写回的。Admin 列表只能看到结果看不到过程。常见的改进是给 VideoAdmin 加一个自定义按钮点击后手动触发转码任务另一个增强是引入第三方后台美化主题比如 simpleui 这类 django admin 界面美化方案把状态标签做成高亮色块。这些都是锦上添花核心还是把 status 流转规则定清楚上传后置为 1转码成功置为 2ffmpeg 失败置为 1 并记录错误日志。5. 部署到 Nginx内部重定向、缓存参数与三个经典坑5.1 X-Accel-Redirect让 Nginx 读文件别让 Django 读生产环境把 MEDIA_ROOT 直接交给 Nginx 托管是最常见的做法但防盗链会失效因为静态资源的鉴权逻辑在 Django 里。折中方案是 Django 校验签名后返回 X-Accel-Redirect 头Nginx 收到后自己读文件返回流量不经过 Python 进程location /media/protected/ { internal; alias /data/var/media/; }Django 视图里校验通过后设置response[X-Accel-Redirect] /media/protected/ rel_path返回空响应即可。提示internal 目录不能和公开 media 目录重叠否则用户可以直接绕过签名访问。5.2 播放量缓存回写防止数据库击穿播放页读的次数远多于写的次数views_count 每次都直接F(views_count)1会频繁触发 UPDATE量大了数据库压力明显。我常用的写法是先放 Redis 累加定时任务批量写回。from django.core.cache import cache def record_view(video_id): key fvideo:views:{video_id} try: cache.incr(key) except ValueError: cache.set(key, 1, timeout3600)缓存要设置过期时间防止冷视频的 key 永久占内存列表页的“热门视频”建议缓存 60 秒秒级过期会让 Redis 形同虚设。5.3 三个经典坑和它们的观察点坑一上传 413。Nginx client_max_body_size 默认 1MB传电影必挂调到client_max_body_size 5g;同时把 Django 的 DATA_UPLOAD_MAX_MEMORY_SIZE 也调大。坑二分片目录没有写权限。ffmpeg 用 nginx 用户跑会写不进 media/hls表现为转码进程正常但目录是空的。检查目录属主比改代码更快。坑三收藏按钮快速双击前端以为失败了。接口本身幂等前端要做的只是请求合并或加 loading 状态。常见的验证手法是用 chrome network 面板模拟慢网络观察是否发送了两个 POST。最后的验证技巧播放失败先看 Network 面板里 m3u8 请求返回码401 是签名问题404 是转码没完成200 但播放器黑屏则优先怀疑 CORS 或分片 sec 参数。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询