ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

基于Django的在线课程学习平台:从权限设计到部署实战

基于Django的在线课程学习平台:从权限设计到部署实战 接手过不少Django项目也帮人排查过很多次线上故障我发现在线教育类系统里“跑起来”从来不是难点真正拉开差距的是数据模型设计、视频权限控制和部署调试这一整条链路。这篇文章就以“基于Django的网络课程在线学习平台”为例从技术选型开始到核心功能拆解再到远程调试和部署实战把我实际做过、踩过、最后验证过的方案完整写出来。适合正在做毕业设计、想快速搭建课程平台原型、或者准备在Django项目上做二次开发的同学参考。1. 为什么选Django做在线课程平台一次完整的技术选型复盘1.1 在线教育平台的典型需求画像网络课程在线学习平台单看名字好像是一个很常见的CRUD项目但只要你把需求展开就会发现它天然带有“多角色、内容重、权限严”这三个特征。先说多角色。一个完整的课程平台至少有三种身份管理员要维护课程分类、审核课程内容、查看平台数据讲师要上传视频、编辑章节、管理自己名下的课程学生要注册登录、浏览课程、选课学习、记录学习进度。这就意味着用户体系不能只做一张表存用户名密码还要有角色区分和权限控制。再说内容重。课程平台的核心资产是视频而视频跟普通文本、图片不一样它涉及文件上传、格式校验、存储路径、播放权限、防盗链、进度记录等一系列问题。很多初学者在做好了课程标题、课程简介的增删改查之后以为项目已经完成80%实际上视频处理这块才是真正的深水区。最后是权限严。一个付费或半付费的课程平台必须保证只有选了课的用户才能看视频访客最多只能看到课程封面和简介。这个权限校验不能只靠前端隐藏播放按钮必须在后端每个视频请求里都做真实校验。需求一旦梳理到这个程度你对技术栈的要求就很清楚了。1.2 Django凭什么合适与Flask、Spring Boot的对比在做技术选型的时候很多人会纠结Django、Flask、Spring Boot这几个框架怎么选。我的观点很直接如果是做一个课程平台这种“业务模块多、需要后台管理、需要快速出成果”的项目Django几乎是性价比最高的方案。Django自带Admin后台这是它最大的杀手锏。课程分类、课程信息、用户管理、订单记录这些后台管理页面在Django里几乎不用写代码注册一下Model就能直接用。对于毕设或者内部系统来说省下的时间非常可观。Django的ORM也是实打实的生产力工具。课程、章节、选课记录这些表关系用ORM外键描述起来非常直观迁移工具还能帮你从模型自动生成数据库表结构不用手写SQL。相比之下Flask确实足够轻量但轻量也意味着组件要靠自己拼装。你需要自己选择SQLAlchemy还是Peewee自己接Flask-Login做登录自己写后台管理页面项目规模一大容易在细节上失控。Spring Boot在企业级开发里很强生态完整、性能可靠但学习曲线明显更陡对于以Python为主、或者想在短时间内完成项目的开发者来说用Spring Boot做课程平台多少有点杀鸡用牛刀。我把选型对比整理成一个表方便你按自己的场景判断。对比维度DjangoFlaskSpring Boot开发效率高自带Admin和ORM中组件需自己组合中低配置项多学习成本中概念统一教程多低入门容易进阶难高Java体系复杂后台管理开箱即用需要自建需集成或自建适合场景内容管理、后台系统、课程平台小型API、微服务大型企业级系统1.3 这套毕设项目的整体架构这套网络课程在线学习平台我建议采用经典的Django MTV架构前端用Django模板加Bootstrap后端用Django原生ORM数据库用MySQL文件存储先用本地目录的MEDIA_ROOT。浏览器访问的完整链路是这样的URL路由根据地址分发到对应的视图函数视图函数通过ORM查询MySQL拿到课程数据把数据丢给模板渲染成HTML再返回给浏览器。如果是视频文件的请求会由Nginx直接处理静态媒体文件同时在后端做权限校验。这个架构的好处是每一层职责清晰出了问题容易定位而且对毕设来说不用引入Redis、Celery这类中间件就可以满足大部分需求。如果后续要优化再把课程播放量统计放到Redis里异步处理就行扩展路径很平滑。2. 核心业务模块拆解从用户体系到课程权限2.1 用户角色的设计与权限控制用户模块是整个平台的基石。我见过很多项目在这里走了弯路最大的问题是自己建了一张User表然后从登录注册到Session管理全部手写。实际上Django自带的认证系统已经非常完整正确做法是继承AbstractUser扩展自己的用户表。扩展之后通过一个用户类型字段区分学生、讲师和管理员配合Django自带的装饰器做视图级权限控制。看课的学生只能访问学习相关的页面讲师进不了后台管理管理员通过Django Admin管理所有数据。from django.contrib.auth.models import AbstractUser from django.db import models class User(AbstractUser): USER_TYPE_CHOICES ( (student, 学生), (teacher, 讲师), (admin, 管理员), ) user_type models.CharField(max_length20, choicesUSER_TYPE_CHOICES, defaultstudent) nickname models.CharField(max_length50, blankTrue) avatar models.ImageField(upload_toavatar/%Y/%m/, blankTrue)在视图里做权限控制的时候我一般这样处理先判断用户是否登录再判断用户类型最后判断是否选了这门课。三个条件缺一不可。很多项目只做了登录校验选课权限和后端播放校验都没做结果浏览器里直接输入视频文件地址就能看这在答辩的时候是很容易被追问的问题。2.2 课程与章节的数据模型设计课程和章节是一对多的关系这是整个系统最核心的数据结构。设计的时候要注意几个细节课程要有封面图、排序权重因为首页要展示推荐课程章节要有视频文件和时长因为播放器需要展示总时长课程和用户之间要有选课关系表因为这是判断用户有没有权限看视频的依据。from django.db import models from django.contrib.auth import get_user_model User get_user_model() class CourseCategory(models.Model): name models.CharField(max_length50, uniqueTrue) class Course(models.Model): title models.CharField(max_length200) cover models.ImageField(upload_tocourse/cover/%Y/%m/, blankTrue) category models.ForeignKey(CourseCategory, on_deletemodels.SET_NULL, nullTrue) teacher models.ForeignKey(User, on_deletemodels.CASCADE, related_namecourses) desc models.TextField(blankTrue) is_published models.BooleanField(defaultFalse) created_at models.DateTimeField(auto_now_addTrue) class Chapter(models.Model): course models.ForeignKey(Course, on_deletemodels.CASCADE, related_namechapters) title models.CharField(max_length200) video models.FileField(upload_tocourse/video/%Y/%m/) video_duration models.IntegerField(default0, help_text视频时长秒) sort models.IntegerField(default0) class UserCourse(models.Model): user models.ForeignKey(User, on_deletemodels.CASCADE, related_nameuser_courses) course models.ForeignKey(Course, on_deletemodels.CASCADE, related_nameuser_courses) created_at models.DateTimeField(auto_now_addTrue)这里有个容易被忽略的点章节里面的sort字段。很多初次设计的人会忽略排序字段结果章节顺序只能按照创建时间排一旦中途插入新章节顺序就乱了。同样的情况在课程列表里也存在所以课程表里也可以加sort或者一个推荐权重字段展示页的排序逻辑才可控。外键的on_delete策略也要想清楚。课程分类被删除时课程怎么办我一般用SET_NULL保留课程本身避免误删分类导致整个课程列表炸掉。讲师删除账号时课程怎么办这里用CASCADE等于讲师账号注销时同步清理其名下课程语义上比较合理。2.3 课程分类与检索的实现思路课程列表页是所有用户进入平台后的第一站它既要支持分类筛选也要支持关键词搜索还要处理分页。Django的Paginator做分页很顺手搜索用Q对象可以同时查标题、简介和讲师名称。from django.db.models import Q from django.core.paginator import Paginator def course_list(request): keyword request.GET.get(keyword, ) category_id request.GET.get(category, ) courses Course.objects.filter(is_publishedTrue) if keyword: courses courses.filter( Q(title__icontainskeyword) | Q(desc__icontainskeyword) | Q(teacher__username__icontainskeyword) ) if category_id: courses courses.filter(category_idcategory_id) courses courses.order_by(-created_at) paginator Paginator(courses, 9) page paginator.get_page(request.GET.get(page)) return page用ORM而不是裸SQL的一大好处是防注入。Q对象加上icontains筛选Django会帮你转义特殊字符不需要手动拼接字符串。不过要注意ORDER BY和大量数据的全文搜索用ORM就不太够用了那是搜索引擎的活。对毕设或者中小型课程平台来说这个方案简单可靠完全够用。3. 视频处理与播放权限控制最容易翻车的技术点3.1 视频上传的格式与存储方案视频上传是整个平台里最容易出问题的环节而且问题往往出现在你意想不到的地方。首先是格式很多同学在自己电脑上测试时用的是MP4没有问题但一换到线上用户传了一个MOV或者MKV浏览器直接无法播放。我的建议是上传时做格式校验服务端只接受MP4、WebM这类适合Web播放的格式在上传接口里对扩展名做白名单判断。存储方面数据库里绝对不要存视频二进制数据而是存文件路径。Django的FileField上传后会保存到MEDIA_ROOT配置的目录中数据库里存的是相对路径。视频文件往往比较大本地上传时要注意几个问题一是Nginx默认有client_max_body_size限制默认是1M不调的话大视频直接传不上去二是Django处理大文件上传时会把文件先写临时目录要确保磁盘空间充足三是文件命名不要用原始文件名中文名、空格都会带来麻烦最好用uuid重命名。import uuid import os def upload_video_path(instance, filename): ext os.path.splitext(filename)[1].lower() filename f{uuid.uuid4().hex}{ext} return os.path.join(course/video, str(instance.course_id), filename)这段代码里的upload_video_path会作为FileField的upload_to参数每次上传时自动生成一个新的UUID文件名既避免了重名覆盖也杜绝了路径穿越这类低级安全问题。3.2 播放权限与防盗链的常见做法视频权限控制是整个平台安全性的核心也是最容易被答辩老师提问题的地方。很多同学在播放页用Django模板的if判断一下用户是否登录、是否选了课然后决定展示还是隐藏视频标签以为这样就安全了。实际上视频地址已经暴露在页面源码里别人拿到URL直接就能下载。正确的做法是后端拦截。我采用的方案是所有视频播放请求都先走一个视图函数在这个视图里做登录校验、选课校验、章节所属课程校验全部通过后才把视频文件内容返回给浏览器。也就是说视频文件的真实路径永远不出现在前端的video标签里前端播放的URL是类似 /media/play/?chapter_id1 这样的接口地址。from django.http import FileResponse, HttpResponseForbidden from django.contrib.auth.decorators import login_required login_required def play_video(request): chapter_id request.GET.get(chapter_id) chapter Chapter.objects.filter(idchapter_id).first() if not chapter: return HttpResponseForbidden(章节不存在) enrolled UserCourse.objects.filter( userrequest.user, coursechapter.course ).exists() if not enrolled and request.user.user_type ! teacher: return HttpResponseForbidden(请先选课) video_path chapter.video.path response FileResponse(open(video_path, rb), content_typevideo/mp4) return response这个方案的优点是权限和文件读取都在后端完成前端拿不到真实路径。缺点是需要后端配合做流式响应Django的FileResponse其实已经处理了分块读取所以性能上问题不大。如果追求更高的性能可以在Nginx层做X-Accel-Redirect内部跳转但这是后续优化的事了。3.3 视频转码与切片从本地视频到流畅播放直接播放MP4文件在局域网或者测试环境里问题不大但生产环境里会遇到两个问题一是视频文件太大用户拖动进度条时浏览器要下载完对应区间才能播放体验差二是编码格式五花八门浏览器兼容性无法保证。我的建议是引入ffmpeg做一次转码和切片把视频转换为H.264编码的MP4或者更进一步切片成HLS格式。HLS把视频切成一个个几秒的ts小文件播放时按需加载拖动进度条几乎不卡顿而且天然支持自适应码率。ffmpeg -i input.mp4 -c:v libx264 -c:a aac -strict -2 -b:v 1500k output.mp4如果要做HLS切片命令类似下面这样ffmpeg -i output.mp4 -codec copy -hls_time 10 -hls_list_size 0 output.m3u8切片生成后前端用hls.js库播放视频加载速度和拖动流畅度都会明显提升。在毕设项目里这个能力可以作为加分项写在文档里因为大多数同学做的平台还停留在直接扔一个MP4给浏览器播放的阶段。4. 学习进度与数据统计让平台“活”起来的关键4.1 学习进度记录的数据结构与断点续看逻辑前面做的课程、章节、选课本质上还是静态的内容管理学习进度记录才让整个平台具备“在线学习”的属性。用户在某个章节看到一半退出下次登录应该能接着看而不是重新从头播放。实现方式不算复杂关键是数据结构要设计好。我给每个用户和每个章节创建一条学习记录记录里保存已经播放的时长和视频总时长。前端播放器定时把当前进度上报到后端后端更新这条记录。class StudyRecord(models.Model): user models.ForeignKey(User, on_deletemodels.CASCADE) chapter models.ForeignKey(Chapter, on_deletemodels.CASCADE) watched_seconds models.IntegerField(default0) duration models.IntegerField(default0) is_finished models.BooleanField(defaultFalse) updated_at models.DateTimeField(auto_nowTrue) class Meta: unique_together (user, chapter)unique_together保证了同一个用户对同一个章节只会有一条记录上报接口反复调用也只会更新不会产生垃圾数据。前端拿到记录后可以判断watch_seconds是否大于0如果大于0就把播放器的当前时间设置到这个位置实现断点续看。上报进度时要注意频率。每秒钟上报一次太浪费请求我一般让前端每5秒上报一次在暂停、切换章节、离开页面时再补一次。还要加一个防刷策略只有播放时长达到视频总时长的80%以上才把is_finished标记为True避免用户直接把进度条拖到末尾刷完成率。4.2 课程评价与互动功能的设计一个课程平台如果只有视频没有互动学习氛围会差很多。常见的互动功能是课程评价和评论区。评论的数据结构很简单核心是用户、课程、内容、星级这些字段。这里我要提醒一个问题用户输入的内容默认是不可信任的。Django模板渲染时会自动转义HTML但只要你在某个地方用了mark_safe或者把用户评论存成了HTML再原样输出就会产生XSS漏洞。我的建议是内容一律按纯文本存储和展示评论里如果有表情符号就用Unicode字符不要允许用户提交任何HTML标签。评论区还要考虑排序和分页。热门评论按点赞数排最新评论按时间排这个逻辑不复杂但要在设计之初就想好否则后面加字段会比较痛苦。如果课程需要评分功能可以单独建一张评分表或者直接在评论表里加一个score字段统计时取平均值展示在课程详情页。4.3 后台数据统计与分析思路平台跑起来之后管理员最关心的是这些数据注册用户数、课程数量、选课人数、视频播放量。Django Admin本身已经能展示这些数据但要想更直观可以做一个简单的数据卡片页面。我一般用一个Dashboard视图把核心指标通过ORM聚合查出来再用模板渲染成卡片。选课最多的课程排行、播放量最高的章节排行这些都可以用annotate加Count或者Sum实现。from django.db.models import Count, Sum top_courses Course.objects.annotate( student_countCount(user_courses) ).order_by(-student_count)[:10]播放量统计这个指标有个坑如果每次进入章节都累加会出现一个用户反复进出导致播放量虚高的情况。我采用的方案是同一个用户对同一个章节在当天只记一次播放或者用StudyRecord的updated_at来判断两次播放间隔超过一定时间才算一次新的有效播放。这些统计逻辑不需要很复杂但一定要有防刷意识。答辩老师问到“平台上线后怎么判断数据是否可信”你能回答出防刷策略整个项目的完成度会明显上一个台阶。5. 远程调试与部署实战毕设项目从本机到服务器5.1 用远程调试定位线上的棘手Bug本地能跑不代表线上能跑这是部署环节最让人头疼的地方。我遇到过DEBUGFalse之后静态文件加载不出来、MySQL字符集导致中文乱码、服务器上Pillow库缺失导致图片上传500这些问题如果不掌握远程调试手段只能靠猜来解决问题。PyCharm专业版提供了远程解释器功能可以通过SSH连接服务器、同步代码、在服务器端运行和调试项目。这样你在本地打的断点会真实地在服务器环境里命中看变量、看堆栈、单步执行效率比print大法高得多。配置路径是PyCharm的Settings里选择Python Interpreter点击Add Interpreter选On SSH填上服务器的IP、端口、用户名密码再选择服务器上虚拟环境里的Python解释器即可。如果没有专业版也可以用最朴素的日志排查法。在代码的关键位置加上logging或者print把错误信息输出到日志文件再用journalctl或者tail命令查看。关键是日志要分级、要带上模块名和行号这样能快速定位到具体代码位置。我印象最深的一次排查经历是这样的部署后所有接口都正常但只要一上传图片就报500。远程调试打上断点才发现报错发生在Pillow读取图片的EXIF信息时因为服务器上的Pillow版本是8.2而本地是9.1两者对某些损坏图片的处理逻辑不一样。升级服务器版本之后问题立刻消失。5.2 服务器环境配置与项目部署流程部署这套Django项目我推荐用Linux服务器加Nginx加uWSGI或者Gunicorn加MySQL的组合。Python环境用虚拟环境隔离不要直接把项目装到系统Python里否则后面升级依赖时非常容易冲突。部署的完整流程大概是这样的在服务器上安装Python 3.8、MySQL、Nginx。创建虚拟环境并安装项目依赖。配置MySQL的数据库、账号、字符集。修改settings.py里的数据库配置、DEBUG、ALLOWED_HOSTS。执行makemigrations和migrate创建数据库表。用collectstatic收集静态文件。用uWSGI启动项目再用Nginx反向代理。python3 -m venv venv source venv/bin/activate pip install -r requirements.txt python manage.py makemigrations python manage.py migrate python manage.py collectstatic --noinput python manage.py createsuperusersettings.py里有一个必须改的配置就是ALLOWED_HOSTS。不填上服务器的域名或者IPDjango会直接拒绝请求并返回DisallowedHost。另一个必须改的是SECRET_KEY不要使用仓库里自带的默认值部署前重新生成一份。5.3 静态文件、媒体文件与数据库的迁移很多从本机搬到服务器上的Django项目第一个报错就是静态文件全挂页面没有任何样式。原因是Django的runserver会自动处理静态文件但一旦换到uWSGI加Nginx的模式就要靠collectstatic把静态文件收集到STATIC_ROOT指定的目录再由Nginx来处理这些文件的访问。Nginx配置的要点是这样两块location /static/ { alias /home/project/static_root/; } location /media/ { alias /home/project/media/; }一个对应Django的STATIC_URL一个对应MEDIA_URL。视频文件、课程封面、用户头像都在media目录下如果不配置Nginx直接访问所有上传文件都会404。数据库迁移这块最常见的坑是本地用的SQLite到了线上换成MySQL。开发时SQLite确实省事但SQLite和MySQL在字段类型、事务行为上有差异最好的做法是一开始就用MySQL这样部署的时候就不用经历一次数据库类型切换。如果已经用了SQLite可以通过Django的dumpdata和loaddata命令备份和恢复数据。6. 拿到源码之后怎么学跑通、读懂、二次开发6.1 五分钟跑通本地环境的全流程拿到一套完整的Django项目源码第一件事不是打开代码逐行阅读而是把它跑起来。只有程序动起来了你对代码的感知才是真实的。跑通项目的标准流程大概是创建虚拟环境安装依赖配置数据库执行迁移创建超级管理员然后启动开发服务器。python -m venv venv source venv/bin/activate pip install -r requirements.txt python manage.py makemigrations python manage.py migrate python manage.py createsuperuser python manage.py runserver这几步做完访问http://127.0.0.1:8000应该能看到首页访问/admin用超级管理员登录后能进入后台。这个过程中最容易出问题的就是安装依赖。如果项目的requirements.txt里有版本过旧的包当前Python版本可能会安装失败。比较稳妥的做法是先把全部依赖升级或者调整到与当前Python版本兼容的版本但要注意Django主版本之间差异比较大比如Django 2.x和Django 4.x的urls写法、中间件配置都不一样不要盲目升级到最新版。6.2 读懂Django项目源码的阅读顺序项目跑起来后我建议按照这样的顺序读代码先看settings.py了解配置再看urls.py了解路由然后按app逐个读models、views、templates。读代码最忌讳的是从头到尾一个个文件啃很容易迷失在细节里。我的方法是找一条主线比如点开首页、选一门课、点进播放页然后顺着这条用户操作链路去追代码。从浏览器地址栏的URL开始找到路由规则找到对应的视图函数再看视图里查询了哪些模型最终渲染的是哪个模板。一条链路走通了项目整体结构自然就清楚了。这个过程里你会发现Django的MVC准确说是MTV分层非常规整models管数据结构views管业务逻辑templates管页面展示urls负责把请求路由到正确的视图。只要掌握了这条链路任何市面上常见的Django开源项目你都能用同一种方法快速读进去。6.3 从毕设到真正可用产品的扩展方向如果这套系统不是为了答辩而是真正打算给学校或者外部机构使用有几个方向值得优先投入。第一个是视频存储和分发。本地目录存视频的问题是磁盘空间有限、出口带宽有限多人同时看视频时卡顿严重。可以换成云存储加CDN分发视频上传到云端播放入口走CDNDjango只管权限和签名。同时配合云厂商的视频转码服务省去自己折腾ffmpeg的时间。第二个是异步任务。课程数据量大了之后选课成功通知、视频转码、数据报表生成这些耗时任务都可以放进Celery里异步处理避免同步等待阻塞请求。第三个是前端体验。纯Django模板加Bootstrap能满足基本需求但动态交互比较弱。可以把课程列表、播放页改造成Vue或者React单页应用Django作为纯API后端用DRF提供接口体验会明显提升。第四个是数据安全。线上的课程平台一定要做访问限流、文件下载控制、敏感操作审计。之前提到的视频防盗链只是一个起点更完整的方案包括登录态校验、签名URL、IP限制等多层配合。我个人的体会是这套基于Django的课程平台本身是一个麻雀虽小五脏俱全的项目它涵盖了用户权限、内容管理、文件上传、在线播放、数据统计这些通用的业务场景。认真把它做完一遍你对Django的理解会有一个质变后续再接触任何Web项目都会觉得清晰很多。
返回列表