Tempo系统架构
Tempo 采用“三套客户端工程、一份同步协议、一个轻量服务端”的结构。iOS 使用 SwiftUI 原生壳与 React Native/TypeScript 内容层,macOS 保持 SwiftUI;Android 使用独立的 Flowtime 衍生 Flutter 工程;Windows 保留独立 Flutter 工程;所有客户端共享 Go 服务端的 JSON 协议。
系统全景
flowchart LR
subgraph Clients[客户端]
IOS[iOS / SwiftUI Shell + OTA Content]
MAC[macOS / SwiftUI]
AND[Android / Flutter]
WIN[Windows / Flutter]
end
IOS -->|HTTPS + Bearer| PROXY
MAC -->|HTTPS + Bearer| PROXY
AND -->|HTTPS + Bearer| PROXY
WIN -->|HTTPS + Bearer| PROXY
subgraph Server[自托管服务器]
PROXY[Nginx / Caddy / 1Panel]
SYNC[Go 同步服务]
STORE[(store.json)]
AI[可选 AI 兼容接口]
PROXY --> SYNC
SYNC --> STORE
SYNC -. 可选代理 .-> AI
end客户端
| 平台 | 目录 | UI 技术 | 本地状态管理 | 安全存储 |
|---|---|---|---|---|
| iOS | apps/apple + apps/ios-runtime | SwiftUI、React Native、WidgetKit、ActivityKit | AppStore | Keychain |
| macOS | apps/apple | SwiftUI | AppStore | Keychain |
| Android | apps/android | Flutter | AppProvider | Hive / SharedPreferences |
| Windows | apps/flutter | Flutter、托盘与窗口插件 | TaskController | flutter_secure_storage |
任务数据使用平台应用数据目录中的 JSON 文件。主题、模块顺序等设备偏好留在本机;任务、番茄钟和 RSS 阅读状态进入同步文档。
iOS 的页面内容、样式、静态资源和基于已有原生组件的路由可通过 OTA 更新。Swift 管理底部导航、顶部液态玻璃按钮、权限、小组件、灵动岛和数据层;设置与工具页面使用内容层的 React Navigation 原生栈。完整边界见 iOS 架构与 OTA。
Apple 系统扩展
iOS 主应用与 TEMPOWidgets 扩展通过 group.one.darker.qingxu App Group 共享最小快照:
- WidgetKit 读取今日任务与专注状态快照。
- ActivityKit 显示番茄钟的锁屏实时活动和灵动岛。
- 运行中的倒计时保存绝对结束时间,不依赖应用在后台每秒执行。
- 扩展不直接访问同步网络;状态由主应用写入共享容器。
本地优先写入
客户端修改数据时遵循同一顺序:
- 更新内存状态。
- 使用临时文件与原子替换写入本机。
- 刷新界面和系统扩展快照。
- 普通编辑经过约 100 毫秒合并窗口后同步;番茄钟关键操作立即同步。
- 网络失败保留本地修改,恢复联网后重新合并。
因此同步服务不可用时,创建、完成和调整任务仍然可以正常工作。
同步模型
- 任务:以
id为永久身份,按updatedAt进行 last-write-wins 合并;相同时间下删除墓碑优先。 - 番茄钟:一个单例 LWW 文档;运行状态使用绝对 UTC
endsAt,phaseID区分连续阶段,dailyFocusGoal与focusHistory支撑多端目标和热力图,响应中的serverTime用于修正设备时钟偏差。 - RSS:一个单例 LWW 文档,保存订阅、分类与文章阅读状态;正文只在本机缓存。
- 实时通知:前台客户端通过
/v1/changes等待修订号变化,变化后再调用/v1/sync。 - 容错:5 分钟完整同步用于重连兜底,不替代变更通知。
- 恢复:客户端持久化最近修订号;进程重启后从该位置继续等待,断线使用带抖动的指数退避。
当前模型是文档级 LWW,不是逐字段 CRDT。两个设备同时修改同一任务时,时间更新的完整任务获胜。
同步服务
services/sync 是单进程 Go HTTP 服务。同步与鉴权使用标准库,服务器二维码由固定版本的 go-qrcode 编码:
GET /health:检查进程与数据目录可写性。GET /v1/ping:验证 Bearer 密钥。POST /v1/sync:原子合并并返回完整同步状态。GET /v1/changes:最长等待 25 秒的低成本变更通知。POST /v1/ai:可选的 RSS 摘要、翻译和任务规划代理。
持久化采用临时文件、fsync 和原子替换,默认写入 /data/store.json。服务定位为单用户、单实例,不能让多个进程同时写同一个文件。
Docker 默认以非 root 用户运行,根文件系统只读,移除 Linux capabilities,限制进程数和 64 MiB 内存;公网 TLS 由 Nginx、Caddy 或 1Panel OpenResty 提供。
AI 调用路径
客户端支持两种模式:
- 自托管代理:客户端使用同步地址与同步密钥调用
/v1/ai,模型密钥只存在服务器.env。 - 客户端直连:iOS/macOS 直接调用 OpenAI、DeepSeek 或兼容 Chat Completions 接口,API Key 写入 Keychain。
服务端和客户端都提供内置默认提示词;用户只需选择连接方式、填写必要凭据并测试连接。AI 失败不会影响任务、本地 RSS 或同步功能。
发布架构
flowchart LR
PUSH[推送 main] --> PATH[识别受影响平台]
PUSH --> OTA[iOS OTA 内容验证]
PATH -->|iOS 变化| IOS[iOS 构建]
PATH -->|macOS 变化| MAC[macOS 构建]
PATH -->|Android 变化| AND[Android 构建]
PATH -->|Windows 变化| WIN[Windows 构建]
PATH -->|服务端变化| DOCKER[Docker 镜像]
IOS --> RELEASE[GitHub Release]
MAC --> RELEASE
AND --> RELEASE
WIN --> RELEASE
RELEASE --> SUM[SHA-256 + 来源证明]
SUM --> VERSION[四端版本元数据回写]
OTA --> PRODUCTION[Production 内容更新]公开版本只使用 x.y.z 数字格式。一次发布只编译受影响的平台,其他平台不重复构建;客户端从 Release 列表中选择最近一个包含本平台资产的版本。TypeScript OTA 不改变安装包版本号。
公开工作流只生成未签名 iOS IPA;个人证书不进入 GitHub Actions,真机包使用自己的本机签名流程安装。
文档站与客户端发布分别由独立工作流处理,互不阻塞。工作流实现保留在仓库的 .github/workflows 中,不在用户文档展示内部发布细节。