本文目录导读:

在 PHP 项目中统计慢跑恢复时间(即运动后的心率恢复、配速恢复或生理恢复时长)数据,是一个典型的数据采集 → 存储 → 计算 → 可视化的过程。
由于 PHP 通常用于后端 API 和 Web 管理后台,它不适合做实时秒级的心率监测(那应该用前端 JS 或移动端原生),但非常适合做数据聚合、趋势分析和日志记录。
以下是完整的实现方案,分为数据建模、核心计算逻辑、统计接口和可视化展示四个部分。
数据建模(MySQL 数据库设计)
你需要存储原始的运动数据和恢复时间记录,建议设计两张表:
运动记录表 run_sessions
用于存储每次跑步的基本信息。
CREATE TABLE `run_sessions` ( `id` BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, `user_id` INT UNSIGNED NOT NULL COMMENT '用户ID', `started_at` DATETIME NOT NULL COMMENT '开始时间', `avg_hr` INT DEFAULT NULL COMMENT '平均心率(bpm)', `max_hr` INT DEFAULT NULL COMMENT '最大心率(bpm)', `total_distance` DECIMAL(6,2) DEFAULT 0 COMMENT '总距离(公里)', `duration_sec` INT DEFAULT 0 COMMENT '总时长(秒)', `resting_hr` INT DEFAULT NULL COMMENT '运动后静息心率(完全休息时)', `pacer_hr` INT DEFAULT NULL COMMENT '配速心率(慢跑时的目标心率)', `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 建立索引,用于快速查询用户的时间序列 ALTER TABLE `run_sessions` ADD INDEX `idx_user_start` (`user_id`, `started_at`);
实时心率明细表(可选,用于精确计算) hr_readings
如果你有手表或心率带实时上传数据,建议每 5 秒记录一次心率,用于计算“瞬时恢复率”。
CREATE TABLE `hr_readings` ( `id` BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, `session_id` BIGINT UNSIGNED NOT NULL COMMENT '关联运动ID', `timestamp` TIME NOT NULL COMMENT '运动过程中的时间点', `hr` INT NOT NULL COMMENT '瞬时心率(bpm)', INDEX (`session_id`, `timestamp`) ) ENGINE=InnoDB;
核心计算逻辑(PHP 后端)
在 PHP 中,我们需要实现两个核心函数:计算单次恢复时间 和 统计周期趋势。
单次恢复时间计算(利用心率表)
著名的 “1分钟心率恢复” 指标(第一分钟心率下降值)是衡量恢复能力的金标准,计算公式为:运动结束时的最高心率 - 结束1分钟后瞬间的心率。
<?php
class RunningRecoveryAnalyzer
{
private $pdo;
public function __construct(PDO $pdo)
{
$this->pdo = $pdo;
}
/**
* 计算单次跑步的恢复指标
* @param int $sessionId 运动记录ID
* @return array 返回恢复数据
*/
public function calculateSessionRecovery(int $sessionId): array
{
// 1. 获取本次运动的基本数据
$session = $this->fetchSession($sessionId);
if (!$session) {
return ['error' => '运动记录不存在'];
}
// 2. 获取最后2分钟的心率数据(用于分析)
$stmt = $this->pdo->prepare(
"SELECT hr, timestamp FROM hr_readings
WHERE session_id = ?
ORDER BY timestamp DESC LIMIT 120" // 取最后120条(假设每5秒一条,约10分钟)
);
$stmt->execute([$sessionId]);
$readings = $stmt->fetchAll(PDO::FETCH_ASSOC);
// 反转数组,让时间从旧到新排列
$readings = array_reverse($readings);
if (count($readings) < 2) {
return ['error' => '心率数据不足'];
}
// 3. 计算关键指标
$end_hr = $readings[count($readings) - 1]['hr']; // 最后心率(接近结束心率)
// 寻找结束前1分钟的对应数据点(假设每5秒一条,1分钟 = 12条数据)
$one_min_ago_hr = $readings[max(0, count($readings) - 13)]['hr'] ?? $end_hr;
// 恢复心率值(正常应大于10,越大会越好)
$recovery_drop = $end_hr - $one_min_ago_hr;
// 恢复时间估算(简易模型:根据心率下降比例估算完全恢复需要的时间)
// 假设目标为回到基础心率 +10%
$time_to_recovery = $this->estimateTimeToRecovery($readings, $session['resting_hr']);
// 4. 更新主记录
$this->updateSessionRecoveryStats($sessionId, $recovery_drop, $time_to_recovery);
return [
'session_id' => $sessionId,
'end_hr' => $end_hr,
'one_min_drop' => $recovery_drop, // 1分钟心率恢复值
'estimated_recovery_min' => $time_to_recovery // 预估恢复时间(分钟)
];
}
/**
* 估算恢复时间(分钟)
* 基于心率衰减指数模型:HR(t) = HR_rest + (HR_max - HR_rest) * e^(-t/tau)
*/
private function estimateTimeToRecovery(array $readings, int $restingHr): int
{
// 简单线性回归或指数拟合较复杂,这里用经验公式:
// 恢复时间 ≈ 运动时间 * 强度系数 / 恢复能力系数
// 简化为:1分钟恢复 > 20 表示恢复极快,恢复时间 = 30分钟;<10 表示很累,恢复时间 = 24小时
if (count($readings) < 2) return 60;
$firstHr = $readings[0]['hr'];
$lastHr = $readings[count($readings) - 1]['hr'];
// 计算平均下降速率 (每分钟)
$total_time_min = (count($readings) - 1) * (5/60); // 每5秒一个点
$avg_drop_per_min = ($firstHr - $lastHr) / max($total_time_min, 1);
// 需要降低到静息心率的 110% 才算恢复
if ($restingHr <= 0 || $lastHr <= $restingHr) {
return 0;
}
$targetHr = $restingHr * 1.1;
$hr_need_to_drop = $lastHr - $targetHr;
if ($hr_need_to_drop <= 0) {
return 0;
}
$estimated_minutes = round($hr_need_to_drop / max($avg_drop_per_min, 0.5));
return max(0, min($estimated_minutes, 1440)); // 限制在0-24小时
}
private function fetchSession(int $sessionId)
{
$stmt = $this->pdo->prepare("SELECT * FROM run_sessions WHERE id = ?");
$stmt->execute([$sessionId]);
return $stmt->fetch(PDO::FETCH_ASSOC);
}
private function updateSessionRecoveryStats(int $sessionId, int $drop, int $recoveryMin): void
{
$stmt = $this->pdo->prepare(
"UPDATE run_sessions
SET one_min_recovery_drop = ?, estimated_recovery_min = ?
WHERE id = ?"
);
$stmt->execute([$drop, $recoveryMin, $sessionId]);
}
}
统计与聚合(生成报表)
当你需要查看“近 30 天的平均恢复时间”或“本月的最佳表现”时,PHP 直接查数据库聚合即可。
<?php
// controllers/RecoveryStatsController.php
public function weeklyStats($userId) {
$pdo = // ... 获取数据库连接
// 查询最近7天的恢复数据统计
$sql = "
SELECT
DATE(started_at) AS date,
COUNT(*) AS runs,
AVG(one_min_recovery_drop) AS avg_drop,
AVG(estimated_recovery_min) AS avg_recovery_min
FROM run_sessions
WHERE user_id = ? AND started_at >= DATE_SUB(CURDATE(), INTERVAL 7 DAY)
GROUP BY DATE(started_at)
ORDER BY date DESC
";
$stmt = $pdo->prepare($sql);
$stmt->execute([$userId]);
$stats = $stmt->fetchAll(PDO::FETCH_ASSOC);
// 计算趋势(是否在变好)
$trend = $this->calculateTrend($stats);
header('Content-Type: application/json');
echo json_encode([
'status' => 'success',
'data' => $stats,
'trend' => $trend
]);
}
// 计算趋势:对比最近3天和之前3天的平均值
private function calculateTrend(array $stats): string
{
if (count($stats) < 2) return 'insufficient_data';
// 取最新3个的平均值
$recent = array_slice($stats, 0, 3);
$previous = array_slice($stats, 3, 3);
$recentAvg = array_sum(array_column($recent, 'avg_recovery_min')) / max(count($recent), 1);
$prevAvg = array_sum(array_column($previous, 'avg_recovery_min')) / max(count($previous), 1);
if ($recentAvg < $prevAvg) {
return 'improving'; // 恢复时间变短,变好
} elseif ($recentAvg > $prevAvg) {
return 'worsening'; // 恢复变慢
} else {
return 'stable';
}
}
前端可视化(与 PHP 配合)
统计完数据后,你需要在前端展示图表。
方案建议:使用 Chart.js 或 ECharts(均支持 PHP 输出 JSON 数据直接绑定)。
<!-- 图表展示 - 使用ECharts (从后端API获取数据) -->
<div id="recoveryChart" style="width: 100%; height: 400px;"></div>
<script src="https://cdn.jsdelivr.net/npm/echarts/dist/echarts.min.js"></script>
<script>
fetch('/api/recovery-stats?user_id=1')
.then(res => res.json())
.then(data => {
var chart = echarts.init(document.getElementById('recoveryChart'));
// 解析PHP返回的数据
var dates = data.data.map(item => item.date);
var avgRecovery = data.data.map(item => item.avg_recovery_min);
chart.setOption({
title: { text: '近7天恢复时间趋势' },
tooltip: { trigger: 'axis' },
xAxis: { type: 'category', data: dates },
yAxis: { type: 'value', name: '分钟' },
series: [{
name: '平均预估恢复时间',
type: 'line',
smooth: true,
data: avgRecovery,
markPoint: {
data: [{ type: 'min', name: '最快恢复' }]
}
}]
});
});
</script>
扩展与进阶优化
-
多影响因素分析:
- 将“睡眠质量”和“前一晚的静息心率”作为调节因子,用 PHP 做多变量回归,预测恢复时间。
- 如果昨晚睡眠 < 6 小时,则恢复时间增加 20%。
-
实时计算优化:
- 如果要求秒级响应,PHP 无法胜任,建议将计算逻辑转移到 Node.js 或 Go 的微服务,PHP 只负责存储调用后的结果。
-
边界情况处理:
- 心率数据缺失(穿戴设备没戴好)时,PHP 应使用插值算法填充缺失值。
one_min_drop为负数(心率还在上升),应判断为“冲刺后遗症”,恢复时间设置为 24 小时。
PHP 在慢跑恢复统计中的最佳实践是:
- 存储端:MySQL 存储跑步记录和心率明细。
- 计算端:PHP 通过 SQL 聚合 + 数学模型估算恢复时间。
- 输出端:RESTful API 输出 JSON,前端图表展示。
核心代码已包含:数据库表结构、单次恢复计算(1分钟心率下降法)、趋势统计、前端图表绑定,你只需将上述代码集成到你的 Laravel / ThinkPHP / 原生框架中即可。