Skip to content

本项目中有两个贯穿全栈的基础工具模块——MyStringUtils(纯静态字符串工具类)和 DateFormat(可实例化的毫秒级时间格式化类)。它们是日志系统、数据脱敏、编码转换和刷写报告时间戳的底层支撑。MyStringUtils 被 22 个源文件 直接引用,是项目中引用范围最广的工具模块;DateFormat 则主要集成在 Tracer 日志系统和 otxproxy.cpp 入口中,为所有日志输出提供毫秒精度的时间戳。

整体架构

两个模块各司其职,共同支撑上层业务。MyStringUtils 处理字符串形态转换(大小写、修剪、拆分、替换、数字互转、编码转换),DateFormat 处理时间形态转换(时间戳 ⟷ 格式化字符串、时间间隔格式化)。它们通过 Tracer 日志系统交汇——DateFormat::currentTimeString() 为日志打时间戳,MyStringUtils::UTF8ToGBK() 将 UTF-8 日志转码后输出到 Windows 控制台。

mermaid
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 --> S7

Sources: 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(当前已禁用历史遗留接口

字符串拆分:splitSplitStringByRegExp

split 采用经典的 find + substr 循环 算法:从位置 0 开始反复查找分隔符,将分隔符之前的子串压入 vector,然后跳过分隔符继续。时间复杂度 O(n),空间上直接复用传入的 vector 引用(调用前会 clear())。默认分隔符为 "\n",支持多字符分隔符。

cpp
// 拆分多行日志
vector<string> lines;
MyStringUtils::split(lines, multiLineLog, "\n");

SplitStringByRegExp 基于 C++ <regex> 库的 regex_search 迭代器模式:每次匹配成功后,将 m.prefix() 压入结果,然后推进迭代器 citer = m[0].second。循环结束后收集剩余尾部。该方法返回拆分后的段数。

Sources: MyStringUtils.cpp | MyStringUtils.cpp

类型转换:toLongtoInttoString 重载族

字符串→数字 方向有两个入口: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%dradix=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

二进制格式化:BufferToStringradix_array2Hex

BufferToString 专为诊断通信日志设计。它根据 type 参数(对应枚举 TEST_LOG)在输出前追加 "REQ=""RES=" 前缀,清晰区分请求与响应日志。内部分配 4 + len * 3 + 1 大小的缓冲区(4 字节前缀),使用 sprintf 逐字节格式化为 "%.2X "

cpp
// type=0 输出 "REQ=A0 01 FF ",type=1 输出 "RES=A0 01 FF "
MyStringUtils::BufferToString(strLog, buff, len, LOG_REQ);

枚举定义如下:

cpp
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),直接返回原字符串不做脱敏。

cpp
string vin = "LSVAU2A30N2100001";
MyStringUtils::desensitize(vin, 3);  // 结果: "LSV***********001"

这个函数在报告采集模块(ReportRecordCollector)中用于保护输出到日志的 VIN 码,确保敏感信息不会以明文形式出现在日志文件和控制台输出中。

Sources: MyStringUtils.cpp

编码转换:UTF8ToGBKGBKToUTF8

UTF8ToGBK 是 Windows 控制台中文输出的关键。该项目内部使用 UTF-8 编码,但 Windows 控制台默认使用 GBK(CP_ACP)编码。转换流程分两步:

  1. UTF-8 → UTF-16(Wide Char):调用 MultiByteToWideChar(CP_UTF8, ...)
  2. 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

前后缀匹配与截断:startWithendWithCutOut

startWith 使用 std::string::compare(0, n, prefix) 从位置 0 按前缀长度精确比较;endWith 使用 compare(len_str - len_suffix, len_suffix, suffix) 从末尾反向比较,且先做长度合法性检查。两者均返回 bool

CutOut 用于删除注释:查找 strCut(默认为 "//")首次出现位置,将字符串截断为该位置之前的子串。如果未找到匹配则原样保留。

cpp
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 天数
H24 小时制%02d(0-23)%02d 小时
h12 小时制%02d(0-11)同 H
aAM/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 能正确截取对应长度的子串。

mermaid
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 最核心的输出方法。流程如下:

  1. 将毫秒时间戳拆分为 time_t sec = timestamp / 1000(秒部分)和 uint32_t msec = timestamp % 1000(毫秒部分)
  2. 调用 localtime(&sec) 获取 struct tm* 分解时间
  3. 调用 format_parse 解析格式字符串
  4. 遍历解析结果,按标记字符 switch-case 输出对应时间分量

12 小时制标记 h 使用 p->tm_hour % 12 计算,a 标记根据 p->tm_hour > 12 输出 "PM""AM"。非标记字符(如 -:、空格)原样追加。

Sources: DateFormat.cpp

fromString:格式化字符串 → 时间戳

fromStringtoString逆向操作。它创建一个 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 处理的是时长而非时间点,年份和月份无意义,因此 yM 标记固定输出 "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 控制台正确显示中文。

cpp
// 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.cppUTF8ToGBK, toString
云端通信jidu_client.cpp, jidu.cppsplit, trim, startWith
数据中心jidu_dataCenter.cppsplit, trim, toInt
配置管理jidu_vehicleConfig.cpp, jidu_envConfig.cppsplit, trim, CutOut, toUpper
ECU 管理ECU.cpp, ECUFlasherManager.cpptoString, split
刷写编排jidu_flasher.cpp, ParallelECUFlasher.cpptoString, startWith
诊断通信MCDCmdTrans.cpp, VbfFileDownloader.cpptoString, radix_array2Hex
故障码jidu_dtc.cpptoString
报告系统ReportRecordCollector.cpp, PrintResultItem.cppdesensitize, toString
加密工具algorithm.cpp, FlashFileUtils.cppradix_array2Hex, toString

DateFormat 的引用更聚焦:仅在 otxproxy.cpptracer.cpp 和自身实现文件中出现,其功能通过 Tracer 间接暴露给所有使用日志的模块。

Sources: findstr 搜索结果(22 个文件引用 MyStringUtils.h,3 个文件引用 Dateformat.h)

阅读建议

理解这两个工具模块后,建议继续深入以下相关文档: