基于微信小程序与Python的实验室预约排课系统设计与实现
发布时间:2026/9/29 16:27:22来源:尧图网络
最近好几个高校的实验室管理员朋友跑来问我能不能把实验室预约从“微信群接龙Excel排课表”里解放出来。聊了一圈我给的方案都是同一个小程序 Python后端做一套实验室预约排课系统。小程序负责学生端和管理员端的使用入口Python负责预约规则、排课冲突检测和数据接口这套组合对于实验室这类场景来说轻量、够用、还特别好落地。这篇文章把我做这个系统的完整思路、核心代码和踩过的坑一次说清楚正在做类似课设或者真想给实验室做个小工具的朋友可以直接拿走参考。1. 项目整体设计与技术选型1.1 为什么前端选小程序而不是App或Web页面实验室预约这个场景有个特点使用频次不算高但用的时候都特别急。今天老师临时调课学生小组明天要做实验没人愿意为了预约一次额外下载一个App。小程序天然适合这种“低频刚需”的场景扫码进入、用完即走不用安装也不用担心安卓和苹果应用市场的审核流程。如果你还在纠结要不要用uniapp开发我的建议是分情况。如果项目只面向微信生态直接写原生微信小程序就够了开发工具顺手、调试方便、API文档也全。如果你们学校未来可能有支付宝小程序、抖音小程序的需求才值得用uniapp做多端适配但学习成本和打包配置的复杂度会明显上升。相比之下原生小程序的wxml、wxss、js这套东西和前端基础语法几乎同源做过网页开发的人一周就能上手。另外有个现实问题实验室预约系统往往还要对接企业微信或学校内部的通知体系微信原生生态在这块最省事。还有一点别忽略小程序不用处理跨平台设备的碎片化问题。安卓和鸿蒙的返回键逻辑、iOS的键盘弹出方式、各种机型的刘海屏适配原生小程序都被微信统一处理得差不多了。如果自己搞App光是这些细节就能消耗掉半个开发周期。所以除非有明确的多端诉求否则不要让“想做App”的冲动主导技术选型。1.2 后端为什么选Python而不是Java或Node.js后端选型上我毫不犹豫选了Python。理由很实际实验室预约排课系统的核心难度不在并发量而在排课规则和预约冲突的判断。Python写这类业务逻辑非常流畅几行就能表达一个复杂的判断条件而且字典、列表这些数据结构处理前端传来的JSON参数特别自然。Python做后端的优势在校园和中小企业场景里体现得尤其明显。开发周期短一个人一周就能把核心接口写完调试方便报错信息直观生态里有Flask、FastAPI、Django这些成熟框架按需选择就行。我自己偏爱FastAPI因为它是异步框架性能比Flask好还能自动生成OpenAPI文档小程序端的人看接口文档都省了。如果团队里都是Python新手选Flask更稳资料最多、坑最少。很多人在这个项目里纠结“用Python会不会性能不够”这就是想多了。高校实验室的预约并发量高峰期也就几百个人同时抢晚上几个时段FastAPI加一个MySQL完全扛得住。真正决定系统好不好用的是你能不能把排课冲突规则写对而不是语言本身快不快。当然你要是想锻炼自己也可以试试用Java Spring Boot但那个开发成本会明显高对实验室预约这种项目来说有些大材小用。1.3 系统整体架构和数据流整套系统的架构非常简单核心就三层小程序前端、Python后端接口、MySQL数据库。小程序端负责页面展示和交互比如实验室列表、日期选择、时段预约、管理员审核后端用FastAPI暴露RESTful接口处理登录鉴权、预约创建、冲突检测、排课数据查询数据库存所有持久化数据包括用户、实验室、预约记录、排课课程。小程序通过wx.request请求后端的http接口数据格式统一走JSON。具体的请求流程可以这样理解学生打开小程序先用微信code登录后端调微信接口拿到openid并返回一个自定义token之后每次请求都带上token后端校验身份学生选择实验室、日期、时间段并提交预约后端先检查这个时段是否已经被课程占用、是否已被别人预约、是否超过实验室容量全部通过才写入数据库管理员端则负责导入实验课表、设置实验室开放时间、处理学生预约的审核和取消。这套流程不复杂但每个环节都有坑后面我逐个拆。2. 核心功能拆解与关键流程设计2.1 用户端登录、实验室列表、预约下单用户端最核心的三个功能是登录、查看实验室、提交预约。登录这块微信小程序一定要用wx.login拿到临时code再把code传给后端由后端调用微信的code2Session接口换取openid。这里有个特别容易被新手忽略的点小程序端绝对不要自己去请求微信接口换openid因为那把appSecret暴露给了前端等于是把用户数据脱裤子给人看。正确做法是后端持有appSecretcode每次用完就失效后端拿到openid后生成自己的token返回给小程序端后续请求都带这个自定义token。实验室列表页面要展示实验室名称、所在位置、可容纳人数、当前开放状态、今天的空闲时段。这里需要注意的是实验室列表不能一次性把所有数据都返回分页是必须的否则数据量大了小程序渲染会卡。我习惯用limit和offset做简单的分页每个页面加载20条触底时再拉下一页。预约下单是整个系统最敏感的操作。前端让用户选择日期、时段、实验类型、参与人数然后提交给后端。千万不要在前端做预约是否冲突的判断就认为万事大吉更不要直接把数据写进数据库。前端的判断只是体验优化真正的冲突检查和写入限制必须在后端完成因为前端代码可以被抓包改参数绕过限制。小程序抓包这个事我就不细说了但做开发的一定要默认所有前端传参都不可信。2.2 管理员端排课导入、预约审核、数据统计很多做预约系统的人容易忽略管理端其实管理端的复杂度比用户端还要高。排课是整个系统的灵魂学生预约是在课程剩下的空余时间上做文章的所以课程排课必须可靠。我建议的排课方式是管理员在后台按实验室、按周次导入课程表。导入方式可以支持手工添加也可以支持Excel批量导入。Excel导入听起来高级但从实际使用来看手工添加反而更稳因为绝大多数实验室一个星期就那么几十节课手工一条条加也就十几分钟。批量导入Excel需要处理格式兼容、日期解析、重复行等问题开发成本和收益不成正比除非你们有几十个实验室才建议做。预约审核这块可以做成“提交即生效”和“需要管理员审批后生效”两种模式。对于校园实验室我更推荐前一种即提交即生效但设置一个防冲突检查和信用分机制。学生预约成功后如果爽约三次拉入黑名单限制预约两周。这样既保证使用效率又减少管理员的负担。人工审批模式适合有特殊设备的实验室比如贵重仪器房需要老师确认才能通过。数据统计也不能缺。管理员需要知道每个实验室的使用率、每个时段的预约热度、每个院系的使用情况。这些数据可以指导下一学期的排课比如某个实验室周三下午总是被约满可以考虑多排专业实验课某个设备孤岛实验室长期无人用就应该重新分配用途。简单用SQL聚合查询就能出报表没必要上大数据组件。2.3 预约排课的核心冲突检测规则冲突检测是这个系统最核心、也最容易写错的地方。规则其实不难我把它拆成下面几条第一时间不能重叠。学生预约的时段不能被课程表里的课程占用也不能被其他已成功的预约占用。判断区间重叠有一个经典公式现有预约开始时间小于新预约结束时间且现有预约结束时间大于新预约开始时间两者同时成立即冲突。用代码写就是 prev_start new_end and prev_end new_start。第二实验室容量限制。预约人数不能超过实验室最大容纳人数这个在请求参数里做校验前端也可以做一个提示但后端必须再查一次数据库里的容量字段。第三同一用户同一时间段不能重复预约。学生可以一次约多个时段但不能同一个时段约两个不同实验室否则会造成名额浪费。后端要用用户ID加时间的唯一性查询。第四课程优先于自由预约。如果某实验室在周一上午第1-2节有固定实验课那这个时间段学生自由预约必须被拒绝除非管理员设置可预约数量增量。课程排课数据可以单独存一张表在预约检查时先查课程表再查预约表。我踩过的最大坑是以为只要在Python代码里做判断就足够了但并发情况下两个请求同时通过检查然后同时写入数据库就会产生超卖。所以除了业务代码判断数据库层面必须加唯一索引或利用事务后面我在代码部分详细讲。3. 数据库设计与核心代码实现3.1 数据表结构设计数据库我选MySQL原因很简单校园环境里让老师装个Redis或者MongoDB不现实MySQL基本是标配而且这类系统的数据结构非常规整用关系型数据库最合适。如果只是开发学习或者本机演示SQLite也能跑起来只要把数据库连接串改一下就行。第一张表是用户表保存所有小程序用户和管理员。字段包括id、openid、昵称、头像、角色student/admin、学号/工号、手机号、信用分、创建时间。openid必须加唯一索引这是用户身份的唯一标识。第二张表是实验室表。字段包括id、名称、地址、容量、负责人、设备说明、是否开放预约、开放开始时间、开放结束时间、备注。重点在于“是否开放预约”这个状态位管理员可以临时关闭某个实验室进行设备维护。第三张表是课程排课表。字段包括id、实验室id、课程名称、教师、星期几、开始节次、结束节次、开始周次、结束周次、单双周标记、备注。这里我用“星期几开始节次结束节次”表述时段比直接存日期时间更符合高校作息排课表本来就是按周重复的。如果是企业培训场景也可以改成具体的开始时间、结束时间。第四张表是预约记录表。这是最核心的表字段包括id、用户id、实验室id、预约日期、开始节次、结束节次、参与人数、状态pending/approved/cancelled/completed/no_show、创建时间、更新时间。状态字段要有默认值如果是免审核模式就直接置为approved。预约记录表建议加两个索引一个是实验室id、预约日期、开始节次、结束节次用来快速查冲突另一个是用户id、预约日期用来查用户的预约历史和防止重复预约。并发情况下光靠索引还不够下面代码段解决。3.2 后端接口实现以FastAPI为例环境准备这里不多废话Python 3.9以上版本安装fastapi、uvicorn、sqlalchemy、pymysql就够了。Linux服务器上如果自带Python版本太低建议用源码编译安装新版或者直接装miniconda管理虚拟环境省去一堆依赖问题。登录接口先从简单的开始。小程序传入code后端拿到code调微信接口换openid再生成自己的token。from fastapi import FastAPI, Depends, HTTPException import httpx, secrets from datetime import datetime from sqlalchemy.orm import Session app FastAPI() # 假设已配置微信小程序 appid, secret WX_APPID your_appid WX_SECRET your_secret app.post(/api/login) async def login(data: dict): code data.get(code) async with httpx.AsyncClient() as client: resp await client.get( https://api.weixin.qq.com/sns/jscode2session, params{ appid: WX_APPID, secret: WX_SECRET, js_code: code, grant_type: authorization_code } ) wx_data resp.json() openid wx_data.get(openid) if not openid: raise HTTPException(status_code401, detail微信登录失败) # 查询或创建用户然后生成 token token secrets.token_hex(16) # 将 openid token 保存到数据库返回 token return {token: token, role: student}真实的项目里token要把user_id、角色、过期时间一起管理最好存到一张login_token表里每次请求通过token查到用户。token不要直接存openid的明文关系在小程序端不然有个人信息泄露的风险。预约接口是重点我们必须保证创建预约时检查冲突是原子操作。简单的方法是用数据库行锁但更稳妥的做法是对预约记录表加一个唯一约束让数据库帮我们挡掉并发重复插入。实际项目中我会在业务检查后再执行一次插入插入时如果撞了唯一索引捕获IntegrityError并返回“该时段已被预约”。下面是一个简化示例from sqlalchemy import and_, func from sqlalchemy.exc import IntegrityError app.post(/api/reserve) async def reserve(data: dict, db: Session Depends(get_db)): # 参数: user_id, lab_id, reserve_date, start_period, end_period, people_count lab db.query(Lab).filter(Lab.id data[lab_id]).first() if not lab: raise HTTPException(status_code404, detail实验室不存在) if not lab.is_open: raise HTTPException(status_code400, detail实验室未开放预约) # 检查课程排课冲突 conflict_course db.query(CourseSchedule).filter( CourseSchedule.lab_id data[lab_id], CourseSchedule.week_day data[week_day], CourseSchedule.start_period data[end_period], CourseSchedule.end_period data[start_period] ).first() if conflict_course: raise HTTPException(status_code400, detail该时段已被实验课占用) # 检查普通预约冲突 conflict_reserve db.query(ReserveRecord).filter( ReserveRecord.lab_id data[lab_id], ReserveRecord.reserve_date data[reserve_date], ReserveRecord.start_period data[end_period], ReserveRecord.end_period data[start_period], ReserveRecord.status.in_([approved, pending]) ).first() if conflict_reserve: raise HTTPException(status_code400, detail该时段已被预约) record ReserveRecord( user_iddata[user_id], lab_iddata[lab_id], reserve_datedata[reserve_date], start_perioddata[start_period], end_perioddata[end_period], people_countdata[people_count], statusapproved ) db.add(record) try: db.commit() except IntegrityError: db.rollback() raise HTTPException(status_code400, detail手慢了该时段刚被别人预约) return {id: record.id, status: record.status}代码里用了一个技巧判断课程冲突时没写死“星期几等于”而是把week_day也作为筛选条件。这里注意数据库查询中的区间重叠判断要和业务需求对应跨天、跨节次的场景要特别测试。如果是晚上20:00到21:00这种按绝对时间存的判断方式一样但要把日期和时间拼成一个datetime再比较。3.3 前端小程序关键页面实现小程序端代码分三块页面、请求封装、状态管理。页面用来展示和交互请求封装统一处理token和错误提示状态管理保存用户信息。请求封装非常重要。我见过很多新手在每个页面里直接写wx.request结果token过期了到处都要改错误提示五花八门。建议把请求封装成一个request.js文件类似下面这样const BASE_URL https://api.example.com; function request(path, method GET, data {}) { return new Promise((resolve, reject) { wx.request({ url: BASE_URL path, method: method, data: data, header: { Content-Type: application/json, Authorization: wx.getStorageSync(token) || }, success: (res) { if (res.statusCode 200) { resolve(res.data); } else if (res.statusCode 401) { wx.navigateTo({ url: /pages/login/login }); } else { wx.showToast({ title: res.data.detail || 请求失败, icon: none }); reject(res); } }, fail: (err) { wx.showToast({ title: 网络异常, icon: none }); reject(err); } }); }); } module.exports { request };预约页面的wxml结构很常规核心就是日期选择器、时段选择、实验室容量提示、提交按钮。需要注意的细节有两个第一日期选择器的范围要设置min属性为今天不能让学生约过去的日期第二时段选择建议用按钮组而不是picker因为一旦选择多个连续时段picker并不方便。用户选完时段后前端先算一下本次预约总共多少节再连同实验室容量一起展示减少后端失败的挫败感。页面JS里调接口也要注意防重复点击。把提交按钮加一个loading状态第一次点击后禁用按钮直到请求完成否则用户连点三下会产生三个重复请求虽然后端有冲突检查但会给用户“约了好几次”的错觉体验极差。4. 常见问题与排查技巧实录4.1 并发情况下预约超卖怎么处理这是实验室预约系统最容易出的线上事故。两个学生同时提交同一个实验室同一个时段的预约后端都先查询了一遍没有冲突然后同时执行插入数据库结果两个请求都成功了实验室被超卖。我在开发阶段就遇到过一次当时还以为是前端按钮没做防重复后来一查是并发问题。解决这个问题的核心思路是查询和写入之间必须有隔离性不能靠业务代码的先后顺序。最实践的做法就是在预约记录表上建立针对实验室、日期、开始节次、结束节次的联合约束但因为时间区间不是枚举唯一索引比较难设计。退而求其次可以用数据库的行级锁。在SQLAlchemy里通过with_for_update()锁定实验室记录行保证同一时间只有一个事务能检查并插入预约记录。如果项目并发量确实大比如全校几千人同时抢晚上7点到9点的实验室那就得上消息队列或者Redis分布式锁。但坦白说这种校内预约系统做到这个程度有点过度设计了。我建议先把数据库事务和行锁做好已经能覆盖绝大多数场景。并发问题的排查也简单高并发压测时观察日志里有没有同时插入成功的情况。4.2 微信登录时session_key和token搞混微信小程序登录流程中主流的说法是通过wx.login拿到code后端用code换openid和session_key。很多新手会把session_key存在自己的服务器然后拿来当身份凭证这是不对的。session_key是微信会话密钥用来解密用户数据不应该服务于业务系统的会话管理。正确做法是后端用code换到openid之后生成一个自己的token比如用secrets.token_hex(16)生成随机字符串关联user_id设置7天过期时间存数据库。小程序端以后请求都带这个token后端通过查询token表验明正身。不要把openid在接口返回里暴露给小程序端虽然openid本身不能直接干坏事但它属于用户敏感信息接口尽量只返回用户id和昵称等业务必要字段。在实际开发中微信官方对js_code2session接口的调用频率有限制如果登录频繁可能会有临时接口报错。这种情况可以做一个简单的本机token缓存小程序启动时如果本地已有未过期的token就直接使用不再重复wx.login只有后端返回401才重新走登录流程。4.3 时间重叠判断的边界问题时间重叠判断这个代码写出来只有一行但边界问题特别多。第一次写这个系统的时候我用的是“开始时间小于新结束时间且结束时间大于新开始时间”然后测试时发现课程表里的“9月30日”和预约表的“2025-09-30”格式不一致直接查不中。后来约定所有日期都存成date类型节次都用整数才算是彻底解决。另一个边界问题是跨天实验。比如晚上实验室通宵开放学生预约了22:00到第二天06:00如果按“日期”作为查询条件这条预约在第二天的记录里就查不到导致第二天白天的时段被重复预约。解决办法是预约表里存“开始时间”和“结束时间”完整datetime查询时用两个时间字段做区间重叠判断而不是用日期加节次的拼接方式。如果坚持用节次模型那就要允许结束节次大于当天总节次并且日期顺延一天这会引入很多麻烦。还有单双周排课的问题。很多高校实验课是单周或双周轮换如果课程排课表不保存“单双周标记”会导致隔周一次的实验课在另一周被错误占用。我的建议是排课表里加一个week_type字段取值为all/odd/even检查时间冲突时把当前预约日期对应的教学周和week_type比对一下只有匹配才视为冲突。4.4 小程序真机预览和HTTPS域名配置这个问题几乎每隔几天就会有人来问我。小程序开发工具里接口调试得好好的一上真机就白屏、请求全部失败原因基本都是域名没配好。小程序正式环境要求所有请求必须是HTTPS而且域名要从小程序后台的“request合法域名”里提前配置。如果你只是本地开发可以在开发工具里勾选“不校验合法域名”但真机上如果还跑localhost那是连不上的因为手机访问不到你电脑上的服务需要后端部署到局域网可访问的IP或者云服务器。我个人的经验是开发阶段直接用内网穿透工具或者部署到一台云服务器上测试不要等到上线再配域名。后端接口路径和前端BASE_URL要一致避免出现开发环境是http、线上环境是https导致请求被拦截的情况。部署后端时用Nginx把Python服务反代到443端口配好SSL证书小程序那边才能顺畅请求。再提醒一个容易忽略的点小程序的request并发限制是10个。如果页面初始化时同时发送实验室列表、课程表、用户信息好几个请求数量不多没问题但如果后续功能膨胀了注意合并接口或者做请求队列不然偶尔会出现高并发时的请求失败。4.5 Python环境与依赖安装的坑Python环境的坑大多是版本不一致造成的。有的服务器自带的Python是3.6但FastAPI要求3.7以上SQLAlchemy新版也可能不兼容Python 3.6所以装好依赖后第一步就是检查版本。建议用虚拟环境管理项目依赖简单说就是在项目根目录执行python3 -m venv venv然后source venv/bin/activate激活再pip install。这样不会污染系统Python换服务器时也能靠requirements.txt一键恢复环境。很多新手遇到的问题是这样本地运行好好的上传服务器后报ModuleNotFoundError。原因基本是忘记执行pip install -r requirements.txt或者requirements.txt没有把依赖写全。生成requirements.txt最稳妥的方法是pip freeze但要注意它会把无关的全局依赖也带进去所以更推荐手动维护一个干净的依赖清单只写fastapi、uvicorn、sqlalchemy、pymysql、httpx这几个核心包带上版本号。Linux系统上安装Python如果使用源码编译千万别忘了先安装libssl-dev和libffi-dev否则Python编译出来缺少ssl模块后面调用微信HTTPS接口会直接报错。另外pip下载慢的问题可以临时指定清华镜像源pip install -i https://pypi.tuna.tsinghua.edu.cn/simple 包名。这些都是老生常谈但每届学生都会踩一遍。5. 一些额外的实践经验做这个系统最值钱的不是代码本身而是把一个真实业务抽象成一套可以被验证的逻辑。我开发的时候把大部分时间花在编写冲突规则和测试边界条件上反而页面布局这些只花了两天。如果你也在做类似项目我强烈建议先画出完整的业务流程图把每种角色、每个状态、每个异常分支都标出来再开始写代码。给即将上线的小项目三个具体建议第一先从一间实验室试运行管理员手工检查一天确认预约准确率再放开所有实验室第二预约成功之后一定要有消息通知最简单的是在小程序里做个“我的预约”列表学生自己来看结果复杂点的可以用订阅消息推送第三建立爽约惩罚机制否则过两个月就会有人恶意占坑。信用分字段我已经放在用户表里了后续就是每次爽约扣分、定时解禁的简单逻辑。如果后续想扩展可以考虑把实验报告提交、设备借用、耗材领用都挂在预约单下一个实验室管理系统就慢慢成形了。但别急着一次做完先让预约排课跑稳定这个核心稳定其他功能都是加分项。
网站建设高端定制企业官网