这个模块要先说清一件事:「后台持续定位」在不同端上的能力差异是根本性的,不是优化程度的差异。
第一版是按小程序做的,因为骑手不用装 App 门槛低。上线后发现压根不可行——小程序切到后台后定位基本停止,骑手一锁屏或者切去看导航,位置就断了。而位置断了的后果很严重:派单查不到他、用户看不到骑手在哪、配送距离算不出来(影响骑手收入)。所以最终结论是履约必须用 App,小程序端只保留查看功能。这个结论本身就是这个模块最重要的产出。
换成 App 之后,剩下三个真实问题。
一是后台定位会被系统回收。Android 各厂商的后台策略差异极大,不做保活的话骑手放兜里十几分钟位置就断了。而保活手段用得过激又会被应用市场判定为流氓行为。
二是耗电。骑手一天连续跑四五个小时,如果我们的应用把电耗光,他就会直接关掉后台定位甚至卸载。耗电对骑手是刚性约束,不是体验问题。
三是弱网下位置点丢失。地下车库、电梯、老旧小区,断网是常态。上报失败如果直接丢弃,那段轨迹就没了,而轨迹是配送距离结算的依据,丢了就是骑手收入算少。
所以三个设计:用系统认可的方式保活(前台服务加持续通知)、自适应频率加客户端预过滤降耗、位置点入本地队列保证不丢。
「履约功能只做 App」是这个模块最重要也最容易被质疑的决策。产品一定会问「为什么不做小程序,骑手不用装」。答案是小程序后台无法持续定位,而位置断了会导致派不到单、用户看不到骑手、配送距离算不出来(骑手收入受损)。硬做的结果是「功能看起来有,实际不可用」,比不做更糟。我保留了小程序端的查看功能(看今日单量、收入、通知),把履约操作放 App,这是折中。
保活和「不当流氓应用」之间必须选合规的一边。有很多绕过系统限制的保活手段,短期有效但会被应用市场判定为违规,而且系统版本一升级就失效,维护成本极高。用前台服务加常驻通知虽然会在通知栏留一条,但它是长期可靠的。代价是要向骑手解释这条通知是干什么的。
上报频率和位置精度是直接冲突的。频率低省电但用户看到的位置滞后、配送距离算得粗;频率高准但耗电。我们的取舍是按状态分档而不是全程用同一个频率——配送中(用户在看、距离要算准)用高频,空闲和静止时用低频。这个「按场景分配资源」的思路比统一调参更有效。
踩过的坑:队列丢弃策略只丢最旧的,导致轨迹断了一大段。骑手在信号差的区域跑了 20 分钟,队列满了,程序按「先进先出」丢弃最旧的点,结果保留下来的是最后几分钟的密集点,前面 15 分钟完全空白,配送距离算出来只有实际的一小部分,骑手投诉收入算少。修法是改成按价值丢弃:状态变更点、拐点、时间分布均匀的点优先保留。教训是:数据丢弃策略必须服务于「数据的用途」——轨迹的用途是还原路径,所以要保形状而不是保连续。
踩过的坑二:补传的旧点覆盖了实时位置。和服务端那边是同一个坑的两面——客户端补传时把队列里的点按顺序发,服务端按接收顺序处理,最后写入的是最早采集的点,用户看到骑手在地图上退回去了。客户端侧的修法是上报时明确标注这是补传批次,并按采集时间升序排列;服务端侧按采集时间戳判定。两端都要处理,只改一边不够可靠。
没做的部分:没做基于运动传感器的定位优化(用加速度计判断骑手是否在移动,静止时完全停止 GPS)。这能进一步省电,但需要原生开发且各端传感器接口差异大,当时用「连续位置点位移判断静止」近似替代。
「后台定位 4 小时稳定存活」怎么测:真机长时间跑——选几个主流机型(不同品牌,因为厂商策略差异大),开启后台定位后锁屏放置四小时,期间检查服务端收到的位置点是否连续、有无长时间空缺。必须报机型和系统版本,因为这个结论高度依赖厂商实现;也要如实说明「部分机型需要用户手动关闭电池优化才能稳定」。
上报量下降:对比开启客户端预过滤前后,同一段真实配送路径产生的上报点数。降到三分之一上下。要说明这个比例高度依赖场景——等餐时间长的订单降幅大,全程骑行的订单降幅小。给一个范围比给一个精确数字诚实。
耗电:用系统的电量统计看应用在一段固定时长内的耗电占比,对比优化前后。这个数字受机型和其他应用影响很大,所以要在同一台设备同一条件下对比,而且要说明测试条件(屏幕是否常亮、是否同时开着导航)。
弱网补传完整性:构造场景——开飞行模式 10 分钟并持续移动(模拟器或真机步行),恢复网络后检查服务端收到的点数与客户端采集的点数是否一致、顺序是否正确、实时位置是否是最新点。这是个可精确验证的点。
不要报「定位成功率 99%」。定位成功率主要由设备、环境、系统决定,客户端改不了。可以报的是「因客户端原因导致的位置点丢失数」——这个是我们能控制的,而且用绝对数。也不要说「解决了后台定位问题」——iOS 和各厂商的限制是客观存在的,我们只是在规则内做到了可用。
骑手端的操作场景有一个别的客户端都没有的前提:用户在移动中,而且大概率不在看屏幕。他可能正在骑车、正在爬楼、戴着手套。这个前提推翻了很多常规的客户端设计假设。
第一版是按普通 App 做的,问题很直接。
一是新单来了骑手不知道。只有一个界面弹窗和系统通知。骑手在骑车,手机在车架上但他在看路,等他看到已经过了接单时限,单被别人抢了或者回池重派。骑手的反馈是「你们的单我根本抢不到」。
二是抢单的并发处理不当。广播模式下十几个骑手同时抢一单,服务端处理不及时,有骑手的界面显示「抢单成功」但实际没抢到,他骑到店里才发现单不是他的。
三是弱网下状态操作阻塞了骑手。他到店点「已到店」,转圈半天,他不能站在原地等——但如果不等,这个状态就丢了。到取货时又点「已取货」,两个操作都在转圈。
四是超时被当成失败,骑手重复操作。点「已送达」超时提示失败,他又点一次,服务端产生两条送达记录,后台对账时对不上。
所以四个设计:语音播报加震动做提醒、抢单归属由服务端原子判定、状态操作先入本地队列不阻塞、结果三态且未知先查询。后两条和支付、核销、审批是同一套模式,但骑手端多了一个约束:必须允许连续操作——不能因为上一个操作没确认就锁住下一个。
where status = '待接单'),影响行数决定归属。客户端绝不能自己判断「我抢到了」——我们踩过界面显示成功但实际没抢到的坑,骑手骑到店才发现。乐观更新界面是必要的,但它引入了「界面和服务端不一致」的窗口。骑手看到状态已变,实际服务端还没收到。缓解手段是「同步中」标记加待同步计数,让不一致是可见的而不是隐藏的。绝对不能做的是静默乐观更新——那样骑手完全不知道有操作没传上去。
允许连续操作 vs 锁住等确认,这里的选择和支付场景相反。支付场景锁住是对的(防重复付款);骑手端锁住会让他没法工作(站在楼道里等网络)。差异的根源是「操作是否可以在物理世界继续发生」——骑手到店和取货是真实发生的事实,系统不该阻止他记录;而支付是系统内的动作,可以也应该被约束。能说清这个差异,说明理解了业务而不是在套模板。
语音播报的取舍:太吵和听不到之间没有中间地带。播报音量小骑手听不到(路上有环境噪音),音量大在餐厅里会引起注意甚至尴尬。做法是让骑手自己调播报音量和是否震动,并且提供「简洁播报」模式(只念「新单,6 元」)。这类和使用环境强相关的参数不要由产品拍死,交给用户调。
踩过的坑:抢单界面显示成功,实际没抢到。早期为了响应快,客户端点击后立刻显示「抢单成功」再发请求,结果十几个骑手同时抢一单,界面都显示成功,只有一个真的抢到,其余骑手骑到店里才发现单不是自己的,白跑一趟,投诉很激烈。修法是抢单绝不做乐观更新,必须等服务端返回归属结果。教训是:乐观更新只能用在「结果几乎必然成功」的操作上,竞争性操作(抢单、秒杀)绝对不能乐观。这也是为什么状态操作可以乐观(到店是既成事实)而抢单不行(归属由服务端裁定)。
踩过的坑二:队列并行提交导致状态乱序被拒。队列里同时有「已到店」和「已取货」,并行提交,取货先到服务端,而服务端的状态机要求前置状态是已到店,于是取货被拒绝,队列里这条一直重试失败。修法是按订单串行提交(同一订单的操作排队,不同订单可并行)。顺序依赖的操作不能并行提交,这是队列设计里容易漏的一点。
没做的部分:没做语音识别接单(骑手说「接单」就接)。骑行中动手不安全,语音交互是对的方向,但识别准确率在噪音环境下不够,误识别导致误接单的代价大,当时没做。
推送到播报的时长:在服务端推送时刻和客户端播报开始时刻各打点,端到端约 1.5 秒(含长连接传输和语音合成)。要说明用的是长连接还是系统推送——两者延迟差别很大,长连接是秒级,系统推送可能几秒到几十秒(取决于系统调度)。只报长连接的数字要注明。
重复接单与重复状态记录验证:限速模拟弱网,对同一单连点抢单 5 次、对同一状态连点 5 次,检查服务端的订单归属只有一个、状态记录只有一条。跑多轮。还要测「多骑手并发抢同一单」——用多个客户端同时抢,验证只有一个抢到且其余收到明确的「已被接单」提示。
断网连续操作恢复验证:开飞行模式,连续点「已到店」「已取货」「已送达」,恢复网络后检查三条状态按正确顺序提交、订单最终状态正确、无重复记录。这是这个模块最该演练的场景。
不要报「接单成功率提升」。抢单成功与否取决于其他骑手的竞争,不是客户端能决定的。可以报的是「从推送到骑手可响应的时长」(这个是我们能优化的)以及「因客户端原因导致的错误操作记录数」。
也不要报「零重复」。正确表述是「压测多轮未出现,且服务端有状态机条件更新作为第二道防线」。
update order set rider_id=?, status='已接单' where id=? and status='待接单',影响行数为 1 才是抢到,为 0 说明已被他人抢走。客户端提交带幂等键(订单号 + 骑手号 + 客户端操作 ID),重试不会产生第二次归属判定。我们踩过一个很痛的坑:早期为了响应快,客户端点击后立刻显示「抢单成功」再发请求,结果十几个骑手界面都显示成功,只有一个真抢到,其余骑手骑到店里才发现单不是自己的,白跑一趟,投诉很激烈。教训是乐观更新只能用在「结果几乎必然成功」的操作上,竞争性操作绝对不能乐观。另外抢单失败要给明确原因——「已被其他骑手接单」和「网络错误」对骑手的意义完全不同,前者他去抢下一单,后者他会重试,笼统提示会让他做错决策。
骑手很少一次只带一单,高峰期在手三到五单是常态。这带来两个第一版完全没考虑的问题。
一是骑手要自己规划顺序,而且经常规划错。三单在手,哪个先取、哪个先送,涉及取货点和送达点交叉排列。新骑手往往按接单顺序做,结果来回折返,超时被罚。他的反馈是「你们派给我的单根本送不完」——实际是顺序不对。
二是导航体验断裂。骑手点导航跳到第三方地图 App,导航完切回来,应用可能已经被系统回收,重新进来回到首页,他要重新找到当前订单。一趟配送要跳好几次,每次都这样。
第三个问题更隐蔽但影响很大:到店和到点的判定。系统需要知道骑手是否真的到了(用于计算商家出餐超时、用于判定配送时长、用于纠纷)。只用定位判定不可靠——定位有几十米误差、商场里定位飘、而且定位可以被模拟软件伪造。而完全靠骑手手动点,他可能提前点(还没到就点已到店,为了避免超时)。
所以三个设计:给顺序建议但允许骑手调整、导航跳转要处理回流状态恢复、到点判定用多信号而不是单一依据。
navigateTo(坐标, 地址, 名称)。顺序建议做成「建议」而不是「强制」,是有代价的。骑手不按建议走时,系统预估的配送时长和 ETA 就会偏离,用户看到的预计时间不准。但强制的代价更大——骑手会觉得系统不懂实际情况,抵触使用,甚至用别的方式绕过(比如提前点已取货)。取舍是接受 ETA 的偏差,换取骑手对工具的信任,同时用「偏差记录」持续改进排序质量。
用第三方地图而不是自建导航。自建导航要接路径规划、要做转向播报、要处理离线地图,工作量巨大而且做不到第三方的水平。而骑手本来就有自己习惯的地图 App。代价是体验断裂(要跳出去再回来),所以回流恢复必须做好。「不做自己做不好的事,但要把衔接做顺」这个判断比硬做一个二流导航正确。
到点判定的方向是「宁可放过,不可误拦」。如果判定过严(定位不在附近就不许确认到店),商场里的骑手就完全没法工作。所以放宽判定 + 事后风控抽查。反过来如果完全不校验,会有骑手远程点到店来规避出餐超时。方向的选择依据是「误拦的代价(骑手没法工作)远大于放过的代价(少数作弊被事后发现)」。
踩过的坑:导航回来后应用回到首页,骑手要重新找单。第一版没做上下文持久化,骑手每次导航完切回来都在首页,一趟配送要重新找单三四次,他的反馈是「你们这个应用用起来很烦」。修法是唤起导航前持久化当前上下文,回流时(切前台或冷启动)读出来直接跳回。教训是:任何「跳出应用再回来」的流程都必须做上下文持久化——不能假设应用还活着,移动端的应用随时会被回收。
踩过的坑二:定位在附近就自动置「已到店」,导致出餐超时统计全错。骑手在商圈里送另一单,路过了这家店,系统自动把状态置成已到店,于是「商家出餐时长」从那一刻开始计算,实际骑手十分钟后才真的进店,商家被误判为出餐慢。商家投诉,我们才发现。修法是自动判定只用于提示,绝不自动置状态,状态必须由骑手确认。教训是:由传感器信号自动推断的业务状态,如果会影响他方的考核或结算,就必须要人确认。
没做的部分:没做多单路径的精确规划(考虑实际道路和实时路况的最优顺序)。目前用直线距离近似顺路性。接路径规划服务要为每种顺序组合都算一次,成本和延迟都不可接受,而且骑手的经验判断往往比算法好。
顺序建议的质量:报「建议顺序与骑手实际执行顺序的一致比例」。这个指标比「配送时长缩短」诚实得多——后者受商家出餐、路况、骑手个人影响,归因不到排序上。一致比例低说明建议不好,高说明骑手认可。要能说出这个数据怎么采集(对比建议顺序和实际状态流转的时间顺序)。
导航唤起与回流:报各端的唤起成功率与降级触发次数(未安装地图 App 而走降级的次数)。回流恢复要做确定性验证:跳出导航后手动杀掉应用,冷启动验证是否回到原订单页;不杀应用直接切回,验证是否回到原位置。两条路径都要测,只测一条会在另一种情况下失效。
到点判定:报「骑手确认与定位校验不一致的次数」以及其中经运营抽查确认为异常的数量。两个数一起看才有意义——不一致的大部分是定位问题(商场、地下),真正作弊的是少数。只报「拦下多少作弊」会显得判定过严,只报「不一致次数」看不出价值。
不要报「配送超时率下降」。那受太多因素影响。也不要报「到点判定准确率 99%」——「准确」的定义需要一个已知真值的样本集,而「骑手是否真的到店」这个真值本身就很难获得。这类无法建立真值的指标不要报比率。
没有匹配的内容,换个关键词试试。
项目拆解 · 骑手端(uni-app)· 共 3 个模块 · 数字均为示例,需替换成自己项目的真实数据