部署指南
本指南将帮助你将 IAM 项目部署到 Cloudflare。当前生产架构中,公开前端域名为 https://iam.cq-i.cn。Hono 后端 iam-api 通过 Cloudflare Service Binding AUTH_API 被 Web Worker 调用,浏览器的 API 流量不需要额外公网入口;iam-api 自身只保留一条 iam.cq-i.cn/api/wecom/callback* 路由用于接收企业微信回调。
Cloudflare 部署
Section titled “Cloudflare 部署”前端部署 (apps/web)
Section titled “前端部署 (apps/web)”1. OpenNext 配置
Section titled “1. OpenNext 配置”前端通过 @opennextjs/cloudflare 部署,apps/web/wrangler.jsonc 绑定自定义域名:
{ "routes": [{ "pattern": "iam.cq-i.cn", "custom_domain": true }], "services": [{ "binding": "AUTH_API", "service": "iam-api" }]}2. API 代理(必做,否则接口无 session)
Section titled “2. API 代理(必做,否则接口无 session)”浏览器必须统一访问 https://iam.cq-i.cn/api/auth/*,再由 Web Worker 通过 Service Binding 转发到 Hono 后端。这样 Better Auth 下发的 HttpOnly Cookie 会保存到 iam.cq-i.cn,前端只通过 authClient.useSession() 获取会话状态。
生产环境不要把 iam-api 当作公开 issuer。OAuth/OIDC issuer 固定为 https://iam.cq-i.cn/api/auth。
3. 环境变量
Section titled “3. 环境变量”前端生产不需要 AUTH_UPSTREAM_URL。该变量只用于本地 Next.js 回退到 http://localhost:8787。
后端部署 (apps/server)
Section titled “后端部署 (apps/server)”1. Worker 配置
Section titled “1. Worker 配置”后端通过 Hono Worker 部署,入口是 src/worker.ts(它 import src/index.ts 的 Hono 应用,并额外实现 scheduled 定时任务),生产环境变量在 apps/server/wrangler.jsonc 中固定公开 issuer:
{ "BETTER_AUTH_URL": "https://iam.cq-i.cn/api/auth", "CORS_ORIGIN": "https://iam.cq-i.cn"}后端还保留一条公网路由 iam.cq-i.cn/api/wecom/callback*,用于接收企业微信的回调请求;其余 API 流量由 Web Worker 经 Service Binding 转发,不需要公网入口。
2. 环境变量
Section titled “2. 环境变量”确保设置所有服务器环境变量:
DATABASE_URL: PostgreSQL 连接字符串DATABASE_URL_DIRECT: 直接数据库连接(用于迁移)BETTER_AUTH_SECRET: 认证密钥(至少 32 个字符)BETTER_AUTH_URL: 公开认证服务 URL,生产固定为https://iam.cq-i.cn/api/authCORS_ORIGIN: 允许的前端来源,生产固定为https://iam.cq-i.cnAUTH_EMAIL_FROM: 认证邮件发件人AUTH_EMAIL_PROVIDER:cloudflare(默认)或tencent-sesNODE_ENV: 环境类型(production)PROVISIONING_DELIVERY_ENABLED: 设为false时只对账、不向接收方投递开通事件;不配置则默认投递。生产当前在vars中显式设为trueWECOM_FULL_SYNC_CRON_TOKEN:/api/internal/wecom/full-sync与/api/internal/maintenance/cleanup的 Bearer token
后端还需要 R2 Binding UPLOADS 指向 iam 存储桶;选择 Cloudflare 发信时还需 Email Binding EMAIL。生产发布请使用根目录 pnpm deploy:它会先部署 iam-api,成功后再部署 iam-web。
切换腾讯云 SES SMTP
Section titled “切换腾讯云 SES SMTP”腾讯云 SES 控制台需先完成发信域名验证、创建发信地址,并为该地址设置 SMTP 密码。广州区可使用 gz-smtp.qcloudmail.com:465(SSL);其他地区应使用控制台对应地址。
在 iam-api Worker 配置以下变量(也可全部用 secrets 保存):
TENCENT_SES_SMTP_HOST=gz-smtp.qcloudmail.comTENCENT_SES_SMTP_PORT=465TENCENT_SES_SMTP_ENCRYPTION=sslTENCENT_SES_FROM=noreply@iam.cq-i.cnTENCENT_SES_FROM_NAME="IAM 账户通知"用户名使用腾讯云 SES 控制台对应的 SMTP 账号,密码使用为发信地址设置的 SMTP 密码,不是腾讯云账号密码。
pnpm -F server exec wrangler secret put TENCENT_SES_SMTP_USERNAMEpnpm -F server exec wrangler secret put TENCENT_SES_SMTP_PASSWORD配置完成后,将 apps/server/wrangler.jsonc 中 AUTH_EMAIL_PROVIDER 改为 tencent-ses,再发布后端。生产变量应保存在 Wrangler 配置中,避免下次部署覆盖控制台临时修改。
验证首次邮箱验证、重发验证邮件和忘记密码邮件,结合腾讯云投递日志确认送达。提交 SMTP 成功不等于邮箱实际收到。发信失败会直接记录错误,不自动切换供应商,避免重复发送。需要回滚时将 provider 改回 cloudflare 并重新发布,保留的 EMAIL Binding 可继续使用。
SMTP 用户名和密码仅保存在服务端 secrets,不要写入 NEXT_PUBLIC_* 或提交 Git。腾讯云 SMTP 的账户资格与发送限制以官方 SMTP 指南为准。
OA_PROVISIONING_* / CRM_PROVISIONING_* 是旧版开通配置的兼容变量,仅用于首次自动导入到 provisioning_connection 表;新应用直接在管理端配置,不再需要这些变量。
3. Secret 与 Binding
Section titled “3. Secret 与 Binding”apps/server/wrangler.jsonc 的 secrets.required 是生产 secret 白名单。数据库、Better Auth 和企业微信凭据使用 wrangler secret put,不要写入 vars 或提交到 Git。
当前 Binding:
UPLOADS:R2 bucketiamEMAIL:认证邮件发送AUTH_API:定义在 Web Worker,目标为iam-api
注意 WECOM_FULL_SYNC_CRON_TOKEN 目前不在 secrets.required 中,但代码会读取它;未配置时内部同步与清理端点会因 token 不匹配而返回 401。部署时请单独用 wrangler secret put 配置。
平台管理员角色以数据库 user.role 为准,由用户管理页维护。旧的 BOOTSTRAP_ADMIN_WECOM_USER_IDS 已停用,升级后可删除 Cloudflare 上遗留的同名变量;部署前确认数据库中至少保留一位 admin。
4. 定时任务
Section titled “4. 定时任务”wrangler.jsonc 配置了两个 Cloudflare Cron(时间为 UTC):
| Cron | 北京时间 | 用途 |
|---|---|---|
15 18 * * * |
02:15 | 企业微信通讯录全量同步 |
*/5 * * * * |
每 5 分钟 | 应用开通投递对账与补发 |
本地 Node 模式没有 Cron 触发,需要手工调用 /api/internal/wecom/full-sync,或使用 Worker 运行模式验证。
Hono 服务器配置
Section titled “Hono 服务器配置”Worker 入口是 apps/server/src/worker.ts:它从 apps/server/src/index.ts 导入 Hono 应用并实现 scheduled 定时任务。
import app from './index'import { runScheduledCron } from './scheduled-maintenance'
export default { fetch(request, env, context) { return app.fetch(request, env, context) }, async scheduled(controller) { await runScheduledCron(controller.cron) }} satisfies ExportedHandler<Env>Wrangler 按 apps/server/wrangler.jsonc 的 main: "src/worker.ts" 将其作为 Worker 部署。改动定时任务时记得同时更新 triggers.crons。
# 构建所有应用pnpm run build
# 检查类型pnpm run check-types
# lint + 格式校验 + 类型检查(不修改文件)pnpm run check-
使用 Turborepo 缓存
- 构建结果自动缓存
- 只重新构建变更的包
-
环境变量优化
- 只在需要的地方使用环境变量
- 使用
@IAM/env包进行类型验证
-
代码分割
- Next.js 自动代码分割
- 使用动态导入减少初始包大小
仓库当前没有配置 Dependabot 或 Renovate,也没有 .github/dependabot.yml / renovate.json;依赖升级目前靠人工发起。
如果需要自动化,可选两种方案:
- GitHub Dependabot:仓库原生,新增
.github/dependabot.yml并按 pnpm workspace 的目录逐个声明package-ecosystem: npm,即可按周为有问题的依赖开 PR。 - Renovate:分组、自动合并等策略更细,安装 App 后加
renovate.json配置。
无论用哪种,生成的 PR 都必须与普通 PR 一样通过 .github/workflows/ci.yml 的门禁(lint、格式、类型检查、Vitest、Playwright)后再合并。
基础设施部署最佳实践
Section titled “基础设施部署最佳实践”地区选择原则
Section titled “地区选择原则”重要提示:Cloudflare Worker 与 Neon 数据库应选择相近地区,避免跨洲往返。
为什么需要同地区部署?
Section titled “为什么需要同地区部署?”-
延迟问题
- 跨洲网络延迟通常为 100-300ms,甚至更高
- 同地区延迟通常 < 10ms
- 认证流程包含多次数据库查询,延迟会累积
- 跨洲部署会导致用户体验显著下降
-
性能影响
- 登录、授权码兑换和限流都会访问 Postgres
- 跨洲延迟会显著增加响应时间
- 可能导致超时和连接失败
-
成本考虑
- 跨洲数据传输可能产生额外费用
- 同地区部署可以优化成本
推荐部署方案
Section titled “推荐部署方案”亚太地区示例:
Cloudflare Workers (iam-web + iam-api,Service Binding) ↓ < 10msPostgreSQL(Neon ap-southeast-1)推荐地区组合:
- 亚太地区:Workers 边缘执行 + Neon
ap-southeast-1 - 其他地区:同样把 Neon 区域选在用户附近
如何验证地区配置
Section titled “如何验证地区配置”-
检查数据库连接字符串
Terminal window # PostgreSQL 连接字符串应包含地区信息# 例如:ep-xxx.ap-southeast-1.aws.neon.tech -
测试延迟
Terminal window # 从 Worker 到 Neon 的往返延迟应该 < 20ms(同地区)# 如果 > 100ms,可能是跨洲部署
部署检查清单
Section titled “部署检查清单”在部署前,确认:
- Cloudflare Worker 部署地区:✅ / ❌
- PostgreSQL 数据库地区:✅ / ❌
- Workers 与 Neon 在相近地区:✅ / ❌
- 测试延迟 < 20ms:✅ / ❌
常见错误示例
Section titled “常见错误示例”❌ 错误配置:
Worker:CloudflarePostgreSQL:Neon(欧洲)结果:每次请求延迟 200-400ms,用户体验极差
✅ 正确配置:
Worker:CloudflarePostgreSQL:Neon (ap-southeast-1)双 Worker 通过 AUTH_API Service Binding 通信结果:每次请求延迟 < 20ms,性能优秀
部署检查清单
Section titled “部署检查清单”- 设置正确的 Root Directory
-
apps/web/wrangler.jsonc配置AUTH_APIService Binding 指向iam-api - 配置所有必要的环境变量
- 测试生产构建:
pnpm run build
- 设置正确的 Root Directory
- 配置所有服务器环境变量
- 确保数据库连接字符串正确
- 创建 R2 存储桶
iam并绑定UPLOADS - 配置
EMAILBinding、发件地址和AUTH_EMAIL_FROM - 配置
WECOM_FULL_SYNC_CRON_TOKEN(内部同步与清理端点使用) - 确认
triggers.crons与src/worker.ts的定时任务一致 - 验证 CORS 配置允许前端域名
- 测试生产构建:
pnpm run build
- 创建生产数据库
- 运行数据库迁移:
pnpm run db:migrate - 验证数据库连接
- 生成安全的
BETTER_AUTH_SECRET - 配置
BETTER_AUTH_URL=https://iam.cq-i.cn/api/auth - 配置
CORS_ORIGIN=https://iam.cq-i.cn - 确认数据库中至少有一位平台管理员,并复核企业微信 secret
- 确认
PROVISIONING_DELIVERY_ENABLED的取值符合预期(false时不投递开通事件) - 如启用应用开通,确认接收方已配置凭据并能通过协议的测试向量
- 检查环境变量是否在
turbo.json中声明 - 验证所有依赖都已安装
- 查看构建日志中的错误信息
- 检查环境变量是否正确设置
- 验证数据库连接
- 检查 CORS 配置
- 运行
pnpm run check-types检查类型 - 确保所有 workspace 依赖都已构建
权限变更事务与同步任务迁移
Section titled “权限变更事务与同步任务迁移”安全加固版本新增迁移 0015_melted_logan.sql,仅创建 provisioning_reconcile_job 表及客户端外键,不修改已有身份字段和业务数据。发布顺序为:在目标环境使用直连数据库执行 pnpm db:migrate,确认成功后部署 iam-api,再部署 iam-web。未迁移时不可发布依赖新表的 API 版本。
应用配置、用户/部门授权变更与关键审计在同一事务内提交;需要同步的变更同时写入对账任务。任务由每五分钟 Cron 消费,后台失败保留重试;同一客户端有新变更时更新 generation,旧任务不能删除新任务。已有全量对账继续作为补偿。
公开 /api/health 仅提供存活状态与时间戳,不再提供环境变量诊断。上传接口忽略客户端 pathname,由服务端使用当前用户 ID 和 UUID 生成新对象,已有文件 URL 可继续读取。
人员撤权通知任务迁移
Section titled “人员撤权通知任务迁移”人员通知可靠性补强新增迁移 0016_real_mariko_yashida.sql,仅创建 provisioning_user_reconcile_job 表及到用户表的级联外键,不修改 Better Auth 配置和认证 schema。发布前先在目标环境执行 pnpm db:migrate,再部署 API 和 Web;未迁移时不可发布依赖该表的版本。
部署后确认每 5 分钟的开通维护 Cron 已启用、PROVISIONING_DELIVERY_ENABLED 未设为 false,并在 CRM/OA 的应用详情中配置且启用同步连接。分别验收企微离职、企微禁用、管理员封禁,以及接收服务暂时故障后的重试;通知成功后还需由接收系统验证旧 Cookie / Token 被拒绝。