简介:一套基于Spring Boot的家政服务管理平台毕业设计项目源码包,面向Java方向毕业生和课程设计学生,也适合正在学习Spring Boot的开发者参考。系统同时包含前台操作与后台管理两条业务线:用户端覆盖首页、服务信息、公告信息、留言反馈、个人中心等常见功能;管理端则提供用户、服务人员、服务类型、服务信息、预约、取消、分配、进度、评价、留言反馈、系统管理等一系列完整模块,流程连贯且贴近真实项目,可直接作为毕设或课设的代码基础与讲解框架。压缩包约75.21MB,内含Spring Boot项目源码、LW设计文档、PPT演示文件和配套演示视频,项目已配置好可正常启动的运行环境,开发语言为Java,采用JDK1.8、Tomcat7、MySQL5.7和Maven3.3.9,并支持在Eclipse、MyEclipse、IDEA等常见IDE中打开运行。目前已有313人学习浏览,利用这份资源可以快速梳理项目结构、理解权限管理和业务流转,还能直接修改复用,节省搭建时间,提升毕设完成质量。
1. 家政服务管理平台为什么值得用 Spring Boot 做毕业设计
家政服务管理的核心难点不在页面多,而在角色杂、状态乱:雇主下单、平台派单、服务人员上门、双方评价,中间还有取消、改期、投诉。用 Spring Boot 做这类平台,等于把一套完整的 Web 后端工程能力压缩到一个可演示的系统里。对准备 springboot 毕设的人来说,家政服务管理平台能覆盖用户管理、服务分类、订单状态流转、评价结算和权限控制这些高频出题点,而且找源码参考时容易对照数据库字段验证。下面按“建模→编码→排错→演示”的顺序,讲清楚一个能本地跑通的 springboot 家政服务管理平台是怎么搭起来的。
2. Spring Boot 家政服务管理平台的技术栈选型与数据模型
2.1 技术选型:Spring Boot 2.7 + MyBatis Plus 3.5 的搭配理由
毕业设计场景里,技术选型的第一原则是“能讲清楚原理,且遇到问题查得到资料”。Spring Boot 2.7.x 是目前毕设和存量项目里使用最广的版本线,相比 3.x,它不需要迁移 jakarta 命名空间,跟 MyBatis Plus、Shiro 这类老牌组件的兼容性文档也齐全。搭配 MyBatis Plus 3.5.x 的理由更实际:单表 CRUD 不用手写 SQL,分页、逻辑删除、自动填充在答辩时可以直接归为“框架能力”,不用贴一屏代码去解释。
项目里常见的依赖组合如下:
<dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.2</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.30</version> </dependency>Spring Boot 的自动配置会在 classpath 里发现这两个 starter 后,自动装配数据源和 SqlSessionFactory。需要改的是 application.yml 里的 datasource 与 mybatis-plus 两段配置:
spring: datasource: url: jdbc:mysql://localhost:3306/housekeeping_db?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver mybatis-plus: global-config: db-config: logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0 configuration: map-underscore-to-camel-case: true这里 serverTimezone 必须显式指定,否则 JDBC 驱动 8.x 会拿系统默认时区去拼接,Windows 中文环境下经常报时区异常。map-underscore-to-camel-case开启后,数据库的 create_time 字段可以直接映射到实体的 createTime 属性,省去大量 ResultMap 配置。
逻辑删除的配置值得单独提一句:家政平台的服务项、服务人员都可能做下线而非物理删除,配置了全局逻辑删除后,MyBatis Plus 内置的 selectById、updateById 会自动过滤 deleted=1 的数据,不需要每个 Mapper 里重复写 WHERE 条件。这在答辩时是一个可以主动讲出来的设计点。
2.2 核心数据表设计:把业务对象转成表结构
家政服务管理平台的数据实体要覆盖三端:管理员的系统配置、雇主的找服务流程、服务人员的履约流程。最少需要这几张表:
| 表名 | 关键字段 | 说明 |
|---|---|---|
| sys_user | id, username, password, role, nickname | role 区分 admin / customer / worker |
| service_category | id, name, sort | 保洁、月嫂、保姆、维修等一级分类 |
| service_item | id, category_id, name, price, unit, tags | 具体服务项,tags 存技能标签 |
| customer_order | id, order_no, customer_id, worker_id, item_id, status, appoint_time, address, remark | 订单主表,status 是状态机字段 |
| order_comment | id, order_id, customer_id, worker_id, score, content | 双边评价表 |
| worker_profile | id, user_id, years_of_exp, score, audit_status | 服务人员档案,关联 sys_user |
实际建表时要注意,order 是 MySQL 保留字,所以表名用 customer_order 更稳妥。order_no 建议用业务编号而不是自增 id,格式类似JK20250501001,对外展示用业务单号,内部关联用主键,这在答辩时可以说明是“对外隔离主键的一种常规做法”。
service_item 的 tags 字段需要做一个取舍。字符串存逗号分隔(例如“油烟机清洗,深度保洁,高温消毒”),查询时用 MyBatis Plus 的 like 匹配,几千条数据量下完全没问题;如果要做严格的技能匹配,再拆一张 worker_skill 关联表。毕设场景推荐前者,代码量少且演示效果好。
一个核心的建表语句如下:
CREATE TABLE customer_order ( id BIGINT PRIMARY KEY AUTO_INCREMENT, order_no VARCHAR(32) NOT NULL UNIQUE COMMENT '业务单号', customer_id BIGINT NOT NULL COMMENT '雇主id', worker_id BIGINT DEFAULT NULL COMMENT '服务人员id', item_id BIGINT NOT NULL COMMENT '服务项id', status TINYINT NOT NULL DEFAULT 0 COMMENT '状态:0待接单 1已接单 2服务中 3待评价 4已完成 5已取消', appoint_time DATETIME COMMENT '预约上门时间', address VARCHAR(255) COMMENT '服务地址', remark VARCHAR(255), create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, deleted TINYINT DEFAULT 0 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;状态字段用 TINYINT 而不是字符串枚举,是为了排序和索引效率;具体含义通过代码里的常量或枚举类控制,而不是直接散落在业务逻辑里。状态流转的约束放到 Service 层统一管理,后面详述。
2.3 分层代码的最小单元:从 Controller 到 Mapper 的完整链路
搭好表之后,按一个纵向用例把分层跑通。拿“查询某个分类下的所有服务项”来说,Controller 层:
@RestController @RequestMapping("/api/item") public class ServiceItemController { @Resource private ServiceItemService serviceItemService; @GetMapping("/list") public Result list(@RequestParam Long categoryId, @RequestParam(defaultValue = "1") Integer pageNum, @RequestParam(defaultValue = "10") Integer pageSize) { Page<ServiceItem> page = serviceItemService.queryByCategory(categoryId, pageNum, pageSize); return Result.ok(page); } }Service 层:
@Service public class ServiceItemServiceImpl extends ServiceImpl<ServiceItemMapper, ServiceItem> implements ServiceItemService { @Override public Page<ServiceItem> queryByCategory(Long categoryId, Integer pageNum, Integer pageSize) { return lambdaQuery() .eq(ServiceItem::getCategoryId, categoryId) .eq(ServiceItem::getStatus, 1) .orderByAsc(ServiceItem::getSort) .page(new Page<>(pageNum, pageSize)); } }这段代码里,lambdaQuery()是 MyBatis Plus 3.x 提供的条件构造器,用方法引用代替字符串列名,编译期就能发现字段名拼写错误。eq(ServiceItem::getCategoryId, categoryId)生成WHERE category_id = ?条件。page(new Page<>(pageNum, pageSize))会自动拼接 LIMIT 并返回分页对象,前端拿到的数据结构里包含 records、total、pages 字段。
到这里,一个最小的“请求 → 条件构造 → SQL → JSON 响应”链路已经闭合。理解这条链路后,后续所有模块都只是在 Service 里增加不同的业务逻辑,这也是很多 springboot 项目源码里代码结构高度一致的原因。
3. 家政服务管理平台的三个核心业务模块实现
3.1 服务分类与技能标签:从产品需求到可检索字段
平台首页的第一屏永远是分类导航。家政平台的产品需求一般是:一级分类固定展示(保洁、月嫂、保姆、搬家、维修),点进去看到服务项列表,服务项支持价格倒序、按标签过滤。用前面设计好的表,分类展示几乎零成本:一个 category 表,一个 item 表,联查只用 item.category_id 关联。
真正的业务量在“技能标签过滤”这个查询上。比如“能接油烟机清洗且已上架的服务项”,一个常见写法是:
LambdaQueryWrapper<ServiceItem> wrapper = new LambdaQueryWrapper<>(); wrapper.like(ServiceItem::getTags, "油烟机清洗") .eq(ServiceItem::getStatus, 1);like参数会生成%油烟机清洗%,如果后续数据量变大,可以换全文索引或右匹配"油烟机清洗%"。毕设数据量下这是最优解——不要在字符串标签上建关联表,徒增 join 复杂度。这个模块展示时能讲的话术是:分类解决路径导航,标签解决同分类内的细分。两者语义不同,所以分开建模。
3.2 预约下单与订单状态机:状态流转是毕设的重头戏
订单模块是家政服务管理平台最核心的演示点,也是最容易在代码里写乱的地方。常见错误是直接在 Controller 里写if (status == 0)的散装判断,状态多了以后改一个逻辑要翻好几个方法。简化掉支付通知后,订单的合法状态变化如下:
0 待接单 → 1 已接单(用户取消则 → 5 已取消) 1 已接单 → 2 服务中(接单后可取消,需走协商) 2 服务中 → 3 待评价(服务完成) 3 待评价 → 4 已完成(双方评价后)在 Service 层里用一个枚举加一张状态迁移表把规则集中管理:
public enum OrderStatus { WAITING(0), ACCEPTED(1), SERVING(2), REVIEWING(3), DONE(4), CANCELLED(5); final int code; OrderStatus(int code) { this.code = code; } }private static final Map<Integer, Set<Integer>> ALLOWED_TRANSITIONS = Map.of( 0, Set.of(1, 5), 1, Set.of(2, 5), 2, Set.of(3), 3, Set.of(4) );下单接口的核心逻辑:
@Transactional public Long createOrder(OrderCreateDTO dto) { CustomerOrder order = new CustomerOrder(); order.setOrderNo(generateOrderNo()); order.setCustomerId(dto.getCustomerId()); order.setItemId(dto.getItemId()); order.setStatus(OrderStatus.WAITING.code); order.setAppointTime(dto.getAppointTime()); order.setAddress(dto.getAddress()); save(order); return order.getId(); }generateOrderNo()生成业务单号可以用yyyyMMddHHmmss + 4位随机数,也可以先查当天已有订单数后自增。后一种更严谨但多一次查询,毕设用前一种足够;答辩时被追问再补一句“生产环境会换 Redis INCR 保证递增”。状态变更统一走一个方法:
@Transactional public void changeStatus(Long orderId, int fromStatus, int toStatus) { CustomerOrder order = getById(orderId); if (order == null) { throw new BizException("订单不存在"); } Set<Integer> allowed = ALLOWED_TRANSITIONS.get(fromStatus); if (allowed == null || !allowed.contains(toStatus)) { throw new BizException("非法状态流转"); } order.setStatus(toStatus); updateById(order); }这个方法在答辩演示时的价值是直接体现在报错上:连续点击“完成订单”按钮,第二次会抛“非法状态流转”,而不是像很多 CRUD 项目那样把 status 原样覆盖一遍。
3.3 评价与结算:双边评价的闭环设计
家政平台与普通电商的差别在于服务完成后是双边评价:雇主评价服务人员的技能和态度,服务人员也可以评价雇主(比如是否好沟通、家里是否方便作业)。这个闭环设计能体现完整的业务思考。
评价表按前面的设计,order_id 加唯一约束,保证一单一评。核心接口如下:
@PostMapping("/submit") public Result submitComment(@RequestBody CommentDTO dto) { // 校验订单状态必须是待评价 CustomerOrder order = orderService.getById(dto.getOrderId()); if (order.getStatus() != OrderStatus.REVIEWING.code) { throw new BizException("当前订单状态不可评价"); } OrderComment comment = new OrderComment(); comment.setOrderId(dto.getOrderId()); comment.setCustomerId(dto.getCustomerId()); comment.setWorkerId(dto.getWorkerId()); comment.setScore(dto.getScore()); comment.setContent(dto.getContent()); comment.setRole(dto.getRole()); // 1 雇主评服务人员,2 服务人员评雇主 commentService.save(comment); // 双方都评价后,订单置为完成 if (commentService.isBothCommented(dto.getOrderId())) { orderService.changeStatus(dto.getOrderId(), OrderStatus.REVIEWING.code, OrderStatus.DONE.code); } return Result.ok(); }isBothCommented的实现是在 order_id 维度按 role 字段统计评价条数,等于 2 说明双边评价完成。这里用一张表存双边评价,比拆两张表省事,数据模型上也清晰:评价是“挂在订单上的事件”,不是“挂在人身上的属性”。
同步更新服务人员的平均分是一个加分项:
Double avgScore = commentService.lambdaQuery() .eq(OrderComment::getWorkerId, dto.getWorkerId()) .eq(OrderComment::getRole, 1) .select(OrderComment::getScore) .list() .stream() .mapToInt(OrderComment::getScore) .average() .orElse(0); workerProfileService.lambdaUpdate() .eq(WorkerProfile::getUserId, dto.getWorkerId()) .set(WorkerProfile::getScore, BigDecimal.valueOf(avgScore).setScale(1, RoundingMode.HALF_UP)) .update();两个操作放进同一个事务里,避免出现评价已落库但评分没更新的中间态。这个细节在答辩时很加分,直接体现事务意识。
4. 权限、并发与通知:Spring Boot 家政平台进阶点的落地
4.1 基于 JWT 的登录鉴权与三端角色控制
家政管理平台天然是三类角色共用一个登录入口:管理员、雇主、服务人员。最常见的做法是在同一个 sys_user 表里用 role 字段区分,配合 JWT 做无状态鉴权,用拦截器实现而不用 Spring Security,是因为毕设场景下拦截器能把原理讲得更透明。
JWT 工具类核心是生成和解析:
private static final SecretKey KEY = Keys.hmacShaKeyFor( "housekeeping-platform-secret-key-2025".getBytes() ); public static String createToken(SysUser user) { return Jwts.builder() .setSubject(user.getId().toString()) .claim("role", user.getRole()) .claim("username", user.getUsername()) .setExpiration(new Date(System.currentTimeMillis() + 24 * 3600 * 1000)) .signWith(KEY, SignatureAlgorithm.HS256) .compact(); }生成 token 时把 role 放在 claim 里,拦截器解析后可以直接拿角色做权限判断,不用再查一次数据库。24 小时过期时间对毕设演示足够,答辩时可以说实际生产会换成短 token 加 refresh token。拦截器里校验的关键代码:
@Component 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.isBlank()) { throw new BizException("未登录"); } try { Claims claims = Jwts.parserBuilder() .setSigningKey(JwtUtil.getKey()) .build() .parseClaimsJws(token.replace("Bearer ", "")) .getBody(); request.setAttribute("userId", Long.valueOf(claims.getSubject())); request.setAttribute("role", claims.get("role")); } catch (JwtException e) { throw new BizException("token无效或过期"); } return true; } }注册拦截器时注意排除登录接口和静态资源:
@Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(jwtInterceptor) .addPathPatterns("/api/**") .excludePathPatterns("/api/auth/login", "/api/register", "/api/auth/**"); }这里的踩坑点在于:如果前端把上传图片放在 static 下的 /upload/**,且没做静态资源映射,拦截器会把资源请求也拦掉。实际排错时先看请求路径是否落进了 addPathPatterns 的匹配范围。
角色校验在方法内判断即可:
String role = (String) request.getAttribute("role"); if (!"admin".equals(role)) { throw new BizException("无权限操作"); }对 springboot 项目源码做二次开发时,这个拦截器类通常是第一个要看的地方——它决定了所有接口的进入门槛。
4.2 订单并发提交的防重与幂等控制
答辩时一个高频追问是:“用户连续点击两次提交订单按钮,会不会生成两条订单?”标准答案分两层:数据库层给 order_no 加唯一索引兜底,接口层用 Redis 的 setIfAbsent 做防重。
Boolean first = redisTemplate.opsForValue() .setIfAbsent("order:create:" + dto.getRequestId(), "1", Duration.ofSeconds(10)); if (Boolean.FALSE.equals(first)) { throw new BizException("请勿重复提交"); }这个方案的关键在于 requestId 由前端在打开下单页时生成并随请求携带。setIfAbsent成功说明这是第一次提交,10 秒内重复提交会被拒绝。没有 Redis 的工程可以用内存 ConcurrentHashMap 加过期时间做简易替代,但演示效果远不如 Redis 方案。如果项目里已集成 Redis 做缓存,这个防重操作没有额外成本,只多一条 key。
4.3 定时任务实现超时自动取消与订单通知
家政平台有一条自然业务规则:“用户下单后 30 分钟没有服务人员接单,系统自动取消”。用 Spring 自带的 @Scheduled 就能实现:
@Component public class OrderAutoCancelTask { @Resource private CustomerOrderMapper orderMapper; @Scheduled(cron = "0 */5 * * * ?") public void autoCancelExpiredOrders() { List<CustomerOrder> expiredOrders = orderMapper.selectExpiredWaiting( LocalDateTime.now().minusMinutes(30)); for (CustomerOrder order : expiredOrders) { orderMapper.updateStatus(order.getId(), OrderStatus.CANCELLED.code); } } }配套的 Mapper 里用注解 SQL:
@Select("SELECT * FROM customer_order WHERE status = 0 AND create_time < #{expireTime} LIMIT 100") List<CustomerOrder> selectExpiredWaiting(LocalDateTime expireTime);启动类上记得加@EnableScheduling,否则 @Scheduled 不生效。这里的缺陷是每个定时周期只取 100 条,避免一次更新太多行锁住 InnoDB 的区间索引。自动取消后往 notice 表插一条消息,用户登录后在消息中心能看到“订单超时已取消”。
0 */5 * * * ?里的?是 Quartz 风格的“不指定域”,Spring 5.3 之后兼容。如果改用@Scheduled(fixedDelay = 300000),含义是上一次任务执行完再等 300 秒跑下一次,不会出现任务堆积;cron 则按自然时间触发,若任务执行时间超过周期,可能造成重叠执行,需要自行加分布式锁或同步块。对毕设来说固定周期更稳妥。
5. 从 springboot 项目源码到本地跑通与答辩演示
5.1 15 分钟启动工程的配置清单
拿到一个 springboot 毕设源码后,第一步永远是看数据库脚本和 application.yml,而不是直接点启动。通常的操作顺序是:
mysql -uroot -p CREATE DATABASE housekeeping_db DEFAULT CHARSET utf8mb4; exit; mysql -uroot -p housekeeping_db < sql/housekeeping.sql mvn spring-boot:run启动失败时按下面顺序排查:本地 MySQL 版本是否为 5.7 以上;application.yml 里的 database 名称和账号密码是否与本地一致;pom.xml 里的 spring-boot-starter-parent 版本是否和当前 JDK 匹配。Spring Boot 2.7 用 JDK 8 或 11 最稳,JDK 17 能跑,但部分第三方 starter 版本不够新时会报模块访问错误。
5.2 答辩演示的 5 条必走路径
家政平台的演示不要按代码顺序走,按业务叙事走:
- 管理员登录,创建两个服务人员账号并审核——展示用户管理与审核流程。
- 雇主登录,按分类浏览服务项,提交预约订单——展示分类检索和下单。
- 服务人员登录,在待接单列表点击接单——展示订单状态从 0 到 1。
- 雇主确认服务开始、结束,进入评价页提交评价——展示状态到 3 再到 4。
- 切到管理员视图,查看订单列表和统计报表。
先跑通这条路径再讲代码,把每一步数据在表里的变化讲清楚,比对着 CRUD 念代码更有说服力。
5.3 三个高频排错点与一个交付技巧
本地跑 springboot 家政平台的高频报错按概率排序:
| 现象 | 原因 | 解法 |
|---|---|---|
| 数据库连接报时区错误 | 驱动 8.x 要求显式指定时区 | url 加serverTimezone=Asia/Shanghai |
| 登录后页面 404 | 拦截器没放行静态资源 | 检查 WebMvcConfigurer 的 addResourceHandlers |
| Redis 相关报错 | 项目集成了 Redis 但本地没启动 | 启动 redis-server,或注释相关配置改内存模式 |
第三个问题在毕设源码里最常见——很多项目带 Redis 做缓存或幂等,演示机没装 Redis 直接启动失败。答辩前把 redis-server 跑起来能省掉现场一半的风险。
交付层面的一个技巧:给项目补一个application-local.yml,把端口、数据库、Redis 地址写死为 localhost,启动时用--spring.profiles.active=local指定。这样任何人拿到源码都能最小改动跑起来,可交付性比只丢一个 zip 包要好得多,也能体现对 springboot 多环境配置的熟练度。
本文还有配套的精品资源,点击获取