小团队想要一个自己可控的即时通讯工具,通常只有三条路:花钱买商业 SaaS、租服务器自托管 Rocket.Chat / Mattermost、或者直接 Mattermost 找运维。EdgeChat 选了第四条路:把整个聊天系统放在 Cloudflare 边缘上跑。这个项目在 GitHub 上有 460 颗星,采用 GPL-3.0 协议,仓库里绝大部分是 JavaScript,加上一个 Kotlin + Jetpack Compose 的 Android 原生客户端。目标很明确:用尽量低的运维成本,在 Cloudflare 生态里落地一套能直接用的站内 IM。
EdgeChat 的在线演示地址是 edgechat-demo.wcjxxgaq.workers.dev,不过那个 Demo 只在浏览器内存里模拟 API,刷新页面就会重置。真正可用的系统需要部署到你自己的 Cloudflare 账号里。README 把 EdgeChat 定位为对标 VoceChat 类产品的边缘聊天系统,取舍很直接:用 Cloudflare 的免费额度换取零服务器维护,用厂商锁定换取一键部署。
为什么把聊天系统跑在 Cloudflare Workers 上
EdgeChat 的核心赌注是「无服务器优先」。把聊天后端写成一个 Cloudflare Worker 后,不再需要维护虚拟机、容器或数据库进程。Cloudflare 负责把 Worker 推到全球边缘节点,用户连接到的就是最近的机房,WebSocket 长连接也交给 Durable Objects 托管。对于访问量不大的小团队来说,这意味着免运维、免补丁、免证书续期,更新代码只需要推送到 GitHub 仓库。
数据归属是另一个卖点。相比把聊天记录存在第三方 SaaS 的数据中心,EdgeChat 的数据库、文件桶和会话全部绑定在你自己的 Cloudflare 账号下。管理员后台可以看到用户数、频道数、存储占用等聚合指标,但源码里已经把查看消息正文和完整会话的入口移除了。README 里的对比表把这称为「Cloudflare 免费额度内可跑」,但具体能不能免费跑,还要看团队规模和功能用法。
功能清单与明确没有的功能
EdgeChat 提供了一套相对完整的团队聊天能力:
- 公开频道、私有频道与一对一私信;
- 实时消息、历史消息分页、消息已读徽章、置顶消息与删除消息;
- 文件上传与头像管理,支持图片、视频、PDF 与文本类型;
- 管理员后台,包含仪表盘、用户管理、注册邀请码、网站设置;
- 用户支持永久封禁或按分钟/小时/天设置临时封禁,到期自动恢复;
- Telegram 群组双向桥接,把公开频道与 Telegram 群绑定后消息两边实时同步;
- Android 原生客户端,走 /api/v1 协议,Android 8 / API 26 起可用。
但有几个能力需要特别注意:EdgeChat 不开放自助注册,只能由管理员创建账号或发放注册邀请链接;项目目前也没有全文消息搜索,管理后台的消息搜索与完整会话查看页面已被移除;Android 首版不接入 FCM,应用切到后台会关闭 WebSocket,回到前台再增量同步。注册邀请码可以设置剩余使用次数,管理员能随时撤销;临时封禁按分钟计算,disabled_until 字段到期后不需要定时任务即可自动恢复,后台管理因此能在没有额外 Cron 的前提下运作。

依赖哪些 Cloudflare 全家桶
源码的 wrangler.example.toml 把资源绑定写得非常清楚,实际也反映在 worker/src 的代码里:
- D1(binding DB):关系型主数据库,worker/schema.sql 里定义了 16 张表,涵盖用户、频道、成员、消息、已读、置顶、站点设置、注册邀请、上传文件元数据、设备会话、实时票据、消息事件、Telegram 桥接映射等;
- KV(binding SESSIONS):只存登录会话,token 映射到用户身份和权限。每次 WebSocket 收到消息都会重新校验一次会话;
- R2(binding FILES):对象存储,用于附件和头像。源码在 worker/src/api/upload.js 里写明了上传前用 AES-256-GCM 加密,默认单文件上限 20 MB,未开通 R2 时上传接口会返回 503;
- Durable Objects:三个 SQLite 后端类。ChannelRoom 按 "kind:id" 命名,每个房间一个实例,负责 WebSocket 广播;UserInbox 按 "user:id" 命名,每人一个实例,跨房间推送未读数;Scheduler 是单例,靠 alarm 每天 UTC 3 点触发垃圾回收。
worker/src/index.js 是单一 Hono 入口,/api/* 和 /files/* 走 Worker,其余静态资源由 [assets] 配置直接返回,并开启 SPA 回退。wrangler.example.toml 还配置了一条 Cron Trigger:每天 UTC 19:00,也就是北京时间凌晨 3:00,触发一次垃圾回收。migrations 目录目前有 16 个 SQL 文件,CI 通过 prepare-d1-migrations.mjs 将这些 SQL 文件合并成单次执行,避免在 Actions 里反复调用 wrangler d1 execute。

部署步骤与成本
官方推荐的部署方式是 GitHub Actions。仓库里已经提供了 .github/workflows/deploy-worker.yml,推到 master 或 main 分支即可自动触发,也可以手动 workflow_dispatch。整个流程大致如下:
1. 在 Cloudflare 控制台创建一个 API Token:使用「编辑 Cloudflare Workers」模板,再补一条「账户 → D1 → 编辑」权限;
2. 在仓库 Settings → Secrets and variables → Actions 里添加 CLOUDFLARE_API_TOKEN、CLOUDFLARE_ACCOUNT_ID、CFCHAT_ADMIN_USERNAME、CFCHAT_ADMIN_PASSWORD;
3. 推送代码后,工作流先跑 npm ci、npm test、npm run build;
4. 脚本自动检查并创建 D1 数据库、KV 命名空间和 R2 桶,没开通 R2 就暂时摘掉 FILES binding;
5. 首次创建 D1 时灌入 worker/schema.sql,随后应用 migrations 目录下的增量迁移;
6. 写入管理员账号,密码使用 PBKDF2 + SHA-256,10 万次迭代加盐;
7. 首次部署自动生成 32 字节 AES 密钥环,作为 Worker Secret 注入,后续部署沿用;
8. 最后执行 wrangler deploy。

CI 会根据 ensure-resources 脚本输出的 R2 是否可用,动态生成 wrangler.ci.toml:把 D1 和 KV 的占位 ID 替换成真实值,R2 不可用时直接删除整个 [[r2_buckets]] 段。这意味着同一份源码可以一键部署到还没开通 R2 的账号,后续开通对象存储后重跑工作流即可补齐附件能力。本地开发可以用 Docker,./docker-start.sh 会在 8788 端口拉起服务,数据库用 D1 本地 SQLite 模式,数据落在 .wrangler/state/ 目录。
成本方面,Cloudflare 免费额度大致是:Workers 10 万次请求/天;D1 500 万行读取/天、10 万行写入/天、5 GB 存储;KV 10 万次读取/天、1000 次写入/天、1 GB 存储;R2 10 GB-month 存储、Class A 操作 100 万次/月、Class B 操作 1000 万次/月、出网流量免费;Durable Objects SQLite 后端 10 万次请求/天、13,000 GB-s 计算/天、5 GB 存储。EdgeChat 的 Durable Objects 使用 SQLite 后端,免费账号可以创建。如果用量超出,Workers Paid 计划最低 $5/月。文档也提醒:调用量上来了之后,Durable Objects 的持续计费需要重点关注。
数据存在哪里、怎么加密
不同类型的数据落在不同的地方。消息正文和元数据在 D1 里;附件与头像加密后放到 R2;会话在 KV;Durable Objects 实例各自维护 WebSocket 连接状态与未读投影。
加密策略来自 worker/src/encryption.js。新写入的消息正文和新上传的附件默认使用 AES-256-GCM,密钥来自 EDGECHAT_ENCRYPTION_KEYRING 这个 Worker Secret,格式为 {"activeKeyId":"v1","keys":{"v1":"BASE64_ENCODED_32_BYTE_KEY"}}。历史明文数据不会被批量迁移,读取时同时兼容旧明文和新密文。密码哈希则走 PBKDF2 + SHA-256,迭代 10 万次。
实时连接的安全性靠短期票据实现。客户端先调用 POST /api/v1/realtime/tickets 获取一次性 ticket,再在 60 秒内凭该票据连接 /api/v1/realtime/ws,长生命周期的 access token 不会出现在 WebSocket URL 里。需要强调的是,这是服务端静态加密,不是端到端加密。Worker 在通过会话权限校验后会解密内容,因此掌握密钥的部署方和 Cloudflare 运行环境仍在信任边界内。默认配置下,消息保留 7 天后会被硬删除,软删除记录保留 60 天,Scheduler 负责每天清理过期数据与孤儿 R2 对象。
适用边界与什么场景不适合
EdgeChat 最适合用户数几十到几百、没有专职运维、希望数据留在自己账号里的小团队。这套方案把厂商锁定集中到了 Cloudflare 一家:代码、数据模型、Durable Objects 调度逻辑都围绕 Cloudflare 设计,迁移到其他平台需要重写。GPL-3.0 协议也意味着如果你二次开发后对外分发,需要提供对应源代码。
以下场景建议谨慎评估:
- 用户量可能快速突破 Workers / D1 / Durable Objects 免费额度的团队;
- 需要端到端加密、合规审计或消息长期归档的组织;
- 希望跨多云部署、避免单一云厂商锁定的环境;
- 依赖消息全文搜索、开放公开注册、移动后台实时推送的业务场景。
从免费额度角度看,最可能被突破的是 Durable Objects 的请求数与持续计算时间。一个 100 人的团队如果每人在十几个频道里保持长连接,DO 实例会长时间活跃,Duration 计费会快速消耗免费额度。其次 D1 的写行数 10 万/天,在高频消息场景下也可能吃紧。另外,R2 虽然免费额度 generous,但如果还没开通 R2,部署后文字聊天可用,附件功能会不可用。开通后重新跑一遍部署即可自动恢复,不需要改配置。
EdgeChat 把「团队聊天」这个传统上需要服务器和数据库的工作负载,完全重构成了 Cloudflare 边缘原生应用。D1 做关系型存储、KV 做会话、R2 做加密附件、Durable Objects 做实时广播与定时任务,再用 GitHub Actions 把创建资源和部署串成一条流水线。这不是一个面面俱到的企业级 IM,但对于想在免费额度内快速落地自托管聊天的小团队,EdgeChat 提供了一个足够完整、源码公开且数据可控的选择。
文章评论