workerman编写微信公众号中控服务器统一获取和刷新access_token

爱语飞飞 2019-09-01 PM 1258℃ 0条
获取access_token https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Get_access_token.html

示例:

微信公众号中控服务器
https://iyuu.cn:2130

前言

access_token是公众号的全局唯一接口调用凭据,公众号调用各接口时都需使用access_token。开发者需要进行妥善保存。access_token的存储至少要保留512个字符空间。access_token的有效期目前为2个小时,需定时刷新,重复获取将导致上次获取的access_token失效

公众平台的API调用所需的access_token的使用及生成方式说明:
1、建议公众号开发者使用中控服务器统一获取和刷新access_token,其他业务逻辑服务器所使用的access_token均来自于该中控服务器,不应该各自去刷新,否则容易造成冲突,导致access_token覆盖而影响业务;
2、目前access_token的有效期通过返回的expire_in来传达,目前是7200秒之内的值。中控服务器需要根据这个有效时间提前去刷新新access_token。在刷新过程中,中控服务器可对外继续输出的老access_token,此时公众平台后台会保证在5分钟内,新老access_token都可用,这保证了第三方业务的平滑过渡;
3、access_token的有效时间可能会在未来有调整,所以中控服务器不仅需要内部定时主动刷新,还需要提供被动刷新access_token的接口,这样便于业务服务器在API调用获知access_token已超时的情况下,可以触发access_token的刷新流程。

公众号和小程序均可以使用AppID和AppSecret调用本接口来获取access_token。AppID和AppSecret可在“微信公众平台-开发-基本配置”页中获得(需要已经成为开发者,且帐号没有异常状态)。调用接口时,请登录“微信公众平台-开发-基本配置”提前将服务器IP地址添加到IP白名单中,点击查看设置方法,否则将无法调用成功。小程序无需配置IP白名单。

workerman源代码

start_token.php 源码

<?php
require_once __DIR__ . '/init.php';
require_once __DIR__ . '/WechatAccessToken.php';
use Workerman\Worker;

global $context;
// 日志文件
Worker::$logFile = SELF_PATH .'/Log/'.basename(__FILE__, '.php'). '.log';
$TokenService = new WechatAccessToken("http://0.0.0.0:2130", $context);

// 如果不是在根目录启动,则运行runAll方法
if (!defined('GLOBAL_START')) {
    Worker::runAll();
}

WechatAccessToken.php 源码

<?php

use Workerman\Worker;
use Workerman\Lib\Timer;
use Workerman\Connection\TcpConnection;

/**
 * 微信公众号的access_token中控服务
 * Class WechatAccessToken
 */
class WechatAccessToken extends Worker
{
    /**
     * 进程名
     * @var string
     */
    public $name = 'WechatAccessTokenService';

    /**
     * 进程数
     * @var int
     */
    public $count = 1;

    /**
     * 进程启动时间
     *
     * @var int
     */
    protected $_startTime = 0;

    /**
     * 微信接口
     */
    const API_URL_PREFIX = 'https://api.weixin.qq.com/cgi-bin';
    const AUTH_URL = '/token?';

    /**
     * 从微信获取到的access_token、expires_in
     */
    private static $access_token = '';      //获取到的凭证
    private static $expires_in = 0;         //凭证有效时间(已经转换成了时间戳)
    /**
     * 微信参数expires_in的提前量(必须在5分钟以内)
     * 官方文档:https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Get_access_token.html
     */
    const client_expires_before = 120;      //客户端凭证有效时间的提前量(2分钟)
    const server_expires_before = 60;       //中控服务器凭证有效时间的提前量(3分钟),是在client_expires_before基础上再提前

    /**
     * 其他配置
     * @var int
     */
    private static $timerSUM     = 0;    //定时器执行次数
    private static $API_OK       = 0;    //请求微信接口,成功次数
    private static $API_ERROR    = 0;   //请求微信接口,失败次数

    /**
     * Redis实例
     * @var Cache
     */
    private $redis = null;

    /**
     * 用户配置
     * @var array
     */
    private $config = [];

    /**
     * 构造函数
     * - 如果设置证书,会使用SSL协议
     * @param string $socket_name
     * @param array  $context_option
     */
    public function __construct($socket_name = '', array $context_option = array())
    {
        if (!empty($context_option)) {
            $this->transport = 'ssl';
        }

        // 运行父方法
        parent::__construct($socket_name, $context_option);
    }

    /**
     * 运行,系统方法
     */
    public function run()
    {
        // 设置 onWorkerStart 回调
        $this->onWorkerStart = array($this, 'onWorkerStart');

        // 设置 onMessage 回调
        $this->onMessage = array($this, 'onMessage');

        // 记录进程启动的时间
        $this->_startTime = time();

        // 强制单进程
        $this->count = 1;

        //运行父方法
        parent::run();
    }

    /**
     * 进程启动时回调
     * @param WechatAccessToken $worker
     * @throws Exception
     */
    public function onWorkerStart($worker)
    {
        // 读取配置
        $this->config = Config::get('application', 'iyuucn');

        // 实例化redis缓存
        $this->redis = new Cache();

        //初始化微信参数
        self::$access_token = $this->redis->get('WCH_access_token');
        self::$expires_in = $this->redis->get('WCH_expires_in');

        // 启动定时器
        Timer::add(20, function () use ($worker) {
            $worker->getAccessToken();
        });
    }

    /**
     * 当客户端通过连接发来数据时,触发的回调函数
     * @param TcpConnection $connection
     * @param $data
     */
    public function onMessage($connection, $data)
    {
        $connection->send($this->index());
    }

    /**
     * 获取access_token
     * 官方文档:https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Get_access_token.html
     * @param bool $newToken 传入true可以强制刷新token
     * @return string | null
     * @throws Exception
     */
    public function getAccessToken($newToken = false)
    {
        // 累加定时器执行次数
        self::$timerSUM++;
        if (self::valid_access_token() && !$newToken) {
            //监测缓存token,如果被误删除,重新写入
            if (empty($this->redis->get('WCH_access_token')) || empty($this->redis->get('WCH_expires_in'))) {
                $this->redis->set('WCH_access_token', self::$access_token, self::$expires_in - time());
                $this->redis->set('WCH_expires_in', self::$expires_in, self::$expires_in - time());
            }
            // 缓存有效,直接返回
            return self::$access_token;
        }

        /**
         * 失效或者强制获取access_token
         */
        $options = $this->config;
        if (!empty($options['appid']) && !empty($options['appsecret'])) {
            $params = [
                'grant_type'=> 'client_credential',
                'appid'     => $options['appid'],
                'secret'    => $options['appsecret']
            ];
            $query_str = http_build_query($params);
            $resp = HttpCurl::get(self::API_URL_PREFIX . self::AUTH_URL . $query_str);
            $response = json_decode($resp);
            if (!empty($response->access_token) && !empty($response->expires_in)) {
                /**
                 * 凭证获取,成功: {"access_token":"ACCESS_TOKEN","expires_in":7200}
                 */
                self::$API_OK++;
                //保存微信参数
                self::$access_token = $response->access_token;
                self::$expires_in = time() + $response->expires_in - self::client_expires_before;

                //微信参数放入redis
                $this->redis->set('WCH_access_token', self::$access_token, $response->expires_in);
                $this->redis->set('WCH_expires_in', self::$expires_in, $response->expires_in);

                return $response->access_token;
            }

            self::$API_ERROR++;
            wLog('获取access_token失败:');
            wLog($resp);
            return null;
        } else {
            //缺少开发者参数
            throw new Exception(__METHOD__ . "appid,appsecret not config");
        }
    }

    /**
     * 校验access_token是否过期
     * @return bool
     */
    private static function valid_access_token()
    {
        // 中控服务器需要提前获取凭证,然后放入缓存中(保障其他应用始终可以从缓存中拿到可用的access_token)
        return self::$access_token && self::$expires_in && (self::$expires_in > (time() + self::server_expires_before));
    }

    /**
     * 首页
     * @return string
     */
    public function index()
    {
        $s = self::$expires_in - time();
        $str = '';
        $str .= '系统启动:'. date("Y-m-d H:i:s", $this->_startTime) ."<br />";
        $str .= '定时器执行:'. self::$timerSUM ."<br />";
        $str .= '凭证Token有效期:'. $s ."秒<br />";
        $str .= '刷新Token成功:'. self::$API_OK ."次<br />";
        $str .= '刷新Token失败:'. self::$API_ERROR ."次<br />";
        return $str;
    }
}

Linux开机以守护进程方式运行

打开/etc/rc.local,在exit 0前添加类似以下代码

    /绝对路径/php /磁盘/路径/start.php start -d
标签: PHP, workerman

非特殊说明,本博所有文章均为博主原创。

评论啦~