起因

wordpress的头像加载一般是最头疼的地方。原因有三,其一是头像缓存的服务器在国外。所以国内使用的话,网络慢得可怜;其二是可以使用国内的头像缓存服务,但是指不定哪天就给关闭了;其三,我们可以直接禁用该服务,目前如果我们需要此服务的话,就只能使用默认头像。所以我想到,是不是可以自己写一个服务去缓存这些头像。就像CDN一样。

Gravatar是一图像跟随著您到访过的网站,当您在博客中留言或发表文章,它将会出现在您的名称旁。头像协助识别您在博客和论坛发表的文章,何乐而不为呢?

cn.gravatar.com

当然,我们也可以直接百度一下,查找本地缓存的例子。博主也查了一下。大部分的代码都年久失修,直接拿来用是不可能的。而他们的一些例子基本上都是写在插件里面,或者functions.php里面。其实这样做也是不合理的,因为会导致页面加载时间变长。毕竟第一次载入还是有些耗时的。所以只有做成服务再加上懒加载技术,才是解决这样的问题最好的路径。



设计

基于这一诉求。我设计了如下服务流程:

用户访问页面时,使用wordpresshook功能,拦截获取头像的方法,然后注入我们的头像服务地址。接着我们的头像服务会根据请求的查询字符串,生成一串唯一ID,并以这个ID做为文件名称,缓存至指定的路径。这就是缓存的步骤。如果该唯一ID的文件存在,则直接返回该文件即可。

所以头像的访问由原来的
https://secure.gravatar.com/avatar/8d94cb8be36f163c7a6148d4dc9a53d9?s=96&d=mm&r=g
调至
https://taliove.com/avatar/8d94cb8be36f163c7a6148d4dc9a53d9?s=96&d=mm&r=g

这两个链接只有域名不同,其它的均相同,返回的结果也一致。只不过从头像缓存主服务器同步缓存至本机而已。

动工

Let’s do it!

头像服务

  • 在网站根目录创建一个目录wp-avatar
  • 在该目录创建文件avatar_service.php,并拷贝以下代码至该文件
  • 给该目录网站的权限 sudo chown -R nginx:nginx ./wp-avatar
    • 我的php-fpm使用的是nginx用户名。不同的服务会不同。所以我这里使用的是nginx
  • 修改nginx配置vim /etc/nginx/conf.d/www.taliove.com.conf,新增如下配置:

location /avatar {
         try_files $uri $uri/wp-avatar/ /wp-avatar/avatar_service.php?$args;
}

修改完nginx后,重启服务systemctl restart nginx

头像缓存服务代码

<?php
$config = array(
    "avatar_dir" => "/usr/local/www/avatar/", //头像目录
//    "avatar_dir" => "./avatar/", //头像目录
    "default_avatar" => "default.jpg"//默认的头像名称
);

function start()
{
    global $config;
    $request_uri = $_SERVER['REQUEST_URI'];
    $file_path = $config['default_avatar'];
    if ($request_uri) {
        $file_path = cache_file($request_uri);
    }
    if (!file_exists($file_path)) {
        $file_path = $config['avatar_dir'] . $config['default_avatar'];
    }
    if (file_exists($file_path)) {
        $fp = fopen($file_path, "rb");
        $image_info = getimagesize($file_path);
        switch ($image_info[2]) {
            case IMAGETYPE_JPEG:
                header("Content-Type: image/jpeg");
                break;
            case IMAGETYPE_GIF:
                header("Content-Type: image/gif");
                break;
            case IMAGETYPE_PNG:
                header("Content-Type: image/png");
                break;
            default:
                header($_SERVER["SERVER_PROTOCOL"] . " 404 Not Found");
                break;
        }
        header("Content-Length: " . filesize($file_path));
        fpassthru($fp);
        exit;
    } else {
        header($_SERVER["SERVER_PROTOCOL"] . " 404 Not Found");
    }
}

/**
 * 缓存或获取缓存的文件
 * @param string $request_uri
 * @return string 缓存的文件名称
 */
function cache_file($request_uri = '')
{
    global $config;
    $file_name = "avatar_" . md5($request_uri);
    // 缓存有效时间
    $expire_time = 2592000;
    if (!is_readable($config['avatar_dir'])) {
        mkdir($config['avatar_dir'], 0777);
    }
    $file_path = $config['avatar_dir'] . $file_name . ".png";
    if (!is_file($file_path)) {
        $file_path = $config['avatar_dir'] . $file_name . ".jpg";
    }
    if (!is_file($file_path)) {
        $file_path = $config['avatar_dir'] . $file_name . ".jpeg";
    }
    if (!is_file($file_path) || (time() - filemtime($file_path)) > $expire_time) {
        //文件不存在,或者超过有效期时,重新获取
        $uri = "https://secure.gravatar.com/{$request_uri}";
        $headers = @get_headers($uri);
        $header = implode(",", $headers);
        if (!preg_match("/200/", $header)) {
            // 没有头像,则新建一个空白文件作为标记
            $handle = fopen($file_path, 'w');
            fclose($handle);
        } else {
            preg_match("/Content\-Type\: image\/(\w+)/i", $header, $matches);
            if ($matches && count($matches) >= 2) {
                $file_name .= ".{$matches[1]}";
                $result = getImage($uri, $config['avatar_dir'], $file_name);
                $file_path = $result['save_path'];
            }
        }
    }
    return $file_path;
}

function getImage($url, $save_dir = '', $filename = '', $type = 0)
{
    if (trim($url) == '') {
        return array('file_name' => '', 'save_path' => '', 'error' => 1);
    }
    if (trim($save_dir) == '') {
        $save_dir = './';
    }
    if (trim($filename) == '') {//保存文件名
        $ext = strrchr($url, '.');
        if ($ext != '.gif' && $ext != '.jpg') {
            return array('file_name' => '', 'save_path' => '', 'error' => 3);
        }
        $filename = time() . $ext;
    }
    if (0 !== strrpos($save_dir, '/')) {
        $save_dir .= '/';
    }
    //创建保存目录
    if (!file_exists($save_dir) && !mkdir($save_dir, 0777, true)) {
        return array('file_name' => '', 'save_path' => '', 'error' => 5);
    }
    //获取远程文件所采用的方法
    if ($type) {
        $ch = curl_init();
        $timeout = 300;
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $timeout);
        $img = curl_exec($ch);
        curl_close($ch);
    } else {
        ob_start();
        readfile($url);
        $img = ob_get_contents();
        ob_end_clean();
    }
    //$size=strlen($img);
    //文件大小
    $fp2 = @fopen($save_dir . $filename, 'a');
    fwrite($fp2, $img);
    fclose($fp2);
    unset($img, $url);
    return array('file_name' => $filename, 'save_path' => $save_dir . $filename, 'error' => 0);
}

start();

自定义插件

定义好服务之后,我们需要将所有的头像访问地址,都指向我们新的服务地址。使用自定义的插件,可以进行灵活的配置。

在网站根目录wp-content/plugins创建文件夹taliove,在其下创建文件functions.php,添加如下代码:

自定义插件

<?php
/**
 * Plugin Name: taliove插件
 * Plugin URI: https://taliove.com
 * Description: 添雨自定义函数插件
 * Author: taliove
 * Author URI: https://taliove.com
 * Version: 0.0.1
 */
if (!function_exists('cache_avatar')) {
    function tf_log($str = '', $tag = '')
    {
        $split = ($tag == '') ? '' : ":\t";
        file_put_contents(WP_CONTENT_DIR . '/taliove.debug.log', $tag . $split . $str . "\n", FILE_APPEND);
    }

    /**
     * 缓存头像
     * @param $avatar
     * @param $id_or_email
     * @param $size
     * @param $default
     * @param $alt
     * @return mixed
     */
    function cache_avatar($avatar, $id_or_email, $size, $default, $alt)
    {
        $site = site_url();
        return preg_replace("/https?\:\/\/.+?avatar\//","{$site}/avatar/", $avatar);
    }

    add_filter('get_avatar', 'cache_avatar', 1, 5);
}

然后在wordpress管理后台启用刚刚创建好的插件。

结尾

头像服务,是网站的个性化功能之一。有的人喜欢,有的人不喜欢。我是属于前者。基于以上操作后,头像服务不依赖于第三方。且访问速度快很多。当然这些缓存的头像也可以走CDN,但是httpsCDN是要收费的。所以暂时先从本地访问。

最终访问效果是,首次访问时,耗时会较长。第二次访问速度将会有质的飞跃。对于这种情况,我们需要安装一下懒加载插件即可。我使用的是a3-lazy-load插件,效果还是挺OK的。

最后博主采用了自建伪CDN,将这个缓存服务放在了CDN域名下。所有的静态资源能走CDN走CDN。毕竟第三方厂商的https CDN服务是要收费的。

Enjoy it!