mirror of
https://github.com/jackwener/wx-cli.git
synced 2026-10-08 21:15:46 +00:00
fix: 深度 review 修复 10 个 bug/问题
Critical & High: - daemon 日志:启动时将 stdout/stderr 重定向到 ~/.wx-cli/daemon.log 而非 /dev/null,使 wx daemon logs 真正可用 - q_history 找不到聊天时改为 bail! 而非 ok:true+error 字段, 避免 CLI 静默返回空输出 - init 写 config.json 默认路径改为 ~/.wx-cli/config.json, 避免写入系统 bin 目录(/usr/local/bin/config.json) - LIKE 通配符:搜索关键词中的 %/_/\ 现在正确转义 - WAL 路径:改用 OsString.push 拼接 "-wal" 后缀, 避免 display() 在非 UTF-8 路径上失效 - cmd_stop:检查 kill() 返回值,ESRCH 时给出明确提示 Performance & Code quality: - full_decrypt:改为流式逐页读写,峰值内存从 2×文件大小降为 O(1) - Regex:msg_table_re() 用 OnceLock 静态编译,避免热路径重复编译 - mtime_nanos:消除 daemon/mod.rs 与 cache.rs 的重复定义 - use super::super::cli::transport → use super::transport - 删除未使用的 save_config、Request::to_json_line 死代码
This commit is contained in:
+11
-6
@@ -80,11 +80,10 @@ impl DbCache {
|
||||
continue;
|
||||
}
|
||||
let db_path = self.db_dir.join(rel_key.replace('\\', std::path::MAIN_SEPARATOR_STR).replace('/', std::path::MAIN_SEPARATOR_STR));
|
||||
let wal_path_str = format!("{}-wal", db_path.display());
|
||||
let wal_path = Path::new(&wal_path_str);
|
||||
let wal_path = wal_path_for(&db_path);
|
||||
|
||||
let db_mt = mtime_nanos(&db_path);
|
||||
let wal_mt = if wal_path.exists() { mtime_nanos(wal_path) } else { 0 };
|
||||
let wal_mt = if wal_path.exists() { mtime_nanos(&wal_path) } else { 0 };
|
||||
|
||||
if db_mt == entry.db_mt && wal_mt == entry.wal_mt {
|
||||
inner.insert(rel_key.clone(), CacheEntry {
|
||||
@@ -135,8 +134,7 @@ impl DbCache {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let wal_path_str = format!("{}-wal", db_path.display());
|
||||
let wal_path = Path::new(&wal_path_str).to_path_buf();
|
||||
let wal_path = wal_path_for(&db_path);
|
||||
|
||||
let db_mt = mtime_nanos(&db_path);
|
||||
let wal_mt = if wal_path.exists() { mtime_nanos(&wal_path) } else { 0 };
|
||||
@@ -195,13 +193,20 @@ impl DbCache {
|
||||
}
|
||||
}
|
||||
|
||||
fn mtime_nanos(path: &Path) -> u64 {
|
||||
pub(super) fn mtime_nanos(path: &Path) -> u64 {
|
||||
std::fs::metadata(path)
|
||||
.and_then(|m| m.modified())
|
||||
.map(|t| t.duration_since(std::time::UNIX_EPOCH).unwrap_or_default().as_nanos() as u64)
|
||||
.unwrap_or(0)
|
||||
}
|
||||
|
||||
/// `foo/bar.db` → `foo/bar.db-wal`(用 OsString 拼接,避免 display() 的 UTF-8 问题)
|
||||
fn wal_path_for(db_path: &Path) -> PathBuf {
|
||||
let mut name = db_path.file_name().unwrap_or_default().to_os_string();
|
||||
name.push("-wal");
|
||||
db_path.with_file_name(name)
|
||||
}
|
||||
|
||||
fn hex_to_32bytes(s: &str) -> Result<[u8; 32]> {
|
||||
if s.len() != 64 {
|
||||
anyhow::bail!("密钥 hex 长度应为 64,实际为 {}", s.len());
|
||||
|
||||
+1
-6
@@ -208,12 +208,7 @@ async fn run_watcher(
|
||||
}
|
||||
}
|
||||
|
||||
fn mtime_nanos(path: &std::path::Path) -> u64 {
|
||||
std::fs::metadata(path)
|
||||
.and_then(|m| m.modified())
|
||||
.map(|t| t.duration_since(std::time::UNIX_EPOCH).unwrap_or_default().as_nanos() as u64)
|
||||
.unwrap_or(0)
|
||||
}
|
||||
use cache::mtime_nanos;
|
||||
|
||||
fn decompress_or_str(data: &[u8]) -> String {
|
||||
if data.is_empty() { return String::new(); }
|
||||
|
||||
+13
-4
@@ -4,9 +4,16 @@ use regex::Regex;
|
||||
use rusqlite::Connection;
|
||||
use serde_json::{json, Value};
|
||||
use std::collections::HashMap;
|
||||
use std::sync::OnceLock;
|
||||
|
||||
use super::cache::DbCache;
|
||||
|
||||
/// 静态编译的 Msg 表名正则,避免在热路径中重复编译
|
||||
fn msg_table_re() -> &'static Regex {
|
||||
static RE: OnceLock<Regex> = OnceLock::new();
|
||||
RE.get_or_init(|| Regex::new(r"^Msg_[0-9a-f]{32}$").unwrap())
|
||||
}
|
||||
|
||||
/// 联系人名称缓存
|
||||
#[derive(Clone)]
|
||||
pub struct Names {
|
||||
@@ -141,7 +148,7 @@ pub async fn q_history(
|
||||
|
||||
let tables = find_msg_tables(db, names, &username).await?;
|
||||
if tables.is_empty() {
|
||||
return Ok(json!({ "error": format!("找不到 {} 的消息记录", display) }));
|
||||
anyhow::bail!("找不到 {} 的消息记录", display);
|
||||
}
|
||||
|
||||
let mut all_msgs: Vec<Value> = Vec::new();
|
||||
@@ -218,7 +225,7 @@ pub async fn q_search(
|
||||
.filter_map(|r| r.ok())
|
||||
.collect();
|
||||
|
||||
let re = Regex::new(r"^Msg_[0-9a-f]{32}$").unwrap();
|
||||
let re = msg_table_re();
|
||||
let mut result = Vec::new();
|
||||
for tname in table_names {
|
||||
if !re.is_match(&tname) {
|
||||
@@ -483,8 +490,10 @@ fn search_in_table(
|
||||
limit: usize,
|
||||
) -> Result<Vec<Value>> {
|
||||
let id2u = load_id2u(conn);
|
||||
let mut clauses = vec!["message_content LIKE ?".to_string()];
|
||||
let mut params: Vec<Box<dyn rusqlite::types::ToSql>> = vec![Box::new(format!("%{}%", keyword))];
|
||||
// 转义 LIKE 通配符,使用 '\' 作为 ESCAPE 字符
|
||||
let escaped_kw = keyword.replace('\\', "\\\\").replace('%', "\\%").replace('_', "\\_");
|
||||
let mut clauses = vec!["message_content LIKE ? ESCAPE '\\'".to_string()];
|
||||
let mut params: Vec<Box<dyn rusqlite::types::ToSql>> = vec![Box::new(format!("%{}%", escaped_kw))];
|
||||
if let Some(s) = since {
|
||||
clauses.push("create_time >= ?".into());
|
||||
params.push(Box::new(s));
|
||||
|
||||
Reference in New Issue
Block a user