简介:会议室预订预约小程序是一套面向写字楼、高校、创业园等场所的在线预订系统,采用微信小程序作为用户端,后台基于原生PHP构建,适合需要快速部署会议室管理方案的团队或用于学习小程序与后端联动开发的开发者。小程序端提供会议室实时查看、时间段选择、在线提交预订等操作,后台则承担会议室资源维护、预订信息管理及二维码核销,现场通过扫码验证即可快速入场,省去人工核对的麻烦。整个资源包共1185个文件,压缩包体积约1MB,其中以WXML、WXSS、JS、JSON、WXS等小程序前后端源码文件为主,同时包含部分Git对象文件,便于查看历史版本。目前已有4145人学习下载,覆盖了从预约发起、后台审核到现场核销的完整链路,代码结构清晰,适合作为二次开发模板,也可帮助初学者理解小程序与PHP接口的协作方式。 公司里最烦的事,恐怕就是到处找人问“哪个会议室空着”“谁把302订了”这种话。钉钉和企业微信有相关功能,但要么要买高级版,要么操作路径太深,对行政和普通员工都不友好。我花了两周时间,把一个会议室预订预约小程序从零搭了起来,包含前端小程序和后端管理台,前后台源码全部在手。这篇就把它完整拆给大家,从表结构设计到预订流程实现,再到后台审核,把踩过的坑和关键代码逻辑一次性说清楚。
1. 项目概述:为什么需要一个会议室预约小程序
先说这个项目能干什么。员工打开微信小程序,能看当天每个会议室的占用情况、按时间段预订会议室、取消自己预订的场次;后台管理端则可以维护会议室列表(比如容纳人数、有无投影仪)、设置开放时间段、查看所有预订记录,甚至在有人恶意占座时强制释放。整个项目分小程序前端和后端管理后台,前后台源码都打包在项目里,直接部署就能用。
这个场景最大的痛点是“信息不同步”。用Excel排班、让前台人工登记,稍微有点规模的公司就会乱成一锅粥。我见过最夸张的情况是,行政每天早上要在群里发一遍当天预订表,然后依然有人撞车。会议室预订小程序的核心价值,就是把“手工登记”变成“在线实时预订”,把“口头约定”变成“有状态约束”。
1.1 核心需求拆解
从需求层面看,这个系统可以拆成三个角色、五个核心场景:
三个角色是普通员工、行政管理员、系统维护者。普通员工只关心“查空、预定、取消”;行政管理员要在后台维护会议室资源、处理异常预订;维护者则关注部署是否顺畅、服务是否稳定。
五个核心场景就是:浏览会议室状态、发起预订、取消预订、后台管理会议室、生成使用统计。
做个优先级排序,最核心的其实是“时间冲突控制”和“状态同步”。前者保证同一个会议室同一个时间段不会被订两次,后者保证前端显示的状态和数据库一致。这两点做好,整个系统就稳了,其他都是锦上添花。
1.2 技术选型与整体架构
技术选型上我用了最成熟的一套组合:
- 前端小程序:原生微信小程序
- 后台框架:Spring Boot 或 ThinkPHP(你想用哪个都行,我这里用的是 Spring Boot,下文代码示例以这个为主)
- 数据库:MySQL
- 前后端通信:RESTful API
- 后台管理界面:Vue Element Admin,或者直接用服务端渲染的简单管理页
为什么不用 uni-app?如果你只需要微信一个端,原生小程序体积小、调试方便、新开项目几乎没有依赖成本。uni-app 适合要同时发布支付宝、抖音小程序的场景,但对这种企业内部工具来说,属于过度设计。
整个架构是标准的“小程序客户端 — 后端服务 — 数据库”三层结构,中间通过 HTTPS 接口通信。后端负责业务校验、权限控制、数据持久化;小程序只负责展示和收集用户操作;后台管理端和后端共享同一套业务服务,但是通过不同的接口路径做区分。
2. 数据库设计与核心业务模型
会议室预订的数据模型不复杂,但设计不好容易埋雷。核心就三张表:会议室表(meeting_room)、预订记录表(booking_record)、用户表(user),另外加一张系统配置表存开放时间之类的全局配置。
2.1 核心表结构设计
先看会议室表,字段如下:
CREATE TABLE `meeting_room` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(64) NOT NULL COMMENT '会议室名称,如 301', `location` varchar(128) DEFAULT NULL COMMENT '所在楼层或区域', `capacity` int(11) DEFAULT '10' COMMENT '容纳人数', `facilities` varchar(255) DEFAULT NULL COMMENT '设施,如投影仪、白板、视频会议设备', `status` tinyint(4) DEFAULT '1' COMMENT '1可用 0停用', `sort_order` int(11) DEFAULT '0' COMMENT '排序', `created_at` datetime DEFAULT CURRENT_TIMESTAMP, `updated_at` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;这张表最关键的是status字段,停用的会议室在前端就不该出现。facilities我用逗号分隔存储,比如“投影仪,白板,视频终端”,查询的时候直接 LIKE 匹配即可,没必要为了这个单独拆一张中间表——企业内部工具,简单才是王道。
再说预订记录表,这是整个系统的核心:
CREATE TABLE `booking_record` ( `id` int(11) NOT NULL AUTO_INCREMENT, `room_id` int(11) NOT NULL COMMENT '会议室ID', `user_id` int(11) NOT NULL COMMENT '预订人ID', `booking_date` date NOT NULL COMMENT '预订日期', `start_time` time NOT NULL COMMENT '开始时间', `end_time` time NOT NULL COMMENT '结束时间', `title` varchar(128) DEFAULT NULL COMMENT '会议主题', `attendee_count` int(11) DEFAULT '0' COMMENT '参会人数', `status` tinyint(4) DEFAULT '0' COMMENT '0待审核 1已确认 2已取消 3已结束', `remark` varchar(255) DEFAULT NULL COMMENT '备注', `created_at` datetime DEFAULT CURRENT_TIMESTAMP, `updated_at` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `idx_room_date_time` (`room_id`, `booking_date`, `start_time`, `end_time`), KEY `idx_user_date` (`user_id`, `booking_date`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;idx_room_date_time这个联合索引很关键。判断时间冲突的时候,最常用的查询就是“根据会议室、日期找该时间段内的预订记录”,没有这个索引,数据量一大,慢查询就来找你了。
用户表就比较常规,小程序端调用 wx.login 拿到 openid,后台据此识别用户,同时维护用户的姓名、部门、管理员标记。
CREATE TABLE `user` ( `id` int(11) NOT NULL AUTO_INCREMENT, `openid` varchar(64) NOT NULL COMMENT '微信openid', `name` varchar(64) DEFAULT NULL COMMENT '姓名', `department` varchar(128) DEFAULT NULL COMMENT '部门', `is_admin` tinyint(4) DEFAULT '0' COMMENT '0普通用户 1管理员', `created_at` datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uniq_openid` (`openid`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;2.2 预订状态机与时间冲突判断
预订状态我设计了四个:待审核、已确认、已取消、已结束。这里有个设计选择要说明:为什么要有“待审核”?
有些公司会议室紧张,需要行政人员统一调控,预订后必须管理员确认才生效。有些公司则完全自助,预订即确认。我的方案是在后台配置一个开关need_review,开启时新预订走“待审核”,关闭时直接置为“已确认”。
时间冲突判断是最容易写错的地方。网上很多人判断冲突是这样写的:
SELECT * FROM booking_record WHERE room_id = ? AND booking_date = ? AND status IN (0, 1) AND start_time <= ?end_time AND end_time >= ?start_time这段 SQL 的思路是:查出所有在“待判断时间段”之前开始、之后结束的记录。具体解释一下,判断条件start_time <= 新的结束时间和end_time >= 新的开始时间,两者同时满足就说明两个时间段有重叠。比如已有预订是 9:00-10:30,你想订 9:30-10:00,那 9:30 <= 10:30 成立,10:00 >= 9:00 也成立,所以冲突,不让你订。这个写法是对的,但它没有排除“已取消”状态的记录,如果你把状态条件只写status = 1,那待审核的记录就绕过冲突校验了,这是不严谨的。
正确的做法是,只要状态是“待审核”或“已确认”的记录都算占用,status IN (0, 1)。等到后台拒绝某个预订时,把状态置为 2,释放时间窗口。
3. 前台小程序端设计与实现
前台功能模块我分成了四个页面:首页(会议室列表)、预订页(时间选择与确认)、我的预订(查看/取消)、个人信息。整体走 TabBar 三栏风格,首页、预订大厅、我的。
3.1 首页会议室列表与状态展示
首页通过调用后端接口GET /api/rooms?date=2024-01-15拿到某一天所有会议室的状态。后端会一次性返回每个会议室的“今日已被占用的时间段列表”,小程序端负责渲染。
状态展示分三种颜色:
- 绿点:该时间段空闲
- 红点:该时间段已占用
- 灰点:会议室停用或不在开放时间内
这里有个体验细节:列表页不要展示全天的完整时间轴,而是一行一个会议室,横向滑动显示当天的时间格子,每个格子代表半小时。这样看起来直观,也不需要用户理解复杂的时间线概念。
核心请求代码大致如下:
// pages/rooms/roomList.js const app = getApp(); Page({ data: { date: '', roomList: [] }, onLoad(options) { const today = new Date().toISOString().slice(0, 10); this.setData({ date: today }); this.fetchRooms(); }, fetchRooms() { wx.request({ url: `${app.globalData.baseUrl}/api/rooms?date=${this.data.date}`, success: (res) => { if (res.data.code === 0) { this.setData({ roomList: res.data.data }); } } }); } });这里的baseUrl是后端服务的域名,开发环境要改成你本地电脑的局域网 IP,而且微信开发者工具里要勾选“不校验合法域名”。这是所有小程序开发新手都会踩的坑,不勾选的话请求直接报错。
3.2 预订流程与时间选择
预订页流程是:选日期、选会议室、选开始时间、选结束时间、填会议主题和人数、提交。前端做两件事:一是显示该会议室当天可预订的时段,二是把提交的数据传给后端做最终校验。
时间段选择我用了“最多连续 4 小时、最小单位 30 分钟”的规则。前端滑块组件在原生小程序里没有现成的,我用最简单的两个 picker 分别选开始和结束时间。后端校验“结束时间大于开始时间”是必须写的,因为前端的校验可以绕过,特别是恶意请求时接口必须独立校验一遍。
时间选择的另一个细节是:要排除掉已经过去的时间。比如现在是下午两点半,你不能订今天下午一点到两点的会议室。后端在入库前会做一次NOW()比较,前端在渲染时间段时也会过滤掉过期时段。两处校验缺一不可。
提交预订的核心接口伪代码:
@PostMapping("/api/bookings") public Result createBooking(@RequestBody BookingRequest request) { // 1. 校验参数 if (request.getEndTime().isBefore(request.getStartTime())) { return Result.error("结束时间必须晚于开始时间"); } // 2. 校验时间冲突 List<Booking> conflicts = bookingMapper.findConflicts( request.getRoomId(), request.getBookingDate(), request.getStartTime(), request.getEndTime(), Arrays.asList(0, 1) ); if (!conflicts.isEmpty()) { return Result.error("该时段已被预订,请选择其他时间"); } // 3. 创建预订 Booking booking = new Booking(); // ... 设置字段 bookingMapper.insert(booking); return Result.success(booking); }前端拿到返回的bookingId后跳转到“我的预订”页面,可以看到自己刚提交的预订记录。
这里我强烈建议给“待审核”状态加一个明显的标识,文字上写成“审核中”,并提示用户“如长时间未确认请联系行政”。
4. 后台管理端核心功能
后台管理端我用了 Vue Element Admin 作为模板,一共做了四个页面:会议室管理、预订审核、预订记录、统计报表。
4.1 会议室管理:增删改查与状态切换
会议室管理页就是标准的 CRUD,但在“编辑会议室”里我加了个很实用的“临时停用”功能。会议室临时漏水或者要装设备,管理员一键把status置为 0,前台就不会再显示这个会议室,已有的预订要不要强制取消,后台给一个二次确认弹窗。
这个页面还可以配置每个会议室的开放时间。有的公司 30 层会议室只开放到晚上 8 点,有的 24 小时可用。我在会议室表加了open_start和open_end两个字段,预订时后端要校验“时间范围在开放时间内”,不在就直接拒绝。
// 校验开放时间 if (request.getStartTime().isBefore(room.getOpenStart()) || request.getEndTime().isAfter(room.getOpenEnd())) { return Result.error("该会议室不在开放时间内,可预订时间为 " + room.getOpenStart() + " - " + room.getOpenEnd()); }这是很多现成系统容易漏掉的逻辑,开放时间不控制,半夜也有人在系统里订会议室,第二天早上一看排得乱七八糟。
4.2 预订审核与统计报表
预订审核列表展示所有“待审核”状态的记录,管理员可以“通过”或“拒绝”。通过就把状态从 0 改成 1,拒绝就把状态改为 2,同时可以填一个审批备注。
统计报表页我用 ECharts 做了两个图表:一是“会议室使用率排行”,二是“每日预订量趋势”。使用率的计算方法是“已确认的预订总时长 / 会议室开放时长”。
比如某会议室开放 9:00-18:00,共 9 小时,当天有人订了 3 个小时,使用率就是 33.3%。计算时要注意排除“已取消”的记录,否则会出现订了又取消导致使用率虚高的情况。
SELECT room_id, SUM(TIMESTAMPDIFF(MINUTE, start_time, end_time)) as total_minutes, COUNT(*) as booking_count FROM booking_record WHERE status = 1 AND booking_date = ? GROUP BY room_id ORDER BY total_minutes DESC这个报表对每周复盘会议室资源够不够用很有参考价值。比如 8 人小会议室日均使用率超过 95%,说明小会议室严重紧缺,可以考虑改造一间大会议室,或者设立“固定抢单时间”规则。
5. 部署与常见问题排查
部署这块,我把后端打成 JAR 包跑在服务器上,Nginx 反向代理,小程序请求走 HTTPS 域名,后台管理端打包成静态文件放在 Nginx 的另一个 location 下。整个部署流程不复杂,但有几个细节容易踩坑。
5.1 前后台部署流程
步骤拆开是这样的:
- 服务器安装 JDK 和 MySQL,初始化数据库,导入建表 SQL
- 修改后端配置文件里的数据库连接地址和 Redis 地址
- 打包后端:
mvn clean package -DskipTests,生成 JAR - 启动:
nohup java -jar meeting.jar > app.log 2>&1 & - 配置 Nginx,把
api.你的域名.com转到本地 8080 端口 - 小程序后台配置合法请求域名
- 打包前台管理端,把静态文件放到
/usr/share/nginx/html/admin目录
这里我要特别说下 HTTPS 证书。微信小程序正式环境强制要求 HTTPS,而且不能是自签名证书,必须是受信任的 CA 签发的。你可以在云厂商免费申请一年期的证书,Nginx 配置也比较简单。
还有一个经常被忽略的点:小程序里的baseUrl不能写 IP。微信开发者工具里,不校验合法域名时虽然可以用 IP 调试,但正式发布的版本必须用备案过的 HTTPS 域名。这个很多人上线的时候才发现,回头还得改代码、发新版本,非常被动。我建议项目一开始就统一用域名,只是本地 hosts 解析到开发机。
5.2 常见问题与避坑速查表
我在开发过程中遇到的坑不算少,挑几个高频的列出来:
| 问题 | 原因 | 解决方式 |
|---|---|---|
小程序请求失败:request:fail | 本地调试未勾选“不校验合法域名” | 开发工具详情-本地设置勾选 |
| 提交预订报错“时间段冲突”但明明没人订 | 状态没排除“已取消”记录 | 查询条件加status IN (0, 1) |
| 后端部署后时间不对 | 服务器时区不是东八区 | 配置Asia/Shanghai |
| 预订成功后前端列表不刷新 | 页面 onShow 没有重新请求数据 | 在 onShow 里调用 fetchRooms |
| 微信登录拿不到 openid | 没有配置 AppSecret 或 code2Session 接口调用失败 | 检查小程序后台的 AppSecret 是否填写正确 |
时间时区这个问题我提一句。MySQL 连接字符串里加serverTimezone=Asia/Shanghai,否则插入时间会比实际晚 8 个小时。我当时排查了半天,原来是因为服务器上 MySQL 用的默认 UTC。这个小问题不仔细容易翻车。
5.3 关于微信支付对接的一点经验
如果会议室预订需要收费(比如园区共享会议室),就要接入微信支付。新版微信支付走 V3 接口,核心是商户号、AppID、API 密钥三件套,签名方式是用商户私钥对请求进行 RSA 签名。
支付回调是另一个容易出问题的点。支付成功后微信服务器会调用你设置的回调 URL,你需要先验签,然后解密支付结果中的transaction_id,再更新订单状态。
但有一点必须提醒:微信小程序对支付有严格类目限制,而且复用已经上线的账号风险很大。如果小程序主体没有对应资质,或者曾经被处罚过,支付功能很可能会被关停。我自己的经验是,企业内部工具能用企业微信的免登录和审批流就尽量不碰微信支付流程,能省掉一大堆合规问题。如果你确实要做收费,一定要先确认小程序账号没有违规记录,再申请微信支付商户号。
6. 总结与个人体会
会议室预订小程序这个项目,麻雀虽小,五脏俱全。它把小程序端、后端接口、管理后台、数据库这四层技术栈完整串起来了,非常适合做入门到进阶的练手项目。我在实际开发中最大的体会是,这类工具的核心不在界面多华丽,而在“状态流转”是否严谨——从预订到确认再到结束,每一个状态变化都要有据可依,每一步操作都要有防冲突逻辑兜底。
最后再给大家一个扩展方向:现有的系统可以加一个“企业微信机器人通知”功能,预订成功后通过 webhook 推送到会议室所在的群,所有人能实时看到预订消息,行政不用再手动转发。这个改动不大,但实用性提升非常明显。整个项目源码都在仓库里,你拿到之后,建议先把数据库表结构过一遍,理解状态设计,再动手部署,这样遇到问题改起来心里有底。
本文还有配套的精品资源,点击获取