7.0.0 把主题、编译器路径这类设置写在注册表里。7.1.0 改成了本机 SQLite —— 理由很实际:私信要存、消息要标已读、还要记录「有没有同步到云端」,这些用键值对表达会很别扭,而注册表也不适合放这类结构化数据。
库文件位置
%APPDATA%\CDCExceed\cdc_data.db 设置 · 账号 · 私信
%APPDATA%\CDCExceed\cdc_ai.cfg AI 的 api_key / model / base_url(仅本机)表结构
static void InitDatabase() {
wstring dbfile = GetAppDataPath() + L"\\cdc_data.db";
if (sqlite3_open16(dbfile.c_str(), &g_db) != SQLITE_OK) { g_db = nullptr; return; }
const char* sql =
"CREATE TABLE IF NOT EXISTS users (user TEXT PRIMARY KEY, pass TEXT);"
"CREATE TABLE IF NOT EXISTS messages ("
" id INTEGER PRIMARY KEY AUTOINCREMENT, `from` TEXT, `to` TEXT,"
" content TEXT, ts INTEGER, read INTEGER DEFAULT 0, synced INTEGER DEFAULT 0);"
"CREATE TABLE IF NOT EXISTS config (key TEXT PRIMARY KEY, value TEXT);";
sqlite3_exec(g_db, sql, nullptr, nullptr, nullptr);
// 兼容旧库:老版本的 messages 表没有 synced 列,就地补上
if (!hasSynced)
sqlite3_exec(g_db, "ALTER TABLE messages ADD COLUMN synced INTEGER DEFAULT 0",
nullptr, nullptr, nullptr);
}synced 这一列是「同步到云端」的去重依据:只挑 synced = 0 的消息上传,成功后再置 1,重复同步不会把同一条私信发两遍。
键值读写
static wstring DbGet(const char* key, const wchar_t* fallback = L"") {
wstring val = fallback;
sqlite3_stmt* st = nullptr;
if (sqlite3_prepare_v2(g_db, "SELECT value FROM config WHERE key=?", -1, &st, nullptr) == SQLITE_OK) {
sqlite3_bind_text(st, 1, key, -1, SQLITE_STATIC);
if (sqlite3_step(st) == SQLITE_ROW) val = Utf8ToWstr((const char*)sqlite3_column_text(st, 0));
sqlite3_finalize(st);
}
return val;
}
static void DbSet(const char* key, const wstring& val) {
sqlite3_stmt* st = nullptr;
if (sqlite3_prepare_v2(g_db,
"INSERT INTO config(key,value) VALUES(?,?) "
"ON CONFLICT(key) DO UPDATE SET value=excluded.value", -1, &st, nullptr) == SQLITE_OK) {
sqlite3_bind_text(st, 1, key, -1, SQLITE_STATIC);
sqlite3_bind_text16(st, 2, val.c_str(), -1, SQLITE_STATIC);
sqlite3_step(st);
sqlite3_finalize(st);
}
}- 全部走预编译语句 —— 键与值都作为参数绑定,不存在拼接字符串的注入问题
ON CONFLICT ... DO UPDATE一条语句完成「有则改、无则插」 —— 不需要先查后写,也就没有两次操作之间的竞态- 取不到值时返回调用方给的默认值 —— 首次运行、库文件被删都能正常启动,不会因为少一个键就崩
旧版残留的清理
// 旧版曾写入 HKCU\Software\TestDataGenerator;当前版本改用 SQLite,
// 此处做一次性清理,避免脏数据一直留在用户机器上。
static void CleanLegacyRegistry() {
HKEY hK;
if (RegOpenKeyExW(HKEY_CURRENT_USER, L"Software\\TestDataGenerator",
0, KEY_READ | KEY_WRITE, &hK) == ERROR_SUCCESS) {
RegCloseKey(hK);
RegDeleteTreeW(HKEY_CURRENT_USER, L"Software\\TestDataGenerator");
}
}