定时任务 (Cron)
使用 Upstash QStash 配置定时任务,实现自动化任务如通讯通知等。
LaunchSaaS 包含一个灵活的定时任务系统,由 Upstash QStash 驱动,让您无需管理自己的 cron 基础设施即可按计划运行自动化任务。
概述
定时任务功能提供:
- 提供者抽象:轻松切换 cron 提供者或完全禁用
- 基于 Webhook 的执行:通过 HTTP webhook 触发任务,非常适合无服务器环境
- 签名验证:使用自动签名验证保护 webhook 端点
- 内置内容通知任务:自动通知通讯订阅者有关新博客文章和更新日志
架构
┌─────────────────┐ 触发 ┌─────────────────┐
│ Upstash QStash │────────────────►│ Webhook API │
│ (调度器) │ │ /api/cron/... │
└─────────────────┘ └────────┬────────┘
│
▼
┌─────────────────┐
│ 任务处理器 │
│ (您的逻辑) │
└────────┬────────┘
│
▼
┌─────────────────┐
│ Newsletter │
│ .broadcast() │
└─────────────────┘配置
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. 在功能配置中启用 Cron
更新 src/configuration/features.ts:
const local: Features = {
// ... 其他功能
cron: {
provider: "qstash", // 或 "disabled"
},
};在 QStash 中创建计划
- 前往 QStash 控制台
- 点击 Schedules → Create Schedule
- 配置计划:
- 目标 URL:
https://your-domain.com/api/cron/qstash/content-notification - 计划:使用 cron 表达式(例如,
0 9 * * *表示每天 UTC 时间上午 9 点) - 方法:
POST - 正文:
{"trigger": "scheduled"}
- 目标 URL:
常用 Cron 表达式
| 表达式 | 描述 |
|---|---|
0 9 * * * | 每天 UTC 时间上午 9:00 |
0 */6 * * * | 每 6 小时 |
0 9 * * 1 | 每周一 UTC 时间上午 9:00 |
0 0 1 * * | 每月第一天 |
内置任务
内容通知任务
当发布新博客文章或更新日志条目时,自动向通讯订阅者发送电子邮件通知。
任务 ID:content-notification
端点:/api/cron/qstash/content-notification
工作原理:
- 检查过去 7 天内发布的内容
- 使用缓存(如果启用)跟踪已通知的内容
- 通过 Resend 向所有通讯订阅者发送单个广播邮件
- 将所有新内容项包含在"最新动态"摘要中
内容检测:
| 缓存启用 | 内容年龄 | 结果 |
|---|---|---|
| 是 | 不在缓存中 | 新内容(发送 + 缓存) |
| 是 | 在缓存中 | 跳过 |
| 否 | 今天创建 | 新内容(发送) |
| 否 | 今天之前创建 | 跳过 |
当缓存禁用时,只有今天创建的内容才会被包含,以防止重复发送旧内容。
创建自定义任务
1. 创建任务处理器
在 src/lib/features/cron/jobs/ 中创建新文件:
// src/lib/features/cron/jobs/my-custom-job.ts
import "server-only";
import { NextResponse } from "next/server";
import { registerJob } from "./index";
registerJob({
id: "my-custom-job",
name: "我的自定义任务",
description: "按计划执行某些操作",
schedule: "0 */12 * * *", // 仅供参考,实际计划在 QStash 中设置
handler: async (request: Request) => {
// 您的任务逻辑
return NextResponse.json({
success: true,
message: "任务完成",
});
},
});2. 注册任务
在 webhook 路由文件中导入您的任务以确保其被注册:
// src/app/api/cron/[provider]/[job]/route.ts
import "@/lib/features/cron/jobs/my-custom-job";3. 在 QStash 中创建计划
在 QStash 仪表板中添加新计划,指向:
https://your-domain.com/api/cron/qstash/my-custom-job通讯集成
定时任务功能与通讯系统集成以发送广播邮件:
通讯订阅
访问者可以通过以下页面上的表单订阅通讯:
- 博客列表页面
- 更新日志页面
广播邮件
Newsletter.broadcast() 方法使用 Resend 的广播 API,通过单个 API 调用高效地向所有订阅者发送邮件,避免大量列表的超时问题。
await Newsletter.broadcast({
subject: "最新动态:LaunchSaaS 的 3 个更新",
react: ContentNotificationTemplate({
blogs: [...],
changelogs: [...],
baseUrl: "https://your-domain.com",
}),
});监控
QStash 仪表板
在 QStash 控制台 中监控您的定时任务:
- Schedules:查看和管理活动计划
- Logs:查看执行历史和错误
- Messages:跟踪单个任务调用
响应代码
| 代码 | 含义 |
|---|---|
| 200 | 任务执行成功 |
| 401 | 无效的 webhook 签名 |
| 404 | 未找到任务或提供者 |
| 500 | 内部服务器错误 |
| 503 | Cron 已禁用 |
禁用定时任务
要完全禁用 cron 功能,请更新您的配置:
// src/configuration/features.ts
cron: {
provider: "disabled",
},禁用后:
- Webhook 端点返回 503
- 任务处理器变为空操作
- 不会发出外部请求
安全性
签名验证
所有传入的 webhook 请求都使用 QStash 的签名机制进行验证:
- QStash 使用您的签名密钥对每个请求进行签名
- Webhook 端点在执行任务之前验证签名
- 无效签名将被拒绝,返回 401 Unauthorized
切勿在客户端代码中暴露您的 QSTASH_TOKEN
或签名密钥。这些是仅限服务器端的凭据。
最佳实践
- 使用 HTTPS:在生产环境中始终为 webhook 端点使用 HTTPS
- 轮换密钥:使用 QStash 密钥轮换功能定期轮换签名密钥
- 监控失败:在 QStash 仪表板中设置任务失败警报
- 幂等任务:设计任务为幂等的(可安全多次运行)
故障排除
常见问题
任务未触发:
- 验证计划在 QStash 仪表板中是否处于活动状态
- 检查目标 URL 是否正确且可公开访问
- 确保在生产环境中设置了环境变量
签名验证失败:
- 确认
QSTASH_CURRENT_SIGNING_KEY和QSTASH_NEXT_SIGNING_KEY与 QStash 控制台匹配 - 检查密钥轮换 - 如果最近轮换过,请更新两个密钥
邮件未发送:
- 验证通讯提供者已配置(
features.newsletter.provider) - 检查 Resend API 密钥和受众 ID 是否已设置
- 在任务响应日志中查找错误
内容未被检测为新内容:
- 使用缓存时:检查内容是否已被处理(已缓存)
- 不使用缓存时:内容必须在今天发布才能被检测到