ARTICLE DETAIL

资讯详情

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

Python+Vue前后端分离实战:从0到1搭建乡村支教系统

Python+Vue前后端分离实战:从0到1搭建乡村支教系统 做这个乡村支教系统其实源于一次朋友之间的聊天。她在乡镇中学支教跟我抱怨最多的事情不是备课累而是“资源太散了”——支教志愿者来了又走课表靠微信群接龙教学资料到处传学期末想复盘连记录都找不全。聊到后面她问能不能搞个简单系统把这些事管起来。我当即答应然后花了三个周末用Python Vue 搭了一个“乡村支教系统”后端主框架选了 Django顺手用 Flask 写了个附件预览服务开发全程在 PyCharm 里完成。如果你正在学 Python、Vue或者正打算做一个前后端分离的实战项目这篇博文会把整个系统的设计思路、核心模块、数据库结构、接口认证、前后端联调以及部署时踩过的坑全部摊开讲。不是教科书式的“项目介绍”而是一个人在电脑前实际写过、改过、跑过的记录。我尽量把每一步为什么这么做的理由也说清楚这样你拿去抄作业也好换皮做自己的管理系统也好都能少走不少弯路。1. 项目定位与技术选型思考1.1 乡村支教系统到底要解决什么问题开始动手之前我先梳理了业务场景。乡村支教和普通培训班不一样它有非常鲜明的特点支教志愿者是流动的而且有明显的周期寒暑假、学期支教、短期周末支教。支教点分散在不同的村镇学校每个学校的年级、科目、学生情况不同。课程安排往往是临时拼凑的需要可调整、可通知。教学资料PPT、教案、录播课视频需要统一存放不然志愿者走的时候东西就跟着带走了。校方和志愿者之间需要双向反馈比如课程效果、学生表现、后续改进意见。所以这个系统从第一天起就锁定了五个核心模块支教项目管理、志愿者注册与审核、课程表排期、教学资源中心、反馈与评价。它本质上并不仅仅是一个“报名网站”而是一套连接支教组织、乡村学校、志愿者三方的信息化系统。技术选型围绕这个场景展开。前后端分离是肯定的因为需要较多交互式的页面算是有一定复杂度的管理系统。前端用 Vue 组件化开发维护起来比 jQuery 时代舒服太多后端用 Django因为它自带 Admin 后台、ORM、认证系统能让我把精力集中在业务逻辑而不是重复造轮子。至于 Flask我后来用它单独做了一个资源预览服务专门处理 PDF、图片、m3u8 视频切片这类静态附件的在线访问避免 Django 主进程被大文件 IO 拖慢。1.2 后端框架Django 还是 Flask关于后端框架我在项目里其实做了分工主业务系统用 Django辅助资源服务用 Flask。如果你了解这两个框架会发现这个选择是很顺其自然的。Django 是“全家桶”自带 ORM、Admin、表单、认证、Session。乡村支教系统需要管理后台Django 的 Admin 几乎是白送的。咱们只需要把数据模型定义好进入/admin就能直接对支教项目、志愿者、课程进行增删改查对于没有专业运维人员的支教组织来说这个后台学习成本极低。另外Django ORM 对数据库的封装很完善后面课程表和报名表之间复杂一点的关联查询写起来确实比原生 SQL 轻松。Flask 的优势是小巧、灵活、可控。在这个项目里我用它实现了一个单独的“附件预览服务”。Falsk 代码总工才几百行主要就是接收 Django 那边传来的文件路径然后做文件流转发或者 PDF 预览。部署时直接python app.py跑在 8001 端口不和 Django 抢资源挂了也不影响主流程。如果你非要在 Django 和 Flask 里二选一小项目可以考虑 Flask但凡是像这种有多个角色、需要后台管理的系统我还是推荐 Django 起步省下的时间足够你多写一套前端页面。1.3 前端为什么选 Vue 和 PyCharm 的组合前端在 Vue 和 React 之间我选了 Vue核心原因是 Vue 的中文资料和学习曲线对新手更友好。乡村支教系统的页面大部分是表单和列表Vue 的v-model双向绑定和组件化设计能让代码量少很多。我使用的是 Vue 3 组合式 API项目用 Vite 构建比 Webpack 配置简单启动也更快。开发工具方面我用 PyCharm 作为主力准确说是PyCharm Professional 前端插件。PyCharm 对 Python 的支持自然不用说虚拟环境venv管理、Python 解释器切换、Django 项目的 manage.py 工具窗口这些配置好了之后效率非常高。网上各种“pycharm安装教程”都在教怎么装我这里补充一点个人建议学生或个人开发者用 Community 版也完全够用我常用的功能无非是调试器、终端、版本控制集成社区版全都有。关键是把 Project Interpreter 指向你虚拟环境里的 Python 解释器不然你明明装了 Django 却总是ModuleNotFoundError。前端部分 Vue 也要在 PyCharm 里跑起来有两种方式直接在 PyCharm 里打开前端目录或者把整个前后端项目作为同一个根目录打开。我倾向于后者因为改完 Vue 代码马上能切到 Python 文件不用开两个窗口翻来翻去。2. 系统模块梳理与数据库设计2.1 核心模块与页面流程支教系统在流程上可以画成两条主线。第一条是“项目发布线”管理员在后台创建支教项目绑定学校和年级填写招募人数、起止时间项目发布后前端首页展示项目卡片。第二条是“报名参与线”志愿者登录或注册完善个人资料姓名、学校/单位、特长学科在项目详情页点击报名管理员在后台审核通过后志愿者就能看到自己的项目课程表并在课程结束后提交反馈。页面设计上前端一共有两层。普通用户端包括首页项目列表 项目详情、注册登录页、报名页、个人中心我的项目、我的课表、我的反馈。管理端包括项目审批列表、志愿者审核列表、课程表管理、资源上传与分类、数据概览。这些页面在 Vue 里对应不同的路由组件之间通过 Pinia 或 Vuex 管理登录状态。我在做这套页面的时候最深的感触是表单项一定要克制。最初我设计了“紧急联系方式”“支教经历描述”“可支教时间”等七八个字段后来砍成了四个必填姓名、手机号、身份类型、可服务时间段。因为对乡村用户来说表单越长提交率越低。系统功能再完整如果数据进不来一切等于零。2.2 数据表设计与关系数据库我用的是 MySQL 8.0实际上开发期用 SQLite 也能跑但为了后面部署方便我从一开始就切到了 MySQL。核心的表其实就六张关系也很清楚表名主要字段说明auth_userDjango 自带的用户表用于登录认证volunteer_profileuser_id, real_name, phone, service_type, intro志愿者扩展资料与用户一对一projecttitle, school, subject, start_date, end_date, quota, status, creator支教项目信息enrollmentvolunteer, project, status, apply_time, remark报名表志愿者和项目多对多course_scheduleproject, date, period, subject, teacher, room项目对应的课程排期teaching_resourcetitle, file_type, file_url, uploader, project, course教学资料可关联课程feedbackvolunteer, course, content, rating课程反馈在 Django 的models.py里面这些表变成了一个个类。拿 enrollment 来说class Enrollment(models.Model): volunteer models.ForeignKey(VolunteerProfile, on_deletemodels.CASCADE, verbose_name志愿者) project models.ForeignKey(Project, on_deletemodels.CASCADE, verbose_name支教项目) status models.CharField(max_length20, choices( (pending, 待审核), (approved, 已通过), (rejected, 已拒绝), ), defaultpending) apply_time models.DateTimeField(auto_now_addTrue) remark models.TextField(blankTrue, verbose_name备注) class Meta: unique_together (volunteer, project)我特意设置了unique_together避免同一志愿者对同一项目重复报名。数据库层面做好唯一约束比在代码里判断要可靠得多——这是我在上线初期被重复报名数据教育过的经验。2.3 基于 Django 的模型实现与查询陷阱Django ORM 用起来很顺手但新手容易在“查询和删除对象”上翻车。项目里志愿者管理页面最常下的命令是删除不规范的报名记录对应到 ORM 就是Enrollment.objects.filter(idxxx).delete()。这里有个关键区别delete()是模型实例的方法也存在于 QuerySet 上。我遇到过的问题是很多人用Enrollment.objects.get(id1).delete()如果 id 不存在get会抛出DoesNotExist异常而filter(...).delete()则不会它只是返回一个删除数量的统计。所以如果做批量删除比如管理员勾选多条记录应该这样写# 批量删除报名记录 Enrollment.objects.filter(id__in[1, 2, 3]).delete()另一个容易忽略的是级联删除。比如删除一个支教项目它的报名记录、课程表会因为我设的on_deletemodels.CASCADE全部消失。这在有些时候是崩溃事故。我后来在删除项目模型里改用了on_deletemodels.PROTECT如果存在关联报名记录删除操作会被数据库拒绝避免误删。这个细节强烈建议你在开发初期就考虑清楚别等数据多了再后悔。还有查询时get和filter的使用场景查志愿者姓名为“张三”的记录如果表里恰好有两个张三用get会直接报MultipleObjectsReturned。所以我在代码里对可能重复的数据一律用filter再结合first()或者捕获异常不能让一个重复数据把整个请求干翻。3. 后端接口开发与认证方案3.1 RESTful 接口设计前端要跟后端打交道必须有一套统一的接口规划。我的接口全部以/api/v1/开头遵循 RESTful 风格比如GET /api/v1/projects/获取项目列表支持?keywordsubject搜索GET /api/v1/projects/{id}/项目详情POST /api/v1/projects/{id}/enroll/报名GET /api/v1/volunteer/enrollments/当前登录人的报名列表POST /api/v1/resources/upload/上传教学资源GET /api/v1/resources/{id}/preview/预览资源所有接口的返回格式保持统一前端解析起来非常省事。我的封装结构是{ code: 0, message: success, data: {} }code 0表示成功非零表示业务错误码如 1001 表示未登录1002 表示权限不足。别把 HTTP 状态码和业务状态码混成一体因为浏览器对 HTTP 状态码有默认行为比如 403 会触发一些代理拦截在业务层做一层更安全。Django 端我用的是基于函数视图加api_view装饰器或者用 Django REST FrameworkDRF。如果你想少写一些序列化和分页代码DRF 是首选。这个项目里我用 DRF 加了分页服务默认每页 10 条前端参数传page和page_size即可。注意要设置MAX_PAGE_SIZE 50不然有人直接传 1000 一次性拉走全部数据服务器扛不住。3.2 Token 认证与 Cookie 设置乡村支教系统需要区分志愿者和管理员认证方案我选了 Token 认证而不是 Session 认证。原因是前后端分离后前端往往部署在另一个域名或端口Session 里存 Cookie 时跨域处理比较麻烦而 Token 则可以在请求头里携带逻辑上更直观。这里特意说一下“django cookie 设置 token”这个大家搜得比较多的点。Django 默认的 Session 框架是把 sessionid 写在 Cookie 里如果你要自建 Token 并把 Token 写入 Cookie需要手动操作。我的做法是用JWTJSON Web Token生成 token然后返回给前端前端存到 localStorage 或者 Cookie 都行。如果存 Cookie要设置HttpOnly防止 JavaScript 读取提升安全性。Django 端实现 JWT 签名的核心代码import jwt from datetime import datetime, timedelta from django.conf import settings def generate_token(user): payload { user_id: user.id, username: user.username, exp: datetime.utcnow() timedelta(days7) } token jwt.encode(payload, settings.SECRET_KEY, algorithmHS256) return token在接口里校验时从请求头Authorization: Bearer token取出值验签再根据用户 ID 从数据库查用户信息。我没有用 Django REST Framework 自带的 TokenAuthentication而是自己写了一个简单的get_user_from_request工具函数因为这样逻辑最透明出了问题也好排查。生产环境建议直接用 DRF 的 SimpleJWT 库它把刷新、过期处理都封装好了。3.3 数据推送WebSocket 还是轮询系统里有一个场景管理员在后台审核了某位志愿者的报名志愿者端需要立刻看到“审核通过”的提示。一开始我用的是最简单的前端定时轮询比如每 30 秒调一次接口后来发现在线人数多时无意义请求太多了。于是我用 Django Channels 给系统加了 WebSocket实现“后台数据变动后向前端推送”。热词里经常看到“django websocket实现后台有数据前端推送”其实就是这个意思。Django 本身是同步框架要支持 WebSocket 需要安装channels和channels-redis然后把ASGI_APPLICATION配置到asgi.py。简单来说WebSocket 建立一个长连接后端可以在任意时间主动给前端发消息。我们的代码大致是# consumers.py import json from channels.generic.websocket import AsyncWebsocketConsumer class NoticeConsumer(AsyncWebsocketConsumer): async def connect(self): self.user self.scope[user] await self.channel_layer.group_add(fuser_{self.user.id}, self.channel_name) await self.accept() async def receive(self, text_data): data json.loads(text_data) if data.get(type) ping: await self.send(json.dumps({type: pong})) async def push_notice(self, event): await self.send(json.dumps({type: notice, content: event[content]}))但如果是第一次做这个功能我不建议一上来就上 WebSocket。你可以先把业务跑通用轮询扛过初期等真正有需求、有精力再把推送重构成 WebSocket。WebSocket 维护长连接需要考虑断线重连、消息序号复杂度是实打实的。这个系统目前只有“审核通知”和“新资源提醒”两个推送点所以我对 WebSocket 的使用非常克制。4. 前端 Vue 实现与联调实战4.1 Vue 项目和路由配置前端我用 Vite 创建了 Vue 3 项目依赖管理使用 npm。创建命令npm create vitelatest teaching-frontend -- --template vue cd teaching-frontend npm install npm install vue-router4 axios pinia路由配置是最先要做的。我建议把页面权限分成两种游客可访问和必须登录才能访问。用vue-router的全局前置守卫做拦截是最方便的方式。代码示例// router/index.js import { createRouter, createWebHistory } from vue-router const routes [ { path: /, name: Home, component: () import(../views/Home.vue) }, { path: /login, name: Login, component: () import(../views/Login.vue) }, { path: /register, name: Register, component: () import(../views/Register.vue) }, { path: /my, name: MyPage, component: () import(../views/MyPage.vue), meta: { requiresAuth: true } }, { path: /projects/:id, name: ProjectDetail, component: () import(../views/ProjectDetail.vue) }, ] const router createRouter({ history: createWebHistory(), routes }) router.beforeEach((to, from, next) { const token localStorage.getItem(token) if (to.matched.some(record record.meta.requiresAuth) !token) { next({ path: /login, query: { redirect: to.fullPath } }) } else { next() } })这里我用了路由懒加载页面组件只在访问对应地址时才加载首屏速度会快不少。关于热词里提到的“vue动态路由”在这个系统里其实用得到管理员登录后我要把后台管理相关的路由动态挂载给他普通志愿者不需要分到这些路由。动态路由实现也很简单只要在登录时根据角色字段调用router.addRoute()加入对应路由即可。注意动态添加的路由要记得在退出登录时移除或者干脆刷新页面重置路由状态否则下次登录会出现权限残留。4.2 页面组件与 Axios 请求封装Axios 的请求封装我建议每个项目都做一份不要在每个页面里直接axios.get(...)。我把统一逻辑抽在utils/request.js里包含 baseURL、超时时间、请求头携带 token、响应拦截器统一处理业务码。核心代码import axios from axios const request axios.create({ baseURL: /api/v1, timeout: 15000 }) request.interceptors.request.use(config { const token localStorage.getItem(token) if (token) { config.headers.Authorization Bearer ${token} } return config }) request.interceptors.response.use( response { const res response.data if (res.code ! 0) { if (res.code 1001) { // 未登录跳转登录页 window.location.href /login } return Promise.reject(new Error(res.message)) } return res.data }, error { return Promise.reject(error) } ) export default request注意baseURL: /api/v1这个设置。开发环境里Vue 的 Vite 开发服务器监听 5173 端口Django 跑在 8000接口要跨域。跨域问题有两种常规解法一是在 Django 后端安装django-cors-headers允许前端源访问二是在 Vite 配置开发代理把/api开头的请求转发到http://localhost:8000。我推荐用 Vite 代理因为这样浏览器里没有跨域Cookie 和 Header 的处理更干净。vite.config.js配置export default defineConfig({ server: { proxy: { /api: { target: http://localhost:8000, changeOrigin: true } } } })生产环境部署时Nginx 也会做同样的代理所以开发和生产环境行为保持一致。4.3 PyCharm 里如何同时跑前后端PyCharm 里同时管理 Django 后端和 Vue 前端的运行其实非常简单用到的核心技巧是配置多个 Run/Debug Configuration。后端配置Run/debug configuration 选 Django ServerPython interpreter 指向虚拟环境 Pythonhost 为127.0.0.1port 为8000。前端配置由于 PyCharm 最新版内置了 Node.js 支持你可以在运行配置里新增npm类型package.json选前端目录的scripts选dev这样点一下绿色三角就能启动 Vite。真正方便的是 Debug 模式。后端接口在views.py里打上断点然后从前端页面发起请求Vite 代理转发到 Django请求就会卡在断点处你可以直接看到前端传过来的数据和后端算出的中间结果。这种前后端联调方式比在代码里写print查看输出高效十倍。要提一个热词里的“vue播放m3u8”。支教系统里经常需要把录播课视频上传后在线播放录播课很多时候是 m3u8 分片格式。Vue 前端播放 m3u8 需要用到hls.js或video.js。我的做法是npm install hls.js然后在组件里判断视频链接后缀如果是.m3u8就用Hls.isSupported()走 HLS 播放。代码不复杂核心就几十行import Hls from hls.js function playM3u8(videoElement, url) { if (Hls.isSupported()) { const hls new Hls() hls.loadSource(url) hls.attachMedia(videoElement) } else { // 兼容部分老的移动端浏览器 videoElement.src url } }需要注意的是m3u8 里的相对路径分片可能跨域要保证 Flask 资源服务设置了Access-Control-Allow-Origin否则视频会一直转圈加载不出来。这个问题我们在部署时才发现建议提前加上。5. 部署与常见问题排查5.1 Flask 在这个项目中到底部署了什么前面反复提到 Flask 作为“资源预览服务”这里具体说说我到底用它做了什么。系统中教学资源包含 PDF 教案、PPT 演示文稿、视频文件、图片等。Django 主程序如果直接负责大文件的读取和在线预览一旦文件量大很容易拖垮 Web 服务。所以我单独在8001端口跑一个 Flask 服务只负责两件事一是根据文件的临时访问 token 校验权限二是把文件流发送给浏览器。Flask 服务端的代码十分精简from flask import Flask, send_file, abort, request import os app Flask(__name__) BASE_DIR /data/teaching_resources app.route(/preview/path:sub_path) def preview(sub_path): token request.args.get(token) if token ! your-shared-token: abort(403) full_path os.path.join(BASE_DIR, sub_path) if not os.path.exists(full_path): abort(404) return send_file(full_path) if __name__ __main__: app.run(host0.0.0.0, port8001)生产环境里我对接 Nginx 时把/resources/路径直接转发到 Flask 服务。也就是说用户在前端点击“预览PDF”真正返回数据的是 Flask 服务Django 只负责生成访问链接。这样分工后两个服务都能更好地分配资源。“flask部署”相关搜索里经常看到有人问是不是需要 uWSGI。其实 Flask 自带的app.run()在小并发场景下也能跑我当时部署时就是直接用gunicorn -w 2 -b 0.0.0.0:8001 app:app启动简单可靠完全够用。5.2 开发中的典型问题与解决方案前后端分离项目问题永远比预想的多。我把整个开发过程中遇到的典型问题整理成一张速查表方便对着排查问题现象排查方向我的解决办法前端请求接口返回 404Vite 代理没生效或 Django URL 路径错误优先看浏览器 Network 里的请求 URL确认是不是被代理转发跨域请求被拦截后端没有配置 CORSVite 代理配置changeOrigin: true生产环境交给 Nginx登录后 Token 失效/过期Token 解签失败或 SECRET_KEY 不一致确认前后端共用一个 SECRET_KEY设置较长的过期时间上传文件失败Nginx 的client_max_body_size限制Nginx 加client_max_body_size 50m;图片/PDF 预览显示不全文件路径中包含中文或空格Flask 端做 URL 编码解码避免直接拼接路径WebSocket 连接秒断Channels 的 ASGI 配置缺失检查asgi.py是否配置了协议类型路由Vue 页面刷新后 404前端路由使用 history 模式但服务器未配置 fallbackNginx 配置try_files $uri $uri/ /index.html;单个问题里最值得说的是“Vue 动态路由刷新后白屏”。如果路由是动态添加的刷新页面时初始状态是空路由必须重新拉取用户信息并addRoute。我这里做了一个笨办法在main.js初始化全局守卫时如果本地有 token就先去调用GET /api/v1/user/me/拿到角色后再拼接动态路由。这样虽然多一次请求但至少保证每次刷新后动态路由是齐全的。5.3 项目上线与安全加固开发期跑通只是第一步真正上线后有几件事必须做。我把项目部署在了一台 2 核 4G 的 Linux 服务器上结构是这样的Nginx 监听 80/443托管 Vue 打包后的dist静态文件/api/路径反向代理到 Django用 Gunicorn 跑在 8000/resources/路径反向代理到 FlaskGunicorn 跑在 8001MySQL 和 Redis 分别跑在 3306 和 6379。Django 侧的安全配置要重点检查几项DEBUG FalseALLOWED_HOSTS改成服务器域名SECRET_KEY通过环境变量读取不要提交到 Git数据库账号不要用 root给应用单独建一个最小权限账号。另外上传文件的校验一定要做文件后缀白名单至少包含jpg、png、pdf、mp4、m3u8、ppt、pptx并且限制文件大小。我就是在资源上传接口里加了一个简单的扩展名检查拦住了不少恶意文件。前端部署后还要记得把axios的baseURL改成正式的/api/v1开发时的 Vite 代理配置不会带到线上。这些细节如果在开发阶段没有分离好上线时就会手忙脚乱。写在最后这个乡村支教系统从需求调研到上线跑通完整走下来给我的最大感受是技术其实不是最难的部分最难的是把业务逻辑揉进代码里还让用户用得不别扭。比如最初我理想化地设计了“志愿者可以按学期查看课程表”但实际支教周期短、变动频繁志愿者的耐心是有限的最后我把页面改成“今日课程 未来一周课程”反而大家用得最多。后来我才意识到很多时候“够用”比“强大”更贴近真实场景。如果你也想做一个类似的系统我个人建议从最小闭环开始先做项目列表、报名、审核这三个功能用 Django 跑后端Vue 写页面PyCharm 做开发。走通之后再加资源中心、消息推送、数据报表。不要一上来就追求大而全否则很容易在架构和功能上迷失连核心流程都迟迟上线不了。代码写的糙一点没关系先把志愿者和校方拉进系统有了真实反馈再迭代那才是最有价值的。
返回列表