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:
88
crates/zclaw-saas/src/tasks/mod.rs
Normal file
88
crates/zclaw-saas/src/tasks/mod.rs
Normal file
@@ -0,0 +1,88 @@
|
||||
//! CLI Task 系统 — 借鉴 loco-rs 的 Task trait 模式
|
||||
//!
|
||||
//! 提供可手动执行的运维命令:
|
||||
//! - seed_admin — 创建管理员账号
|
||||
//! - cleanup_devices — 清理不活跃设备
|
||||
//! - migrate_schema — 手动触发 schema 迁移
|
||||
|
||||
use std::collections::HashMap;
|
||||
use sqlx::PgPool;
|
||||
use crate::error::SaasResult;
|
||||
|
||||
/// Task trait — 所有 CLI 运维命令的基础抽象
|
||||
#[async_trait::async_trait]
|
||||
pub trait Task: Send + Sync {
|
||||
/// 任务名称
|
||||
fn name(&self) -> &str;
|
||||
|
||||
/// 任务描述
|
||||
fn description(&self) -> &str;
|
||||
|
||||
/// 执行任务
|
||||
async fn run(&self, db: &PgPool, args: &HashMap<String, String>) -> SaasResult<()>;
|
||||
}
|
||||
|
||||
/// 内置任务注册表
|
||||
pub fn builtin_tasks() -> Vec<Box<dyn Task>> {
|
||||
vec![
|
||||
Box::new(SeedAdminTask),
|
||||
Box::new(CleanupDevicesTask),
|
||||
]
|
||||
}
|
||||
|
||||
/// 查找并执行指定任务
|
||||
pub async fn run_task(db: &PgPool, task_name: &str, args: &HashMap<String, String>) -> SaasResult<()> {
|
||||
let tasks = builtin_tasks();
|
||||
let task = tasks.into_iter()
|
||||
.find(|t| t.name() == task_name)
|
||||
.ok_or_else(|| crate::error::SaasError::NotFound(format!("Task '{}' not found", task_name)))?;
|
||||
|
||||
tracing::info!("Running task: {} — {}", task.name(), task.description());
|
||||
task.run(db, args).await
|
||||
}
|
||||
|
||||
// ============ 内置任务实现 ============
|
||||
|
||||
/// 创建管理员账号
|
||||
struct SeedAdminTask;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl Task for SeedAdminTask {
|
||||
fn name(&self) -> &str { "seed_admin" }
|
||||
fn description(&self) -> &str { "创建管理员账号(如不存在)" }
|
||||
|
||||
async fn run(&self, db: &PgPool, args: &HashMap<String, String>) -> SaasResult<()> {
|
||||
let username = args.get("username").map(|s| s.as_str()).unwrap_or("admin");
|
||||
let password = args.get("password")
|
||||
.ok_or_else(|| crate::error::SaasError::InvalidInput("Missing 'password' argument".into()))?;
|
||||
|
||||
// 临时设置环境变量让 db::seed_admin_account 使用
|
||||
std::env::set_var("ZCLAW_ADMIN_USERNAME", username);
|
||||
std::env::set_var("ZCLAW_ADMIN_PASSWORD", password);
|
||||
crate::db::seed_admin_account(db).await
|
||||
}
|
||||
}
|
||||
|
||||
/// 清理不活跃设备
|
||||
struct CleanupDevicesTask;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl Task for CleanupDevicesTask {
|
||||
fn name(&self) -> &str { "cleanup_devices" }
|
||||
fn description(&self) -> &str { "清理超过指定天数未活跃的设备" }
|
||||
|
||||
async fn run(&self, db: &PgPool, args: &HashMap<String, String>) -> SaasResult<()> {
|
||||
let cutoff_days: i64 = args.get("cutoff_days")
|
||||
.and_then(|v| v.parse().ok())
|
||||
.unwrap_or(90);
|
||||
|
||||
let cutoff = (chrono::Utc::now() - chrono::Duration::days(cutoff_days)).to_rfc3339();
|
||||
let result = sqlx::query("DELETE FROM devices WHERE last_seen_at < $1")
|
||||
.bind(&cutoff)
|
||||
.execute(db)
|
||||
.await?;
|
||||
|
||||
tracing::info!("Cleaned up {} inactive devices (>={} days)", result.rows_affected(), cutoff_days);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user