目录

自建头像缓存服务

起因

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

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

cn.gravatar.com

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

设计

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

/images/self-host-avatar-cache-service-1.png

用户访问页面时,使用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,并拷贝以下代码至该文件

  • 给该目录网站的权限

    1
    
    sudo chown -R nginx:nginx ./wp-avatar
    
    • 我的php-fpm使用的是nginx用户名。不同的服务会不同。所以我这里使用的是nginx
  • 修改nginx配置vim /etc/nginx/conf.d/www.taliove.com.conf,新增如下配置:

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

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

头像缓存服务代码

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
<?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,添加如下代码:

自定义插件

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<?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!