微信公众号开发,刷新access_token的问题

时间:2022-11-07 17:21:41
先上一段官方的文字
微信公众号开发,刷新access_token的问题

如果有定时任务,如crontab,那一切好说.可是我现在没服务器啊,没钱,只能弄个域名,再开个几百块的虚拟主机..
这样是没法装定时任务的...

我想将这逻辑在脚本里实现..即:如果把access_token缓存到文件中,如果发现还有5分钟就过期了,则去刷一次,,反正微信能保证,在此期间新旧的都能用....

现在问题是,如果同时,10来20个人发现快要过期了,那每个人都去刷,一来是很快用完access_token的次数,微信规定每天刷新access_token只能2000次,,二来无法保证10来20个人同时刷时,最后面刷新的,最后面来,,,,如果最后面保存的并不是最后面刷新的,那已新不是最新的了,可能就access_token无效了...


我的代码如下:


<?php
include_once __DIR__ . '/../lib/access_token.php';

/**
 * 文件缓存机制的凭据中控程序.支持并发,支持多appid.
 */
class FileCacheTokenControl {
protected $m_appid = null;
protected $m_appsecret = null;
protected $m_path = null;

private function get_php_file($filename) {
return file_exists($filename) ? trim(substr(file_get_contents($filename), 15)) : "";
}
private function set_php_file($filename, $content) {
if(!file_exists(dirname($filename)))
mkdir(dirname($filename), 0777, true);
file_put_contents($filename, "<?php exit();?>" . $content);
/*
 * $fp = fopen($filename, "w");
 * write($fp, "<?php exit();?>" . $content);
 * fclose($fp);
 */
}

/**
 * @param string $appid 公众号应用ID
 * @param string $appsecret 公众号应用密钥
 */
public function __construct($appid, $appsecret) {
$this->m_appid = $appid;
$this->m_appsecret = $appsecret;
$this->m_path = __DIR__ . "/cache/" . md5($this->m_appid); //支持多appid
}

/**
 * 获取access_token票据.使用缓存机制
 */
public function get_access_token() {
$result = json_decode($this->get_php_file($this->m_path . "/access_token.php"));
if(!$result || $result->expire_time < time() || $result->appid != $this->m_appid || $result->appsecret != $this->m_appsecret) {
$result = json_decode(access_token($this->m_appid, $this->m_appsecret));
if(!isset($result) || !isset($result->access_token))
return "";
$data = new \stdClass();
$data->expire_time = time() + 7000;
$data->access_token = $result->access_token;
$data->appid = $this->m_appid;
$data->appsecret = $this->m_appsecret;
$this->set_php_file($this->m_path . "/access_token.php", json_encode($data, JSON_UNESCAPED_UNICODE));
}
return $result->access_token;
}

/**
 * 获取jsapi_ticket票据.使用缓存机制
 */ 
public function get_jsapi_ticket() {
$result = json_decode($this->get_php_file($this->m_path . "/jsapi_ticket.php"));
if(!$result || $result->expire_time < time() || $result->appid != $this->m_appid || $result->appsecret != $this->m_appsecret) {
$result = json_decode(jsapi_ticket($this->get_access_token()));
if(!isset($result) || !isset($result->ticket))
return "";
$result->jsapi_ticket = $result->ticket; //为了名移风格统一,稍微改个名.^_^
$data = new \stdClass();
$data->expire_time = time() + 7000;
$data->jsapi_ticket = $result->jsapi_ticket;
$data->appid = $this->m_appid;
$data->appsecret = $this->m_appsecret;
$this->set_php_file($this->m_path . "/jsapi_ticket.php", json_encode($data, JSON_UNESCAPED_UNICODE));
}
return $result->jsapi_ticket;
}

/**
 * 获取card_ticket票据.使用缓存机制
 */ 
public function get_card_ticket() {
$result = json_decode($this->get_php_file($this->m_path . "/card_ticket.php"));
if(!$result || $result->expire_time < time() || $result->appid != $this->m_appid || $result->appsecret != $this->m_appsecret) {
$result = json_decode(card_ticket($this->get_access_token()));
if(!isset($result) || !isset($result->ticket))
return "";
$result->card_ticket = $result->ticket; //为了名移风格统一,稍微改个名.^_^
$data = new \stdClass();
$data->expire_time = time() + 7000;
$data->card_ticket = $result->card_ticket;
$data->appid = $this->m_appid;
$data->appsecret = $this->m_appsecret;
$this->set_php_file($this->m_path . "/card_ticket.php", json_encode($data, JSON_UNESCAPED_UNICODE));
}
return $result->card_ticket;
}
}
?>



/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

下面这些是使用curl访问http的代码,,可看可不看...


<?php
include_once __DIR__ . '/utils.php';

/**
 * 获取通用access_token凭据
 * @param string $appid 公众号应用ID
 * @param string $appsecret 公众号应用密钥
 * @return json字符串.成功时如{"access_token": "ACCESS_TOKEN", "expires_in":7200}; 失败时如{"errcode": 40013, "errmsg": "invalid appid"}
 */
function access_token($appid, $appsecret) {
$url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=$appid&secret=$appsecret";
$result = curl_http_get($url, null, 30);
return $result;
}

/**
 * 获取jsapi_ticket凭据
 * @param string $access_token 通用access_token凭据
 * @return json字符串.成功时如: {"errcode": 0, "errmsg": "ok", "ticket": "TICKET", "expires_in":7200}; 失败时如: {"errcode": 40013, "errmsg": "invalid appid"}
 */
function jsapi_ticket($access_token) {
//如果是企业号用以下URL获取jsapi_ticket
//$url = "https://qyapi.weixin.qq.com/cgi-bin/get_jsapi_ticket?access_token=$access_token";
$url = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?type=jsapi&access_token=$access_token";
$result = curl_http_get($url, null, 30);
return $result;
}

/**
 * 获取card_ticket凭据
 * @param string $access_token 通用access_token凭据
 * @return json字符串.成功时如: {"errcode": 0, "errmsg": "ok", "ticket": "TICKET", "expires_in":7200}; 失败时如: {"errcode": 40013, "errmsg": "invalid appid"}
 */
function card_ticket($access_token) {
//如果是企业号用以下URL获取jsapi_ticket
//$url = "https://qyapi.weixin.qq.com/cgi-bin/get_jsapi_ticket?access_token=$access_token";
$url = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?type=wx_card&access_token=$access_token";
$result = curl_http_get($url, null, 30);
return $result;
}
?>



/**
 * @tutorial 支持http,https
 * @param string $url 要访问的站点
 * @param array,object $data get只能是key/value的参数集合
 * @param int $timeout 超时时间,单位秒
 * @return 站点数据或失败信息
 */
function curl_http_get($url, $data = null, $timeout = 30) {
while(strlen($url) > 0 && (strrpos($url, "&") == strlen($url) - 1 || strrpos($url, "?") == strlen($url) - 1))
$url = substr($url, 0, strlen($url) - 1);
if(isset($data) && (is_array($data) || is_object($data)))
$url .= (strpos($url, "?") === false ? "?" : "&") . http_build_query($data);
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, $url);
curl_setopt($curl, CURLOPT_TIMEOUT, $timeout);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); //将curl会话获取的信息以字符串返回,而不是直接输出
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false); //禁止curl验证对等证书(peer's certificate)
curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 0); //不检查服务器SSL证书中是否存在一个公用名(common name)
//curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, true); //允许curl验证对等证书(peer's certificate)
//curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 2); //严格检查服务器SSL证书中是否存在一个公用名(common name)
curl_setopt($curl, CURLOPT_POST, false); //发送 GET请求.类型为:application/x-www-form-urlencoded
//curl_setopt($curl, CURLOPT_POSTFIELDS, $data); //发送数据.可以是字符串或键值对数组.如果是数组,Content-Type头会被设置成multipart/form-data.使用@前缀语法时,必须是数组.
$result = curl_exec($curl);
if($errno = curl_errno($curl))
$result = "$errno:" . curl_error($curl);
curl_close($curl);
return $result;
}

/**
 * @tutorial 支持http,https
 * @param string $url 要访问的站点
 * @param  mixed $data post可以是任意类型的数据,例如string,array,object,json等
 * @param int $timeout 超时时间,单位秒
 * @return 站点数据或失败信息
 */
function curl_http_post($url, $data, $timeout = 30) {
while(strlen($url) > 0 && (strrpos($url, "&") == strlen($url) - 1 || strrpos($url, "?") == strlen($url) - 1))
$url = substr($url, 0, strlen($url) - 1);
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, $url);
curl_setopt($curl, CURLOPT_TIMEOUT, $timeout);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); //将curl会话获取的信息以字符串返回,而不是直接输出
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false); //禁止curl验证对等证书(peer's certificate)
curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 0); //不检查服务器SSL证书中是否存在一个公用名(common name)
//curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, true); //允许curl验证对等证书(peer's certificate)
//curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 2); //严格检查服务器SSL证书中是否存在一个公用名(common name)
curl_setopt($curl, CURLOPT_POST, true); //发送 POST请求.类型为:application/x-www-form-urlencoded
curl_setopt($curl, CURLOPT_POSTFIELDS, $data); //发送数据.可以是字符串或键值对数组.如果是数组,Content-Type头会被设置成multipart/form-data.使用@前缀语法时,必须是数组.
$result = curl_exec($curl);
if($errno = curl_errno($curl))
$result = "$errno:" . curl_error($curl);
curl_close($curl);
return $result;
}

13 个解决方案

#1


如果有锁的机制就好了,,,,access_token还没快过期,大家都用这个access_token就好了..
如果快过期了,,最先的人去刷,,其他人等着他的结果,,这样保证一个人刷,,,,
就是对文件锁,数据存锁机制不太熟悉,,,之前爱用pthread,发现它在php下要安装的,,可是我木有服务器,只有虚拟主机哇...

#2


多关注一些 阿里云 ECS 的活动
几百块钱买个入门级别的 ECS 绰绰有余了

#3


引用 2 楼 Mechnaic 的回复:
多关注一些 阿里云 ECS 的活动
几百块钱买个入门级别的 ECS 绰绰有余了

对的,但并发访问同一个数据,数据加锁与安全,这个基本功底还是要有滴..

就我这个要求:
1.如果access_token没过期,随便用..
2.如果快过期了,去刷一下..
3.如果发现别人已经在刷了,等别人的结果..

还是很有用的,应该挺多场合都能用得上...

#4


你把 set_php_file 放在 function access_token 这几个方法里 加上过期时间
然后再写一个 get_php_file 的方法
每次请求来直接 get_php_file 
如果不存在的话 再重新生成

#5


set_php_file  和  get_php_file 最好写在公共函数库里面

#6


access_token 存数据库里面啊,有人 请求   都用这个 access_token     多少人 请求都用这个  access_token  


然后 5分钟刷一次 就行了 

#7


引用 6 楼 qq_34494805 的回复:
access_token 存数据库里面啊,有人 请求   都用这个 access_token     多少人 请求都用这个  access_token  


然后 5分钟刷一次 就行了 

有 2 小时过期时间的
过期了再存一遍数据库吗

#8


是啊   有人请求 就再自动存一次   反正我的想法都是这样的,不知道 各位大神啥样的。

#9


引用 6 楼 qq_34494805 的回复:
access_token 存数据库里面啊,有人 请求   都用这个 access_token     多少人 请求都用这个  access_token  
然后 5分钟刷一次 就行了 


存数据库,可以,有锁.
还有能文件锁,或者内存锁就搞定这件事么?

因为我是没有定时任何啊,所以只能是在脚本里做以下逻辑:

1.如果access_token没过期,随便用..
2.如果快过期了,去刷一下..
3.如果发现别人已经在刷了,等别人的结果..

--------------------------------------------------------------
so,这些脚本,可能是并发的啊..
比如说10个人同时发现快过期了,要保证1个人去刷,别的人不用再刷,等结果拿来用就好了.

#10


数据库有锁机制啊, 可以做到.
你要把access_token, 存储在自己的数据库里, 写个程序过程来获取(包括更新)这个access_token,
只要锁用得得当, 脚本再并发也会排队.
没有定时器,也没关系

#11


引用 6 楼 qq_34494805 的回复:
access_token 存数据库里面啊,有人 请求   都用这个 access_token     多少人 请求都用这个  access_token  


然后 5分钟刷一次 就行了 


回到了原点啊..
我没有定时任务,,因为我没有服务器,只有虚拟主机..
做不到说XX时间去刷一次..

我想的办法是:用锁,不管是内存锁,文件锁,数据库锁..
在xx.php里...
1.如果没过期随便用;
2.如果过期了,去刷一下.
3.如果发现别人已经在刷了,等别人的结果...

在过期的这一瞬间,,如果多个访问这个xx.php,,会出现什么情况,,要保证access_token能用....

#12


引用 10 楼 trainee 的回复:
数据库有锁机制啊, 可以做到.
你要把access_token, 存储在自己的数据库里, 写个程序过程来获取(包括更新)这个access_token,
只要锁用得得当, 脚本再并发也会排队.
没有定时器,也没关系

就是不太会用锁...你能给个意见能?比如,看哪个例子,哪个大牛写的文章.
我要的功能是:

1.如果没过期随便用;
2.如果过期了,去刷一下.
3.如果发现别人已经在刷了,等别人的结果...

#13



    public function __construct() {
        $this -> init(); 
    }

public function init() {
        if(!get_php_file('access_token')){
$this -> reloadCache();//重新加载缓存

    }

public function reloadCache() {
        $access_token = 'access_token';
set_php_file('access_token', $access_token);
    }

public function set_php_file() {
        
    }

public function get_php_file() {
        
    }

#1


如果有锁的机制就好了,,,,access_token还没快过期,大家都用这个access_token就好了..
如果快过期了,,最先的人去刷,,其他人等着他的结果,,这样保证一个人刷,,,,
就是对文件锁,数据存锁机制不太熟悉,,,之前爱用pthread,发现它在php下要安装的,,可是我木有服务器,只有虚拟主机哇...

#2


多关注一些 阿里云 ECS 的活动
几百块钱买个入门级别的 ECS 绰绰有余了

#3


引用 2 楼 Mechnaic 的回复:
多关注一些 阿里云 ECS 的活动
几百块钱买个入门级别的 ECS 绰绰有余了

对的,但并发访问同一个数据,数据加锁与安全,这个基本功底还是要有滴..

就我这个要求:
1.如果access_token没过期,随便用..
2.如果快过期了,去刷一下..
3.如果发现别人已经在刷了,等别人的结果..

还是很有用的,应该挺多场合都能用得上...

#4


你把 set_php_file 放在 function access_token 这几个方法里 加上过期时间
然后再写一个 get_php_file 的方法
每次请求来直接 get_php_file 
如果不存在的话 再重新生成

#5


set_php_file  和  get_php_file 最好写在公共函数库里面

#6


access_token 存数据库里面啊,有人 请求   都用这个 access_token     多少人 请求都用这个  access_token  


然后 5分钟刷一次 就行了 

#7


引用 6 楼 qq_34494805 的回复:
access_token 存数据库里面啊,有人 请求   都用这个 access_token     多少人 请求都用这个  access_token  


然后 5分钟刷一次 就行了 

有 2 小时过期时间的
过期了再存一遍数据库吗

#8


是啊   有人请求 就再自动存一次   反正我的想法都是这样的,不知道 各位大神啥样的。

#9


引用 6 楼 qq_34494805 的回复:
access_token 存数据库里面啊,有人 请求   都用这个 access_token     多少人 请求都用这个  access_token  
然后 5分钟刷一次 就行了 


存数据库,可以,有锁.
还有能文件锁,或者内存锁就搞定这件事么?

因为我是没有定时任何啊,所以只能是在脚本里做以下逻辑:

1.如果access_token没过期,随便用..
2.如果快过期了,去刷一下..
3.如果发现别人已经在刷了,等别人的结果..

--------------------------------------------------------------
so,这些脚本,可能是并发的啊..
比如说10个人同时发现快过期了,要保证1个人去刷,别的人不用再刷,等结果拿来用就好了.

#10


数据库有锁机制啊, 可以做到.
你要把access_token, 存储在自己的数据库里, 写个程序过程来获取(包括更新)这个access_token,
只要锁用得得当, 脚本再并发也会排队.
没有定时器,也没关系

#11


引用 6 楼 qq_34494805 的回复:
access_token 存数据库里面啊,有人 请求   都用这个 access_token     多少人 请求都用这个  access_token  


然后 5分钟刷一次 就行了 


回到了原点啊..
我没有定时任务,,因为我没有服务器,只有虚拟主机..
做不到说XX时间去刷一次..

我想的办法是:用锁,不管是内存锁,文件锁,数据库锁..
在xx.php里...
1.如果没过期随便用;
2.如果过期了,去刷一下.
3.如果发现别人已经在刷了,等别人的结果...

在过期的这一瞬间,,如果多个访问这个xx.php,,会出现什么情况,,要保证access_token能用....

#12


引用 10 楼 trainee 的回复:
数据库有锁机制啊, 可以做到.
你要把access_token, 存储在自己的数据库里, 写个程序过程来获取(包括更新)这个access_token,
只要锁用得得当, 脚本再并发也会排队.
没有定时器,也没关系

就是不太会用锁...你能给个意见能?比如,看哪个例子,哪个大牛写的文章.
我要的功能是:

1.如果没过期随便用;
2.如果过期了,去刷一下.
3.如果发现别人已经在刷了,等别人的结果...

#13



    public function __construct() {
        $this -> init(); 
    }

public function init() {
        if(!get_php_file('access_token')){
$this -> reloadCache();//重新加载缓存

    }

public function reloadCache() {
        $access_token = 'access_token';
set_php_file('access_token', $access_token);
    }

public function set_php_file() {
        
    }

public function get_php_file() {
        
    }