PHP项目统计慢跑恢复时间数据方案
慢跑恢复时间(通常指运动后心率恢复到静息水平的时间)是一个常见的运动健康统计需求,下面给你一套完整的实现思路。

数据模型设计
核心表结构(MySQL)
-- 运动记录表
CREATE TABLE running_records (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
user_id BIGINT UNSIGNED NOT NULL,
start_time DATETIME NOT NULL, -- 开始时间
end_time DATETIME NOT NULL, -- 结束时间
distance DECIMAL(10,2) DEFAULT 0, -- 距离(km)
avg_pace INT DEFAULT 0, -- 平均配速(秒/km)
avg_heart_rate TINYINT UNSIGNED, -- 平均心率
max_heart_rate TINYINT UNSIGNED, -- 最大心率
end_heart_rate TINYINT UNSIGNED, -- 结束即时心率
recovery_seconds INT DEFAULT NULL, -- 恢复时间(秒)★核心
recovery_heart_rate TINYINT UNSIGNED, -- 恢复后心率
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
INDEX idx_user_time (user_id, start_time),
INDEX idx_recovery (user_id, recovery_seconds)
);
-- 心率采样表(用于精确计算恢复时间)
CREATE TABLE heart_rate_samples (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
record_id BIGINT UNSIGNED NOT NULL,
user_id BIGINT UNSIGNED NOT NULL,
bpm TINYINT UNSIGNED NOT NULL,
sampled_at DATETIME NOT NULL,
INDEX idx_record_time (record_id, sampled_at)
);
恢复时间计算逻辑
class RecoveryCalculator
{
/** 静息心率(可用用户近7天晨起平均值) */
private int $restingHr;
/** 恢复阈值:心率降至静息+20或低于最大心率65% */
private int $threshold;
public function __construct(int $restingHr, int $maxHr)
{
$this->restingHr = $restingHr;
$this->threshold = max($restingHr + 20, (int)($maxHr * 0.65));
}
/**
* 根据心率采样序列计算恢复时间(秒)
* @param array $samples [['bpm'=>120,'ts'=>1699999999], ...] 按时间升序
*/
public function calculate(array $samples): ?int
{
if (count($samples) < 2) return null;
$startTs = $samples[0]['ts'];
foreach ($samples as $s) {
if ($s['bpm'] <= $this->threshold) {
return $s['ts'] - $startTs;
}
}
return null; // 未恢复到阈值
}
}
统计查询场景
场景1:用户恢复时间趋势(按月)
public function monthlyTrend(int $userId, string $month): array
{
$sql = "SELECT
DATE(start_time) AS day,
AVG(recovery_seconds) AS avg_recovery,
MIN(recovery_seconds) AS best_recovery,
COUNT(*) AS run_count
FROM running_records
WHERE user_id = :uid
AND recovery_seconds IS NOT NULL
AND start_time >= :start
AND start_time < :end
GROUP BY DATE(start_time)
ORDER BY day";
$stmt = $this->pdo->prepare($sql);
$stmt->execute([
':uid' => $userId,
':start' => $month . '-01 00:00:00',
':end' => date('Y-m-d', strtotime("$month-01 +1 month")) . ' 00:00:00',
]);
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
场景2:恢复能力评估(对比前后期)
public function comparePeriods(int $userId, string $p1Start, string $p1End, string $p2Start, string $p2End): array
{
$sql = "SELECT
CASE
WHEN start_time BETWEEN :p1s AND :p1e THEN 'before'
ELSE 'after'
END AS period,
AVG(recovery_seconds) AS avg_recovery,
AVG(avg_heart_rate) AS avg_hr
FROM running_records
WHERE user_id = :uid
AND recovery_seconds IS NOT NULL
AND (start_time BETWEEN :p1s AND :p1e
OR start_time BETWEEN :p2s AND :p2e)
GROUP BY period";
// ... 参数绑定
}
场景3:恢复速度分级统计
SELECT
CASE
WHEN recovery_seconds < 60 THEN '优秀(<1min)'
WHEN recovery_seconds < 180 THEN '良好(1-3min)'
WHEN recovery_seconds < 300 THEN '一般(3-5min)'
ELSE '偏慢(>5min)'
END AS level,
COUNT(*) AS cnt,
ROUND(COUNT(*) * 100.0 / SUM(COUNT(*)) OVER (), 2) AS pct
FROM running_records
WHERE user_id = ? AND recovery_seconds IS NOT NULL
GROUP BY level;
性能优化建议
| 问题 | 方案 |
|---|---|
| 单表数据量大 | 按 user_id 分表或按月分区 |
| 频繁聚合统计 | 预计算天级/周级汇总表 recovery_daily_stats |
| 心率采样查询慢 | 只存"运动结束到恢复"窗口的采样点 |
| 高并发查询 | Redis 缓存用户最近30天统计数据 |
汇总表设计
CREATE TABLE recovery_daily_stats (
user_id BIGINT UNSIGNED,
stat_date DATE,
avg_recovery INT,
min_recovery INT,
run_count INT,
updated_at TIMESTAMP,
PRIMARY KEY(user_id, stat_date)
);
通过定时任务(Crontab + PHP 脚本)或消息队列异步更新。
输出示例(API 返回)
{
"user_id": 1001,
"period": "2024-11",
"summary": {
"avg_recovery_seconds": 156,
"best_recovery_seconds": 68,
"worst_recovery_seconds": 420,
"total_runs": 18
},
"trend": [
{"day": "2024-11-01", "avg_recovery": 180, "run_count": 1},
{"day": "2024-11-03", "avg_recovery": 145, "run_count": 2}
],
"level_distribution": {
"优秀": 4,
"良好": 9,
"一般": 4,
"偏慢": 1
}
}
关键注意点
- 恢复时间定义要统一:是降到静息心率,还是降到"静息+20"?建议用后者,否则数据永远不达标。
- 静息心率动态化:用近7天晨起心率均值,比固定值 60 bpm 更科学。
- 异常值过滤:
recovery_seconds < 10或> 3600视为无效(传感器误差)。 - 缺失处理:
recovery_seconds IS NULL的记录不应参与 AVG,否则会拉低统计。 - 时区问题:
start_time存 UTC,展示时转换,避免按天分组错乱。
需要我针对具体某块展开吗?
- 心率采样的实时计算(PHP + Redis 流式处理)
- 对接运动手表 API(Garmin/华为/Apple Health 数据导入)
- 可视化图表(ECharts 前端联动)
- 分表/分区实施细节
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播