作为一个带过不少学生做毕设、同时也被朋友问过无数次"停车场管理系统该怎么做"的人,我太清楚这类题目的套路和坑了。标题里的"基于 Java 的停车场管理系统、SpringBoot、车位预约与缴费"这几个关键词,几乎把毕设题目的命门都点出来了:绝大多数同学真正想要的是一个既能跑通演示、又能在答辩时讲清楚原理、还能顺手写进简历的项目。
但恰恰因为这类题太常见,网上搜出来的资料七零八落,有教你怎么搭脚手架的,有只给前端页面的,还有完全跑不起来的半成品代码。这篇文章我打算换个思路,不给你贴大段大段的完整代码——那东西网上多的是,而是把开发一个基于 SpringBoot 的停车场管理系统时,真正决定你能不能顺利毕业的那些关键点一次性讲透。包括需求怎么拆、表怎么建、预约和缴费的流程怎么设计、哪些坑会在你写到一半时突然冒出来、以及答辩时老师最可能追问的几个地方。
适合谁看?准备做 Java 方向毕设的同学、想系统梳理 SpringBoot 实战流程的初级开发者,以及那些还不知道"车位数怎么统计""预约超时怎么办""支付回调怎么保证不丢单"这些细节的人。
1. 为什么停车场管理系统是毕业设计的"安全牌",但也是"平庸牌"
先说个比较扎心的结论:停车场管理系统是毕设题目里的"安全牌",因为它业务流程清晰、角色分明、技术栈生态成熟,几乎不会出现"需求做不出来"的翻车情况。但同样的原因,它也是最容易被做成"平庸牌"的题目——如果只是把增删改查做完就交差,答辩时很难拿到高分。
1.1 这个题目的真实难度等级
如果说一个中型电商项目难度是 8 分,那停车场管理系统的核心业务难度大概在 5 分左右。它的难点不在某一个单点技术上,而在"业务流程的完整度"。
一个合格的停车场管理系统,至少需要这些角色和场景:
- 管理员:管理车位、设置收费标准、查看订单、统计收入、处理用户反馈
- 用户/车主:注册登录、预约车位、到停车场扫码入场、车位导航/查找、缴费离场
- 系统层面:车位状态实时更新、预约超时释放、入场记录、出场计费、支付状态同步
比较常见的误区,是很多同学把重心全放在"界面好看"上,Vue 前端写得花里胡哨,后台接口却只是最简单的一张表 CRUD。而反过来,真正能拿高分的项目,往往是业务闭环做得完整、异常情况处理得清楚的项目。
1.2 从答辩老师视角看这个题目
答辩老师每年要看几十个管理系统项目,他对停车场系统的期待其实非常具体:
- 第一,能不能讲清楚车位状态是如何变化的。从空闲到占用、从占用到预约、从预约到超时释放,这条状态流转链路如果讲不清楚,说明你没有真正理解业务。
- 第二,收费计算是否正确。免费时限、按小时计费、跨天计费、预约免停时长、会员折扣,这些规则你处理了几种?
- 第三,并发和一致性问题有没有自己的思考。同一时刻两个用户抢最后一个车位,怎么办?用户缴费通知发了两次,后台会不会重复扣费?
所以问题就来了:如果你想把这个看似简单的题目做成亮点,需要补的东西其实比想象中多。
1.3 我给选题建议:方向细化比堆叠功能更稳
在这几年的实际指导经验里,凡是用"这么简单的管理系统"思路去做这个题目的,几乎都会被问懵;而只要把范围稍微细化,比如做成"支持分时段预约的停车场车位预约与缴费管理系统",或者"基于动态定价的停车场智能管理系统",不但工作量可控,而且答辩时能讲的东西立刻多了一倍。
这就是为什么标题里"车位预约与缴费"这几个字特别关键——它才是你区别于普通车位管理系统的核心加分点。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 需求拆解:把"停车场管理系统"拆成能落地的模块边界
刚开始动手写代码前最重要的一步,不是装环境,而是把需求拆清楚。如果你上来就建表,写着写着一定乱。我建议按照"用户端 + 管理端 + 基础支撑"三个维度来拆分,并且把每个模块的边界用一句话说清楚。
2.1 用户端核心流程:预约 → 入场 → 出场 → 缴费
用户端的核心业务链路是我说的"预约-入场-出场-缴费"四步,每一步背后都对应一个或几个接口。
- 预约车位:用户选择停车场、选择时间段(或实时车位)、提交预约。系统要检查车位是否可用,并生成一条预约记录。
- 入场:用户到场后,可以通过车牌号自动识别入场,也可以手动输入订单号入场。入场后车位状态变为"占用"。
- 出场/缴费:出场时根据入场时间、当前时间、预约订单的免停时长,计算应收费用。用户在线支付后,抬杆放行。
这条链路中,最关键的是"每个操作对应车位状态和订单状态的哪些变化"。我建过一个状态追踪表,每次开发新接口时都先问自己:这个操作会改变哪些状态?如果状态变了,是否要更新相关的记录?养成了这个习惯,后面写业务逻辑几乎不会乱。
2.2 管理端核心流程:车位管理、收费标准、订单查询、统计报表
管理端是很多同学容易忽略但其实很加分的一部分。基础功能包括车位管理(增删改查、状态筛选)、收费标准设置(按时段、按车型)、订单管理(搜索、退款、手工修正)、收入统计(按日/按月、按停车场维度)。
这里有一个建议:如果你实在没时间做复杂的图表报表,至少做两个数据统计接口,一个是"今日收入"、一个是"车位利用率"。这两个数据不仅实现简单,而且答辩时演示效果非常好,是老师最容易感知到"这个系统有实际使用价值"的点。
2.3 基础支撑模块:登录认证与权限控制
停车场管理系统虽然小,但一定有两类角色的权限区分:普通用户和管理员。这就涉及到 SpringBoot 项目中绕不开的认证授权问题。
我个人的建议是:如果你对 Spring Security 不熟悉,不要硬上,因为配置复杂、出错排查成本高。用 JWT + 拦截器 的方式就足够支撑这个项目了。关于 JWT 的实现方式,网上资料一大把,核心代码就几十行,理解成本低,答辩时也容易讲清楚。如果你想复杂一点,可以引入 Sa-Token 这类轻量级权限框架,比 Spring Security 的学习曲线平缓很多,而且国内资料也丰富。
2.4 非功能性需求:响应时间、并发量、异常兜底
毕设阶段不用追求高并发架构,但你必须能回答出以下问题:
- 如果大量用户同时预约,系统会不会超卖车位?
- 如果支付成功但回调延迟,订单状态不一致怎么办?
- 如果用户预约后不来,车位会不会被一直占用?
这三个问题不是让你用微服务、消息队列那套方案去解决,而是让你至少能在数据库层面、代码层面给出一个合理的兜底方案。比如车位超卖,其实就是"乐观锁 + 库存判断"能解决的事。
3. 技术选型和环境准备:SpringBoot 版本怎么选,依赖怎么配,JDK 版本别犯低级错
很多同学拿到这个项目第一步就卡住了:"SpringBoot 版本是不是越新越好?"、"JDK 应该装 8 还是 17 还是 21?"、"我用的是 SpringBoot 3.x 会不会遇到什么坑?"这些问题是最近我被问得最多的,也是搜索热词里很高频的话题,我索性一次性说清楚。
3.1 SpringBoot 版本与 JDK 版本匹配表
先给出一张经验值表格,这张表我踩过坑之后整理出来的,基本能帮你避开大部分兼容性灾难:
| SpringBoot 版本 | 对应 JDK 版本(推荐) | 适用场景说明 |
|---|---|---|
| 2.7.x | JDK 8 或 JDK 11 | 最稳妥的组合,网上资料最多,绝大部分教程都能直接用 |
| 3.0.x - 3.2.x | JDK 17 | 属于新版本的过渡期,部分老依赖不兼容,尤其是 javax 包名问题 |
| 3.3.x 及以上 | JDK 17 或 JDK 21 | 更长期支持的版本,但很多第三方 starter 可能还停留在 2.x 没有完全适配 |
如果你是为了快速完成毕设、求稳,我强烈建议 SpringBoot 2.7.x + JDK 8 或 JDK 11。这个组合非常成熟,你在网上搜到的百分之八九十的报错都能搜到答案。如果你是一个喜欢折腾、愿意看官方文档的人,那用 3.x + JDK 17 也完全没问题,但一定要知道 3.x 里把 javax 包换成了 jakarta 这个变化,还有 Spring Security 6.x 的配置方式和 5.x 差别很大。
注意:如果你的项目里涉及到某些不太常用的第三方依赖,在选 SpringBoot 3.x 前,一定要先去 Maven 仓库确认该依赖是否发布了适配版本。我见过有人项目里集成了某个旧版短信 SDK,结果在 JDK 17 下直接编译都过不了。
3.2 核心依赖清单
一个标准的停车场管理系统,核心依赖其实就这些:
xml复制<dependencies>
<!-- Web 支持 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 数据库访问 MyBatis-Plus 或 Spring Data JPA 二选一 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3.2</version>
</dependency>
<!-- MySQL 驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!-- JWT -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<!-- 参数校验 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<!-- Lombok 简化代码 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
这里重点说一下持久层框架的选择。MyBatis-Plus 是毕设项目的首选,它继承了 MyBatis 的灵活性,又提供了 BaseMapper 提供的单表 CRUD 方法,配合条件构造器 QueryWrapper 和 LambdaQueryWrapper,可以让你的代码量减少一半以上。而且它的分页插件、逻辑删除、自动填充这些功能非常实用,尤其是自动填充(省去手工填 create_time、update_time 的烦恼),几乎就是给毕设项目量身定做的。
3.3 配置文件里最容易踩的坑
配置文件是整个项目的基础,这里列几个特别容易被忽略的点:
server.port一定要显式指定,不要用默认的 8080。因为一旦你的前端项目也跑在 8080,就会发生端口冲突。建议后端端口设置为8081,和前端 Vite 开发服务器的端口错开。- 数据库连接串里的
serverTimezone=Asia/Shanghai一定要加上,否则日期字段会差 8 个小时。这在停车场系统里是致命问题——差 8 小时可能导致计费整整算错半天。 - MySQL 8.x 需要指定
useSSL=false,并且驱动类名是com.mysql.cj.jdbc.Driver,别再用老版本的com.mysql.jdbc.Driver。
我见过最离谱的一个案例,同学做停车场系统,页面上下载下来的"最大车位数量"一直在变,折腾了一晚上没解决。最后我一看,他根本没有给 MySQL 配置正确的时区,数据库存的 create_time 和展示给用户的时间差了整整 8 个小时,导致各种时间计算全部错乱。所以时区这个点,一定要在开始就写好。
3.4 千万别在环境上死磕太久
如果你的开发环境搭了一个下午还没跑起来第一个项目,不要恋战,直接找学长要一份能跑的配置,或者把项目删掉重新用 Spring Initializr 生成一次。我见过太多同学把时间耗在环境配置上,最后留给业务逻辑开发的时间只剩两三天。
4. 数据库设计:预约和缴费的核心,是这几张表和状态字段
数据库设计是整个系统的地基。很多同学建表喜欢把"所有字段都塞进一张大表",比如把用户数据、车位数、订单状态全部塞在 parking_order 表里,导致表字段爆炸,查询逻辑无比混乱。我建议按"主数据 + 业务数据 + 明细数据"的思路来设计,下面是我经过几轮迭代后比较稳定的一套表结构。
4.1 核心表结构总览
| 表名 | 中文含义 | 核心字段 | 用途说明 |
|---|---|---|---|
user |
用户表 | id, username, password, phone, car_number, balance, create_time | 平台用户,既包含普通注册用户,也可通过角色字段区分管理员 |
parking_lot |
停车场表 | id, name, address, total_space, available_space, longitude, latitude | 如果有多个停车场就建立此表;如果只有一个停车场,可以简化为常量 |
parking_space |
车位表 | id, lot_id, space_number, type, status, charge_rule_id | 每个具体车位的状态和所属停车场 |
reservation |
预约表 | id, user_id, space_id, lot_id, start_time, end_time, status, amount, create_time | 用户预约车位的核心记录 |
parking_order |
停车订单表 | id, user_id, order_no, space_id, car_number, enter_time, exit_time, total_amount, pay_status | 入场到出场这一完整的业务订单 |
payment_record |
支付流水表 | id, order_id, pay_type, pay_amount, pay_time, transaction_id, status | 支付信息的流水记录,保证和订单状态不丢不乱 |
charge_rule |
收费规则表 | id, rule_name, free_duration, unit_price, over_unit_price, max_daily_charge | 白天/夜间/高峰/非高峰等不同的收费策略 |
这里有一点一定要理解:预约表和停车订单表是两张不同的表。预约是"计划",订单是"事实"。用户提前预约车位,这只是一条预约记录;等用户真正入场了,才生成停车订单。如果不区分这两者,后面处理"预约了但没来"和"没预约直接入场"这两种场景时会非常难受。
4.2 车位 status 字段的状态流转
车位状态建议用枚举值,不要用随意写死的字符串。一个合理的状态设计是:
0:空闲1:已预约(还没被停入,但已被锁定)2:占用(车辆已停放)3:维护/禁用(车位故障或管理人员暂时封闭)
状态之间不是随意跳转的,而是有明确流转方向的:
- 空闲 → 已预约(用户预约成功)
- 已预约 → 占用(车辆入场)
- 已预约 → 空闲(预约超时/用户取消)
- 占用 → 空闲(车辆出场并缴费成功)
你在代码里控制状态流转时,建议在 Service 层专门写一个 SpaceStatusService 来做状态变更操作,不要散落在各个地方。这样后面如果要加"管理员手动释放车位"这个功能,只需要改这个 Service 的方法就行。
4.3 收费规则表:把逻辑从代码里抽出来
收费计算是答辩时最容易暴雷的点。如果把收费标准硬编码在代码里,比如"每小时五元",后续老师问一句"怎么改价格?"你就只能尴尬地说是改代码然后重新部署。
建议把收费标准设计成一张表,按停车场和时间段配置:
sql复制CREATE TABLE charge_rule (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
lot_id BIGINT NOT NULL COMMENT '停车场ID',
rule_name VARCHAR(64) NOT NULL COMMENT '规则名称,如白天规则',
start_time TIME NOT NULL DEFAULT '08:00:00' COMMENT '生效开始时间',
end_time TIME NOT NULL DEFAULT '22:00:00' COMMENT '生效结束时间',
fee_per_hour DECIMAL(10,2) NOT NULL COMMENT '每小時费用',
free_duration INT NOT NULL DEFAULT 0 COMMENT '免费时长/分钟',
max_daily_charge DECIMAL(10,2) DEFAULT NULL COMMENT '每日封顶费用',
enabled TINYINT NOT NULL DEFAULT 1 COMMENT '是否启用'
) COMMENT='收费规则表';
计算逻辑放在 Service 层,根据入场时间和规则表算出费用。这样设计的好处是,管理和前端操作简单直观,而且答辩时你可以舒服地演示"把晚上八点后每小时的费用改成两块,系统立刻生效",这是一个非常不错的展示反馈点。
4.4 时间字段统一用 datetime,别折腾时间戳
关于时间字段有一个个人建议:数据库里统一用 datetime 类型,Java 实体里用 LocalDateTime,JSON 序列化时配置好时间格式。不要用 int 时间戳,因为你在 idea 里调试和看数据库数据时非常不直观。很多教程为了省事用 bigint 存时间戳,后期排查问题简直是灾难。还有,涉及"跨天停车"的计费时,datetime 类型的直观性会让你少掉很多头发。
5. 预约与缴费的核心业务逻辑:给系统装上"状态机"和"防并发"的脑子
需求拆完了、表建好了,接下来就到了最核心的部分,也是答辩时老师最喜欢追问的部分:预约和缴费的业务逻辑到底怎么实现。这一部分我们不贴完整代码,只讲清楚两件事,第一件是核心接口调用的整体流程是怎么设计的,第二件是每次处理业务时你要重点关注哪些细节。
5.1 预约功能的接口流程与状态机
预约流程的核心接口建议设计成这样:
java复制public interface ReservationService {
/**
* 用户提交预约
* @param userId 用户ID
* @param spaceId 车位ID
* @param startTime 预约开始时间
* @param endTime 预约结束时间
* @return 预约订单信息
*/
ReservationVO createReservation(Long userId, Long spaceId,
LocalDateTime startTime, LocalDateTime endTime);
/**
* 用户取消预约
*/
boolean cancelReservation(Long reservationId, Long userId);
/**
* 系统检查超时预约并释放车位
* 建议由定时任务调用
*/
void releaseTimeoutReservations();
/**
* 预约入场,创建停车订单
*/
ParkingOrderDO enterParking(Long reservationId);
}
创建预约的完整流程可以分解为这几步:
- 校验用户是否有未完成订单(防止一人占多个车位)
- 查询车位是否存在且状态为"空闲"
- 判断预约时间段是否与已有预约/订单冲突
- 通过乐观锁或悲观锁保护车位状态,将车位从"空闲"改为"已预约"
- 生成预约记录,状态设为"待入场"
- 距离预约开始时间前 N 分钟,发送提醒(这里可以用定时任务实现,也可以用消息队列,毕设阶段定时任务足够)
预约不能无限放着不处理,这里就涉及"超时释放"机制。比较常见的实现方式是用 Spring 内置的 @Scheduled 定时任务,每 30 秒扫描一次预约表,把所有"已预约但超过预约开始时间 15 分钟仍未入场"的记录找出来,将状态改为"预约超时取消",同时释放车位。这个逻辑非常容易在答辩时讲清楚,而且也是系统运维时要考虑的真实问题。
5.2 入场和出场缴费流程
入场时,如果用户有预约,直接根据预约记录生成停车订单;如果没有预约,则新建一条停车订单,同时将车位状态改为"占用"。出场时,根据入场时间、当前时间、预约免停时长、停车时段和收费规则,计算费用。
计算费用时的几个关键场景:
- 免费时长:订单计费时长 = max(0, 实际停车时长 - 免费时长)
- 按时段区分:白天和夜间价格不同,需要把停车时长拆成多个区间分别计算
- 每日封顶:如果某天的计算费用超过封顶值,按封顶值收取
- 跨天停车:把停车时长按自然日切割,每天分别计算再求和
这段计费逻辑是"收费计算"模块最核心的部分,建议写成独立的 ChargeCalculator 类,用单元测试跑几个用例。比如"晚上 10 点入场第二天凌晨 1 点离场"这样的场景,提前写好测试代码,后面接真实前端时能少赔不少手续费。
5.3 支付流程:状态一致性不是闹着玩的
支付环节最怕的问题是"订单已经支付成功,但系统里状态还是未支付"。在毕设项目里,一般不需要真的对接微信支付/支付宝支付的沙箱环境(虽然能对接上也是极大的加分项),但即使你用"模拟支付"也要把状态流转的逻辑设计对。
我的建议是模拟支付时,至少实现这个逻辑:
- 用户发起支付请求,创建一条
payment_record流水,状态为"待支付" - 模拟支付回调接口,比如直接调用
/api/payment/mock/notify模拟支付成功回调 - 回调接口里做两件事:更新支付流水状态为"已支付",更新订单的
payStatus为"已支付" - 支付成功后如果停车场是出口收费场景,需要联动释放车位
这里有一个比较实用的点:不要在一个事务里同时更新支付流水和订单状态,除非你能处理分布式事务。尽管在单体项目里一个事务确实能保证一致性,但等未来接入真实支付渠道时,支付回调本身可能是异步且重复调用的,这就要求你的回调接口必须幂等。所以建议一开始就把支付回调写成"先查流水状态,如果已处理就直接返回成功,否则才继续处理",这个习惯能让你少写很多 bug。
提示:为保证幂等,支付回调里一定要先根据
transactionId或orderNo查询已存在的记录,判断是否重复回调。现实中支付平台的回调机制偶尔会因为网络原因重试多次,这几乎是每时每刻都会发生的真实行为。
5.4 并发问题的两个经典解决方案
毕设级项目可以不搞 Redis 分布式锁,但一定要知道下面两个方案:
- 乐观锁:车位表上加一个
version字段,更新车位状态时在 SQL 里拼上WHERE status = 0 AND version = ?。如果影响行数为 0,说明这个车位已经被别人抢走了,直接提示用户"车位已被预约"。 - 数据库唯一索引:在预约表里为
(user_id, 时间段)或(space_id, 时间段)加唯一索引,从数据库层面防止重复预约同一时间段。
如果你用 MyBatis-Plus,乐观锁只需要两步:配置 MybatisPlusConfig 里的乐观锁拦截器,然后在实体类的版本字段上加 @Version 注解。全程不到两分钟,但在答辩时你可以理直气壮地说"我这套系统已经考虑到并发抢车位的问题"。
6. 定时任务、事务失效与前后端联调:开发中那些让你疯狂的小怪兽
到了实际开发阶段,你会遇到很多和"业务逻辑"本身没多大关系、但足以让你心态爆炸的问题。这一部分我把近几年学员和我自己踩过的高频坑集中梳理一下,希望你能绕开。
6.1 SpringBoot 定时任务:预约超时释放的正确姿势
预约超时释放,最推荐的实现方式是使用 @Scheduled 定时任务。但这里有个初学者特别容易犯的错:把定时任务写在 Controller 里。
正确做法是单独建一个 ScheduleTaskService 或者 TaskRunner 类,里面写一个定时任务方法:
java复制@Component
public class ReservationTimeoutTask {
@Resource
private ReservationService reservationService;
/**
* 每 30 秒执行一次,释放超时预约
*/
@Scheduled(fixedDelay = 30000)
public void releaseTimeoutReservations() {
reservationService.releaseTimeoutReservations();
}
}
注意 fixedDelay 和 cron 的区别。fixedDelay 是上次任务执行完后,再过 30 秒再执行下一次;cron 是写死某个时间点触发。毕设场景里用 fixedDelay 更合适,不会造成任务堆积。另外,要在主启动类上加上 @EnableScheduling,忘了这个注解会是"定时任务完全不执行",排查半天发现是这种低级错误。
6.2 事务失效:为什么我的数据只改了一半
SpringBoot 里事务失效的原因五花八门,但最常见的就那么几种:
- 方法加了
@Transactional,但该方法被同一个类里的另一个方法调用,此时事务代理不生效,这就是著名的this调用问题。 - 异常被 catch 住了,事务感知不到异常,自然就不会回滚。
- 方法不是
public的,@Transactional对非 public 方法不生效。
结合停车场系统的实际场景:比如"创建预约"这个方法,需要先扣减车位可用数量、再插入预约记录,这两个操作必须是在同一个事务里。如果你把扣减操作提取到另一个 Service 方法里,而在同一个类里直接调用它,事务就会失效。
java复制@Service
public class ReservationServiceImpl implements ReservationService {
@Override
@Transactional(rollbackFor = Exception.class)
public ReservationVO createReservation(...) {
// 扣减车位数量
parkingSpaceService.deductSpaceCount(spaceId);
// 插入预约记录
...
}
}
这里我刻意把 rollbackFor = Exception.class 加上。因为默认情况下 @Transactional 只对 RuntimeException 回滚,如果业务异常是自定义的 Exception,不加这个属性就回滚不了。这是面试题里经常考的点,也是毕设项目里最容易触发的隐患。
6.3 MyBatis-Plus 的自动填充与逻辑删除
在停车场系统里,几乎每张表都有 create_time、update_time 字段。每次插入和更新都手动 set 也可以,但一旦字段多起来非常痛苦。MyBatis-Plus 提供了 MetaObjectHandler 自动填充机制,你只需要写一个处理器:
java复制@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now());
this.strictInsertFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
}
@Override
public void updateFill(MetaObject metaObject) {
this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
}
}
实体类字段上加 @TableField(fill = FieldFill.INSERT) 和 @TableField(fill = FieldFill.INSERT_UPDATE) 注解,然后在 application.yml 里配置逻辑删除的全局字段:
yaml复制mybatis-plus:
global-config:
db-config:
logic-delete-field: deleted
logic-delete-value: 1
logic-not-delete-value: 0
这样配置好之后,你的删除操作就会自动变成 update ... set deleted = 1,查询时自动过滤已删除数据。这在答辩时也是一个很值得一提的点:"我的系统支持数据软删除,可以有效保留操作历史日志。"
6.4 前后端联调时的跨域问题
如果你用的是 Vue + 前端分离模式,后端一定要配置跨域。否则前端调用后端接口时,浏览器会直接拦截请求,报错内容类似 CORS ... preflight。这个配置其实非常简单,用一个 WebMvcConfigurer 就能解决:
java复制@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOriginPatterns("*")
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.allowedHeaders("*")
.allowCredentials(true)
.maxAge(3600);
}
}
如果你使用 Spring Security 或 Sa-Token,跨域配置还需要放行预检请求(OPTIONS 请求)。这个点坑了很多新手,因为前端莫名其妙请求报错,后端日志里却看不到任何输出——其实是请求根本没有进入 Controller。
6.5 前端页面:不做复杂也能好的方案
如果你前端基础有限,不想从零写一个花哨的 Vue 页面,我推荐一个省力方案:直接用 Bootstrap 或 Element Plus 的现成管理后台模板。网上有大量免费开源的 admin 模板,改改页面内容、调整配色、接入接口,五六天时间完全能搞定一套看起来专业度达标的界面。好的毕设项目从来不是比拼谁写的 CSS 更炫,而是谁能把业务闭环完整地跑通、讲透。
7. 如何让这个毕设项目在答辩时拿高分
最后聊聊答辩。很多同学代码写得还行,一到答辩就语无伦次,老师问一个点答一个点,完全没有结构感。我带了这么多届学生,确实总结出了几件能显著提升答辩效果的事情。
7.1 提前准备一张业务流程图和一张表结构图
答辩前准备流程图,是非常重要的环节。老师最喜欢问的一句话是"你这个系统的整体流程是什么样的",你如果只是口头描述,会很干;但如果你能拿出一张清晰的业务流程图,照着图讲预约流程、入场流程、出场缴费流程,效果会好很多。画图工具随便,ProcessOn、Draw.io、甚至 PPT 都能画。
画图时记住一个原则:图要体现状态变化。"用户提交预约时,车位状态从空闲变为已预约;如果超时未入场,系统自动释放车位,状态从已预约变为空闲。"这样的描述配合流程图,会让老师觉得你真的理解了项目。注意不要用 Mermaid 语法来画这种图,线下用普通绘图工具就行。
7.2 准备一份"项目亮点"清单,主动抛给老师
答辩时不要等着老师问,你可以在讲完项目功能后,主动补充几个亮点,这会直接拉升老师对项目的印象分。我建议准备 2-3 个亮点就够了,不要全是堆功能:
- 并发安全:我使用了乐观锁和唯一索引来防止车位超卖
- 定时任务的实用场景:预约超时自动释放,避免车位资源被无效占用
- 支付回调的幂等设计:支付结果不因重复通知而重复处理
- 收费规则的配置化管理:调整计费策略不需要改代码
每个亮点用一句话说"我做了什么",再用一句话说"解决了什么问题"。比如乐观锁那条:我在预约扣减车位时使用乐观锁,避免了两个人同时抢同一个车位,同时用数据库唯一索引做兜底。
7.3 常见追问模拟:提前把答案备好
我把这几年停车场相关项目里,老师最爱问的几个问题整理成了一份问答清单:
| 老师的问题 | 建议的回答思路 |
|---|---|
| 如果两个用户同时预约同一个车位会怎样? | 乐观锁版本判断 + 数据库唯一索引兜底,说明处理并发的基本思路 |
| 用户预约后没来,你怎么办? | 定时任务扫描预约超时记录,状态改为超时取消并释放车位 |
| 计费规则是怎么设计的,能修改吗? | 收费规则表化,按时间段配置单价,支持免费时长和每日封顶 |
| 用户支付成功后,你的订单状态如何更新? | 模拟支付回调接口,回调里先查重(幂等),再更新支付流水和订单状态 |
| 这个系统能支持多停车场吗? | 设计了停车场表,所有业务以 lotId 区分,可扩展多停车场 |
| 你的密码存的是明文吗? | 使用 BCrypt 加密(Spring Security 自带),不推荐明文存储 |
其中最后一个问题几乎在毕设中必问,密码加密一定要做。在 SpringBoot 中引入 spring-security-crypto 依赖,调用 BCryptPasswordEncoder 就能完成加密和校验,和你的 JWT 认证方案配合得很舒服。
7.4 最后一句真心话
从我的经验来看,一个毕设项目的最终答辩成绩,往往不是由你代码量多少决定的,而是由"完整的业务逻辑 + 清晰的表达 + 少数几个亮点"决定的。停车场管理系统足以支撑你拿到一个不错的成绩,前提是你不要只把它当成一个 CRUD 练习,而是真的愿意在预约流程、计费规则、并发控制这些细节上多花点心思。这些细节,才是你答辩时最值钱的东西。
