Spring Boot停车场系统:多状态车位建模与逻辑删除实践
2026/9/17 13:21:54 网站建设 项目流程

简介:本资源是一份面向计算机专业本科生的Java毕业设计参考论文,聚焦停车场管理系统的全流程开发实践,解决传统人工停车管理效率低、易出错、数据难追溯等实际问题。全文共六章,系统覆盖课题背景与意义、SpringBoot+MySQL开发环境搭建、可行性与功能需求分析、E-R图与数据库表结构设计、管理员与用户双角色功能实现(含车位预订、车辆出入计费、公告管理等)、以及完整功能测试用例与结果分析,内容详实、结构规范,适合作为课程设计或毕设选题的技术蓝本。资源为单个Word文档(.doc),大小3.65MB,内容包含摘要、目录、六章正文及致谢,章节编号清晰、代码与界面描述结合紧密。目前已有321人学习下载,读者可直接获取开题逻辑、技术选型依据、模块化设计思路及可复用的数据库设计范式,快速掌握基于SpringBoot的中小型管理系统开发全链路。

1. 这不是又一个“登录+增删改查”的毕设模板,而是一套能跑通车位状态流转、支持逻辑删除与分时计费的 Spring Boot 停车场系统

你手头这份《基于Java的停车场管理系统论文.doc》,表面看是本科毕设文档,但拆开第4章数据库设计和第5章功能实现细节会发现:它没用“用户表+车位表+订单表”三张表硬凑,而是通过chewei_yuyue_shichang(预计停车时间)、chewei_tingchefei(金额/小时)、shangxia_types(是否上架)、chewei_delete(逻辑删除)等字段,把真实停车场运营中“车位可预订、可下架、可按小时计费、可软删除不丢历史”的业务约束,原样落到了 MySQL 表结构里。这不是教科书式 CRUD,而是对「资源状态机」的建模——车位有“空闲/已预订/已占用/已下架”多态,预订有“待审核/已通过/已过期/已取消”生命周期。整套系统用 Spring Boot + MyBatis-Plus 实现,没有 XML 配置,靠@TableLogic注解自动处理逻辑删除,靠@TableName("chewei")显式绑定物理表名,规避了中文表名在 IDEA 中的编码兼容问题。适合两类人:一是正在写 Java 毕设、卡在“怎么让车位状态变化影响前端按钮显隐”的同学;二是想快速验证 Spring Boot 多状态业务建模能力的初级开发者——它不追求高并发,但每一步状态变更都经得起@Transactional回滚检验。

2. 从 E-R 图到物理表:为什么车位表要拆出chewei_uuid_number而不是直接用主键 ID?

2.1 实体关系建模中的“业务主键”陷阱

论文第4.3.1节给出的 E-R 图里,车位实体(矩形)连着“车位编号”(椭圆)属性,但没说明这个编号的生成规则。很多初学者直接把数据库自增 ID 当作对外展示的“车位号”,结果在管理员导出报表、用户查看预订详情时暴露id=127这种无意义数字,既难记忆又易被恶意遍历。本系统在表4.2 车位表中单独定义chewei_uuid_number VARCHAR(64)字段,正是为了解耦“系统唯一标识”与“业务可读编号”。实际开发中,这个字段应由服务层生成,常见做法是:

// com.example.parking.service.impl.CheweiServiceImpl.java public String generateCheweiNumber() { // 规则:CW + 年月日 + 4位随机数(避免重复) String prefix = "CW" + LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd")); String suffix = String.format("%04d", new Random().nextInt(10000)); return prefix + suffix; }

提示:不要用 UUID.randomUUID().toString() 生成车位号——它太长(36位),且含横线,不利于扫码枪识别和人工核对;也不要依赖数据库自增,否则分布式部署时需额外处理 ID 冲突。

2.2 逻辑删除字段chewei_delete的双重作用

表4.2chewei_delete INTEGER字段看似只是标记“是否删除”,但结合 Spring Boot 的@TableLogic注解,它实际承担两个关键职责:

  • 数据安全隔离:当管理员点击“下架车位”,执行的是UPDATE chewei SET chewei_delete = 1 WHERE id = ?,而非物理删除。后续所有SELECT * FROM chewei查询(包括 MyBatis-Plus 的list()方法)会自动追加AND chewei_delete = 0条件,确保已下架车位不出现在用户端列表中;
  • 状态回滚基础:若误操作下架,只需UPDATE chewei SET chewei_delete = 0 WHERE id = ?即可恢复,无需从备份库捞数据。

该字段类型选INTEGER(非TINYINTBOOLEAN)是为预留扩展:当前0=正常,1=下架,未来可扩展2=维修中,3=临时禁用等状态,避免 ALTER TABLE 修改字段类型。

2.3 车位预订表中的时间精度设计

表4.3 车位预订表chewei_yuyue_shichang INTEGER字段存储“预计停车时间”,单位是“小时”。这看似简单,却暗含业务妥协:

  • 若存DECIMAL(5,2)支持 0.5 小时(30分钟),则计费逻辑需处理chewei_tingchefei * chewei_yuyue_shichang的浮点运算,MySQL 中浮点数比较易出精度误差;
  • 若存BIGINT存秒级时间戳,则前端传参复杂,且chewei_yuyue_shichang语义变为“预计停车总秒数”,违背“小时”这一业务单位。

因此,INTEGER是平衡点——它强制业务方约定“最小计费单位为1小时”,简化了ParkingFeeCalculator类的实现:

// com.example.parking.util.ParkingFeeCalculator.java public BigDecimal calculateFee(BigDecimal hourlyRate, Integer hours) { // 强制向上取整:停2.1小时按3小时计费 int actualHours = (int) Math.ceil(hours.doubleValue()); return hourlyRate.multiply(BigDecimal.valueOf(actualHours)); }
2.3.1 数据库索引策略:为什么chewei_address不建索引?

表4.2chewei_address VARCHAR(255)存储车位地点(如“A区地下二层东侧”)。虽然常被 WHERE 查询,但不建议为此字段建普通索引,原因有三:

  1. 地址文本长度大,索引体积膨胀快,拖慢 INSERT/UPDATE 性能;
  2. 用户搜索地址多为模糊匹配(LIKE '%A区%'),B+Tree 索引对前导通配符无效;
  3. 真实场景中,地址常与区域、楼层等结构化字段组合查询(如WHERE area='A区' AND floor=2),应优先对areafloor等离散值字段建联合索引。

注意:若必须支持地址全文检索,应启用 MySQL 5.6+ 的FULLTEXT索引,并配合MATCH() AGAINST()语法,而非滥用 LIKE。

2.4 MyBatis-Plus 配置与实体映射

论文未提 ORM 框架,但从chewei_delete字段命名风格及 Spring Boot 环境可推断使用 MyBatis-Plus。需在application.yml中配置逻辑删除:

# src/main/resources/application.yml mybatis-plus: global-config: db-config: logic-delete-field: chewei_delete # 全局逻辑删除字段名 logic-delete-value: 1 # 已删除值 logic-not-delete-value: 0 # 未删除值

对应实体类Chewei.java需标注:

@TableLogic // 标记此字段为逻辑删除字段 private Integer cheweiDelete;

此时调用cheweiService.removeById(123)不会执行DELETE FROM chewei,而是UPDATE chewei SET chewei_delete = 1 WHERE id = 123 AND chewei_delete = 0—— 后半段AND chewei_delete = 0是防重删的安全锁。

字段名类型是否允许为空业务含义MyBatis-Plus 注解
chewei_nameVARCHAR(50)YES车位名称(如“A001”)@TableField("chewei_name")
chewei_uuid_numberVARCHAR(64)YES业务编号(CW202405200001)@TableField("chewei_uuid_number")
chewei_tingchefeiDECIMAL(10,2)YES每小时停车费@TableField("chewei_tingchefei")
shangxia_typesINTEGERYES上架状态(0=下架,1=上架)@TableField("shangxia_types")
chewei_deleteINTEGERYES逻辑删除(0=正常,1=已删除)@TableLogic

3. 管理员核心流程:如何用 Spring Security 控制“车位预订审核”与“违规处理”的权限分离?

3.1 基于角色的 URL 权限控制

论文第5.1节提到“管理员功能包括车位预订管理、违规管理”,但未说明权限如何隔离。真实系统中,不能让负责审核预订的管理员误删违规记录。Spring Security 的HttpSecurity配置需精确到接口级别:

// com.example.parking.config.SecurityConfig.java @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers("/admin/chewei/yuyue/**").hasRole("BOOKING_AUDITOR") // 预订审核专用 .antMatchers("/admin/weigui/**").hasRole("VIOLATION_HANDLER") // 违规处理专用 .antMatchers("/admin/chewei/**").hasRole("PARKING_MANAGER") // 车位全量管理 .anyRequest().authenticated(); }

此时,/admin/chewei/yuyue/approve?id=456接口只对BOOKING_AUDITOR角色开放,即使同一账号拥有多个角色,也需显式分配。

3.2 预订审核状态机的事务边界

表4.3chewei_yuyue_yesno_types INTEGER字段存储预订状态(论文未给枚举值,但根据上下文应为:0=待审核,1=已通过,2=已拒绝,3=已过期)。审核操作必须保证原子性:

  • 更新预订状态为“已通过”;
  • 同时将对应车位的shangxia_types设为 0(下架,防止重复预订);
  • 记录操作日志。

若分步执行,中间失败会导致状态不一致。正确做法是用@Transactional包裹整个审核方法:

// com.example.parking.service.impl.CheweiYuyueServiceImpl.java @Transactional(rollbackFor = Exception.class) public boolean approveBooking(Long yuyueId) { // 1. 查询预订记录 CheweiYuyue yuyue = yuyueMapper.selectById(yuyueId); if (!Objects.equals(yuyue.getCheweiYuyueYesnoTypes(), 0)) { throw new BusinessException("仅可审核待审核状态的预订"); } // 2. 更新预订状态 yuyue.setCheweiYuyueYesnoTypes(1); // 已通过 yuyueMapper.updateById(yuyue); // 3. 下架对应车位(关联查询获取车位ID) Long cheweiId = yuyue.getCheweiId(); Chewei chewei = cheweiMapper.selectById(cheweiId); chewei.setShangxiaTypes(0); // 下架 cheweiMapper.updateById(chewei); // 4. 记录日志(省略具体实现) logService.record("预订审核", "ID:" + yuyueId + " 已通过"); return true; }

提示:@Transactional默认只对RuntimeException回滚。此处BusinessException需继承RuntimeException,否则需显式声明rollbackFor = BusinessException.class

3.3 违规处理中的“软关联”设计

论文第4.3.1节的 E-R 图显示“违规实体”与“车辆实体”关联,但表4.1 车辆表中并无外键指向违规表。这是典型的软关联(Soft Foreign Key):违规表weigui中存cheliang_id INTEGER,但数据库不建FOREIGN KEY约束。理由很现实——

  • 若车辆被逻辑删除(cheliang_delete=1),违规记录仍需保留以供审计;
  • 若强加外键,删除车辆时需先删违规,破坏业务完整性。

因此,查询某车辆的所有违规记录时,不能依赖数据库 JOIN,而应手动关联:

// com.example.parking.controller.WeiguiController.java @GetMapping("/vehicle/{cheliangId}") public List<Weigui> getViolationsByVehicle(@PathVariable Long cheliangId) { // 先查车辆是否存在且未删除 Cheliang cheliang = cheliangMapper.selectOne( new QueryWrapper<Cheliang>().eq("id", cheliangId).eq("cheliang_delete", 0) ); if (cheliang == null) { throw new BusinessException("车辆不存在或已被删除"); } // 再查违规记录(不加 cheliang_delete 条件,因违规需永久保留) return weiguiMapper.selectList( new QueryWrapper<Weigui>().eq("cheliang_id", cheliangId) ); }

3.4 登录流程中的密码安全实践

论文第3.2.2节登录流程图只画了“判断账号密码是否正确”,但生产环境必须防范暴力破解。Spring Security 可集成DaoAuthenticationProvider并自定义密码校验逻辑:

// com.example.parking.security.CustomUserDetailsService.java @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { // 1. 查询用户(含加密密码、账号状态) Admin admin = adminMapper.selectOne( new QueryWrapper<Admin>().eq("username", username).eq("delete_flag", 0) ); if (admin == null) { throw new UsernameNotFoundException("用户名不存在"); } // 2. 检查账号是否被锁定(论文未提,但必须加) if (admin.getLockTime() != null && System.currentTimeMillis() - admin.getLockTime().getTime() < 30 * 60 * 1000L) { throw new LockedException("账号已被锁定,请30分钟后重试"); } return new org.springframework.security.core.userdetails.User( admin.getUsername(), admin.getPassword(), // BCrypt 加密后的密码 AuthorityUtils.createAuthorityList("ROLE_" + admin.getRole()) ); }

密码加密必须用BCryptPasswordEncoder(非 MD5/SHA),因其带盐且计算耗时,天然防彩虹表和暴力破解。配置如下:

@Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(12); // 强度12,平衡安全与性能 }

4. 前端交互与状态同步:如何让“车位预订成功”后,用户端实时看到车位变灰?

4.1 基于 WebSocket 的状态推送

论文第5章只提“功能界面展示”,未涉及实时性。但真实停车场中,用户提交预订后,若其他用户同时刷新页面,可能看到“已预订”车位仍显示“可预订”,引发冲突。解决方案是引入 WebSocket,当预订状态更新时,主动推送消息给相关用户:

// com.example.parking.config.WebSocketConfig.java @Configuration @EnableWebSocketMessageBroker public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { @Override public void configureMessageBroker(MessageBrokerRegistry config) { config.enableSimpleBroker("/topic"); // 订阅主题 config.setApplicationDestinationPrefixes("/app"); // 发送前缀 } }

预订成功后,服务端推送:

// com.example.parking.service.impl.CheweiYuyueServiceImpl.java @Autowired private SimpMessagingTemplate messagingTemplate; @Transactional public void createBooking(CheweiYuyue yuyue) { yuyueMapper.insert(yuyue); // 推送车位状态变更(向所有订阅 /topic/chewei/123 的客户端) messagingTemplate.convertAndSend( "/topic/chewei/" + yuyue.getCheweiId(), Map.of("status", "booked", "bookingId", yuyue.getId()) ); }

前端 JavaScript 监听:

// user-page.js const stompClient = new StompJs.Client({ brokerURL: 'ws://localhost:8080/ws' }); stompClient.onConnect = () => { stompClient.subscribe(`/topic/chewei/${cheweiId}`, (message) => { const data = JSON.parse(message.body); if (data.status === 'booked') { document.getElementById('chewei-btn').disabled = true; document.getElementById('chewei-btn').innerText = '已预订'; } }); }; stompClient.activate();

注意:WebSocket 连接需在用户登录后建立,且连接关闭时需清理订阅,避免内存泄漏。

4.2 分页查询中的“状态一致性”保障

用户查看可预订车位列表时,后端执行:

// Page<Chewei> page = cheweiService.page( // new Page<>(current, size), // new QueryWrapper<Chewei>() // .eq("shangxia_types", 1) // 仅查上架车位 // .eq("chewei_delete", 0) // 仅查未删除 // );

但此查询存在隐患:若分页查询期间,其他事务将某车位shangxia_types从 1 改为 0,该车位可能在第1页出现、第2页消失(幻读)。解决方法不是加SELECT FOR UPDATE(性能差),而是前端缓存 + 版本号校验

  • 后端返回分页数据时,附带lastUpdateTime(最新车位更新时间戳);
  • 前端下次请求时,带上该时间戳作为If-Modified-Since
  • 后端比对:若无新更新,直接返回304 Not Modified,前端复用本地缓存。
// Controller 中添加响应头 response.setHeader("Last-Modified", new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss 'GMT'", Locale.US) .format(cheweiList.get(0).getUpdateTime()));

4.3 文件上传的防踩坑配置

论文中chewei_photo(车位照片)、cheliang_photo(车辆照片)字段均为VARCHAR,说明图片存路径而非二进制。但application.yml必须配置文件上传限制,否则大图上传会报 400 错误:

# src/main/resources/application.yml spring: servlet: context-path: /parking web: resources: static-locations: classpath:/static/,file:/opt/parking/upload/ servlet: multipart: max-file-size: 10MB max-request-size: 10MB

上传接口需校验文件类型与大小:

@PostMapping("/upload/photo") public Result<String> uploadPhoto(@RequestParam("file") MultipartFile file) { if (file.isEmpty()) { return Result.fail("文件不能为空"); } if (file.getSize() > 10 * 1024 * 1024) { return Result.fail("文件大小不能超过10MB"); } String contentType = file.getContentType(); if (!"image/jpeg".equals(contentType) && !"image/png".equals(contentType)) { return Result.fail("仅支持 JPG/PNG 格式"); } // 保存文件到 /opt/parking/upload/ 并返回相对路径 String fileName = UUID.randomUUID() + "_" + file.getOriginalFilename(); Path path = Paths.get("/opt/parking/upload/", fileName); Files.write(path, file.getBytes()); return Result.success("/upload/" + fileName); // 前端用此路径显示 }

5. 生产部署前的三项硬性检查:MySQL 字符集、Spring Boot Banner、IDEA 编码

5.1 MySQL 字符集必须设为 utf8mb4

论文未提数据库初始化脚本,但chewei_name(车位名称)、chewei_content(车位介绍)可能含 emoji 或生僻字。若 MySQL 用默认utf8(实为utf8mb3),插入 🚗 或 “𠮷” 会报错。必须在创建数据库时指定:

CREATE DATABASE parking_db CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci;

并在application.yml的 JDBC URL 中显式声明:

spring: datasource: url: jdbc:mysql://localhost:3306/parking_db?useUnicode=true&characterEncoding=utf8mb4&serverTimezone=Asia/Shanghai

提示:utf8mb4是 MySQL 对 UTF-8 的完整实现,utf8是阉割版(最多3字节),2023年后所有新项目必须用utf8mb4

5.2 关闭 Spring Boot Banner 避免启动干扰

论文第2.4节说 Spring Boot “不需要配置 XML”,但未提 Banner。默认启动时打印 ASCII 艺术字,不仅浪费日志空间,在容器化部署时还可能触发日志采集器误判。应在application.yml中关闭:

spring: main: banner-mode: off # 或 console(仅控制台显示)、log(仅日志显示)

若需自定义 Banner,可用 Spring Boot Banner Generator 在线生成.txt文件,放入src/main/resources/目录。

5.3 IDEA 编码设置:三处必须统一为 UTF-8

论文第2.3节夸 IDEA 代码提示好,但若编码不一致,.doc论文中“车位”二字在 Java 文件里可能变成乱码。需检查三处:

  1. 全局编码File → Settings → Editor → File EncodingsGlobal Encoding设为UTF-8
  2. 项目编码:同页面 →Project Encoding设为UTF-8
  3. 属性文件编码:同页面 →Default encoding for properties files设为UTF-8,并勾选Transparent native-to-ascii conversion(自动转义中文)。

注意:修改后需重启 IDEA,且已有乱码文件需右键 →Reload project from disk

5.4 一键生成数据库建表 SQL 的实用技巧

论文第4.3.2节只列了字段,未给建表语句。手动写易出错,推荐用 IDEA 的 Database 工具:

  • Database面板连接 MySQL;
  • 右键目标数据库 →SQL Scripts → Generate DDL
  • 选择cheweicheliangchewei_yuyue等表;
  • 生成的 SQL 会自动包含ENGINE=InnoDB DEFAULT CHARSET=utf8mb4,复制到src/main/resources/sql/init.sql即可。

若需在 Spring Boot 启动时自动执行,配置:

spring: sql: init: mode: always schema-locations: classpath:sql/init.sql

这样,每次mvn spring-boot:run都会重建干净库,省去手动清库步骤。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询