LogoLaunchSaaS

定时任务

使用 Upstash QStash 配置定时任务,实现自动化的服务端工作流。

LaunchSaaS 包含一个灵活的定时任务系统,由 Upstash QStash 驱动,让您无需管理自己的 cron 基础设施即可按计划运行自动化任务。

更新时间:2026-03-22 - 移除内置内容通知示例,改为说明自定义任务注册流程。

概述

定时任务功能提供:

  • 提供者抽象:轻松切换 cron 提供者或完全禁用
  • 基于 Webhook 的执行:通过 HTTP webhook 触发任务,非常适合无服务器环境
  • 签名验证:使用自动签名验证保护 webhook 端点
  • 自定义任务注册:可将任意 CronJob 实现注册到 provider 中

架构

┌─────────────────┐     触发       ┌────────────────────────────────┐
│  Upstash QStash │────────────────►│  POST /api/cron/{provider}/{id} │
│  (调度器)        │                 └──────────────┬─────────────────┘
└─────────────────┘                                │
                                                   │ 1. 检查 capabilities.cron
                                                   │ 2. 验证 webhook 签名
                                                   │ 3. 从 cronProvider.jobs 查找任务

                                        ┌─────────────────┐
                                        │  任务处理器      │
                                        │  (CronJob)       │
                                        └─────────────────┘

系统的两个核心组件:

  • CronProvidercapabilities.cron)— 面向外部:验证来自 QStash 的请求签名,可选通过 API 管理计划;同时通过 cronProvider.jobs 持有已注册的任务列表

配置

1. 获取 QStash 凭据

  1. 前往 Upstash 控制台
  2. 导航到 QStash 部分
  3. 复制您的凭据:
    • QSTASH_TOKEN
    • QSTASH_CURRENT_SIGNING_KEY
    • QSTASH_NEXT_SIGNING_KEY

2. 设置环境变量

将以下内容添加到您的 .env 文件:

# Cron 提供者 - Upstash QStash
QSTASH_TOKEN=your_qstash_token
QSTASH_CURRENT_SIGNING_KEY=your_current_signing_key
QSTASH_NEXT_SIGNING_KEY=your_next_signing_key

3. 在 capabilities 中启用 Cron 提供者

更新 src/capabilities.ts

import { QStashCronProvider } from "@launchsaas/cron-qstash";

export const capabilities = {
  // ...
  cron: QStashCronProvider.create([]),
};

cronnull 时,webhook 端点返回 503,不会发出任何外部请求。

4. 配置 Webhook 路由

创建接收 QStash webhook 的 API 路由文件:

// src/app/api/cron/[provider]/[job]/route.ts
import { toHandler } from "@launchsaas/cron";
import { capabilities } from "@/capabilities";

export const { POST } = toHandler(capabilities.cron);

[provider] 段会与 capabilities.cron.name 进行校验,[job] 则匹配已注册的任务 ID。

在 QStash 中创建计划

  1. 前往 QStash 控制台
  2. 点击 SchedulesCreate Schedule
  3. 配置计划:
    • 目标 URLhttps://your-domain.com/api/cron/qstash/my-custom-job
    • 计划:使用 cron 表达式(例如,0 9 * * * 表示每天 UTC 时间上午 9 点)
    • 方法POST

常用 Cron 表达式

表达式描述
0 9 * * *每天 UTC 时间上午 9:00
0 */6 * * *每 6 小时
0 9 * * 1每周一 UTC 时间上午 9:00
0 0 1 * *每月第一天

创建自定义任务

1. 创建任务处理器

src/lib/features/cron/jobs/ 中创建新文件:

// src/lib/features/cron/jobs/my-custom-job.ts
import "server-only";

import type { CronJob } from "@launchsaas/cron";
import { NextResponse } from "next/server";

export class MyCustomJob implements CronJob {
  readonly id = "my-custom-job";
  readonly name = "我的自定义任务";
  readonly description = "按计划执行某些操作";
  readonly schedule = "0 */12 * * *"; // 仅供参考,实际计划在 QStash 中设置

  async handle(_request: Request): Promise<Response> {
    // 您的任务逻辑

    return NextResponse.json({ success: true, message: "任务完成" });
  }
}

2. 注册任务

src/capabilities.ts 中将其传给 provider:

import { QStashCronProvider } from "@launchsaas/cron-qstash";
import { MyCustomJob } from "@/lib/features/cron/jobs/my-custom-job";

cron: QStashCronProvider.create([
  new MyCustomJob(),
]),

3. 在 QStash 中创建计划

在 QStash 仪表板中添加新计划,指向:

https://your-domain.com/api/cron/qstash/my-custom-job

URL 中的 {job} 段必须与任务类的 id 属性完全匹配。

CronProvider 的工作方式

当 QStash 触发 webhook 时:

  1. POST /api/cron/qstash/my-custom-job 请求到达
  2. 路由检查 capabilities.cron——若为 null,返回 503
  3. 路由验证 URL 中的提供者名称capabilities.cron.name 是否一致
  4. 路由调用 capabilities.cron.verifySignature(request) ——验证 QStash 签名
  5. 路由通过 capabilities.cron.jobs.find(j => j.id === jobId) ——查找处理器
  6. 路由调用 job.handle(request) ——执行您的业务逻辑
// 简化的路由处理器
const cronProvider = capabilities.cron; // CronProvider(QStash)
const job = cronProvider.jobs.find((j) => j.id === jobId); // 您的任务类
await cronProvider.verifySignature(request); // 验证确实来自 QStash
await job.handle(request); // 执行您的代码

通讯集成

自定义任务可与通讯系统集成以发送广播邮件:

const newsletter = capabilities.newsletter;
if (newsletter) {
  await newsletter.broadcast({
    subject: "LaunchSaaS 产品更新",
    react: MyEmailTemplate({}),
  });
}

监控

QStash 仪表板

QStash 控制台 中监控您的定时任务:

  • Schedules:查看和管理活动计划
  • Logs:查看执行历史和错误
  • Messages:跟踪单个任务调用

响应代码

代码含义
200任务执行成功
401无效的 webhook 签名
404未找到任务或提供者
500内部服务器错误
503Cron 未配置

安全性

签名验证

所有传入的 webhook 请求都使用 QStash 的签名机制进行验证:

  1. QStash 使用您的签名密钥对每个请求进行签名
  2. Webhook 端点在执行任务之前验证签名
  3. 无效签名将被拒绝,返回 401 Unauthorized

切勿在客户端代码中暴露您的 QSTASH_TOKEN 或签名密钥。这些是仅限服务器端的凭据。

最佳实践

  1. 使用 HTTPS:在生产环境中始终为 webhook 端点使用 HTTPS
  2. 轮换密钥:使用 QStash 密钥轮换功能定期轮换签名密钥
  3. 监控失败:在 QStash 仪表板中设置任务失败警报
  4. 幂等任务:设计任务为幂等的(可安全多次运行)

故障排除

常见问题

任务未触发

  • 验证计划在 QStash 仪表板中是否处于活动状态
  • 检查目标 URL 是否正确且可公开访问
  • 确保在生产环境中设置了环境变量

签名验证失败

  • 确认 QSTASH_CURRENT_SIGNING_KEYQSTASH_NEXT_SIGNING_KEY 与 QStash 控制台匹配
  • 检查密钥轮换——如果最近轮换过,请同时更新两个密钥

任务未找到(404)

  • 确认 URL 中的任务 ID 与任务类的 id 属性完全匹配
  • 确认该任务实例已传入 src/capabilities.ts 中的 QStashCronProvider.create([...])

邮件未发送

  • 确认 src/capabilities.tsnewsletter 已配置(非 null)
  • 检查 Resend API 密钥和受众 ID 是否已设置
  • 在任务响应日志中查找错误

内容未被检测为新内容

  • 使用缓存时:检查内容是否已被处理(已缓存)
  • 不使用缓存时:内容必须在今天发布才能被检测到