简介:基于微信小程序的图书馆座位预约系统,是一份完整的Java毕业设计资源,面向计算机专业毕业生、Java初学者及需要快速完成课程设计的学生。系统功能覆盖用户管理、图书馆维护、座位信息状态更新、预约选座、签到签退、论坛互动与留言反馈等模块,业务链路完整,能直观体现前后端分离开发与小程序交互特点,适合用于毕业设计演示、答辩说明或日常练手。资源共含1400个文件,以vue前端页面、java后端逻辑、wxml/wxss小程序界面、js脚本以及png/jpg图片和sql数据库脚本为主,22.95MB的压缩包便于本地下载,目录结构清晰,便于按模块定位代码。目前已有46人学习下载。除完整源代码外,还附带一键安装、运行与构建脚本及部署教程,可帮助使用者快速启动项目,理解座位状态流转、预约时间配置和管理员权限分配等关键设计,省去从零配置环境的时间。
1. 图书馆座位预约微信小程序:能跑通的 java 毕设都有这些模块
每年毕设季都能看到同一个场景:源码包下载了,解压了,然后卡在环境配置上。这套基于微信小程序的图书馆座位预约系统,后端是 java,前端是原生小程序加一套 Vue 管理后台,功能覆盖用户管理、图书馆管理、座位状态维护、预约选择、签到签退、论坛和留言反馈,是一个完整度比较高的毕设选题。我拆完这套资源后最大的感受是:数据链路清晰,小程序端和管理后台的职责分得开,部署脚本也给到了,适合自己动手复现一遍,而不是只看代码截图。这篇文章就按「数据怎么流转 → 怎么跑起来 → 坑在哪 → 怎么二次开发」的顺序讲透。
2. 数据链路与权限设计:从用户登录到签退的完整流转
这套系统的核心业务是「预约座位 → 到馆签到 → 离馆签退」,所有模块都围绕这条链路展开。我拆代码时习惯先把用户、图书馆、座位、预约四张表的关系理清楚:图书馆是一级,座位挂在图书馆下,预约记录关联座位和用户,签到签退又反过来修改预约状态。理解这条链路,后面改任何功能都不会抓瞎。
2.1 用户管理与登录态:openid 换 token,会话别断
小程序端登录走的是微信官方流程:wx.login 拿临时 code,后端拿 code 去换 openid 和 session_key。这套资源里的用户表除了 openid,还存了昵称、角色和状态字段,角色用来区分普通用户和管理员。常见实现是后端把用户 ID 和角色签成一个 token 返回,小程序存进本地缓存,后续请求都在 header 里带上。
@PostMapping("/login") public Result login(@RequestBody LoginRequest req) { String sessionUrl = "https://api.weixin.qq.com/sns/jscode2session" + "?appid=" + wxConfig.getAppid() + "&secret=" + wxConfig.getSecret() + "&js_code=" + req.getCode() + "&grant_type=authorization_code"; WxSession wxSession = restTemplate.getForObject(sessionUrl, WxSession.class); if (wxSession == null || wxSession.getOpenid() == null) { return Result.fail("登录凭证已失效,请重新授权"); } User user = userMapper.selectByOpenid(wxSession.getOpenid()); if (user == null) { user = new User(); user.setOpenid(wxSession.getOpenid()); user.setNickname(req.getNickname()); userMapper.insert(user); } String token = JwtUtil.createToken(user.getId(), user.getRole()); return Result.ok(token); }这段逻辑里有几个参数值得注意。appid 和 secret 是小程序后台的凭证,secret 千万别写死在前后端代码里,更不能提交到 git 仓库,我一般会放到后端配置文件并用环境变量覆盖。jscode2session 接口返回的 session_key 有有效期,业务上如果用到微信加密数据才需要保存,单纯做登录的话只用 openid 就够了。token 签发的过期时间建议设置成 30 分钟以上,不然学生坐图书馆刷个手机回来就掉线,体验很差。
小程序端的请求封装也要配套:登录成功用 wx.setStorageSync 存 token,请求拦截器统一从缓存取,未登录就跳转登录页。常见翻车点有两个:一个是后端没配 CORS,小程序端在开发者工具里请求直接报跨域;另一个是 token 过期后接口返回 401,前端没有统一处理,用户会以为系统卡死了。
2.2 座位状态机:可用、预约中、维修中的合法迁移
座位信息管理模块里,每个座位有一个状态字段,这可不是简单存个字符串的事。把状态当成状态机来设计,能省掉大量 if-else 判断。这套系统里座位至少有三个状态:可用、预约中、维修中,再加上签到后的「使用中」,一共四种。
状态之间不是任意切换的,合法的迁移路径只有几条:可用 → 预约中(用户提交预约);预约中 → 可用(用户取消);预约中 → 使用中(到馆签到);使用中 → 可用(签退);可用或预约中 → 维修中(管理员维护);维修中 → 可用(维修完成)。写代码时如果把状态迁移写死成校验规则,比每个接口里单独判断状态要可靠得多。
public void changeSeatStatus(Long seatId, SeatStatus from, SeatStatus to) { int rows = seatMapper.updateStatusIfCurrent(seatId, from.name(), to.name()); if (rows == 0) { throw new BizException("座位当前状态不是 " + from.getDesc() + ",请刷新后重试"); } }对应的 SQL 是这个:
UPDATE seat SET status = #{to} WHERE id = #{seatId} AND status = #{from}这段的巧妙之处在于把并发控制交给了数据库的 WHERE 条件。两个用户同时预约同一个座位,都执行 UPDATE,数据库层面只有一条记录能从「可用」改成「预约中」,另一条的更新影响行数为 0,代码里就能捕获冲突并提示用户。这是典型的乐观锁思路,比在应用层加 synchronized 或分布式锁轻量得多,也足够撑住图书馆这种中等并发场景。管理员把座位改成维修中之前,最好先检查一下该座位有没有处于预约中或使用中的记录,否则会出现座位被维修、学生还在座位上签到的尴尬情况。
2.3 预约冲突与签到签退:时间重叠校验的两个边界
预约选择管理是整个系统最容易出 bug 的地方。同一座位同一时间段只能有一个有效预约,这个校验必须在数据库层面做,不能只靠前端传一个「是否可选」的标识。时间重叠的判断标准是:已有预约的开始时间早于新预约的结束时间,并且已有预约的结束时间晚于新预约的开始时间,两个条件同时成立才算冲突。
SELECT COUNT(*) FROM reservation WHERE seat_id = #{seatId} AND status IN ('RESERVED', 'CHECKED_IN') AND start_time < #{endTime} AND end_time > #{startTime}参数说明:seat_id 是座位编号,status 过滤条件很关键,只统计还在生效的预约,已取消或已签退的记录不参与冲突判断;start_time 和 end_time 是用户新提交的预约起止时间。两个不等号都是严格小于和严格大于,意味着边界时间的处理要格外小心。比如已有预约是 10:00 到 12:00,新预约如果有人填 12:00 到 14:00,按这个 SQL 是不冲突的,但如果你的业务规定 12:00 那一秒也要释放给下一个人,就得加上等号判断。
签到信息管理验证的是「预约与实际使用的一致性」。常见做法是签到接口接收座位二维码或座位号,后端拿到当前用户 ID,查是否存在一条状态为「预约中」、座位匹配、当前时间落在预约时间窗内的记录,有就把预约状态改成「使用中」,再把座位状态从「预约中」改成「使用中」。签退是反向操作,找到当前用户正在使用的记录,写入签退时间,把两个状态都改回「可用」。
这里有个血泪经验:签到和签退接口一定要在后端判断「这个座位当前是不是被这个用户占用着」。否则会出现 A 预约了座位,B 抢先去签到,系统把座位释放出来给别人预约,A 到馆发现座位没了。最开始我图省事只查「该座位当前是预约中」就直接签到,结果用户间互相顶替,最后不得不把 user_id 也加进校验条件。
3. 源码落地:三个 bat 脚本、小程序域名与管理后台配置
资源包里三个批处理文件是这套东西最容易被人忽略但最有价值的部分:1-install.bat、2-run.bat、3-build.bat。很多人拿到源码第一件事是打开 IDE 看代码,我反而建议先双击跑一遍这三个脚本,先把项目转起来再谈理解。
3.1 1-install.bat、2-run.bat、3-build.bat 里到底装了什么
这三个脚本的命名很简单直观:安装、运行、构建。我拆过的项目里,Spring Boot 后端加 Vue 管理后台加小程序三件套,最常见的脚本内容是这样的。
@echo off echo 正在安装后端依赖... cd /d %~dp0backend call mvn clean install -DskipTests echo 后端依赖安装完成 pause@echo off cd /d %~dp0backend java -jar target\xxx.jar --spring.profiles.active=dev@echo off cd /d %~dp0web call npm install call npm run build pause参数说明:%~dp0 表示当前脚本所在目录,这样不管从哪个路径双击脚本都能定位到项目根目录;-DskipTests 跳过测试,省掉编译打包时跑单测的时间;--spring.profiles.active=dev 指定开发环境配置,读取 application-dev.yml,避免连到生产库。如果你发现双击 2-run.bat 后控制台一闪而过,多半是 jar 包没生成,先执行 1-install.bat 再回来跑。
实际拆包时我注意到一个问题:项目正文里列出的文件名大多带 .bak 后缀,说明这套代码是经过多次改版后保留下来的。.bak 文件不是垃圾,恰恰是后悔药。改坏了某个页面配置,把 .bak 改名替换回来就能复原,比重新下载源码包快得多。
3.2 小程序端改造:把 BASE_URL 换成你的后端
小程序原生代码里,请求地址通常集中在一个 request.js 或 api.js 文件里。默认配置大概率是开发者的测试地址,你要做的第一件事就是全局搜 BASE_URL,把它改成自己的后端地址。
// utils/request.js const BASE_URL = 'http://127.0.0.1:8080/api'; function request(path, method = 'GET', data = {}) { return new Promise((resolve, reject) => { wx.request({ url: BASE_URL + path, method, data, header: { 'Authorization': wx.getStorageSync('token') }, success: res => resolve(res.data), fail: err => reject(err) }); }); } module.exports = { request, BASE_URL };参数说明:BASE_URL 有三处要改,本地联调用 127.0.0.1 加后端端口,真机调试要用电脑的局域网 IP,上线后要换成 HTTPS 域名。Authorization 从缓存里取 token,如果用户没登录就带空串,后端拦截器遇到受保护接口会返回 401。有个容易踩的细节:微信开发者工具默认校验 request 合法域名,本地调试时要在「详情 → 本地设置」里勾选「不校验合法域名」,否则请求刚发出去就被拦了。这个设置在项目重启后有时会重置,每次打开开发者工具要先检查一遍。
3.3 管理后台:.vue.bak 备份文件与常见配置项
管理后台是一套 Vue 项目,从资源包里的文件名能看出大致结构:IndexMain.vue 是主布局框架,IndexAsideStatic.vue 是侧边菜单,IndexHeader.vue 是顶部栏,BreadCrumbs.vue 是面包屑导航,update-password.vue 是修改密码页。这些 .vue.bak 文件是改版前的备份,打开对比一下就能看出开发者在哪些地方动过手,对理解功能演进很直观。
后台路由菜单的权限控制一般有两层:前端根据用户角色渲染不同菜单,后端在接口层面做权限拦截。常见做法是把菜单配置存到数据库菜单表里,管理员登录后返回可见菜单列表,前端动态生成路由。如果你只想快速跑通,直接在前端路由的 meta 字段里写死角色判断也可以,但毕设答辩时如果老师问权限怎么控制的,还是数据库动态配置的说法更有说服力。
后台还有个容易被忽略的配置项是图书馆信息的维护界面。座位是挂在图书馆下的,所以新增座位前必须先把图书馆建好,否则座位管理的下拉框是空的。这个顺序问题在演示系统时最容易翻车,建议首次登录后台先把图书馆、管理员、座位基础数据全部铺好,再去演示预约流程。
4. 部署避坑手册:五个高频故障与排查路径
这套资源我前后跑了两遍,第一遍踩了不少坑,第二遍就顺了。总结下来高频故障集中在五个地方,每个都有明确的排查路径。
4.1 请求报错:url not in domain list
现象:小程序端所有接口请求都失败,控制台提示「url not in domain list」。原因:微信开发者工具默认校验 request 合法域名,只有在小程序后台配置过的域名才允许访问。解决:本地开发时在开发者工具「详情 → 本地设置」勾选「不校验合法域名、web-view(业务域名)、TLS 版本以及 HTTPS 证书」;上线前必须在小程序管理后台的「开发管理 → 服务器域名」里把 request 合法域名配好,且必须是 HTTPS。
4.2 数据库连不上:端口、时区、密码三大玄学
现象:后端启动时报 Communications link failure,或者 HikariPool 初始化超时。原因:MySQL 默认端口是 3306,但很多人本机装了多个 MySQL,或者用了 Docker 映射了其他端口;MySQL 8 的默认密码加密方式跟老版本不一样,驱动连不上。解决:先确认 application.yml 里的 url、username、password 三者都对,url 里加上 useSSL=false 和 serverTimezone=Asia/Shanghai 两个参数,否则还会遇到时区报错。MySQL 8 的连接要在 url 里把驱动指定为 com.mysql.cj.jdbc.Driver。这类问题排查起来像玄学,实际就是配置不对,一条条核对能省半小时。
4.3 预约冲突偶发漏判
现象:同一座位的同一时间段出现了两条预约记录。原因:前端做了时间冲突校验,但后端接口在并发请求时没有数据库层面的排他检查,或者校验 SQL 里没过滤 status 状态,把已取消的记录也算进去了。解决:用第 2.3 节那条 SQL 做二次校验,在插入预约记录前执行 COUNT 查询,同时保证事务隔离级别是 READ_COMMITTED 以上,靠数据库兜底。另外预约表要给 seat_id 和 start_time、end_time 建联合索引,否则数据量大了之后 COUNT 查询会慢。
4.4 签到后座位状态没变
现象:用户扫码签到成功,但管理后台看座位还是「预约中」,其他人也预约不了。原因:签到接口只更新了预约表状态,没同步更新座位表状态,或者更新座位时用了不带状态条件的 UPDATE 语句,被并发请求覆盖回旧值。解决:签到事务里同时更新两张表,座位状态更新用 2.2 节的「WHERE status = 当前状态」写法,更新行数为 0 就抛异常回滚。检查时优先看日志里有没有 BizException,有就是状态匹配失败。
4.5 上线后 HTTPS 证书问题打不开
现象:真机预览时安卓手机能打开,iPhone 打开白屏,控制台提示证书错误。原因:后端接口没有配置 HTTPS 证书,或者证书是自签名的。解决:小程序正式环境要求所有请求走 HTTPS,证书要在正规服务商购买并部署到服务器,不能自签名。部署后用浏览器直接访问后端接口地址,确认地址栏有小锁图标再提交审核,这一步能省掉一次发布驳回。
5. 进阶:定时清座任务与座位利用率统计
系统跑通之后,真正让它从「毕设 demo」变成「能日常使用」的,是两个不太起眼的细节:超时未签到的座位自动释放,以及用签到数据算真实利用率。这两件事代码量不大,但对使用体验影响很明显。
5.1 用 @Scheduled 兜底释放超时未签到座位
预约了座位但人没来,座位就会一直占着。常见做法是预约生效后加一个宽限期,比如 15 分钟,超过宽限期还没签到就自动取消预约,把座位释放给下一个人。Spring Boot 里加一个定时任务就能解决:
@Scheduled(fixedRate = 60000) public void releaseTimeoutReservations() { LocalDateTime deadline = LocalDateTime.now().minusMinutes(15); int count = reservationMapper.cancelTimeoutReservations(deadline); if (count > 0) { log.info("自动释放 {} 条超时未签到预约", count); } }配套的 SQL 用时间判断筛选超时记录,并避免清理掉正在使用的座位。
5.2 一条 SQL 算出座位真实利用率
签到签退数据攒下来后,可以用一条 SQL 统计最近一周每天的平均使用时长。这类统计在答辩时是很好的加分项,也能帮你发现哪些座位是热门座位、哪些时间段闲置严重。
SELECT DATE(check_in_time) AS day, AVG(TIMESTAMPDIFF(MINUTE, check_in_time, check_out_time)) AS avg_minutes FROM seat_checkin WHERE check_in_time >= DATE_SUB(CURDATE(), INTERVAL 7 DAY) GROUP BY DATE(check_in_time) ORDER BY day;部署这套系统时我在定时任务上吃过一次亏:刚开始把 fixedRate 设成了 1000 毫秒,结果每分钟跑 60 次扫描,数据库 CPU 直接飙高。后来改成 60000 毫秒,并在 SQL 里加好索引,才算消停。从那以后我每次做定时任务都强制检查一遍执行频率和 SQL 扫描行数,先把这两点想清楚再部署。希望这些拆解和踩坑记录能帮你在复现这套图书馆座位预约系统时少走几步弯路。
本文还有配套的精品资源,点击获取