news 2026/9/11 11:00:31

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
django+vue构建在线继续教育系统:从模型设计到部署全解析

1. 继续教育系统的核心业务与功能拆解

在线继续教育系统这个题目,乍一看只是个普通的管理系统,但真上手做的时候你会发现,它比一般的电商后台或资讯站要复杂得多。继续教育本身有一套完整的业务闭环:学员注册、选课报名、在线学习、课时记录、考试测评、学时认定、证书发放,再加上管理端的课程审核、教师管理、数据统计。任何一个环节做得太浅,答辩或者实际落地的时候都会露怯。

先把这个系统的角色和核心模块理清楚,后面写代码心里才有底。我按最常见的三类角色来拆:

角色核心诉求对应的功能模块
学员快速找到课程、记录学习进度、完成考试、拿到学时证明课程浏览、在线播放、学习进度保存、在线考试、学时查询
教师/讲师发布课程内容、管理章节、出题阅卷、查看学员学习情况课程管理、章节管理、试题管理、成绩管理
系统管理员审核课程与讲师资质、管理用户、查看平台运营数据用户管理、课程审核、分类管理、数据看板

从技术实现的角度看,这个系统最核心的难点其实不在业务本身,而在于“状态流转”。用户看了一半视频,进度存哪里?考试做到一半断了,怎么恢复?课程下架了,已经学到一半的学员怎么处理?这些状态问题才是后端设计的重头戏。

前端那边也有自己的复杂点:视频播放器要不要支持m3u8格式?学习进度是定时上报还是暂停时上报?课程列表的筛选、搜索、分页怎么做得顺手?管理端的表格、表单、权限控制怎么跟后端的接口配合?

我见过的很多半成品项目,问题都出在“只做了展示,没做状态”。课程列表能打开、详情页能看,但学习进度存不住,考试只能做客观题不能判分,这种系统拿到真实场景里根本没法用。所以本文的每个模块我都会把状态设计说清楚。

2. 技术选型:django与flask的现实取舍

标题里同时出现了django和flask,说明你在选型上还在犹豫,或者参考文献里两套方案都有。这里我不和稀泥,直接说结论:做在线继续教育这种偏管理型的系统,优先选django;如果你的重心在API快速开发且业务相对简单,flask也能胜任,但很多功能要自己拼装。

为什么这么说?咱们把两个框架在“继续教育系统”这个具体场景下的优劣势摆一摊:

对比维度djangoflask
ORM和数据建模自带django ORM,模型迁移一键完成,association table处理多对多很顺手需要自己配SQLAlchemy,首次集成有学习成本
用户认证自带User模型、session认证、权限组,改一改就能适配学员/教师/管理员需要装flask-login或自己写JWT逻辑
Admin后台自带admin站点,配置下就能给管理员用,毕设演示很加分需要装flask-admin,而且配置不如django顺滑
学习进度/考试状态设计模型关系清晰,ORM直接表达“用户-课程-进度”的多表关联也能做,但多数要手写更多SQL或ORM查询
文件/视频处理有storage抽象,配合云存储或nginx分发都比较方便同样能接,但需要自己规划静态文件与上传逻辑
API接口开发DRF(django rest framework)生态成熟,序列化、认证、分页全套写REST接口灵活,但要自己组装插件

如果你选flask,大概率是看中它的“轻”和“自由”。flask用好了确实开发速度快,因为你的代码完全自己掌控,不需要迁就框架的约定。但代价是:用户登录注册、权限控制、模型变更记录、admin后台这些“系统标配”,flask都不会直接给你,你得从零搭。一个继续教育系统做下来,这些功能一个都跑不掉。

反过来,django的“重”主要体现在框架约束多、初始化目录结构复杂,可一旦你跑通一个完整的CRUD,后面的课程、章节、试题管理就全是套路化的活。特别是有django admin加持,管理端的基本增删改查几乎不用写前端页面就能演示。

还有一个很现实的考虑:如果你用的是pycharm,django的集成度是几个Python框架里最好的。新建项目时直接选Django模板,IDE自动识别manage.py、settings.py、urls.py这些文件结构,运行配置点一下就起服务。flask在pycharm里也能跑,但很多模板和迁移功能需要自己装插件。

所以我在下面的方案里以django + djangorestframework + Vue 3为主体,flask我会在文末专门讲一下怎么改造成本最小。

3. 环境准备与项目骨架搭建:pycharm + python + vue的第一次磨合

很多新手在环境这一步就被劝退了,不是因为难,而是因为“不知道什么是正常的”。我先把版本组合给出一套稳定方案,再讲为什么这样配。

3.1 一套能跑通全流程的版本组合

  • Python:3.10或3.11(3.12开始部分依赖包适配有坑,不推荐新手直接用)
  • django:4.2 LTS(5.x也可以用,但配套的第三方包可能还没跟上)
  • djangorestframework:3.15+
  • Node.js:18 LTS或20 LTS(不要装22以上,有些老依赖会警告)
  • Vue:3.4+,配合Vite 5
  • pycharm:2023.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

解释一下为什么要分三个app:django的app模块化设计,本质上是让你把不同业务边界切开。users管认证和角色,courses管课程、章节、学习进度,exams管试题和考试成绩。以后扩展轮播图、公告、支付都可以再新开app,互不干扰。

3.3 Vue项目创建:为什么用Vite不用vue-cli

网上大量旧教程还在用vue-cli,但官方生态2024年以后已经全面迁移到Vite。Vite冷启动快,热更新快,对新手来说最直观的感受是“保存代码后页面秒刷新”,不像webpack时代每次要等好几秒。

cd online_edu # 回到项目根目录 npm create vue@latest frontend

这一步会交互式问你一堆配置项——TypeScript? Vue Router? Pinia? 我的建议是:如果不熟TS,先选JavaScript;Vue 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:8000,Environment variables里加 DJANGO_SETTINGS_MODULE=online_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_delete=models.CASCADE) role = models.CharField(max_length=20, choices=ROLE_CHOICES, default='student') real_name = models.CharField(max_length=50, blank=True) total_credit = models.FloatField(default=0) # 累计学时 org_name = models.CharField(max_length=100, blank=True) # 工作单位 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_length=200) cover = models.ImageField(upload_to='covers/', blank=True) category = models.ForeignKey('Category', on_delete=models.SET_NULL, null=True) teacher = models.ForeignKey(User, on_delete=models.CASCADE, related_name='courses') status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='pending') created_at = models.DateTimeField(auto_now_add=True) description = models.TextField(blank=True) class Chapter(models.Model): course = models.ForeignKey(Course, on_delete=models.CASCADE, related_name='chapters') order = models.IntegerField(default=1) title = models.CharField(max_length=200) video_url = models.CharField(max_length=500, blank=True) # 支持mp4或m3u8地址 duration = models.IntegerField(default=0) # 视频时长,单位秒 class Meta: ordering = ['order'] class LearningRecord(models.Model): student = models.ForeignKey(User, on_delete=models.CASCADE) chapter = models.ForeignKey(Chapter, on_delete=models.CASCADE) progress = models.FloatField(default=0) # 0~1 completed = models.BooleanField(default=False) updated_at = models.DateTimeField(auto_now=True) class Meta: unique_together = ('student', 'chapter')

add_unique_together 这个字段组合特别重要。如果不加约束,同一个学员可能对同一章节产生多条学习记录,统计学时的时候就会翻倍。我在实际项目中踩过这个坑,数据扣起来极其痛苦。

4.3 考试模块:客观题自动判分+主观题人工批改

考试是继续教育的核心验证手段。模型拆成Exam、ExamQuestion、ExamRecord、ExamAnswer四张表:

class Exam(models.Model): title = models.CharField(max_length=200) course = models.ForeignKey(Course, on_delete=models.CASCADE) duration_minutes = models.IntegerField(default=60) pass_score = models.FloatField(default=60) start_time = models.DateTimeField() end_time = models.DateTimeField() class ExamQuestion(models.Model): exam = models.ForeignKey(Exam, on_delete=models.CASCADE, related_name='questions') qtype = models.CharField(max_length=20, choices=(('single', '单选题'), ('multi', '多选题'), ('judge', '判断题'), ('essay', '简答题'))) content = models.TextField() options = models.JSONField(default=dict) # 例如 {"A": "选项1", "B": "选项2"} answer = models.JSONField(default=list) # 正确答案,单选存 ["A"],多选存 ["A","C"] score = models.FloatField(default=5) class ExamRecord(models.Model): exam = models.ForeignKey(Exam, on_delete=models.CASCADE) student = models.ForeignKey(User, on_delete=models.CASCADE) score = models.FloatField(null=True) status = models.CharField(max_length=20, default='in_progress') # in_progress/submitted/graded submit_time = models.DateTimeField(null=True, blank=True) class ExamAnswer(models.Model): record = models.ForeignKey(ExamRecord, on_delete=models.CASCADE, related_name='answers') question = models.ForeignKey(ExamQuestion, on_delete=models.CASCADE) student_answer = models.JSONField(default=list)

这里有个设计取舍:为什么考卷答案用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编写课程API

DRF的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(status='published') permission_classes = [permissions.IsAuthenticated] def get_serializer_class(self): if self.action == 'retrieve': return CourseDetailSerializer return CourseListSerializer

Serializer这边要注意嵌套关系的深度。课程详情需要包含章节列表,但章节不要在外面再嵌套一大堆内容,否则接口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(student=user, chapter=obj).first() return record.progress if record else 0 def get_completed(self, obj): user = self.context['request'].user record = LearningRecord.objects.filter(student=user, chapter=obj).first() return record.completed if record else False

4.5 学习进度上报:接口设计要防“刷学时”

这个点是继续教育系统最容易被诟病的地方,也是答辩时老师经常追问的:学员把视频挂在后台快进,你系统怎么判定他真的学完了?

我的方案是把进度上报接口做成“心跳式”。前端每隔15秒提交一次当前观看进度,接口做两件事:更新播放进度百分比;同时把两次上报之间的时间差计入有效学习时长。

@api_view(['POST']) @permission_classes([permissions.IsAuthenticated]) def report_progress(request, chapter_id): chapter = Chapter.objects.get(pk=chapter_id) progress = float(request.data.get('progress', 0)) # 校验progress合法性 if not (0 <= progress <= 1): return Response({'error': 'progress参数非法'}, status=400) record, created = LearningRecord.objects.get_or_create( student=request.user, chapter=chapter ) # 上一次上报时间戳,通过前端传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 class="player-wrapper"> <video ref="videoRef" 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回调里设置currentTime:

video.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-vue,API熟悉度无缝衔接
  • 如果是循规蹈矩做管理后台,选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的改动不会自动reload(runserver的自动重载是监控文件变化,但有时新装包的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有没有token,Authorization头格式是否正确
404 Not FoundURL路径拼错打开Network看实际请求的URL,跟django的urls.py比对
500 Internal Server Error后端代码异常看后端命令行的Traceback,前端只显示服务器内部错误
CORS policy报错白名单漏配检查CORS_ALLOWED_ORIGINS里是否包含香菇前端域名

6.5 一个排查案例:课程发布后前端列表不显示

我在实际项目里遇到过这个状况:教师通过django admin发布了课程,状态改成published,但前端课程列表始终是空的。排查链路是:

  1. 先用浏览器直接请求课程列表API:http://localhost:8000/api/courses/,返回了数据,说明后端没问题。
  2. 再看前端请求:Network面板请求返回200,数据也是完整的。
  3. 最后发现是前端v-for渲染时filter写错了:我误用了v-if="course.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 decorated

flask的优势在部署时反而体现出来:进程模型简单,同样用gunicorn跑,配置几乎不用改。

根据我个人的体会,无论选django还是flask,在线继续教育系统能不能做到“能用”,关键不在框架、不在页面好不好看,而在于三个基础细节有没有想透:学习进度状态是否可靠、考试判分逻辑是否严谨、权限控制是否覆盖到每一个接口。这三个细节通关了,系统就已经具备实用价值;这三个做不好,页面再精致,演示完了依然会被问倒。

最后一个小经验:项目开发过程中,一定要养成写接口文档的习惯。哪怕就是用一个简单的Markdown文件记录每个接口的路径、参数、返回结构,也能让你在前后端联调时节省大量互相扯皮的时间。如果项目周期紧,可以用DRF自带的文档或apifox直接抓接口生成,把精力省下来去调试边界情况——比如“学员同时打开两个浏览器Tab学习同一门课,学习进度会不会互相覆盖”,这种问题才是答辩时真正显功力的地方。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/11 11:00:05

Simulink建模:多能源协同调频系统设计与优化

1. 多能源调频系统概述在电力系统频率调节领域&#xff0c;多能源协同调频已成为现代电网稳定运行的关键技术。传统电力系统主要依赖火电机组进行频率调节&#xff0c;但随着新能源渗透率的不断提高&#xff0c;风电、光伏等波动性电源的大规模并网给系统频率稳定带来了新的挑战…

作者头像 李华
网站建设 2026/9/11 10:57:59

DOM添加节点完全指南:API、性能与安全

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/11 10:56:49

三步确定你的目标

大家对树立正确的目标的重要性&#xff0c;但真的动手梳理时却无从下手。情况往往不是想不出东西&#xff0c;而是脑子的东西太多了&#xff0c;时间和精力明显无法应付这么多想法。就算最后勉强确定下来&#xff0c;也发现无从落地&#xff0c;这些目标就留到明年再考虑了。 如…

作者头像 李华
网站建设 2026/9/11 10:56:44

SKY77652-31射频功率放大器设计与优化指南

1. SKY77652-31放大器核心特性解析SKY77652-31是一款专为移动通信设备设计的高效射频功率放大器模块。这个看似普通的黑色封装器件&#xff0c;实际上集成了现代无线通信中最关键的几个技术突破点。我在实际项目中多次使用该器件&#xff0c;发现其最突出的特点是工作频段覆盖8…

作者头像 李华
网站建设 2026/9/11 10:56:28

【单片机毕业设计】基于 STM32 或 51 单片机的多参数环境安全监测终端设计 基于 STM32 或 51 单片机的阈值可配置火灾声光报警系统设计(023807)

博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于嵌入式单片机&#xff0c;Java、小程序技术领域和毕业项目实战 ✌️…

作者头像 李华
网站建设 2026/9/11 10:55:47

DeerFlow 实战:从零部署到搭建可视化 AI 自动化工作流

最近在折腾自动化工作流的时候&#xff0c;朋友给我推荐了一个叫 DeerFlow 的开源项目。第一反应是这个名字挺有意思&#xff0c;带个“鹿”字&#xff0c;后来仔细一想&#xff0c;其实挺贴切的——鹿跑起来轻快、灵活&#xff0c;而 Flow 本身就是流程、流转的意思&#xff0…

作者头像 李华