简介面向Java毕业设计与课程设计的SpringBoot家政服务管理平台基于JDK1.8、MySQL5.7、Tomcat7等技术栈覆盖前台用户操作与后台管理双端适合需要完整可运行项目作参考的计算机专业学生。压缩包约75.21MB内含项目源码、设计文档LW、答辩PPT和演示视频。系统前台提供首页、服务信息、公告信息、留言反馈、个人中心等功能后台包含用户管理、服务人员管理、服务信息管理、服务类型管理、服务预约管理、服务取消管理、服务分配管理、服务进度管理、评价信息管理、留言反馈与系统管理等模块业务链条完整并贴近实际家政服务场景。目前已有313人学习借助源码与配套文档可快速掌握SpringBoot后台管理系统的部署流程、数据表设计及接口调用思路便于毕业设计参考和二次开发。1. 家政服务平台到底在管什么一个家政公司最头疼的不是找不到阿姨而是订单派下去之后谁在服务、干到哪一步、客户满不满意全靠微信群里喊。这套 springboot 家政服务管理平台就是把「预约—派单—进度—评价」这条链路搬到线上前台用户能看服务信息、发预约、写评价后台管理员维护用户、服务人员、服务类型处理预约分配和进度流转。对准备 java 毕业设计或课程设计的人来说它既覆盖了 springboot 项目源码该有的前后台分层又把数据库一对一、一对多、多对一关系落到实际业务里是一个可以直接跑起来改的完整样本。本文按环境搭建、库表设计、前台链路、后台管理、部署排错五层拆开讲重点说清楚状态字段怎么设计、请求链路怎么走、换环境后启动失败看哪里。2. springboot JDK1.8 Maven3.3.9 环境搭建与启动验证2.1 开发环境版本对照表拿到压缩包后面临的第一个问题不是看代码而是把环境对齐。这套项目用的是 springboot 框架配合 JDK1.8mysql 5.7 做数据库Maven 3.3.9 做依赖管理最终可以打成 war 包部署到 tomcat7。环境不一致是启动报错的第一大来源先把版本锁死。组件版本说明JDK1.8低于 1.8 无法编译高于 1.8 可能遇到 javassist 等依赖的兼容警告Maven3.3.93.6 以上对某些旧插件会报 pluginPrefix 解析错误MySQL5.75.6 不支持部分索引语法8.0 的驱动和时区配置差异较大Tomcat7对应 servlet 3.0 规范springboot 内嵌容器不冲突时可忽略Navicat11用于导入 sql 脚本高版本 Navicat 也兼容IDEeclipse/myeclipse/idea推荐 idea注解处理和 lombok 支持更稳定如果机器上已经装了更高版本的 JDK不用卸载单独配置JAVA_HOME指向 1.8 的安装目录即可。Maven 的settings.xml里localRepository和mirror建议提前配好否则首次拉依赖会非常慢而且旧版本 springboot 的依赖从中央仓库拉取时经常被墙到超时。2.2 数据库初始化mysql 5.7 Navicat11常见做法是先用 Navicat 新建一个名为home_service的空数据库字符集选utf8mb4排序规则选utf8mb4_general_ci然后右键运行 SQL 文件把压缩包里的.sql脚本导入。脚本里一般同时包含建库建表和初始数据指望一条source命令完全替代图形工具操作是行不通的因为这条路径下最容易踩的坑是编码SET NAMES utf8mb4; SET FOREIGN_KEY_CHECKS 0; -- 导入后恢复外键检查 SET FOREIGN_KEY_CHECKS 1;这段脚本的作用是先把客户端连接字符集固定为 utf8mb4再临时关闭外键检查。关闭外键检查是为了让多张表之间互相引用的数据能按任意顺序导入否则先插子表后插父表时直接报Cannot add or update a child row。导入完成后重新打开外键检查避免后续正常操作时失去约束保护。2.3 多环境配置application.yml 与数据源项目默认提供的是本地开发配置路径在src/main/resources/application.yml。核心是数据源和 mybatis 的 mapper 扫描改环境只动这一处文件不要每个业务代码里写死连接信息。server: port: 8080 servlet: context-path: / spring: datasource: driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://localhost:3306/home_service?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai username: root password: root jpa: hibernate: ddl-auto: update mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.home.service.entity这里要重点看两个参数。useSSLfalse是因为 mysql 5.7 默认不开 SSL开着反而会多一次握手延迟serverTimezoneAsia/Shanghai解决 JDBC 驱动读取数据库时间时差 8 小时的问题。如果密码不是 root只改 password 就行。ddl-auto: update意味着实体类属性变化时表结构会自动增量更新但不负责删字段想改变字段名还是去数据库里手动改。2.4 启动入口与常见环境坑点二进制包拉到本地后正常启动顺序是确认JAVA_HOME指向 JDK1.8用mvn clean install -DskipTests把依赖全部拉下来再运行主类里SpringBootApplication标注的入口方法。启动过程中出现Failed to configure a DataSource说明数据库没连上出现ClassNotFoundException: com.mysql.jdbc.Driver说明 mysql 驱动没被 Maven 正确引入检查pom.xml里mysql-connector-java的 scope 是否被误写成了provided。提示JDK 版本高于 1.8 时springboot 2.0 前的内嵌 tomcat 容易报IllegalArgumentException: Unsupported class file major version这不是代码问题单纯是编译运行环境不一致。3. 核心数据表设计与 ER 关系梳理3.1 用户、服务人员、服务类型三张基础表家政平台本质上是三类对象在发生关系用户发起需求服务人员承接需求服务类型给需求分类。用户表t_user至少要包含id、username、password、realname、phone、address、create_time服务人员表t_staff除了账号基础信息之外还要有work_years、score、order_count这些字段是后续「筛选优秀人员」的数据基础。服务类型表t_service_type只有id和type_name它做的事情就是控制分类维度。三张表的关系要落在外键设计上。服务信息表t_service里同时存type_id和staff_id分别指向服务类型和服务人员。这样设计的含义是一个服务类型下可以有多个服务人员一个服务人员也可以承担多个服务类型本质是多对多关系通过中间业务表t_service拆开了。你在看这套 springboot 项目源码时凡是出现ManyToOne注解的实体对应的就是这条外键链路。3.2 服务预约与取消状态字段的设计预约表t_reservation是整套系统中最核心的一张表。它把一次完整的上门服务拆成了这样的字段user_id谁预约的、service_id预约的哪个服务、appointment_time期望上门时间、status当前处于什么阶段。status 字段是用一个 int 还是 varchar 是面试时高频追问点这里我的建议是用 int 维护状态码用常量类统一管理而不是直接在业务代码里散落魔法数字。public class OrderStatus { public static final int WAIT_ASSIGN 0; // 待分配 public static final int ASSIGNED 1; // 已分配 public static final int IN_PROGRESS 2; // 服务中 public static final int COMPLETED 3; // 已完成 public static final int CANCELLED 4; // 已取消 }为什么用常量类而不是枚举这类毕业设计项目通常页面端需要直接展示中文状态名如果用枚举前端要么遍历枚举取值要么写死 in 参数改造起来比常量类多绕一层。而且常量的比较用就能完成不会因为equals没重写踩坑。取消操作单独建表t_cancel_record记录reservation_id、cancel_reason、cancel_time这样做的好处是预约表里只需要一个状态值标记最终结果取消的完整背景都沉淀在另一张表里便于统计取消率。3.3 服务分配与服务进度运营流程的表支撑管理员在后台看到预约单之后要执行「分配」动作。分配表t_assign记录reservation_id、staff_id、assign_time、assign_admin_id。因为一次预约可能被分配给不同人员人员请假、用户改约所以把分配动作单独建表比直接给预约表加一个staff_id更合适保留历史操作记录。服务进度表t_progress用reservation_id关联预约progress_content存文字描述progress_time存更新节点时间。本项目实际代码里服务进度表是拿预约主键做的关联查询SELECT p.progress_content, p.progress_time FROM t_progress p WHERE p.reservation_id #{reservationId} ORDER BY p.progress_time DESC;这条 SQL 的逻辑是取某个预约的全部进度记录按时间倒序展示最新的进度排最上面。ORDER BY p.progress_time DESC配合页面端简单循环即可输出时间线效果不需要额外在服务层做排序运算。参数#{reservationId}由 MyBatis 自动绑定注意t_progress表中要在reservation_id字段上建索引否则系统跑到 10 万条进度记录后这条查询就是全表扫描的重灾区。3.4 评价与留言用户反馈闭环预约状态流转到「已完成」之后用户才能写评价。评价表t_comment有reservation_id、rating、content、comment_time。加一条 único 约束在reservation_id上保证一次预约只能有一条评价这个约束比在 Service 层里先查再插要可靠得多。留言反馈表和评价表的区别是它不依赖预约用户在首页任何位置都可以发起所以t_feedback的表结构只要user_id、content、reply_content、feedback_time。提示评价表不直接外键关联服务人员而是通过预约表二次跳到t_assign找到对应人员。这样设计的原因是一旦分配关系被重新指派评价始终落在最终完成服务的那个人员身上不会漂移。4. 前台用户端从注册到预约完成的完整请求链路4.1 注册登录与 session 会话前台操作部分按 springboot 项目最常见的做法用户登录成功后在 session 中放入用户 id 和用户名没有引入 jwt 那套复杂机制。注册时前端把表单数据 POST 到/user/register后端先按 username 查重再插入用户记录。因为 password 在数据库里是明文存的为了能在简历上说清安全性改进点二次开发时可以加一层 BCrypt 加密这里给出改造后最核心的两个方法签名public interface UserService { // 注册时对明文密码做 BCrypt 散列 boolean register(UserRegisterDTO dto); // 登录时用 matches 校验原始密码与散列值 LoginVO login(UserLoginDTO dto); }说明一下 RegisterDTO 和 LoginVO 的分层意义DTO 是接收前端参数的载体不直接透传实体类避免了password字段在 JSON 序列化过程里被带出去LoginVO 是返回给前端的视图对象里面的字段比实体类少几个安全敏感属性。BCrypt 散列相比 MD5 的关键点是自动加盐相同密码两次散列结果不同即使数据库泄露也无法用彩虹表反推原始密码。4.2 服务信息查询与预约提交首页服务信息列表是按服务类型 Tab 切换的前端通过typeId参数请求/service/list后端根据类型过滤服务数据。预约入口在服务详情页用户点「立即预约」弹出日期和地址选择框最终调用的接口是/reservation/add。该接口内部要完成的事务包括创建预约记录、把预约状态置为待分配、给管理员生成一条待办提示。PostMapping(/reservation/add) public ResultVOString addReservation(RequestBody ReservationAddDTO dto, HttpSession session) { // 从 session 中获取当前登录用户 Integer userId (Integer) session.getAttribute(userId); if (userId null) { return ResultVO.fail(请先登录后再预约); } // 幂等校验同一用户同一服务在待分配状态下不能重复提交 boolean exists reservationService.existsPendingOrder(userId, dto.getServiceId()); if (exists) { return ResultVO.fail(您有未完成的服务订单请先完成后再预约); } // 生成预约单并初始化状态 Reservation r new Reservation(); r.setUserId(userId); r.setServiceId(dto.getServiceId()); r.setAppointmentTime(dto.getAppointmentTime()); r.setAddress(dto.getAddress()); r.setStatus(OrderStatus.WAIT_ASSIGN); reservationService.saveOrder(r); return ResultVO.success(预约成功等待管理员分配服务人员); }这段代码的核心是幂等校验它的业务背景是用户连点两次提交按钮时不能生成两条预约单。existsPendingOrder底层 SQL 是查user_id、service_id且 status 为待分配或已分配的行数只要 count 大于 0 就拒绝。HttpSession在这里承担认证职责所有前台用户相关接口都从 session 取 userId而不是信任前端传参防止用户篡改请求体伪造他人身份。4.3 取消预约的边界条件用户在「我的预约」页面可以看到自己的全部预约单只有状态为「待分配」时允许取消一旦管理员已分配人员再接取消就会导致人员白跑一趟。取消操作的后端逻辑是写入取消记录、把预约状态改为已取消并返回取消结果PostMapping(/reservation/cancel) public ResultVOString cancelReservation(RequestParam Integer reservationId, HttpSession session) { Integer userId (Integer) session.getAttribute(userId); Reservation order reservationService.getById(reservationId); if (order null || !order.getUserId().equals(userId)) { return ResultVO.fail(预约不存在或无权操作); } if (!order.getStatus().equals(OrderStatus.WAIT_ASSIGN)) { return ResultVO.fail(服务已分配无法取消请联系管理员); } reservationService.cancelOrder(order); return ResultVO.success(取消成功); }这里的双条件判断要重点看先判断订单归属是不是当前用户再判断状态是否允许取消。归属判断防止 A 用户通过修改接口参数取消 B 用户的预约状态判断保证流程边界不被绕过。取消动作内部开启一个事务先向t_cancel_record插入取消原因再更新预约状态两个操作要么都成功要么都失败避免出现「取消记录存在但订单还是待分配」的脏数据。4.4 评价提交与完成闭环服务状态变为已完成时前台「我的预约」列表里才会出现「去评价」按钮。评价功能带校验逻辑未完成的预约不能评价已评价过的预约不能再评。这就是之前在表设计里提到的reservation_id唯一约束发挥作用的地方数据库层面的唯一约束是最后一层防线。5. 后台管理端服务分配、进度管理与权限控制5.1 管理员登录与菜单权限后台入口独立于前台页面登录后写入管理员身份标识。系统管理模块里包含公告信息管理管理员可以发布首页公告控制前台公告栏展示的开关。用户管理模块支持管理员对前台注册用户进行冻结、重置密码操作这部分直接调用UserService的对应方法没有单独拆微服务。5.2 服务预约列表与分配逻辑后台预约管理页默认按状态 Tab 分组显示待分配、已分配、进行中、已完成、已取消。管理员的日常工作集中在「待分配」Tab点击分配按钮后弹出服务人员选择框选择后提交分配信息。分配动作的事务包含两步往t_assign插入分配记录、更新t_reservation的 status 为已分配。Transactional(rollbackFor Exception.class) public void assignOrder(AssignRequest request) { // 第一步写入分配记录 StaffAssign assign new StaffAssign(); assign.setReservationId(request.getReservationId()); assign.setStaffId(request.getStaffId()); assign.setAssignAdminId(request.getAdminId()); assign.setAssignTime(new Date()); staffAssignMapper.insert(assign); // 第二步更新预约状态 Reservation order reservationMapper.selectById(request.getReservationId()); if (!OrderStatus.WAIT_ASSIGN.equals(order.getStatus())) { throw new BusinessException(该预约已被分配); } order.setStatus(OrderStatus.ASSIGNED); reservationMapper.updateById(order); // 第三步写入初始进度 Progress p new Progress(); p.setReservationId(request.getReservationId()); p.setProgressContent(已完成服务人员分配); p.setProgressTime(new Date()); progressMapper.insert(p); }Transactional的rollbackFor Exception.class意思是任何异常都触发回滚不加这个参数时默认只回滚RuntimeException而这里捕获的业务异常BusinessException是受检异常必须显式声明才回滚。三步操作放在一个事务里保证不会出现「分配记录有了但预约还停在待分配」的不一致状态。分配前再查一次状态是因为管理员在多个 Tab 页面之间切换时该订单可能已被另一位管理员抢先分配并发场景下的乐观保护不能省。5.3 进度管理如何驱动状态流转进度管理模块的思路是每录入一条进度预约状态就向终态推进。录入「已到达用户家」时状态从已分配改为进行中录入「服务完成」时状态从进行中改为已完成。该模块用状态码做递增限制而不是完全靠管理员手选状态这样减少误操作public void addProgress(ProgressAddDTO dto) { Reservation order reservationMapper.selectById(dto.getReservationId()); if (order.getStatus() OrderStatus.COMPLETED) { throw new BusinessException(订单已完成不能再补充进度); } if (order.getStatus() OrderStatus.CANCELLED) { throw new BusinessException(订单已取消无法补充进度); } // 只有待分配状态不允许录进度必须等分配完成 if (order.getStatus() OrderStatus.WAIT_ASSIGN) { throw new BusinessException(请先分配服务人员再补充进度); } Progress p new Progress(); p.setReservationId(dto.getReservationId()); p.setProgressContent(dto.getProgressContent()); p.setProgressTime(new Date()); progressMapper.insert(p); // 根据录入的进度内容推进状态 if (开始服务.equals(dto.getProgressContent())) { order.setStatus(OrderStatus.IN_PROGRESS); } else if (服务完成.equals(dto.getProgressContent())) { order.setStatus(OrderStatus.COMPLETED); } reservationMapper.updateById(order); }使用中文字面量判断状态推进虽然直观但不够优雅二次开发时建议给ProgressContent增加一个progress_type字段用0-初始进度, 1-开始服务, 2-服务完成的数字类型替代字符串比较性能和可维护性都更好。这里保留字面量的原因是这套源码里没有该字段新增字段要同时改数据库表和 MyBatis 的 resultMap工程量相对大一些。5.4 服务人员与评价信息管理服务人员管理支持管理员新增、编辑、停用人员账号停用后分配选择框里自动过滤掉。评价信息管理页面展示所有已完成预约的评价内容这个页面最大的用途是运营复盘管理员可以统计某个人员收到的差评数反向驱动人员考核。留言反馈处理模块比较简单管理员在反馈列表里针对一条留言填写回复内容回复后前台用户能在个人中心看到回复结果。6. 部署到 Tomcat7 与二次开发避坑指南6.1 war 包打包与外部 Tomcat 部署springboot 项目默认打包成可执行 jar但这套资源要求部署到 tomcat7所以要把打包方式改成 war。改动位置在pom.xmlpackagingwar/packaging再把启动类改成继承SpringBootServletInitializerSpringBootApplication public class HomeServiceApplication extends SpringBootServletInitializer { Override protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) { return builder.sources(HomeServiceApplication.class); } public static void main(String[] args) { SpringApplication.run(HomeServiceApplication.class, args); } }继承SpringBootServletInitializer的作用是让外部 tomcat 启动时知道从哪里装配 spring 容器。如果只改 packaging 不改启动类war 包丢进 tomcat 的webapps目录后启动不报错但访问路径永远是 404。打包用mvn clean package生成的 war 包放在target目录下复制到 tomcat7 的webapps下重启即可访问。6.2 数据库导入与启动常见故障Navicat 导入 SQL 脚本时最容易遇到Unknown collation: utf8mb4_0900_ai_ci这是因为脚本来自 mysql 8.0 导出而本地是 5.7。解决办法是全局替换utf8mb4_0900_ai_ci为utf8mb4_general_ci再替换utf8mb4_unicode_ci同值。另一个高发错误是端口占用tomcat7 默认 8080 被其他服务占用时修改conf/server.xml里的port属性同时检查application.yml里的端口是否保持一致前端所有axios请求都走相对路径时则不需要同步修改。6.3 给项目添加新的服务类型的完整路径二次开发最常见的需求是新增一种服务类型改起来涉及五处在t_service_type插入一条记录在ServiceTypeMapper.xml里确认查询语句没有硬编码 type 数量在后台「服务类型管理」页添加新增按钮对应的方法新增类型通常不需要改表结构在服务信息管理里给新类型创建一条服务信息并绑定服务人员回到前台首页验证新类型的 Tab 是否展示且可跳转。如果把新增类型的流程走一遍仍然不显示优先怀疑前端菜单是静态写死的不是动态渲染的去index.html的 Tab 区域追加一个li标签即可。6.4 面向简历升级的 Redis 缓存与异步改造这套 springboot 家政服务管理平台的主流程完整但性能纵深不够。面试被问「如何优化」时可以给出两个升级方向第一首页服务信息和公告信息是高频读低频繁写的数据引入 Redis 缓存 key 为service:list:type_{id}缓存时间 10 分钟管理员修改服务信息后主动删除 key第二预约成功后的通知动作——给管理员发待办消息、给用户发确认短信——用Async异步执行避免同步阻塞预约提交接口。这两处改造不改变原有表结构只在 Service 层增加逻辑和本项目源码唯一需要衔接的点是确认 springboot 版本里是否内置了spring-boot-starter-data-redis没有就手动添加依赖并配置 Redis 连接地址。本文还有配套的精品资源点击获取