Agent X-Ray
RuntimeNotesAbout
Notes/代码工程/Encore/第8章

第8章:定时任务与密钥 —— CronJob 与 Secrets

4 分钟 · 更新于 2026-09-02

两个"小而关键"的原语:CronJob 让定时任务成为一行声明(不用管调度器基础设施),Secrets 让密钥彻底离开代码库。两者都有几条必须记住的硬约束。


一、CronJob:声明式定时任务

typescript
import { CronJob } from "encore.dev/cron";
import { api } from "encore.dev/api";

// 被调度的端点:无参、无响应
export const reconcileOrders = api({}, async () => {
  // 对账逻辑
});

// 声明定时任务
const _ = new CronJob("reconcile-orders", {
  title: "对账任务",
  every: "1h",
  endpoint: reconcileOrders,
});

不需要部署任何调度器:Encore(自托管镜像内置调度,Encore Cloud 平台托管)负责按时调用端点。

两种调度表达

typescript
// every:周期式,从 UTC 午夜起算
every: "10m"    // 每 10 分钟:00:00, 00:10, 00:20 ...
every: "6h"     // 每 6 小时:00:00, 06:00, 12:00, 18:00

// schedule:标准 cron 表达式,复杂调度用
schedule: "0 4 15 * *"   // 每月 15 日 UTC 04:00

every 必须整除 24 小时 10m6h 合法;7h 非法(24 无法整除)——这是为了保证每天的执行时刻可预期。不能整除的需求用 schedule cron 表达式表达。注意两种表达都以 UTC 计时,换算北京时间自己减 8 小时。

四条硬约束

  1. 本地和 Preview 环境不执行——encore run 下 Cron 不会跑。本地调试就手动 curl 那个端点(所以端点常同时声明 expose 或留私有再用仪表盘 API Explorer 调);
  2. 端点必须无参——调度器不会构造参数;
  3. 端点必须幂等——调度系统故障恢复时可能补跑或重跑;
  4. 端点可以是私有(不写 expose)——Cron 调度属于内部调用,无需暴露公网。

适用边界

CronJob 适合运维型周期任务:对账、清理过期数据、缓存预热、报表生成、监控巡检(第 14 章实战)。它不是业务流程编排器——"下单 30 分钟未支付自动取消"这类单实例定时需求,用"Cron 每分钟扫表"模式实现,而不是幻想给每个订单挂一个定时器。

typescript
// 典型模式:Cron 扫表处理到期业务
export const cancelExpired = api({}, async () => {
  await db.exec`
    UPDATE orders SET status = 'CANCELLED'
    WHERE status = 'CREATED' AND created_at < NOW() - INTERVAL '30 minutes'
  `;
});

const _ = new CronJob("cancel-expired", {
  title: "取消超时未支付订单",
  every: "1m",
  endpoint: cancelExpired,
});

二、Secrets:密钥管理

声明与使用

typescript
import { secret } from "encore.dev/config";

// 声明:返回的是一个函数
const paymentApiKey = secret("PaymentApiKey");

// 使用:调用函数取值(运行时按当前环境解析)
async function callPaymentGateway() {
  const resp = await fetch("https://gateway.example.com/pay", {
    headers: { Authorization: `Bearer ${paymentApiKey()}` },
  });
}

要点:

  • secret("Name") 也是静态分析识别的原语——应用模型知道你的应用需要哪些密钥,缺值会在部署前报出;
  • 密钥名全应用唯一(跨服务共享同名密钥即同一个值);
  • 代码库里永远没有明文。

三种设置方式

bash
# 1. CLI(推荐):按环境类型设置
encore secret set --type dev,local,pr PaymentApiKey
encore secret set --type prod PaymentApiKey

# 2. 本地覆盖文件(gitignore 掉):.secrets.local.cue
#    PaymentApiKey: "test-key-xxxx"

# 3. Encore Cloud 控制台:Settings → Secrets

环境类型四种:production(prod) / development(dev) / preview(pr) / local。解析优先级:具体环境的专属值 > 环境类型值;本地开发时 .secrets.local.cue 覆盖一切。

管理命令:

bash
encore secret list                # 列出全部密钥
encore secret archive <id>        # 归档旧值
encore secret unarchive <id>      # 恢复

本机 B2B 项目的密钥实践 上游分销网关的 AK/SK、支付渠道私钥全部走 secret() + .secrets.local.cue(gitignored),仓库里只提交 .secrets.local.cue.example 模板。可选配置类 secret(如网关域名覆盖 DistGatewayUrl)约定"空值=默认行为,配错报错不回落"——把配置错误暴露在启动期而不是线上请求期。

自托管场景

不用 Encore Cloud 时,密钥通过 Docker 运行时的环境变量/挂载注入(第 16 章 infra-config.json{"$env": "DB_PASSWORD"} 的形式),secret() 的代码写法不变。

三、两原语的共同哲学

CronJob 和 Secret 看似不相关,但体现同一设计:把"应用需要什么"声明在代码里,把"具体值/具体调度"留给环境。于是:

  • 静态分析能画出完整依赖图(这个应用需要 3 个密钥、2 个定时任务);
  • 换环境不改代码;
  • 评审代码就能看见全部基础设施需求——不存在"部署时才发现少配了个密钥"。

四、常见误区

  1. every: "7h"——不能整除 24h,编译报错。
  2. 在本地等 Cron 触发——本地不执行,手动调端点。
  3. Cron 端点带参数或不幂等——前者不满足约束,后者在补跑时出重复副作用。
  4. 忘了 UTC——schedule: "0 4 * * *" 是北京时间中午 12 点。
  5. 把密钥写进 encore.app 或普通配置文件——密钥只走 secret()
  6. .secrets.local.cue 提交进 git——必须 gitignore,仓库只放 .example 模板。
  7. 在 handler 里 secret("Name") 现声明——原语包级声明;handler 里只调用取值函数。
  8. 用 Cron 实现"每订单一个定时器"——用扫表模式。

五、实践练习

  1. 给订单应用加"每分钟取消超时未支付订单"的 Cron(扫表模式),本地手动 curl 验证逻辑,观察仪表盘里 Cron 的注册信息。
  2. 把练习 1 的间隔改成 90s,看编译器的反应;再改用 schedule 表达"每天北京时间 09:00"(提示:UTC 01:00)。
  3. 声明 SmsApiKey 密钥,用 .secrets.local.cue 提供本地值,在通知服务里读取;确认明文不出现在任何提交文件里。
  4. encore secret set --type dev,local,pr--type prod 给同一密钥设不同值,说出每个环境各取到哪个。
  5. 结合第 7 章:实现 outbox 补发——事件表 + 每分钟 Cron 扫描未发布事件重新 publish。

六、总结

  1. CronJob = 一行声明的定时任务:every 须整除 24h、schedule 走 cron 表达式、全部 UTC。
  2. 四条硬约束:本地不跑、端点无参、必须幂等、可以私有。
  3. Cron 的正确用法是运维型周期任务与"扫表处理到期业务",不是业务编排器。
  4. secret() 声明密钥需求,值按环境类型解析:CLI 设置 / .secrets.local.cue 本地覆盖 / 云控制台。
  5. 两者共同哲学:需求进代码、取值留环境,依赖在编译期全部可见。

请继续阅读:第9章:对象存储与缓存


原始资料引用



本章目录
一、CronJob:声明式定时任务二、Secrets:密钥管理三、两原语的共同哲学四、常见误区五、实践练习六、总结原始资料引用Related Documents
苏ICP备2025204887号-2