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

获取access_token https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Get_access_token.html

示例:

微信公众号中控服务器
https://www.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
use Workerman\Worker;
use Workerman\Lib\Timer;
use Workerman\MySQL\Connection;
define('GLOBAL_HTTPS', 1);
define("APP_PATH",  dirname(__FILE__));
require_once __DIR__ . '/../../vendor/autoload.php';
require_once APP_PATH . '/Library/Function.php';
require_once APP_PATH . '/Library/Config.php';
require_once APP_PATH . '/Library/Cache.php';
require_once APP_PATH . '/Library/Wechat.php';
$context = array(
    'ssl' => array(
        // 请使用绝对路径
        'local_cert'    => __DIR__ . '/../../Cert/www.iyuu.cn.crt', // 也可以是crt文件
        'local_pk'        => __DIR__ . '/../../Cert/www.iyuu.cn.key',
        'verify_peer'                => false,
        //'allow_self_signed' => true, //如果是自签名证书需要开启此选项
    )
);
$worker = new Worker("http://0.0.0.0:2130",$context);
$worker->transport = 'ssl';
$worker->name = 'WechatTokenService';
//单进程
$worker->count = 1;
$worker->onWorkerStart = function($worker)
{
    sc('微信token服务进程 已启动!');
    // 设置时区
    date_default_timezone_set('PRC');
    global $config, $redis, $db;
    $dbName = 'mysql.typecho';
    $config = \Config::get('application');
    $redis = new \Cache();
    //$db = new Connection($config[$dbName]['host'], $config[$dbName]['port'], $config[$dbName]['user'], $config[$dbName]['password'], $config[$dbName]['database']);
    // 初始化任务
    \Wechat::start();
    // 启动定时器
    Timer::add(5, function()
    {
        \Wechat::getAccessToken();
    });
};
// 接收到浏览器发送的数据时回复hello world给浏览器
$worker->onMessage = function($connection, $data)
{
    // 向浏览器发送hello world
    $connection->send('hello world');
};
// 进程关闭时
$worker->onWorkerStop = function($worker)
{
    //通知运维人员
    sc('微信token服务进程异常退出,请检查');
};
// 如果不是在根目录启动,则运行runAll方法
if(!defined('GLOBAL_START'))
{
    Worker::runAll();
}

/Library/Wechat.php 源码

<?php
require_once APP_PATH . '/Library/Config.php';
require_once APP_PATH . '/Library/HttpCurl.php';
class Wechat {
    const API_URL_PREFIX = 'https://api.weixin.qq.com/cgi-bin';
    const AUTH_URL = '/token?';
    const TEMPLATE_SEND_URL = '/message/template/send?access_token=';
    private static $access_token;
    private static $expires_in;
    //守护进程初次启动,从Redis读取
    public static function start(){
        global $redis;
        self::$access_token = $redis->get('WCH_access_token');
        self::$expires_in = $redis->get('WCH_expires_in');
        wlog('系统启动');
    }
    /**
     * 获取access_token
     * @param bool $newToken 传入true可以强制刷新token
     * @return string | null
     */
    public static function getAccessToken($newToken = false){
        global $redis;
        if (self::valid_access_token() && !$newToken) {
            //监测缓存token,如果被误删除,重新写入
            if(empty($redis->get('WCH_access_token'))){
                $redis->set('WCH_access_token', self::$access_token, self::$expires_in-time());
                wlog('access_token被误删除,已恢复!!');
            }
            if(empty($redis->get('WCH_expires_in'))){
                $redis->set('WCH_expires_in', self::$expires_in, self::$expires_in-time());
                wlog('expires_in被误删除,已恢复!!');
            }
            return self::$access_token;
        }else{
            $options = Config::get('application', 'iyuucn');
            if(isset($options['appid']) && isset($options['appsecret'])){
                $params = array('grant_type' => 'client_credential','appid' => $options['appid'], 'secret' => $options['appsecret']);
                $str = http_build_query($params);
                $response = HttpCurl::get(self::API_URL_PREFIX.self::AUTH_URL.$str, 'json');
                //记录日志
                wlog($response);
                if(isset($response->errcode)){

                    //获取access_token异常
                    return NULL;
                }else{
                    //存数据库
                    #code...
                    //存类静态成员
                    self::$access_token = $response->access_token;
                    self::$expires_in = time()+$response->expires_in-300;
                    //存redis缓存
                    $redis->set('WCH_access_token',self::$access_token,$response->expires_in-300);
                    $redis->set('WCH_expires_in',self::$expires_in,$response->expires_in-300);
                    return $response->access_token;
                }
            }else{
                //缺少开发者参数
                throw new Exception("appid,appsecret not config");
            }
        }
    }
    /**
     * 校验access_token是否过期
     * @return bool
     */
    private static function valid_access_token($access_token = '',$expires_in = '')
    {
        if(empty($access_token) && empty($expires_in)){
            return isset(self::$access_token) && isset(self::$expires_in) && self::$expires_in > (time()+30);
        }
        return isset($access_token) && isset($expires_in) && $expires_in > (time()+30);
    }
    /**
     * 发送模板消息
     * @param array $data 消息结构
     * @return boolean|array
     */
    public static function sendTemplateMessage($data){
        if (!self::valid_access_token()) {
            if(!self::getAccessToken()){
                return false;
            }
        }
        $response = HttpCurl::post(self::API_URL_PREFIX.self::TEMPLATE_SEND_URL.self::$access_token, json_encode($data, JSON_UNESCAPED_UNICODE));
        if($response){
            $json = json_decode($response,true);
            //记录日志
            wlog($json);
            //echo date("Y-m-d H:i:s")."\n\n";
            //p($json);
            if (!$json || !empty($json['errcode'])) {
                return false;
            }
            return $json;
        }
        return false;
    }
}

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

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

    /绝对路径/php /磁盘/路径/start.php start -d
最后修改:2019 年 08 月 28 日 02 : 10 PM
如果觉得我的文章对你有用,请随意赞赏

发表评论