基于微信小程序与Java的会议室预约系统设计与实现

📅 发布时间:2026/9/12 0:57:27
基于微信小程序与Java的会议室预约系统设计与实现
简介这是一套基于微信小程序的软件学院会议室管理系统完整毕业设计项目面向计算机相关专业学生及有Java微信小程序开发需求的读者提供可直接参考的源码、数据库与演示视频。系统以Java为后端核心采用MYSQL数据库围绕会议室预约、审批、状态查看、使用记录等流程实现管理功能有助于解决会议室资源冲突、利用率不高等实际问题。资源包共320个文件压缩包容量约61.59MB包含前端小程序页面文件wxml、wxss、js、json、Java后端源码及编译后的class文件、数据库SQL脚本、项目配置文件与说明文档同时附带演示视频和部署说明方便对照学习。目前已有185人学习下载项目结构清晰前端小程序与后端接口分离便于理解前后端交互流程适合用于毕业设计选题参考、课程设计实践或作为二次开发的项目底座。1. 认识会议室管理系统的前后端边界在软件学院这类场景里会议室预约常常还是靠纸质登记或者群聊接龙冲突、遗忘、权限混乱都是常态。这个毕业设计题目把「微信小程序」和「Java」放在一起本质是让你交付一个完整的业务闭环小程序端承担预约入口和管理员操作界面Java 后端提供业务接口和数据持久化两者通过 HTTP JSON 通信。对做毕设来说它的价值在于技术栈通用、演示直观、延伸空间大——你可以把小程序换成 uniapp把 Java 换成别的后端语言核心逻辑不变。我一般会把系统拆成三个角色来设计普通用户负责查会议室、发起预约、取消预约管理员负责审核、管理会议室信息和查看统计系统本身处理时间冲突、状态流转和数据校验。对于一个 5 年以上经验的工程师这类 CRUD 系统并不复杂但站在毕业设计的评测角度「完整度」和「逻辑严谨性」比「用了多新潮的框架」更重要。本文就按「数据模型 → 小程序端 → Java 接口 → 联调排错 → 答辩验证」这条线把整套方案讲透。2. 数据模型先行会议室状态与预约冲突怎么建模动手写代码之前先把数据库结构理清。会议室管理系统最核心的实体是会议室、用户、预约记录或叫预订单如果还需要审批流再拆一张审批记录表。很多初学者一上来就写接口写到预约冲突检测时才回头改表结构这是最常见的返工原因。2.1 三张核心表加一张扩展表先看一套我常用的最小表结构。用户信息不直接存密码和昵称而是以微信小程序登录返回的openid作为唯一标识会议室表保存位置、容量和设备信息预约表则把时间段、参与人数、用途都挂在这里用状态字段区分申请中、已通过、已拒绝、已取消。CREATE TABLE t_user ( id INT NOT NULL AUTO_INCREMENT, openid VARCHAR(64) NOT NULL COMMENT 微信用户唯一标识, name VARCHAR(32) DEFAULT COMMENT 姓名, role TINYINT DEFAULT 0 COMMENT 0普通用户 1管理员, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_openid (openid) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE t_meeting_room ( id INT NOT NULL AUTO_INCREMENT, room_name VARCHAR(64) NOT NULL COMMENT 门牌号/名称, location VARCHAR(128) DEFAULT COMMENT 所在楼层, capacity INT DEFAULT 0 COMMENT 容纳人数, equipment VARCHAR(255) DEFAULT COMMENT 投影/白板等, status TINYINT DEFAULT 1 COMMENT 1启用 0禁用, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;预约表是整张数据模型的中心。它通过user_id和room_id分别关联用户和会议室用start_time、end_time记录时间区间。这里的重点在于不要只存日期要把时分秒也纳入区间判断。因为会议室最常见的冲突就是「上午 9 点到 10 点半」和「10 点到 11 点」这种跨小时重叠。CREATE TABLE t_booking ( id INT NOT NULL AUTO_INCREMENT, user_id INT NOT NULL, room_id INT NOT NULL, title VARCHAR(128) DEFAULT COMMENT 会议主题, start_time DATETIME NOT NULL, end_time DATETIME NOT NULL, status TINYINT DEFAULT 0 COMMENT 0待审核 1已通过 2已拒绝 3已取消, remark VARCHAR(255) DEFAULT , create_time DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_room_time (room_id, start_time, end_time), KEY idx_user (user_id), KEY idx_status (status) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;我在room_id start_time end_time上建了联合索引是为了让冲突检测的WHERE条件能快速定位候选记录。还没完如果题目要求管理员必须审批再加一张t_approval表记录审批意见、操作人、操作时间如果不需要审批流直接在t_booking.status上完成自动通过也行这取决于你的功能清单。2.2 一张状态机图说清预约状态流转预约记录的状态只有四到五个但你得定死它们的流转方向。我一般这么约束普通用户提交预约 →待审核或直接已通过取决于是否开启自动审批管理员审核通过 →已通过管理员审核拒绝 →已拒绝用户主动取消 →已取消且只有待审核和已通过状态可以取消已拒绝的记录不允许再次编辑必须重新发起不要把「已结束」作为状态存在数据库里它可以通过end_time NOW()在查询时动态判断。这样表里少一个状态统计过去使用率时也更简单。这一点在答辩时如果被问到「为什么不做定时任务去更新状态」可以解释为查询时计算更轻量、无状态。2.3 时间冲突检测的两种 SQL 写法冲突检测是会议室系统里含金量最高的逻辑点。判断两段时间是否重叠等价于判断「新预约的开始时间是否早于已有预约的结束时间」且「新预约的结束时间是否晚于已有预约的开始时间」。我用一组数据来演示区间重叠的判定已有预约新预约是否冲突09:00-10:0009:30-10:30冲突09:00-10:0010:00-11:00边界紧邻不冲突若允许09:00-10:0010:01-11:00不冲突第一种 SQL 写法是纯数据库查询逻辑放在 SQL 里SELECT COUNT(*) FROM t_booking WHERE room_id ? AND status IN (0, 1) AND start_time #{endTime} AND end_time #{startTime};参数说明#{endTime}和#{startTime}是前端传来的新预约起止时间。这段 SQL 的本质是「找所有开始时间早于新预约结束、结束时间晚于新预约开始的记录」只要 COUNT 大于 0就说明存在重叠。注意在状态上只查待审核和已通过因为已取消、已拒绝的记录不应该参与冲突判断。第二种写法是「先查候选集再在应用层判断」。适用于后端需要拿到重叠记录的完整信息做提示的场景比如告诉用户「与哪场会议冲突、谁预约的」。SQL 部分和上面一样只是把COUNT(*)换成SELECT *后遍历比对逻辑完全一致。有一点要特别提醒如果允许相邻时间背靠背预约前一场 10:00 结束后一场 10:00 开始上面的 SQL 刚好认为不冲突因为条件是start_time 新结束时间等于边界值时不命中。如果你想让边界紧密相邻也算冲突就把改成。这个细节建议在答辩时主动讲出来能体现你在边界条件上的思考。3. 微信小程序端从登录到预约提交的完整链路小程序的开发我默认采用原生语法做演示因为毕业设计评阅老师更看重你能不能讲清楚每一行代码的作用。如果你熟悉 uniapp也可以把本文的页面改成 uniapp 的写法业务逻辑不变。3.1 第一步用 wx.login 拿到 openid小程序端不能直接拿到用户数据库里的用户 ID它的身份识别依赖微信的code2Session接口。整个链路是小程序端调用wx.login()拿到临时凭证code把code发给你的 Java 后端后端拿着code AppID AppSecret去微信服务端换openid。openid就是你在数据库t_user.openid字段里存的东西。// pages/login/login.js wx.login({ success: async (res) { if (res.code) { const loginResp await wx.request({ url: http://localhost:8080/api/auth/login, method: POST, data: { code: res.code } }); wx.setStorageSync(token, loginResp.data.token); wx.setStorageSync(userId, loginResp.data.userId); } } });为什么不能只传code到后端让后端存起来因为code一次性有效有效期只有五分钟真正的用户标识必须由后端向微信服务器换取。你在答辩时要把这句话说清楚很多评审老师会追问「openid 能不能放前端」。3.2 会议室列表页加载状态与请求封装列表页是用户进入系统最先看到的页面也是热词里「修改刚进入的加载页面」常出现的位置。我的做法是页面onLoad时请求/api/room/list返回的数据用wx.setData绑定到roomList。这里有个踩坑点小程序wx.request的success回调里this指向问题要用箭头函数或者提前保存const that this。Page({ data: { roomList: [], loading: false }, onLoad() { this.fetchRooms(); }, fetchRooms() { this.setData({ loading: true }); wx.request({ url: http://localhost:8080/api/room/list, method: GET, success: (res) { this.setData({ roomList: res.data.data }); }, fail: () { wx.showToast({ title: 加载失败, icon: none }); }, complete: () { this.setData({ loading: false }); } }); } });列表项渲染时wx:for配合wx:key指定唯一字段这是必做的。如果后台返回的数据量大或者图片多还可以在wxml里配合wx:if做骨架屏或加载态切换——也就是热搜词里「修改刚进入的加载页面」的实际落地场景。另外wx.request的url必须是https://开头并在小程序后台配置合法域名这是最常见的联调卡点。本地开发可以勾选「不校验合法域名」但演示视频里我会建议你使用已备案域名的后端接口否则真机预览时可能直接白屏。3.3 预约提交页日期时间选择与参数组装预约页的交互核心是让用户选择「哪天 几号会议室 几点到几点」。时间选择我建议用picker组件的modedate和modetime分别选择日期和起止时间组装后传给后端。submitBooking() { const { date, startTime, endTime, roomId } this.data; const start ${date} ${startTime}:00; const end ${date} ${endTime}:00; wx.request({ url: http://localhost:8080/api/booking/create, method: POST, data: { roomId, startTime: start, endTime: end, title: this.data.title }, success: (res) { if (res.data.code 0) { wx.showToast({ title: 提交成功 }); wx.navigateBack(); } else { wx.showModal({ title: 提示, content: res.data.msg || 预约失败可能时间冲突, showCancel: false }); } } }); }这里有两个值得在文档或答辩里说的点。第一startTime和endTime必须做前端校验确保endTime startTime越早拦截越省后端压力第二字符串拼接格式严格使用yyyy-MM-dd HH:mm:ss因为 Java 端的LocalDateTime.parse()或 MyBatis 的日期映射都对格式敏感。后端最终以DateTime类型接收前端传字符串还是传时间戳要在接口文档里定死。4. Java 后端接口预约创建与并发控制的正确姿势后端我用 Spring Boot 做演示因为它几乎是 Java 毕业设计的事实标准。很多教程会把代码写成「Controller 调 Service、Service 调 Mapper」的三层结构这不难但要让系统在并发场景下不出错核心在预约创建的幂等与锁机制上。4.1 三层架构与实体映射按常规结构拆包controller层接收小程序请求、service层写业务逻辑、mapper层操作数据库。Controller 的代码几乎没有难度无非是用RestController标注、RequestMapping(/api)定义前缀。真正的业务逻辑放在BookingService里。Service public class BookingService { Autowired private BookingMapper bookingMapper; Transactional(rollbackFor Exception.class) public boolean createBooking(Booking booking) { // 1. 参数校验 if (booking.getStartTime().isAfter(booking.getEndTime())) { throw new BusinessException(开始时间不能晚于结束时间); } // 2. 查重带上悲观锁 int count bookingMapper.countConflict(booking.getRoomId(), booking.getStartTime(), booking.getEndTime()); if (count 0) { throw new BusinessException(该时段已有会议预约); } // 3. 插入记录 return bookingMapper.insert(booking) 0; } }Transactional的作用是保证「查重 插入」在同一个数据库事务里要么全部成功要么全部回滚。如果去掉它可能出现两个人同时查到空档、又同时插入的脏数据这在多用户并发访问时是真实风险。我在 Service 层已经加了注释你可以照着这个骨架往里面继续加审批、取消、统计等接口。4.2 悲观锁与乐观锁怎么选刚才的示例里countConflict走的只是普通查询严格来说在隔离级别为 READ COMMITTED 的 MySQL 默认配置下仍然存在并发插入的窗口。解决方案有两种方案一在查询语句后面加FOR UPDATE也就是把同一会议室的冲突检查变成行级锁。这适合预约频率高的场景但要注意FOR UPDATE必须命中索引否则会锁全表。SELECT COUNT(*) FROM t_booking WHERE room_id #{roomId} AND status IN (0, 1) AND start_time #{endTime} AND end_time #{startTime} FOR UPDATE;方案二在t_booking表加唯一索引比如把room_id start_time作为唯一键从数据库层面挡住重复预约。但这种方法只能挡住「同一会议室同一开始时间」没法挡住跨时间重叠所以通常还是要配合应用层查重用。我给的实践建议是毕业设计场景用方案一。它逻辑最直白答辩时也好解释「为什么能防止并发重复」。方案二可以用在扩展思路里作为「你还能怎么优化」的回答。4.3 状态字段驱动的管理端接口管理端操作通常包括批准、拒绝、禁用会议室、查看预约列表。这些接口的原理都是状态机流转写起来不复杂。比如审核接口PostMapping(/booking/approve) public Result approve(RequestParam Long bookingId, RequestParam Integer status) { // status 1通过 2拒绝 Booking booking bookingMapper.selectById(bookingId); if (booking null) { return Result.error(预约记录不存在); } booking.setStatus(status); bookingMapper.updateById(booking); return Result.ok(); }值得注意的是审核通过时也要做一次冲突检查因为有可能在「待审核」期间已经有另一单被审核通过了。稳妥的做法是在approve方法里同样调用countConflict只是排除掉自己这条记录。你可以在 SQL 里加AND id ! #{bookingId}来实现。5. 本地联调、常见报错与答辩演示自查开发到部署完成之间有一段容易卡壳的路小程序真机预览连不上本地后端、request请求返回 404、时间格式解析报错等。这些问题本身不难但没有经验的话会消耗大量时间。5.1 本地联调的网络配置小程序开发工具默认可以勾选「不校验合法域名」但真机预览时手机会访问不到你电脑上的localhost。正确做法是让手机和电脑连同一个 Wi-Fi后端启动时监听0.0.0.0小程序请求地址换成你电脑的局域网 IP如http://192.168.1.8:8080。注意在微信开发者工具里把「详情 → 本地设置 → 不校验合法域名」打开否则提示会被拦截。java -jar meeting-room-system.jar --server.address0.0.0.0 --server.port8080参数说明--server.address0.0.0.0允许局域网内设备访问--server.port8080固定端口。Windows 防火墙如果弹窗记得允许 Java 入站请求否则真机访问时会出现请求超时。5.2 三个必踩的接口报错第一个是「Failed to convert value of type java.lang.String to required type java.time.LocalDateTime」。这是因为小程序端传的是字符串Spring 默认无法直接映射到LocalDateTime。解决办法是在application.yml里配置全局日期格式或者在实体类的日期字段上加JsonFormat注解二选一即可不要同时用两套格式。spring: jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT8第二个是「Invalid bound statement (not found)」。这个报错通常是 MyBatis 的 Mapper 接口与 XML 文件路径不匹配。查看resources/mapper目录下的 XML 文件确认 namespace 与接口全限定名一致、id与方法名一致然后检查application.yml里的mybatis.mapper-locations: classpath:mapper/*.xml是否配置。第三个是zip压缩包崩溃类问题。毕业设计交付物通常是一个包含源码、数据库 SQL 文件、演示视频、说明文档的压缩包。评阅老师下载后如果解压失败或文件缺失印象分会大打折扣。打包前我用zip -r或压缩软件测试解压一次确认 MySQL 的.sql文件能正常导入、前端项目npm install能跑通然后再提交。5.3 演示视频怎么录才不被扣分演示视频在题目里是加分项但很多同学录成「打开小程序点几下就关掉」。我的建议是按这条脚本走登录进入首页 → 展示会议室列表 → 选择一间会议室发起预约 → 切到 MySQL 展示新插入的记录 → 切换账号扮演管理员 → 审核通过 → 回到小程序端看到状态变化 → 最后把项目源码结构展开逐个介绍controller、service、mapper文件夹的职责。整个视频控制在 5 到 8 分钟模板化地录一遍声音干净、无卡顿答辩时直接剪辑到关键节点给老师看。5.4 答辩时的三个高频追问准备好数据库设计说明为什么openid用VARCHAR(64)而不是INT答openid是微信生成的字符串不是自增主键但可以作为逻辑主键或唯一索引。第二个问法是「如果两个人同时预约最后一个空档系统会发生什么」你可以把悲观锁与唯一索引的双保险策略原理解释一遍这是整场答辩最能体现技术深度的时刻。第三问通常是「统计某个会议室当月的使用率」这需要一条按天聚合的 SQLSELECT DATE(start_time) AS day, COUNT(*) AS booking_count FROM t_booking WHERE room_id #{roomId} AND status 1 AND start_time DATE_FORMAT(CURDATE(), %Y-%m-01) AND end_time DATE_ADD(DATE_FORMAT(CURDATE(), %Y-%m-01), INTERVAL 1 MONTH) GROUP BY DATE(start_time) ORDER BY day;这条 SQL 的聚合条件写在了WHERE里而不是HAVING里因为我们要先筛出当月已通过的预约再按天分组统计。答案能直接满足「按会议室、按月、按天」三个维度在管理端做柱状图或列表展示时可以直接复用。返回给小程序端时再套一层ListMapString, Object前端拿出day和bookingCount两个字段就能画图不用再改后端。本文还有配套的精品资源点击获取