扫码业务 APP 后端接口设计的核心挑战
扫码业务 APP 看似简单——用户打开摄像头、扫描条码、获取结果。但在企业级场景中,后端接口设计远比前端复杂。仓库盘点、物流追踪、医疗耗材管理、资产清查等业务,要求 APP 在弱网或离线环境下也能稳定工作,同时保证多用户并发扫码的数据一致性。
本文从鉴权、API 设计、数据同步、离线冲突和版本兼容五个维度展开,面向有后端开发经验的团队,讨论工程层面的具体决策。
一、鉴权方案:长 Token 与短 Token 的分工
扫码业务 APP 的鉴权不能简单套用 Web 的 Session-Cookie 模式。APP 的生命周期长,用户可能几天才关闭一次应用,且扫码操作频繁,每次请求都重新鉴权会显著增加延迟。
1.1 双 Token 机制
推荐使用 Access Token + Refresh Token 组合:
- **Access Token**:有效期短(通常 15-30 分钟),携带在每次 API 请求的 Header 中。扫码场景下,用户连续扫码时不需要频繁刷新,减少网络交互。
- **Refresh Token**:有效期长(7-30 天),存储在 APP 安全存储区(iOS Keychain / Android EncryptedSharedPreferences)。当 Access Token 过期时,APP 自动用 Refresh Token 换取新 Access Token,用户无感知。
1.2 设备绑定与离线鉴权
离线场景下,用户无法与服务器通信,但扫码功能必须可用。处理方法是:
- 首次登录时,服务器下发一个加密的 **离线凭证**(Offline Token),包含用户 ID、权限范围和过期时间,用服务端密钥签名。
- APP 本地存储该凭证。离线扫码时,APP 将扫码结果和离线凭证打包,等网络恢复后一并提交。
- 服务端收到离线提交时,先验证凭证签名和有效期,再处理数据。凭证有效期通常设为 24-72 小时,防止凭证泄露后长期滥用。
1.3 权限粒度控制
扫码业务 APP 的权限通常按操作类型划分:
- 扫码查询(只读)
- 扫码入库/出库(写入)
- 扫码盘点(修改库存状态)
- 管理员操作(修改配置、导出数据)
后端接口应在鉴权中间件中校验权限,而不是在业务代码中重复判断。用 RBAC(基于角色的访问控制)模型,将权限与角色绑定,用户登录时返回角色列表,接口层统一验证。
二、API 设计:RESTful 还是 RPC?
扫码业务 APP 的 API 设计没有绝对标准,取决于团队习惯和业务复杂度。我参与过的项目两种都使用过,这里给出对比和适用场景。
2.1 RESTful 风格
适合资源模型清晰的场景,例如:
- `GET /api/v1/products/{barcode}` — 根据条码查询商品信息
- `POST /api/v1/inventory/records` — 提交扫码入库记录
- `PUT /api/v1/inventory/records/{id}` — 修改某条扫码记录
优点:语义清晰,容易缓存,符合 HTTP 规范。缺点:批量操作和复杂查询需要额外设计,例如一次扫码可能涉及多个资源更新(库存减少、记录增加、日志写入),用 REST 需要多个请求。
2.2 RPC 风格(推荐)
对于扫码业务,我更推荐轻量 RPC 风格,因为扫码操作往往是动作导向,而非资源导向。例如:
- `POST /api/v1/scan/check` — 扫码校验(返回商品信息和校验结果)
- `POST /api/v1/scan/commit` — 提交扫码结果(包含条码、数量、位置、时间)
- `POST /api/v1/scan/batch-commit` — 批量提交离线扫码记录
RPC 风格的接口可以针对业务场景优化参数和返回值,减少前后端沟通成本。缺点是接口命名可能膨胀,需要做好版本管理。
2.3 接口版本管理
无论选择哪种风格,接口版本管理是必须的。推荐两种方式:
- **URL 路径版本**:`/api/v1/scan/commit`,简单直观,适合中小型项目。
- **Header 版本**:通过 `Accept-Version` 头指定版本,适合大型系统,避免 URL 污染。
扫码 APP 的版本迭代频繁,例如从支持单条码升级到支持批量条码扫描,后端接口版本必须向前兼容至少一个主版本。放弃旧版本时,提前一个版本周期(通常 3-6 个月)在响应头中返回 `Deprecation` 警告。
三、数据同步:增量同步与冲突处理
离线扫码是扫码业务 APP 的核心能力,也是后端设计最大的难点。数据同步不是简单的“上传下载”,必须处理并发冲突、数据一致性和带宽优化。
3.1 增量同步机制
全量同步在数据量超过 10 万条时不可行,扫码业务 APP 必须采用增量同步。
- **客户端**:维护一个本地 `sync_cursor`(同步游标),通常是上次同步成功的时间戳或递增 ID。每次同步请求时带上该游标。
- **服务端**:返回自游标之后发生变更的数据,包括新增、更新和删除记录。删除记录使用软删除(逻辑删除),同步时标记为 `deleted`,客户端收到后本地删除或标记。
- **数据格式**:使用 JSON Lines 或 Protocol Buffers,单次同步数据量控制在 1MB 以内。如果超过,服务端返回分页信息,客户端循环请求。
3.2 冲突类型与处理策略
离线扫码中最常见的冲突场景是:两个用户在不同地点同时扫码操作同一件商品,或者同一用户离线时扫码,在线后又扫码同一商品。
**冲突类型一:库存冲突**
假设仓库中某商品库存为 100 件。用户 A 离线扫码出库 10 件,用户 B 在线扫码出库 20 件。当 A 提交离线数据时,服务器库存已变为 80 件。此时:
- **策略:后提交失败**。服务器检测到库存不足(80 - 10 = 70,大于 0,实际可执行),但如果 A 提交时库存已经为 50,则拒绝操作,返回冲突错误。
- **策略:强制覆盖**。某些业务(如盘点)允许覆盖,服务器直接更新库存为 A 提交的值。这种策略风险高,需要业务确认。
- **推荐策略:版本号乐观锁**。每条库存记录带一个 `version` 字段。提交时服务端校验 `version` 是否匹配,不匹配则返回冲突,客户端提示用户重新扫码确认。
**冲突类型二:数据重复**
用户离线扫码时,本地生成了一个临时 ID(UUID),提交到服务端。服务端可能已经存在相同条码的记录(例如同一商品被多次扫码入库)。处理方式:
- 在服务端以 `barcode + location + timestamp` 组合去重,允许一定时间窗口(如 5 秒)内的重复提交被忽略。
- 客户端使用幂等提交:每次提交附带一个唯一请求 ID(UUID),服务端缓存已处理的 ID,重复提交直接返回成功。
3.3 同步时序设计
同步流程建议采用“先拉取后提交”的顺序:
1. APP 启动或从后台恢复时,先请求增量同步,获取最新的商品信息、库存状态和配置。
2. 用户离线扫码,数据暂存本地 SQLite 或 Realm。
3. 网络恢复后,APP 按时间顺序批量提交离线记录。
4. 提交完成后,再次拉取增量同步,确认数据一致。
这个顺序能最大程度减少冲突:用户先看到最新库存,再操作,提交后立即确认结果。
四、离线处理的工程细节
离线处理不只是数据同步,还涉及本地存储、队列管理和 UI 反馈。
4.1 本地数据库选型
- **SQLite**:稳定、成熟,适合结构化扫码数据。配合 WAL 模式提高并发写入性能。
- **Realm**:对象存储,适合复杂数据模型,但库体积较大,且需要关注版本兼容。
- **Room**(Android)和 **Core Data**(iOS)是平台推荐方案,但跨平台项目建议统一使用 SQLite。
本地表结构至少包含:`local_id`(UUID)、`barcode`、`action_type`(入库/出库/盘点)、`quantity`、`location`、`timestamp`、`sync_status`(pending/synced/failed)、`server_id`(同步后填充)。
4.2 离线队列管理
扫码记录在本地以队列形式存储。APP 需要:
- 按 FIFO(先进先出)顺序提交,保证操作时序。
- 支持重试机制:提交失败(网络错误或冲突)后,将记录标记为 `failed`,等待下次同步或用户手动处理。
- 限制并发:一次最多提交 50 条记录,避免大包请求导致超时。
4.3 用户离线体验
- 扫码后立即显示结果,不要等待服务器确认。显示“已保存(离线)”状态。
- 在 APP 顶部显示同步进度条或待同步数量。
- 冲突发生时,不要自动覆盖,弹出提示让用户选择“以我的为准”或“以服务器为准”。选择后记录用户决策,便于审计。
五、版本兼容:API 向前兼容的实践
扫码业务 APP 的版本更新频率高,但用户不一定及时更新。后端接口必须兼容至少两个大版本。
5.1 接口字段兼容
- 新增字段时,使用可选字段(optional),客户端不传则使用默认值。
- 废弃字段时,在响应中保留该字段但标记为 `deprecated`,并在文档中注明废弃版本和替代方案。
- 不要修改已有字段的类型或含义。例如,`quantity` 从整数改为浮点数,会导致旧客户端解析错误。
5.2 同步协议版本
在同步请求和响应中携带 `sync_protocol_version` 字段。当服务端升级同步协议时,旧版本客户端仍使用旧协议交互,直到强制升级。
5.3 灰度发布与回滚
扫码业务对实时性要求高,后端接口变更必须支持灰度发布。建议:
- 使用 Feature Flag 控制新接口的启用范围。
- 新接口上线后,监控错误率和响应时间,至少观察 48 小时。
- 准备回滚脚本:如果新接口导致大量冲突或同步失败,快速切回旧版本。
六、性能与安全考量
6.1 扫码接口响应时间
用户扫码后期望 1 秒内看到结果。后端接口(如扫码校验)的 P99 响应时间应控制在 500ms 以内。优化手段:
- 使用 Redis 缓存商品信息和条码映射,避免每次查询数据库。
- 批量扫码场景下,服务端支持一次请求处理多个条码,减少网络往返。
- 对高频查询接口启用 HTTP 缓存头(Cache-Control: max-age=60),允许 APP 短时间缓存。
6.2 防刷与限流
扫码接口容易被恶意脚本攻击。必须实施:
- **频率限制**:按用户 ID + 设备 ID 限制每秒请求数,正常扫码频率约为每秒 2-5 次,超过 10 次/秒视为异常。
- **条码校验**:对条码长度和字符集做基本校验,拒绝明显非法的条码(如全零、超长字符串)。
- **签名校验**:敏感操作(如出库、修改库存)的请求必须携带 HMAC 签名,防止请求被篡改。
6.3 日志与审计
扫码业务涉及库存和资产变动,所有操作必须有完整的审计日志。日志至少记录:用户 ID、设备 ID、操作时间、条码、操作类型、变更前后数值、IP 地址。日志存储周期建议不少于 180 天。
七、总结:从项目经验看设计取舍
扫码业务 APP 的后端设计,本质上是在“高可用”和“数据一致性”之间做权衡。离线场景越重,同步逻辑就越复杂。
在 SystemDo 参与过的多个扫码项目中,我们发现一个规律:团队往往在前期低估离线冲突的复杂性,等到上线后才发现数据对不上。因此,设计阶段就应把离线同步和冲突处理作为核心功能来规划,而不是后期修补。
最后,技术选型没有银弹。如果你的业务是纯在线扫码(如门店收银),可以简化离线逻辑;如果是仓库盘点或野外巡检,则必须投入精力做好冲突处理和版本兼容。理解业务场景,比追求完美架构更重要。