前阵子我把一套家政服务平台信息管理系统完整跑通了,后端是SpringBoot,前端是Vue,数据库用的MySQL,整个工程确实可以直接运行。做这套系统的初衷很简单:很多人在做课设、毕设或者全栈练手时,经常卡在前后端联调和环境配置上,项目本身不难,但坑不少。这个家政平台覆盖了用户下单、服务人员接单、管理员审核与统计这几条主链路,角色清晰,业务闭环完整,很适合拿来学习SpringBoot+Vue的项目结构,也适合直接在此基础上二次开发成跑腿、预约、维修之类的小型平台。下面我会把项目的拆解思路、表结构、关键实现、运行步骤和实际踩坑记录完整写出来,希望能让拿到源码的同学少走几步弯路。
1. 项目定位与整体设计思路
1.1 为什么选家政服务这个场景
家政服务是个非常适合做管理系统的业务模型。它的核心实体不过就是用户、服务项目、订单、服务人员和评价,但订单会经历预约、派单、接单、完成、取消等多个状态,天然需要做状态管理。相比单纯的商品商城,家政平台更强调“人”的调度和线下履约,因此前后端要处理的不只是CRUD,还有权限区分、流程控制和状态同步。
我当时拿到这个场景时,第一反应是把它按角色拆开。普通用户需要浏览服务项目、生成预约订单、查看订单状态;服务人员需要查看自己被派到的订单、更新服务进度;平台管理员则需要维护服务分类、管理用户和订单、查看经营数据。这三个角色如果混在一个后台里,后期维护会痛不欲生。所以这个项目的权限模型从一开始就是基于角色做划分,而不是简单地给所有接口加一个登录校验。
1.2 角色划分与业务边界
这个系统一共有三种登录身份,对应三个不同的功能域:
- 普通用户:注册登录后可以浏览服务列表、按分类筛选服务、提交预约订单、填写服务地址和时间、查看订单详情、取消未开始的订单。
- 服务人员:由管理员在后台添加或用户通过平台申请入驻,登录后可以查看被分配给自己或者自己主动“抢单”的订单,并执行开始服务、确认完成操作。
- 平台管理员:拥有最高权限,可以管理服务分类、管理服务人员账号、审核订单、处理退款或取消请求,还能看到订单量、营业额、用户增长等统计图表。
这里有一个细节比较重要:服务人员的入驻方式。如果你做的是一个对外开放的C2C平台,服务人员注册后需要后台审核;但如果只是内部管理系统,服务人员账号通常由管理员直接创建。这个项目默认采用了管理员后台添加服务人员账号的模式,简化了审核流程,也让权限边界更清楚。后续想改成入驻审核模式,只需要额外加一个“待审核”用户状态即可。
1.3 技术选型:SpringBoot、Vue、MySQL为什么够用
这套系统没有引入过于复杂的微服务架构,而是坚持用最经典的单体应用方案。后端选用SpringBoot 2.x,因为它的自动配置和Starter机制可以大幅减少配置工作量;前端选用Vue 2.x配合Element UI,组件生态成熟,做管理后台和业务页面都很顺手;数据库使用MySQL 8.0,支持事务、外键和JSON字段,能覆盖系统的全部数据存储需求。
有人会问,为什么不用Vue 3和SpringBoot 3?从“可直接运行”的角度看,Vue 2 + Element UI的稳定性更好,网上资料多,遇到问题好查;SpringBoot 2.x的版本兼容性也相对宽松,无论JDK 8还是JDK 11都能跑起来。如果你后续想升级技术栈,这个项目的业务逻辑是松耦合的,替换前端或后端框架都不会伤筋动骨。
1.4 项目目录结构速览
拿到源码后,先看根目录下的dbsql文件夹,里面是完整的数据库脚本;然后是backend目录,是一个标准Maven工程,包名按controller、service、mapper、entity、config划分;再就是frontend目录,用Vue CLI生成,src下包含api、router、views、components、store等模块。这个分层对于学习阶段来说已经足够清晰,改起来也有明确路径。
2. 数据库设计与核心表结构
2.1 从业务需求到ER模型
家政管理系统的核心数据关系很简单:用户和服务人员都关联订单,订单关联具体的服务项目。但实际建表时,只建三五张表远远不够。还要考虑地址、评价、管理员操作记录、服务项目分类等辅助数据。
我建议画一下草图画清楚关系:服务分类是一级,服务项目挂在分类下面;用户下单后生成订单,订单包含服务项目ID、预约时间、联系人、联系电话、地址ID、服务人员ID;服务人员在完成服务后可以收到评价,评价表关联订单ID和用户ID。这个设计能防止后续加功能时不停地改表结构。例如以后想加优惠券,就只需要再加一张券表,并给订单加一个券ID字段即可。
2.2 核心表字段说明
整个数据库里最重要的表是user表、service_item表和orders表。先看user表,它需要同时容纳用户、服务人员和管理员三种身份,因此用role字段做区分,取值可以是0、1、2,分别对应管理员、服务人员、普通用户。密码字段存放的是经过BCrypt加密后的密文,不是明文,这一点必须注意,否则会有安全隐患。用户表里还包含手机号、头像、余额等字段,余额可以为后续的支付和结算功能打底。
service_item表记录家政服务项目,比如日常保洁、深度保洁、空调清洗、月嫂服务等。核心字段有分类ID、服务名称、封面图、价格、单位(按次或按小时)、销量和评分。价格用decimal类型,不要用float,因为涉及金额计算时浮点类型会出现精度误差。
orders表是整条业务链的重中之重。它的字段较多,我列出几个必须理解的:
- order_no:业务订单号,用时间戳加随机数生成,方便人工溯源。
- user_id:下单用户ID。
- worker_id:接单服务人员ID,派单前可为空。
- item_id:服务项目ID。
- appointment_time:预约上门时间。
- address_info:服务地址快照,下单时直接把用户填写的地址冗余到订单表里,防止用户之后修改地址导致订单信息不一致。
- status:订单状态,0待派单,1已派单,2进行中,3已完成,4已取消。
- amount:订单金额。
这些字段基本可以支撑整个下单履约流程。地址快照这一点很多人会忽略,等真正上线后用户改收货地址却发现老订单地址跟着变,就会意识到冗余字段有多好用了。
2.3 订单状态机设计
状态管理是家政系统最容易写乱的环节。一开始我处理订单状态时,直接用if-else在controller里判断,结果后面改动非常痛苦。后来把所有订单状态流转集中到一个状态机里,代码才清晰起来。
订单的最初状态是待派单,管理员在后台看到新订单后可以手动派单给服务人员;服务人员登录后看到待接单订单,也可以自己接单;接单后订单变成进行中;服务完成后服务人员点击确认完成,订单变为已完成;用户可以在待派单状态下取消订单,服务开始后则不能再取消。
这里要注意:取消订单的权限要区分。待派单状态下,用户和管理员都能取消;已派单但还没开始时,需要管理员确认后才能取消;进行中状态则不允许任何一方直接取消,需要走线下协商。这种状态约束应当在后端接口中统一校验,而不是只在前端按钮上做控制。
2.4 SQL脚本与初始化数据
项目里的SQL脚本不是只有create table,还包括admin账号、默认服务分类以及几条演示服务项目的数据。导入时直接执行整个SQL文件即可,后续自己调试可以往里面加测试订单。初始化数据的角色id和密码要注意看脚本注释,最好统一改成自己熟悉的账号密码。我测试时就把管理员账号改成了admin,密码重置为123456,这样每次登录都省事。
数据库字符集建议使用utf8mb4,因为订单地址、客户备注里可能出现特殊字符或表情,utf8mb4能完整存储。排序规则用utf8mb4_general_ci即可,不用去纠结更复杂的排序规则。
3. SpringBoot后端核心实现
3.1 启动类与全局配置
后端工程入口是一个标准的SpringBootApplication启动类,没有特殊代码。真正需要关注的是application.yml里的配置项,下面这份配置是整理后的精简版,可直接参考:
server: port: 8080 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/housekeeping?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai username: root password: 123456 jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT+8 mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl global-config: db-config: logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0几个关键点需要说明。url里必须带上serverTimezone=Asia/Shanghai,否则MySQL驱动启动时会报时区错误。jackson的日期格式化统一放在全局配置里,能避免接口返回给前端的时间格式多种多样。mybatis-plus的逻辑删除配置也很重要,这里实现了deleted字段软删除,所有删除操作都变成update,不会破坏历史数据。
3.2 统一返回结果与全局异常处理
任何管理系统的接口返回值都不应该一会儿是Map,一会儿是字符串,不然前端联调会痛苦死。项目里定义了一个R对象,核心结构是code、msg和data。比如登录成功返回code 200,数据取不到返回code 404,服务器异常返回code 500。前端axios拦截器只需要判断这一层code,然后统一处理错误提示即可。
同时我在项目里加了一个全局异常处理类,用@RestControllerAdvice标注,专门捕获业务异常和未知异常。这样controller里不需要到处写try-catch,业务代码看起来干净很多。后来联调时前端小伙伴反馈,接口报错信息统一之后,定位问题速度快了很多。
一个值得强调的是,不要把数据库原始异常直接抛给前端。比如插入订单时字段太长报错,如果直接把SQLException信息返回给用户,用户根本看不懂。全局异常处理里应当统一转成“系统繁忙,请稍后再试”这类友好提示,同时把详细异常堆打进日志文件里待查。
3.3 登录鉴权:JWT加拦截器
家政平台前后端分离,登录状态不依赖Session,而是用JWT。用户登录成功后,后端生成一个token,前端每次请求在请求头里带上Authorization: token,后端通过拦截器校验token中的用户信息。
实现上我用了一个简单的JwtUtil工具类,生成token时把用户ID和角色放进去,过期时间设置为24小时。拦截器在处理请求前先从请求头取出token,解析成功就放行,并把用户ID放到request的attribute里,后续controller就能取到当前操作人身份。
这里有三个实际的坑。第一个是登录接口和注册接口必须放行,否则用户还没登录就被拦截了。第二个是静态资源请求有时也会带token,如果前端把OSS等资源挂在同一个域名下,要额外配置放行路径。第三个是token过期时的处理,如果过期直接报401,前端axios拦截器收到401后应该跳转到登录页并清除本地token,这个联动机制要前后端一起约定好。
3.4 核心业务接口:下单、派单、接单、完成
下单是用户端的核心动作。前端提交订单时,后端会先校验服务项目是否存在且上架,再校验预约时间是否合理,然后把user_id、item_id、appointment_time、amount等数据写入orders表。这里有一个细节:服务价格不能完全相信前端传值,后端要根据服务项目表重新查一次价格,防止有人通过抓包把金额改成1元下单。我在这里吃过一次亏,后来在后端增加了一道价格的服务端校验,才彻底堵住漏洞。
派单接口是管理员操作,需要传入订单ID和服务人员ID。后端先判断订单状态是否为待派单,再判断服务人员是否存在,最后更新worker_id和status。这个接口建议加一个事务,防止订单部分更新成功导致数据不一致。
接单接口是服务人员侧操作,逻辑上是给订单设置worker_id,但要注意并发问题。如果多个服务人员同时抢同一单,简单的update语句可能被最后提交的人覆盖。我在项目里用了乐观锁方案,在orders表加了一个version字段,更新时带上where version = ?,版本不匹配则更新失败。虽然家政平台的并发量不会很大,但学习这套思路对以后处理秒杀、抢单类业务非常有帮助。
完成订单接口相对简单,服务人员点击完成只允许本人操作自己的订单,同时校验状态值,然后把status改成已完成。如果需要自动计算服务人员收入,可以在完成时把订单金额的某个比例累加到服务人员账户余额里,这个逻辑项目里保留了扩展位,我没有直接实现,是为了避免结算规则不清晰导致误判。
3.5 文件上传与本地存储
家政项目里服务项目需要封面图,用户和头像服务人员也需要上传图像。这个项目采用的是本地存储方案,后端接收MultipartFile后,把文件保存到项目下的upload目录,并返回一个可访问的URL路径。简单实现的关键代码大致如下:
String fileName = System.currentTimeMillis() + "_" + file.getOriginalFilename(); String filePath = uploadDir + File.separator + fileName; File dest = new File(filePath); file.transferTo(dest);本地存储适合开发和演示,但如果要上线,就必须换成对象存储或云存储,否则服务器重启或者磁盘空间不够都会导致图片丢失。我在项目的配置类里做了静态资源映射,把/upload/**路径映射到本地upload目录,这样前端直接访问图片URL即可。
4. Vue前端页面与交互设计
4.1 前端工程结构与路由设计
前端的src目录采用常规结构:api目录集中存放接口请求方法,router目录定义路由表,views目录存放页面组件,store目录用Vuex管理用户信息。这种分层的好处是,当你需要修改某个页面的逻辑时,不需要去其他目录里翻找,各自边界清晰。
路由设计上,这里是分模块处理的。基础路由包含登录页、注册页和首页;用户模块下是一系列需要用户权限的页面,比如下单页和订单列表;人员模块下是服务人员工作台;管理模块下是管理员后台页面。路由守卫里会读取本地缓存的用户角色,如果不匹配就跳转登录页。注意,前端路由守卫只能改善体验,真正安全校验还是要靠后端接口拦截,因为前端路由是可以被绕过的。
4.2 Axios封装与请求拦截
项目里的request.js是axios的二次封装。我设置了baseURL为后端接口地址,请求超时时间设为10秒。请求拦截器会从localStorage里取出token,加到请求头中。响应拦截器根据后端返回的code做统一处理:成功就返回data,业务异常就触发轻提示,token失效就清除本地用户信息并跳转登录。
这里要提醒一下,开发环境下前后端如果不同端口,会产生跨域问题。解决方式有两种,一种是在后端写CORS配置,另一种是在前端vue.config.js里配置devServer的proxy代理。我推荐用proxy方式,因为它可以让前端请求路径保持一致,部署到生产环境时也不需要改动axios的baseURL。
在vue.config.js里配置大概是这样:
devServer: { proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true } } }之后所有请求都以/api开头,后端controller的requestMapping路径里也统一加了/api前缀,这样本地联调不用操心跨域,部署到Nginx后反向代理也容易配置。
4.3 用户端核心页面拆解
用户端首页展示服务项目,左侧是分类列表,右侧是项目卡片。点击卡片进入项目详情页,详情页展示封面图、价格、销量和服务介绍。预约下单页则是一个表单,包含联系人、联系电话、地址、预约时间、备注信息。表单提交前会用Element UI的规则校验手机号格式和必填项,保证脏数据不会进入后端。
订单列表页面我觉得是整个前端里最需要注意的。它需要根据订单状态显示不同的操作按钮,待派单时用户能取消,进行中时只能查看进度,已完成时可以写评价。按钮的显隐逻辑不能写死在前端,而应该根据后端返回的状态字段动态渲染。这样即使后续后端新增了订单状态,前端也能自动适配,不用每个状态都去改一遍模板代码。
4.4 服务人员和管理员页面
服务人员端页面相对简洁:工作台展示待接单列表和我的订单列表。待接单列表显示订单概要信息和接单按钮;我的订单列表则可以根据状态切换,进行中的订单可以点“开始服务”和“确认完成”。为了直观展示订单处理的情况,服务人员端还加了订单数、待处理数、完成数的小统计卡片。
管理员后台是另一个工程量大头。项目里设置了多个管理页面:服务项目管理、服务分类管理、用户管理、订单管理、数据看板。数据看板是用ECharts画柱状图和折线图,统计最近7天的订单量、营业额和新增用户。这个页面非常适合用来展示整条链路的数据汇总,因为图表数据都来自后端聚合查询接口,而不是前端自己算。
做后台页面时,table组件几乎都离不开分页、搜索、状态筛选和操作列。我建议把常用的表格逻辑封装成公共组件,比如把搜索表单、分页参数和刷新函数都放到一个通用mixin里,后期新增一个管理页面就能省不少事。
5. 项目运行与部署全流程
5.1 环境版本匹配建议
这套项目能在常见的开发环境里直接跑,但我还是要先把版本对应关系说清楚,避免不同环境下的奇怪问题。
- JDK:推荐JDK 8,最低要求是1.8,不推荐用太高版本的JDK,因为某些老版MyBatis Plus在JDK 17下容易遇到反射相关警告甚至报错。
- Maven:3.6以上即可,编译时需要联网下载依赖,如果网络不好,可以配置阿里云镜像。
- Node:推荐14.x或16.x,Vue CLI和node-sass在更高版本node下可能会有兼容问题。
- MySQL:推荐8.0,SQL脚本里如果使用了utf8mb4和最新驱动,MySQL 5.7也能兼容,但建议直接上8.0和项目保持一致。
启动顺序应该是先导入数据库,再启动后端,然后启动前端。后端的8080端口不能被占用,前端的devServer默认端口是8081,如果被占用可以修改vue.config.js里的port。
5.2 数据库导入步骤
打开MySQL客户端,执行以下操作:
- 创建数据库housekeeping,字符集utf8mb4。
- 使用该数据库。
- 执行源码中的housekeeping.sql脚本。
执行命令其实很简单:
mysql -u root -p create database housekeeping default character set utf8mb4; use housekeeping; source /path/to/housekeeping.sql导入完成后可以检查一下表数量,如果表数量对不上,多半是脚本中间某条语句报错,需要从错误位置往前排查。SQL脚本里每个建表和插入语句我建议都加上DROP TABLE IF EXISTS,这样脚本可以反复执行,不会因为重复导入而报错。
5.3 后端启动过程
用IDEA打开backend目录,等待Maven下载依赖完成后,修改application.yml里的数据库账号密码为本地实际值。找到主启动类运行,看到如下日志基本就是启动成功:
Tomcat started on port(s): 8080启动失败的常见情况有两种。一种是数据库连接失败,检查url里的数据库名和账号密码是否正确;另一种是依赖没下载完整,比如本地Maven仓库里缺少某些jar包,这时可以执行mvn clean install重新拉取。后端启动后,可以在浏览器访问http://localhost:8080/api/user/list,如果返回401或JSON错误,说明接口服务和拦截器都正常工作了。
5.4 前端启动过程
用终端进入frontend目录,先安装依赖:
npm install如果安装速度慢,可以使用国内镜像。安装完成后执行:
npm run serve看到编译成功日志后,访问http://localhost:8081。此时如果使用代理模式,前端页面上的接口请求会自动转发到8080端口。我第一次启动时遇到node-sass编译失败,解决方法很简单,把package.json里的node-sass换成sass,然后重新npm install,顺便用sass替代了原来的编译依赖。
5.5 常见启动报错与修复
我整理了一份问题速查表,几乎覆盖了初跑项目时最容易遇到的情况。
| 现象 | 可能原因 | 解决方法 |
|---|---|---|
| 启动后页面白屏 | 前端依赖没装完或路由配置错误 | 查看npm run serve的控制台报错,重新安装依赖 |
| 登录接口返回401 | 拦截器放行路径配置不正确 | 检查WebConfig中排除登录、注册等无需认证路径 |
| 接口返回乱码 | 后端编码或数据库字符集不对 | 确认application.yml中字符集和数据库utf8mb4设置 |
| 下单提示时间格式不对 | 前端传的时间格式和后端不匹配 | 统一使用yyyy-MM-dd HH:mm:ss格式 |
| 上传图片后访问404 | 静态资源映射没配置或upload目录不存在 | 创建upload目录,检查资源映射配置类 |
| 头像上传成功但列表不刷新 | 前端没有重新拉取列表 | 成功后调用列表接口并更新当前页 |
这张表是排查过程中总结出来的,基本能解决90%的启动问题。剩下10%大概率是端口占用或数据结构变更后没重新导入SQL,清理后重启即可。
6. 踩坑记录与扩展建议
6.1 跨域配置背后的细节
开发时我一开始在后端配置了全局CORS,允许所有来源访问,后来发现服务人员上传头像时一直失败,原因就是请求头里带了自定义的Authorization字段,CORS不允许这个请求头。于是我把allowedHeaders改成*,才最终通过。但生产环境这么放开风险很大,稳妥做法是用代理或配置精确的允许来源。
6.2 Vue2与Element UI的组件版本坑
Element UI如果版本太旧,table列宽显示会异常;如果升级到2.15版本,部分写法又有细微差别。如果你拿到的源码用的是人工锁版本,不要在package.json里随意改动依赖版本。我遇到过升级Element UI后表单校验组件报错的问题,最后回滚版本才恢复正常。所以项目里我特意没有写“latest”依赖,全部锁定精确版本,保证可直接运行。
6.3 订单金额的计算和展示
订单金额在数据库是decimal类型,在后端用BigDecimal接收,在前端展示时需要toFixed(2)来保留两位小数。如果你用JavaScript的parseFloat做计算,会出现0.1加0.2不等于0.3的经典问题。我在项目总结里反复提醒自己,金额相关的任何计算都必须在后端完成,前端只负责展示。
6.4 如何在这个基础上继续扩展
这套系统完成之后,我给它规划过几个扩展方向。如果直接拿去二次开发,可以优先加这几个模块:
- 支付模块:对接微信或支付宝支付,在订单完成按钮前插入支付回调流程,支付成功后再修改订单状态。
- 消息通知:引入WebSocket或短信服务,用户下单后实时通知服务人员和管理员,比手动刷新订单列表体验好很多。
- 数据统计:当前看板只有订单和用户统计,可以增加服务人员收入排行、服务项目热力分析、投诉率监控等指标。
- 多门店网点:如果要把平台做成覆盖多个小区或城市的模式,可以在订单表中增加网点ID,并把服务人员表和网点关联起来。
这些扩展并不需要推翻现有代码,只要保持现有表结构和接口风格,按同样的模式继续添加模块就好。不要一开始就想着重构技术架构,先把业务跑通、数据闭环完善,后续再谈性能优化和分布式改造更有意义。
我从实际运行这套系统的过程中最大的体会是,一个项目能不能“直接运行”,考验的不是功能多不多,而是细节处理到不到位。数据库脚本是否完整、配置文件是否清晰、依赖版本是否锁定、接口返回是否统一,这些都决定了别人拿到手之后会不会在第一步就卡住。如果你准备把这份代码作为你的课设作品或简历项目,我建议你先自己完整地把从数据库导入到前后端跑通的流程走两遍,再顺手把接口文档整理出来。相信我,这个过程比写代码本身更能让你理解一个全栈项目的全貌。