ARTICLE DETAIL

资讯详情

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

微信小程序+Flask+Vue:家校通平台完整开发实践

微信小程序+Flask+Vue:家校通平台完整开发实践 这两年我接过不少家校通、校园服务类的项目发现一个很普遍的现象很多学校还在靠微信群来传递通知、布置作业、收发成绩家长群一多消息刷屏严重老师也疲于维护。微信小程序FlaskVue这个组合就是我在实际项目中反复验证过的一套家校通平台方案。小程序给家长当高频入口Vue做老师的运营管理后台后端统一走Flask API三端各司其职开发效率和运行稳定性都兼顾了。这篇文章不聊虚的直接把完整的设计思路、核心实现细节和部署排查经验一次性说清楚适合正在做类似教育信息化项目的人参考。1. 项目整体设计与思路拆解1.1 家校联系的痛点与平台切入点家校通这个需求听起来很简单无非是让家长知道学校发生了什么。但真正落地时痛点比想象中多。微信群的通知会被聊天记录淹没老师在多个群里维护秩序要花大量精力成绩这类敏感信息又不适合直接发群里请假、作业、回执这类结构化数据在微信里更是完全没有沉淀。这个项目要解决的恰恰是这些问题通知有专门的信息流、作业有明确的布置与提交入口、成绩只能家长本人查看、请假走标准审批流程。平台的角色划分也很关键一共三类超级管理员负责系统配置和全校公告教师负责班级内的作业、成绩、请假审批家长端只能看到自己孩子相关的数据。这样的权限边界必须在设计阶段就定死不然后面接口层面很容易出现越权问题。我在这类项目上吃过亏早期的家校通版本把家长权限放开得过大结果一位家长通过接口遍历看到了整个年级的成绩单那次的教训非常深刻。所以从第一版开始所有查询类接口必须带上学生归属校验。1.2 三端技术选型的取舍逻辑很多人在选型时纠结为什么家长端用微信小程序而不是直接做个H5或者开发独立APP。我的判断标准很简单家长的年龄跨度大让家长去装一个专门的教育APP门槛太高很多人不愿意装H5虽然免安装但入口不够固定通知触达能力也弱。微信小程序一方面基于微信生态扫码就能用几乎无需额外学习成本另一方面它有官方的模板消息和订阅消息能力可以比较稳定地把通知触达到家长微信上这是H5做不到的。管理端选Vue而不是直接写原生JS纯粹是从开发效率和维护角度看。Vue的组件化开发特别适合后台这种表单多、列表多、状态切换频繁的场景Element Plus组件库把表格、日期选择器、弹窗这些常用组件都封装好了开发一个班级管理页面从写原生代码的三四天缩短到大半天。至于Flask这个选择就更偏务实了。项目体量在中小型范围Flask的轻量和灵活是最大优势作为一个API服务端完全够用而且Python上手快团队里换人也容易接手。当然如果你预期的并发量很大比如全市级别的家校通那建议换Django这类重框架或者直接上Go但这是后期的事不要过早设计。1.3 模块边界与功能架构总览把整个平台拆开看功能集中在四个大模块上。第一个是通知公告模块支持富文本编辑、按班级或全校范围投放、已读未读统计这是家校通最核心的模块。第二个是作业管理模块教师布置作业并设置截止时间学生端的作业以图片或文字形式提交教师可批注打分。第三个是成绩管理模块教师录入成绩后仅对对应学生家长可见家长端展示历次成绩曲线。第四个是请假考勤模块家长发起请假申请教师端审批审批结果实时回到小程序端。这四个模块相互独立但共享一套用户体系和班级组织架构。这里有个设计细节值得注意学生、班级、家长三者之间的绑定关系不要直接在代码里写死而是单独建关联表因为实际项目中经常出现家长更换手机号、学生转班、一个学生绑定多个家长的情况。把关系表独立出来后续调整就只是一次update操作的事不用改动业务逻辑。2. 后端实现Flask框架搭建与核心模块2.1 工程结构与初始化配置后端工程我习惯按模块拆分的结构组织而不是所有代码堆在几个文件里。以家校通项目为例目录结构大致如下school_comm/ ├── run.py # 启动入口 ├── config.py # 配置文件环境区分 ├── app/ │ ├── __init__.py # 应用工厂注册蓝图、扩展 │ ├── models/ # SQLAlchemy数据模型 │ │ ├── user.py │ │ ├── classes.py │ │ ├── notice.py │ │ ├── homework.py │ │ └── grade.py │ ├── api/ # 蓝图路由按模块划分 │ │ ├── auth.py │ │ ├── notice_api.py │ │ ├── homework_api.py │ │ └── ... │ └── utils/ # 通用工具认证装饰器、文件处理等 │ ├── jwt_utils.py │ └── response.py └── requirements.txt应用工厂模式是Flask项目的推荐做法好处是可以为开发、测试、生产环境分别创建app实例配置不互相污染。config.py里按环境区分数据库地址、密钥、小程序appid和secret我一般还会加一个DEBUG开关。启动时通过环境变量指定加载哪份配置# config.py 核心片段 import os class BaseConfig: SQLALCHEMY_TRACK_MODIFICATIONS False SECRET_KEY os.environ.get(SECRET_KEY, dev-secret-key) class DevConfig(BaseConfig): SQLALCHEMY_DATABASE_URI mysqlpymysql://root:password127.0.0.1/school_comm?charsetutf8mb4 class ProdConfig(BaseConfig): SQLALCHEMY_DATABASE_URI os.environ.get(DATABASE_URL, ) SQLALCHEMY_POOL_SIZE 20 SQLALCHEMY_POOL_RECYCLE 3600另外在配置里一定要把数据库连接池参数调好SQLALCHEMY_POOL_RECYCLE这个参数很多人忽略默认情况下MySQL的wait_timeout大约8小时超过这个时间空闲的连接会被服务端断开连接池里的连接却还认为自己是可用的下次请求就报MySQL server has gone away。我在部署阶段因为这个报错排查了很久后来把连接回收时间设为3600秒问题就没再出现过。2.2 数据模型设计与关系梳理数据模型是这类平台的地基设计得好后面所有接口写起来都顺。家校通的核心模型我拆成这六张主表用户表userid、姓名、手机号、角色1管理员、2教师、3家长、微信openid、密码哈希、创建时间班级表classinfoid、班级名称、年级、班主任id外键到user表、入学年份学生表studentid、姓名、班级id、学籍号、性别关联表parent_studentid、家长用户id、学生id、是否主联系人通知表noticeid、标题、内容、创建人id、目标范围1全校、2特定班级、创建时间已读表notice_readid、通知id、用户id、读取时间作业、成绩、请假表属于业务数据各自独立但都通过外键关联到班级或学生。建表时有两个细节我特别强调。第一个是字符集统一用utf8mb4因为Emoji表情在老的utf8字符集下会报错家长提交作业时完全可能带个表情符号。第二个是时间字段建议用DATETIME而不是TIMESTAMPDATETIME的范围更宽而且不会受时区转换影响给家长展示成绩发布时间的时侯不用去做一次隐含时区换算。学生和家长的绑定关系单独建表这一点再重复一次因为它直接决定了个人信息展示的准确性。家长在微信小程序登录后后端通过token解析出用户id再通过parent_student表拿到其关联的学生列表所有查询都基于这个学生列表做过滤。如果家长绑定了两个孩子比如家里老大老二都在这个学校家长端还要有切换孩子身份的功能这时候关联表的作用就体现出来了。2.3 认证方案与接口权限控制微信小程序的登录流程和前几年对比已经简化了很多核心就三步小程序端调用wx.login()拿到临时code把code通过API传给Flask后端后端拿着code加上小程序的appid和secret去微信的jscode2session接口换取openid和session_key最后根据openid戳到用户表判断用户是否存在存在就发JWT token不存在就返回一个特定错误码让小程序端跳转到绑定页面。JWT签发我用的是PyJWT这个库token里只需要包含user_id和角色两个关键信息过期时间设为7天。签发和校验的代码封装在utils/jwt_utils.py里import jwt import time from flask import current_app def generate_token(user_id, role): payload { user_id: user_id, role: role, exp: int(time.time()) 7 * 24 * 3600 } return jwt.encode(payload, current_app.config[SECRET_KEY], algorithmHS256) def parse_token(token): try: payload jwt.decode(token, current_app.config[SECRET_KEY], algorithms[HS256]) return payload, None except jwt.ExpiredSignatureError: return None, token_expired except jwt.DecodeError: return None, token_invalid权限控制方面我写了一个装饰器role_required(roles)接口上声明允许哪些角色访问。比如提交请假申请只有角色为3家长才能调审批请假只有角色为2教师才能调。装饰器里解析token并校验角色不通过就统一返回401。还有一类接口需要校验数据归属关系比如家长拉取成绩单不能只校验角色是家长还要校验该家长确实绑定了这个学生这类逻辑写在具体的API处理函数里。3. 双前端落地微信小程序家长端与Vue管理后台3.1 微信小程序家长端的功能布局微信小程序端的页面结构我按家长的使用频率来设计底部Tab首页是通知信息流第二页是班级包含作业和孩子信息第三页是消息聊天和系统通知第四页是“我的”个人资料、绑定管理、设置。底部Tab控制在四个以内再多就会让家长用户感到压力。小程序开发中我最有体会的是网络请求的封装。小程序原生的wx.request能力很基础如果每个页面对接接口都写一遍回调逻辑代码会非常散。我通常会在utils/request.js里做一层统一封装核心是处理三件事请求头自动带上token、登录态过期时统一跳转登录页、后端返回的业务错误码统一弹出Toast提示。封装之后页面里面调用接口就是很简洁的Promise风格// utils/request.js 简化版 const request (url, method GET, data {}) { const token wx.getStorageSync(token) return new Promise((resolve, reject) { wx.request({ url: BASE_URL url, method, data, header: token ? { Authorization: Bearer ${token} } : {}, success: (res) { if (res.statusCode 401) { wx.navigateTo({ url: /pages/login/login }) reject(res.data) return } if (res.data.code 0) { resolve(res.data.data) } else { wx.showToast({ title: res.data.msg, icon: none }) reject(res.data) } }, fail: (err) reject(err) }) }) }上传作业照片时用wx.uploadFile注意这个接口的header传法和wx.request不太一样token不能放在JSON的header里会自动序列化需要手动指定Authorization字段为字符串拼接。另外图片上传之前一定要做压缩处理小程序端wx.compressImage压缩后体积能降到原来的五分之一否则学生交一张手机原图几M大小后端接收慢数据库里也不方便展示。3.2 Vue管理后台的页面和权限设计Vue管理后台是给教师和管理员用的对颜值的要求没有C端那么高但数据表格必须清晰。技术栈我用的是Vue 3 Vite Element Plus Pinia路由用Vue Router的history模式。整体布局是左侧菜单栏、顶部面包屑、中间内容区。教师视角的菜单项有班级管理、通知发布、作业管理、成绩录入、请假审批管理员权限在此基础上多出教师账号管理、全校通知和系统设置。这个差异是通过路由守卫和动态路由实现的登录成功后接口返回该用户的角色和可访问菜单列表前端根据这个列表动态添加路由。不要在前端静态定义好所有路由再靠按钮隐藏那只是视觉隐藏技术上还是可以通过地址栏直接访问到无权限页面。以前接过一个后台项目就干过这事用户直接输URL越权看数据所以现在权限都走动态路由没有注册的路由路径直接显示404。作业管理页是最能体现Vue组件化优势的地方。列表页用el-table展示所有作业每行展开后能看到提交学生列表和提交状态教师可以在详情抽屉里查看学生提交的图片并给出批改分数。这样一个页面信息层级很多如果用原生JS写DOM操作会非常痛苦Vue的数据驱动模式让状态维护简单很多。3.3 前后端联调与跨域问题处理前后端联调阶段最常见的坑就是跨域。开发环境下小程序端在开发者工具里可以勾选“不校验合法域名”然后直接请求本机局域网IP上的Flask服务http://192.168.x.x:5000没有跨域问题。但Vue管理后台跑在Vite开发服务器上默认端口5173它向Flask后端发请求就存在CORS跨域。解决CORS我推荐在Flask端统一处理而不是让前端代理。后端安装flask-cors扩展后一行配置就能放开所有来源from flask_cors import CORS CORS(app, resources{r/api/*: {origins: *}})生产环境再根据实际域名收紧来源。需要注意的限制是flask-cors默认只处理简单请求和跨域预检如果接口用了自定义Header比如我们加了Authorization浏览器会先发一个OPTIONS预检请求Flask这边要确保OPTIONS请求也能正常返回200否则前端会看到“CORS policy”报错。用flask-cors之后它会自动处理OPTIONS不需要手工写before_request去拦截。联调过程中我习惯维护一个接口文档不用太重的工具项目里建一份Markdown接口约定文档每次接口变更同步更新。这样做表面看多花了时间实际上在联调阶段省下的沟通成本远大于记录成本。4. 核心业务功能实现拆解4.1 通知公告发布与已读回执通知模块不难做但“已读未读”这个功能容易想简单。如果通知A发给了全校500个家长后端难道每次都扫描这500个人的阅读状态吗数据量不大时这样写也没问题但逻辑上更好的做法是只记录“谁读了”用两条查询搞定一条查通知相关人总数一条查notice_read表里读过的记录数一减就是未读数。发布通知的接口要处理富文本内容。Vue管理端用的是Element Plus的el-input配合v-html实现简单编辑但我实际不建议在生产环境让用户直接粘贴网页内容容易带上危险脚本。后端在保存通知内容时可以用bleach这个库白名单化HTML标签只保留安全的p、br、img、a等标签脚本和事件属性一律过滤掉。这一步做了之后云端部署时安全上会踏实很多。小程序端信息流展示通知卡片每条通知显示标题、发布时间、已读状态右下角一个小圆点标记未读。家长点击通知详情后小程序请求一个/api/notice/read接口记录已读。这里有个细节通知详情页的onLoad声明周期里请求详情数据但如果用户从列表页直接分享卡片给别人打开页面没有列表页的上下文后端返回详情时还要顺带返回通知ID前端再拿这个ID调已读接口。类似这种离开列表页就丢失状态的问题建议后端把已读标记也一起在详情接口返回时处理掉前端少一次请求逻辑也简单。4.2 作业布置、提交与批改闭环作业模块的状态流转设计成这样教师创建作业草稿状态为0→ 发布作业状态为1家长可见→ 截止时间到作业自动标记为结束批改状态为2→ 教师批改完成状态为3。四个状态在Vue管理端用不同的Tag颜色区分家长端只展示状态为1和3的作业。学生提交作业在小程序端实现。先说图的部分学生拍照后传给后端后端保存到uploads目录并把访问URL写入数据库。这里一定要处理重名文件我用的是uuid4().hex加原文件后缀生成文件名避免并发时文件互相覆盖。附件路径问题的隐患就开始冒头了后面部署章节里我会详细讲。文字部分比如作文类作业用textarea输入提交时注意字符长度限制。这里有一个容易被忽略的点截止时间之后家长端应自动转为只读状态但是判断截止的代码不能只写在接口里前端也要做判断。我遇到过一种情况学生在截止时间前打开提交页面停留了半小时才点提交此时后端已经判为超时接口返回错误。用户体验上要给一个明确提示作业已截止无法提交。这比只返回“提交失败”要友好得多。教师批改作业时对图文作业可以直接给评分对文字作业还可以写简短评语。评语存作业表的一个字段里家长端在作业详情页能看到。这个闭环完成后教师的工作负担相比传统纸质批改减轻了很多家长对孩子的学习情况也能及时把握。4.3 请假审批流程的设计请假功能看起来简单就一个提交一个审批但业务细节不少。家长端先选择请假类型事假、病假、其他填写请假时间范围和原因提交后生成一条请假记录状态默认是待审批。教师端在小程序或者Vue后台都能看到待审批列表点击详情可以同意或驳回驳回时最好能填写理由家长端会收到状态变更的通知。审批状态变更后怎么通知家长这里用微信小程序的订阅消息。订阅消息和旧版模板消息的区别是用户必须先订阅才能收到推送且一次性订阅只能推送一次。家长在提交请假申请时同时弹窗引导家长允许订阅“请假审批结果通知”点击同意后小程序调用wx.requestSubscribeMessage授权后端在审批动作发生时通过微信接口推送订阅消息。这里要特别说明订阅消息需要在小程序的后台申请模板审核通过后才能发送所以这部分功能排期时要把审核等待时间考虑进去。还有一个边界情况跨天请假比如周五请到下周一会在数据库里存开始时间和结束时间两个字段任何一步审批通过后学生在这段时间内的考勤记录自动标记为“请假”这样月末出勤统计不需要额外处理。5. 部署上线与运维排查5.1 Flask服务部署与附件路径问题Flask应用在Linux服务器上的部署组合我基本固定是Nginx Gunicorn数据库和生产环境用 MySQL应用层通过Gunicorn启动。Gunicorn的启动命令是gunicorn -w 4 -b 127.0.0.1:5000 run:app4个worker进程对于中小规模的家校通平台足够支撑。Nginx负责接收外部请求把/api/前缀的请求转发给Gunicorn同时托管Vue构建后的静态文件。部署阶段踩得最深的坑就是附件路径。开发时在Windows环境下写的代码是这样保存文件with open(uploads/ filename, wb) as f: ...Windows上这种写法没问题但部署到Linux服务器上之后相对路径的工作目录往往会因为Nginx和Gunicorn的启动方式不同而发生变化图片要么传上去找不到要么就存到了奇怪的目录。我的教训是文件存储路径一定要通过配置项显式指定绝对路径并且启动时检查目录是否存在不存在就自动创建import os from flask import current_app def save_upload(file_storage, filename): upload_dir current_app.config[UPLOAD_DIR] # /data/school_comm/uploads os.makedirs(upload_dir, exist_okTrue) file_storage.save(os.path.join(upload_dir, filename)) return os.path.join(upload_dir, filename)还有个细节Nginx上配置静态文件访问时要保证location /uploads/指向真实的文件目录否则前端拿到图片地址返回404。这属于两边的映射漏一个就白搭。5.2 微信小程序发版与合规注意点小程序端开发完要上传代码到微信公众平台提交审核、发布。审核阶段有几个常见驳回理由需要提前注意第一涉及学生成绩、家庭住址等隐私信息必须在小程序隐私保护指引里面明确声明收集和使用目的微信对个人信息的审核很严第二如果不小心在代码里留下了测试环境和生产环境切换的开关而且默认是测试环境提交审核时审核员看到的是测试数据很容易误判为“功能无法正常使用”第三所有网络请求域名必须是HTTPS且已在小程序后台配置白名单。基于这些坑我在项目里专门做了一套环境配置管理配置文件里用const BASE_URL https://api.school.example.com写死生产地址不提供动态切换的入口。调试阶段用开发者工具的本地调试功能但要改代码正式提交审核前必须撤销掉本地调试的改动。5.3 高频问题排查记录我自己维护了一份这个问题速查表按端分类遇到同类问题可以秒定位问题描述可能原因解决动作小程序请求接口报request:fail域名未加入白名单或未启用HTTPS检查小程序后台request合法域名配置登录后请求接口返回401token过期或未存入storage检查request.js统一头逻辑过期则跳转登录页Flask日志报MySQL server has gone away连接池空闲超时配置SQLALCHEMY_POOL_RECYCLE 3600Vue部署后刷新页面404history模式路由需要服务端回退Nginx配置try_files $uri $uri/ /index.html上传图片后前端访问返回404存储路径与Nginx静态映射不一致排查UPLOAD_DIR和Nginx location映射是否对齐小程序端授权弹窗多次出现订阅消息每次要求重新授权在关键操作前收集授权不要每次进页面都弹接口返回跨域错误CORS未配置或预检请求未处理确认flask-cors已配置并支持OPTIONS成绩日期显示差8小时MySQL时区与北京时间不一致连接串加charsetutf8mb4并在启动SQL设置时区这里面最容易被忽视的是Vue刷新404的问题。很多人在本地跑Vite开发服务器刷新一切正常部署到Nginx后刷新一个子页面比如/dashboard/homework就404了这是因为history模式下的URL在服务端找不到对应文件。Nginx里加一行try_files $uri $uri/ /index.html就可以解决后台和前端联调时必踩。6. 性能调优与安全加固实践6.1 数据库查询优化与索引设计家校通项目的数据量短期内不会特别夸张但查询模式比较固定且频繁索引设计好了性能就有保障。通知列表页按时间倒序展示所以notice表的create_time字段要加索引作业查询最常见的过滤条件是班号加状态所以homework表加(class_id , status)联合索引成绩表按学生查student_id必须有索引。索引不是越多越好每个索引都会增加写入开销家校通这种读多写少的场景优先给查询条件字段加索引即可。在Flask的ORM层面查询时不要图省事把整张表查出来在Python里过滤。我见过有人在视图函数里把所有通知列表拉出来然后循环判断当前用户角色来决定展示哪些数据量小的时候看着没毛病但班里上千条通知之后接口响应会肉眼可见地变慢。正确的做法是SQLAlchemy查询时就把过滤条件带到SQL里比如Notice.query.filter(Notice.target.in_([0, class_id])).order_by(Notice.create_time.desc()).paginate(...)数据库索引能直接命中。6.2 接口安全加固接口层面我要提几个实际经验。第一所有修改类接口发布通知、提交作业、审批请假必须校验当前操作用户的身份和权限JWT里存的user_id不要轻易信任前端传来的任何id。第二用户上传的文件类型不能只看扩展名要校验Content-Type甚至读取文件头信息判断真实格式防止有人传一个伪装成jpeg的脚本文件。第三给图片上传接口加单次文件大小限制Flask默认请求最大16MB再大就该报413错误了。还有一点容易被忽视的是错误信息不要暴露太详细。比如登录失败时只返回“手机号或密码错误”不要区分“用户不存在”和“密码错误”否则等于给恶意请求做了用户枚举。同理后端异常只记录到日志里返回给前端的是统一格式的{code: 500, msg: 服务端错误}不要把Python堆栈信息直接透出。6.3 轻量化部署后的日常巡检项目上线之后我习惯做三件例行的事。第一件是每天看一遍Gunicorn的访问日志特别关注5XX错误的比例如果某天突然飙升基本就是接口或数据库出了问题。第二件是监控服务器磁盘空间家校通平台上图片上传量增长速度很快uploads目录几个月就能攒几GB不加清理策略迟早写满磁盘。我的做法是写一个定时任务定期把超过半年没被访问根据数据库里的上传时间的作业图片归档到冷存储并在数据库里更新其访问地址为归档后的地址。第三件是定期检查HTTPS证书有效期设置了自动续期提醒不然证书过期当天小程序直接报“域名证书无效”所有家长端功能全部瘫痪这种低级错误在线下出一次就够了。7. 关于这个项目的几句总结做完这个家校通平台再回头看我最想强调的一点是技术组合本身不复杂真正决定项目成败的是需求边界是否清晰、数据归属关系是否可靠、部署阶段是否认真对待了环境和路径这类基础问题。微信小程序、Vue、Flask单拎出来都有大量现成文档但把它们串成一个完整闭环并且扛住真实用户的使用压力需要在细节上下足功夫。如果你正在做类似的项目建议把重点时间花在数据库关系设计和权限边界梳理上这两个地方一旦返工代价是整个上层业务跟着重写。项目上线后还有很多可以延伸的方向比如班级相册、在线缴费、学生成长档案这些模块只要底层的用户体系和班级关系没乱扩展起来会顺手很多。
返回列表