Skip to main content

sz_rust_cli/cmd/
scheduler.rs

1//! `scheduler:*` 命令 — 接入 sz-orm-scheduler
2//!
3//! ## 命令列表
4//!
5//! - `scheduler:list` — 列出已注册的调度任务
6//! - `scheduler:run` — 立即执行一次到期任务
7//! - `scheduler:start` — 启动调度器(持续运行)
8//!
9//! ## PHP 对齐
10//!
11//! PHP ThinkPHP 6 通过 `think\swoole\crontab\Crontab` 注册定时任务。
12//! Rust 端复用 `sz-orm-scheduler::CronScheduler`。
13
14use std::sync::Arc;
15
16use clap::Subcommand;
17
18use sz_orm_scheduler::Scheduler;
19
20use crate::error::CliError;
21
22/// `scheduler` 子命令枚举
23#[derive(Subcommand, Debug)]
24pub enum SchedulerCommand {
25    /// 列出已注册的调度任务
26    #[command(name = "list")]
27    List,
28
29    /// 立即执行一次到期任务(对齐 `php think scheduler:run`)
30    #[command(name = "run")]
31    Run,
32
33    /// 启动调度器(持续运行,对齐 `php think scheduler:start`)
34    #[command(name = "start")]
35    Start {
36        /// 调度器 tick 间隔(毫秒,默认 1000)
37        #[arg(short = 't', long, default_value = "1000")]
38        tick_ms: u64,
39    },
40}
41
42/// 执行 scheduler 子命令
43pub fn execute(cmd: &SchedulerCommand) -> Result<(), CliError> {
44    let scheduler = sz_orm_scheduler::CronScheduler::new();
45
46    match cmd {
47        SchedulerCommand::List => execute_list(&scheduler),
48        SchedulerCommand::Run => execute_run(&scheduler),
49        SchedulerCommand::Start { tick_ms } => execute_start(&scheduler, *tick_ms),
50    }
51}
52
53/// 执行 scheduler:list
54fn execute_list(scheduler: &sz_orm_scheduler::CronScheduler) -> Result<(), CliError> {
55    let tasks = scheduler.list_tasks();
56
57    if tasks.is_empty() {
58        println!("No scheduled tasks registered.");
59        println!();
60        println!("To register tasks, create a scheduler configuration file");
61        println!("or use the scheduler API in your application code.");
62        return Ok(());
63    }
64
65    println!(
66        "{:<15} {:<20} {:<25} {:<8}",
67        "ID", "Name", "Cron", "Enabled"
68    );
69    println!("{}", "-".repeat(70));
70
71    for task in &tasks {
72        println!(
73            "{:<15} {:<20} {:<25} {:<8}",
74            task.id, task.name, task.cron_expr, task.enabled
75        );
76    }
77
78    println!();
79    println!("Total: {} task(s)", tasks.len());
80    Ok(())
81}
82
83/// 执行 scheduler:run
84///
85/// 触发一次到期任务的执行(对齐 PHP `scheduler:run`)
86fn execute_run(scheduler: &sz_orm_scheduler::CronScheduler) -> Result<(), CliError> {
87    let now = chrono::Utc::now();
88    let fired = scheduler.try_fire_due(now);
89    println!("Scheduler run: {} task(s) fired at {}", fired, now);
90    Ok(())
91}
92
93/// 执行 scheduler:start
94///
95/// 启动调度器并持续运行(对齐 PHP `scheduler:start`)
96fn execute_start(
97    scheduler: &sz_orm_scheduler::CronScheduler,
98    tick_ms: u64,
99) -> Result<(), CliError> {
100    println!("Starting scheduler with tick interval: {}ms", tick_ms);
101    println!("Press Ctrl+C to stop.");
102
103    // 注册示例任务(演示用途)
104    register_demo_tasks(scheduler)?;
105
106    // 启动调度器
107    scheduler
108        .start(tick_ms)
109        .map_err(|e| CliError::Scheduler(e.to_string()))?;
110
111    println!("Scheduler started. Waiting for tasks to fire...");
112
113    // 阻塞主线程,直到收到 Ctrl+C 信号
114    // 注意:这是简化实现,生产环境应使用 tokio::signal::ctrl_c()
115    loop {
116        std::thread::sleep(std::time::Duration::from_secs(1));
117    }
118}
119
120/// 注册演示任务
121///
122/// 展示调度器功能,实际应用中应由用户代码注册。
123fn register_demo_tasks(scheduler: &sz_orm_scheduler::CronScheduler) -> Result<(), CliError> {
124    // 示例任务 1:每分钟执行(5 字段 cron:second minute hour day month)
125    let task1 = sz_orm_scheduler::ScheduledTask::new("demo-1", "每分钟任务", "0 * * * *")
126        .with_callback("demo::minute_task");
127    scheduler
128        .schedule(task1)
129        .map_err(|e| CliError::Scheduler(e.to_string()))?;
130
131    // 示例任务 2:每小时执行
132    let task2 = sz_orm_scheduler::ScheduledTask::new("demo-2", "每小时任务", "0 0 * * *")
133        .with_callback("demo::hourly_task");
134    scheduler
135        .schedule(task2)
136        .map_err(|e| CliError::Scheduler(e.to_string()))?;
137
138    // 注册处理器
139    let handler: Arc<dyn sz_orm_scheduler::JobHandler> = Arc::new(DemoJobHandler);
140    scheduler.register_handler("demo-1", handler.clone());
141    scheduler.register_handler("demo-2", handler);
142
143    println!("Registered 2 demo tasks (demo-1, demo-2)");
144    Ok(())
145}
146
147/// 演示任务处理器
148struct DemoJobHandler;
149
150impl sz_orm_scheduler::JobHandler for DemoJobHandler {
151    fn handle(&self, task: &sz_orm_scheduler::ScheduledTask) -> Result<(), String> {
152        println!(
153            "[{}] Task fired: {} ({})",
154            chrono::Utc::now(),
155            task.name,
156            task.id
157        );
158        Ok(())
159    }
160}
161
162#[cfg(test)]
163mod tests {
164    use super::*;
165    use sz_orm_scheduler::JobHandler;
166    use sz_orm_scheduler::Scheduler;
167
168    #[test]
169    fn test_execute_list_empty() {
170        let scheduler = sz_orm_scheduler::CronScheduler::new();
171        let result = execute_list(&scheduler);
172        assert!(result.is_ok());
173    }
174
175    #[test]
176    fn test_execute_list_with_tasks() {
177        let scheduler = sz_orm_scheduler::CronScheduler::new();
178        let task = sz_orm_scheduler::ScheduledTask::new("test-1", "测试任务", "0 * * * *");
179        scheduler.schedule(task).unwrap();
180
181        let result = execute_list(&scheduler);
182        assert!(result.is_ok());
183    }
184
185    #[test]
186    fn test_execute_run() {
187        let scheduler = sz_orm_scheduler::CronScheduler::new();
188        let result = execute_run(&scheduler);
189        assert!(result.is_ok());
190    }
191
192    #[test]
193    fn test_register_demo_tasks() {
194        let scheduler = sz_orm_scheduler::CronScheduler::new();
195        let result = register_demo_tasks(&scheduler);
196        assert!(result.is_ok());
197
198        let tasks = scheduler.list_tasks();
199        assert_eq!(tasks.len(), 2);
200    }
201
202    #[test]
203    fn test_register_demo_tasks_with_invalid_cron() {
204        let scheduler = sz_orm_scheduler::CronScheduler::new();
205        // 直接测试无效 cron 表达式的 schedule 调用
206        let task = sz_orm_scheduler::ScheduledTask::new("bad", "bad", "");
207        let result = scheduler.schedule(task);
208        assert!(result.is_err());
209    }
210
211    #[test]
212    fn test_demo_job_handler() {
213        let handler = DemoJobHandler;
214        let task = sz_orm_scheduler::ScheduledTask::new("test", "测试", "0 * * * *");
215        let result = handler.handle(&task);
216        assert!(result.is_ok());
217    }
218}