refactor(backend): replace unsafe unwrap calls with proper error handling

- Add to_json_string helper for safe JSON serialization
- Add lock_conn macro for safe Mutex locking
- Replace 41 unwrap() calls with proper error handling:
  - database.rs: JSON serialization and Mutex operations (31 fixes)
  - lib.rs: macOS NSWindow and tray icon handling (3 fixes)
  - services/provider.rs: Claude model normalization (1 fix)
  - services/prompt.rs: timestamp generation (3 fixes)
  - services/skill.rs: directory name extraction (2 fixes)
  - mcp.rs: HashMap initialization and type conversions (5 fixes)
  - app_config.rs: timestamp fallback (1 fix)

This improves application stability and prevents potential panics.
This commit is contained in:
YoVinchen
2025-11-23 16:23:18 +08:00
parent be1c2ac76e
commit 0cb8b30f15
7 changed files with 332 additions and 74 deletions

View File

@@ -7,6 +7,14 @@ use crate::prompt::Prompt;
use crate::prompt_files::prompt_file_path;
use crate::store::AppState;
/// 安全地获取当前 Unix 时间戳
fn get_unix_timestamp() -> Result<i64, AppError> {
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_secs() as i64)
.map_err(|e| AppError::Message(format!("Failed to get system time: {e}")))
}
pub struct PromptService;
impl PromptService {
@@ -64,10 +72,7 @@ impl PromptService {
.find(|(_, p)| p.enabled)
.map(|(id, p)| (id.clone(), p))
{
let timestamp = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs() as i64;
let timestamp = get_unix_timestamp()?;
enabled_prompt.content = live_content.clone();
enabled_prompt.updated_at = Some(timestamp);
log::info!("回填 live 提示词内容到已启用项: {enabled_id}");
@@ -135,10 +140,7 @@ impl PromptService {
let content =
std::fs::read_to_string(&file_path).map_err(|e| AppError::io(&file_path, e))?;
let timestamp = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs() as i64;
let timestamp = get_unix_timestamp()?;
let id = format!("imported-{timestamp}");
let prompt = Prompt {
@@ -167,4 +169,56 @@ impl PromptService {
std::fs::read_to_string(&file_path).map_err(|e| AppError::io(&file_path, e))?;
Ok(Some(content))
}
/// 首次启动时从现有提示词文件自动导入(如果存在)
/// 返回导入的数量
pub fn import_from_file_on_first_launch(
state: &AppState,
app: AppType,
) -> Result<usize, AppError> {
let file_path = prompt_file_path(&app)?;
// 检查文件是否存在
if !file_path.exists() {
return Ok(0);
}
// 读取文件内容
let content = match std::fs::read_to_string(&file_path) {
Ok(c) => c,
Err(e) => {
log::warn!("读取提示词文件失败: {file_path:?}, 错误: {e}");
return Ok(0);
}
};
// 检查内容是否为空
if content.trim().is_empty() {
return Ok(0);
}
log::info!("发现提示词文件,自动导入: {file_path:?}");
// 创建提示词对象
let timestamp = get_unix_timestamp()?;
let id = format!("auto-imported-{timestamp}");
let prompt = Prompt {
id: id.clone(),
name: format!(
"Auto-imported Prompt {}",
chrono::Local::now().format("%Y-%m-%d %H:%M")
),
content,
description: Some("Automatically imported on first launch".to_string()),
enabled: true, // 首次导入时自动启用
created_at: Some(timestamp),
updated_at: Some(timestamp),
};
// 保存到数据库
state.db.save_prompt(app.as_str(), &prompt)?;
log::info!("自动导入完成: {}", app.as_str());
Ok(1)
}
}

View File

@@ -424,9 +424,9 @@ impl ProviderService {
/// 归一化 Claude 模型键:读旧键(ANTHROPIC_SMALL_FAST_MODEL),写新键(DEFAULT_*), 并删除旧键
fn normalize_claude_models_in_value(settings: &mut Value) -> bool {
let mut changed = false;
let env = match settings.get_mut("env") {
Some(v) if v.is_object() => v.as_object_mut().unwrap(),
_ => return changed,
let env = match settings.get_mut("env").and_then(|v| v.as_object_mut()) {
Some(obj) => obj,
None => return changed,
};
let model = env

View File

@@ -231,7 +231,12 @@ impl SkillService {
// 解析技能元数据
match self.parse_skill_metadata(&skill_md) {
Ok(meta) => {
let directory = path.file_name().unwrap().to_string_lossy().to_string();
// 安全地获取目录名
let Some(dir_name) = path.file_name() else {
log::warn!("Failed to get directory name from path: {path:?}");
continue;
};
let directory = dir_name.to_string_lossy().to_string();
// 构建 README URL考虑 skillsPath
let readme_path = if let Some(ref skills_path) = repo.skills_path {
@@ -305,7 +310,12 @@ impl SkillService {
continue;
}
let directory = path.file_name().unwrap().to_string_lossy().to_string();
// 安全地获取目录名
let Some(dir_name) = path.file_name() else {
log::warn!("Failed to get directory name from path: {path:?}");
continue;
};
let directory = dir_name.to_string_lossy().to_string();
// 更新已安装状态
let mut found = false;