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

szuzsq 2017-11-21 11:31:13
先上一段官方的文字


如果有定时任务,如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;
}
...全文
1392 13 点赞 打赏 收藏 举报
写回复
13 条回复
切换为时间正序
当前发帖距今超过3年,不再开放新的回复
发表回复
Mechnaic 2017-12-07

    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() {
        
    }

  • 打赏
  • 举报
回复
szuzsq 2017-12-07
引用 10 楼 trainee 的回复:
数据库有锁机制啊, 可以做到. 你要把access_token, 存储在自己的数据库里, 写个程序过程来获取(包括更新)这个access_token, 只要锁用得得当, 脚本再并发也会排队. 没有定时器,也没关系
就是不太会用锁...你能给个意见能?比如,看哪个例子,哪个大牛写的文章. 我要的功能是: 1.如果没过期随便用; 2.如果过期了,去刷一下. 3.如果发现别人已经在刷了,等别人的结果...
  • 打赏
  • 举报
回复
szuzsq 2017-12-07
引用 6 楼 qq_34494805 的回复:
access_token 存数据库里面啊,有人 请求 都用这个 access_token 多少人 请求都用这个 access_token 然后 5分钟刷一次 就行了
回到了原点啊.. 我没有定时任务,,因为我没有服务器,只有虚拟主机.. 做不到说XX时间去刷一次.. 我想的办法是:用锁,不管是内存锁,文件锁,数据库锁.. 在xx.php里... 1.如果没过期随便用; 2.如果过期了,去刷一下. 3.如果发现别人已经在刷了,等别人的结果... 在过期的这一瞬间,,如果多个访问这个xx.php,,会出现什么情况,,要保证access_token能用....
  • 打赏
  • 举报
回复
trainee 2017-11-22
数据库有锁机制啊, 可以做到. 你要把access_token, 存储在自己的数据库里, 写个程序过程来获取(包括更新)这个access_token, 只要锁用得得当, 脚本再并发也会排队. 没有定时器,也没关系
  • 打赏
  • 举报
回复
szuzsq 2017-11-21
引用 2 楼 Mechnaic 的回复:
多关注一些 阿里云 ECS 的活动 几百块钱买个入门级别的 ECS 绰绰有余了
对的,但并发访问同一个数据,数据加锁与安全,这个基本功底还是要有滴.. 就我这个要求: 1.如果access_token没过期,随便用.. 2.如果快过期了,去刷一下.. 3.如果发现别人已经在刷了,等别人的结果.. 还是很有用的,应该挺多场合都能用得上...
  • 打赏
  • 举报
回复
Mechnaic 2017-11-21
多关注一些 阿里云 ECS 的活动 几百块钱买个入门级别的 ECS 绰绰有余了
  • 打赏
  • 举报
回复
szuzsq 2017-11-21
如果有锁的机制就好了,,,,access_token还没快过期,大家都用这个access_token就好了.. 如果快过期了,,最先的人去刷,,其他人等着他的结果,,这样保证一个人刷,,,, 就是对文件锁,数据存锁机制不太熟悉,,,之前爱用pthread,发现它在php下要安装的,,可是我木有服务器,只有虚拟主机哇...
  • 打赏
  • 举报
回复
szuzsq 2017-11-21
引用 6 楼 qq_34494805 的回复:
access_token 存数据库里面啊,有人 请求 都用这个 access_token 多少人 请求都用这个 access_token 然后 5分钟刷一次 就行了
存数据库,可以,有锁. 还有能文件锁,或者内存锁就搞定这件事么? 因为我是没有定时任何啊,所以只能是在脚本里做以下逻辑: 1.如果access_token没过期,随便用.. 2.如果快过期了,去刷一下.. 3.如果发现别人已经在刷了,等别人的结果.. -------------------------------------------------------------- so,这些脚本,可能是并发的啊.. 比如说10个人同时发现快过期了,要保证1个人去刷,别的人不用再刷,等结果拿来用就好了.
  • 打赏
  • 举报
回复
qq_34494805 2017-11-21
是啊 有人请求 就再自动存一次 反正我的想法都是这样的,不知道 各位大神啥样的。
  • 打赏
  • 举报
回复
Mechnaic 2017-11-21
引用 6 楼 qq_34494805 的回复:
access_token 存数据库里面啊,有人 请求 都用这个 access_token 多少人 请求都用这个 access_token 然后 5分钟刷一次 就行了
有 2 小时过期时间的 过期了再存一遍数据库吗
  • 打赏
  • 举报
回复
qq_34494805 2017-11-21
access_token 存数据库里面啊,有人 请求 都用这个 access_token 多少人 请求都用这个 access_token 然后 5分钟刷一次 就行了
  • 打赏
  • 举报
回复
Mechnaic 2017-11-21
set_php_file 和 get_php_file 最好写在公共函数库里面
  • 打赏
  • 举报
回复
Mechnaic 2017-11-21
你把 set_php_file 放在 function access_token 这几个方法里 加上过期时间 然后再写一个 get_php_file 的方法 每次请求来直接 get_php_file 如果不存在的话 再重新生成
  • 打赏
  • 举报
回复
相关推荐
发帖
基础编程
加入

2.1w+

社区成员

从PHP安装配置,PHP入门,PHP基础到PHP应用
申请成为版主
帖子事件
创建了帖子
2017-11-21 11:31
社区公告
暂无公告