Gravatar头像玩独立博客的人都非常熟悉。如果在Gravatar的服务器上放置了你自己的头像,那么在任何支持Gravatar的网站留言时,只要提供你与这个头像关联的email地址,就能够显示出你的Gravatar头像来。

但是,其访问速度却非常鸡肋,即便用上cdn方案,也差强人意,很久以前有人提供过本地缓存的方案,但是实用性并不好,无法解决留言时加载慢的问题。于是就有了下面的方案。

特点:

1,甄别QQ邮箱和其他邮箱,优先显示QQ头像,毕竟QQ头像家在还是非常快的。
2,利用比较快的cdn及时显示当前评论者的头像。
3,利用popen异步实现缓存头像到本地。
4,为什么要异步?因为:如果不异步那么访客首次评论是仍要忍受龟速。

代码:

<?php
/*
 * [函数16-0] isValImg
 * 判断一个链接是否为可用图片链接
 */

function isValImg($url){
    $url = str_replace('s=160&r=G&d=identicon','?d=404',$url);//尝试将后缀替换为?d=404用于检测是否有非默认头像
    // 创建一个cURL资源
    $ch = curl_init();
    // 设置URL和相应的选项
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_HEADER, 1);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
    curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5);
    curl_setopt($ch, CURLOPT_NOBODY,true);
    $content = curl_exec($ch);//抓取URL并把它传递给浏览器
    $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);  //curl的httpcode
    $http_type = curl_getinfo($ch,CURLINFO_CONTENT_TYPE); //获取type
    curl_close($ch);// 关闭cURL资源,并且释放系统资源

    $img_type = explode("/",$http_type); //将'Content-Type: image/jpeg'中的值用/分隔开
    if ($http_code == 200 && strtolower($img_type[0]) == 'image') {
        $url = str_replace('?d=404','s=160&r=G&d=identicon',$url);//是图像则替换回后缀后输出
    } else {
        $url = Typecho_Widget::widget('Widget_Options')->themeUrl. '/assets/img/avatar/'.rand(1,38).'.jpg';//不是图像,输出默认
    }
    return $url;
}

/*
 * [函数16-1] isValImgEasy
 * 判断一个链接是否为可用图片链接
 * 优点:简洁
 */

function isValImgEasy($url){
    $url = str_replace('s=160&r=G&d=identicon','?d=404',$url);
    $url_headers = implode(get_headers($url));
    $is_200 = preg_match("|200|", $url_headers);
    $is_img = preg_match("|image|", $url_headers);
    if ($is_200 && $is_img) {
        $url = str_replace('?d=404','s=160&r=G&d=identicon',$url);
    } else {
        $url = Typecho_Widget::widget('Widget_Options')->themeUrl. '/assets/img/avatar/'.rand(1,38).'.jpg';
    }
    return $url;
}

/*
 * [函数16] localAvatar 
 * 
 * 功能:实现gravatar头像本地缓存
 * 逻辑:本地 > QQ > Gravatar && 默认头像用本地随机(并缓存)
 */
function localAvatar($mail){
    $mail_hash = md5(strtolower(trim($mail)));
    $time = 1209600*14; //缓存过期时间(*天)
    $avatar_doc = __TYPECHO_ROOT_DIR__ . '/usr/uploads/avatar/'.$mail_hash.'.jpg'; //头像绝对路径
    $local_url = Typecho_Widget::widget('Widget_Options')->siteUrl.'usr/uploads/avatar/'.$mail_hash.'.jpg'; //头像网络地址

    preg_match_all('/((\d)*)@qq.com/', $mail, $is_qq_mail);//正则匹配QQ邮箱

    if (is_file($avatar_doc) && ((time() - filemtime($avatar_doc)) < $time) && filesize($avatar_doc)>900){
        $url = $local_url;
    }else{
        if ($is_qq_mail[1][0]){
            $url = 'https://q1.qlogo.cn/g?b=qq&nk='.$is_qq_mail[1][0].'&s=160';
        }else{
            $url = Helper::options()->gravatar . $mail_hash . 's=160&r=G&d=identicon';
        }
        
    //已经得到了头像的url,不过有些人的qq邮箱是瞎编的,有些人是没有gravatar头像的(默认头像),要把$url传参给上面的is_val_img进行判断处理。
    $url = isValImg($url);

    //执行异步缓存:因为是异步执行的所以下面可以直接返回$url,缓存的建立不会被察觉(但要避免同时大量缓存文件的建立,服务器会卡)
    $cmd = "php ".__THEME_DIR__."ic/AsynAvatar.php  '$url' '$avatar_doc'>/dev/null 2>&1 ";
    pclose(popen($cmd.'&', 'r'));
    }
    return  $url;
}

中间反反复复修改了好多个版本,到这里就已经非常成熟好用了,删掉中间过程,贴上这些代码。

完结!

更新

说明:使用中发现了问题,首先,首次评论时还是会有些卡,体验不好;其次,如果判断头像时网络超时则会给出错误的缓存结果。
后面又改动了一点,谈不上好坏,就是取舍不同罢了。

主体函数

<?php
/*
 * local_avatar 
 * 
 * 功能:实现gravatar头像本地缓存
 * 逻辑:本地 > QQ > Gravatar && 默认头像用本地随机(并缓存)
 */
function local_avatar($mail){
    $mail_hash = md5(strtolower(trim($mail)));
    $local_img = __TYPECHO_ROOT_DIR__ . '/usr/uploads/avatar/'.$mail_hash.'.jpg';
    $local_img_url = Typecho_Widget::widget('Widget_Options')->siteUrl.'usr/uploads/avatar/'.$mail_hash.'.jpg';
    $url = Helper::options()->gravatar . $mail_hash . 's=80&r=G&d=identicon';
    $default_dir = __TYPECHO_ROOT_DIR__ . '/usr/themes/Garden/assets/img/avatar/'.rand(1,38).'.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_qq_mail[1][0]){
        // 如果用的是QQ邮箱评论时直接输出QQ头像
        $url = 'https://q2.qlogo.cn/headimg_dl?dst_uin='.$is_qq_mail[1][0].'&spec=100';//必须加&spec=100且不可更改
    }
    // 判断主题设置是否开启了本地缓存头像
    if(Helper::options()->lavatar){
        //判断本地是否有该头像文件
        if (is_file($local_img)){
            $url = $local_img_url;
        }else{

            //执行异步缓存;(但要避免同时大量缓存文件的建立,服务器会卡)
            $cmd = "php ".__THEME_DIR__."ic/AsynAvatar.php  '$url' '$local_img' '$default_dir'>/dev/null 2>&1 ";
            pclose(popen($cmd.'&', 'r'));
        }
    }
    return $url;
}

异步执行内容AsynAvatar.php

思路:就是吧网络判断部分也写到异步执行内容里面,这样避免长时间加载,不过不能在评论当下给出最有判断后的反馈。这两点无法兼顾!

<?php
$url = $argv[1];
$local_img = $argv[2];
$default_dir = $argv[3];
url = str_replace('s=80&r=G&d=identicon','?d=404',$url);
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_HEADER, 1);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 2);
curl_setopt($ch, CURLOPT_NOBODY,true);
$content = curl_exec($ch);//抓取URL并把它传递给浏览器
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);  //curl的httpcode
$http_type = curl_getinfo($ch,CURLINFO_CONTENT_TYPE); //获取type
curl_close($ch);// 关闭cURL资源,并且释放系统资源
$img_type = explode("/",$http_type); //将'Content-Type: image/jpeg'中的值用/分隔开
if ($http_code == 200 && strtolower($img_type[0]) == 'image') {
    $url_from = str_replace('?d=404','s=80&r=G&d=identicon',$url);//是图像则替换回后缀后输出
} else {
    $url_from = $default_dir;
}
copy($url_from, $local_img);