运维与安全
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 reCAPTCHA | v2 复选框或 v3 评分(可设最低分);脚本域名可选 www.recaptcha.net,中国大陆可访问 |
| 关闭 | 只保留频率限制 |
保存前必须用新设置完成一次验证,密钥填错不会把管理员锁在后台外。生效顺序:
- 环境变量
NUXT_CAPTCHA_PROVIDER(none | local | turnstile | recaptcha)——应急用,例如被锁在外时设为none重新部署; - 后台保存的选择;
- 站点默认
runtimeConfig.public.captchaDefault(站点 nuxt.config 或NUXT_PUBLIC_CAPTCHA_DEFAULT); - 自动:配置了 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 文件。