news 2026/9/16 5:19:00

Spring Boot公寓报修系统毕设实战:状态机、并发控制与文件处理

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Spring Boot公寓报修系统毕设实战:状态机、并发控制与文件处理

简介:本资源是一套基于Spring Boot与Vue开发的公寓报修管理系统完整毕业设计项目,面向计算机专业本科生及Java全栈初学者,解决高校或中小型公寓物业中报修流程线上化、工单闭环管理、多角色协同等实际问题。压缩包共460个文件,9.85MB,涵盖139个Java后端核心代码文件(含MyBatisPlus数据层与Spring Security权限控制)、51个Vue组件(如update-password.vue.bak等管理界面原型)、161个SVG图标资源及配套JPG/PNG素材、YML配置与SQL脚本等,结构清晰,模块对应绪论、系统分析、数据库设计、前后端实现等毕设标准章节。目前已有126人学习下载,读者可直接导入IDE运行调试,获取含用户管理、图片素材上传、工单状态流转、响应式后台界面在内的可部署系统源码,以及完整的项目文档目录与技术选型说明,是实践B/S架构、前后端分离与MySQL数据库设计的优质教学参考案例。

1. 为什么一个「公寓报修管理系统」能成为 Java 毕设高频选题?它真只是个 CRUD 吗?

很多计算机专业学生在毕设选题时点开「公寓报修管理系统」,第一反应是:这不就是增删改查+上传图片+发邮件?但实际交付中,90% 的挂科或答辩卡点,恰恰出在「看似简单」的环节——比如报修单状态流转被并发修改导致工单丢失、维修人员接单后超时未响应却无自动预警、多栋楼多管理员权限隔离失效、图片上传后缩略图生成失败引发前端白屏。这些不是理论问题,而是 Spring Boot 在真实业务流中必须面对的事务边界、异步解耦、文件处理与权限模型落地细节。本系统面向高校宿舍/长租公寓场景,核心价值不在界面美观,而在用最小技术栈(Spring Boot + MyBatis Plus + Thymeleaf + MySQL)跑通「用户提报→客服分派→师傅接单→现场处理→用户评价→数据统计」全链路闭环。适合 Java 基础扎实、想通过毕设夯实 Web 开发全流程能力的学生,尤其适合作为 Spring Boot 面试题的实战载体——比如「如何保证接单操作的幂等性」「怎样设计报修单状态机」「上传图片如何防超大文件阻塞线程」,全是面试官爱问的硬核点。

2. 用 Spring Boot 3.2 + JDK 17 搭建最小可运行骨架:从空项目到能登录的后台

2.1 初始化项目并确认关键依赖版本兼容性

Spring Boot 版本选择直接影响后续集成稳定性。当前主流毕设环境推荐 Spring Boot 3.2.x(非 3.3.x),因其对 JDK 17 支持成熟,且与 MyBatis Plus 3.5.5+、Thymeleaf 3.1.2 兼容性经过大量学生验证。避免使用 Spring Boot 3.3+,该版本默认启用虚拟线程(Virtual Threads),在毕设级部署(如学生本地 Tomcat 或阿里云轻量应用服务器)易因 JVM 参数未调优导致线程调度异常。初始化命令如下:

# 使用 Spring Initializr CLI(需提前安装)生成基础项目 spring init \ --build=maven \ --java-version=17 \ --packaging=jar \ --name=apartment-repair-system \ --package-name=com.example.repair \ --dependencies=web,thymeleaf,mybatis-plus,mysql,validation,lombok,actuator \ --boot-version=3.2.12 \ ./apartment-repair-system

提示:actuator用于后期暴露/actuator/health等端点验证服务状态,validation是表单校验必需依赖,lombok减少@Data等样板代码。若使用 IDEA 创建,务必在 New Project → Spring Initializr 页面手动将 Spring Boot Version 设为3.2.12,而非默认最新版。

2.2 配置 MySQL 数据源与 MyBatis Plus 自动建表

毕设数据库建议使用 MySQL 8.0+(避免 MySQL 5.7 的datetime默认值兼容问题)。在application.yml中配置连接池参数,重点控制最大连接数与超时时间,防止本地开发时因连接泄漏导致服务假死:

spring: datasource: url: jdbc:mysql://localhost:3306/apartment_repair?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true username: root password: your_password driver-class-name: com.mysql.cj.jdbc.Driver hikari: maximum-pool-size: 10 minimum-idle: 2 connection-timeout: 30000 idle-timeout: 600000 max-lifetime: 1800000 mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl global-config: db-config: id-type: assign_id # 使用雪花算法生成 Long 类型 ID,避免 UUID 字符串索引性能损耗 table-prefix: t_ # 所有表名加前缀,便于区分系统表

启动类添加@MapperScan注解扫描 Mapper 接口:

@SpringBootApplication @MapperScan("com.example.repair.mapper") public class ApartmentRepairSystemApplication { public static void main(String[] args) { SpringApplication.run(ApartmentRepairSystemApplication.class, args); } }

2.3 实现基于角色的登录认证:管理员、维修员、住户三类用户

不引入 Spring Security 复杂配置,采用轻量级 Session + 自定义拦截器实现权限控制。创建User实体类,字段包含id,username,password,role(枚举:ADMIN/TECHNICIAN/RESIDENT),status(启用/禁用):

@Data @TableName("t_user") public class User { @TableId(type = IdType.ASSIGN_ID) private Long id; private String username; private String password; // 存储 BCrypt 加密后的密文 private Integer role; // 0-管理员, 1-维修员, 2-住户 private Integer status; // 0-禁用, 1-启用 }

密码加密使用BCryptPasswordEncoder,在用户注册/修改密码时调用:

@Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); }

登录逻辑封装在LoginController中,关键校验点:

  • 用户名存在且状态为启用;
  • 密码匹配(passwordEncoder.matches(rawPassword, user.getPassword()));
  • 根据role值跳转不同首页(/admin/index,/tech/index,/resident/index)。

注意:Session 超时时间需在application.yml中显式设置,避免默认 30 分钟过短导致操作中断:

server: servlet: session: timeout: 1800 # 单位秒,即 30 分钟

3. 报修单全生命周期管理:状态机驱动的业务逻辑与数据库设计

3.1 报修单状态流转模型与数据库表结构设计

报修单(RepairOrder)不是静态记录,而是具备明确状态变迁规则的业务实体。常见状态包括:WAITING(待受理)、ASSIGNED(已指派)、PROCESSING(处理中)、COMPLETED(已完成)、REJECTED(已拒绝)、CLOSED(已关闭)。状态变更必须满足前置条件,例如「只有状态为WAITING的单子才能被指派」,否则应抛出业务异常。数据库表t_repair_order关键字段设计如下:

字段名类型说明
idBIGINT主键,雪花ID
titleVARCHAR(100)报修标题,非空
descriptionTEXT详细描述,支持换行
image_urlsJSON存储图片 URL 数组,如["/upload/abc.jpg","/upload/def.png"]
statusTINYINT状态码:0-WAITING, 1-ASSIGNED, 2-PROCESSING, 3-COMPLETED, 4-REJECTED, 5-CLOSED
applicant_idBIGINT提报人 ID(关联 t_user.id)
assignee_idBIGINT指派人 ID(管理员)
technician_idBIGINT维修员 ID(可为空,接单后填充)
created_timeDATETIME创建时间
updated_timeDATETIME最后更新时间

提示:image_urls使用 JSON 类型存储,而非单独建图库表。毕设场景下简化设计,避免多表关联复杂度;生产环境则需拆分为t_repair_image表并建立外键。

3.2 状态变更服务层实现:用枚举约束状态合法性

定义RepairStatus枚举,明确每个状态的合法前驱状态(allowedFrom),确保状态机不可绕过:

public enum RepairStatus { WAITING((byte) 0, Collections.emptySet()), ASSIGNED((byte) 1, Set.of(WAITING)), PROCESSING((byte) 2, Set.of(ASSIGNED, WAITING)), // 允许管理员直派处理中 COMPLETED((byte) 3, Set.of(PROCESSING)), REJECTED((byte) 4, Set.of(WAITING, ASSIGNED)), CLOSED((byte) 5, Set.of(COMPLETED, REJECTED)); private final byte code; private final Set<RepairStatus> allowedFrom; RepairStatus(byte code, Set<RepairStatus> allowedFrom) { this.code = code; this.allowedFrom = allowedFrom; } public boolean canTransitionFrom(RepairStatus from) { return this.allowedFrom.contains(from); } public static RepairStatus fromCode(byte code) { for (RepairStatus status : values()) { if (status.code == code) return status; } throw new IllegalArgumentException("Unknown status code: " + code); } }

状态更新方法在RepairOrderService中实现,强制校验:

@Transactional public void updateStatus(Long orderId, RepairStatus newStatus, Long operatorId) { RepairOrder order = repairOrderMapper.selectById(orderId); if (order == null) { throw new BusinessException("报修单不存在"); } RepairStatus currentStatus = RepairStatus.fromCode(order.getStatus()); if (!newStatus.canTransitionFrom(currentStatus)) { throw new BusinessException("状态变更非法:从 " + currentStatus + " 不可变为 " + newStatus); } order.setStatus(newStatus.getCode()); order.setUpdatedTime(LocalDateTime.now()); // 若变为 PROCESSING 或 COMPLETED,记录操作人 if (newStatus == PROCESSING || newStatus == COMPLETED) { order.setTechnicianId(operatorId); // operatorId 此时为维修员ID } repairOrderMapper.updateById(order); }

3.3 住户提报与维修员接单的并发安全控制

提报和接单操作均涉及数据库写入,需防止并发冲突。例如两个维修员同时点击同一单的「接单」按钮,应只允许一人成功。传统SELECT ... FOR UPDATE在高并发下易锁表,毕设场景推荐乐观锁方案:在t_repair_order表增加version字段(INT类型,默认 0),MyBatis Plus 自动识别@Version注解:

@TableField(fill = FieldFill.INSERT) @Version private Integer version;

接单方法中,SQL WHERE 条件加入version和当前状态双重校验:

// 更新语句自动生成:UPDATE t_repair_order SET status=?, technician_id=?, version=? // WHERE id=? AND version=? AND status=? int rows = repairOrderMapper.update(order, Wrappers.<RepairOrder>lambdaUpdate() .eq(RepairOrder::getId, orderId) .eq(RepairOrder::getStatus, RepairStatus.WAITING.getCode()) .eq(RepairOrder::getVersion, oldVersion)); if (rows == 0) { throw new BusinessException("接单失败:该报修单已被他人处理"); }

4. 文件上传与图片处理:本地存储 + 缩略图生成的轻量级方案

4.1 基于 Spring Boot MultipartFile 的安全上传实现

毕设无需接入 OSS,采用本地目录存储更可控。关键安全措施:

  • 限制单文件大小 ≤ 5MB(spring.servlet.multipart.max-file-size=5MB);
  • 校验文件扩展名,仅允许jpg/jpeg/png/gif
  • 重命名文件为 UUID + 时间戳,避免恶意文件名(如../../../webshell.jsp);
  • 存储路径隔离:按日期分目录(/upload/2024/06/15/xxx.jpg),防止单目录文件过多。

配置项在application.yml中:

spring: servlet: multipart: max-file-size: 5MB max-request-size: 10MB

上传控制器代码:

@PostMapping("/upload") @ResponseBody public Result<String> uploadImage(@RequestParam("file") MultipartFile file) { // 1. 校验文件类型 String contentType = file.getContentType(); if (!"image/jpeg".equals(contentType) && !"image/png".equals(contentType) && !"image/gif".equals(contentType)) { return Result.fail("仅支持 JPG/PNG/GIF 格式"); } // 2. 生成安全文件名 String originalFilename = file.getOriginalFilename(); String extension = "." + FilenameUtils.getExtension(originalFilename); String safeName = UUID.randomUUID().toString().replace("-", "") + System.currentTimeMillis() + extension; // 3. 构建存储路径(按日期分层) LocalDate now = LocalDate.now(); String subPath = String.format("%d/%d/%d", now.getYear(), now.getMonthValue(), now.getDayOfMonth()); Path uploadDir = Paths.get("upload", subPath); try { Files.createDirectories(uploadDir); Path targetPath = uploadDir.resolve(safeName); file.transferTo(targetPath); String url = "/upload/" + subPath + "/" + safeName; return Result.success(url); } catch (IOException e) { log.error("文件上传失败", e); return Result.fail("上传失败,请重试"); } }

4.2 使用 Thumbnailator 生成缩略图并嵌入 Thymeleaf 模板

原图直接展示易拖慢页面加载,需生成 200x200 缩略图。引入thumbnailator依赖:

<dependency> <groupId>net.coobird</groupId> <artifactId>thumbnailator</artifactId> <version>0.4.19</version> </dependency>

UploadService中添加缩略图生成逻辑,与原图同目录保存,命名规则为original_name_thumb.jpg

public String generateThumbnail(String originalPath) throws IOException { Path original = Paths.get(originalPath); String thumbName = FilenameUtils.getBaseName(original.getFileName().toString()) + "_thumb" + FilenameUtils.getExtension(original.getFileName().toString()); Path thumbPath = original.getParent().resolve(thumbName); Thumbnails.of(original.toFile()) .size(200, 200) .outputQuality(0.8) .toFile(thumbPath.toFile()); return "/upload/" + thumbPath.toString().substring("upload".length() + 1); }

Thymeleaf 模板中使用缩略图:

<div th:each="imgUrl : ${repairOrder.imageUrls}"> <img th:src="${imgUrl.replace('.jpg', '_thumb.jpg').replace('.png', '_thumb.png')}" th:alt="报修图片" width="200" height="200"/> </div>

注意:replace操作需确保原图命名规范,否则缩略图路径错误。更健壮做法是在RepairOrder实体中额外维护thumbnailUrls字段,上传时一并生成并存库。

5. 毕设答辩高频问题应对:从代码细节到架构演进的深度准备

5.1 面试官必问的 3 个 Spring Boot 配置陷阱与修复方案

问题场景错误配置正确配置原因说明
启动报错Failed to configure a DataSourcespring.datasource.url未配置或拼写错误(如datasoruce检查application.ymlspring.datasource.url是否完整,含?useSSL=false&serverTimezone=Asia/ShanghaiSpring Boot 2.4+ 默认启用DataSource自动配置,URL 缺失会触发健康检查失败
Thymeleaf 模板不渲染,显示原始 HTMLspring.thymeleaf.cache: true(开发时未关闭)开发环境设为false
spring.thymeleaf.cache: false
spring.thymeleaf.enabled: true
缓存开启导致修改模板后需重启,毕设调试阶段必须关闭
日志输出中文乱码(IDEA 控制台)未指定 JVM 参数在 IDEA 的 Run Configuration → VM Options 中添加:
-Dfile.encoding=UTF-8
Windows 系统默认编码为 GBK,Spring Boot 日志框架读取配置文件时解码异常

5.2 如何向答辩老师证明你理解了「为什么用 MyBatis Plus 而不是 JPA」?

不要只说「JPA 学不会」,要从技术选型依据切入:

  • SQL 可控性:报修系统需复杂查询(如「统计各楼栋本月报修量 Top5」),MyBatis Plus 的QueryWrapper可精准构造GROUP BY+ORDER BY+LIMIT,而 JPA 的 Criteria API 写法冗长且易出错;
  • 学习成本与调试效率:MyBatis Plus 自动生成 SQL 并打印到控制台(mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl),学生能直接看到执行语句,快速定位 N+1 查询问题;JPA 的@Query注解需反复编译验证;
  • 生态适配性:本系统需对接 EasyExcel 导出报表,MyBatis Plus 的selectList返回List<Map<String, Object>>与 Excel 模板无缝衔接;JPA 实体需额外转换。

5.3 毕设代码质量提升的 3 个具体动作

  1. 为所有 Service 方法添加@Transactional显式声明
    即使当前只有单表操作,也养成习惯。未来扩展「提报报修单同时发送站内信」时,事务能保证两者原子性。避免依赖 Spring Boot 默认的@Transactional代理机制失效(如自调用)。

  2. Result<T>统一封装返回值,禁止裸return new HashMap<>()
    定义标准响应体:

    public class Result<T> { private int code; // 200-成功, 500-失败 private String msg; private T data; // getter/setter }

    所有 Controller 返回Result<?>,前端统一处理code,避免if (res.data != null)等散落判断。

  3. application-dev.yml中配置 HikariCP 连接池监控端点
    添加 Actuator 依赖后,访问http://localhost:8080/actuator/metrics/hikaricp.connections.active可实时查看活跃连接数。答辩时演示「模拟 100 并发提报,观察连接池是否打满」,体现工程化思维。

提示:答辩前务必导出mvn dependency:tree -Dverbose输出,确认无冲突依赖(如spring-boot-starter-webspring-boot-starter-webflux共存)。使用mvn clean compile全流程验证,避免 IDEA 缓存导致的编译通过但打包失败。

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/16 5:18:58

Pentagi:AI智能体驱动的渗透测试新架构

1. “Pentagi”不是拼写错误&#xff0c;而是一个正在成型的技术概念锚点你搜“pentagi”&#xff0c;页面上跳出来的全是“pentest”“penetration testing”“AI agents”“Docker”“Neo4j”——没有官方文档、没有GitHub仓库、没有公司主页&#xff0c;甚至没有一篇像样的技…

作者头像 李华
网站建设 2026/9/16 5:18:55

LTE/NR信道模型全解析:从TDL/CDL参数配置到外场测试避坑

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/16 5:17:20

Windows 11临时文件清理全指南:从系统工具到bat脚本,彻底释放C盘空间

C盘又红了&#xff1f;开机右下角弹出“磁盘空间不足”的时候&#xff0c;别急着装那些什么C盘清理大师、垃圾清理全家桶&#xff0c;先冷静一下。绝大多数情况下&#xff0c;Windows 11自己的临时文件清理加上几条命令&#xff0c;就已经能解决大半问题。这篇我就把我自己平时…

作者头像 李华
网站建设 2026/9/16 5:17:07

Kimi K2.8 Preview:AST级代码理解如何重塑IDE智能开发

1. Kimi K2.8 Preview 不是“又一个大模型更新”&#xff0c;而是开发者工作流的临界点突破最近在几个技术群和开源社区里&#xff0c;大家聊得最多的一句不是“Kimi又发新模型了”&#xff0c;而是“Kimi Code插件一装&#xff0c;我本地的VS Code突然会‘读代码’了”。这背后…

作者头像 李华
网站建设 2026/9/16 5:16:40

56G PAM4 SerDes发射端为何必须用4-tap数字FFE

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/16 5:16:28

基于Simulink的四旋翼6DOF建模与串级PID控制仿真指南

简介&#xff1a;面向自动控制、机器人及无人机方向的本科生与研究生&#xff0c;这是一份基于MATLAB/Simulink的四旋翼飞行器建模仿真综合实验项目资料&#xff0c;源自北京航空航天大学课程实践&#xff0c;重点解决六自由度&#xff08;6DOF&#xff09;动力学建模、姿态解算…

作者头像 李华