做服务调度类项目最让人头大的从来不是页面能做得有多花哨而是“这件事到底该派给谁、怎么快速判断那个人现在真能用”这一连串业务规则背后的逻辑。最近我负责了一个基于React Native鸿蒙跨平台实现的调度分配模块核心需求很明确对候选资源做可用性筛选再按距离最优原则给出分配建议并且系统要同时支持人工干预和智能分配两种模式。这套模块最终跑在了跨平台移动端上业务侧的派单准确率和响应速度都有明显提升。这篇文章我会把整个项目的设计思路、算法选型、实操细节和踩坑过程完整梳理一遍。项目里涉及的场景我做了脱敏处理但核心代码逻辑和技术方案都是可以直接复用的适合正在做派单、巡检、上门服务、维修工单分配这类需求的朋友参考。1. 项目整体设计与思路拆解1.1 需求定位这个模块到底解决了什么问题先说业务背景。项目模拟的是一个多区域服务调度平台每天有大量工单产生每个工单需要分派给合适的服务人员而服务人员分布在不同的地理位置具备不同的技能标签当前的工作状态也各不相同。过去靠人工在群里喊、在表格里查效率低不说还经常出现“人就在附近但被派给了远处的人”这种离谱情况。所以这个模块的本质是把“人找活”变成“活匹配人”。但匹配不能瞎匹配必须满足三个约束条件这个人当前是可用的不能被其他任务占用也不能在非工作时段这个人具备处理这个工单所需的能力标签在满足前两条的人里优先选择距离最近的那个同时兼顾工作负载的均衡。这三个约束条件对应到系统里就是两个核心算法模块可用性筛选和距离最优计算。而“人工干预”和“智能分配”两种模式则分别对应两套运行逻辑既要保证自动化运转的效率又要给调度员留出人工判断的空间。1.2 为什么选React Native加鸿蒙这套组合移动端跨平台方案其实有很多选择但这个项目有它的特殊性终端设备一部分是现有安卓机另一部分是新采购的鸿蒙设备团队里又积累了比较多的前端和React技术栈经验。综合考虑下来选择了React Native配合鸿蒙适配层来做跨平台。为什么不用纯原生开发因为两套原生代码的维护成本太高业务逻辑迭代快不可能每次改动都同步写两份。为什么不用Flutter团队对Dart不熟而且现有大量业务组件都是基于React生态的迁移成本大。React Native在鸿蒙上的适配目前主要走的是社区和厂商联合维护的OpenHarmony适配路线。简单理解就是通过桥接层把RN的JavaScript引擎和鸿蒙的ArkUI能力打通JS侧写业务逻辑原生侧提供地图、定位、通知等系统能力调用。实测下来基础组件渲染、Touch事件、网络请求这些核心能力都稳定可用。1.3 双模式机制的设计取舍这个项目最值得一提的设计点是“人工干预”和“智能分配”的并存。一开始也犹豫过既然有了算法为什么还要人工介入这不是多此一举吗实际做过调度系统的人应该都懂完全交算法、不让人说话的系统基本在业务侧是推不下去的。原因很简单算法能处理常规情况但处理不了“例外”。比如某个客户点名要老张师傅过去比如某个区域临时有交通管制这些信息不会及时反映在数据里必须给调度员一个手动覆盖或者手动跳过的入口。所以在设计上我做了这样的划分智能分配模式下系统自动跑筛选和距离排序把最优分配结果直接推送下去适合量大、常规的日常工单人工干预模式下系统只做候选推荐把TopN列表和推荐理由展示给调度员由调度员确认或修改后再下发适合需要权衡特殊因素的工单。两种模式共用同一套筛选和排序核心只是在最终是否自动执行这一层做了分流。这个设计的好处是业务方可以先在人工干预模式下跑一段时间观察推荐结果的合理性觉得稳定了再切换成智能分配上线风险小很多。2. 可用性筛选与距离最优原则的实现原理2.1 可用性筛选状态机、技能标签和时间窗口可用性这个词听起来简单真落到代码里要处理的信息维度其实不少。我把它拆成了四个子条件每个子条件都对应一套判断逻辑第一个是状态判断。服务人员的状态包括离线、空闲、忙碌、休假等只有当状态为空闲时才算初步可分配。这里要注意不能只存一个布尔值因为“忙碌”可能是有截止时间的比如当前工单还有一个小时结束那理论上一个小时后这个人又可以接单了。所以我给每个资源维护了一个状态机并记录状态预期恢复时间。第二个是技能标签匹配。每个工单有一个技能要求列表比如需要“电工证高空作业证”那候选资源必须同时包含这两个标签才能进入推荐池。这个判断用集合运算就能搞定关键在于标签体系要提前做好标准化否则“电工”和“电工证”这种写法会导致匹配结果不稳定。第三个是时间窗口校验。服务人员可以设置自己的工作时段比如有些人只接上午的单有些人周末不排班。筛选时要拿工单的期望服务时间跟资源的工作时段做交集判断不满足的直接淘汰。第四个是并发上限控制。为了避免给一个人同时派多个工单我会给每个空闲资源加一个“最大并发任务数”默认是1但考虑到有些轻量任务可以顺路处理也支持配置大于1的值。这四个条件合在一起就构成了可用性筛选的完整过滤器。实际操作中我会把筛选逻辑写成一条过滤链每个环节只做自己该做的事这样任何一个规则发生变化都不需要改动其他环节。2.2 距离最优原则从Haversine到实操中的混合策略距离最优是整个系统的另一个核心。距离算得准不准直接影响推荐的合理性。最简单的方案是用Haversine公式计算两个经纬度坐标之间的球面距离公式本身不复杂function haversineDistance( lat1: number, lng1: number, lat2: number, lng2: number ): number { const R 6371; // 地球半径单位公里 const toRad (deg: number) (deg * Math.PI) / 180; const dLat toRad(lat2 - lat1); const dLng toRad(lng2 - lng1); const a Math.sin(dLat / 2) * Math.sin(dLat / 2) Math.cos(toRad(lat1)) * Math.cos(toRad(lat2)) * Math.sin(dLng / 2) * Math.sin(dLng / 2); const c 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); return R * c; }这个方案算的是“直线距离”在服务半径比较小、路网比较规则的区域参考价值还是不错的而且计算速度快适合在筛选阶段做粗排。但实操中我很快就发现只算直线距离远远不够。很多区域中间隔了一条河直线距离两公里实际绕路要走八公里。所以最终方案是“Haversine粗筛 地图路径距离精算”的混合策略先用Haversine把所有候选资源按距离从小到大排序取前N个进入精算环节这个N我一般设为10对这N个候选调用地图服务的路径规划接口获取真实的驾车或步行距离用真实距离重新排序得到最终的距离最优推荐。为什么不让所有候选资源都走路径规划接口因为地图服务接口有配额限制而且响应时间不稳定如果每次分配都对几百个候选资源做路径规划那系统延迟会变得不可接受。粗筛加精算的组合是在精度和性能之间最实用的折中。2.3 数据流与状态同步设计筛选和排序都依赖一个前提——资源数据是新鲜且一致的。这里我踩过一个大坑后面会在排查章节详细说先讲一下正确做法。资源位置和状态这两类数据都是高频变化的位置可能每几秒就变状态则受工单流转影响。我在系统设计上采用了“客户端定时上报 服务端缓存聚合 分配时实时校验”三层结构移动端每30秒上报一次定位信息定位权限异常时退化为手动选择区域服务端把资源位置和状态存在缓存里设置合理的过期时间避免读到僵尸数据分配动作发生时不是直接信任缓存而是对最终推荐名单做一次实时状态回查确保没有把已经被其他任务占用的资源分配出去。这个三层结构能覆盖绝大多数情况既保证了列表展示的流畅性又保证了分配决策那一刻的数据准确性。3. 实操过程两种模式的核心实现3.1 智能分配模式的完整链路智能分配模式的代码逻辑是整条链路的主干。实际实现中我给分配引擎定义了一个入参对象和返回结果结构interface AssignRequest { orderId: string; requiredSkills: string[]; location: { lat: number; lng: number }; expectedTime: string; mode: auto | manual; } interface AssignResult { candidates: Candidate[]; assignedResourceId?: string; strategy: string; }智能模式下的执行顺序是这样的根据工单的技能要求和工作时段从全量资源池里做第一轮粗筛这一步直接过滤掉八成以上的无效候选对粗筛通过的资源做Haversine距离计算按距离升序排序取前10个候选做路径精算换算成真实距离引入负载均衡因子对排序分数做一次修正公式是score realDistance workloadWeight * (当前并发数 / 最大并发数)避免距离最近的人永远被派单、活活累死而稍远一点的人一直闲着取修正后分数最小的资源为最终分配结果写分配流水并下发工单通知。代码里我想强调一个细节排序时不要把距离和时间混在一起算权重除非你有完整的标定数据。我在第一版里试过给“预计耗时距离/平均速度”加权结果因为不同区域的交通状况差异太大权重怎么调都不对最后还是改成了“距离为主、工作量微调”的简单方案。3.2 人工干预模式的交互细节人工干预模式不是简单地把自动结果弹出来让人确认那样人工就变成了流程上的摆设。真正好用的干预模式一定要给人足够的决策依据和操作空间。我在移动端实现了一个干预面板核心内容有三块第一块是候选人推荐列表。列表按综合分数从低到高展示前5名每项包含姓名、距离、预计耗时、当前负载、技能匹配度这五个关键指标。第二块是推荐理由标签。每个候选人都带一两个简短标签比如“距离最近”“负载较低”“指定偏好”让调度员不用逐个对比数据就能快速理解推荐逻辑。第三块是操作区。调度员可以选择直接指派某个人也可以选择“暂不分配、备注原因”。备注原因不是可有可无的功能它会进入分配流水后续查问题时有据可依。这个模式跑了一个月后业务侧反向给我提了个需求希望在干预面板上能看到候选人最近一个工单的完成时间用来判断这个人现在是不是真的在赶路还是已经收工了。后来我在候选列表里加了一个“最近工单状态”字段数据的实时性明显变好了。3.3 鸿蒙端适配的几个关键配置React Native的代码在鸿蒙上运行表面上看是“复制粘贴换个壳”实际要处理的适配问题一堆。先说导航和地图模块。鸿蒙端没有直接可用的第三方地图SDK我的处理方式是封装一个统一的地图接口安卓端走原有的地图SDK鸿蒙端走系统级定位服务加WebView地图展示兜底业务层完全感知不到差异。再说定位权限。鸿蒙对定位权限的申请时机和弹窗文案有自己的一套要求需要在原生侧做适配。具体来说我写了一个原生定位模块通过TurboModule的方式暴露给JS侧调用返回统一的经纬度对象。// 原生模块暴露给RN侧的接口 import { NativeModules } from react-native; interface LocationModule { getCurrentPosition(): Promise{ latitude: number; longitude: number }; } const { LocationModule } NativeModules;还有一个容易忽略的问题是字体和布局适配。鸿蒙系统的默认字体渲染和安卓有细微差异极端情况下会导致文本截断。我的经验是在关键文本节点上不要依赖默认行高至少设置一个最小高度属性避免底部按钮上的文字被切掉。4. 常见问题与排查技巧实录4.1 鸿蒙适配里的三个典型坑适配过程中遇到的问题不少挑三个最有代表性的说。第一个是图片资源路径解析异常。安卓端能正常显示的本地产图片在鸿蒙端偶尔会报找不到资源。查了很久才发现是打包脚本对资源目录的大小写处理不一致导致的。解决方案是放弃本地图片的复杂目录结构改成统一放入一个扁平目录文件名全部用小写加下划线。这个改动虽然粗暴但之后再也没有出过资源找不到的问题。第二个是定位返回空值。部分鸿蒙设备在首次安装应用后如果用户没有主动触发定位权限弹窗定位模块会静默返回空对象。我加了一个“权限预检 失败重试”逻辑在进入分配页面前就检查权限状态如果未授权则弹出去设置页的引导。第三个是地图组件层叠遮挡。RN的Modal弹层在鸿蒙端层级异常导致弹层下方的地图触摸事件穿透。这个问题的解法是给Modal内的根容器设置一个elevation样式值把弹层的渲染层级抬高。4.2 距离计算性能瓶颈与优化发布后有一段时间智能分配接口的响应时间涨到了三秒以上根本原因是候选资源数量级上来后路径精算环节没有控制好并发量。一开始我的逻辑是粗筛后的10个候选逐个同步调用地图路径规划接口一个返回后才能请求下一个串行消耗了太多时间。后来改成“并行请求 熔断降级”10个候选的路径规划请求同时发出用Promise.all等待全部返回设置单次请求超时为1.5秒超时的候选降级使用Haversine直线距离如果连续3次出现超过3个候选超时则整个分配流程自动降级为纯Haversine模式不再调用路径规划接口。这个改造做完后接口响应时间稳定在800毫秒以内最坏情况也不会超过2秒。顺带说一句路径规划接口的配额消耗也要留意。我加了本地缓存同一个起点和终点组合在10分钟内只请求一次这个缓存命中率相当高接口配额的压力小了很多。4.3 双模式切换导致的状态冲突最隐蔽的问题出现在人工干预和智能分配共享同一套状态数据的时候。现象是这样的调度员在人工模式下看到候选人A状态为空闲于是准备指派但就在他按下确认的这几十秒里系统自动任务已经给A分配了一个在线工单A的状态变成了忙碌。这时候人工确认再下发就产生了任务超配。排查后发现问题根源在于人工干预的确认操作没有重新校验资源状态。解法分两步一是在客户端下发前增加一次轻量校验接口确认资源仍处于空闲状态二是服务端在写入分配结果时加一个乐观锁用版本号比较版本不一致就拒绝写入并提示调度员刷新列表。处理完之后超配问题基本消失了。这个经验也说明了一个问题任何人工操作的入口都不能跳过最终状态校验界面展示的数据只能当作参考快照不能当作决策依据。4.4 问题排查速查表现象可能原因排查顺序解决方法鸿蒙端图片不显示资源路径大小写不一致先看日志有无资源加载失败统一扁平目录小写命名定位返回空权限未预检检查权限状态接口返回增加权限预检与引导智能分配响应超3秒路径规划串行请求看接口调用耗时分布并行请求加降级缓存人工确认后任务超配状态校验缺失查分配流水时间线增加二次校验和乐观锁Modal遮挡地图渲染层级异常在鸿蒙端复现点击事件设置elevation抬升层级距离异常偏大路径规划接口降级看是否触发熔断开关调整超时和失败阈值这个表格是我后来整理出来贴在项目文档里的团队新成员接手时照着排查整个定位效率高了不少。最后再分享一点个人体会这个项目做下来我最大的感悟是算法再漂亮也要给业务留一个“反悔”和“插手”的口子。可用性筛选和距离最优原则解决的是常规场景下的效率问题但真正让系统能被业务方接受并长期用下去的是那套人工干预机制。它看起来只是多了一个确认页面实际上是在算法和现实世界之间搭了一座桥。如果你也想在自己项目里落地类似的调度分配逻辑我的建议是先把筛选条件设计好再考虑距离算法。筛选条件决定了下限距离排序决定了上限。另外双模式一定要做成可配置的哪怕业务初期只在人工干预模式下运行也比一上来就全自动要稳妥得多。这个内容后续还可以扩展的点是加一个分配质量回评模块用工单完成时长和客户评分反过来校正算法权重让系统越跑越准。