refactor(saas): 架构重构 + 性能优化 — 借鉴 loco-rs 模式

Phase 0: 知识库
- docs/knowledge-base/loco-rs-patterns.md — loco-rs 10 个可借鉴模式研究

Phase 1: 数据层重构
- crates/zclaw-saas/src/models/ — 15 个 FromRow 类型化模型
- Login 3 次查询合并为 1 次 AccountLoginRow 查询
- 所有 service 文件从元组解构迁移到 FromRow 结构体

Phase 2: Worker + Scheduler 系统
- crates/zclaw-saas/src/workers/ — Worker trait + 5 个具体实现
- crates/zclaw-saas/src/scheduler.rs — TOML 声明式调度器
- crates/zclaw-saas/src/tasks/ — CLI 任务系统

Phase 3: 性能修复
- Relay N+1 查询 → 精准 SQL (relay/handlers.rs)
- Config RwLock → AtomicU32 无锁 rate limit (state.rs, middleware.rs)
- SSE std::sync::Mutex → tokio::sync::Mutex (relay/service.rs)
- /auth/refresh 阻塞清理 → Scheduler 定期执行

Phase 4: 多环境配置
- config/saas-{development,production,test}.toml
- ZCLAW_ENV 环境选择 + ZCLAW_SAAS_CONFIG 精确覆盖
- scheduler 配置集成到 TOML
This commit is contained in:
iven
2026-03-29 19:21:48 +08:00
parent 5fdf96c3f5
commit 8b9d506893
64 changed files with 3348 additions and 520 deletions

View File

@@ -0,0 +1,75 @@
//! Account 表相关模型
use sqlx::FromRow;
/// accounts 表完整行 (含 last_login_at)
#[derive(Debug, FromRow)]
pub struct AccountRow {
pub id: String,
pub username: String,
pub email: String,
pub display_name: String,
pub role: String,
pub status: String,
pub totp_enabled: bool,
pub last_login_at: Option<String>,
pub created_at: String,
}
/// accounts 表行 (不含 last_login_at用于 auth/me 等场景)
#[derive(Debug, FromRow)]
pub struct AccountAuthRow {
pub id: String,
pub username: String,
pub email: String,
pub display_name: String,
pub role: String,
pub status: String,
pub totp_enabled: bool,
pub created_at: String,
}
/// Login 一次性查询行(合并用户信息 + password_hash + totp_secret
#[derive(Debug, FromRow)]
pub struct AccountLoginRow {
pub id: String,
pub username: String,
pub email: String,
pub display_name: String,
pub role: String,
pub status: String,
pub totp_enabled: bool,
pub password_hash: String,
pub totp_secret: Option<String>,
pub created_at: String,
}
/// operation_logs 表行
#[derive(Debug, FromRow)]
pub struct OperationLogRow {
pub id: i64,
pub account_id: Option<String>,
pub action: String,
pub target_type: Option<String>,
pub target_id: Option<String>,
pub details: Option<String>,
pub ip_address: Option<String>,
pub created_at: String,
}
/// Dashboard 统计聚合行
#[derive(Debug, FromRow)]
pub struct DashboardStatsRow {
pub total_accounts: i64,
pub active_accounts: i64,
pub active_providers: i64,
pub active_models: i64,
}
/// Dashboard 今日统计聚合行
#[derive(Debug, FromRow)]
pub struct DashboardTodayRow {
pub tasks_today: i64,
pub tokens_input: i64,
pub tokens_output: i64,
}

View File

@@ -0,0 +1,15 @@
//! api_tokens 表相关模型
use sqlx::FromRow;
/// api_tokens 表行 (用于列表查询)
#[derive(Debug, FromRow)]
pub struct ApiTokenRow {
pub id: String,
pub name: String,
pub token_prefix: String,
pub permissions: String,
pub last_used_at: Option<String>,
pub expires_at: Option<String>,
pub created_at: String,
}

View File

@@ -0,0 +1,33 @@
//! config_items + config_sync_log 表相关模型
use sqlx::FromRow;
/// config_items 表行
#[derive(Debug, FromRow)]
pub struct ConfigItemRow {
pub id: String,
pub category: String,
pub key_path: String,
pub value_type: String,
pub current_value: Option<String>,
pub default_value: Option<String>,
pub source: String,
pub description: Option<String>,
pub requires_restart: bool,
pub created_at: String,
pub updated_at: String,
}
/// config_sync_log 表行
#[derive(Debug, FromRow)]
pub struct ConfigSyncLogRow {
pub id: i64,
pub account_id: String,
pub client_fingerprint: String,
pub action: String,
pub config_keys: String,
pub client_values: Option<String>,
pub saas_values: Option<String>,
pub resolution: Option<String>,
pub created_at: String,
}

View File

@@ -0,0 +1,15 @@
//! devices 表相关模型
use sqlx::FromRow;
/// devices 表行
#[derive(Debug, FromRow)]
pub struct DeviceRow {
pub id: String,
pub device_id: String,
pub device_name: Option<String>,
pub platform: Option<String>,
pub app_version: Option<String>,
pub last_seen_at: String,
pub created_at: String,
}

View File

@@ -0,0 +1,33 @@
//! 类型化数据库模型 (sqlx::FromRow)
//!
//! 替代原始元组解构 `(String, String, ...)`,提供编译期字段检查。
//! 每个结构体对应一个数据库查询结果,字段名与 SQL 列名一致。
pub mod account;
pub mod api_token;
pub mod config;
pub mod device;
pub mod model;
pub mod permission_template;
pub mod prompt;
pub mod provider;
pub mod provider_key;
pub mod relay_task;
pub mod role;
pub mod telemetry;
pub mod usage;
// Re-export all row types for convenient access
pub use account::*;
pub use api_token::*;
pub use config::*;
pub use device::*;
pub use model::*;
pub use permission_template::*;
pub use prompt::*;
pub use provider::*;
pub use provider_key::*;
pub use relay_task::*;
pub use role::*;
pub use telemetry::*;
pub use usage::*;

View File

@@ -0,0 +1,34 @@
//! models + account_api_keys 表相关模型
use sqlx::FromRow;
/// models 表行
#[derive(Debug, FromRow)]
pub struct ModelRow {
pub id: String,
pub provider_id: String,
pub model_id: String,
pub alias: String,
pub context_window: i64,
pub max_output_tokens: i64,
pub supports_streaming: bool,
pub supports_vision: bool,
pub enabled: bool,
pub pricing_input: f64,
pub pricing_output: f64,
pub created_at: String,
pub updated_at: String,
}
/// account_api_keys 表行
#[derive(Debug, FromRow)]
pub struct AccountApiKeyRow {
pub id: String,
pub provider_id: String,
pub key_label: Option<String>,
pub permissions: String,
pub enabled: bool,
pub last_used_at: Option<String>,
pub created_at: String,
pub key_value: String,
}

View File

@@ -0,0 +1,14 @@
//! permission_templates 表相关模型
use sqlx::FromRow;
/// permission_templates 表行
#[derive(Debug, FromRow)]
pub struct PermissionTemplateRow {
pub id: String,
pub name: String,
pub description: Option<String>,
pub permissions: String,
pub created_at: String,
pub updated_at: String,
}

View File

@@ -0,0 +1,31 @@
//! prompt_templates + prompt_versions 表相关模型
use sqlx::FromRow;
/// prompt_templates 表行
#[derive(Debug, FromRow)]
pub struct PromptTemplateRow {
pub id: String,
pub name: String,
pub category: String,
pub description: Option<String>,
pub source: String,
pub current_version: i32,
pub status: String,
pub created_at: String,
pub updated_at: String,
}
/// prompt_versions 表行
#[derive(Debug, FromRow)]
pub struct PromptVersionRow {
pub id: String,
pub template_id: String,
pub version: i32,
pub system_prompt: String,
pub user_prompt_template: Option<String>,
pub variables: String,
pub changelog: Option<String>,
pub min_app_version: Option<String>,
pub created_at: String,
}

View File

@@ -0,0 +1,18 @@
//! providers 表相关模型
use sqlx::FromRow;
/// providers 表行
#[derive(Debug, FromRow)]
pub struct ProviderRow {
pub id: String,
pub name: String,
pub display_name: String,
pub base_url: String,
pub api_protocol: String,
pub enabled: bool,
pub rate_limit_rpm: Option<i64>,
pub rate_limit_tpm: Option<i64>,
pub created_at: String,
pub updated_at: String,
}

View File

@@ -0,0 +1,33 @@
//! provider_keys + key_usage_window 表相关模型
use sqlx::FromRow;
/// provider_keys 精选行 (用于 select_best_key)
#[derive(Debug, FromRow)]
pub struct ProviderKeySelectRow {
pub id: String,
pub key_value: String,
pub priority: i32,
pub max_rpm: Option<i64>,
pub max_tpm: Option<i64>,
pub quota_reset_interval: Option<String>,
}
/// provider_keys 完整行 (用于列表查询)
#[derive(Debug, FromRow)]
pub struct ProviderKeyRow {
pub id: String,
pub provider_id: String,
pub key_label: String,
pub priority: i32,
pub max_rpm: Option<i64>,
pub max_tpm: Option<i64>,
pub quota_reset_interval: Option<String>,
pub is_active: bool,
pub last_429_at: Option<String>,
pub cooldown_until: Option<String>,
pub total_requests: i64,
pub total_tokens: i64,
pub created_at: String,
pub updated_at: String,
}

View File

@@ -0,0 +1,23 @@
//! relay_tasks 表相关模型
use sqlx::FromRow;
/// relay_tasks 表行
#[derive(Debug, FromRow)]
pub struct RelayTaskRow {
pub id: String,
pub account_id: String,
pub provider_id: String,
pub model_id: String,
pub status: String,
pub priority: i64,
pub attempt_count: i64,
pub max_attempts: i64,
pub input_tokens: i64,
pub output_tokens: i64,
pub error_message: Option<String>,
pub queued_at: String,
pub started_at: Option<String>,
pub completed_at: Option<String>,
pub created_at: String,
}

View File

@@ -0,0 +1,15 @@
//! roles 表相关模型
use sqlx::FromRow;
/// roles 表行
#[derive(Debug, FromRow)]
pub struct RoleRow {
pub id: String,
pub name: String,
pub description: Option<String>,
pub permissions: String,
pub is_system: bool,
pub created_at: String,
pub updated_at: String,
}

View File

@@ -0,0 +1,24 @@
//! telemetry_reports 表相关模型
use sqlx::FromRow;
/// telemetry 按 model 分组统计
#[derive(Debug, FromRow)]
pub struct TelemetryModelStatsRow {
pub model_id: String,
pub request_count: i64,
pub input_tokens: i64,
pub output_tokens: i64,
pub avg_latency_ms: Option<f64>,
pub success_rate: Option<f64>,
}
/// telemetry 按天分组统计
#[derive(Debug, FromRow)]
pub struct TelemetryDailyStatsRow {
pub day: String,
pub request_count: i64,
pub input_tokens: i64,
pub output_tokens: i64,
pub unique_devices: i64,
}

View File

@@ -0,0 +1,22 @@
//! usage_records 表相关聚合模型
use sqlx::FromRow;
/// usage 按 model 分组统计
#[derive(Debug, FromRow)]
pub struct UsageByModelRow {
pub provider_id: String,
pub model_id: String,
pub request_count: i64,
pub input_tokens: i64,
pub output_tokens: i64,
}
/// usage 按天分组统计
#[derive(Debug, FromRow)]
pub struct UsageByDayRow {
pub day: String,
pub request_count: i64,
pub input_tokens: i64,
pub output_tokens: i64,
}