多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

基于Python+Vue的支教管理系统开发实战:Django与Flask双后端架构

基于Python+Vue的支教管理系统开发实战:Django与Flask双后端架构 1. 为什么是“Python Vue”支教系统背后的真实业务场景做这套系统的起因其实挺朴素。之前帮一个公益助学组织做信息化改造他们的日常管理还停留在微信群接龙和Excel表格哪个学校需要什么科目的支教老师、哪批志愿者能来几周、每天上了哪些课、物资发放记录全靠几个人工整理。一学期下来光是汇总各支教点的课时统计就要折腾好几天出错率还很高。团队里熟悉后端的人大多是写Python的前端则需要一个上手快、组件化的框架于是就有了这套“Python Vue”的组合。说直白一点乡村支教系统的核心不是炫技而是把一套很零散的线下流程搬到线上让它跑得通、改得动业务人员经过简单培训就能用起来。1.1 乡村支教系统到底要管哪些事我把业务拆成了五条主线这也是大部分支教协作场景的共性需求支教学校管理学校的基础信息比如地点、年级数、学生人数、住宿条件更关键的是“本学期缺什么科目的老师”。这个字段经常被忽略但学校提出需求时全靠它来判断是否匹配得上。志愿者管理姓名、联系方式、可支教时间段、擅长科目、有没有支教经历。这里有个现实问题志愿者的时间往往只有几个月的窗口期系统需要支持按时间段检索。支教项目发布与报名支教团队根据学校需求发起某个支教点、某个时间段、某类课程的项目志愿者在线报名支教队审核最后形成实际支教排期。课时与教学内容记录每天上完课之后录入上课时间、课程主题、参与学生数这是后续统计支教覆盖率、志愿时长的基础数据。教学资源归档课件、教案、课程视频这些材料的分类上传和在线预览尤其视频类资源到了后来就牵扯到流媒体播放的问题后文会专门讲。1.2 业务方提需求时最容易漏掉的细节这里必须提醒一句做这类系统的第一版功能不用贪大但有两个地方一开始就得想清楚否则后面返工成本极高。第一个是数据权限范围。支教团的组织架构一般是“总队—分队—支教点”总队要能看全部数据分队只能看自己负责的学校学校老师只能提交需求和查看本校记录。如果第一版不做权限边界的预留后期再往模型里加层级会很痛苦。第二个是后端字段要预留扩展。我刚接这个项目时就吃过亏学校模型里只设计了学生总数字段结果一个合作学校被拆成了小学部和初中部分别需要申报不同科目最后只能靠加一个school_type字段和两个外键关系去补救。如果你现在也要做类似系统建议一开始就把“归属校区”“所属学期”这类维度设计成独立字段避免写死在某个表里。2. Django 与 Flask 的选型取舍一套系统里两套框架怎么分工标题里同时出现了 Django 和 Flask很多朋友看到第一反应是奇怪的为什么不二选一其实放到具体项目里这两个框架完全可以共存而且分工明确。2.1 Django 做主后端的原因对于支教管理系统这种典型的业务管理型项目我选 Django 的理由非常直白它的自带能力能省掉一大半基建工作。自带 ORM 和数据迁移学校表、志愿者表、项目表、课时表之间关系复杂Django 的 ORM 让我不用手写 SQLpython manage.py makemigrations一条命令就能同步结构。这在项目初期字段经常微调的时候价值非常明显。自带 Admin 后台这套系统的用户里支教队的运营人员不是程序员。Django 自带的后台稍做定制就能让运营人员自己维护字典数据、审核志愿者报名不需要我单独开发一套内部管理页面。开发周期至少省了两周。自带用户认证体系User模型、Group、Permission直接复用再加上djangorestframework-simplejwt发 token权限体系基本不用从零做起。对应到项目实操我建了一个teaching的 app然后按业务域拆了schools、volunteers、projects、courses四个模块。没有用大而全的单体模块因为支教系统的业务边界相对清晰拆开之后后面接 Flask 的服务也好定位。2.2 Flask 在项目里扮演的角色既然 Django 已经覆盖了主业务为什么我还要引入 Flask答案是系统里有些轻量级服务用 Django 去实现反而“重”了。我留了两个场景给 Flask视频转码切片服务支教系统里老师会上传课堂实录原始视频文件大线上直接播放很卡。我写了一个独立的 Flask 服务接收上传通知后用ffmpeg把视频转成 HLS 格式也就是.m3u8文件加 ts 切片。这个服务逻辑简单、流量独立专门放在一台轻量机器上和 Django 主服务解耦。这就是热词里“vue播放m3u8”出现的原因前端播放器配合 hls.js 或者 video.js 就能流畅播放。数据导出与统计接口学期结束时需要给公益组织出报表统计各支教点课时数、志愿者服务时长。这部分逻辑我用 Flask 写成一个独立服务通过定时任务汇总数据按学校维度输出 JSON 或 Excel避免在主库里跑复杂的聚合查询。从架构上看Django 管“业务数据的增删改查”Flask 管“周边辅助能力”。两套框架通过 HTTP 接口通信谁也不影响谁。你要在简历或者项目答辩里说清楚这点是很加分的你不是简单“用 Django 做了一个系统”而是做了基于多应用的职责拆分。2.3 为什么 Pycharm 能同时管好这两套框架这个项目我全程用 Pycharm 开发原因不只是编辑器顺手。Pycharm 在这类多框架项目里有几个功能确实省事虚拟环境管理不用切窗口我习惯在项目根目录建venvPycharm 会在打开项目时自动激活装依赖直接用界面里的 Python Packages 管理面板。热词里有人搜“pycharm怎么安装pandas包”实际操作就是在设置里找到 Project Interpreter搜索 pandas 点击安装即可比命令行直观不少。数据库工具Professional 版自带的 Database 面板可以直接连 MySQL 或 SQLite查看表结构、执行 SQL、甚至逆向生成模型定义。排查数据问题时我很少再单独打开数据库客户端。HTTP Client平时调试接口不需要额外开 Postman直接在.http文件里写请求比如模拟志愿者登录拿 token再带上 token 调报名接口非常方便。下面我会专门演示这个用法。3. 后端核心设计从建表到接口的完整实现这一章把后端关键代码和设计思路展开你可以直接参照着搭一套。3.1 数据模型设计支教系统的核心模型我建议至少包含这么几张表# teaching/models.py from django.db import models from django.contrib.auth.models import User class School(models.Model): name models.CharField(max_length100) region models.CharField(max_length50) school_type models.CharField( max_length10, choices[(primary, 小学), (middle, 初中)], defaultprimary, ) student_count models.IntegerField(default0) need_subjects models.CharField(max_length200, blankTrue) contact_name models.CharField(max_length20) contact_phone models.CharField(max_length20) created_at models.DateTimeField(auto_now_addTrue) class Volunteer(models.Model): user models.OneToOneField(User, on_deletemodels.CASCADE) phone models.CharField(max_length20) skills models.CharField(max_length200) available_start models.DateField(nullTrue, blankTrue) available_end models.DateField(nullTrue, blankTrue) experience models.TextField(blankTrue) class TeachingProject(models.Model): title models.CharField(max_length120) school models.ForeignKey(School, on_deletemodels.CASCADE) subject models.CharField(max_length20) start_date models.DateField() end_date models.DateField() status models.CharField( max_length10, choices[(draft, 草稿), (recruiting, 招募中), (ongoing, 进行中), (finished, 已结束)], defaultdraft, ) created_by models.ForeignKey(User, on_deletemodels.CASCADE) class Enrollment(models.Model): project models.ForeignKey(TeachingProject, on_deletemodels.CASCADE) volunteer models.ForeignKey(Volunteer, on_deletemodels.CASCADE) status models.CharField( max_length10, choices[(pending, 待审核), (approved, 已通过), (rejected, 已拒绝)], defaultpending, ) created_at models.DateTimeField(auto_now_addTrue) class CourseRecord(models.Model): project models.ForeignKey(TeachingProject, on_deletemodels.CASCADE) date models.DateField() topic models.CharField(max_length200) student_count models.IntegerField(default0) content models.TextField(blankTrue)设计的时候我特意加了region和school_type两个字段。前者让总队可以按片区筛选支教点后者解决了前面说的小学部和初中部分开申报需求的问题。created_by字段记录项目发起人后面做数据权限过滤会用到。3.2 用 Django 管理后台打通业务闭环如果你是 Django 新手我建议把 Admin 后台先用起来。创建 app 之后在admin.py里注册模型运营人员就能直接维护数据了# teaching/admin.py from django.contrib import admin from .models import School, Volunteer, TeachingProject, Enrollment, CourseRecord admin.site.register(School) admin.site.register(Volunteer) admin.site.register(TeachingProject) admin.site.register(Enrollment) admin.site.register(CourseRecord)注册完之后别急着说“完事了”。实际运营中工作人员最常用的操作是“审核志愿者报名”所以在后台里做两个优化在EnrollmentAdmin里加list_filter (status, project__school)让运营人员能按项目和学校快速筛选待审核记录给志愿者和学校列表加search_fields比如按姓名、学校名、手机号搜索这在数据量上来之后非常关键。3.3 接口层用 DRF 快速产出可用 API系统接口我用了 Django REST FrameworkDRF因为它配好 ModelViewSet 之后增删改查接口几乎不用写重复代码# teaching/api.py from rest_framework.viewsets import ModelViewSet from rest_framework.permissions import IsAuthenticated from .models import TeachingProject, Enrollment from .serializers import TeachingProjectSerializer, EnrollmentSerializer class TeachingProjectViewSet(ModelViewSet): queryset TeachingProject.objects.all() serializer_class TeachingProjectSerializer permission_classes [IsAuthenticated] class EnrollmentViewSet(ModelViewSet): queryset Enrollment.objects.all() serializer_class EnrollmentSerializer permission_classes [IsAuthenticated]但有个实际问题是不是所有已登录用户都能修改任何数据。比如某个志愿者不能把别人的报名记录改成“已通过”。所以我提了一个自定义权限类只允许项目的创建者审核该项目的报名# teaching/permissions.py from rest_framework.permissions import BasePermission class IsProjectOwnerOrReadOnly(BasePermission): def has_object_permission(self, request, view, obj): if request.method in (GET, HEAD, OPTIONS): return True return obj.project.created_by request.user这里没有必要把权限做得很复杂但要保证“每个人只能动自己的数据”这个底线。对于文件上传Django 侧主要在settings.py里配置好MEDIA_ROOT和MEDIA_URL上传接口返回文件的绝对路径给前端。课程视频比较大我只会把原始文件暂存然后丢给 Flask 服务去转码转码完成后再回调 Django 接口更新视频字段的地址。4. Vue 前端落地页面搭建、路由和接口对接后端接口准备到位之后前端才是让这套系统真正被用起来的关键。这里我分享一下用 Vue 搭建前端时的核心思路尤其是一些和支教场景强相关的页面。4.1 工程初始化和环境配置创建前端工程我建议直接使用npm create vuelatest生成基于 Vite 的项目。相比以前的 Vue CLIVite 启动速度快很多尤其是在配置了代理的情况下改代码能秒级生效。装依赖时别漏了这几样vue-router、pinia、axios。很多刚上手的朋友会在 Pycharm 终端里敲npm install卡很久这里其实是网络问题可以考虑先用镜像源npm config set registry https://registry.npmmirror.com npm install前端工程和后端分开两个目录结构类似rural-teaching-system/ ├── backend # Django │ └── manage.py └── frontend # Vue ├── package.json └── src/开发阶段的跨域问题最省事的方案是在vite.config.js里配代理// vite.config.js import { defineConfig } from vite; import vue from vitejs/plugin-vue; export default defineConfig({ plugins: [vue()], server: { port: 5173, proxy: { /api: { target: http://127.0.0.1:8000, changeOrigin: true }, /media: { target: http://127.0.0.1:8000, changeOrigin: true } } } });这样前端代码里请求/api/teaching-projects/就能直接打到 Django 服务不需要在 Axios 里写一长串地址部署时也更方便切换环境。4.2 核心页面拆解我把页面分成三类角色游客/志愿者、支教队运营人员、学校管理员。系统默认显示前端“首页 项目列表 单校详情”的公开信息。首页展示近期招募中的支教项目卡片点击进入详情页项目详情页展示学校信息、支教时间、需求科目志愿者登录后可点击报名报名页填写个人可支教时间段、能教的科目、支教经历提交后由运营人员在后台审核。页面数量不多但路由最好配置成支持动态参数的。热词里有“vue动态路由”这里确实是它的典型场景——比如项目详情页的路径是/project/:id不同 id 渲染不同数据// router/index.js import { createRouter, createWebHistory } from vue-router; const router createRouter({ history: createWebHistory(), routes: [ { path: /, name: home, component: () import(/views/HomeView.vue) }, { path: /project/:id, name: projectDetail, component: () import(/views/ProjectDetail.vue) }, { path: /my, name: my, component: () import(/views/MyEnrollments.vue) } ] });为什么不把所有页面都写成静态路由因为支教系统后面还可能加“学校端管理”“总队看板”这类受限页面如果用动态路由配合权限来控制菜单就不需要每次上线都改路由表了。当然第一版项目也可以从简单开始我给的例子适合入门后继续演进。4.3 Axios 封装与文件上传接口请求如果每个页面都写一遍axios.get后续维护成本会很高。我习惯封装一个request.js请求拦截器自动带上 JWT token响应拦截器统一处理登录过期。// utils/request.js import axios from axios; const request axios.create({ baseURL: /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 error.response.status 401) { // 跳转登录页或刷新 token } return Promise.reject(error); } );文件上传这里很多人习惯直接走input typefile但在支教系统里运营人员和老师往往是在会议室、教室用旧电脑上传网络环境一般所以我建议在 axios 里做上传进度条。用一个FormData把文件传上去配合onUploadProgress显示进度体验会好很多。课程视频这块原始文件很大我前面的 Flask 服务会转成多个 ts 切片文件和.m3u8索引文件。前端播放时用 hls.js 就能直接播import Hls from hls.js; function playVideo(url, videoElement) { if (Hls.isSupported()) { const hls new Hls(); hls.loadSource(url); hls.attachMedia(videoElement); } else if (videoElement.canPlayType(application/vnd.apple.mpegurl)) { videoElement.src url; } }在 PC 上 hls.js 的兼容性很好在手机自带的 Safari 上也可以因为支持 HLS 直接播放所以这套方案在乡村支教点常见的安卓平板上也能跑通。5. Pycharm 里的开发全流程从环境配置到前后端联调这一章结合热词里频繁出现的“pycharm安装教程”“pycharm配置python环境”“django创建app”这些搜索点分享我在这套项目里实际摸出来的流程和坑。5.1 环境准备Python 解释器和虚拟环境用 Pycharm 新建 Django 项目时我一般会选New environment using VirtualenvPython 版本选 3.10 或 3.11。这里有个容易踩坑的地方如果你本机装了多个 Python 版本Pycharm 默认会选系统 PATH 里靠前的那一个但你可能需要的是另一个版本的解释器。解决办法在设置里手动指定解释器路径比如C:\Python311\python.exe虚拟环境继承它。建立好之后用python --version确认一下版本别等到makemigrations因为语法不兼容报错才发现。装依赖的命令我习惯在 Pycharm 的 Terminal 面板里执行因为这个终端会自动激活虚拟环境命令前面不需要再手动venv\Scripts\activatepip install django djangorestframework djangorestframework-simplejwt django-cors-headers pandas热词里有朋友搜“pycharm怎么安装pandas包”其实在这里用pip install pandas就行。如果网络比较慢也可以先在设置里配置国内镜像源再安装。5.2 创建 app 和跑起第一个接口热词里“django创建app”也很常见。流程是python manage.py startapp teaching然后在settings.py的INSTALLED_APPS里加上teaching。这里提醒一个坑新增 app 后如果不重启开发服务器migrate时可能出现找不到模型的奇怪问题所以尽量在新增 app 之后重启一下 Pycharm 里的 Django Server。在 Pycharm 中配置运行方式打开“Edit Configurations”点加号选 Django Server设置settings模块路径。这样点一下绿色三角就能直接跑起来断点调试也非常方便。5.3 用 Pycharm 的 HTTP Client 做接口测试前后端联调之前我先用 Pycharm 自带的 HTTP Client 把接口都测一遍。新建一个api_tests.http文件可以直接在编辑器里点击发送请求### 获取 token POST http://127.0.0.1:8000/api/token/ Content-Type: application/json { username: volunteer01, password: test123456 } ### 创建支教项目 POST http://127.0.0.1:8000/api/teaching-projects/ Content-Type: application/json Authorization: Bearer token { title: 春季乡村小学趣味数学课, school: 1, subject: math, start_date: 2025-03-01, end_date: 2025-06-30, status: recruiting }这个方法比在浏览器里调试直观多了。如果请求返回 500直接看 Pycharm 终端里的 Traceback哪里崩了一目了然。新手在这里最大的问题是不看报错就到处问其实 90% 的问题都写在 Traceback 里。5.4 实际开发中遇到的几个典型问题说三个我真实踩过的坑都是搜索引擎里高频出现的问题端口被占用跑 Django 开发服务器时提示 8000 端口被占用特别是之前开过python manage.py runserver没有正常关闭。我一般是先netstat -ano | findstr :8000看是哪个进程占用然后任务管理器里结束进程再重新运行。Vue 热更新不生效Pycharm 里跑npm run dev之后改了代码页面不刷新。这种情况多数是因为 Vite 的默认监听端口和代理配置冲突检查一下是不是同时开了多个前端 dev server。前后端时间字段不一致Django 默认使用 UTC 时间前端页面显示的支教时间比本地时间差 8 小时。解决办法是在settings.py里设置TIME_ZONE Asia/Shanghai并且USE_TZ False如果项目不涉及多时区用户这个配置最简单直接。6. 部署、推广与后续扩展让系统真正被用起来开发完成只算走了一半系统最终要部署到服务器上给支教队用才算真正落地。6.1 简化的部署方案对于中小规模项目我不推荐一上来就上容器编排那套。最简单可靠的组合是Django Gunicorn 跑主服务Nginx 监听 80 端口转发 API 请求到 Gunicorn同时托管 Vue 打包后的静态文件Flask 服务独立跑在 9000 端口专门处理转码和导出SQLite 或小规模 MySQL 都够用数据量不大优先选 SQLite备份就是一个文件。前端构建npm run build生成的dist目录放到服务器 Nginx 的站点根目录同时配置location / { try_files $uri $uri/ /index.html; } location /api { proxy_pass http://127.0.0.1:8000; }try_files那行是为了让 Vue 的路由在刷新页面时不出现 404这个配置经常被新手忽略项目一部署刷新后白屏多半就是缺了它。6.2 系统上线后的功能迭代方向第一版上线后如果运营稳定后续可以在不伤筋动骨的情况下做这些扩展课时统计看板把 CourseRecord 的数据按学校、科目、月份聚合做成可视化报表。这里就能用上前面的pandas做数据处理再输出给前端 ECharts 展示。支教课程资源的在线预览除了视频还可以支持 PDF 课件预览。热词里有人问“vue image能显示pdf吗”这个不是 image 标签能直接解决的一般会用pdf.js或者服务端转图片再预览。移动端轻量化适配乡下学校的老师普遍用手机访问所以前端的项目详情、报名、课时录入页面必须做响应式适配。Vue Vant 或者普通 CSS 媒体查询都行核心是别让老师上课间隙点半天按钮。6.3 给正在复现这套系统的人一个经验总结如果你照着这个思路自己搭一套我建议第一版严格控制在“学校信息、志愿者信息、项目发布、线上报名、课时记录”这五个功能内别急着加积分商城、直播课堂、AI 课程推荐这些花哨能力。先把主流程跑通让支教队的运营人员真的用起来再根据他们的反馈迭代。我个人的体会是这类系统能不能活下来关键不在技术用了多新的框架而在于业务方是否愿意每天打开它。所以开发阶段多花点时间在后端 Admin 的字段筛选、前端页面的加载速度、上传视频的转码流畅度这些体验细节上远比多做几个概念功能有价值。等这套流程稳定了再往其他乡村教育协作场景复制就只是换皮和改字段的事了。
返回列表