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:
75
crates/zclaw-saas/src/models/account.rs
Normal file
75
crates/zclaw-saas/src/models/account.rs
Normal 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,
|
||||
}
|
||||
15
crates/zclaw-saas/src/models/api_token.rs
Normal file
15
crates/zclaw-saas/src/models/api_token.rs
Normal 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,
|
||||
}
|
||||
33
crates/zclaw-saas/src/models/config.rs
Normal file
33
crates/zclaw-saas/src/models/config.rs
Normal 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,
|
||||
}
|
||||
15
crates/zclaw-saas/src/models/device.rs
Normal file
15
crates/zclaw-saas/src/models/device.rs
Normal 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,
|
||||
}
|
||||
33
crates/zclaw-saas/src/models/mod.rs
Normal file
33
crates/zclaw-saas/src/models/mod.rs
Normal 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::*;
|
||||
34
crates/zclaw-saas/src/models/model.rs
Normal file
34
crates/zclaw-saas/src/models/model.rs
Normal 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,
|
||||
}
|
||||
14
crates/zclaw-saas/src/models/permission_template.rs
Normal file
14
crates/zclaw-saas/src/models/permission_template.rs
Normal 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,
|
||||
}
|
||||
31
crates/zclaw-saas/src/models/prompt.rs
Normal file
31
crates/zclaw-saas/src/models/prompt.rs
Normal 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,
|
||||
}
|
||||
18
crates/zclaw-saas/src/models/provider.rs
Normal file
18
crates/zclaw-saas/src/models/provider.rs
Normal 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,
|
||||
}
|
||||
33
crates/zclaw-saas/src/models/provider_key.rs
Normal file
33
crates/zclaw-saas/src/models/provider_key.rs
Normal 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,
|
||||
}
|
||||
23
crates/zclaw-saas/src/models/relay_task.rs
Normal file
23
crates/zclaw-saas/src/models/relay_task.rs
Normal 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,
|
||||
}
|
||||
15
crates/zclaw-saas/src/models/role.rs
Normal file
15
crates/zclaw-saas/src/models/role.rs
Normal 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,
|
||||
}
|
||||
24
crates/zclaw-saas/src/models/telemetry.rs
Normal file
24
crates/zclaw-saas/src/models/telemetry.rs
Normal 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,
|
||||
}
|
||||
22
crates/zclaw-saas/src/models/usage.rs
Normal file
22
crates/zclaw-saas/src/models/usage.rs
Normal 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,
|
||||
}
|
||||
Reference in New Issue
Block a user