☰
家政服务管理系统全栈实战:Spring Boot + MyBatis-Plus从设计到部署
2026/10/19 6:09:56 网站建设 项目流程

刚拿到这个题目的时候,我第一反应是:家政服务管理系统,这不就是典型的CRUD堆功能吗?但真正动工之后才发现,里面藏着的业务状态流转、角色权限划分、订单闭环管理,每一个环节都能单独拎出来写一篇排坑笔记。尤其是毕设场景下,既要控制工作量,又要在答辩时讲清楚“设计思路”和“实现难点”,这个度并不好拿捏。

这篇博文我会按自己实际做这个项目时的完整路径来拆:从需求分析、技术选型、数据库设计,到核心模块实现、部署调试,最后附上我踩过的坑和排错思路。项目代号就沿用毕设课题编号,叫“家政服务管理系统05300”,所有代码思路和表结构我都整理成了可复现的方案,适合正在做Java Web毕设、或者想快速搭一个完整SSM/Spring Boot全栈项目练手的同学参考。

1. 项目概述与需求拆解

1.1 家政行业的真实痛点与系统定位

家政服务这个行业,线上化程度其实一直不高。很多线下家政公司接单靠电话、派单靠微信群聊、结算靠手工记账,客户投诉和售后跟踪更是基本靠记忆。这类系统要解决的,本质上不是“把订单录进电脑”,而是把整个服务链路——从用户浏览服务项目、预约下单,到管理员派单、家政人员接单执行,再到用户确认完成、评价打分——完整地数字化。

所以家政服务管理系统05300定位很明确:一个面向三类角色(用户、家政服务人员、管理员)的Web管理平台。核心不是页面多好看,而是业务流程能不能闭环。我当时跟导师确认课题时,重点强调了三件事:

  • 订单状态必须可追踪,不能只有“下单”和“完成”两个状态;
  • 不同角色看到的数据和操作权限要严格隔离;
  • 评价体系要能反哺服务项目管理,比如评分低的项目要能在后台直观看到。

1.2 三类角色与权限边界设计

我在设计角色权限时没有引入Spring Security那套重量级框架,而是用拦截器 + 注解的方式做了轻量级权限控制。原因后面技术选型部分会细说。先看角色边界:

  • 用户端(前台):注册登录、浏览服务项目、按分类筛选、在线下单预约、查看自己的订单列表和状态、服务完成后提交评价、个人资料管理。
  • 家政人员端(前台):登录后可查看被指派给自己的订单、更新订单状态(接单、开始服务、完成服务)、查看自己的历史服务和评价详情。
  • 管理员端(后台):服务项目CRUD、分类管理、家政人员信息审核与上下架、订单全流程管理(派单、取消、查看详情)、用户管理、评价管理与数据统计。

权限隔离的设计原则只有一句:谁的数据谁操作,跨角色操作必须走管理后台。比如用户绝对不能修改订单的服务人员字段,家政人员绝对不能把跟自己无关的订单标记为完成。这些规则不是靠前端隐藏按钮实现的,而是在后端每个接口里都做了归属校验。

2. 技术选型与架构设计思路

2.1 为什么选 Spring Boot + MyBatis-Plus + Vue 这套组合

毕设技术栈的选择要同时考虑三件事:工作量可控、答辩有讲头、知识点覆盖全面。我最终定的方案是:

  • 后端:Spring Boot 2.7.x + MyBatis-Plus + MySQL 8.0 + Maven
  • 前端:Vue 2 + Element UI + Axios(前台和后台共用一套,通过路由和权限区分)
  • 部署:后端打包成jar包运行,前端build后由Nginx托管或直接放在Spring Boot的static目录下

这里重点解释一下MyBatis-Plus的选择。很多人纠结用MyBatis还是JPA,我的建议是毕设直接用MyBatis-Plus。它的好处是单表CRUD完全不用写SQL,自带分页插件、逻辑删除、自动填充这些高频功能,省下来的时间可以投入到订单状态流转和权限校验这些真正有含金量的业务逻辑上。而且MyBatis-Plus的语法和原生MyBatis高度一致,答辩时被问到底层实现,往“通用Mapper动态拼接SQL”这个方向讲完全站得住。

前端没有上Vue 3 + Vite,是因为Element UI对Vue 2的生态最成熟,网上的踩坑资料也最多,对毕设来说稳定性和可查性比技术新颖更重要。而且很多学校的项目验收环境网络受限,Vue 2 + npm install的兼容性问题最少。

2.2 前后端分离还是服务端渲染:针对毕设规模的取舍

这个项目我一开始纠结过要不要做前后端完全分离。后来权衡了一下,决定采用半分离方案:前端项目独立开发调试,但最终打包后的静态文件直接复制到后端resources/static目录下,和后端jar包一起发布。这样做的好处有三个:

  • 开发时前端用Vue CLI的devServer跑在8080端口,后端跑在8081端口,通过proxy代理解决跨域,开发体验和完全分离没区别;
  • 交付时只有一个jar包,不需要单独部署Nginx,在答辩现场用一台电脑就能完整演示,避免环境问题翻车;
  • 部署复杂度降低后,可以把精力集中在业务流程的完整性和代码质量上。

真正的跨域问题只在开发阶段存在,我通过前后端约定的接口路径前缀/api配合Vue的devServer代理解决,具体配置后面有实例。

2.3 核心依赖清单与版本搭配

给出一份可以直接用的版本组合,这些版本我都实测过,互相之间没有坑:

Spring Boot 2.7.18 MyBatis-Plus 3.5.3.1 MySQL Connector 8.0.33 Lombok 1.18.30 Hutool 5.8.25(工具类,用于生成订单号、Bean拷贝等) JWT 0.9.1(用于登录令牌)

注意:JDK版本建议统一用1.8。Spring Boot 2.7.x对JDK 8的支持非常完整,不要为了赶时髦上JDK 17,否则MyBatis-Plus和JWT的老版本可能有兼容性告警。

3. 数据库设计与核心表结构

3.1 从业务流程推导数据表

数据库设计是这个项目的灵魂。我画完角色权限图之后,梳理出的核心业务链路是:用户下单 → 管理员派单 → 家政人员接单 → 执行服务 → 用户确认并评价。围绕这条链路,数据表最终定了7张核心表:

  • user用户表(前台用户)
  • worker家政人员表(含个人信息、服务项目关联、状态)
  • service_category服务分类表(如保洁、月嫂、家电清洗)
  • service_item服务项目表(挂在分类下面,含价格、单位、封面图)
  • order_info订单主表(核心中的核心)
  • evaluation评价表(用户对一次服务的打分和评论)
  • admin_user管理员表(后台登录)

这里重点说下单这个动作。用户下单时选中的服务项目价格需要“快照”到订单表里,而不是下单后每次都去关联查询service_item表的最新价格。为什么?因为管理员可能在用户下单后调价,如果不做快照,用户查历史订单时会发现价格变了,这就会产生纠纷。我在order_info表里冗余了service_name和service_price两个字段,简单粗暴但非常实用。

3.2 订单状态机设计与状态流转SQL

订单表是整个系统里字段最多、逻辑最复杂的表。我的order_info表核心字段如下:

id, order_no, user_id, worker_id, service_item_id, service_name, service_price, service_time(预约时间), address(服务地址), remark, status, create_time, update_time

status字段我定义了五个状态,用整数存储,含义如下:

  • 0:待接单(用户已下单,管理员还没派单)
  • 1:待服务(已派单,家政人员还没接单)
  • 2:服务中(家政人员已接单,正在执行)
  • 3:已完成(家政人员标记完成,用户还没评价)
  • 4:已取消(用户取消或管理员取消)

这个状态机的设计核心是:每一步状态流转都只允许从前一个状态过来。我写了一个私有的状态校验方法,每次更新前都先检查当前状态是否是目标状态的前驱,防止脏数据出现。例如用户取消订单只允许在状态0(待接单)时操作,一旦派单后就不能随便取消了。

状态流转的SQL用MyBatis-Plus的UpdateWrapper来做,核心是带上状态条件防止并发覆盖:

boolean updateOrderStatus(Long orderId, Integer fromStatus, Integer toStatus, Long workerId) { LambdaUpdateWrapper<OrderInfo> wrapper = new LambdaUpdateWrapper<>(); wrapper.eq(OrderInfo::getId, orderId) .eq(OrderInfo::getStatus, fromStatus) // 关键:带上原始状态 .set(OrderInfo::getStatus, toStatus) .set(workerId != null, OrderInfo::getWorkerId, workerId); return orderInfoMapper.update(null, wrapper) > 0; }

3.3 关键索引与字段陷阱

数据库设计里最容易忽视的是索引。订单表的user_id、worker_id、status这三个字段是查询频率最高的,一定要建联合索引或单列索引。我建了两个索引:idx_user_status (user_id, status)和idx_worker_status (worker_id, status),实测在大批量数据下分页查询速度有明显提升。

另一个坑是时间字段。预约的service_time我用了datetime类型,Java实体类用LocalDateTime。这里千万不要用java.util.Date,否则JSON序列化返回给前端时格式会非常难处理。配合MySQL连接串里的serverTimezone=Asia/Shanghai,时间问题基本能一次过。

4. 核心功能模块的实现细节

4.1 登录鉴权:JWT还是Session?我选了JWT但做了精简

毕设登录鉴权用JWT还是Session,网上吵得很厉害。我的选择是JWT,但只用了最简单的场景:登录成功后生成token返回前端,前端每次请求在请求头里带Authorization: Bearer token,后端写一个拦截器统一解析token并存入ThreadLocal,业务代码里直接通过UserContext.getUserId()获取当前登录用户。

JWT的好处是后端无状态,答辩时讲起来有亮点。但要注意,纯JWT有一个缺陷:无法主动让token失效。对这个项目影响不大,因为毕设场景不涉及严格的退出登录控制。我在用户表里加了一个status字段,如果用户被管理员禁用,即使token有效,拦截器里也会校验用户状态,从根上避免问题。

拦截器的核心逻辑:

public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { String token = request.getHeader("Authorization"); if (StringUtils.isBlank(token) || !token.startsWith("Bearer ")) { throw new BusinessException(401, "未登录"); } // 解析JWT,拿到userId Integer userId = JwtUtil.parseToken(token.replace("Bearer ", "")); // 存入ThreadLocal UserContext.setUserId(userId); return true; }

4.2 预约下单与派单逻辑:订单号如何生成、派单策略怎么写

下单接口的流程比想象中要多几个环节。用户提交订单时,后端要做的事包括:校验用户登录状态、校验服务项目是否存在且上架、生成唯一订单号、写入订单表。

订单号生成我踩过坑。一开始用UUID.replace("-", ""),生成出来长度32位,用户根本看不下去。后来改用Hutool的IdUtil.getSnowflakeNextIdStr(),生成18位纯数字ID,再拼接业务前缀形成订单号:JT + yyyyMMddHHmmss + 6位随机数,例如JT20250415103045123456。这个格式有两个好处:一是包含时间信息,排错时能直接看出下单时间;二是纯数字+日期格式,在页面上展示非常整齐,答辩演示时观感很好。

派单逻辑我做了简化处理。管理员在后台看到一个“待接单”的订单,手动选择家政人员完成派单。也做了一个简单的自动派单:管理员可以按服务项目分类,勾选一位当前状态为空闲的家政人员,直接指派。家政人员端会立即看到一条待服务的订单,点击“接单”后状态变更为服务中。

4.3 评价模块与评分计算的隐蔽坑

用户确认订单完成之后,系统会引导用户评价。评价表结构设计得比较简单:

id, order_id, user_id, worker_id, service_item_id, score, content, create_time

这里的陷阱在于:评价应该只允许针对“已完成”的订单,而且一个订单只能评价一次。我用唯一索引uk_order_id (order_id)从数据库层面保证了一单一评,而不是纯粹依赖业务代码判断。这是很多同学容易漏掉的地方——业务代码里就算忘了判重,数据库唯一索引也能兜底。

评分计算上,我在服务项目的列表页展示平均分。这个平均值不能每次查询都实时聚合计算整张评价表,否则数据量一大接口就慢。我的做法是:每次新增评价后,用一条SQL更新service_item表里的avg_score和evaluation_count字段:

UPDATE service_item SET avg_score = (avg_score * evaluation_count + #{score}) / (evaluation_count + 1), evaluation_count = evaluation_count + 1 WHERE id = #{serviceItemId}

用这种“增量更新”而不是“全表聚合”的方式,接口响应速度稳定在几十毫秒以内。

5. 系统部署与演示环境搭建

5.1 本机打包运行的完整步骤

到了项目后期,部署环节其实是很多人的噩梦。我把整套打包部署步骤整理成了一条命令链,确保在答辩现场能从头演示一遍:

# 1. 前端构建 cd frontend npm install npm run build # 2. 把构建产物复制到后端静态目录 cp -r dist/* ../src/main/resources/static/ # 3. 后端打包 cd .. mvn clean package -DskipTests # 4. 运行 java -jar target/home-service-0.0.1-SNAPSHOT.jar

启动后访问http://localhost:8080,前端页面和后端接口在同一个端口下,没有跨域问题,也没有多进程管理的烦恼。

5.2 初始化数据脚本的准备

创业项目初始化数据很重要,特别是演示的时候没有数据可看就尴尬了。我在resources目录下放了一个data.sql,配合Spring Boot的spring.sql.init.mode=always配置,第一次启动时自动执行,写入基础数据。

初始化数据里要注意两点:一是密码不能明文存储,我在SQL脚本里直接写的是BCryptPasswordEncoder生成好的密文,密码统一是123456,答辩时演示登录非常方便;二是服务项目的封面图要提前准备好上传目录,演示时图片404太影响效果。我在application.yml里配置了静态资源映射,把本地的/upload/**路径映射到项目外的物理目录,这样图片上传后重启服务也不会丢失。

5.3 部署时最常翻车的三个坑

第一坑是端口被占用。Spring Boot默认8080端口很容易被其他程序占用,我的建议是直接在application.yml里改成一个不常用的端口,比如server.port=8090,现场演示时不用跟任何程序抢端口。

第二坑是MySQL字符集。建库的时候一定要用CREATE DATABASE home_service DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;,否则用户地址里写入“深圳市南山区xx路”这类中文完全没问题,但一旦遇到生僻字或者表情符号就报错。

第三坑是前端路由刷新404。因为Vue Router用的是history模式,后端没配置对应的转发时,直接刷新/detail?id=xx页面会404。我后端加了一个简单的Controller转发,所有非/api开头的路径都转发到index.html,一次性解决。

6. 常见问题排查实录与避坑清单

6.1 开发阶段跨域问题排查

跨域问题是前后端分离开发时最常遇到的。我在application.yml里配置了一个全局CORS过滤器,允许本地的http://localhost:8080跨域访问,同时在Vue的vue.config.js里配置了devServer代理:

devServer: { proxy: { '/api': { target: 'http://localhost:8090', changeOrigin: true } } }

这里有个小细节:前端请求的URL是/api/login,代理会把请求转发到http://localhost:8090/api/login,所以后端Controller接口的映射路径要带/api前缀,或者在后端统一配置server.servlet.context-path=/api。我选择了后者,因为拦截器只对/api下的接口生效,静态资源路径不受影响。

6.2 MyBatis-Plus分页查询失效的坑

MyBatis-Plus的分页插件如果没注册,分页查询会返回全量数据,这属于必踩的坑。我当时忘了配置MybatisPlusInterceptor,查订单列表接口一直返回所有记录,还以为是SQL写错了。正确配置如下:

@Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; }

另外,使用分页时返回对象应该接收Page<T>类型,而不是直接返回List<T>。我封装了一个统一返回体Result,把Page对象里的records、total、current、size一并返回前端,前端分页组件直接对接这四个字段就完事了。

6.3 时间字段显示8小时偏差的排查思路

项目里的“服务时间”字段在页面上显示永远比数据库里少8个小时,这是典型的时区问题。排查路径很长,我先检查了MySQL的连接串,确认加了serverTimezone=Asia/Shanghai;又检查了Jackson的序列化配置,加上了spring.jackson.date-format和time-zone。最终的根因其实很简单:前端浏览器和本地MySQL处于同一时区,但后端启动时的JVM默认时区可能不对,尤其是部署到云服务器时。我统一在启动类加了时区设置:

@PostConstruct void setDefaultTimeZone() { TimeZone.setDefault(TimeZone.getTimeZone("GMT+8")); }

这个写法叫“系统级兜底”,加了之后无论部署在哪台服务器上,时间显示都不会再出差错。排查时间偏差问题时建议按这个顺序来:数据入库时先查数据库直接SELECT看值,再查后端接口返回的JSON,最后查前端展示。用二分法定位到是“入库错”还是“出库错”,效率最高。

6.4 文件上传大小限制导致图片上传失败

服务项目图片上传时,Element UI的Upload组件报错“请求被中止”。排查后发现Spring Boot默认的单文件上传上限是1MB。我改了一下配置:

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

同时在Controller里对文件名做了重命名处理,统一用UUID + 原文件后缀,彻底避免中文文件名在部分浏览器上的编码问题。上传后的图片路径存入数据库,返回给前端展示时用/upload/文件名拼接完整URL。

7. 优化扩展与后续进阶思路

7.1 从毕设到真实项目的三个关键差距

做完这套系统后,我对比了一下市面上商业家政平台的实现,发现毕设和真实项目之间还有三个核心差距。第一个是消息通知机制:真实项目中,用户下单后家政人员接单、服务完成,每一步都要推送短信或站内信提醒,而我的系统只做了页面轮询刷新;第二个是支付环节:毕设里有订单金额字段,但没有对接支付接口,用户可以“下单不付款”;第三个是地理位置服务:真实家政平台会基于LBS推荐附近的家政人员,并在地图上实时展示服务人员位置。

如果想把项目往深度方向扩展,我建议优先做支付对接。支付可以接一个沙箱环境,下单后跳到支付页面,支付成功才更新订单状态。这个模块一旦实现,整个订单闭环就真正完整了,答辩时可以从“业务完整性”的角度好好讲一讲。其次是消息通知,用WebSocket给家政人员端推送“新订单派单提醒”,用Spring的@Async异步发短信,技术点覆盖得也很全面。

7.2 基于当前系统的代码层面重构建议

从代码质量角度看,我建议如果你有精力,可以做三件事:第一,把Controller层里的参数校验从手写if-else改成@Validated注解,用@NotBlank、@NotNull配合全局异常处理器统一返回错误信息;第二,把订单状态机的流转逻辑抽成一个独立的OrderStateMachine类,用策略模式替代冗长的if-else嵌套;第三,引入Redis缓存服务分类和服务项目列表,减少数据库的实时查询压力,这个点比较加印象分。

这些重构不一定全做,但至少把第一件和第二件做了。因为它能直接体现你对代码规范的理解,而不仅仅是“能跑就行”。答辩时导师通常会翻代码,这两处改动会让代码结构看起来专业很多。

7.3 数据可视化与Excel导出的小加分项

如果你想把演示效果做得更生动,可以在后台加一个简单的数据统计页面,用ECharts展示近7天订单量折线图和分类销售占比饼图。实现方式很简单:写一个统计SQL,按日期分组查订单数量,再把结果封装成图表需要的格式返回前端。

另一个实用的功能是Excel导出。用Hutool的ExcelWriter工具类,几十行代码就能把订单列表导出成.xlsx文件。导出的内容可以包含订单号、用户姓名、服务项目、服务时间、金额、状态、评价分数字段。这两个功能实现成本低,但演示时的效果非常直观,也能证明你不只会写增删改查。

最后说几句实际的

做这个项目的过程中,我踩过的坑和绕过的弯都写在上面了。你要是正在做类似的毕设,我最想提醒的一句话是:先把业务流程和数据表设计想清楚再动手写代码,千万别一上来就对着页面敲CRUD。我见过不少同学把系统做完才发现订单状态缺一个流转分支,或者评价表和订单表对不上,回头改表结构和代码,工作量直接翻倍。

还有一个建议是,务必保留一版最简可运行的代码。答辩前一天,哪怕所有功能都写完了,也要确保当前分支能在没有任何多余依赖的情况下启动起来。现场如果出问题,这一版能救回来。项目做完后,我还整理了一份完整的SQL脚本,建库建表、初始化数据、演示账号一条龙准备好,到时候直接往MySQL里一导入就能跑。

家政服务管理系统05300这套代码,整体难度适中,业务完整度足够撑起一篇不错的毕设论文。按照上面的设计方案和坑点去写,你有很大概率能一遍跑通。做系统的过程中遇到具体报错解决不了的,可以顺着我写的几个排查思路往底层去找,基本都能定位到问题所在。这套思路不仅适用这个项目,你以后做别的Java Web项目也能用得上。

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

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

立即咨询