资讯动态

SpringBoot+Vue实现家政服务上门预约系统全栈开发实战

发布时间:2026/9/15 4:28:52 来源:尧图企业网站定制
接手“springbootvuejava家政服务上门预约系统”这个项目的时候我刚从纯后端开发转向全栈没多久。说实话这类系统在毕设和外包项目里非常常见业务不复杂但五脏俱全——有用户端、服务端、管理端有预约流程、订单流转、评价体系还要处理服务人员排期这类偏实际的调度问题。这篇文章我会完整复盘这个项目的设计与实现把我踩过的坑和最后稳定的方案一并聊透。如果你正在做类似的Java全栈项目或者想了解SpringBootVue这套组合在实际业务里怎么落地这篇文章应该能帮你省不少走弯路的时间。1. 家政上门预约系统的核心业务与需求拆解1.1 家政服务场景的特殊性家政上门预约和我们平时做的电商系统很不一样。电商是标准化的“选品→下单→支付→发货”而家政服务是典型的非标服务服务人员的技能分保洁、育儿、养老护理等方向服务时长按小时计服务地址在城市里分散服务质量高度依赖具体的人。这就决定了系统不能只做简单的CRUD而是要围绕“人、时间、地点、服务项”四个维度去建模。我第一次梳理需求时把角色分成了三类用户下单方、服务人员接单方、管理员平台运营方。用户关心的是“什么时间能约到什么样的人”服务人员关心的是“我的排班和实际结算”管理员关心的是“订单量和平台服务质量”。三方的诉求既有交集又有冲突比如用户希望服务人员越快到越好但服务人员有固定的排班和接单上限。1.2 项目功能模块盘点根据上面的角色分析我把系统功能拆成了三个端用户端注册登录、浏览服务项目、按时间和地址筛选可预约的服务人员、下单、在线支付如果接入、查看订单状态、取消订单、评价已完成的订单。服务端服务人员端查看排班与已接订单、接单/拒单、标记上门与完成、查看历史收入和结算明细。管理端用户管理、服务人员入驻审核、服务项目管理、订单全流程监控、评价管理、数据统计订单量、营收、热门服务项。这里要说明一点家政预约的核心难点不在某个单独的功能而在“同一时间、同一服务人员不能重叠接收订单”这个约束。这不是一个普通的CRUD能解决的需要合理的数据库设计和并发控制后面我会细讲。1.3 业务流程闭环设计我用一条主线把整个系统的业务流程串起来用户选择服务项目 → 选择期望的服务日期和时间段 → 系统推荐在此时段空闲且有该服务能力的服务人员 → 用户确认下单 → 服务人员接单或系统自动派单 → 服务人员按约定时间上门 → 服务中/完成 → 用户确认并评价 → 平台完成资金结算简化场景下可不接入真实支付。这条链路里最关键的环节是“推荐空闲服务人员”。如果系统不做这个筛选用户只能盲选然后客服在后台协调这就失去了线上化的意义。所以我在设计时把这个环节做成了系统的核心算法逻辑先根据服务项目和地域圈定候选人员集合再通过该人员该时间段是否已有重叠订单来过滤。2. 技术选型为什么是SpringBootVueJava2.1 后端选型SpringBoot的优势后端技术栈选用SpringBoot是权衡了开发效率、生态成熟度和学习成本后的结果。SpringBoot基于Spring框架但省去了大量的XML配置内嵌Tomcat可以通过java -jar直接启动。开发一个RESTful API服务你只需要建一个SpringBootApplication主类加上必要的RestController和Service项目就能跑起来。从项目本身的需求来看它需要连接MySQL、做权限认证、处理文件上传、定时任务这些SpringBoot都有对应的starter可以引入基本零配置就能集成。我用过的组合是SpringBoot 2.7.x MyBatis-Plus MySQL 8.0 Redis缓存与分布式锁场景 JWT登录鉴权。这套组合很成熟问题排查时网上的资料也最多。一个建议如果你做类似项目用于毕设不建议追求最新版本的SpringBoot 3.x。3.x基于Jakarta EE规范部分老教程的javax.*包名写法直接不兼容网上多数资料和视频还停留在2.x时代。选2.7.x这个稳健版本遇到问题时更容易找到答案。2.2 前端选型Vue的工程化优势前端选择Vue最大的理由是组件化和渐进式开发。家政系统的前端页面虽然不超过二十个但表单校验、日期选择、状态标签、分页表格这类交互非常重复。Vue的组件系统让我把这些通用逻辑封装成一个个独立的组件比如封装一个带时间冲突检测的预约时间选择器在用户端和管理端复用开发和维护成本低一大截。我使用的是Vue 2 Element UI用Vue 3 Element Plus也完全可以原理一样。状态管理用的Vuex路由用的Vue Router。构建工具方面Vue CLI创建项目最简单适合中小型项目如果机器性能较好、追求启动速度也可以用Vite。我自己用Vue CLI不是因为Vite不好而是这个项目里我需要修改一些webpack配置来解决代理和打包问题Vue CLI的配置方式我更熟。2.3 前后端分离架构与部署方式这个项目是典型的前后端分离架构后端只提供RESTful API不关心页面渲染前端通过Axios调用接口拿到JSON数据后在浏览器端渲染。这样做的直接好处是前后端可以并行开发并且将来如果做小程序端或App端后端API可以直接复用。部署时我采用了Nginx托管前端静态文件 反向代理后端接口的方式server { listen 80; server_name your-domain.com; # 前端静态文件 root /home/www/dist; index index.html; # 将所有 /api 请求转发到后端 location /api/ { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }这种方式把前后端端口隔离浏览器永远只需要访问80端口不会遇到跨域问题前端开发时用Vue的devServer代理解决跨域部署后由Nginx解决。3. 数据库设计与核心表结构3.1 核心表梳理与字段设计数据库设计我严格按照业务需求来不提前引入无关表。整套系统我设计了11张表核心的六张表如下表名用途关键字段user用户表包含用户、服务人员、管理员的公共账号信息id, phone, password, role, nickname, avatarservice_worker服务人员信息扩展表id, user_id, real_name, service_type, service_area, rating, total_orders, statusservice_item服务项目表id, item_name, item_desc, base_price, unit_price, iconworker_item服务人员技能绑定表id, worker_id, item_id, price, levelorder订单表id, order_no, user_id, worker_id, item_id, service_date, start_time, end_time, address, amount, status, create_timerating评价表id, order_id, user_id, worker_id, content, score, reply这里重点说下用户表和订单表。用户表虽然只有一张但通过role字段区分了三种角色0用户1服务人员2管理员服务人员特有的信息放到扩展表里。这样设计减少了账号登录的复杂度——所有人都走同一个登录接口只是登录后根据角色返回不同的菜单和数据权限。订单表的status字段我用了整数枚举0待接单1已接单2服务中3待评价4已完成5已取消6申请退款。用整数的好处是前端的switch-case逻辑清晰后端判断状态流转时也方便。3.2 时间窗口冲突检测的实现家政预约里最核心的数据库问题是怎么判断一个服务人员在某段时间内是否已有订单。如果只查“是否有重叠订单”SQL很直观SELECT COUNT(*) FROM order WHERE worker_id #{workerId} AND status NOT IN (5, 6) -- 排除已取消和已退款 AND service_date #{serviceDate} AND start_time #{endTime} AND end_time #{startTime}这条SQL的本质是判断两个区间是否有交集一个区间是现有订单的[start_time, end_time]另一个是待插入的[startTime, endTime]。两个区间有交集当且仅当旧开始 新结束且旧结束 新开始。不用背公式画一条时间轴就一目了然。但光有这条SQL还不够。如果两个用户刚好同时提交了同一服务人员、同一时间段的订单会存在典型的并发覆盖问题。两个请求同时查到“没有重叠订单”然后同时插入就出现了同一个人的时间被约了两次。我的解决方案是双保险在订单表上增加worker_id service_date start_time的联合唯一索引前提是业务上不允许同一个人在同一日期同一个开始时间有两笔有效订单。事务隔离级别设为读已提交插入订单前先使用SELECT ... FOR UPDATE锁定该服务人员的记录保证判断与插入的原子性。3.3 定时任务超时未接单自动取消用户下单后如果不指定服务人员系统进入待接单状态。我设置了一个规则30分钟内没有服务人员接单订单自动取消并释放该时间段。这个功能用SpringBoot自带的Scheduled注解就能实现。Scheduled(fixedRate 60000) // 每60秒扫描一次 public void cancelTimeoutOrders() { LocalDateTime deadline LocalDateTime.now().minusMinutes(30); ListOrder timeoutOrders orderMapper.selectByStatusAndCreateTime(0, deadline); for (Order order : timeoutOrders) { order.setStatus(5); // 已取消 orderMapper.updateById(order); } }有些教程会让每30秒扫一次其实没必要那么频繁60秒一次完全够用。真正要做到毫秒级变更就得用Redis的延迟队列或者RabbitMQ的延迟消息但家政预约这个场景对时间不敏感数据库扫描方案最简单也够用。4. SpringBoot后端核心实现细节4.1 统一响应体与全局异常处理后端接口我统一返回了一个ResultT结构包含code、message、data三个字段。这样前端拦截器可以根据code统一判断code为200表示成功401表示未登录500表示服务器异常。所有Controller的返回值都封装成这个结构避免前端对接时每写一个接口都要单独解析JSON。Data public class ResultT { private Integer code; private String message; private T data; public static T ResultT success(T data) { ... } public static T ResultT error(Integer code, String message) { ... } }配合RestControllerAdvice做全局异常处理把业务异常、参数校验异常、未知异常统一拦截下来返回规范的JSON而不是一股脑返回500。这对前后端联调和线上排查问题特别重要。4.2 JWT登录鉴权与角色权限控制登录鉴权我选了JWTJSON Web Token而不是传统的Session。原因很简单前后端分离后后端可能同时服务Web端和未来的小程序端Session共享很麻烦JWT把用户身份信息加密放在Token里后端只需要验签不用查库天然适合分布式环境。处理逻辑是用户登录成功后后端用HMAC SHA-256算法生成Token里面存userId和role设置7天有效期前端把Token存在localStorage里每次请求在Axios拦截器中添加Authorization: Bearer token后端写一个拦截器或Spring Security的过滤器校验Token合法性并解析出当前用户信息放入ThreadLocal中后续业务代码直接通过UserContext.getUserId()获取当前用户。角色权限控制我用了一个简单但有效的方案在需要管理员权限的Controller方法上加上自定义注解RequireRole(admin)在拦截器里读出JWT中的role字段做匹配。家政系统的角色少这种硬编码校验方式比引入Spring Security的一整套复杂配置更直观也更好排查权限问题。4.3 文件上传服务人员实名资质提交服务人员入驻时需要上传身份证照片和服务资质证明。文件上传我直接用了SpringBoot自带的MultipartFile支持没有额外引入OSS。文件存储位置是服务器的/home/www/uploads/目录通过Nginx静态映射来访问location /uploads/ { alias /home/www/uploads/; }这里有一个值得注意的坑文件大小默认限制是1MB上传身份证照片时很容易超限。必须在配置文件中显式加大spring.servlet.multipart.max-file-size10MB spring.servlet.multipart.max-request-size20MB上传文件重命名时我用UUID.randomUUID()作为新文件名保留原始文件后缀。这样既避免文件名冲突也避免中文文件名可能导致的编码问题。4.4 接口设计预约下单的完整链路一个完整的预约下单接口不是简单地在订单表插入一条记录。我在OrderService.createOrder()里做了四件事校验用户提交的参数服务项目是否存在、地址是否填写、时间是否合理。校验服务人员是否存在且在当前时段空闲走前面说的冲突检测SQL。计算订单金额——基础价格加上超出时长的加价如果用了优惠券还要扣减。插入订单记录状态设为待接单并返回订单号给前端。订单号我用了“日期随机数”的格式20250607120345 4位随机数。不用数据库自增ID的原因是订单号会展示给用户自增ID容易暴露平台单量也容易被人遍历爬取。5. Vue前端实现与联调要点5.1 前端项目结构与路由权限控制前端的项目目录我用的是Vue CLI默认生成的src结构内部稍作调整src/ ├── api/ // 按模块拆分的接口调用文件 │ ├── order.js │ ├── user.js │ └── worker.js ├── router/ // 路由配置 ├── store/ // Vuex状态管理 ├── views/ // 页面级组件 │ ├── user/ │ ├── worker/ │ └── admin/ ├── components/ // 通用组件 └── utils/ // 工具函数axios封装等路由权限控制是前端一个容易遗漏的重点。我的做法是在router.beforeEach导航守卫里读取Vuex中的用户角色按角色动态拼接可访问的路由表。比如管理员访问/admin路径而普通用户访问时直接重定向到首页并提示无权限。5.2 Axios封装与Token失效处理Axios封装如果写得好前后端联调阶段能省下大量重复工作。我统一在request.js里做了三件事设置baseURL为/api开发环境由Vue CLI代理转发到后端请求拦截时自动携带Token响应拦截时统一处理错误码。axios.interceptors.response.use( response { const res response.data if (res.code 401) { // Token过期清空用户信息并跳转登录页 store.commit(clearUser) router.push(/login) return Promise.reject(new Error(登录已过期)) } return res }, error { // 处理网络错误和HTTP错误 } )特别提醒后端返回的HTTP状态码不一定要用401。我在后端全局异常处理中用的策略是业务异常统一返回HTTP 200 code字段标记状态只有网络层或未知异常才返回HTTP 500或404。这样做的好处是前端拦截器逻辑简单不需要区分业务失败和网络失败。5.3 预约时间选择器的实现预约时间选择器是用户端交互最复杂的组件。用户先选服务日期再选时间段比如上午9:00-11:00然后系统返回该时间段可用的服务人员列表。我在前端用了Element UI的DatePicker限制只能选三天后的日期给自己留出派单时间时间段用固定选项如9:00-11:00、14:00-16:00等避免用户随意输入造成的解析困难。时间槽位的可用性筛选要调后端接口传入日期、开始时间、结束时间、服务项目ID后端返回候选服务人员列表。前端再把这个列表渲染成卡片展示头像、评分、服务次数和价格用户点击确认后就带着sellerId跳转到确认下单页。5.4 状态更新与前端交互前端展示订单状态时我用了一个orderStatusMap对象把后端的整数状态映射为中文标签和Element UI的Tag类型statusMap: { 0: { text: 待接单, type: warning }, 1: { text: 已接单, type: primary }, 2: { text: 服务中, type: info }, 3: { text: 待评价, type: success }, 4: { text: 已完成, type: success }, 5: { text: 已取消, type: danger } }用户端看到可操作的按钮也是基于状态条件渲染的待接单状态显示“取消订单”按钮待评价状态显示“去评价”按钮已完成状态显示“查看评价”按钮。服务人员端则在已接单状态显示“开始服务”、“确认完成”按钮。前后端状态机必须完全一致否则会出现前端显示但后端拒绝操作的bug。6. 常见问题与排查技巧实录6.1 跨域问题与Vue开发代理开发阶段最常见的问题是跨域。前后端分离后前端跑在localhost:8081后端跑在localhost:8080浏览器请求后端接口时肯定跨域。我的解决办法是在Vue CLI的vue.config.js里配置devServer代理module.exports { devServer: { proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } } }前端代码里请求URL统一写成/api/order/list代理会把请求转发到http://localhost:8080/order/list后端接口映射不要把/api也写进后端RequestMapping。后端就不需要配置CORS跨域支持了因为浏览器看到的请求是同源的——这是最干净、最推荐的开发模式。6.2 SpringBoot版本过高引发的启动失败我最初在本机装了SpringBoot 3.2.x结果连MyBatis-Plus都启动不了。原因是MyBatis-Plus 3.5.x之前的老版本底层用了javax.annotation包而SpringBoot 3.x只有jakarta.annotation直接报NoClassDefFoundError。这个问题是springboot版本太高这个坑最典型的体现。解决办法有三个一是把SpringBoot降级到2.7.x二是升级MyBatis-Plus到适配Jakarta的版本三是手动引入缺失的javax.annotation-api依赖。我实测下来对学习项目来说降级SpringBoot到2.7.x是最快最稳的路不需要为了新而新。6.3 Vue打包后布局异常前端开发环境运行正常但执行npm run build后用Nginx部署发现页面布局错乱、图片加载不出来——这个问题我也踩过。原因有两个第一是vue.config.js里没设置publicPath导致打包后静态资源引用的是绝对路径/js/app.js而部署在子路径时找不到文件第二是CSS中引用的背景图片路径这里的资源没有被正确转换。解决办法是在vue.config.js中设置publicPath: ./这样打包后资源全部走相对路径不管部署在哪个子目录都能正常加载。还有一个相关坑Vue Router如果使用history模式部署后刷新页面会出现404需要Nginx配置try_files $uri $uri/ /index.html回退到前端入口文件。这套Nginx配置location / { try_files $uri $uri/ /index.html; }6.4 数据库时间差8小时问题这种带预约场景的项目时间字段的准确性极其重要。我第一次联调时发现后端插入的create_time和service_date看起来正常但订单详情页显示的时间比实际迟了8小时。排查后确认是MySQL连接串缺少serverTimezone配置或使用了错误的时区。我最终的MySQL连接串写法jdbc:mysql://localhost:3306/home_service?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai同时在Java代码里统一用LocalDateTime处理日期时间不用已过时的java.util.Date。前端用dayjs格式化展示订单时间为2025-06-07 14:00:00不转来转去也就不容易出错。6.5 并发重复订单的规避实测刚才提到并发场景下要防止同一服务人员同一时间段被重复预约。我在测试阶段用JMeter模拟100个并发请求抢同一个服务人员的同一时段第一次测试确实复现了重复订单——因为两条事务同时查到了空档然后同时插入成功。加上SELECT ... FOR UPDATE锁后后进入的事务会等待前一个事务提交然后重新查询发现时段已被占用返回“该服务人员当前时间段已被预约”提示。这个问题的本质是数据库层需要保证“检查与插入”操作的原子性。如果不用锁就必须在代码层面加分布式锁比如Redis的setnx但对于单体应用数据库行锁已经足够。7. 项目扩展方向与一些个人心得系统做完之后我其实又回头做了两轮重构。第一轮是把所有Mapper里的复杂SQL检查了一遍不必要的联表查询拆成简单查询代码组装第二轮是把前端订单列表的分页和筛选逻辑封装成通用组件。整个过程下来我对SpringBootVue这套组合的理解比看十遍教程都深刻。如果你正在做类似的项目我的建议是先花一整天把业务流程图和数据表结构彻底理清再动手敲代码。很多所谓“做不出来”的问题根本原因是业务梳理不清而不是技术不会。比如预约冲突检测想清楚“区间重叠”的判断条件SQL十分钟就能写出来想不清楚写一天也是在瞎试。另外要提一句家政上门预约这类项目的价值在于业务闭环完整非常适合用来练手前后端分离开发。做完它SpringBoot的接口开发、参数校验、全局异常处理、定时任务Vue的组件通信、路由守卫、状态管理、打包部署这些全栈开发的硬骨头基本都过了一遍。后续如果还有精力可以尝试接入微信小程序端复用后端API或者加上地图选地址功能——后端基本上不需要改动这也是当初选前后端分离架构最划算的地方。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价