主题后台有更换 Gravatar 地址的功能,其实国内这些 Gravatar 头像镜像源速度上还是很不错的,本身使用也没啥问题,但最近发现即使是再稳定的镜像源也存在不稳定的情况。当然网站服务器本身也不能保证百分百稳定。服务器出问题是整个网站打不开,这个没办法。但如果服务器没有问题,头像镜像源出现不稳定情况就有点尴尬,网站打开速度杠杠的,头像确迟迟加载不出来...这种情况建站以来已经碰到过好几次了。
再就是发现有的博主好像并没有使用 Gravatar 头像,应该是只注册了 cravatar.cn 的头像。为保证更多的涵盖所有博主头像,镜像源好像也只有 https://cravatar.cn/avatar 更为合适。问题是这个镜像源好像在加载使用 QQ 邮箱的头像时速度很慢。综上就有了将评论头像缓存到本地的想法。
网上也看了好几份关于评论头像本地缓存的教程,尝试了几个发现跟这个主题并不适配。这个主题有个功能还是不错的,就是在评论表单中填写邮箱时会自动通过镜像源地址加载评论头像。如果此时再把加载的评论头像缓存到本地不就可以了。现将实现该功能的主要代码记录如下:
1. 在 core.php 中创建一个函数来处理头像的下载和缓存:
// 定义头像缓存目录 define('AVATAR_CACHE_DIR', WP_CONTENT_DIR . '/avatars'); // 定义头像缓存URL define('AVATAR_CACHE_URL', WP_CONTENT_URL . '/avatars'); // 定义头像缓存有效期,默认7天 define('AVATAR_CACHE_LIFETIME', apply_filters('avatar_cache_lifetime', 7 * DAY_IN_SECONDS)); // 定义头像最大存储时间,默认30天 define('AVATAR_CLEANUP_AGE', apply_filters('avatar_max_age', 30 * DAY_IN_SECONDS)); /** * 缓存用户头像到本地 * * @param string $email 用户邮箱 * @param array $args 头像参数 * @return string 头像URL */ function cache_avatar_locally($email, $args = array()) { // 检查WP_Filesystem函数是否存在,若不存在则加载文件操作类 if (!function_exists('WP_Filesystem')) { require_once ABSPATH . 'wp-admin/includes/file.php'; } WP_Filesystem(); global $wp_filesystem; // 默认头像参数 $default_args = array( 'default' => '404', 'size' => 96, 'rating' => 'g', 'scheme' => null ); $args = wp_parse_args($args, $default_args); $args = apply_filters('avatar_cache_params', $args, $email); // 对参数进行排序,以确保生成一致的hash值 $sorted_args = ksort_deep($args); $param_string = http_build_query($sorted_args); // 根据用户邮箱和参数生成唯一的hash值 $hash = md5(strtolower(trim($email)) . '|' . $param_string); $base_path = AVATAR_CACHE_DIR . '/' . $hash; // 查找是否已有缓存的头像文件 $avatar_file = find_existing_avatar($base_path); if ($avatar_file) { // 如果头像文件存在且需要刷新,则安排刷新操作 if (should_refresh_avatar($avatar_file)) { schedule_avatar_refresh($email, $args); } // 返回头像的URL return str_replace(AVATAR_CACHE_DIR, AVATAR_CACHE_URL, $avatar_file); } // 获取Gravatar头像URL $gravatar_url = get_avatar_url($email, $args); // 获取头像内容 $response = wp_remote_get($gravatar_url, array('timeout' => 10)); // 如果请求失败,则返回Gravatar默认头像URL if (is_wp_error($response) || 200 !== wp_remote_retrieve_response_code($response)) { return $gravatar_url; } // 获取图片类型 $content_type = wp_remote_retrieve_header($response, 'content-type'); // 获取文件扩展名 $ext = get_extension_from_mime($content_type); // 设置缓存头像的路径 $avatar_path = "{$base_path}.{$ext}"; $avatar_dir = dirname($avatar_path); // 如果缓存目录不存在,则创建目录 if (!file_exists($avatar_dir)) { wp_mkdir_p($avatar_dir); } // 将头像内容保存到本地 if ($wp_filesystem->put_contents($avatar_path, wp_remote_retrieve_body($response), 0644)) { return AVATAR_CACHE_URL . "/{$hash}.{$ext}"; } // 如果保存失败,则返回Gravatar默认头像URL return $gravatar_url; } /** * 深度排序数组 * * @param array $array 输入数组 * @return array 排序后的数组 */ function ksort_deep($array) { if (is_array($array)) { ksort($array); foreach ($array as $key => $value) { $array[$key] = ksort_deep($value); } } return $array; } /** * 查找已存在的头像文件 * * @param string $base_path 头像缓存基础路径 * @return string|null 头像文件路径,若没有则返回null */ function find_existing_avatar($base_path) { $files = glob("{$base_path}.*"); foreach ($files as $file) { if (is_file($file) && in_array(pathinfo($file, PATHINFO_EXTENSION), ['jpg', 'jpeg', 'png', 'webp'])) { return $file; } } return null; } /** * 判断头像是否需要刷新 * * @param string $file 头像文件路径 * @return bool 是否需要刷新头像 */ function should_refresh_avatar($file) { $file_time = filemtime($file); $cache_lifetime = apply_filters('avatar_cache_lifetime', AVATAR_CACHE_LIFETIME); return (time() - $file_time) > $cache_lifetime; } /** * 根据MIME类型获取文件扩展名 * * @param string $mime MIME类型 * @return string 文件扩展名 */ function get_extension_from_mime($mime) { $mime_map = [ 'image/jpeg' => 'jpg', 'image/png' => 'png', 'image/webp' => 'webp' ]; return $mime_map[$mime] ?? 'jpg'; } /** * 安排头像缓存刷新 * * @param string $email 用户邮箱 * @param array $args 头像参数 */ function schedule_avatar_refresh($email, $args) { static $scheduled = array(); $key = md5($email . serialize($args)); // 如果头像刷新任务未安排且当前请求不是AJAX或Cron if (!isset($scheduled[$key]) && !wp_doing_ajax() && !wp_doing_cron()) { // 安排一个随机时间执行更新任务 wp_schedule_single_event(time() + mt_rand(0, 300), 'update_avatar_cache', array($email, $args)); $scheduled[$key] = true; } } /** * 更新头像缓存 * * @param string $email 用户邮箱 * @param array $args 头像参数 */ function update_avatar_cache($email, $args) { $args['force_refresh'] = true; cache_avatar_locally($email, $args); } /** * 清理过期的头像缓存 */ function cleanup_expired_avatars() { // 该函数待实现 } // 添加更新头像缓存的钩子 add_action('update_avatar_cache', 'update_avatar_cache'); // 添加定时清理任务 add_action('wp_loaded', 'schedule_avatar_cleanup'); // 每日清理过期头像的任务 add_action('daily_avatar_cleanup', 'cleanup_expired_avatars'); /** * 安排头像清理任务 */ function schedule_avatar_cleanup() { // 如果没有安排清理任务,则安排一个每日清理任务 if (!wp_next_scheduled('daily_avatar_cleanup')) { wp_schedule_event(time(), 'daily', 'daily_avatar_cleanup'); } }
2.在 core-rest.php 中修改访客信息获取的函数,使其使用本地缓存的头像:
function ajax_get_visitor_info_callback() { $email = tryGetValue('email'); $key = md5($email); if (false === ($result = get_transient($key))) { $result = [ 'author'=>'', 'email' =>$email, 'avatar'=>cache_avatar_locally($email, array('size' => 96)), 'url' =>'' ]; $comment = get_comments([ 'number' => 1, 'author_email' => $email, 'order' => 'DESC', 'orderby' => 'comment_date' ]); if ($comment && count($comment)) { $result['author'] = $comment[0]->comment_author; $result['url'] = $comment[0]->comment_author_url; set_transient($key,$result,MONTH_IN_SECONDS); } } wp_send_json_success($result); } add_action('wp_ajax_get_visitor_info','ajax_get_visitor_info_callback'); add_action('wp_ajax_nopriv_get_visitor_info','ajax_get_visitor_info_callback');
3.在 core.php 中修改评论格式化函数,使其也使用本地缓存的头像:
function formatter_comment( $comment, $friends = [] ) { $res = (object) []; $formatter = [ "comment_ID" => "id", "comment_post_ID" => "post_id", "comment_author" => "author", "comment_author_url" => "url", "comment_author_IP" => "ip", "comment_date" => "date", "comment_date_gmt" => "date_gmt", "comment_content" => "content", "comment_karma" => "karma", "comment_approved" => "approved", "comment_agent" => "agent", "comment_type" => "type", "comment_parent" => "parent", ]; if ( user_can( $comment->user_id, "administrator" ) ) { $res->sign = 'admin'; } else if ( in_array( $comment->comment_author_email, $friends ) ) { $res->sign = 'friends'; } else { $res->sign = ''; } $res->avatar = cache_avatar_locally($comment->comment_author_email, array('size' => 96)); foreach ( $formatter as $old => $new ) { $res->{$new} = $comment->{$old}; } if ( $res->parent > 0 && get_comment( $res->parent ) ) { $res->content = '<a href="#comment-' . $res->parent . '">@' . get_comment_author( $res->parent ) . '</a> ' . $res->content; } if ( function_exists( 'get_user_city' ) ) { $res->ip_city = get_user_city( $res->ip ); } return $res; }
4.在 page-wall.php 中修改读者墙页面,使其也使用本地缓存的头像:
<?php foreach ( get_readers_wall() as $comment ) : $url = $comment->comment_author_url; $alt = mb_substr( $comment->comment_author, 0, 1 ); $avatar = cache_avatar_locally($comment->comment_author_email, array('size' => 96)); ?> <li class="column col-3 col-sm-4 col-xs-6 p-2"> <div class="card uni-card uni-shadow flex-center text-center p-2"> <a class="text-gray text-tiny m-2" href="<?= ( $url ?: 'javascript:void(0);' ); ?>" target="<?= ( $url ? '_blank' : '_self' ); ?>"> <figure class="avatar avatar-xl bg-gray badge" data-badge="<?= $comment->cnt ?>" data-initial="<?= $alt; ?>"> <img src="<?= $avatar; ?>" alt="<?= $alt; ?>" no-view/> </figure> <span class="d-block text-ellipsis mt-1"><?= $comment->comment_author ?></span> </a> </div> </li> <?php endforeach; ?>
5.在 functions.php 中修改作者信息部分的头像获取方式:
function the_author_info($post_id = null) { $post = get_post($post_id); $author_email = get_the_author_meta('user_email', $post->post_author); $data = [ 'display_name' => get_the_author_meta('display_name', $post->post_author), 'description' => get_the_author_meta('description', $post->post_author), 'avatar' => cache_avatar_locally($author_email, array('size' => 96)), ]; echo wp_json_encode($data, JSON_UNESCAPED_SLASHES); }
修改后,当用户首次填写邮箱时:获取 Gravatar 头像将头像下载并保存到本地目录,返回本地缓存的头像 URL。当用户再次访问时:检查本地是否已有缓存的头像如果有,直接使用本地缓存的头像。如果没有,重复第一步的流程。
关于头像缓存的几个节点说明如下:
1.评论表单填写邮箱时:
// static/helper.js visitor(email, cb) { // 当用户输入邮箱时触发 this.ajax({ query: { action: 'get_visitor_info', email } }) // 调用后端 ajax_get_visitor_info_callback 函数,此时会缓存头像 }
2.显示评论列表时:
// inc/core.php function formatter_comment($comment, $friends = []) { // 格式化每条评论时会缓存头像 $res->avatar = cache_avatar_locally($comment->comment_author_email, array('size' => 96)); }
3.显示读者墙时:
// page-wall.php foreach (get_readers_wall() as $comment) : $avatar = cache_avatar_locally($comment->comment_author_email, array('size' => 96)); ?>mail);
4.显示作者信息时:
// functions.php function the_author_info($post_id = null) { $author_email = get_the_author_meta('user_email', $post->post_author); $data = [ 'avatar' => cache_avatar_locally($author_email, array('size' => 96)), ]; }
缓存的过程是:首先检查 /wp-content/avatars/ 目录下是否已有该邮箱对应的头像文件如果有,直接返回本地缓存的头像 URL。如果没有:获取 Gravatar URL下载头像图片保存到本地返回本地缓存的头像 URL。
说明:由于将头像缓存路径改为了 /wp-content/avatars/,需保证 wp-content 和 wp-content/avatars/ 文件夹权限设置为 777 。