OnecardDemo Change 单点功能与字段说明

项目 内容
文档版本 1.10
日期 2026-08-24
对应实现 /Users/liang/AndroidStudioProjects/OnecardDemo
适用范围 本地消费、服务器连接与查询、消费记录上传
一致性口径 本文必填性、字段名、类型与页面/Java 实现 1:1 对应

1. 状态说明

能力 实现状态 验证状态
主钱包本地消费 已实现 Windows 1 分抓包已确认;Android 真卡扣款待验证
补助钱包本地消费 已实现 读钱包已确认;需要有补助余额的 CPU 卡验证扣款
补贴优先自动分账 已实现 计划与部分成功语义已单测;真卡两钱包场景待验证
SOAP 请求生成、连接与查询 已实现 YF_029E 真实 WebService 连接成功,用户总数返回 25965;单个用户已完成字段解析与真机验证
全部用户 SQLite 持久化 已实现 51 字段解析、唯一性、批事务和页面相关真机测试通过;现场全量同步待执行
全量同步分阶段耗时日志 已实现 JVM 成功/失败阶段和敏感信息边界测试通过;真机日志文件与现场全量同步待验证
一键服务器稳定性测试 已实现 YF_029E 三轮共 300 次全部成功,综合成功率 100%,平均 107.09 ms、P95 129 ms、最大 336 ms;最大值为第三轮首请求且成功
消费记录上传 已实现 WSDL 请求结构单测通过;服务端接收和返回码待联调

“已实现”表示 Android 页面和 Java 调用链存在,不代表缺少现场条件的功能已经验收。

2. 本地消费单点功能

2.1 页面输入

页面字段 控件 ID 必填 Java 类型 校验与转换
账号 input_consume_account long 分步读卡、一键读卡或自动读卡成功后自动填入卡内 CustomerID;用户仍可修改;执行前必须与当前卡内账号一致。不会填入 CardNO
消费额 input_consume_amount long 单位为分;必须大于 0,单笔不超过无符号 32 位范围且不得超过目标钱包余额
钱包策略 spinner_consume_wallet 枚举/策略 补贴优先(自动分账)主钱包(0)补助钱包(1) 三选一
消费时间 input_consume_time_millis 自动 页面显示 String,内部 long 页面首次打开显示当前系统时间;点击“本地消费”时重新获取当前时间,不采用手工修改值;显示格式固定为 yyyy-MM-dd HH:mm:ss,内部毫秒值仍按设备默认时区转换为 yyyyMMddHHmmss 的 7 字节 BCD

本地消费只能在读卡器已经连接、页面不忙且自动寻卡读卡开关关闭时执行。执行时内部会先寻卡和读卡,用户不需要额外点击“寻卡”或“读卡”。

主页面和服务器单点页面的所有可编辑输入框均为单行输入,软键盘回车动作统一显示“完成”。服务器消费记录上传中的 OPDT 是原始交易时间,仍由用户按 yyyy-MM-dd HH:mm:ss 填写,不会在上传时覆盖为当前时间。

2.2 钱包与分账规则

页面选择 代码值 卡文件 行为
主钱包 0 EC11 只扣主钱包
补助钱包 1 EC12 只扣补助钱包
补贴优先 最多两笔 EC12,后 EC11 先使用补助余额,不足部分再扣主钱包

补贴优先时,总余额不足会在写卡前拒绝;补助钱包余额为 0 时只产生主钱包交易;两边都参与时产生两笔独立卡上交易,每笔有各自的消费计数、PSAM 脱机交易序号和 TAC。

2.3 本地消费结果

结果字段 Java 来源 含义
钱包 ConsumptionResult.wallet 主钱包或补助钱包
消费额 amount 单位分
扣款前/后余额 balanceBefore / balanceAfter 目标钱包余额,单位分
扣款前/后计数 operationCountBefore / operationCountAfter 目标钱包消费计数
消费时间 timeMillis 同时显示格式化时间和原始毫秒值
PSAM 卡号 psamIdHex / psamIdDecimal 同时显示 6 字节十六进制和十进制
PSAM 脱机交易序号 psamTransactionNumber PSAM 80 70 返回值
TAC tac 用户卡 80 54 返回的 4 字节交易验证码,页面按 8 位十六进制显示
PSAM 确认 psamConfirmed 80 72 是否成功;失败不表示卡片未扣款

2.4 异常与禁止重试规则

错误 触发条件 处理
INVALID_INPUT 缺字段、金额不大于 0、字段越界 写卡前拒绝
ACCOUNT_MISMATCH 输入账号与卡内账号不一致 写卡前拒绝
CARD_STATUS_INVALID 卡状态不是 0xF1 写卡前拒绝
INSUFFICIENT_BALANCE 单钱包或总余额不足 写卡前拒绝
PSAM_ERROR PSAM 候选槽 2、3 均未返回 ATR/9000 依次探测两个槽;0101 转下一槽,其他异常仅在任何扣款 APDU 前关闭当前槽并重试一次;仍失败则显示各槽原始响应和长度
TRANSACTION_OUTCOME_UNKNOWN 80 54 已发送但未获得可信响应 禁止直接重试;先重新读卡核对余额和计数
PSAM 确认失败 卡片已返回 TAC,但 80 72 失败 返回已扣款结果且 psamConfirmed=false禁止再次扣卡
自动分账部分成功 第一笔成功、第二笔失败 保留第一笔完整结果;不回滚、不自动重试

3. 服务器连接

服务器单点页面使用与 LogViewerActivity 一致的 ActionBar 左上角返回箭头;页面启用 setDisplayHomeAsUpEnabled(true),点击后由 onSupportNavigateUp() 调用 finish() 关闭 ServerOperationsActivity 并返回主页面,不发送服务器请求。内容区不再放置独立的普通“返回”按钮。

页面字段 控件 ID 必填 Java 类型 校验
WebService 地址 input_server_url String 只接受 http/https,路径必须以 .asmx 结尾
应用 ID input_server_app_id long 不得留空,必须为十进制整数

SOAP 1.1 命名空间和 SOAPAction 前缀均为 http://NewCap.com/NewCapecWebService/。点击“连接服务器”会调用 GetCustomerCount(appId) 验证地址、应用 ID、HTTP 和 SOAP 响应;appId 的大小写必须与 WSDL 完全一致。成功后才启用查询和上传按钮。点击“断开服务器”只清除本页内存中的地址和应用 ID,不向服务器发送断开请求。

当前 Android 清单允许 HTTP 明文连接,是为了兼容厂商文档中的局域网 HTTP WebService;生产部署应优先使用 HTTPS 或按域名配置网络安全策略。

SOAP 响应解析兼容 Android 11 的 Harmony XML 实现:不再强制要求平台支持 Apache 专用的 disallow-doctype-decl feature。解析前会直接拒绝任何包含 DOCTYPE 的响应;对平台支持的安全 feature 继续关闭外部通用实体和外部参数实体,并通过 EntityResolver 二次拒绝外部实体。这样正常 SOAP XML 可以在 Android 上解析,同时维持 XXE 防护边界。

2026-08-24 对同一服务地址和 AppID 6 做了大小写对照:错误参数 <appid>6</appid> 稳定返回 1900009-在加载应用认证信息时,应用配置信息=null!,正确参数 <appId>6</appId> 返回用户总数 25965,与 Windows Demo 一致。根因是 Android 请求元素名大小写错误,不是缺少 Android 版 SysInit.dat 或 AppID 6 服务端配置。YF_029E 安装修复版后,连接服务器和单个用户查询均成功。

3.1 一键服务器稳定性测试

服务器连接状态下方提供“一键服务器稳定性测试”。该功能固定执行 100 轮,每轮都新建独立的 OnecardWebServiceClient 并调用一次 GetCustomerCount(appId);上一轮完成后等待 1000 毫秒再开始下一轮,最后一轮结束后不等待。启动前会快照页面当前 WebService 地址和 AppID,并清除普通连接的本地状态。

单轮成功必须同时满足 HTTP 请求完成、SOAP 不含 Fault、用户总数为非负整数。HTTP、超时、XML、SOAP Fault 或返回值错误只记为该轮失败,测试继续执行后续轮次。运行中按钮变为“停止稳定性测试”;停止不会强行打断当前 HTTP 请求,而是在当前请求完成或超时后生成部分报告。

页面实时显示完成轮数、成功数、失败数和本轮毫秒耗时。最终摘要包含计划/实际轮数、成功率、最短/最长/平均/P95 成功耗时、开始/结束时间、总耗时、停止状态和错误分组。P95 使用 nearest-rank 计算;没有成功样本时明确显示“无成功样本”。

完整逐轮报告使用 UTF-8 保存到应用专属文件目录的 server_stability 子目录,文件名为 server-stability-yyyy-MM-dd-HH-mm-ss-SSS.log;页面显示报告绝对路径。报告包含服务器地址和 AppID,但不记录注册码、密码摘要、证件号或卡片敏感信息。该功能仅用于短时现场验收,不是生产环境每秒重连机制。

4. 查询单点功能

除表中“附加必填字段”外,当前页面实现的查询请求都隐式携带连接时填写的 appId。SOAP 元素名区分大小写。

页面功能 SOAP 操作 附加必填字段 页面校验 页面结果
获取单个用户信息 GetInfoByCustID 账号 CustomerID 非空十进制 long 解析后的中文字段列表;不显示密码摘要和证件号
获取部分用户信息 GetCustomerByNum 起始账号 custid、数量 num 起始账号非负;数量大于 0 且不超过 Integer.MAX_VALUE 原始 SOAP XML
获取用户总数 GetCustomerCount 返回值必须是非负整数 数量文本
获取全部用户信息 GetCustomerCount + 多次 GetCustomerByNum 当前 Android 自动分页每批 1000 条,最后一批按剩余数量;分页值来自 Constants.ONE_CARD_CUSTOMER_BATCH_SIZE,是当前实现策略,不是厂家文档规定的服务端硬上限;从返回的最大 CUSTOMERID + 1 继续 每批提交后的保存进度;完成后显示同步 ID、服务器总数、保存数、批次数、SQLite 用户数和耗时
获取黑名单总数 GetBLLTCount 返回值必须是非负整数 数量文本
获取全部黑名单 GetBLLTAll 无额外输入 原始 SOAP XML
获取部门总数 GetCustDeptCount 返回值必须是非负整数 数量文本
获取全部部门信息 GetCustDeptInfo 无额外输入 原始 SOAP XML

HTTP 连接超时为 8 秒,读取超时为 15 秒。HTTP 非 2xx、空响应、SOAP Fault、数量格式错误和分段用户响应缺少 CUSTOMERID 都会显示为失败。

单个用户结果解析并展示以下服务端字段:CUSTOMERIDNAMEOUTIDCARDNOCARDSNSCARDSNRSTATUSCARDTYPECUSTDEPTODDFARESUBODDFAREOPCOUNTOPENDTNOUSEDATE。日期中的 T 会转换为空格;金额保持服务端原始字符串,不自行换算单位。PWDQUERYPWDIDCARDNO 等认证或个人敏感字段不会进入页面结果。

4.1 全部用户 SQLite 保存规则

“获取全部用户信息”不再把所有 SOAP Envelope 聚合到内存。每个 GetCustomerByNum 响应解析成功后,立即使用独立 SQLite 事务保存;事务提交成功才更新页面进度并请求下一批。数据库文件为应用私有目录中的 onecard.db,当前使用三张表:

用途 唯一性/关键规则
customers 用户主表和常用查询字段 customer_id 主键,对应原始 CUSTOMERID;重复账号更新,不产生重复用户
customer_fields 保存每个用户的全部原始 XML 字段 (customer_id, field_name) 联合主键,字段名不区分大小写;更新用户时替换其全部旧字段
customer_sync_runs 保存同步开始、完成、数量、批次、状态和错误 sync_id 主键;状态为 RUNNINGSUCCESSFAILED

当前现场响应已观察到 51 个字段,代码按字段名和值原样保存,并兼容服务端后续增加的未知字段。按用户要求,PWDQUERYPWDIDCARDNO 也会落库;这些敏感字段不显示到页面、不写日志、不写外部存储。Manifest 已关闭系统自动备份,但当前 SQLite 是应用私有目录中的明文数据库,不等同于数据库加密。

每批内部任何用户解析或插入失败时整批回滚,之前已经提交的批次保留。只有服务器总数对应的全部批次均成功,才删除本轮未出现的旧用户并标记同步 SUCCESS;中途失败只标记 FAILED,不清理旧用户。页面失败摘要显示此前成功保存的批次数和数量,不显示用户原始字段值。

4.2 全量同步分阶段耗时日志

全量同步复用应用现有 ReaderLog,日志写入应用私有目录 files/reader_logs/,按天生成、单文件最大5MB并保留最近7天,可从“查看读卡日志”页面查看和复制。该功能不新增数据库表,不把耗时明细写入 onecard.db,不增加页面控件。

耗时使用 System.nanoTime() 单调时钟计算,单位统一为毫秒。系统时间调整不会影响阶段耗时;结果为 0 表示阶段不足1毫秒。

日志事件 关键字段 含义
CUSTOMER_SYNC_START syncIdbatchSize 全量同步开始
CUSTOMER_SYNC_COUNT_SUCCESS expectedCountrequestMs 获取服务器用户总数成功及请求耗时
CUSTOMER_SYNC_BATCH_START batchstartingCustomerIdrequestedCount 单批开始及分页定位
CUSTOMER_SYNC_REQUEST_SUCCESS batchrequestMsresponseBytes 分页 SOAP 请求耗时及原始响应 UTF-8 字节数
CUSTOMER_SYNC_PARSE_SUCCESS batchrecordsparseMs XML 解析条数与耗时
CUSTOMER_SYNC_SAVE_SUCCESS batchbatchSavedtotalSavedsaveMsbatchMs SQLite 批事务保存耗时及本批总耗时
CUSTOMER_SYNC_SUCCESS savedCountbatchesdatabaseCountelapsedMs 整轮同步成功摘要
CUSTOMER_SYNC_FAILURE stagebatchsavedCountsuccessfulBatchesstageMselapsedMs 失败阶段和失败前已完成进度;stagecount/request/parse/save/complete

日志只记录同步 ID、批次号、起始账号、数量、字节数、耗时和异常类型,不记录 SOAP 原文、姓名、卡号、证件号、PWDQUERYPWD 或其他用户字段值。日志写入异常会被隔离,不改变同步成功或失败结果。

5. 消费记录上传

5.1 调用

5.2 页面、厂商字段和 SOAP DTO 的 1:1 映射

页面字段 控件 ID 文档/C 字段 SOAP DTO 元素 必填 页面/代码类型与校验
终端编号 input_upload_terminal_id termID TermID 十进制 long,不得留空
账号 input_upload_customer_id custID CustomerID 十进制 long,不得留空
卡号 input_upload_card_number cardNO CardNO 十进制 long,不得留空
持卡序号 input_upload_card_serial cardSn Cardsnr 十进制 long,不得留空
卡类型 input_upload_card_class cardClass TradeCardType 十进制 long;文档值 4=M1、8=CPU
用户卡应用序列号 input_upload_card_asn cardASN CardSN 非空字符串
总额 input_upload_total_amount sumFare SumFare 十进制 long,单位分
管理费 input_upload_management_fee mngFare MngFare 十进制 long,单位分,可填 0
钱包卡类型 input_upload_wallet_type objNo PurseSector 十进制 long0=主钱包、1=补助钱包
消费额 input_upload_consumption_amount opFare OpFare 十进制 long,单位分
扣款后余额 input_upload_balance oddFare OddFare 十进制 long,单位分
扣款后消费计数 input_upload_operation_count opCount Opcount 十进制 long
消费时间 input_upload_consumption_time jyDT OPDT 必须严格匹配 yyyy-MM-dd HH:mm:ss
PSAM 卡号 input_upload_psam_id psamID SAMCardNO 仅十进制数字字符串;本地消费结果同时提供可直接填写的十进制值
PSAM 卡脱机交易序号 input_upload_psam_sequence psamJyNo SAMTradeNO 十进制 long
交易验证码 input_upload_tac tac CardMac 十进制 long;若页面结果是十六进制 TAC,上传前需转成无符号十进制

当前上传页面不会自动带入本地消费结果,也不会自动上传;这是本阶段“单点功能、不串业务流程”的既定边界。

6. 实现文件索引

范围 文件
本地消费页面 MainActivity.javaactivity_main.xml
消费编排与结果 NewcapecUsbCardReader.javaConsumptionRequest.javaConsumptionResult.javaConsumptionBatchResult.java
消费协议 NewcapecProtocol.javaConsumptionApdu.javaHidFrameCodec.java
服务器页面 ServerOperationsActivity.javaactivity_server_operations.xml
SOAP 客户端 OnecardWebServiceClient.javaSoapRequestFactory.javaSoapResponse.javaCustomerInfoFormatter.java
全部用户解析与同步 CustomerRecord.javaCustomerBatchParser.javaCustomerPageSource.javaCustomerSyncService.javaCustomerSyncResult.java
用户 SQLite 持久化 CustomerRepository.javaSQLiteCustomerRepository.java
全量同步耗时日志 SyncLogger.javaReaderLogSyncLogger.javaCustomerSyncService.javaReaderLog.javaReaderLogStore.java
服务器稳定性测试 ServerStabilityRunner.javaServerStabilityRound.javaServerStabilityResult.javaServerStabilityReportFormatter.javaServerStabilityReportStore.java
上传模型 UploadRecord.java

7. 现场验收顺序

  1. Android 主钱包执行 1 分消费,核对余额、计数、PSAM 序号和 TAC。
  2. 使用有补助余额的 CPU 卡分别验证补助钱包和补贴优先跨钱包交易。
  3. 使用真实 .asmx 地址和 AppID 连接服务器,逐个验证 8 个查询按钮。
  4. 点击“获取全部用户信息(分段)”,核对服务器总数、本轮保存数、SQLite 用户数相同,并确认同步状态为成功。
  5. 执行一键服务器稳定性测试,核对 100 轮进度、成功率、P95、错误汇总和本地报告。
  6. 使用服务器中已登记的终端、用户和卡数据上传一条测试记录,核对服务端 rtn 与后台流水。