步数、距离、卡路里、心率和睡眠数据需要由健康与营养应用从用户的平台(Android 上的 Android Health Connect,iOS 上的 Apple HealthKit)中提取,并将其呈现为一个干净、去重后的每日个人资料。这两个 API 之间几乎没有相似之处:独立的读取机制、不同的授权方案和不同的数据结构。我们构建了移动同步层,使它们看起来像一个整体。
挑战
- 两个原生 API 互不兼容。Apple HealthKit 通过回调返回每日聚合数据;Health Connect 返回带有丰富来源元数据的原始分页样本。概念相同,形态完全不同——应用必须将两者规范化为一个 schema。
- 重复计算是默认情况。手机、智能手表和第三方应用都可能报告相同的 8,000 步。简单地将它们相加会夸大所有指标。应用需要识别重叠的来源并只计算每个实际活动一次。
- 电池和网络无法为新鲜度买单。健康数据全天都在变化,但每次同步都读取完整历史记录会耗尽电池电量并占用蜂窝网络。同步频率必须保持轻量级,同时仍能感受到实时性。
权限可能令人困惑。数十种数据类型、两种权限模型、后台读取限制,以及授予部分权限但拒绝其他权限的用户——所有这些应用都必须处理,而不能中断流程。
我们的解决方案
我们构建了一个三层同步:一个薄的原生层读取每个平台的 API,一个设备端归一化层对数据进行去重并将其汇总为每日摘要,后端存储并将其整合到一个单一的可信 ledger 中。客户端从不发送原始样本——它发送干净、时区正确的每日汇总数据。

架构
- iOS 通过 react-native-health 读取 Apple HealthKit;Android 通过 react-native-health-connect 读取 Health Connect。
- 设备端归一化将两个平台的输出转换为一个 schema 并在数据离开手机前进行去重。
- 同步触发器——首次启动时获取 30 天的历史数据,之后每 10 分钟以及每次应用恢复时进行轻量级 1 天增量读取。
- 传输——规范化的每日汇总数据通过 POST 请求发送到主服务器的 /user-health/system-activity 接口,该接口将其(带用户 ID + 时区)转发到健康微服务。
- 持久化——每个来源/天的数据都作为 HealthSystemAggregate 幂等地存储;然后 MongoDB change stream 将其整合为每日的 HealthLedger。
使用原生 HealthKit 和 Health Connect 模块的 Expo / React Native 托管工作流。
主要功能
- 针对两个 API 的统一规范化契约。平台特定的读取器将数据馈送到一个单一的 formatHealthData 步骤,无论来源如何,都输出相同的形状——因此后端和 UI 不会因 iOS 或 Android 而产生分支。
2. 设备端基于最大值的去重。在每个来源内部,一天的读数被求和;然后每日值是跨来源的 Math.max()(卡路里由 source|deviceType 键控),因此来自同一设备的多次读数会累加,而手表和手机报告同一天的数据不会重复:
// Sum readings within each source, then take the max ACROSS sources dateSourceMap[date][source] += entry.count; const totalSteps = Math.ceil(Math.max(...Object.values(dateSourceMap[date]))); |
3.每个平台采用正确的读取策略。Health Connect 读取通过 pageToken 循环进行分页(1,000 条记录/页);HealthKit 的每日聚合数据按天循环。每个特殊处理都包含在自己的读取器中,对应用的其余部分不可见。
4. 尊重电池续航的同步频率。完整的 30 天回填只运行一次(由首次调用标志守护);之后,每次同步只拉取一天数据——在蜂窝网络上速度快,功耗低。
5. 时区正确的每日分组。样本按用户自己的时区归入 YYYY-MM-DD 键,因此无论服务器位于何处,晚上 11 点的锻炼都能归到正确的日期。
6. 幂等后端写入。每个汇总数据都通过 (userId, deviceId, source, date) 进行 upsert 操作——重新发送同一天的数据是空操作,这使得重试和重叠同步变得安全。
7. 优雅降级。缺失权限、空读取和 Health Connect 限速错误都会被捕获并清晰地呈现——空数据日永远不会导致同步崩溃,用户会收到清晰的提示而不是无声的失败。
成果
- iOS 和 Android 之间统一、一致的健康档案——平台差异对应用的其他部分是不可见的。
- 重叠的设备/应用来源被去重,因此步数和卡路里反映的是实际情况而不是夸大的总和。
- 轻量级增量同步保持数据新鲜,同时不耗尽电池或消耗移动数据。
时区正确、幂等的写入意味着每个指标都能落在正确的日期,并且重试永远不会破坏记录。
技术栈
React Native (Expo) · react-native-health (HealthKit) · react-native-health-connect · NestJS · MongoDB · MongoDB Change Streams · moment-timezone

