简介:面向停车场系统开发者及运维人员,这份完整源码覆盖H5小程序端、管理后台与后端服务,可实现相机识别自动计费、在线缴费、附近停车场查询、预约车位及断网断电人工接管等功能。后端采用Java技术栈,以SpringBoot2、Dubbo、MySQL/MongoDB/Redis为核心,配合Netty4消息推送,后台管理基于vue-typescript-admin-template,灵活兼容主流停车场硬件。资源共1239个文件,压缩包仅8.46MB,主要包含276个class、213个java、172个png、83个xml及若干vue、js、sql、properties等类型,分别对应编译产物、源代码、界面图片、配置与数据库脚本,结构清晰便于二次开发。已有1421人浏览学习。随包提供部署说明及caifeng数据库创建指引,能帮助从环境配置、数据初始化到功能调通完成落地上线,适合具备一定Java基础、希望快速搭建商用停车场系统的开发人员。
1. 停车场系统源码:一套能本地跑通的小程序加前后端闭环
做停车场的项目,最怕的不是写代码,而是业务方今天要“车牌识别对接”、明天要“月卡用户不扣费”、后天又要“小程序上看不到入场记录”。市面上能搜到的停车场系统源码不少,但大多数要么只给一个后端接口文档,要么只给一个静态小程序页面,真正能拿到手、本地启动、前后端打通、再照着部署说明上线跑起来的完整工程,反而是少数。这套「停车场系统源码」的价值就在这里:它包含了微信小程序前端、管理端后端服务,以及一份能让你从零把服务跑起来的部署说明,覆盖的是一个真实停车场最核心的闭环——车辆入场、计费、缴费离场、订单查询。
适合谁来读?一类是接外包或做毕业设计的开发者,需要一套结构清楚、能改能扩展的基线工程;另一类是物业或停车运营方自己的技术同学,想把现有道闸设备和计费逻辑先小范围验证一遍。下面按我实际折腾这类工程的习惯,把拿到源码后的第一件事、最容易踩的坑、以及必须调整的参数,一条条拆开讲。
2. 先读懂工程结构再动手:目录划分、技术栈与一次完整的数据流
2.1 拿到压缩包后第一件事:别急着启动,先画数据流
很多人在本地打开停车场系统源码后,习惯性先npm install再npm run dev,结果小程序端请求后端接口直接失败,或者管理后台登录页白屏,于是开始怀疑源码有问题。我一般会先花二十分钟把整个工程的目录结构过一遍,确认它属于哪种组织方式。
常见做法是,这类源码压缩包解压后会有三个明显的顶层目录:server(后端服务)、admin(管理后台前端)、miniprogram(用户端微信小程序)。有的工程会把管理后台和小程序合并成一个frontend目录再用pages和admin区分路由路径。目录命名不一定完全一致,但核心角色不会变。
parking-lot-system/ ├── server/ # 后端服务(Node.js + Express 或 Spring Boot) │ ├── src/ │ │ ├── controllers/ # 接口层:处理请求参数、调用 service │ │ ├── services/ # 业务层:计费逻辑、订单状态流转 │ │ ├── models/ # 数据模型:车辆、订单、停车场、月卡 │ │ ├── routes/ # 路由注册 │ │ └── config/ # 数据库连接、微信小程序配置 │ ├── package.json │ └── README.md ├── admin/ # 管理后台(Vue 或 React) │ ├── src/ │ │ ├── views/ # 停车记录、收费规则、月卡管理页面 │ │ ├── api/ # 封装的后端接口请求 │ │ └── router/ │ └── package.json └── miniprogram/ # 微信小程序用户端 ├── pages/ │ ├── index/ # 首页:查找停车场、剩余车位 │ ├── profile/ # 我的车辆、月卡、订单记录 │ └── pay/ # 缴费页 ├── utils/ ├── app.js └── project.config.json目录结构看清楚之后,下一步是定位“部署说明”文档。这份文档一般会写在 README 里,或者独立成docs/部署说明.md。不要跳过它直接看代码,因为数据库初始化脚本和执行顺序通常只在部署说明里写清楚。先把文档里提到的环境要求记下来,再对照本地环境逐项核对。
2.2 一次入场缴费完整走一遍:从摄像头抬杆到小程序支付回调
理解了目录还不够,必须弄明白一条完整的数据流:车辆入场时,道闸摄像头识别车牌,系统记录入场时间,生成一条进行中的订单;等车主要走时,在小程序里输入车牌查询订单,系统根据停车时长套用计费规则算出金额,用户支付后,系统回调更新订单状态为“已支付”,道闸抬杆放行。
用代码来看这条链路会清晰很多。下面是一段典型的订单查询接口实现(Node.js 风格),很多停车场源码的后端都长这样:
// server/src/controllers/order.js const Order = require('../models/order'); const ParkingLot = require('../models/parking_lot'); const calcFee = require('../services/fee_calc'); // 小程序端按车牌查订单 exports.getOrderByPlate = async (req, res) => { const { plateNo, lotId } = req.query; if (!plateNo || !lotId) { return res.status(400).json({ code: 1, msg: '车牌和停车场ID不能为空' }); } // 查询该停车场下、该车牌未支付且未离场的订单 const order = await Order.findOne({ where: { plateNo, lotId, status: 'pending' } }); if (!order) { return res.status(404).json({ code: 1, msg: '未找到进行中的停车订单' }); } // 计算当前时长和费用 const durationMinutes = Math.floor((Date.now() - order.entryTime) / 60000); const fee = calcFee(durationMinutes, order.vehicleType); res.json({ code: 0, data: { orderId: order.id, entryTime: order.entryTime, durationMinutes, fee } }); };这段代码的重点有两个。第一,查询条件是plateNo + lotId + status='pending'三者同时命中,避免多停车场场景下串单。第二,费用不是查出来的,而是实时计算的,时长一变化金额就变,这符合停车场“按分钟计价”的业务习惯。你在改自己项目时,最需要替换的就是calcFee这个函数——免费时长、首小时单价、后续单价、单日封顶价,全部集中在一个纯函数里,方便写单元测试。
2.3 管理后台与小程序的登录链路:两套身份体系别搞混
停车场系统里存在两套登录,这是新手最容易混淆的地方。普通车主使用微信小程序时,走的是微信登录,后端拿到code换 openid,再绑定手机号和车牌;而停车场管理员或物业人员使用管理后台时,走的是传统的账号密码登录,部分工程会套一层 RBAC 权限模型。两套体系的 session 管理完全不同,小程序端通常用自定义 token,管理后台可能用 JWT。
看源码时,优先看后端routes目录里是否区分了client和admin两套中间件,或者接口前缀是否做了区分。比如/api/client/order和/api/admin/order/list,前者要求微信登录态,后者要求管理员角色。改造时千万不要把两套中间件混用,不然会出现“车主能查所有订单”这类权限问题。
3. 小程序端核心模块:登录获取手机号、车牌绑定与缴费页的轮询逻辑
3.1 从wx.login到手机号授权:一个完整的登录链路实现
停车场小程序区别于普通商城小程序的地方在于:手机号不只是营销触点,它是绑定车牌、接收入场提醒的凭证。现在微信已经把getPhoneNumber收紧为“必须企业主体小程序且通过认证才能调用”,个人主体小程序无法使用,这是硬件门槛,源码救不了你。但代码逻辑是通用的。
下面是一段标准的登录流程实现,放到了小程序的app.js里:
// miniprogram/app.js App({ onLaunch() { // 静默登录:拿code换openid和自定义token wx.login({ success: async (res) => { const { code } = res; const loginRes = await wx.request({ url: 'https://your-api.com/api/client/login', method: 'POST', data: { code } }); if (loginRes.data.code === 0) { wx.setStorageSync('token', loginRes.data.data.token); this.globalData.userInfo = loginRes.data.data.userInfo; } } }); } });注意wx.login返回的code有效期只有五分钟且只能使用一次。后端拿着这个 code 调用微信的jscode2session接口换取 openid 和 session_key。openid 是用户的唯一标识,后端发 token 时直接把 openid 作为 payload 的一部分,以后每个请求都带着 token,后端解析即可,不需要每次都查数据库确认用户身份。
而获取手机号则需要用户主动点击触发。现在的推荐写法是button组件上绑定open-type="getPhoneNumber",在回调里拿到code(注意,不是手机号本身),把code发给后端,由后端用 access_token 调用微信接口换取手机号。这个 code 也是一次性的。
<!-- miniprogram/pages/profile/bind-phone.wxml --> <button open-type="getPhoneNumber" bind:getphonenumber="onGetPhoneNumber"> 绑定手机号 </button>// 对应的事件处理 async onGetPhoneNumber(e) { const { code } = e.detail; if (!code) { wx.showToast({ title: '已取消授权', icon: 'none' }); return; } const res = await wx.request({ url: 'https://your-api.com/api/client/bind-phone', method: 'POST', data: { code } }); if (res.data.code === 0) { wx.showToast({ title: '绑定成功', icon: 'success' }); } }逻辑说明:前端只负责收集 code,后端拿 code 去换手机号,这样手机号不会经过前端存储,降低隐私合规风险。如果你要测试这段逻辑,需要准备一个已认证的企业小程序,并在后台配置好服务器域名白名单,开发工具的“不校验合法域名”选项只能帮你绕过本地调试,真机体验时还是会拦截。
3.2 车牌绑定与默认车辆切换:一个容易做砸的交互细节
停车场小程序的首页一般长这样:顶部显示停车场名称、剩余车位,中间是“输入车牌查询停车费”,底部是我的车辆列表。真正的难点在车辆管理——一个家庭可能有两辆车,用户要能添加多辆车,设置其中一辆为默认车辆,查询订单时可以一键切换。
// miniprogram/pages/cars/index.js const cars = [ { id: 1, plateNo: '粤B12345', isDefault: true }, { id: 2, plateNo: '粤A67890', isDefault: false } ]; Page({ data: { cars }, setDefaultCar(e) { const id = e.currentTarget.dataset.id; const carsCopy = this.data.cars.map(car => ({ ...car, isDefault: car.id === id })); this.setData({ cars: carsCopy }); wx.setStorageSync('defaultCarId', id); }, deleteCar(e) { const id = e.currentTarget.dataset.id; const remain = this.data.cars.filter(car => car.id !== id); this.setData({ cars: remain }); // 这里需要调用后端接口,不能只改本地 } });这里有个“血泪经验”:很多新手在小程序端删车牌时只调setData更新页面数据,没发请求给后端,结果页面显示删了,一刷新又回来了。删除车牌必须走后端接口,同时要判断这辆车是否有未支付的订单——如果订单未支付就把车牌删了,后续查询会查不到,用户无法缴费离场。工程代码里一般会返回一个明确的错误码,比如4001表示“该车牌有未完成订单”,前端拿到后要提示用户先去缴费。
3.3 缴费后的订单状态更新:靠支付回调而非前端轮询
小程序端缴费时,最稳妥的方案是调用wx.requestPayment发起微信支付,支付成功后,微信服务器会向后端配置的支付回调地址发送通知,后端在回调里更新订单状态,同时向小程序端推送“支付成功”的结果。有些工程为了省事,会在前端收到success回调后直接调用“确认订单完成”接口,这存在风险:如果用户支付成功但前端没收到回调,或者前端被杀掉,订单会一直卡在“已支付待确认”状态。
所以在做缴费页时,关注源码里支付回调的实现。下面是典型的支付回调伪代码:
// server/src/services/wxpay_callback.js const crypto = require('crypto'); // 微信支付V3回调:验签 + 解密 exports.handlePayCallback = async (req, res) => { const { headers, body } = req; const signature = headers['wechatpay-signature']; const timestamp = headers['wechatpay-timestamp']; const nonce = headers['wechatpay-nonce']; const serial = headers['wechatpay-serial']; // 用微信支付平台证书公钥验签,防止伪造回调 const valid = verifySignature(signature, timestamp, nonce, body); if (!valid) { return res.status(401).json({ code: 'FAIL' }); } // 解密 resource 中的数据,拿到 out_trade_no(商户订单号) const decrypted = decryptAes256Gcm(body.resource); const { outTradeNo, transactionId } = decrypted; // 更新订单状态:pending -> paid const order = await Order.findOne({ where: { outTradeNo } }); if (order && order.status === 'pending') { order.status = 'paid'; order.payTime = new Date(); order.transactionId = transactionId; await order.save(); } // 回复微信:成功接收 res.json({ code: 'SUCCESS', message: '成功' }); };支付回调的坑通常集中在“幂等性”上:微信支付回调可能会发送多次,后端必须保证同一次回调重复进来时,不会把订单状态从“已支付”改回“未支付”,也不会重复累计收入统计。上面代码里if (order.status === 'pending')就是幂等保护,已经支付过的订单直接跳过更新逻辑。
4. 后端服务与计费引擎:订单状态机、并发扣费与接口安全
4.1 计费规则怎么设计:不要写死在接口里,要进数据库
停车场系统的技术核心不在登录,而在计费规则。不同停车场有完全不同的计费策略:有的前 30 分钟免费、首小时 10 元、之后每小时 5 元、24 小时封顶 60 元;有的夜间(20:00—次日 08:00)按次收费;有的区分新能源车和燃油车。把这些规则硬编码在接口里,等于每次调价都要改代码、重新部署。我见过太多改造失败的工程,问题都出在“计费规则没有表驱动”。
好的做法是设计一张fee_rule表,存储规则 JSON 或分段的费率设置:
CREATE TABLE `fee_rule` ( `id` int(11) NOT NULL AUTO_INCREMENT, `parking_lot_id` int(11) NOT NULL COMMENT '停车场ID', `vehicle_type` varchar(20) NOT NULL COMMENT 'vehicle_type: car / truck / new_energy', `free_minutes` int(11) NOT NULL DEFAULT 0 COMMENT '免费时长(分钟)', `first_hour_fee` decimal(10,2) NOT NULL COMMENT '首小时费用', `hourly_fee` decimal(10,2) NOT NULL COMMENT '超出后每小时费用', `daily_cap` decimal(10,2) NOT NULL DEFAULT 0 COMMENT '24小时封顶,0表示不封顶', `night_fee` decimal(10,2) NOT NULL DEFAULT 0 COMMENT '夜间按次收费,0表示不启用', `updated_at` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `idx_lot_type` (`parking_lot_id`, `vehicle_type`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;对应的计费函数可以这样实现,计算逻辑要尽量纯,不要在里面取数据库,方便做单元测试:
// server/src/services/fee_calc.js exports.calcFee = (durationMinutes, vehicleType, rule) => { if (!rule) { throw new Error('未配置计费规则'); } // 免费时长内,直接返回0 if (durationMinutes <= rule.freeMinutes) { return 0; } // 超出免费时长后,从最后一段收费时间开始计算 const billableMinutes = durationMinutes - rule.freeMinutes; const hours = Math.ceil(billableMinutes / 60); let fee = rule.firstHourFee; if (hours > 1) { fee += (hours - 1) * rule.hourlyFee; } // 封顶:如果启用了 dailyCap 且费用超出,则按封顶价 if (rule.dailyCap > 0 && fee > rule.dailyCap) { fee = rule.dailyCap; } return Math.round(fee * 100) / 100; };参数说明:Math.ceil把不满一小时的时长向上取整,这是停车场最常见的计费习惯——只要你停了 61 分钟,就要按 2 小时收费。Math.round(fee * 100) / 100是为了避免浮点误差,比如 0.1 + 0.2 在 JS 里不等于 0.3 的问题,计费金额必须精确到分。如果你改造成 Java 后端,用BigDecimal而不是double,否则银行对账时你会非常痛苦。
夜间计费在真实停车场里是最容易出“玄学问题”的。因为夜间规则不是简单地“超过某时间就切换计费模式”,而是“入场在白天、离场在夜间”跨时段。处理方式一般是:如果入场时间在夜间时段内,直接按夜间价收;如果入场在白天但停车时长跨越了夜间开始点,则需要把白天和夜晚分段计算。这个逻辑建议你独立成函数,处理跨天场景(如“白天入场停到次日凌晨”),不要硬套上面的纯函数。
4.2 订单状态机:pending、paid、closed 与异常订单
停车场订单的状态变化比一般电商订单要简单,但依旧有一些边界情况容易翻车:车进场后产生pending订单,用户支付成功变为paid,道闸抬杆放行后变为closed(离场完成)。但如果用户支付后一直没离场呢?或者支付超时订单要不要自动释放?常见的状态定义如下:
pending:入场后待支付,可参与计费实时计算。paid:已支付,待离场。此状态下计费不再增加,但车辆还在场内。closed:已完成离场,订单归档。exception:异常订单,如入场记录没有对应离场记录,或支付金额与计费不符。
我建议你拿到源码后,先全局搜status字段的赋值点,看有没有遗漏的流转路径。很多源码会漏掉“已支付后再次查询”的处理——订单变成paid之后,如果小程序端再次查费,应该返回“该订单已支付,无需重复缴费”,同时提供离场二维码或出门凭证,而不是重新计算费用并生成一笔新订单。
// 查询时增加状态判断 if (order.status === 'paid') { return res.json({ code: 0, data: { orderId: order.id, status: 'paid', msg: '订单已支付,请直接离场' } }); } if (order.status === 'closed') { return res.json({ code: 1, msg: '订单已完成,如有疑问请联系停车场管理员' }); }4.3 并发和重复提交:订单创建接口的幂等保护
停车场小程序里一个典型的并发场景是:用户手快连续点了两次“确认支付”,或者网络重试导致同一个订单请求被发送两次。如果后端不做幂等保护,数据库里会出现两条相同车牌、相同停车场的重复订单,计费会乱套。
解决方式有两种:第一种是前端的按钮加 loading 锁,放一次点击请求;第二种是后端在创建订单时,先用plateNo + lotId + status=pending查重,存在就直接返回已有订单,不再创建。前者改善体验,后者保证数据不坏。工程源码里一般两种都会做,你拿到代码后要确认后端是否真的做了查重,有些工程只做了前端防抖,这是不够的。
// 创建订单(入场记录) exports.createOrder = async (req, res) => { const { plateNo, lotId, vehicleType } = req.body; // 幂等保护:相同车牌、相同停车场、未支付订单已存在则复用 const existOrder = await Order.findOne({ where: { plateNo, lotId, status: 'pending' } }); if (existOrder) { return res.json({ code: 0, data: { orderId: existOrder.id } }); } const order = await Order.create({ plateNo, lotId, vehicleType, entryTime: new Date(), status: 'pending' }); res.json({ code: 0, data: { orderId: order.id } }); };这个接口还有一个隐藏要求:入场时间必须以服务端时间为准,不能信任小程序传上来的时间。小程序的设备时钟可能不准,用户离线时甚至可能传一个昨天的时间。服务端解码请求到达时间后,统一用new Date()生成入场时间,对账时才不会有出入。
4.4 前后端接口鉴权:小程序 token 与管理员 JWT 的差异化策略
停车场系统有两类接口,鉴权策略是分开的。小程序端的每个请求都用自定义 token,放在请求头Authorization里。token 的过期时间可以设短一些(比如 2 小时),配合微信登录的静默刷新来续期。管理后台的 JWT 则可以设 7 天有效期,并加入角色字段,管理员接口在路由层做角色校验。
查看后端源码时,重点确认两个文件的覆盖率:middlewares/auth.js是否挂到了所有/api/client/*路由上;middlewares/adminAuth.js是否挂到了所有/api/admin/*路由上。如果某些敏感接口(比如订单列表)没有挂鉴权中间件,任何人都可以拿着 Postman 直接调用,这就是一个典型的信息泄露漏洞。
5. 部署与配置说明:本地跑通到服务器上线的避坑清单与排查方法
5.1 环境准备:Node.js、MySQL、Redis、微信开发者工具与 HTTPS 证书
本地跑通这套停车场系统源码,需要准备的环境包括:Node.js(12 以上,V14 最稳)、MySQL 5.7 或 8.0、Redis(用作 session 或缓存,部分工程也会用它做计费时的分布式锁)、微信开发者工具(用于运行小程序端)。后端部署文档一般会要求先npm install再配置.env文件,把数据库连接串和微信小程序的appid、secret填进去。
这里有一个高概率翻车的点:MySQL 8.0 的默认认证插件是caching_sha2_password,而旧版 Node.js MySQL 库不支持,启动后端时直接报ER_NOT_SUPPORTED_AUTH_MODE。解决办法是在建库时指定mysql_native_password,或者升级数据库驱动。这是部署说明文档里经常不会写到的坑。
# 用命令行创建数据库并指定认证方式 CREATE DATABASE parking_lot DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; CREATE USER 'parking_user'@'localhost' IDENTIFIED WITH mysql_native_password BY 'your_password'; GRANT ALL PRIVILEGES ON parking_lot.* TO 'parking_user'@'localhost'; FLUSH PRIVILEGES;初始化数据库时,不要直接拿可视化工具导入 SQL 脚本了事。建议按顺序执行:先建库,再导入表结构,最后导入基础数据。基础数据里通常会包含一个默认管理员账号和一个测试停车场记录,如果漏掉这一步骤,管理后台登录进去是空数据,显示不出“停车记录”模块。
5.2 微信小程序端配置:appid、服务器域名与开发环境的三个开关
小程序端要走通,必须改三处配置:project.config.json里的appid、utils/request.js里的 API 请求基地址(本地调试时改成http://127.0.0.1:3000)、以及微信公众平台后台的“服务器域名”白名单。如果你只用本地模拟器调试,可以在微信开发者工具的“详情-本地设置”里勾选“不校验合法域名、web-view(业务域名)、TLS 版本以及 HTTPS 证书”,这样请求http://localhost不会报错。
但真机预览时,这个开关不生效,必须走真实域名且是 HTTPS。线上部署时,建议用 Nginx 反向代理后端服务,配置好 SSL 证书,这样小程序端只暴露一个域名,后端端口不直接暴露公网。Nginx 配置里有两个关键点:一是静态文件缓存,二是 WebSocket 长连接的支持(如果工程里做了“车辆状态实时上报”功能)。
server { listen 443 ssl; server_name api.your-parking.com; ssl_certificate /etc/nginx/ssl/fullchain.pem; ssl_certificate_key /etc/nginx/ssl/privkey.pem; location /api/ { proxy_pass http://127.0.0.1:3000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } }注意:proxy_pass http://127.0.0.1:3000结尾不要加/,加了会把/api前缀吞掉。很多部署文档没讲这个细节,结果小程序端请求/api/client/order被反向代理成了/client/order,后端 404。
5.3 避坑清单:停车场源码部署中最常见的五个问题
下面按“现象 → 原因 → 解决”列出我在本地跑这套工程时踩过的坑,每一条都是实际调试过的,新手可以直接对着排查。
坑一:后端启动无报错,但小程序请求接口全部超时。
现象:npm run dev输出“server running at port 3000”,但小程序端wx.request一直卡在 pending。原因:后端监听的地址是127.0.0.1,而小程序模拟器访问的是localhost或局域网 IP,部分网络环境下 IPv6 解析导致连不上。解决:把后端监听地址改为0.0.0.0,或在小程序端请求基地址里写http://127.0.0.1:3000(注意不要写localhost,真机调试时改为局域网 IP)。
坑二:车牌输入框无法提交中文。
现象:小程序端车牌输入框绑定的是input组件的bindinput,用户输入“粤B12345”后,点击查询提示“车牌号不能为空”。原因:部分源码里车牌输入框用了type="number",数字键盘把中文过滤掉了。解决:改用type="text"并加大写转换value.toUpperCase(),同时做车牌号格式的正则校验(如^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领A-Z]{1}[A-Z]{1}[A-Z0-9]{4}[A-Z0-9挂学警港澳]{1}$)。注意新能源车牌比传统车牌多一位,正则要兼容 6 位和 7 位两种格式。
坑三:支付回调一直不触发。
现象:小程序端wx.requestPayment支付成功,但后端订单状态没变成paid。原因:微信支付回调要求外网可访问的 HTTPS 地址,本地http://127.0.0.1:3000/wxpay/callback微信服务器根本访问不到。解决:本地测试时用内网穿透工具把本地端口暴露到公网,或者在服务器上先部署一版再联调支付。这个环节最容易劝退新手,建议把支付联调放在整体部署的后面,先把无支付的流程走通。
坑四:数据库导入 SQL 时报Unknown collation: utf8mb4_0900_ai_ci。
现象:MySQL 5.7 执行从 8.0 导出的 SQL 脚本报错。原因:utf8mb4_0900_ai_ci是 MySQL 8.0 新增的排序规则,5.7 不认识。解决:把 SQL 脚本里的utf8mb4_0900_ai_ci全局替换为utf8mb4_general_ci,再执行。类似的还有utf8mb4_unicode_ci,兼容性更好。
坑五:管理后台登录后跳转空白页,控制台报路由守卫死循环。
现象:登录接口返回成功,前端跳转到/dashboard后又被重定向回/login。原因:路由守卫里没有正确处理 token 更新。解决了 token 的存储 key 不一致——登录时用的是localStorage.setItem('token', ...),守卫里读的是sessionStorage.getItem('admin_token'),两个 key 对不上。解决:统一 token 的存取 key,并在axios响应拦截器里对 401 状态做统一处理,而不是在每个页面都单独跳登录。
5.4 上线前要检查的四个业务开关
部署到服务器之前,建议花半小时检查源码里这几个业务配置项——它们的默认值不一定符合你的停车场场景。
第一个是“免费时长”和“入场缓冲时间”。真实停车场里,车辆进场后 15 分钟内出场一般不收费,但系统如果只按entryTime计算,道闸在 14 分 59 秒抬杆时,订单却已生成为需支付状态,车主会投诉。你需要确认源码里是否有一个“免费时长内自动关闭订单”的任务,如果没有,要补一个定时任务扫描免费时长内已离场的订单并自动关闭。
第二个是“月卡车辆免计费”。小程序端绑定的车牌分为临时车和月卡车,月卡车进出场时应跳过计费逻辑,直接放行。很多源码只做了临时车的计费,月卡车没有接入状态判断,导致月卡用户在出场时被要求缴费。
第三个是“车位剩余数量”的实时扣减。入场时total_space - 1,离场时+1,如果部署的是多实例后端,必须用 Redis 的原子操作,不能直接查库再更新。
第四个是“订单支付超时自动取消”。有些停车场要求入场后 15 分钟内必须支付。后端需要一个定时任务把超时未支付的pending订单标记为exception或自动关闭。
6. 进阶技巧与验证思路:从本地跑通到敢于真机演示的最后一个环节
6.1 全局搜索硬编码:找出所有写着“测试”的关键配置
本地跑通不等于这个项目真的能交付。我建议你拿到源码后,全局搜索test、demo、123456、placeholder和localhost这五个词,把测试环境留下的硬编码全部揪出来。最典型的是后端config里的数据库密码和管理员初始密码,很多源码的默认配置是root/123456,上线前不修改等于裸奔。
# 在工程根目录搜索硬编码配置 grep -r "123456" --include="*.js" --include="*.json" --include="*.env" . grep -r "your-api.com" --include="*.js" .搜索结果里往往会有三到五处需要替换的内容,包括微信支付商户号、小程序 appid、数据库连接、管理后台的密钥签名。这个步骤不花时间,但能避免上线后出现“所有人都知道管理员密码”的严重事故。
6.2 用真实道闸模拟做一次端到端验证:从入场到离场 8 个检查点
在正式投入使用之前,至少要做一次人工端到端验证。准备一个真实或模拟的车牌,依次走完这 8 个检查点,每个步骤记录耗时和接口返回,确认整条链路没有断点:
| 步骤 | 操作 | 预期结果 |
|---|---|---|
| 1 | 小程序登录 | 拿到 token,用户信息入库 |
| 2 | 绑定手机号 | 手机号成功写入用户表 |
| 3 | 添加车牌 | 车牌入库并设为默认 |
| 4 | 模拟入场(本地创建订单) | 生成pending订单,车位减一 |
| 5 | 小程序查询费用 | 实时返回停车时长和金额 |
| 6 | 发起支付 | 订单变为paid,支付回调日志输出 |
| 7 | 模拟离场 | 订单变为closed,车位加一 |
| 8 | 查历史订单 | 订单列表中该条记录状态正确 |
其中最容易被忽略的是第 4 步的“模拟入场”。真实停车场中入场记录来自道闸摄像头识别,但本地开发环境没有真实摄像头,所以要么在管理后台手动创建订单,要么直接调用后端的创建订单接口。不管哪种方式,都要确认入场记录的时间戳正确。
6.3 两个调试效率神器:小程序抓包与定时任务日志
真机调试停车场小程序时,我常用的手段是用抓包工具看小程序的请求明细。在电脑上启动抓包工具,配置好 HTTPS 证书和代理,手机端小程序的所有请求都会经过电脑,能看到每个接口的 URL、请求头、请求体和响应。这个手段在排查“为什么支付回调没触发”“为什么接口报 401”时效率极高,比在小程序里打断点调试要快得多。
抓包的重点不是看字段,而是看时序:小程序端请求了哪些接口,接口的顺序是否符合预期。比如登录时,正常顺序应该是先wx.login再请求后端登录接口,如果发现wx.login没有触发就去请求业务接口,多半是app.js里没有正确处理异步时序——wx.login的回调还没执行完,页面已经发起了业务请求。
后端定时任务的日志也需要重点关注。停车场系统里常驻的定时任务包括:免费时长订单自动关闭、超时未支付订单提醒、月卡到期提醒。这些任务出错时一般没有前端感知,但会在日志里留下痕迹,上线后至少每天看一次cron.log或者后端控制台输出,确认任务调度正常。我的习惯是部署时单独把日志重定向到一个文件。
nohup node server/src/app.js > /var/log/parking-lot/app.log 2>&1 &这样做的好处是,第二天早上可以直接tail -f /var/log/parking-lot/app.log查看昨晚定时任务是否运行。一些 SQL 报错、回调失败的堆栈信息都会留在这里。这个习惯帮我排掉过很多“玄学问题”——比如某个停车场凌晨计费异常,最后从日志里发现是跨天日期边界导致了时间差错误。
这套源码最值得投入的地方,不在代码量本身,而在于它给了你一个可以直接改的停车场业务基线。建议你做的第一件改造不是美化界面,而是把计费规则改成表驱动,给费用计算函数补上单元测试。全项目里最容易翻车、最影响收益的部分就是计费,多花一天时间把它筑牢,后续接真实道闸、对接支付,都只是在这个基线上补模块。希望帮到你。
本文还有配套的精品资源,点击获取