
简介这是一套基于Java、Spring Boot、Vue与MySQL开发的停车场管理系统完整毕业设计资源面向高校计算机相关专业学生及需要课程设计、期末大作业参考的开发者可直接用于毕设答辩或作为企业级项目学习案例。压缩包共852个文件约43.56MB涵盖113个Java后端源码、59个Vue前端组件、161个JavaScript脚本、44个HTML页面、52个CSS样式及1个SQL数据库脚本另附论文文档与部署说明结构清晰、模块分明。系统实现车位管理、车辆出入登记、数据统计等核心功能前后端分离界面美观、操作便捷并经过严格调试确保可运行。目前已有86人学习下载。读者可从中获得完整源码、数据库脚本、论文参考及Maven构建配置快速理解Spring Boot与Vue的整合思路掌握数据库表结构设计与前后端交互逻辑适合作为学习模板或二次开发基础。1. 停车场管理系统从一份毕设源码里拆出能跑通的技术栈很多同学拿到“基于javaspringbootvuemysql的停车场管理系统”这类毕设项目时第一反应是解压、找README、照着文档敲命令结果卡在数据库连不上、前端跨域报错、SpringBoot版本冲突这些地方三天都跑不起来。这个标题背后其实是一套非常典型的Java全栈组合SpringBoot做后端接口Vue做前端页面MySQL存业务数据核心业务是车辆进出场、车位状态管理、计费规则和订单记录。它适合正在做毕设的计算机专业学生也适合想用一个完整项目练手SpringBootVue前后端分离的初级Java工程师。这篇文章不讲空泛的架构图而是按“环境怎么搭、表怎么建、接口怎么写、前后端怎么联调、坑怎么绕”的顺序把一套停车场管理系统从源码到跑通的路径拆开讲清楚。你跟着走一遍至少能拿到一个能演示、能答辩、能继续改的版本。2. 环境准备与项目结构先把SpringBoot和Vue的版本对齐2.1 为什么版本对齐比写代码更先要做停车场管理系统这类毕设项目翻车最多的地方不是业务逻辑而是环境。SpringBoot 2.x和3.x的差异很大Vue 2和Vue 3的写法也不一样MySQL 5.7和8.0在驱动类和连接串上也有区别。你拿到源码后第一件事不是急着mvn spring-boot:run而是先看pom.xml里的SpringBoot版本、package.json里的Vue版本、以及数据库脚本里的建表语句用的是哪个MySQL版本。常见做法是如果源码用SpringBoot 2.7.x就配JDK 8或11如果用SpringBoot 3.x就必须JDK 17起步。Vue这边如果package.json里是vue: ^2.6.x你就别用Vue 3的createApp写法去改它。我一般会先把三个东西确认清楚JDK版本、Node版本、MySQL版本。JDK用java -version看Node用node -v看MySQL用mysql --version看。这三个版本对不上后面全是玄学问题。2.2 后端项目结构controller、service、mapper三层怎么分一个标准的SpringBoot停车场管理系统后端目录结构通常是这样src/main/java/com/parking/ ├── controller/ // 接口层接收前端请求 ├── service/ // 业务逻辑层处理计费、车位分配 ├── mapper/ // 数据访问层MyBatis或MyBatis-Plus ├── entity/ // 数据库实体类 ├── config/ // 跨域配置、拦截器配置 └── ParkingApplication.javacontroller层只做参数校验和调用service不要在里面写业务计算。service层是停车场系统的核心比如“车辆入场时分配车位”“出场时按小时计费”都写在这里。mapper层用MyBatis-Plus的话很多单表操作可以直接继承BaseMapper不用手写SQL。// 示例车辆入场接口 RestController RequestMapping(/api/vehicle) public class VehicleController { Autowired private VehicleService vehicleService; PostMapping(/entry) public Result entry(RequestBody EntryDTO dto) { // 参数校验车牌号不能为空 if (dto.getPlateNumber() null || dto.getPlateNumber().isEmpty()) { return Result.error(车牌号不能为空); } return vehicleService.vehicleEntry(dto); } }这段代码的逻辑是前端传车牌号和入场时间后端先做基础校验再交给service处理。Result是统一返回体包含code、msg、data三个字段。参数说明EntryDTO里至少要有plateNumber和entryTime如果系统支持预约车位还要加parkingSpaceId。2.3 前端项目结构Vue页面和API请求怎么组织Vue前端一般用vue-cli或vite搭建目录结构如下src/ ├── api/ // 封装axios请求 ├── views/ // 页面组件 ├── router/ // 路由配置 ├── store/ // Vuex状态管理可选 └── main.jsapi目录下按模块建文件比如vehicle.js、parkingSpace.js、order.js。每个文件里封装对应的请求方法页面组件只调用这些方法不直接写axios.get。// src/api/vehicle.js import request from /utils/request export function vehicleEntry(data) { return request({ url: /api/vehicle/entry, method: post, data: data }) }request是封装好的axios实例里面统一配了baseURL和拦截器。baseURL一般指向后端地址比如http://localhost:8080。这样做的目的是后端地址变了只改一个地方不用每个页面都改。提示如果前端启动后请求报404先看baseURL有没有写对再看后端接口的RequestMapping路径是否匹配。跨域问题在开发阶段用proxy解决生产环境用Nginx反向代理。3. 数据库设计停车场管理系统的表结构和建表脚本3.1 核心表车位表、车辆表、订单表怎么设计停车场管理系统的数据库设计核心就三张表parking_space车位表、vehicle车辆进出记录表、order订单表。如果要做会员或月卡再加member表。车位表里要有车位编号、区域、状态空闲/占用/维修。车辆表里要有车牌号、入场时间、出场时间、车位ID。订单表里要有车牌号、计费金额、支付状态、创建时间。CREATE TABLE parking_space ( id INT NOT NULL AUTO_INCREMENT, space_no VARCHAR(20) NOT NULL COMMENT 车位编号, area VARCHAR(50) DEFAULT NULL COMMENT 区域, status TINYINT DEFAULT 0 COMMENT 0空闲 1占用 2维修, PRIMARY KEY (id), UNIQUE KEY uk_space_no (space_no) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE vehicle ( id INT NOT NULL AUTO_INCREMENT, plate_number VARCHAR(20) NOT NULL COMMENT 车牌号, space_id INT DEFAULT NULL COMMENT 车位ID, entry_time DATETIME DEFAULT NULL, exit_time DATETIME DEFAULT NULL, PRIMARY KEY (id), KEY idx_plate (plate_number) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE order ( id INT NOT NULL AUTO_INCREMENT, plate_number VARCHAR(20) NOT NULL, amount DECIMAL(10,2) DEFAULT 0.00 COMMENT 金额, pay_status TINYINT DEFAULT 0 COMMENT 0未支付 1已支付, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;建表时注意space_no加唯一索引防止重复车位编号。plate_number加普通索引因为查询车辆记录时经常按车牌号查。金额字段用DECIMAL不要用FLOAT避免精度问题。3.2 计费规则表按小时还是按次如果系统支持不同计费规则建议单独建一张billing_rule表CREATE TABLE billing_rule ( id INT NOT NULL AUTO_INCREMENT, rule_name VARCHAR(50) NOT NULL, free_minutes INT DEFAULT 15 COMMENT 免费时长分钟, hourly_rate DECIMAL(10,2) DEFAULT 5.00 COMMENT 每小时费率, daily_max DECIMAL(10,2) DEFAULT 50.00 COMMENT 每日封顶, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;free_minutes控制免费停车时长hourly_rate是超出后的每小时费用daily_max是单日最高收费。计费逻辑写在service层出场时间减入场时间先扣免费时长再按小时向上取整最后和daily_max比较取小值。3.3 数据库连接配置application.yml里几个必调参数SpringBoot的数据库配置在application.yml或application.properties里spring: datasource: url: jdbc:mysql://localhost:3306/parking_db?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: your_password driver-class-name: com.mysql.cj.jdbc.DriverserverTimezone必须配否则MySQL 8.0会报时区错误。characterEncodingutf8防止中文乱码。如果用的是MySQL 5.7驱动类可以写com.mysql.jdbc.Driver但建议统一用com.mysql.cj.jdbc.Driver兼容性更好。注意数据库名、用户名、密码要和本地实际环境一致。如果导入SQL脚本时报错先检查脚本里的CREATE DATABASE语句有没有指定字符集建议手动改成utf8mb4。4. 后端接口实现车辆进出场和计费逻辑怎么写4.1 车辆入场分配车位和更新状态车辆入场的业务逻辑分三步查车牌是否已在场内、找空闲车位、插入车辆记录并更新车位状态。这三步要放在一个事务里任何一步失败都回滚。Service public class VehicleServiceImpl implements VehicleService { Autowired private ParkingSpaceMapper spaceMapper; Autowired private VehicleMapper vehicleMapper; Transactional(rollbackFor Exception.class) Override public Result vehicleEntry(EntryDTO dto) { // 1. 检查是否已在场内 Vehicle exist vehicleMapper.selectByPlateAndExitNull(dto.getPlateNumber()); if (exist ! null) { return Result.error(该车辆已在场内); } // 2. 找空闲车位 ParkingSpace space spaceMapper.selectOneFreeSpace(); if (space null) { return Result.error(车位已满); } // 3. 插入车辆记录 Vehicle vehicle new Vehicle(); vehicle.setPlateNumber(dto.getPlateNumber()); vehicle.setSpaceId(space.getId()); vehicle.setEntryTime(new Date()); vehicleMapper.insert(vehicle); // 4. 更新车位状态 space.setStatus(1); spaceMapper.updateById(space); return Result.success(入场成功); } }Transactional保证事务一致性。selectByPlateAndExitNull是自定义查询查exit_time IS NULL的记录。selectOneFreeSpace用LIMIT 1查第一个空闲车位。参数说明EntryDTO里的plateNumber要做去空格和转大写处理避免“京A12345”和“京a12345”被当成两个车。4.2 车辆出场计费算法和时间差处理出场逻辑比入场复杂核心是算钱。用java.time.Duration算时间差比手动减毫秒更可靠。Override Transactional(rollbackFor Exception.class) public Result vehicleExit(String plateNumber) { Vehicle vehicle vehicleMapper.selectByPlateAndExitNull(plateNumber); if (vehicle null) { return Result.error(未找到在场记录); } // 计算停车时长 LocalDateTime entry vehicle.getEntryTime().toInstant() .atZone(ZoneId.systemDefault()).toLocalDateTime(); LocalDateTime exit LocalDateTime.now(); long minutes Duration.between(entry, exit).toMinutes(); // 计费免费15分钟超出按小时向上取整 BillingRule rule billingRuleMapper.selectDefault(); BigDecimal amount BigDecimal.ZERO; if (minutes rule.getFreeMinutes()) { long chargeMinutes minutes - rule.getFreeMinutes(); long hours (chargeMinutes 59) / 60; // 向上取整 amount rule.getHourlyRate().multiply(BigDecimal.valueOf(hours)); if (amount.compareTo(rule.getDailyMax()) 0) { amount rule.getDailyMax(); } } // 更新车辆出场时间 vehicle.setExitTime(new Date()); vehicleMapper.updateById(vehicle); // 释放车位 ParkingSpace space spaceMapper.selectById(vehicle.getSpaceId()); space.setStatus(0); spaceMapper.updateById(space); // 生成订单 Order order new Order(); order.setPlateNumber(plateNumber); order.setAmount(amount); order.setPayStatus(0); orderMapper.insert(order); return Result.success(amount); }Duration.between算出的minutes是总停车分钟数。(chargeMinutes 59) / 60是向上取整的常用写法比如61分钟算2小时。BigDecimal做金额运算避免double精度丢失。参数说明freeMinutes和hourlyRate从billing_rule表读方便后台调整。4.3 分页查询和条件筛选MyBatis-Plus怎么配车辆记录和订单列表一般需要分页。用MyBatis-Plus的话先配分页插件Configuration public class MybatisPlusConfig { Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }然后service里这样写public PageVehicleVO pageQuery(int pageNum, int pageSize, String plateNumber) { PageVehicle page new Page(pageNum, pageSize); LambdaQueryWrapperVehicle wrapper new LambdaQueryWrapper(); if (plateNumber ! null !plateNumber.isEmpty()) { wrapper.like(Vehicle::getPlateNumber, plateNumber); } wrapper.orderByDesc(Vehicle::getEntryTime); vehicleMapper.selectPage(page, wrapper); // 转换成VO返回 return page.convert(this::toVO); }PaginationInnerInterceptor必须加否则selectPage不会真正分页。LambdaQueryWrapper用方法引用写条件避免字段名写错。orderByDesc按入场时间倒序最新的记录排前面。提示如果分页查出来total为0但数据明明存在检查分页插件有没有被Spring扫描到。MybatisPlusConfig要放在SpringBootApplication同级或子包下。5. 前后端联调与避坑跨域、时间格式、接口404怎么排查5.1 跨域配置开发阶段用CorsConfig还是proxy前后端分离项目前端跑在localhost:8081后端跑在localhost:8080浏览器会拦跨域请求。两种解法后端加CORS配置或者前端配proxy。我一般两个都配开发阶段用proxy生产环境用Nginx。后端CORS配置Configuration public class CorsConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/**) .allowedOriginPatterns(*) .allowedMethods(GET, POST, PUT, DELETE) .allowCredentials(true) .maxAge(3600); } }allowedOriginPatterns(*)比allowedOrigins(*)更安全因为后者在allowCredentials(true)时会报错。maxAge(3600)表示预检请求缓存1小时减少OPTIONS请求次数。前端proxy配置vue.config.jsmodule.exports { devServer: { proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } } }changeOrigin: true让后端收到的请求头里的Host是后端地址避免一些框架的Host校验。5.2 时间格式前端显示和数据库存储不一致常见现象数据库里存的是2024-01-15 10:30:00前端显示成2024-01-15T10:30:00.00008:00。原因是SpringBoot默认用Jackson序列化Date格式是ISO 8601。解决方式是在application.yml里配spring: jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT8或者在实体类的Date字段上加JsonFormat(pattern yyyy-MM-dd HH:mm:ss, timezone GMT8)。推荐后者因为只影响需要格式化的字段不会全局改。5.3 接口404从URL、方法、参数三个方向查接口404是联调阶段最高频的问题。排查顺序先看前端请求的URL和后端RequestMapping是否完全一致包括大小写和斜杠再看请求方法GET/POST/PUT/DELETE是否匹配最后看参数是RequestParam还是RequestBody前端传的是query还是body。常见翻车场景后端写PostMapping(/entry)前端用axios.get(/api/vehicle/entry)方法不匹配报405而不是404但浏览器控制台可能显示成404。另一个场景后端类上写了RequestMapping(/vehicle)方法上写了PostMapping(/entry)完整路径是/vehicle/entry前端却请求/api/vehicle/entry少了/api前缀。要么后端加server.servlet.context-path/api要么前端baseURL改成http://localhost:8080。5.4 避坑清单5条血泪经验现象一启动报“Failed to configure a DataSource”。原因application.yml里数据库配置没写全或者spring.datasource.url拼写错误。 解决检查url、username、password、driver-class-name四项是否都有值url里数据库名是否和实际一致。现象二前端请求返回“Access-Control-Allow-Origin”错误。原因后端CORS没配或者配了但allowedOrigins写了具体域名而前端端口变了。 解决开发阶段用allowedOriginPatterns(*)或者前端配proxy走同源。现象三车辆出场计费金额总是0。原因entry_time存的是Date取出来转LocalDateTime时区不对导致Duration算出来是负数或0。 解决统一用LocalDateTime存时间或者Date转Instant时指定ZoneId.systemDefault()。现象四MyBatis-Plus分页查不出总数。原因分页插件没注册或者selectPage的Page对象泛型不对。 解决确认MybatisPlusConfig里有PaginationInnerInterceptor且PageVehicle的泛型是实体类。现象五Vue打包后放到SpringBoot的static目录刷新页面404。原因Vue是单页应用路由用history模式刷新时请求的是后端路径后端没有对应接口。 解决后端加一个配置把所有非/api开头的请求转发到index.html或者Vue路由改用hash模式。Controller public class IndexController { RequestMapping(value /{path:[^\\.]*}) public String forward() { return forward:/index.html; } }这段代码把所有不带点号的路径转发到index.html让Vue路由接管。注意不要拦截/api开头的请求否则接口全挂。6. 从能跑到能答辩几个让项目加分的小技巧6.1 用定时任务自动释放超时占用车位停车场系统有个现实问题车辆入场后一直不出场车位永远被占。可以加一个定时任务每天凌晨检查入场超过24小时且未出场的记录自动标记为异常并释放车位。Component public class ParkingScheduledTask { Autowired private VehicleMapper vehicleMapper; Autowired private ParkingSpaceMapper spaceMapper; Scheduled(cron 0 0 2 * * ?) public void releaseTimeoutSpaces() { ListVehicle timeoutList vehicleMapper.selectTimeoutVehicles(24); for (Vehicle v : timeoutList) { ParkingSpace space spaceMapper.selectById(v.getSpaceId()); if (space ! null) { space.setStatus(0); spaceMapper.updateById(space); } } } }Scheduled(cron 0 0 2 * * ?)表示每天凌晨2点执行。selectTimeoutVehicles(24)查入场超过24小时的记录。这个功能在答辩时很加分因为它体现了对真实业务场景的考虑。6.2 用ECharts做车位使用率看板前端加一个数据看板用ECharts画车位使用率饼图和今日收入柱状图。后端提供一个统计接口GetMapping(/statistics) public Result statistics() { long total spaceMapper.selectCount(null); long occupied spaceMapper.selectCount( new LambdaQueryWrapperParkingSpace().eq(ParkingSpace::getStatus, 1) ); BigDecimal todayIncome orderMapper.selectTodayIncome(); MapString, Object map new HashMap(); map.put(total, total); map.put(occupied, occupied); map.put(free, total - occupied); map.put(todayIncome, todayIncome); return Result.success(map); }前端用echarts的pie和bar组件渲染。这个看板不需要太复杂但能让答辩老师一眼看到“数据可视化”能力。6.3 接口文档和测试用Postman跑一遍全流程答辩前一定要用Postman把“入场→查询→出场→支付”全流程跑一遍。建一个Collection按顺序调接口检查每一步的返回值和数据库状态。常见问题入场后车位状态没变、出场后订单没生成、支付后状态没更新。这些都要在Postman里验证。我自己的习惯是每改一次service层的计费逻辑就用Postman重新跑一遍出场接口对比金额和预期是否一致。这个习惯帮我省了很多答辩现场的尴尬。6.4 论文里怎么描述技术选型论文的技术选型章节不要只写“用了SpringBoot和Vue”要写清楚为什么选。比如SpringBoot的自动配置和起步依赖减少了XML配置适合快速开发中小型管理系统Vue的组件化和双向绑定提高了前端开发效率MySQL的InnoDB引擎支持事务适合停车计费这种需要数据一致性的场景。把这些理由写进去比堆技术名词更有说服力。希望帮到你。本文还有配套的精品资源点击获取