城市运动预约小程序源码拆解:排期、并发与支付核心逻辑

城市运动预约小程序源码拆解:排期、并发与支付核心逻辑 简介开源城市运动预约小程序源码是一套面向20-45岁运动爱好者、适用于大型综合文体中心及单一运动场馆的轻量级预约工具。它覆盖羽毛球、足球、篮球、健身房、乒乓球、游泳、网球等场馆预约并包含场馆动态、运动常识等模块后台支持预约时间/人数灵活配置、自定义客户填写项以及线下签到、核销、二维码自助签到等凭证核验方式还可将预约名单导出Excel并打印方便运营管理。资源包共494个文件以JavaScript184个、WXSS105个、WXML82个、JSON70个等小程序前后端源码文件为主另有PNG图片与少量HTML/TXT说明文档压缩包仅2.68MB。采用腾讯云开发方案无需自备服务器和域名已有196人浏览/学习适合开发者直接改装或学习小程序云开发实战技巧。 做运动预约类小程序尤其是面向一个城市级别的多场馆场景坑远比想象中多。最近我把一套开源的城市运动预约工具类小程序源码完整跑了一遍从前端页面到后端接口从场地排期到支付回调算是把这类型项目的骨架彻底摸清了。这套源码定位很直接不是做商城不做社交就专注搞定“用户找场地—选时段—付钱—到场核销”这条链路。如果你正准备上手类似项目或者想找一套能直接二次开发的小程序底座这篇东西应该能帮你省不少时间。1. 这个项目到底是做什么的1.1 一句话说透项目定位城市运动预约小程序本质上是一个连接“运动场馆”和“用户”的撮合工具。用户打开小程序根据地理位置或运动类型找到附近的篮球馆、羽毛球馆、网球馆、游泳馆查看某个场地的空闲时段选定时间后完成支付到店出示核销码商家端确认销账。整套流程我用一个词总结工具属性极强。这和商城类小程序有本质区别。商城小程序的逻辑是“逛—加购—下单”重点是商品展示和营销转化而运动预约小程序的逻辑是“查—约—付—核销”重点是资源排期和时段冲突处理。我见过不少团队直接用商城模板改预约功能结果一到多人抢同一时段就出问题根源就是底层的数据模型没按预约场景设计。开源的这套源码在这点上做得比较老实场地、时段、订单、核销四张核心表的关系清晰值得作为参考。1.2 解决的核心痛点传统运动场馆的预约方式有多痛我自己深有体会。大部分小型场馆还在用“微信群报名线下转账”场地管理员每天手动排期用户要反复确认有没有空场遇到爽约更是扯皮。稍微大一点的连锁场馆会买一套SaaS系统但年费不低数据还在别人手里。这套开源小程序解决的痛点很明确场地状态实时可见用户自己就能看到今天、明天、未来一周每个场地的占用情况。预约流程标准化选场地、选时段、支付、拿核销码全流程线上化不需要人工介入。爽约成本可控先付费后使用配合超时取消规则场馆方的空场损失明显减少。数据沉淀谁在什么时间用了哪个场地、复购率如何这些数据对场馆运营非常有用。2. 整体设计思路与功能拆解2.1 核心功能模块划分运动预约小程序虽然看起来功能不多但每个模块拆开都有讲究。我按源码里的实际实现把核心功能分成五块场地管理模块后台维护场馆信息包括场地名称、类型篮球/羽毛球/网球等、位置、图片、可预约的时段规则、价格规则。这里有个细节需要注意不同运动的计费方式差异很大羽毛球按小时计费篮球有时按半场计费游泳按人次计费。源码里的价格模型是“场地时段”组合定价比较灵活基本能覆盖大多数场景。时段排期模块根据管理员设置的规则自动生成未来N天的可预约时段。比如一个羽毛球馆有4片场地运营时间是9:00-22:00每1小时一个时段那系统每天自动生成4片场地×13个时段也就是52个可预约格子。生成后用户可以按日期查看已经被预约的格子置灰。预约下单模块用户选择场地和时段后创建订单并锁定时段。这个模块是并发问题的高发区必须做好防重复预约。后面我会单独讲这部分的实现细节。支付模块对接微信支付包括下单、支付回调、退款三部分。微信支付v3接口的签名机制和回调验签逻辑是很多新手容易踩坑的地方。核销模块用户到店后出示核销码场馆管理员在管理员端可以是小程序内嵌的管理页面也可以是一个独立的管理后台扫一扫或手动输入核销码完成核销。核销后订单状态变为“已完成”对应时段彻底释放。2.2 周边辅助功能除了上述核心链路这套源码还带了一些提升体验的辅助功能。比如场馆搜索和筛选支持按距离、按运动类型、按价格区间筛选订单中心用户能查看待支付、已预约、已完成、已取消的订单优惠券功能后台可以创建满减券、折扣券用于拉新和促活。这些辅助功能里我觉得订阅消息通知值得特别说一句。用户预约成功后小程序通过微信订阅消息推送预约成功提醒距离预约开始前30分钟再推送一条临期提醒。别小看这个功能它直接关系到到场率。实测下来有临期提醒的场地爽约率能降不少。3. 技术架构与选型3.1 前端原生小程序还是跨端框架这套源码的前端用的是微信小程序原生语法WXMLWXSSJS。我最初以为现在应该都用uni-app或者Taro了但看完代码发现原生实现也有它的合理性。原生小程序最大的好处是没有框架层损耗页面渲染性能更可控对微信API的调用也更直接。比如预约需要获取用户地理位置原生实现只需要wx.getLocation配合腾讯地图的逆地址解析即可如果用了uni-app还得考虑不同端的兼容封装层是否覆盖完整。对于运动预约这种工具类小程序页面结构并不复杂主要就是列表页、详情页、订单页、个人中心几个页面交互逻辑也不算重。原生语法完全够用而且源码里针对长列表渲染做了优化——场馆列表用分页加载每页10条配合onReachBottom触发下一页避免了数据量大了之后页面卡顿的问题。如果你团队里全是React或Vue背景的开发者用Taro或uni-app重写一版也不是不行但就这套源码来说原生代码的可读性已经不错了。3.2 后端与数据库设计后端源码选的是Spring Boot 2.x MyBatis Plus MySQL Redis的组合。这个选型在开源项目里非常主流生态成熟、资料多、招人容易。数据库表设计是这套源码比较出彩的地方核心表我梳理了一下场地表记录场馆信息、场地名称、所属场馆ID、运动类型、价格基准。时段表记录某个场地的某一天、某个具体时间段如“2025-06-15 14:00-15:00”包含状态字段可预约/已被锁定/已预约/已核销。订单表记录用户ID、场地ID、时段ID、金额、支付状态、订单状态。核销记录表记录核销时间、核销人、关联订单ID。这里最关键的设计是“时段”被独立成了一张表而不是在订单里去校验时间交叉。这么做的好处是判断“这个时间段还空不空”只需要查时段表的状态字段性能和代码复杂度都低很多。如果用订单表做时间区间交叉校验SQL写起来费劲不说索引还不好建。Redis在这个项目里承担了分布式锁和热点缓存两个职责。预约时会先尝试获取Redis锁key设计成“场地ID日期时段编号”这样并发请求同时抢同一个时段时只有一个线程能拿到锁并进入下单流程其余请求直接返回“该时段已被预约”。后面我会细讲这个逻辑。4. 核心逻辑的实现细节4.1 时段自动排期怎么生成我自己最感兴趣的一个模块是排期生成。你想想如果靠后台管理员手动去创建未来一周每个场地每一天的每个时段那工作量得有多大。这套源码里用了一个定时任务来解决。具体思路是在每天凌晨执行一个定时任务自动生成“从今天开始未来7天”的时段数据。比如一个篮球馆有2片场地、每天营业时间为10:00-22:00、每2小时一个时段那么系统会为明天生成2片×6个时段12条记录后天也是12条以此类推生成完7天共84条记录。生成时根据场地表里的时段规则逐一场地、逐一天、逐一规则循环插入。这里有个值得参考的点为什么不实时生成而是提前生成因为预约场景下用户查询某个日期的时段列表是一个高频操作。如果每次查询都现场计算时段势必要把所有场地规则加载到内存、循环拼接日期和时段响应时间就会变长。而提前生成后时段表里的数据已经是实体记录用户查询就是简单的SQL查表加上日期索引毫秒级返回。这就是典型的用空间换时间。补充一下源码里生成时段时会先把即将过期的旧时段标记为“过期不可约”同时对已经过了开始时间但还没被核销的时段做异常状态清理。这个清理逻辑很重要否则会出现一个诡异场景用户看到某个时段显示“可预约”但时间其实已经过了。4.2 并发预约的防超卖处理这是整个项目里最核心、也最容易写崩的部分。我实操的时候反复测过并发场景比如50个用户同时抢同一个场地的同一个热门时段系统必须保证最终只生成一个订单。源码里的方案分两步第一步Redis预占锁。用户点击“立即预约”时前端先调用后端接口后端在Redis里执行SETNX操作key为reserve:{场地ID}:{日期}:{时段编号}value为userId同时设置过期时间比如5分钟。SETNX成功了说明当前没有人在预约这个时段可以继续往下走SETNX失败说明别人正在抢或者已经被抢走直接返回“手慢了该时段已被约”。第二步数据库乐观锁占位。拿到Redis锁之后执行更新时段表状态的SQL类似UPDATE time_slot SET status 1, lock_user_id #{userId} WHERE id #{slotId} AND status 0注意这个SQL的核心是WHERE status 0这就是乐观锁的体现——只有当这个时段当前还是“可预约”状态时才能更新为“锁定”状态。如果更新的影响行数为0说明在Redis锁竞争到SQL执行之间时段状态已经被改变了这时候要释放Redis锁并向用户返回失败。做完这两步才开始创建订单、调起微信支付。这里有个时序上的小细节先锁时段再创建订单。如果反过来的话极端情况下会出现订单创建成功但时段被别人抢走订单变成了无资源可用的脏数据。我还发现这套源码在支付回调的处理上比较稳妥。微信支付成功回调后后端会更新订单状态为“已支付”同时把时段状态更新为“已预约”。回调接口做了幂等处理——即使微信因为网络原因重复推送回调后端也能通过订单号判断状态避免重复更新。4.3 支付对接中的一个容易忽略的坑支付回调验签是最容易出问题的地方。微信支付v3的回调头部有Wechatpay-Signature、Wechatpay-Timestamp、Wechatpay-Noncebody是加密的报文。实操中我发现不少人的问题是直接用语言自带的JSON库去解析请求体而平台要求使用AES-256-GCM解密容易把密文当成明文存库导致回调成功了但库里订单金额是乱码。正确的流程是先验签再解密用平台证书验签确认回调确实来自微信。拿到resource里的密文用APIv3密钥做AES-256-GCM解密。解出来之后再从明文JSON里取订单号和金额核对金额是否与订单一致。核对通过后再更新订单状态。另外一个小坑是回调地址必须配置在微信支付商户平台的APIv3回调通知里而且必须是HTTPS公网地址。我在本地调试的时候就因为回调地址填了内网IP导致支付成功但订单迟迟不变状态排查了半天才发现是这个问题。5. 部署与二次开发实操5.1 你需要准备哪些环境和配置把源码跑起来之前你需要准备好以下几样东西。这里我列一个清单按准备顺序排微信小程序AppID和AppSecret从微信公众平台申请。微信支付商户号以及APIv3密钥、商户API证书。这里注意小程序和商户号要做关联绑定。一台能跑Spring Boot的服务器推荐2核4G及以上配置操作系统最好选CentOS 7.x或Ubuntu 20.04。MySQL 5.7Redis 5.0。一个HTTPS域名并完成ICP备案。同时要在小程序后台配置request合法域名、uploadFile合法域名。配置项主要集中在后端项目的application.yml里。我把关键配置项整理成一个表方便你对照修改配置项说明示例值wx.appid小程序AppIDwx1234567890abcdefwx.secret小程序AppSecret32位字符串pay.mchId微信支付商户号1230000109pay.apiV3KeyAPIv3密钥32位字符串pay.serialNo商户证书序列号从证书中读取pay.privateKeyPath商户私钥文件路径/etc/certs/apiclient_key.pemdb.urlMySQL连接地址jdbc:mysql://localhost:3306/sports_reserve配置好之后导入源码里的sql目录下的数据库脚本然后启动后端服务。如果一切正常日志里会打印“server started successfully”并且定时任务开始自动生成明天的时段数据。5.2 前端编译和对接前端部分用微信开发者工具打开修改app.js里的baseUrl为你的后端HTTPS地址然后编译预览。这里有一个我在调试时发现的小窍门在微信开发者工具里勾选“不校验合法域名”选项可以在本地用HTTP的IP地址调试能省去很多来回配置域名的麻烦。但上线前一定要记得关闭这个选项并配置好真实合法域名。另外源码里用到了wx.login静默登录换取openid的流程。如果你之前只做过普通的网页端登录第一次看这段逻辑可能会有一点头疼。简单说就是前端调用wx.login获取一个临时code把code传给后端后端拿code加上AppID、AppSecret请求微信的接口换取openid和session_key拿到openid后在后端生成一个自定义的登录态token返回给前端后续所有需要登录的接口都带着这个token即可。这是微信小程序标准的登录链路源码里已经封装好了直接调用就行。6. 典型问题与排查技巧6.1 支付成功但订单状态未更新这是我在测试中遇到的第一个让人头大的问题微信支付页显示扣款成功但小程序订单中心里还是“待支付”。排查路径式很清晰第一步看微信支付商户平台后台确认回调通知是否发送成功。如果回调通知列表里有记录但返回码不是200说明后端处理回调时报错了。第二步看后端日志重点搜“notify”关键词确认是否进到了回调接口。如果根本没进接口检查回调地址配置是否正确、HTTPS证书是否受信任。第三步如果进了接口但处理失败多半是验签失败。常见原因是服务器时间不准导致回调请求的时间戳校验不通过用NTP服务同步一下系统时间就好。当商户没有配置回调地址或回调异常时微信支付会按照一定频率重复通知通常15秒/15秒/30秒/3分钟/10分钟…所以这类问题最终会被重试掩盖。但用户可能已经等不及了所以很多项目会额外做一个主动查询支付状态的定时任务每5分钟扫描一遍待支付超时的订单向微信发起订单查询作为回调的兜底。这个逻辑源码里没有建议你自己加上。6.2 并发测试时出现超卖我在本地用JMeter做了50并发同时抢同一个时段的压测第一次跑就有几个请求成功创建了订单但时段只有一个——这显然超卖了。排查后发现原因是我在测试时没有先初始化RedisRedis锁没有生效只剩SQL乐观锁兜底理论上也不会超卖。但看代码发现SQL的UPDATE语句里status 0的判断条件被写成了status ! 22代表已预约这就导致状态为1锁定中的时段也能被重复更新出现了漏网之鱼。把条件改回严格的status 0之后再跑压测50并发下只有1个请求成功其余返回失败表现正常。所以在这里提醒一句锁时段的条件要严格判断等于“可预约”状态不能用不等于排除法。虽然这属于源码里的一处小缺陷但恰恰是这类问题的典型代表——很多超卖问题不是没加锁而是锁的判断条件写得太宽松。6.3 预约成功后时段被释放还有一个场景值得注意。用户创建订单但一直没有支付如果这个时段一直被锁住对场馆方来说就是浪费。源码里提供了“超时自动释放”方案定时任务扫描已锁定超过15分钟且未支付的订单将时段状态重新置为“可预约”同时把订单标记为“已取消”。这个逻辑是必须的否则每天会产生大量无效锁定真正想约的人约不上。实现上要特别注意自动释放时段和用户继续支付之间会有竞争。用户可能在15分钟临界点点支付而定时任务同时释放了时段这时候支付回调回来订单还在但时段已经被释放给其他人了。处理方案是在支付回调里再做一次校验如果订单对应的时段状态已经变成别的用户的预约说明该订单已经失效需要走退款流程。这种边界情况在并发场景下一定要考虑到。最后的实操心得把全套代码跑通、再改掉几个坑之后我对这种城市级运动预约项目的技术难度有了更准确的判断它不算难但足够琐碎。真正考验功夫的不是某个单点技术而是预约状态机的正确性——从可预约、锁定中、已支付、已核销到过期释放每个状态转换都要考虑并发和异常场景。如果你准备基于这套源码做二次开发我建议先不要着急加功能而是把时段状态流转画一张状态图自己画就行不需要给谁看把所有可能的分支列出来再对着源码看是否都覆盖了。另外授权协议也要提前看清楚。这类的开源项目一般用Apache 2.0或MIT协议允许商用但要求保留版权声明如果用了GPL协议那你想闭源发布就会比较麻烦。动手改之前先确认协议免得后期商务对接时出岔子。总的来说这套源码作为学习样本或者业务原型都值得花几天时间仔细过一遍。本文还有配套的精品资源点击获取