PHP项目统计慢跑恢复时间数据方案
慢跑恢复时间(通常指运动后心率恢复到正常区间所需时间)的统计在PHP项目中实现,需要从数据建模、采集、计算、统计展示几个层面来设计,下面给你一套完整可落地的方案。

数据建模
核心表设计
-- 用户表(已有)
-- users(id, name, ...)
-- 慢跑记录表
CREATE TABLE run_records (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
user_id BIGINT UNSIGNED NOT NULL,
start_time DATETIME NOT NULL, -- 开始跑步时间
end_time DATETIME NOT NULL, -- 结束跑步时间
duration INT NOT NULL, -- 持续秒数
distance DECIMAL(8,2), -- 距离(km)
avg_pace INT, -- 平均配速(秒/km)
avg_heart_rate TINYINT, -- 平均心率
max_heart_rate TINYINT, -- 最大心率
end_heart_rate TINYINT, -- 结束时心率
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
INDEX idx_user_time (user_id, start_time)
);
-- 心率采样表(可选,用于精确计算恢复时间)
CREATE TABLE heart_rate_samples (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
run_record_id BIGINT UNSIGNED NOT NULL,
user_id BIGINT UNSIGNED NOT NULL,
heart_rate TINYINT NOT NULL,
recorded_at DATETIME NOT NULL,
phase ENUM('running','recovery') DEFAULT 'running', -- 跑步中/恢复中
INDEX idx_run (run_record_id, recorded_at)
);
-- 恢复记录表(关键)
CREATE TABLE recovery_records (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
run_record_id BIGINT UNSIGNED NOT NULL,
user_id BIGINT UNSIGNED NOT NULL,
peak_heart_rate TINYINT NOT NULL, -- 结束瞬间心率
target_heart_rate TINYINT NOT NULL, -- 目标恢复心率(如静息+10)
recovery_seconds INT NOT NULL, -- 恢复耗时
recovery_1min TINYINT, -- 1分钟后心率
recovery_2min TINYINT, -- 2分钟后心率
hrr_1min TINYINT, -- 1分钟心率恢复值(HRR1)
hrr_2min TINYINT, -- 2分钟心率恢复值(HRR2)
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
INDEX idx_user (user_id, created_at)
);
关键指标说明:
- HRR1(1分钟心率恢复):运动停止后1分钟内心率下降值,是心血管健康的经典指标
- 恢复时间:从结束心率降到目标心率(通常静息心率+10~20)所需时间
恢复时间计算逻辑(PHP)
class RecoveryCalculator
{
/**
* 根据心率采样计算恢复数据
* @param array $samples 按时间排序的恢复期心率采样
*/
public function calc(array $samples, int $restingHr): array
{
if (empty($samples)) return [];
$peak = $samples[0]['heart_rate'];
$target = $restingHr + 10; // 目标心率
$startTs = strtotime($samples[0]['recorded_at']);
$recoverySeconds = null;
$hr1min = $hr2min = null;
foreach ($samples as $s) {
$t = strtotime($s['recorded_at']) - $startTs;
if ($t >= 60 && $hr1min === null) $hr1min = $s['heart_rate'];
if ($t >= 120 && $hr2min === null) $hr2min = $s['heart_rate'];
if ($recoverySeconds === null && $s['heart_rate'] <= $target) {
$recoverySeconds = $t;
}
}
// 未达标时,用线性回归外推(或标记为未恢复)
if ($recoverySeconds === null) {
$recoverySeconds = $this->extrapolate($samples, $target);
}
return [
'peak_heart_rate' => $peak,
'target_heart_rate'=> $target,
'recovery_seconds' => $recoverySeconds,
'recovery_1min' => $hr1min,
'recovery_2min' => $hr2min,
'hrr_1min' => $hr1min ? $peak - $hr1min : null,
'hrr_2min' => $hr2min ? $peak - $hr2min : null,
];
}
private function extrapolate(array $samples, int $target): ?int
{
// 简单对数拟合:HR(t) = a + b*ln(t+1)
// 生产环境建议用最小二乘法,这里省略具体实现
return null; // 或返回估算值
}
}
慢跑恢复统计查询(SQL + PHP)
平均恢复时间趋势(按月)
SELECT
DATE_FORMAT(r.start_time, '%Y-%m') AS month,
COUNT(*) AS run_count,
ROUND(AVG(rec.recovery_seconds), 0) AS avg_recovery_sec,
ROUND(AVG(rec.hrr_1min), 1) AS avg_hrr1,
MIN(rec.recovery_seconds) AS best_recovery,
MAX(rec.recovery_seconds) AS worst_recovery
FROM run_records r
JOIN recovery_records rec ON rec.run_record_id = r.id
WHERE r.user_id = :user_id
AND r.start_time >= :start
GROUP BY month
ORDER BY month;
用户恢复能力画像
SELECT
u.id,
u.name,
COUNT(*) AS total_runs,
ROUND(AVG(rec.recovery_seconds), 0) AS avg_recovery,
ROUND(AVG(rec.hrr_1min), 1) AS avg_hrr1,
-- 恢复能力评级
CASE
WHEN AVG(rec.recovery_seconds) < 180 THEN '优秀'
WHEN AVG(rec.recovery_seconds) < 300 THEN '良好'
WHEN AVG(rec.recovery_seconds) < 480 THEN '一般'
ELSE '需改善'
END AS level
FROM users u
JOIN run_records r ON r.user_id = u.id
JOIN recovery_records rec ON rec.run_record_id = r.id
WHERE r.start_time >= DATE_SUB(NOW(), INTERVAL 30 DAY)
GROUP BY u.id, u.name
HAVING total_runs >= 3
ORDER BY avg_recovery ASC;
性能优化要点
| 问题 | 优化手段 |
|---|---|
| 心率采样数据量大 | 冷热分离:近期数据 MySQL,历史归档到 ClickHouse/TDengine |
| 统计查询慢 | 预聚合日/周/月汇总表 recovery_stats_daily,定时任务刷新 |
| 实时计算卡顿 | 恢复数据在跑步结束时异步(队列)计算并落库 |
| 高频查询用户画像 | Redis 缓存,TTL 1小时,写入时失效 |
| 大数据量分页 | 用 WHERE id < last_id LIMIT 20 替代 OFFSET |
预聚合表示例
CREATE TABLE recovery_stats_daily (
user_id BIGINT UNSIGNED,
stat_date DATE,
run_count INT,
avg_recovery_seconds INT,
avg_hrr1 DECIMAL(4,1),
best_recovery_seconds INT,
PRIMARY KEY (user_id, stat_date)
);
业务建议
- 目标心率个性化:不要用固定值,用
静息心率 + 10~20,或按年龄(220-年龄)*0.5。 - HRR1 是核心指标:研究表明运动后1分钟心率下降 <12 次提示心血管风险,可作为健康预警。
- 异常过滤:剔除采样间隔 >10s、心率突变 >30bpm 的脏数据。
- 可视化:前端展示恢复曲线(时间-心率),用折线图 + 目标线更直观。
- 对比维度:支持同一用户纵向对比(周/月/季)和同龄人群横向对比。
如果你能说明一下数据来源(手环/APP/手动录入)和规模量级(日增多少条),我可以给出更针对性的存储和计算架构方案。