定时任务
使用 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) │
└─────────────────┘系统的两个核心组件:
CronProvider(capabilities.cron)— 面向外部:验证来自 QStash 的请求签名,可选通过 API 管理计划;同时通过cronProvider.jobs持有已注册的任务列表
配置
1. 获取 QStash 凭据
- 前往 Upstash 控制台
- 导航到 QStash 部分
- 复制您的凭据:
QSTASH_TOKENQSTASH_CURRENT_SIGNING_KEYQSTASH_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_key3. 在 capabilities 中启用 Cron 提供者
更新 src/capabilities.ts:
import { QStashCronProvider } from "@launchsaas/cron-qstash";
export const capabilities = {
// ...
cron: QStashCronProvider.create([]),
};当 cron 为 null 时,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 中创建计划
- 前往 QStash 控制台
- 点击 Schedules → Create Schedule
- 配置计划:
- 目标 URL:
https://your-domain.com/api/cron/qstash/my-custom-job - 计划:使用 cron 表达式(例如,
0 9 * * *表示每天 UTC 时间上午 9 点) - 方法:
POST
- 目标 URL:
常用 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-jobURL 中的 {job} 段必须与任务类的 id 属性完全匹配。
CronProvider 的工作方式
当 QStash 触发 webhook 时:
POST /api/cron/qstash/my-custom-job请求到达- 路由检查
capabilities.cron——若为null,返回 503 - 路由验证 URL 中的提供者名称与
capabilities.cron.name是否一致 - 路由调用
capabilities.cron.verifySignature(request)——验证 QStash 签名 - 路由通过
capabilities.cron.jobs.find(j => j.id === jobId)——查找处理器 - 路由调用
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 | 内部服务器错误 |
| 503 | Cron 未配置 |
安全性
签名验证
所有传入的 webhook 请求都使用 QStash 的签名机制进行验证:
- QStash 使用您的签名密钥对每个请求进行签名
- Webhook 端点在执行任务之前验证签名
- 无效签名将被拒绝,返回 401 Unauthorized
切勿在客户端代码中暴露您的 QSTASH_TOKEN
或签名密钥。这些是仅限服务器端的凭据。
最佳实践
- 使用 HTTPS:在生产环境中始终为 webhook 端点使用 HTTPS
- 轮换密钥:使用 QStash 密钥轮换功能定期轮换签名密钥
- 监控失败:在 QStash 仪表板中设置任务失败警报
- 幂等任务:设计任务为幂等的(可安全多次运行)
故障排除
常见问题
任务未触发:
- 验证计划在 QStash 仪表板中是否处于活动状态
- 检查目标 URL 是否正确且可公开访问
- 确保在生产环境中设置了环境变量
签名验证失败:
- 确认
QSTASH_CURRENT_SIGNING_KEY和QSTASH_NEXT_SIGNING_KEY与 QStash 控制台匹配 - 检查密钥轮换——如果最近轮换过,请同时更新两个密钥
任务未找到(404):
- 确认 URL 中的任务 ID 与任务类的
id属性完全匹配 - 确认该任务实例已传入
src/capabilities.ts中的QStashCronProvider.create([...])
邮件未发送:
- 确认
src/capabilities.ts中newsletter已配置(非 null) - 检查 Resend API 密钥和受众 ID 是否已设置
- 在任务响应日志中查找错误
内容未被检测为新内容:
- 使用缓存时:检查内容是否已被处理(已缓存)
- 不使用缓存时:内容必须在今天发布才能被检测到