首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >手持终端的离线补传:信号盲区怎么办

手持终端的离线补传:信号盲区怎么办

原创
作者头像
用户12627399
发布于 2026-10-26 18:19:11
发布于 2026-10-26 18:19:11
1220
举报

去年夏天我们在冷库加了一组货架,深处那两排完全没信号。手持终端扫完码点提交,转两秒,弹一句"网络异常",操作工就以为没提交成功,再点一次。等到月底对账,有十一天的单据在系统里是两条,实际只出了一次货。

另一种更麻烦:点了提交,界面卡住,操作工以为失败,货照发,单没落库。这种单在系统里根本不存在,只能靠盘亏发现。那半年我们盘出过三次这样的差异,每次都要花两天回溯。

后来的做法是把提交改成"先落本地,再排队上传",终端不再依赖当场有网。这篇写这套机制的几个关键决定,以及一份能直接用的补传配置。

一、盲区在哪,单丢在哪

1.1 一次盘亏查出来的丢单

那次盘亏的起点是一箱价值不高的密封件,账上少四箱。查监控、查拣货记录、查交接本,折腾两天,发现是操作工在冷库深处出了一次货,终端提交失败,他没当回事。

这类单的特点是没有任何痕迹:服务端没收到,终端也没留。要查只能靠人回忆。所以补传机制的价值不只是"重试",而是先在本地留一份,哪怕传不上去,单子还在。

1.2 提交失败的三种形态

现场遇到的失败有三种。一种是明确失败:终端弹"网络异常",两秒内返回,操作工能看见。一种是超时:请求发出去了,服务端可能处理了,也可能没处理,终端等八秒没回应就报错。还有一种是假成功:终端收到响应,但服务端事务回滚了,返回体里的业务码是失败。

三种形态要分开处理。明确失败直接进队列等下一次;超时要带幂等键重发,让服务端自己判断是不是重复;假成功要看业务码,业务码是校验错误就不要再重试,重试一万次也是错。

1.3 不能只加一个重试按钮

起初我们的做法是弹窗上加个"重试"按钮。结果是操作工连点五次,服务端收到五条一样的请求,四条成功入库,库存多扣四倍。

重试的前提是幂等。没有幂等键的重试,等于把网络问题放大成数据问题。这个道理说起来简单,现场加一个按钮的时候很少有人先想清楚。

1.4 盲区不止在冷库

后面复盘发现,盲区比想象的多:货梯里、月台外沿、钢结构货架后面、地下卸货通道。这些位置不是完全没信号,是信号在两格和零格之间跳。

信号跳的时候比完全没信号更难处理。请求发出去一半断了,终端不知道服务端收没收到。所以判断"有没有网"不能靠信号格数,要靠一次真实的小请求探活。

二、补传的四个决定

2.1 先落本地再上传

顺序必须反过来:点击提交的瞬间先写本地库,写成功才提示"已暂存";然后再尝试上传,上传成功把本地记录标记为已同步。

本地库用手持终端自带的轻量数据库就够,一张表存单号、报文、创建时间、重试次数、状态。状态只有四个:待传、传输中、已同步、待人工。四个状态够用,多了现场看不懂。

2.2 单号在前,上传在后

单号必须在终端生成,不能等服务端返回。服务端生成的单号意味着没联网就没有单号,没单号就没法幂等,整条链路立不住。

我们的单号规则是终端编号加日期加当天流水,长度固定十八位。流水号存在本地,每次加一,重装应用会重置,所以要在单号里带上终端编号,避免两台终端撞号。

2.3 顺序与并发

补传要按生成顺序来,不能并发。原因很实际:同一个物料的出库和退库如果乱序上传,库存会短暂出现负数,虽然终归一致,但中间那一刻的报表是错的,值班的人会来问。

我们的做法是单线程顺序补传,一次取二十条,传完再取下一批。二十条这个数不是拍的,是实测出来的:再大,一次失败要重传的量大;再小,请求开销占比高。

2.4 一份补传配置

下面这份配置是终端侧的实际参数,字段名按我们自己的叫法,逻辑通用。重点看三块:队列的退避策略、提交的重试白名单、冲突的处理方式。

代码语言:json
复制
{
  "offline": {
    "enabled": true,
    "storage": "sqlite",
    "maxRecords": 5000,
    "maxDays": 7,
    "warnDepth": 500
  },
  "queue": {
    "writeAhead": true,
    "order": "bySeqNo",
    "batchSize": 20,
    "flushIntervalMs": 30000,
    "maxRetry": 8,
    "backoff": {
      "baseMs": 2000,
      "factor": 2,
      "capMs": 300000,
      "jitter": 0.3
    }
  },
  "submit": {
    "idempotencyKey": "clientDocNo",
    "timeoutMs": 8000,
    "retryOn": ["NETWORK", "TIMEOUT", "SERVER_5XX"],
    "noRetryOn": ["VALIDATION", "AUTH", "DUPLICATE"],
    "probeBeforeFlush": true
  },
  "conflict": {
    "strategy": "serverWins",
    "quarantineTable": "sync_conflict",
    "onQtyMismatch": "review"
  },
  "observability": {
    "logQueueDepth": true,
    "uploadNetworkOnly": false
  }
}

几个字段值得单独说。maxRetry 设八次,配合指数退避,从两秒涨到三百秒封顶,八次大概覆盖四十分钟,正好是一个班次内的合理窗口;超过八次就转人工,不再无限重试。jitter 加三成随机,是为了避免二十台终端在同一秒一起重连,把接入端冲垮。

retryOn 和 noRetryOn 分开列,是关键的一刀。校验错误、鉴权失效、服务端明确说重复,这三类重试没有意义,重试只会把队列堵死。probeBeforeFlush 是补传前先发一个轻量探活请求,探不通就不启动整批,省掉二十次超时等待。

onQtyMismatch 设成 review 而不是自动覆盖,是因为库存这类数不能盲信终端。补传的单据如果发现当前库存不足以扣减,直接进隔离表,让人看,不自动改账。

三、冲突怎么处理

3.1 终端时间不可信

补传的报文里带客户端时间,但这个时间只能当参考。操作工改过系统时间、终端电池拔过、时区设错,都会让时间失真。

真正用于排序的是终端本地的自增序号,单调递增,不依赖时钟。服务端收到之后按序号重排,再落库。时间字段只用来给人看,不用来做业务判断。

3.2 服务端为准的字段

一条出库单里有几类字段:操作工填的数量、终端采集的条码、系统算的单价、库存扣减结果。前面两类以终端为准,因为只有现场知道;后面两类以服务端为准,因为终端拿到的是旧值。

这条界线要写进接口约定,不能靠默契。我们起初没写清,终端把单价一起传上来,服务端照着存,结果调价之后补传的单据还是老价格,对账对不平。

3.3 补传时库存已经变了

补传发生在几分钟到几小时之后,这段时间库存可能已经被别人动过。硬扣会出现负数,不扣又和实物不符。

我们的处理是加一道条件扣减:库存够就扣,不够就整单进隔离表,同时给终端回一条"待人工确认"。隔离表里的单子由库管当天处理,能调的调,不能调的走差异流程。

3.4 隔离件要有人管

隔离表怕的是没人看。上线初期我们建了表,没建待办,一个月积了七十多条,等发现的时候已经过了追溯期。

现在的做法是隔离件进班组长的待办列表,每天早会过一遍,超过二十四小时未处理的自动升级到主管。技术上不复杂,难的是把它变成日常动作。

四、现场怎么用

4.1 员工看到的只有三个状态

终端界面上,操作工只需要看三个提示:已上传、已暂存、待确认。不要给他看队列深度、重试次数、错误码,那些是给运维看的。

"已暂存"这个提示要做得明确,带一个小图标和条数,比如"已暂存 3 单,回到有信号的地方会自动上传"。含糊的提示会让操作工反复点提交,反而制造重复。

4.2 交接班查一次队列

我们要求交接班时看一眼终端上的待传条数,写进交接本。这条规则很土,但有效:待传条数不清零,说明有问题,接班的人会去问。

这一条也是我们补传调度的一个兜底,队列积压超过阈值会推一条提醒给班组长,具体的定时调度放在搭贝的定时任务里跑,和补传队列的探活错开时间。这个提醒不解决技术问题,但能让问题在当天被发现。

4.3 电量与存储也要算进方案

冷库作业一个班八小时,终端开着扫码和定位,电量掉得快。电量到两成还必须能完成本地落库,这是硬要求,不能因为省电把写本地关掉。

存储也一样。五千条的上限是按一兆左右的报文估的,实际跑下来一条出库单平均一点二K,五千条六兆,终端的剩余空间够。上限到了之后先传,传不出去才提示清理,不要主动删未同步的数据。

五、上线后的情况

5.1 丢单从每月十一张到零

改成离线优先之后,头一个月还有三张假成功没被识别,都是业务码判断漏了。补上业务码白名单之后,连续四个月没有出现无痕迹的丢单。

重复单也降下来了。以前一个月平均七条重复,现在幂等键在服务端挡着,重复提交要么被识别为已处理,要么直接返回原单号,不再产生第二条库存流水。

5.2 一个没做好的地方

补传失败的提示还是偏技术。终端上写的是"同步失败,错误码 409",操作工看不懂,只能找班长。这一块我们后来改成中文短句加一个"找谁"的说明,但推送更新的那两周,现场抱怨不少。

技术方案能解决数据一致性,解决不了理解成本。这一条我记着,下次做终端功能,提示语要在设计阶段就写出来,别留到上线前补。

常见问题

问:幂等键放在终端生成,两台终端撞号怎么办?

答:单号里必须带终端唯一编号。只靠日期加流水,两台终端的流水都从一往上加,撞号是必然的。服务端收到之后先查这个键,存在就返回原单结果,不再落库。如果业务上确实可能出现同号不同内容,说明单号规则设计有问题,不要靠服务端补救。

问:本地队列积压到上限,新单还能不能落库?

答:能,但要按策略丢弃旧数据中的哪一类必须提前定。我们的做法是优先保留未同步的作业单,先清已同步的历史记录,再清重复提交产生的冗余记录。绝不能按时间先进先出直接删,那样删掉的可能是唯一一份实物凭证。

问:补传顺序被打乱,库存出现负数怎么防?

答:两道。终端侧单线程顺序补传,保证同一台终端不乱序;服务端侧所有扣减都带条件,库存不足就整单转隔离,不做部分扣减。跨终端之间仍然可能乱序,所以服务端的条件扣减不能省,它才是兜底的那一道。

问:终端时间被改过,补传的先后怎么判断?

答:用本地自增序号,不用时间。序号由终端维护,单调递增,写库时和报文一起持久化,不随系统时间变化。服务端只按序号排序,时间字段仅作展示。如果终端被重装导致序号重置,靠单号里的终端编号加日期做二次分段。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

目录
  • 一、盲区在哪,单丢在哪
    • 1.1 一次盘亏查出来的丢单
    • 1.2 提交失败的三种形态
    • 1.3 不能只加一个重试按钮
    • 1.4 盲区不止在冷库
  • 二、补传的四个决定
    • 2.1 先落本地再上传
    • 2.2 单号在前,上传在后
    • 2.3 顺序与并发
    • 2.4 一份补传配置
  • 三、冲突怎么处理
    • 3.1 终端时间不可信
    • 3.2 服务端为准的字段
    • 3.3 补传时库存已经变了
    • 3.4 隔离件要有人管
  • 四、现场怎么用
    • 4.1 员工看到的只有三个状态
    • 4.2 交接班查一次队列
    • 4.3 电量与存储也要算进方案
  • 五、上线后的情况
    • 5.1 丢单从每月十一张到零
    • 5.2 一个没做好的地方
  • 常见问题
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档