refactor(backend): implement transaction mechanism and i18n errors for provider service

This commit completes phase 4 service layer extraction by introducing:

1. **Transaction mechanism with 2PC (Two-Phase Commit)**:
   - Introduced `run_transaction()` wrapper with snapshot-based rollback
   - Implemented `LiveSnapshot` enum to capture and restore live config files
   - Added `PostCommitAction` to separate config.json persistence from live file writes
   - Applied to critical operations: add, update, switch providers
   - Ensures atomicity: memory + config.json + live files stay consistent

2. **Internationalized error handling**:
   - Added `AppError::Localized` variant with key + zh + en messages
   - Implemented `AppError::localized()` helper function
   - Migrated 24 error sites to use i18n-ready errors
   - Enables frontend to display errors in user's preferred language

3. **Concurrency optimization**:
   - Fixed `get_custom_endpoints()` to use read lock instead of write lock
   - Ensured async IO operations (usage query) execute outside lock scope
   - Added defensive RAII lock management with explicit scope blocks

4. **Code organization improvements**:
   - Reduced commands/provider.rs from ~800 to ~320 lines (-60%)
   - Expanded services/provider.rs with transaction infrastructure
   - Added unit tests for validation and credential extraction
   - Documented legacy file cleanup logic with inline comments

5. **Backfill mechanism refinement**:
   - Ensured live config is synced back to memory before switching
   - Maintains SSOT (Single Source of Truth) architecture principle
   - Handles Codex dual-file (auth.json + config.toml) atomically

Breaking changes: None (internal refactoring only)
Performance: Improved read concurrency, no measurable overhead from snapshots
Test coverage: Added validation tests, updated service layer tests
This commit is contained in:
Jason
2025-10-28 17:47:15 +08:00
parent 88a952023f
commit 5c3aca18eb
6 changed files with 1005 additions and 559 deletions

View File

@@ -46,6 +46,12 @@ pub enum AppError {
McpValidation(String),
#[error("{0}")]
Message(String),
#[error("{zh} ({en})")]
Localized {
key: &'static str,
zh: String,
en: String,
},
}
impl AppError {
@@ -69,6 +75,14 @@ impl AppError {
source,
}
}
pub fn localized(key: &'static str, zh: impl Into<String>, en: impl Into<String>) -> Self {
Self::Localized {
key,
zh: zh.into(),
en: en.into(),
}
}
}
impl<T> From<PoisonError<T>> for AppError {