停车场管理系统毕业设计实战:SpringBoot+车牌识别+计费策略全链路
简介这份毕业设计资源聚焦停车场管理系统的完整实现面向计算机相关专业学生与课程设计开发者帮助解决车辆进出、车位调度、费用结算等业务场景的工程落地问题。资源包共592个文件约142.93MB以184个class编译文件、89个java源码、135个xml配置、66个jar依赖为主辅以34个js、26个html前端页面及4个sql脚本覆盖车牌识别、后台逻辑、数据库交互等模块。已有2528人学习下载说明其参考价值获得一定认可。读者可从中获取可运行的工程结构、数据库表设计思路、前后端分层代码与依赖配置便于对照理解Spring Boot或Django类框架下的业务实现并借鉴图像处理与支付接口集成的组织方式适合作为课程设计或毕业设计的起步模板与排错参考。1. 停车场管理系统毕业设计从选题到跑通我踩过的坑比代码还多每年一到三四月计算机毕业设计选题就成了大四学生最头疼的事。有人选大数据方向结果卡在环境配置有人选人工智能发现数据集根本跑不动。停车场管理系统这个题目看起来平平无奇但它恰好卡在一个很舒服的位置——业务逻辑够清晰技术栈够主流答辩时老师也能听懂你在做什么。我带过几届学生的毕设也自己做过类似的工程说实话这个题目如果只做成“增删改查一个数据库”大概率会被答辩组追问到哑口无言。但如果你在车牌识别、车位调度、计费策略上做出一点真东西它就能变成一份拿得出手的作品。这篇文章不讲空话我会把停车场管理系统从技术选型、数据库设计、核心模块实现到部署调试的完整路径拆开让你看完就能动手少走我当年走过的弯路。2. 停车场管理系统的技术选型别一上来就微服务2.1 为什么 SpringBoot MySQL 是毕业设计的稳妥组合很多同学一听到“管理系统”就想到 SSM觉得老框架稳妥。但 SSM 的 XML 配置量在毕设周期里是实打实的负担光是整合 MyBatis 和 Spring MVC 就能耗掉一周。SpringBoot 把 Tomcat 内嵌、自动装配、起步依赖这些事全包了你只需要关注业务代码。数据库层面MySQL 8.0 是当前主流社区资料多遇到问题搜得到答案。前端如果不想花太多时间Thymeleaf 或者 Vue Element UI 都行前者适合快速出活后者适合想在前端也展示能力的同学。我一般会建议学生用 SpringBoot 2.7.x 或 3.x 搭配 MyBatis-PlusMyBatis-Plus 的 BaseMapper 和 LambdaQueryWrapper 能把单表 CRUD 的代码量压到极低省下来的时间拿去打磨车牌识别和计费逻辑。JDK 版本选 17 或 11 都行但要注意 SpringBoot 3.x 要求 JDK 17 起步如果你的学校机房还在用 JDK 8那就老老实实选 SpringBoot 2.7。2.2 车牌识别模块的三种落地路径与选型对比停车场管理系统最核心的差异化功能就是车牌识别。没有它你的系统就是一个普通的车位信息录入工具。常见的做法有三种第一种是调用云服务 API比如百度 AI 开放平台的车牌识别接口。优点是识别率高、接入快缺点是依赖网络答辩现场如果网络不稳定就尴尬了。第二种是本地部署开源模型比如 HyperLPR 或者基于 OpenCV 的传统图像处理方案。HyperLPR 对中文车牌的识别效果在开源方案里算不错的安装也相对简单。第三种是自己训练一个 YOLO 模型做车牌检测再用 CRNN 做字符识别这套方案工作量最大但如果你选的是人工智能方向的毕设这条路反而更对味。方案开发周期识别率答辩亮点适用方向云 API1-2 天高一般软件工程HyperLPR 本地部署3-5 天中高较好计算机/物联网YOLOCRNN 自训练2-3 周取决于数据强人工智能选型建议很直接如果你的毕设方向是软件工程或计算机科学与技术用 HyperLPR 本地部署就够了既展示了技术深度又不会在训练模型上耗掉太多时间。如果是人工智能专业那就老老实实走 YOLO 路线数据集可以用 CCPD 或者自己标注一批。2.3 数据库表结构设计五张核心表撑起整个系统停车场管理系统的数据库不需要太复杂但几张核心表必须设计到位。下面是我常用的表结构字段经过多轮调整基本能覆盖大部分业务场景。-- 用户表系统管理员和操作员 CREATE TABLE sys_user ( user_id BIGINT NOT NULL AUTO_INCREMENT COMMENT 主键, username VARCHAR(50) NOT NULL COMMENT 登录名, password VARCHAR(128) NOT NULL COMMENT 密码BCrypt加密, role TINYINT DEFAULT 1 COMMENT 角色0管理员 1操作员, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (user_id), UNIQUE KEY uk_username (username) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 车位表记录每个车位的基本信息和状态 CREATE TABLE parking_spot ( spot_id INT NOT NULL AUTO_INCREMENT, spot_no VARCHAR(20) NOT NULL COMMENT 车位编号如A-001, area VARCHAR(10) DEFAULT A COMMENT 区域, status TINYINT DEFAULT 0 COMMENT 0空闲 1占用 2预约 3维修, type TINYINT DEFAULT 0 COMMENT 0普通 1新能源 2无障碍, PRIMARY KEY (spot_id), UNIQUE KEY uk_spot_no (spot_no) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 车辆表登记车辆信息 CREATE TABLE vehicle ( vehicle_id BIGINT NOT NULL AUTO_INCREMENT, plate_no VARCHAR(10) NOT NULL COMMENT 车牌号, owner_name VARCHAR(50) DEFAULT NULL, phone VARCHAR(20) DEFAULT NULL, vehicle_type TINYINT DEFAULT 0 COMMENT 0临时车 1月租车 2VIP, PRIMARY KEY (vehicle_id), UNIQUE KEY uk_plate (plate_no) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 停车记录表每次进出产生一条记录 CREATE TABLE parking_record ( record_id BIGINT NOT NULL AUTO_INCREMENT, plate_no VARCHAR(10) NOT NULL, spot_id INT DEFAULT NULL, entry_time DATETIME NOT NULL COMMENT 入场时间, exit_time DATETIME DEFAULT NULL COMMENT 出场时间, fee DECIMAL(10,2) DEFAULT 0.00 COMMENT 费用, status TINYINT DEFAULT 0 COMMENT 0停车中 1已出场, PRIMARY KEY (record_id), KEY idx_plate (plate_no), KEY idx_entry (entry_time) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 计费规则表支持不同车型不同费率 CREATE TABLE billing_rule ( rule_id INT NOT NULL AUTO_INCREMENT, vehicle_type TINYINT NOT NULL, free_minutes INT DEFAULT 15 COMMENT 免费时长, hourly_rate DECIMAL(6,2) DEFAULT 5.00 COMMENT 每小时费率, daily_max DECIMAL(8,2) DEFAULT 40.00 COMMENT 每日封顶, PRIMARY KEY (rule_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;这几张表的逻辑关系是车辆入场时系统根据车牌号查 vehicle 表判断车型然后在 parking_spot 里找一个空闲车位分配同时往 parking_record 插一条入场记录。出场时更新记录的 exit_time 和 fee并把车位状态改回空闲。billing_rule 表让计费策略可配置答辩时老师问“费率改了怎么办”你可以直接说改表就行不用动代码。注意plate_no 字段不要设成唯一索引在 parking_record 表上因为同一辆车可以多次进出每次都是一条新记录。唯一约束只加在 vehicle 表上。3. 核心功能实现从车牌识别到出场计费的完整链路3.1 用 HyperLPR 在本地跑通车牌识别的完整代码HyperLPR 的安装不算复杂但依赖项容易出问题。我一般会在 Python 3.8 到 3.10 的环境里装太新的版本反而容易遇到兼容性问题。安装命令如下pip install hyperlpr3 # 如果安装慢可以换清华源 pip install hyperlpr3 -i https://pypi.tuna.tsinghua.edu.cn/simple装完之后识别一张车牌图片的代码非常简洁import cv2 import hyperlpr3 as lpr3 # 创建识别器参数说明 # detector_model_path 和 rec_model_path 不传则用内置模型 # 如果显存不够可以设置 use_gpuFalse catcher lpr3.LicensePlateCatcher(detect_levellpr3.DETECT_LEVEL_LOW) def recognize_plate(image_path): 输入图片路径 输出识别结果列表每个元素包含车牌号和置信度 image cv2.imread(image_path) if image is None: raise ValueError(f无法读取图片{image_path}) # 识别结果格式[(京A12345, 0.98), ...] results catcher(image) plates [] for plate_no, confidence in results: plates.append({ plate: plate_no, confidence: round(confidence, 4) }) return plates if __name__ __main__: res recognize_plate(test_car.jpg) for item in res: print(f车牌号{item[plate]}置信度{item[confidence]})这段代码的关键在detect_level参数。DETECT_LEVEL_LOW适合图片中车牌较大的场景速度快如果车牌在画面中很小比如监控摄像头远距离拍摄就要用DETECT_LEVEL_HIGH但耗时会明显增加。use_gpu参数默认是 True如果你的电脑没有 NVIDIA 显卡或者 CUDA 环境没配好一定要显式设成 False否则会报错。识别出来的车牌号要跟数据库联动。我一般会在 SpringBoot 里写一个 Controller接收前端上传的图片调用 Python 脚本或者通过 HTTP 调用一个独立的 Python 服务拿到车牌号后查 vehicle 表判断是临时车还是月租车然后走不同的入场逻辑。3.2 车位自动分配与出场计费的 Java 实现车位分配的核心逻辑是从 parking_spot 表里找一条 status0 且 type 匹配的记录把它改成 status1同时往 parking_record 插一条数据。这里要注意并发问题——如果两个入口同时有车进来不加锁就会把同一个车位分配给两辆车。我一般用 MySQL 的乐观锁或者 Redis 分布式锁来解决毕设环境里用SELECT ... FOR UPDATE就够了。Service public class ParkingService { Autowired private ParkingSpotMapper spotMapper; Autowired private ParkingRecordMapper recordMapper; Autowired private BillingRuleMapper billingRuleMapper; /** * 车辆入场 * param plateNo 车牌号 * param vehicleType 车型0临时车 1月租车 2VIP * return 分配的车位编号 */ Transactional(rollbackFor Exception.class) public String enter(String plateNo, int vehicleType) { // 1. 查找空闲车位加行锁防止并发分配 ParkingSpot spot spotMapper.selectOne( new LambdaQueryWrapperParkingSpot() .eq(ParkingSpot::getStatus, 0) .last(LIMIT 1 FOR UPDATE) ); if (spot null) { throw new BusinessException(车位已满请等待); } // 2. 更新车位状态为占用 spot.setStatus(1); spotMapper.updateById(spot); // 3. 插入停车记录 ParkingRecord record new ParkingRecord(); record.setPlateNo(plateNo); record.setSpotId(spot.getSpotId()); record.setEntryTime(LocalDateTime.now()); record.setStatus(0); recordMapper.insert(record); return spot.getSpotNo(); } /** * 车辆出场计算费用 */ Transactional(rollbackFor Exception.class) public BigDecimal exit(String plateNo) { // 1. 查当前未出场的记录 ParkingRecord record recordMapper.selectOne( new LambdaQueryWrapperParkingRecord() .eq(ParkingRecord::getPlateNo, plateNo) .eq(ParkingRecord::getStatus, 0) .orderByDesc(ParkingRecord::getEntryTime) .last(LIMIT 1) ); if (record null) { throw new BusinessException(未找到入场记录); } // 2. 计算停车时长分钟 LocalDateTime exitTime LocalDateTime.now(); long minutes Duration.between(record.getEntryTime(), exitTime).toMinutes(); // 3. 查询计费规则 BillingRule rule billingRuleMapper.selectOne( new LambdaQueryWrapperBillingRule() .eq(BillingRule::getVehicleType, 0) // 简化处理实际按车辆类型查 ); // 4. 计费逻辑免费时长内不收费超出按小时计不足一小时按一小时 BigDecimal fee BigDecimal.ZERO; if (minutes rule.getFreeMinutes()) { long billableMinutes minutes - rule.getFreeMinutes(); long hours (billableMinutes 59) / 60; // 向上取整 fee rule.getHourlyRate().multiply(BigDecimal.valueOf(hours)); if (fee.compareTo(rule.getDailyMax()) 0) { fee rule.getDailyMax(); } } // 5. 更新记录 record.setExitTime(exitTime); record.setFee(fee); record.setStatus(1); recordMapper.updateById(record); // 6. 释放车位 ParkingSpot spot spotMapper.selectById(record.getSpotId()); if (spot ! null) { spot.setStatus(0); spotMapper.updateById(spot); } return fee; } }这段代码里有几个参数需要根据实际情况调整。freeMinutes默认 15 分钟这是很多商场的标准但学校答辩时你可以设成 30 分钟来演示“免费时段”的效果。hourlyRate和dailyMax从数据库读方便演示时改费率。计费逻辑里(billableMinutes 59) / 60是向上取整的写法比如停了 61 分钟算 2 小时。提示Transactional注解在出场方法上必须加因为更新记录和释放车位是两个操作如果释放车位失败但记录已更新数据就不一致了。3.3 用 Postman 和 JUnit 验证接口的正确性代码写完了不代表能跑通。我一般会先用 Postman 把几个核心接口过一遍确认请求参数和返回格式没问题然后再写 JUnit 测试覆盖边界情况。入场接口的测试用例至少包括正常入场、车位已满、同一车牌重复入场。出场接口要测正常出场计费、未入场直接出场、免费时段内出场。SpringBootTest class ParkingServiceTest { Autowired private ParkingService parkingService; Test void testEnterAndExit() { // 入场 String spotNo parkingService.enter(京A12345, 0); assertNotNull(spotNo); // 出场 BigDecimal fee parkingService.exit(京A12345); assertTrue(fee.compareTo(BigDecimal.ZERO) 0); } Test void testExitWithoutEntry() { assertThrows(BusinessException.class, () - { parkingService.exit(京B99999); }); } }JUnit 测试跑通之后再用 Postman 模拟前端调用确认 JSON 序列化和日期格式没问题。日期格式我一般用JsonFormat(pattern yyyy-MM-dd HH:mm:ss)统一处理避免前端拿到时间戳不知道怎么显示。4. 避坑与排查那些让我熬夜到凌晨三点的翻车现场4.1 车牌识别中文乱码OpenCV 读图路径的隐藏陷阱现象HyperLPR 识别结果里车牌号的中文变成问号或者乱码。原因OpenCV 的cv2.imread在 Windows 上不支持中文路径如果图片放在“D:\毕业设计\测试图片\car.jpg”这种路径下读出来就是 None。解决要么把图片路径改成全英文要么用cv2.imdecode(np.fromfile(path, dtypenp.uint8), -1)来读。我一般直接建议学生把测试图片放在项目根目录的test_images文件夹下路径全英文省事。4.2 SpringBoot 上传图片报 413请求体大小限制现象前端上传一张 3MB 的车牌照片后端返回 413 Request Entity Too Large。原因SpringBoot 默认的 multipart 请求体大小限制是 1MB。解决在application.yml里加两行配置spring: servlet: multipart: max-file-size: 10MB max-request-size: 10MB改完之后重启服务问题解决。这个坑几乎每个做图片上传的毕设都会遇到记住就行。4.3 MySQL 8.0 时区报错The server time zone value is unrecognized现象SpringBoot 连接 MySQL 8.0 时启动报错提示时区无法识别。原因MySQL 8.0 的默认时区是 UTC而 JDBC 驱动需要明确的时区设置。解决在 JDBC URL 后面加上?serverTimezoneAsia/Shanghai完整写法是jdbc:mysql://localhost:3306/parking?serverTimezoneAsia/ShanghaiuseUnicodetruecharacterEncodingutf8。这个问题在 MySQL 5.7 上不会出现所以很多教程里没提但 8.0 用户必踩。4.4 车位分配并发问题两辆车抢同一个车位现象压测时发现同一个车位被分配给了两辆车。原因SELECT和UPDATE之间没有加锁两个线程同时查到同一条空闲记录。解决在查询语句后面加FOR UPDATE并且把整个方法放在Transactional里。注意FOR UPDATE必须在事务中才生效如果你在 Service 方法上没加Transactional加了FOR UPDATE也没用。4.5 前端日期显示为时间戳Jackson 序列化配置遗漏现象后端返回的entryTime在前端显示为1700000000000这样的数字。原因Jackson 默认把LocalDateTime序列化成时间戳数组或数字。解决在实体类的日期字段上加JsonFormat(pattern yyyy-MM-dd HH:mm:ss, timezone GMT8)或者在全局配置里注册 JavaTimeModule。我一般直接在字段上加注解简单直接。5. 让答辩组眼前一亮的两个进阶技巧5.1 用 ECharts 做车位热力图从数据到可视化答辩时老师最喜欢看的就是可视化。你可以在系统里加一个车位热力图页面用 ECharts 的 heatmap 组件展示不同区域的车位占用率。数据来源很简单按区域 group by 统计parking_spot表里 status1 的数量除以该区域总车位数得到一个 0 到 1 的占用率。前端用 ECharts 渲染成颜色深浅不同的方块占用率越高颜色越红。这个功能代码量不大但视觉效果很好答辩时打开页面就能看到哪些区域车位紧张老师会觉得你的系统有“管理”的味道而不只是增删改查。5.2 计费策略的可配置化让老师随便改费率很多同学把计费规则写死在代码里老师一问“如果费率改了怎么办”就卡住了。我的做法是把billing_rule表做成可配置的前端加一个“计费规则管理”页面管理员可以修改免费时长、小时费率、每日封顶。改完之后下一次出场计费自动用新规则。这个设计的巧妙之处在于它把“业务逻辑”和“业务规则”分开了代码不用动改数据就行。答辩时你可以现场演示把免费时长从 15 分钟改成 30 分钟然后模拟一辆停了 20 分钟的车出场费用从 5 元变成 0 元。这个演示效果比你说十句“系统很灵活”都有用。最后说一个我自己的习惯每次改完代码不管多小的改动都要重新跑一遍入场和出场的完整流程。我见过太多人改了一个字段名结果出场计费报错答辩前一天晚上才发现。停车场管理系统的核心链路就是“识别→入场→计费→出场”这条链路跑通了毕设就稳了。希望帮到你。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联
返回资讯列表 →