背景:开源调度基础设施的演进
在 SaaS 调度工具(如 Calendly)逐步收紧免费策略、提高订阅价格的背景下,企业对数据主权和自托管能力的需求日益迫切。cal.diy 作为 Cal.com 的社区维护分支,采用 MIT 许可证,剥离了企业版功能(Teams、Organizations、SSO/SAML 等),保留了核心的调度基础设施能力,为个人开发者和小型团队提供了真正可掌控的开源替代方案。
与原版 Cal.com 相比,cal.diy 的定位更加纯粹:它不是一个商业化产品,而是一个可嵌入、可扩展的调度基础设施组件。这种定位决定了其架构设计必须兼顾模块化与可部署性。
技术架构:模块化单体与清晰边界
cal.diy 采用 Next.js 全栈框架,配合 tRPC 提供类型安全的 API 层,Prisma ORM 处理数据持久化,PostgreSQL 作为主存储,Redis 负责会话缓存和任务队列。这种技术选型在 Node.js 生态中属于成熟稳定的主流方案,降低了团队的接入门槛。
核心模块划分
代码库采用 monorepo 结构,按业务域划分模块边界:
- Booking 模块:处理预订创建、冲突检测、时区转换
- Availability 模块:管理可预订时间段、缓冲时间、最短提前通知
- Calendar 模块:抽象 Google Calendar、Outlook、CalDAV 等提供商的同步逻辑
- User 模块:用户配置、个人资料、预订页面定制
- Webhook 模块:事件触发与外部系统集成
这种模块划分使得开发者可以替换或扩展特定组件而不影响其他部分。例如,如果需要接入企业内部的日历系统,只需在 Calendar 模块中实现统一的接口契约,无需改动预订核心逻辑。
数据流与状态管理
预订流程涉及多个外部系统的协调,cal.diy 采用异步事件驱动模式处理日历同步:当用户创建预订时,系统首先写入本地数据库确认冲突,然后通过队列任务异步推送至外部日历。这种设计避免了外部 API 延迟对用户体验的影响,同时通过 Redis 的可靠性保证任务最终一致性。
自托管部署:Docker Compose 实践
cal.diy 的部署方案围绕 Docker 构建,官方提供预构建镜像,支持 x86_64 和 ARM64 架构。以下是生产环境部署的关键配置要点。
资源规划
| 资源类型 | 最低配置 | 推荐配置 |
|---|---|---|
| RAM | 2 GB(全栈) | 4 GB |
| CPU | 1 核 | 2 核 |
| 磁盘 | 10 GB | 20 GB |
| 应用空闲内存 | ~300 MB | ~400 MB |
PostgreSQL 是主要的内存消费者,建议为数据库容器单独分配资源限制。对于日均预订量低于 1000 的场景,最低配置足以支撑。
Docker Compose 配置
services:
calcom:
image: calcom/cal.com:v6.2.0
depends_on:
calcom-db:
condition: service_healthy
calcom-redis:
condition: service_started
environment:
- DATABASE_URL=postgresql://calcom:${DB_PASSWORD}@calcom-db:5432/calendso
- REDIS_URL=redis://calcom-redis:6379
- NEXTAUTH_SECRET=${NEXTAUTH_SECRET}
- CALENDSO_ENCRYPTION_KEY=${ENCRYPTION_KEY}
- NEXTAUTH_URL=${BASE_URL}/api/auth
- NEXT_PUBLIC_WEBAPP_URL=${BASE_URL}
ports:
- "3000:3000"
restart: unless-stopped
calcom-db:
image: postgres:15
environment:
- POSTGRES_DB=calendso
- POSTGRES_USER=calcom
- POSTGRES_PASSWORD=${DB_PASSWORD}
volumes:
- calcom_db:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U calcom -d calendso"]
interval: 10s
timeout: 5s
retries: 5
restart: unless-stopped
calcom-redis:
image: redis:7-alpine
volumes:
- calcom_redis:/data
restart: unless-stopped
volumes:
calcom_db:
calcom_redis:
环境变量配置
关键环境变量需要严格匹配部署域名:
# 公网 URL,用于 OAuth 回调和预订链接
BASE_URL=https://cal.example.com
# 数据库密码
DB_PASSWORD=change_me_to_a_strong_password
# 认证密钥,生成命令:openssl rand -base64 32
NEXTAUTH_SECRET=generate_a_random_string_here
# 数据加密密钥
ENCRYPTION_KEY=generate_another_random_string_here
常见陷阱:NEXTAUTH_URL 和 NEXT_PUBLIC_WEBAPP_URL 必须与公网域名完全一致,包括协议头(https://)。不匹配会导致认证循环或客户端获取错误。
日历集成:OAuth 流程与同步策略
cal.diy 支持 Google Calendar、Outlook/Office 365 和 CalDAV 协议。日历集成的核心挑战在于 OAuth 授权流程的配置和双向同步的冲突处理。
Google Calendar 接入
- 在 Google Cloud Console 创建项目并启用 Google Calendar API
- 创建 OAuth 2.0 凭据,配置重定向 URI 为
https://cal.example.com/api/auth/callback/google - 将客户端 ID 和密钥编码为 JSON,通过
GOOGLE_API_CREDENTIALS环境变量注入
时区处理
cal.diy 在数据库层统一存储 UTC 时间戳,在应用层根据用户配置的时区进行转换。这种设计避免了夏令时切换带来的歧义,同时支持跨时区团队的协作场景。每个预订事件都携带明确的 IANA 时区标识符(如 Asia/Shanghai),确保客户端展示的一致性。
运维与监控
备份策略
PostgreSQL 数据是唯一的持久化状态,建议每日执行逻辑备份:
docker exec calcom-db pg_dump -U calcom calendso > calcom-backup-$(date +%Y%m%d).sql
Redis 数据为临时缓存,丢失后可通过重新同步日历恢复,无需专门备份。
安全加固
- 通过反向代理(Caddy/Nginx)终止 TLS,容器内部使用 HTTP
- 定期轮换
NEXTAUTH_SECRET和ENCRYPTION_KEY - 限制数据库容器的网络暴露,仅允许应用容器访问
已知限制
cal.diy 作为社区维护分支,明确移除了企业级功能:
- 团队协作(Teams)和组织架构(Organizations)
- 高级分析(Insights)和工作流自动化(Workflows)
- SSO/SAML 认证
对于需要这些能力的场景,需要考虑替代方案或自行开发扩展。
选型建议
cal.diy 适合以下场景:
- 个人开发者:需要定制化的预订页面,拒绝 SaaS 的订阅模式
- 小型团队:成员数少于 10 人,无需复杂的权限管理
- 隐私敏感场景:医疗、法律等行业要求数据不出境
- 嵌入式集成:需要将预订功能集成到现有产品中的 ISV
如果需求涉及多团队管理、企业级 SSO 或高级分析,建议评估商业方案或基于 cal.diy 的代码进行定制开发。
资料来源
- Cal.com GitHub Repository
- cal.diy GitHub Repository
- Self-Hosting Cal.com with Docker Compose
- Hacker News Discussion on Cal.diy
内容声明:本文无广告投放、无付费植入。
如有事实性问题,欢迎发送勘误至 i@hotdrydog.com。