ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

家政订单管理系统实战:从业务建模到状态机设计

家政订单管理系统实战:从业务建模到状态机设计 去年给本地一家家政公司做了一套家政订单管理系统从需求梳理到交付完整源码工程前后花了一个半月时间。这类系统的难点其实不在技术本身而在于你得先搞懂家政行业的订单跟电商订单完全不是一回事。今天把这套系统的完整设计思路、核心模块、源码结构和实际开发中踩过的坑整理出来希望能给正在做类似项目的人一点参考。这套系统适合谁看有几点背景先交代清楚如果你是要接单做外包开发的可以参考完整的业务建模和技术方案如果你是家政公司想自研或者找外包做系统的可以用这篇文章作为需求沟通的参考如果你是学编程想找一个有真实业务背景的实战项目练手这套订单系统的数据表设计和状态机设计也足够你研究一阵子。文章涉及的具体代码片段我都做了删减重点讲清楚设计逻辑和落地思路。1. 先搞清楚家政订单和普通电商订单差在哪1.1 这不是一个普通的进销存系统很多人一听“订单管理系统”第一反应就是照着电商后台抄一个商品列表、购物车、下单、支付、发货、完成完事。但家政行业的订单逻辑完全不是这样它的核心不是“货”而是“人”和“时间段”。家政订单有几个显著特点预约制为主客户提前一天甚至一周预约服务不是即时交易服务时长不固定按小时或按次计费服务过程中还可能加钟履约过程依赖阿姨服务人员人的因素远大于商品因素会出现迟到、换人、临时爽约等各种状况。最核心的一点订单状态颗粒度要更细每个状态之间的流转也不是简单点个按钮就完事中间还嵌着一个“派单”环节而派单本身就是一个需要脑子去匹配的过程。这套系统里我把订单定义成一个贯穿“客户—平台—服务人员”三方的核心业务对象围绕它拆出客户管理、服务项目管理、派单管理、履约管理、结算管理几个模块。所有模块的数据流转都以订单为主线每一个状态变更都记录操作日志全部可追溯。1.2 家政业务的五个关键角色和他们的诉求在动手设计表结构之前我花了一周时间蹲点观察他们公司的日常运营把整个业务流程从头到尾捋了一遍。家政公司里实际有五个角色每个角色的诉求不一样这是系统设计的基础。客户想快速约到合适的阿姨能看到订单进度服务完能评价能售后。前台调度是整个系统的核心使用者负责接电话、建单、派单、处理客诉他们的核心诉求是操作效率每个操作最好不超过三次点击。服务人员阿姨通过手机端看到自己的工作安排服务前后要打卡确认。财务月底要结算阿姨的工资核对平台抽成要求数据准确、能导出。老板/管理者要看每天的订单量、营收、各社区覆盖情况、阿姨接单率等经营数据。这些角色对应到系统里就是管理后台、小程序客户端口、小程序阿姨端口三个端。第一期我重点做的是管理后台因为后台是所有业务流程的汇聚点也是决定这个系统能不能跑起来的关键。源码工程里我把三个端的代码分成了独立的目录方便后续单独部署和维护。2. 业务建模与订单状态机设计2.1 订单的九种状态与状态流转规则状态机是所有业务系统的灵魂这个设计错了后面全是坑。家政订单的状态我最终定为九个涵盖了从创建到售后的完整生命周期。状态编码状态名称说明0待派单客户预约成功等待调度员派单1已派单已指定阿姨等待阿姨确认2已接单阿姨确认接单3服务中阿姨开始上门服务4待结算服务完成等待财务核算5已完成结算完成订单闭环6已取消客户或平台取消7售后中客户发起投诉或售后8已退款售后处理完成退款到账每个状态之间的流转必须有明确的操作入口和权限控制。举个例子待派单状态只能由调度员操作可以流转到已派单或已取消已派单状态只能由阿姨操作可以流转到已接单接单或退回待派单拒单服务中状态可以由调度员发起取消特殊场景比如阿姨爽约、客户家里临时有事。这里有一个很容易忽略的细节已派单和已接单之间的时间窗口。实际业务中阿姨不一定会立刻看手机所以系统要支持调度员在派单后给阿姨打电话确认电话确认后可以在后台代替阿姨点“确认接单”这个操作要记录操作人是谁避免出问题时说不清楚。2.2 数据库表设计的核心思路家政订单系统的数据表我最终设计出来一共三十多张不追求大而全但核心的几张表必须逻辑严密。这里挑三张最关键的说一下。订单主表设计时一个很重要的决策是把客户地址、服务项目名称、阿姨姓名这些经常需要展示的冗余字段直接冗余在订单表里而不是全部通过关联查询。因为订单查询页面的列表频率最高每次都去关联三四张表数据量上来之后性能会很难看。当时的做法是把订单表拆成订单主表和订单明细表主表存核心状态和人员信息明细表存服务明细和金额这样兼顾了查询效率和结构清晰。客户和阿姨的管理上家政公司特别在意社区和区域这个概念因为派单时要考虑阿姨的负责区域和服务半径。我单独建了一张社区表客户地址里存社区ID阿姨的服务区域也存社区ID集合做匹配时直接按区域筛选效率远高于用字符串做模糊匹配。派单记录表是容易被忽略但很关键的一张表它记录了每一次派单、拒单、改派的历史。比如同一个订单被派给不同阿姨的情况很常见每一次变更都会保留记录包括谁派的、派给了谁、什么时间派的、原因是什么这样出了问题能快速回溯阿姨之间也不容易扯皮。2.3 状态机在源码里的落地方式状态机设计好了但代码写得一塌糊涂的项目我见过太多了最常见的就是if else写得到处都是改一个逻辑要全局搜索。我最开始写这个系统的时候用的是状态枚举加状态流转校验器核心思路是把“当前状态可以执行哪些动作”集中在一个地方管理。public enum OrderStatus { PENDING_ASSIGN(0, 待派单), ASSIGNED(1, 已派单), ACCEPTED(2, 已接单), SERVING(3, 服务中), PENDING_SETTLE(4, 待结算), COMPLETED(5, 已完成), CANCELED(6, 已取消), AFTER_SALE(7, 售后中), REFUNDED(8, 已退款); private final int code; private final String desc; // getter、constructor省略 } public class OrderStatusTransition { private static final MapOrderStatus, ListOrderStatus TRANSITION_MAP new HashMap(); static { // 每个状态允许流转到哪些目标状态 TRANSITION_MAP.put(OrderStatus.PENDING_ASSIGN, Arrays.asList(OrderStatus.ASSIGNED, OrderStatus.CANCELED)); TRANSITION_MAP.put(OrderStatus.ASSIGNED, Arrays.asList(OrderStatus.ACCEPTED, OrderStatus.PENDING_ASSIGN, OrderStatus.CANCELED)); // 其余状态省略 } public static boolean canTransition(OrderStatus from, OrderStatus to) { return TRANSITION_MAP.getOrDefault(from, Collections.emptyList()).contains(to); } }这样写的好处是所有状态流转规则集中可见新增状态只需要改一个文件不会出现业务代码里到处散落状态判断的问题。美团配送和很多大型系统的状态机设计也是这个思路只是他们的流转配置是用配置中心做的实时可调整。家用这个规模直接代码里管就够用了没有引入工作流引擎的必要。3. 核心功能模块的拆解与源码组织3.1 派单模块从人工到半自动派单是家政公司订单管理系统和普通订单系统区别最大的模块也是最难做好的模块。我设计的派单流程分两层第一层是调度员手动派单时的辅助推荐第二层是自动派单规则。调度员在后台点“派单”按钮时系统会自动检索并列出符合条件的三类阿姨同一社区且技能匹配的相邻社区且技能匹配的技能匹配但评分相对较高的。排序权重是社区匹配度最高其次是当月接单量防止某些阿姨被过度分配最后是历史好评率。后台自动推荐的核心SQL其实就是一组带优先级的过滤条件SELECT e.id, e.name, e.phone, e.score, e.order_count FROM employee e WHERE e.status 1 AND e.is_verified 1 AND e.id NOT IN ( SELECT o.employee_id FROM order o WHERE o.appointment_date #{appointmentDate} AND o.employee_id e.id AND o.status IN (1, 2, 3) AND NOT EXISTS (SELECT 1 FROM employee_schedule es WHERE es.employee_id e.id AND es.date #{appointmentDate} AND #{endTime} es.start_time AND #{startTime} es.end_time) ) ORDER BY CASE WHEN e.community_id #{communityId} THEN 0 ELSE 1 END, e.order_count ASC, e.score DESC LIMIT 10;这里需要注意两点一是状态过滤要包含已派单、已接单、服务中三种状态因为从派出去到服务完成整个周期这个阿姨的时间都已经被占了二是时间冲突判断的SQL虽然看着直观但数据量大了以后性能会明显下降更优做法是给时间区间建联合索引或者在内存中做时段占用匹配。我实测下来几百个阿姨的规模直接SQL没有问题但如果做到几千个阿姨就要考虑把排班数据放进Redis用有序集合做时间段匹配。3.2 订单列表和详情信息架构大于技术订单管理后台每天被调度员高频使用这个页面的体验直接决定系统成败。很多项目把订单列表做成了所有字段平铺的表格字段多到横向滚动条能拖三屏调度员看一眼眼睛都花了。我的做法是把列表页的信息分成三层。第一层是列表项的核心字段包括订单编号、客户姓名、手机号、服务项目、预约时间、状态、当前阿姨。这7个字段必须在一屏内全部展示完凡是超过7个字段的列表页基本都需要重新考虑信息架构。第二层是列表页的筛选条件我做了客户手机号、订单状态、预约日期范围、服务类型四种维度组合这样调度员接到电话的时候能快速定位某个客户的所有订单。第三层是操作按钮区根据订单状态动态显示可以执行的操作不同状态下的按钮不一样避免调度员对系统的状态流转产生困惑。订单详情页我建议只展示不做编辑操作。所有编辑操作最好都放在对应的独立功能入口里比如改约、换阿姨、改地址、标记异常。原因是调度员在用系统时注意力已经很紧张直接在详情页大范围编辑很容易误操作而这种误操作一旦发生因为涉及状态和结算后续补偿成本很高。3.3 财务结算模块最容易被低估的难点我到现在还记得第一次被财务拉去开会时被问倒的场景她说“你把订单状态改成已完成但我下个月怎么知道该给每个阿姨发多少钱” 当时才意识到订单系统的最后一个环节一旦没有提前设计好财务整个月都要手动对账那是要命的。结算模块设计起来有三个关键点。第一每个订单都要冗余一份金额快照包含客户支付金额、平台抽佣、基础服务费、加钟服务费、阿姨提成比例冻结在订单完成那一刻之后不许改变。这样做的好处是结算按订单快照走不受后续售后、改价的影响。第二阿姨的月结报表必须从已结算状态的订单生成未结算和售后中的订单要单独列出来不能混在一起否则财务永远对不平。第三结算周期我设置为自然月每月1号凌晨自动生成上个月的阿姨结算单财务可以在线审核审核通过后导出Excel表格直接发工资。SELECT employee_id, SUM(base_amount * commission_rate extra_amount * extra_commission_rate) AS total_salary FROM order_settlement WHERE settle_month #{month} AND status 1 -- 已审核 GROUP BY employee_id;值得注意的是家政行业的提成和抽佣比例并不是什么都统一的按服务项目类型不同平台抽佣比例从20%到35%不等阿姨的提成比例则是保底加阶梯制接单越多提成越高。这些规则我在系统里全部做成配置表而不是硬编码在代码里运营调整起来只需后台直接改配置不需要发版。3.4 源码工程的目录组织和部署方案最终交付的源码工程是三个独立项目加一个数据库初始化脚本整个工程结构清晰到接手的人不会迷路。home-service-system/ ├── admin/ # 管理后台前端Vue3 Element Plus ├── portal/ # 客户小程序端uniapp ├── worker/ # 服务人员小程序端uniapp ├── server/ # 后端服务Spring Boot 2.7 MyBatis-Plus │ ├── common/ # 通用模块统一返回、异常处理、工具类 │ ├── system/ # 系统管理用户、角色、权限 │ ├── customer/ # 客户模块 │ ├── employee/ # 阿姨模块 │ ├── order/ # 订单模块状态机、派单、履约 │ ├── settle/ # 结算模块 │ └── report/ # 统计报表模块 └── docs/ # 部署文档、数据库脚本、接口文档后端选择Spring Boot和MyBatis-Plus的组合一是因为客户公司现有技术栈能接得住二是这套组合在中小型管理系统的开发效率确实高代码量少团队成员上手快。管理后台选择Vue3Element Plus是目前后台系统的主流搭配组件丰富文档成熟你要是让我重新选我大概率还是选它。部署方案我给了两套一套是低成本的单机部署一台4核8G的云服务器装Docker和MySQL跑一个后端镜像加一个Nginx前后端都用Nginx托管能满足一家中小型家政公司3000个客户、50个阿姨的业务量。另一套是后续扩容的集群方案把订单服务拆出来独立部署加Redis缓存这部分我在部署文档里写清楚了但实际交付用的是第一套小规模业务用集群纯属浪费钱。4. 实操过程中真实踩过的坑4.1 并发派单导致同一个阿姨被重复分配开发完派单功能做了个简单接口测试就上线了没想到上线第三天就出问题了。两个调度员同时在后台给不同订单派单结果系统把同一个阿姨同时派给了两个客户或者至少是界面显示上看起来同时派了。阿姨接到两个电话懵了。这个问题查下来很有意思原因是查询可用阿姨和更新阿姨状态分了两个事务两个请求同时查出来的都以为阿姨可用各自更新订单后才会去更新阿姨的状态这个时机差导致重复分配。解决办法是给订单表加一个employee_confirmed字段并在派单操作时开启悲观锁。// 对阿姨进行锁定防止并发派单 Employee employee employeeMapper.selectByIdForUpdate(employeeId); if (employee.getStatus() ! 1) { throw new BusinessException(该阿姨当前不可派单); }selectByIdForUpdate这条SQL会把阿姨的这行数据锁住第二个事务必须等第一个事务提交后才能读。用悲观锁的原因是这个场景并发量并不高乐观锁反复重试反而增加复杂度。处理完之后我写了一个并发测试脚本模拟20个调度员同时派单结果稳定后没有再出过重复分配的情况。这个坑整整花了我一个下午排查但后来想想也算值得因为它逼我把派单的并发场景彻底想清楚了。4.2 服务时长变更导致订单金额对不上试运行阶段财务发现一个诡异的问题明明订单显示已经完成但结算金额和客户实际付款金额差了几十块钱。查了几天才发现原来是阿姨在服务过程中超过了预约时长也就是所谓的加钟调度员在后台手动改了服务时长但只改了订单明细里的时长字段订单总价和结算快照没有同步更新导致财务看到的是两个价。这次问题的根源还是我把订单金额计算分散在了多个位置。后来我做了两个改动一是金额计算集中到一个服务类里所有入口改价都通过这个类进行避免订单总价和明细总价不一致二是加钟操作必须生成单独的一条加钟记录不走原订单明细的修改而是追加明细这样每一笔金额变动都有据可查。这两个改动看起来简单但彻底解决了财务对账的痛点。后来想想其实最初设计就把金额快照和订单明细绑定的思想贯彻到位这个坑完全可以避免。4.3 边界情况同时约了两个单时间重叠有一类特殊场景是客户固定每周一、周三上午9点到11点预约同一个阿姨做保洁已经持续几个月了。结果有一周客户在后台又约了一个周一下午2点的深度保洁而客户自助下单的时候系统没拦住因为下午2点和上午9点到11点看起来不冲突。但实际情况是阿姨离客户家距离很远上午那边的服务拖了半小时下午势必会迟到。我一开始只做了服务时段本身的冲突检测没有考虑通勤和缓冲时间。后来在派单推荐和数据校验里都加入了缓冲时间默认每个订单结束后预留半小时的移动时间如果两张订单的结束时间和下张订单开始时间间隔小于30分钟系统提示“时间间隔过短建议调整服务时间或更换阿姨”。也支持调度员在后台针对个别阿姨关闭这个限制毕竟有的阿姨是同一个小区连续服务走路五分钟就到了。4.4 数据权限调度员之间互相建单的边界当时同时上线了三个调度员没过几天就发现有人在订单列表里看到了别人的订单也能进行派单操作。一次操作失误导致两个调度员同时在处理同一个订单。这个问题的根源是我一开始没有设计数据权限把订单查询和操作做成了全员可见。家政公司的调度员通常每人分管一个区域或一组固定的阿姨他们只需要看到自己管辖区域的订单。于是我在用户表里增加了data_scope字段把调度员绑定到具体的社区查询订单和派单时按社区过滤。管理员账号保留全局权限财务和老板看数据不受影响。这个改动表面上是个权限问题背后其实是岗位职责的边界系统是用来固化流程的流程没想清楚功能越开放越混乱。4.5 客户手机号改号带来的历史订单查询有个客户换了手机号新手机号登录后查不到自己的历史订单然后投诉到了12315。这种问题很典型因为我把客户身份全部绑定在手机号上没有设计统一的客户ID体系。虽然这个坑是上线之后才暴露的但设计阶段本来可以避免。我后来在客户表里加了union_id字段把客户的主账号手机号、备用手机号、微信openid都关联到这个统一ID上登录时自动识别这样即使换了手机号历史订单一样能查到。这个设计在人多的系统里几乎都是标配但在做小项目的时候很容易忽略等出了问题再返工就很痛了。5. 测试过程中遇到的高频问题和排查技巧5.1 用一个速查表整理测试问题测试阶段我整理了一张高频问题表每次回归测试都对照着看这里分享给大家参考。常见问题排查思路解决方法订单状态乱跳查看order_log操作记录确认操作入口状态机流转校验非法流转直接拒绝派单后阿姨看不到订单检查阿姨小程序的token时效和数据权限token过期自动刷新强制重新登录客户支付成功但订单仍是待支付查看支付回调日志确认回调地址和签名回调幂等处理失败重试机制后台导出Excel乱码导出时没有设置BOM头生成文件时写入UTF-8 BOM阿姨端打卡定位不准检查是否申请了定位权限增加高德定位SDKWeb端加定位降级方案数据库中文乱码检查数据库连接串是否指定字符编码连接串加characterEncodingutf8这些问题的共性是大部分都能通过日志定位所以在系统里打日志是一件不能偷懒的事情。我自己的习惯是核心业务流程的关键节点都打日志并且日志里带上订单号和数据快照排查问题时一句日志就能锁定问题。5.2 为什么强调订单日志表和操作留痕在做这个项目的过程中我越来越觉得订单日志不是可有可无的附加功能而是整个系统的地基。有一次客户投诉说阿姨迟到前台查了订单日志发现阿姨当天早上8点52分确认接单9点40分才点“开始服务”这个48分钟的迟到记录清清楚楚客户那边看了一句话都没说。我在订单表旁边设计了order_log表记录每一次状态变更、每一次派单、每一次金额修改字段包括操作人ID、操作人类型调度员/阿姨/客户/系统、操作前状态、操作后状态、操作内容、操作时间。这个设计几乎不增加开发成本但对运营和售后带来的价值非常大。5.3 兼容性问题阿姨端的手机型号太杂了家政阿姨用的手机型号五花八门最头疼的是很多阿姨用旧款安卓手机微信版本也低小程序一打开就白屏。后来查出来是小程序用了比较新的CSS特性旧手机上的微信WebView不支持。解决方法是小程序端降低生态组件库的版本减少使用较新的CSS语法必要时做兼容处理并在低版本微信里提示升级微信版本。这是我第一次做需要照顾终端碎片化的项目以前做后台管理系统从来没想过这种问题。从这次之后我在做任何小程序项目时都会把目标用户的机型和使用场景纳入考虑已经形成习惯了。6. 这套源码后续怎么扩展系统上线稳定运行半年后客户老板提出了一些更进阶的需求其中几项我认为非常值得在源码基础上继续做下去。一是阿姨端增加人脸识别打卡。家政公司一直担心阿姨的服务时长问题上门服务打卡是GPS定位开始和结束都可以动手脚。人脸识别的意义在于确保打卡的是本人这就把冒名顶替的漏洞补上了。二是增加客户充值卡和次卡功能。现金流对家政公司来说太重要了充值卡能提前锁定客户未来的消费次卡则是把按次服务变成预付费模式这两个功能在财务模型上完全不一样。三是消息通知的自动化和模板化。现在短信和微信模板消息都是手动触发的调度员每天花大量时间在通知客户和阿姨这两件事上完全可以做成服务状态变化时自动触发。四是经营数据看板。老板要看的核心其实就是四个数字今日订单量、今日营收、待派单数量、阿姨接单率把这四个数字做成大屏放办公室比做一堆花哨报表有用得多。从我的实际开发经验来看家政公司的订单管理系统是典型的业务逻辑重于技术实现的系统它没有高并发没有复杂算法难的是你要把现实世界里那些繁琐的、有特例的、靠口头默契维持的流程抽象成一套严谨的数据模型和状态逻辑。把业务吃透代码怎么组织都会稳业务没吃透再牛的框架也白搭。最后再说一点个人体会做这种给传统行业用的管理系统最忌讳坐在办公室里闭门造车我蹲点的那一周里了解到的业务细节比后来远程沟通一个月的收获都大。如果你也想做一个类似的项目建议先花时间跟真正的调度员坐在一起看她们怎么工作你会发现她们的业务手账里全是系统里想不到的特例。把这些特例抽象成规则你的系统才算真正立住了。
返回列表