停车场微信小程序源码:车位预约+支付闭环全解析
简介一套面向停车场运营场景的微信小程序源码工程适合计算机相关专业毕业设计、小程序开发入门与二次开发实践可快速搭建包含车位展示、地图定位等模块的移动端应用原型。压缩包共收录393个文件以png图片、json配置、js逻辑、wxml页面结构、wxss样式及vue组件为主另有map地图文件、scss/less样式辅助以及md说明、license授权信息整体4.11MB目录层级清晰便于按页面或功能模块检索学习。已有469人浏览/下载属于轻量级高复用的小程序参考项目。资料内含完整移动端页面源码与配套逻辑重点覆盖地图组件使用、页面交互与样式适配可直接导入开发工具运行调试结合现有代码还能理解uniapp项目结构、数据绑定与组件化开发思路为后续扩展或毕业设计答辩提供可展示的工程基础。1. 停车场微信小程序源码拆解一个能直接答辩、能上线的车位预约闭环凌晨一点物业在群里说出口道闸不抬杆后面积了七八台车。你打开手机想远程看一眼车位状态是不是没同步——这个念头就是这套停车场系统微信小程序源码存在的意义。它不是一个只有静态页面的壳子而是一条完整的业务闭环车主在小程序里查空闲车位、预约锁位、支付离场费用后端用 Java 处理车位状态流转和微信支付回调MySQL 落表。这几年毕业设计里出现频率很高的一条真实业务线拿来改改就能当毕设主项目或者拆成模块塞进外包项目里直接复用。适合两类人一类是拿它做课程设计、毕业设计的学生答辩时能讲清楚“并发锁位”和“支付回调幂等”就够有深度了另一类是刚接单的开发者需要一个可运行、可二开的停车场小程序底座省掉从零画原型和设计表的时间。整个源码包分小程序前端和 Java 后端两大块数据库脚本、接口文档都在压缩包里按后文步骤跑起来二十分钟内能看到第一个真实车位页面。2. 整体架构与技术栈三层分离前端不背锅2.1 前后端分离怎么分小程序、Java 后端、MySQL 的关系这套项目走的是最标准的微信小程序 Spring Boot 单体后端结构没有引入微服务那套复杂东西但层次非常清晰。小程序端只负责展示和交互所有业务判定都交给后端接口用户点“预约”按钮前端把车位 ID 传给后端后端判断车位是否空闲、能不能预约、预约后多久过期再把结果回给前端。好处是业务逻辑集中后续如果要加安卓壳或者接第三方硬件只动接口调用不用重写判断逻辑。前端用微信小程序原生框架WXML WXSS JavaScript直接在微信开发者工具里打开就能编译。后端是典型的 Spring Boot 分层结构Controller 只做参数收口Service 放业务规则Mapper 用 MyBatis-Plus 操作 MySQL。我一般接这种带小程序的源码项目第一件事就是确认后端提供的接口跟小程序里 request 的 url 是否能对上这套源码的接口路径是统一过一遍的省掉不少联调时间。parking-lot/ ├── backend/ # Java 后端工程 │ ├── src/main/java/ │ │ └── com/parking/ │ │ ├── controller/ # 接口层登录、车位、订单、支付 │ │ ├── service/ # 业务层锁位、计费、回调处理 │ │ ├── mapper/ # 数据访问配合 MyBatis 注解与 XML │ │ ├── entity/ # 数据库实体车位、订单、用户 │ │ ├── config/ # 拦截器、全局异常、支付配置 │ │ └── common/ # 统一响应体、分页工具 │ └── src/main/resources/ │ ├── application.yml # 数据源、端口、微信 appid 配置 │ └── mapper/*.xml # 手写 SQL 存放目录 ├── miniprogram/ # 微信小程序前端 │ ├── pages/ │ │ ├── index/ # 首页车位列表、空闲状态 │ │ ├── reserve/ # 预约、入场、结算支付 │ │ ├── wallet/ # 钱包、订单记录、发票信息 │ │ └── mine/ # 我的车牌、个人设置 │ ├── utils/request.js # 封装 wx.request统一带 token │ └── app.js └── sql/ └── parking.sql # 建库建表脚本含初始数据2.2 目录结构与核心文件拿到源码先看哪几个文件拿到压缩包后建议按这个顺序读代码能少走弯路。先开sql/parking.sql把表结构过一遍因为停车场的核心就在车位状态和订单状态两个字段上表设计看明白了业务代码基本猜得八九不离十。然后看backend/src/main/resources/application.yml改 MySQL 连接串和微信小程序 appid 配置这是启动后端的前置条件。最后再翻miniprogram/utils/request.js小程序端所有请求都从这里发出统一处理了 token 注入和错误提示。这里要特别强调一个关键点小程序端utils/request.js里用的 baseUrl 不是线上域名而是一段本地开发用的http://127.0.0.1:8080之类的地址。微信开发者工具默认不允许请求 http 接口需要在“详情 - 本地设置”里勾选“不校验合法域名”否则一启动就是白屏或者报 url not in domain list。这个问题排在第 5 章的避坑清单里但建议现在就知道因为几乎每个新手都会在这里卡一次。server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/parking?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: 123456 wx: appid: 你的小程序appid secret: 你的小程序secret mch-id: 微信支付商户号 pay-key: 支付API密钥把 application.yml 里的wx段单独拎出来说是因为它直接决定了微信登录和支付能不能跑通。appid和secret是小程序维度的用户在开发者后台“开发管理 - 开发设置”里能找到mch-id和pay-key是微信支付商户号维度的如果不涉及真实商户支付流程可以用测试商户号或者把支付按钮的调用暂时注释掉不影响车位预约主流程联调。2.3 数据库设计五张表撑起一套计费闭环这套核心表控制在八张以内删掉日志和审核冗余表后核心业务其实就五张特别适合拿去答辩。用户表、车牌表、车位表、预约记录表、订单表五张表相互咬合支撑起从预约到支付的全部状态流转。表名关键字段状态值设计作用memberid, openid, nickname, phone无小程序用户主体carid, member_id, plate_no无绑定车牌出场校验用parking_spaceid, lot_id, status, position1空闲 / 2预约 / 3占用车位状态流转核心reservationid, space_id, member_id, enter_time, expire_time0进行中 / 1已完成 / 2已取消预约锁位记录pay_orderid, reservation_id, order_no, amount, status0待支付 / 1已支付 / 2已退款支付与计费落地parking_space.status是整套系统最敏感的一个字段车位状态没变化后面结算、入场校验全都会跟着乱。源码里所有修改状态的操作都放在事务内执行这样设计是为了避免并发请求把车位状态改丢。reservation.expire_time是预约超时时间超过这个时间没入场车位自动释放回空闲这种机制在真实停车场里非常常见。注意改表结构或者加索引时优先保证parking_space.status和pay_order.status这两个字段上有索引。它们出现在 where 条件里的频率最高加索引对高并发场景的收益明显。3. 核心后端拆解并发锁位、计时计费、支付回调解读3.1 车位查询与锁位不让两个用户抢到同一个车位停车场业务最简单的玩法就是“查列表 - 点预约 - 缴费离场”但这套源码有意思的地方在于把“锁位”单独做了一步。查询空闲车位的接口很常规一条带状态的 SQL 加一个 limit 控制一次查询量避免小程序端一次拉太多数据。SELECT id, lot_id, position, status FROM parking_space WHERE lot_id #{lotId} AND status 1 LIMIT #{limit}这条 SQL 的逻辑很简单但要注意status 1这个条件必须和业务层配合不能只靠前端隐藏按钮来控制。用户在页面看到空闲车位后如果犹豫了十几秒再点预约期间车位可能已经被别人锁走。所以后端的预约接口必须做真正的状态校验和行级锁这也是这套源码里含金量比较高的部分。Transactional public ReserveResult lockSpace(Long spaceId, Long memberId) { // 第一层校验先确认车位存在且空闲 ParkingSpace space parkingSpaceMapper.selectById(spaceId); if (space null || space.getStatus() ! ParkingSpace.STATUS_FREE) { throw new BizException(车位状态异常请刷新后重试); } // 行级锁把这条车位记录锁住防止并发同时读到空闲状态 ParkingSpace locked parkingSpaceMapper.selectByIdForUpdate(spaceId); if (locked.getStatus() ! ParkingSpace.STATUS_FREE) { throw new BizException(手慢了车位被预约了); } // 第二层更新车位状态为预约 locked.setStatus(ParkingSpace.STATUS_RESERVED); parkingSpaceMapper.updateById(locked); // 生成预约单15 分钟内未入场自动释放 Reservation reservation new Reservation(); reservation.setSpaceId(spaceId); reservation.setMemberId(memberId); reservation.setEnterTime(LocalDateTime.now()); reservation.setExpireTime(LocalDateTime.now().plusMinutes(15)); reservationMapper.insert(reservation); return new ReserveResult(reservation.getId(), reservation.getExpireTime()); }这段代码的核心思路是“先读再校验再写”并且借助selectByIdForUpdate把行锁拿到手里。注解Transactional保证这三个步骤要么全部成功、要么全部回滚不会出现“车位改成预约了但预约单没生成”的脏状态。这里说明一下selectByIdForUpdate的原理它在数据库层面给选中的行加上排他锁直到事务提交才释放另一个线程来读同一条记录时只能等待从根上避免两个用户同时提交预约。如果去掉这层锁只靠状态判断并发高的时候大概率会出现超卖——两个用户都以为自己预约成功了。提示selectByIdForUpdate必须放在事务里才生效。如果你在 Service 方法上没有加Transactional锁不会持续到提交阶段相当于白写。3.2 计费规则实现不足 15 分钟按 15 分钟封顶逻辑是这样算的计费是停车场系统的门面功能用户可能不在乎你的表结构设计得漂亮不漂亮但一定在乎多收没多收钱。这套源码的计费规则可配置按单价、计费单位、每日封顶三个参数控制适合不同场地。核心公式是停车时长除以计费单位向上取整再乘以单价最后跟封顶值取较小的一方。public BigDecimal calculateAmount(LocalDateTime enterTime, LocalDateTime leaveTime) { long minutes Duration.between(enterTime, leaveTime).toMinutes(); // 不足一个计费周期按一个周期算 long periods (minutes CHARGING_UNIT - 1) / CHARGING_UNIT; BigDecimal amount unitPrice.multiply(BigDecimal.valueOf(periods)); // 每日封顶逻辑超过封顶金额后不再累加 if (amount.compareTo(dailyCap) 0) { amount dailyCap; } // 金额保留两位小数避免前端出现 0.30000000000000004 这种问题 return amount.setScale(2, RoundingMode.HALF_UP); }(minutes CHARGING_UNIT - 1) / CHARGING_UNIT这个写法是取整除法的向上取整技巧比Math.ceil更直观。参数说明CHARGING_UNIT是计费单位分钟比如 15unitPrice是单位价格用BigDecimal而不是double避免浮点数精度翻车dailyCap是每日封顶金额。有点良心的停车场都会配封顶价不然停一天几十块的计费谁受得了。这套源码把三个参数写进了数据字典运营人员可在后台配置不用改代码。计费相关的另一个点是“预约超时释放”。用户预约后 15 分钟内不入场车位要自动释放回空闲状态不然一个车位会被一个没有到场的人占住一整天。源码实现方式是定时任务扫描 reservation 表将超过 expire_time 且 status 还是 0 的记录改为已取消同时把对应 parking_space 的 status 改回 1Scheduled(fixedDelay 30000) public void releaseExpiredReservation() { ListReservation list reservationMapper.selectExpired(); for (Reservation item : list) { item.setStatus(Reservation.STATUS_CANCELLED); reservationMapper.updateById(item); ParkingSpace space parkingSpaceMapper.selectById(item.getSpaceId()); space.setStatus(ParkingSpace.STATUS_FREE); parkingSpaceMapper.updateById(space); } }这套实现看起来直接但要注意任务执行频率fixedDelay 30000表示上一次任务执行完成后间隔 30 秒再跑下一次。如果业务量很大定时频率太密会把数据库拖慢太疏又会让用户产生“明明预约过期了车位还锁着”的投诉。常见做法是把扫描范围限制在本小时内到期的记录配合 expire_time 上的索引查询压力可控。3.3 支付回调解读幂等处理防微信重复回调支付部分是这套系统里最能看出代码功底的地方。微信支付回调机制是“不确定性的”同一个支付结果可能会推送多次后端如果处理不当用户支付成功后订单状态被更新两遍甚至出现金额翻倍。Transactional public String handleWechatPayNotify(String orderNo, String transactionId) { PayOrder order payOrderMapper.selectByOrderNo(orderNo); // 已经处理过直接返回成功防止重复回调 if (order.getStatus() PayOrder.STATUS_PAID) { return WxPayResult.success(); } // 比对数据库中的金额和微信回调里的金额不一致直接拒绝 if (!order.getAmount().equals(callbackAmount)) { return WxPayResult.fail(金额不一致); } order.setStatus(PayOrder.STATUS_PAID); order.setTransactionId(transactionId); order.setPaidTime(LocalDateTime.now()); payOrderMapper.updateById(order); // 支付成功释放车位并记录入场 parkingSpaceMapper.finishReservation(order.getReservationId()); return WxPayResult.success(); }这段代码最关键的是第一层判断order.getStatus() PayOrder.STATUS_PAID时直接返回成功。微信收到成功响应后就不会再重发回调而这一层判断同时承担了幂等和防重复更新两个职责。但这里有一个真实的血泪经验如果只判断状态再更新在高并发下两个回调线程可能同时读到 status 为 0然后同时去执行更新。更稳的写法是直接执行一条带条件的 UPDATE 并检查影响行数比如UPDATE pay_order SET status 1 WHERE order_no ? AND status 0如果影响行数为 0 说明已经处理过。源码里两层都做了事务加条件更新单看哪一层都不容易出问题合起来才稳妥。4. 小程序前端实战登录、车位状态轮询与支付拉起4.1 微信登录code 换 token别在前端碰 appSecret小程序端的登录逻辑跟普通网页登录不一样关键区别在于小程序不能直接拿appid appSecret去微信接口换 openid那样会把密钥暴露在用户手机里任何人反编译小程序都能拿到你的 secret风险很大。正确的姿势是前端先用wx.login拿一个临时凭证 code再把 code 传给后端由后端拿着 code 加自己的 appSecret 去微信服务器换 openid 和 session_key。// 登录wx.login 拿 code后端换 openid前端只存 token const login async () { const { code } await wx.login(); const res await request({ url: /api/wx/login, method: POST, data: { code } }); if (res.code 0) { wx.setStorageSync(token, res.data.token); wx.setStorageSync(memberId, res.data.memberId); } else { wx.showToast({ title: 登录失败, icon: none }); } };后端处理这个接口时实际上就是拿前端传来的 code 去调微信的jscode2session接口换取 openid 后在 member 表里找到或创建用户再生成一个自定义 token 返回前端。之后的所有请求都在 header 里带上这个 token后端拦截器解析 token 得到用户身份。要注意 token 的有效期设计源码里默认是七天如果用户长期不用再打开需要重新登录这是常见做法不要设计成永不过期安全上容易留下隐患。4.2 车位列表与状态轮询10 秒一次的实时感用户打开首页需要看到当前哪些车位是空闲的、哪些被预约、哪些被占用。小程序端不能像 App 一样用长连接推送最常见的实现就是轮询每隔若干秒请求一次车位列表把最新的状态渲染到页面上。// 车位状态轮询每 10 秒拉一次全量列表 const startPolling () { if (this.pollTimer) { clearInterval(this.pollTimer); } this.pollTimer setInterval(async () { const res await request({ url: /api/parking/list, method: GET }); if (res.code 0) { // 全量替换避免 setData 增量更新带来脏数据 this.setData({ spaceList: res.data.list }); } }, 10000); }; const stopPolling () { if (this.pollTimer) { clearInterval(this.pollTimer); this.pollTimer null; } };这里有个很容易被新手忽略的点轮询定时器在页面隐藏或者退出时必须清除否则页面走了定时器还在后台跑既浪费用户流量还可能在你已经离开页面后弹报错。源码里在onShow里启动轮询在onHide和onUnload里停止逻辑闭环。每次轮询全量替换列表数据数据量不算大最多几十个车位性能上完全没问题如果你要接一个几百车位的大场建议改成只返回状态变化的车位增量数据或者加一个 version 字段做状态对比减少 setData 的开销。4.3 支付拉起wx.requestPayment 的标准姿势小程序端调起微信支付的代码其实很薄真正有含金量的是后端把统一下单参数算好签名返回给前端。前端拿到参数后调用微信提供的wx.requestPayment这一步不需要你做任何金额计算金额在后端算好并签名小程序端只是把这些参数原样传给微信支付。const pay async (orderNo) { const res await request({ url: /api/pay/unifiedOrder, method: POST, data: { orderNo } }); if (res.code ! 0) { wx.showToast({ title: res.msg || 下单失败, icon: none }); return; } wx.requestPayment({ timeStamp: res.data.timeStamp, nonceStr: res.data.nonceStr, package: res.data.packageValue, signType: RSA, paySign: res.data.paySign, success: () { wx.showToast({ title: 支付成功 }); // 刷新订单状态 this.fetchOrderDetail(orderNo); }, fail: () { wx.showToast({ title: 已取消支付, icon: none }); } }); };这段代码的注意点集中在参数名上后端返回的参数timeStamp是字符串类型而小程序端要求的是字符串别传成数字类型不一致微信会直接报requestPayment:fail。package是关键词不能直接作为变量名所以代码里用packageValue接收再转交给wx.requestPayment。signType的值取决于后端签名方式现在微信支付 APIv3 很多场景默认 RSA。如果支付签名不对优先去后端日志里看微信返回的具体错误码比在前端瞎猜快得多。5. 本地运行与常见问题排查从白屏到计费异常的五个坑5.1 坑一小程序请求直接被平台拦截报 url not in domain list现象小程序编译后页面能打开但所有请求都失败控制台提示url not in domain list。原因微信小程序安全策略默认不允许请求 http 接口或未配置的域名。解决在微信开发者工具里点击“详情”找到“本地设置”勾选“不校验合法域名、web-view业务域名、TLS 版本以及 HTTPS 证书”。如果真机预览依然报错需要在小程序后台的“开发管理 - 服务器域名”里配置 request 合法域名本地开发工具勾选不校验只对模拟器有效。5.2 坑二登录态一重启就失效每次打开小程序都要重新登录现象后端明明启动正常数据库里有用户数据但每次冷启动小程序后 token 都是空的或者登录后接口依然返回 401。原因常见有两种一种是前端把 token 存在内存变量里而不是wx.setStorageSync小程序一旦切后台或被回收变量被重置另一种是后端把 token 存内存 Map后端一重启所有 token 就全没了。解决前端统一走utils/request.js每次从 storage 读取 token 注入 header后端用 Redis 存 token 并设置过期时间或者用 JWT 无状态方案让后端重启不影响用户登录态。5.3 坑三并发预约同一车位出现两个成功订单现象两个用户同时点同一个空余车位的预约按钮两个人都收到“预约成功”的提示但车位只有一个。原因去掉selectByIdForUpdate之后两个请求同时读到 status 为 1 的空闲状态同时走了后面的更新逻辑数据库层面没有互相等待。解决给预约接口加回行级锁或者用乐观锁UPDATE parking_space SET status 2 WHERE id ? AND status 1再检查影响行数如果为 0 说明车位已被抢走直接返回失败。这是并发类 Bug 里最容易现场演示的典型案例也是答辩时最容易被追问的点。5.4 坑四支付回调重复执行用户被扣两次钱或者订单状态被重复更新现象用户明明只支付了一次后台订单记录里却出现重复的状态流转或者钱包余额被扣减了两次。原因微信支付回调在网络抖动时会自动重试回调接口收到同一条支付结果多次后端每次都没有做幂等判断直接执行业务逻辑。解决如 3.3 节所写处理回调前先查订单当前状态已经支付的直接返回成功另外给 pay_order 表的order_no加唯一约束从数据库层面兜底哪怕代码逻辑有漏洞第二次插入也会被数据库拒绝。5.5 坑五MySQL 中文乱码和时区差八小时现象页面上显示的车位位置和用户昵称全是问号数据库里直接看也是乱码或者订单创建时间比本地时间早了八个小时。原因MySQL 连接串里没带characterEncodingutf8和serverTimezoneAsia/ShanghaiMySQL 服务端默认字符集不是 utf8mb4时区用了系统默认 UTC。解决像第 2 章 application.yml 里写的那样在数据源连接串中显式加上useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai同时把 MySQL 表结构字符集统一改成 utf8mb4。改完这些参数后记得重启后端服务并重新执行建表脚本否则已经建好的表不会自动迁移。提示这几个坑在换数据库、换部署环境时最容易复发即使之前跑通过换到另一台机器上也可能重新踩一遍。每个项目拿到手先按这五条过一遍能省出大半天的排查时间。6. 二次开发建议给这套系统加月卡、加场、加车牌识别6.1 先加一张月卡表区分免费和计费入口停车场系统最常见的二开需求就是加月卡会员。思路不复杂新增一张 member_card 表记录车牌号、生效日期、失效日期出场结算时先判断当前车牌是否在有效月卡范围内如果是就直接放行并生成一条零金额订单否则走原有的按次计费逻辑。public boolean hasActiveMonthlyCard(String plateNo) { LocalDate today LocalDate.now(); MonthlyCard card monthlyCardMapper.selectActive(plateNo, today); return card ! null; }这个接口对接出场逻辑时注意月卡判断要在计费计算之前做而且要区分“月卡车辆入场未绑定车牌”的情况。我一般会在 entance 接口里强制校验车辆和车牌是否绑定如果车牌没绑入场时就提示用户先去小程序绑定避免出场时月卡判断找不到对应关系。6.2 往无人值守的方向改对接车牌识别相机如果手头有硬件条件可以继续把项目改成无人值守模式。方象是道闸相机识别到车牌后调用后端出场接口查询该车牌是否有预约、是否在免费时段、是否月卡。这套源码的接口设计是按“车位 ID”维度做的预约改造时可以先加一个 plate_no 字段到 reservation 表让预约记录和车牌强关联再给入场出场接口各加一个按车牌查询的版本。硬件相机厂商的接口各不相同但后端只需要暴露一个“根据车牌查询订单并结算”的接口剩下的是在硬件端把识别结果拼装成请求。从那以后我每次拿到电商、停车、预约类带支付闭环的项目都会强制先走一遍支付幂等核对、并发锁位检查、数据库连接参数确认这三件事再动手改业务代码。这套停车场系统微信小程序源码的价值不在于代码量有多大而在于它把真实业务里最常见的坑都过了一遍照着源码里的写法扩展新功能比自己从零搭要省力得多。希望帮到你。本文还有配套的精品资源点击获取