ARTICLE DETAIL

资讯详情

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

Python-Flask携微信小程序打造大学生课表查询与上课提醒系统

Python-Flask携微信小程序打造大学生课表查询与上课提醒系统 开学抢课、调课、单双周轮换、临时换教室这些东西经历过大学的人都懂。我做的这个“Python-Flask大学生课表查询和上课提醒系统小程序”就是把课表查询、课程管理和到点提醒这三件事焊在一起后端用Flask提供API前端跑在微信小程序里课表随手查上课前自动收到订阅消息提醒。对想练全栈的同学来说这是一套非常典型的“小后端小前端”项目麻雀虽小但五脏俱全能一口气串起Python Web开发、小程序生命周期、微信订阅消息、定时任务和基础部署运维。下面我把这个项目的完整实现思路、核心代码设计、踩过的坑和上线经验全部拆开讲你可以直接照着复现。1. 项目概述与核心需求解析1.1 大学生课表场景的痛点在哪先说需求来源。大学课表和中学课表最大的区别就是“不稳定”课程不是每天固定几节而是按周次、星期、节次、单双周排列经常出现第3周才开始上课、单周上双周不上、节假日调课补课这类情况。传统做法无非三种把教务系统截图存相册、用Excel手排课表、买商业课表App。截图课表没法提醒Excel改起来痛苦商业App要么收费要么广告多最关键的是它们都不懂你的“这周到底上不上课”。所以这个系统的核心价值就三个字准、快、自动。“准”是能正确处理周次和单双周“快”是打开小程序就能看到今日课程“自动”是到点主动提醒你不用自己定闹钟。1.2 技术选型为什么是Flask小程序很多人在技术选型时会纠结后端要不要用Django前端要不要用原生小程序我的结论是这种体量的项目用Flask最舒服。Flask轻量、灵活、文档全一个文件就能跑起接口非常适合个人开发者和课程设计场景。它不像Django那样自带Admin、ORM、Migrate全家桶但恰恰因为“缺东西”你才能明白路由、请求、响应、数据库连接这些Web基础到底是怎么工作的。项目大了也不用怕Flask支持蓝图Blueprint可以把路由按模块拆分后面我会上代码说明。小程序选微信原生原因更直接第一微信是高频应用课表查询这种“每天打开一两次”的工具型产品不需要用户专门下载App第二微信订阅消息天然就是做上课提醒的通道不需要自己写推送服务、不需要管iOS和Android的推送差异第三原生小程序框架对课表这种简单的信息展示页完全够用不需要引入uniapp或Taro增加构建复杂度。1.3 系统整体功能拆解功能不需要多但每个都要能用课表查询按周次展示课程表支持左右切换周次高亮“本周”今日课程首页直接展示今天有哪几节课、几点上、在哪个教室课程管理新增、修改、删除课程支持设置节次、星期、周次范围、单双周上课提醒用户授权订阅消息后课前N分钟自动收到提醒点击可直接跳转小程序登录与数据隔离通过微信登录绑定openid每人只能看到和管理自己的课表这套功能拆出来会发现每个单点都不复杂难点全在“数据如何组织”和“提醒如何准确触发”。接下来我按后端到前端的顺序逐步拆解。2. 环境准备与后端整体设计2.1 Python环境与依赖安装后端我用的Python 3.10Flask版本建议2.x以上不要用太老的1.x。先建虚拟环境再装依赖这一步很多人偷懒跳过结果全局环境越来越乱换项目就炸。# 创建虚拟环境 python -m venv venv # Windows激活 venv\Scripts\activate # macOS/Linux激活 source venv/bin/activate # 安装核心依赖 pip install flask flask-cors apscheduler requestsflask-cors必须装否则小程序端请求接口会被浏览器同源策略拦截虽然小程序不是浏览器但加上CORS总是更稳妥后续调试Web工具也更方便。APScheduler用来做定时任务requests用来调用微信的code2Session接口换openid。注意如果你要连数据库简单场景直接用SQLite就够不需要单独装MySQL。学生个人项目的数据量远没到需要独立数据库服务的程度SQLite零配置、单文件、好迁移优先选它。2.2 数据模型设计让课表变成可计算的数据课表不能是一张图片必须拆成结构化字段。我设计了三张表用户表、课程表、提醒记录表。用户表保存微信用户基础信息核心字段就是openid它是用户在微信生态里的唯一身份标识。课程表是核心我建议至少包含这些字段字段说明示例id课程ID1user_openid所属用户oXkCk5xxxxcourse_name课程名称高等数学teacher教师张老师classroom教室教3-201weekday星期几1-71start_section开始节次1-121end_section结束节次2start_week开始周次1end_week结束周次16week_type0为每周1为单周2为双周0remind_before提前提醒分钟数15这里最关键的字段是week_type。单双周是大学课表最常见的坑我见过很多半成品项目直接忽略这个字段导致单周课程在双周也提醒被用户骂到自闭。设计成0/1/2三态最保险程序里判断的逻辑我后面会细说。提醒记录表用来记录每次提醒的发送状态避免同一节课在同一个触发周期内重复发送同时方便查看推送失败的原因。2.3 Flask项目结构与API路由规划不好的项目一个app.py堆到底几百行后根本没法维护。我按Blueprint把路由拆开结构清晰后面加功能也不怕。course-reminder/ ├── app.py # 应用入口 ├── config.py # 配置 ├── models.py # 数据模型 ├── utils/ │ ├── course_utils.py # 课表时间计算 │ └── wechat_utils.py # 微信接口封装 ├── blueprints/ │ ├── auth.py # 登录 │ ├── courses.py # 课表CRUD │ └── remind.py # 提醒相关 └── scheduler.py # 定时任务路由规划如下POST /api/loginwx.login拿到code后端换取openidGET /api/courses查询当前用户课表支持week参数过滤POST /api/courses新增课程PUT /api/courses/ 修改课程DELETE /api/courses/ 删除课程GET /api/today查询今日课程POST /api/remind/subscribe保存用户订阅授权记录每个接口都统一返回JSON格式{code: 0, data: ..., msg: success}code为0表示成功。小程序端统一封装请求遇到非0的code统一弹错误提示这样前后端联调省事得多。3. 核心API开发与上课提醒实现3.1 课程数据的录入方式课程数据怎么进去是这类项目最容易被低估的一步。让用户手动一门课一门课地填非常痛苦体验极差但如果做成自动从教务系统爬课表又面临验证码、教务系统接口变动、法律风险等一堆破事。我最终采用的是“半自动导入”方案后端提供一个导入接口前端在“我的-导入课表”里粘贴一段按固定格式排列的文本或直接选教务系统导出的Excel文件后端解析后批量写入课程表。格式类似于高等数学|张老师|教3-201|1|1|2|1-16|0 线性代数|李老师|教2-105|2|3|4|1-16|2每行七段用竖线分隔分别对应课程名、教师、教室、星期、开始节次、结束节次、周次范围、单双周标记。这样用户一次性粘贴几秒钟就能完成整学期课表的导入比手填体验好很多。解析代码核心就是split(|)然后做字段校验。这里要特别注意周次范围的解析“1-16”要拆成start_week1和end_week16“1,3,5-8”这种不连续的也要处理我建议用正则把逗号分隔的每一项解析成连续区间最后展开成一个set查询时直接判断当前周是否在集合里。别看这只是个细节处理不好会导致周次判断全错。3.2 课表查询接口周次和单双周怎么算查询课表的核心函数是“今天上什么课”。给定一个周次和目标日期要判断某节课是否生效需要三步判断当前周是否在课程的周次范围内判断当前日期的星期是否等于课程的weekday判断节课是否受单双周限制第三步的逻辑很简单如果week_type为0直接通过为1则要求当前周为奇数为2则要求当前周为偶数。比如“高等数学|第1-16周|单周”系统在第3周会提醒在第4周就不会。这个判断在课程查询和定时提醒里都要复用所以我把它封装在course_utils.py的is_course_active_in_week函数里避免两处逻辑不一致。返回给前端的数据里我除了返回课程基础字段外还附带计算好的“状态”字段即将开始、正在上课、已结束、今天无课。前端拿到这个字段直接在页面上渲染不同颜色卡片逻辑完全不用重复写。3.3 微信登录从code到openid的转换微信小程序登录流程看似简单但第一次做的人容易搞混。前端wx.login()拿到的code是临时凭证有效期只有5分钟后端需要拿着这个code去微信服务器换取openid和session_key。后端接口实现如下app.route(/api/login, methods[POST]) def login(): data request.get_json() code data.get(code) url ( https://api.weixin.qq.com/sns/jscode2session? fappid{appid}secret{secret}js_code{code} grant_typeauthorization_code ) resp requests.get(url).json() if openid in resp: # 查询或创建用户 user User.query.filter_by(openidresp[openid]).first() if not user: user User(openidresp[openid]) db.session.add(user) db.session.commit() return jsonify({code: 0, data: {openid: resp[openid]}}) return jsonify({code: 400, msg: 登录失败})这里有个实际项目必须注意的点小程序端不要自己保存openid更不要在前端代码里硬编码appid和secret。正确做法是后端登录后生成一个随机token返回给前端前端后续请求带上token后端根据token查出对应的openid。或者最简单的方式是后端直接把openid返回给前端小程序端存到storage里用于之后的请求虽然不够严谨但课程设计级别够用了。如果你想做严谨一点可以加token机制我自己的项目就是加了token的逻辑其实不复杂但能明显提升安全意识。3.4 上课提醒定时任务与订阅消息组合提醒是整个项目里最亮眼的功能也是最容易翻车的地方。先说微信订阅消息的机制小程序需要先让用户点击“授权提醒”按钮每次授权只能发送一条订阅消息用户拒绝一次之后后续就无法推送除非用户再次在设置中手动开启。这个限制必须接受因为微信规则就是这样的。所以我在前端做了两个策略来缓解第一提醒设置页用开关形式的列表每门课程可单独设置是否提醒第二用户开启某门课程的提醒时我们请求订阅一次用户同意后我们就在提醒记录表里记一条“剩余可发送次数”每次真正推送后减1。定时任务的实现用APScheduler。我设置每30秒扫描一次数据库找出当前时间与上课时间差等于remind_before的课程记录再检查是否已提醒过如果没提醒过就发送订阅消息。scheduler BackgroundScheduler() def check_upcoming_courses(): now datetime.now() weekday now.isoweekday() current_week calculate_current_week(now) courses Course.query.all() for course in courses: if not is_course_active_in_week(course, current_week, weekday): continue class_time get_start_time(course.weekday, course.start_section) diff (class_time - now).total_seconds() / 60 if 0 diff course.remind_before and not has_reminded(course.id, now.date()): send_subscribe_message(course) scheduler.add_job(check_upcoming_courses, interval, minutes0.5) scheduler.start()这里有个容易踩的坑get_start_time需要根据节次时间表计算比如第1节是8:00-8:45第2节是8:55开始那么第1-2节的上课时间就是8:00。学校不同作息时间也不同这个节次时间表必须做成配置文件别写死在代码里。send_subscribe_message里需要调用微信的subscribeMessage.send接口关键参数是用户openid、模板ID、课程名称、上课时间、教室等。模板ID要在微信公众平台申请审核通过了才能用申请时选择“课程提醒”之类的模板类目就行。发送成功后会返回errcode为0其他code按文档排查常见的有43101用户拒绝授权、47003模板参数不匹配。4. 微信小程序前端开发4.1 页面结构设计小程序端我设计了三个主页面课表页、今日页、我的页。课表页是核心页面上方是周次切换器中间是课表网格下方是当前周课程列表。周次切换器左右各一个箭头中间显示“第X周”点击中间数字可以弹出一个选择器直接跳转到指定周。今日页展示当天的所有课程卡片按时间排序卡片上直接标出“距上课还有XX分钟”或“正在上课”状态。我的页面放用户信息、课程管理入口、提醒设置入口。小程序的路由配置在app.json里只需要注册这三个页面和配套的子页面课程编辑、提醒设置、课表导入。每个页面都对应一个同名的js、wxml、wxss、json文件这是小程序的规定结构不用像Web前端那样自己配路由。4.2 课表网格组件的核心实现课表网格用小程序的view布局实现不用canvas因为canvas处理点击事件太麻烦。我采用的是“列行”两重循环列方向是星期一到星期日7列行方向是节次1-12节。网格数据是一个二维数组第一维是节次第二维是星期。每个格子根据课程是否在当前格显示来渲染内容。课程跨越多个节次时我在响应的多个格子中只渲染一次其他格子用CSS控制占位避免课程卡片重复显示。关键点在于每个格子的宽高比例手机屏幕宽度有限7列网格每列只有50px左右课程名很容易折行。我用的方案是课程卡片只显示课程名和教室字号设为10rpx多余文字用省略号处理点击课程卡片后弹出一个详情弹窗显示完整信息。这样既保证了课表一屏能看全又不损失信息。周次切换要配合后端的week参数请求GET /api/courses?week3后端返回第3周有效的课程前端收到后重新渲染网格和底部列表。切换时我加了loading状态虽然本地数据库理论上更快但从接口拿数据逻辑更统一后续如果做多人课表也方便。4.3 今日课程与上课状态显示首页“今日”的体验直接影响用户留存做得好了用户每天至少打开一次。我做了两个细节一是状态倒计时。从上个接口返回的status字段前端在wxml里直接用wx:if渲染三种样式绿色卡片显示“即将开始还有XX分钟”红色卡片显示“正在上课”灰色卡片显示“已结束”。倒计时刷新不是每秒翻一次那样耗性能和电量我设置在每分钟开始时刷新一次时间计算对用户来说体验完全一样。二是空态设计。周末很多人一整天没课页面如果空白一片会让用户困惑。我加了一个空状态组件显示“今天没有课程安排休息一下吧”再配一个去课表页看看的按钮。这个细节看似不起眼但能显著减少用户“是不是坏了”的疑惑。4.4 提醒设置与订阅授权提醒设置放在“我的”页面进入列出所有课程每门课后面一个开关。用户打开开关时会弹出一个订阅消息授权框wx.requestSubscribeMessage用户同意后后端记录一次订阅次数并在数据库里更新该课程的remind_enabled为1。这里有几个小坑我必须提醒wx.requestSubscribeMessage必须在用户点击事件中调用不能自己触发否则直接返回fail一次只能传一个模板ID如果你有多个模板就要多次调用我设计的时候就只用了一个课程提醒模板避免复杂化用户点了“总是保持以上选择”之后后续每次请求并不会自动通过还是需要用户至少同意过一次才能发送订阅消息的模板内容里如果有“年月日”这类日期参数必须保证格式与模板要求完全一致否则发送报错授权成功后用户关闭开关会自动置为0同时后端删除该课程的提醒记录这样就不会在用户关闭后还收到推送。5. 部署上线与常见问题排查5.1 Flask后端部署要点开发调试时Flask自带服务器够用但要上线必须换WSGI服务器我用的Gunicorn简单稳定。部署结构就是Gunicorn Flask SQLite轻量得不能再轻。pip install gunicorn gunicorn -w 2 -b 0.0.0.0:5000 app:app两个worker就够这种查询型小项目对并发要求不高。用SQLite时要注意多线程并发写可能触发“database is locked”解决办法是给SQLite配置连接池或者在写操作时重试。我建议直接给Flask SQLAlchemy配置app.config[SQLALCHEMY_ENGINE_OPTIONS] { connect_args: {timeout: 15} }timeout设为15秒能显著减少锁定概率。重要提醒项目上线后一定要修改数据库文件路径默认的instance目录要确保运行用户有写权限很多人上线后遇到“unable to open database file”就是权限问题。5.2 小程序发布与审核注意小程序端开发时可以在开发者工具里勾选“不校验合法域名”本地调试很爽。但真机预览和发布时要把后端域名加到小程序后台的request合法域名列表里。合法域名必须是HTTPS这个注意如果你用http协议只有开发者工具本地能用真机一定请求失败。审核时还有几个容易踩的坑小程序类目选择“教育-教育信息服务”需要的资质相对少页面里不能出现测试数据、mock数据、待办提示文字如果有课程导入功能输入框需要做占位符提醒不能让人一眼看上去是空页面隐私协议弹窗是强制的需要在app.json里配置用户隐私保护指引否则审核会被打回订阅消息模板审核需要时间请提前几天申请别等上线前一天才去弄5.3 常见问题速查表问题现象排查思路开发者工具请求正常真机不行真机所有请求全部报错检查request合法域名是否配置域名是否为HTTPS备案是否完成登录成功但课表为空能拿到openid但GET /api/courses返回空数组检查课程数据是否确实存在检查user_openid是否存到了正确的字段订阅消息发送失败errcode43101用户没授权前端触发时机不对必须在点击事件里调用requestSubscribeMessage提醒没有触发到点了没收到消息检查APScheduler是否启动检查服务器时区是否为Asia/Shanghai检查数据库里remind_enabled状态数据库locked写入时偶尔报错SQLite连接timeout调大或换MySQL修改课程后课表没变前端页面没实时刷新检查小程序端是否有下拉刷新或重新加载逻辑不要依赖缓存单双周课程判断错了单周提醒了双周也提醒重点查week_type字段是否存对is_course_active_in_week逻辑是否覆盖week_type为0的情况调试时有个工具很实用小程序开发者工具里的Network面板能看到所有请求的完整返回后端报错时直接在Console里看响应内容。后端日志我建议用print输出到控制台配合systemd或pm2日志重定向线上问题基本都能第一时间定位。6. 项目心得与扩展思路在这个项目里我最深的体会是课表系统难的不是课表展示而是“周次、节次、单双周”这六个字背后的数据逻辑。一个看似很简单的查询在边界条件上能翻出无数花来第17周还有课、单周和双周切换那天用户看哪个周次、节次表改了时间之后提醒还准不准。这些细节全部处理好系统才敢说真的可用。如果你只做一半就停下来那它只是一个增删改查的练习项目但当你把提醒、订阅消息、定时任务、部署全部打通之后你真的可以把它发给同学用看着他们在真实环境中给你反馈“第3周没提醒”“教室号显示不全”这种反馈带来的成长是写一百道题都比不上的。后续可以扩展的方向也很多比较实用的有考试周临时课表导入、节假日调课自动处理、课程冲突检测预警、多学期课表归档对比甚至可以把同学之间互相查看课表做成组队自习功能。核心结构都好了加功能只是往上叠模块的事。
返回列表