SpringBoot+Vue停车场管理系统:核心表设计、计费规则与并发防重实战
简介基于SpringBoot和Vue前后端分离架构的智能停车场管理系统完整源码面向需要完成毕业设计或课程设计的开发者群体也适合用来巩固Java后端与Vue前端配合开发的能力。系统主要实现号牌识别、车辆管理、角色管理、系统管理、账号管理等模块后端通过RESTful API交互前端以组件化方式呈现界面。压缩包内总计118个文件包含105个Java源文件以及PNG图片、XML配置、HTML页面和TXT文本说明等文件资源包整体约3.88MB目前已吸引184人学习。源码中涵盖Excel数据导入导出、用户服务实现、角色服务实现、动态查询等典型业务代码目录结构清晰可直接导入运行体验也可作为二次开发的基础。对于想快速上手SpringBootVue项目、理解前后端分离站点建设流程的读者这套代码具备较强的参考价值。1. 智能停车场管理系统源码这套 SpringBootVue 组合到底解决什么智能停车场管理系统是前后端分离项目里最经典的交付组合后端 SpringBoot 扛住车辆入场、出场计费、车位与订单管理前端 Vue 负责收费员操作台、管理后台和车位大屏。网上这类源码不少但真正能让人一天内跑起来、改得动的核心就三块数据模型怎么建、计费规则怎么算、前后端怎么联调。下面把我自己落地这类系统的做法完整拆开——从建表到后端接口再到 Vue 路由最后给源码到手后最容易翻车的五个点。适合接外包的开发者、做课程设计的在校生以及要给小区或园区停车场做信息化改造的从业者。我的结论很直接这套系统的难点从来不在页面而在计费规则和并发下的数据一致性。2. 架构与数据模型把功能边界和四张核心表一次定死后面少返工2.1 功能边界哪些功能第一版必须做哪些是加分项拿到这套智能停车场管理系统源码第一步不是打开 IDE 跑起来而是先看它包含了哪些功能模块。一套能真正交付的停车场系统功能主线只有一条车辆进来登记、车辆出去结算、车位状态随时可查、收支流水能对账。围绕这条主线权限管理管理员、收费员、费率规则配置、月度统计是必须做的车位预约、室内导航、扫码支付、大屏炫酷动画都是加分项。我一般会先把边界划死。第一版只做“进、出、算费、查流水”月卡不单独建表而是作为费率规则的一种类型——月卡车辆绑定车牌和有效期出场时命中“月卡规则”直接免单。扫码支付因为涉及商户号资质源码阶段用一个支付开关替代开关打开时出场结算默认支付成功接入真实微信支付时只需要替换一个实现类。这样划分的好处是数据库只沿着一条主链延伸后面加功能不伤筋动骨。关于车牌识别也要说清楚。真正的摄像机识别需要设备厂商的 HTTP 回调把抓拍结果推给后端源码项目一般不会内置某个品牌的相机 SDK而是做成“手动录入 图片上传”。所以校验规则要写好普通蓝牌 7 位、新能源绿牌 8 位正则里把省份简称和字母数字范围都约束住否则收费员手输错一个字符出场时就查不到这辆车。2.2 四张核心表怎么建车位、车辆流水、费率规则和用户数据库是这套源码的地基。下面四张表是主流源码项目里都会有的核心表字段选型直接给出来照着建基本不用返工。主键统一用 bigint 自增时间字段用 datetime金额用 decimal(10,2)。千万不要用 float 存金额对账时精度问题会让你怀疑人生。parking_space 车位表字段类型说明idbigint主键space_novarchar(20)车位编号唯一索引area_namevarchar(50)所属区域A 区/B 区space_typetinyint0临时车位1固定车位statustinyint0空闲1占用bind_platevarchar(20)固定车位绑定的车牌可空car_record 车辆流水表字段类型说明idbigint主键plate_novarchar(20)车牌普通索引space_idbigint关联车位 IDentry_timedatetime入场时间exit_timedatetime出场时间在场时为空fee_rule_idbigint计费采用的费率规则amountdecimal(10,2)实收金额出场时写入statustinyint0在场1已离场order_novarchar(32)订单号唯一索引fee_rule 费率规则表字段类型说明rule_namevarchar(50)规则名称free_minutesint免费时长分钟first_hoursint首时段小时数first_pricedecimal(10,2)首时段价格per_hour_pricedecimal(10,2)超出后单价daily_capdecimal(10,2)单日封顶0 表示不封顶round_typetinyint0按小时取整1按 30 分钟取整sys_user 用户表字段类型说明idbigint主键usernamevarchar(50)登录名唯一passwordvarchar(100)BCrypt 密文roletinyint0管理员1收费员为什么 car_record 是核心中的核心因为入场和出场都围绕它转。入场时插入一条 status0 的记录出场时更新 exit_time、amount 和 status。车位表的 status 只是实时同步的快照真正有法律效力的数据是流水表。订单号建议用时间戳加随机数生成或者直接上雪花算法保证流水号全局唯一财务对账时靠它检索。2.3 SpringBoot 项目结构与配置拿到源码先看这四个文件常见的 SpringBoot 停车场源码都是标准三层结构controller 接收请求service 写业务逻辑mapper 用 MyBatis-Plus 的 BaseMapper 免写大量 XML。拿到一份源码我建议你先打开这四个文件把调用链读通pom.xml 看 SpringBoot 版本和依赖application.yml 看端口、数据库连接启动类看 MapperScan 扫哪个包然后挑一个 Controller顺藤摸瓜看到 Service 和 Mapper20 分钟就能把项目的地形摸清楚。server: port: 8080 servlet: context-path: /api spring: datasource: url: jdbc:mysql://localhost:3306/parking_db?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalseallowPublicKeyRetrievaltrue username: root password: root driver-class-name: com.mysql.cj.jdbc.Driver mybatis-plus: mapper-locations: classpath*:mapper/**/*.xml global-config: db-config: logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0这段配置有三个地方容易看走眼。第一driver-class-name 在 MySQL 8 下必须是 com.mysql.cj.jdbc.Driver如果你用的是 MySQL 5.x 的老项目驱动名是 com.mysql.jdbc.Driver混用会出现启动失败或时区异常。第二url 里 serverTimezoneAsia/Shanghai 最好显式写上否则驱动默认取 JVM 时区跨时区部署时入库时间会错几个小时。第三MyBatis-Plus 的逻辑删除配置让流水和车位记录只做假删除停车场这类需要留痕的场景物理删数据是事故不是功能。注意server.servlet.context-path 配了 /api前端 axios 的 baseURL 就写 /api生产环境 Nginx 也要把 /api 前缀转发到后端三处必须一致。启动类一般长这样MapperScan 指定 mapper 接口所在的包路径。如果源码里没有 MapperScan也没问题但在每个 Mapper 接口上加 Mapper 注解效果等价。我习惯用 MapperScan因为新加 mapper 接口时不用重复写注解。SpringBootApplication MapperScan(com.parking.system.mapper) public class ParkingApplication { public static void main(String[] args) { SpringApplication.run(ParkingApplication.class, args); } }3. 后端接口落地入场校验、出场计费和并发防重复结算3.1 车辆入场接口三层校验和车位分配入场接口是整个系统的入口它的数据质量决定后续所有功能的准确性。我一般会写三层校验第一层是参数校验车牌非空且匹配“省份简称 城市字母 5 位数字字母”的正则第二层是业务校验查询同一车牌是否已经有 status0 的在场记录有就直接拒绝防止同牌重复入场第三层是资源校验查有没有空闲的临时车位没有就返回车位已满。PostMapping(/entry) public ResultEntryVO entry(RequestBody EntryDTO dto) { if (!Pattern.matches(^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领][A-HJ-NP-Z][A-HJ-NP-Z0-9]{5,6}$, dto.getPlateNo())) { return Result.fail(车牌格式不正确); } return Result.ok(parkingService.entry(dto.getPlateNo())); }Service 层实现Transactional(rollbackFor Exception.class) public EntryVO entry(String plateNo) { Long inPark carRecordMapper.selectCount(new LambdaQueryWrapperCarRecord() .eq(CarRecord::getPlateNo, plateNo) .eq(CarRecord::getStatus, 0)); if (inPark ! null inPark 0) { throw new BizException(该车牌已在场内请勿重复入场); } ParkingSpace space parkingSpaceMapper.selectOne(new LambdaQueryWrapperParkingSpace() .eq(ParkingSpace::getStatus, 0) .eq(ParkingSpace::getSpaceType, 0) .last(LIMIT 1 FOR UPDATE)); if (space null) { throw new BizException(暂无空闲车位); } CarRecord record new CarRecord(); record.setPlateNo(plateNo); record.setSpaceId(space.getId()); record.setEntryTime(LocalDateTime.now()); record.setStatus(0); carRecordMapper.insert(record); space.setStatus(1); parkingSpaceMapper.updateById(space); return new EntryVO(record.getId(), space.getSpaceNo(), record.getEntryTime()); }逻辑说明Transactional 保证插入流水和更新车位这两个动作要么一起成功要么一起回滚LIMIT 1 FOR UPDATE 是 InnoDB 的行级锁两个收费员同时点入场时后一个事务会等前一个提交之后再去查空闲车位不会出现两辆车同时分配到同一个车位。单机部署时这个方案完全够用只有到多实例部署才需要考虑 Redis 分布式锁源码阶段不必提前引入。参数说明车牌正则是按国内民用车牌写的省份简称是硬编码集合新能源车牌最后一位是字母也兼容了。如果要对接具体品牌的识别相机把相机抓拍结果当作入参直接调这个接口就行识别置信度低于阈值的图片可以转人工确认这一步留在前端处理。3.2 出场计费首小时、逐小时和封顶规则的计算实现计费是整套源码的灵魂也是需求和测试扯皮最多的模块。最常见的需求是“免费 15 分钟首小时 X 元超过一小时后每小时 Y 元单日封顶 Z 元”。这类需求有两个关键点必须定死超过整小时怎么取整封顶按自然日还是按 24 小时滚动。我见过不少项目在这里翻车因为把规则写死在 if-else 里运营想改成“按 30 分钟计费”就得改代码再重新发版。所以我把计费参数全部抽到 fee_rule 表由管理员在页面里配置代码里只保留一个通用的计算器。下面这段就是计算器的核心逻辑roundType 控制取整粒度dailyCap 为 0 表示不封顶。计算全程用 BigDecimal禁止用 double 参与金额运算。public BigDecimal calc(FeeRule rule, LocalDateTime entryTime, LocalDateTime exitTime) { long minutes Duration.between(entryTime, exitTime).toMinutes(); if (minutes rule.getFreeMinutes()) { return BigDecimal.ZERO; } long billMinutes minutes - rule.getFreeMinutes(); if (rule.getRoundType() 1) { billMinutes ((billMinutes 29) / 30) * 30; } BigDecimal amount rule.getFirstPrice(); long firstMinutes rule.getFirstHours() * 60L; if (billMinutes firstMinutes) { long extraMinutes billMinutes - firstMinutes; BigDecimal extra BigDecimal.valueOf(extraMinutes) .divide(BigDecimal.valueOf(60), 4, RoundingMode.HALF_UP) .multiply(rule.getPerHourPrice()) .setScale(2, RoundingMode.HALF_UP); amount amount.add(extra); } if (rule.getDailyCap() ! null rule.getDailyCap().compareTo(BigDecimal.ZERO) 0 amount.compareTo(rule.getDailyCap()) 0) { return rule.getDailyCap(); } return amount.setScale(2, RoundingMode.HALF_UP); }这里的 roundType1 表示按 30 分钟向上取整。(billMinutes 29) / 30 * 30 是整数除法实现取整的经典写法比 Math.ceil(billMinutes / 30.0) * 30 更直白也避免了浮点误差。为什么推荐 30 分钟而不是直接按小时取整因为“1 小时 01 分出场的车按 2 小时收费”是投诉重灾区改成 30 分钟粒度后绝大多数用户感知不到涨价运营也少处理很多客诉。封顶判断放在最后先用未封顶金额和 dailyCap 比较超过直接返回封顶值。这里有个隐藏点跨天场次怎么算封顶。常见做法是按入场时间所在的自然日分组跨天的订单拆分成两段分别计费再求和。源码里如果没做拆分至少要在 README 里写清楚当前口径否则月底对账时会有一堆边界订单看不明白。3.3 出场结算接口事务加状态条件挡住重复扣费出场结算比入场更敏感因为涉及钱。界面上的按钮一旦被双击或者收费员习惯性地连点两下鼠标后端就会收到两个一模一样的出场请求。如果代码是“先查在场记录再更新状态”两个请求都会查到同一辆车生成两笔费用。Transactional(rollbackFor Exception.class) public ExitVO exit(String plateNo) { CarRecord record carRecordMapper.selectOne(new LambdaQueryWrapperCarRecord() .eq(CarRecord::getPlateNo, plateNo) .eq(CarRecord::getStatus, 0) .last(FOR UPDATE)); if (record null) { throw new BizException(未找到在场车辆); } FeeRule rule feeRuleMapper.selectById(record.getFeeRuleId()); BigDecimal amount feeCalc.calc(rule, record.getEntryTime(), LocalDateTime.now()); record.setExitTime(LocalDateTime.now()); record.setAmount(amount); record.setStatus(1); carRecordMapper.updateById(record); ParkingSpace space parkingSpaceMapper.selectById(record.getSpaceId()); space.setStatus(0); parkingSpaceMapper.updateById(space); return new ExitVO(record.getId(), amount); }这里有几层保护。第一查询用 FOR UPDATE 把这一行在事务里锁住第二查询条件里写死 status0第一个请求提交后状态变成 1第二个请求即使进入事务也查不到记录第三出场时间和金额都以服务器本地时间计算不信任前端传参。出场时间一旦由前端传用户改一下手机时间就能少交几十块这是安全漏洞不是功能需求。关于 fee_rule_id入场时就要把规则挂上吗我建议入场时只记录车位和车牌出场时才根据车辆是否绑定月卡来选择费率。因为停车期间运营可能调整费率用出场时刻的规则结算才是行业通行的做法。如果你在入场时就把 fee_rule_id 写死跨日调价后会出现“同一天停的车收费标准不一样”的尴尬。4. Vue 前端从环境到联调路由守卫、请求拦截与收费员操作台4.1 Vue 安装与环境配置Vue2 配 Element UIVue3 配 Element Plus前端这部分拿到源码之后的第一件事不是读组件而是把环境配好。判断项目是 Vue 2 还是 Vue 3直接看 package.json 里 vue 的版本号主版本是 2 就用 Element UI主版本是 3 就用 Element Plus。混用是新手最容易踩坑的点——用 Vue 3 的项目去 npm install element-ui控制台会报一堆版本不兼容。node -v npm install -g vue/cli # 创建项目时手动选择 Router 和 Vuex vue create parking-web cd parking-web npm install axios element-ui # 如果是 Vue3 项目改成 npm install axios element-plus npm run servenode 版本很关键。Vue 2 的老项目在 Node 18 以上经常出现 node-sass 编译失败建议用 Node 14 或 16Vue 3 Vite 则推荐 Node 18太老的版本跑不起来。如果你拿到的源码带 package-lock.json直接 npm install 就能按锁定版本装齐依赖不要手动一个个装否则版本漂移会让你在奇怪的地方报错。另外说个高频问题“vue 项目源码怎么发给别人”。我一般压缩前先删掉 node_modules 和 dist对方拿到后自己 npm install。把 node_modules 一起发过去跨平台大概率翻车因为 node-sass、esbuild 这类包有平台二进制Windows 的包到 Mac 上直接报 module mismatch。这也是很多新手说“源码能跑怎么发出去就坏了”的真正原因。4.2 路由与登录态路由守卫和 axios 拦截器怎么配合管理端的页面跳转由 Vue Router 控制但权限控制不能只靠页面里的 v-if。正确做法是路由守卫统一拦截没有 token 就跳到登录页有 token 才放行。同时后端接口也要校验 token不然有人直接在浏览器里敲接口地址照样能拿到数据。前后端两层校验才算闭环。import Vue from vue import Router from vue-router Vue.use(Router) const router new Router({ routes: [ { path: /login, component: () import(/views/Login.vue) }, { path: /, component: () import(/layout/Layout.vue), redirect: /dashboard, children: [ { path: dashboard, name: Dashboard, component: () import(/views/Dashboard.vue) }, { path: car/entry, name: CarEntry, component: () import(/views/CarEntry.vue) }, { path: car/record, name: CarRecord, component: () import(/views/CarRecord.vue) }, { path: fee/rule, name: FeeRule, component: () import(/views/FeeRule.vue) } ] } ] }) router.beforeEach((to, from, next) { const token localStorage.getItem(parking_token) if (to.path ! /login !token) { next({ path: /login }) } else { next() } }) export default router这里所有页面组件都用 () import() 懒加载首屏只加载 Layout 和当前页面访问收费员页面时才加载它的代码包首屏打开速度能快不少。实现里注意把 /login 排除在守卫之外否则没登录时跳转到登录页登录页又会触发守卫形成死循环跳转。axios 拦截器承担两件事自动附带 token以及统一处理 401 登录失效。登录接口成功后把后端返回的 token 存到 localStorage之后每个请求在请求拦截器里加到 Authorization 头。响应拦截器里判断 code 字段业务失败统一弹提示401 则清除 token 并跳回登录页。import axios from axios const service axios.create({ baseURL: /api, timeout: 10000 }) service.interceptors.request.use(config { const token localStorage.getItem(parking_token) if (token) config.headers.Authorization Bearer token return config }) service.interceptors.response.use(resp { const data resp.data if (data.code ! 0) { alert(data.msg) return Promise.reject(data) } return data.data }, err { if (err.response err.response.status 401) { localStorage.removeItem(parking_token) window.location.href /login } return Promise.reject(err) }) export default service4.3 收费员操作台与大屏回车入场、出场弹窗和 ECharts收费员操作台的设计标准是“一个页面完成入场和出场”不需要来回跳菜单。顶部一个车牌输入框扫码枪扫到时相当于键盘输入一串字符加回车所以监听 keyup.enter 就能自动触发入场查询——现实中扫码枪本质就是一个快速键盘输入器。这个交互细节做好了收费员会真心觉得系统好用。template div classentry-panel el-input v-modelplateNo placeholder输入或扫描车牌后回车 sizelarge keyup.enter.nativehandleEntry / el-button typeprimary :loadingsubmitting clickhandleEntry入场登记/el-button el-table :datarecords stripe el-table-column propplateNo label车牌 width140 / el-table-column propspaceNo label车位 width100 / el-table-column propentryTime label入场时间 / el-table-column label操作 width120 template slot-scope{ row } el-button typewarning sizemini clickshowExit(row)出场结算/el-button /template /el-table-column /el-table /div /template出场结算弹窗前先调后端计算金额的接口把应收金额显示成弹窗收费员确认后才真正提交结算。这样做的目的是给收费员一个核对的机会也避免误触直接收款。大屏展示则用 ECharts24 小时车流折线图、当前车位占用率环形图、今日营收数字滚动数据源都来自后端的 /stats 汇总接口前端只做渲染。组件库的 slot-scope 是老版 Element UI 的写法Vue 3 的 Element Plus 里模板插槽改成了 #default{ row }。如果你拿到的源码是 Vue 2 却报插槽相关错误先确认是不是装了 Element Plus。这类“同名组件 API 不兼容”的问题在 Element UI 和 Element Plus 之间特别多遇到就查组件库版本别在业务代码里找原因。5. 源码落地避坑环境、跨域与并发结算的 5 个典型问题5.1 启动失败MySQL 连接报 Communications link failure现象项目启动时控制台报 Communications link failure或者 Access denied for user XXX但密码确定没有输错数据库客户端也能正常连上。这个问题在各类源码交流群里几乎每周都有人问。原因八成是 MySQL 驱动和 URL 参数不匹配。MySQL 5.x 用 com.mysql.jdbc.DriverMySQL 8 必须换成 com.mysql.cj.jdbc.Driver另外 MySQL 8 默认认证插件是 caching_sha2_password不显式给 serverTimezone 和 allowPublicKeyRetrieval 就会失败。解决把 application.yml 里的 driver-class-name 和 url 改成 2.3 小节的写法重启即可。这个报错 80% 是配置问题不是代码 bug。如果改了还报 SSL 相关错误把 useSSLfalse 补上大部分本机跑起来的案例都是靠这个组合解决的。5.2 SpringBoot 版本太高导致依赖冲突编译一片红现象用 IDEA 打开源码pom 依赖一直标红编译报 lombok 或 javax.annotation.Resource 找不到符号main 方法旁边甚至有小红叉。原因很多现成源码 pom 写的是 spring-boot-starter-parent 2.3.x而你的本机 JDK 是 17 甚至 21。SpringBoot 2.x 在 JDK 17 下需要配套升级 lombok 到 1.18.30否则注解处理器直接失效。反过来如果源码是 SpringBoot 3.x你又用 JDK 8 编译javax 换成 jakarta 也会报错。解决先看 pom.xml 里的 java.version按源码声明装对应版本的 JDK或者把 lombok、maven-compiler-plugin 升到最新版。不要盲目拿新版本去跑老项目——能跑通原项目的第一版环境就是最好的环境。5.3 前端跨域报错接口变成 /api/api/xxx现象前端 npm run serve 跑起来打开页面所有请求报 CORS 或 Failed to fetch后端日志里能看到请求进来但返回 404。原因前后端分离下前端端口 8081、后端 8080浏览器默认跨域拦截另外后端如果配置了 context-path: /api前端 axios 的 baseURL 又写成了 http://localhost:8080请求会变成 /api/api/xxx。两个问题叠加最常见。解决开发期最好用 devServer 代理把 /api 开头的请求转发到后端// vue.config.js module.exports { devServer: { proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } } }然后把 axios baseURL 改回 /api让它走代理。生产部署再用 Nginx 统一转发 /api 到后端。不要同时在前后端各配一套 CORS两套叠加容易出现“预检过了、正式请求又被拦”的奇怪表现。5.4 出场按钮连点两次产生两条重复扣费流水现象收费员双击“出场结算”系统刷出两条金额一样的流水财务对账对不上找过来的时候后台也没有报错。原因前端没做提交防抖后端“查记录”和“更新状态”之间没有并发控制两个请求都读到 status0 的记录各自算费各自更新最后生成两笔订单。解决前端按钮加 :loading 状态请求期间禁用后端出场事务里用 FOR UPDATE 锁住记录见 3.3查询条件带 status0。双保险上齐之后这个坑不会再踩。3.3 小节的实现就是按这个思路写的照抄即可。如果还担心极端情况可以给 car_record 加唯一约束以 order_no 或“在场唯一车牌”为维度再兜一层。5.5 免费时长边界算错15 分 30 秒出场被收费现象规则写的是免费 15 分钟客户 15 分 30 秒出场被收了钱投诉到前台或者 23:50 进场、00:10 出场封顶规则算错多收了几十块。原因计费用 LocalDateTime 做字符串比较或者免费时长判断用了 strictlyLessThan跨天场次没有按自然日拆分封顶没有按日期重置。解决计费统一转成时间戳毫秒计算用 Duration.between 求分钟差免费边界用 而不是 跨天场次按“入场日期”归属统计封顶按自然日 0 点重置。把这套口径写进 fee_rule 表的注释里避免后期换人维护时理解偏差。计费逻辑别当黑匣子把每一步的中间结果打到日志里出问题五分钟能定位。6. 上线验证与二次开发一条 SQL 跑完计费清单再谈月卡与扫码支付6.1 上线前的一小时验证清单验证不要靠人肉点页面。我一般用 Apifox 或者一个临时测试脚本直接跑接口先构造 5 辆车入场其中一辆停满 2 小时再出场核对金额和手算结果一致。下面这条 SQL 是快速造一条跨时段数据的常用做法任何时候执行都能模拟“跨 2.5 小时”的场次INSERT INTO car_record (plate_no, space_id, entry_time, exit_time, amount, status) VALUES (京A12345, 1, DATE_SUB(NOW(), INTERVAL 150 MINUTE), NOW(), 0.00, 1);这里 entry_time 用相对时间而不是写死保证脚本随时重跑都有效。验证项按这个清单过一遍免费时长内出场金额为 0跨首小时时段金额正确封顶生效同一车牌二进二出正常并发双击只生成一笔订单。全过之后再手工打开页面走一遍真实流程基本就稳了。6.2 二次开发方向月卡、扫码支付与车牌识别接入三个最刚需的扩展方向改造成本从低到高。月卡最简单在 fee_rule 增加月卡规则类型车辆表加一个绑定字段和有效期出场时命中月卡规则直接免单。扫码支付在后端加一个“支付回调”接口替换掉模拟支付开关前端把订单号传给微信/支付宝拉起支付回调里确认金额后更新流水状态。车牌相机对接时厂商一般会提供 HTTP 回调把识别结果推给你你在 entry 接口前面加一层回调适配即可核心业务代码不用动。我最早做停车场系统时时间大头花在画大屏上结果计费规则里“首小时后每小时、封顶”的逻辑没想清上线第一周就被财务找上门。后来把计费字段全部参数化、用状态条件防重复结算才彻底消停。这套源码真的不难难的是把边界和并发想清楚。建议你先按 6.1 的清单跑一遍再动手改希望帮到你。本文还有配套的精品资源点击获取