微信小程序+Spring Boot的图书馆座位预约管理系统设计与实践
我把“图书馆座位预约管理系统”从选题背景、技术选型、核心业务实现到数据库设计、联调排坑、答辩准备完整拆了一遍。这里没有照抄网上的老源码而是按一套能真正跑起来、能讲得清逻辑的思路来讲。1. 项目整体设计与思路拆解1.1 为什么是“微信小程序Spring Boot”而不是别的组合先聊选型。毕设项目最忌讳一上来就堆技术把Spring Cloud、Redis、RabbitMQ、分布式事务全塞进来给自己找麻烦。这个题目用微信小程序做客户端、Spring Boot做后端、MySQL存数据是最稳妥也最合理的组合。为什么客户端选微信小程序因为图书馆座位预约的使用场景是“站在图书馆门口掏出手机扫一下就预约”天然适合轻量应用。用户不需要去应用商店下载App微信里搜索或者扫码就能用这对校园场景尤其友好。从毕设角度看微信小程序还自带开发者工具、模拟器、真机调试环境搭建比原生Android开发简单得多展示效果也直观。后端选Spring Boot核心原因是生态成熟、资料多。MyBatis Plus做持久层Spring MVC写接口Spring Task做定时任务一个框架全搞定。如果你熟悉别的语言用Node.js、Python Flask也能做但Spring Boot在毕设指导老师那里的接受度最高出了问题也最容易搜到解决方案。1.2 两个角色、一条预约主链路先看用户角色学生端和管理员端。很多同学拿到题目会想当然地做成两个完全分离的系统其实在同一个后端服务里用角色字段区分就行。小程序端面向学生管理员可以通过专属页面或者一个简单的后台Web页面操作。核心业务链路很简单一句话概括“学生查看座位→选择时间段→提交预约→按时签到→使用结束离座”。围绕这条主线还可以拆出取消预约、超时自动取消、违约扣信用分、扫码签到、座位报修等子流程。从高内聚的角度我把业务分成三层座位管理座位信息、区域楼层、状态变更、预约交易预约下单、取消、签到、履约状态机、用户与信用登录、个人信息、信用分。这样划分的好处是写论文时每一章有明确主题写代码时包结构清晰后期排查问题也快。1.3 座位状态机把业务规则变成代码这是整个系统最核心的设计点。座位不是只有“空闲”和“被占”两种状态而是在不同业务阶段有不同状态。我定义的状态大概是这样座位状态含义可触发动作AVAILABLE空闲可被预约提交预约APPOINTED已被预约等待签到签到、取消、超时释放OCCUPIED用户已签到正在使用结束使用、临时离开可选DISABLED故障/维护不可预约管理员启用预约记录的状态又是另一套PENDING待签到、CANCELLED已取消、EXPIRED超时未签到、COMPLETED正常结束、VIOLATED违约。两套状态不要混在一起否则后期改需求会很痛苦。时间规则也要提前定死。我常用的配置是最晚可在预约开始前30分钟取消预约开始后30分钟未签到系统自动取消该预约并扣信用分单次预约最长4小时同一时间段内一个用户只能有一条有效预约。这些规则并不是一次就能定好的最初版本可能只做了“预约后不可取消”后来发现用户体验太差才加了取消和自动释放机制。这里为什么要强调“状态机”而不是直接在代码里if-else因为座位状态和预约状态是两套独立的状态交叉组合非常多。如果不提前定义清楚后面加一个“临时离开”的字段就会像打补丁一样到处都是问题。用枚举定义好状态把状态流转集中在Service层处理界面只读状态渲染代码会清爽很多。2. 核心功能模块与实现要点2.1 微信登录和用户体系别再用wx.getUserInfo了以前很多老教程教学生用wx.getUserInfo直接拿头像昵称这个接口在现在的微信版本已经拿不到真实数据了新规范要求用户主动点击头像和昵称输入框才能授权。这里要特别注意很多学生拿到的老源码还在用旧接口跑起来头像全是灰色默认头像。正确做法分两步登录态小程序端调用wx.login拿到临时code传给后端后端用code调微信的jscode2session接口换回openid和session_key。openid作为用户唯一标识第一次登录就自动注册返回一个自定义token给小程序后续所有请求都带这个token。个人资料前端用button的open-typechooseAvatar引导用户选择头像用input typenickname输入昵称提交到后端更新资料。不要在登录时强制要昵称头像很多用户不喜欢一上来就填资料先把登录态建立起来资料后面可以慢慢补。token我用的是自定义的UUID字符串存在Redis里并设置过期时间没有用JWT。原因很简单毕设场景不追求无状态反而Redis方案在管理端想强制下线某个用户时特别方便直接删key就行。接口设计上登录接口叫POST /api/auth/login用户信息接口是GET /api/user/profile和PUT /api/user/profile。2.2 可视化选座前端座位图和后端冲突检测可视化选座是整个小程序端最亮眼的功能也是演示时最出效果的部分。页面布局参考实际图书馆的平面图左侧区域筛选中间是一排排座位格子不同状态用不同颜色区分空闲绿色、已预约橙色、使用中红色、故障灰色。前端渲染并不复杂后端提供一个按楼层/区域返回的座位列表前端用wx:for循环渲染格子view就行。真正有难度的是座位的坐标布局如果每个座位手写样式会很痛苦我建议在座位表里增加row_no、col_no字段前端根据行列号动态计算格子位置这样后续新增一排座位也不需要改前端代码。后端冲突检测是整个系统的核心逻辑不能只查“这个座位当前是否有预约”必须校验“同一座位、同一时间段”是否有重叠预约。时间段重叠的判断条件是一个时间段[a, b]与已有的[c, d]重叠条件是 a d 且 c b。举个例子用户想预约今天14:00到16:00的座位。库里已有一条14:30到15:30的记录。两条记录满足 a d14:00 15:30且 c b14:30 16:00所以冲突不能预约。这个判断在SQL里的写法是SELECT COUNT(*) FROM appointment WHERE seat_id #{seatId} AND appoint_date #{date} AND status IN (PENDING,COMPLETED,OCCUPIED) AND start_time #{endTime} AND end_time #{startTime}按这个SQL建一个联合索引seat_id, appoint_date, start_time查起来基本上就是毫秒级。注意状态过滤一定不能漏否则用户已经取消的记录也会参与冲突判断。2.3 预约、签到、取消、违约规则优先还是代码优先这部分属于业务规则密集区我的经验是先把规则写成文档再写代码。很多同学一拿到需求就开写接口结果边界情况不断返工。毕设答辩时老师最喜欢问的就是这些规则的边界情况比如“用户预约了又不去怎么办”“用户同时占两个座位怎么办”。预约接口逻辑大致是校验用户是否登录校验信用分是否低于预约门槛校验用户当前时间是否有其他未完成预约防止一人占多个座位校验座位状态是否AVAILABLE做时间段冲突检测全部通过后把座位状态改为APPOINTED写入预约记录返回预约单号。这个流程必须放在一个事务里第2步到第5步任何一步失败都要回滚不能出现“座位改了状态但预约记录没写进去”的情况。Spring里直接用Transactional注解包住即可。代码逻辑用伪代码示意一下Transactional(rollbackFor Exception.class) public Appointment createAppointment(CreateAppointmentRequest req) { // 1. 校验用户登录态和信用分 User user userMapper.selectById(req.getUserId()); if (user.getCredit() 60) throw new BizException(信用分过低无法预约); // 2. 校验当前时段是否有未完成预约 int count appointmentMapper.countActiveByUser(user.getId(), req.getDate()); if (count 0) throw new BizException(当前时段已有预约); // 3. 校验座位状态 Seat seat seatMapper.selectByIdForUpdate(req.getSeatId()); if (!AVAILABLE.equals(seat.getStatus())) throw new BizException(座位已被预约); // 4. 冲突检测 int conflict appointmentMapper.countConflict(req.getSeatId(), req.getDate(), req.getStartTime(), req.getEndTime()); if (conflict 0) throw new BizException(该时段座位已被占用); // 5. 更新座位状态 插入预约记录 seatMapper.updateStatus(req.getSeatId(), APPOINTED); Appointment appointment new Appointment(); // ... 填充字段 appointmentMapper.insert(appointment); return appointment; }签到逻辑相对简单可以通过“一键签到”触发也可以做成“扫码签到”。扫码签到时生成的二维码最好带一个短时有效的token后端校验token和当前用户、当前时间防止截图伪造。取消预约必须设时间限制。我采用的是预约开始前30分钟可免费取消之后取消要扣信用分。很多同学忽略这个问题导致用户随便取消座位资源又被浪费了。超时未签到的座位释放用Spring的Scheduled定时任务每分钟扫描一次PENDING状态且开始时间超过30分钟的预约记录批量更新为EXPIRED同时把座位状态改回AVAILABLE再写一条信用分扣减记录。三个操作也在同一个事务里。2.4 管理端与数据统计不是简单增删改查管理端一般不用做得特别复杂但要让老师看到你有“管理思想”。核心页面至少要有座位管理增删改查、批量导入座位、预约记录查询按用户、日期、状态筛选、信用分管理、数据统计。很多学生做统计时只会写最简单的“总预约数”我建议至少做到“今日入座率 今日签到成功的预约数 / 今日座位总数”、“近7天使用趋势图”、“热门区域排行Top5”、“违约用户排行”。这些统计报表用SQL的GROUP BY配合日期函数就能写不用引入复杂的大数据组件但写进论文里效果很好。管理端的技术选型如果前端基础好用Vue3 Element Plus做独立页面如果时间紧后端直接用Thymeleaf模板引擎把表格渲染在服务端页面里。我推荐后一种少一个前端项目部署和答辩演示都省心。不过用Thymeleaf的前提是你对它的基本语法熟悉不然反而会增加调试时间。3. 数据库设计与接口开发实操3.1 四张核心表和一个关键索引数据库设计决定了整个项目的天花板后面改表结构是最痛苦的事所以一开始就要把表设计稳。我用四张核心表这里把建表SQL贴出来并逐个说明设计意图。用户表 t_userCREATE TABLE t_user ( id BIGINT PRIMARY KEY AUTO_INCREMENT, wx_openid VARCHAR(64) NOT NULL UNIQUE, nickname VARCHAR(64), avatar VARCHAR(255), role TINYINT DEFAULT 0 COMMENT 0学生 1管理员, credit INT DEFAULT 100 COMMENT 信用分, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) COMMENT 用户表;wx_openid必须加唯一索引因为整个登录体系都建立在它上面。信用分默认100角色默认0这两个字段都设了默认值后端插入时不用特意填减少出错概率。座位表 t_seatCREATE TABLE t_seat ( id BIGINT PRIMARY KEY AUTO_INCREMENT, seat_no VARCHAR(32) NOT NULL COMMENT 座位编号, floor INT NOT NULL COMMENT 楼层, area VARCHAR(32) COMMENT 区域, row_no INT COMMENT 行号, col_no INT COMMENT 列号, seat_type TINYINT DEFAULT 0 COMMENT 0普通 1靠窗 2电源, status VARCHAR(20) DEFAULT AVAILABLE, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) COMMENT 座位表;row_no和col_no不要省它们直接决定前端座位图能不能自动布局。seat_no是给用户看的编号比如“A301”floor和area是做筛选条件用的status状态字段必须和代码里的枚举保持一致。预约表 t_appointmentCREATE TABLE t_appointment ( id BIGINT PRIMARY KEY AUTO_INCREMENT, appointment_no VARCHAR(64) NOT NULL COMMENT 预约单号, user_id BIGINT NOT NULL, seat_id BIGINT NOT NULL, appoint_date DATE NOT NULL, start_time TIME NOT NULL, end_time TIME NOT NULL, status VARCHAR(20) DEFAULT PENDING, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, finish_time DATETIME COMMENT 实际结束时间, KEY idx_seat_time (seat_id, appoint_date, start_time), KEY idx_user_status (user_id, status) ) COMMENT 预约表;这张表是业务核心查询最频繁。idx_seat_time联合索引就是给冲突检测SQL用的idx_user_status索引是给“我的预约”列表用的。两个索引覆盖了系统里最常用的两种查询场景。信用记录表 t_credit_logCREATE TABLE t_credit_log ( id BIGINT PRIMARY KEY AUTO_INCREMENT, user_id BIGINT NOT NULL, change_value INT NOT NULL COMMENT 正数加分 负数扣分, reason VARCHAR(255) COMMENT 扣分原因, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) COMMENT 信用流水表;信用分的变化都要记流水答辩时老师会问“扣分凭什么有据可查”这张表就是答案。每一笔扣分都对应一条流水前端用户中心里也可以展示“信用变化记录”功能又丰富了一层。3.2 接口设计RESTful风格与统一返回结构后端接口我全部走RESTful风格下面的接口清单可以直接用方法路径说明POST/api/auth/login微信登录入参code返回tokenGET/api/user/profile获取当前用户信息PUT/api/user/profile更新头像昵称GET/api/seat/list?floor3areaA查询座位列表GET/api/seat/status?seatIdxxdatexx查某座位某天的占用时段POST/api/appointment/create创建预约POST/api/appointment/cancel取消预约POST/api/appointment/signIn签到GET/api/appointment/my?statusxx我的预约记录GET/api/admin/seat/page管理端分页查询座位PUT/api/admin/seat/status管理端修改座位状态GET/api/admin/stats/overview统计看板数据所有接口返回统一结构前端可以统一拦截处理{ code: 200, message: success, data: {} }接口返回的日期时间格式要统一后端全部转成“yyyy-MM-dd HH:mm:ss”的字符串返回避免前端各种时区转换踩坑。配置Spring Boot的JSON序列化时区也要在application.yml里显式声明spring: jackson: time-zone: GMT8 date-format: yyyy-MM-dd HH:mm:ss3.3 小程序端请求封装与页面适配细节小程序端把wx.request封装一层很有必要否则每个页面都写一遍header、error处理会特别烦。封装时至少要做三件事自动携带token、统一处理code401时跳转登录页、网络异常时弹出统一提示。下面的封装代码是我常用的基础版本const request (url, method, data) { const token wx.getStorageSync(token) return new Promise((resolve, reject) { wx.request({ url: ${BASE_URL}${url}, method, data, header: { Authorization: token ? Bearer ${token} : }, success(res) { if (res.data.code 200) { resolve(res.data.data) } else if (res.data.code 401) { wx.navigateTo({ url: /pages/login/login }) reject(res.data) } else { wx.showToast({ title: res.data.message, icon: none }) reject(res.data) } }, fail(err) { wx.showToast({ title: 网络异常请重试, icon: none }) reject(err) } }) }) }页面适配有几个经验点。自定义导航栏时状态栏高度要取wx.getWindowInfo().statusBarHeight再叠加导航栏自身高度不然右上角的胶囊按钮会顶出去。搜索框下方有列表时手机软键盘经常遮挡查询结果解决方法是给input设置adjust-position属性让键盘自动顶起页面内容。座位页面的下拉刷新和触底加载用enablePullDownRefresh和onReachBottom即可onReachBottom是页面滚动到接近底部时触发做分页加载很合适。4. 常见问题与毕设答辩避坑指南4.1 联调阶段最常踩的5个坑第一个坑是HTTPS和合法域名问题。小程序真机预览要求所有请求域名必须是HTTPS但开发模式下在微信开发者工具右上角“详情→本地设置”里勾选“不校验合法域名”就能调通。很多学生不知道这个设置卡了几天。要注意真机调试也可以开启这个选项但正式发布必须有正式域名和HTTPS证书。第二个坑是日期时区。前后端一传时间发现数据库里多了8小时或者少了1天多半是JSON序列化时区问题。Spring Boot里统一在application.yml配置jackson时区前端传参统一用字符串不要用时间戳能省很多麻烦。第三个坑是并发重复预约。用户快速点击两次“提交预约”或者多人同时抢同一个座位就会出现一条数据被两次插入。解决思路在4.2里细说。第四个坑是微信开发者工具提示“不在以下合法域名列表”。本质上和第一点一样开发时勾选不校验就行。这里提醒一句网上流传的各种抓包工具或者破解方法不要碰老老实实用开发者工具自带的Network面板调试信息完全够用。第五个坑是扫码签到二维码过期。如果二维码里直接存预约单号不设过期时间用户把二维码截图发给别人就完蛋了。建议二维码内容只用一串随机token5分钟后失效后端每次校验有效期能有效防截图刷签到。4.2 并发抢座问题的3种处理方案并发抢座是毕设里最值得展开的问题也是老师最喜欢问的业务难点。三个方案由浅入深方案一数据库层面加唯一索引。比如给预约表的user_id、appoint_date、start_time加唯一索引再配合状态过滤可以从数据库硬约束层面挡住重复预约。缺点是用户体验上提示不够友好冲突时只能看到数据库异常。方案二乐观锁。在座位表加version字段更新座位状态时带条件UPDATE t_seat SET status APPOINTED, version version 1 WHERE id #{seatId} AND status AVAILABLE影响行数为0说明已经被别人抢先了直接返回“座位刚被预约请换一个”。这个方案实现简单又不破坏事务一致性是最推荐的方法。方案三Redis分布式锁。适合已经在项目里用了Redis的情况用SETNX key value EX 5拿到锁再执行预约逻辑。如果毕业设计里写了Redis用这个方案会显得技术水平更高但要注意锁的过期时间、释放时机处理不好容易死锁。我建议毕设至少写到方案二并在论文里分析三者的适用场景回答“如果并发量特别大怎么办”时把方案三作为扩展点提出来效果比单纯背概念好得多。4.3 答辩准备老师最爱问的4类问题第一类为什么选微信小程序回答要点使用场景高频轻量用户免安装、即扫即用开发成本低相比App更符合图书馆临时选座的碎片化场景。第二类如何保证同一座位不被重复预约直接讲时间段重叠判断SQL再补上乐观锁和唯一索引方案。这里建议把代码和SQL提前背熟现场写出来非常加分。第三类信用分是怎么自动扣的从定时任务的角度讲Scheduled每分钟扫描超时未签到记录在一个事务里完成状态变更、信用分扣除、座位释放同时写信用流水。这个过程并不复杂但能讲清楚“事务一致性”就是踩中了评分点。第四类如果用户量到五千、一万会怎么样不要慌也不用真去压测。回答思路是接口做缓存热点座位信息Redis缓存、预约表加索引、定时任务分批处理、后续可以用消息队列削峰。重点是展示你有“架构演进”的意识不要求你真的实现。4.4 扩展方向微信订阅消息与支付V3对接注意毕设做完后如果想冲高分或者做成真正实用的系统有两个扩展方向很常见一是微信订阅消息。预约成功时发送“预约成功通知”开始前30分钟发“签到提醒”。使用流程不复杂小程序端wx.requestSubscribeMessage申请模板消息权限后端通过access_token调用subscribeMessage.send接口。注意订阅消息是一次授权一条用户拒绝后就不能再发了这是微信的规则限制不是代码问题。二是微信支付V3。如果想把系统扩展成付费自习室预约才需要接入支付。开通微信支付的是企业主体个人开发者的号基本不具备这个能力。对接流程上新版支付V3的核心是APIv3密钥、商户证书、平台证书和回调验签比老版本多了证书签名的复杂度。另外要提醒一句小程序类目和支付相关资质必须合规很多做预约类小程序的同学都遇到过支付功能被暂停的提示这种情况往往是小程序类目不符或涉及虚拟支付处理起来很麻烦。所以我的建议是除非指导老师明确要求或者你有真实的企业主体去走认证流程否则大学生毕设不要碰支付把时间花在业务逻辑上更值得。结尾我个人带过好几轮这类项目最大的感受是图书馆座位预约这个题目真正拉开分数差距的地方不在“会不会写增删改查”而在“有没有真正理解业务规则背后的取舍”。比如为什么取消预约要留30分钟缓冲因为太晚取消就没法把座位释放给其他人了系统整体效率会下降。为什么信用分从100分开始扣而不是从0分开始加因为需要让绝大多数用户的初始状态一致默认可预约违规才惩罚。这些细节想得越清楚写出来的代码和论文都会更有说服力。最后再分享一个小技巧准备答辩之前把系统里每一个状态变化都写成一页纸的清单包括触发条件、执行动作、对用户的影响。老师问任何一个流程问题你都能从这张纸上找到答案。这个习惯我到现在做实际项目还在用确实能让人少掉很多头发。
上一篇/下一篇内容由系统自动关联
返回资讯列表 →