ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

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

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 \ --buildmaven \ --java-version17 \ --packagingjar \ --nameapartment-repair-system \ --package-namecom.example.repair \ --dependenciesweb,thymeleaf,mybatis-plus,mysql,validation,lombok,actuator \ --boot-version3.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?useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrue 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/RESIDENTstatus启用/禁用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主键雪花IDtitleVARCHAR(100)报修标题非空descriptionTEXT详细描述支持换行image_urlsJSON存储图片 URL 数组如[/upload/abc.jpg,/upload/def.png]statusTINYINT状态码0-WAITING, 1-ASSIGNED, 2-PROCESSING, 3-COMPLETED, 4-REJECTED, 5-CLOSEDapplicant_idBIGINT提报人 ID关联 t_user.idassignee_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 SetRepairStatus allowedFrom; RepairStatus(byte code, SetRepairStatus 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类型默认 0MyBatis 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.RepairOrderlambdaUpdate() .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采用本地目录存储更可控。关键安全措施限制单文件大小 ≤ 5MBspring.servlet.multipart.max-file-size5MB校验文件扩展名仅允许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 ResultString 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 groupIdnet.coobird/groupId artifactIdthumbnailator/artifactId version0.4.19/version /dependency在UploadService中添加缩略图生成逻辑与原图同目录保存命名规则为original_name_thumb.jpgpublic 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:eachimgUrl : ${repairOrder.imageUrls} img th:src${imgUrl.replace(.jpg, _thumb.jpg).replace(.png, _thumb.png)} th:alt报修图片 width200 height200/ /div注意replace操作需确保原图命名规范否则缩略图路径错误。更健壮做法是在RepairOrder实体中额外维护thumbnailUrls字段上传时一并生成并存库。5. 毕设答辩高频问题应对从代码细节到架构演进的深度准备5.1 面试官必问的 3 个 Spring Boot 配置陷阱与修复方案问题场景错误配置正确配置原因说明启动报错Failed to configure a DataSourcespring.datasource.url未配置或拼写错误如datasoruce检查application.yml中spring.datasource.url是否完整含?useSSLfalseserverTimezoneAsia/ShanghaiSpring Boot 2.4 默认启用DataSource自动配置URL 缺失会触发健康检查失败Thymeleaf 模板不渲染显示原始 HTMLspring.thymeleaf.cache: true开发时未关闭开发环境设为falsespring.thymeleaf.cache: falsespring.thymeleaf.enabled: true缓存开启导致修改模板后需重启毕设调试阶段必须关闭日志输出中文乱码IDEA 控制台未指定 JVM 参数在 IDEA 的 Run Configuration → VM Options 中添加-Dfile.encodingUTF-8Windows 系统默认编码为 GBKSpring Boot 日志框架读取配置文件时解码异常5.2 如何向答辩老师证明你理解了「为什么用 MyBatis Plus 而不是 JPA」不要只说「JPA 学不会」要从技术选型依据切入SQL 可控性报修系统需复杂查询如「统计各楼栋本月报修量 Top5」MyBatis Plus 的QueryWrapper可精准构造GROUP BYORDER BYLIMIT而 JPA 的 Criteria API 写法冗长且易出错学习成本与调试效率MyBatis Plus 自动生成 SQL 并打印到控制台mybatis-plus.configuration.log-implorg.apache.ibatis.logging.stdout.StdOutImpl学生能直接看到执行语句快速定位 N1 查询问题JPA 的Query注解需反复编译验证生态适配性本系统需对接 EasyExcel 导出报表MyBatis Plus 的selectList返回ListMapString, Object与 Excel 模板无缝衔接JPA 实体需额外转换。5.3 毕设代码质量提升的 3 个具体动作为所有 Service 方法添加Transactional显式声明即使当前只有单表操作也养成习惯。未来扩展「提报报修单同时发送站内信」时事务能保证两者原子性。避免依赖 Spring Boot 默认的Transactional代理机制失效如自调用。用ResultT统一封装返回值禁止裸return new HashMap()定义标准响应体public class ResultT { private int code; // 200-成功, 500-失败 private String msg; private T data; // getter/setter }所有 Controller 返回Result?前端统一处理code避免if (res.data ! null)等散落判断。在application-dev.yml中配置 HikariCP 连接池监控端点添加 Actuator 依赖后访问http://localhost:8080/actuator/metrics/hikaricp.connections.active可实时查看活跃连接数。答辩时演示「模拟 100 并发提报观察连接池是否打满」体现工程化思维。提示答辩前务必导出mvn dependency:tree -Dverbose输出确认无冲突依赖如spring-boot-starter-web与spring-boot-starter-webflux共存。使用mvn clean compile全流程验证避免 IDEA 缓存导致的编译通过但打包失败。本文还有配套的精品资源点击获取
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进