Redis 缓存余额 + 前端异步局部刷新 + 提供手动刷新按钮
在 ThinkPHP 6 (TP6) 框架下,针对这种对接多个第三方游戏导致页面加载慢的场景,我们可以使用 Swoole 并发请求(最推荐,大幅提升响应速度)或 Redis 缓存结合异步延迟更新 的方案来落地。
以下是具体的 TP6 实现代码和设计方案:
方案一:Swoole 多协程并发请求(针对必须实时返回的场景)
如果业务要求必须在一次请求中实时获取所有三方的余额,绝对不能在 foreach 循环里同步请求。在 TP6 中,如果配置了 topthink/think-swoole 驱动,可以利用 Swoole 协程的 WaitGroup 实现并发请求,把总等待时间缩短为耗时最长的那一个三方接口的时间。
php
<?php
namespace app\controller;
use think\Response;
use Swoole\Coroutine;
use Swoole\Coroutine\WaitGroup;
class GameBalance
{
public function getBalances(): Response
{
// 假设这是你需要查询的 4 个第三方游戏平台标识
$platforms = ['AG', 'BBIN', 'KY', 'LEG'];
$results = [];
// 创建 WaitGroup 计数器
$wg = new WaitGroup();
foreach ($platforms as $platform) {
$wg->add(); // 计数器加 1
// 开启协程并发处理
Coroutine::create(function () use ($platform, $wg, &$results) {
try {
// 使用 Swoole 的协程 HTTP 客户端,或者支持协程的 Guzzle
$results[$platform] = $this->fetchThirdPartyBalance($platform);
} catch (\Exception $e) {
$results[$platform] = '维护中';
} finally {
$wg->done(); // 计数器减 1
}
});
}
// 阻塞等待所有协程执行完毕,并设置总超时时间(例如 2.5 秒)
$wg->wait(2.5);
return json(['code' => 200, 'data' => $results]);
}
// 模拟调用三方接口,必须设置严格的 Timeout
private function fetchThirdPartyBalance($platform)
{
$client = new \Swoole\Coroutine\Http\Client('://thirdparty.com', 80);
$client->set(['timeout' => 1.5]); // 单个三方请求超时 1.5 秒,超时直接熔断
$client->get("/balance?type={$platform}");
if ($client->statusCode === 200) {
$res = json_decode($client->body, true);
return $res['balance'] ?? 0;
}
return 0;
}
}
请谨慎使用此类代码。
方案二:TP6 + Redis 缓存 + 前端异步局部刷新(最常用,推荐)
如果不想依赖 Swoole 复杂的常驻内存环境,最稳妥的办法是首屏读 Redis 缓存,前端再异步通过 Ajax 逐个请求或批量刷新。
1. 后端获取余额接口(优先读缓存)
php
<?php
namespace app\controller;
use think\facade\Cache;
use think\Request;
class Balance
{
/**
* 获取用户所有三方余额(用于页面首屏渲染)
*/
public function index(Request $request)
{
$userId = $request->uid; // 假设已从中间件获取用户ID
$platforms = ['AG', 'BBIN', 'KY', 'LEG'];
$data = [];
foreach ($platforms as $platform) {
// 从 Redis 缓存读取余额,缓存不存在则默认显示 0.00
$cacheKey = "user_balance:{$userId}:{$platform}";
$data[$platform] = Cache::store('redis')->get($cacheKey, '0.00');
}
return json(['code' => 200, 'data' => $data]);
}
/**
* 异步单平台刷新接口(前端加载完后,或用户点刷新按钮时调用)
*/
public function refreshSingle(Request $request)
{
$userId = $request->uid;
$platform = $request->param('platform'); // 接收具体要刷新的平台
if (empty($platform)) {
return json(['code' => 400, 'msg' => '参数错误']);
}
// 1. 频率限制:防止用户疯狂点击刷新按钮,设置 3 秒冷却时间
$lockKey = "lock:balance_refresh:{$userId}:{$platform}";
if (Cache::store('redis')->get($lockKey)) {
// 如果在冷却期,直接返回上一次的缓存值,不请求三方
$currentBalance = Cache::store('redis')->get("user_balance:{$userId}:{$platform}", '0.00');
return json(['code' => 200, 'balance' => $currentBalance, 'msg' => '来自缓存']);
}
// 2. 请求三方接口获取最新余额(使用 GuzzleHttp 并设置 timeout)
$newBalance = $this->requestApi($userId, $platform);
// 3. 更新 Redis 缓存(缓存有效时间根据业务定,比如 5 分钟)
Cache::store('redis')->set("user_balance:{$userId}:{$platform}", $newBalance, 300);
// 4. 设置 3 秒冷却锁
Cache::store('redis')->set($lockKey, 1, 3);
return json(['code' => 200, 'balance' => $newBalance]);
}
private function requestApi($userId, $platform)
{
try {
$client = new \GuzzleHttp\Client(['timeout' => 2.0]); // 严格设置 2 秒超时
$response = $client->request('GET', "https://game.com", [
'query' => ['user' => $userId, 'platform' => $platform]
]);
$res = json_decode($response->getBody()->getContents(), true);
return $res['balance'] ?? '0.00';
} catch (\Exception $e) {
// 接口超时或挂了,返回之前存的旧值,确保页面不报错
return Cache::store('redis')->get("user_balance:{$userId}:{$platform}", '0.00');
}
}
}
请谨慎使用此类代码。
2. 前端配合逻辑
页面初始化:调用
index接口,瞬间拿到 Redis 里的数据并渲染(此时页面秒开)。异步更新:页面加载完成后(
mounted或window.onload),循环遍历平台列表,分别异步请求refreshSingle?platform=AG。局部动画:在收到响应前,每个平台的余额旁边显示一个小旋转动画(Spinner),哪个先回来,哪个就先停止动画并更新数值。
总结建议
如果你们的项目已经在使用 Swoole 或 Think-Swoole,建议直接用 方案一(协程并发),后端能在极短时间内把所有最新的余额打包返回。
如果是常规的 LNMP/WAMP(PHP-FPM)环境,强烈建议采用 方案二(Redis 缓存 + 前端单点异步刷新),不仅完全消除了页面卡顿,还能有效降低由于用户频繁刷新页面对服务器和三方接口造成的并发压力。