news 2026/9/28 14:10:24

SpringBoot家政保洁预约系统项目实战:从零到一完整实现

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
SpringBoot家政保洁预约系统项目实战:从零到一完整实现

每年这个时候,总有不少同学在后台私信问我:SpringBoot 的毕设项目到底该怎么做才能拿得出手?恰好我手头刚完成了一个家政保洁预约系统的完整开发,项目代号就叫mwrnnvi8_zl032,从数据库设计到权限控制再到前后端联调,踩了不少坑,也沉淀了不少可以直接复用的经验。这次就把整个项目从零到一的完整思路拆给你看,不管是拿来当毕设、课程设计,还是自己练手做全栈,这篇都够用了。

1. 项目整体设计与技术选型

1.1 这个项目到底在做什么

家政保洁预约系统,本质上解决的是一个典型的“服务交易撮合”问题:顾客需要保洁服务,保洁员/服务商有供给能力,系统要做的就是把预约、派单、支付、评价这条链路串起来。

从需求视角拆一下,这个系统至少包含三类角色:

  • 用户端:注册登录、浏览服务项目、选择时间段、下单预约、在线支付(或下单后线下结算)、查看订单状态、取消订单、评价服务。
  • 管理员端:服务项目管理、保洁员管理、订单管理(派单/改派/取消)、用户管理、数据统计、反馈处理。
  • 保洁员端:接单提醒、确认接单、完成服务、查看收益记录(如果做到财务模块的话)。

我实际做的时候,把重心放在了用户端和管理员端,保洁员端用的是一个简化版的 H5 页面,逻辑和后台管理端有部分复用。这样做的好处是工作量可控,同时三个端都有了,答辩时讲演示也不虚。

1.2 技术选型:为什么是 SpringBoot 这套组合

先说结论,这套技术栈是这个项目最稳妥的组合,没有之一:

  • 后端:Spring Boot 2.7.x + MyBatis-Plus + Spring Security + JWT
  • 前端:Vue 2 + Element UI(后台管理端),用户端用的则是轻量的 Bootstrap 模板改造
  • 数据库:MySQL 8.0 + Redis(会话/缓存)
  • 开发工具:IDEA + Navicat + Postman + Git
  • 构建:Maven,打包方式为 Jar,部署到服务器跑 Nginx 反向代理

选 SpringBoot 的核心原因,一是它足够成熟,生态完善,面试时也好讲;二是它对毕设和中小型项目来说开发效率极高,起步快、调试方便,不用像 SSM 那样配一堆 XML。

注意:Spring Boot 3.x 已经出来了,但很多第三方组件(尤其是一些老牌的分页插件、代码生成器)对它的兼容还不太稳定。我这个项目用的是2.7.x 版本,JDK 用 1.8(或者 11 也行)。如果你不是想尝鲜,别在版本上给自己挖坑。

1.3 架构思路:前后端分离但还是“传统”了点

这个项目采用了前后端分离结构,RESTful API 风格。前端通过 Axios 调用后端接口,后端统一返回Result<T>结构。

{ "code": 200, "message": "success", "data": { } }

后端内部按四层走:Controller → Service → Mapper → Database,这不是什么花哨的微服务架构,但足够清晰,也好扩展。我一直觉得,做毕设或者中小型真实项目,没必要一上来就拆微服务,单体+模块化就已经是合理上限了。

有一个容易被人忽略的设计点:业务层面做了模块切分。用户模块、订单模块、服务项目模块、评价模块、统计模块之间尽量解耦,加上头条搜索词里有“springboot项目全局过滤器处理上传pdf文件时xss攻击”这种问题,我当时也把全局异常处理和 XSS 过滤统一做了,后面会讲。

2. 数据库设计与表结构核心逻辑

2.1 建表思路:六张核心表

家政预约系统的表设计并不复杂,但如果直接照着业务表一层层堆,后期往往会因为字段冗余反反复复改。我最后沉淀下来的核心表如下:

  • user:用户表(含角色字段:USER / ADMIN / WORKER)
  • service_category:服务分类表(日常保洁、深度保洁、开荒保洁、家电清洗等)
  • service_item:服务项目表(挂在分类下面,含价格、时长、图片等)
  • appointment_order:预约订单表(核心表)
  • order_status_log:订单状态变更日志表(这个表容易被忽略,但做订单管理必备)
  • review:服务评价表

如果你想扩展功能(比如积分、优惠券、财务结算),在这个基础上加表就可以了,不会伤筋动骨。

2.2 预约订单表的设计细节

订单表是整个系统的“中枢”,我列出几个关键字段,方便大家直接参考:

字段名类型说明
order_novarchar订单编号,生成规则建议“日期+随机串”
user_idbigint下单用户
worker_idbigint派单保洁员
service_item_idbigint服务项目
service_datedate预约日期
service_timeslotvarchar预约时段,如 09:00-12:00
addressvarchar服务地址
statustinyint状态机
payment_statustinyint支付状态
total_amountdecimal订单金额
remarkvarchar备注

状态机设计我建议这样定义:1-待支付 / 2-待派单 / 3-待服务 / 4-服务中 / 5-已完成 / 6-已取消。这里有个小细节:很多人喜欢只在订单表里保留一个 status 字段,然后直接 update,但这样对“订单被谁在什么时候改过状态”没有记录。我加了order_status_log,虽然多写几行代码,但调试和答辩时真的能加分。

2.3 数据库索引与性能上的几个取舍

用户下单查询、管理员后台订单列表,最常见的查询条件是status、user_id、service_date,所以强烈建议在订单表上建联合索引。我当时建的是:

ALTER TABLE appointment_order ADD INDEX idx_user_status_date (user_id, status, service_date);

索引不是越多越好,尤其是在 MySQL 8.0 里。我当时在remark字段上尝试加过全文索引,发现对这种小型系统完全用不上,反而拖慢写入,后来直接删了。如果你的查询条件相对固定,就只建那两三个最有价值的联合索引即可。

3. 核心功能模块与接口设计

3.1 登录鉴权:JWT + Spring Security 还是“轻量拦截器”?

很多同学一看到“Spring Security”就头大,觉得配置太麻烦。我对这类需求的建议是:单纯做毕设,JWT + 拦截器就可以了;如果你希望简历上写 Spring Security,就用它来做。

这个项目我是用 Spring Security + JWT 实现的。核心思路很简单:用户登录成功后,后端生成一个 token 返回给前端,前端存在 localStorage,每次请求时放到请求头Authorization里。后端用一个过滤器校验 token 合法性,并解析出用户信息放到 SecurityContext 里。

关键配置有三块:

  • WebSecurityConfigurerAdapter配置放行规则(登录、注册、服务列表等接口放行,其他接口需要认证)。
  • JwtAuthenticationFilter:继承OncePerRequestFilter,在请求进入 Controller 前完成 token 校验和用户身份组装。
  • 密码加密:必须用 BCrypt,不要用 MD5,这一点面试时问到加密时很容易成为加分项。

这里踩过一个大坑:Spring Security 的过滤器链和 CORS 配置之间偶尔会“打架”,如果你遇到“前端请求能到后端但拿不到响应头”的情况,检查一下是不是corsConfigurationSource没有注册到 Security 的配置里。

3.2 下单预约的核心流程

预约下单的流程比较常规,但有两个细节要额外注意:

第一是库存/名额校验。家政项目的“库存”不是具体商品数量,而是保洁员在某时段的可服务名额。我当时的做法是:在service_item表里增加一个max_orders_per_slot字段,下单时用 Redis 的incr记录某保洁员在某时段的订单数,超过上限就拒绝下单。虽然 MySQL 也能做,但用 Redis 的原子操作更稳,并发场景下不容易超卖。

第二是订单号生成。直接用数据库自增 id 当订单号,很容易暴露业务量,而且不太好看。我用的规则是:

String orderNo = "JZ" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")) + RandomStringUtils.randomNumeric(4);

大概就是你看到的JZ202409151430552847这种结构。后来发现并发高的时候同秒可能出现重复随机串,所以加了个“订单号唯一索引+重试机制”,稳妥一点。

3.3 派单逻辑:先用人工派单,再谈自动派单

很多同学在这上面纠结很久,到底要不要做自动派单算法?我的建议是:不要一开始就做复杂的智能派单,那是给自己挖坑。

这个项目采用“管理员派单为主,用户选保洁员为辅”的方式。用户下单时可以选择“指定保洁员”,也可以选择“平台指派”。平台指派模式下,订单进入“待派单”状态,管理员在后台可以查看当前所有订单,手动选择保洁员并点击“派单”。

为什么不做自动派单?因为自动派单需要维护位置距离、实时档期、历史评分、技能匹配等一系列数据,逻辑写起来不算难,但调试真的很耗时。等基础版本稳定之后,我再规划了一个简单的智能派单(按“当日订单数最少 + 评分最高”优先),作为扩展功能。

4. 安全设计:XSS 过滤、统一异常与文件上传

4.1 全局 XSS 过滤器,细节必须抠

搜索词里提到“springboot项目全局过滤器处理上传pdf文件时xss攻击”,这个问题在真实开发里确实会遇到——很多人在文本输入框做了 XSS 过滤,但上传的文件名、PDF 文件的元数据里照样可以塞恶意脚本。

我当时的做法是写了一个XssFilter,继承OncePerRequestFilter,对请求体里的参数做转义处理,重写了HttpServletRequestWrapper的getParameter()、getParameterValues()和getInputStream()。

同时,针对 PDF 上传场景,我在文件上传接口中专门处理了文件名:

String safeFileName = HtmlUtils.htmlEscape(file.getOriginalFilename(), "UTF-8");

如果文件类型是 PDF,还会用 PDFBox 解析元数据,把标题、作者等属性里的<script>等标签剔除掉。这一层保护在日常demo里很少见,但真正上线或者参加比赛时会很加分。

4.2 统一异常处理

另一个项目必备项是全局异常处理器,我用@RestControllerAdvice实现了三种异常的分层处理:

  • 业务异常(例如余额不足、库存不足),返回 code = 500 + 自定义 message。
  • 参数校验异常,返回 code = 400 + 定位到具体字段名。
  • 兜底异常,返回 code = 500 + 不包含敏感信息的通用提示。

有一点需要特别提醒:线上环境不要把异常堆栈直接返回给前端,这不仅暴露内部实现细节,而且对用户体验也很差。我当时写了一个逻辑,如果当前是 dev 环境,才在响应里附带堆栈信息;如果是 prod,只在服务端日志里输出。

4.3 文件上传:本地存储还是 MinIO?

这个项目的服务图片、用户头像,最初是存到本地目录的,但随着项目代码在不同电脑之间迁移,路径问题开始频繁冒出来。后来我换成了 MinIO 做对象存储,客户端通过预签名 URL 直传,不经过后端转存。

这样做的好处是:后端不扛流量、不占内存,文件访问走 MinIO 的地址即可,扩展起来也方便。但是有一个细节要特别注意:预签名 URL 是有过期时间的,如果前端页面停留时间过长,图片链接会失效,需要在响应时设置一个足够长的有效期(例如 7 天),或者前端定期刷新 URL。

String presignedUrl = minioClient.getPresignedObjectUrl( GetPresignedObjectUrlArgs.builder() .bucket("home-service") .object(objectName) .expiry(60 * 60 * 24 * 7) .build());

不过这里也要提醒一下,本地存储对于毕设来说完全够用,MinIO 是为了练手对象存储而加的。如果你时间紧、追求稳定,本地存储或七牛云/阿里云 OSS 都可以。

5. 前后端联调与典型问题排查

5.1 联调之前,先把接口文档定好

很多同学习惯先写完后端再写前端,结果接口字段对不上,联调阶段一地鸡毛。我这次用 Apifox 统一管理接口文档,后端的实体字段和前端 VO 字段保持一致,再生成前端模拟数据。这样一来,前端和后端完全可以并行开发。

这里有一个实用建议:接口返回的数据结构,不要直接返回实体类(Entity),而是返回 VO(View Object)。因为实体类往往会包含密码、手机号、逻辑删除字段等,直接暴露给前端既不安全,也容易把前端的取值字段搞乱。我的做法是每个核心模块建一个*.vo包,用BeanUtils.copyProperties把 Entity 转成 VO。

5.2 联调时的四个高频问题

问题一:前端拿到的时间串不对

MySQL 里的datetime返回给前端时是2025-06-12T09:00:00这种带 T 的格式,直接显示非常难看。解决办法是在application.yml里配置:

spring: jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT+8

问题二:跨域请求失败

后端开启全局跨域配置后,仍然偶尔出现前端报跨域错误。大部分情况是 Spring Security 拦截了 CORS 预检请求(OPTIONS),需要显式放行:

http.cors().and().csrf().disable()

问题三:分页插件失效

我之前用 MyBatis-Plus 分页插件,经常出现在“第一页正常,第二页数据重复”的情况。原因基本是分页插件没有正确注册到 MyBatis 的拦截器链里,在配置类中重新注入MybatisPlusInterceptor并添加分页插件即可。

问题四:Redis 缓存了脏数据

用户修改头像或昵称后,个人信息接口返回的还是旧值,因为缓存没有更新。这类问题没有捷径,必须统一封装缓存操作工具类,在“更新数据库”时主动删除缓存,并保证一套更新的操作同时执行。

6. 项目部署与优化扩展

6.1 部署方式:从本地到云服务器

这个项目我最终部署的方式是:一台 Linux 云服务器(2核4G),上面跑 MySQL、Redis、MinIO、Spring Boot Jar 包,前端 Vue 打包后的静态文件放在 Nginx 里,Nginx 做端口转发和反向代理。

关键部署步骤不复杂,但有几个坑要提前说:

  • 不要把application.yml里的配置写死,改成读取环境变量。否则每次换环境都要重新打包,非常愚蠢。
  • MySQL 账号密码不要用 root/root 上生产,至少要单独建一个库账号并限制 IP。
  • 服务器上跑 Jar 包时,一定要用nohup java -jar xxx.jar > logs/out.log 2>&1 &这种后台启动方式,并用jps -l确认进程是否起来。

6.2 Docker 一键编排是否必要

如果你想让部署更优雅一点,或者想顺便展示一下 Docker 技能,那就用 Docker Compose 编排一套。

version: '3' services: mysql: ... redis: ... minio: ... backend: build: ./backend ports: "8080:8080" frontend: build: ./frontend ports: "80:80"

注意:Docker 部署 Spring Boot 项目时,服务启动顺序不能随便编排,因为如果 MySQL 还没初始化好,后端启动就会报连接错误。我当时的做法是在启动脚本里加了一个简单的等待机制(延时 + 重试连接),比 Docker Compose 原生的depends_on更稳。

6.3 还能扩展哪些高价值功能

如果你的项目想更进一步,从“完成项目”变成“有亮点”,下面这几个方向可以选:

  • 定时任务统计报表:每天凌晨统计订单数、营收数据,Spring 自带的@Scheduled就能搞定。
  • 消息通知:接入 WebSocket,用户下单后实时推送订单状态变化。
  • 优惠券系统:后台发券、用户领券、下单抵扣。
  • 多城市配置:服务根据用户定位展示不同城市的服务项目。

不过这些功能一定要按“增量”的思路做,不要一次性铺开,否则很容易把所有东西堆成半成品。我自己的习惯是:在核心链路稳定之前,绝不碰边缘功能。

7. 几个避坑指南与经验总结

7.1 通用避坑点

  • 项目结构不要乱:controller/service/mapper/entity/vo/config 各司其职,别把业务逻辑写在 Controller 里,这种代码后面根本没法维护。
  • 事务注解必须用对:下单操作涉及订单表、日志表甚至库存表,一定要在 service 方法上加@Transactional(rollbackFor = Exception.class),默认只回滚RuntimeException,这点很多人会忽略。
  • 日志别全用 System.out.println:至少用@Slf4j输出日志,调试和排查线上问题时差别巨大。
  • 敏感信息不要硬编码:数据库密码、JWT 密钥不要写在代码里,至少放配置中心或环境变量。

7.2 关于答辩和面试的准备

做这个项目过程中最有价值的不是把功能做完,而是能把每一步的设计理由说出来。面试官如果问“为什么用 Redis 做时段计数”,你要是能讲清楚“MySQL 在并发更新下锁竞争严重,而 Redis 的原子自增更轻量”这种深度对比,就已经超过大部分候选者了。

另外有一个高频问题是“订单超时未支付怎么办”。这个项目里我用的方案是:用户下单后写一张预支付订单,存到 Redis 缓存中并设置过期时间(比如 30 分钟),Redis 的 key 过期时触发一个监听回调,去关单或者放开名额。如果你的版本不想依赖 Redis,也可以用定时任务扫表,但实时性不如 Redis 方案。

类似这种细节,平时在写代码时有意积累三五个,简历和面试就都有了着落。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/28 14:10:05

Android开发实战:从环境搭建到AI大模型集成的完整指南

我手机里有一个叫“Android项目”的文件夹&#xff0c;里面不是代码仓库&#xff0c;而是塞满了几百张截图、网页链接、随手记的报错信息。从以content://开头的一串串URI&#xff0c;到SystemUI架构图、GGUF模型加载崩溃栈&#xff0c;再到九宫格密码控件的实现片段。说实话&a…

作者头像 李华
网站建设 2026/9/28 14:09:46

YOLO半挂车检测数据集实战:从解压到部署的完整指南

简介&#xff1a;这套YOLO半挂车检测数据集&#xff0c;面向使用YOLO系列算法进行目标检测的开发者与研究学习者&#xff0c;用于半挂车识别、道路车辆检测等模型的训练、验证与测试。压缩包共607个文件&#xff0c;包含546张已标注的半挂车JPG图像、30个YOLO格式TXT标签、30个…

作者头像 李华
网站建设 2026/9/28 14:08:56

hindsight 记忆架构实战:从分层存储到 MCP 与 Docker 落地

1. 从“hindsight”说起&#xff1a;为什么记忆是 Agent 落地的最后一公里第一次看到 “hindsight” 这个词&#xff0c;是在一个做 LLM Agent 的群里。有人丢了一张截图&#xff0c;说他们的 Agent 在连续对话到第 40 轮之后开始“胡言乱语”&#xff0c;前面用户明确说过的偏…

作者头像 李华
网站建设 2026/9/28 14:08:11

Jev照片修复模型深度解析:本地部署、低显存优化与Codex集成

1. 为什么一个照片修复模型能刷屏&#xff1a;先说我对 Jev 的第一印象热搜词和社区里讨论 Jev 的人已经很多了&#xff0c;我这两天也把模型完整刷了一遍。先说结论&#xff1a;如果你经常接触老照片修复、模糊人像增强、低分辨率素材放大&#xff0c;那 Jev 大概率是今年目前…

作者头像 李华
网站建设 2026/9/28 14:05:36

ESP-IDF驱动ST7789彩屏:从SPI配置到动态刷新完整实践

1. 项目概述与整体思路拆解1.1 为什么选择ESP-IDF驱动ST7789拿到“用ESP-IDF驱动ST7789屏幕”这个需求&#xff0c;第一反应大概率是&#xff1a;网上教程一堆&#xff0c;直接抄不就行了&#xff1f;但真上手之后你会发现&#xff0c;坑远比想象的多。ST7789这颗驱动IC在国产小…

作者头像 李华
网站建设 2026/9/28 14:05:06

微信公众号模板推送全指南:服务号与订阅号的区别及实现方案

我做了多年公众号开发和运营&#xff0c;发现一个特别常见的现象&#xff1a;一提“模板推送”&#xff0c;很多人第一反应就是把服务号和订阅号混为一谈&#xff0c;结果权限都开通完了才发现——订阅号压根没有模板消息接口&#xff0c;白忙一场。反过来&#xff0c;也有人把…

作者头像 李华