/Users/liang/.codex/worktrees/d33c/ZhctTrayBindingMachinedocs/2026-08-29-consumption-onecard-sync-dialog-design.mddocs/2026-08-29-consumption-onecard-sync-dialog-implementation-plan.md消费页进入后自动显示同步弹框并执行“一卡通连接验证 → 每批最多 1000 人全量拉取 → 可靠 JSON 暂存 → 配置服务器顺序转发”。连接结果、主同步结果、人数与毫秒时间会持久化;右侧镜像入口按最近连接结果显示绿色或红色,并可重新打开弹框。手动同步复用同一流程和应用级单任务门禁,本轮主同步终态展示 1 秒后自动关闭弹框。
每个人员批次先写入 noBackupFilesDir/onecard_sync_spool:临时文件写入、flush、文件描述符 sync、长度/SHA-256/JSON 校验、同目录原子改名,再由 AtomicFile 更新 manifest。远端消费者与 SQLite 消费者只读取 READY JSON,互不等待。远端采用至少一次整批投递,只有整批 failed == 0 才推进确认游标;服务端需按 customerid 幂等。SQLite 使用 STAGING/ACTIVE 快照,完整人数核对后原子切换;SQLite 仅承担辅助存储和查询,不参与主同步成功口径。
应用启动、消费页进入和网络恢复均会合并恢复任务。页面和弹框关闭只解除 UI 观察,不终止正在执行的连接、拉取、转发或后台入库。已补齐“SQLite 已激活但 manifest 尚未写 ACTIVE”崩溃窗口:恢复时只补记 manifest,不重灌已生效快照。已确认损坏的批次停止无限重试,普通临时 I/O 故障仍重试。
app/src/main/java/com/zhct/traybinding/onecard/customer/AtomicCustomerSyncManifestStore.javaapp/src/main/java/com/zhct/traybinding/onecard/customer/ConsumptionPersonSyncRecoveryScheduler.javaapp/src/main/java/com/zhct/traybinding/onecard/customer/ConsumptionPersonSyncRuntime.javaapp/src/main/java/com/zhct/traybinding/onecard/customer/ConsumptionPersonSyncTask.javaapp/src/main/java/com/zhct/traybinding/onecard/customer/CustomerBatchCorruptionException.javaapp/src/main/java/com/zhct/traybinding/onecard/customer/CustomerBatchJsonCodec.javaapp/src/main/java/com/zhct/traybinding/onecard/customer/CustomerBatchMetadata.javaapp/src/main/java/com/zhct/traybinding/onecard/customer/CustomerBatchSink.javaapp/src/main/java/com/zhct/traybinding/onecard/customer/CustomerBatchSnapshot.javaapp/src/main/java/com/zhct/traybinding/onecard/customer/CustomerBatchSource.javaapp/src/main/java/com/zhct/traybinding/onecard/customer/CustomerPullResult.javaapp/src/main/java/com/zhct/traybinding/onecard/customer/CustomerSnapshotImporter.javaapp/src/main/java/com/zhct/traybinding/onecard/customer/CustomerSpoolPullService.javaapp/src/main/java/com/zhct/traybinding/onecard/customer/CustomerSyncManifest.javaapp/src/main/java/com/zhct/traybinding/onecard/customer/CustomerSyncManifestJsonCodec.javaapp/src/main/java/com/zhct/traybinding/onecard/customer/CustomerSyncManifestStore.javaapp/src/main/java/com/zhct/traybinding/onecard/customer/CustomerSyncSpool.javaapp/src/main/java/com/zhct/traybinding/onecard/customer/DatabaseProgressStore.javaapp/src/main/java/com/zhct/traybinding/onecard/customer/RemoteProgressStore.javaapp/src/main/java/com/zhct/traybinding/widget/ConsumptionPersonSyncDialog.javaapp/src/main/java/com/zhct/traybinding/workmanager/ConsumptionPersonUploadRecoveryWorker.javaapp/src/main/java/com/zhct/traybinding/workmanager/CustomerSnapshotImportWorker.javaapp/src/main/res/layout/consumption_person_sync_dialog.xmlapp/src/main/res/drawable/ic_onecard_service.xmlapp/src/main/res/drawable/consumption_onecard_status_button_background.xmlapp/src/main/res/drawable/consumption_sync_dialog_background.xmlapp/src/main/res/drawable/consumption_sync_manual_button_background.xmlapp/src/test/java/com/zhct/traybinding/onecard/customer/CustomerBatchJsonCodecTest.javaapp/src/test/java/com/zhct/traybinding/onecard/customer/CustomerSnapshotImporterTest.javaapp/src/test/java/com/zhct/traybinding/onecard/customer/CustomerSpoolPullServiceTest.javaapp/src/test/java/com/zhct/traybinding/onecard/customer/CustomerSyncManifestTest.javaapp/src/test/java/com/zhct/traybinding/onecard/customer/CustomerSyncSpoolTest.javaapp/src/androidTest/java/com/zhct/traybinding/onecard/customer/SQLiteCustomerRepositorySnapshotTest.javaapp/src/main/java/com/zhct/traybinding/application/CptApplication.javaapp/src/main/java/com/zhct/traybinding/main/ConsumptionActivity.javaapp/src/main/java/com/zhct/traybinding/onecard/customer/BusinessPersonSyncService.javaapp/src/main/java/com/zhct/traybinding/onecard/customer/ConsumptionPersonSyncController.javaapp/src/main/java/com/zhct/traybinding/onecard/customer/ConsumptionPersonSyncCoordinator.javaapp/src/main/java/com/zhct/traybinding/onecard/customer/ConsumptionPersonSyncStatus.javaapp/src/main/java/com/zhct/traybinding/onecard/customer/ConsumptionPersonSyncStatusJsonCodec.javaapp/src/main/java/com/zhct/traybinding/onecard/customer/CustomerRepository.javaapp/src/main/java/com/zhct/traybinding/onecard/customer/SQLiteCustomerRepository.javaapp/src/main/res/layout/activity_consumption.xmlapp/src/main/res/values/colors.xmlapp/src/main/res/values/dimens.xmlapp/src/main/res/values/strings.xmlapp/src/test/java/com/zhct/traybinding/onecard/customer/BusinessPersonSyncServiceTest.javaapp/src/test/java/com/zhct/traybinding/onecard/customer/ConsumptionPersonSyncControllerTest.javaapp/src/test/java/com/zhct/traybinding/onecard/customer/ConsumptionPersonSyncCoordinatorTest.javaapp/src/test/java/com/zhct/traybinding/onecard/customer/ConsumptionPersonSyncStatusJsonCodecTest.javaapp/src/main/java/com/zhct/traybinding/onecard/customer/CustomerSyncService.javaapp/src/main/java/com/zhct/traybinding/onecard/customer/CustomerSyncResult.java| 验证项 | 命令/证据 | 结果 |
|---|---|---|
| 全量 JVM 单元测试 | bash ./gradlew -q :app:testDebugUnitTest | 退出码 0;161 tests,0 failures,0 errors,0 skipped |
| Debug APK 与 AndroidTest 编译 | bash ./gradlew -q :app:assembleDebug :app:compileDebugAndroidTestJavaWithJavac | 退出码 0 |
| APK | app/build/outputs/apk/debug/app-debug.apk | 已生成,约 64 MB |
| 补丁空白检查 | git diff --check | 无输出 |
| 存储边界 | 检查 cache、旧结果类型、刷盘、SHA-256、noBackup、唯一 WorkManager | cache/旧类型无命中;可靠性关键点均有命中 |
构建只有既有 Java 8 编译器读取 build-tools 30 的主版本提示和既有 deprecated 提示,没有新增编译错误。
| AC | 当前证据 | 状态 |
|---|---|---|
| AC-001~AC-003 | Activity/Dialog 资源编译;运行时单任务和连接结果单测 | 自动化覆盖;真机视觉未执行 |
| AC-004~AC-006 | 1001 拆为 1000+1、游标/唯一性、原子 JSON 与 READY 门禁单测 | 自动化通过 |
| AC-007~AC-009 | 双消费者顺序、整批确认、远端结果口径、数据库失败隔离单测 | 自动化通过 |
| AC-010 | 1 秒命名延迟和仅本轮终态调度已编译 | 真机计时未执行 |
| AC-011 | importer 崩溃恢复单测;SQLite 仪器测试已编译 | 设备执行未授权,未运行 |
| AC-012 | 至少一次游标恢复和重复整批逻辑单测 | 真实服务幂等联调未执行 |
| AC-013 | SHA-256 篡改拒绝、低空间失败单测 | 断电/真实磁盘故障未执行 |
| AC-014~AC-015 | 运行时并发门禁、页面 detach、状态持久化和恢复调度单测/编译 | 自动化覆盖;进程级真机恢复未执行 |
| AC-016 | 代码记录 tempWriteMs 与 fsyncMs | 目标设备 P95 未执行 |