首页 / 资讯中心 / 文章详情

SpringBoot+Vue社区家政预约平台:从状态机设计到上线交付全解析

SpringBoot+Vue社区家政预约平台:从状态机设计到上线交付全解析 ★ FEATURED ARTICLE
前阵子陆续有几位读者来问同一个需求想做一个社区家政服务预约平台技术栈限定springbootvue还要带源码、文档最好能帮忙调试、改改基础功能、答疑。我一开始以为只是随便问问后来发现这个问题背后其实是完整的一类项目诉求——既要能通过课程设计或毕业设计的验收又要真正跑起来给小区物业、中小家政公司内部试用。顺手把我的经验整理成一篇从需求设计、数据库、后端、前端到联调交付尽量说全说透希望能帮你省下几周摸坑的时间。1. 为什么社区家政预约平台选择了springbootvue这条技术路线社区家政服务的典型场景大家都不陌生保洁、家电清洗、保姆月嫂、管道维修这类服务过去靠电话预约或者微信群接龙漏单、撞单、爽约经常发生想要查某个订单的历史记录比翻聊天记录还难。一个预约平台要做的就是三件事业主能在线看服务、选时间下单家政人员能接单和确认完成平台方能审核上架和做基本的数据统计。技术选型上springbootvue几乎是最稳妥的组合甚至可以说是这类管理型Web项目的默认答案。先说后端。SpringBoot把配置简化到了极致一个启动类加一个application.yml就能跑起来内嵌Tomcat连部署都不用额外装。配合MyBatis-Plus这类国产增强框架单表增删改查基本不用写SQL代码量可以压到很低。对大多数学生项目来说不需要上微服务、消息队列这些重型中间件SpringBoot单体能扛住几百人的小区使用量也足够答辩演示。再说前端。Vue的数据绑定和组件化思想对新手来说上手门槛远低于React。配合Element Plus组件库表单、表格、弹窗、时间选择器这些后台管理页面的常规组件全都现成一个上午就能把页面框架搭出来。加上Vue生态的成长路线很平滑后面想升级做小程序端、或改成移动端H5核心逻辑都能复用。还有一个很现实的原因遇到问题时能搜到的资料率很高。SpringBoot的接口如何写、JWT怎么集成、Element Plus怎么用、Vue路由守卫怎么写几乎每个坑都有人踩过、有人写博客讲清楚。对一个以完成项目为主要目标的开发者来说这比任何技术先进性都重要。实际上这几年我接触到的家政类、预约类、社区服务类项目超过一半都采用了这套组合跑通率也最高。选它不一定最惊艳但一定最不容易翻车。2. 业务闭环先于代码订单状态机的设计决定了系统上限很多开发者拿到项目需求第一件事就是打开数据库建表恨不得一个中午把表建完。但如果家政预约平台也这么干后面大概率要做大量返工。原因很简单这类系统的核心不是服务分类的增删改查而是订单从创建到完成的整个生命周期。生命周期里的状态怎么流转、每个状态下谁能做什么操作、不允许跳转哪些状态这些不做清楚接口写出来一定前后矛盾。理清业务闭环我建议先守住三张角色图和一条主流程线。三个角色分别是客户下单的人、家政人员接单干活的人、管理员平台运营的人。客户的操作路径是注册登录、浏览服务分类、查看服务详情、选择预约日期时间、填地址与联系人、提交订单、等待确认、服务完成后评价。家政人员的路径是登录后查看被分配或者被点单的预约、确认接单、按时上门、服务结束后确认完成。管理员路径是审核服务项目是否上架、审核家政人员入驻资质、查看订单列表处理异常单、查看基础数据看板。主流程线就是一条订单状态机。我实际项目里的状态取值是这样的状态值名称触发动作0待确认客户提交预约订单1已确认家政人员接单或管理员后台确认2服务中家政人员点击开始服务3已完成家政人员点击完成服务4已取消客户取消或管理员取消5售后中客户发起投诉或退款申请状态机的核心原则是每次状态变更只能从当前状态跳转到合法状态不允许跳级不允许逆向特殊情况除外比如管理员强制取消。例如待确认状态可以由客户自己取消变成已取消但一旦家政人员接单变成已确认就不能静默取消必须走退款或售后流程。这个规则在代码里必须显式定义而不是靠每个接口各自做一个if判断。为方便讲解订单状态机的设计值得前置到建表之前。先画状态转换表再对着表写接口逻辑会清晰很多。通常我建一个常量类专门管理状态值不让魔法数字散落在业务代码里。3. 后端落地细节分层结构、登录鉴权与统一返回格式3.1 三层结构怎么拆才清晰后端代码结构不必花哨但一定要分层明确。我习惯拆成这样controller只接收参数、调用服务、返回统一结果不在里面写业务逻辑service负责业务逻辑比如下单校验订单状态、校验服务时间冲突mapper继承BaseMapper负责数据库交互entity与表字段一一对应的实体类dto / vo入参对象和出参对象避免把实体类直接暴露给前端有人图省事直接在controller里写一堆业务代码项目小的时候还能跑但只要加上预约时间是否可选工人当天是否已经被约满这些判断controller会迅速膨胀到几百行。分层的好处不是代码好看是后续排查问题和改需求时能快速定位。比如面试官或老师问订单状态这个逻辑在哪里控制你能直接说出service.order.status里的checkAndUpdate方法这就是说服力。3.2 JWT登录和角色权限用一套账号表顶三代家政平台涉及三种角色的登录。很多新手会建三张表user表、worker表、admin表然后分别写三套登录接口。这个设计维护成本很高而且三种角色其实共享账号密码、手机号、头像这些基础字段。我更推荐一张account账号表加一个role字段区分角色值分别为USER、WORKER、ADMIN。家政人员或管理员本质上都是account扩展一个worker_info表存他们的专属资料即可。登录接口返回JWT令牌前端把令牌存localStorage每次请求带上Authorization头。后端写一个登录拦截器对需要登录的接口校验令牌并从令牌中解析出用户id和角色。这里提个建议如果项目没有强制要求不必上Spring Security。它虽然功能全但概念多、配置重对这类系统属于杀鸡用牛刀。用拦截器配合角色判断代码简单、运行效率高讲起原理也更容易自圆其说。3.3 统一返回与全局异常别让报错暴露给用户前后端联调时最怕的就是接口返回结构不统一。有的接口返回true有的返回操作成功前端取数据时要针对每个接口单独处理极容易出bug。建议定义统一返回类Resultpublic class ResultT { private Integer code; private String message; private T data; public static T ResultT success(T data) { ResultT result new Result(); result.code 200; result.message success; result.data data; return result; } public static T ResultT error(Integer code, String message) { ResultT result new Result(); result.code code; result.message message; return result; } }同时写一个全局异常处理器把参数校验异常、业务异常、兜底异常统一转成Result返回这样即使代码某个位置漏了处理前端也只会看到规范化的JSON而不是一长串堆栈信息。配合自定义异常类BizException在service里随时抛出业务错误比如该时间段已被预约前端直接弹message提示即可。后端接口里订单相关的操作我建议刻意取语义清晰的动作词createOrder对应下单confirmOrder对应接单cancelOrder对应取消completeOrder对应完成。动词式接口配合状态机整个调用链会非常直观。4. 数据库表这样设计后续改需求才不会推倒重来4.1 七张核心表的职责划分家政预约平台不算复杂但至少需要七张核心业务表。一张表对应一个清晰领域概念这能让后期改造的维护成本大幅降低。account表账号公共信息username、password、phone、role、nickname、avatar、statusaddress表客户的收货/服务地址receiverName、receiverPhone、省市区、详细地址、是否默认service_category表服务分类如保洁、维修、月嫂service_item表服务项目挂接分类包含名称、价格、计价单位、预计耗时、封面图worker_info表家政人员资料关联account包含真名、电话、可服务项目、接单状态、审核状态order_info表预约订单主表comment表服务评价关联订单、用户和家政人员分开来看只是普通的“增删改查表”但合理拆分的价值在于新增一个服务维度字段只要改service_item表不影响订单家政人员入驻审核功能只需扩展worker_info字段未来想给不同服务加不同计价规则也只要在service_item里加字段。表职责清晰意味着需求变更的冲击被隔离在局部而不是一路波及所有表。4.2 订单表字段设计的几个关键决策order_info是整个系统的心脏它的设计质量直接决定开发体验。核心字段我建议这样安排id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT 自增ID, order_no VARCHAR(32) UNIQUE COMMENT 订单号给用户看的编号, user_id BIGINT COMMENT 下单用户ID, worker_id BIGINT COMMENT 接单家政人员ID, service_item_id BIGINT COMMENT 服务项目ID, address_id BIGINT COMMENT 服务地址ID, service_time_start DATETIME COMMENT 预约开始时间, service_time_end DATETIME COMMENT 预约结束时间, status TINYINT COMMENT 订单状态 0待确认 1已确认 2服务中 3已完成 4已取消 5售后中, order_amount DECIMAL(10,2) COMMENT 订单金额, pay_type TINYINT COMMENT 支付方式, remark VARCHAR(255) COMMENT 客户备注, create_time DATETIME COMMENT 创建时间, pay_time DATETIME COMMENT 支付时间这里有几个决策值得展开。第一为什么订单号order_no要单独存在而不是直接用自增id因为用户和后台沟通时会说我那个订单号是多少自增id位数少且连续容易被试探和猜测也不利于分段检索。订单号通常用时间戳加随机数生成长度16到20位之间即可。第二为什么预留service_time_end而不是只存service_time_start因为家政服务普遍按时长计费。保洁按小时、维修按上门时长预留结束时间后订单列表页可以展示预计服务时长统计家政人员每天的总工作量也要用到这个值。哪怕MVP阶段用不到也应该先占住这个位置后面补上比改表结构容易得多。第三物理外键要不要加我的建议是不加。数据库级的外键约束删除分类或服务时会受到各种限制维护成本高。业务层面通过service_item_id关联在service代码里做存在性校验即可。这个做法在真实企业项目里很常见面试时也能讲出理由。第四关于时间的坑。这类系统任何时间字段都要统一用datetime或者timestamp且后端接收前端传来的时间参数时一定要用JsonFormat标注格式或者全局配置Jackson序列化规则否则前端传2025-06-01 09:00:00后端解析失败直接报400错误。我甚至遇到过前后端显示时区不一致数据库存的时间比实际早了8小时的情况排查到最后发现是MySQL时区配置问题。这些内容在后端配置阶段就应该处理掉而不是等到联调再救火。5. 前端从0到能点通全流程路由、组件与预约表单的体验5.1 前后台同工程路由和布局家政平台的前端我建议把用户前台和管理员后台放在同一个Vue工程里。很多教程喜欢拆成两个前端项目说是职责分离但实际上两人份的登录态维护、两套打包配置、两处部署对单人开发是负担。用路由前缀区分即可普通页面放在 / 下面后台管理页面统一挂在 /admin 下。路由层面简单的划分公共页面登录页、注册页、首页、服务列表、服务详情用户页面预约下单、订单列表、订单详情、服务地址管理、个人中心管理页面数据看板、订单管理、服务分类管理、服务项目管理、家政人员管理、评价管理这样划分后权限控制也方便普通用户登录后能访问用户页面管理员进入后台路由守卫按角色过滤。5.2 登录态与权限路由守卫必须做前端路由守卫是这个系统不能省的部分。用Vue Router的beforeEach钩子做统一拦截逻辑也不复杂判断目标路由是否需要登录需要则检查本地有token没有有token还要判断访问的是不是管理员页面是则校验角色是否ADMIN不满足条件就跳回登录页。有人觉得后台的某些页面反正接口也会校验权限前端路由守卫是多余的。实际上不是这样。前端守卫的价值在于体验未登录用户点击我的订单时能及时被带到登录页而不是等接口报401再弹错误。权限错误和未登录错误的提示文案也应该区分否则用户完全不知道为什么点按钮没反应。5.3 预约表单的体验细节时间选择、防重复提交预约下单页是整个前端交互的难点问题集中在三个地方。第一个是时间选择。Element Plus的date-picker和time-picker支持禁用过去时间设置disabled-date为今天之前的日期不可选即可。如果业务上要求同一天内上午的时间也禁掉可以用time-picker的disabled-hours把当前时分之前的时间禁用。这块很容易被忽略等测试人员发现为什么能预约昨天的时间时再改又要多一轮联调。第二个是地址联动。选择省市区用级联选择器最后一行填详细地址。地址保存后要支持设为默认地址这个字段在account里用is_default标记下单页默认选中默认地址即可。注意切换地址时表单校验要更新防止用户改了地址但提交时仍是旧数据。第三个是防重复提交。用户点击提交预约后如果接口响应慢他会习惯性再点一次导致同一个订单创建两份。前端解决方案是提交后按钮立刻loading并禁用等待接口返回后再恢复。但这只是体验层面的兜底真正的安全防线在后端的防重复创建逻辑这一点下一章详细讲。用户端预约成功后的引导也很重要。下单成功后不要直接清空页面而是跳转到一个支付成功或下单成功提示页并显示查看订单按钮让用户看到下一步的等待确认状态。这能大幅减少我的订单到底提交了没有这类反馈。6. 预约冲突与订单状态流转联调时最容易翻车的地方6.1 并发预约冲突数据库唯一索引 vs 事务查询下面聊两个真正会在联调阶段炸掉的点。第一个是预约冲突。场景是这样用户A预约了某保洁阿姨周六上午9点到11点用户B在同一时间也要约同一个阿姨。如果后端只做普通的查询发现9点到11点没记录就允许下单在高并发或用户双击的场景下可能两个请求同时查到空闲然后同时插入成功阿姨就被约重了。网上常见的防御手段有两种。第一种是前端把时间按钮置灰看起来能解决问题但同一个用户开两个浏览器标签页、或者手速够快连点两次照样绕过去。第二种是数据库加唯一索引比如对(worker_id, service_time_start)加唯一约束能挡住同一开始时间的重复预约但如果用户A约9点到11点用户B约10点到12点两个记录的开始时间不同唯一索引也拦不住。真正可靠的做法是用事务加锁。在创建订单时开一个事务先查该家政人员在目标时间段是否存在重叠订单Transactional public Long createOrder(OrderCreateDTO dto) { // 加行锁或区间锁防止并发 Long conflictId orderMapper.selectConflictOrder(workerId, startTime, endTime); if (conflictId ! null) { throw new BizException(该家政人员在此时段已被预约请更换时间); } // 插入订单 ... }对应SQL查询重叠订单的逻辑是SELECT id FROM order_info WHERE worker_id #{workerId} AND status IN (0, 1, 2) AND service_time_start #{endTime} AND service_time_end #{startTime} LIMIT 1这个条件判断的是时间区间是否有交集覆盖率比单纯查startTime准确得多。配合数据库里的worker_id和service_time_start唯一索引双保险下来基本可以杜绝撞单。对课程设计而言事务加重叠查询已经十分够用不需要上分布式锁。6.2 状态转换不写保护后台一定会把已取消订单接单第二个坑是状态转换的原子性。我见过不少代码这么写接单接口先查订单拿到订单后判断status是不是0如果是0就update成1。问题在于网络延迟、用户取消请求先到、管理员并发操作时两步操作之间状态可能已经被改了但第二步拿着旧数据照样更新成功。要避免这个问题最好的办法是把校验和更新合成一条带状态条件的SQLint rows orderMapper.updateStatusIfMatch(orderId, targetStatus, expectStatus); if (rows 0) { throw new BizException(订单状态已变化请刷新后重试); }对应的更新SQLUPDATE order_info SET status #{targetStatus}, update_time NOW() WHERE id #{orderId} AND status #{expectStatus}这里影响行数rows为0说明状态不是预期状态直接抛异常让前端刷新。这一招在电商、库存类系统里是常客叫作乐观锁。好处是简单、无额外依赖、天然防并发。像取消订单这些操作也一样统一走带状态校验的更新方法订单状态就不会被用户反复横跳搞乱。如果服务层能保证所有状态变更都收敛到同一个方法里比如changeOrderStatus(orderId, expectedStatus, targetStatus)后面加一个待支付状态或者退款中状态都只要在这个方法里补充规则而不是满项目找散落的update语句这也是前面说状态机设计重要的落地体现。7. 从本机跑通到交付验收源码交付阶段的调试、修改与答疑7.1 本机把项目跑起来的顺序无论你是学这套项目还是准备交付给他人先保证自己能按干净步骤跑起来。顺序一般是这样新建数据库导入项目根目录下sql文件确认初始化数据存在修改后端application.yml里的数据源配置数据库名、用户名、密码按本地环境调整启动后端确认控制台无报错接口文档页或测试接口能通前端目录执行npm install然后npm run dev等待页面自动打开用初始化给的默认账号登录跑一遍下单、接单、完成的完整流程强调一个容易被卡住的细节很多项目的sql文件是有人用MySQL旧版本导出的字符集可能是utf8mb4也可能不是。导入之前先确认排序规则如果是utf8mb4_general_ci统一在数据库配置里也设置成utf8mb4否则中文数据可能显示乱码。另外前后端数字端口要分开后端常见8080前端Vite开发端口常是5173或8081不要让两个端口撞在一起。7.2 跨域问题的三种处理方式前后端分离必然遇到跨域。报错信息通常是Access-Control-Allow-Origin之类新手一看见就慌其实就三类解法。第一种在后端写CORS配置类允许指定来源跨域。这种方式代码最少但如果前端端口改了要回头改后端配置稍微麻烦。第二种在前端开发服务器配代理比如Vue CLI的vue.config.js里配置devServer.proxy把/api转发到后端地址。这种方式最推荐在开发阶段使用前端代码里所有请求都走相对路径/api/xxx跟部署后的nginx代理行为一致迁移到生产环境时前端代码一行不用改。第三种在上线后用nginx反向代理把 / 指向前端dist静态文件/api/ 转发到后端jar的端口彻底进站同一域名从根上消灭跨域。实际开发时我一般开发用第二种、上线用第三种。后端不写跨域代码框架层面干干净净。7.3 文档、基础修改与答疑的边界交付阶段的文档和答疑比很多人想的重要。落地说就是三份东西数据库脚本、环境启动说明、功能清单表。环境说明里至少写清JDK版本、Maven或Gradle版本、Node版本、MySQL版本这些不一致是跑不起来的头号原因。功能清单表按角色列清楚每个页面和接口的对应关系验收时照着表格走一遍双方都省心。所谓基础修改在装修服务预约平台里通常指换logo和站点名称、改首页轮播图、调整服务项目价格、修改联系方式。这些改动的共同特点是不涉及表结构变化。做这类改动时建议把静态资源统一放在assets目录文案尽量集中在配置项里而不是散落在几十个vue文件内部。我甚至会把站点名称做成一个公共常量文件改一行全局生效答疑时只需要告诉对方这个文件第二行就是站点名。答疑阶段最常被问的技术问题其实大半都不是业务代码的问题而是环境问题端口占用、npm install失败、MySQL密码加密导致连接失败、Node版本过高导致依赖不兼容。准备一份常见环境问题排查清单比反复远程看屏幕要高效得多。Node版本过高导致安装依赖失败这个尤其常见遇到时优先建议降级到16或18版本再试能解决七成相关报错。8. 接手一套源码时最快读懂项目的方式最后分享一个我自己屡试不爽的经验拿到一套陌生源码不要从头一行一行读代码。那是图书馆式阅读法效率极低。正确顺序是先把项目跑起来注册账号走一遍完整业务流程对系统能做什么建立体感然后打开数据库看表对着订单状态流转图理解数据怎么变化最后再回到代码里重点看service层的事务和状态更新逻辑。三步下来你基本就知道改哪个文件能改什么功能了。这套方式对家政预约平台的源码同样适用。如果要改预约时间粒度从小时变成半小时先改前端时间选择器的format和step再改后端的service_time字段校验最后检查重叠查询逻辑里的时间比较方式三处改完就对齐了。很多人觉得基础修改很难其实难在不敢动、没有定位问题的思路而不是改动本身。我实际操作中还有个小习惯每拿到一个系统先在本地开一个分支或者备份一份sql任何改动前都能一键还原。对待交付项目这能避免改坏了又不好意思问原作者的尴尬。希望这篇能把家政预约平台的骨架和容易踩的坑讲清楚接下来你可以动手打开代码了跑通第一个订单的时候会特别有成就感。
阅读完成 · 觉得有帮助?
咨询建站