基于 HarmonyOS Next 的运动健康应用实战:心率监测与健康报告系统

基于 HarmonyOS Next 的运动健康应用实战:心率监测与健康报告系统

本文将深入探讨如何使用 HarmonyOS SDK 和 AppGallery Connect 构建一个功能丰富的运动健康应用。我们将聚焦核心功能:心率实时监测、数据云端存储、健康报告生成及成就激励系统。

一、核心功能设计与技术栈

核心模块:

  1. 心率监测: 利用设备传感器获取实时心率数据
  2. 数据同步: 将心率数据安全存储至 AppGallery Connect 云数据库
  3. 健康报告: 使用云函数分析数据生成每日/每周报告
  4. 成就系统: 基于运动数据展示排行榜和勋章

技术栈:

  • 前端: HarmonyOS ArkTS UI 框架
  • 数据存储: AppGallery Connect 云数据库 (NoSQL)
  • 后端逻辑: AppGallery Connect 云函数
  • 数据采集: HarmonyOS 传感器框架

二、实时心率监测实现 (ArkTS)

1. 权限申请与传感器初始化

// pages/HeartRateMonitor.ets
import sensor from **********';
import abilityAccessCtrl, { Permissions } from **********';

// 1. 动态申请心率传感器权限
async function requestPermission(): Promise<void> {
  try {
    let atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager();
    let permission: Permissions = 'ohos.permission.HEALTH_DATA'; // 健康数据权限
    await atManager.requestPermissionsFromUser(getContext(this), [permission]);
    console.info('心率权限申请成功');
  } catch (err) {
    console.error(`权限申请失败: ${err.code}, ${err.message}`);
  }
}

// 2. 初始化心率传感器
let heartRateSensor: sensor.HeartRateResponse | null = null;

function initHeartRateSensor(): void {
  try {
    // 获取心率传感器实例
    heartRateSensor = sensor.getSensor(sensor.SensorId.HEART_RATE);
    if (!heartRateSensor) {
      console.error('设备不支持心率传感器');
      return;
    }

    // 设置传感器数据回调函数
    heartRateSensor.on('data', (data: sensor.HeartRateResponse) => {
      if (data.heartRate) {
        // 更新UI显示实时心率 (假设在组件中定义了@State heartRate: number = 0)
        this.heartRate = data.heartRate;
        console.info(`实时心率: ${this.heartRate} bpm`);
        
        // 将有效心率数据同步到云端 (稍后实现)
        this.uploadHeartRateData(this.heartRate);
      }
    });

    // 设置错误回调
    heartRateSensor.on('error', (err: BusinessError) => {
      console.error(`心率传感器错误: ${err.code}, ${err.message}`);
    });

  } catch (err) {
    console.error(`初始化传感器失败: ${err.code}, ${err.message}`);
  }
}

// 3. 启动和停止监听
function startMonitoring(): void {
  if (heartRateSensor) {
    try {
      // 设置采样率 (NORMAL: 约5秒一次,适合连续监测)
      heartRateSensor.setInterval(sensor.Interval.NORMAL);
      heartRateSensor.start();
    } catch (err) {
      console.error(`启动监测失败: ${err.code}, ${err.message}`);
    }
  }
}

function stopMonitoring(): void {
  if (heartRateSensor) {
    try {
      heartRateSensor.stop();
    } catch (err) {
      console.error(`停止监测失败: ${err.code}, ${err.message}`);
    }
  }
}

// 在页面显示时初始化并请求权限
aboutToAppear(): void {
  requestPermission().then(() => {
    initHeartRateSensor();
  });
}

// 页面隐藏时停止传感器以节省资源
aboutToDisappear(): void {
  stopMonitoring();
}

代码说明:

  • 权限申请 (requestPermission): 使用 abilityAccessCtrl 动态申请 HEALTH_DATA 权限,确保用户知情同意。
  • 传感器获取 (initHeartRateSensor): 通过 sensor.getSensor() 获取心率传感器实例。
  • 数据监听 (on('data')): 注册回调函数,当有新心率数据时更新UI并触发上传。
  • 生命周期管理 (aboutToAppear/aboutToDisappear): 在页面显示时初始化并开始监听,页面隐藏时停止监听,优化性能和电量消耗。

三、数据同步至 AppGallery Connect 云数据库

1. 配置云数据库

  • 在 AppGallery Connect 控制台创建项目,启用 云数据库 (Cloud DB)
  • 创建对象类型 HeartRateData,包含字段: userId: String (用户ID)heartRate: Integer (心率值)timestamp: Date (时间戳)
  • 配置数据存储位置和访问规则(建议设置用户级权限)。

2. ArkTS 数据上传代码

// utils/CloudDBManager.ets
import cloud from **********';
import { HeartRateData } from '../model/HeartRateData'; // 假设定义的模型类

// 初始化 Cloud DB 服务
const agcCloud = cloud.agconnectCloudDb();
const cloudDbZone = agcCloud.agconnectCloudDbZone(); // 获取默认或指定Zone

// 定义数据模型类 (需与云端对象类型一致)
export class HeartRateData {
  userId: string = '';
  heartRate: number = 0;
  timestamp: Date = new Date();

  // 构造函数用于创建实例
  constructor(userId: string, heartRate: number, timestamp: Date) {
    this.userId = userId;
    this.heartRate = heartRate;
    this.timestamp = timestamp;
  }

  // 可选:定义对象类型名 (需与云端ObjectTypeName一致)
  static getObjectTypeName(): string {
    return 'HeartRateData';
  }
}

// 上传心率数据到 Cloud DB
export async function uploadHeartRateData(heartRate: number): Promise<void> {
  try {
    // 1. 获取当前登录用户ID (需集成AGC Auth服务)
    const currentUser = getCurrentUser(); // 假设已实现获取当前用户
    if (!currentUser) {
      console.warn('用户未登录,无法上传数据');
      return;
    }

    // 2. 创建数据对象
    const hrData = new HeartRateData(
      currentUser.uid,
      heartRate,
      new Date() // 使用当前时间戳
    );

    // 3. 执行 Upsert 操作 (存在则更新,不存在则插入)
    const upsertResult = await cloudDbZone.upsert(HeartRateData.getObjectTypeName(), [hrData]);
    console.info('心率数据上传成功!', upsertResult);
  } catch (err) {
    console.error(`上传心率数据失败: ${err.code}, ${err.message}`);
    // 此处可添加重试逻辑或本地缓存策略
  }
}

// 在 HeartRateMonitor.ets 的 data 回调中调用
uploadHeartRateData(this.heartRate);

代码说明:

  • 模型定义 (HeartRateData): 定义与云端对象类型结构一致的本地模型类。
  • 初始化 (agcCloud, cloudDbZone): 获取 Cloud DB 服务和操作区域实例。
  • 数据操作 (upsert):upsert 是插入或更新数据的常用方法。将封装好的 HeartRateData 对象插入云数据库。
  • 用户关联 (userId): 数据记录关联当前用户ID,确保用户只能访问自己的数据(需结合 AppGallery Connect 认证服务)。
  • 错误处理: 捕获并记录上传错误,生产环境应添加重试或本地暂存逻辑。

四、生成健康报告 (AppGallery Connect 云函数)

1. 云函数逻辑 (Node.js)

  • 场景: 每天凌晨计算用户前一天的静息心率、平均心率、最高心率、最低心率。
// functions/generateDailyReport.js (云函数代码)
const agconnect = require('@agconnect/common-server');
const cloud = require('@agconnect/cloud-function-server');
const cloudDb = require('@agconnect/database-server').cloudDb;

// 1. 定义云函数入口
exports.main = async function (event) {
  // 初始化AGC服务 (使用默认配置)
  agconnect.instance().init();

  // 2. 获取 Cloud DB 服务实例和Zone
  const agcCloudDb = cloudDb.cloudDb();
  const cloudDbZone = agcCloudDb.agconnectCloudDbZone();

  // 3. 获取所有需要生成报告的用户ID (简化示例,实际可能需分页或定时触发)
  // 通常根据业务需求获取特定用户列表

  // 4. 计算指定用户前一天的心率统计数据
  const calculateStats = async (userId) => {
    // 构建查询:前一天00:00:00 到 23:59:59 的心率数据
    const yesterday = new Date();
    yesterday.setDate(yesterday.getDate() - 1);
    const startTime = new Date(yesterday.setHours(0, 0, 0, 0));
    const endTime = new Date(yesterday.setHours(23, 59, 59, 999));

    const query = cloudDb.CloudDBZoneQuery.where(HeartRateData)
      .equalTo('userId', userId)
      .greaterThanOrEqualTo('timestamp', startTime)
      .lessThanOrEqualTo('timestamp', endTime);

    // 执行查询
    const snapshot = await cloudDbZone.executeQuery(query);
    const hrDataList = snapshot.getSnapshotObjects();

    // 计算统计值
    if (hrDataList.length === 0) return null;

    const values = hrDataList.map(data => data.heartRate);
    const stats = {
      date: startTime.toISOString().split('T')[0], // YYYY-MM-DD
      min: Math.min(...values),
      max: Math.max(...values),
      avg: Math.round(values.reduce((a, b) => a + b, 0) / values.length),
      resting: calculateRestingHr(values), // 假设有计算静息心率的方法
      userId: userId
    };
    return stats;
  };

  // 5. 将统计结果保存到新的报告对象类型 (HealthReport) 中
  const saveReport = async (report) => {
    if (!report) return;
    const reportObj = new HealthReport(report);
    await cloudDbZone.upsert(HealthReport.getObjectTypeName(), [reportObj]);
    console.log(`为用户 ${report.userId} 生成 ${report.date} 日报成功`);
  };

  // 6. 假设这里有一个目标用户列表 (实际应用中需动态获取)
  const targetUsers = ['user123', 'user456']; 
  for (const userId of targetUsers) {
    const report = await calculateStats(userId);
    await saveReport(report);
  }

  return { message: '健康日报生成任务完成' };
};

// 假设的 HealthReport 模型 (需在CloudDB中定义对应ObjectType)
class HealthReport {
  constructor({ date, min, max, avg, resting, userId }) {
    this.reportDate = date;
    this.minHeartRate = min;
    this.maxHeartRate = max;
    this.avgHeartRate = avg;
    this.restingHeartRate = resting;
    this.userId = userId;
    this.generatedAt = new Date();
  }
  static getObjectTypeName() { return 'HealthReport'; }
}

逻辑说明:

  1. 定时触发: 云函数配置为每天凌晨触发 (timer 触发器)。
  2. 数据查询: 查询特定用户前一天的所有心率数据。
  3. 统计分析: 计算最小值、最大值、平均值和静息心率(静息心率算法通常需要更复杂的逻辑,如取晨起特定时段最低值)。
  4. 报告存储: 将计算结果存储到 HealthReport 对象类型中,供客户端查询展示。

五、成就系统与排行榜 (ArkTS + Cloud DB 聚合查询)

1. 查询用户今日运动数据 (步数示例)

// pages/Achievements.ets
import { HealthReport, HeartRateData } from '../model'; // 引入模型
import { cloudDbZone } from '../utils/CloudDBManager'; // 引入初始化好的cloudDbZone

@State dailyStats: HealthReport | null = null; // 日报数据
@State stepCount: number = 0; // 今日步数 (假设从步数传感器获取)

// 1. 查询用户今日健康报告
async function fetchTodayReport(): Promise<void> {
  try {
    const currentUser = getCurrentUser();
    if (!currentUser) return;

    const today = new Date().toISOString().split('T')[0]; // YYYY-MM-DD
    const query = cloudDb.CloudDBZoneQuery.where(HealthReport)
      .equalTo('userId', currentUser.uid)
      .equalTo('reportDate', today);

    const snapshot = await cloudDbZone.executeQuery(query);
    const reports = snapshot.getSnapshotObjects();
    if (reports.length > 0) {
      this.dailyStats = reports[0]; // 取最新的一条
    }
  } catch (err) {
    console.error('获取日报失败:', err);
  }
}

// 2. 勋章计算逻辑 (示例:连续7天达标)
checkConsistencyMedal(): void {
  if (this.dailyStats && this.dailyStats.restingHeartRate < 60) {
    // 更新本地连续达标天数
    this.consecutiveDays++;
    if (this.consecutiveDays >= 7) {
      // 授予勋章! (更新用户档案或展示)
      console.log('恭喜获得"心率稳定之星"勋章!');
    }
  } else {
    this.consecutiveDays = 0; // 中断则重置
  }
}

// 3. 获取步数排行榜 (前10名)
@State leaderboard: { userId: string, steps: number, userName?: string }[] = [];

async function fetchStepLeaderboard(): Promise<void> {
  try {
    // 假设有一个存储每日步数汇总的对象类型 DailyStepSummary
    const query = cloudDb.CloudDBZoneQuery.where(DailyStepSummary)
      .equalTo('date', new Date().toISOString().split('T')[0]) // 查今天
      .orderByDesc('totalSteps') // 按步数降序
      .limit(10); // 前10名

    const snapshot = await cloudDbZone.executeQuery(query);
    const topList = snapshot.getSnapshotObjects();

    // 可能需要根据 userId 查询用户名 (需集成用户信息服务)
    this.leaderboard = topList.map(item => ({
      userId: item.userId,
      steps: item.totalSteps,
      // userName: await getUserName(item.userId) // 异步获取用户名
    }));
  } catch (err) {
    console.error('获取排行榜失败:', err);
  }
}

UI 渲染示例 (简化):

build() {
  Column() {
    // 显示今日报告卡片
    if (this.dailyStats) {
      Card() {
        Text(`今日心率报告 (${this.dailyStats.reportDate})`)
          .fontSize(18)
          .margin(10)
        Row() {
          Text(`平均: ${this.dailyStats.avgHeartRate}`)
          Text(`最低: ${this.dailyStats.minHeartRate}`)
          Text(`最高: ${this.dailyStats.maxHeartRate}`)
          Text(`静息: ${this.dailyStats.restingHeartRate}`)
        }.justifyContent(FlexAlign.SpaceAround)
      }
    }

    // 显示勋章
    if (this.hasMedal) {
      Image($r('app.medal.heart_steady')) // 勋章图片资源
        .width(80)
        .height(80)
    }

    // 显示步数排行榜
    List({ space: 5 }) {
      ForEach(this.leaderboard, (item, index) => {
        ListItem() {
          Row() {
            Text(`${index + 1}. `)
            // Text(item.userName || '用户' + item.userId.slice(0, 4)) // 显示用户名或部分ID
            Text(`步数: ${item.steps}`).fontColor(Color.Blue)
          }.padding(10)
        }
      })
    }
  }
}

功能说明:

  • 报告展示 (fetchTodayReport): 从 Cloud DB 查询并展示当日生成的健康报告。
  • 勋章系统 (checkConsistencyMedal): 基于业务逻辑(如连续7天静息心率达标)判断是否授予勋章。勋章状态可存储在用户档案或本地。
  • 排行榜 (fetchStepLeaderboard): 利用 Cloud DB 的排序 (orderByDesc) 和限制 (limit) 功能,高效获取步数排行榜前10名数据。用户名可能需要额外查询用户信息服务。

六、总结与最佳实践

通过本案例,我们实现了基于 HarmonyOS Next 的运动健康应用核心功能:

  1. 精准数据采集: 利用 HarmonyOS 传感器框架安全、高效获取心率等健康数据。
  2. 可靠云端同步: 通过 AppGallery Connect 云数据库实现用户数据的持久化存储和跨设备同步。
  3. 智能数据分析: 利用云函数在后台执行复杂的数据聚合与报告生成任务,减轻客户端负担。
  4. 互动激励体系: 结合勋章和排行榜,提升用户参与度和运动积极性。

最佳实践建议:

  • 权限最小化: 仅申请应用必需的健康数据权限,并在使用场景中清晰告知用户。
  • 数据缓存与重试: 在网络不稳定时,实现本地数据缓存和云端同步重试机制。
  • 电量优化: 传感器使用遵循生命周期管理,及时停止不必要的监听。
  • 隐私保护: 敏感健康数据在传输和存储时必须加密 (AppGallery Connect 已提供存储加密),严格遵守隐私政策。
  • 云函数优化: 对大数据量操作进行分页处理,设置合理的超时时间和内存配置。
  • UI 体验: 健康数据展示要清晰易懂,使用图表直观呈现趋势变化。

本案例提供了一个坚实的开发框架,开发者可在此基础上扩展睡眠监测、运动模式识别、个性化建议等更高级功能,构建出功能全面、用户体验卓越的运动健康应用。

全部评论

相关推荐

评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务