EnvConfig 是整个 OTX 代理插件中负责多环境配置切换的核心模块。它采用单例模式统一管理应用运行环境(Dev/Test/Staging/Prod),通过哨兵文件检测与编程式设置两种机制确定当前环境,并将环境信息注入到 HTTP 客户端的主机地址、证书路径和安全凭据等关键配置中,从而实现同一个 DLL 在不同部署场景下无缝切换后端服务端点。
Sources: jidu_envConfig.h, jidu_envConfig.cpp
环境枚举定义
环境类型由 jidu_macro.h 中的 App_Env_t 枚举统一描述。该枚举定义了六个环境等级,其中 Dev、Test、Staging、Prod 构成主要的四级递进流水线,而 ProdTest 和 ProdStaging 作为额外的混合环境预备扩展。
| 枚举常量 | 整数值 | 含义 |
|---|---|---|
App_Env_Dev | 1 | 开发环境 |
App_Env_Test | 2 | 测试环境 |
App_Env_Staging | 3 | 预发布/ staging 环境 |
App_Env_Prod | 4 | 生产环境 |
App_Env_ProdTest | 5 | 生产测试混合(预留) |
App_Env_ProdStaging | 6 | 生产 Staging 混合(预留) |
Sources: jidu_macro.h
架构总览:从哨兵文件到端点注入
EnvConfig 在多环境架构中扮演"配置源"角色——它本身只负责检测和存储当前环境标识,真正的环境差异化配置(如服务器 URL、证书路径)由 JiduClient 在其 loadEnvirment() 方法中消费环境标识后完成。下图展示了从环境检测到端点注入的完整数据流:
flowchart TD
subgraph 环境检测层
SF[哨兵文件检测<br/>useProd.txt / useStaging.txt / ...]
PROG[编程式设置<br/>setEnvironment]
end
subgraph EnvConfig 单例
EC[EnvConfig::getInstance]
ENV[environment: App_Env_t]
GEP[getEnvPath / getEnvironment]
CK[checkEnvironment]
end
subgraph 环境消费者
LE[loadEnvirment<br/>设置 eolHost / pkiHost / cfgHost / diagHost / certDir]
LP[loadParam<br/>按环境码索引 certPwd / certName]
API[getAppEnvironment API<br/>对外暴露环境信息]
end
subgraph 配置文件
WEB[WebApi.json<br/>运行时覆盖 host]
CFG[Diagnosis.Entry.exe.config<br/>staging_2_prod / 集成测试]
end
SF -->|构造时| ENV
PROG --> ENV
ENV --> GEP
CK -->|重新扫描| ENV
GEP --> LE
GEP --> LP
GEP --> API
LE --> WEB
LE --> CFGSources: jidu_envConfig.cpp, jidu_client.cpp, jidu_client.cpp
哨兵文件检测机制
环境检测的核心逻辑位于 构造函数 和 checkEnvironment() 方法中。两者实现完全相同的检测链:按优先级从高到低依次检查工作目录下是否存在特定的哨兵文件(sentinel file),一旦命中即设置对应环境并终止检查。
哨兵文件的存在性通过 POSIX stat() 调用判定——只需文件存在且为常规文件类型即可,文件内容不参与判断。检测优先级如下表:
| 优先级 | 哨兵文件 | 对应环境 | 适用工厂 |
|---|---|---|---|
| 1(最高) | useProd.txt | Prod | 通用 |
| 2 | useDmaProd.txt | Prod | DMA |
| 3 | useStaging.txt | Staging | 通用 |
| 4 | usePma.txt | Staging | PMA |
| 5 | useDma.txt | Staging | DMA |
| 6 | useTest.txt | Test | 通用 |
| 7 | useDmaTest.txt | Test | DMA |
| 8 | useDev.txt | Dev | 通用 |
| 9 | useDmaDev.txt | Dev | DMA |
| 兜底 | (无哨兵文件) | Prod | — |
关键设计决策:当没有任何哨兵文件存在时,系统默认回退到生产环境(Prod)。这意味着生产环境是"最安全"的缺省选择,避免了因配置疏忽导致测试流量进入生产系统的风险反转。
flowchart LR
A[启动] --> B{useProd.txt?}
B -->|是| P[Prod]
B -->|否| C{useDmaProd.txt?}
C -->|是| P
C -->|否| D{useStaging.txt?}
D -->|是| S[Staging]
D -->|否| E{usePma.txt?}
E -->|是| S
E -->|否| F{useDma.txt?}
F -->|是| S
F -->|否| G{useTest.txt?}
G -->|是| T[Test]
G -->|否| H{useDmaTest.txt?}
H -->|是| T
H -->|否| I{useDev.txt?}
I -->|是| DV[Dev]
I -->|否| J{useDmaDev.txt?}
J -->|是| DV
J -->|否| PSources: jidu_envConfig.cpp, jidu_envConfig.cpp
环境输出接口
EnvConfig 对外暴露四个核心访问方法,分别服务于不同的消费场景:
| 方法 | 返回值 | 用途 |
|---|---|---|
getEnvPath() | "Dev" / "Test" / "Staging" / "Prod" | 对外 API(getAppEnvironment)的字符串映射源 |
getEnvironment() | int(1~4) | 用于 loadParam() 中按环境码索引证书密码和证书文件名 |
getAppPath() | DLL 所在目录的绝对路径 | 所有配置文件和证书的路径前缀 |
checkEnvironment() | bool(true=环境未变化) | 在每次 callServer 前检查哨兵文件是否变更,实现热切换 |
getAppPath() 的内部实现值得一提:它通过 GetModuleHandleEx + GetModuleFileName 获取当前 DLL 的加载路径(而非进程工作目录 getcwd),从而确保无论宿主进程从何处启动,配置文件查找始终基于 DLL 的实际部署位置。该路径结果被缓存为静态变量,避免重复调用 Windows API。
Sources: jidu_envConfig.cpp, jidu_envConfig.cpp, jidu_envConfig.cpp
环境热切换:checkEnvironment 与 init() 的协作
运行时环境热切换通过 JiduClient::init() → EnvConfig::checkEnvironment() 链实现。每次 callServer() 调用都会触发 init(),后者调用 checkEnvironment() 重新扫描哨兵文件。如果哨兵文件集合发生变化(例如管理员在两次请求之间放置了 useStaging.txt),checkEnvironment() 返回 false,随后 init() 会清空已缓存的证书凭据并重新调用 loadEnvirment() 和 loadParam(),实现无缝环境切换。
sequenceDiagram
participant CS as callServer
participant INIT as JiduClient::init()
participant EC as EnvConfig
participant FS as 文件系统
CS->>INIT: 每次请求前调用
INIT->>EC: checkEnvironment()
EC->>FS: stat(哨兵文件列表)
FS-->>EC: 文件存在性结果
EC-->>INIT: true(未变) / false(已变)
alt 环境已变化
INIT->>INIT: keyPem.clear()
INIT->>INIT: loadEnvirment()
INIT->>INIT: loadParam()
end
INIT-->>CS: 继续执行请求Sources: jidu_client.cpp, jidu_envConfig.cpp
环境→端点映射:loadEnvirment() 的多维路由
JiduClient::loadEnvirment() 是环境配置的实际消费者,它将环境标识转化为四个服务端点和证书目录。映射逻辑引入了一个额外的维度——工厂标识(Factory):DataCenter::getInstance()->factory 决定使用 DMA、PMA 还是售后/工程的端点集合。每个工厂×环境组合对应独立的域名和证书路径。
以 PMA 工厂 为例的端点映射(DMA 和售后/工程场景遵循相同模式但使用不同域名):
| 环境 | eolHost | pkiHost | diagHost |
|---|---|---|---|
| Dev | eol-server-service-dev.jiduauto.com | pki-thirdparty.jidudev.com | diagnostic-service.jidudev.com |
| Test | eol-server-service.jidupmatest.com | pma-pki-thirdparty.jidupmatest.com | diagnostic-service.jidutest.com |
| Staging | eol-server-service-staging.jiduauto.com | pma-pki-thirdparty.jidupmastaging.com | diagnostic-service.jidustaging.com |
| Prod | eol-server-service.jidupma.com | pma-pki-thirdparty.jidupma.com | diagnostic-service.jiduprod.com |
证书目录则遵循 {appPath}pkiCertificate/{Factory}/{EnvPath}/ 的路径模板,例如 PMA 工厂在 Staging 环境下的证书路径为 {appPath}pkiCertificate/PMA/Staging/。
Sources: jidu_client.cpp
WebApi.json:外部化端点覆盖
在 loadEnvirment() 的硬编码映射完成之后,系统还会尝试加载 {appPath}WebApi.json 文件。如果该文件存在且包含与当前工厂和环境匹配的 DomainName 配置节,则会覆盖此前设置的 eolHost、pkiHost 和 cfgHost(diagHost 不受影响)。这为运维人员提供了一种无需重新编译 DLL 即可调整服务端点的机制。
WebApi.json 的结构层次为 DomainName → {Factory: DMA/PMA} → {Env: Dev/Test/Staging/Prod} → {EOL, PKI, Config},与代码内的分支逻辑一一对应:
{
"DomainName": {
"PMA": {
"Staging": {
"EOL": "https://custom-eol.example.com/eol-server",
"PKI": "https://custom-pki.example.com",
"Config": "https://custom-cfg.example.com/api"
}
}
}
}Sources: jidu_client.cpp
Staging→Prod 桥接:staging_2_prod() 配置开关
在 Staging 环境下,loadEnvirment() 还引入了一个特殊逻辑:调用 staging_2_prod() 检查 Diagnosis.Entry.exe.config 配置文件中的 eolEnvironment 键值。当该值为 "true" 时,Staging 环境的 eolHost 将被替换为生产环境的 EOL 服务器地址。这种设计允许在 Staging 环境下使用生产级的 EOL 数据进行集成验证,同时保持其他服务(PKI、Config、Diag)仍指向 Staging。
该机制同样作用于 WebApi.json 覆盖阶段——当 staging_2_prod() 返回 true 时,WebApi.json 中 PKI 和 Config 仍使用 Staging 的值(从 Staging 节点取出覆盖到 Prod 节点),仅 EOL 使用真正的 Prod 地址。
Sources: jidu_client.cpp, jidu_client.cpp
集成测试 Mock 模式
IsIntegrationTesting() 和 getMockDataFormat() 两个函数同样依赖 EnvConfig 提供的 getAppPath() 来定位 Diagnosis.Entry.exe.config 配置文件。在集成测试模式下,EOL 请求不会真正发出 HTTP 调用,而是从本地 EolServerMockData 目录读取预置的 JSON 或 XML Mock 数据文件。Mock 数据文件的命名规则为 URL 路径中 /open/diagnosis/ 之后的部分经 /→_ 替换后拼接 .json/.xml 后缀。
flowchart TD
CS[callServer] --> IT{IsIntegrationTesting?}
IT -->|否| REAL[真实 HTTP 请求]
IT -->|是| FMT{getMockDataFormat}
FMT -->|json| MJ[读取 .json Mock 文件]
FMT -->|xml| MX[读取 .xml Mock 文件<br/>经 mockDataXml2Json 转换]
MJ --> RESP[返回 response]
MX --> RESPSources: jidu_client.cpp, jidu_client.cpp, jidu_client.cpp
对外 API:getAppEnvironment 与 setFactory
OTX 运行时通过两个 C 语言导出的 API 与环境系统交互:
getAppEnvironment():返回简化的环境分类——1表示 Prod、2表示 Staging、0表示其他。该函数通过EnvConfig::getEnvPath()获取字符串后做二次映射,为上层 OTX 脚本提供足够的环境感知能力。setFactory(const char* factory):设置工厂标识("DMA"、"PMA"等),并立即触发JiduClient::loadEnvirment()和loadParam()的重新加载。这是工厂切换的入口——改变工厂后,所有后续callServer调用都将使用新工厂对应的端点集合和证书。
setFactory 的调用时机通常在诊断会话建立之初,由 OTX 脚本根据诊断设备的部署场景传入。这也解释了为什么 loadEnvirment() 中的端点映射需要按工厂维度分叉——同一套 DLL 需要在 DMA 产线、PMA 产线和售后门店等不同物理场景中使用完全不同的后端服务集群。
Sources: jidu.cpp, jidu.cpp, jidu.h
环境码在证书选择中的应用
loadParam() 中使用环境码的另一个关键场景是加密证书密码的索引。pver 文件中的 certPwd 和 certName 均以环境码为键存储——对于 DMA 工厂,环境码会额外 +6 偏移(例如 Dev=1→7),从而在同一个 JSON 结构中隔离 DMA 和 PMA 的凭据:
环境码映射(loadParam):
PMA: Dev=1, Test=2, Staging=3, Prod=4
DMA: Dev=7, Test=8, Staging=9, Prod=10这确保即使在同一台设备上切换工厂,也不会错误地使用另一工厂的证书凭据。
Sources: jidu_client.cpp
阅读建议
理解 EnvConfig 后,建议继续阅读以下关联文档以建立完整的配置管理认知:
- JiduClient:与集度服务器的 HTTPS 双向认证通信客户端 — EnvConfig 的主要消费者,理解环境配置如何转化为实际的 HTTP 请求端点
- DataCenter:车辆订单、安全常量、证书信息的统一缓存中心 —
factory标识的存储位置及其对多环境路由的影响 - 证书管理:P12 解析、OTA 证书与 ECU 级安全常量获取 — 环境码如何决定证书的选择与解密
- callServer 接口设计:请求类型路由、预处理/后处理与脱敏机制 —
init()调用链在每次请求中的触发时机与意义