TypechoJoeTheme

醉秋风

登录
用户名
密码
/
注册
用户名
邮箱

醉秋风

要相信,一切都是最好的安排!!

获取微信AccessToken中控脚本

2020-12-28
/
1 评论
/
317 阅读
/
正在检测是否收录...
12/28

access_token说明

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

本脚本说明

该脚本可以直接在web运行,或者以接口的形式调用均可。

解决的问题

access_token在集群环境共享,避免重复调用。
公众号、小程序均兼容。
目前access_token是存在MySQL数据库的,当然也可以存在其他数据库,实现方式很多,根据自己的情况而定。

结语

数据库文件就放在文章末尾了。。

<?php
/**
 * 获取微信AccessToken中控脚本
 * @author: slomoo <slomoo@aliyun.com> 2020-12-25
 */
// 常量设置
defined('APPID') or define('APPID', 'your appid');
defined('APPKEY') or define('APPKEY', 'your appkey');
defined('TYPES') or define('TYPES', 1);//1=公众号  2=小程序
/**
 * 实用工具集合
 */
final class Utils
{
    /**
     * GET请求
     */
    static public function httpGet($url, $data = null)
    {
        return self::curlHelper($url, $data, 'GET');
    }

    /**
     * POST请求
     */
    static public function httpPost($url, $data = null)
    {
        return self::curlHelper($url, $data, 'POST');
    }

    /**
     * Curl请求
     * 
     * @param string $url  请求URL
     * @param null|array|string $data 请求参数
     * @param string $method 请求方法,支持: GET、POST
     * @param null|array $headers 请求Header
     * @param null|array $options Curl请求选项
     * @return null|string
     * @throw Exception
     */
    static public function curlHelper($url, $data = null, $method = 'GET', $headers = null, $options = [])
    {
        // 默认选项
        $options = array_replace([
            "CURLOPT_HEADER" => 0,
            "CURLOPT_RETURNTRANSFER" => 1,
            "CURLOPT_USERAGENT" => 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:50.0) Gecko/20100101 Firefox/50.0',
            "CURLOPT_TIMEOUT" => 10,
            "CURLOPT_SSL_VERIFYPEER" => 0,
            "CURLOPT_SSL_VERIFYHOST" => 0,
        ], $options);

        // 构建请求Headers
        if (is_array($headers) && 0 < count($headers)) {
            $request_headers = [];
            foreach ($headers as $header => $value) {
                $request_headers[] = "{$header}: {$value}";
                unset($header);
                unset($value);
            }
            $option["CURLOPT_HTTPHEADER"] = $request_headers;
            unset($request_headers);
        }

        // 设置选项
        if ("GET" == strtoupper($method)) {
            // GET请求
            $query = is_array($data) && 0 < count($data) ?  http_build_query($data) : "";
            if (!empty($query)) {
                $url .= (strpos($url, "?") !== false ? "&" : "?") . $query;
            }
            unset($query);

            $options["CURLOPT_URL"] = $url;
            $options["CURLOPT_POST"] = 0;
        } else {
            // POST请求
            $options["CURLOPT_URL"] = $url;
            $options["CURLOPT_POST"] = 1;
            if (!is_null($data)) {
                $options["CURLOPT_POSTFIELDS"] = $data;
            }
        }

        // Curl请求
        $ch = curl_init();
        foreach ($options as $option => $value) {
            curl_setopt($ch, constant($option), $value);
        }
        $result = curl_exec($ch);
        $code = curl_errno($ch);
        $error = curl_error($ch);
        curl_close($ch);

        // 判断结果
        if ($code != 0) {
            throw new Exception($error, $code);
        }
        return $result;
    }

    /**
     * 解析JSON字符串
     * 
     * @param string $json_string JSON字符串
     * @return array
     */
    static public function parseJson($json_string)
    {
        $data = json_decode($json_string, true);
        return is_array($data) ? $data : [];
    }
}

/**
 * 连接MySQL
 * 私有的构造方法-为了防止在类外使用new关键字实例化对象
 * 私有的成员属性-为了防止在类外引入这个存放对象的属性
 * 私有的克隆方法-为了防止在类外通过clone成生另一个对象
 * 公有的静态方法-为了让用户进行实例化对象的操作
 */
class ConnectMysqli {
    //私有的属性
    private static $dbcon=false;
    private $host;
    private $port;
    private $user;
    private $pass;
    private $db;
    private $charset;
    private $link;
    //私有的构造方法
    private function __construct($config=array()) {
        //数据库配置
        $this->host = isset($config['host']) ? $config['host'] : '127.0.0.1';
        $this->port = isset($config['port']) ? $config['port'] : '3306';
        $this->user = isset($config['user']) ? $config['user'] : 'root';
        $this->pass = isset($config['pass']) ? $config['pass'] : '';
        $this->db   = isset($config['db']) ? $config['db'] : 'test';
        $this->charset=isset($arr['charset']) ? $arr['charset'] : 'utf8';
        //连接数据库
        $this->db_connect();
        //选择数据库
        $this->db_usedb();
        //设置字符集
        $this->db_charset();
    }
    //连接数据库
    private function db_connect() {
        $this->link=mysqli_connect($this->host.':'.$this->port,$this->user,$this->pass);
        if(!$this->link) {
            echo "数据库连接失败<br>";
            echo "错误编码".mysqli_errno($this->link)."<br>";
            echo "错误信息".mysqli_error($this->link)."<br>";
            exit;
        }
    }
    //设置字符集
    private function db_charset() {
        mysqli_query($this->link,"set names {$this->charset}");
    }
    //选择数据库
    private function db_usedb() {
        mysqli_query($this->link,"use {$this->db}");
    }
    //私有的克隆
    private function __clone() {
        die('clone is not allowed');
    }
    //公用的静态方法
    public static function getIntance() {
        if(self::$dbcon==false) {
            self::$dbcon=new self;
        }
        return self::$dbcon;
    }
    //执行sql语句的方法
    public function query($sql) {
        $res=mysqli_query($this->link,$sql);
        if(!$res) {
            echo "sql语句执行失败<br>";
            echo "错误编码是".mysqli_errno($this->link)."<br>";
            echo "错误信息是".mysqli_error($this->link)."<br>";
        }
        return $res;
    }
    //获得最后一条记录id
    public function getInsertid() {
        return mysqli_insert_id($this->link);
    }
    /**
    * 查询某个字段
    * @param
    * @return string or int
    */
    public function getOne($sql) {
        $query=$this->query($sql);
        return mysqli_free_result($query);
    }
    //获取一行记录,return array 一维数组
    public function getRow($sql,$type="assoc") {
        $query=$this->query($sql);
        if(!in_array($type,array("assoc",'array',"row"))) {
            die("mysqli_query error");
        }
        $funcname="mysqli_fetch_".$type;
        return $funcname($query);
    }
    //获取一条记录,前置条件通过资源获取一条记录
    public function getFormSource($query,$type="assoc") {
        if(!in_array($type,array("assoc","array","row"))) {
            die("mysqli_query error");
        }
        $funcname="mysqli_fetch_".$type;
        return $funcname($query);
    }
    //获取多条数据,二维数组
    public function getAll($sql) {
        $query=$this->query($sql);
        $list=array();
        while ($r=$this->getFormSource($query)) {
            $list[]=$r;
        }
        return $list;
    }
}
/**
 * 微信公众平台/小程序接口获取AccessToken
 */
final class AccessTokenProxy
{
    /**
     * 获取AccessToken
     * @param string $appid 应用ID
     * @param string $appkey 应用密钥
     * @return array AccessToken信息,{"access_token":"获取到的凭证", "expires_in":凭证有效时间,单位:秒}
     */
    public function get($appid, $appkey)
    {
        // 请求
        $url = "https://api.weixin.qq.com/cgi-bin/token";
        $data = [
            "grant_type" => "client_credential",
            "appid" => $appid,
            "secret" => $appkey,
        ];
        $result = $this->request($url, $data, 'GET');
        // 返回结果
        return [
            "access_token" => $result["access_token"],
            "expires_in" => intval($result["expires_in"]),
        ];
    }
    
    // 统一请求方法,解析结果
    protected function request($url, $data = null, $method = 'GET', $is_post_json_string = true)
    {
        // 请求
        $headers = null;
        if ("POST" == strtoupper($method) && $is_post_json_string) {
            $headers["Content-Type"] = "application/json";
            $data = is_array($data) ? json_encode($data, JSON_UNESCAPED_UNICODE) : $data;
        }
        $json_string = Utils::curlHelper($url, $data, $method, $headers);

        // 解析结果
        $result = Utils::parseJson($json_string);
        if (isset($result["errcode"]) && 0 != $result["errcode"]) {
            throw new Exception($result["errmsg"], $result["errcode"]);
        }
        return $result;
    }
}

/**
 * AccessToken类
 */
final class AccessToken
{
    /**
     * 添加应用信息
     */
    public function addApp($properties)
    {
        //如果存在直接返回null
        if($this->getApp($properties['appid'])){
            return null;
        }
        //添加到数据库
        $db     =   ConnectMysqli::getIntance();
        $sql    =   "INSERT INTO wechat_devs_info ( `appid`, `appsecret`, `access_token`, `expires_in`, `expires_at`, `create_time`, `types`, `last_time` )
                    VALUES
                    (
                        '".$properties['appid']."',
                        '".$properties['appsecret']."',
                        '".$properties['access_token']."',
                        '".$properties['expires_in']."',
                         ".$properties['expires_at'].",
                         ".$properties['create_time'].",
                         ".$properties['types'].",
                         ".$properties['last_time']."
                    )";
        $res    =   $db->query($sql);
        return $res;
    }

    /**
     * 获取应用信息
     */
    public function getApp($appid)
    {
        $db     =   ConnectMysqli::getIntance();
        $sql    =   "select * from wechat_devs_info where appid = '".$appid."'";
        $row    =   $db->getRow($sql);
        return $row;
    }

    /**
     * 修改应用信息
     */
    public function editApp($appid, $properties)
    {
        //修改信息
        $db     =   ConnectMysqli::getIntance();
        $sql    =   "update wechat_devs_info set access_token = '".$properties['access_token']."',
                    expires_in = ".$properties['expires_in'].",
                    expires_at = ".$properties['expires_at'].",
                    last_time  = ".$properties['last_time']."
                    where appid = '".$appid."'";
        $res    =   $db->query($sql);
        return $res;
    }

    /**
     * AccessToken中控
     */
    public function getAccessToken($appid, $apk, $refresh = false)
    {
        // 获取应用信息
        $app_info = $this->getApp($appid);
        //首次请求,需要直接请求access_token,并入库
        if (empty($app_info)) {
            try {
                // 从接口中获取AccessToken
                $api = new AccessTokenProxy();
                $api_result     = $api->get($appid, $apk);
                $access_token   = $api_result["access_token"];
                $expires_at     = $api_result["expires_in"] + time();

                // 插入AccessToken到数据库
                $addArr = [
                    "appid"         => $appid,
                    "appsecret"     => $apk,
                    'types'         => TYPES,
                    "access_token"  => $access_token,
                    "expires_in"    => $api_result["expires_in"],
                    "expires_at"    => $expires_at,
                    "last_time"     => time(),
                    "create_time"   => time(),   
                ];

                $result = $this->addApp($addArr);
                echo $access_token;exit;
            } catch (Exception $e) {
                echo null;exit;
            } finally {

            }
        }
        // 判断存储的AccessToken是否已过期
        $current_time = time();
        $access_token = isset($app_info["access_token"]) ? $app_info["access_token"] : null;
        $expires_at   = isset($app_info["expires_at"]) ? $app_info["expires_at"] : null;
        if (!$refresh && !empty($access_token) && ($expires_at > ($current_time + 5 * 60))) {
            // 不强制刷新,AccessToken已存在,AccessToken还未到过期时间前5min
            echo $access_token;exit;
        }
        //更新access_token操作
        try {
            //可能抛出异常
            // 从接口中获取AccessToken
            $api = new AccessTokenProxy();
            $api_result     = $api->get($appid, $apk);
            $access_token   = $api_result["access_token"];
            $expires_at     = $api_result["expires_in"] + $current_time;

            // 更新AccessToken到数据库
            $this->editApp($appid, [
                "access_token"  => $access_token,
                "expires_in"    => $api_result["expires_in"],
                "expires_at"    => $expires_at,
                "last_time"     => $current_time
            ]);

            echo $access_token;exit;
        } catch (Exception $e) {
            //捕获异常
            echo $e->getMessage();exit;
        } finally {
            //不管有无异常都执行
        }
    }
}
//请求token案例
try {
    $helper       = new AccessToken();
    $access_token = $helper->getAccessToken(APPID,APPKEY);
    //考虑加密。。。。延后操作。。。。
    return $access_token;
} catch (Exception $e) {
    var_dump($e->getMessage());
}

数据库:

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for wechat_devs_info
-- ----------------------------
DROP TABLE IF EXISTS `wechat_devs_info`;
CREATE TABLE `wechat_devs_info`  (
  `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID',
  `appid` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '公众号/小程序  开发者APPID',
  `appsecret` varchar(150) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '开发者密码',
  `access_token` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '接口调用凭据 access_token',
  `expires_in` int(10) NOT NULL DEFAULT 0 COMMENT '过期时间',
  `expires_at` int(10) NOT NULL DEFAULT 0 COMMENT '到期时间 = 现在时间+过期时间',
  `create_time` int(11) NOT NULL DEFAULT 0 COMMENT '生成时间',
  `types` tinyint(1) NOT NULL COMMENT '开发信息类型   1=公众号  2=小程序',
  `last_time` int(11) NOT NULL DEFAULT 0 COMMENT '最后一次修改时间',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '公众号/小程序开发信息' ROW_FORMAT = Compact;

SET FOREIGN_KEY_CHECKS = 1;
朗读
赞(1)
赞赏
感谢您的支持,我会继续努力哒!

三合一收款

下面三种方式都支持哦

微信
QQ
支付宝
打开支付宝/微信/QQ扫一扫,即可进行扫码打赏哦
版权属于:

醉秋风

本文链接:

https://blog.slomoo.cn/180.html(转载时请注明本文出处及文章链接)

评论 (1)
  1. 站元素主机 作者
    Windows 10 · FireFox

    赞一个

    2021-02-26 回复

人生倒计时

今日已经过去小时
这周已经过去
本月已经过去
今年已经过去个月
广告

标签云