LogoLaunchSaaS

定时任务 (Cron)

使用 Upstash QStash 配置定时任务,实现自动化任务如通讯通知等。

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

概述

定时任务功能提供:

  • 提供者抽象:轻松切换 cron 提供者或完全禁用
  • 基于 Webhook 的执行:通过 HTTP webhook 触发任务,非常适合无服务器环境
  • 签名验证:使用自动签名验证保护 webhook 端点
  • 内置内容通知任务:自动通知通讯订阅者有关新博客文章和更新日志

架构

┌─────────────────┐     触发       ┌─────────────────┐
│  Upstash QStash │────────────────►│  Webhook API    │
│  (调度器)        │                 │  /api/cron/...  │
└─────────────────┘                 └────────┬────────┘


                                    ┌─────────────────┐
                                    │  任务处理器      │
                                    │  (您的逻辑)      │
                                    └────────┬────────┘


                                    ┌─────────────────┐
                                    │  Newsletter     │
                                    │  .broadcast()   │
                                    └─────────────────┘

配置

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. 在功能配置中启用 Cron

更新 src/configuration/features.ts

const local: Features = {
  // ... 其他功能
  cron: {
    provider: "qstash", // 或 "disabled"
  },
};

在 QStash 中创建计划

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

常用 Cron 表达式

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

内置任务

内容通知任务

当发布新博客文章或更新日志条目时,自动向通讯订阅者发送电子邮件通知。

任务 IDcontent-notification

端点/api/cron/qstash/content-notification

工作原理

  1. 检查过去 7 天内发布的内容
  2. 使用缓存(如果启用)跟踪已通知的内容
  3. 通过 Resend 向所有通讯订阅者发送单个广播邮件
  4. 将所有新内容项包含在"最新动态"摘要中

内容检测

缓存启用内容年龄结果
不在缓存中新内容(发送 + 缓存)
在缓存中跳过
今天创建新内容(发送)
今天之前创建跳过

当缓存禁用时,只有今天创建的内容才会被包含,以防止重复发送旧内容。

创建自定义任务

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内部服务器错误
503Cron 已禁用

禁用定时任务

要完全禁用 cron 功能,请更新您的配置:

// src/configuration/features.ts
cron: {
  provider: "disabled",
},

禁用后:

  • Webhook 端点返回 503
  • 任务处理器变为空操作
  • 不会发出外部请求

安全性

签名验证

所有传入的 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 控制台匹配
  • 检查密钥轮换 - 如果最近轮换过,请更新两个密钥

邮件未发送

  • 验证通讯提供者已配置(features.newsletter.provider
  • 检查 Resend API 密钥和受众 ID 是否已设置
  • 在任务响应日志中查找错误

内容未被检测为新内容

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