家庭设备维修服务系统这类项目这几年在Java课程设计和毕业设计里出现的频率相当高。原因也简单业务场景足够贴近生活需求容易理解前后端交互清晰技术栈又能完全踩中企业招聘JD上的主流关键词。我前前后后帮人review过不少类似的系统也实际动手搭过完整项目今天就把这套基于Springboot Vue的Web家庭设备维修服务系统从源码设计、核心代码讲解到部署文档整理成一篇完整的实操记录。无论你是准备拿它做课程设计还是想学习前后端分离项目的完整落地流程这篇文章都够你用。先把这个项目的定位说清楚。它本质上是给小区物业或第三方维修平台做的一套线上报修管理工具用户在小程序或网页上提交维修单管理员派单维修工接单处理全过程在线流转替代传统电话报修加纸质登记的模式。系统的核心价值在于三个角色之间的信息同步业主不用反复打电话催进度维修工不用等客服口头派单管理员能实时看到所有工单的状态和完成率。这套逻辑放到哪个维修场景里都成立所以项目的业务模型具有很强的可复用性。下面我按自己做项目时的推进顺序把整套东西拆开来讲。1. 项目核心需求与整体设计拆解1.1 这个系统到底要解决什么问题做项目之前先别急着写代码先把业务痛点梳理清楚。传统的家庭设备维修流程是这样的业主发现水管漏水或者电路故障打电话给物业物业记在纸上然后通知维修师傅师傅再抽空上门。这个流程最大的问题在于信息的断裂和黑盒化业主不知道维修师傅什么时候来维修师傅不知道除了这一单之外还有没有顺路能一起处理的单子物业经理统计月度维修工作量只能靠翻记录本。这套维修服务系统要做的就是把这些流程线上化。业主在线提交报修单填写故障类型、设备位置、问题描述可以上传照片维修工可以看到分派给自己的任务处理完以后填写维修结果和材料费用管理员负责审核用户、分配工单、查看统计数据。整个过程的状态是透明可追踪的每一单从提交到完成都能回溯这就是它相比传统模式最本质的改进。理解了这一点你再看网上流传的各种版本源码就会发现它们的功能模块再怎么变核心都是围绕“工单状态流转”这个主线来设计的待派单、已接单、维修中、已完成、已评价。任何状态设计得混乱的版本业务逻辑十有八九是讲不通的。1.2 角色划分与功能模块梳理这套系统的角色一般分为三类普通用户业主、维修工、管理员。也有部分版本会把维修工和管理员的权限合并但正规的设计里两者一定要分开因为它们的操作对象完全不一样。普通用户端的核心功能是报修全流程提交报修单、查看自己历史工单列表、查看工单处理进度、对完成的工单进行评价。这里要注意一个细节用户只能看到自己创建的工单不能看到别人的所以所有的查询都要带上当前登录用户ID作为条件这既是业务逻辑要求也是数据隔离的基本安全要求。维修工端的核心功能是接单和处理查看分派给自己的工单列表、接单或者拒单、填写维修记录包括故障原因、维修方式、材料费用、修改个人状态空闲/忙碌。有些项目里还做了维修工的技能标签比如擅长电路还是水暖方便管理员派单时做匹配这个功能可以作为加分项。管理员端是最重的包括用户管理审核注册用户、禁用恶意用户、维修工管理新增维修工账号、分配角色、工单管理查看所有工单、派单给指定维修工、处理投诉、数据统计按周/月统计工单量、完成率、客单价。在实现的时候管理员端的功能会直接决定这个项目的难度上限如果你想拿高分数据统计这块的建议是一定要做图表可视化而不是简单的表格罗列。1.3 为什么选择Spring Boot Vue这套组合这个问题面试官或答辩老师一定会问你自己心里也要有底。选择这套技术栈不是因为它新而是因为它最适合中小型Web管理系统的开发效率要求。先说Spring Boot。它解决了传统SSH/SSM框架最头疼的配置地狱问题内嵌Tomcat容器打包成jar直接跑自动配置机制让开发初期几乎不需要关心Bean的装配细节。对于课程设计这个体量的项目来说Sprng Boot能把你的精力从“怎么让项目跑起来”解放到“怎么把业务逻辑写清楚”上这是它最大的价值。再说Vue。Vue在前端框架里的定位是渐进式、轻量、上手快。相比React的学习曲线Vue的模板语法、双向绑定、组件化开发方式更适合没有系统学过前端框架的人。特别是Vue配合Element UI这类组件库做后台管理界面几乎是拼积木一样的体验一个表格组件、一个表单组件、一个弹窗组件拼一拼页面就出来了。再加上Vue Router做路由管理、Axios做HTTP请求前后端通过JSON格式交互清晰明了。关键的是这套组合也是目前中小型公司用得最多的技术方案之一。你把它完整做一遍等于模拟了一次真实的企业开发流程。我在实际review项目的时候最看重的是候选人能不能讲清楚前后端数据是怎么流通的、请求经过哪些层、状态码怎么约定——这些东西你在手写这套系统的过程中会自然形成肌肉记忆比背一百道面试题都有用。2. 源码结构解析与关键代码逻辑2.1 后端项目结构与分层职责拿到一套Spring Boot源码第一件事不是急着跑起来而是先看目录结构。正规的Spring Boot项目一定遵循分层的MVC架构我用这套维修系统给你做一个标准的目录拆解。src/main/java/com/example/repair/ ├── controller/ // 控制层接收前端请求返回JSON │ ├── UserController.java │ ├── OrderController.java │ └── AdminController.java ├── service/ // 业务层核心业务逻辑处理 │ ├── OrderService.java │ ├── UserService.java │ └── impl/ ├── mapper/ // 数据访问层MyBatis的Mapper接口 │ ├── OrderMapper.java │ ├── UserMapper.java │ └── RepairmanMapper.java ├── entity/ // 实体类对应数据库表结构 │ ├── User.java │ ├── RepairOrder.java │ └── Repairman.java ├── config/ // 配置类跨域、拦截器、JWT等 │ ├── WebConfig.java │ └── JwtInterceptor.java ├── common/ // 通用类统一返回结果、异常处理 │ ├── Result.java │ └── GlobalExceptionHandler.java └── RepairApplication.java // 启动类这套分层没有花活但它是合理的。Controller只负责接收参数和返回结果不写任何业务逻辑Service里放真正的业务规则Mapper只做数据库交互。好处是后期好维护、好测试、答辩的时候也容易讲清楚层次关系。有一点我要特别强调统一返回结果类是所有Controller的通行证。我见过不少项目每个接口返回的数据结构都不一样有的返回Map有的返回JSONObject有的直接把实体类丢回去前端联调的时候痛苦得要命。规范的做法是定义一个Result类所有接口统一返回比如Data public class ResultT { private Integer code; // 状态码200成功400业务错误500系统错误 private String message; // 提示信息 private T data; // 返回数据 public static T ResultT success(T data) { ResultT result new Result(); result.setCode(200); result.setMessage(操作成功); result.setData(data); return result; } public static T ResultT error(String message) { ResultT result new Result(); result.setCode(400); result.setMessage(message); return result; } }前端拿到这个结构直接判断code 200就知道请求是否成功不用关心数据格式千奇百怪的问题。这个习惯如果你从现在开始培养后面做任何项目都会受益。2.2 数据库设计与核心表结构数据库设计是整套系统里最能体现功夫的环节。很多同学拿到源码第一步就跑程序结果报错就懵了因为表结构没看明白。我先帮你们把这套系统最核心的几张表拎出来。核心表至少需要四张用户表、维修工表、报修工单表、评价表。如果需要做管理员单独的表也可以但很多项目直接用一张用户表加角色字段来区分我建议用后者省一张表不说权限判断也统一。用户表和维修工表结构比较常规重点是字段的冗余设计。比如维修工表里可以直接冗余一个current_status字段用0/1表示空闲/忙碌这样管理员派单的时候就不用实时去算该维修工有几单在途直接查这个字段就行性能好且逻辑简单。这个思路在生产环境里叫“用空间换时间”用适当的字段冗余简化查询逻辑。报修工单表是整张表的核心字段必须覆盖完整业务链路CREATE TABLE repair_order ( id int NOT NULL AUTO_INCREMENT, order_no varchar(32) NOT NULL COMMENT 工单编号, user_id int NOT NULL COMMENT 报修用户ID, repairman_id int DEFAULT NULL COMMENT 接单维修工ID, device_type varchar(20) DEFAULT NULL COMMENT 设备类型水/电/暖/门窗等, description text COMMENT 故障描述, image_url varchar(255) DEFAULT NULL COMMENT 故障照片地址, address varchar(255) NOT NULL COMMENT 维修地址, status tinyint NOT NULL DEFAULT 0 COMMENT 状态0待派单 1已接单 2维修中 3待评价 4已完成 5已取消, appointment_time datetime DEFAULT NULL COMMENT 预约上门时间, create_time datetime DEFAULT NULL COMMENT 提交时间, finish_time datetime DEFAULT NULL COMMENT 完成时间, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;这个表的字段设计是有讲究的。状态字段是整套系统的灵魂每一步操作其实都是在改变这个状态值。派单是把status从0改成1接单是把1改成2维修完成是把2改成3用户评价后变成4。理解了这个状态机你对整个项目的理解就通了一大半。order_no这个字段容易被忽略但它是工单的可视化标识。用户打电话咨询时不可能说“我那个ID为82的单子”但是可以说“我那个编号RW20250115001的报修单”这就是业务编号存在的意义。生成规则一般用日期加自增序号写一个简单的工具方法就能生成。2.3 核心业务流程的代码实现讲几个最核心的业务逻辑点这些也是代码讲解的重头戏答辩的时候讲这些比讲CRUD有说服力得多。第一个是报修单提交的完整事务链。用户提交报修信息的时候前端传过来的数据里有设备类型、问题描述、图片等。你不要只在OrderMapper里做一个简单的insert要想想业务上还有什么隐含的动作。比如我习惯在提交之后同时做两件事生成工单编号、记录操作日志。工单编号可以在Service层生成后直接set进实体类操作日志是为了后续追溯。第二个是派单逻辑。管理员派单时后端要做三件事校验维修工状态是空闲、把维修工ID写入工单、把维修工状态置为忙碌。这三步必须在一个事务里完成否则可能出现维修工被重复派单的情况。我的建议是在Service方法上加Transactional注解任何一步失败就整体回滚Transactional public Result assignOrder(Integer orderId, Integer repairmanId) { // 1. 校验工单状态是待派单 RepairOrder order orderMapper.selectById(orderId); if (order null || order.getStatus() ! 0) { return Result.error(工单状态不允许派单); } // 2. 校验维修工状态 Repairman repairman repairmanMapper.selectById(repairmanId); if (repairman null || repairman.getCurrentStatus() ! 0) { return Result.error(该维修工当前不可接单); } // 3. 更新工单和维修工状态 order.setRepairmanId(repairmanId); order.setStatus(1); orderMapper.updateById(order); repairman.setCurrentStatus(1); repairmanMapper.updateById(repairman); return Result.success(null); }你看这个逻辑每一步都对上一个步骤的结果做了校验这种“前置校验”意识是区分初级和中级开发者的重要分水岭。不要等数据已经错了才去兜底。第三个是权限校验拦截器。前后端分离项目的接口默认都是裸奔的任何接口任何人都能直接调用所以要对需要登录才能访问的接口做拦截。常规做法是实现一个HandlerInterceptor在preHandle方法里从请求头取token解析出用户身份后再放行public class JwtInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String token request.getHeader(Authorization); if (token ! null token.startsWith(Bearer )) { String realToken token.substring(7); // 解析token中的用户信息写回request request.setAttribute(userId, JwtUtil.parseUserId(realToken)); return true; } response.setStatus(401); return false; } }这里我踩过一个坑需要提醒你如果拦截器把所有接口都拦截了前端登录接口自己也被拦了这就形成了死锁。解决办法是在WebConfig里注册拦截器时用excludePathPatterns把登录、注册这类白名单接口排除掉。这也是一个很常被问到的面试点。2.4 前端Vue工程的组织方式Vue前端项目的结构我建议你用Vue CLI或者Vite从零搭一个自己熟悉的再去对比源码这样源码头绪才理得清。典型的工程目录长这样src/ ├── api/ // 接口请求封装每一个后端接口对应一个js文件 │ ├── user.js │ ├── order.js │ └── request.js // Axios实例封装统一拦截器 ├── router/ // 路由配置路径和组件的映射关系 │ └── index.js ├── store/ // 状态管理Vuex/Pinia存用户登录态等 │ └── index.js ├── views/ // 页面组件一个文件夹对应一个页面 │ ├── login/ │ ├── user/ │ └── admin/ ├── components/ // 公共组件表格、表单、弹窗等 └── App.vue前端最核心的两个文件是request.js和router/index.js。request.js的作用是统一处理所有HTTP请求比如在请求拦截器里自动加上token请求头在响应拦截器里统一处理401跳转登录页这样每个业务页面里调接口的时候就不用重复做这些逻辑了。router/index.js里要配合后端的角色做路由守卫用户未登录就访问需要权限的页面时直接重定向到登录页。前端的API封装这一点几乎没人会认真做但它是项目整洁度的分水岭。每个页面直接从/api/order.js里import方法调用而不是在组件里满屏写axios.get(...)代码可读性完全是两个档次。我后来带前端实习生的时候第一个要求就是API必须集中管理。3. 部署文档实操从零到系统能跑起来3.1 环境准备与版本选型部署环节是这套操作里最劝退新手的因为版本不匹配会引发一堆莫名其妙的报错。先记下一套我自己验证过很多次、兼容稳定的版本组合组件版本说明JDK1.8或11不要上17以上部分老版本依赖会出兼容性问题Maven3.6.x构建后端项目的依赖管理工具MySQL5.7或8.0不要用5.5utf8mb4支持不完整Node.js14.x或16.x对应Vue CLI 4.x/5.xVue CLI4.5.x或5.x脚手架工具也可以直接用ViteNginx1.20.x部署前端静态文件和反向代理版本这里真的别贪新。很多同学一上来就装了最新的JDK 21结果Spring Boot 2.7项目跑不起来还以为是代码有问题其实纯粹是版本兼容性。Spring Boot 2.x配JDK 8是最稳的组合你先把项目跑通再考虑升级不迟。另一个容易出问题的环境变量配置是MAVEN_HOME。如果命令行里mvn -v提示找不到命令检查两件事是否配置了Maven的bin目录到Path环境变量、是否配置了MAVEN_HOME变量指向Maven的安装根目录。国内下载Maven依赖慢的话记得把Maven仓库地址换成国内镜像具体在settings.xml里配置这个操作用搜索引擎一搜就有不展开了。3.2 数据库初始化与配置文件修改MySQL装好之后第一步是创建数据库然后导入项目里附带的SQL脚本。常规做法是用命令行先登录mysql -u root -p123456登录后执行CREATE DATABASE repair_system DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE repair_system;找到源码目录下sql/文件夹里的SQL文件用source命令导入source D:/project/repair_system.sql;导入完以后可以验证一下表是否创建成功SHOW TABLES;。导入成功之后去改后端的配置文件。Spring Boot的主配置文件是src/main/resources/application.yml你需要改的就两处数据库连接信息和服务器端口。关键配置如下server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/repair_system?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver这里要特别注意serverTimezoneAsia/Shanghai这个参数不配置的话数据库连接时会报时区错误这是新手最常见的一个坑。useSSLfalse是让本地开发时不要开启SSL握手节省时间。密码改成你自己MySQL的密码。3.3 后端打包与启动后端项目的启动方式有两种一种是在IDE里直接运行启动类适合开发调试另一种是打包成jar用命令行跑适合部署和生产环境。两种方式都有必要学会。开发调试直接找到RepairApplication.java右键运行就行。如果控制台打印出Spring Boot的启动banner最后看到“Started RepairApplication”说明后端已经跑起来了默认地址是http://localhost:8080。如果中间报错百分之八十是数据库连接失败回去检查用户名密码和数据库名字。打包发布用Maven命令mvn clean package -DskipTests-DskipTests的意思是跳过测试代码执行否则Maven可能会尝试运行项目里的单元测试测试环境没配好又会导致打包失败。打包成功以后target/目录下会出现一个repair-system-0.0.1-SNAPSHOT.jar文件然后就可以用java命令启动java -jar target/repair-system-0.0.1-SNAPSHOT.jar如果线上服务器内存比较小可以用-Xms和-Xmx来限制JVM内存分配java -Xms256m -Xmx512m -jar target/repair-system-0.0.1-SNAPSHOT.jar这一步是为了防止默认的JVM最大内存占用太大把服务器拖垮。3.4 前端依赖安装、打包与Nginx发布前端部分的操作分三步安装依赖、本地跑通、打包部署。下载前端项目代码后在项目根目录下执行npm install这一步会按照package.json里的依赖清单把项目需要的所有依赖包下载到本地的node_modules文件夹。npm install第一次跑通常会比较久属于正常现象。如果中间报错大概率是网络问题可以换成国内的npm镜像源。安装成功后执行npm run serve启动成功后控制台会显示访问地址一般是http://localhost:8081。此时后端和前端都在本地还需要配置代理让前端能请求到后端。在前端项目根目录下创建vue.config.jsVite项目则是vite.config.js配置跨域代理module.exports { devServer: { port: 8081, proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } } };这里把前端的/api开头的请求代理到后端的8080端口这样开发环境就不会有跨域问题。注意后端接口路径如果原本就是/api/xxx那代理后前端就写/api/xxx如果后端路径不带/api前缀那前端写请求路径时要记得手动拼上/api去匹配代理规则。本地联调没问题之后打包成静态文件npm run build打包完成后会生成一个dist/目录里面是压缩好的HTML、CSS、JS文件。这个dist目录需要交给Nginx做静态文件服务和反向代理。Nginx配置的关键内容server { listen 80; server_name localhost; location / { root /usr/share/nginx/html/dist; index index.html; try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }try_files $uri $uri/ /index.html这一行是Vue Router使用history模式时的标配不加的话刷新子路由页面会出现404。还有一个细节要注意proxy_pass http://127.0.0.1:8080;后面没加斜杠这意味着/api/xxx会被完整转发给后端如果后端接口本身带/api前缀这个写法是对的。如果后端没有/api前缀要改成proxy_pass http://127.0.0.1:8080/;把/api从路径中消掉。Nginx配置完成后执行nginx -t检查语法然后nginx -s reload重载配置。浏览器访问http://localhost如果能看到登录页面说明整套部署流程已经通了。4. 常见问题排查与避坑指南4.1 数据库连接类报错这类问题在启动阶段出现得最频繁我把常见的几种情况直接列成速查表你照着对就行。报错信息原因解决方式Access denied for user rootlocalhost数据库用户名或密码错误检查application.yml里的账号密码Unknown database repair_system数据库不存在执行CREATE DATABASE语句创建库Public Key Retrieval is not allowedMySQL 8.0的认证插件问题连接URL加allowPublicKeyRetrievaltrueThe server time zone value is unrecognized时区未配置连接URL加serverTimezoneAsia/ShanghaiTable xxx doesnt exist表名不匹配或数据库未导入执行SHOW TABLES确认表是否存在尤其要警惕表名大小写问题。MySQL在Linux环境下默认是区分大小写的Windows下不区分。如果你的SQL脚本建的表名是repair_order代码里写的注解却是TableName(RepairOrder)在Windows上可能没问题部署到Linux服务器上就直接报表不存在。所以在写代码时表名统一小写实体类用驼峰命名并通过MyBatis-Plus的map-underscore-to-camel-case配置自动映射避免手动映射出错。4.2 跨域和接口联调问题前后端分离项目最常见的坑就是跨域。你在前端页面上F12打开控制台看到类似Access to XMLHttpRequest at http://localhost:8080/... from origin http://localhost:8081 has been blocked by CORS policy这样的报错就说明跨域配置没生效。跨域的解决办法总共有三条路。第一条已经在前面提过开发环境用vue.config.js的devServer代理。第二条是后端开启CORS配置允许指定的来源访问Configuration public class CorsConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/**) .allowedOrigins(http://localhost:8081) .allowedMethods(GET, POST, PUT, DELETE, OPTIONS) .allowCredentials(true); } }第三条是部署环境用Nginx反向代理把前后端放在同一个域名下从根源上解决跨域。这里我推荐优先用代理方案因为CORS配置如果写法不当比如allowCredentials(true)和allowedOrigins(*)不能同时使用容易自己也踩进去出不来。还有一个被忽视的联调问题是前端登录成功后每次请求怎么携带token。Axios拦截器里这样配置// 请求拦截器 axios.interceptors.request.use(config { const token localStorage.getItem(token); if (token) { config.headers.Authorization Bearer token; } return config; });如果你发现后端接口总是报401检查两个地方第一前端有没有把token存进localStorage第二请求头字段名是否跟后端读取的一致。我曾经见过有人后端取的头部叫Authorization前端发的却叫authentication这种问题光用眼睛看很难查出来用开发者工具看网络请求的Headers是最快的。4.3 部署环境下的其他疑难杂症页面刷新404原因在于前端使用的是Vue Router的history模式Nginx没有配置try_files。解决方式已经在Nginx配置里写了这一条务必好好记住。图片上传后访问不到用户提交报修单时可以上传故障照片项目里通常会把图片保存到本地的某个目录下比如/upload。如果你用Nginx做了前端静态资源托管图片上传接口和图片访问路径容易出现错位比如后端保存到D:/upload/xxx.jpg但前端访问的时候是http://localhost/upload/xxx.jpg因为Nginx默认只代理了/api路径/upload路径没代理到后端。解决方式是在Nginx再加一条location /upload/ { proxy_pass http://127.0.0.1:8080; }这里也顺便回答一个很多新手会问的问题为什么图片不能直接放在前端项目的static目录里因为前端打包以后每次重新发布都会清空dist目录你上传的图片会被一起清掉所以上传文件一定要落在独立的目录最好是后端管理的目录或者对象存储服务里。Spring Boot打包后文件上传路径问题如果你用的是System.getProperty(user.dir)这种相对路径来保存上传文件开发环境和打jar包运行后的环境可能不一致导致图片找不到。推荐用绝对路径保存并把上传目录配置到application.yml里这样每次部署的时候可以灵活修改。5. 二次开发与性能优化方向5.1 业务功能如何扩展如果你想把这套系统做得更有竞争力我建议在完成基本功能之后沿着三个方向去扩展。第一个方向是预约维修。当前系统一般是用户提交工单维修工挤时间上门。如果加上预约时间功能用户在下单时就可以选择未来三天内的某个时间段维修工在接单时看到预约时间能更好地规划自己的路线。这个功能改动量不大只要在repair_order表加一个appointment_time字段前端加一个时间选择器后端在派单时校验一下预约时间即可。第二个方向是消息通知。工单状态一变就通知相关人这个在真实场景里非常重要。实现方式可以走简单路线在状态变更的核心方法里查一下用户手机号调用短信服务商的HTTP接口发消息或者更简单一点做个站内信模块用户在系统里能看到消息列表。从学习角度出发站内信更合适因为你不用依赖外部服务商而且能顺带练习消息表的读写逻辑。第三个方向是多小区/多网点支持。现在的模型是单维修点、单管理员。真实场景里一个物业公司可能管着好几个小区每个小区配备不同的维修工。只要加一个community_id字段然后所有查询都带上这个条件系统就能从单点模型扩展成多点模型。这个扩展思路能体现出你对业务的理解深度面试时讲出来是加分项。5.2 从课程设计到生产环境的思维升级项目做完不是终点思考怎么让这个项目“看起来很专业”才是提升的关键。同样是课程设计为什么有些人的项目看起来就像企业级产品有些人的项目一看就是教学Demo差别就在几个容易被忽略的细节上。日志记录是第一个分水岭。业务系统里关键操作一定要打日志比如派单操作、状态变更操作日志里要能看出是谁在什么时间做了什么。没有日志的系统线上出问题根本不具备排查能力。Spring Boot直接用Slf4j注解在关键业务方法里用log.info记录就行。异常处理的统一是第二个分水岭。不要满Controller都是try-catch正确的做法是抛出自定义业务异常然后由全局异常处理器统一捕获转换。这样Controller看起来非常干净业务代码里只表达正常流程异常情况统一兜底。输入参数校验是第三个分水岭。前端传上来的参数不能直接信任手机号段要校验、工单状态要校验、空值要拦截。用Validated注解加NotNull、Pattern这些约束在校验失败时自动返回统一的错误提示这也是面试时能聊半天的知识点。做项目这件事我一直认为做十个小项目不如把一个项目吃透。这套维修服务系统麻雀虽小五脏俱全从前端交互到后端事务、从权限认证到部署发布整套链路走一遍你获得的不仅仅是答辩时能讲清楚一张流程图而是真正建立起对Web前后端分离项目完整生命周期的体感。建议你拿到源码后先按部署文档跑通再一行一行读代码最后自己动手把某个模块改掉重写一遍这个过程里踩到的坑、想明白的原理才是这门课程带给你最值钱的东西。