ARTICLE DETAIL

资讯详情

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

django+vue构建在线继续教育系统:从模型设计到部署全解析

django+vue构建在线继续教育系统:从模型设计到部署全解析 1. 继续教育系统的核心业务与功能拆解在线继续教育系统这个题目乍一看只是个普通的管理系统但真上手做的时候你会发现它比一般的电商后台或资讯站要复杂得多。继续教育本身有一套完整的业务闭环学员注册、选课报名、在线学习、课时记录、考试测评、学时认定、证书发放再加上管理端的课程审核、教师管理、数据统计。任何一个环节做得太浅答辩或者实际落地的时候都会露怯。先把这个系统的角色和核心模块理清楚后面写代码心里才有底。我按最常见的三类角色来拆角色核心诉求对应的功能模块学员快速找到课程、记录学习进度、完成考试、拿到学时证明课程浏览、在线播放、学习进度保存、在线考试、学时查询教师/讲师发布课程内容、管理章节、出题阅卷、查看学员学习情况课程管理、章节管理、试题管理、成绩管理系统管理员审核课程与讲师资质、管理用户、查看平台运营数据用户管理、课程审核、分类管理、数据看板从技术实现的角度看这个系统最核心的难点其实不在业务本身而在于“状态流转”。用户看了一半视频进度存哪里考试做到一半断了怎么恢复课程下架了已经学到一半的学员怎么处理这些状态问题才是后端设计的重头戏。前端那边也有自己的复杂点视频播放器要不要支持m3u8格式学习进度是定时上报还是暂停时上报课程列表的筛选、搜索、分页怎么做得顺手管理端的表格、表单、权限控制怎么跟后端的接口配合我见过的很多半成品项目问题都出在“只做了展示没做状态”。课程列表能打开、详情页能看但学习进度存不住考试只能做客观题不能判分这种系统拿到真实场景里根本没法用。所以本文的每个模块我都会把状态设计说清楚。2. 技术选型django与flask的现实取舍标题里同时出现了django和flask说明你在选型上还在犹豫或者参考文献里两套方案都有。这里我不和稀泥直接说结论做在线继续教育这种偏管理型的系统优先选django如果你的重心在API快速开发且业务相对简单flask也能胜任但很多功能要自己拼装。为什么这么说咱们把两个框架在“继续教育系统”这个具体场景下的优劣势摆一摊对比维度djangoflaskORM和数据建模自带django ORM模型迁移一键完成association table处理多对多很顺手需要自己配SQLAlchemy首次集成有学习成本用户认证自带User模型、session认证、权限组改一改就能适配学员/教师/管理员需要装flask-login或自己写JWT逻辑Admin后台自带admin站点配置下就能给管理员用毕设演示很加分需要装flask-admin而且配置不如django顺滑学习进度/考试状态设计模型关系清晰ORM直接表达“用户-课程-进度”的多表关联也能做但多数要手写更多SQL或ORM查询文件/视频处理有storage抽象配合云存储或nginx分发都比较方便同样能接但需要自己规划静态文件与上传逻辑API接口开发DRFdjango rest framework生态成熟序列化、认证、分页全套写REST接口灵活但要自己组装插件如果你选flask大概率是看中它的“轻”和“自由”。flask用好了确实开发速度快因为你的代码完全自己掌控不需要迁就框架的约定。但代价是用户登录注册、权限控制、模型变更记录、admin后台这些“系统标配”flask都不会直接给你你得从零搭。一个继续教育系统做下来这些功能一个都跑不掉。反过来django的“重”主要体现在框架约束多、初始化目录结构复杂可一旦你跑通一个完整的CRUD后面的课程、章节、试题管理就全是套路化的活。特别是有django admin加持管理端的基本增删改查几乎不用写前端页面就能演示。还有一个很现实的考虑如果你用的是pycharmdjango的集成度是几个Python框架里最好的。新建项目时直接选Django模板IDE自动识别manage.py、settings.py、urls.py这些文件结构运行配置点一下就起服务。flask在pycharm里也能跑但很多模板和迁移功能需要自己装插件。所以我在下面的方案里以django djangorestframework Vue 3为主体flask我会在文末专门讲一下怎么改造成本最小。3. 环境准备与项目骨架搭建pycharm python vue的第一次磨合很多新手在环境这一步就被劝退了不是因为难而是因为“不知道什么是正常的”。我先把版本组合给出一套稳定方案再讲为什么这样配。3.1 一套能跑通全流程的版本组合Python3.10或3.113.12开始部分依赖包适配有坑不推荐新手直接用django4.2 LTS5.x也可以用但配套的第三方包可能还没跟上djangorestframework3.15Node.js18 LTS或20 LTS不要装22以上有些老依赖会警告Vue3.4配合Vite 5pycharm2023.3 专业版或社区版均可专业版对前端支持更好3.2 pycharm里的Python环境配置用pycharm打开项目目录后第一件事是配置虚拟环境。这里有个常见的坑不要直接用系统的Python解释器否则pip装了一堆包换台电脑或换个项目就全乱套。# 在pycharm终端里创建虚拟环境 python -m venv venv # Windows激活 venvScriptsactivate # macOS/Linux激活 source venv/bin/activate # 激活后命令行前缀会出现(venv)此时安装依赖 pip install django djangorestframework django-cors-headers pillow然后新建django项目django-admin startproject online_edu cd online_edu python manage.py startapp users python manage.py startapp courses python manage.py startapp exams解释一下为什么要分三个appdjango的app模块化设计本质上是让你把不同业务边界切开。users管认证和角色courses管课程、章节、学习进度exams管试题和考试成绩。以后扩展轮播图、公告、支付都可以再新开app互不干扰。3.3 Vue项目创建为什么用Vite不用vue-cli网上大量旧教程还在用vue-cli但官方生态2024年以后已经全面迁移到Vite。Vite冷启动快热更新快对新手来说最直观的感受是“保存代码后页面秒刷新”不像webpack时代每次要等好几秒。cd online_edu # 回到项目根目录 npm create vuelatest frontend这一步会交互式问你一堆配置项——TypeScript? Vue Router? Pinia? 我的建议是如果不熟TS先选JavaScriptVue Router选上因为多页面系统必须有路由Pinia看你熟悉程度不熟就用一个简单的reactive对象或localStorage做状态持久化也可以ESLint/Prettier选上能帮你规范格式但也可能在运行时报烦人的警告新手可以先skip。前端目录结构我习惯这样规划frontend/ src/ api/ # 封装axios请求 assets/ # 静态资源 components/ # 公共组件 router/ # 路由配置 stores/ # 状态管理 views/ # 页面组件 Dashboard.vue Login.vue CourseList.vue CourseDetail.vue VideoPlayer.vue ExamRoom.vue Admin/3.4 pycharm里的运行配置这是很多人忽略的一点。pycharm默认的Python运行配置只能跑Python脚本但你希望“一键跑django”和“一键跑前端”。django这边菜单 Run - Edit Configurations - 新增 Django Server在Parameters里填 runserver 0.0.0.0:8000Environment variables里加 DJANGO_SETTINGS_MODULEonline_edu.settings。前端这边如果你用的是pycharm专业版可以直接把npm run dev配到配置里社区版的话我习惯直接在pycharm的终端里打开frontend目录跑 npm run dev也不麻烦。两个服务都起来后后端在8000端口前端在5173端口。这时候直接访问前端页面会碰到一个很典型的问题——跨域我会在联调章节专门讲。4. 后端核心模块数据模型设计与API实现思路4.1 用户模型不用重复造轮子django自带的User模型已经包含用户名、密码、邮箱、权限组够用但不够满足继续教育场景。我们需要给用户加上角色字段和学时信息最干净的做法是使用Profile扩展from django.contrib.auth.models import User from django.db import models class Profile(models.Model): ROLE_CHOICES ( (student, 学员), (teacher, 教师), (admin, 管理员), ) user models.OneToOneField(User, on_deletemodels.CASCADE) role models.CharField(max_length20, choicesROLE_CHOICES, defaultstudent) real_name models.CharField(max_length50, blankTrue) total_credit models.FloatField(default0) # 累计学时 org_name models.CharField(max_length100, blankTrue) # 工作单位 def __str__(self): return f{self.user.username}-{self.role}为什么不用自定义User替换django自带用户因为替换User模型需要在第一次迁移前就配置好而且第三方应用比如DRF的token认证往往默认兼容内置User。用OneToOne扩展是最稳妥、可演进性最好的方案。4.2 课程与学习进度模型状态设计的核心继续教育系统的课程不是简单的文章列表它需要结构化组织。我的模型设计是三层Course课程标题、封面、分类、简介、讲师外键、审核状态Chapter章节所属课程、序号、标题、视频文件或m3u8地址LearningRecord学习记录用户、章节、学习进度百分比、最近学习时间、是否完成class Course(models.Model): STATUS_CHOICES ( (pending, 待审核), (published, 已发布), (offline, 已下架), ) title models.CharField(max_length200) cover models.ImageField(upload_tocovers/, blankTrue) category models.ForeignKey(Category, on_deletemodels.SET_NULL, nullTrue) teacher models.ForeignKey(User, on_deletemodels.CASCADE, related_namecourses) status models.CharField(max_length20, choicesSTATUS_CHOICES, defaultpending) created_at models.DateTimeField(auto_now_addTrue) description models.TextField(blankTrue) class Chapter(models.Model): course models.ForeignKey(Course, on_deletemodels.CASCADE, related_namechapters) order models.IntegerField(default1) title models.CharField(max_length200) video_url models.CharField(max_length500, blankTrue) # 支持mp4或m3u8地址 duration models.IntegerField(default0) # 视频时长单位秒 class Meta: ordering [order] class LearningRecord(models.Model): student models.ForeignKey(User, on_deletemodels.CASCADE) chapter models.ForeignKey(Chapter, on_deletemodels.CASCADE) progress models.FloatField(default0) # 0~1 completed models.BooleanField(defaultFalse) updated_at models.DateTimeField(auto_nowTrue) class Meta: unique_together (student, chapter)add_unique_together 这个字段组合特别重要。如果不加约束同一个学员可能对同一章节产生多条学习记录统计学时的时候就会翻倍。我在实际项目中踩过这个坑数据扣起来极其痛苦。4.3 考试模块客观题自动判分主观题人工批改考试是继续教育的核心验证手段。模型拆成Exam、ExamQuestion、ExamRecord、ExamAnswer四张表class Exam(models.Model): title models.CharField(max_length200) course models.ForeignKey(Course, on_deletemodels.CASCADE) duration_minutes models.IntegerField(default60) pass_score models.FloatField(default60) start_time models.DateTimeField() end_time models.DateTimeField() class ExamQuestion(models.Model): exam models.ForeignKey(Exam, on_deletemodels.CASCADE, related_namequestions) qtype models.CharField(max_length20, choices((single, 单选题), (multi, 多选题), (judge, 判断题), (essay, 简答题))) content models.TextField() options models.JSONField(defaultdict) # 例如 {A: 选项1, B: 选项2} answer models.JSONField(defaultlist) # 正确答案单选存 [A]多选存 [A,C] score models.FloatField(default5) class ExamRecord(models.Model): exam models.ForeignKey(Exam, on_deletemodels.CASCADE) student models.ForeignKey(User, on_deletemodels.CASCADE) score models.FloatField(nullTrue) status models.CharField(max_length20, defaultin_progress) # in_progress/submitted/graded submit_time models.DateTimeField(nullTrue, blankTrue) class ExamAnswer(models.Model): record models.ForeignKey(ExamRecord, on_deletemodels.CASCADE, related_nameanswers) question models.ForeignKey(ExamQuestion, on_deletemodels.CASCADE) student_answer models.JSONField(defaultlist)这里有个设计取舍为什么考卷答案用JSONField存而不是传统的“每道题一行用户做了就插入一行”因为考试场景天然是一次性提交不要求频繁逐题回写而且JSONField存数组可以直接跟标准答案做集合比较判分逻辑可以写得非常简洁def grade_exam(record): total_score 0 for answer in record.answers.all(): question answer.question if question.qtype essay: continue # 简答题留给老师手动判分 correct set(question.answer) student set(answer.student_answer) if question.qtype judge or question.qtype single: if correct student: total_score question.score else: # 多选题全对才得分少选或多选都不得分 if correct student: total_score question.score record.score total_score record.status graded if total_score is not None else submitted record.save()这种判分逻辑足够应对90%的继续教育考试需求。如果你的业务要求“少选但选对的部分给半分”那就是另一套计分策略可以在多选题分支里加权重计算。4.4 使用DRF编写课程APIDRF的core点是ViewSet加Serializer。课程列表和详情我可以直接用ReadOnlyModelViewSet加权限控制from rest_framework import viewsets, permissions from .models import Course, Chapter from .serializers import CourseListSerializer, CourseDetailSerializer class CourseViewSet(viewsets.ReadOnlyModelViewSet): queryset Course.objects.filter(statuspublished) permission_classes [permissions.IsAuthenticated] def get_serializer_class(self): if self.action retrieve: return CourseDetailSerializer return CourseListSerializerSerializer这边要注意嵌套关系的深度。课程详情需要包含章节列表但章节不要在外面再嵌套一大堆内容否则接口payload会很大。同时建议在详情接口里带上“当前用户的学习进度”这样前端播完视频刷新页面才能立即知道这一节是否已完成class ChapterProgressSerializer(serializers.ModelSerializer): progress serializers.SerializerMethodField() completed serializers.SerializerMethodField() class Meta: model Chapter fields [id, order, title, video_url, duration, progress, completed] def get_progress(self, obj): user self.context[request].user record LearningRecord.objects.filter(studentuser, chapterobj).first() return record.progress if record else 0 def get_completed(self, obj): user self.context[request].user record LearningRecord.objects.filter(studentuser, chapterobj).first() return record.completed if record else False4.5 学习进度上报接口设计要防“刷学时”这个点是继续教育系统最容易被诟病的地方也是答辩时老师经常追问的学员把视频挂在后台快进你系统怎么判定他真的学完了我的方案是把进度上报接口做成“心跳式”。前端每隔15秒提交一次当前观看进度接口做两件事更新播放进度百分比同时把两次上报之间的时间差计入有效学习时长。api_view([POST]) permission_classes([permissions.IsAuthenticated]) def report_progress(request, chapter_id): chapter Chapter.objects.get(pkchapter_id) progress float(request.data.get(progress, 0)) # 校验progress合法性 if not (0 progress 1): return Response({error: progress参数非法}, status400) record, created LearningRecord.objects.get_or_create( studentrequest.user, chapterchapter ) # 上一次上报时间戳通过前端传last_timestamp或服务器自己记 last_ts record.updated_at.timestamp() current_ts time.time() delta min(current_ts - last_ts, 60) # 单次上报间隔超过60秒按60秒记 record.watch_seconds delta record.progress max(record.progress, progress) if record.progress 0.95: record.completed True record.save() return Response({status: ok, credit_seconds: record.watch_seconds})这个设计防不了真正的恶意刷学时但能拦住最常见的“把视频扔后台快进完”的情况——因为每次上报的delta受60秒封顶限制你必须老老实实挂满时长。5. 前端页面Vue组件化开发与接口联调的关键细节5.1 路由与权限控制继续教育系统前端页面的第一道门槛是登录访问控制。Vue Router的beforeEach钩子里做全局鉴权// router/index.js router.beforeEach((to, from, next) { const token localStorage.getItem(access_token) if (to.meta.requiresAuth !token) { next({ path: /login, query: { redirect: to.fullPath } }) return } if (to.path /login token) { next(/) return } next() })但这只是前端层面的按钮隐藏后端每个接口的permission_classes才是真正的安全底线。这两个缺一不可前端防“看不见”后端防“恶意调接口”。5.2 视频播放m3u8支持是避不开的课题在线教育系统里视频播放格式绕不开m3u8。m3u8本质是Apple的HLS协议把长视频切片成一个个TS分片播放器逐个请求。好处是支持清晰度切换和拖动流畅坏处是原生HTML5 video标签默认不支持必须引入hls.js库。// components/VideoPlayer.vue template div classplayer-wrapper video refvideoRef controls playsinline/video /div /template script setup import { ref, onMounted, watch } from vue import Hls from hls.js const props defineProps({ src: { type: String, required: true }, progress: { type: Number, default: 0 } }) const videoRef ref(null) function initPlayer() { const video videoRef.value if (video.canPlayType(application/vnd.apple.mpegurl)) { // Safari原生支持HLS直接赋值 video.src props.src } else if (Hls.isSupported()) { const hls new Hls() hls.loadSource(props.src) hls.attachMedia(video) window.__hls hls } else { alert(当前浏览器不支持HLS播放) } } function reportProgress() { const video videoRef.value const ratio video.currentTime / video.duration // 上报进度到后端 // ... } /script进度续播是另一个体验细节。用户上次看到58%刷新页面后应该从58%继续。方法就是进入播放器时读取后端返回的progress在onloadedmetadata回调里设置currentTimevideo.addEventListener(loadedmetadata, () { if (props.progress 0.02 props.progress 0.98) { video.currentTime video.duration * props.progress } })5.3 Element Plus还是Ant Design Vue热搜词里同时提到了ant design vue和vue的组件库选型。两个库都很成熟我的建议是看你的审美偏好和习惯如果之前写过React Ant Design那直接选ant-design-vueAPI熟悉度无缝衔接如果是循规蹈矩做管理后台选Element Plus中文文档详细示例代码多遇到问题一搜一大把我这次选Element Plus主要是它的Form校验、Table组件、分页组件开箱即用配合Vue 3的Composition API写起来很顺手。课程管理页面用el-table展示数据状态切换用el-tag筛选区用el-select和el-date-picker半天时间就能把管理端“骨相”搭出来。5.4 学习中心页面选课、学习、考试三联动学员端最重要的页面是“学习中心”它需要把课程列表、我的课程、我的考试三个入口整合在一起。“我的课程”应该展示报名了哪些课、总学时进度多少。这个页面的数据来自后端聚合接口课程总数、已完成章节数、累计学习时长、待参加考试数。后端聚合好返回一个dashboard摘要前端不用做多次请求{ course_count: 6, finished_chapter_count: 18, total_chapter_count: 32, total_credit: 45.5, pending_exams: 2 }前端拿到这个数据画几个进度环或卡片页面看起来就很专业。6. 前后端联调跨域、认证与接口对接的实操排错这一章是很多新手项目“卡死”的地方。前端页面写好了后端接口测通了但放到浏览器里一访问全是红叉。我按排查链路把最常见的问题列出来。6.1 跨域的根治方案前端跑在5173后端跑在8000浏览器会默认拦截跨域请求。django后端需要安装django-cors-headers并配置白名单# settings.py INSTALLED_APPS [ ... corsheaders, ] MIDDLEWARE [ corsheaders.middleware.CorsMiddleware, ... ] CORS_ALLOWED_ORIGINS [ http://localhost:5173, http://127.0.0.1:5173, ]注意配置完CORS后记得重启django服务middleware和settings的改动不会自动reloadrunserver的自动重载是监控文件变化但有时新装包的middleware不生效我遇到过几次重启最保险。6.2 认证头axios拦截器前后端分离后登录态的传递靠Token。axios请求拦截器统一往headers里塞Authorization字段// api/request.js import axios from axios import { ElMessage } from element-plus import router from /router const request axios.create({ baseURL: http://localhost:8000/api, timeout: 15000 }) request.interceptors.request.use(config { const token localStorage.getItem(access_token) if (token) { config.headers.Authorization Bearer ${token} } return config }) request.interceptors.response.use( response response.data, error { if (error.response?.status 401) { localStorage.removeItem(access_token) router.push(/login) } ElMessage.error(error.response?.data?.detail || 请求失败) return Promise.reject(error) } )这里要特别提醒401的处理逻辑必须放在响应拦截器里。不然每个页面组件都要写一遍“没登录就跳登录页”代码重复无比。6.3 轮询式自动保存别等页面关闭才存进度视频学习页面的进度上报不能依赖beforeunload事件因为刷新、崩溃、切后台都会导致丢失。正确的做法是结合时间监听和事件触发每15秒触发一次进度上报定时器视频暂停时立即上报视频播放结束时立即上报页面切换路由时onBeforeUnmount立即上报定时器方案有一个隐患如果用户切到其他tab浏览器会降低定时器执行频率。所以上报时要带上video.currentTime的实际值后端以progress为准而不是以“上报次数”为准。6.4 前端报错排查的常用动作错误现象可能原因排查动作Network Error后端没启动或CORS未配置先curl一下后端接口看通不通再看浏览器Network面板标红请求401 UnauthorizedToken缺失或过期检查localStorage有没有tokenAuthorization头格式是否正确404 Not FoundURL路径拼错打开Network看实际请求的URL跟django的urls.py比对500 Internal Server Error后端代码异常看后端命令行的Traceback前端只显示服务器内部错误CORS policy报错白名单漏配检查CORS_ALLOWED_ORIGINS里是否包含香菇前端域名6.5 一个排查案例课程发布后前端列表不显示我在实际项目里遇到过这个状况教师通过django admin发布了课程状态改成published但前端课程列表始终是空的。排查链路是先用浏览器直接请求课程列表APIhttp://localhost:8000/api/courses/返回了数据说明后端没问题。再看前端请求Network面板请求返回200数据也是完整的。最后发现是前端v-for渲染时filter写错了我误用了v-ifcourse.status published但status字段在API序列化时返回的是中文标签“已发布”而非代码值“published”。这个问题的教训是API字段返回的是代码值还是展示值前后端约定一定要提前定死。我的做法是Serializer里保存代码值展示文字由前端映射const statusMap { pending: 待审核, published: 已发布, offline: 已下架 }7. 部署上线django在Linux服务器上的常见问题与处置项目开发完、答辩通过、要真正部署到服务器这时候会踩到另一批坑。这里我不重复那些“照着宝塔面板点几下就装好”的教程重点讲部署过程中技术核心的理解。7.1 django静态文件与媒体文件的分离部署django到生产环境时runserver是不能用的得有gunicorn跑应用nginx做反向代理。其中最容易出错的配置是静态文件和媒体文件settings.py里DEBUG False ALLOWED_HOSTS [your-domain.com, 服务器IP] STATIC_URL /static/ STATIC_ROOT BASE_DIR / staticfiles MEDIA_URL /media/ MEDIA_ROOT BASE_DIR / media先执行python manage.py collectstatic收集所有静态文件再把media目录和static目录都映射到nginx里location /static/ { alias /path/to/online_edu/staticfiles/; } location /media/ { alias /path/to/online_edu/media/; } location / { proxy_pass http://127.0.0.1:8000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; }7.2 gunicorn启动与进程守护gunicorn的启动命令需要注意worker数量。按CPU核心数配2核机器给2-4个worker即可不要贪多gunicorn online_edu.wsgi:application -w 4 -b 127.0.0.1:8000 --timeout 120--timeout 120很重要。如果你的视频上传接口或Excel导出接口执行时间超过默认30秒就会被gunicorn掐断。7.3 宝塔部署和命令行部署的取舍热搜词里出现了“宝塔部署django”说明这个工具在国内开发者中使用率确实高。我的建议是如果你对Linux不熟、只想快速把站点跑起来宝塔确实省事但如果你追求对环境的完全控制建议手动来一遍至少要知道nginx、gunicorn、systemd这些组件各自做了什么。宝塔部署django的麻烦之处在于Python版本管理。宝塔自带的Python版本可能比较旧而django 4.2要求Python 3.8以上。安装新版本Python时要注意别把系统的python覆盖了官方推荐用pyenv或者宝塔的Python项目管理器两者能隔离环境不会搞乱系统依赖。7.4 数据库迁移的生产注意事项在服务器上执行python manage.py migrate时如果报错提示某个表already exists通常是因为之前跑过一次迁移或Django检测到迁移文件和数据库状态不一致。处理办法是先执行python manage.py makemigrations --check检查迁移文件是否完整再手动进入数据库查看django_migrations表对比。注意千万不要在生产库上直接删表重建尤其当里面已经有真实学员的学习记录和考试成绩时。正确做法是拷贝数据库到本地在本地测试环境中把迁移问题解决再回放到生产环境。8. flask方案速改指南与尾声经验如果你最终仍然选择了flask我给你一条“最小改造路径”不需要推翻整套思路。flask版本的架构可以这样组织flask_app/ app.py # 应用入口 models.py # SQLAlchemy模型 auth.py # 登录注册蓝本 courses.py # 课程蓝本 exams.py # 考试蓝本 requirements.txt模型直接用SQLAlchemy定义字段设计与前面django版保持一致只是ORM语法略有差别。因为继续教育系统的核心难点在业务状态设计这部分跟框架本身关系不大把前面的模型表拿过来SQLAlchemy迁移时改一改类型映射就行。认证那部分我的建议是别用flask-login的session方案它在前后端分离场景下需要配置跨域cookie麻烦。直接用PyJWT生成token写一个before_request的装饰器校验token这套逻辑很简单代码量不超过50行import jwt from functools import wraps from flask import request, jsonify SECRET_KEY your-secret-key def token_required(f): wraps(f) def decorated(*args, **kwargs): token request.headers.get(Authorization, ).replace(Bearer , ) if not token: return jsonify({detail: 未登录}), 401 try: payload jwt.decode(token, SECRET_KEY, algorithms[HS256]) current_user_id payload[user_id] except jwt.ExpiredSignatureError: return jsonify({detail: 登录已过期}), 401 except jwt.InvalidTokenError: return jsonify({detail: 无效Token}), 401 return f(current_user_id, *args, **kwargs) return decoratedflask的优势在部署时反而体现出来进程模型简单同样用gunicorn跑配置几乎不用改。根据我个人的体会无论选django还是flask在线继续教育系统能不能做到“能用”关键不在框架、不在页面好不好看而在于三个基础细节有没有想透学习进度状态是否可靠、考试判分逻辑是否严谨、权限控制是否覆盖到每一个接口。这三个细节通关了系统就已经具备实用价值这三个做不好页面再精致演示完了依然会被问倒。最后一个小经验项目开发过程中一定要养成写接口文档的习惯。哪怕就是用一个简单的Markdown文件记录每个接口的路径、参数、返回结构也能让你在前后端联调时节省大量互相扯皮的时间。如果项目周期紧可以用DRF自带的文档或apifox直接抓接口生成把精力省下来去调试边界情况——比如“学员同时打开两个浏览器Tab学习同一门课学习进度会不会互相覆盖”这种问题才是答辩时真正显功力的地方。
返回列表