部署与运行环境
推荐主路径(单人 / 默认可维护路径)
| 场景 | 建议 |
|---|---|
| Vercel / Serverless 生产 | Turso:TURSO_DATABASE_URL + TURSO_AUTH_TOKEN;Vercel KV:KV_REST_API_URL + KV_REST_API_TOKEN;可选 WEBHOOK_FLOW_ASYNC + Cron 调 worker |
| 本机 / Docker | Node 22.5+ + DATABASE_ENGINE=nodejs-sqlite 与 SQLITE_PATH;或 file: libSQL |
MySQL(DATABASE_ENGINE=mysql)保留为次要路径,需单独维护 migrations/mysql/,与默认 migrations/*.sql 链路等价物需自建对照。
上线安全核对清单
-
NEXTAUTH_SECRET:强随机,生产必填。 -
INSTALL_SECRET:生产下安装变更 API 必填(头x-install-secret)。 -
FLOW_WORKER_SECRET:若启用WEBHOOK_FLOW_ASYNC,生产必填;POST /api/internal/flow-queue/worker须带x-flow-worker-secret。 -
CRON_TICK_SECRET:若使用控制台「定时任务」,生产必填;由 Cron / QStash 定期POST /api/internal/cron/tick,请求头x-cron-tick-secret与之相同。未配置时生产环境该路由会拒绝执行(开发环境可不配 secret)。 -
/api/dev/*:生产已由路由挡板禁止危险接口;开发环境若设DEV_DANGEROUS_API_SECRET,访问/api/dev/init-rbac等须带头x-dev-dangerous-secret。
赞助链接(可选)
应用可免费使用;若希望在营销首页、控制台侧栏与用户菜单、新手引导枢纽底部展示「赞助」出站链接:
NEXT_PUBLIC_SPONSOR_URL:主链接(例如 GitHub Sponsors、Ko-fi、爱发电)。侧栏与头像菜单使用该 URL 作为主入口。NEXT_PUBLIC_SPONSOR_LINKS_JSON(可选):JSON 数组,每项为{ "label": "显示名", "url": "https://..." };与主链接合并、去重后用于首页页脚与引导页多链接展示。- 未配置任何有效
http/httpsURL 时,全站不展示赞助入口(无「敬请期待」占位)。 GET /api/auth/public-config的响应体中含sponsor:{ enabled, primaryUrl, links },无敏感信息,可供其他客户端扩展。- 隐私:点击后用户即离开本站进入第三方页面,需遵守对应平台的条款与隐私政策。
控制台多语言(next-intl)
- 路由:页面路径为
/{locale}/...(当前zh-CN、en,localePrefix: always);访问/由src/proxy.ts中的 next-intl 中间件与 Cookie /Accept-Language协商后重定向。 - 文案:
src/messages/<locale>.json。站内导航请使用@/i18n/navigation的Link/useRouter/usePathname;服务端重定向使用localizedRedirect()(src/i18n/server-redirect.ts,内部getLocale()+ next-intlredirect)。 - API:标准错误体
jsonApiError的message随 CookieNEXT_LOCALE(next-intl 语言切换时写入)在apiRequireAuth/apiRequirePermission中选文案;中间件对未登录的/api/*亦使用同一套pickMessage。 - 扩展语言:在
src/i18n/routing.ts的locales增加语言代码,新增src/messages/<locale>.json,并保证src/app/[locale]/layout.tsx的generateStaticParams仍返回全部 locale。
应用核心是 Next.js App Router,与具体云平台解耦。数据分为两层:
| 层级 | 技术 | 用途 |
|---|---|---|
| 主库(SQL) | 可选引擎(见下) | 用户、RBAC、Flow/Job/Trigger、Bot 配置、聊天落库等 |
| 缓存(可选) | Redis over HTTP(@upstash/redis;优先 KV_REST_API_*,与 Vercel KV 一致) | 聊天列表缓存、联系人/群列表缓存、Webhook 事件日志 |
首次安装(/install)与版本升级(/upgrade)
- 门禁:
src/proxy.ts根据GET /api/install/status的phase分流(旧名middleware已弃用):no_database/needs_install→ 重定向到/install(首次:配库、可选同步 Vercel、一键建表 + RBAC)。needs_upgrade→ 重定向到/upgrade(实例已在跑,仅磁盘上migrations/*.sql有未应用版本时)。installed→ 正常应用;Webhook、Auth、安装 API 等白名单路径不受挡。
/install:填写TURSO_*(或本地node:sqlite路径),生成NEXTAUTH_*/ 可选KV_REST_API_*;可复制环境变量到托管控制台。- Vercel:可填写 Personal Token + Project ID,使用「同步环境变量到 Vercel」后 必须重新部署,再执行「数据库一键安装」。
/install与/upgrade均通过POST /api/install/complete执行迁移 + RBAC 校验;会对migrations/*.sql(不含mysql/)按文件名顺序应用:已记录在表schema_migrations的版本会跳过;遇非预期错误则中止。GET /api/install/status返回phase与lastAppliedMigration。INSTALL_SECRET(生产必填):POST /api/install/complete、POST /api/install/vercel-env在生产环境必须配置,且请求头x-install-secret与之相同;开发环境可不配。NEXTAUTH_SECRET(生产必填):用于会话加密;并作为 安装完成 Cookie(sb_install_ok) 的 HMAC 签名密钥(也可单独设INSTALL_COOKIE_SECRET)。勿使用可猜值。- GitHub 注册:默认
GITHUB_SIGNUP未设或open时允许新用户通过 GitHub 入库;若设GITHUB_SIGNUP=existing_only则仅允许已在users表内的账号。 - 账号即时停用:JWT 会按
SESSION_USER_CHECK_INTERVAL_MS(默认 120s)复查is_active与角色;停用后短时内会话失效。 - 新手引导(枢纽 + 分板块)(结构见
migrations/001_initial.sql/migrations/mysql/001_initial.sql)users.onboarding_completed_at:表示 枢纽/首次入门已处理(完成枢纽或明确跳过入门后由POST /api/onboarding/complete写入)。users.onboarding_sections_json:存各板块done/skipped等进度;不参与控制台强制重定向。- 门禁:
src/lib/onboarding/onboarding-gate.ts仅看onboarding_completed_at;具备roles:create/bots:create/flows:create/agents:manage之一且该列为空时,访问(dashboard)会进/onboarding枢纽。板块进度由GET/PATCH /api/onboarding/progress维护。
主库(SQL)— 三种后端
由 src/lib/database/db.ts 根据环境变量选择实现(应用侧请通过 src/lib/data-layer 统一访问),方言由 DATABASE_ENGINE 或隐式规则决定。
1. libSQL(Turso / 自建 sqld / file:)
- 首选:
TURSO_DATABASE_URL、TURSO_AUTH_TOKEN(Vercel Turso 集成默认) - 次选:
LIBSQL_URL、LIBSQL_AUTH_TOKEN(仅当未设置TURSO_DATABASE_URL时使用 URL;Token 两组可交叉后备) - 本地文件示例:
file:./relative/path.db(可不配 token)
2. 本地 SQLite(Node.js 自带 node:sqlite)
- 需 Node.js ≥ 22.5(仓库
package.json中engines.node) - 推荐显式:
DATABASE_ENGINE=nodejs-sqlite - 路径:仅
SQLITE_PATH(可写file:./data.db,会去掉file:前缀) - 不要同时配置可用的
LIBSQL_URL/TURSO_DATABASE_URL,否则推断会走 libsql(见下「优先级」)
3. MySQL 8.x(可选,非默认)
- 必须同时设置:
DATABASE_ENGINE=mysql(不会仅因MYSQL_DATABASE自动启用) MYSQL_USER、**MYSQL_DATABASE**必填;MYSQL_HOST(默认127.0.0.1)、MYSQL_PORT(默认3306)、MYSQL_PASSWORD可选- 首次建表:执行
migrations/mysql/001_initial.sql(先CREATE DATABASE ... utf8mb4) - 业务假定迁移已含
owner_id等列,不会像 SQLite 那样在运行时自动补列
优先级(未设置 DATABASE_ENGINE 时)
- 若配置了
SQLITE_PATH且未配置LIBSQL_URL/TURSO_DATABASE_URL→ node:sqlite - 否则 → libsql(依赖 URL/token,见
resolvePrimarySqlConfig/pickLibsqlFromEnv) - MySQL 仅在显式
DATABASE_ENGINE=mysql且配好MYSQL_USER/MYSQL_DATABASE时启用(默认部署以 SQLite / libSQL 为主)
未配置任一可用主库时:读查询多返回空、写入不落库(仅适合无面板调试)。
SQLite 迁移在 migrations/*.sql(当前主要为 001_initial.sql),面向空库首次执行;若线上库结构已落后于仓库,请清空后重建 Turso 库或自行导出数据后换库,勿指望迁移内包含对极旧表的逐列 ALTER。MySQL 使用 migrations/mysql/。另有 ensure-owner-schema.ts 仅补 owner 相关旧列。
缓存(Redis REST)
聊天与日志使用 @upstash/redis(HTTP REST,与 Vercel KV / Upstash 一致)。
- 首选(与 Vercel 控制台一致):
KV_REST_API_URL、KV_REST_API_TOKEN(须读写 Token,勿用只读 Token)。 - 次选:Upstash Console 的
UPSTASH_REDIS_REST_URL、UPSTASH_REDIS_REST_TOKEN。
未配置可用的 URL+Token 或 KV_BACKEND=memory:使用进程内内存缓存(重启丢失)。
ChatStore(Hybrid):SQL 与 KV 谁主谁备
控制台聊天、联系人、群列表由 getChatStore() 返回的 HybridChatStore(src/lib/persistence/chat-store.ts)读写:
- 写路径:若已配置关系库,先尽量写 SQL,再写 KV。SQL 失败时打 结构化日志(
event: hybrid_chat_sql_failure)。若设置CHAT_SQL_REQUIRED=1(或true),SQL 写失败将 抛出错误且不再写 KV,避免「面板与主库」静默分叉。 - 读路径:若 SQL 可用,优先从 SQL 读;SQL 未配置或读失败则回落到 KV(失败同样记录结构化日志)。
因此:持久真相以 SQL 为准(在已配置且写入成功时);KV 为加速 / 会话级缓存(TTL 见实现,如联系人列表约 30 分钟)。未配 SQL 时,仅剩 KV/内存,重启可能丢数据。
flowchart LR
API[Chat API / Webhook]
H[HybridChatStore]
SQL[(SQL 主库 messages / contacts / groups)]
KV[(Redis REST 或内存)]
API --> H
H -->|写: 先 SQL 再 KV| SQL
H -->|写: 始终| KV
H -->|读: 优先 SQL| SQL
H -->|读: SQL 不可用时| KV
Serverless 超时与异步执行
Flow、LLM、外向 HTTP 等在 Vercel Functions 上受单次请求最大执行时间限制(计划与区域不同,常见默认约 10s~60s,需在控制台或计划中确认)。长时间工作易 504 / 被平台切断。
已实现(仓库内):
WEBHOOK_MAX_DURATION_SEC(默认60,上限300):/api/webhook/...与/api/chat/send的maxDuration。FLOW_PROCESS_BUDGET_MS:非 0 时,FlowProcessor在步骤前与delay步骤中 respect 软截止(超时则停止后续步骤并记录原因)。- 异步入队:设置
WEBHOOK_FLOW_ASYNC=1(或true)后,Webhook 在校验与解析成功后 将事件 JSON 推入 Redis/内存列表(键flow:webhook:queue)。默认在入队前对event.id做 NX 去重(WEBHOOK_FLOW_DEDUPE_TTL_SEC,默认 86400s)。若设WEBHOOK_FLOW_DEDUPE_ON_SUCCESS_ONLY=1,则入队前不去重,仅在 Flow 成功结束后 写入去重键,便于 IM 侧用同一event.id对失败投递重试(可能与并发重复入队并存,由业务权衡)。
需由 Cron / QStash 等定期请求:POST /api/internal/flow-queue/worker(处理主队列 + 将到期延时重试并入队)- 请求头:
x-flow-worker-secret: <FLOW_WORKER_SECRET>(生产 必填)。 - 常用环境变量:
FLOW_WORKER_BATCH(默认 8)、FLOW_WORKER_MAX_DURATION_SEC(默认 300)、FLOW_WORKER_MAX_ATTEMPTS(默认 3,超过进 DLQ)、FLOW_WORKER_RETRY_DELAY_MS(默认 0;大于 0 时使用 sorted setflow:webhook:retry_z延时再次进入主队列)、FLOW_WORKER_DLQ_MAX(死信列表flow:webhook:dlq截断长度,默认 2000)、WEBHOOK_FLOW_QUEUE_MAX(主队列上限,默认 5000)。
Worker 返回 JSON 含promotedRetries、retried、dlqPushed;每次出队的异步任务会打结构化日志event: flow_worker_job(outcome:ok|retry|dlq|skip)。
- 观测(不返回队列明文):
GET /api/internal/flow-queue/status,请求头同样带x-flow-worker-secret,响应queueLen/dlqLen/retryDueCount/retryScheduledTotal/kvBackend。可接监控告警(例如queueLen长时间高于阈值、dlqLen持续增长)。 - 仍可通过 Upstash QStash 等对 worker URL 做重试与观测。
Flow 异步队列运维
- Cron 频率:建议 ≤ 主队列平均滞留时间(例如每秒或每 5~15 秒一次,视流量调整);延时重试依赖 worker 拉取到期项,频率过低会拉长重试延迟。
- DLQ 处置:在 Upstash Console 对 key
flow:webhook:dlq做LRANGE查看 JSON 条目(kind: corrupt为无法解析的原始行;kind: failed为处理失败);确认后可DEL或按需写脚本回放(回放需自行防重)。 - 秘钥轮换:更新
FLOW_WORKER_SECRET后,同步修改 Cron/QStash 请求头;旧 secret 立即失效。 - 已知限制:消费采用
lpop,若在 取出后、执行完前 进程被平台强杀,极少数情况会丢单。缓解:控制FLOW_WORKER_BATCH、用 QStash 对 worker HTTP 重试、或后续自研 RPOPLPUSH + lease 回收(需扩展 Redis 命令与回收路由)。
自建 Docker / 长驻 Node 时一般无平台级 maxDuration,但仍需注意反向代理超时。
定时任务(Job 步骤驱动)
控制台 「定时任务」 将 Cron 表达式 与已有 步骤流水线(Job)+ 机器人(Bot) 绑定;到期时由内部路由 POST /api/internal/cron/tick 扫描到期项并调用与 Webhook 相同的 FlowProcessor / 步骤执行器(合成 notice 事件上下文,可在步骤里用变量如 scheduledTaskId、cronFiredAt)。
- 环境变量:
CRON_TICK_SECRET(生产必填);请求头x-cron-tick-secret。 maxDuration:tick 路由与 flow worker 类似建议使用较长上限(仓库内已设300)。- 幂等与重试:仅当 Job 整链成功 后更新
last_run_at;失败则下次 tick 可再次尝试。 - 执行记录:完整初始化脚本已包含表
scheduled_task_runs;每次 tick 会写入开始/结束时间与ok/error及错误摘要,详情页可拉取GET /api/scheduled-tasks/:id/runs。若库里 缺少该表(极旧库),接口会返回200、runs: []、degraded: true(reason: scheduled_task_runs_missing),便于与真实 500 区分。 - Vercel Cron 示例:在
vercel.json增加对https://<部署域名>/api/internal/cron/tick的定时POST,并在 Environment Variables 中配置CRON_TICK_SECRET,Cron 请求的 Headers 需附带x-cron-tick-secret(若平台不支持自定义 Header,需改用 QStash 等可带头部的调度)。
运行形态
| 环境 | 说明 |
|---|---|
| 任意 Serverless / Edge | 只要支持 Node 或 Edge + 出站 HTTPS,能连主库与可选 Redis REST 即可。 |
| Docker / VPS | 使用仓库 Dockerfile(output: 'standalone');配齐 SQL + 可选 Redis REST。 |
| Railway / Fly.io / Render | 与 Docker 类似,注入环境变量;PORT、HOSTNAME=0.0.0.0 已在 Dockerfile 中示例。 |
对外 URL
NEXTAUTH_URL:公网根地址(无末尾斜杠);代码里 getPublicAppUrl() 只读此变量。
另见 getRuntimeSummary()(src/lib/runtime/runtime.ts)。其中 getRuntimeSummary().sql 使用 isRelationalDatabaseConfigured(),覆盖 libsql / node:sqlite / MySQL。
Docker 示例
docker build -t serverless-bot .
docker run --rm -p 3000:3000 \
-e NEXTAUTH_URL=https://your.domain \
-e NEXTAUTH_SECRET=... \
-e LIBSQL_URL=file:/data/app.db \
-e UPSTASH_REDIS_REST_URL=... \
-e UPSTASH_REDIS_REST_TOKEN=... \
serverless-bot
本地 node:sqlite 示例:挂载数据目录并设置 DATABASE_ENGINE=nodejs-sqlite、SQLITE_PATH=/data/app.db。
远端 MySQL:设置 DATABASE_ENGINE=mysql 与 MYSQL_*,且不在该容器内依赖 LIBSQL_URL。