avatar

mdo

Hello

  • 首页
  • 知识库
  • 归档
  • 标签
  • 关于
主页 Redis 缓存余额 + 前端异步局部刷新 + 提供手动刷新按钮
文章

Redis 缓存余额 + 前端异步局部刷新 + 提供手动刷新按钮

发表于 2026-06-23 更新于 2026-06- 23
作者 mdo
13~17 分钟 阅读

在 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. 前端配合逻辑

  1. 页面初始化:调用 index 接口,瞬间拿到 Redis 里的数据并渲染(此时页面秒开)。

  2. 异步更新:页面加载完成后(mounted 或 window.onload),循环遍历平台列表,分别异步请求refreshSingle?platform=AG。

  3. 局部动画:在收到响应前,每个平台的余额旁边显示一个小旋转动画(Spinner),哪个先回来,哪个就先停止动画并更新数值。


总结建议

  • 如果你们的项目已经在使用 Swoole 或 Think-Swoole,建议直接用 方案一(协程并发),后端能在极短时间内把所有最新的余额打包返回。

  • 如果是常规的 LNMP/WAMP(PHP-FPM)环境,强烈建议采用 方案二(Redis 缓存 + 前端单点异步刷新),不仅完全消除了页面卡顿,还能有效降低由于用户频繁刷新页面对服务器和三方接口造成的并发压力。

知识库
许可协议:  CC BY 4.0
分享

相关文章

7月 21, 2026

think-orm 2.0.62 单独设置数据表字段缓存驱动和数据缓存驱动

think-cache 拥有强大的多通道(Multi-store)管理能力,但问题的根源在于 think-orm 底层的调用机制太死板。即使您在 think-cache 中配置了 file 和 redis 两个完全独立的通道,think-orm 默认也只会向您通过 Db::setCache() 注入

7月 1, 2026

WebSocket Server + 独立 RPC Server

从架构角度来说,它已经接近 think-swoole 能做到的极限了,但是我仍然不建议继续这样维护。 原因不是代码写法,而是 think-swoole 本身的事件模型。 第一处问题:addListener() 仍然共享 Worker 你的代码: $rpcServer = $server->addLi

6月 25, 2026

Linux | Reqable · API抓包调试 + API测试一站式工具

Reqable是什么? Reqable = Fiddler + Charles + Postman 极简的设计、极高的性能、丰富的功能、桌面手机多端平台。 多协议流量分析 基于经典的MITM中间人代理方案捕获和分析您的应用流量,自适应HTTP/HTTPS/SOCKS4/SOCKS5等多种代理协议,并

下一篇

开发一个博客文章发布 App

上一篇

让 TP6 的 Session 组件重新接管控制权

最近更新

  • 完美地解决 TP3 老系统数据的平滑读取
  • thinkphp3 redis序列化和反序列化
  • Table 空间极易发生哈希冲突并溢出
  • 将监控程序直接跑在云端
  • AI 驱动型 Facebook 群组关键词监控 Chrome 浏览器插件

热门标签

API CodeGeex Gitkraken Management Manticore Premiere Sublime Swoole ThinkPHP ThinkPHP5

目录

©2026 mdo. 保留部分权利。