手持核销车辆系统.md 80.2 KB

手持核销车辆系统 — 开发方案

状态:方案已按评审意见细化界面与流程,待最终确认后开发(暂不写代码) 日期:2026-09-25 目标设备:新大陆 NLS-MT95L 手持机(当前 ADB 已连接:MT95LTFFD00631),同时兼容通用安卓手机 识别引擎:百度车牌识别安卓离线 SDK V7.5 包名(已确认):com.ectrip.checkincar 本期策略:先模拟跑通全流程(识别 + 核销),百度 SDK 同步集成但等授权到位后启用 本次细化:第 5 章界面与交互(线框图 + 核销页 5 状态机 + 异常分支 + 硬件键/反馈 + 适配)


1. 需求概述

在手持机上开发一个竖屏安卓 App,供核销员工使用:

  1. 主界面:两个大按钮 —— 「车辆核销」「系统设置」。
  2. 车辆核销流程:调起后置摄像头拍摄/对准车牌 → 调用百度离线 SDK 识别车牌 → 展示识别结果并允许人工修正 → 调用后台预约平台接口完成核销 → 展示核销结果(预约信息)。
  3. 系统设置页:显示/配置
    • 本机 IP 地址(自动获取、只读、可刷新)
    • 本机 MAC 地址(自动获取、只读)
    • 核销员工 ID(可编辑保存)
    • 后台核销地址(可编辑保存)
    • (附加)设备 SN、百度授权设备指纹、授权状态、识别引擎与模拟开关、版本号
  4. 今天(第一期)后台与识别均先模拟,界面与流程按真实业务设计,后续只替换引擎与接口实现,不改界面。

2. 实测环境

2.1 目标手持机(ADB 实测)

项目 实测值
厂商/型号 Newland(新大陆)NLS-MT95L
序列号 SN MT95LTFFD00631
系统 Android 11(API 30),固件 MT95L-X_V1.01.002_111006
CPU ABI arm64-v8a(8 核)
内存/存储 2.8GB RAM;/data 20GB(仅用 593MB)
屏幕 720 × 1440,density 320(xhdpi),竖屏
摄像头 后摄 + 前摄,支持自动对焦、闪光灯,camera level FULL
网络 WLAN(wlan0),当前 IP 192.168.50.70/24,SSID HUAWEI-ECTRIP-5G
MAC a4:e8:a3:8d:82:56(/sys/class/net/wlan0/address,权限 -r--r--r--,App 无需权限可直接读取真实 MAC,已实测节点存在且全局可读)
百度设备指纹(实测) 153DA2A38757D7D7016E76F8EE0AACE367(35 位大写。用官方 demo APK 7.5 在本机激活页实测取得,授权绑的就是它,不是 SN;正式 App 用同一套 jar 预期取值一致,以 App 设置页实显为准)

2.2 开发机(macOS)

  • Android Studio 已安装;Android SDK 平台 19~36 齐全,build-tools 含 34.0.0
  • JDK 8 / 11 / 17 / 19 均有(新工程用 JDK 17)
  • adb 路径 /opt/homebrew/bin/adb,设备连接正常

2.3 百度 SDK 物料

路径:/Volumes/USERDATA/anroidprj/Baidu_OCR_platenumber_OfflineSDK_Android_7.5-2/7.5车牌sdk/

文件 用途
sdk.zip 集成物料:3 个 jar + 两种 ABI 的 so + 模型资源
ocr-sdk-offline-demo.zip 官方 demo 工程(Gradle,Java,已分析源码)
车牌识别安卓离线SDK_V7.5用户接入文档.pdf 官方接入文档(13 页,已逐页阅读)
车牌识别SDK_7.5.0.apk 官方演示 APK
readme.txt 模型与授权文件放 assets 的说明

3. 百度 SDK V7.5 技术分析(结论)

3.1 包内容(sdk.zip)

Java 依赖(3 个 jar)

  • libocrgveplate.jar —— 车牌识别引擎(核心类 com.baidu.vis.ocrgve.plate.OcrgveEnginePlate)
  • bd_unifylicense.jar —— 统一授权组件(AndroidLicenser / BDLicenseActivator)
  • liantian.jar —— 安全模块

Native 库(两种 ABI 都提供,本设备用 arm64-v8a)

  • libocrgveplate.so(arm64 约 6.9MB)、libpaddle_light_api_shared_visocr.so(PaddleLite 推理,约 4.4MB)、libbd_unifylicense.so、libliantian.so

模型资源(assets,约 30MB,整目录拷入工程 assets)

  • 目录名固定:assets/ocrgve_resource_plate/
  • 含 5 个加密模型(.dpmodel.encrypted:车牌检测、主分类、单行识别 a/b、车牌颜色)、configure.ini、字典与分类配置等
  • initFromAssets() 内部按固定目录名加载,目录名不可改

3.2 支持范围

  • Android 6.0+(文档声明),提供 armeabi-v7a / arm64-v8a;本设备 arm64-v8a 完全匹配
  • 纯 Java 接口,离线推理(PaddleLite CPU),识别不依赖网络;网络仅授权时需要

3.3 核心 API(已通过 javap 反编译核对)

// 算法 ID(车牌识别固定值,demo 实测 = 7,以 getAlgorithmId() 返回为准)
int algId = OcrgveEnginePlate.getAlgorithmId();

// ① 授权(必须在子线程调用;成功后才能初始化模型)
//    方式A:本地授权文件(从控制台下载,放 assets)
AndroidLicenser.ErrorCode ret = AndroidLicenser.getInstance()
        .authFromFile(context, licenseKey, licenseFileName, true/*isRemote*/, algId);
//    方式B:在线(单台/批量)序列号激活,返回 0 成功
int ret = BDLicenseActivator.initLicenseOnLine(context, serialKey, "", algId);

// 设备指纹(控制台绑定授权用)
String deviceId = AndroidLicenser.getDeviceId(context);
// 授权信息(有效期等)
BDLicenseAuthInfo info = AndroidLicenser.getInstance().authGetAuthInfo(context, algId); // expireTime

// ② 初始化模型(子线程;授权成功后调用;全局只初始化一次,重复初始化返回 MULTI_INIT=6)
OcrgveEnginePlate engine = new OcrgveEnginePlate();
OcrgveEnginePlate.setPreFiltering(false);      // 纯背景前置过滤,可选
int code = engine.initFromAssets(context);    // 0=成功

// ③ 识别(子线程)
ArrayList<OcrgvesdkResponsePlate> results =
        engine.processOcrgvesdk(rgbaBytes, 10/*PIX_RGBA*/, width, height, 0/*orientation*/);

// ④ 释放(App 退出时)
engine.uninit();

识别结果 OcrgvesdkResponsePlate 字段

字段 含义
name 类型,取 "plate_number" 为车牌结果
mystring 车牌号字符串(支持新能源 8 位等)
color 颜色:blue/yellow/green/white/black(蓝/黄/绿/白/黑)
detScore 检测置信度
recgProb[] 每个字符的识别置信度(可用最小值做整体过滤,demo 默认阈值 0.8)
coords[8] 车牌四角坐标

初始化错误码:0 成功、1 未知、2 非法参数、3 内部错误、4 鉴权错误、5 未初始化、6 重复初始化、7 图片处理错误、9 模型文件未找到。

授权错误码(高频):7 设备 ID 校验错误、8 包名(applicationId)校验错误、9 过期时间不正确、11 授权已过期、12 本地文件读取失败、13 远程数据拉取失败。

3.4 授权机制(关键约束,影响工程决策)

重要澄清:百度授权绑定的是「设备指纹 Device-id」,不是设备硬件 SN。

标识 是什么 怎么取 用途
百度设备指纹 Device-id 百度安全模块(liantian)综合多因子硬件信息生成的一串大写 ID(带 fingerVersion 指纹版本) 代码 AndroidLicenser.getDeviceId(context);或 demo logcat 搜 BDLicenseLocalInfo(文档 2.5 节原文:「设备指纹即 deviceid」) 百度离线授权的唯一设备标识,控制台必须绑它
设备硬件 SN 厂商出厂序列号 ro.serialno(本机 = MT95LTFFD00631,与 ADB 设备号一致) Build.SERIAL / 属性读取 / 设置页可手填兜底 仅我方资产管理、核销上报告备用,百度授权不用它

注:demo 激活页把 Device-id 标注为「序列号」是口语化叫法,二者不是一回事。

指纹构成(已反编译 liantian.jar 字节码核实):com.baidu.liantian.ac.LH.getId() 采集 ANDROID_ID、IMEI/IMSI/SIM 序列号、直接读取 /sys/class/net/wlan0/address 与 eth0/address 的真实 MAC(键名 rpmacadd)、WiFi BSSID、Build 硬件信息(MODEL/PRODUCT/BOARD/HARDWARE/MANUFACTURER/DEVICE/DISPLAY、CPU_ABI)等综合生成。

稳定性(文档 6.9):刷机、更换硬件会导致指纹变化;安卓系统升级、App 卸载重装、恢复出厂值不会。

本机实测(2026-09-25,官方 demo 7.5 激活页):设备指纹 = 153DA2A38757D7D7016E76F8EE0AACE367,页面原文要求「点击复制下方本机硬件指纹 → 百度云控制台:离线 SDK 管理-单台设备授权-离线激活」。同时 logcat 显示 Android 11 已拒绝第三方读取 IMEI(TelephonyPermissions ... getDeviceId:-1),即本设备指纹实际由 ANDROID_ID + 真实 MAC + Build 硬件信息等因子生成,无电话权限也能稳定取到,App 无需额外申请 READ_PHONE_STATE。

  1. 官方支持 4 种授权(V7.5 接入文档 2.1 节原文核实,jar 签名 javap 复核一致),SN 和设备指纹都涉及,区别在激活方式:

| 方式 | 控制台操作 | 设备端调用(algId = OcrgveEnginePlate.getAlgorithmId() = 7) | 要不要手工提指纹 | |---|---|---|---| | ① 控制台离线授权文件 | 申请序列号 → 单台设备授权选「车牌识别」→ 点「离线激活」绑定设备指纹 → 下载 .ini 授权文件 | 文件放 assets,AndroidLicenser.getInstance().authFromFile(ctx, licenseKey, licenseFile, true, 7),文档示例 key/file = AISEE_OCRGVE_7_ANDROID / AISEE_OCRGVE_7_ANDROID.ini(key/file 同名时可直接照搬 demo) | 要,先取指纹再去控制台绑定 | | ② 按 App 授权文件 | 申请按 app 授权的文件(绑包名+签名 MD5),licenseKey 随文件提供 | 同上 authFromFile,licenseKey/licenseName 填授权方给的值 | 否(绑包名+签名) | | ③ 在线单台设备序列号 | 控制台申请单台序列号(SN) | 设备联网调 BDLicenseActivator.initLicenseOnLine(ctx, 序列号, "", 7),返回 0 成功;首次联网激活时 SDK 自动把本机指纹绑定到该 SN | 否(自动绑定,最省事) | | ④ 在线批量序列号 | 控制台申请批量序列号(SN) | 同③,同一个 SN 可激活多台设备 | 否 |

指纹获取 API(文档 2.5 节「获取本地鉴权设备指纹信息(离线激活需要)」,原文「设备指纹即 deviceid」):

  • 直接取:AndroidLicenser.getDeviceId(context)(本 App 设置页采用,实测 153DA2A3…)
  • 完整本地信息:AndroidLicenser.getInstance().authGetLocalInfo(ctx, 7) 返回 BDLicenseLocalInfo,字段含 deviceId / packageName / md5(签名MD5) / licenseKey / fingerVersion;demo 也可在 logcat 搜 BDLicenseLocalInfo 拿 Device-id。
    1. 授权同时绑定「设备指纹 Device-id + 应用包名(applicationId)」,一机一证;换设备、刷机、改包名均会失效;系统 OTA / 卸载重装不改变指纹。
    2. 当前拿到的 SDK 包内没有任何授权文件(assets 只有模型;demo 工程也没有 license),官方 demo 必须输入有效序列号/授权文件才能识别。
    3. 因此:应用包名必须先定下来再去申请授权,中途改包名授权作废(包名已确认,见第 14 章;申请流程见第 11 章)。
    4. 第一期 App 的设置页会同时展示「设备 SN(备查)」与「百度设备指纹(点按复制,授权必用)」,未购授权也能先取指纹去控制台申请。
    5. 推荐落地路径:手持机能联网时优先走方式③(设置页输入控制台申请的单台 SN,initLicenseOnLine 一键激活,无需手工收集指纹);设备现场无网络时走方式①(设置页读指纹 → 控制台离线激活下载 .ini → 随 App 打包进 assets,LICENSE_KEY/LICENSE_FILE 见 LicenseManager.java 常量)。
    6. 已实测坑:未授权状态下 getAuthStatus(7) 在该 SDK 内会抛 ArrayIndexOutOfBoundsException: length=18; index=1000(其内部用状态码索引 ErrorCode 枚举),必须 try/catch;authGetAuthInfo() 未授权时直接 native SIGSEGV 崩溃,必须先确认 SUCCESS 再调。

3.5 demo 流程分析(可直接借鉴)

StartActivity(检查授权)→ ActivitionActivity(三种激活方式 + 展示设备指纹)→ HomeActivity(子线程 initFromAssets)→ ScaleActivity(Camera1 连续取流 640×480 NV21 → 旋转为正向 Bitmap → RGBA → 子线程识别 → 置信度过滤 → 出结果)。

demo 相机部分较老旧(Camera1 + 离屏 SurfaceTexture + ImageView 显示画面),新工程不照搬,改用 CameraX;授权、初始化、识别的调用时序与线程模型严格按 demo/文档实现。


4. 总体设计

4.1 业务流程(详细设计见第 5 章)

┌─────────┐  车辆核销   ┌──────────────┐  对准/拍照   ┌──────────────┐
│ 主界面  │ ─────────▶ │ 相机识别页    │ ──────────▶ │ 结果确认卡片  │
│ Main    │  ①点击     │ VerifyActivity│  ②自动/拍照 │ (可人工改牌)  │
└─────────┘            └──────────────┘            └──────┬───────┘
     │ 系统设置                      ▲  重拍/继续识别       │ ③确认核销
     ▼                               └─────────────────────┤
┌─────────┐                                                  ▼
│ 设置页   │                                       ┌──────────────────┐
│ Settings│                                       │ 调用后台核销接口  │
└─────────┘                                       │ (本期 Mock)       │
                                                  └──────┬───────────┘
                                                         ▼
                                                  ┌──────────────────┐
                                                  │ ④核销结果(成功/失败)│
                                                  │ 成功→2s 自动回取景 │
                                                  │   (连刷下一辆)   │
                                                  └──────────────────┘

设计目标:每辆车常规操作只需 2 次点击 —— 主屏点「车辆核销」→ 对准车牌(自动识别)→ 点「确认核销」→ 成功后自动回到取景位继续下一辆,无需返回主屏。

识别策略(已确认:自动 + 拍照):连续自动识别为主(对准即出结果),手动拍照按钮为辅;取流帧节流约 400ms、单线程串行识别,命中且最小字符置信度 ≥ 阈值(默认 0.80,可设)后停止取流进入确认;支持「重拍」恢复。附闪光灯开关;预留音量下键/手持机实体扫描键作为拍照快捷键。

4.2 技术栈

项 选型 理由
语言 Java 1.8 百度 SDK 与 demo 均为 Java,可最大量直接复用经过验证的调用代码
IDE/构建 Android Studio + Gradle 8.x + AGP 8.x + JDK 17 本机已具备;compileSdk/targetSdk 34
minSdk 23(Android 6.0) 对齐百度声明的支持下限,兼容通用安卓手机
ABI arm64-v8a + armeabi-v7a 覆盖手持机与主流手机
相机 CameraX 1.3.x(camera-camera2 + lifecycle + view) 生命周期安全、自动处理预览方向/分辨率;MT95L 为标准 Camera2 FULL 设备
UI AndroidX AppCompat + Material + ViewBinding,传统 View/XML 竖屏固定,720×1440 dp 适配(基准宽 360dp)
网络 OkHttp 4 + org.json 轻量;核销接口只有一个
存储 SharedPreferences(设置)+ 本地 JSON(核销记录,最近 100 条) 不引入数据库,保持简单
方向 全应用 screenOrientation="portrait" 需求明确竖屏

若 CameraX 在该工业设备上出现兼容性问题(概率低),回退方案:移植 demo 的 Camera1 取流代码(已验证可在百度引擎上跑通),识别/业务层不变。

4.3 工程结构(拟新建于当前目录)

checkInCar/
├── 手持核销车辆系统.md            ← 本文档
├── settings.gradle / build.gradle / gradle.properties / gradlew
└── app/
    ├── build.gradle
    └── src/main/
        ├── AndroidManifest.xml
        ├── assets/
        │   └── ocrgve_resource_plate/        ← 从 sdk.zip 整目录拷贝(约30MB)
        │       (百度授权 *.ini 待用户提供后放这里)
        ├── jniLibs/
        │   ├── arm64-v8a/  ← 4 个 so(手持机用)
        │   └── armeabi-v7a/ ← 4 个 so(通用手机兼容)
        ├── libs/  ← libocrgveplate.jar / bd_unifylicense.jar / liantian.jar
        ├── res/   ← layout/drawable/values/mipmap(竖屏 UI)
        └── java/com/ectrip/checkincar/
            ├── App.java                       ← Application,引擎单例初始化
            ├── MainActivity.java              ← 主界面(2 个按钮 + 今日核销数)
            ├── ui/settings/SettingsActivity.java
            ├── ui/verify/VerifyActivity.java  ← 相机 + 识别 + 结果确认 + 核销
            ├── core/prefs/AppPrefs.java       ← 全部设置项的读写
            ├── core/device/DeviceInfo.java    ← IP / MAC / SN 获取(多策略)
            ├── plate/
            │   ├── PlateInfo.java             ← 车牌号/颜色/置信度/位图
            │   ├── PlateRecognizer.java       ← 识别引擎接口(可切换)
            │   ├── MockPlateRecognizer.java   ← 模拟识别(本期默认)
            │   └── BaiduPlateRecognizer.java  ← 百度 SDK 封装(授权+init+识别)
            ├── license/LicenseManager.java    ← 百度授权状态/在线激活/设备指纹
            ├── net/
            │   ├── VerifyApi.java             ← 核销接口(真实 HTTP)
            │   └── MockVerifyApi.java         ← 模拟核销(本期默认)
            ├── model/VerifyRequest.java / VerifyResult.java
            └── history/VerifyHistory.java     ← 本地核销记录(JSON)

包名 com.ectrip.checkincar(结合目录名 checkInCar 与现场 WiFi ECTRIP)已评审确认采用,App 显示名「车辆核销」;该包名将用于百度授权申请,之后不再变更。


5. 界面与交互详细设计(本章为本次重点细化内容)

5.0 设计原则

  1. 竖屏单手操作:全部页面锁竖屏;核心按钮放在拇指热区(屏幕下半部),高度 ≥56dp。
  2. 名称四字对齐:主按钮统一 4 个字 —— 「车辆核销」「系统设置」「确认核销」「重拍」。
  3. 步骤最少:常规每车仅 2 次点击(进核销 → 确认核销),识别与连刷全部自动化。
  4. 紧凑两列:设置页信息项采用「左标签 : 右值」左右两列、分组卡片排列,减少纵向行数,把屏幕空间留给状态/记录区域。
  5. 状态可见:每个耗时动作都有明确提示(识别中、核销中),结果用大字 + 颜色 + 振动/音效三重反馈,户外嘈杂环境也能用。

5.1 页面清单与跳转关系

页面 Activity 进入方式 返回行为
主界面 MainActivity 启动页 —
系统设置 SettingsActivity 主界面「系统设置」 返回即回主界面,配置自动保存
车辆核销 VerifyActivity 主界面「车辆核销」 返回主界面;核销成功不关闭本页,自动回取景态

页面间不产生堆叠(核销页只有一个实例),应用内不做登录页(员工 ID 设置页手填)。

5.2 主界面 MainActivity(线框,基准 360×720dp)

2026-09-25 更新:主界面按「鼎游 ECTRIP 科技风」改版(品牌 Logo + 模式状态 + 大按钮),最新设计以第 15.2 节为准,本节线框保留作历史对照。

┌──────────────────────────────┐
│ 车辆核销            v1.0.0     │ ← 标题栏 48dp,右侧版本号
│                              │
│                              │
│      ┌────────────────┐      │
│      │                │      │
│      │   🚗 车辆核销    │      │ ← 主按钮:高 110dp,主色实心,
│      │                │      │   28sp 粗体,点击进核销页
│      └────────────────┘      │
│                              │
│      ┌────────────────┐      │
│      │   ⚙  系统设置   │      │ ← 次按钮:高 96dp,浅色描边
│      └────────────────┘      │
│                              │
│                              │
│  引擎:模拟  网络:WLAN ✔       │ ← 状态行 14sp
│  今日已核销 12 台             │
└──────────────────────────────┘
  • 两按钮纵向居中、宽度占屏 80%;「车辆核销」视觉权重最高。
  • 底部状态行实时显示:识别引擎(模拟/百度)、网络是否在线、今日成功核销数(从本地记录统计,进页面 onResume 刷新)。
  • 连续点击「车辆核销」防抖;若选百度引擎但未授权/模型未就绪,按钮仍可点,进核销页时弹提示并引导去设置(不闪退)。

5.3 车辆核销页 VerifyActivity —— 单页 5 个状态(核心)

该页只有一个 Activity,通过覆盖层切换 5 个状态,避免页面跳转打断节奏:

S1 取景待识别 ──自动帧/拍照──▶ S2 识别中 ──命中且置信度达标──▶ S3 结果确认
     ▲                            │                            │
     │                         未命中/低分                     │ 确认核销
     │                            └──────────────▶ 回 S1      ▼
     │                                                     S4 核销提交中
     │◀──────────── 成功:2s 后自动返回(连刷)──────────────────┤
     │                                                          │
     └────────────────── 失败:停留,手动「重试/重拍」◀── S5 核销结果(成功/失败)

S1 取景待识别

┌──────────────────────────────┐
│ ✕        车辆核销      💡 开 │ ← 顶栏:左=返回主界面,右=闪光灯开关
│                              │
│        后摄全屏预览           │
│    ┌────────────────────┐    │
│    │ ╲                  ╱ │    │ ← 车牌对齐框(横向矩形,四角 L 形
│    │                      │    │   高亮线 + 半透明遮罩)
│    │                      │    │
│    │ ╱                  ╲ │    │
│    └────────────────────┘    │
│                              │
│   将车牌移入框内,自动识别     │ ← 提示文字随状态变化
│                              │
│              ⬤               │ ← 底部居中大快门(高 72dp),
│          [拍照识别]           │   手动拍照;音量下键/扫描键等效
└──────────────────────────────┘
  • CameraX 全屏预览,画面按竖屏自动旋转校正;对齐框宽占屏 86%、高约宽的 1/3。
  • 自动识别在后台持续进行(用户无感);快门是主动兜底。
  • 连续 3 秒无任何检测时,提示文字轮播:「请靠近车牌 / 保持光线充足 / 点击下方按钮拍照」。

S2 识别中

  • 命中帧后立即停止取流,画面定格;对齐框四角变为加载色,提示「识别中…」(正常 200~600ms,不做全屏遮罩,避免闪烁)。
  • 结果不达标(无车牌或最低字符置信度

S3 结果确认(底部抽屉式卡片,不挡全图)

┌──────────────────────────────┐
│              (定格的车牌画面) │
│                              │
├──────────────────────────────┤
│ 车牌号                   蓝牌 │ ← 颜色标签:蓝/黄/绿/白/黑
│  ┌──────────────────────────┐│
│  │ 京 A8D6F8              ✎ ││ ← 36sp 超大号;点 ✎ 弹键盘手改
│  └──────────────────────────┘│
│ 置信度 0.96   员工 10086       │ ← 次要信息 13sp 一行
│                              │
│  ┌──────────┐ ┌────────────┐ │
│  │  重拍     │ │  确认核销   │ │ ← 左次右主,等高 56dp
│  └──────────┘ └────────────┘ │
└──────────────────────────────┘
  • 车牌可人工修正:点击号码区域弹数字+省份简称键盘(系统键盘即可,输入自动去空格/转大写);手改后置信度置灰显示「人工输入」。
  • 员工 ID 取自设置页;若为空,点「确认核销」时提示「请先在设置中填写核销员工 ID」并给「去设置」入口。
  • 后台地址为空或非法同理拦截,不发无效请求。

S4 核销提交中

  • 卡片切换为 Loading:旋转指示 +「核销中,请勿移开…」;按钮全部禁用防重复提交;HTTP 超时 10s。

S5 核销结果

成功(绿底)                         失败(红底)
┌──────────────────────┐    ┌──────────────────────┐
│         ✔            │    │         ✕            │
│      核销成功         │    │      核销失败         │
│   京A8D6F8           │    │   京A8D6F8           │
│ ─────────────────── │    │ 原因:该车牌无预约记录 │ ← 后台 msg 原文
│ 预约人:张三          │    │                      │
│ 场  地:P1预约车位    │    │                      │
│ 时  段:今日09-18点   │    │                      │
│ 2 秒后自动继续…       │    │ [重拍]  [手改车牌重试] │
│        [立即继续]     │    │        [返回]        │
└──────────────────────┘    └──────────────────────┘
  • 成功:提示音 + 长振动 1 次,展示后台返回的预约信息(无字段则只显车牌),2 秒倒计时后自动回 S1 连刷;点「立即继续」不等计时。
  • 失败:短促两声振动 + 低音,原因直接显示后台 msg(如:无预约 / 已核销 / 不在有效期 / 网络异常),不自动跳转,由员工选择重拍、手改车牌重试或返回。
  • 无论成败都写本地记录(时间、车牌、颜色、员工、结果、原因),成功计入今日数。

5.4 系统设置 SettingsActivity(紧凑两列分组卡片)

┌──────────────────────────────┐
│ ‹  系统设置                   │
│ ── 设备信息(自动获取)─────────│
│ 本机 IP    192.168.50.70   ⟳  │ ← 左标签120dp 右值,⟳=刷新
│ MAC 地址  a4:e8:a3:8d:82:56   │   值可点复制;IP/MAC 整行左右排
│ 设备 SN    MT95LTFFD00631  📋 │   硬件序列号,仅备查/上报
│ 授权指纹   86F3A2…D9C1     📋 │ ← 百度Device-id,授权必绑(详见3.4)
│ 指纹版本   v3 / 算法ID 7       │
│ 授权状态   未授权              │
│ ── 核销配置 ─────────────────│
│ 员工 ID   [ 10086          ] │ ← 标签左、输入框右,同一行
│ 核销地址  [ http://192.16…  ] │ ← 一行,保存时校验 URL
│ ── 识别配置 ─────────────────│
│ 识别引擎  (●模拟 ○百度离线)   │ ← 单选 segmented
│ 置信阈值  [────●────] 0.80    │ ← 滑块 0.50~0.99
│ ── 模拟配置(模拟引擎时显示)──│
│ 模拟车牌  [ 留空=随机       ] │
│ 识别耗时  [ 300 ] ms          │
│ 核销结果  (●成功 ○失败 ○随机) │
│ ── 授权(百度引擎时显示)──────│
│ 序列号    [                ] │
│           [      激活        ]│
│                              │
│ 版本 v1.0.0    今日核销 12 台 │
└──────────────────────────────┘

交互规则:

  • 所有配置改完即存(失焦/切换即写 SharedPreferences),不做「保存」按钮,减少一次点击。
  • 「模拟配置」与「授权」分组按当前引擎联动展开/收起,不堆无关项。
  • IP、MAC、授权指纹、SN 点击整行复制到剪贴板并 Toast「已复制」(申请授权、报备设备时高频使用);「授权指纹」行用浅色小字注明「百度授权绑定此 ID,非设备 SN」,防止混淆(见 3.4)。
  • 授权状态:未授权 / 已授权(至 yyyy-MM-dd)/ 授权过期(红色)。
  • 核销地址做格式校验(http(s):// 前缀 + host),不合法时行尾红字提示且不保存。
  • 整页可滚动,但上述内容在 720×1440 一屏内基本可见,不做冗余留白。

5.5 异常分支与处理方式一览

场景 触发时机 界面处理
相机权限未授予 进核销页 内嵌说明卡片 +「去授权」按钮跳系统设置;返回后自动重试
设备无后摄/打开失败 绑定相机 结果位显示「相机不可用」+ 返回按钮(模拟模式下可用纯手动输牌继续演示)
百度未授权/授权过期 引擎=百度且校验失败 弹窗显示设备指纹与错误码中文说明 →「去设置激活」;可切回模拟模式
模型初始化失败 进核销页前 提示错误码(4 鉴权/9 模型缺失等),禁止进入 S1,回设置处理
一直识别不到车牌 S1/S2 不打断,文字引导;员工可手动拍照
识别到但置信度低 S2 Toast「识别不确定,请靠近或手改」后回 S1;若手改则直接进 S3
员工 ID/核销地址为空 S3 点确认 输入框抖动提示 +「去设置」,不发请求
网络不通/超时(10s) S4 S5 失败态:原因「网络异常,请检查 WiFi 与核销地址」,可原样重试不重拍
后台返回 code≠0 S4→S5 失败态显示 msg(无预约/已核销/不在有效期…),支持改牌重试
HTTP 非 200/返回非法 JSON S4 失败态「服务异常(HTTP 500)」并记录日志
连续重复提交 S4 按钮禁用,二次点击忽略
识别中按返回 S1~S3 直接回主界面,释放相机与分析器,引擎不销毁
来电话/切后台 生命周期 onPause 停取流停对焦;回前台自动恢复 S1

5.6 硬件键与反馈

  • 物理键映射(手持机友好):音量下键 = 拍照(KeyEvent 监听);如后续确认 MT95L 实体扫描键可用,将其键值映射为同一动作(设置里加开关,默认开)。
  • 音效:成功/失败两种短音(assets 内置或系统提示音),跟随系统媒体音量;设置页暂不做静音开关(可后加)。
  • 振动:成功长振 1 次、失败短振 2 次,使用 VibrationEffect(API 26+),低版本降级。
  • 闪光灯:每次进页默认关闭,状态不记忆(避免白天误开)。

5.7 屏幕适配

  • 基准宽 360dp(720px@320dpi),用 ConstraintLayout + LinearLayout 组合;文字用 sp,触控目标 ≥48dp。
  • 长屏手机(≥800dp 高)拉伸按钮间距与预览区,设置页两列比例固定;小屏(320dp 宽老机)允许设置页滚动,核销页对齐框按比例缩放。
  • 颜色仅取功能语义(主色蓝、成功绿、失败红、标签灰),白天户外对比度优先;不使用横屏布局。

6. 关键模块设计

6.1 设备信息获取(IP / MAC / SN,已在设备上验证可行路径)

  • IP:NetworkInterface.getNetworkInterfaces() 枚举,取 wlan0(其次 eth0)上非 loopback、非 link-local 的 IPv4。实测当前 192.168.50.70。无需权限。
  • MAC(Android 6+ 官方 API 被屏蔽,返回 02:00:00:00:00:00),按顺序兜底:
    1. 直接读 /sys/class/net/wlan0/address —— 本机实测权限 -r--r--r-- 可读,值 a4:e8:a3:8d:82:56,作为首选;
    2. 枚举 /sys/class/net/*/address 找首个非 00/02 开头地址;
    3. NetworkInterface.getHardwareAddress();
    4. WifiInfo.getMacAddress()(基本是兜底假值,并明确标注「系统限制」)。
  • SN:Build.getSerial() 在 API 29+ 需要 READ_PRIVILEGED_PHONE_STATE(普通应用拿不到),改用 Build.SERIAL(28 以下)+ 读 ro.serialno 反射/备用;本设备 SN 与 ADB 一致(MT95LTFFD00631)。如系统接口受限,设置页允许手工填写设备编号并保存。
  • 进入设置页与点击刷新时重新获取。

6.2 相机与识别

  • CameraX:Preview + ImageAnalysis(STRATEGY_KEEP_ONLY_LATEST),目标 640×480 / 接近 4:3;输出 YUV_420_888 → 转 Bitmap(按 targetRotation 旋转为正向)→ 必要时缩放到宽 ≤1280 → copyPixelsToBuffer 得到 RGBA byte[]。
  • 识别在单线程 Executor 串行执行(ReentrantLock 思路同 demo),帧间隔 ≥400ms;识别期间丢弃新帧。
  • 结果过滤:仅取 name=plate_number;min(recgProb) ≥ 阈值(可配)才接受;记录颜色。
  • 命中后 analysis.clearAnalyzer() 停止取流,显示结果卡片;重拍时重新绑定。
  • 手电筒:CameraControl.enableTorch;权限:CAMERA 运行时申请。
  • 引擎生命周期:BaiduPlateRecognizer 随 Application 持有单例,进入核销页前确保已授权 + initFromAssets 完成(只初始化一次);切换引擎/页面不重复初始化。

6.3 模拟识别(MockPlateRecognizer,本期默认)

  • 实现同一个 PlateRecognizer 接口,延时 200~500ms 返回 PlateInfo:
    • 车牌 = 设置项固定值,或内置车牌库随机(如 京A·8D6F8 蓝牌、粤B·D12345 新能源绿牌、黄底牌等);
    • 置信度 0.90~0.99;颜色随牌型。
  • 也支持在结果卡片直接手输车牌(真机演示时的保底手段)。
  • 目的:不依赖百度授权即可完整演示「拍(模拟)→ 识 → 改 → 核销 → 记录」闭环。

6.4 后台核销接口

  • 统一封装 VerifyApi 接口,MockVerifyApi 与真实 HttpVerifyApi 可按设置切换,业务层无感。
  • 请求在子线程,OkHttp 超时连接 5s / 读取 10s;支持 http 明文(现场内网地址),通过 network_security_config 放行内网明文,不全局放开。
  • 接口契约为草案(待与预约平台对齐),见第 7 章;Mock 按该契约返回。
  • 每次成功/失败均写本地记录(时间、车牌、员工、结果、原因),主界面显示「今日成功核销数」。

6.5 设置存储 AppPrefs

  • 单个 SharedPreferences(car_verify_prefs)封装全部键:staffId、baseUrl、engineType、scoreThreshold、mockPlate、mockResult、设备编号手填值等;改动即时生效。

6.6 授权管理 LicenseManager

  • 封装:查询设备指纹、查询授权状态与到期时间、在线序列号激活、离线文件 authFromFile。
  • 严格遵守官方时序:授权(子线程)成功 → 再 initFromAssets(子线程),二者不并发、不重复初始化;所有错误码映射为中文提示(第 3.3 节)。
  • App 启动时若引擎为「百度」:后台静默校验授权与模型状态,主界面/设置页展示;未授权则点击「车辆核销」时引导去授权页,不会闪退。

7. 后台核销接口契约(草案,待平台方确认)

请求

POST {后台核销地址}/api/checkin/carVerify
Content-Type: application/json
{
  "plateNo": "京A8D6F8",        // 车牌号(必填)
  "plateColor": "blue",         // blue/yellow/green/white/black
  "staffId": "10086",           // 核销员工ID
  "deviceSn": "MT95LTFFD00631", // 设备SN
  "mac": "a4:e8:a3:8d:82:56",
  "ip": "192.168.50.70",
  "timestamp": 1727230000000    // 毫秒
}

响应

{
  "code": 0,                    // 0=核销成功,其他=失败
  "msg": "核销成功",
  "data": {
    "reservationId": "YY20260925001",
    "visitorName": "张三",
    "lotName": "P1 预约车位",
    "visitTime": "2026-09-25 09:00-18:00",
    "plateNo": "京A8D6F8"
  }
}
  • 常见失败:无预约、已核销、不在有效期、车牌不匹配等,以 code/msg 展示。
  • 真实路径、字段名、鉴权方式(是否加 token/签名/租户号)拿到平台文档后只改 HttpVerifyApi 与 model,界面不动。

8. 第一期(今天,模拟版)交付内容与验收

交付:可在 MT95L 上安装运行的 debug APK + 完整 Android Studio 工程。

  1. 新建工程、拷贝百度 jar/so/模型进工程(不启用不调用,仅保证能打包通过)。
  2. 主界面两个按钮 + 今日核销数;全屏竖屏。
  3. 设置页:IP/MAC/SN 自动显示并可刷新;员工 ID、核销地址可保存;引擎/阈值/模拟参数可配;显示百度设备指纹(即使未授权也能取到,供申请授权用)。
  4. 核销页:CameraX 后摄预览、对焦、闪光灯、对齐框、手动拍照;模拟识别出车牌;结果卡片可改车牌;模拟核销返回成功/失败(可配);成功/失败结果页 + 振动;本地记录与今日计数。
  5. 相机、网络等运行时权限处理。
  6. adb install -r 安装到 MT95LTFFD00631 真机走查全部流程(竖屏、720×1440 适配)。

验收标准

  • 手持机上能连续演示:进入核销 → 对准/拍照 →(模拟)出号 → 改号 → 确认核销 → 成功/失败 → 继续下一辆。
  • 设置页显示的 IP=192.168.50.x、MAC=a4:e8:a3:8d:82:56(以实际为准),员工 ID 与地址重启后保留。
  • 切换「模拟核销结果=失败/随机」时错误流程与提示正确。

9. 第二期(授权与真实接口到位后)

  1. 你方在百度控制台完成授权(流程见第 11 章),或提供在线序列号。
  2. 引擎切「百度离线识别」:真机初始化模型 → 真实车牌识别调试(光照、距离、新能源/黄牌、置信度阈值、识别耗时实测)。
  3. 按预约平台真实文档实现 HttpVerifyApi、联调核销(含无预约/重复核销等异常分支),必要时加 token/签名。
  4. release 签名、混淆(追加百度 keep 规则:-keep class com.baidu.vis.**{*;}、-keep class com.baidu.liantian.**{*;}、keep native 方法)、出正式包。
  5. 如多台手持机推广:采用在线批量序列号,在设置页逐台激活,避免每台单独打包 license 文件。

10. 构建、安装与验证命令

# 工程就绪后(本机 JDK17 + Android SDK 34)
./gradlew assembleDebug
adb -s MT95LTFFD00631 install -r app/build/outputs/apk/debug/app-debug.apk
adb logcat | grep -iE "Ocrgve|BDLicense|carverify"   # 看授权/初始化/识别日志
  • 真机信息复核:adb shell wm size → 720x1440;getprop ro.product.cpu.abi → arm64-v8a。
  • 授权排障时以设备指纹 + 授权错误码(第 3.3)为准,不凭猜测下结论。

11. 百度授权办理流程(需要你方操作,开发侧配合)

  1. applicationId 已确认为 com.ectrip.checkincar,授权申请即按此包名,之后不再更改。
  2. 安装第一期 APK 后,在「系统设置」页复制百度设备指纹(Device-id,授权必绑,注意不是硬件 SN)——每台手持机不同;第一期 App 无需授权即可显示该值。当前这台 MT95L 用同版本 SDK 实测指纹为 153DA2A38757D7D7016E76F8EE0AACE367(可直接拿它去控制台申请,正式 App 装好后再复核一次)。
  3. 登录百度智能云控制台 → 车牌识别离线 SDK 授权管理:
    • 单台离线授权:新建单台授权,选择模型「车牌识别」、填设备指纹、绑定包名 → 离线激活 → 下载授权文件(*.ini + 对应 licenseKey)→ 发给开发放入 assets/ 重新出包(一台一证,适合设备少)。
    • 在线序列号(推荐多机):申请单台/批量序列号 → 在 App 设置页输入激活(需联网一次),激活信息本地持久化,之后离线可用。
  4. 开发侧用 authFromFile(key, 文件名, true, algId) 或 initLicenseOnLine(序列号) 完成鉴权,成功后初始化模型。
  5. 注意:App 签名、包名需与申请时一致;正式发布前用 release 签名重新核对授权策略。

12. 风险与对策

风险 影响 对策
暂无百度授权文件/序列号 真机无法真实识别 第一期用模拟引擎跑通全部业务;SDK 物料与封装先行;设置页先取设备指纹便于立即申请授权
授权绑定包名+设备指纹 包名定死、换机失效 包名先评审确认;多设备用在线批量序列号;换机要重新授权(官方规则)
Android 11+ MAC 被系统屏蔽 设置页显示假 MAC 首选读 sysfs(本机实测可读真实 MAC),多级兜底并标注来源
工业设备 CameraX 兼容性 取流异常 CameraX 先行;异常回退 demo 的 Camera1 方案,识别层不变
现场内网 HTTP 明文 9.0+ 默认禁止明文 networkSecurityConfig 仅放行配置的内网地址/端口
模型/so 体积大(约 50MB) APK 偏大 arm64+v7a 都保留(通用手机兼容);后续可按渠道拆 ABI 包
重复初始化/线程错误 模型初始化失败 严格单例、子线程、授权后初始化;错误码中文映射
后台接口未确定 无法真实核销 接口层抽象 + Mock;契约草案先行,平台文档到位后仅替换实现

13. 配置/权限清单(开发时照此实施)

  • AndroidManifest.xml:CAMERA、INTERNET、ACCESS_NETWORK_STATE、READ_EXTERNAL_STORAGE(仅相册调试,API≤28);largeHeap=true;全部 Activity 竖屏;FileProvider(相册选图)。
  • build.gradle(app):minSdk 23 / target+compile 34;ndk { abiFilters 'arm64-v8a','armeabi-v7a' };jniLibs.srcDirs=['libs'] 或使用标准 jniLibs 目录;implementation files('libs/*.jar');CameraX / OkHttp 依赖;compileOptions 1.8。
  • 资源:assets/ocrgve_resource_plate/ 整目录;so 与 jar 不重打包损坏(从 zip 解压后二进制原样拷贝)。

14. 评审结论与遗留事项

已确认(2026-09-25 评审)

  1. ✅ 包名 applicationId:com.ectrip.checkincar,显示名「车辆核销」。
  2. ✅ 第一期默认模拟识别 + 模拟核销,百度 SDK 同步完成集成与封装、暂不启用真实识别(当前无授权文件,这是今天跑通真机全流程的方式)。
  3. ✅ 识别交互:对准自动识别 + 手动拍照兜底 + 结果可人工修正(详见 5.3 五状态设计)。
  4. ✅ 核销员工 ID:系统设置页手工填写并本地保存。
  5. ✅ 本次重点细化方向:界面与流程(第 5 章已补充设计原则、页面跳转、三屏线框图、核销页状态机、异常分支、硬件键与反馈、适配规则)。

仍遗留(不阻塞第一期开发,可后续提供)

  1. 预约平台核销接口是否已有正式文档?没有则先按第 7 章契约模拟,拿到后仅替换 HttpVerifyApi 与 model。
  2. 百度授权由谁在控制台申请、预计何时提供序列号或授权文件(流程见第 11 章)。本机指纹 153DA2A38757D7D7016E76F8EE0AACE367 已实测取得,现在就可以去控制台申请离线授权;官方 demo APK(车牌识别7.5)已临时安装在手持机上用于取指纹,可保留用于授权后自测,也可卸载。
  3. 是否需要核销记录的查询/导出页面?(第一期仅本地落盘 + 主界面今日计数;如需要可补一个记录列表页,支持按日期查看。)
  4. MT95L 实体扫描键是否需要映射为拍照快捷键(第一期先适配音量下键,扫描键拿到真机键值后即可加开关)。

方案已按评审意见完成界面与流程细化。如无其他修改意见,请确认,我将新增 com.ectrip.checkincar 工程并在 MT95L 真机上开发、安装、验证第一期(模拟版)。


15. 第二轮变更(2026-09-25):品牌图标、科技风主界面、在线/离线识别三路由

第一期模拟版真机走查通过后追加。本章与前述章节冲突处,以本章为准。

15.1 应用图标更换为 ECTRIP 鼎标(本轮已实施 ✅)

  • 源文件:工程根目录 ectrip.ico(Windows ICO,含 16/32/48/256 四档,256 主档透明底红色「鼎形 ECTRIP」标,与现场手持机上「智能移动验证系统」图标同源)。
  • 已用 PIL 从 256px 主档生成全套 Android 启动图标(桌面图标 = 安装后显示图标,Android 不区分二者):
    • mipmap-{mdpi,hdpi,xhdpi,xxhdpi,xxxhdpi}/ic_launcher.png(48/72/96/144/192px 方形,白底红鼎)
    • 同目录 ic_launcher_round.png(圆形裁剪版,供部分启动器)
    • 同目录 ic_launcher_foreground.png(108/162/216/324/432px,自适应图标前景,鼎标占画布 60%,保证落在 66dp 安全圈内)
    • mipmap-anydpi-v26/ic_launcher.xml / ic_launcher_round.xml:白底 + 鼎标前景(API26+ 自动裁剪成圆形/圆角方形);旧蓝底车牌矢量前景已删除
  • 背景取白色(红鼎在深蓝底上偏闷;参考 App 的品牌呈现也是红鼎 + 浅蓝徽标轮廓 + 白底)。
  • 已 assembleDebug + 装机(2026-09-25),桌面/任务管理/安装确认界面均显示新图标。
  • 主界面顶部要展示的竖版品牌区另用 256px 透明底 PNG(drawable-nodpi/ic_logo_ectrip.png),实施 15.2 时拷入,不做进 mipmap(mipmap 仅用于启动图标)。

15.2 主界面科技风改版(参考现场鼎游「智能移动验证系统」界面)(本轮已实施 ✅)

实施落地(2026-09-25 真机截图确认):品牌区 200dp 深蓝渐变 + 八边形描边徽标(drawable/bg_octagon.xml,内嵌 drawable-nodpi/ic_logo_ectrip.png 38dp 红鼎)+「鼎 游 信 息 / ECTRIP · SMART VERIFY」+ 25sp 宽字距标题 + 模式胶囊;底 2dp #35C2FF 高光线;内容区四角 L 形青色角标(drawable/decor_corners.xml);主按钮 96dp 蓝渐变 bg_btn_tech_primary.xml、车形图标在左;次按钮 88dp 白底蓝描边齿轮图标;状态行改为三张白底小卡片(引擎/网络/今日核销);左下角版本号 + 设备编号。核销页顶栏用半透明深蓝渐变(bg_brand_header_overlay.xml,压住相机预览),设置页顶栏用实色渐变。

参考照片(Newland MT95 上现有 App)关键元素:顶部红色 ECTRIP 鼎标 +「鼎游信息」、深色大标题、绿色「当前:在线检票」模式状态、整宽蓝色大按钮(字距拉开,硬朗、高对比)、底部「版本号 / 设备编号」。在此基础上融入科技感。

竖屏线框(360×720dp,从上到下)

┌────────────────────────────────────┐
│  ▓▓▓▓▓ 深蓝→藏青渐变标题区 ▓▓▓▓▓▓▓  │ 高 200dp
│      ╭──────────────╮              │
│      │  ◇ 红鼎ECTRIP ◇ │            │ ← 浅蓝八边形徽标(矢量描边)
│      ╰──────────────╯              │   内嵌鼎标 64dp
│          鼎 游 信 息                │ ← 12sp 浅蓝白,字距加宽
│                                    │
│        车 辆 核 销 系 统            │ ← 24sp 白色粗体,字距 6sp
│      ┌────────────────────┐        │
│      │ ● 当前:在线识别    │        │ ← 模式胶囊,在线=荧光绿 #39FF88
│      └────────────────────┘        │   离线=亮蓝 #4FC3F7 / 模拟=暖橙 #FFB74D
├────────────────────────────────────┤
│  (浅灰底 #F4F6FA,微弱科技网格/角标) │
│   ┌──────────────────────────────┐ │
│   │  ▰▰  车 辆 核 销             │ │ ← 主按钮 高 96dp,蓝色渐变
│   └──────────────────────────────┘ │   #1565C0→#0D47A1,白色车形图标
│   ┌──────────────────────────────┐ │   圆角 12dp,按压 0.97 缩放
│   │  ⚙   系 统 设 置             │ │ ← 次按钮 高 88dp,白底蓝描边
│   └──────────────────────────────┘ │
│                                    │
│  引擎:在线   网络:在线  今日核销:1  │ ← 13sp 深灰状态行
├────────────────────────────────────┤
│  版本号 v1.0.0                     │
│  设备编号 MT95LTFFD00631           │ ← 12sp 浅灰,左对齐(同参考App)
└────────────────────────────────────┘

设计要点:

  1. 品牌区:深蓝渐变(#1565C0→#0D2B5C)标题区,内含浅蓝八边形(双切角矩形)描边徽标 + 红鼎 +「鼎 游 信 息」;标题文字白色、宽字距,复刻现场 App 的硬朗工业感。
  2. 科技感装饰(克制、不影响信息):标题区底部一道 2dp 荧光青 #35C2FF 高光线;内容区四角放 L 形青色角标线;背景 #F4F6FA 叠极淡网格(可选,避免视觉噪点)。
  3. 模式胶囊随设置引擎实时变色(onResume 刷新),文字「当前:在线识别 / 离线识别 / 模拟识别」——直接对应现场 App「当前:在线检票」。
  4. 按钮整宽 88%、大字距(4 字之间留空,与参考一致)、主按钮带车形图标;保持本期已验证的点击区域(底部手掌握持区)。
  5. 底部双行信息保留参考 App 习惯:版本号 + 设备编号(SN);其上保留我们自有的「引擎/网络/今日核销」状态行。
  6. 核销页不推翻:顶栏改为同样的深蓝渐变 + 青色高光,车牌框四角 L 线沿用(本来就是科技风),其余 5 状态机不动。
  7. 设置页顶栏同步深蓝渐变;卡片式分组不变。

15.3 识别引擎改三路由:模拟 / 在线 / 离线

设置页「识别引擎」由 2 段改 3 段:模拟识别 | 在线识别 | 离线识别。业务层(VerifyActivity 状态机、结果卡片、核销接口)零改动,只在识别实现层增加一个云端实现:

引擎 实现类 依赖 要授权/密钥吗 识别位置
模拟识别 MockPlateRecognizer(已有,默认) 无 否 本地假数据
在线识别(新增) OnlinePlateRecognizer(新增) 外网 + 百度 AI 开放平台 AK/SK(已提供,预置默认值) 百度云 REST
离线识别 BaiduPlateRecognizer(已有) 离线 SDK 授权 设备指纹授权/序列号(第 11 章) 端侧 so

工厂 PlateRecognizerFactory.java create(Context) 按 AppPrefs 当前引擎返回实例;主界面状态卡片/胶囊文案同步改为三态。旧引擎值迁移:AppPrefs 初始化时把旧 engine=1(百度离线)一次性迁到 2,标记位 engineV2Migrated。

15.3.1 在线识别接口(依据示例 rest2.0ocrv1license_plate.java 与百度 AI 开放平台文档)

  1. 获取 access_token(AK/SK 换 token,有效期 30 天,需缓存):
POST https://aip.baidubce.com/oauth/2.0/token
Content-Type: application/x-www-form-urlencoded
body: grant_type=client_credentials&client_id={APIKey}&client_secret={SecretKey}
→ JSON 取 access_token、expires_in(秒,通常 2592000)
  1. 车牌识别:
POST https://aip.baidubce.com/rest/2.0/ocr/v1/license_plate?access_token=...
Content-Type: application/x-www-form-urlencoded
body: image={JPEG 的 Base64,URLEncode}&multi_detect=false&multi_scale=false
成功:{"words_result":{"color":"blue","number":"粤BD12345","probability":[...]},
       "words_result_num":1,"log_id":...}
失败:{"error_code":110,"error_msg":"Access token invalid"}
  1. 已提供的凭证(控制台应用「闸机检票系统」,ak_sk 鉴权):
项 值 App 内存储
client_id(API Key / AK) iKKGwJRwHxIe9CAAXgafSpyt AppPrefs,作默认值预填,可在设置改
client_secret(Secret Key / SK) FS0byCisfASTaqoaEcRDoye4wiPFuosq 同上,输入框默认密文显示(小眼睛切换)

15.3.2 客户端实现要点(新增 OnlinePlateRecognizer)

  • token 管理(新增 BaiduCloudToken 小类):成功获取后把 token 与到期时间戳存 AppPrefs;剩余有效期 >24h 直接复用,否则重新获取;识别返回 error_code=110/111(token 失效)时自动重取一次并重试一次,仍失败才报错。
  • 图像:沿用现有 JPEG 通路(YUV→NV21→JPEG / 拍照 JPEG),宽压到 ≤1280、质量 80,Base64 后通常 100~300KB;OkHttp FormBody 自动 URLEncode;连接 5s / 读取 10s(云端比本地慢)。
  • 节流:在线请求有 QPS 与计费,自动分析帧间隔由 400ms 放宽到 ≥1000ms,识别中丢帧;命中即停(同离线)。S2 文案显示「云端识别中…」。
  • 自动帧本地预筛(省次数):自动分析帧在上传前先缩到 160 宽灰度,统计平均亮度与梯度能量(OnlinePlateRecognizer.frameWorthSending):亮度 245、边缘能量 手动拍照是有意触发,不做预筛(再暗也只产生一次调用,避免"按了没反应")。
  • 空结果静默:error_code=282102/282103(未检测到车牌/目标识别错误,对空画面百度每次都回这个)与无 words_result 一样按"本帧无车牌"静默处理,不弹 Toast;其余错误(17/18/19 额度·QPS·并发、110/111 鉴权、网络)才经 getLastError() 在界面节流提示(3s 一次)。
  • 结果映射:words_result.number→车牌;color(blue/yellow/green/white/black/yellowgreen)→现有颜色标签(yellowgreen→green 按新能源处理);取 probability 数组最小值为置信度;云返回不带 probability 时 PlateInfo.confidence=-1,结果卡片显示「云识别」。在线引擎不过本地置信阈值。
  • 错误中文映射:网络不可达 →「无法连接百度云,请检查外网/WiFi」;token 获取失败 →「鉴权失败:…」;error_code 17/18/19 →「云端识别今日次数已超限 / QPS 超限 / 并发超限」;216200/216201/216202 图片类错误;其余显示 error_msg(code)。失败后不静默降级,由员工决定切引擎。
  • 在线引擎不校验百度离线授权、不加载 so 模型;LicenseManager 与百度 jar 不参与该路径。
  • 核销接口不变:VerifyApiFactory 仍按「仅模拟引擎 → MockVerifyApi,其余 → HttpVerifyApi」工作;在线识别 + 真实后台即生产组合,无后台时确认核销走失败页(「网络异常: …」,不闪退)。

15.3.3 设置页联动(在现有「识别配置」分组上改)

── 识别配置 ──────────────────────
识别引擎   [模拟识别|在线识别|离线识别]   ← 3 段
置信阈值   [────●────] 0.80              ← 仅「离线识别」显示
── 在线配置(引擎=在线时展开)────────
API Key    [iKKGwJ****afSpyt        👁]  ← 预填默认 AK,可改
Secret Key [********************    👁]  ← 预填默认 SK,密文
鉴权状态   未获取 Token / 有效(至 MM-dd HH:mm)  ← 绿色,实测 30 天
           [ 测试鉴权并获取 Token ]      ← 立即调 oauth 验证 AK/SK
── 模拟配置(引擎=模拟时展开,已有)──
── 离线授权(引擎=离线时展开,已有)──
  • AppPrefs 新增键:engine(0模拟/1在线/2离线,复用原键改值域并迁移)、cloudAk、cloudSk、cloudToken、cloudTokenExpireAt、engineV2Migrated;旧两段选择迁移(旧值 1 → 2)。AK/SK 输入框 TextWatcher 即时保存,改动即 clearCloudToken() 强制重鉴。
  • 在线引擎立即可用的前提:手持机能访问外网(aip.baidubce.com);现场若只有内网,切离线或模拟。
  • 主界面「网络:在线」检测需能区分内网通但外网不通:在线模式启动时可选做一次轻量探测(HEAD baidu.com / DNS),不通时胶囊仍可点但进核销页提示。

15.3.4 安全与计费说明(须知)

  1. AK/SK 内置在 APK 中可被逆向提取,仅适用于专用手持机、受控分发场景;控制台请勿开启无关付费接口,必要时在百度云设置用量告警。更稳妥的后续方案:我方业务后台代发 OCR 请求(App 调自己的核销地址,由后台持密钥),列为二期可选优化。
  2. 在线识别按次计费(车牌识别有每日免费额度,超出按次计费,以百度价目表为准),故自动帧 1s 节流 + 暗/糊帧本地预筛(15.3.2),仅在 S1 自动识别;手动拍照为操作员有意触发,不节流、不预筛,每次只产生一次调用。
  3. 在线引擎不需要第 11 章的设备指纹授权,包名变更不影响在线识别;离线引擎仍受包名+指纹绑定约束。

15.4 实施任务与验收(2026-09-25 实施)

# 任务 状态
A ECTRIP 图标全套 ✅ 已装机(15.1)
B 主界面/顶栏科技风改版:品牌区、渐变、胶囊、角标高光、版本/SN ✅ 真机截图与参考照一致
C OnlinePlateRecognizer + token 缓存/重试 + 颜色/错误映射 + 1s 节流 + 帧预筛 ✅ 新增 OnlinePlateRecognizer.java、BaiduCloudToken.java、PlateRecognizerFactory
D 设置页 3 段引擎、在线配置分组(AK/SK 密文预填、测试鉴权)、联动、prefs 迁移 ✅
E MT95L 真机走查 ✅ ②③④ 已验(详见下);①待对准真实车牌实测

已验记录(2026-09-25,设备 MT95LTFFD00631,WiFi HUAWEI-ECTRIP-5G 可出外网):

  1. 鉴权:设置页「测试鉴权并获取 Token」成功,状态「有效(至10-25 12:57)」;AK/SK 与 oauth/REST 通路在设备上打通。
  2. OCR 接口与凭证:用合成蓝牌「京A12345」调 license_plate 成功返回 number=京A12345、color=blue、probability 数组(各字符 0.9998+),解析契约与 App 实现一致。
  3. 在线取景:核销页提示「请将车牌放入框内(在线识别)」、S2「云端识别中…」;无牌暗画面经帧预筛零云端调用(实测 10s 0 次请求);282103 空识别静默不弹窗。
  4. 三切换联动:模拟→模拟配置组;在线→在线配置组(AK 预填/SK 密文+小眼睛/鉴权状态/测试按钮);离线→阈值滑块+授权激活组;主界面胶囊橙/荧光绿/亮蓝三态与引擎卡片同步。
  5. 不回归:模拟引擎自动识别(粤JS77X4 蓝牌 94%)、结果卡片/重拍/确认流程正常;在线引擎下核销无后台时落失败页,不闪退。

待办(现场补测):引擎切「在线识别」对准真实车牌(蓝/黄/新能源绿牌)验证 1~2 秒回号并走通核销;改错误 SK 后中文失败提示;断网进入不闪退。

验收标准:引擎切「在线识别」、设备连外网时,对准真实车牌 1~2 秒内回号并走通核销(核销仍走 Mock/后台);AK/SK 错误时有明确中文提示;断网时不闪退;模拟与离线现有功能不回归。

16. 第三轮变更(2026-09-27):现场演示打磨(已装机 ✅)

目标:客户现场真机演示前的界面与流程微调。全部改动已构建装机(MT95LTFFD00631)并走查,当前引擎停留在「在线识别」(prefs engine=1)。

16.1 变更清单

# 需求 实施
1 核销失败弹窗也显示车牌 VerifyActivity.java 失败分支 addInfoRow("车牌", currentPlate.number),位于失败原因下方
2 识别结果卡增加「返回」按钮 底部由「重拍 / 确认核销」改为「重拍 / 确认核销 / 返回」三按钮;返回 = finish() 回主页(activity_verify.xml btnBackHome)
3 车牌取景框加大 引导框 300×96dp → 340×116dp → 356×120dp(屏宽 360dp,横向近满屏,竖向按车牌 440:140 比例)。纯视觉,识别仍送整帧
4 首页引擎状态醒目 引擎卡片值 15sp → 17sp 加粗并按引擎着色:在线=#00A854 绿、离线=#0288D1 蓝、模拟=#EF6C00 橙(与顶部胶囊同色);onResume 刷新
5 核销后台固定走模拟、固定成功 VerifyApiFactory.java 不再按识别引擎分流,恒返回 MockVerifyApi;Mock 去掉随机失败/失败原因分支,固定 600ms 返回成功。留 TODO:正式后台就绪后恢复 HttpVerifyApi 分流
6 成功页预约信息补全 展示行:预约号 / 预约时间 / 预约人 / 证件号(脱敏 3502*******1234)* / 预约类型(老人/正常) / 进入日期 / 核销流水(HX+时间戳);模型新增 visitorIdCard / visitorType / verifySerialNo,fromJson 同步解析同名字段
7 「车位」改为「进入日期」 字段契约全局收口:lotName → enterDate(VerifyResult、VerifyRecord 本地历史键、MockVerifyApi、VerifyActivity)。取值规则:data.enterDate 优先,空则取 data.enterTime 兼容;Mock 演示值=当天 yyyy-MM-dd(实测显示 2026-09-27)

16.2 真机走查记录(2026-09-27)

  • 模拟引擎全流程:自动识别 → 三按钮结果卡 → 确认 → 绿色成功页,7 行信息完整(含进入日期当日),2 秒自动连刷正常。
  • 失败弹窗车牌行:Mock 失败路径造数验证过(本轮后失败路径在现场不再触发,因核销恒成功)。
  • 在线引擎取景页文案、引导框尺寸、引擎卡片绿色加粗,截图确认。
  • 百度云 access_token 性质澄清(已向客户说明):应用级凭证,不绑设备,同一 AK/SK 可多台 MT95L 共用,额度/QPS 按 AK 累计;与离线 License 的「指纹+包名一机一授权」不同。Token 约 30 天有效(2026-10-25 前后到期,App 自动刷新)。

16.3 明天(及后续)待办

  1. 现场真实车牌实测:在线识别对准蓝/黄/新能源绿牌,确认 1~2s 回号(合成牌通路已验,真牌尚未现场拍)。
  2. 正式核销接口对接:后台 JSON 就绪后,① VerifyApiFactory 恢复/新增 HttpVerifyApi 分流;② 确认 data.enterDate 真实字段名(不一致则在 VerifyResult.fromJson 的兼容处加映射,只改数据入口,不动界面);③ 预约时间/预约人/证件号/预约类型/核销流水字段名一并对齐。
  3. 非阻塞遗留:离线 License 激活(控制台 SN 流程见第 11 章)、MT95L 扫描键映射、核销记录查询页、断网/错误 SK 的现场演示话术。

16.4 现场操作备忘

  • 设备当前:引擎=在线识别,核销=模拟恒成功(任何引擎下点确认核销都成功),今日核销计数每天 0 点重置。
  • 常用命令:构建 ./gradlew assembleDebug --no-daemon;装机 adb -s MT95LTFFD00631 install -r app/build/outputs/apk/debug/app-debug.apk;启动 adb shell am start -n com.ectrip.checkincar/.ui.main.MainActivity(VerifyActivity/SettingsActivity 未导出,不能直接 am start,需从主页点入)。
  • 坐标(720×1440):主页「车辆核销」约 (360,705)、「系统设置」约 (360,976);设置页引擎三段 y≈1237:模拟 x≈157 / 在线 x≈360 / 离线 x≈563。

17. 第四轮:对接真实核销后台 mqttScan(2026-09-28)

依据:车牌订单核销模拟接口说明.md + 后端源码(只读参考,严禁修改):/Volumes/USERDATA/ideaProjects/wts_vehicle_reservation(分支 tdos-dev)。

17.1 后台事实(源码核实)

  • 入口 ParkingController.mockMqttScan(open-api/.../parking/controller/ParkingController.java)→ CallbackService.processRecognizedPlate → ParkingService.queryVehicleInfo;通道业务模式=3 时调 ParkingOrderHandler.parkingVerify 执行真实订单消费(消费畅游通订单 + 写 OrderCheckRecord 核销流水 + MQTT 开闸)。
  • 请求:GET {baseUrl}/parking/mock/mqttScan?deviceCode=&licensePlate=&colorValue=;colorValue 0蓝/1绿/2黄(LicensePlateColorEnum);车牌明文需 URL 编码。
  • 响应外壳 AjaxData{success:boolean, info:string, data:VehicleInfoVO}(不是 code/msg)。
  • VehicleInfoVO:orderId、orderCode、licencePlate(Base64 明文车牌)、createTime(首次入场时间)、seq、vehicleType(1本地/2工作/3预约/4非预约/5非本地/10免票)、licenseColor(蓝/绿/黄)、carType、pass(isPass→JSON 键 pass)、secondEntry、entryTimes。
  • 判定规则(ParkingService 427-462 行):模式 1 只查询;模式 2 查询并放行;模式 3 + vehicleType=3 + pass=true 才真实核销。
  • 已核销预约车再扫:status=1 → pass=false、secondEntry=true、entryTimes=N;免票车 vehicleType=10 且 pass=false;非预约 4/5 直接 return(pass=false,不开闸不核销,但仍写一条 VehicleChannelRecord 通道流水)。
  • 3 秒同设备同车牌 Redis 去重:success=false, info="3秒内重复车牌识别消息已忽略"。
  • 坑:parkingVerify 抛异常被 processBusinessMode catch 仅打印日志,HTTP 仍 success=true —— App 必须按 data 的 pass/vehicleType/secondEntry 组合判定,不能只看 success。
  • 副作用警告:接口名为 mock 实为正式链路,真实预约车牌测一次即消费一单。

17.2 实施方案(用户已确认三项决策)

  1. 核销方式开关:设置页「核销配置」组新增分段「模拟核销|后台核销」(AppPrefs verifyMode 0/1,默认 0);VerifyApiFactory.create() 按此分流,默认模拟防误核销。识别引擎三路由不受影响。
  2. 设备编号:AppPrefs 新增 deviceCode,默认 DEVICE001,设置页可改(即时保存);与手持机 SN 无关,须为后台已配置(device.status=1)且绑定模式 3 通道的编号。
  3. HttpVerifyApi 重写:
    • GET,query 参数 deviceCode / licensePlate(URLEncoder UTF-8) / colorValue(blue→0、green→1、yellow→2,未知→0);连 5s 读 8s。
    • 解析 AjaxData:success=false → VerifyResult.fail(info)(原样展示后台中文原因)。
    • 解析 data:orderCode→预约号、orderId、Base64 解码 licencePlate、vehicleType→中文类型、licenseColor、createTime(兼容字符串/毫秒)、seq、pass、secondEntry、entryTimes。
    • 成功/失败判定只看 AjaxData.success:false → 红色失败页,原样显示 info(设备未配置/已禁用/3秒重复等);true → 成功页,data 九项字段全部显示(null 显示“-”):订单号 orderCode、车牌号(licencePlate Base64 解码)、首次入场时间 createTime、入场序号 seq、车辆类型(编码+中文)、车牌颜色、是否允许通行 pass(是/否)、是否重复入场 secondEntry(是/否)、当前入场次数 entryTimes。标题:预约车(type=3)=核销成功,其余=处理成功,副标题显示 info 原文。
  4. 成功页分行展示:真实模式固定九行(见上,全部显示);模拟模式保持现有 7 行演示信息(Mock 继续填模拟值)。
  5. 红色失败页保留后台 info 原文 + 车牌行。

17.3 联调与测试

  • 安全通路测试(不消费订单):用无预约的假车牌调后台 → 应得 success=true、vehicleType=4、pass=false,验证网络/解析/判定(副作用仅一条通道流水日志)。
  • 真实核销测试前提:后台提供绑定模式 3 通道的有效 deviceCode + 当天未核销预约车牌(一次性);核销后第二次扫同牌应拦截为重复入场。
  • 回归:核销方式切回模拟时,全流程与第 16 章一致。

2026-09-28 现场网络勘察结果:192.168.50.1:8080 当前返回 404 / Server: Embedded Server/1.0.0(网关或道闸设备自带服务,非本系统 Spring 后台);192.168.50.1-254 全段 8080 扫描无其他服务在线,手持机(.70)正常。结论:后台服务今日未部署/未启动在本网段,真实链路待后台上线后联调;App 侧代码已就绪,后台不通时红色失败页显示「后台返回 HTTP 404 / 网络异常」,不闪退。另注:open-api 模块 resources 无 application.yml(配置走外部配置中心),端口与 context-path 以部署环境为准,当前按文档直连 8080 根路径。

17.4 待后台确认/遗留

  • DEVICE001 是否真实存在且绑定模式 3 通道;不行则由后台管理员提供 deviceCode(设置页可直接改,无需发版)。
  • 后台不返回预约人/证件/预约类型/核销流水,成功页暂不展示;后续后台加字段后仅在数据入口加映射。
  • parkingVerify 失败对调用方不可见(HTTP 仍成功)——如需精确核销结果,需后台后续在 VehicleInfoVO 增加核销结果字段或提供核销流水查询接口。

18. 第六轮:核销确认机制 + 预约查询接口(2026-10-27)

背景:前几轮识别车牌后直接提交核销,无信息确认环节。用户要求增加确认弹窗,显示车牌号、驾驶员、预约日期、订单号等信息,确认后才执行核销。 核心问题:原核销接口 /tdos/consumeOrder_/appedit.do 只返回 {success, info} 两字段,不含任何预约详情,无法在核销前展示。 解决方案:后台新增免登录预约查询接口,App 侧改为两步流程(先查后核销)。

18.1 后台新增查询接口(已上线)

请求地址:

GET /tdos/consumeOrder_/appquery.do
POST /tdos/consumeOrder_/appquery.do

请求参数:

参数 必填 类型 说明
licensePlate 是 String Base64 编码后的车牌号(UTF-8),GET 时需再 URL 编码
callback 否 String JSONP 回调函数名

Base64 编码规则:与核销接口 appedit.do 完全一致——Base64.encodeToString(plate.getBytes("UTF-8"), Base64.NO_WRAP),再用 URLEncoder.encode(encoded, "UTF-8") 处理 + / = 特殊字符。

成功响应(查到有效预约):

{
  "success": true,
  "info": "查询成功",
  "data": {
    "licensePlate": "粤A12345",
    "orderId": 10001,
    "orderCode": "202610270001",
    "driverName": "张三",
    "driverPhone": "13800138000",
    "useStartDate": "2026-10-27",
    "parkingAddress": "停车场A区",
    "status": 0,
    "enterTime": null
  }
}

成功响应(无有效预约):

{
  "success": false,
  "info": "未查询到当天有效车牌预约信息",
  "data": {
    "licensePlate": "粤A12345"
  }
}

错误响应:

{"success": false, "info": "车牌号Base64编码格式错误"}

返回字段说明:

字段 类型 说明
licensePlate String 解码后的车牌号
orderId Integer 订单 ID
orderCode String 订单编号
driverName String 驾驶员姓名
driverPhone String 驾驶员手机号
useStartDate String 预约使用日期
parkingAddress String 停车地点
status Integer 0=待入场,1=已入场核销
enterTime String/null 入场核销时间,未核销时为 null

接口特点:

  • 免登录(与 appedit.do 同属 /consumeOrder_/ 路径,Shiro 白名单已配置)
  • 只查不消费订单,不会核销
  • Spring MVC .do 后缀拦截规则生效(路径必须带 .do)
  • 生产地址前缀:https://wtspw.wtsykfwzx.com:8145/tdos
  • 开发测试地址前缀:https://wtstest.jingqu.cn:9143/tdos

18.2 App 侧新增文件

文件 说明
model/QueryResult.java 查询结果模型:ok(网络层) + success(业务层) + info + data 各字段
net/QueryApi.java 查询接口定义:query(String plateNo) 返回 QueryResult
net/HttpQueryApi.java 真实后台实现:GET {baseUrl}/tdos/consumeOrder_/appquery.do,5s 超时
net/MockQueryApi.java 模拟实现:固定 300ms 耗时,80% 概率返回模拟预约详情
res/layout/dialog_verify_confirm.xml 确认弹窗自定义布局(车牌号/状态/驾驶员/手机号/订单号/预约日期/停车地点 + 取消/确认核销按钮)
res/drawable/bg_btn_outline.xml 取消按钮背景(描边样式)

18.3 已有文件改动

文件 改动
net/VerifyApiFactory.java 新增 createQuery() 方法,按 verifyMode 分流真实/模拟查询
ui/verify/VerifyActivity.java ① onRecognized() 改为先调 queryAndShowConfirm() 再弹确认窗;② 取消自动核销(AUTO_VERIFY_ON 选项保留但不再自动提交);③ 新增 showConfirmDialog()(有预约)和 showNoReservationDialog()(无预约降级);④ 新增 confirmDialog 和 queryPending 状态变量;⑤ backToScanning() 和 onDestroy() 中清理弹窗状态
core/prefs/AppPrefs.java ① 核销方式缺省值 VERIFY_MOCK → VERIFY_SERVER(新安装默认后台核销);② 识别引擎缺省值 ENGINE_MOCK → ENGINE_ONLINE(新安装默认在线识别);③ migrateEngineV2() 中旧默认值同步更新

18.4 核销流程(改造后)

拍照/识别车牌
    │
    ▼
onRecognized(PlateInfo)
    │
    ├─ 取消原 AUTO_VERIFY_ON 自动提交逻辑
    │
    ▼
workExecutor.execute: QueryApi.query(车牌)
    │
    ┌──────────────────────────────────┐
    │                                  │
 查询成功 + success=true           查询失败 或 success=false
 (有有效预约)                    (无预约 / 网络异常)
    │                                  │
    ▼                                  ▼
 弹确认弹窗                      Toast + 双按钮对话框
 ┌─────────────────────┐         ┌─────────────────┐
 │ 🚗 粤A12345        │         │ 未查到预约信息   │
 │ 📌 待入场/已入场    │         │ 是否继续核销?   │
 │ 👤 张三            │         │ [继续核销][取消] │
 │ 📱 138****8000     │         └─────────────────┘
 │ 📋 202610270001    │                  │
 │ 📅 2026-10-27      │           ┌──────┴──────┐
 │ 🅿️ A区停车场       │           │             │
 ├─────────────────────┤     submitVerify   backToScanning
 │ [取消]  [确认核销]  │
 └─────────────────────┘
       │              │
   backToScanning  submitVerify(currentPlate)
                          │
                          ▼
                    调 HttpVerifyApi.submit()
                    POST /tdos/consumeOrder_/appedit.do
                          │
                          ▼
                    展示核销结果页

18.5 边界处理

场景 处理
查询成功 + status=0(待入场) 正常弹窗,确认核销走原流程
查询成功 + status=1(已入场) 弹窗状态标签红色「已入场」,点「确认核销」后二次确认
查询成功 + 无预约(success=false) 提示"未查到预约信息",双按钮:继续核销 / 取消
查询超时 / 网络异常 降级为"未查到预约信息"处理,不阻塞核销
模拟核销模式 MockQueryApi 返回模拟预约详情,流程与真实模式完全相同
手动输入车牌 同样走查询+确认弹窗流程,与识别的车牌无区别
查询中返回扫描态 queryPending 标志位防止重复提交,用户返回后忽略查询结果
确认弹窗中按返回键 setCancelable(true),取消弹窗回到扫描态

18.6 接口路径汇总

功能 方法 路径 说明
预约查询 GET/POST /tdos/consumeOrder_/appquery.do 免登录,只查不核销,Base64+URL 编码车牌
核销提交 POST /tdos/consumeOrder_/appedit.do 免登录,真实消费订单,Base64 编码车牌
生产地址前缀 — https://wtspw.wtsykfwzx.com:8145/tdos .do 后缀由路径自行拼接
开发测试地址前缀 — https://wtstest.jingqu.cn:9143/tdos 同上

18.7 关键代码参考

// HttpQueryApi.java — 查询接口实现
private static final String PATH = "/tdos/consumeOrder_/appquery.do";

public QueryResult query(String plateNo) throws Exception {
    String base = AppPrefs.getBaseUrl();  // 自动去除末尾 /
    String encodedPlate = Base64.encodeToString(
            plateNo.getBytes("UTF-8"), Base64.NO_WRAP);
    String url = base + PATH + "?licensePlate="
            + URLEncoder.encode(encodedPlate, "UTF-8");
    // GET 请求,5s 超时
    // 解析 JSON → QueryResult.fromServerJson(body)
}
// VerifyActivity.java — 识别成功后先查询再弹窗
private void onRecognized(PlateInfo info) {
    currentPlate = info;
    queryAndShowConfirm(info);  // 替代原 autoVerify 逻辑
}

private void queryAndShowConfirm(PlateInfo plate) {
    workExecutor.execute(() -> {
        QueryResult result = VerifyApiFactory.createQuery().query(plate.number);
        runOnUiThread(() -> {
            if (result.ok && result.success) {
                showConfirmDialog(result);       // 有预约 → 弹确认窗
            } else {
                showNoReservationDialog(result.info);  // 无预约 → 降级提示
            }
        });
    });
}

18.8 2026-10-27 现场测试结果

  • 缺省值调整:新安装 App 默认为「在线识别 + 后台核销 + 生产正式地址」
  • 查询接口 .do 后缀修正(初版漏写导致 404)
  • Base64 编码 NO_WRAP + URL 编码处理 + / = 特殊字符
  • 完整流程:拍照识别 → 查询 → 弹确认窗 → 确认核销 → 展示结果,测试通过