一卡通人员全量转发与可选本地保存设计
文档信息
| Use Case ID | UC-ONECARD-002 | 版本 / 状态 | v1.5 / 人员编号已改为 OUTID,待真机业务转发验证 |
| 名称 | 终端全量获取新开普人员并分批转发业务服务器 | DoR | READY |
| 优先级 | P0 | 目标项目 / 日期 | ZhctTrayBindingMachine / 2026-09-03 |
1. 背景与现场结论
现有实现先按 1000 条从新开普一卡通拉取并写入 SQLite,形成完整本地快照后,再从 SQLite 按 1000 条读取并转发业务服务器。
- 2026-09-03 真机核对:新开普全量人员共 26,082 条,源端拉取和本地落库成功。
- 业务转发在发出请求前失败,错误为
BM is required。
- 源端部门字段为
CUSTDEPT;人员编号应使用 OUTID。旧快照 26,082 人中 OUTID 全部不同,EMPCODE 仅 8 个不同值,旧映射造成业务端重复编号拒绝。
- 同步状态
pulledCount=26082、processedCount=0,失败点是本地字段映射,不是一卡通连接或业务 HTTP 接口。
- 一次请求 26,089 人后,真机在
ByteArrayOutputStream.toString() 将约 75 MB 响应复制为字符串时 OOM,源端必须限制单次响应体量。
2. 已确认需求与技术决定
- 先取得总人数,再按每批最多 1000 人调用
GetCustomerByNum;首批 custid=0,后续按上一批最大 CUSTOMERID + 1 推进。
- 所有分页须全部解析、跨批去重和严格核数,并在发出第一批业务请求前完成全部字段映射校验。
- 调用
POST /api/oneCardTerminal/syncPersons 时每批最多 1000 条,按原顺序串行转发。
- 采用 JSON,
Content-Type: application/json; charset=UTF-8。
- 请求增加
one_card_type,固定为 新开普,协议常量集中定义。
- “一卡通设置”增加“本地是否保存人员数据”,默认关闭并由 MMKV 持久化。
- 开关关闭时不创建、不写入本地快照;打开时仅在全部业务批次成功且
failed == 0 后静默保存。
- 本地保存失败只记录错误,不反向改判已完成的业务转发。
- 不修改业务 Base URL、鉴权和网络日志策略。
3. 范围
In Scope
- 源端 1000 人分页获取并汇总、正确字段映射、1000 条 JSON 分批转发。
- 一卡通设置页保存开关、后置静默本地快照。
- 同步状态、日志、单元测试、构建和真机验证。
Out of Scope
- 不修改一卡通服务端或业务服务器。
- 不实现单人补偿队列、失败明细下载或断点续传。
- 不修改读卡、绑盘、扣费和消费记录上传,不新增第三方依赖。
- 不执行 Git 暂存、提交、推送、分支切换等 Git 写操作。
4. 参与者与数据边界
| 参与者 / 系统 | 职责 | 数据边界 | 交互方式 |
| 设备管理员 | 配置地址、AppID 和保存开关 | 当前设备配置 | 一卡通设置 |
| 绑盘机终端 | 拉取、校验、转发、可选保存 | 本轮全量人员 | 后台同步任务 |
| 新开普一卡通 | 提供总数和人员字段 | 新开普人员数据 | SOAP |
| 业务服务器 | 接收标准化人员 | 每批最多 1000 条 | JSON HTTP API |
| SQLite | 可选保存完整快照 | 仅成功转发的全量数据 | 本地事务 |
5. 成功与失败后置条件
- 成功:所有业务批次
code == 0 且累计 failed == 0;开关关闭时本地不写入,打开时保存完整快照。
- 拉取、核数或映射失败:不上传、不保存。
- 业务批次失败:停止后续批次、不保存;已成功批次无法由终端回滚。
- 某批存在失败人员:完成剩余转发并报告部分失败,但不保存本地。
- 本地保存失败:事务回滚、保留旧快照、记录日志,业务转发仍判成功。
6. 主流程
| 步骤 | 系统动作 | 数据 / 状态 | AC |
| M1 | 读取一卡通地址、AppID 和保存开关 | 开关默认 false | AC-001 |
| M2 | 连接并取得 GetCustomerCount | 非负总人数 | AC-002 |
| M3 | 人数大于 0 时循环请求 GetCustomerByNum,单批最多 1000 人 | 每批解析后释放原始 XML | AC-003 |
| M4 | 累计分页、核数、校验游标与跨批账号唯一 | 完整内存列表 | AC-004 |
| M5 | 映射全部业务 DTO | 网络上传前完成全量校验 | AC-005 |
| M6 | 按 1000 条切分并串行转发 | 汇总 total/created/updated/unchanged/failed 和 trace ID | AC-006 |
| M7 | 成功后检查保存开关 | 关闭则结束,打开则保存 | AC-007 |
| M8 | 事务替换本地快照 | 成功保存或失败回滚 | AC-008 |
| M9 | 释放资源并回报业务同步结果 | 不显示本地保存进度 | AC-009 |
7. 分支与异常 / 恢复
| ID | 条件 | 处理 | 恢复 |
| A1@M3 | 总人数为 0 | 不调用要求 num > 0 的查询,以空列表完成业务阶段 | 开关打开时保存空快照 |
| A2@M7 | 开关关闭 | 跳过全部 SQLite 写入 | 下一轮打开后生效 |
| E1@M2-M4 | SOAP / 解析 / 核数失败 | 不上传、不保存,记录阶段 | 下一轮全量重试 |
| E2@M5 | 字段缺失或非法 | 报告字段与人员标识,确保未上传 | 修正数据或映射后重试 |
| E3@M6 | HTTP、code、空响应或汇总异常 | 停止后续批次,不保存 | 依赖业务幂等全量重试 |
| E4@M6 | failed > 0 | 累计并继续后续批次,最终不保存 | 按 trace ID 排查后重试 |
| E5@M8 | SQLite 异常 | 事务回滚,保留旧快照 | 下一轮成功转发后重试 |
8. 字段与接口契约
| 业务字段 | 新开普字段 | JSON 类型 | 规则 |
bm | CUSTDEPT | String | 必填,最长 200 |
xh | OUTID | String | 必填,最长 60;人员外部唯一编号 |
name | NAME | String | 必填,最长 40 |
sex | SEX | Integer | 必填;1 男、0 女 |
customerid | CUSTOMERID | Integer | 必填并与记录 ID 一致 |
cardno | CARDNO | Integer | 必填,十进制整数 |
status | STATUS | Integer | 保持源值 |
cardsn | CARDSN | Integer | 必填,十进制整数 |
不得截断字符串或为非法值补默认值。
业务请求:POST /api/oneCardTerminal/syncPersons;Content-Type: application/json; charset=UTF-8;每批 1..1000 条。
{
"one_card_type": "新开普",
"items": [{
"bm": "013",
"xh": "employee001",
"name": "示例人员",
"sex": 1,
"customerid": 1,
"cardno": 655493,
"status": 1,
"cardsn": 2
}]
}
接口资料中的 OpenAPI form-urlencoded 描述与 JSON 示例冲突,本轮以用户确认的 JSON 契约为准;下载目录中的原资料不改动。
业务成功响应兼容 unchanged(已存在且无需更新);旧响应缺失时按 0 处理,汇总按 created + updated + unchanged + failed == total 校验。
9. 设置与本地保存契约
- 页面 / 标签:“一卡通设置” / “本地是否保存人员数据”;复用现有
SwitchButton。
- MMKV 默认值
false;每轮开始时读取一次,同步中切换只影响下一轮。
- 只有全部业务批次结束且累计失败为 0 才保存。
- 完整快照在一个 SQLite 事务中替换;失败不得留下半份新快照。
- 保存过程无独立进度或弹窗,只写开始、数量、耗时和失败日志。
10. 实现边界
- 全量人数来自本轮
GetCustomerCount,不使用固定 50000。
- 一卡通拉取由
CustomerFullFetchService.SOURCE_PAGE_SIZE = 1000 维护;业务上限由 SyncPersonsRequest.MAX_ITEMS = 1000 维护,两者不复用。
- 拆分全量获取、业务转发、可选本地保存三个职责,由协调器顺序调用。
- 开关关闭时不实例化本地人员仓库。
11. 验收标准
| AC | Given / When | Then | 证据 |
| AC-001 | 从未保存开关 / 打开设置 | 显示关闭 | UI / MMKV 测试 |
| AC-002 | 连接正常 / 启动同步 | 总人数驱动全量获取 | 测试 / 日志 |
| AC-003 | 总数 26,089 / 获取 | 27 次请求:前 26 批 1000、末批 89,游标按最大账号加一推进 | mock / 真机日志 |
| AC-004 | 数量或账号异常 / 解析 | 上传前失败 | 单元测试 |
| AC-005 | 源字段 CUSTDEPT / OUTID / 映射 | 生成 bm / xh,不使用 EMPCODE | mapper 测试 / 真机统计 |
| AC-006 | 2,601 人 / 转发 | 1000、1000、601 串行发送并带固定 type | 服务测试 |
| AC-007 | 开关关闭 / 转发成功 | SQLite 不写入 | 协调器测试 |
| AC-008 | 开关打开 / 转发成功 | 完整快照保存,异常回滚 | 仓库测试 |
| AC-009 | 批次失败或人员失败 / 结束 | 不保存本地 | 测试 / 日志 |
| AC-010 | 业务收到请求 / 核对 | JSON UTF-8 且每批不超 1000 | 契约测试 / 日志 |
| AC-011 | 业务响应包含 unchanged / 校验 | 计入 total 汇总,不误报汇总无效 | 服务测试 / 日志 |
12. 测试矩阵
| Test ID | 层级 | 覆盖 | 预期 |
| UT-001 | 单元 | 开关默认 / 保存 | 默认 false,切换持久化 |
| UT-002 | 单元 | 全量获取 0、1、2,601、26,089 | 分页正确、跨批去重并严格核数 |
| UT-003 | 单元 | 字段映射 | CUSTDEPT / OUTID 正确映射,缺失 OUTID 时拒绝 |
| UT-004 | 单元 | 请求序列化 | 固定 one_card_type、items 和 1000 上限 |
| UT-005 | 单元 | 业务分批 | 边界和顺序正确 |
| UT-006 | 单元 | 协调器 | 仅成功且开关打开才保存 |
| UT-007 | 单元 | 业务响应 unchanged | 计入 total,缺失时按 0 兼容 |
| IT-001 | Android 仓库 | 快照原子替换 | 成功一致,异常保留旧快照 |
| BUILD-001 | 构建 | 单测 / APK | testDebugUnitTest、assembleDebug 通过 |
| DEVICE-001 | 真机 | 26,089 条,默认关闭 | 27 个源端分页、27 批转发、无 OOM、不落库 |
| DEVICE-002 | 真机 | 保存开关打开 | 转发后静默落库,数量一致 |
13. 非功能、安全与可观测性
- 容量至少覆盖现场 26,089 条;单个 SOAP 原始响应最多承载 1000 人。
- 业务批次串行;每个源端分页解析后释放原始 XML,只保留轻量记录,任务结束后释放全量对象。
- 日志不打印完整请求体,只记录人数、批次、业务标识、字段、耗时和 trace ID。
- 失败重试从第一批重新转发,依赖业务端按人员唯一标识幂等更新。
- 单批 1000 人沿用现有网络超时,并记录每批请求与解析耗时。
14. 风险、回滚与待确认项
风险:内存仍保留完整轻量人员列表;中途失败时已成功业务批次无法回滚;原接口资料的 Content-Type 表述不一致。
- 代码可回滚到旧的“先本地快照、再上传”流程。
- 本地事务失败回滚旧快照;不涉及数据库版本迁移。
- P0 待确认项:无。
- 本轮没有唯一云效项目 / 需求编号,本文作为本地可执行需求真源;不声称已同步云效。
15. 追踪矩阵
| 步骤 | 页面 / API | 规则 | AC / Test | 实现 |
| M1 | 一卡通设置 / MMKV | 默认关闭、单轮冻结 | AC-001 / UT-001 | 已实现,构建通过 |
| M2-M5 | 后台任务 / SOAP | 源端 1000 人分页、先全量校验 | AC-002~005 / UT-002~003 | 真机 26,089 人、27 页通过,无 OOM |
| M6 | syncPersons | JSON、1000 条、串行 | AC-006/010 / UT-004~005 | 已实现,单测通过 |
| M7-M8 | SQLite | 成功后按开关原子保存 | AC-007~009 / UT-006/IT-001 | 已实现,真机 SQLite 待验 |
| M9 | 状态 / 日志 | 静默保存、资源释放 | AC-009 / BUILD/DEVICE | 构建通过,真机待验 |
16. 变更记录
| 版本 | 日期 | 变更内容 | 来源 |
| v1.0 | 2026-09-03 | 一次全量拉取、业务端 1000 条 JSON 分批、字段纠正、固定一卡通类型和可选后置本地保存 | 用户确认、真机日志、现有代码及接口资料 |
| v1.1 | 2026-09-03 | 回写 sync_user_list 实现状态、自动化结果和真机待验边界 | 源码差异、153 个单测和 Debug 构建 |
| v1.2 | 2026-09-03 | 一次全量响应 OOM 后,将一卡通获取改为每批最多 1000 人,仍保持全部拉取后再业务上传 | AndroidRuntime 崩溃栈、用户确认 |
| v1.3 | 2026-09-03 | 26,089 人分 27 页拉取成功;业务端累计拒绝 26,081 人,作为独立问题待查 | 设备日志与 trace ID |
| v1.4 | 2026-09-03 | 兼容业务响应新增的 unchanged,汇总改为 created + updated + unchanged + failed = total | 真机响应与用户确认 |
| v1.5 | 2026-09-03 | 根据唯一性证据将人员编号从 EMPCODE 改为 OUTID | 旧快照字段基数、业务响应成功数、用户确认 |