Gravatar头像玩独立博客的人都非常熟悉。如果在Gravatar的服务器上放置了你自己的头像,那么在任何支持Gravatar的网站留言时,只要提供你与这个头像关联的email地址,就能够显示出你的Gravatar头像来。
但是,其访问速度却非常鸡肋,即便用上cdn方案,也差强人意,很久以前有人提供过本地缓存的方案,但是实用性并不好,无法解决留言时加载慢的问题。于是就有了下面的方案。
特点:
1,甄别QQ邮箱和其他邮箱,优先显示QQ头像,毕竟QQ头像家在还是非常快的。
2,利用比较快的cdn及时显示当前评论者的头像。
3,利用popen异步实现缓存头像到本地。
4,为什么要异步?因为:如果不异步那么访客首次评论是仍要忍受龟速。
代码:
/*
* [函数11] local_avatar
*
* 功能:实现gravatar头像本地缓存
* 逻辑:本地 > QQ > Gravatar && 默认头像用本地随机(并缓存)
*/
function local_avatar($mail){
$from_avatar = '';
// default_avatar 默认头像为本地的一些预置的头像图片,随机输出一张(并且缓存后不会改变)
$default_avatar = Typecho_Widget::widget('Widget_Options')->themeUrl. '/assets/img/avatar/'.rand(1,6).'.png';
// 邮箱地址转hash
$mail_hash = md5(strtolower($mail));
// avatar_time 设置本地缓存过期时间*天数
$avatar_time = 1209600*14;
// avatar_doc 本地头像的绝对路径
$avatar_doc = __TYPECHO_ROOT_DIR__ . '/usr/uploads/avatar/'.$mail_hash.'.jpg';
// local_url 本地头像的网络地址
$local_url = Typecho_Widget::widget('Widget_Options')->siteUrl.'usr/uploads/avatar/'.$mail_hash.'.jpg';
//匹配邮箱是否为QQ邮箱
preg_match_all('/((\d)*)@qq.com/', $mail, $is_qq_mail);//正则匹配QQ邮箱
//判断本地是否有该头像可用(存在 且 未过期 且 大小正常)的缓存,有就直接调用
if (is_file($avatar_doc) && (time() - filemtime($avatar_doc)) < $avatar_time && filesize($avatar_doc)>900){
$url = $local_url;
//如果没有可用的本地缓存
}else{
// 如果评论者用的不是QQ邮箱
if (empty($is_qq_mail['1']['0'])){
// url 有两个用处,1,评论是输出 2,不知执行时传参(作为本地缓存的来源)3,loli源算是比较快的~
$url = 'https://gravatar.loli.net/avatar/' . $mail_hash . 's=80&r=X&d=';
// from_avatar 时用来传给异步执行文件的一个参数,判断头像来源
$from_avatar = 'gravatar';
// 如果用的是QQ邮箱
}else{
// 评论时直接输出QQ头像
$url = 'https://q2.qlogo.cn/headimg_dl?dst_uin='.$is_qq_mail['1']['0'].'&spec=100';
// 头像来源为 'qq'
$from_avatar = 'qq';
}
//执行异步缓存
pclose(popen("php usr/themes/QuarkGarden/ic/asyn_avatar.php '$url' '$avatar_doc' '$mail_hash' 'from_avatar' '$default_avatar'>/dev/null 2>&1 &", 'r'));
}
// 返回头像
return $url;
}
异步文件asyn_avatar内容:
<?php
// 一共传递过来5个参数,重新赋值方便调用
$url = $argv[1];
$url_to = $argv[2];
$mail_hash = $argv[3];
$from_avatar = $argv[4];
$default_avatar = $argv[5];
// 判断头像来源是否gravatar
if($from_avatar != 'gravatar'){
// 如果不是
$url = $url;
// 如果是gravatar
}else{
// 判断是否有gravatar
$avatar_gravatar = 'http://gravatar.loli.net/avatar/'.$mail_hash.'?d=404';
$avatar_headers = @get_headers($avatar_gravatar);
if (!preg_match("|200|", $avatar_headers)) {
// 如果没有gravatar那就用本地的头像(毕竟官方默认是在不怎么好看)
$url = $default_avatar;
}
}
// 用copy 完成本地缓存
copy($url, $url_to);
以上就是全部实现代码了。
调用:
<img class="avatar" src="<?php echo local_avatar($comments->mail);?>" />
拓展:
如果想要再智能一些,可以吧$default_avatar换成动态生成的和评论者名字相关的图片,可以利用网上的占位图API来轻松实现。
只需要在函数里再添加一个参数来传递评论者名称即可。
思考更新
上面条理已经比较清晰了,但是还有不足:
1,对于“胡编乱造的”qq邮箱
2,代码本身有些复杂,异步文件里参数太多了
另一个思考
如果把get_headers后的判断(图片是否可用)放在主函数里面执行,可能会导致页面卡顿(当然对于整体缓存方案来说自由“当时”一条评论的头像需要判断);如果放到异步进程中去,有没有办法解决“默认”头像和“假”QQ头像这两种情况的”首次替换“(即评论者当时就能考到随机头像)能力。
因此这是一个固有矛盾,不可解决。
这里以”有效显示为先“重新整理代码如下:
/*
* [函数11-0] is_val_img
* 判断一个链接是否为可用图片链接
*/
function is_val_img_easy($is_url_val,$url){
$default_avatar = 'http://link.zizdog.com/avatar/'.rand(1,13).'.jpg';
$url_headers = implode(get_headers($is_url_val));
$is_200 = preg_match("|200|", $url_headers);
$is_img = preg_match("|image|", $url_headers);
if ($is_200 && $is_img) {
$url = $url;
} else {
$url = $default_avatar;
}
return $url;
}
local_avatar主体函数
/*
* [函数11] local_avatar
*
* 功能:实现gravatar头像本地缓存
* 逻辑:本地 > QQ > Gravatar && 默认头像用本地随机(并缓存)
*/
function local_avatar($mail){
$from_avatar = '';
// 邮箱地址转hash
$mail_hash = md5(strtolower(trim($mail)));
// avatar_time 设置本地缓存过期时间*天数
$avatar_time = 1209600*14;
// avatar_doc 本地头像的绝对路径
$avatar_doc = __TYPECHO_ROOT_DIR__ . '/usr/uploads/avatar/'.$mail_hash.'.jpg';
// local_url 本地头像的网络地址
$local_url = Typecho_Widget::widget('Widget_Options')->siteUrl.'usr/uploads/avatar/'.$mail_hash.'.jpg';
//匹配邮箱是否为QQ邮箱
preg_match_all('/((\d)*)@qq.com/', $mail, $is_qq_mail);//正则匹配QQ邮箱,如果是,则配位结果为,$is_qq_mail[0]='xx@qq.com';$is_qq_mail[1]='xx';$is_qq_mail[0]='2'(is_qq_mail的长度);$is_qq_mail[1][0]为qq好
//判断本地是否有该头像可用(存在 且 未过期 且 大小正常)的缓存,有就直接调用
if (is_file($avatar_doc) && (time() - filemtime($avatar_doc)) < $avatar_time && filesize($avatar_doc)>900){
$url = $local_url;
//如果没有可用的本地缓存
}else{
// 如果评论者用的不是QQ邮箱
if ($is_qq_mail[1][0]){
// 评论时直接输出QQ头像
$url = 'https://q2.qlogo.cn/headimg_dl?dst_uin='.$is_qq_mail[1][0].'&spec=100';//必须加&spec=100且不可更改
$is_url_val_easy = $url;
// 如果用的是QQ邮箱
}else{
$url = Helper::options()->gravatar . $mail_hash . 's=80&r=G&d=identicon';
$is_url_val = 'http://cn.gravatar.com/avatar/'.$mail_hash.'?d=404';
}
//include 'ic/is_img.php';
$url = is_val_img_easy($is_url_val,$url);
//执行异步缓存
pclose(popen("php usr/themes/QuarkGarden/ic/asyn_avatar.php '$url' '$avatar_doc'>/dev/null 2>&1 &", 'r'));
}
// 返回头像
return $url;
}
异步文件asyn_avatar内容:
<?php
$url = $argv[1];
$avatar_doc = $argv[2];
copy($url, $avatar_doc);
最后的思考
其实比较权衡的方案应该是吧is_val_img这部分放到异步文件里面去,这样评论者的速度体验上是有优势的,毕竟,对于故意填写一个错误QQ好的评论者,也没多大必要及时显示一个有效头像。只要后续缓存成功,对于其他访问者来说体验是很完美的。
值得注意的是:
如果只判断一个链接是否200的时候curl 是要由于 get_headers的,因此可以用下面的方案替代[函数11-0]
/*
* [函数11-0] is_val_img
* 判断一个链接是否为可用图片链接
* 优点:比get_headers快(理论上是的),缺点:代码增多
*/
function is_val_img($is_url_val,$url){
// default_avatar 默认头像为本地的一些预置的头像图片,随机输出一张(并且缓存后不会改变)
//$default_avatar = Typecho_Widget::widget('Widget_Options')->themeUrl. '/assets/img/avatar/'.rand(1,13).'.jpg';
$default_avatar = 'http://link.zizdog.com/avatar/'.rand(1,13).'.jpg';
// 创建一个cURL资源
$ch = curl_init();
// 设置URL和相应的选项
curl_setopt($ch, CURLOPT_URL, $is_url_val); //设置URL
curl_setopt($ch, CURLOPT_HEADER, 1); // 将头文件的信息作为数据流输出
//curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1); //是否跟着爬取重定向的页面,这一项不需要开启,因为我们判断的是链接本身,不是跳转后内容
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); //将curl_exec()获取的值以文本流(字符串)的形式返回,而不是直接输出。
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5); //设置超时时间
curl_setopt($ch, CURLOPT_NOBODY,true); //设置只获取header不获取body
// curl_exec($ch);抓取URL并把它传递给浏览器
$content = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE); //curl的httpcode
$http_type = curl_getinfo($ch,CURLINFO_CONTENT_TYPE); //看看能不能直接或获取type
// 关闭cURL资源,并且释放系统资源
curl_close($ch);
$img_type = explode("/",$http_type); //将'Content-Type: image/jpeg'中的值用/分隔开
if ($http_code == 200 && strtolower($img_type[0]) == 'image') {
$url = $url;
} else {
$url = $default_avatar;
}
return $url;
}
又一次更新
上面有一处问题,异步文件路径。
$cmd = "php ".__THEME_DIR__."ic/asyn_avatar.php '$url' '$avatar_doc'>/dev/null 2>&1 ";
pclose(popen($cmd.'&', 'r'));
完结!
https://blog.loli.top/archives/gravatarcdn.html
可以尝试一下我这个
如果你细心,会发现代码中提到了loli·cdn~haha
不一样呀,不是一家。我说的是这个
https://cdn.s.loli.top/avatar/
这个不错,感觉是最完善的方案了!!
那我这种没头像的呢