本文说明 src/op/OpLog.h 中 OpLogMgr 的使用方式,并结合 OpLog.cpp 说明当前实现的异步、路径和线程安全特性。
#include "op/OpLog.h"
using namespace stt;
using namespace stt::op;
OpLogMgr* logMgr = OpLogMgr::getInstance(getRuntime());
OpLogMgr 是进程级单例。构造时启动 detach 的无限后台线程,约每 60 秒将队列中的 SQL 写入数据库。传入的 Runtime、数据库和管理器必须在整个线程运行期间有效。
| API | 作用 | 主要注意事项 |
|---|---|---|
| getInstance | 获取单例 | 首次 Runtime 生效 |
| add | 将一条日志加入待写队列 | 不会立即落盘 |
| refresh | 立即刷新待写 SQL | 不刷新显示控件 |
| pause / resume | 暂停或恢复接收日志 | 暂停期间日志直接丢弃 |
| clear | 清空已保存日志 | 不清空尚未落盘的队列 |
| bind | 将日志视图绑定到表格 | 仅支持 STTTable |
| exportAs | 同步导出 | 会阻塞调用线程并显示 UI 提示 |
| exportAsAsync | 名称为异步,当前实际不是独立异步线程 |
void add(
const string& page,
const string& widget,
const string& action,
const string& var,
const string& info
);
示例:
logMgr->add(
"Frm01",
"wMButtonExport",
"CLICK",
"ExportState",
"用户开始导出历史数据"
);
add 只把 INSERT SQL 放进内存队列。后台线程通常约 60 秒后写盘;需要立即查询或导出最新记录时,先调用 refresh。
关键行为:
因此输入至少应避免单引号,安全要求较高的项目应先修复底层为参数化 SQL。
void refresh();
logMgr->add("Frm01", "btnSave", "CLICK", "", "保存配方");
logMgr->refresh();
refresh 会立即执行当前待写 SQL,但不会让已绑定的 STTTable 自动重新查询。写盘后如要更新页面,还需调用表格的导航或刷新方法,例如 navFirst。
void pause();
void resume();
logMgr->pause();
// 此期间调用 add 的日志会丢弃
logMgr->resume();
适合在批量维护操作中临时禁止记录,但不能用作“缓冲后批量写入”。
static void clear();
clear 删除数据库中的持久化行并重置 SQLite 自增序列:
// 先决定如何处理尚未写盘的队列
logMgr->refresh();
OpLogMgr::clear();
table->navFirst();
注意:clear 不会清空内存中的待写队列。若直接 clear,后台 refresh 后可能把 clear 之前排队的记录重新插入。需要“彻底清空到当前时刻”时,应先暂停,调用 refresh,再 clear,最后根据业务决定何时 resume。
bool bind(STTWidget* wTable, bool isDesc = true);
STTTable* table = /* 页面中的日志表 */;
bool ok = logMgr->bind(table, true);
if (!ok) {
// 检查控件类型和数据库状态
}
isDesc=true 表示最新记录在前。当前实现只接受 STTTable,会 new 一个 TTableView 并绑定。头文件没有明确暴露该视图的所有权,页面反复绑定可能引入生命周期或泄漏问题,应尽量只绑定一次。
static bool exportAs(const string& path);
logMgr->refresh();
bool ok = OpLogMgr::exportAs("logs/oplog.csv");
该方法会同步查询全部日志到内存并写 CSV,调用期间可能阻塞 UI,同时显示成功或失败对话框。
路径规则:
| 平台 | path 的处理 |
|---|---|
| ARM | 在 path 前拼接检测到的 U 盘根路径 |
| Windows | 在 path 前拼接 ./data/ |
父目录不会自动创建。传 "logs/oplog.csv" 前应确保对应 logs 目录存在。大数据量会同时占用较多内存和 UI 时间。
static void exportAsAsync(const string& path);
logMgr->refresh();
OpLogMgr::exportAsAsync("/mnt/udisk/oplog.csv");
名称虽然是 Async,但当前实现直接调用 FileUtil::exportDB2CSV 的四参数同步重载:
因此不要假设它不会阻塞。需要真正异步时,应使用已修复的 FileUtil 异步重载,或在确保 Runtime/UI 生命周期安全的前提下由业务工作线程执行同步导出。
void Frm01::recordExportClick() {
OpLogMgr* logs = OpLogMgr::getInstance(getRuntime());
logs->add("Frm01", "btnExport", "CLICK", "", "开始导出");
logs->refresh();
}
bool Frm01::exportLogs() {
OpLogMgr* logs = OpLogMgr::getInstance(getRuntime());
logs->refresh();
// ARM 下 path 是 U 盘根目录内的相对路径
return OpLogMgr::exportAs("oplog.csv");
}