简介:这是一份停车场系统完整源码,配套小程序端与后台管理,适合需要快速搭建智慧停车平台的开发者或项目团队。系统兼容市面上主流相机设备,相机识别后自动上传云端并校验硬件序列号,支持微信、支付宝、银行接口支付,还可按停车场指定商户收款;同时提供附近停车场查询、车位预约、断电断网时岗亭app接管录入等功能。技术栈为Java SpringBoot2 + Dubbo + OAuth2,数据库采用MySQL/MongoDB/Redis,底层即时通讯基于Netty4,后台管理使用vue-typescript-admin-template。资源共1239个文件,包含Java源码与class编译产物、小程序wxml/wxss/js前端、properties/yml/xml配置、SQL建库脚本、PNG界面图等,压缩包8.46MB。已有1422人学习。整套代码目录结构清晰,附带部署说明,可直接导入数据库与开发工具二次开发,适用于毕业设计、课设及商业项目改造。
1. 停车场系统源码解决的不只是“先绑定车牌再抬杆”
拿到一套停车场系统源码,有人当天跑通,有人一周还在对账。差距不在界面,而在两处:计费规则写在哪、支付回调怎么对账。这套含前后端与部署说明的停车场系统源码,核心是要把车辆入场、计费、支付、出场全链路串成一条可靠的数据流,而不只是“先绑定车牌再抬杆”的演示工程。
它适合三类人:想给某个停车场做日常运营管理的负责人,需要从零搭后台和小程序的开发者,以及买来学习“硬件+支付+小程序”完整业务闭环的新手。你不用从摄像头协议开始造轮子,但必须搞清车牌识别结果、订单状态、计费规则这三件事在系统里是怎么流转的。
本文按“拆解系统→跑通后端→对接小程序→部署上线→验证用例”的顺序讲,所有示例都围绕一套最常见的 Java 后端 + 原生小程序方案展开,命令和参数可以直接照抄。
2. 前后端必懂的系统拆解:计费引擎、支付回调和车牌识别是三个核心
先说技术栈为什么这么选。这套源码常见做法是:后端用 Spring Boot + MySQL + Redis,小程序端用平台原生语法开发,管理后台用 Vue。选型理由很实际:Spring Boot 生态成熟、网上能查到的坑位最多,MySQL 负责订单和车位这些强一致数据,Redis 扛入场出场的高频读和防重复放行。小程序原生的好处是不用引入额外框架,拿到源码改页面结构、换接口地址就能跑。
很多新手拿到源码先看登录注册,这顺序错了。停车场系统真正的核心是三个:计费引擎怎么算钱不留漏洞、支付回调怎么对账不重复、车牌识别结果怎么可靠地变成一条停车记录。三个点只要有一个是坏的,线上运营一定出账目问题,而且要到月底对账时才发现。
2.1 计费引擎:按分钟分段、跨天、封顶,规则全写在库表里
计费引擎不能把价格写死在 Java 代码里。常见做法是把规则放进一张 fee_rule 表,运营改价格不用动代码。核心字段包括免费时长、区间起点与终点、区间费用、每日封顶,以及这条规则是给临时车、月卡车还是免费车用的。
CREATE TABLE `fee_rule` ( `id` bigint NOT NULL AUTO_INCREMENT, `lot_id` bigint NOT NULL COMMENT '所属停车场ID', `car_type` tinyint NOT NULL COMMENT '车辆类型:1临时车 2月卡车 3免费车', `free_minutes` int NOT NULL DEFAULT '0' COMMENT '入场后免费时长,单位分钟', `start_minute` int NOT NULL DEFAULT '0' COMMENT '计费区间起点,单位分钟', `end_minute` int NOT NULL DEFAULT '0' COMMENT '计费区间终点,单位分钟', `charge_amount` decimal(10,2) NOT NULL COMMENT '该区间费用,单位元', `daily_cap` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '单日封顶费用,单位元', `is_cyclic` tinyint NOT NULL DEFAULT '0' COMMENT '是否循环计费:1是 0否', PRIMARY KEY (`id`) );这张表的设计思路是“按分钟查区间”。比如“首小时 10 元,后每 30 分钟 3 元,24 小时封顶 30 元”,可以拆成三条规则:0 到 60 分钟收 10 元,60 到 90 分钟收 3 元,90 到 120 分钟收 3 元,后续每 30 分钟继续循环。计算时把停车总时长减去免费时长,再按区间累加,最后和 daily_cap 取较小值。
跨天是计费最容易做错的地方。不要按自然日拆分两段分别计算,因为“24 小时封顶”和“跨天后重新计第一小时”完全是两套规则。正确做法是先把入场到出场的总分钟数算出来,对总时长套用循环区间累加,再和封顶比较。这样 22:00 入场次日 08:00 出场,得到的是一个连续时长,而不是两个自然日账单。
2.2 支付与回调:小程序拉起支付,后端靠异步通知记账
支付流程的时序是固定的:小程序调后端预支付接口拿到支付参数,拉起支付面板;用户确认后,支付服务商先返回给小程序一个前端结果,同时用异步通知把最终结果打到后端回调地址。前端结果只能用来刷新界面提示,能不能入账必须以异步通知为准。
订单状态机建议只保留四种状态:待支付、已支付、已退款、已关闭。入场时生成的订单进入待支付;异步通知成功后进入已支付;出场校验已支付才放行;超过 15 分钟未支付的订单定时关闭。
| 订单状态 | 含义 | 触发动作 |
|---|---|---|
| PAY_WAIT | 待支付 | 出场生成订单时 |
| PAY_SUCCESS | 已支付 | 收到异步通知且校验通过 |
| REFUNDED | 已退款 | 人工退款或异常退款处理 |
| CLOSED | 已关闭 | 定时任务超时未支付 |
回调接口必须做幂等。支付服务商的异步通知可能重复推送多次,甚至先通知后面的订单再通知前面的订单。如果回调处理时不判断当前订单状态,同一笔支付会被记两次流水,月底账目必乱。回调的第一步是验签,验签失败直接返回失败让服务商继续重试;第二步查订单;第三步判断订单如果已经是已支付状态,直接返回成功,不再更新任何数据。
2.3 车牌识别与硬件对接:摄像头结果不落库,一切白搭
车牌识别来源通常有两种接法。第一种是摄像头或识别一体机通过 HTTP 主动上报识别结果,后端提供接口接收;第二种是后端定时去设备 SDK 拉取识别结果。绝大多数成品停车场系统用的是第一种,因为摄像头厂商都会提供 HTTP 上报能力,部署简单。
上报接口最怕的是重复上报导致一辆车被记录两次入场。解决办法是接口里按“设备编号 + 抓拍时间 + 车牌号”做去重,缓存里放一个 30 秒的标记。同时要记录识别置信度,低于 90% 的结果不要直接放行,而是进待确认列表由岗亭人工确认。车牌识别结果如果没有写进停车记录表并附上抓拍图片地址,后续所有计费、结算、纠纷追溯都没有依据。
2.4 角色权限:从岗亭到管理后台,至少要四类账号
停车场系统不是只有车主一个角色。源码里至少要能看到四类角色的权限划分:超级管理员、停车场运营管理、岗亭值班、车主小程序端。四类角色在前端页面、后端接口、数据范围上都要分开,否则岗亭人员能改订单金额,或者普通车主能调到管理后台接口,都是事故隐患。
| 角色 | 端 | 关键权限 |
|---|---|---|
| 超级管理员 | 管理后台 | 停车场配置、费率修改、财务对账 |
| 运营管理员 | 管理后台 | 订单查询、月卡办理、退款审核 |
| 岗亭值班 | 岗亭端 | 手动入场、手动出场、异常放行 |
| 车主 | 小程序 | 绑定车牌、查询订单、支付、开票 |
明确了这套权限边界,再看源码时就不容易迷路。很多二次开发的坑,本质上不是代码难写,而是刚拿到工程时不知道哪些模块是主链路、哪些是附属功能。计费、支付、车牌记录是主链路,界面和权限是外壳,调试时优先保证主链路数据正确,页面好看不好看反而是后话。
3. 跑通后端的最小动作:从初始化 SQL 到接口自测
后端跑通的标志不是管理后台能打开,而是能用 curl 模拟一辆车入场、出场、生成订单并算出正确费用。整个过程只需要三件事:建好核心表、启动服务、调用接口。先看核心表,再看业务代码,最后用命令行自测。
3.1 初始化核心表结构:停车记录、订单、车位状态
停车记录表是系统的账本,字段要能回答“这辆车什么时候进的、什么时候出的、停了几分钟、收多少钱”。订单表负责支付状态。车位表负责剩余车位。三张表的关系是:停车记录是主表,订单与停车记录一对一;车位与停车记录是状态关系。
CREATE TABLE `park_record` ( `id` bigint NOT NULL AUTO_INCREMENT, `plate_no` varchar(20) NOT NULL COMMENT '车牌号', `car_type` tinyint NOT NULL DEFAULT '1' COMMENT '1临时 2月卡 3免费', `lot_id` bigint NOT NULL COMMENT '停车场ID', `enter_time` datetime NOT NULL COMMENT '入场时间', `exit_time` datetime DEFAULT NULL COMMENT '出场时间', `duration_minutes` int DEFAULT '0' COMMENT '停车时长,分钟', `fee_amount` decimal(10,2) DEFAULT '0.00' COMMENT '应收金额', `status` tinyint NOT NULL DEFAULT '1' COMMENT '1在场 2已出场 3异常', PRIMARY KEY (`id`), KEY `idx_plate_lot` (`plate_no`, `lot_id`), KEY `idx_status` (`status`) ); CREATE TABLE `parking_order` ( `id` bigint NOT NULL AUTO_INCREMENT, `record_id` bigint NOT NULL COMMENT '停车记录ID', `order_no` varchar(64) NOT NULL COMMENT '业务订单号', `plate_no` varchar(20) NOT NULL COMMENT '车牌号', `total_fee` decimal(10,2) NOT NULL COMMENT '订单金额', `status` tinyint NOT NULL DEFAULT '0' COMMENT '0待支付 1已支付 2已退款 3已关闭', `pay_time` datetime DEFAULT NULL COMMENT '支付时间', `callback_count` int NOT NULL DEFAULT '0' COMMENT '收到回调次数,用于排查重复通知', PRIMARY KEY (`id`), UNIQUE KEY `uk_order_no` (`order_no`) );这两张表是整套停车场源码的地基。注意订单表里加了 callback_count,这在生产环境非常有用。支付服务商重复回调时,你可以从数字上看出这个订单被通知了多少次,而不是靠猜。业务上要保证:一张停车记录只能生成一条有效订单,订单号必须唯一,所以订单表对 order_no 建了唯一索引。
3.2 入场、出场与计费的完整链路代码
入场接口要做的事按顺序拆开:查车牌是否是有效月卡、查车位是否还有余量、写停车记录、更新车位占用状态。一个月卡车入场不生成订单,只写记录。
@PostMapping("/api/park/enter") public Long enter(@RequestBody EnterDTO dto) { // 1. 查月卡:有效月卡直接标记为月卡入场 MonthlyCard card = monthlyCardMapper.findValidByPlate( dto.getPlateNo(), dto.getLotId(), new Date()); int carType = card != null ? 2 : 1; // 2. 查车位余量:优先读缓存,缓存没有再查库 int available = parkingSpaceCache.available(dto.getLotId()); if (available <= 0) { throw new BizException("车位已满"); } // 3. 创建停车记录,状态为在场 ParkRecord record = new ParkRecord(); record.setPlateNo(dto.getPlateNo()); record.setCarType(carType); record.setLotId(dto.getLotId()); record.setEnterTime(new Date()); record.setStatus(1); parkRecordMapper.insert(record); // 4. 扣减车位并写缓存 parkingSpaceCache.occupy(dto.getLotId()); return record.getId(); }入场参数里最重要的是 plateNo 和 lotId。plateNo 要做大小写归一化,不然“粤A12345”和“粤a12345”会被当成两辆车。lotId 用于区分多停车场部署,单停车场时固定传 1。月卡判断要同时校验证件有效期,只判断“存在”不判断“未过期”,会导致过期月卡还能免费出场。
出场接口比入场复杂,因为要同时完成计费和生成订单。计费引擎的核心是算准分钟数,再按规则表累加。
public long calcFee(ParkRecord record) { // 月卡车辆不走临时计费 if (record.getCarType() == 2 || record.getCarType() == 3) { return 0L; } long minutes = Duration.between( record.getEnterTime().toInstant(), record.getExitTime().toInstant()).toMinutes(); if (minutes <= 0) { minutes = 1; // 至少按1分钟计,避免免费钻空子 } // 读取该停车场的临时车规则 List<FeeRule> rules = feeRuleMapper.findByLotAndType( record.getLotId(), 1); long billable = Math.max(0, minutes - freeMinutes(rules)); long fee = 0; long cycleLength = totalCycleLength(rules); while (billable > 0) { for (FeeRule rule : rules) { if (billable <= 0) break; long inRange = Math.min(billable, rule.getEndMinute() - rule.getStartMinute()); fee += inRange / 60.0 * rule.getChargePerHour(); billable -= inRange; } // 循环区间:如果规则没有封顶,按最大区间长度兜底 } // 封顶:费用不能超过 daily_cap * 停车的自然天数 long cap = dailyCap(rules) * daysBetween(record); return Math.min(fee, cap); }这段代码有几个参数值得说明。billable 是扣掉免费时长后的可计费分钟数;cycleLength 是循环区间总长度,如果规则是“每 30 分钟 3 元”,循环区间就是 30 分钟;dailyCap 是 24 小时封顶值。封顶按自然天数计算是兼容“跨天”场景的常用妥协方案,虽然严格意义上有边界误差,但绝大多数停车场运营接受这个口径。计费函数返回的单位是“分”,传给前端时再除以 100,避免浮点数精度问题。
出场接口拿到费用后生成订单,订单金额只信任后端算出的 fee,不信任前端传过来的金额。前端传金额只能用于展示,入库必须以后端重算为准。
3.3 用 curl 自测接口:不依赖页面的最快验证方式
服务启动后,先不要急着打开小程序。用 curl 直接打接口,能最快确认主链路通不通。下面两个命令模拟“一辆临时车入场 65 分钟后出场”。
# 模拟车辆入场,返回停车记录ID curl -X POST "http://127.0.0.1:8080/api/park/enter" \ -H "Content-Type: application/json" \ -d '{"plateNo":"粤A12345","lotId":1}' # 模拟车辆出场,生成待支付订单 curl -X POST "http://127.0.0.1:8080/api/park/exit" \ -H "Content-Type: application/json" \ -d '{"recordId":1}'第一条命令的响应里会包含 recordId,第二条命令需要把它填进去。如果入场接口返回“车位已满”,先检查车位表里有没有初始化数据。很多源码自带 SQL 里只建了表,没插停车场和车位基础数据,这是部署后第一个拦路虎。如果出场接口返回金额为 0,检查 fee_rule 表有没有插入规则,以及规则里的 free_minutes 是不是把 65 分钟全免掉了。
自测时建议直接开一个独立测试库,不要把生产数据混进来。测试库初始化后,用脚本连续跑 10 次出入场,再人工核对订单金额,比打开页面点来点去快得多,也更容易发现计费函数里的边界漏洞。
4. 小程序端把页面接上接口:登录、绑定车牌、支付三个环节
后端接口通顺后,再看小程序端。小程序源码里需要改的地方集中在三个文件:请求封装、个人中心页、支付回调页。拿到源码先别急着改 UI,先把接口地址和登录逻辑打通,页面自然就有数据了。
4.1 请求封装与 token 处理:统一入口避免到处改地址
小程序端的请求不能每个页面单独写一遍。标准做法是封装一个 request 函数,所有页面调用它。这个函数负责拼接基础地址、带上 token、统一处理 401 和网络错误。
function request(path, method, data = {}) { const token = storage.get('token') || ''; return new Promise((resolve, reject) => { platformRequest({ url: BASE_URL + path, method: method, data: data, header: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + token }, success: (res) => { if (res.data.code === 401) { storage.remove('token'); // 跳转登录页 } else if (res.data.code === 0) { resolve(res.data.data); } else { reject(new Error(res.data.message)); } }, fail: (err) => reject(err) }); }); }这个封装做了三件必要的事:token 自动附加、401 自动踢回登录、统一按业务码解析数据。BASE_URL 是后端对外地址,上线前要改成 HTTPS 域名。注意 success 里判断的是 res.data.code,不是 HTTP 状态码,这是前后端约定的业务码体系;如果源码里的返回结构是 code/data/message,这套写法直接通用,如果不是,要按源码实际结构调整。
4.2 登录与车牌绑定:先拿到身份再绑定车牌
小程序登录的标准动作是:调用平台登录接口拿到临时凭证,把这个凭证发给后端,后端换出自己的 token 返回给小程序。后续所有请求都带这个 token。车牌绑定必须在登录之后做,绑定关系是“用户 openId 对应车牌号”,这样才能保证用户只能查看和支付自己名下的订单。
async function login() { const code = await platformLogin(); const res = await request('/api/auth/login', 'POST', { code }); storage.set('token', res.token); } async function bindPlate(plateNo) { const normalized = plateNo.trim().toUpperCase(); const res = await request('/api/car/bind', 'POST', { plateNo: normalized }); return res; }登录接口返回的 token 建议同时设置过期时间,存到本地存储时带上 expiresIn。车牌绑定前一定要做 trim 和 toUpperCase,很多莫名其妙的“查不到车牌”,都是前后端对大小写处理不一致导致的。后端接收车牌时也要做同样的归一化,两端都做才能形成闭环。
4.3 支付拉起与状态轮询:前端别等回调,要主动查
支付完成后的界面更新,不能依赖支付面板返回的结果,因为那个结果在弱网环境下可能丢失。可靠做法是:拉起支付后面临“支付结果未知”状态,小程序端定时去后端查订单状态,查到已支付再刷新页面。
async function payOrder(orderId) { const params = await request('/api/order/pay', 'POST', { orderId }); await platformPay(params); // 支付完成后轮询订单状态,最多10次,每次间隔2秒 for (let i = 0; i < 10; i++) { const order = await request('/api/order/' + orderId, 'GET'); if (order.status === 1) { return { paid: true, order }; } await sleep(2000); } return { paid: false, order: null }; }轮询为什么是 10 次而不是无限次?支付回调通常几秒内到达,10 次覆盖 20 秒已经绰绰有余。如果 20 秒还没变状态,再轮询意义不大,应该提示用户“支付结果确认中”,让后端定时任务兜底。真正入账的依据永远是后端异步通知,前端轮询到已支付只是为了刷新界面,这一点要在代码注释里写明,避免后来维护的人看不见,以为前端轮询就是入账逻辑。
5. 部署与避坑:一套源码跑不起来,九成卡在这几处
部署说明写得再详细,到了真机环境照样有一堆玄学问题。我见过的情况里,九成不是源码本身的问题,而是部署环境的基础配置不对。下面这五条是停车场系统部署最常见的翻车点,每条都是“现象→原因→解决”的结构,能帮你省下至少两天的排查时间。
5.1 跨天计费日期错乱:22:00 入场次日 08:00 出场,费用按两个自然日算了
现象:测试跨天停车时,发现计费结果和规则表对不上,明明写了 24 小时封顶 30 元,实际却收了 50 多。
原因:服务器时间、MySQL 时间和业务代码用了三个时区口径。代码里取 new Date() 存库,MySQL 连接串里却指定了 UTC,展示时前端又按本地时区解析,导致日期边界整体偏移,跨天场景被割裂成两天。
解决:统一时区口径。MySQL 连接串加 serverTimezone=Asia/Shanghai,服务器 timezone 改成 Asia/Shanghai,业务代码里所有日期计算用带时区的对象,不要用本地时间隐式转换。跨天计费函数里的“自然日”按业务时区计算,而不是数据库服务器时区。部署后立刻测一个 22:00 入场、次日 08:00 出场的用例,再测一个连续停放 25 小时的用例,这两个通过基本说明时区没毛病。
5.2 小程序真机请求全失败:开发工具能通,真机预览一片红
现象:开发工具里接口全部正常,一扫码真机预览,所有请求都报“网络错误”,但手机本身网络没问题。
原因:小程序平台对线上请求有严格的域名白名单校验。开发工具可以勾选“不校验合法域名”,真机没有这个开关,所有请求域名必须在平台侧完成配置,而且必须 HTTPS。另一个隐性问题是证书链不完整,有些证书服务商给的是中间证书,Nginx 配置只贴了主证书,安卓真机校验严格就会失败。
解决:在小程序管理后台把后端域名加进 request 合法域名列表,域名必须备案。Nginx 配置 HTTPS 时把 fullchain 证书链完整配置进去,不要只贴 crt 文件。开发阶段可以在开发工具勾选“不校验域名”先联调,但上线前一定要去掉勾选,用真机重新验证一遍所有接口。
5.3 支付回调重复通知,月底对账金额对不上
现象:某天对账发现一笔订单在支付流水里出现了两笔收入,但订单表里只有一条数据,金额差正好等于某一笔重复订单的金额。
原因:支付服务商为了保证通知送达,会以递增间隔重试回调,直到商户返回成功。如果回调处理代码没有幂等判断,每次都执行一次更新,就会造成支付流水重复记账。还有一类情况是代码里用了“先查后更”的方式,两个线程同时查到待支付状态,同时对数据库做更新,也会重复入账。
解决:回调处理必须做两件事。第一,更新订单时用状态条件更新,例如 UPDATE parking_order SET status = 1 WHERE order_no = ? AND status = 0,影响行数为 0 就说明已经被处理过,直接返回成功。第二,支付流水表对“支付服务商流水号”建唯一索引,重复插入直接报错并捕获记录。幂等不是靠业务代码的判断,是靠数据库约束兜底。
5.4 车位缓存与数据库不一致:重启后车位全空,车辆重复入场
现象:服务重启后,已停满的停车场显示还有大量空位,保安手动录入了多辆重复入场车,全部显示在场。
原因:车位余量只存在 Redis 里,重启后缓存丢失,系统以为所有车位都空着。反过来还有一种情况:定时任务把缓存中的占用数写回数据库,但写入时机和入场动作错位,缓存和库互相覆盖。停车场项目的车位状态必须支持重启恢复,不能把 Redis 当作唯一数据源。
解决:车位余量的权威数据放在数据库车位表里,Redis 只作为读缓存。服务启动时执行一次缓存重建,从数据库加载当前占用数。入场扣减时先更新数据库,再删缓存或更新缓存;不要先更新缓存再异步写库。如果源码里没有重建逻辑,部署说明里一定要补一条启动命令:先清空车位相关缓存,再启动服务。这个顺序错了,线上必出重复入场。
5.5 车牌识别摄像头只在内网生效:部署到公网后设备上报全失败
现象:在办公室局域网联调,识别一体机正常上报车牌,系统能自动抬杆。部署到云服务器后,同一台设备识别正常,但后端收不到任何上报数据。
原因:摄像头配置的上报地址是内网 IP,公网不可达。部分摄像头虽然支持 HTTP 上报,但固件对目标地址有限制,只能访问局域网;另外云服务器公网 IP 与停车场内网之间没有打通,摄像头自然找不到后端。
解决:不要在公网后端直接等摄像头上报,正确拓扑是停车场本地放一个边缘网关,摄像头上报到网关,网关再通过公网 API 转发到云后端。如果临时调试,也可以用内网穿透工具把后端地址映射到摄像头所在网络,但那只适合联调,不适合生产。方案选型时,优先选支持“后端主动拉取”的摄像头 SDK,主动拉取可以完全绕开内网上报的连通性问题,代价是实时性略低,设置 3 秒拉一次足够应对车辆进出。
6. 验证这套源码值不值得投入使用:三个必测用例
一套源码让你快速跑通,不等于能直接上生产。我每次拿到新的停车场源码,都会用三个用例做最终验收,每个用例都对应一次真实踩坑记录,你可以直接照着设计测试数据。
第一个用例是跨天停车计费。设计一辆 18:00 入场、次日 09:00 出场的临时车,全程 15 小时。预期结果:免费时长 15 分钟扣掉后,按区间规则累加,并受单日封顶约束。这个用例能同时暴露时区、封顶、循环区间三个问题。如果结果比人工计算结果高或低,先查规则表数据,再查跨天封顶代码。
第二个用例是并发入场和重复支付。用脚本并发向入场接口发送 20 个请求,其中 5 个是同一车牌。预期结果:同车牌只创建一条在场记录,不重复入场;剩余车位扣减量等于实际入场的不同车牌数,而不是请求数。支付侧则是重复向回调接口发送同一笔成功通知两次,预期结果:订单表和流水表都只有一条记录。这个用例检验的是防重入和幂等,直接影响运营对账。
第三个用例是月卡与免费时长边界。一张当月有效的月卡车入场,应直接标记为月卡车辆且不生成计费订单。一辆临停车入场 14 分钟出场,免费时长 15 分钟,应收 0 元;入场 16 分钟出场,应收至少一个计费最小单位。很多源码在这里栽跟头,把免费时长边界判断写成 minutes > freeTime 而不是 minutes >= freeTime,导致 15 分钟整免和 15 分 01 秒起步的处理不一致。
下面是一份我会在部署前逐项确认的自检清单,也是你评估“这套源码是否值得投入”的参考底线。清单里任何一项不通过,都不要急着正式运营。
| 检查项 | 通过标准 |
|---|---|
| 跨天计费 | 22:00 入场次日 08:00 出场,金额符合规则表人工计算 |
| 幂等回调 | 同一笔回调重复推送 10 次,订单状态不变,流水不重复 |
| 车位一致性 | 模拟 20 辆车入场后重启服务,剩余车位数不变化 |
| 证书与域名 | 真机小程序能正常请求全部接口,无网络错误 |
| 月卡边界 | 过期月卡不再免费,有效月卡不生成订单 |
| 免费时长边界 | 恰好在免费时长内出场收 0 元,超时 1 分钟正常计费 |
这套验证跑完,你基本能判断这份停车场系统源码的成熟度。计费、支付、车位、小程序四个主链路全部通过,才有放进生产的基础。我个人的习惯是把跨天计费用例放在部署后的第一件事,因为大多数停车场源码的演示数据都不会覆盖跨天场景,而它恰恰是上线第一个月最容易出客诉的地方。希望这些方法和踩坑记录帮到你,至少让你少走一遍我走过的弯路。
本文还有配套的精品资源,点击获取