资讯动态

多场馆预约系统实战:FastAdmin+小程序从架构到部署全解析

发布时间:2026/8/31 21:25:03 来源:尧图企业网站定制
简介这是一套面向场馆运营方与中小型SaaS服务商的多场馆预约系统完整源码基于FastAdminThinkPHPUniapp技术栈构建专为解决体育场馆、自助酒店、商务办公等实体场所的场地预约、在线支付、核销管理等核心运营痛点。资源包含后台管理系统、微信小程序前端及全套搭建教程支持私有化独立部署满足对数据安全与业务自主性的高要求。压缩包共2000个文件主体为1682个JavaScript逻辑文件、171个HTML页面模板、70个Vue组件及65个Markdown文档辅以SQL数据库脚本与配置文件总大小20.66MB结构清晰、模块解耦便于二次开发与场景适配。目前已有87人学习下载提供无加密全量源码、可运行的前后端工程、关键流程如短信登录、订单核销的实现逻辑以及install.html等部署引导页显著降低本地调试与上线门槛。 做这套多场馆预约系统之前我手里同时管过三家羽毛球馆和一处共享会议室。之前那套玩法是前台小姐姐拿本子记电话预约周末高峰时段同一个场地能被记两次顾客到了现场才发现撞场投诉一个接一个。后来我干脆用 FastAdmin 把这套预约、支付、核销的流程全部做成小程序后台管场馆、场地、订单和核销记录前端用户自己选时间、付款、到店出示核销码前后端直接跑通。这篇文章就把整套源码的架构逻辑、核心模块、搭建步骤和上线后踩过的坑完整拆出来给同样想做场地预约小程序的团队一个可直接复盘的落地方案。这套系统前端是微信小程序后端是 FastAdmin ThinkPHP 5 的经典组合数据库用的 MySQL支付走微信支付核销用后台扫码/手动输入核销码完成。它适合三类人看一是手上有多个场馆需要统一做预约管理的运营方二是正在学 FastAdmin 二次开发、想看一套真实业务怎么落到控制器、模型、权限和小程序接口里的开发者三是接外包项目、想快速交付一套“场馆预约”类产品的自由职业者。下面我不会只贴源码截图而是按我当时设计这套系统的思路从产品模型、数据表设计、订单状态流转、部署上线到并发踩坑一条线讲清楚。1. 这类预约系统先要解决什么从电话约场到在线闭环开始写代码之前先要把业务模型理清。多场馆预约系统听起来复杂但拆开之后核心是三层递进关系场馆、场地、场次时间段。很多初学的人上来就建一张 “order” 表、一个 “goods” 表结果场馆一多、场地种类一多就乱了。我当时花了一个下午把模型重新捋了一遍后面所有开发都顺畅很多。1.1 场馆、场地、场次三层模型必须先理清场馆是物理空间的概念比如“东区羽毛球馆”“西区会议中心”场馆下面有多个可预约的场地羽毛球馆里有 1 号场、2 号场会议中心里有 A 厅、B 厅每个场地可以独立设置价格、开放时间、状态再往下是场次也就是某一个场地在具体某一天的某个时间段是否可约比如“1号场 2025-06-20 19:00-20:00”。三层模型对应到数据库就是三张核心表fa_venue场馆表保存场馆名称、封面图、地址、经纬度、营业开始/结束时间、简介fa_area场地表保存所属场馆 ID、场地名称、场地类型、默认时价、状态启用/停用fa_schedule场次表保存所属场地 ID、日期、开始时间、结束时间、该场次实际价格、状态可约/锁定/已约/停用。为什么强调一定要拆这么细因为拆开之后每个维度都可以独立运营。今天我想把 2 号场打九五折我只需要改场地表或生成场次时改价格明天新增一家分馆我只需要在加场馆、再加场地不用动订单表结构。更重要的是后面做预约冲突检测时场次表能直接作为锁定单元查询效率和逻辑清晰度都会好很多。1.2 一次完整预约到底走了多少步用户在微信小程序里的操作看起来很简单选场馆 → 选日期 → 看场地时间格子 → 点选可约时段 → 下单支付 → 到店出示核销码。但站在系统角度看每一步动作都在改后端数据状态。完整链路是这样的用户进入小程序首页拉取场馆列表用户选择日期系统根据场馆和场地列表结合当天场次库存渲染出可预约时间段用户选择一个时间段确认金额小程序端向后端POST /api/order/create提交订单后端校验时间段是否仍可约成功则生成订单状态为“待支付”同时把对应场次标记为“锁定”用户调用微信支付支付成功回调更新订单状态为“已支付”场次状态变为“已约”用户到店前台/核销员在 FastAdmin 后台输入核销码或扫描二维码后端校验核销码有效且订单未使用更新订单状态为“已核销”整单结束后续可以在后台财务模块看到该笔订单收入。这 8 步里最关键的是第 4 步和第 7 步。第 4 步决定了会不会出现“两个人同时约到同一块场地同一小时”的问题第 7 步决定了会不会出现“一个核销码用两次”和“没有付钱的人进场”的问题。后面我会单独拿两个小节详细展开。1.3 后台要服务哪些角色预约系统不是只有用户和系统管理员两类角色。实际运营时还需要店长、前台核销员、财务这类不同权限的角色。FastAdmin 自带的 RBAC 权限系统正好能覆盖这块需求不用自己再开发一套权限管理。我当时在后台划分了三类角色超级管理员拥有全部权限可以配置场馆信息、场地价格、支付参数、审核退款等场馆运营负责自己名下场馆的上架、下架、场次生成、价格调整、查看订单统计数据核销员只开放“核销”菜单权限输入核销码或扫码确认用户订单是否有效不能改价格、不能看财务汇总。权限边界清晰有一个特别大的好处小程序端接入的核销员账号即使被拍照、泄露也只会拿到一个纯核销入口动不了后台配置真正出问题可以追责到人。2. 为什么技术底座选 FastAdmin一个能少写一半后台代码的方案很多人在做这种项目时纠结后台是选若依、Vue Element Admin还是自己从零写一套我选 FastAdmin 的理由很简单这个项目的重心在小程序和预约业务逻辑后台只是管理支撑我需要一个能快速生成 CRUD、自带权限菜单、有现成附件上传方案的框架而不是把时间耗在写后台管理界面上。2.1 FastAdmin 自带的能力清单FastAdmin 基于 ThinkPHP 5 开发它的核心优势不是某一个惊艳的功能而是一整套“后台中台”的集成能力一键 CRUD数据表建好后通过命令行php think crud -t fa_area就能生成控制器、模型、验证器、JS、视图后台菜单都不用手动一条条加自带 RBAC管理员、角色、权限节点、菜单层级都是现成的刚才说的核销员权限划分就是通过节点勾选实现通用搜索/排序/导出列表页自动支持字段搜索下拉、时间范围筛选、数据导出 Excel这在小程序后台看订单明细时太实用了附件管理FastAdmin 内置了附件上传和Upload接口场馆封面图、场地图片直接走后台附件库插件扩展机制后续想加短信通知、微信支付配置、小程序登录都可以通过插件应用市场或自己写插件包实现不需要改动框架核心。对于预约系统来说后台至少 60% 的代码量是 FastAdmin 已经替你打好的地基。你需要做的是围绕业务表写 API 接口、预约锁场逻辑、支付回调、核销逻辑。2.2 后台界面快速成型的方法我实际开发时第一步不是写 API而是先把表结构和数据准备好然后使用php think crud生成场地管理、订单管理、核销记录、场馆配置等模块后面再根据业务需要手动微调字段和按钮。举一个典型的例子在订单列表页我需要给每行加一个“核销”按钮同时核销员点按钮后不跳页面直接用 AJAX 提交核销码并刷新状态。FastAdmin 里做法很简单在 JS 里定义 Table.api 的自定义按钮和事件然后调用Fast.api.ajax提交到后台方法。在开发中你可能会经常用到自定义按钮不只是核销还包括关闭订单、取消预约、标记异常等操作这样就可以避免给运营人员开放一堆裸 SQL 和原始编辑界面降低操作失误。这类自定义按钮的核心代码类似这样Table.api.addon(verify, function (table) { table.on(click, .btn-verify, function () { var id $(this).data(id); Fast.api.ajax({ url: /admin/order/verify, data: { id: id, verify_code: $(#verify_code_input).val() }, success: function () { table.bootstrapTable(refresh); } }); }); });后台控制器对应的方法是public function verify() { $id $this-request-post(id); $verifyCode $this-request-post(verify_code); // 校验订单状态 更新核销状态 }2.3 核心数据流一个订单的前半生后半生订单是整套系统最核心的实体我建议以订单表为索引去理解所有模块。订单表fa_order大概包含这些字段订单号、用户ID、场馆ID、场地ID、预约日期、开始时间、结束时间、金额、状态、核销码、支付时间、核销时间、创建时间。下单阶段前半生前端创建订单 → 订单状态置为 0待支付→ 场次状态锁定支付回调微信回调 → 订单状态置为 1已支付→ 场次状态变为已约核销阶段后半生核销员后台核销 → 订单状态置为 2已核销→ 场次状态变为已完成超时未支付定时任务扫描 → 订单状态置为 4已过期→ 场次状态释放为可约。这样设计的好处是订单表和场次表始终联动后台运营随时能看到“某场馆今天有多少已约场次”“有多少待核销订单”不需要额外写统计接口做复杂 join因为状态流转是被严格约束的。3. 场地预约最核心的两个逻辑冲突检测与状态机预约系统跟普通商城系统最大的区别在于库存不是简单的数字减一而是“时间段是否被占用”。这个逻辑没写好系统上线第一天就会出现重约、超卖。而核销则是财务安全门写错了就等于给漏洞开了门。这两块值得单独展开。3.1 时段冲突检测不能靠循环判断先看一个错误示范很多新手判断某场地某时间段是否可约时会写一个循环把所有已预约订单拉出来再逐一比较时间是否重叠。这种做法在小数据量时没问题但一旦订单多起来接口响应会越来越慢而且在并发场景下根本挡不住两个用户同时提交。正确做法是在数据库层做区间重叠判断。判断两个时间段是否重叠逻辑很简单新时间段的开始时间小于已有时间段的结束时间且新时间段的结束时间大于已有时间段的开始时间。用 SQL 表示就是SELECT id FROM fa_schedule WHERE area_id :areaId AND date :bookDate AND status IN (1, 2) AND (start_time :endTime AND end_time :startTime) LIMIT 1;配合后端代码在创建订单前先执行这条查询如果有返回记录则说明该时段已经被锁定或预约直接返回“该时段不可约”。在 tpshop 这类商城系统中你可能习惯查库存字段但预约场景一定要转变思路库存区间是动态的必须用重叠判断。3.2 订单状态机与状态流转约束不要在设计数据库时只用“状态”一个字段表示所有业务阶段更不要随口写死一堆存在规则之外的意外状态。订单状态最好定义整型枚举我这边统一规则如下状态值含义允许的下一步0待支付已支付、已过期1已支付待使用已核销、已过期、已取消需判断过期时间2已核销终态不可再流转3已取消终态不可再流转4已过期终态不可再流转状态流转的核心原则是只允许从当前状态流转到表中“允许的下一步”绝不是任何状态下都能点取消、点核销。比如一个已核销订单不能再次核销一个已取消订单不能重新变成待支付。这些约束在控制器里集中写checkOrderStatusCanXxx()方法不要散落到各个 API 里后期维护会轻松很多。3.3 支付回调怎么写才稳微信支付的回调通知是异步的可能因为网络原因重复推送也可能第一次推送时服务器正在重启导致接收失败所以回调处理必须做幂等。我当时的处理方式分三步接收微信回调先记录原始数据到日志表方便排查问题解析数据验证签名、验证商户号、验证订单金额与订单号是否匹配更新订单状态前判断当前订单状态是否为“待支付”如果是才更新为“已支付”否则直接返回成功。伪代码类似$order OrderModel::getByOrderNo($orderNo); if ($order $order-status 0) { $order-status 1; $order-pay_time time(); $order-save(); // 对应场次改为已约 }因为回调可能重复推送所以“当前订单状态是否等于待支付”这个判断是幂等关键。不做这个判断可能出现已取消的订单收到迟到回调后又被误标记为已支付然后用户拿着已取消单号来核销场地这不乱套了么。3.4 核销码防一码多用的实现核销码是一串给用户和核销员短期验证的凭证我用的是“订单号随机串购买时间”做 MD5再取其中 8 位大写字符串作为体验码。生成时不追求复杂只要求唯一、难猜、在有效期内能定位到订单。真正的难点在核销那一刻。一个核销码只能被核销一次这要求核销动作具备原子性。我的做法是在fa_order表加verify_status字段核销时执行一个带条件更新的 SQLUPDATE fa_order SET verify_status 1, verify_time NOW(), verify_admin_id :operatorId WHERE order_no :orderNo AND verify_status 0 AND status 1如果影响行数为 1说明核销成功如果影响行数为 0说明这张订单已经不是待核销状态直接提示“该核销码已使用或订单状态异常”。这种“条件更新”相当于数据库层的乐观锁不需要额外加锁也能避免并发重复核销。同时再建一张fa_verify_record表每核销一笔记录一条完整日志订单号、核销码、核销人、时间、备注。就算后续顾客和前台发生纠纷也能通过日志恢复现场。4. 搭建部署完整记录从服务器到小程序后台源码到手之后怎么把整个系统跑起来是很多人卡住的环节。我这里按自己实际部署顺序把从云服务器到小程序上线中间的关键步骤写清楚。4.1 运行环境准备FastAdmin 基于 ThinkPHP 5官方推荐环境是 Nginx PHP 7.2/7.4 MySQL 5.7。我建议不要一上来就追新上 PHP 8.x因为老版本依赖组件可能还没有完全兼容部署时会无故多出不少报错。服务器配置方面2 核 4G 起步带宽按实际情况选 3~5M这跑一个日活几千的预约小程序完全够。MySQL 和 PHP 可以通过宝塔面板一键安装下面这些重点别遗漏PHP 需要开启fileinfo扩展、redis扩展如果用到缓存、opcache需要关闭 PHP 函数禁用列表中的proc_open和putenv否则 Composer 安装依赖会失败MySQL 字符集统一 utf8mb4避免用户昵称里的表情符号存不进去。4.2 后端部署步骤拿到源码包后目录结构大概是application应用控制器、public站点根目录、addons插件、runtime运行时缓存、database.sql数据库备份文件。操作步骤将源码整个上传到服务器站点目录设置运行目录为public建立数据库把database.sql导入进去修改application/database.php或根目录.env文件填入数据库地址、库名、用户名、密码修改application/config.php里的app_trace和调试模式本地可以先开启上线务必关闭在网站配置里添加 Nginx 伪静态规则指向public/index.php后台入口目录默认是public/admin.php第一次登录后强烈建议重命名为不容易猜的路径例如public/backend2025.php。伪静态配置我直接放一份location / { if (!-e $request_filename) { rewrite ^(.*)$ /index.php?s$1 last; } }如果你是用的宝塔在网站设置 → 伪静态里选择thinkphp模板效果一样。之后通过http://你的域名/admin.php访问后台初始账号密码请查阅源码包说明首次登录一定要马上修改。4.3 小程序端对接小程序端需要配置的内容包括project.config.json里的appid替换成你自己的小程序 AppID服务端application/extra/wechat.php里的app_id、app_secret微信支付相关配置包括商户号mch_id、API 密钥key、证书文件路径退款和转账用小程序后台的服务器合法域名把https://你的API域名加到 request 合法域名把校验码之后要用到的页面资源也一并检查。常见问题是开发工具里接口通但真机预览就调不通。不用怀疑代码先检查是否忘了配合法域名以及后端返回的 JSON 是否被强制为text/html导致 dataType 不一致。这两个坑我至少帮别人排查过五次。4.4 crontab 定时任务必须配预约系统里一定会有“超时未支付自动取消”这类动作。用户下单后如果不付钱场次会被锁定其他用户就约不了这个状态必须靠定时任务释放。我在系统里加了两个定时任务* * * * * cd /www/wwwroot/your_project php think autoCancelExpiredOrders runtime/auto_cancel.log 21 * * * * * cd /www/wwwroot/your_project php think autoReleaseExpiredSchedules runtime/auto_release.log 21第一个任务把超过 15 分钟未支付的订单改为已过期并把场次状态释放为可约第二个任务把“运营手工锁场但超时未生效”的场次解开。定时任务频率设为每分钟一次压力不大效果很稳。5. 小程序端的几个实现要点用户视角的体验细节后端逻辑再严密用户感知全在小程序上的操作流程。小程序端页面我拆成了五个核心页面首页场馆列表、场地选择、订单确认、订单列表、核销码详情。下面每个页面都有值得注意的细节。5.1 场馆列表与场地状态实时展示首页不展示所有场地而是先展示场馆卡片点击场馆进去再按日期选择场地。因为绝大多数用户的目标是“找个离家近的馆场进去再看有没有场”先选馆再选场更符合直觉。场地列表页的日期选择器可以用微信小程序的原生picker模式date同时后端按日期返回该日期的场次列表。返回格式大致是{ area_id: 1, area_name: 1号场, schedules: [ {time: 18:00-19:00, status: 0, price: 60}, {time: 19:00-20:00, status: 1, price: 80} ] }status为 0 表示可约为 1 表示已约/锁定为 2 表示停用。前端根据状态渲染不同颜色格子用户不可约的格子置灰不跳转下单页。5.2 下单与支付衔接下单接口/api/order/create接收到场地ID、日期、时间段后先做冲突检测再生成订单。这里要注意订单生成后系统锁定场次必须给用户一个合理支付时间我设的是 15 分钟。超过 15 分钟定时任务释放场次前端在支付回调返回order_expired时弹出“该订单已过期请重新预约”。前端支付流程不能自己调起支付后再调后端确认正确姿势是wx.requestPayment({ timeStamp: res.data.timeStamp, nonceStr: res.data.nonceStr, package: res.data.package, // 即 prepay_idxxxx signType: MD5, paySign: res.data.paySign, success: function () { // 支付成功调后端刷新订单状态跳转订单详情 }, fail: function () { // 支付失败或取消跳转订单列表仍可继续支付 } });wx.requestPayment的参数来自后端调用微信统一下单接口createOrder后返回的支付参数前端不要自己拼签名不要自己把金额和订单号传给微信。5.3 核销码展示与刷新订单支付成功后用户在我的订单列表点“查看核销码”加载一个展示页。核销码页面最好做得简单干净大号核销码文字 二维码 场地信息 使用须知并做屏幕常亮处理。这里有一个小的体验优化如果用户是预约当天到场核销码页面打开后应该自动拉取订单最新状态防止用户已经核销完但页面还停留在“待核销”。我加了 onShow 刷新逻辑顾客和前台都不会产生“到底核销了没有”的疑惑。5.4 取消预约与退款策略预约系统的取消策略不能太宽松否则场地空置率会升高。我当时定的规则是预约开始时间前 2 小时以上用户可自行取消退款原路退回预约开始时间前 2 小时内不可在线取消如需取消必须联系场馆运营已核销订单不允许取消。取消和退款的后端逻辑同样要带状态约束只有“已支付待使用”且当前时间距离开始时间大于 2 小时的订单才能执行取消和退款。退款调用微信支付退款接口后把订单状态置为已取消对应场次释放。6. 上线后最容易踩的坑并发预约、回调丢失、时间边界系统跑起来容易稳定跑起来才是真考验。下面这几个问题是我在实际运行中真实遇到并解决的每一个都值得在做二次开发时提前规避。6.1 并发下同一场地同一时段被重复预约第一版做冲突检测时我只在创建订单前查了一次场次表状态。结果上线第二天晚上高峰时段用户同时约同一块场地后台出现两个待支付订单都锁定了同一时段。两个人都支付成功前台只能当场协调换场体验极差。原因很明确查询和插入之间不是原子的两个请求同时查到“场次可约”然后各自插入订单。修复方案是在事务里加行级锁先锁定场次记录再判断状态Db::startTrans(); try { $schedule Db::name(schedule) -where(id, $scheduleId) -lock(true) -find(); if ($schedule[status] ! 0) { throw new \Exception(该时段已被预约); } // 更新场次为锁定创建订单 Db::name(schedule)-where(id, $scheduleId)-update([status 1]); Db::commit(); } catch (\Exception $e) { Db::rollback(); return error($e-getMessage()); }lock(true)会生成SELECT ... FOR UPDATE其他事务在这条记录提交前会阻塞等待。这样就把“检查与更新”变成了原子操作彻底解决并发重约问题。注意使用事务时连接必须保持同一个不能中途重新获取连接否则锁无效。6.2 支付回调丢失导致订单卡在待支付还有一种情况用户确实支付成功了但微信回调因为网络抖动或者服务器响应超时没有到达订单状态一直停在“待支付”用户以为没约上又去重复下单支付。这种问题不能只靠“重新推送回调”最好做一个主动查单兜底。在用户进入订单详情页时如果订单状态是待支付且创建时间超过一定时长前端调后端接口后端再调微信支付orderquery接口主动查一次订单状态$result WechatPay::orderQuery($orderNo); if ($result[trade_state] SUCCESS $order-status 0) { // 手动确认订单已支付更新本地状态 }一般高峰期 1~2 秒内可完成主动查单用户体验几乎无感知。这个方法在回调缺失场景下非常可靠。6.3 时间边界和时区预约系统对时间极其敏感。我第一次部署时服务器用的 UTC 时间用户约的晚上 8 点场地在后台看到却是下午 1 点核销员当场懵了。解决方案修改 PHP 时区在php.ini里设置date.timezone Asia/Shanghai修改 MySQL 时区执行SET GLOBAL time_zone 08:00或在连接数据库的 DSN 里带上后端统一用时间戳存储前端展示时再格式化涉及场次的日期比较如2025-06-20这种字符串只做日期维度比较不参与时区转换。时间边界还包括状态机里“已过期”的判断。如果用户下单时是 23:50设置的 15 分钟支付有效期跨过自然日定时任务判断过期时要用“创建时间 15 分钟”和当前时间比较而不是比较日期。6.4 二次开发时值得留意的几个 FastAdmin 细节最后收尾时分享几个我在做 FastAdmin 二次开发时觉得特别有用的点FastAdmin 列表页普通字段是可以直接关联表显示的但要提前在模型里定义好belongsTo关联并用with预加载避免列表页加载时产生大量 SQL 查询自定义按钮不要直接内联onclick写一堆 JS尽量在 JS 里通过Table.api.addon维护方便多个页面复用API 返回的 JSON 建议统一包一层{ code: 1, msg: , data: {} }结构小程序端 axios 封装内部直接解析 data不要每个控制器各写一套返回格式后台入口admin.php上线务必改名或者用 Nginx 对后台路径加 IP 白名单。不要嫌麻烦预约系统的核销后台一旦泄露弱口令损失的不只是数据还有用户的支付记录。这套系统从开发到稳定运行大约花了两周时间期间把预约、支付、核销的业务闭环完整走通后续又花了几天补了数据统计和核销员权限。回头来看最核心的收获不是代码量多少而是把“预约”这个看似简单的业务拆成了明确的模型、状态、约束三个层次。如果你正要上手类似项目我的建议是先用这套源码把完整流程跑通再根据自己场馆的实际运营情况改价格策略、加会员等级、做场次批量生成。预约系统的难点从来不是页面多花哨而是底层状态被约束得足够严谨。按这个思路往下做后面加新功能会顺手很多。本文还有配套的精品资源点击获取

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

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

免费获取报价