SpringBoot停车场管理系统:车位预约与计时收费核心实现

发布时间:2026/10/3 10:40:03
SpringBoot停车场管理系统:车位预约与计时收费核心实现 想动手做一个“智能停车场管理系统”作为毕业设计或者单纯想在简历里加一个能讲的 Web 项目这个选题其实挺经典的。Java SpringBoot 车位预约 计时收费听起来不算花哨但真要把逻辑理清、代码写干净、答辩能自圆其说里面藏着不少值得拆解的细节。这篇文章我尽量按实际做项目的思路来写从需求梳理到技术选型从建表到核心代码实现再到我踩过的坑一次性说透。1. 先想清楚你的系统到底要管什么很多人一上来就写代码结果做到一半发现需求含糊、表结构返工、前后端接口对不上。做毕设也好做项目练习也好第一步永远不是写代码而是把“这个系统到底服务谁、解决哪些具体问题”想清楚。1.1 别急着追求“智能”先把场景还原出来“智能停车场管理系统”这个概念其实很宽。往大了说可以做成车牌识别、车位引导、无感支付那种工业级产品但作为毕业设计还是要落在你能独立完成的范围内。我见过的比较合理、也好演示的项目边界是这样车主通过 Web 端注册登录在线查看空闲车位预约某个车位预约后生成订单记录预约时间、入场时间、出场时间车辆入场、出场时系统根据停车时长自动计算费用管理员后台管理车位信息、计费规则、订单流水、用户列表车位状态实时变化后台能看到占用率、收入统计。这其实就是“预约 收费”的核心闭环。再往细里加才会去考虑摄像头对接、硬件设备联动。作为毕业设计把预约和计费做成闭环数据流跑通已经能很好地展示你的设计能力和编码能力。1.2 需求不明确的坑我见过太多了提到“智能”就想去接硬件、做小程序、搞大屏可视化心思全是好的但工作量和你的精力往往不成正比。毕设最怕的是摊子铺得很大结果每个功能都只能做到“表面能点”。真正给评委留下好印象的往往是少数几个功能做到逻辑严谨、细节到位。我建议你按“一个核心闭环 几个加分项”的思路来规划核心闭环登录 → 查车位 → 预约 → 入场 → 出场计费 → 支付/记录加分项停车场分楼层/区域管理、不同区域不同计费规则、预约超时自动取消、车位占用率的可视化统计。先把闭环跑通再做加分项代码质量和完成度都会有保障。2. 技术选型为什么是 SpringBoot 这套组合毕设的技术栈不需要花哨关键是稳并且你要能讲清楚为什么选它。SpringBoot MyBatis/MyBatis-Plus MySQL Vue/Thymeleaf 是一套非常成熟的组合网上资料多、出问题好查答辩时也不会被问倒。2.1 每个组件选它的理由SpringBoot解决了 Spring 配置繁琐的问题内嵌 Tomcat打 jar 包就能跑非常适合快速开发这类单体 Web 系统。另外一个容易被忽视但答辩常问到的点是 SpringBoot 的自动装配原理。简单说SpringBootApplication 上有个 EnableAutoConfiguration 注解它通过读取 spring.factories 里配置的自动配置类在项目启动的时候按条件比如你引入了 spring-boot-starter-web就自动帮你把 DispatcherServlet、内嵌 Tomcat 配置好把这些 Bean 装配进容器。你引入什么依赖它就自动配置好对应的东西这就是“约定大于配置”的核心体现。MyBatis-Plus是我比较推荐的 ORM 层选择。它比 JPA 好上手SQL 可以自己控制又有内置的 CRUD 接口、分页插件、代码生成器开发效率明显高很多。用 JPA 写复杂查询时你会很痛苦用 MyBatis 原生写 SQL 的话基础增删改查又很浪费时间MyBatis-Plus 正好卡在中间。MySQL不用多解释开源、稳定、资料多毕业设计用它完全足够。索引、事务、关联查询这些点在你答辩时都能成为加分话题。2.2 后端分层与目录结构直接参考这套一个规范的三层结构是 MVC 的延伸Controller 只负责接收请求和返回结果Service 做业务逻辑Mapper 只做数据库操作。我建议用一个带 module 或者 package-by-feature 风格的目录但不要过度设计com.example.parking ├── controller // 接口层接收参数、返回统一响应 ├── service // 业务逻辑层处理预约、计费等 │ └── impl ├── mapper // MyBatis-Plus 的 Mapper 接口 ├── entity // 数据库实体的映射类 ├── dto // 前端交互的数据传输对象 ├── vo // 返回给前端的视图对象 ├── config // 配置类如拦截器、Redis、跨域 ├── common // 统一响应封装、异常处理、工具类 ├── task // 定时任务如超时释放车位 └── ParkingApplication.java不用纠结 entity/dto/vo 会不会太多对于停车系统来说它们对保持代码干净作用很大。比如数据库里车位表的 status 字段可能是 0、1、2 这样的数字但你返回给前端时最好解析成“空闲/已预约/已占用”这样的文本放在 VO 里处理就很合适。2.3 前端选型和我的一点偏好前端有两类方案可以选择一类是 Thymeleaf 服务端渲染一类是前后端分离Vue Element UI。如果你前端基础一般只求整体可用Thymeleaf 更省事页面里直接写${}取值跟后端代码放一起部署不用处理跨域开发链路更短。如果你希望简历上能写“前后端分离项目”那就老老实实用 Vue Element UI页面会好看很多但你要额外处理跨域问题、Token 传递问题还要熟悉 npm 构建流程。我的经验是想做出“看起来像个正经产品”的效果用 Vue Element UI只想稳妥做完、把重点放在后端逻辑上用 Thymeleaf。两者都能过看你的时间投入。3. 数据库设计车位、预约和钱是核心中的核心表结构设计得好不好直接决定你后面写业务代码是顺手还是痛苦。停车场系统的核心表我总结了六张一套闭环跑下来不多不少。3.1 核心表的字段设计思路用户表user用户表没什么特别的但要注意密码不能明文存储。用 BCrypt 加密保存同时设计一个 role 字段用来区分普通用户和管理员。车位表parking_space这张表是整个系统的地基。字段至少有space_code 车位编号如 A-01方便展示area_id 区域 id方便做分区管理status 0 空闲 / 1 已预约 / 2 占用type 普通车位 / 充电车位等加分项这里有个关键设计点关于车位状态的流转。我建议把车位状态和订单状态分开维护车位的 status 是实时的、能展示给用户看的订单的状态是业务流程的比如预约已取消、已完成。不要混在一起否则算费用的时候逻辑会很混乱。预约订单表parking_order这是整张系统里最重要的一张表字段建议这样设计order_no 订单编号如日期随机字符串给用户看和查user_id 预约用户space_id 预约车位start_time 预约开始时间end_time 预约结束时间简称预计离场时间enter_time 实际入场时间可为空exit_time 实际出场时间可为空status 0 已预约 / 1 已完成 / 2 已取消 / 3 超时未入场amount 实际收费金额预约开始和结束时间一个用于展示一个用于超时判断后者用于最终的计费。实际入场前费用都还不产生预约只是占住了车位一旦入场就开始计时直到出场时结算。计费规则表parking_fee_rule这个表是留给“管理员可以自己配置价格”这个加分项的。字段设计为规则名称、生效区域、首小时费用、超出部分每半小时费用、每日封顶金额、生效状态。有这个表你的系统就在“硬编码计费”之上多了一个灵活性答辩的时候很加分。出入记录表parking_record这相当于停车流水字段包括订单号、车位号、入场时间、出场时间、应收金额、实收金额、支付方式。它是为了后续做统计报表用的。管理员操作日志表可选如果做了删除订单、修改价格这些敏感操作最好记录一个操作日志。这个不是必须但能体现你在系统安全性上的思考。3.2 订单状态流转图文字版描述我用文字把这个流程描述一下你建表后写代码就会很清楚用户选择一个空闲车位预约 → 插入一条订单状态为“已预约”车主的车进入停车场管理员确认入场或者车主自己点击“我已入场” → 订单记录入场时间车位状态变为“占用”车主驶离点击“结算离场” → 系统按入场时间和当前时间计算费用订单状态变为“已完成”车位状态变为“空闲”如果预约成功后一直没有入场超过预约保留时间 → 定时任务把订单状态改为“超时取消”车位状态恢复“空闲”。这套流程下来数据的每一步变化都是有依据的不会出现“订单说完成但车位还是占用”这种经不起推敲的脏数据。3.3 建表时的三个细节提醒金额字段用 decimal 类型不要用 float/double。浮点数在计算金额时会有精度问题停车费这种分、角级别的计算decimal(10, 2) 最稳妥。时间字段全部用 datetime不要只用 date。这会涉及跨天计费的问题后面我会专门讲。凡是作为条件查询的字段比如 order 表的 user_id、statusspace 表的 area_id记得建索引。数据量虽然不大但加索引能让 SQL 更规范也是一个可以讲的点。4. 核心功能实现最难的是并发和计费逻辑架构搭好、表建好了开始写业务代码。这一部分我按功能模块来拆重点讲预约、计费、超时释放这几个核心逻辑的实现思路。4.1 登录认证用 JWT 就够了前后端分离的项目登录认证基本都是 JWT。用户在登录接口输入账号密码后端校验通过后生成一个 token里面带上 userId 和 role签名后返回给前端。前端每次请求都在 Header 里带上 “Authorization: Bearer token”后端写一个拦截器校验 token。校验逻辑可以在 Spring 的 HandlerInterceptor 里做也可以直接用 Sa-Token 或者 Spring Security。但我建议如果只是毕设用拦截器 自定义注解 JWT 工具类就够了代码量不大且你能把原理讲得很清楚Spring Security 体系对新手来说配置成本较高除非你非常熟练否则答辩时很容易被细节问住。JWT 本身有几个坑要注意token 过期时间不要设太长一般建议 2 小时配合前端重新登录或者刷新 token一定要加签名不要往 payload 里放密码等敏感信息做登出功能时仅删除前端存的 token 是不太够的但因为毕设场景黑白名单方案讲一下思路即可不必强做。4.2 车位预约怎么防止同一车位被两个人同时抢到车位预约看起来很简单查询空闲车位 → 点击预约 → 改状态。但这里有并发问题。两个用户同时看到一个车位空闲同时点击预约如果逻辑只做到“先查再改”可能两个人都预约成功。数据库层面会出现数据不一致。解决思路有三种从小到大排列方案一数据库乐观锁。在车位表加一个 version 字段更新时带上版本号判断UPDATE parking_space SET status 1, version version 1 WHERE id ? AND status 0。只要更新行数为 1说明抢到了为 0说明被别人抢先。这个方案简单可靠我推荐你优先用。方案二Redis 分布式锁。用 SETNX 给车位加锁加锁成功才能预约用完删锁。这个方案在高并发下表现更好但引入 Redis 后你需要解释清楚分布式锁的过期时间、锁的释放时机复杂度上了一个台阶。方案三数据库行锁。用 SELECT ... FOR UPDATE 先把这行车锁住再更新状态。这个方案在事务里也有效但并发相对较低。我建议毕设阶段用“乐观锁 数据库事务”就足够了。用 MyBatis-Plus 写更新时可以自定义一个 SQLupdate idoccupySpaceById UPDATE parking_space SET status 1, version version 1 WHERE id #{spaceId} AND status 0 /update然后在 Service 层开启事务调用更新后如果 affected 行数是 1就创建订单是 0就抛异常提示“该车位已被抢请更换车位”。这一套下来逻辑闭环答辩也能侃侃而谈。4.3 计费逻辑跨天、跨时段、封顶价这些坑一次解决计费是整个系统业务上最容易出问题的模块。最常见的需求是阶梯计费首小时 5 元之后每小时 2 元24 小时封顶 20 元。听起来简单但真正算起来有两个坑第一个坑是跨天。停车 26 小时可能跨了三天你不能简单地算“总小时 × 每小时单价”要先算有没有达到封顶天数再算剩余时间。我建议把计费做成一个独立的方法输入入场时间和出场时间输出金额。核心逻辑是总时长 出场时间 - 入场时间 先算整天数满24小时的部分按封顶价算 再算不足24小时的尾数部分按首小时后每小时的规则算 总费用 整天数 × 日封顶价 尾数费用注意尾数部分如果不足 1 小时很多停车场的规则还是按 1 小时算。我建表时专门设计了“最小计费单位”字段这样规则灵活代码也不用写死。第二个坑是预约免费时长。比如平台承诺“预约后 15 分钟内免费入场检查”那计费开始时间应该是入场时间而不是预约时间。这些边界条件在答辩时极其容易被问演示前一定要把计算过程想明白。还有一个容易被忽视的点就是计算精度。所有金额用 分 或者 BigDecimal 的元最终结果四舍五入到“分”。不要用 double 乘除法会出现 0.1 0.2 0.30000000000000004 这种尴尬情况。4.4 超时未入场的自动释放用定时任务还是延迟消息预约了车位但一直不来的情况很常见。设计上要给一个保留时间比如 30 分钟。这个功能通常用两种做法一种是用 Spring 自带的 Scheduled 定时任务每分钟扫描一次状态为“已预约”、且 start_time 已经超过预约保留时间的订单把它们批量标记为“超时取消”然后把车位状态改回空闲。优点是实现简单缺点是每分钟扫一次超时判断不够精确。另一种是使用延迟队列或 RocketMQ 延迟消息预约成功后发一条延迟消息30 分钟后消费并判断用户是否入场。这个方案响应及时但要引入 MQ复杂度偏高。毕业设计我建议用定时任务方案然后在 config 类里开启 EnableScheduling再写一个定时任务方法Component public class OrderTimeoutTask { Scheduled(fixedRate 60000) public void releaseTimeoutOrders() { LocalDateTime deadline LocalDateTime.now().minusMinutes(30); // 查询 status0已预约且 create_time deadline 的订单 // 标记为超时取消释放车位 } }有个细节需求上“预约 30 分钟后超时”还是“超过预约的 start_time 后超时”客户理解往往不一样。你把它做成常量配置放在配置文件里比如parking.order.timeout-minutes30到时候改起来非常方便也是答辩时的加分项。4.5 车位状态实时更新页面怎么感知变化如果是前后端分离车位状态怎么同步到前端是个体验问题。两种方案轮询和 WebSocket。轮询就是前端定时器每隔几秒请求一次车位状态接口实现简单、可靠但不够实时且有一定的多余请求量。WebSocket 则在服务端建立长连接车位状态一变就主动推给前端效果更实时。毕设阶段我的建议是用轮询就够了最大程度保证稳定且实现成本低。如果老师提到“要体现智能化”可以考虑用 WebSocket 断点续传的思路来刷加分并且答辩时可以讲清楚心跳机制、重连机制这些是实打实的技术点比做一堆花哨页面更得体。4.6 管理后台的统计报表是体现“系统完整度”的利器除了预约和收费管理后台最好能给管理者展示停车场的“经营状况”比如今日收入、今日入场车辆数、当前车位占用率、近七日停车趋势。这些数据都不需要单独建表直接用 SQL 聚合查询订单表就行。例如今日收入SELECT COUNT(*), IFNULL(SUM(amount), 0) FROM parking_order WHERE exit_time CURDATE() AND status 1当前占用率SELECT COUNT(*) / (SELECT COUNT(*) FROM parking_space) AS occupancy_rate FROM parking_space WHERE status 2用过程画一个简单的柱状图或者图表放在管理员首页整个系统的完整度和展示效果立刻不一样。即使你用的是 Thymeleaf用 ECharts 引入几张图表也很容易。5. 我把这套系统从 0 搭到能答辩踩过的坑和结束前的叮嘱很多细节在你真正动手做的时候才会体会到。这一节我总结一下建表和写业务过程中的血泪教训以及答辩时老师最爱问的问题帮你提前准备好。5.1 时间处理的坑最容易翻车Java 8 之后一定要用 LocalDateTime别再用 java.util.Date。LocalDateTime 配合 MyBatis-Plus 的自动填充功能可以优雅地处理 create_time、update_time 字段。另外跨天计费、预约过期、统计报表里的时间范围查询统统要小心时区问题。本地开发的机器和服务器有可能时区不同MySQL 连接字符串里务必带上 serverTimezoneAsia/Shanghai否则时间会差 8 小时找问题时长会很折腾。5.2 状态字段用数字还是字符串我的建议数据库里状态用 tinyint 存数字就好0、1、2 这种但在 Java 代码里不要到处散落魔法数字。建议建一个枚举类比如public enum SpaceStatus { FREE(0, 空闲), BOOKED(1, 已预约), OCCUPIED(2, 已占用); private final Integer code; private final String desc; // 构造方法、getter 省略 }这么做的好处是代码可读性高前端传到后端的状态也是数字但展示给用户时是文字逻辑清楚多了。5.3 统一返回格式和全局异常真的要在一开始就建好很多新手容易犯的毛病是每个接口返回的数据格式都不一样有的返回 Map有的返回实体有的直接返回字符串。前端对接时会有种想摔键盘的冲动。我建议一开始就定义一个统一的返回类public class ResultT { private Integer code; private String message; private T data; // 成功、失败静态方法 }所有 Controller 都返回 Result配合全局异常处理器把业务异常统一拦截成标准格式。这样前端只用处理一种结构代码也整洁很多。另外全局异常里要考虑校验异常、参数格式异常、空指针异常这几个大类。尤其是校验异常前端表单不符合规则时返回的信息要友好不要把堆栈直接丢给前端。5.4 答辩时老师最喜欢问这些问题提前想好答案基于这个题目的高频问题我按个人经验列了一下你在答辩前要能用自己的话讲清楚车位预约是怎么防并发的答用乐观锁update 的时候判断 status 和 version。用户超时没来车位怎么释放的答定时任务扫描超时订单事务里改订单状态和车位状态。计费怎么算的跨天怎么办答按整天用封顶价尾数按首小时后单价累加用 BigDecimal 运算。密码是怎么保存的答BCrypt 加盐哈希不存明文。为什么选 SpringBoot 而不是 SSM答自动装配、内嵌服务器、快速启动减少配置文件把精力放在业务上。如果并发再大比如 1 万人同时抢 100 个车位你的方案还能扛住吗答乐观锁会有一定冲突率可以考虑 Redis 分布式锁 异步队列但单体架构下乐观锁已经能满足需求。这些问题答案没有绝对的对错但你要能自圆其说体现你思考过。很多同学挂在“背了概念但没理解”比如把乐观锁说成“锁一下数据库”那基本就会被追问到哑口无言。5.5 几个能明显提升演示效果的“小心机”这一部分算是我传授点私货不是必须做但做了很容易让人眼前一亮做一个模拟数据的初始化脚本。用 Spring 的 CommandLineRunner 或者数据初始化 SQL启动项目时自动生成一批车位数据和几条订单记录演示时不用手动造数据展示出来的效果更完整。在管理员后台做一个“一键模拟入场、一键模拟出场”的按钮或者直接把业务操作做在演示页面上这样当面演示预约、入场、计费、出场整个闭环时不会因为缺少硬件而手足无措。把项目打成 jar 包确保在任何一台干净环境的机器上都能一键启动运行。很多同学的代码在自己电脑上跑得好好的换一台电脑就缺依赖、启动不了这种技术在答辩现场通常会比较狼狈。给自己准备一个简短的文档包含接口文档、表结构说明、核心流程说明。老师翻的时候觉得条理清晰自然容易给高分。5.6 最后再分享一个个人习惯我每次写完这种带状态流转的项目都会先把核心流程从头到尾在纸上走一遍比如从用户注册到预约成功从入场到最终结算离场每一步数据库里哪些字段变、变成什么值、界面有什么反馈、异常情况下回滚到哪个状态全部写清楚。然后照着这个流程手动测一遍。这样做的效果是很多逻辑上的死角在写代码之前就被暴露了真正写实现的时候反而很顺。这个项目做下来你对 SpringBoot 的自动装配、AOP、事务管理、MyBatis-Plus 的实际使用、并发控制的基本手段、定时任务、JWT 认证这些知识点都会有一个很扎实的落地理解。这些东西不是背出来的是真正“写过、调过、跑通过”的。把这套流程捋顺了你的毕业设计代码质量大概会超过身边一半以上的同学而且面试官再问你项目经验你也能讲得很有底气。