实时 PHP 项目:显示场上谁更占优势
这是一个很常见的需求,比如体育比赛实时比分优势展示、游戏对战优劣势面板、直播互动票数领先等,下面给你一套可落地的方案。

核心思路
"谁更占优势"本质是把多个维度的数据归一化 + 加权,算出一个优势指数。
优势指数 = Σ(维度得分 × 权重)
前端定时(或 WebSocket 推送)向 PHP 后端拉取数据,PHP 计算后返回,前端用进度条 / 环形图 / 文字展示。
数据模型示例(以体育比赛为例)
CREATE TABLE matches (
id INT PRIMARY KEY AUTO_INCREMENT,
team_a VARCHAR(50),
team_b VARCHAR(50),
status TINYINT DEFAULT 0 -- 0未开始 1进行中 2结束
);
CREATE TABLE match_stats (
id INT PRIMARY KEY AUTO_INCREMENT,
match_id INT,
team ENUM('A','B'),
score INT DEFAULT 0, -- 得分
possession INT DEFAULT 0, -- 控球率 %
shots INT DEFAULT 0, -- 射门
shots_on_target INT DEFAULT 0,
fouls INT DEFAULT 0,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX(match_id)
);
PHP 计算优势指数
<?php
// advantage.php
header('Content-Type: application/json; charset=utf-8');
$matchId = (int)($_GET['match_id'] ?? 0);
if (!$matchId) exit(json_encode(['error' => 'missing match_id']));
$pdo = new PDO('mysql:host=127.0.0.1;dbname=live;charset=utf8mb4', 'root', 'pass', [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
]);
// 1. 拉取双方统计
$stmt = $pdo->prepare("SELECT team, score, possession, shots, shots_on_target, fouls
FROM match_stats WHERE match_id = ?");
$stmt->execute([$matchId]);
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
if (count($rows) < 2) exit(json_encode(['error' => 'no data']));
$stats = [];
foreach ($rows as $r) $stats[$r['team']] = $r;
$A = $stats['A']; $B = $stats['B'];
// 2. 定义维度权重(可按业务调整)
$weights = [
'score' => 3.0, // 得分最重要
'possession' => 1.0,
'shots' => 1.2,
'shots_on_target'=> 1.5,
'fouls' => -0.5, // 犯规扣分
];
// 3. 归一化:每个维度算 A 占比(0~1)
function ratio($a, $b) {
$sum = $a + $b;
return $sum == 0 ? 0.5 : $a / $sum;
}
$dims = [
'score' => ratio($A['score'], $B['score']),
'possession' => ratio($A['possession'], $B['possession']),
'shots' => ratio($A['shots'], $B['shots']),
'shots_on_target' => ratio($A['shots_on_target'], $B['shots_on_target']),
'fouls' => ratio($B['fouls'], $A['fouls']), // 反向:犯规少者占优
];
// 4. 加权求和
$totalWeight = array_sum(array_map('abs', $weights));
$scoreA = 0;
foreach ($dims as $k => $v) {
$scoreA += $v * abs($weights[$k]);
}
$scoreA = $scoreA / $totalWeight; // 0~1
$scoreB = 1 - $scoreA;
// 5.
$diff = abs($scoreA - $scoreB);
if ($diff < 0.05) $verdict = '势均力敌';
elseif ($scoreA > $scoreB) $verdict = $A['team'] . ' 略占优势';
else $verdict = $B['team'] . ' 略占优势';
echo json_encode([
'match_id' => $matchId,
'team_a' => ['name' => 'A队', 'advantage' => round($scoreA * 100, 1)],
'team_b' => ['name' => 'B队', 'advantage' => round($scoreB * 100, 1)],
'verdict' => $verdict,
'dims' => $dims,
'updated_at'=> date('H:i:s'),
]);
返回示例:
{
"team_a": {"name":"A队","advantage": 62.3},
"team_b": {"name":"B队","advantage": 37.7},
"verdict": "A队 略占优势",
"dims": {"score":0.66,"possession":0.55,"shots":0.7,...}
}
前端实时展示(两种方式)
方式 1:轮询(简单,兼容性好)
<div id="panel">
<div class="bar">
<span id="a-name">A队</span>
<div class="progress"><div id="a-bar"></div></div>
<span id="a-val">0%</span>
</div>
<div class="bar">
<span id="b-name">B队</span>
<div class="progress"><div id="b-bar"></div></div>
<span id="b-val">0%</span>
</div>
<p id="verdict">加载中…</p>
</div>
<script>
async function refresh() {
const r = await fetch('/advantage.php?match_id=1').then(x => x.json());
if (r.error) return;
document.getElementById('a-name').textContent = r.team_a.name;
document.getElementById('b-name').textContent = r.team_b.name;
document.getElementById('a-bar').style.width = r.team_a.advantage + '%';
document.getElementById('b-bar').style.width = r.team_b.advantage + '%';
document.getElementById('a-val').textContent = r.team_a.advantage + '%';
document.getElementById('b-val').textContent = r.team_b.advantage + '%';
document.getElementById('verdict').textContent = r.verdict;
}
refresh();
setInterval(refresh, 2000); // 每2秒刷新
</script>
方式 2:WebSocket / SSE 推送(真·实时)
PHP 原生对 WebSocket 支持有限,推荐:
- Workerman:纯 PHP 常驻进程,广播推送
- Swoole:性能更好
- SSE(Server-Sent Events):PHP-FPM 也能做,单向推送足够
SSE 简例:
// sse.php
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');
while (true) {
$data = file_get_contents('http://localhost/advantage.php?match_id=1');
echo "data: {$data}\n\n";
@ob_flush(); flush();
sleep(1);
}
const es = new EventSource('/sse.php');
es.onmessage = e => render(JSON.parse(e.data));
让"优势"更准的进阶技巧
| 技巧 | 说明 |
|---|---|
| 近期加权 | 5 分钟的数据权重更高(时间衰减) |
| 动量(Momentum) | 用滑动窗口计算近 3 分钟得分变化率 |
| Elo / 贝叶斯 | 结合赛前赔率、队伍实力先验,避免开场全是 50% |
| 维度标准化 | 每维度先 min-max 归一化到 0~1,再按权重合并 |
| 可视化增强 | 中心线 + 左右对称条形图,直观显示"哪边在涨" |
| 异常过滤 | 剔除被刷的数据,防止优势指数被瞬间拉爆 |
动量示例:
// 3 分钟得分变化率
$recent = $pdo->prepare("SELECT team, SUM(score) s FROM match_events
WHERE match_id=? AND created_at > NOW() - INTERVAL 3 MINUTE
GROUP BY team");
$recent->execute([$matchId]);
$momentum = [...]; // 加 0.3 权重到最终指数
架构建议
[数据源] → [写入 match_stats / match_events]
↓
[PHP 计算层 advantage.php]
↓
┌─────────────┴─────────────┐
[轮询 API] [WebSocket/SSE 推送]
↓ ↓
前端可视化面板
- 数据量大时用 Redis 缓存 计算结果(如 1 秒过期),避免每次查库
- 高频更新场景(如直播),把计算逻辑下推到 Workerman 常驻内存里,PHP-FPM 只做读
我可以继续帮你
告诉我你的具体场景,我可以给出更贴合的代码:
- 是体育比赛?电竞?还是投票/互动? 维度不同,权重不同
- 数据来源是手动录入、第三方 API 还是用户交互?
- 要不要历史曲线 / 优势变化趋势图?(可以用 Chart.js)
- 要不要我用 Workerman 写一个完整可运行的实时推送 demo?
你把场景说清楚,我直接给你能跑起来的完整项目结构。