
从「找一个免费、网页 + 安卓 / 鸿蒙都能用、还得有周视图和三日视图的日历」开始,前后试了十几款软件、自建服务、协议网关和手机客户端,最后定下来的其实不是某一个 App,而是一套混合架构:主力继续用新版 Outlook,日程经 CalDAVconnect 实时同步到自建的 Radicale,Web 端用 Calino、手机端用 KashCal 直连 Radicale 作为备用入口。
这套结构最大的价值不是「免费」,而是容灾:微软服务抽风的时候,日程不丢,而且还能继续改。下面把最终方案和一路上的取舍整理成一篇可复现的笔记。
一、需求与硬约束
- 免费,不接受订阅制;周视图 / 三日视图是刚需,不能藏在高级会员里;
- 网页端 + 手机端(安卓 / 鸿蒙)都要能看、能写;
- 国内网络可用,界面要能方便地编辑、加标签 / 分类、拖拽改时间,类似 Outlook 的体验;
- 数据要有一份握在自己手里的副本,不能全托给某一家云服务;
- 主力工具挂掉时,要有可以立即切换的备用方案。
二、最终架构
新版 Outlook ──(CalDAVconnect 双向桥接)── Radicale(飞牛 NAS)
├── Calino(Web / Docker)
└── KashCal(Android,内置 CalDAV)
| 层 | 选型 | 职责 |
|---|---|---|
| 主力界面 | 新版 Outlook | 日常新建 / 编辑日程,网页与手机端的界面、推送体验最好 |
| 协议桥接 | CalDAVconnect | 免费套餐,OAuth 授权后把 Outlook / Microsoft 365 与自建 CalDAV 双向同步 |
| 数据底座 | Radicale(飞牛 NAS 上 Docker 部署) | 存放全部日程,自有副本,随时可换回纯本地方案 |
| Web 端 | Calino(Docker 自部署) | 周 / 三日视图、拖拽编辑;Outlook 不可用时的备用主力 |
| 手机端 | KashCal(Android,Apache-2.0) | 内置 CalDAV 直连 Radicale,真三日 / 周视图,免费无内购 |
| 外网访问 | Cloudflare Tunnel(可换 Tailscale / IPv6 / 国内 FRP) | 让手机在外网也能连到 NAS 上的 Radicale |
数据怎么流动
- 正常情况:在 Outlook 里改动 → 微软通过 Webhook 通知 CalDAVconnect → 1–2 秒内写进 Radicale;Calino、KashCal 从 Radicale 自动拉取。反过来,在 Calino / KashCal 里改的日程也会被桥接推回 Outlook。当然,正常情况下我在手机上用的是 Outlook 的手机版。
- 同步基于 ETag 增量比对,只传变化过的那几条日程,不会整本日历重写。
Renew webhook是个很容易误解的按钮:它不是给聊天机器人发消息,而是向微软续订「日程有变就通知我」的回调订阅。订阅有有效期,一般会自动续订;只有发现 Outlook 的改动迟迟没同步过来、或后台提示订阅即将过期时,才需要手动点一下。真想让机器人播报日程,用 Power Automate 监听 Outlook 事件更省事。
三、为什么最后是这套:被否掉的路
| 候选 | 放弃原因 |
|---|---|
| 滴答清单 | 免费版只有月视图,日 / 三日 / 周视图要订阅 |
| Google 日历、Notion Calendar | 国内不可用,或移动端没有完整周视图 |
| 微约日历、Teamup、Any.do | 太小众,或各平台的视图有阉割 |
| 飞书日历 | 办公套件太笨重;鸿蒙版后台通知有 bug |
| 华为 / 鸿蒙原生日历 | 没有 Windows 端;系统隐私策略不允许向第三方 CalDAV 账户写入新日程 |
| SyncCalendar(同步日历) | 能写 CalDAV,但所谓「7 天」只是顶部导航栏,下面一次只显示一天 |
| 52Time / AiCal / 优效日历 | 视图合适,但收费模式不明或存在订阅 |
| QQ DAV / 飞书 CalDAV / 钉钉 CalDAV | 标准支持情况不确定 |
| Outlook CalDav Synchronizer | 没有尝试,AI说:只支持经典桌面版 Outlook;新版 Outlook 本质是 Web 应用,不支持 COM 插件 |
| DavMail | 没有尝试,AI说:能把 Graph 翻译成 CalDAV,但官方自述同步成功率不稳定,有数据丢失风险 |
| outlook-caldav-sync / 导出 ICS | 没有尝试,AI说:Power Automate + JSON 脚本链路长且非实时;ICS 只是单向静态快照 |
关键结论是:新版 Outlook 原生不支持 CalDAV,微软官方也确认插件路线走不通,所以只剩「云端桥接」这条省事的路。而 Radicale 作为底座,保证了这个第三方桥接随时可以被换掉。
四、部署与配置要点
Radicale:飞牛 NAS 上的 Docker 部署
Radicale 只是个单进程的 CalDAV 服务器:镜像约 25 MB、空闲内存约 20 MB,日程以纯文本文件存放,备份一个目录就够了。在飞牛的「Docker → Compose」里新建项目,目录按官方约定准备成这样:
radicale/
├── compose.yaml
├── config/
│ ├── config # 主配置文件
│ ├── users # htpasswd 账号文件
│ └── rights # 可选:自定义权限规则
└── data/ # 日历数据,也是备份目标
compose.yaml(官方镜像使用 ./config 和 ./data 两个目录):
services:
radicale:
image: radicale/radicale:stable
container_name: radicale
restart: unless-stopped
ports:
- "5232:5232"
volumes:
- ./config:/config:ro
- ./data:/data
environment:
- RADICALE_CONFIG=/config/config
- 端口:容器内部固定监听 5232,只改冒号左边。飞牛后台占用 5666 / 5667,另有 8000 / 8001、80 / 443、3000 / 8080 / 9000 等常用端口;5232 被占用就改成
"15232:5232",选之前用ss -tlnp | grep 5232确认空闲。 - 镜像标签:官方推荐
stable,latest多数时候指向 nightly 构建,长期自用建议固定到stable或具体版本。
config/config 的最小可用配置。注意两个默认值必须显式改掉:3.5 以后默认 denyall(拒绝一切登录),且默认只绑定 localhost(容器外访问不到):
[server]
hosts = 0.0.0.0:5232
[auth]
type = htpasswd
htpasswd_filename = /config/users
htpasswd_encryption = autodetect
[storage]
filesystem_folder = /data/collections
[rights]
type = owner_only
在 config/ 目录下生成账号文件(-5 是 SHA-512;老版本 htpasswd 可换 -B 用 bcrypt)。飞牛上没有 htpasswd 命令时,在任意 Linux / WSL 上生成后把 users 文件放进 config/ 即可:
htpasswd -5 -c users xuzikuan12
启动并验证:
docker compose up -d
docker compose logs -f radicale
- 浏览器打开
http://飞牛IP:5232,用刚建的账号登录,在自带 Web 界面里新建一个日历集合(形如/xuzikuan12/calendar)。 - 各客户端统一填根地址
http://飞牛IP:5232(必要时补/用户名/):CalDAVconnect、Calino、KashCal 都指向同一个地址,配合/.well-known/caldav自动发现日历集合。 - 数据全在
data/目录,纯文本,直接打包就是完整备份;Calino 也可以写进同一个 compose 项目一起管理。
飞牛上两个常见坑:宿主机目录属主与容器内运行用户不一致时,日志会报
Permission denied,给data/授权或在 compose 里显式加user: "1000:1000"(按 NAS 实际 UID 调整)即可;另外config/config每次改动都要docker compose restart radicale才生效。
Radicale:CORS 与可写权限
Calino 是纯浏览器前端,跨域访问 Radicale 必须先开 CORS。把下面这段追加到 config/config:
[headers]
# 公网环境建议把 * 换成 Calino 的确切地址
Access-Control-Allow-Origin = *
Access-Control-Allow-Methods = GET, POST, OPTIONS, PROPFIND, PROPPATCH, REPORT, PUT, MOVE, DELETE, LOCK, UNLOCK, MKCALENDAR
Access-Control-Allow-Headers = User-Agent, Authorization, Content-type, Depth, If-match, If-None-Match, Lock-Token, Timeout, Destination, Overwrite, X-client, X-Requested-With
Access-Control-Expose-Headers = Etag
- 有反向代理时,更推荐在代理层统一加 CORS,而不是写进 Radicale;也可以用 Calino 自带的
calino-proxy绕过跨域(见下一节)。 - 可写权限:默认
owner_only已经允许登录用户读写自己/用户名/下的集合。若手机端仍把日历识别成只读、新建日程时选不到它,就换成from_file显式授权——在config/rights里写:
[root]
user: .+
collection:
permissions: R
[principal]
user: .+
collection: {user}
permissions: RW
[calendars]
user: .+
collection: {user}/[^/]+
permissions: rw
同时在 config/config 中把 [rights] 改为:
[rights]
type = from_file
file = /config/rights
- 让反代放行
/.well-known/caldav、/.well-known/carddav,并补齐X-Forwarded-Proto、X-Forwarded-Host;客户端就能只填一个根地址自动发现日历集合。
Calino:飞牛 NAS 上的 Docker 部署
services:
calino:
image: ghcr.io/ivan-malinovski/calino:latest
container_name: calino
restart: unless-stopped
ports:
- "18080:8080"
- 容器内部固定监听 8080,只改冒号左边的宿主机端口;避开飞牛后台的 5666 / 5667,以及 8000 / 8001、80 / 443、3000 / 8080 / 9000 等常用端口。选端口前用
ss -tlnp | grep 18080确认空闲。 - 不想在 Radicale 侧开 CORS,也可以启用官方
calino-proxy镜像(docker compose --profile proxy up -d),在 Calino 连接设置里把 Proxy URL 填为http://NAS:8081。 - 把自己部署的实例安装成 PWA:界面资源缓存在本地,秒开且可离线查看;装
calino.io的 PWA 没用,慢的是海外服务器。 - 桌面通知必须在 HTTPS 安全上下文下才能申请权限。若「允许」一点击就自动变回「阻止」,重置站点权限、注销 Service Worker,并确认是通过受信任的 HTTPS 访问。
- 简体中文在 2026-11-06 才合入源码(
feat(i18n): add Simplified Chinese (zh) locale),Docker 版没有中文多半是镜像滞后:先docker compose pull calino && docker compose up -d calino;仍不行就检查镜像内是否真的有zh语言文件,必要时挂载补上或提 Issue。
KashCal 与 CalDAVconnect
- KashCal:F-Droid / IzzyOnDroid 获取,Apache-2.0、无内购;内置 CalDAV 客户端直连 Radicale,不用 DAVx⁵ 中转,三日视图是三列并排的真视图,支持拖拽改时间。
- CalDAVconnect:在
caldavconnect.de/dashboard填入 Radicale 地址并 OAuth 授权微软账户即可,本地不用装任何东西。授权走 OAuth,不需要交出账号密码,且随时能在微软账户安全中心撤销;注册用独立强密码,授权时看一眼它申请的权限是否只涉及日历读写。
外网访问(可替换的一层)
Cloudflare 代理回家里 NAS 偏慢,慢的是连接层而不是 Radicale,所以这一层随时可换:
- Tailscale:用 MagicDNS 主机名
radicale.<tailnet>.ts.net作为所有客户端的统一地址;手机上设「离开家里 Wi-Fi 才自动连接」或应用级分流(只让日历 App / DAVx⁵ 走 Tailscale),不必常驻全局 VPN,WireGuard 待机耗电基本可忽略。 - IPv6 + DDNS:宽带和手机都有公网 IPv6 时最无感,客户端直接填域名,完全不需要 VPN。
- 国内节点 FRP / 轻量内网穿透:延迟比 CF Tunnel 好,但要留意服务商稳定性和免费额度。
五、容灾:Outlook 崩了怎么办
- 正常:Outlook 作主力,改动同步到 Radicale,Calino / KashCal 自动拉取。
- Outlook 崩溃 / 断网:直接打开 Calino 或 KashCal。它们直连 NAS,只要 NAS 在运转就照常看、照常改;CalDAVconnect 此时向微软推送会报错或挂起,但期间所有变更都落在 Radicale 里。
- Outlook 恢复:桥接自动把 Radicale 里的增量变更反推回 Outlook,刷新一下就能看到崩溃期间加的日程,再切回主力。
⚠️ 唯一要留意的是冲突:如果崩溃前在 Outlook 改了某条日程但没同步出去,随后又在 Calino 改了同一条,恢复后多数同步工具按「最后写入者胜出(Last Write Wins)」处理,前一次修改可能被覆盖。遇到大面积故障,恢复后扫一眼当天日程有无出入即可。
六、代价与边界
- 这是混合架构而非纯自建:同步过程会经过 CalDAVconnect 的服务器(德国服务、受 GDPR 约束、OAuth 可撤销),极其机密的日程不要放进去。
- 但完整副本一直在自己的 Radicale / 飞牛 NAS 上:哪天它倒闭、不免费了,关掉换回纯本地方案即可,数据毫发无损——进可攻,退可守。
- 其余都是小坑:浏览器通知要 HTTPS、Docker 镜像中文包滞后、鸿蒙系统日历不让第三方账户写入。用 Calino / KashCal 作为「写入端」就绕开了最后一条。
小结
Outlook 负责颜值和生态,CalDAVconnect 负责协议翻译,Radicale 负责兜底和绝对控制权,Calino / KashCal 负责灾备。
折腾一圈的体会是:不必执着于「万物自建」。把数据底座留在自己手里,把最难的「最后一公里」交给现成服务,反而是在便利、稳定、自主、免费之间最舒服的平衡点。
