ZhctTrayBindingMachineConsumptionActivityUC-ONECARD-001项目已经具备一卡通连接验证、每批 1000 人全量拉取、本地 SQLite 快照以及向业务服务器分批上传的底层能力,但当前实现把 SQLite 分批写入放在拉取关键路径中,业务上传又依赖成功完成的数据库快照。数据库写入耗时会直接延后配置服务器转发,同时消费页尚未调用和展示该链路。
进入消费页时自动执行一次“连接验证—全量拉取—可靠 JSON 暂存—业务上传”流程;操作人员能够查看连接与同步进度、最近结果和毫秒级操作时间,也能从页面右侧入口手动再次同步。本地 SQLite 降级为辅助存储与必要查询,由独立后台任务导入,不参与主同步成功判定,也不得阻塞配置服务器转发。
ConsumptionActivity 后自动显示同步弹框并启动一次同步。一卡通 WebService 使用 HTTP/SOAP,connect() 通过 GetCustomerCount 验证 URL、AppID、网络和认证,并非建立需要持续保持的 Socket。同步结束后客户端会清除本地连接参数。
因此,右侧入口展示“最近一次连接验证结果”,而不是 OnecardWebServiceClient.isConnected() 的瞬时值:
| 最近验证结果 | 图标与颜色 | 文案 |
|---|---|---|
| 成功 | 绿色 | 一卡通服务可用 |
| 失败或从未验证 | 红色 | 一卡通服务不可用 |
连接验证结果和人员同步结果相互独立:连接验证成功但业务服务器上传失败时,入口仍为绿色,弹框中的同步结果为失败。
ConsumptionActivity.onCreate() 完成现有初始化后显示弹框。onStart() 重复同步。弹框标题为“一卡通服务同步”,包含:
弹框视觉必须复用项目现有设置类弹框规范:1000dp 白色圆角容器、蓝色渐变标题栏、白色 40sp 标题、灰色描边内容区、蓝色描边关闭按钮、蓝色渐变手动同步按钮,以及与 pop_bac_color 一致的遮罩透明度。运行期间手动同步按钮使用禁用态灰色背景。
可见时间统一格式为 yyyy-MM-dd HH:mm:ss.SSS。
| 步骤 | 系统行为 | 可见状态 | 数据变化 | 验收 |
|---|---|---|---|---|
| M1 | 页面完成现有初始化 | 弹框出现,显示准备连接 | 读取最近状态 | AC-001 |
| M2 | 调用 GetCustomerCount 验证一卡通服务 | 正在连接一卡通服务 | 记录本轮开始 | AC-002 |
| M3 | 连接验证成功 | 显示连接成功、人数和毫秒时间;右侧变绿 | 持久化最近连接成功结果 | AC-003 |
| M4 | 按账号游标每批最多 1000 人拉取、解析和校验 | 显示批次及已拉取/总人数 | 生成不可变人员批次 | AC-004 |
| M5 | 每批写入应用私有 JSON 文件 | 显示正在暂存 | 强制刷盘、SHA-256 校验、原子提交批次 | AC-005 |
| M6 | 收齐全部批次并核对总人数 | 显示全量准备完成 | 原子标记本次快照 READY | AC-006 |
| M7 | 启动远端上传和后台数据库导入 | 显示上传进度;本地显示后台更新中 | 两个消费者只依赖 JSON,不彼此等待 | AC-007 |
| M8 | 按业务接口上限每批最多 1000 人上传 | 显示批次、已处理和失败数 | 原子记录远端已确认批次并汇总结果 | AC-008 |
| M9 | 全部上传完成且失败数为 0 | 显示主同步成功、时间和人数 | 持久化最近主同步成功结果 | AC-009 |
| M10 | 展示主同步终态 1 秒 | 保留最终结果;数据库可继续后台运行 | 无 | AC-010 |
| M11 | 自动关闭弹框 | 消费页继续运行 | 无 | AC-010 |
| M12 | 新数据库快照完整并通过人数校验 | 本地存储显示已完成 | 原子切换活动快照并清理旧快照 | AC-011 |
临时快照放在 Context.getNoBackupFilesDir() 下的应用私有目录,不使用可能被系统回收的 cacheDir,也不进入系统云备份:
onecard_sync_spool/<syncId>/
├── manifest.json
├── batch-00001.json
├── batch-00002.json
└── ...
JSON 保存本地查询和业务转发所需的完整人员原始字段。单批元数据至少包含 syncId、批次号、人员数、起止 customerId、文件字节数和 SHA-256。
batch-xxxxx.json.tmp;flush() 和 FileDescriptor.sync();batch-xxxxx.json;AtomicFile 原子更新 manifest.json;.tmp。COLLECTING / READY / FAILED。total/created/updated/failed 和 trace ID。PENDING / WRITING / ACTIVE / RETRY_WAIT / SUPERSEDED 及已处理批次;SUPERSEDED 同时记录已成功激活的更新快照 ID。customerid 幂等新增或更新,因此远端采用“至少一次投递”;崩溃恢复时允许安全重传。failed > 0 时不确认该批,整批保留并在后续重试。SUPERSEDED 后清理。更新快照仅为 READY、正在导入或导入失败时,不得替代或清理旧候选。| 编号 | 条件 | 系统处理 | 结果与恢复 |
|---|---|---|---|
| A1@M1 | 已有最近结果 | 弹框先展示历史结果,再切换为本轮运行态 | 历史结果不会阻止自动同步 |
| A2@M1 | 用户关闭运行中的弹框 | 只关闭界面,不取消后台任务 | 右侧入口可重新打开 |
| A3@M8 | 单批返回部分失败人员 | 累计失败数且不确认该批 | 最终整体标记同步失败;后续整批幂等重传 |
| E1@M2 | URL、AppID、网络、认证或响应校验失败 | 记录失败时间和原因,不执行拉取及上传 | 右侧变红;可手动重试 |
| E2@M4 | 拉取或解析失败 | 本轮保持非 READY,不上传业务服务器 | 丢弃未完成 .tmp,下次重新全量拉取 |
| E3@M5 | JSON 写入、刷盘或校验失败 | 标记本轮失败且禁止上传 | 保留诊断信息;空间恢复后重新全量拉取 |
| E4@M8 | 业务 API 网络、HTTP、业务码或响应数量异常 | 停止后续上传,记录已处理数量和失败原因 | 一卡通入口保持最近连接验证颜色;保留 JSON 后续恢复 |
| E5@M7 | SQLite 导入失败 | 不改变主同步结论,不切换活动快照 | 继续查询旧快照;保留 JSON 并后台重试 |
| E6 | 进程在远端成功后、本地记账前退出 | 重启后重传未确认批次 | 服务端按 customerid 幂等处理,不重复生成人员 |
| E7 | JSON 被截断或校验和不一致 | 隔离损坏文件,禁止上传和入库 | 重新执行完整拉取,不静默跳过人员 |
| E8 | 页面销毁 | 解除 UI 观察和延迟关闭,不取消应用级任务 | 不持有已销毁 Activity 或弹框;任务可恢复 |
| E9 | 主任务运行时重复点击手动同步 | 按钮保持禁用,不创建第二个任务 | 当前任务继续 |
BR-001:自动同步仅在每个 ConsumptionActivity 实例的 onCreate() 启动一次。BR-002:连接验证成功后才允许拉取;只有 JSON 全量人数校验通过并标记 READY 后才允许业务上传。BR-003:一卡通拉取与业务上传均顺序执行,每批最多 1000 人。BR-004:任一时刻最多存在一个连接、拉取或远端上传主任务;数据库导入由独立唯一后台任务串行执行。BR-005:连接结果与同步结果分别持久化,时间精确到毫秒。BR-006:业务返回 failed > 0 时整体同步结果为失败,成功人数为 created + updated。BR-007:结果弹框只在本轮流程进入终态时延迟 1 秒关闭,纯历史查看不自动关闭。BR-008:主同步成功只取决于一卡通完整拉取、JSON 完整校验和配置服务器处理结果;SQLite 是否完成不改变主同步结论。BR-009:数据库导入期间查询始终使用旧 ACTIVE 完整快照,新快照完整校验后一次切换。BR-010:同步失败不得阻断消费页主业务,也不得清理最近一次成功的完整数据库快照。BR-011:磁盘空间不足时必须明确失败,不得跳过批次或上传残缺全量。数据库在逻辑上增加快照元数据、候选快照和活动快照指针:
syncId 把 JSON 批次写入 STAGING 快照;人员和扩展字段的唯一键包含快照 ID。getField(customerId, fieldName))保持不变,仓库内部只读取活动快照指针指向的数据。syncId,随后异步删除旧快照。| 组件 | 职责 |
|---|---|
ConsumptionActivity | 绑定右侧入口,显示/关闭弹框,接收主线程状态,不承载同步算法 |
ConsumptionPersonSyncController | 连接页面与应用级同步运行时、拒绝主任务并发、生成统一可见状态 |
ConsumptionPersonSyncCoordinator | 编排连接、全量拉取、JSON 快照完成、远端上传和断开,并报告明确阶段 |
ConsumptionPersonSyncState | 表达运行阶段、进度、终态、时间和人数,保持为可单元测试的纯 Java 状态 |
ConsumptionPersonSyncStatusStore | 使用现有 MMKV 保存和恢复最近连接及同步终态 |
ConsumptionPersonSyncDialog | 只渲染状态并转发手动同步/关闭事件,不发起网络和数据库操作 |
CustomerBatchSpool | 原子写入/读取批次 JSON,校验长度、人数和 SHA-256,不解释业务状态 |
CustomerSyncManifestStore | 使用 AtomicFile 维护拉取、远端和数据库处理进度 |
BusinessPersonSpoolUploader | 从完整 JSON 快照分批映射并上传,记录远端确认和汇总,不读取 SQLite |
CustomerSnapshotImportWorker | 低优先级后台导入 JSON,校验并原子激活新数据库快照 |
SQLiteCustomerRepository | 保存多版本完整快照并使现有查询透明读取活动版本 |
现有 OnecardWebServiceClient、字段解析/映射、业务网关和网络鉴权继续复用。CustomerSyncService 与 BusinessPersonSyncService 的数据源边界调整为 JSON 队列,但不复制已有协议和字段规则。
持久化分为 UI 汇总、可恢复 JSON 和数据库活动快照:
| 存储 | 内容 | 清理规则 |
|---|---|---|
| MMKV 最近连接验证 | SUCCESS/FAILURE、完成时间、服务端人数、失败摘要 | 新结果覆盖旧结果 |
| MMKV 最近人员同步 | SUCCESS/FAILURE、完成时间、拉取数、处理总数、成功数、失败数、失败摘要 | 新结果覆盖旧结果 |
noBackupFilesDir JSON | 完整人员批次、校验元数据和处理进度 | 远端确认且数据库已激活,或被另一个已激活的更新快照标记为 SUPERSEDED 后删除 |
| SQLite 活动快照 | 本地必要查询使用的完整人员数据 | 新快照激活后异步删除旧版本 |
进程退出后不把旧 UI 运行态直接恢复为“正在同步”;由 manifest 扫描结果决定显示“恢复上传”或“后台入库重试”。
READY 后,远端上传使用正常优先级顺序执行,数据库导入使用独立低优先级唯一后台任务;两者不等待彼此。ReaderLog。batchBytes、tempWriteMs、fsyncMs、uploadMs 和 databaseSaveMs。noBackupFilesDir,不得写外部公共存储、不得进入备份、不得通过日志输出正文或卡号。| AC | Given | When | Then | 证据 |
|---|---|---|---|---|
| AC-001 | 消费页首次创建 | 完成现有初始化 | 自动显示弹框且主消费流程仍可运行 | 页面检查/代码测试 |
| AC-002 | 无任务运行 | 自动或手动同步开始 | 只创建一个后台任务并执行连接验证 | 单元测试 |
| AC-003 | GetCustomerCount 成功或失败 | 返回结果 | 记录毫秒时间并正确刷新红绿入口 | 单元测试/真机日志 |
| AC-004 | 服务端有 1001 人 | 执行全量拉取 | 生成 1000 和 1 两个完整批次,人数、游标和跨批唯一性正确 | 单元测试 |
| AC-005 | 单批 JSON 写入 | 正常完成或进程在任意写入点退出 | 只消费刷盘、校验并原子登记的完整文件 | 故障注入测试 |
| AC-006 | 拉取中途失败或全量人数不一致 | 协调器结束 | 不标记 READY、不调用业务上传、不改变活动数据库快照 | 单元测试 |
| AC-007 | JSON 快照已 READY | 同时启动上传和数据库导入 | 上传不调用、不等待 SQLite;数据库低优先级后台执行 | 单元/并发测试 |
| AC-008 | 完整快照有 1001 人 | 上传业务服务器 | 以 1000 和 1 两批顺序上传并原子记录确认进度 | 单元测试 |
| AC-009 | 上传全部成功、存在失败数或数据库失败 | 主流程结束 | 主结果仅按远端结果显示;数据库失败只显示等待重试 | 单元测试 |
| AC-010 | 本轮主流程进入终态且弹框可见 | 等待 1 秒 | 自动关闭;历史查看不自动关闭;数据库任务可继续 | 状态/调度测试、真机检查 |
| AC-011 | 新数据库快照未完成或已完成 | 执行本地查询 | 未完成时始终查旧完整快照,完成后一次切换到新快照 | 仪器测试 |
| AC-012 | 远端成功后、本地确认前进程退出 | 应用恢复任务 | 允许整批重传且服务端无重复人员 | 故障注入/联调 |
| AC-013 | JSON 被截断、篡改或磁盘空间不足 | 扫描或写入 | 明确失败并禁止跳过、上传或激活损坏数据 | 单元/仪器测试 |
| AC-014 | 同步运行中 | 重复点击手动同步 | 手动按钮禁用且不创建并发主任务 | 单元测试 |
| AC-015 | 应用重新进入消费页 | 读取 MMKV 和 manifest | 恢复最近终态及未完成上传/入库任务,再按门禁决定自动同步 | 单元测试/真机检查 |
| AC-016 | 目标设备写入单批 1000 人 | 重复执行安全落盘 | tempWriteMs + fsyncMs 的 P95 不超过 100 ms | 真机性能日志 |
| Test ID | 层级 | 覆盖对象 | 预期 | 运行方式 |
|---|---|---|---|---|
| UT-001 | 单元 | 状态模型 | 连接、拉取、上传、成功、失败状态及人数正确 | :app:testDebugUnitTest 定向测试 |
| UT-002 | 单元 | 状态存储编解码 | 最近连接和同步结果可往返恢复 | :app:testDebugUnitTest 定向测试 |
| UT-003 | 单元 | 并发门禁 | 自动/手动任务不并发 | :app:testDebugUnitTest 定向测试 |
| UT-004 | 单元 | 失败判定 | 部分失败整体为失败,成功数正确 | :app:testDebugUnitTest 定向测试 |
| UT-005 | 单元 | 自动关闭策略 | 本轮终态延迟 1 秒,历史查看不关闭 | :app:testDebugUnitTest 定向测试 |
| UT-006 | 单元 | JSON spool | .tmp 不可消费,SHA-256/人数/长度校验和原子登记正确 | :app:testDebugUnitTest 定向测试 |
| UT-007 | 单元 | manifest 恢复 | 各阶段重启后只恢复未确认的远端和数据库工作 | :app:testDebugUnitTest 定向测试 |
| UT-008 | 单元 | 幂等重传 | 远端成功、本地未记账时重传同批且进度不倒退 | :app:testDebugUnitTest 定向测试 |
| IT-001 | 集成 | 协调器 | 连接成功后拉取、完整 JSON 后上传、最终断开 | 现有协调器测试扩展 |
| IT-002 | 仪器 | 数据库快照 | 导入期间旧快照可查,校验后原子切换,失败不切换 | :app:connectedDebugAndroidTest |
| IT-003 | 仪器 | 故障恢复 | 写文件、远端确认、数据库导入和切换点杀进程后恢复 | 目标设备故障注入 |
| PERF-001 | 性能 | JSON 安全落盘 | 单批 1000 人 P95 不超过 100 ms | 目标设备日志统计 |
| BUILD-001 | 构建 | Java 与 Android 资源 | 单元测试和 Debug APK 构建通过 | :app:testDebugUnitTest、:app:assembleDebug |
| DEVICE-001 | 人工 | 页面与真实服务 | 自动弹框、右侧入口、幂等恢复、快照切换、毫秒时间和人数符合设计 | 目标设备联调 |
items 契约。| ID | 风险 | 应对 |
|---|---|---|
| RISK-001 | 全量同步时间较长,弹框可能遮挡消费操作 | 允许随时关闭,任务后台继续,右侧入口可恢复查看 |
| RISK-002 | 真实服务返回脏数据或部分业务失败 | 保留完整日志和人数汇总,不把部分成功误报为成功 |
| RISK-003 | 远端成功后进程退出导致同批重传 | 服务端已确认按 customerid 幂等,采用至少一次投递并记录确认游标 |
| RISK-004 | 数据库持续失败造成候选文件积压 | 数据库唯一任务重试,只保留最新可用候选;新同步前检查空间,不删除未被替代的有效数据 |
| RISK-005 | JSON 含人员和卡片信息 | 使用 noBackupFilesDir、最小文件权限、禁止正文日志并在双消费者完成后立即删除 |
| RISK-006 | 页面销毁时同步调用仍在阻塞 | UI 观察与应用级任务隔离,页面销毁不取消任务,设备联调验证恢复 |
P0 待确认项:无。