mcpField
C
cloudflare-cron-triggers

cloudflare-cron-triggers

github.com/jackspace云与 DevOps1646 次安装12 周前更新
代码仓库

关于 cloudflare-cron-triggers

面向 Cloudflare Workers 开发者的 Cron Triggers 配置与开发指南,涵盖计划任务处理器、Cron 表达式、Wrangler 配置和本地测试。也介绍多计划任务、环境绑定及与 Workflows 配合的模式。

主要功能

  1. 01配置 scheduled 处理器和 Cron 触发器
  2. 02编写五字段 Cron 表达式
  3. 03使用 Wrangler 本地测试
  4. 04按触发器区分计划任务
  5. 05将定时任务接入 Workflows

适用场景

  1. 01按小时或日程运行 Worker 任务
  2. 02本地验证 scheduled 处理器
  3. 03为不同任务设置多个执行计划
  4. 04定时启动多步骤工作流

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/jackspace/claudeskillz --skill cloudflare-cron-triggers

安装后在 Claude Code 中输入/cloudflare-cron-triggers确认已加载。

附带文件README.mdSKILL.json

详细介绍

Cloudflare Cron Triggers

状态:生产就绪 ✅ 最后更新:2025-10-23 依赖项:cloudflare-worker-base(用于 Worker 设置) 最新版本:[email protected]、@cloudflare/[email protected]


快速入门(5 分钟)

1. 向 Worker 添加定时处理程序

src/index.ts:

export default {
  async scheduled(
    controller: ScheduledController,
    env: Env,
    ctx: ExecutionContext
  ): Promise<void> {
    console.log('Cron job executed at:', new Date(controller.scheduledTime));
    console.log('Triggered by cron:', controller.cron);

    // Your scheduled task logic here
    await doPeriodicTask(env);
  },
};

这很重要的原因:

  • 处理程序必须准确命名为 scheduled(不能是 scheduledHandler 或 onScheduled)
  • 必须在默认导出对象中导出
  • 必须使用 ES 模块格式(不能使用 Service Worker 格式)
2. 在 Wrangler 中配置 Cron Trigger

wrangler.jsonc:

{
  "name": "my-scheduled-worker",
  "main": "src/index.ts",
  "compatibility_date": "2025-10-23",
  "triggers": {
    "crons": [
      "0 * * * *"  // Every hour at minute 0
    ]
  }
}

重要:

  • Cron 表达式使用 5 个字段:minute hour day-of-month month day-of-week
  • 所有时间都只能使用 UTC(不支持时区转换)
  • 更改最多需要 15 分钟才能传播到全球
3. 在本地测试
# Enable scheduled testing
npx wrangler dev --test-scheduled

# In another terminal, trigger the scheduled handler
curl "http://localhost:8787/__scheduled?cron=0+*+*+*+*"

# View output in wrangler dev terminal

测试提示:

  • /__scheduled 端点仅在使用 --test-scheduled 标志时可用
  • 可以在查询参数中传入任意 Cron 表达式
  • Python Workers 使用 /cdn-cgi/handler/scheduled
4. 部署
npm run deploy
# or
npx wrangler deploy

部署后:

  • 更改最多可能需要 15 分钟才能传播
  • 查看仪表板:Workers & Pages > [Your Worker] > Cron Triggers
  • 在 Logs 标签页中查看过去的执行记录

Cron 表达式语法

五字段格式
* * * * *
│ │ │ │ │
│ │ │ │ └─── Day of Week (0-6, Sunday=0)
│ │ │ └───── Month (1-12)
│ │ └─────── Day of Month (1-31)
│ └───────── Hour (0-23)
└─────────── Minute (0-59)
特殊字符
字符含义示例
*每个* * * * * = 每分钟
,列表0,30 * * * * = 每小时的 :00 和 :30
-范围0 9-17 * * * = 每天上午 9 点到下午 5 点之间的每小时
/步长*/15 * * * * = 每 15 分钟
常用模式
# Every minute
* * * * *

# Every 5 minutes
*/5 * * * *

# Every 15 minutes
*/15 * * * *

# Every hour at minute 0
0 * * * *

# Every hour at minute 30
30 * * * *

# Every 6 hours
0 */6 * * *

# Every day at midnight (00:00 UTC)
0 0 * * *

# Every day at noon (12:00 UTC)
0 12 * * *

# Every day at 3:30am UTC
30 3 * * *

# Every Monday at 9am UTC
0 9 * * 1

# Every weekday at 9am UTC
0 9 * * 1-5

# Every Sunday at midnight UTC
0 0 * * 0

# First day of every month at midnight UTC
0 0 1 * *

# Twice a day (6am and 6pm UTC)
0 6,18 * * *

# Every 30 minutes during business hours (9am-5pm UTC, weekdays)
*/30 9-17 * * 1-5

重要:仅支持 UTC 时区

  • 所有 Cron Trigger 均按 UTC 时间执行
  • 不支持时区转换
  • 请手动将本地时间转换为 UTC
  • 示例:PST 上午 9 点 = UTC 下午 5 点(夏令时期间为次日)

ScheduledController 接口

interface ScheduledController {
  readonly cron: string;           // The cron expression that triggered this execution
  readonly type: string;           // Always "scheduled"
  readonly scheduledTime: number;  // Unix timestamp (ms) when scheduled
}
属性
controller.cron(string)

触发此次执行的 Cron 表达式。

export default {
  async scheduled(controller: ScheduledController, env: Env): Promise<void> {
    console.log(`Triggered by: ${controller.cron}`);
    // Output: "Triggered by: 0 * * * *"
  },
};

**使用场景:**区分多个 Cron 计划(参见“多个 Cron Trigger”模式)。

controller.type(string)

Cron 触发的执行始终返回 "scheduled"。

if (controller.type === 'scheduled') {
  // This is a cron-triggered execution
}
controller.scheduledTime(number)

计划执行时间的 Unix 时间戳(自纪元起的毫秒数)。

export default {
  async scheduled(controller: ScheduledController): Promise<void> {
    const scheduledDate = new Date(controller.scheduledTime);
    console.log(`Scheduled for: ${scheduledDate.toISOString()}`);
    // Output: "Scheduled for: 2025-10-23T15:00:00.000Z"
  },
};

注意:这是计划执行时间,而非实际执行时间。受系统负载影响,实际执行可能略有延迟(通常小于 1 秒)。


执行上下文

export default {
  async scheduled(
    controller: ScheduledController,
    env: Env,
    ctx: ExecutionContext  // ← Execution context
  ): Promise<void> {
    // Use ctx.waitUntil() for async operations that should complete
    ctx.waitUntil(logToAnalytics(env));
  },
};
ctx.waitUntil(promise: Promise<any>)

扩展执行上下文,使处理程序返回后仍会等待异步操作完成。

使用场景:

  • 向外部服务记录日志
  • 跟踪分析数据
  • 清理操作
  • 非关键后台任务
export default {
  async scheduled(controller: ScheduledController, env: Env, ctx: ExecutionContext): Promise<void> {
    // Critical task - must complete before handler exits
    await processData(env);

    // Non-critical tasks - can complete in background
    ctx.waitUntil(sendMetrics(env));
    ctx.waitUntil(cleanupOldData(env));
    ctx.waitUntil(notifySlack({ message: 'Cron completed' }));
  },
};

**重要:**第一个失败的 waitUntil() 将作为状态记录在仪表板日志中。


集成模式

1. 独立的定时 Worker

**适用场景:**仅按计划运行的 Worker(不处理 HTTP 请求)

// src/index.ts
interface Env {
  DB: D1Database;
  MY_BUCKET: R2Bucket;
}

export default {
  async scheduled(
    controller: ScheduledController,
    env: Env,
    ctx: ExecutionContext
  ): Promise<void> {
    console.log('Running scheduled maintenance...');

    // Database cleanup
    await env.DB.prepare('DELETE FROM sessions WHERE expires_at < ?')
      .bind(Date.now())
      .run();

    // Generate daily report
    const report = await generateDailyReport(env.DB);

    // Upload to R2
    await env.MY_BUCKET.put(
      `reports/${new Date().toISOString().split('T')[0]}.json`,
      JSON.stringify(report)
    );

    console.log('Maintenance complete');
  },
};

2. 与 Hono 结合(Fetch + Scheduled)

**适用场景:**同时处理 HTTP 请求和定时任务的 Worker

// src/index.ts
import { Hono } from 'hono';

interface Env {
  DB: D1Database;
}

const app = new Hono<{ Bindings: Env }>();

// Regular HTTP routes
app.get('/', (c) => c.text('Worker is running'));

app.get('/api/stats', async (c) => {
  const stats = await c.env.DB.prepare('SELECT COUNT(*) as count FROM users').first();
  return c.json(stats);
});

// Export both fetch handler and scheduled handler
export default {
  // Handle HTTP requests
  fetch: app.fetch,

  // Handle cron triggers
  async scheduled(
    controller: ScheduledController,
    env: Env,
    ctx: ExecutionContext
  ): Promise<void> {
    console.log('Cron triggered:', controller.cron);

    // Run scheduled task
    await updateCache(env.DB);

    // Log completion
    ctx.waitUntil(logExecution(controller.scheduledTime));
  },
};

采用此模式的原因:

  • 一个 Worker 同时处理两种使用场景
  • 共享环境绑定
  • 减少需要管理的 Worker 数量
  • 降低成本(只需一个 Worker 订阅)

3. 多个 Cron Trigger

**适用场景:**为不同任务设置不同计划

wrangler.jsonc:

{
  "triggers": {
    "crons": [
      "*/5 * * * *",    // Every 5 minutes
      "0 */6 * * *",    // Every 6 hours
      "0 0 * * *"       // Daily at midnight UTC
    ]
  }
}

src/index.ts:

export default {
  async scheduled(
    controller: ScheduledController,
    env: Env,
    ctx: ExecutionContext
  ): Promise<void> {
    // Route based on which cron triggered this execution
    switch (controller.cron) {
      case '*/5 * * * *':
        // Every 5 minutes: Check system health
        await checkSystemHealth(env);
        break;

      case '0 */6 * * *':
        // Every 6 hours: Sync data from external API
        await syncExternalData(env);
        break;

      case '0 0 * * *':
        // Daily at midnight: Generate reports and cleanup
        await generateDailyReports(env);
        await cleanupOldData(env);
        break;

      default:
        console.warn(`Unknown cron trigger: ${controller.cron}`);
    }
  },
};

重要:

  • 使用完全匹配的 Cron 表达式(空白字符也必须匹配)
  • 每个 Worker 最多支持 3 个 Cron Trigger(免费计划)
  • Standard/Paid 计划支持更多(请查看限制)

4. 访问环境绑定

定时处理程序可使用所有 Worker 绑定:

interface Env {
  // Databases
  DB: D1Database;

  // Storage
  MY_BUCKET: R2Bucket;
  KV_NAMESPACE: KVNamespace;

  // AI & Vectors
  AI: Ai;
  VECTOR_INDEX: VectorizeIndex;

  // Queues & Workflows
  MY_QUEUE: Queue;
  MY_WORKFLOW: Workflow;

  // Durable Objects
  RATE_LIMITER: DurableObjectNamespace;

  // Secrets
  API_KEY: string;
}

export default {
  async scheduled(controller: ScheduledController, env: Env): Promise<void> {
    // D1 Database
    const users = await env.DB.prepare('SELECT * FROM users WHERE active = 1').all();

    // R2 Storage
    const file = await env.MY_BUCKET.get('data.json');

    // KV Storage
    const config = await env.KV_NAMESPACE.get('config', 'json');

    // Workers AI
    const response = await env.AI.run('@cf/meta/llama-3-8b-instruct', {
      prompt: 'Summarize today\'s data',
    });

    // Send to Queue
    await env.MY_QUEUE.send({ type: 'process', data: users.results });

    // Trigger Workflow
    await env.MY_WORKFLOW.create({ input: { timestamp: Date.now() } });

    // Use secrets
    await fetch('https://api.example.com/webhook', {
      headers: { Authorization: `Bearer ${env.API_KEY}` },
    });
  },
};

5. 与工作流结合

最适合: 按计划触发的多步骤长时间运行任务

wrangler.jsonc:

{
  "triggers": {
    "crons": ["0 2 * * *"]  // Daily at 2am UTC
  },
  "workflows": [
    {
      "name": "daily-report-workflow",
      "binding": "DAILY_REPORT"
    }
  ]
}

src/index.ts:

interface Env {
  DAILY_REPORT: Workflow;
}

export default {
  async scheduled(controller: ScheduledController, env: Env): Promise<void> {
    console.log('Triggering daily report workflow...');

    // Trigger workflow with initial state
    const instance = await env.DAILY_REPORT.create({
      params: {
        date: new Date().toISOString().split('T')[0],
        reportType: 'daily-summary',
      },
    });

    console.log(`Workflow started: ${instance.id}`);
  },
};

为什么使用工作流:

  • 工作流可以运行数小时(cron 处理程序有 CPU 限制)
  • 内置重试和错误处理
  • 跨步骤持久化状态
  • 更适合复杂的多步骤流程

参考: Cloudflare Workflows 文档


6. 在定时处理程序中进行错误处理
export default {
  async scheduled(
    controller: ScheduledController,
    env: Env,
    ctx: ExecutionContext
  ): Promise<void> {
    try {
      // Main task
      await performScheduledTask(env);
    } catch (error) {
      // Log error
      console.error('Scheduled task failed:', error);

      // Send alert
      await sendAlert({
        worker: 'my-scheduled-worker',
        cron: controller.cron,
        error: error.message,
        timestamp: new Date(controller.scheduledTime).toISOString(),
      });

      // Store failure in database
      ctx.waitUntil(
        env.DB.prepare(
          'INSERT INTO cron_failures (cron, error, timestamp) VALUES (?, ?, ?)'
        )
          .bind(controller.cron, error.message, Date.now())
          .run()
      );

      // Re-throw to mark execution as failed
      throw error;
    }
  },
};

async function sendAlert(details: any): Promise<void> {
  await fetch('https://hooks.slack.com/services/YOUR/WEBHOOK/URL', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      text: `🚨 Cron job failed: ${details.worker}`,
      blocks: [
        {
          type: 'section',
          fields: [
            { type: 'mrkdwn', text: `*Worker:*\n${details.worker}` },
            { type: 'mrkdwn', text: `*Cron:*\n${details.cron}` },
            { type: 'mrkdwn', text: `*Error:*\n${details.error}` },
            { type: 'mrkdwn', text: `*Time:*\n${details.timestamp}` },
          ],
        },
      ],
    }),
  });
}

Wrangler 配置

基本配置
{
  "name": "my-scheduled-worker",
  "main": "src/index.ts",
  "compatibility_date": "2025-10-23",
  "triggers": {
    "crons": ["0 * * * *"]
  }
}

多个 Cron 触发器
{
  "triggers": {
    "crons": [
      "*/5 * * * *",     // Every 5 minutes
      "0 */6 * * *",     // Every 6 hours
      "0 2 * * *",       // Daily at 2am UTC
      "0 0 * * 1"        // Weekly on Monday at midnight UTC
    ]
  }
}

限制:

  • 免费版:最多 3 个 cron 计划
  • 付费版:限制更高(请查看当前限制)

特定环境的 Cron
{
  "name": "my-worker",
  "main": "src/index.ts",
  "env": {
    "dev": {
      "triggers": {
        "crons": ["*/5 * * * *"]  // Dev: every 5 minutes for testing
      }
    },
    "staging": {
      "triggers": {
        "crons": ["*/30 * * * *"]  // Staging: every 30 minutes
      }
    },
    "production": {
      "triggers": {
        "crons": ["0 * * * *"]  // Production: hourly
      }
    }
  }
}

部署特定环境:

# Deploy to dev
npx wrangler deploy --env dev

# Deploy to production
npx wrangler deploy --env production

移除所有 Cron 触发器
{
  "triggers": {
    "crons": []  // Empty array removes all crons
  }
}

部署后,Worker 将不再按计划执行。


测试与开发

使用 Wrangler 进行本地测试
# Start dev server with scheduled testing enabled
npx wrangler dev --test-scheduled

这会开放 /__scheduled 端点,用于触发定时处理程序。


触发定时处理程序
# Trigger with default cron (if only one configured)
curl "http://localhost:8787/__scheduled"

# Trigger with specific cron expression
curl "http://localhost:8787/__scheduled?cron=0+*+*+*+*"

# Trigger with URL-encoded cron
curl "http://localhost:8787/__scheduled?cron=*/5+*+*+*+*"

注意: 在 URL 中使用 + 代替空格,或正确进行 URL 编码。


验证处理程序输出
# Start dev server
npx wrangler dev --test-scheduled

# In another terminal, trigger and watch output
curl "http://localhost:8787/__scheduled?cron=0+*+*+*+*"

输出会显示在 wrangler dev 终端中:

[wrangler:inf] GET /__scheduled?cron=0+*+*+*+* 200 OK (45ms)
Cron job executed at: 2025-10-23T15:00:00.000Z
Triggered by cron: 0 * * * *
Scheduled task completed successfully

测试多个 Cron 表达式
# Test hourly cron
curl "http://localhost:8787/__scheduled?cron=0+*+*+*+*"

# Test daily cron
curl "http://localhost:8787/__scheduled?cron=0+0+*+*+*"

# Test weekly cron
curl "http://localhost:8787/__scheduled?cron=0+0+*+*+1"

测试 Python Workers
# Python Workers use different endpoint
curl "http://localhost:8787/cdn-cgi/handler/scheduled?cron=*+*+*+*+*"

绿色计算

仅在由可再生能源供电的数据中心运行 cron 触发器。

启用绿色计算

通过控制面板:

  1. 前往 Workers & Pages
  2. 在 Account details 部分中找到 Compute Setting
  3. 点击 Change
  4. 选择 Green Compute
  5. 点击 Confirm

适用范围:

  • 账户中的所有 cron 触发器
  • 减少碳足迹
  • 不产生额外费用
  • 可能会导致某些地区出现轻微延迟

工作原理:

  • Cloudflare 将 cron 执行路由到由绿色能源供电的数据中心
  • 使用可再生能源:风能、太阳能、水力发电
  • 通过购电协议(PPAs)和可再生能源证书(RECs)进行验证

已知问题预防

此技能可预防 6 个已记录的问题:

问题 #1:Cron 更改未生效

错误: wrangler.jsonc 中的 cron 触发器已更新,但未执行

来源: Cloudflare 文档 - Cron 触发器

原因:

  • cron 触发器的更改最多需要 15 分钟 才能在全球范围内生效
  • Cloudflare 网络需要时间更新边缘节点
  • 不会像常规部署一样即时生效

预防方法:

  • 部署后等待 15 分钟,再确认其是否执行
  • 检查控制面板:Workers & Pages > [Worker] > Cron Triggers
  • 仅更改触发器时,使用 wrangler triggers deploy
# If you only changed triggers (not code), use:
npx wrangler triggers deploy

# Wait 15 minutes, then verify in dashboard

问题 #2:处理程序未导出

错误: Handler does not export a 'scheduled' method

来源: 常见部署错误

原因:

  • 处理程序的名称不完全是 scheduled
  • 处理程序未在默认导出对象中导出
  • 使用的是 Service Worker 格式,而不是 ES 模块

预防方法:

// ❌ Wrong: Incorrect handler name
export default {
  async scheduledHandler(controller, env, ctx) { }
};

// ❌ Wrong: Not in default export
export async function scheduled(controller, env, ctx) { }

// ✅ Correct: Named 'scheduled' in default export
export default {
  async scheduled(controller, env, ctx) { }
};

问题 #3:UTC 时区混淆

错误: Cron 在错误的时间运行

来源: 用户预期与实际情况不符

原因:

  • 所有 cron 触发器仅按 UTC 时间 运行
  • 不支持时区转换
  • 用户预期使用本地时区

预防方法:

手动将本地时间转换为 UTC:

// Want to run at 9am PST (UTC-8)?
// 9am PST = 5pm UTC (17:00)
{
  "triggers": {
    "crons": ["0 17 * * *"]  // 9am PST = 5pm UTC
  }
}

// Want to run at 6pm EST (UTC-5)?
// 6pm EST = 11pm UTC (23:00)
{
  "triggers": {
    "crons": ["0 23 * * *"]  // 6pm EST = 11pm UTC
  }
}

// Remember: DST changes affect conversion!
// PST is UTC-8, PDT is UTC-7

工具:


问题 #4:Cron 表达式无效

错误: Cron 未执行,且未显示错误

来源: 验证失败但未提示

发生原因:

  • 无效的 Cron 语法会静默失败
  • 验证发生在部署时,但可能不易察觉
  • 常见错误:字段顺序错误、范围无效

预防措施:

# ❌ Wrong: Too many fields (6 fields instead of 5)
"crons": ["0 0 * * * *"]  # Has seconds field - not supported

# ❌ Wrong: Invalid minute range
"crons": ["65 * * * *"]  # Minute must be 0-59

# ❌ Wrong: Invalid day of week
"crons": ["0 0 * * 7"]  # Day of week is 0-6 (use 0 for Sunday)

# ✅ Correct: 5 fields, valid ranges
"crons": ["0 0 * * 0"]  # Sunday at midnight UTC

验证:

  • 使用 Crontab Guru 验证表达式
  • 检查 wrangler deploy 输出中的错误
  • 使用 --test-scheduled 在本地测试

问题 #5:缺少 ES 模块格式

错误: Worker must use ES modules format

来源: 旧版 Service Worker 格式

发生原因:

  • 定时处理程序要求使用 ES 模块格式
  • 不支持旧版 Service Worker 格式
  • 代码库中混用了不同格式

预防措施:

// ❌ Wrong: Service Worker format
addEventListener('scheduled', (event) => {
  event.waitUntil(handleScheduled(event));
});

// ✅ Correct: ES modules format
export default {
  async scheduled(controller, env, ctx) {
    await handleScheduled(controller, env, ctx);
  },
};

问题 #6:超出 CPU 时间限制

错误: CPU time limit exceeded

来源: 长时间运行的定时任务

发生原因:

  • 默认 CPU 时间限制:30 秒
  • 长时间运行的任务会超出限制
  • 不会自动延长超时时间

预防措施:

选项 1:在 wrangler.jsonc 中提高 CPU 时间限制

{
  "limits": {
    "cpu_ms": 300000  // 5 minutes (max for Standard plan)
  }
}

选项 2:对长时间运行的任务使用 Workflows

// Instead of long task in cron:
export default {
  async scheduled(controller, env, ctx) {
    // Trigger Workflow that can run for hours
    await env.MY_WORKFLOW.create({
      params: { task: 'long-running-job' },
    });
  },
};

选项 3:拆分为更小的任务块

export default {
  async scheduled(controller, env, ctx) {
    // Process in batches
    const batch = await getNextBatch(env.DB);

    for (const item of batch) {
      await processItem(item);
    }

    // If more work, send to Queue for next batch
    const hasMore = await hasMoreWork(env.DB);
    if (hasMore) {
      await env.MY_QUEUE.send({ type: 'continue-processing' });
    }
  },
};

始终要做 ✅

  1. 使用准确的处理程序名称 - 必须是 scheduled,而不是 scheduledHandler 或其变体
  2. 使用 ES 模块格式 - 在默认对象中导出,而不是使用 addEventListener
  3. 转换为 UTC - 所有 Cron 时间均为 UTC,请从本地时区转换
  4. 等待 15 分钟 - Cron 更改最多需要 15 分钟才能生效
  5. 先在本地测试 - 使用 wrangler dev --test-scheduled
  6. 验证 Cron 语法 - 使用 Crontab Guru
  7. 妥善处理错误 - 记录日志、发送警报,并可选择重新抛出错误
  8. 使用 ctx.waitUntil() - 用于非关键的异步操作
  9. 考虑使用 Workflows - 适用于需要超过 30 秒 CPU 时间的任务
  10. 监控执行情况 - 定期检查仪表板日志

绝不要做 ❌

  1. 绝不要假定使用本地时区 - 所有 Cron 均按 UTC 运行
  2. 绝不要使用 6 字段 Cron 表达式 - Cloudflare 使用 5 字段格式(不含秒)
  3. 绝不要依赖立即生效 - 更改最多需要 15 分钟才能生效
  4. 绝不要使用 Service Worker 格式 - 必须使用 ES 模块格式
  5. 绝不要忘记错误处理 - 未捕获的错误会静默导致失败
  6. 绝不要在未提高限制时运行 CPU 密集型任务 - 默认限制为 30 秒
  7. 绝不要使用星期字段值 7 - 星期日使用 0(仅限 0–6 范围)
  8. 绝不要未经测试就部署 - 始终先使用 --test-scheduled 测试
  9. 绝不要忽略执行日志 - 仪表板会显示过去的失败记录
  10. 绝不要为测试硬编码计划 - 使用特定环境的配置

常见用例

1. 数据库清理

每天 UTC 时间凌晨 2 点:删除旧记录

export default {
  async scheduled(controller: ScheduledController, env: Env): Promise<void> {
    // Delete sessions older than 30 days
    const thirtyDaysAgo = Date.now() - (30 * 24 * 60 * 60 * 1000);

    await env.DB.prepare('DELETE FROM sessions WHERE created_at < ?')
      .bind(thirtyDaysAgo)
      .run();

    // Delete soft-deleted users older than 90 days
    const ninetyDaysAgo = Date.now() - (90 * 24 * 60 * 60 * 1000);

    await env.DB.prepare('DELETE FROM users WHERE deleted_at < ?')
      .bind(ninetyDaysAgo)
      .run();

    console.log('Database cleanup completed');
  },
};

wrangler.jsonc:

{
  "triggers": {
    "crons": ["0 2 * * *"]  // Daily at 2am UTC
  }
}

2. API 数据收集

每 15 分钟:从外部 API 获取数据

interface Env {
  DB: D1Database;
  API_KEY: string;
}

export default {
  async scheduled(controller: ScheduledController, env: Env): Promise<void> {
    try {
      // Fetch from external API
      const response = await fetch('https://api.example.com/v1/data', {
        headers: {
          Authorization: `Bearer ${env.API_KEY}`,
        },
      });

      const data = await response.json();

      // Store in D1
      for (const item of data.items) {
        await env.DB.prepare(
          'INSERT INTO collected_data (id, value, timestamp) VALUES (?, ?, ?)'
        )
          .bind(item.id, item.value, Date.now())
          .run();
      }

      console.log(`Collected ${data.items.length} items`);
    } catch (error) {
      console.error('Failed to collect data:', error);
      throw error;  // Mark execution as failed
    }
  },
};

wrangler.jsonc:

{
  "triggers": {
    "crons": ["*/15 * * * *"]  // Every 15 minutes
  }
}

3. 每日报告生成

每天 UTC 时间上午 8 点:生成并通过电子邮件发送报告

export default {
  async scheduled(controller: ScheduledController, env: Env, ctx: ExecutionContext): Promise<void> {
    // Generate report from database
    const report = await generateDailyReport(env.DB);

    // Store in R2
    const fileName = `reports/${new Date().toISOString().split('T')[0]}.json`;
    await env.MY_BUCKET.put(fileName, JSON.stringify(report));

    // Send via email
    ctx.waitUntil(sendReportEmail(report, env.RESEND_API_KEY));

    console.log('Daily report generated and sent');
  },
};

async function generateDailyReport(db: D1Database) {
  const yesterday = new Date();
  yesterday.setDate(yesterday.getDate() - 1);
  const startOfDay = yesterday.setHours(0, 0, 0, 0);
  const endOfDay = yesterday.setHours(23, 59, 59, 999);

  const stats = await db
    .prepare(`
      SELECT
        COUNT(*) as total_users,
        COUNT(DISTINCT user_id) as active_users,
        SUM(revenue) as total_revenue
      FROM events
      WHERE timestamp BETWEEN ? AND ?
    `)
    .bind(startOfDay, endOfDay)
    .first();

  return {
    date: yesterday.toISOString().split('T')[0],
    stats,
  };
}

wrangler.jsonc:

{
  "triggers": {
    "crons": ["0 8 * * *"]  // Daily at 8am UTC
  }
}

4. 缓存预热

每小时:使用热门内容预热缓存

export default {
  async scheduled(controller: ScheduledController, env: Env): Promise<void> {
    // Get most popular pages from analytics
    const popularPages = await env.DB
      .prepare('SELECT url FROM pages ORDER BY views DESC LIMIT 100')
      .all();

    // Fetch each page to warm cache
    const requests = popularPages.results.map((page) =>
      fetch(`https://example.com${page.url}`, {
        cf: {
          cacheTtl: 3600,  // Cache for 1 hour
        },
      })
    );

    await Promise.all(requests);

    console.log(`Warmed cache for ${popularPages.results.length} pages`);
  },
};

wrangler.jsonc:

{
  "triggers": {
    "crons": ["0 * * * *"]  // Every hour
  }
}

5. 监控与运行状况检查

每 5 分钟:检查系统运行状况

export default {
  async scheduled(controller: ScheduledController, env: Env): Promise<void> {
    const checks = await Promise.allSettled([
      checkDatabaseHealth(env.DB),
      checkAPIHealth(),
      checkStorageHealth(env.MY_BUCKET),
    ]);

    const failures = checks.filter((check) => check.status === 'rejected');

    if (failures.length > 0) {
      // Send alert
      await sendAlert({
        service: 'health-check',
        failures: failures.map((f) => f.reason),
        timestamp: new Date().toISOString(),
      });
    }
  },
};

async function checkDatabaseHealth(db: D1Database): Promise<void> {
  const result = await db.prepare('SELECT 1 as health').first();
  if (!result || result.health !== 1) {
    throw new Error('Database health check failed');
  }
}

async function checkAPIHealth(): Promise<void> {
  const response = await fetch('https://api.example.com/health');
  if (!response.ok) {
    throw new Error(`API health check failed: ${response.status}`);
  }
}

async function checkStorageHealth(bucket: R2Bucket): Promise<void> {
  const testObject = await bucket.get('health-check.txt');
  if (!testObject) {
    throw new Error('Storage health check failed');
  }
}

wrangler.jsonc:

{
  "triggers": {
    "crons": ["*/5 * * * *"]  // Every 5 minutes
  }
}

TypeScript 类型

// Scheduled event controller
interface ScheduledController {
  readonly cron: string;
  readonly type: string;
  readonly scheduledTime: number;
}

// Execution context
interface ExecutionContext {
  waitUntil(promise: Promise<any>): void;
  passThroughOnException(): void;
}

// Scheduled handler
export default {
  async scheduled(
    controller: ScheduledController,
    env: Env,
    ctx: ExecutionContext
  ): Promise<void>;
}

限制与定价

限制
功能免费计划付费计划
每个 Worker 的 Cron 触发器数量3更多(请查阅文档)
每次执行的 CPU 时间10 ms(平均)30 秒(默认),5 分钟(最大)
墙钟时间30 秒15 分钟
内存128 MB128 MB
定价

Cron 触发器使用 Standard Workers 定价:

  • Workers Paid Plan:每月需要支付 $5
  • 请求:每百万个请求 $0.30(超过 1,000 万次免费额度后)
  • CPU 时间:每百万个 CPU-ms $0.02(超过 3,000 万次免费额度后)

Cron 执行 = 1 个请求

示例:

  • Cron 每小时运行一次(每天 24 次)
  • 30 天 × 24 次执行 = 每月 720 次执行
  • 每次执行平均使用 50ms CPU 时间

费用:

  • 请求:720 次(远低于 1,000 万次免费额度)
  • CPU 时间:720 × 50ms = 36,000ms(低于 3,000 万次免费额度)
  • 总计:每月 $5(仅订阅费用)

高频率示例:

  • Cron 每分钟运行一次(每天 1,440 次)
  • 30 天 × 1,440 次 = 每月 43,200 次执行
  • 仍低于免费额度
  • 总计:每月 $5

故障排除

问题:Cron 未执行

可能原因:

  1. 更改尚未生效(等待 15 分钟)
  2. Cron 表达式无效
  3. 处理函数未正确导出
  4. Worker 尚未部署

解决方案:

# Re-deploy
npx wrangler deploy

# Wait 15 minutes

# Check dashboard
# Workers & Pages > [Worker] > Cron Triggers

# Check logs
# Workers & Pages > [Worker] > Logs > Real-time Logs

问题:处理函数已执行,但失败了

可能原因:

  1. 处理函数中存在未捕获的错误
  2. 超出 CPU 时间限制
  3. 缺少环境绑定
  4. 网络超时

解决方案:

export default {
  async scheduled(controller, env, ctx) {
    try {
      await yourTask(env);
    } catch (error) {
      // Log detailed error
      console.error('Handler failed:', {
        error: error.message,
        stack: error.stack,
        cron: controller.cron,
        time: new Date(controller.scheduledTime),
      });

      // Send alert
      ctx.waitUntil(sendAlert(error));

      // Re-throw to mark as failed
      throw error;
    }
  },
};

在控制面板中查看日志,了解错误详情。


问题:执行时间错误

原因: 混淆了 UTC 和本地时区

解决方案:

将所需的本地时间转换为 UTC:

// Want 9am PST (UTC-8)?
// 9am PST = 5pm UTC (17:00)

{
  "triggers": {
    "crons": ["0 17 * * *"]
  }
}

工具:


问题:本地测试无法运行

可能原因:

  1. 缺少 --test-scheduled 标志
  2. 端点错误(应为 /__scheduled)
  3. Python Worker(请使用 /cdn-cgi/handler/scheduled)

解决方案:

# Correct: Start with flag
npx wrangler dev --test-scheduled

# In another terminal
curl "http://localhost:8787/__scheduled?cron=0+*+*+*+*"

生产环境检查清单

将 Cron 触发器部署到生产环境前:

  • 已在 Crontab Guru 上验证 Cron 表达式
  • 默认导出中的处理函数名称准确为 scheduled
  • 使用 ES 模块格式(而非 Service Worker)
  • 已将本地时区转换为 UTC
  • 已实现带日志记录的错误处理
  • 已配置失败告警
  • 如有需要,已提高 CPU 限制(limits.cpu_ms)
  • 已测试环境绑定
  • 已使用 --test-scheduled 在本地测试
  • 已在预发布环境中测试部署
  • 部署后已等待 15 分钟以便更改生效
  • 已在控制面板日志中确认执行情况
  • 已配置监控和告警
  • 已更新包含计划详情的文档

相关文档


最后更新:2025-10-23 版本:1.0.0 维护者:Jeremy Dawes | [email protected]

评论

支持 Markdown