Skip to content

JiduClient 是整个 OTX 代理插件中与集度云端服务进行 HTTPS 双向认证通信的核心枢纽类。它以单例模式运行,封装了 libcurl HTTP 客户端能力,并通过 host 路由 + 请求类型分发 + 预处理/后处理 的三段式调用框架,统一管理四大服务网关(EOL、PKI、Diag、Config)的所有 API 请求。其核心价值在于:上层 OTX 诊断脚本只需传递一个整数类型的接口编号(iType)和 JSON 入参,即可透明地完成环境路由、证书加载、身份认证签名、请求构造、响应解析与数据中心缓存的全流程操作。


类继承体系与在系统中的定位

JiduClient 继承自 HttpClient,后者是对 libcurl 的 C++ 封装,提供 post()get() 两个基础原子操作。JiduClient 在这一层之上叠加了三项关键能力:多服务网关路由双向 TLS 证书管理、以及基于 ECDSA 签名的 API 代理认证

mermaid
classDiagram
    class HttpClient {
        -string clientCert
        -string clientKey
        -string token
        +post(url, params, response) bool
        +get(url, response) bool
        +setClientCert(cert) bool
        +setClientKey(key) bool
        +setToken(token) bool
    }
    class JiduClient {
        -static JiduClient* instance
        +string deviceSn
        +string eolHost
        +string pkiHost
        +string cfgHost
        +string diagHost
        +callServer(iType, inParams, outBuffer, outBufferSize) int
        +loadEnvirment() bool
        +loadParam() bool
        +init() void
        -gen_pem(p12Path, passwd, cert, key) void
        -parsed(response) int
        -getAccessToken(token) bool
        -callServer_preHandler(iType, inParams) int
        -callServer_affterHandler(iType, inParams, response) int
        -desensitize(obj) void
        -add_powerType(inParams) void
        -add_carType(inParams) void
    }
    HttpClient <|-- JiduClient : 继承
    JiduClient ..> EnvConfig : 获取环境配置
    JiduClient ..> DataCenter : 读写缓存
    JiduClient ..> vtable : 路由表查询

Sources: jidu_client.h, HttpClient.h


四大服务网关与 host 路由架构

JiduClient 对接集度后端的四个独立服务网关,每个网关有不同的认证方式与 URL 模式。所有 API 端点的路由信息集中定义在 vtable(位于 jidu_uri.h)中:

cpp
typedef struct invoke { int sn = 0; string method; string host; string url; } invoke_t;
invoke_t vtable[100] = { ... };

Sources: jidu_uri.h

路由表通过数组索引(即 iType)将每个接口映射到 {HTTP方法, 目标host, URI路径} 三元组。四个 host 的值及其通信策略如下表:

Host 标识服务网关典型用途认证方式URL 构建模式
"eol"EOL 产线服务车辆订单、CCP 数据、TPMS、工位结果无需 token,直连 HTTPSeolHost + url
"pki"PKI 证书服务安全常量查询、证书签发、密钥管理双向 TLS 证书认证(PEM 客户端证书 + 私钥)pkiHost + url
"diag"诊断服务(售后)车辆信息、ECU 列表、证书操作、OTA 升级Bearer Token + ECDSA 签名diagHost + "/api/business/apiProxy/invoke?lang=" + language
"cfg"配置服务VSP 配置上传与状态查询Bearer Token + ECDSA 签名cfgHost + "/business/apiProxy/invoke"

这种按 host 分流的架构使得 callServer() 的核心逻辑成为一个 if-else 路由决策链:先查 vtable 获取目标 host,再根据 host 类型执行完全不同的认证与请求构造逻辑。

Sources: jidu_client.cpp, jidu_uri.h


callServer() 请求处理流程

callServer(int iType, string inParams, char* outBuffer, unsigned int outBufferSize) 是 JiduClient 的唯一公开入口方法。其完整处理流程如下图所示:

mermaid
flowchart TD
    A["callServer(iType, inParams, outBuffer, outBufferSize)"] --> B["获取互斥锁 std::lock_guard"]
    B --> C["清理上次同 iType 响应缓存"]
    C --> D["init() 检查环境是否变更"]
    D --> E["记录日志: callServer.interface"]
    E --> F["callServer_preHandler() 前置处理"]
    F --> G{"vtable[iType].host ?"}
    G -->|"diag / cfg"| H["getAccessToken() 获取 Bearer Token"]
    H --> I{"Token 有效?"}
    I -->|"否"| ERR["返回 -1"]
    I -->|"是"| J["构造 API Proxy JSON 包体"]
    J --> K["ECDSA 签名: ECDSA_sign(inParams+url+method+serviceName+timestamp, keyPem)"]
    K --> L["HttpClient::post() 发送请求"]
    G -->|"eol"| M{"IsIntegrationTesting() ?"}
    M -->|"是"| N["加载本地 Mock 数据 (JSON/XML)"]
    M -->|"否"| O["HttpClient::get() 或 post()"]
    G -->|"pki"| P["设置客户端 TLS 证书: setClientCert + setClientKey"]
    P --> Q["HttpClient::get() 或 post()"]
    L --> R["记录请求元信息到 DataCenter"]
    N --> R
    O --> R
    Q --> R
    R --> S{"response 是否为空?"}
    S -->|"是"| RERR["设置 errMsg, 返回 -2"]
    S -->|"否"| T["parsed() 解析 code 字段"]
    T --> U{"code == 0 或 200?"}
    U -->|"否"| V["提取 message/msg 到 errMsg, 返回 -2"]
    U -->|"是"| W["日志打印 (PKI 接口脱敏)"]
    W --> X["callServer_affterHandler() 后置处理"]
    X --> Y{"后置处理结果 < 0?"}
    Y -->|"是"| YERR["设置 errMsg, 返回错误码"]
    Y -->|"否"| Z["memcpy 到 outBuffer, 返回 response.length()"]

这个流程中有几个关键设计决策值得注意:

  1. 全局互斥锁static std::mutex mLock 确保所有 callServer() 调用串行化执行,防止并发请求导致 DataCenter 缓存状态不一致。jidu_client.cpp

  2. 环境热检测:每次调用都会执行 init(),其内部通过 EnvConfig::checkEnvironment() 判断运行环境是否变更,若变更则重新加载证书和主机配置。jidu_client.cpp

  3. 集成测试 Mock 机制:当 IsIntegrationTesting() 为 true 时(通过 XML 配置文件 Diagnosis.Entry.exe.config 中的 IsIntegrationTesting 键判断),EOL 请求不会真实发出,而是读取本地 EolServerMockData\ 目录下的 JSON 或 XML 文件作为模拟响应。这为离线测试提供了完整的 fake server 能力。jidu_client.cpp

Sources: jidu_client.cpp


双向 TLS 认证:PKI 网关的客户端证书机制

PKI(公钥基础设施)服务是集度安全体系中最敏感的网关,处理车辆证书签发、密钥查询等操作。与 PKI 通信时,JiduClient 需要同时提供 客户端证书(client certificate)客户端私钥(client private key),实现完整的双向 TLS 认证。

证书加载链路

mermaid
flowchart LR
    A["pver 文件 (加密的JSON)"] -->|"loadParam()"| B["解密 certPwd"]
    B -->|"DES-CBC 解密"| C["获得明文密码"]
    C -->|"gen_pem()"| D["parse_cert_p12()"]
    D -->|"从 P12 提取"| E["certPem (X.509)"]
    D -->|"从 P12 提取"| F["keyPem (私钥)"]
    E -->|"setClientCert()"| G["curl blob_clientCert"]
    F -->|"setClientKey()"| H["curl blob_clientKey"]

loadParam() 从应用程序目录下的 pver 文件读取设备身份信息。该文件是加密的 JSON 结构,包含 deviceID(设备序列号)和 certPwd(证书密码,使用 DES-CBC 算法加密)。解密使用的硬编码密钥为 "siuhuali"。解密后的明文密码配合 certName 字段定位到 .p12 证书文件,再通过 parse_cert_p12() 函数解析出 PEM 格式的证书和私钥。

cpp
// loadParam() 中的核心解密逻辑
int bLen = base64_decode(tempString, (int)strlen(tempString), data, 64);
int rLen = des_cbc_pkcs5_decrypt(data, bLen, (char*)"siuhuali", certPwd, 64);

Sources: jidu_client.cpp, algorithm.h

证书使用时机

PKI 认证证书仅在 host 为 "pki" 时设置。在 callServer() 的 pki 分支中:

cpp
setClientCert(this->certPem);
setClientKey(this->keyPem);

这会在底层 libcurl 中启用双向 TLS —— 服务端会验证客户端证书是否由受信任的 CA 签发,同时客户端也验证服务端证书链。值得注意的是,HttpClient 中将 CURLOPT_SSL_VERIFYPEERCURLOPT_SSL_VERIFYHOST 均设为 false,意味着客户端不验证服务端证书(这与"双向认证"中服务端验证客户端的方向一致,但客户端验证服务端被跳过)。

Sources: jidu_client.cpp, HttpClient.cpp


ECDSA 签名认证:Diag/Cfg 网关的 API Proxy 机制

诊断服务(diag)和配置服务(cfg)使用另一种认证模式 —— API 代理 + ECDSA 数字签名。请求通过统一的 /api/business/apiProxy/invoke 端点转发,原始请求参数被包装在一个 JSON 代理信封中:

json
{
    "serviceName": "diagnostic-adapter-service",
    "method": "POST",                          // 原始 HTTP 方法
    "contentType": "application/json",
    "invoke": "/uds/device/fetch/vehicle/info", // 原始 URI
    "content": "{...}",                         // 原始请求体 JSON
    "timestamp": "1712345678",
    "signature": "MEUCIQD...==",               // ECDSA 签名
    "serialNo": "DEVICE_SN_001"                // 设备序列号
}

签名的生成逻辑是:将 content + invoke + method + serviceName + timestamp 五个字段拼接为明文字符串,然后使用设备私钥 (this->keyPem) 进行 ECDSA 签名。服务端用对应的公钥验签,确保请求来源合法且未被篡改。

cpp
string plainMsg = inParams + vtable[iType].url + vtable[iType].method + serviceName + timeStamp;
ecdsa_signature(plainMsg, this->keyPem, signature);

同时,请求头中附带 Authorization: Bearer <token> 字段。Token 来源于诊断仪本地的 profile\profile.json 文件(用户登录后持久化的用户令牌),通过 getAccessToken() 方法读取。

Sources: jidu_client.cpp, jidu_client.cpp


预处理与后处理:请求生命周期钩子

callServer_preHandlercallServer_affterHandler 构成了 JiduClient 的 模板方法模式:在请求发送前清理目标缓存,在响应返回后将数据写入 DataCenter。

callServer_preHandler:缓存清理

在请求发出前,根据 iType 清空 DataCenter 中对应的静态字段,确保旧数据不会污染新结果。典型映射关系:

iType清理的 DataCenter 字段说明
4m_VehicleOrderInfo.strTpmsId胎压 ID
6, 59m_EcuSafetyConstantECU 安全常量映射表
19, 58m_EolKey (7 个字段)EOL 全密钥
20, 56m_EcuCertInfo (vid, ecuCa, ecuCertInfo)ECU 证书信息
21, 55m_aesKeyAES 密钥映射表
26m_VehicleOrderInfo (swpn, ccp, vehicleType, ecuList)车辆订单信息
27m_OtaCert (caCert, clientCert)OTA 证书
79m_vecEbocInfoEBOC 选装件列表
88m_VehicleOrderInfo.vecListEcuNew新版 ECU 列表

Sources: jidu_client.cpp

callServer_affterHandler:响应解析与缓存写入

后置处理器负责将服务器返回的 JSON 数据解析后写入 DataCenter 对应的静态字段。这是一个大型 switch-case 结构(约 500 行),覆盖了 20+ 个接口类型的特定解析逻辑。例如,iType=26(工厂环境获取车辆订单信息)会解析 vin、ccpCode、vehicleType、eboc、baseLine、displayVersion、swpn、vehicleEnvironment 等字段,并遍历 ecuList 数组构建 JD_VEHICLE_ORDER_t 结构体写入 DataCenter。

Sources: jidu_client.cpp


环境与工厂的多维配置矩阵

loadEnvirment() 方法实现了 环境 × 工厂 的二维配置矩阵。环境分为 Dev、Test、Staging、Prod 四档(通过 App_Env_t 枚举定义),工厂分为 PMA 和 DMA 两种。每种组合对应独立的证书目录和服务主机域名。

mermaid
graph TB
    subgraph "环境维度 (App_Env_t)"
        DEV["Dev (1)"]
        TEST["Test (2)"]
        STAGING["Staging (3)"]
        PROD["Prod (4)"]
    end
    subgraph "工厂维度"
        PMA["PMA 工厂"]
        DMA["DMA 工厂"]
        AFTER["售后/工程 (无工厂)"]
    end
    DEV --> CERT_DEV["pkiCertificate/{Factory}/Dev/"]
    TEST --> CERT_TEST["pkiCertificate/{Factory}/Test/"]
    STAGING --> CERT_STAG["pkiCertificate/{Factory}/Staging/"]
    PROD --> CERT_PROD["pkiCertificate/{Factory}/Prod/"]

此外,loadEnvirment() 还支持通过本地 WebApi.json 文件覆盖默认的 host 配置。该 JSON 文件按工厂(PMA/DMA)和环境分组,允许运维人员在不重新编译的情况下修改服务端点 URL。特殊的 staging_2_prod() 配置项允许在 Staging 环境下将 EOL 请求路由到生产环境(用于日志上传场景)。

环境默认值为 App_Env_Staging,存储在 EnvConfig 单例中。

Sources: jidu_client.cpp, jidu_macro.h


parsed():响应码提取与嵌套解包

parsed() 方法负责解析 HTTP 响应中的业务状态码。集度服务端 API 的通用响应格式为 {"code": 0, "data": {...}, "message": "..."}parsed() 同时兼容 code 为字符串或数字的两种情况。当 code == 0code == 200 时,如果 data 内部嵌套了另一个 {"code":..., "data":...} 结构,则会递归解析直到提取出最内层的 data 对象。当 code 非成功状态时,提取 messagemsg 字段存入 DataCenter::errMsg

Sources: jidu_client.cpp


辅助方法详解

add_powerType()add_carType()

这两个方法用于在 PKI 和 diag 请求的入参 JSON 中自动注入车辆属性。powerType 来自 DataCenter::m_vehicleInfo.powerType(0=400V 平台,2=800V 平台,其中 2 会被归一化为 1),carType 来自 DataCenter::m_vehicleInfo.carTypeId(1=MarsOne,2=Venus)。它们同时兼容 JSON 格式和 URL 查询字符串格式的入参。

Sources: jidu_client.cpp

desensitize()

对 PKI 接口返回的响应 JSON 进行递归脱敏处理:遍历 JSON 树中所有字符串值,调用 MyStringUtils::desensitize() 将敏感数据(如密钥、证书内容)替换为脱敏标识。脱敏仅在日志打印时生效(Tracer::json),不影响实际写入 DataCenter 和 outBuffer 的原始数据。

Sources: jidu_client.cpp

staging_2_prod() / IsIntegrationTesting() / getMockDataFormat()

这三个文件级函数通过解析 Diagnosis.Entry.exe.config XML 配置文件来控制调试行为:

  • staging_2_prod():在 Staging 环境下是否将日志上传到 Prod 环境
  • IsIntegrationTesting():是否启用集成测试 Mock 模式
  • getMockDataFormat():Mock 数据文件格式(jsonxml

Sources: jidu_client.cpp


与其他模块的协作关系

JiduClient 处于整个 OTX 代理插件的通信中枢位置,通过以下路径与周边模块交互:

协作模块交互方式数据流向
EnvConfiggetInstance()->getEnvironment() / getAppPath() / checkEnvironment()读取环境配置
DataCenter通过 callServer_affterHandler 写入静态字段JiduClient → DataCenter(写入)
HttpClient继承关系,调用 post() / get() / setClientCert() / setClientKey()JiduClient → HttpClient(委托)
加密算法集调用 ecdsa_signature() / base64_decode() / des_cbc_pkcs5_decrypt() / parse_cert_p12()JiduClient → algorithm(调用)
CVehicleConfiggetInstance()->getDiagnosisDir() 获取 Token 文件路径读取配置目录
Tracer日志输出(logger / error / warn / debug / json / hex)JiduClient → Tracer(写入日志)
OTX 脚本层(通过 jidu.hcallServer() C 函数直接委托到 JiduClient::getInstance()->callServer()OTX 脚本 → JiduClient(入口)

Sources: jidu.cpp, jidu_client.cpp


补充阅读建议

在理解 JiduClient 的通信架构后,建议按以下顺序深入阅读相关模块:

  1. HttpClient:基于 libcurl 的 HTTP 请求基类与证书管理 — 理解底层 HTTP 传输、TLS 证书设置、以及请求日志脱敏的具体实现
  2. callServer 接口设计:请求类型路由、预处理/后处理与脱敏机制 — 深入剖析 vtable 路由表与所有 95 个接口类型的完整清单
  3. DataCenter:车辆订单、安全常量、证书信息的统一缓存中心 — 理解 JiduClient 写入数据的目的地及缓存结构
  4. EnvConfig:多环境(Dev/Test/Staging/Prod)配置切换机制 — 理解 loadEnvirment() 所依赖的环境配置来源