Skip to content

callServer 是 OTX 代理插件与集度云端服务通信的核心枢纽——所有车辆数据查询、安全常量获取、证书操作、刷写文件列表拉取等操作均通过此单一接口完成。其设计围绕三个正交维度展开:请求类型路由(按 host 分发到不同服务网关)、预处理/后处理管道(请求前清缓存、响应后写入 DataCenter)、以及 脱敏机制(PKI 敏感数据在日志中自动掩码)。理解 callServer 即理解了整个插件的"数据神经系统"。

Sources: jidu_client.h jidu.h

双层 API 架构

callServer 以"一个接口,两副面孔"的方式呈现给不同调用方。面向 OTX 运行时,它是一个纯 C 函数;面向内部 C++ 调用,它是 JiduClient 单例的方法。

层级声明位置签名角色
C API(外部)jidu.h:16OTX_API int callServer(int iType, const char* inParams, char* outBuffer, uint32_t outBufferSize)OTX 序列直接调用,参数通过扁平的字符缓冲区传递
C++ 实现(内部)jidu_client.h:28int JiduClient::callServer(int iType, string inParams, char* outBuffer, unsigned int outBufferSize)内部以 std::string 处理参数,共享同一出口缓冲区

调用链路为:OTX 脚本 → C API(位于 jidu.cpp)→ JiduClient::getInstance()->callServer() → HTTP 请求 → 响应解析 → DataCenter 缓存。C API 层仅做薄转发,真正的业务逻辑集中于 JiduClient。

Sources: jidu_client.h jidu.h

请求类型路由:vtable 与四网关分发

所有 100 个请求类型(iType 0~95)在一个静态路由表 vtable 中注册,每个条目定义了调用的四要素:HTTP 方法目标网关(host)API 路径。这张表构成了整个系统的"路由地图"。

mermaid
graph TD
    A["callServer(iType, inParams)"] --> B{"vtable[iType].host"}
    B -->|"eol"| C["EOL Server<br/>工厂产线服务"]
    B -->|"pki"| D["PKI Server<br/>安全证书/密钥服务"]
    B -->|"diag"| E["Diag Service<br/>诊断适配服务"]
    B -->|"cfg"| F["Config Service<br/>配置上传服务"]
    
    C --> C1["直接 GET/POST<br/>集成测试走 Mock 数据"]
    D --> D1["双向 TLS 认证<br/>加载 clientCert + clientKey"]
    E --> E1["API Proxy 模式<br/>ECDSA 签名 + Token 认证"]
    F --> F1["API Proxy 模式<br/>异步模式 (asynchronous=true)"]

路由表定义于 jidu_uri.h,每种 host 对应完全不同的认证与请求封装策略:

Host典型 iType服务角色认证方式请求封装
eol1~4, 26, 28, 31~33工厂产线 EOL 服务器无额外认证 / Mock 数据兜底直接 GET/POST,query string 或 JSON body
pki5~27PKI 第三方服务(安全常量、证书签发、AES 密钥)双向 TLS(P12 证书 → PEM 私钥+证书)直接 POST,自动注入 powerType
diag51~95诊断适配服务(售后诊断、软件版本查询、刷写流程)Bearer Token + ECDSA 签名API Proxy 封装(serviceName + invoke + signature)
cfg29~30配置管理服务(数据上传、渠道软件状态查询)Bearer TokenAPI Proxy 封装(异步模式)

Sources: jidu_uri.h jidu_client.cpp

EOL 路由:产线直连与 Mock 降级

vtable[iType].host == "eol" 时,构造完整的 EOL 服务地址后,会先检查当前是否处于集成测试模式IsIntegrationTesting())。若为集成测试,不从网络获取真实数据,转而从本地 EolServerMockData\ 目录读取预置的 JSON 或 XML 文件——这是离线调试的关键设计。Mock 文件路径遵循规则:将 URL 中的 / 替换为 _,拼接 .json.xml 后缀。XML Mock 数据会通过 mockDataXml2Json() 自动转换为 JSON。

非测试模式下,根据 HTTP 方法执行标准 GET/POST 请求。GET 请求将 inParams 拼入 query string,POST 请求将 inParams 作为 body 发送。

Sources: jidu_client.cpp

PKI 路由:双向 TLS 与 powerType 注入

PKI 路由服务于所有安全相关操作——查询 ECU 安全常量、签发 EOL 密钥、创建虚拟证书等。关键特征:

  1. 双向 TLS 认证:通过 setClientCert(this->certPem)setClientKey(this->keyPem) 设置客户端证书和私钥——这些 PEM 材料在 loadParam() 中从受 DES-CBC 加密保护的 P12 文件解析而来。
  2. powerType 自动注入:POST 请求在发送前会调用 add_powerType(inParams),从 DataCenter::m_vehicleInfo.powerType 读取车辆动力类型并注入到请求 JSON 中。若 powerType 为 0x02(某个特定动力类型),会归一化为 0x01

Sources: jidu_client.cpp jidu_client.cpp

Diag/CFG 路由:API Proxy 代理模式

Diag 和 CFG 服务共用一套 API Proxy 代理模式——不同于 EOL/PKI 的直接调用,这里将所有请求封装在一个代理 JSON 中统一发送到 /api/business/apiProxy/invoke 端点:

json
{
  "serviceName": "diagnostic-adapter-service",
  "method": "POST",
  "contentType": "application/json",
  "invoke": "/uds/device/fetch/vehicle/info",
  "content": "<原始 inParams>",
  "timestamp": "1700000000",
  "signature": "<ECDSA 签名>",
  "serialNo": "<设备序列号>"
}

ECDSA 签名是关键安全机制:签名原文由 inParams + url + method + serviceName + timestamp 拼接而成,使用设备私钥 this->keyPem 进行 ECDSA 签名。这是 API 网关验证请求完整性和来源的核心手段。

Diag 路由中的特殊处理:当 URL 以 /vehicle/cert 开头时(iType 55~66,证书相关操作),额外调用 add_powerTypeadd_carType 注入车辆类型信息。

CFG 路由的唯一差异是设置了 "asynchronous": true,表明配置上传采用异步处理模式。

Token 获取通过 getAccessToken() 从本地 profile/profile.json 文件中读取登录令牌,若令牌缺失则整个 callServer 立即返回 -1。

Sources: jidu_client.cpp jidu_client.cpp

预处理管道:缓存清理策略

在发起 HTTP 请求之前,callServer_preHandler() 根据 iType 清理 DataCenter 中对应的缓存字段——这是确保数据一致性的关键步骤。其核心理念是:先清空旧数据,再拉取新数据,避免使用过期缓存。

mermaid
graph LR
    subgraph "Pre-Handler 清理策略"
        P1["iType=4"] --> C1["清空 TPMS ID<br/>重置供应商ID为00000000"]
        P2["iType=6/59"] --> C2["清空 ECU安全常量映射"]
        P3["iType=19/58"] --> C3["清空 EOL密钥六元组"]
        P4["iType=20/56"] --> C4["清空 ECU证书信息"]
        P5["iType=21/55"] --> C5["清空 AES密钥映射"]
        P6["iType=26"] --> C6["清空整车订单信息"]
        P7["iType=27/64"] --> C7["清空 OTA证书"]
        P8["iType=54"] --> C8["清空 ECU列表"]
        P9["iType=79"] --> C9["清空 EBOC信息"]
        P10["iType=88"] --> C10["清空新版ECU列表"]
    end

值得注意的是,默认分支(default)只清理 DataCenter::m_callServerRecord[iType].response——即单个请求类型的响应缓存,而不做全域清理。这意味着大部分接口仅刷新自身相关数据。

Sources: jidu_client.cpp

响应解析与后处理管道

HTTP 响应返回后,callServer 执行一个两阶段后处理流程:

第一阶段:parsed() — 响应结构标准化

parsed() 方法解决一个常见问题:某些服务器会将成功的业务数据包裹在嵌套的 data.data 结构中。当检测到 code == 0code == 200 且响应体中的 data 字段本身又包含一个 data 子字段时,parsed() 会递归展开外层包裹,直到找到真正的业务数据。

此外,若 code 字段为字符串类型(如 "200"),parsed() 会将其原地转换为数字类型——这对下游 JSON 解析的一致性至关重要。

当响应码为非成功状态时,parsed() 提取 messagemsg 字段内容存入 DataCenter::getInstance()->errMsg,供调用方通过 getLastErrMsg() 查询失败原因。

Sources: jidu_client.cpp

第二阶段:callServer_affterHandler() — 数据提取与缓存

后处理器的核心职责是将响应 JSON 中的业务数据解构并写入 DataCenter 单例的对应字段。这是一个大型 switch 语句,按 iType 分发:

iType 范围数据流向典型提取字段
1, 4DataCenter::m_VehicleOrderInfoCCP Code、车辆类型、TPMS ID
6, 59DataCenter::m_EcuSafetyConstant四类安全常量映射(swdl/immo/sal/common)
11, 19, 57, 58DataCenter::m_EolKeyEOL 密钥六元组
20, 23, 56, 65DataCenter::m_EcuCertInfo证书列表、DVC Key、VID、ECU CA
21, 55DataCenter::m_aesKey按 ECU 名称索引的 AES 密钥表
26DataCenter::m_VehicleOrderInfo整车订单全量字段 + ECU SWPN 列表
27, 64DataCenter::m_OtaCertOTA 服务 CA 证书 + 客户端证书
28DataCenter::m_summaryData工位测试汇总数据(三层嵌套结构)
51DataCenter::m_VehicleOrderInfo.strSwpn整车软件版本号
54DataCenter::m_VehicleOrderInfo.vecListEcuECU 列表 + 硬件版本 + SWPN
79DataCenter::m_vecEbocInfoEBOC 特征码列表
88DataCenter::m_VehicleOrderInfo.vecListEcuNew新版 ECU 列表 + 订单刷写路径

特殊容错逻辑:当 iType 为 21 或 55 且返回码为 9002/9004/9005(表示 PKI 服务中的 AES 密钥无效或过期)时,后处理器不会报错退出,而是注入一个硬编码的全零 AES 密钥(Base64 编码后的 \x00\x11\x22...\xFF)到 ACU、BGM、CDC、TCAM 四个 ECU 的映射中——这是一种降级容错策略,确保在 PKI 服务不可用时刷写流程仍可继续(使用默认密钥)。

Sources: jidu_client.cpp jidu_client.cpp

脱敏机制:日志安全与 PKI 敏感数据保护

脱敏是 callServer 中一项至关重要的安全设计。当响应成功且 host 为 "pki" 时,日志输出前会调用 desensitize() 对 JSON 中的每一个字符串值进行掩码处理。

mermaid
graph TD
    A["desensitize(cJSON* obj)"] --> B{"obj 是 Object 或 Array?"}
    B -->|Yes| C["遍历所有子节点"]
    C --> D{"子节点类型?"}
    D -->|"String"| E["调用 MyStringUtils::desensitize()"]
    D -->|"Object/Array"| F["递归 desensitize()"]
    E --> G["cJSON_ReplaceItemInObject<br/>原地替换为掩码值"]

MyStringUtils::desensitize() 的实现简洁而有效:保留字符串首尾各 plainLen 个字符(默认为 3),中间部分全部替换为 *。若字符串长度不超过 2 × plainLen,不做任何处理直接返回原文。例如:

  • "ABCD1234EFGH5678""ABC************78"(首尾各保留 3 个可见字符)
  • "ABC""ABC"(长度不足,保留原文)

对于 iType 28(工位测试汇总数据),响应内容过于庞大且不涉及敏感信息,直接跳过日志打印(else if (iType != 28) 分支)。

非 PKI 的响应仅在成功时通过 Tracer::json() 完整打印 JSON——这确保了调试便利性,同时将所有敏感数据(证书、密钥、安全常量)的日志输出限制在脱敏后的安全形式。

Sources: jidu_client.cpp MyStringUtils.h MyStringUtils.cpp

完整调用时序

下图汇总了 callServer 从入口到返回的完整生命周期:

mermaid
sequenceDiagram
    participant OTX as OTX 脚本
    participant API as C API (jidu.cpp)
    participant JC as JiduClient::callServer
    participant DC as DataCenter
    participant HTTP as HttpClient
    participant Server as 云端服务

    OTX->>API: callServer(iType, inParams, outBuffer, size)
    API->>JC: getInstance()->callServer(...)
    
    Note over JC: std::lock_guard 互斥锁
    JC->>JC: init() 检查环境/证书是否过期
    JC->>DC: callServer_preHandler(iType) 清理缓存
    
    alt host == "diag" or "cfg"
        JC->>JC: getAccessToken() → token
        JC->>JC: ecdsa_signature() → 签名
        JC->>JC: 构建 API Proxy JSON
    end
    
    alt host == "pki"
        JC->>JC: setClientCert / setClientKey
        JC->>JC: add_powerType(inParams)
    end
    
    alt IsIntegrationTesting() == true
        JC->>JC: 读取本地 Mock 文件
    else
        JC->>HTTP: GET/POST → Server
        Server-->>HTTP: HTTP Response
    end
    
    JC->>JC: parsed(response) 标准化
    JC->>JC: callServer_affterHandler(iType) → DC
    
    alt host == "pki" && success
        JC->>JC: desensitize(obj) 脱敏
        JC->>JC: Tracer::json 打印脱敏日志
    else iType != 28 && success
        JC->>JC: Tracer::json 打印完整日志
    end
    
    JC->>API: memcpy → outBuffer
    API-->>OTX: 返回 response.length()

Sources: jidu_client.cpp

关键设计模式总结

设计模式体现位置解决的问题
单例模式JiduClient::getInstance()全局唯一 HTTP 客户端,共享证书和 Token 状态
策略模式vtable 按 host 分发四种调用策略同一接口适配四种异构后端服务
模板方法preHandler → HTTP → parsed → afterHandler固化调用骨架,子步骤由 iType 决定行为
缓存失效preHandler 按 iType 选择性清空 DataCenter避免使用过期数据,但不过度清空
降级容错集成测试 Mock / AES 密钥默认值确保在离线或服务异常时系统不崩溃
关注点分离脱敏与业务逻辑正交日志安全不侵入核心数据处理流程

阅读建议

要深入理解 callServer 机制,推荐以下阅读路径: