php项目统计慢跑恢复时间数据如何?

wen PHP项目 3

PHP项目统计慢跑恢复时间数据的方案

慢跑恢复时间数据统计",我理解可能有两种场景,先帮你区分一下,再给出对应的PHP实现方案。

php项目统计慢跑恢复时间数据如何?

先明确需求场景

场景A:运动App/健康类项目 — 统计用户跑步后的心率恢复时间(运动生理学概念:跑后心率回落到某个区间所需时间)。

场景B:系统性能监控 — 统计接口/任务"慢"的恢复时间(如慢查询恢复、服务从慢状态恢复正常的耗时)。

下面以场景A(运动数据)为主给方案,场景B在最后补充,如果你说的是另一个,告诉我再调整。


数据模型设计(运动恢复时间)

-- 跑步记录表
CREATE TABLE run_records (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    user_id BIGINT NOT NULL,
    start_time DATETIME NOT NULL,
    end_time DATETIME NOT NULL,
    distance DECIMAL(8,2),          -- 公里
    avg_pace INT,                   -- 平均配速(秒/公里)
    avg_hr INT,                     -- 平均心率
    max_hr INT,                     -- 最大心率
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
    INDEX idx_user_time (user_id, start_time)
);
-- 心率采样表(核心:用于计算恢复时间)
CREATE TABLE hr_samples (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    run_id BIGINT NOT NULL,
    user_id BIGINT NOT NULL,
    sample_time DATETIME NOT NULL,
    heart_rate INT NOT NULL,
    phase TINYINT DEFAULT 0,        -- 0=运动中 1=恢复期
    INDEX idx_run (run_id, sample_time)
);
-- 恢复时间结果表(由脚本异步计算写入)
CREATE TABLE recovery_stats (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    run_id BIGINT NOT NULL UNIQUE,
    user_id BIGINT NOT NULL,
    hr_at_stop INT,                  -- 停止时心率
    hr_1min INT,                     -- 1分钟心率
    hr_2min INT,
    hr_drop_1min INT,                -- 1分钟心率下降值(HRR1)
    recovery_time_sec INT,           -- 恢复到静息心率区间耗时
    computed_at DATETIME,
    INDEX idx_user (user_id)
);

关键指标:HRR1(跑后1分钟心率下降值)是运动医学公认的恢复能力指标,通常下降 >12 bpm 为正常。


核心计算逻辑(PHP)

class RecoveryCalculator
{
    private PDO $db;
    // 静息心率阈值:恢复到 静息+10 视为"恢复"
    private int $restingHr = 70;
    public function __construct(PDO $db)
    {
        $this->db = $db;
    }
    /**
     * 计算某次跑步的恢复数据
     */
    public function calcForRun(int $runId): ?array
    {
        // 1. 取出该跑步的所有心率采样(按时间排序)
        $stmt = $this->db->prepare(
            "SELECT sample_time, heart_rate, phase
             FROM hr_samples
             WHERE run_id = :rid
             ORDER BY sample_time ASC"
        );
        $stmt->execute([':rid' => $runId]);
        $samples = $stmt->fetchAll(PDO::FETCH_ASSOC);
        if (count($samples) < 2) {
            return null;
        }
        // 2. 找到"停止跑步"的临界点(最后一个 phase=0 的样本)
        $stopIdx = null;
        foreach ($samples as $i => $s) {
            if ((int)$s['phase'] === 0) {
                $stopIdx = $i;
            }
        }
        if ($stopIdx === null || !isset($samples[$stopIdx + 1])) {
            return null;
        }
        $stopTime = strtotime($samples[$stopIdx]['sample_time']);
        $hrAtStop = (int)$samples[$stopIdx]['heart_rate'];
        // 3. 找 1min / 2min 心率点
        $hr1 = $this->findHrNear($samples, $stopTime + 60);
        $hr2 = $this->findHrNear($samples, $stopTime + 120);
        $hrr1 = $hr1 !== null ? $hrAtStop - $hr1 : null;
        // 4. 计算恢复到静息区间所需时间
        $targetHr = $this->restingHr + 10;
        $recoveryTime = null;
        foreach (array_slice($samples, $stopIdx + 1) as $s) {
            if ((int)$s['heart_rate'] <= $targetHr) {
                $recoveryTime = strtotime($s['sample_time']) - $stopTime;
                break;
            }
        }
        $result = [
            'run_id'            => $runId,
            'hr_at_stop'        => $hrAtStop,
            'hr_1min'           => $hr1,
            'hr_2min'           => $hr2,
            'hr_drop_1min'      => $hrr1,
            'recovery_time_sec' => $recoveryTime,
        ];
        $this->save($result);
        return $result;
    }
    /**
     * 找最接近目标时间点的心率样本(容差±15秒)
     */
    private function findHrNear(array $samples, int $targetTs): ?int
    {
        $best = null;
        $minDiff = PHP_INT_MAX;
        foreach ($samples as $s) {
            $diff = abs(strtotime($s['sample_time']) - $targetTs);
            if ($diff < $minDiff) {
                $minDiff = $diff;
                $best = (int)$s['heart_rate'];
            }
        }
        return $minDiff <= 15 ? $best : null;
    }
    private function save(array $r): void
    {
        $sql = "INSERT INTO recovery_stats
                (run_id, user_id, hr_at_stop, hr_1min, hr_2min,
                 hr_drop_1min, recovery_time_sec, computed_at)
                VALUES (:run_id, :user_id, :hr_stop, :hr1, :hr2,
                        :hrr1, :recovery, NOW())
                ON DUPLICATE KEY UPDATE
                    hr_at_stop = VALUES(hr_at_stop),
                    hr_1min = VALUES(hr_1min),
                    hr_2min = VALUES(hr_2min),
                    hr_drop_1min = VALUES(hr_drop_1min),
                    recovery_time_sec = VALUES(recovery_time_sec),
                    computed_at = NOW()";
        // ... bind & execute
    }
}

批量统计与性能优化

关键原则:实时接口只查汇总表,重计算异步跑。

// 批量补算(建议放到定时任务/队列,分页处理避免内存爆)
$pageSize = 500;
$lastId = 0;
while (true) {
    $runs = $db->query(
        "SELECT id, user_id FROM run_records
         WHERE id > $lastId
           AND id NOT IN (SELECT run_id FROM recovery_stats)
         ORDER BY id LIMIT $pageSize"
    )->fetchAll();
    if (!$runs) break;
    foreach ($runs as $run) {
        $calc->calcForRun((int)$run['id']);
        $lastId = $run['id'];
    }
}

性能优化要点:

  1. hr_samples 按 (run_id, sample_time) 建联合索引,避免全表扫。
  2. 单次跑步采样数据先一次性拉全在内存算,不要在循环里逐条查库。
  3. 大量历史数据用分批 + 队列(Redis/RabbitMQ),别在 Web 请求里同步算。
  4. 汇总统计(如某用户平均恢复时间)在 recovery_stats 上用 SQL 聚合:
SELECT user_id,
       AVG(hr_drop_1min)   AS avg_hrr1,
       AVG(recovery_time_sec) AS avg_recovery,
       COUNT(*) AS run_count
FROM recovery_stats
WHERE user_id = :uid
  AND computed_at >= DATE_SUB(NOW(), INTERVAL 30 DAY)
GROUP BY user_id;

高频查询的用户统计结果可缓存到 Redis(如 TTL 1 小时),写时失效。


如果其实是场景B(系统慢恢复时间)

那"统计"通常指:记录服务从慢/故障状态恢复到正常的耗时,做法:

CREATE TABLE slow_recovery_log (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    service VARCHAR(64),
    metric VARCHAR(64),          -- 如 p99_latency
    slow_start DATETIME,         -- 进入慢阈值时间
    normal_time DATETIME,        -- 恢复正常时间
    duration_ms INT,             -- 恢复耗时
    INDEX idx_service (service, slow_start)
);

用 PHP 定时采集指标,状态机检测 正常 → 慢 → 正常 的跳变并记录 duration,再做 P50/P95 聚合分析。


能否确认一下你指的是哪种场景? 如果是运动数据,还需要确认:心率采样是实时上传还是事后批量导入?是否已有静息心率数据?这样我可以把计算逻辑再校准。

抱歉,评论功能暂时关闭!