Amplify项目TS/Node环境下如何动态创建AWS Cron定时任务
Amplify 动态Cron任务体系实现方案
核心实现基于AWS EventBridge Scheduler服务,完全通过代码完成定时任务的创建、调度、销毁,无需手动操作CLI或Web控制台,适配TypeScript/Node技术栈。整套体系分为两层:
- 主任务层:你已通过Amplify CLI创建的固定调度Lambda,作为任务调度入口
- 次级任务层:主任务执行过程中动态生成的调度规则,可触发任意Lambda或其他AWS服务,支持一次性延迟执行、固定速率执行、Cron周期执行,任务完成后可自动清理规则或继续调度新任务
前置配置
1. 给主任务Lambda加权限
在Amplify项目对应主Lambda的目录下找到custom-policies.json(没有就新建),添加以下权限配置,执行amplify push后权限会自动部署,无需手动操作IAM控制台:
[ { "Action": [ "scheduler:CreateSchedule", "scheduler:DeleteSchedule", "scheduler:UpdateSchedule", "scheduler:GetSchedule" ], "Resource": ["arn:aws:scheduler:*:*:schedule/*/dynamic-tasks/*"] }, { "Action": ["lambda:GetFunction"], "Resource": ["arn:aws:lambda:*:*:function:*"] } ]
2. 创建调度服务角色
首次使用时创建一个EventBridge Scheduler专用的执行角色:
- 信任策略配置为允许
scheduler.amazonaws.com服务代入角色 - 角色权限添加
lambda:InvokeFunction权限,允许调度服务调用你的Lambda函数 - 将该角色的ARN配置到Amplify的环境变量
SCHEDULER_ROLE_ARN中,所有动态调度复用此角色即可
3. 安装依赖
在主Lambda项目下安装AWS SDK对应客户端:npm install @aws-sdk/client-scheduler @aws-sdk/client-lambda
Amplify Node运行时已内置AWS SDK v3,安装指定版本可避免版本兼容问题
核心代码实现(TypeScript)
初始化客户端
import { SchedulerClient, CreateScheduleCommand, DeleteScheduleCommand, FlexibleTimeWindowMode } from "@aws-sdk/client-scheduler"; import { LambdaClient, GetFunctionCommand } from "@aws-sdk/client-lambda"; // 客户端默认使用Lambda执行角色的权限,无需硬编码密钥 const schedulerClient = new SchedulerClient({ region: process.env.AWS_REGION }); const lambdaClient = new LambdaClient({ region: process.env.AWS_REGION }); // 按Amplify环境区分调度组,避免测试/生产资源混淆 const SCHEDULE_GROUP_NAME = `${process.env.ENV}-dynamic-tasks`;
通用调度方法封装
/** * 动态创建触发Lambda的定时任务 * @param scheduleName 任务唯一标识,建议拼接业务ID/时间戳避免重名 * @param targetLambdaName 要触发的次级Lambda函数名 * @param scheduleExpression 调度表达式,支持at(一次性)、rate(固定速率)、cron(周期)三种格式 * @param input 传给次级Lambda的事件参数 * @param deleteAfterRun 执行完成后是否自动删除调度规则,一次性任务设为true */ async function createDynamicLambdaSchedule( scheduleName: string, targetLambdaName: string, scheduleExpression: string, input: Record<string, any>, deleteAfterRun: boolean = false ) { // 自动获取目标Lambda的ARN const lambdaInfo = await lambdaClient.send(new GetFunctionCommand({ FunctionName: targetLambdaName })); const lambdaArn = lambdaInfo.Configuration?.FunctionArn; if (!lambdaArn) throw new Error(`目标Lambda不存在: ${targetLambdaName}`); const createRes = await schedulerClient.send(new CreateScheduleCommand({ Name: scheduleName, GroupName: SCHEDULE_GROUP_NAME, ScheduleExpression: scheduleExpression, // 关闭弹性时间窗口,到点立即触发 FlexibleTimeWindow: { Mode: FlexibleTimeWindowMode.OFF }, Target: { Arn: lambdaArn, RoleArn: process.env.SCHEDULER_ROLE_ARN, Input: JSON.stringify(input) }, // 配置任务执行完成后的动作 ActionAfterCompletion: deleteAfterRun ? 'DELETE' : 'NONE' })); return createRes; } /** * 删除不需要的定时任务 * @param scheduleName 任务唯一标识 */ async function deleteDynamicSchedule(scheduleName: string) { return schedulerClient.send(new DeleteScheduleCommand({ Name: scheduleName, GroupName: SCHEDULE_GROUP_NAME })); }
主任务中调用示例
export const handler = async (event: any) => { // 原有主任务业务逻辑 console.log('主任务开始执行', event); // ...... 你的业务代码 ...... console.log('主任务执行完成,开始调度次级任务'); // 示例1:创建2小时后执行的一次性数据清理任务 const oneTimeRunTime = new Date(Date.now() + 2 * 60 * 60 * 1000); // 一次性任务使用at表达式,参数为ISO8601格式时间 const oneTimeExpression = `at(${oneTimeRunTime.toISOString()})`; await createDynamicLambdaSchedule( `cleanup-${Date.now()}`, process.env.CLEANUP_LAMBDA_NAME!, // 次级Lambda名配置在环境变量中 oneTimeExpression, { bizId: 'order-12345', type: 'temp-cleanup' }, true // 执行完自动删除规则 ); // 示例2:创建每天北京时间凌晨1点执行的日报任务 // 注意:EventBridge默认使用UTC时间,北京时间UTC+8,对应UTC时间17点 const dailyCronExpression = 'cron(0 17 * * ? *)'; await createDynamicLambdaSchedule( `daily-report-task`, process.env.REPORT_LAMBDA_NAME!, dailyCronExpression, { reportType: 'daily' }, false // 周期任务不自动删除,不需要时调用delete方法移除 ); return { code: 0, msg: '主任务执行完成,次级任务调度成功' }; };
注意事项
- 时区问题:EventBridge所有时间表达式默认使用UTC时区,配置Cron/一次性任务时必须做时区偏移,避免触发时间不符合预期
- 命名规范:调度任务名称全局唯一,建议拼接业务ID、时间戳,重名会导致创建失败
- 资源清理:一次性任务务必开启
ActionAfterCompletion: 'DELETE',避免无用规则堆积产生额外费用 - 环境隔离:所有资源名称、配置项通过Amplify环境变量注入,不要硬编码,避免测试环境操作生产资源
- 触发限制:EventBridge Scheduler最小调度精度为1分钟,不支持秒级触发
内容的提问来源于stack exchange,提问作者yosef Mardini
相关产品推荐
相关产品推荐

