C++读取txt日志到自定义对象并存入MySQL数据库
ATM日志解析入库落地实现方案
1. 核心实现逻辑拆分
整个流程按顺序拆为3个独立模块,和你现有代码结构完全兼容,不需要改动已完成的类、结构体定义:
- 日志分块:按固定分隔符把整份txt拆成单条事件的独立文本块
- 字段解析:遍历每个文本块,提取字段值赋值给
events结构体,填充到Journal对象的trans_events集合 - 安全入库:用MySQL预处理语句批量写入全量字段,避免SQL注入和语法错误
2. 模块实现代码
所有代码可以直接写到你现有的file.cpp中,补充对应的函数声明到header.h即可。
2.1 通用工具函数
先实现字符串去空格逻辑,避免日志前后的换行、空格导致字段匹配失败:
#include <fstream> #include <vector> #include <string> #include <cctype> #include <cstring> #include <mysql/mysql.h> std::string trim(const std::string& s) { auto start = s.begin(); while (start != s.end() && std::isspace(static_cast<unsigned char>(*start))) start++; auto end = s.rbegin(); while (end != s.rend() && std::isspace(static_cast<unsigned char>(*end))) end++; return std::string(start, end.base()); }
2.2 日志分块函数
逐行读取日志,遇到分隔符就切分独立事件块,自动跳过空块:
const std::string LOG_DELIMITER = "************************"; std::vector<std::vector<std::string>> split_log_blocks(std::ifstream& log_file) { std::vector<std::vector<std::string>> all_blocks; std::vector<std::string> current_block; std::string line; while (std::getline(log_file, line)) { std::string trimmed_line = trim(line); if (trimmed_line == LOG_DELIMITER) { if (!current_block.empty()) { all_blocks.push_back(current_block); current_block.clear(); } continue; } current_block.push_back(trimmed_line); } // 处理文件末尾没有尾随分隔符的最后一个事件块 if (!current_block.empty()) all_blocks.push_back(current_block); return all_blocks; }
2.3 字段解析函数
遍历单个事件块的每一行,按字段名匹配提取值,填充events结构体:
events parse_single_event(const std::vector<std::string>& event_block) { events ev{}; // 结构体默认初始化 for (const auto& line : event_block) { // 按你日志实际的字段分隔符调整,这里默认是key: value格式,如果是key=value就把':'改成'=' size_t split_pos = line.find(':'); if (split_pos == std::string::npos) continue; std::string key = trim(line.substr(0, split_pos)); std::string val = trim(line.substr(split_pos + 1)); // 以下字段匹配规则和你定义的events结构体一一对应,缺的字段按相同格式补全即可 if (key == "id_event") ev.id_event = std::stoi(val); else if (key == "id_event_ligne") ev.id_event_ligne = std::stoi(val); else if (key == "amount_withdraw") ev.amount_withdraw = std::stod(val); } return ev; }
2.4 MySQL安全入库函数
替换你原来硬编码单字段调用mysql_query的逻辑,用预处理语句自动处理转义,支持批量插入:
bool save_journal_to_db(MYSQL* mysql_conn, const Journal& journal) { // SQL字段顺序必须和你的journal_contents表结构完全一致,?是预处理占位符 const char* insert_sql = R"( INSERT INTO journal_contents (id, id_atm, id_ligne, id_event, id_event_ligne, amount_withdraw) VALUES (?, ?, ?, ?, ?, ?) )"; MYSQL_STMT* stmt = mysql_stmt_init(mysql_conn); if (!stmt) return false; if (mysql_stmt_prepare(stmt, insert_sql, strlen(insert_sql))) { mysql_stmt_close(stmt); return false; } // 开启事务,批量插入效率比单条提交高10倍以上 mysql_query(mysql_conn, "START TRANSACTION"); const int PARAM_COUNT = 6; // 按实际字段总数修改 MYSQL_BIND bind[PARAM_COUNT]; memset(bind, 0, sizeof(bind)); // 绑定Journal类的固定公共属性 bind[0].buffer_type = MYSQL_TYPE_LONG; bind[0].buffer = (void*)&journal.id; bind[1].buffer_type = MYSQL_TYPE_LONG; bind[1].buffer = (void*)&journal.id_atm; bind[2].buffer_type = MYSQL_TYPE_LONG; bind[2].buffer = (void*)&journal.id_ligne; // 遍历所有事件逐行绑定插入 for (const auto& ev : journal.trans_events) { bind[3].buffer_type = MYSQL_TYPE_LONG; bind[3].buffer = (void*)&ev.id_event; bind[4].buffer_type = MYSQL_TYPE_LONG; bind[4].buffer = (void*)&ev.id_event_ligne; bind[5].buffer_type = MYSQL_TYPE_DOUBLE; bind[5].buffer = (void*)&ev.amount_withdraw; // 字符串类型字段绑定示例:bind[i].buffer_type = MYSQL_TYPE_STRING; bind[i].buffer = (void*)str_val.c_str(); bind[i].buffer_length = str_val.size(); if (mysql_stmt_bind_param(stmt, bind)) { mysql_query(mysql_conn, "ROLLBACK"); mysql_stmt_close(stmt); return false; } if (mysql_stmt_execute(stmt)) { mysql_query(mysql_conn, "ROLLBACK"); mysql_stmt_close(stmt); return false; } } mysql_query(mysql_conn, "COMMIT"); mysql_stmt_close(stmt); return true; }
3. 流程串联
在你现有打开日志文件的逻辑后面,替换原来硬编码查找8902行的测试代码即可:
- 调用
split_log_blocks拿到所有事件块 - 实例化
Journal对象,给id、id_atm、id_ligne等公共属性赋值(去掉硬编码的测试ID,从配置、日志文件名等位置读取真实值) - 遍历所有事件块,调用
parse_single_event解析出events对象,push到journal.trans_events集合中 - 调用
save_journal_to_db完成全量数据入库 - 关闭文件句柄、释放MySQL连接资源
4. 注意事项
- 所有
std::stoi、std::stod调用外层加try-catch,遇到格式异常的字段打错误日志标记位置,不要让整个程序崩溃 - 单日志文件超过100M时,不要把所有事件块存在vector中,读一块、解析一块、入库一块,降低内存占用
- 预处理语句会自动处理特殊字符转义,不要手动拼接SQL字符串,避免语法错误和SQL注入风险
- 如果日志编码是GBK,入库前统一转成UTF-8,避免中文乱码
内容的提问来源于stack exchange,提问作者Hajar lyoubi
相关产品推荐
相关产品推荐

