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
分享

相关文章

10月 7, 2026

比尔盖茨的智慧:想多赚钱,就每天循环做这3件事

我们总有一种非常固执的错觉,认为那些站在财富金字塔顶端的人,必然拥有某种超越常人的特异功能。我们在脑海中给比尔盖茨这样的大佬描绘了一幅极其悲壮的奋斗画像。大家总觉得,他之所以能富可敌国,肯定是因为他每天只睡三个小时,同时对着八个电脑屏幕疯狂敲击键盘,每一秒钟都在做出价值几亿美金的生死抉择。 为了模仿

9月 28, 2026

程序员越想创业,越不要急着动手

一个能源方面的前辈找到我,希望通过我把一些人工的工作 AI 自动化。 能源方面我不懂,找 Gemini 聊完发现这个是可以复制的,非常兴奋。我跟老婆说,这个项目做好以后可以做成平台,推广到其他公司,你就等着做总裁夫人吧! 她听完以后跟我说,这个项目还是太定制化,和我之前做的一个项目很像。 那个项目一

9月 25, 2026

忍了一年多,我终于对i18n下手了过去一年,我主要参与国际机票业务的开发工作,因此每天都要和多语言(i18n)打交道

前言 大家好,我是奈德丽。 过去一年,我主要参与国际机票业务的开发工作,因此每天都要和多语言(i18n)打交道。熟悉我的朋友都知道,我这个人比较“惜力”(并不是,实际上只是忍不下去了),对于重复笨拙的工作非常抵触,于是,我开始思考如何优化团队的多语言管理模式。 痛点背景 先说说我们在机票项目中遇到的

下一篇

开发一个博客文章发布 App

上一篇

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

最近更新

  • 比尔盖茨的智慧:想多赚钱,就每天循环做这3件事
  • iOS 侧载(Sideloading)工具
  • 程序员越想创业,越不要急着动手
  • 注册 Chat Participant
  • 中秋给在外游子的一封信

热门标签

API CodeGeex Coding Cursor DeepSeek Docker Gitkraken Harness Laravel Management

目录

©2026 mdo. 保留部分权利。