本文还有配套的精品资源,点击获取
简介:一套开箱即用的停车场智能管理解决方案,前端用微信小程序实现车位地图可视化、实时空位查询、在线预约和订单管理;后端基于Java语言,采用Spring+SpringMVC+MyBatis(SSM)框架开发,MySQL存储数据,支持标准B/S架构部署。系统划分为用户端和管理端:用户通过小程序完成车位浏览、预约下单、订单查看等操作;管理员通过后台页面统一维护用户信息、配置车位基础数据(如区域/类型/状态)、审核预约记录、调整系统参数。资源包包含全部可运行代码——微信小程序源码(含pages、components、app.js等结构清晰模块)、Java后端完整Maven工程(含pom.xml、src主目录、SSM核心配置文件)、MySQL建表SQL脚本及字段说明、微信开发者工具配置文件(project.config.)、详细开发说明文档(含接口说明与页面逻辑)、以及分步式部署指南(readme.txt)。所有代码注释完整、目录规范,适合作为本科毕业设计选题、Java全栈课程实践或小型智慧停车项目快速启动参考。
1. 这不是“又一个Demo”,而是一套能真正在小区、园区落地的停车管理闭环系统
我带过六届计算机专业毕业设计,每年都会收到至少二十份“停车场系统”选题——其中八成是纯后台CRUD、三成加了个简陋HTML页面,真正能跑起来、有地图、能预约、还能管得过来的,掰着手指头数不出五个。直到去年帮一个老同学改造他家物业公司的地下车库,才真正把这套微信小程序+SSM的停车场系统从“课程作业”打磨成了“能签合同交付”的轻量级解决方案。它不追求高并发、不堆AI算法、不接车牌识别硬件,但把“用户想快速找到空位—点几下完成预约—到现场扫码核销—管理员后台一眼看清全局”这个最核心链路,用最扎实的工程方式走通了。
关键词里写的“微信小程序、SSM框架、停车场系统、车位预约、毕业设计”,每一个都不是虚词。小程序端不是套模板的静态页面:它用Canvas动态渲染二维车位地图,支持缩放拖拽,每个车位块实时显示状态(空闲/已预约/已占用/维修中),点击即弹出详情并触发预约流程;SSM后端也不是Spring Boot的简化版——它严格分层:Controller只做参数校验与跳转,Service层封装完整的业务原子操作(比如“预约车位”必须同时检查该车位是否在有效时段内、是否被他人锁定、用户当日预约是否超限、库存扣减是否成功),Mapper层SQL全部手写带注释,连<if>嵌套都控制在三层以内,避免后期维护踩坑;数据库设计更不是照搬教科书:一张parking_space表用area_code(A区/B区/C区)、row_no(第3排)、col_no(第5列)三个字段代替冗余的“位置描述”,配合唯一索引(area_code, row_no, col_no),让前端地图坐标转换和后台查询效率直接拉满;而“毕业设计”这个标签,恰恰说明它规避了所有学生容易翻车的坑——没有用冷门中间件、不依赖Docker环境、部署只需JDK8+Tomcat8+MySQL5.7,连数据库脚本都按建库→建表→插初始数据三步拆开,readme.txt里甚至写了“如果启动报错java.lang.ClassNotFoundException: com.mysql.jdbc.Driver,请把mysql-connector-java-5.1.47.jar复制到tomcat/lib目录下”。
这套系统真正解决的是什么?不是技术炫技,而是物理空间数字化管理中最痛的三个点:第一,用户站在车库入口举着手机刷新“附近还有几个空位”,结果刷出来全是隔壁楼的;第二,物业拿着Excel表格手动登记谁约了哪个车位,月底对账发现漏了三单;第三,新来的保安不知道3号电梯旁那个蓝色车位为什么一直显示“维修中”,翻记录才发现是上个月系统故障没恢复状态。它用最朴素的技术组合,把这三个场景全闭环了——小程序定位自动匹配所属停车场,预约成功立刻锁位并推送微信服务通知,后台操作日志完整记录谁在什么时候把哪个车位设为维修状态。如果你正为毕设发愁、想接个小项目练手、或是物业经理想找套能马上试用的系统,这套东西不是“参考”,而是“开箱即用”的生产级起点。
2. 系统整体架构与设计思路拆解:为什么坚持用SSM而不是Spring Boot?
很多人看到“SSM”第一反应是“过时”,尤其现在Spring Boot教程铺天盖地。但我在实际交付中反复验证过:对于本科毕设、小型园区管理这类需求,SSM反而是更稳、更透明、更适合教学和二次开发的选择。这不是守旧,而是基于三个硬性约束的理性取舍。
2.1 架构分层逻辑:清晰到每行代码都知道自己该干什么
整个系统采用经典的四层结构,但每一层的职责边界比教科书更严苛:
表现层(小程序端):只负责UI渲染与用户交互,所有数据请求必须通过统一的
/api/xxx接口,绝不允许在wxml里写wx:if="{{item.status == 1}}"这种硬编码状态判断。状态值全部由后端枚举类定义(如SpaceStatusEnum.FREE(1, "空闲")),前端用statusMap映射,保证前后端状态语义完全一致。控制层(SpringMVC Controller):这里做了两件事:一是参数合法性兜底(比如预约时间不能早于当前时间2小时,不能晚于72小时后),二是统一异常处理。所有业务异常抛出
BusinessException,被@ControllerAdvice捕获后返回标准JSON格式{"code": 1001, "msg": "车位已被预约", "data": null},小程序端只需一个handleError()函数就能处理全部错误提示。业务逻辑层(Service):这是真正的“大脑”。以“用户预约车位”为例,Service方法
reserveSpace(Long userId, Long spaceId, LocalDateTime reserveTime)内部包含五个原子操作:① 校验用户身份有效性;② 查询车位当前状态及可预约时段;③ 检查用户当日预约总数是否已达上限(默认3个);④ 执行数据库乐观锁更新(UPDATE parking_space SET status = 2 WHERE id = ? AND status = 1);⑤ 创建预约订单并发送微信模板消息。这五个步骤要么全部成功,要么全部回滚——用@Transactional(isolation = Isolation.REPEATABLE_READ)保证,而不是靠try-catch手动回滚。数据访问层(MyBatis Mapper):拒绝XML文件里写复杂SQL。所有关联查询拆成单表查询+Java内存组装。比如“查询某用户所有预约记录及对应车位信息”,Mapper只写
select * from reservation where user_id = #{userId}和select * from parking_space where id in (#{spaceIds})两条简单SQL,Service层用Map<Long, ParkingSpace>缓存车位数据,再遍历组装。这样做的好处是:SQL易读易调优,分页时不会因JOIN导致数据重复,更重要的是——学生调试时能一眼看懂每条SQL干了什么。
提示:为什么不用Spring Boot?因为Boot的自动配置会掩盖很多底层细节。比如数据库连接池HikariCP的
connection-timeout参数,默认30秒,但在校园网环境下可能因DNS解析慢导致首次连接超时,学生根本找不到原因。而SSM里druid.properties明明白白写着druid.initialSize=5,出了问题直接改配置重启,不绕弯子。
2.2 小程序端地图渲染:不用第三方SDK,Canvas手绘二维网格
小程序里那个能拖拽缩放的车位地图,没用腾讯地图API,也没接高德SDK,就是纯Canvas绘制。原因很实在:车库平面图是固定的矩形区域,车位排列规则(比如A区30排×10列),用Canvas画格子比加载地图SDK快3倍,且完全离线可用。
核心逻辑在pages/map/map.js里:
// 根据车位数据生成网格坐标 generateGridData() { const { spaces } = this.data; const grid = []; spaces.forEach(space => { // 将逻辑坐标(area_code, row_no, col_no)转为Canvas像素坐标 const x = (space.col_no - 1) * this.CELL_WIDTH + this.PADDING_LEFT; const y = (space.row_no - 1) * this.CELL_HEIGHT + this.PADDING_TOP; grid.push({ id: space.id, x, y, width: this.CELL_WIDTH, height: this.CELL_HEIGHT, status: space.status // 1空闲/2已预约/3已占用/4维修 }); }); return grid; }绘制时用不同颜色区分状态:空闲用绿色渐变,已预约用橙色边框,维修中加灰色斜纹图案。最关键的是状态同步机制:小程序首页进入时拉取一次全量车位数据,之后每30秒发起长轮询/api/space/status?lastUpdate=1712345678900,只返回变更的车位ID及新状态,前端局部重绘对应格子,避免整页刷新卡顿。这个设计让学生第一次理解“增量更新”和“局部渲染”的实际价值——不是概念,是肉眼可见的流畅。
2.3 数据库设计哲学:宁可多建表,绝不冗余字段
parking_space表只有12个字段,但支撑起全部业务:
| 字段名 | 类型 | 说明 | 索引 |
|--------|------|------|------|
| id | BIGINT PK | 主键 | PRIMARY |
| area_code | VARCHAR(10) | 区域编码(A区/B区) | INDEX |
| row_no | TINYINT | 行号(1~50) | INDEX |
| col_no | TINYINT | 列号(1~20) | INDEX |
| status | TINYINT | 状态(1空闲/2预约中/3已占用/4维修) | INDEX |
| type | TINYINT | 类型(1普通/2新能源/3VIP) | INDEX |
| created_time | DATETIME | 创建时间 | — |
| updated_time | DATETIME | 更新时间 | — |
重点看area_code+row_no+col_no联合索引——它让“查询A区第5排所有车位”变成WHERE area_code='A' AND row_no=5,走索引扫描;让“定位某个车位”变成WHERE area_code=? AND row_no=? AND col_no=?,走唯一索引查找。而放弃用location_desc VARCHAR(100)存“A区5排3列”这种字符串,是因为:① 字符串无法高效排序(比如查“A区所有车位按行列排序”);② 修改区域划分时要批量更新字符串,易出错;③ 前端计算坐标需要数字,每次都要split('区')[0]再parseInt,性能损耗。
另一张关键表reservation的设计更体现克制:
| 字段名 | 类型 | 说明 |
|--------|------|------|
| id | BIGINT PK | — |
| user_id | BIGINT | 关联用户 |
| space_id | BIGINT | 关联车位 |
| reserve_start_time | DATETIME | 预约开始时间(精确到分钟) |
| reserve_end_time | DATETIME | 预约结束时间(同上) |
| status | TINYINT | 1待确认/2已生效/3已取消/4已过期 |
| created_time | DATETIME | — |
没有存“用户手机号”“车位编号”等冗余字段,所有关联信息都靠JOIN查。虽然初学者会觉得“多查一次表好麻烦”,但当系统要支持“导出预约报表”时,你会发现:只要SELECT u.phone, s.area_code, s.row_no, s.col_no...就能拿到完整信息,不用在reservation表里维护一堆可能不一致的副本字段。
3. 核心功能模块实现详解:从预约下单到后台审核的完整链路
这套系统的价值不在代码量,而在每个环节都经得起推敲。下面以“用户预约车位”这个最常用操作为例,拆解从前端点击到后台落库的完整链路,包括所有关键参数、校验逻辑和异常处理。
3.1 小程序端预约流程:三步完成,每步都有防错设计
用户在地图上点击一个绿色车位块,触发预约流程。整个过程分为三步,每步都有明确反馈:
第一步:弹出预约时段选择器
不是让用户随便填时间,而是根据车位类型预设可选时段:
- 普通车位:仅开放未来2小时至72小时内,每30分钟一个档位(如14:00-14:30, 14:30-15:00…)
- 新能源车位:额外增加“充电时长”选项(1小时/2小时/4小时),自动计算结束时间
- VIP车位:需输入“预约事由”(文本框,必填)
实操心得:时段选择器用
picker-view组件而非picker mode="time",因为后者无法限制起始时间。我们用JavaScript生成时段数组:javascript generateTimeOptions() { const now = new Date(); const start = new Date(now.getTime() + 2 * 60 * 60 * 1000); // 2小时后 const end = new Date(now.getTime() + 72 * 60 * 60 * 1000); // 72小时后 let options = []; for (let t = start; t <= end; t = new Date(t.getTime() + 30 * 60 * 1000)) { options.push({ value: t.toISOString().slice(11, 16), // "14:30" label: `${t.getHours()}:${t.getMinutes()<10?'0':''+t.getMinutes()}` }); } return options; }
第二步:提交前二次确认弹窗
显示清晰信息:
✅ 车位位置:A区5排3列(蓝色新能源车位)
⏱ 预约时段:2024-05-20 14:30 - 15:00(30分钟)
💰 费用:免费(首单体验)
⚠️ 注意:请于开始时间前15分钟到达,超时未扫码将自动释放
这个弹窗不是形式主义——它强制用户确认关键信息,减少误操作。点击“确认预约”后按钮变灰并显示加载动画,防止重复提交。
第三步:预约结果实时反馈
调用/api/reservation/create接口,成功返回:
{ "code": 200, "msg": "预约成功", "data": { "id": 12345, "qrCodeUrl": "https://xxx.com/qrcode/12345.png", "reserveTime": "2024-05-20T14:30:00", "expireTime": "2024-05-20T14:15:00" // 到达截止时间 } }前端立即做三件事:① 在地图上将该车位块变为橙色;② 展示二维码并倒计时(15分钟内有效);③ 跳转到“我的预约”页面,新增该订单。
注意:二维码不是临时生成的,而是提前用
qrcode.js在前端生成base64图片,避免网络延迟影响用户体验。qrCodeUrl字段实际是占位符,真实使用时替换为本地data URL。
3.2 后端预约核心逻辑:五重校验,一个都不能少
ReservationService.reserveSpace()方法是整个系统的“心脏”,它执行以下校验(按顺序):
校验1:用户资质检查
User user = userMapper.selectById(userId); if (user == null || user.getStatus() != UserStatusEnum.NORMAL.getCode()) { throw new BusinessException("用户不存在或已被禁用"); }不仅查用户是否存在,还检查status字段(1正常/2冻结/3注销),避免已冻结账号还能预约。
校验2:车位状态与类型匹配
ParkingSpace space = spaceMapper.selectById(spaceId); if (space == null || space.getStatus() != SpaceStatusEnum.FREE.getCode()) { throw new BusinessException("车位不可用"); } // 新能源车位只允许新能源车用户预约 if (space.getType() == SpaceTypeEnum.EV.getCode() && !user.getCarType().equals(CarTypeEnum.EV.getCode())) { throw new BusinessException("新能源车位仅限新能源车辆预约"); }校验3:时间窗口有效性
// 预约开始时间不能早于当前时间+2小时 if (reserveStartTime.isBefore(LocalDateTime.now().plusHours(2))) { throw new BusinessException("预约开始时间不能早于当前时间2小时"); } // 结束时间不能晚于开始时间+72小时 if (reserveEndTime.isAfter(reserveStartTime.plusHours(72))) { throw new BusinessException("单次预约最长72小时"); } // 检查该时段内是否有其他预约冲突(核心!) int conflictCount = reservationMapper.countBySpaceAndTime( spaceId, reserveStartTime, reserveEndTime); if (conflictCount > 0) { throw new BusinessException("该时段车位已被预约"); }校验4:用户当日预约限额
int todayResCount = reservationMapper.countByUserAndDate( userId, LocalDate.now()); if (todayResCount >= 3) { // 默认上限3个 throw new BusinessException("今日预约已达上限(3个)"); }校验5:数据库乐观锁更新
// 尝试将车位状态从"空闲"改为"预约中" int updateCount = spaceMapper.updateStatusByIdAndStatus( spaceId, SpaceStatusEnum.FREE.getCode(), SpaceStatusEnum.RESERVING.getCode()); if (updateCount == 0) { throw new BusinessException("车位状态已变更,请刷新重试"); }这里用UPDATE ... WHERE id = ? AND status = 1确保并发安全。如果两个用户同时点同一个车位,只有一个能成功更新,另一个会拿到updateCount=0,触发业务异常。
最后一步:创建预约记录,并发送微信模板消息:
Reservation reservation = new Reservation(); reservation.setUserId(userId); reservation.setSpaceId(spaceId); reservation.setReserveStartTime(reserveStartTime); reservation.setReserveEndTime(reserveEndTime); reservation.setStatus(ReservationStatusEnum.WAITING.getCode()); reservationMapper.insert(reservation); // 发送模板消息(需提前在微信公众平台配置模板ID) wechatService.sendReservationSuccessMsg( user.getOpenId(), reservation.getId(), space.getAreaCode() + space.getRowNo() + "排" + space.getColNo() + "列", reserveStartTime.format(DateTimeFormatter.ofPattern("MM-dd HH:mm")), reserveEndTime.format(DateTimeFormatter.ofPattern("HH:mm")) );3.3 后台管理端审核机制:不是简单开关,而是分级管控
管理员后台的“预约审核”模块,常被学生做成一个“通过/拒绝”按钮。但这套系统把它设计成三级管控:
一级:自动生效
普通预约(非VIP、非特殊事由)提交后直接status=2(已生效),无需人工干预。这是为了提升用户体验——用户不想等审批。
二级:人工审核
VIP车位预约、事由含“政府公务”“医疗急救”等关键词的,自动标记status=1(待审核),管理员在后台看到带红色感叹号的订单,点击查看事由、用户信用分(如有)、历史预约记录,再决定通过或拒绝。
三级:紧急释放
管理员发现某车位被恶意长期占用(比如连续7天预约但从未扫码),可点击“强制释放”按钮。系统执行:
1. 将该预约status设为CANCELED;
2. 将对应车位status重置为FREE;
3. 向用户发送模板消息:“您预约的A区5排3列车位因超时未使用已被释放,信用分扣除5分”。
实操心得:后台所有操作都记录
admin_log表,字段包括operator_id(操作人)、operation_type(1审核通过/2强制释放/3修改参数)、target_id(目标ID)、before_data(操作前JSON)、after_data(操作后JSON)。有一次物业经理误删了车位数据,靠这条日志3分钟就还原了。
4. 部署与运行全流程实录:从零开始,30分钟跑通
这套系统最大的优势是“部署无门槛”。我用一台4核8G的阿里云ECS(CentOS 7.9)实测,从系统初始化到小程序扫码成功,全程32分钟。以下是分步记录,每一步都标注了常见坑点。
4.1 环境准备:三件套版本锁定
| 组件 | 版本 | 安装命令 | 坑点提醒 |
|---|---|---|---|
| JDK | 1.8.0_291 | yum install java-1.8.0-openjdk-devel | 必须装devel包,否则编译报错 |
| Tomcat | 8.5.99 | wget https://archive.apache.org/dist/tomcat/tomcat-8/v8.5.99/bin/apache-tomcat-8.5.99.tar.gz | 不要用最新版,Tomcat 9+对Servlet 3.1兼容性有坑 |
| MySQL | 5.7.42 | yum install mysql57-community-release-el7-11.noarch.rpm→yum install mysql-community-server | 初始化密码在/var/log/mysqld.log里找 |
提示:
mysql_secure_installation一定要执行!否则远程连接会被拒绝。设置root密码后,立即创建应用专用账户:sql CREATE DATABASE parking_system DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; CREATE USER 'parking_user'@'%' IDENTIFIED BY 'StrongPass123!'; GRANT ALL PRIVILEGES ON parking_system.* TO 'parking_user'@'%'; FLUSH PRIVILEGES;
4.2 数据库初始化:三步执行,顺序不能错
资源包里的db_init.sql文件已拆解为三个独立脚本:
-01_create_table.sql:建所有表(含外键约束)
-02_insert_init_data.sql:插入初始车位数据(A区30×10,B区20×8等)
-03_update_config.sql:设置系统参数(如预约时限、信用分规则)
执行顺序必须严格:
mysql -u parking_user -pStrongPass123! parking_system < 01_create_table.sql mysql -u parking_user -pStrongPass123! parking_system < 02_insert_init_data.sql mysql -u parking_user -pStrongPass123! parking_system < 03_update_config.sql为什么不能合并?因为02_insert_init_data.sql里有INSERT INTO parking_space (...) VALUES (...),(...),(...);批量插入,如果表结构没建好会直接报错中断。而03_update_config.sql依赖system_config表已存在。
4.3 后端部署:Maven打包+Tomcat热部署
进入Java工程根目录(含pom.xml),执行:
mvn clean package -Dmaven.test.skip=true生成target/parking-system.war。复制到Tomcat的webapps/目录:
cp target/parking-system.war /opt/tomcat/webapps/Tomcat会自动解压并启动。关键检查点:
- 查看/opt/tomcat/logs/catalina.out末尾是否有INFO [main] org.apache.catalina.startup.HostConfig.deployWAR Deployment of web application archive [...] has finished in [XX] ms
- 访问http://服务器IP:8080/parking-system/api/test,返回{"code":200,"msg":"OK"}即后端启动成功
注意:如果报
java.lang.NoClassDefFoundError: com/mysql/jdbc/Driver,说明MySQL驱动jar没放对位置。正确路径是/opt/tomcat/lib/mysql-connector-java-5.1.47.jar(资源包里已提供)。
4.4 小程序配置与真机调试:微信开发者工具不是终点
小程序源码在mp-weixin目录。配置要点:
-project.config.json里appid需替换为你自己的(免费注册小程序账号即可)
-utils/request.js里BASE_URL改为你的服务器地址:https://你的域名.com/parking-system
-app.js里wx.login()成功后,用code向后端/api/user/login换取token,此token存入wx.setStorageSync('token', res.data.token)
真机调试必做三件事:
1. 在微信开发者工具 → 详情 → 本地设置 → 取消勾选“不校验合法域名”,否则真机无法请求你的服务器
2. 服务器Nginx需配置HTTPS(微信要求),简易方案:用Let’s Encrypt申请免费证书
3. 小程序后台 → 开发管理 → 添加服务器域名:https://你的域名.com
实测时用同事手机扫开发者工具里的二维码,打开小程序首页,地图正常渲染,点击车位弹出预约框——此时后端日志应出现ReservationController.createReservation调用记录,证明全链路打通。
5. 常见问题与排查技巧实录:那些文档没写的坑,我都替你踩过了
交付过程中遇到的问题,90%都集中在环境和配置。我把高频问题整理成速查表,并附上独家排查技巧。
5.1 小程序端典型问题速查
| 现象 | 可能原因 | 排查技巧 | 解决方案 |
|---|---|---|---|
地图空白,控制台报canvas is not defined | 微信基础库版本过低 | 在开发者工具右上角查看“基础库版本”,必须≥2.10.0 | 在app.json里设置"requiredBackgroundModes": ["audio"]无效,正确做法是:小程序管理后台 → 开发管理 → 开发者工具 → 勾选“启用新版调试基础库” |
| 预约成功但地图车位颜色不变 | WebSocket连接失败或长轮询未触发 | 在小程序调试器Network标签页,筛选/api/space/status,看是否返回200且data有内容 | 检查后端application.yml里server.servlet.context-path是否为/parking-system,前端请求URL必须严格匹配 |
| 二维码生成失败,显示“加载中…” | qrcode.js未正确引入或Canvas尺寸为0 | 在WXML里给<canvas>加style="width:200px;height:200px;",再看是否渲染 | qrcode.js需放在utils/目录,map.js里用const QRCode = require('../../utils/qrcode.js')引入,不能用ES6 import |
5.2 后端部署高频故障
| 报错信息 | 根本原因 | 关键日志线索 | 一招解决 |
|---|---|---|---|
Failed to bind properties under 'spring.datasource' | application.yml里数据库密码含特殊字符(如!)未转义 | 日志出现Caused by: java.lang.IllegalArgumentException: Could not resolve placeholder 'password' | 将密码用单引号包裹:password: 'StrongPass123!' |
org.springframework.web.HttpRequestMethodNotSupportedException: Request method 'POST' not supported | 前端请求URL拼写错误(如/api/reservation/createe多打一个e) | Tomcat日志出现WARN o.s.web.servlet.PageNotFound - Request method 'POST' not supported for '/api/reservation/createe' | 用Postman测试/api/reservation/create,确认接口存在且能返回401(未登录)而非404 |
java.sql.SQLException: The server time zone value 'XXX' is unrecognized | MySQL时区配置与JVM不一致 | 日志出现The server time zone value 'UTC' is unrecognized | 在JDBC连接串末尾加&serverTimezone=Asia/Shanghai,如jdbc:mysql://localhost:3306/parking_system?useSSL=false&serverTimezone=Asia/Shanghai |
5.3 数据库相关致命陷阱
陷阱1:中文乱码导致预约失败
现象:用户预约时输入“政府公务”,后台查到的却是“æ”乱码,审核列表显示为空。
根源:MySQL安装时默认字符集是latin1,即使建库指定utf8mb4,表字段仍可能是latin1。
诊断:SHOW CREATE TABLE reservation;查看reason字段的CHARACTER SET。
解决:逐级修正
ALTER DATABASE parking_system CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci; ALTER TABLE reservation CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; ALTER TABLE reservation MODIFY reason VARCHAR(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;陷阱2:乐观锁失效引发超预约
现象:两个用户同时预约同一车位,数据库里出现两条status=2的预约记录。
根源:MyBatis的updateStatusByIdAndStatus方法没加事务,或者@Transactional注解没生效(比如在private方法上)。
诊断:看space_mapper.xml里<update>标签是否包含WHERE status = #{oldStatus},再检查Service方法是否被Spring代理(不能是this.reserveSpace()调用)。
解决:确保ReservationService类上加@Service,方法用public修饰,且调用方是Spring容器注入的对象。
5.4 毕业设计答辩加分技巧
作为指导老师,我告诉学生:答辩时不要讲“我用了SSM框架”,要说清楚为什么在这个场景下SSM比Spring Boot更合适。比如:
- “我对比过两种方案的启动时间:SSM在Tomcat里平均启动12秒,Spring Boot内置Tomcat启动23秒,这对需要频繁重启调试的毕设场景更友好”
- “SSM的XML配置让我能精准控制每个Bean的作用域,比如SqlSessionFactoryBean必须是singleton,而SqlSessionTemplate必须是prototype,Spring Boot的自动配置反而容易混淆”
- “数据库连接池Druid的监控页面(/druid)直接暴露在后台,答辩时我可以现场演示慢SQL分析,这是Spring Boot Actuator做不到的直观性”
最后分享一个小技巧:答辩PPT里放一张真实部署截图——不是本地localhost,而是你租的云服务器IP,配上curl -I http://你的IP:8080/parking-system/api/test返回的HTTP头。评委老师看到HTTP/1.1 200 OK,就知道这不是Demo,是真跑起来的系统。
这套系统我已在三个不同学校的毕设中验证过:计算机系学生用它拿了校级优秀,信息管理专业学生加了“停车费自动结算”模块拓展了课题,甚至有个创业团队基于它做了社区团购车位共享的MVP。它不完美,但足够扎实——就像一辆保养良好的老款丰田,不炫酷,但拉货、载人、跑长途,从不掉链子。
本文还有配套的精品资源,点击获取
简介:一套开箱即用的停车场智能管理解决方案,前端用微信小程序实现车位地图可视化、实时空位查询、在线预约和订单管理;后端基于Java语言,采用Spring+SpringMVC+MyBatis(SSM)框架开发,MySQL存储数据,支持标准B/S架构部署。系统划分为用户端和管理端:用户通过小程序完成车位浏览、预约下单、订单查看等操作;管理员通过后台页面统一维护用户信息、配置车位基础数据(如区域/类型/状态)、审核预约记录、调整系统参数。资源包包含全部可运行代码——微信小程序源码(含pages、components、app.js等结构清晰模块)、Java后端完整Maven工程(含pom.xml、src主目录、SSM核心配置文件)、MySQL建表SQL脚本及字段说明、微信开发者工具配置文件(project.config.)、详细开发说明文档(含接口说明与页面逻辑)、以及分步式部署指南(readme.txt)。所有代码注释完整、目录规范,适合作为本科毕业设计选题、Java全栈课程实践或小型智慧停车项目快速启动参考。
本文还有配套的精品资源,点击获取