消费页面一卡通自动寻卡与人员同步 Implementation Plan

执行状态:已完成。已使用 superpowers:executing-plans 逐项实施并验证。仓库策略禁止 Git 写操作,因此没有提交、暂存、推送或分支步骤。

Goal:消费页进入后自动连接新开普读卡器并持续寻卡,同时从一卡通服务器每批 1000 条拉取完整人员快照,再通过当前业务服务器分批同步人员。

Architecture:页面只负责生命周期和状态渲染;配置、字段映射、批量上传、同步编排和 USB 寻卡分别由聚焦的类承担。完整 SQLite 快照形成后才允许业务上传。

Tech Stack:Java 8、Android API 24-30、Android USB Host、SQLite、MMKV、Retrofit、Gson、JUnit 4 和项目现有一卡通包。

文件结构

Task 1:一卡通设置模型与设置页

文件:OnecardSettings.javaValidatedSettingInputPop.javaConstants.javaSetBasicFragment.javaset_basic_fragment_layout.xmlstrings.xmlOnecardSettingsTest.java

  1. 先写默认 URL/AppID 合法及非法协议、非 asmx、0、非数字被拒绝的失败测试。
  2. 实现集中默认值、HTTP/HTTPS + .asmx 校验、正整数 AppID 校验和两个 MMKV 键。
  3. 实现不含业务文案的通用输入弹窗,以 Validator 返回资源化错误。
  4. 基础设置增加服务器地址和 AppID 两项,默认值分别为确认的 URL 和 6,保存到 MMKV。
  5. 运行指定 JUnit 测试并要求 PASS。
JAVA_HOME=/Users/liang/Library/Java/JavaVirtualMachines/corretto-1.8.0_482/Contents/Home ./gradlew -q :app:testDebugUnitTest --tests com.zhct.traybinding.onecard.config.OnecardSettingsTest

Task 2:接口契约与字段映射

文件:SyncPersonsRequest.javaSyncPersonsResult.javaBusinessPersonMapper.javaNetworkResult.javaCPTNetworkRequest.javaNetworkService.javaCPTService.java 及 mapper 测试。

  1. 先写八字段成功映射、必填缺失、超长字符串、非法整数和 CUSTOMERID 不一致测试。
  2. 请求 DTO 强制 items 为 1-1000 条;字符串不截断,整数不伪造默认值。
  3. 响应 DTO读取 total/created/updated/failed;通用响应增加 trace_id
  4. 增加 POST /api/oneCardTerminal/syncPersons 和 ServiceCall 包装,复用当前 Base URL 和拦截器。
  5. 运行 mapper 测试并要求 PASS。
@POST("/api/oneCardTerminal/syncPersons")
Call<NetworkResult<SyncPersonsResult>> syncPersons(@Body SyncPersonsRequest request);

Task 3:完整快照分页读取

文件:CustomerRepository.javaSQLiteCustomerRepository.java

  1. 接口增加 loadBatch(syncId, afterCustomerId, limit),限制 limit 为 1-1000。
  2. 先验证同步记录状态为 SUCCESS;再由第一条 SQL 选出下一批账号及最大账号,第二条 SQL 联结字段表重建完整记录。
  3. 查询必须限定 last_seen_sync_id,按账号升序并关闭全部 Cursor。
  4. 不使用 1000 个占位符的 IN 查询,规避 SQLite 变量上限。

Task 4:分批上传与汇总

文件:BusinessPersonGateway.javaNetworkBusinessPersonGateway.javaBusinessPersonSyncResult.javaBusinessPersonSyncService.java 及服务测试。

  1. 先覆盖 1001 条拆为 1000+1、0 条、部分失败继续、非零 code 停止、空 data、数量不一致和网关异常。
  2. Gateway 通过现有 NetworkManager 同步执行 Retrofit ServiceCall,便于用 fake 测试。
  3. 服务按 customerId 游标顺序上传,只有当前批响应合法才进入下一批。
  4. 累计四项结果和 trace ID;failed > 0 继续,协议或网络失败停止。
void onProgress(int batchNumber, int batchSize, int uploadedCount,
                int created, int updated, int failed);

Task 5:完整拉取后上传的同步编排

文件:ConsumptionPersonSyncCoordinator.java 及顺序测试。

  1. 先验证事件顺序必须为 connect、pull-complete、upload、disconnect。
  2. 验证一卡通拉取失败时业务网关调用次数为 0。
  3. 以三个可替换任务接口注入连接、源快照和目标上传,避免测试继承 final 类;coordinator 不创建线程、不持有 Activity。
  4. finally 中始终断开一卡通客户端。
connectionTask.connect(serverUrl, appId);
CustomerSyncResult source = sourceSyncTask.sync(listener::onPullProgress);
BusinessPersonSyncResult target = targetSyncTask.sync(source.getSyncId(), listener::onUploadProgress);

Task 6:自动寻卡状态机与 USB 管理器

文件:CardSearchStateMachine.javaReaderDeviceState.javaConsumptionCardSearchManager.java 及两个状态测试。

  1. 测试连接后寻卡、找到卡等待移卡、三次 NO_CARD 确认移除、致命停止和五种设备状态。
  2. 管理器提供 start/stop/close 生命周期;USB 权限 action 基于 BuildConfig applicationId。
  3. 连接和寻卡只在单线程执行器运行;主线程 Handler 调度 500ms 轮询。
  4. NO_CARD 属于正常状态;其他 ReaderException 停止本轮并回调错误。
public void start();
public void stop();
public void close();

Task 7:消费页接入与状态展示

文件:ConsumptionActivity.javaactivity_consumption.xmlstrings.xmldimens.xml

  1. 增加等宽的读卡器状态和人员同步状态卡片,尺寸、颜色、文案全部资源化。
  2. onCreate 初始化日志、仓库、读卡器管理器、同步 coordinator 和独立人员同步线程,只启动一次同步。
  3. onStart 启动寻卡;onStop 停止;onDestroy 标记销毁、关闭设备、线程和数据库。
  4. 保留标题栏与实体返回键回到功能选择页的现有导航。
  5. 迟到回调不得更新已销毁页面。

Task 8:全量验证与成对变更记录

  1. 运行全部 JVM 测试和 Debug 构建,要求 BUILD SUCCESSFUL。
  2. 扫描默认地址、接口路径、1000、500ms 和中文文案,确认只出现在职责明确的常量或资源中。
  3. 运行 git diff --checkgit status --short 只读核对。
  4. 创建 change_records/2026-08-26-consumption-onecard-sync.md/.html,只记录实际执行结果。
JAVA_HOME=/Users/liang/Library/Java/JavaVirtualMachines/corretto-1.8.0_482/Contents/Home ./gradlew -q :app:testDebugUnitTest
JAVA_HOME=/Users/liang/Library/Java/JavaVirtualMachines/corretto-1.8.0_482/Contents/Home ./gradlew -q :app:assembleDebug
git diff --check
git status --short

自审结论