部署与运维

运维与安全

v1.2 起基座内置以下运维能力,全部可选:不配置对应变量就自动关闭,不影响现有站点。

定时发布

文章、页面和产品可以设为「定时」(编辑器里填将来的发布时间后点「定时发布」)。到点后自动发布并触发 content.published 事件:配置了定时任务调度时由 /api/cron/dispatch 处理;没有配置时,正常访问也会触发检查(每个实例最多每 5 分钟一次,在后台进行,不影响访问速度),所以不配 cron 也能用,只是没人访问时会延后到下一次访问。

定时任务调度

后台「定时任务」里配置的任务按 cron 表达式自动执行,由 POST /api/cron/dispatch 统一调度。

NUXT_CRON_SECRET=<随机长字符串>     # 调度接口的 Bearer 令牌
NUXT_CRON_TZ_OFFSET=480             # 排期时区(分钟),默认北京时间

谁来唤醒调度器:

部署形态方式
Cloudflare Pages部署 deploy/cron-worker(复制 wrangler.toml.example,wrangler secret put CRON_SECRET,wrangler deploy),默认每 5 分钟
Linux / 宝塔crontab 或宝塔「计划任务」:curl -fsS -X POST -H "Authorization: Bearer $NUXT_CRON_SECRET" https://站点/api/cron/dispatch
多个站点集中触发 + 失败告警一台宝塔服务器、每站一条「Shell 脚本」任务(每 15 分钟)运行 deploy/cron-baota/run.sh;失败时输出关键词 NUXTCMS_CRON_ALERT,宝塔按关键词通过飞书 / 邮件告警。见 deploy/cron-baota/README.md
手动补跑同一命令加 ?all=1,忽略排期把启用的任务全跑一遍

每次调度还会顺带做维护:任务日志只保留最近 500 条,清理过期的限流记录。

只有管理员能查看、新建和执行定时任务。调度密钥只会发给本站:路径以 /api/cron/ 开头的任务总是请求本站当前域名(自动带上 Authorization: Bearer …),请求头或请求体里的 {{cronSecret}} 占位符也只在请求本站域名时替换,发往其他网址时会被替换为空。

编辑(非管理员)在后台保存的文章、页面、产品等内容,会自动去掉脚本、iframe、表单和事件属性;管理员保存的内容保持原样。

内置任务接口

在后台新建任务时,URL 填下面的地址,请求头填 {"Authorization":"Bearer <NUXT_CRON_SECRET>"}:

接口作用需要的设置项
/api/cron/send-contact-emails把新的联系表单发邮件给站长contactNotifyEmail(缺省用 companyEmail),并在「邮件配置」里配好发信
/api/cron/push-search-engines主动推送 URL 给 IndexNow(Bing、Yandex 等)和百度indexNowKey、baiduPushToken,没填的渠道跳过;/{indexNowKey}.txt 自动提供

健康检查

GET /api/health 做一次数据库往返,返回驱动、存储、版本与耗时;数据库不可用时返回 503。适合接入 Uptime Kuma、宝塔监控或 Cloudflare 健康检查。

人机验证(后台 → 安全验证)

后台「安全验证」页选择验证方式,并勾选作用位置(后台登录、联系表单、博客评论、邮件订阅、商城下单):

方式说明
内置图形验证码无需第三方服务和密钥,国内外均可用;可选字母数字或算术题。验证码一次性、5 分钟有效
Cloudflare Turnstile大多数访客无感通过;填站点密钥与私钥,或使用环境变量
Google reCAPTCHAv2 复选框或 v3 评分(可设最低分);脚本域名可选 www.recaptcha.net,中国大陆可访问
关闭只保留频率限制

保存前必须用新设置完成一次验证,密钥填错不会把管理员锁在后台外。生效顺序:

  1. 环境变量 NUXT_CAPTCHA_PROVIDER(none | local | turnstile | recaptcha)——应急用,例如被锁在外时设为 none 重新部署;
  2. 后台保存的选择;
  3. 站点默认 runtimeConfig.public.captchaDefault(站点 nuxt.config 或 NUXT_PUBLIC_CAPTCHA_DEFAULT);
  4. 自动:配置了 Turnstile 环境变量则用 Turnstile,配置了 reCAPTCHA 环境变量则用 reCAPTCHA,否则用内置图形验证码。

所选方式缺少密钥时自动退回内置验证码,表单不会卡死。密钥也可以放在环境变量里(后台对应字段留空):

NUXT_TURNSTILE_SECRET_KEY=...
NUXT_PUBLIC_TURNSTILE_SITE_KEY=...
NUXT_RECAPTCHA_SECRET_KEY=...
NUXT_PUBLIC_RECAPTCHA_SITE_KEY=...

站点自定义表单:前端用 useCaptcha('contact')(旧名 useTurnstile() 仍可用,自动套用当前方式),把容器 ref 渲染出来、提交时带上 token、提交后调用 reset();服务端用 assertCaptcha(event, token, 'contact')(旧名 assertTurnstile(event, token) 按请求路径识别位置)。自定义表单暂时无法渲染组件时,在站点 nuxt.config 里声明,这些位置不会被强制校验,后台显示为不可选:

runtimeConfig: { public: { captchaUnsupportedScenes: ['contact'] } }

站点如自定义了 CSP,需在 script-src 与 frame-src 中放行:Turnstile 为 https://challenges.cloudflare.com;reCAPTCHA 为 https://www.google.com https://www.gstatic.com https://www.recaptcha.net。内置验证码无需放行。

限流

登录、联系表单、安装、浏览量接口带限流。计数存放在 rate_limits 表里,多个 Worker 实例、多个 Node 进程共享同一份计数;表不存在时退化为单实例内存计数。

浏览量

GET /api/views?kind=post&id=1 读取,POST /api/views 计数一次(每 IP 每分钟最多 60 次)。数据存放在 content_views 表,不改动文章与产品表。页面里用 useViewCounter('post', () => id),基座的资讯和产品详情页已经接入。

访问统计

NUXT_PUBLIC_CF_ANALYTICS_TOKEN=...   # Cloudflare Web Analytics,无 Cookie,不需要同意弹窗

后台页面不统计。

启动环境检查

启动时检查密钥缺失、示例值、成对配置只配了一半(Turnstile、对象存储)等问题,默认只打警告日志;设置 NUXT_STRICT_ENV=1 后生产环境直接启动失败。

数据库迁移

v1.2 新增 0013_rate_limits、0014_content_views 两个 SQLite 迁移(均为 IF NOT EXISTS,可重复执行),PostgreSQL 与 MySQL 有对应迁移。站点部署时使用 pnpm deploy:migrate,或对 D1 直接执行这两个 SQL 文件。