本项目中有两个贯穿全栈的基础工具模块——MyStringUtils(纯静态字符串工具类)和 DateFormat(可实例化的毫秒级时间格式化类)。它们是日志系统、数据脱敏、编码转换和刷写报告时间戳的底层支撑。MyStringUtils 被 22 个源文件 直接引用,是项目中引用范围最广的工具模块;DateFormat 则主要集成在 Tracer 日志系统和 otxproxy.cpp 入口中,为所有日志输出提供毫秒精度的时间戳。
整体架构
两个模块各司其职,共同支撑上层业务。MyStringUtils 处理字符串形态转换(大小写、修剪、拆分、替换、数字互转、编码转换),DateFormat 处理时间形态转换(时间戳 ⟷ 格式化字符串、时间间隔格式化)。它们通过 Tracer 日志系统交汇——DateFormat::currentTimeString() 为日志打时间戳,MyStringUtils::UTF8ToGBK() 将 UTF-8 日志转码后输出到 Windows 控制台。
graph TB
subgraph "上层业务模块(22 个引用方)"
A1[jidu_client - HTTPS 通信]
A2[jidu_flasher - 刷写编排]
A3[jidu_dataCenter - 数据中心]
A4[jidu_vehicleConfig - 配置解析]
A5[jidu_dtc - 故障码管理]
A6[ECU / ECUFlasherManager - ECU 管理]
A7[ReportRecordCollector - 报告采集]
A8[algorithm - 加密算法]
A9[FlashFileUtils - 刷写文件解密]
end
subgraph "MyStringUtils 静态工具类"
S1[split / SplitStringByRegExp - 字符串拆分]
S2[toUpper / toLower - 大小写转换]
S3[trim / replace / CutOut - 修剪替换]
S4[toLong / toInt / toString - 类型互转]
S5[startWith / endWith - 前后缀匹配]
S6[desensitize - 数据脱敏]
S7[UTF8ToGBK / GBKToUTF8 - 编码转换]
S8[radix_array2Hex / BufferToString - 二进制格式化]
end
subgraph "DateFormat 时间格式化类"
D1[toString - 时间戳→格式化字符串]
D2[fromString - 格式化字符串→时间戳]
D3[duration - 毫秒时长→格式化字符串]
D4[currentTimeString - 静态快捷方法]
D5[now - 当前毫秒时间戳]
end
subgraph "Tracer 日志系统"
T1[currentTimestamp → DateFormat::currentTimeString]
T2[saveLogger → MyStringUtils::UTF8ToGBK]
end
A1 --> S1
A2 --> S2
A3 --> S5
A4 --> S3
A5 --> S4
A6 --> S7
A7 --> S6
A8 --> S8
A9 --> S8
T1 --> D4
T2 --> S7Sources: MyStringUtils.h, Dateformat.h, tracer.cpp
MyStringUtils:全静态字符串工具类
MyStringUtils 是一个纯静态方法类——所有 18 个公开方法均为 static,无需实例化即可调用。它不持有任何状态,仅作为函数命名空间组织代码。该类引入了 <regex>(仅用于 SplitStringByRegExp)和 Windows API 头文件 <stringapiset.h>(仅用于编码转换的 MultiByteToWideChar / WideCharToMultiByte),因此编码转换功能仅在 Windows 平台有效。
方法全景速查
| 方法签名 | 功能 | 典型场景 |
|---|---|---|
split(vec, str, delim) | 按分隔符拆分字符串到 vector | 解析换行分隔的配置文件、分割多行日志 |
SplitStringByRegExp(str, vec, reg) | 按正则表达式拆分 | 复杂分隔场景(如多个空格、混合标点) |
toUpper(str) / toLower(str) | 原地大小写转换 | VIN 码标准化、命令关键字匹配 |
trim(str, strTrim) | 去除首尾指定字符,返回引用 | JSON 值清理、用户输入预处理 |
replace(str, old, new) | 全局子串替换 | URL 模板替换、路径修正 |
CutOut(str, strCut) | 截断字符串(删除匹配点及之后全部内容) | 去除注释(// 之后的内容) |
toLong(str, nType) | 字符串→unsigned long(支持进制) | 解析十六进制地址、十进制计数 |
toInt(str) | 字符串→int(10 进制) | 解析数组索引、配置数值 |
toString(buffer, len) | 字节数组→十六进制字符串("A0 B1 " 格式) | 二进制数据日志输出 |
toString(nValue) | unsigned int→4 位十六进制字符串 | CAN ID、地址格式化 |
toString(nValue, radix) | int→指定进制字符串 | 十进制或十六进制灵活输出 |
toString(nValue) | uint8_t→2 位十进制字符串 | 小整数格式化 |
BufferToString(strLog, buff, len, type) | 字节数组→带 REQ/RES 前缀的十六进制字符串 | 诊断请求/响应日志 |
startWith(str, prefix) | 判断是否以指定前缀开头 | 协议标识符匹配、路径前缀检查 |
endWith(str, suffix) | 判断是否以指定后缀结尾 | 文件扩展名检查(.vbf、.bin) |
desensitize(str, plainLen) | 保留首尾明文,中间替换为 * | VIN 码/证书序列号日志脱敏 |
radix_array2Hex(inParams, inLen, str, strHold) | 字节数组→无 0x 前缀的十六进制字符串 | 加密密钥/签名字节串行化 |
UTF8ToGBK(strUTF8) | UTF-8→GBK(Windows 本地编码) | 控制台中文输出 |
GBKToUTF8(szGbk) | GBK→UTF-8(当前已禁用) | 历史遗留接口 |
字符串拆分:split 与 SplitStringByRegExp
split 采用经典的 find + substr 循环 算法:从位置 0 开始反复查找分隔符,将分隔符之前的子串压入 vector,然后跳过分隔符继续。时间复杂度 O(n),空间上直接复用传入的 vector 引用(调用前会 clear())。默认分隔符为 "\n",支持多字符分隔符。
// 拆分多行日志
vector<string> lines;
MyStringUtils::split(lines, multiLineLog, "\n");SplitStringByRegExp 基于 C++ <regex> 库的 regex_search 迭代器模式:每次匹配成功后,将 m.prefix() 压入结果,然后推进迭代器 citer = m[0].second。循环结束后收集剩余尾部。该方法返回拆分后的段数。
Sources: MyStringUtils.cpp | MyStringUtils.cpp
类型转换:toLong、toInt 与 toString 重载族
字符串→数字 方向有两个入口:toLong 内部先调用 trim 清理空白,再调用标准库 strtoul 按指定进制解析(默认 10 进制),适合解析如 "0x1A" 或 "255" 的混合输入;toInt 直接调用 atoi,简单高效但无错误处理。
数字/字节→字符串 方向有 4 个重载:
| 重载 | 格式 | 示例 |
|---|---|---|
toString(unsigned char* buffer, int len) | 每字节 %02X 后跟空格 | buffer={0xA0,0x01} → "A0 01 " |
toString(unsigned int nValue) | %04X 固定 4 位十六进制 | nValue=255 → "00FF" |
toString(int nValue, int radix=16) | %04X 或 %d | radix=16 → "00FF";radix=10 → "255" |
toString(uint8_t nValue) | %02d 固定 2 位十进制 | nValue=7 → "07" |
toString(unsigned char* buffer, int len) 是刷写数据日志的核心方法——将二进制 buffer 转换为可读的十六进制字符串,字节间用空格分隔。它在内部按 len * 3 + 1 分配缓冲区(每字节 2 个十六进制字符 + 1 个空格,末尾 \0),循环调用 sprintf 填充。
Sources: MyStringUtils.cpp
二进制格式化:BufferToString 与 radix_array2Hex
BufferToString 专为诊断通信日志设计。它根据 type 参数(对应枚举 TEST_LOG)在输出前追加 "REQ=" 或 "RES=" 前缀,清晰区分请求与响应日志。内部分配 4 + len * 3 + 1 大小的缓冲区(4 字节前缀),使用 sprintf 逐字节格式化为 "%.2X "。
// type=0 输出 "REQ=A0 01 FF ",type=1 输出 "RES=A0 01 FF "
MyStringUtils::BufferToString(strLog, buff, len, LOG_REQ);枚举定义如下:
enum TEST_LOG {
LOG_REQ = 0, // 请求
LOG_RES // 响应
};radix_array2Hex 提供更精细的十六进制转换:手动提取每字节的高 4 位和低 4 位,分别映射为 '0'-'9' 或 'A'-'F',并在字节间插入自定义分隔符 strHold(默认为空格)。末尾字节不追加分隔符。该方法的优点是完全避免了 sprintf 的格式字符串开销,适合高频调用场景。
Sources: MyStringUtils.cpp | MyStringUtils.cpp | MyStringUtils.h
数据脱敏:desensitize
desensitize 实现 首尾明文、中间掩码 的脱敏策略:保留字符串前 plainLen 个字符和后 plainLen 个字符不变,中间部分逐字符替换为 *。当字符串总长度不足 2 * plainLen 时(例如长度 ≤ 6 且 plainLen=3),直接返回原字符串不做脱敏。
string vin = "LSVAU2A30N2100001";
MyStringUtils::desensitize(vin, 3); // 结果: "LSV***********001"这个函数在报告采集模块(ReportRecordCollector)中用于保护输出到日志的 VIN 码,确保敏感信息不会以明文形式出现在日志文件和控制台输出中。
Sources: MyStringUtils.cpp
编码转换:UTF8ToGBK 与 GBKToUTF8
UTF8ToGBK 是 Windows 控制台中文输出的关键。该项目内部使用 UTF-8 编码,但 Windows 控制台默认使用 GBK(CP_ACP)编码。转换流程分两步:
- UTF-8 → UTF-16(Wide Char):调用
MultiByteToWideChar(CP_UTF8, ...) - UTF-16 → GBK:调用
WideCharToMultiByte(CP_ACP, ...)
该函数在 Tracer::saveLogger 中被调用,将 tag 和 msg 从 UTF-8 转为 GBK 后再输出到控制台。
GBKToUTF8 的反向转换已被 #if false 禁用——函数体直接 return szGbk;,不做任何转换。被禁用的实现保留了完整的 GBK→UTF-16→UTF-8 转换逻辑,可能是为将来跨平台移植预留。
Sources: MyStringUtils.cpp | tracer.cpp
前后缀匹配与截断:startWith、endWith、CutOut
startWith 使用 std::string::compare(0, n, prefix) 从位置 0 按前缀长度精确比较;endWith 使用 compare(len_str - len_suffix, len_suffix, suffix) 从末尾反向比较,且先做长度合法性检查。两者均返回 bool。
CutOut 用于删除注释:查找 strCut(默认为 "//")首次出现位置,将字符串截断为该位置之前的子串。如果未找到匹配则原样保留。
string line = "key=value // this is comment";
MyStringUtils::CutOut(line, "//"); // 结果: "key=value "Sources: MyStringUtils.cpp | MyStringUtils.cpp
DateFormat:毫秒级时间格式化引擎
DateFormat 是一个可实例化的时间工具类,内部持有一个 uint64_t timestamp(毫秒级 Unix 时间戳)。它提供两个维度的操作:时间点格式化(时间戳 ⟷ 格式化字符串)和时间间隔格式化(毫秒时长 → 天-时-分-秒-毫秒 字符串)。
格式化标记体系
DateFormat 使用一套与 Java SimpleDateFormat 类似的单字符标记,通过 format_parse 私有方法将格式字符串解析为 {character, count} 数组。标记连续出现的次数仅用于解析时确定子串长度,不影响输出宽度(输出宽度由代码中的 sprintf 格式字符串固定)。
| 标记 | 含义 | toString 输出格式 | fromString 解析 | duration 输出 |
|---|---|---|---|---|
y | 年份 | %04d(4 位,如 2024) | ✓ | 固定输出 "0" |
M | 月份 | %02d(2 位,1-12) | ✓ | 固定输出 "0" |
d | 日期 | %02d(2 位,1-31) | ✓ | %02d 天数 |
H | 24 小时制 | %02d(0-23) | ✓ | %02d 小时 |
h | 12 小时制 | %02d(0-11) | 同 H | — |
a | AM/PM | "AM" 或 "PM" | — | — |
m | 分钟 | %02d(0-59) | ✓ | %02d 分钟 |
s | 秒 | %02d(0-59) | ✓ | %02d 秒 |
S | 毫秒 | %03d(0-999) | ✓ | %03d 毫秒 |
| 其他字符 | 字面量 | 原样输出(如 -、:、空格) | 跳过 | 原样输出 |
默认格式为 "yyyy-MM-dd HH:mm:ss.SSS",输出如 "2024-01-15 08:30:45.123"。
Sources: Dateformat.h | DateFormat.cpp
format_parse:格式字符串标记化引擎
这是 DateFormat 的核心私有算法。它将格式字符串 "yyyy-MM-dd HH:mm:ss.SSS" 解析为连续的 {character, count} 结构数组:
输入: "yyyy-MM-dd HH:mm:ss.SSS"
输出:
{y,4} → {'-',1} → {M,2} → {'-',1} → {d,2} → {' ',1} →
{H,2} → {':',1} → {m,2} → {':',1} → {s,2} → {'.',1} → {S,3}算法采用双循环扫描:外层 i 遍历原始字符串,内层 j 从当前位置向后计数连续相同字符。当字符发生变化或达到最大计数(10)时退出内循环,记录 {当前字符, 连续计数},然后 i = i + j 跳到下一段。
这个设计使得 "yyyy"(4 个 y)和 "y"(1 个 y)在解析后得到不同的 count 值,从而 fromString 能正确截取对应长度的子串。
flowchart LR
A["输入: yyyy-MM-dd"] --> B{i=0}
B --> C["内循环 j: 从 i 开始统计连续相同字符"]
C --> D["j=4 (y连续出现4次)"]
D --> E["记录 {y,4}"]
E --> F["i = i + j = 4"]
F --> G{i < len?}
G -->|是| H["下一字符 '-'"]
H --> C
G -->|否| I["返回段数 k"]Sources: DateFormat.cpp
toString:时间戳 → 格式化字符串
toString 是 DateFormat 最核心的输出方法。流程如下:
- 将毫秒时间戳拆分为
time_t sec = timestamp / 1000(秒部分)和uint32_t msec = timestamp % 1000(毫秒部分) - 调用
localtime(&sec)获取struct tm*分解时间 - 调用
format_parse解析格式字符串 - 遍历解析结果,按标记字符 switch-case 输出对应时间分量
12 小时制标记 h 使用 p->tm_hour % 12 计算,a 标记根据 p->tm_hour > 12 输出 "PM" 或 "AM"。非标记字符(如 -、:、空格)原样追加。
Sources: DateFormat.cpp
fromString:格式化字符串 → 时间戳
fromString 是 toString 的逆向操作。它创建一个 localtime 获得的 struct tm* 作为容器,按格式标记从输入字符串中逐段截取子串,通过 atoi 解析并填入 tm 结构,最后调用 mktime(p) * 1000 + msec 合成毫秒时间戳。
需要注意的是,fromString 内部 localtime(&rawtime) 的 rawtime 初始为 0(即 epoch: 1970-01-01 00:00:00),未显式指定的时间分量(如秒)会保留为 epoch 默认值,因此使用时必须确保格式字符串与输入的时间字符串完全匹配。
Sources: DateFormat.cpp
duration:时间间隔格式化
duration 将纯毫秒时长(不是绝对时间戳)转换为可读的 天-时-分-秒-毫秒 字符串。算法从毫秒开始逐级向上分解:
msec = ts % 1000 // 毫秒余数
sec = (ts / 1000) % 60 // 总秒数模 60
min = (ts / 1000 / 60) % 60 // 总分钟模 60
hour = (ts / 1000 / 60 / 60) % 24 // 总小时模 24
day = ts / 1000 / 60 / 60 / 24 // 总天数(整数除法)由于 duration 处理的是时长而非时间点,年份和月份无意义,因此 y 和 M 标记固定输出 "0"。典型用法如 duration(90061000, "HH:mm:ss.SSS") → "25:01:01.000"(表示 25 小时 1 分 1 秒)。
Sources: DateFormat.cpp
构造函数与 now 方法
默认构造函数通过 ftime(&t) 获取当前系统时间(timeb 结构体提供秒 + 毫秒),合并为 t.time * 1000 + t.millitm。代码中保留了一段 #if false 禁用的备选实现,使用 Windows 8+ 的 GetSystemTimePreciseAsFileTime API 获得更高精度(100ns 单位)。
带参构造函数 Dateformat(uint64_t ts) 直接接受外部时间戳,用于格式化指定的历史时间。
now() 静态方法与默认构造函数逻辑相同(ftime),但直接返回 uint64_t 毫秒时间戳而不创建对象。
currentTimeString() 静态方法是项目中最频繁使用的时间接口——创建一个匿名 Dateformat 实例并调用 toString("yyyy-MM-dd HH:mm:ss.SSS")。它在 Tracer::currentTimestamp() 中被封装为日志时间戳的便捷入口。
Sources: DateFormat.cpp | DateFormat.cpp | DateFormat.cpp
两个模块的协作:Tracer 日志系统中的交汇
Tracer 是 MyStringUtils 和 DateFormat 的主要交汇点。在 Tracer::currentTimestamp() 中,DateFormat::currentTimeString() 为每条日志提供毫秒精度的时间戳;在 Tracer::saveLogger() 中,MyStringUtils::UTF8ToGBK() 将 UTF-8 日志消息转码为 GBK 以便在 Windows 控制台正确显示中文。
// tracer.cpp 中的集成
string Tracer::currentTimestamp(void) {
return Dateformat::currentTimeString(); // 时间戳格式化
}
void Tracer::saveLogger(string levelString, string tag, string msg) {
append2Console(levelString,
MyStringUtils::UTF8ToGBK(tag), // 编码转换
MyStringUtils::UTF8ToGBK(msg)); // 编码转换
}在 append2Console 的注释代码中还保留了将时间戳直接嵌入控制台输出的备选方案(cout << Dateformat::currentTimeString() << " " << levelString ...),当前版本的时间戳由上层调用方(如 OTX 运行时)负责拼接。
Sources: tracer.cpp | tracer.cpp
项目中的引用全景
MyStringUtils 是项目中覆盖率最高的工具模块,从网络通信到 ECU 刷写再到报告生成,几乎所有子系统都依赖它:
| 子系统 | 引用文件 | 主要使用的方法 |
|---|---|---|
| 入口 & 日志 | otxproxy.cpp, tracer.cpp | UTF8ToGBK, toString |
| 云端通信 | jidu_client.cpp, jidu.cpp | split, trim, startWith |
| 数据中心 | jidu_dataCenter.cpp | split, trim, toInt |
| 配置管理 | jidu_vehicleConfig.cpp, jidu_envConfig.cpp | split, trim, CutOut, toUpper |
| ECU 管理 | ECU.cpp, ECUFlasherManager.cpp | toString, split |
| 刷写编排 | jidu_flasher.cpp, ParallelECUFlasher.cpp | toString, startWith |
| 诊断通信 | MCDCmdTrans.cpp, VbfFileDownloader.cpp | toString, radix_array2Hex |
| 故障码 | jidu_dtc.cpp | toString |
| 报告系统 | ReportRecordCollector.cpp, PrintResultItem.cpp | desensitize, toString |
| 加密工具 | algorithm.cpp, FlashFileUtils.cpp | radix_array2Hex, toString |
DateFormat 的引用更聚焦:仅在 otxproxy.cpp、tracer.cpp 和自身实现文件中出现,其功能通过 Tracer 间接暴露给所有使用日志的模块。
Sources: findstr 搜索结果(22 个文件引用 MyStringUtils.h,3 个文件引用 Dateformat.h)
阅读建议
理解这两个工具模块后,建议继续深入以下相关文档:
- Tracer:分级日志系统 — MyStringUtils 和 DateFormat 的主要消费方,深入了解日志级别控制与输出策略
- CBinary:高效二进制数据缓冲区操作类 — 与
toString和radix_array2Hex紧密配合的二进制数据容器 - 加密算法集 —
radix_array2Hex在密钥和签名序列化中的实际应用场景 - 报告数据模型 —
desensitize在报告生成中的脱敏应用