资讯动态

大学自习室预约小程序源码:从占座乱象到扫码即坐的落地路径

发布时间:2026/10/27 14:50:47 来源:尧图企业网站定制
简介这是一套基于微信小程序平台开发的大学自习室预约系统源码面向具备小程序开发基础、希望实践前后端一体化项目的学生与开发者可用于课程设计、毕业设计或校园服务类应用参考。资源包共475个文件以186个js逻辑脚本、105个wxss样式表、82个wxml结构模板和70个json配置文件为主体另含少量png、gif等图片素材及一份安装使用手册文档压缩包约2.79MB目录结构清晰便于按模块阅读与二次开发。功能上覆盖预约管理、自定义填写项、二维码签到核销、预约名单Excel导出与打印等核心场景并涉及腾讯云数据库存储、微信API调用、数据加密与用户体验优化等实现细节。目前已有867人学习下载适合想快速理解小程序预约类项目完整实现思路、对照源码查漏补缺的开发者参考。1. 大学自习室预约小程序从占座乱象到扫码即坐的落地路径图书馆早上六点半排队抢座、桌上贴满“此座已占”的纸条、管理员拿着钥匙挨个教室开门——这是很多高校自习室管理的日常。大学自习室预约小程序源码要解决的就是把“人肉占座”变成“线上选座、扫码签到、超时释放”的闭环。它基于微信小程序平台开发学生不用装 App微信授权登录后就能看空闲座位、选时段、生成预约码管理端能配置开放时段、查看上座率、处理违约。这套源码适合两类人一是高校信息化团队想快速搭一套能跑的系统二是学生开发者拿它当微信小程序全栈练手项目。下面按“需求拆解→数据建模→核心接口→避坑→进阶”的顺序把能复现的细节讲透。2. 需求拆解与数据建模自习室预约到底要存哪些表2.1 先分清三类角色和四条主流程做预约系统最容易翻车的地方不是代码写不出来而是一开始没把角色和状态理清楚。大学自习室场景里至少有三类角色学生预约者、管理员教室/座位维护者、系统定时任务。四条主流程分别是学生授权登录并绑定学号、按教室和时段查询空闲座位、提交预约并生成签到凭证、签到/签退/超时自动释放。我一般会先把状态机画在纸上再建表。预约单的状态至少有待使用、使用中、已完成、已取消、已违约。很多新手只设“已预约/已取消”两个状态结果超时释放和违约统计全乱套。状态字段建议用 tinyint 存枚举值别用中文否则后面做统计查询时排序和索引都会难受。另一个容易忽略的是“时段”的粒度。常见做法是把一天切成若干固定时段比如 8:00-10:00、10:00-12:00每个时段对应一条可预约记录也有按小时动态生成的。固定时段实现简单、并发冲突少适合先跑通动态时段灵活但需要处理跨时段预约的边界。源码里如果用的是固定时段你接手后想改成动态改动量主要在时段生成逻辑和唯一索引上。2.2 核心表结构与字段说明下面这套表结构是我在多个校园预约类项目里反复用过的精简版字段不多但够跑通主流程。注意seat表和reservation表之间用seat_id关联time_slot单独抽出来是为了让管理员能批量配置开放时段而不是把时间写死在代码里。-- 用户表微信 openid 与学号绑定 CREATE TABLE user ( id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, openid VARCHAR(64) NOT NULL COMMENT 微信登录唯一标识, student_no VARCHAR(20) DEFAULT NULL COMMENT 学号绑定后写入, nickname VARCHAR(50) DEFAULT NULL, role TINYINT NOT NULL DEFAULT 0 COMMENT 0学生 1管理员, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_openid (openid), UNIQUE KEY uk_student_no (student_no) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 自习室/教室表 CREATE TABLE room ( id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, name VARCHAR(50) NOT NULL COMMENT 如图书馆三楼东, open_time TIME NOT NULL DEFAULT 08:00:00, close_time TIME NOT NULL DEFAULT 22:00:00, status TINYINT NOT NULL DEFAULT 1 COMMENT 1开放 0关闭 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 座位表 CREATE TABLE seat ( id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, room_id INT UNSIGNED NOT NULL, seat_no VARCHAR(10) NOT NULL COMMENT 如 A-01, has_power TINYINT NOT NULL DEFAULT 0 COMMENT 是否带电源, status TINYINT NOT NULL DEFAULT 1 COMMENT 1可用 0维修, KEY idx_room (room_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 时段表 CREATE TABLE time_slot ( id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, room_id INT UNSIGNED NOT NULL, start_time TIME NOT NULL, end_time TIME NOT NULL, KEY idx_room_slot (room_id, start_time) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 预约单表 CREATE TABLE reservation ( id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, user_id INT UNSIGNED NOT NULL, seat_id INT UNSIGNED NOT NULL, slot_id INT UNSIGNED NOT NULL, reserve_date DATE NOT NULL COMMENT 预约日期, status TINYINT NOT NULL DEFAULT 0 COMMENT 0待使用 1使用中 2已完成 3已取消 4已违约, checkin_code VARCHAR(32) DEFAULT NULL COMMENT 签到码, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_seat_slot_date (seat_id, slot_id, reserve_date, status), KEY idx_user_date (user_id, reserve_date) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;建表时有三个参数值得单独说。第一reservation上的唯一索引uk_seat_slot_date把status也放进去了这是为了让“已取消”的记录不阻塞同一座位同一时段被重新预约——如果只锁seat_id slot_id reserve_date用户取消后座位就永远空不出来。第二checkin_code用随机字符串而不是自增 ID避免学生猜到别人的签到码。第三所有时间字段用DATETIME而不是TIMESTAMP因为TIMESTAMP有 2038 年上限校园系统虽然不至于跑那么久但统一用DATETIME省心。提示如果学校要求“同一学生同一天最多约 2 个时段”不要写死在代码里加一张config表存max_reserve_per_day管理员改配置不用重新发版。3. 微信小程序端核心实现登录、选座与签到码3.1 微信登录获取手机号与学号绑定微信小程序的登录链路是前端调wx.login拿code后端用code换openid和session_key再生成自定义登录态返回给前端。很多源码在这里只做了openid登录学生换手机就找不到自己的预约记录所以必须补一步学号绑定。常见做法是首次登录后弹窗让学生填学号后端校验学号格式比如 10 位数字后写入user表。// 小程序端登录并绑定学号 wx.login({ success: (res) { if (!res.code) return; wx.request({ url: https://your-domain.com/api/login, method: POST, data: { code: res.code }, success: (resp) { const { token, needBind } resp.data; wx.setStorageSync(token, token); if (needBind) { // 跳转到学号绑定页 wx.navigateTo({ url: /pages/bind/bind }); } } }); } });这段代码的关键在needBind字段后端判断该openid是否已有student_no没有就返回true前端跳绑定页。绑定接口收到学号后先查user表里该学号是否已被其他openid占用如果占用就返回“该学号已被绑定请联系管理员”避免一人多号刷预约。参数上code只能用一次5 分钟过期token建议用 JWT有效期设 7 天过期后前端静默重新wx.login。如果学校要求获取手机号小程序端用button open-typegetPhoneNumber触发后端拿code调微信接口换手机号。注意这个接口需要企业主体小程序个人主体拿不到手机号源码里如果写了这个功能但你是个人号会一直报错。我一般建议先跑通学号绑定手机号作为可选补充。3.2 选座页面的时段查询与并发防重选座页的核心逻辑是用户选日期和时段前端请求该时段下所有座位的占用情况渲染成网格。这里最容易出的问题是并发两个学生同时看到同一个空座位同时点预约如果不做防重会生成两条预约单。// 后端预约接口的防重逻辑Node.js 示例 async function reserveSeat(userId, seatId, slotId, date) { const conn await pool.getConnection(); try { await conn.beginTransaction(); // 用 SELECT ... FOR UPDATE 锁住该座位该时段 const [rows] await conn.query( SELECT id FROM reservation WHERE seat_id? AND slot_id? AND reserve_date? AND status IN (0,1) FOR UPDATE, [seatId, slotId, date] ); if (rows.length 0) { await conn.rollback(); return { code: 409, msg: 该座位已被预约 }; } const checkinCode Math.random().toString(36).slice(2, 10); await conn.query( INSERT INTO reservation (user_id, seat_id, slot_id, reserve_date, checkin_code) VALUES (?,?,?,?,?), [userId, seatId, slotId, date, checkinCode] ); await conn.commit(); return { code: 0, checkinCode }; } catch (e) { await conn.rollback(); throw e; } finally { conn.release(); } }FOR UPDATE是行级锁配合事务能保证同一座位同一时段只有一个请求能插入成功。参数上status IN (0,1)表示只锁“待使用”和“使用中”的单子已取消和已完成的单子不参与防重。checkinCode用Math.random().toString(36)生成 8 位随机串够用且不依赖额外库。如果并发量很大FOR UPDATE可能造成锁等待这时可以在应用层加 Redis 分布式锁key 用seat:slot:date过期时间设 10 秒。注意选座页渲染时前端不要只请求“空闲座位”而要请求“所有座位 状态”否则座位编号会跳变学生体验很差。3.3 签到码的生成与核销签到环节有两种常见方案一是学生到座位后扫座位上的二维码二是管理员在门口扫学生的签到码。源码里如果用的是第一种需要在seat表加qr_code字段存每个座位的唯一码第二种则用reservation.checkin_code。我一般推荐第二种因为座位二维码容易被拍照传播学生躺宿舍也能“云签到”。核销逻辑是管理员端调/api/checkin传checkin_code后端查reservation表把status从 0 改成 1并记录checkin_at。如果超过时段开始 15 分钟未签到定时任务把单子改成 4已违约座位释放。定时任务用 Linux crontab 或 Node.js 的node-cron都行频率建议 5 分钟一次别太密。# crontab每 5 分钟检查超时未签到 */5 * * * * curl -s https://your-domain.com/api/task/release-timeout这个接口内部逻辑是查reservation里status0且reserve_date今天且当前时间已超过slot.start_time 15分钟的记录批量改成status4。参数15分钟建议做成配置项不同学校宽限时间不一样。4. 避坑与排查自习室预约小程序最容易翻车的 5 个点4.1 现象学生取消预约后座位还是显示“已占用”原因reservation表的唯一索引把status也包含进去了但查询空闲座位时只查了status0没把status3已取消排除干净或者前端缓存没刷新。解决空闲座位查询条件写成status NOT IN (0,1)的反向逻辑即“不存在 status 为 0 或 1 的记录才算空闲”同时取消接口返回后前端要重新拉一次座位列表别只改本地状态。4.2 现象微信登录偶尔失败报code been used原因wx.login的code只能用一次如果前端在onLoad和onShow里都调了登录或者用户快速点击按钮触发两次请求第二个请求就会失败。解决在wx.login外层加一个isLogging标志位请求中直接 return后端对同一个code的重复请求做幂等第二次返回第一次的结果。4.3 现象管理员配置了 8:00-22:00但学生只能约到 20:00原因时段生成逻辑用了for (let h open; h close; h)当close是 22 时最后一个时段是 21:00-22:00但如果时段长度是 2 小时h 2会在 20:00 后跳到 22:00导致 20:00-22:00 这个时段没生成。解决时段生成用“开始时间 时长 关闭时间”作为循环条件而不是简单比较小时数。4.4 现象签到码核销后学生端还显示“待使用”原因核销接口只改了数据库没有推送或前端没轮询。解决学生端在“我的预约”页加onShow重新拉取或者用 WebSocket 推送状态变更。校园项目并发不高onShow重拉最简单可靠。4.5 现象定时任务释放超时座位把已签到的单子也释放了原因定时任务的查询条件漏了status0把status1使用中的单子也当成超时处理了。解决SQL 里明确写WHERE status 0 AND ...并且在更新前先SELECT确认别直接UPDATE。我一般会在定时任务里加一行日志记录本次释放了哪些单子出问题能回溯。5. 进阶技巧用上座率数据反哺自习室管理跑通预约只是第一步真正让这套源码有价值的是数据。我习惯在reservation表基础上加一张daily_stats汇总表每天凌晨跑一次记录每个教室每个时段的上座率、违约率、取消率。管理员看到“周三下午 14:00-16:00 违约率 30%”就能针对性调整开放座位数或加强提醒。-- 每日上座率汇总 INSERT INTO daily_stats (room_id, stat_date, slot_id, total_seats, reserved, checked_in, no_show) SELECT s.room_id, r.reserve_date, r.slot_id, COUNT(DISTINCT s.id) AS total_seats, COUNT(DISTINCT r.id) AS reserved, SUM(CASE WHEN r.status IN (1,2) THEN 1 ELSE 0 END) AS checked_in, SUM(CASE WHEN r.status 4 THEN 1 ELSE 0 END) AS no_show FROM seat s JOIN reservation r ON r.seat_id s.id WHERE r.reserve_date CURDATE() - INTERVAL 1 DAY GROUP BY s.room_id, r.reserve_date, r.slot_id;这个查询里COUNT(DISTINCT s.id)和COUNT(DISTINCT r.id)要分开算因为一个座位可能对应多条预约记录不同时段直接COUNT(*)会重复计数。checked_in把status1和status2都算上因为使用中和已完成都代表学生实际到了。验证方法很简单连续跑一周看no_show最高的三个时段如果集中在早上 8:00说明学生约了起不来可以考虑把早上时段改成“提前 10 分钟签到”或减少早间座位数。另一个技巧是给违约 3 次以上的学生加一个“冷却期”比如 7 天内不能预约这个逻辑写在预约接口的前置校验里用SELECT COUNT(*) FROM reservation WHERE user_id? AND status4 AND created_at DATE_SUB(NOW(), INTERVAL 7 DAY)判断。我自己的习惯是每接一个校园预约类项目先花半天把状态机和表结构定死再动手写接口。这套源码你拿到手后别急着改 UI先把reservation表的唯一索引和定时任务逻辑读一遍这两个地方对了后面基本不会出大问题。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑