后端前端移动开发【免费下载链接】SparkyFitnessSparkyFitness: Built for Families. Powered by AI. Track food, fitness, water, and health — together.项目地址https://gitcode.com/gh_mirrors/sp/SparkyFitness点击查看免费下载SparkyFitness 是一款面向家庭的健康与健身应用在 iOS 端通过 kingstinct/react-native-healthkit 桥接 Apple HealthKit将步数、心率、睡眠、营养与运动记录同步到自有服务端。本文以 HealthKit 运动记录文档 为骨架逐字段拆解一条运动记录Exercise Session从 HealthKit 原始样本到服务端可入库结构化数据的完整链路读者将掌握readHealthRecords原始数据形态、transformHealthRecords转换逻辑、HKWorkoutActivityType 数值映射、单位与时区处理以及运动遥测GPS、心率、配速的采集与幂等上传机制。一、整体数据流从 HealthKit 样本到服务端条目运动记录Workout/ExerciseSession在 SparkyFitness 移动端的处理遵循一条清晰的管道全部实现在 SparkyFitnessMobile/src/services/healthkit/ 目录下读取层index.ts 中的handleWorkout调用queryWorkoutSamples从 HealthKit 拉取时间窗口内的HKWorkoutTypeIdentifier样本并补充统计量消耗热量、距离、步数与遥测数据平台编排层provider.ts 将读取、预处理postProcessRaw、转换transform统一封装为healthReadProvider供跨平台同步引擎 healthSyncEngine 调用转换层dataTransformation.ts 中的Workout/ExerciseSession直接转换器Direct Transformer把原始记录重塑为服务端 API 要求的TransformedExerciseSession结构。三个模块各司其职读取层只负责“从 HealthKit 拿到什么”转换层负责“向上传什么”共享的转换驱动工厂 shared/dataTransformation.ts 则负责统一调度值转换器与直接转换器。二、第一步readHealthRecords 返回的原始数据文档中给出的原始数据结构是handleWorkout经过统计量补充后的记录形态。HealthKit 的queryWorkoutSamples原生返回的 workout 样本只携带起止时间与活动类型而 SparkyFitness 的读取层会在 JS 侧继续通过w.getStatistic(...)补齐三类统计量如 index.ts 第 1084 行起 所示{ startTime: 2026-01-08T10:00:00.000Z, endTime: 2026-01-08T10:45:00.000Z, activityType: 37, duration: { unit: s, quantity: 2700 }, totalEnergyBurned: 320, totalDistance: 5200 }各字段含义与来源字段来源说明startTime/endTimeworkout 样本的startDate/endDateISO 8601 时间戳用于日期归并与时区推导activityTypeworkout 样本的workoutActivityType数字枚举HKWorkoutActivityType如 37 代表 Runningdurationworkout 样本的durationHealthKit 返回{ unit, quantity }形式的 Quantity 对象单位秒totalEnergyBurned优先取自w.getStatistic(HKQuantityTypeIdentifierActiveEnergyBurned, kcal)失败则回退样本直读字段单位固定为 kcaltotalDistance依次尝试 DistanceWalkingRunning、DistanceCycling、DistanceSwimming 等五种距离类型取首个非零值单位固定为米mtotalSteps可选w.getStatistic(HKQuantityTypeIdentifierStepCount, count)仅当 HealthKit 将该次锻炼与步数关联时存在单位钉死unit pinning代码注释明确指出若不显式传单位HealthKit 会按用户偏好单位返回常见为英里 / kJ而转换层假设米 / kcal会造成静默的数值错标。因此每一次getStatistic调用都显式携带kcal、m、count等单位参数见 index.ts 第 1175-1249 行。此外读取层还会在记录上附加uuid幂等键、metadata.HKTimeZone时区元数据以及自有手表会话标记SparkyFitnessSessionId用于排除自身写回数据详见第五节。三、第二步transformHealthRecords 转换后的服务端结构原始记录交由转换层后输出为 TransformedExerciseSession 结构。文档示例{ type: ExerciseSession, source: HealthKit, date: 2026-01-08, entry_date: 2026-01-08, timestamp: 2026-01-08T10:00:00.000Z, startTime: 2026-01-08T10:00:00.000Z, endTime: 2026-01-08T10:45:00.000Z, duration: 2700, activityType: Running, title: Running, caloriesBurned: 320, distance: 5200, notes: Source: HealthKit, raw_data: { ...: 原始记录 } }转换逻辑集中在 dataTransformation.ts 的 Workout 直接转换器逐字段说明type/source固定为ExerciseSession与HealthKit常量HEALTHKIT_SOURCE服务端据此路由入库date/entry_date由getDateString(rec.startTime)生成本地日期字符串YYYY-MM-DD供服务端按天聚合timestamp/startTime/endTime原样透传 ISO 时间戳服务端依赖“时间戳 时区”推导日历日因此客户端绝不预先把记录归入某一天duration从{ unit: s, quantity: 2700 }解包为纯数字秒数也兼容duration直接为数字的旧样本activityType/title通过ACTIVITY_MAP把数值 37 翻译为人类可读的Running详见第四节caloriesBurnedtotalEnergyBurnedkcal的直传缺失时回退为 0distance需要注意当前实现会除以 1000 转为公里——distance: parseFloat((totalDistanceMeters / 1000).toFixed(2))以匹配服务端运动条目的存储单位types/healthRecords.ts 的类型注释明确说明“Stored in kilometers to match exercise entry API/storage”。文档示例保留了米数 5200属示意简化实际链路中输出为5.2notes固定标注Source: HealthKit便于在日记中识别数据来源raw_data原始 HealthKit 记录整体保留供排查与审计。四、ACTIVITY_MAPHKWorkoutActivityType 数值映射表activityType从数字到人读名称的转换依赖 dataTransformation.ts 中的 ACTIVITY_MAP它与 Apple 官方HKWorkoutActivityType枚举一一对应注释标注了官方文档来源覆盖从 1American Football到 3000Other的 90 余种活动。几条关键规则activityType: 37→Running这正是文档示例命中的映射未知数值回退ACTIVITY_MAP[activityType] || \Workout type ${activityType}保证新增活动类型不会导致解析失败缺失字段回退无activityType字段时使用Workout Session兜底title与activityType同值直接作为日记条目的展示标题。测试用例对映射行为做了完整覆盖见 dataTransformation.test.ts 第 433 行起的 ExerciseSession/Workout 分组37 → Running、3000 → Other、999 →Workout type 999、缺失 → Workout Session。五、幂等与去重source_id、exercise_source_id 与自写数据排除转换后的运动记录携带三组与“重复同步”相关的字段source_id原样透传 HealthKit 样本的uuid服务端以(user, source, source_id)作为幂等 upsert 键同一会话重复同步只更新不新增exercise_source_idString(activityType)即数字类型标识供服务端做稳定的运动库匹配如与 exerciseService 中的动作/活动库对齐自有数据排除writeback feedback-loop guard当 SparkyFitness 通过配对手表 App 记录锻炼时手表会把会话写入 HealthKit 并盖上SparkyFitnessSessionId元数据键常量 WATCH_SESSION_METADATA_KEY。由于手表 bundle id 与手机不同无法用sourceBundleId识别因此转换器用isOwnWatchWorkout检查该元数据这些锻炼的组次sets在实时记录时已写入日记若再导入 HealthKit 副本会造成同一会话重复记账。该键与手表端 Swift 代码中的sessionMetadataKey是必须保持一致的“约定字面量”dataTransformation.ts 第 46-55 行专门注释了两端无法共享常量、单侧改名会静默引入重复锻炼的风险。此外转换器还会生成sets数组——把整段锻炼表达为单个工作集set_number: 1, set_type: Working Set关键是用秒而不是分钟duration_seconds: Math.round(durationInSeconds)旧模型曾用分钟字段易被新服务端误读类型注释明确说明了这一演进。六、时区处理HKTimeZone 元数据与设备时区兜底HealthKit 样本可能携带metadata.HKTimeZoneIANA 时区名。转换器通过extractTimezoneMetadatadataTransformation.ts 第 102-111 行提取它规则为优先使用记录级时区record_timezone保证“当地时间的日历日”推导正确例如跨时区旅行时记录的运动不会被归错天无元数据时回退为设备时区Intl.DateTimeFormat().resolvedOptions().timeZone读取层在 index.ts 第 1264-1269 行 会把metadataTimeZone扁平字段归一化为metadata.HKTimeZone确保转换层总能找到时区。测试对时区透传亦有覆盖如 ExerciseSession 包含record_timezone的用例dataTransformation.test.ts 第 942 行。七、运动遥测GPS 轨迹、心率序列与汇总指标除摘要字段外SparkyFitness 还会为运动记录采集“遥测包”workoutTelemetry.ts经attachWorkoutTelemetry挂到转换结果上包括gps_pointsGPS 轨迹点时间、经纬度、海拔、速度、心率、功率等短键名以控制长运动载荷体积hr_samples逐秒心率序列laps分段窗口服务端据此派生每段距离、心率、配速等telemetry会话级汇总平均/最大心率、平均/最大速度与功率、海拔增益/损失、步频、触地时间、垂直振幅、步幅、卡路里等键名刻意对齐服务端exercise_entries列名以便直接入库。遥测读取并非无限制——index.ts 第 64-68 行 定义了双层并发预算统计查询并发上限 6、遥测系列并发上限 2AGGREGATE_CONCURRENCY/TELEMETRY_CONCURRENCY原因在于每次遥测采集要发出 GPS 路线与逐样本查询结果在 JS 线程反序列化无界并发会把一次宽窗口同步变成阻塞 UI 的突发。已采集过的会话会按缓存键跳过强制重跑force run则按“最久未采集优先”顺序补齐历史缺口。采集成功率与预算统计以日志形式输出作为同步健康度信号。八、读取层的完整入口readHealthRecords 与 readHealthRecordsDetailed文档提到的readHealthRecords是 index.ts 第 1799 行 导出的只读便捷包装它固定使用零预算、非交互的遥测运行上下文createTelemetryRunContext({ budget: 0, interactive: false })专供展示与诊断路径调用绝不触发路线授权弹窗或消耗预算。同步引擎则直接使用readHealthRecordsDetailed自行注入运行上下文并消费{ records, error }信封——读取失败会携带error返回而非抛出调用方据此保持同步游标不前进避免把一次失败读当作“已同步 0 条”而永久跳过该窗口同样语义见 ReadResult 类型。对锻炼类记录读取层还会探测“最早样本”从 1970 纪元开始、limit: 1升序查询用于历史导入下限检测见 index.ts 的 probe 实现。九、测试与验证本功能有完善的测试保障读者可借此深入理解行为契约dataTransformation.test.ts覆盖 ACTIVITY_MAP 映射、duration 对象/数字两种形态、sets秒数生成含小数四舍五入与缺失回退 0、source_id透传、时区元数据与遥测挂载provider.test.ts验证readCumulativeByDay按 recordType 路由到原生统计查询、未知类型返回 null能力缺失回退原始路径、原生失败返回错误信封而非 nullworkoutTelemetry.test.ts 与 writeback.test.ts分别覆盖遥测采集与自写数据排除。十、延伸阅读同步 API 文档转换结果如何通过健康数据 API 上传服务端后台同步文档遥测预算与同步游标在后台同步场景下的行为开发文档HealthKit 桥接的工程约定与调试方式Health Connect 对应实现Android 端 Nutrition/Exercise 转换与 iOS 共享相同的转换驱动工厂可对照阅读跨平台一致性设计。赞分享后端前端移动开发【免费下载链接】SparkyFitnessSparkyFitness: Built for Families. Powered by AI. Track food, fitness, water, and health — together.项目地址https://gitcode.com/gh_mirrors/sp/SparkyFitness点击查看免费下载相关推荐Apache Flink DataStream Parquet 格式全解析RowData 向量化读取与 Avro 记录读取实战Apache Flink DataStream Parquet 格式全解析RowData 向量化读取与 Avro 记录读取实战 本指南围绕 Apache Fl后端大数据流处理批处理CsvHelper 动态记录读取使用 GetRecordsdynamic() 将 CSV 行转换为动态对象CsvHelper 动态记录读取使用 GetRecordsdynamic 将 CSV 行转换为动态对象 导读 当 CSV 的列结构在编译期未知、或者你希望跳后端数据工程ChatLab 使用 AI Agent 转换聊天记录chatlab-convert Skill 全流程实战指南ChatLab 使用 AI Agent 转换聊天记录chatlab convert Skill 全流程实战指南 ChatLab 是一款本地优先Local f人工智能AI Agent数据分析桌面应用后端前端即时通讯MCP 服务本地部署上一篇如何高效管理喜马拉雅音频收藏跨平台下载工具使用指南下一篇如何用League Akari工具箱三倍提升英雄联盟游戏效率创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考