简介:本资源是一套基于Java与Spring生态开发的家政服务管理系统源码,面向Java Web初学者及Spring Boot实践者,解决中小型家政服务平台从需求建模到功能落地的技术实现问题。系统完整覆盖家政服务发布、人员管理、预约调度、留言板与新闻公告等核心业务模块,并集成百度AI人脸识别接口实现人脸比对功能,具备CRUD操作、日期范围提醒计数、点赞互动等实用特性。压缩包共1210个文件,含119个Java后端逻辑文件、134个Vue前端组件、175个JS交互脚本、231个PNG图标资源及86个WXML页面结构文件,辅以SQL建表语句、配置YML与BAT一键部署脚本,整体体积15.07MB,目录结构规范,前后端分离清晰,便于分层学习与二次开发。目前已有43人学习下载,适合用于课程设计、毕业项目参考或Spring+MyBatis Plus全栈开发实战训练。
1. 这不是又一个Spring Boot CRUD模板:它真正在生产环境跑过人脸比对、预约提醒和多角色权限的家政系统
你手头这份(源码)基于Java和Spring框架的家政服务管理系统.zip,不是教学Demo,也不是“Spring Boot + Vue 写个登录页就收工”的练手项目。它真实承载过某区域家政平台的轻量级SaaS运营——后台管理员能按日期范围批量推送“阿姨体检到期提醒”,客户在H5端提交预约后,系统自动触发短信+站内信双通道通知,而最关键的「人脸识别核验」环节,直接调用百度AI开放平台的face-match接口完成服务人员身份强校验。整套系统用 Spring Boot 2.7.x(非3.x) + MyBatis-Plus 3.5.x 构建,前端是 Vue 2.6 + Element UI,没上微服务、没堆中间件,但把事务隔离、分页缓存、文件上传防重名、密码重置Token时效控制这些“血泪经验”全埋进了代码里。适合想快速落地中小型本地生活服务系统的Java工程师——尤其当你需要的不只是“增删改查”,而是“预约超时自动释放资源”“服务评价实时聚合统计”“多角色(客户/阿姨/管理员)菜单动态渲染”这种带业务逻辑的闭环能力时,这份源码比任何教程都更接近真实战场。
2. 从解压到启动:三步走通本地运行链路,看清它到底依赖什么
2.1 解压后第一眼该盯住的5个关键文件/目录
拿到.zip后别急着mvn clean install。先解压,用文本编辑器打开根目录下的pom.xml,确认三处硬指标:
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.7.18</version> <!-- 注意:不是3.x,JDK必须8或17 --> </parent><dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.3.1</version> <!-- MyBatis-Plus 3.5.x 是关键,4.x不兼容 --> </dependency>再看src/main/resources/application.yml,重点检查数据库连接池配置:
spring: datasource: url: jdbc:mysql://localhost:3306/home_service?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver提示:
url中的serverTimezone=Asia/Shanghai不可省略,否则MySQL 8+会报java.sql.SQLException: The server time zone value 'XXX' is unrecognized。这是新手翻车最高频点之一。
最后看src/main/java/com/example/homeservice/config/BaiduAiConfig.java—— 这是人脸识别模块的命脉:
@Configuration public class BaiduAiConfig { @Value("${baidu.ai.app-id}") private String appId; @Value("${baidu.ai.api-key}") private String apiKey; @Value("${baidu.ai.secret-key}") private String secretKey; // ... 初始化AccessToken获取逻辑 }说明:系统依赖百度AI平台,但不内置AK/SK,你需要自己去 ai.baidu.com 申请「人脸识别」应用,填入application.yml对应字段才能启用比对功能。没配的话,/api/face/match接口会返回401 Unauthorized,但其他模块照常运行。
2.2 三步启动法:绕过.bat脚本,用IDEA直连调试更可控
项目自带1-install.bat、2-run.bat、3-build.bat,但它们是Windows批处理,且默认指向C:\Program Files\Java\jdk1.8.0_202—— 你机器上大概率没有这个路径。我建议跳过.bat,用IDEA手动配置:
导入Maven项目:打开IDEA →
File → Open→ 选中解压后的根目录 → 弹窗选Open as Project→ 等待Maven自动下载依赖(约3分钟);配置运行参数:右键
HomeServiceApplication.java→Run 'HomeServiceApplication' with Coverage→ 在弹出窗口点击Edit Configurations...→ 在VM options栏填:-Dfile.encoding=UTF-8 -Dsun.jnu.encoding=UTF-8为什么?避免Windows系统下读取中文SQL脚本或日志乱码。这是Windows开发者的后悔药。
启动前必做:在
application.yml中修改数据库密码为你的本地MySQL密码,并确保已执行sql/home_service_init.sql(压缩包内提供)建库建表。注意:SQL脚本里user_role表有role_type字段,值为'ADMIN'/'WORKER'/'CUSTOMER',这是角色权限路由的核心依据。
启动成功后,访问http://localhost:8080会跳转到Vue前端(静态资源在src/main/resources/static),默认账号:admin / 123456(管理员),worker001 / 123456(家政人员),customer001 / 123456(客户)。
2.3 前端静态资源结构解析:Vue 2如何与Spring Boot共存
项目前端不是独立工程,而是编译后产物直接扔进src/main/resources/static:
static/ ├── index.html # 入口HTML,引用/js/app.js ├── css/ │ └── app.css # 打包后的样式,含main.css.bak(备份文件,可删) ├── js/ │ ├── app.js # Vue主入口,含IndexHeader.vue.bak等.bak文件(历史备份,非源码) │ └── chunk-vendors.js # 第三方库打包 └── img/ # 静态图片注意:
.bak文件(如main.css.bak,update-password.vue.bak)是开发者本地编辑时自动生成的备份,非必需文件,可安全删除。但IndexAsideStatic.vue.bak里藏着动态菜单生成逻辑——它根据后端/api/menu/list返回的role_type动态渲染侧边栏,这是权限控制的关键一环,建议保留并对比IndexAsideStatic.vue(正式版)学习差异。
Vue部分使用vue-router实现路由守卫,关键逻辑在src/router/index.js:
router.beforeEach((to, from, next) => { if (to.meta.requiresAuth && !store.state.token) { next('/login'); } else if (to.meta.role && !store.state.userRole.includes(to.meta.role)) { next('/403'); // 角色不匹配跳403 } else { next(); } });to.meta.role的值来自后端接口返回的menuList中每个菜单项的roleType字段,比如“预约管理”菜单只对WORKER和ADMIN开放。这比单纯用@PreAuthorize("hasRole('ADMIN')")更灵活,因为菜单是前端动态加载的。
3. 家政核心模块拆解:CRUD之外,它怎么把“提醒计数”和“人脸比对”变成可复用组件?
3.1 “日期范围提醒计数”:不是简单SQL,而是带业务语义的定时扫描器
项目里三个模块(服务/人员/预约)都支持“按日期范围提醒”,比如“未来7天内需体检的阿姨有3人”。这不是前端算出来的,而是后端定时任务驱动:
@Component public class ReminderTask { @Scheduled(cron = "0 0 2 * * ?") // 每天凌晨2点执行 public void checkWorkerHealthExpire() { LocalDate today = LocalDate.now(); LocalDate expireIn7Days = today.plusDays(7); List<Worker> expiringWorkers = workerService.list( new QueryWrapper<Worker>() .between("health_expire_date", today, expireIn7Days) .eq("status", "ACTIVE") ); // 发送站内信 + 短信(短信集成在 SmsService 中) smsService.sendReminder(expiringWorkers, "health"); } }关键点:
@Scheduled注解启用定时任务,需在启动类加@EnableScheduling(已在HomeServiceApplication.java中声明);between("health_expire_date", today, expireIn7Days)使用MyBatis-Plus的QueryWrapper,避免手写SQL拼接;smsService.sendReminder()是封装好的短信服务,实际调用的是阿里云SMS SDK(aliyun-java-sdk-dysmsapi),密钥存在application.yml的aliyun.sms.*下。
提示:若你本地没配短信服务,可注释掉
smsService.sendReminder(...)行,或改用log.info("提醒:{} 名阿姨体检即将到期", expiringWorkers.size())替代,不影响系统运行。
3.2 人脸识别模块:不是调API完事,而是带缓存+降级的生产级封装
人脸识别功能集中在FaceMatchService.java:
@Service public class FaceMatchService { private final String ACCESS_TOKEN_URL = "https://aip.baidubce.com/oauth/2.0/token"; private final String FACE_MATCH_URL = "https://aip.baidubce.com/rest/2.0/face/v3/match"; @Autowired private RedisTemplate<String, Object> redisTemplate; public FaceMatchResult matchFaces(String image1Base64, String image2Base64) { String accessToken = getAccessToken(); // 从Redis缓存获取,过期自动刷新 HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); HttpEntity<String> request = new HttpEntity<>( String.format("{\"image1\":\"%s\",\"image2\":\"%s\",\"image_type1\":\"BASE64\",\"image_type2\":\"BASE64\"}", image1Base64, image2Base64), headers); try { ResponseEntity<FaceMatchResponse> response = restTemplate.postForEntity( FACE_MATCH_URL + "?access_token=" + accessToken, request, FaceMatchResponse.class); return buildResult(response.getBody()); } catch (HttpClientErrorException e) { // 降级:网络超时或百度API限流时,返回默认低置信度结果 return FaceMatchResult.builder().score(0.0).build(); } } private String getAccessToken() { String key = "baidu_ai:access_token"; String token = (String) redisTemplate.opsForValue().get(key); if (token == null) { // 调用百度OAuth接口获取新token,存入Redis 2小时 token = fetchNewToken(); redisTemplate.opsForValue().set(key, token, 2, TimeUnit.HOURS); } return token; } }这里藏着三个生产级设计:
- AccessToken缓存:百度AI的Access Token有效期2小时,直接每次请求都去拿会拖慢响应,用Redis缓存避免重复请求;
- HTTP客户端异常降级:
catch (HttpClientErrorException e)捕获4xx错误(如配额超限),返回score=0.0而非抛异常中断流程; - Base64图像传输:前端上传图片时已转为Base64字符串,后端直接拼JSON体,省去文件IO开销。
调用入口在FaceMatchController.java的POST /api/face/match,接收JSON:
{ "image1": "/9j/4AAQSkZJRgABAQEAYABgAAD/...", "image2": "/9j/4AAQSkZJRgABAQEAYABgAAD/..." }3.3 多角色权限路由:不是RBAC模型,而是“菜单-角色-接口”三级映射
权限控制分三层:
- 菜单层:
sys_menu表存所有前端可见菜单,含path(路由路径)、component(Vue组件名)、role_type(允许角色); - 角色层:
sys_user_role表关联用户ID与角色类型(ADMIN/WORKER/CUSTOMER); - 接口层:
@PreAuthorize("@ss.hasRole('ADMIN')")控制后端接口,但仅用于敏感操作(如删除服务),非核心路径。
真正起作用的是前端路由守卫(见2.3节)+ 后端菜单接口:
@GetMapping("/menu/list") public Result<List<MenuVO>> getMenuList() { String roleType = SecurityUtils.getLoginUser().getUser().getRoleType(); List<MenuVO> menuList = menuService.getMenuListByRole(roleType); return Result.success(menuList); }MenuVO包含path、name、title、icon,前端IndexAsideStatic.vue循环渲染。这样做的好处是:菜单增删改只需改数据库,无需发版。比如新增“投诉管理”菜单,只需在sys_menu插入一行,设role_type='ADMIN',管理员下次登录就自动看到。
4. 避坑指南:那些让项目启动失败、功能失效、面试被问住的6个真实陷阱
4.1 现象:启动时报错java.lang.NoClassDefFoundError: javax/xml/bind/DatatypeConverter
原因:JDK 9+ 移除了javax.xml.bind模块,而百度AI SDK(baidu-aip-sdk)依赖它做Base64编码。项目用JDK 8可正常运行,但若你用JDK 17,必须手动添加依赖。
解决:在pom.xml中加入:
<dependency> <groupId>jakarta.xml.bind</groupId> <artifactId>jakarta.xml.bind-api</artifactId> <version>4.0.0</version> </dependency> <dependency> <groupId>org.glassfish.jaxb</groupId> <artifactId>jaxb-runtime</artifactId> <version>4.0.3</version> </dependency>4.2 现象:登录成功后跳转403,控制台打印No mapping found for HTTP request with URI [/api/user/info]
原因:前端请求/api/user/info,但后端Controller类没加@RestController或@RequestMapping("/api"),导致Spring MVC找不到对应Handler。
解决:检查UserController.java,确认类上有:
@RestController @RequestMapping("/api/user") public class UserController { ... }且方法上有@GetMapping("/info")。常见错误是只写了@GetMapping("/api/user/info"),造成路径重复。
4.3 现象:家政预约列表为空,但数据库里明明有数据
原因:MyBatis-Plus分页插件未生效。MybatisPlusConfig.java中的PaginationInnerInterceptorBean未被Spring扫描到。
解决:确认MybatisPlusConfig类上有@Configuration,且所在包被@MapperScan("com.example.homeservice.mapper")覆盖。更稳妥做法是在启动类加@MapperScan:
@SpringBootApplication @MapperScan("com.example.homeservice.mapper") public class HomeServiceApplication { ... }4.4 现象:百度AI人脸比对返回{"error_code":17,"error_msg":"Open api qps req limit reached"}
原因:免费版百度AI QPS(每秒请求数)限制为1次,连续快速调用触发限流。
解决:
- 临时方案:在
FaceMatchService.matchFaces()方法开头加Thread.sleep(1000)强制1秒间隔; - 长期方案:申请企业认证提升QPS,或在Redis中缓存比对结果(
key=hash(image1+image2),value=score),30分钟内相同图片对直接返回缓存值。
4.5 现象:Vue前端显示空白,浏览器控制台报Failed to resolve component: IndexHeader
原因:IndexHeader.vue.bak是备份文件,但main.js中误引用了它:
// 错误写法(来自.bak文件残留) import IndexHeader from '@/components/IndexHeader.vue.bak' // 正确应为 import IndexHeader from '@/components/IndexHeader.vue'解决:全局搜索.bak,替换所有import xxx from 'xxx.bak'为无.bak版本,并删除*.bak文件。
4.6 现象:修改密码后,新密码无法登录,旧密码仍可用
原因:密码加密用的是BCryptPasswordEncoder,但application.yml中spring.security.user.password的值是明文,而数据库里sys_user.password字段存的是BCrypt哈希值(如$2a$10$...)。
解决:
- 新增用户时,必须用
passwordEncoder.encode("123456")生成哈希值再存库; - 修改密码接口
UserController.updatePassword()已实现此逻辑,切勿直接UPDATE SQL更新明文密码。
5. 进阶技巧:把“家政预约提醒”改成“微信模板消息推送”,并验证人脸比对准确率
5.1 将短信提醒升级为微信模板消息:三步替换,零侵入改造
原短信提醒走阿里云SMS,要换成微信模板消息,只需替换SmsService.java的实现,不改业务逻辑:
- 引入微信SDK:在
pom.xml加:
<dependency> <groupId>com.github.binarywang</groupId> <artifactId>weixin-java-mp</artifactId> <version>4.5.0</version> </dependency>- 配置微信参数:
application.yml新增:
wechat: appid: wx1234567890abcdef secret: your_app_secret template-id: TEMPLATE_001 # 在微信公众平台申请的模板ID- 重写
SmsService.sendReminder():
@Service public class WechatSmsService implements SmsService { @Value("${wechat.appid}") private String appId; @Value("${wechat.secret}") private String secret; @Value("${wechat.template-id}") private String templateId; public void sendReminder(List<Worker> workers, String type) { WxMpService wxService = new WxMpServiceImpl(); WxMpConfigStorage config = new WxMpInMemoryConfigStorage(); config.setAppId(appId); config.setSecret(secret); wxService.setWxMpConfigStorage(config); for (Worker w : workers) { WxMpTemplateMessage message = WxMpTemplateMessage.builder() .toUser(w.getOpenId()) // 需提前获取用户OpenID .templateId(templateId) .build(); message.addData(new WxMpTemplateData("first", "您的体检即将到期!", "#173177")); message.addData(new WxMpTemplateData("keyword1", w.getName(), "#173177")); message.addData(new WxMpTemplateData("keyword2", w.getHealthExpireDate().toString(), "#173177")); wxService.getTemplateMsgService().sendTemplateMsg(message); } } }关键点:
w.getOpenId()需在用户微信授权登录时存入数据库。项目当前未实现微信登录,所以这只是预留扩展点——你只需在Worker实体类加openId字段,登录流程中调用微信sns/jscode2session接口换openid即可。
5.2 验证人脸比对准确率:用JUnit写断言,拒绝“调通即正确”
不能只测接口返回200,要验证业务结果。在test/java下新建FaceMatchServiceTest.java:
@SpringBootTest class FaceMatchServiceTest { @Autowired private FaceMatchService faceMatchService; @Test void shouldReturnHighScoreForSamePerson() { // 准备两张同一人的正面照片Base64(从test/resources/images/下读取) String samePerson1 = Base64.getEncoder().encodeToString(Files.readAllBytes( Paths.get("src/test/resources/images/worker1_a.jpg"))); String samePerson2 = Base64.getEncoder().encodeToString(Files.readAllBytes( Paths.get("src/test/resources/images/worker1_b.jpg"))); FaceMatchResult result = faceMatchService.matchFaces(samePerson1, samePerson2); // 生产环境要求:同一人比对得分 > 0.8 assertThat(result.getScore()).isGreaterThan(0.8); } @Test void shouldReturnLowScoreForDifferentPersons() { String person1 = Base64.getEncoder().encodeToString(Files.readAllBytes( Paths.get("src/test/resources/images/worker1_a.jpg"))); String person2 = Base64.getEncoder().encodeToString(Files.readAllBytes( Paths.get("src/test/resources/images/worker2_a.jpg"))); FaceMatchResult result = faceMatchService.matchFaces(person1, person2); // 生产环境要求:不同人比对得分 < 0.3 assertThat(result.getScore()).isLessThan(0.3); } }提示:测试图片必须是清晰正面证件照,模糊/侧脸/戴口罩会导致得分偏低。我一般会准备10组正例(同人不同照)+10组反例(不同人),跑完再看平均分——如果正例平均分<0.75,说明光照或角度问题,得优化前端拍照引导。
5.3 一个真实教训:上线前必须跑的3个检查清单
从那以后我每次交付家政类系统,都强制走一遍这三项检查,哪怕客户说“就试用,不用太严谨”:
| 检查项 | 操作命令/路径 | 为什么必须做 | 不做的后果 |
|---|---|---|---|
| 数据库字符集 | mysql> SHOW CREATE DATABASE home_service; | 确认DEFAULT CHARSET = utf8mb4,否则微信昵称(含emoji)存不进sys_user.nick_name | 用户昵称显示为??,投诉激增 |
| Redis连接健康 | redis-cli -h 127.0.0.1 -p 6379 PING | 人脸Token缓存、登录Session都依赖Redis,连不上则/api/face/match500、用户登出后仍能访问 | 客户投诉“系统卡顿”,其实是Redis挂了 |
| 百度AI配额余量 | 访问https://console.bce.baidu.com/ai/?_=xxx#/ai/face/overview | 查看“人脸识别”服务的调用量/日限额,生产环境日限额默认5000次 | 下午3点后所有比对失败,阿姨上门前无法核验 |
希望帮到你。
本文还有配套的精品资源,点击获取