中午,小熊发来一篇来自歧路亡羊博客的精彩教程:《wordpress 利用代码来实现缓存》。粗略看了一下,发现这个代码在几个月之前我就用过,不过由于此代码无法区分多个域名,从而会导致移动站无法跳转的情况。
我利用午休的时间,仔细看了下这篇文章,发现博主在原代码的基础上,还加上了自动刷新缓存的机制,不过貌似是和 WP Super Cache 插件一样,只要更新文章,就会清除所有缓存,感觉不太适合我。
不过,这篇文章还是激发了我兴趣。折腾了几个月,我现在也能基本看得懂 PHP 了,所以决定把这个代码修改一下,让它可以适应多个域名的情况。
一、何为多域名?
正式分享之前,我必须说清楚一下,什么是多域名网站!
熟悉的朋友,应该知道张戈博客除了 zhang.ge 之外,还有一个 m.zhang.ge 的移动域名。当 js 检测到是移动端的 UA 时,将会自动跳转到 m.zhang.ge,从而实现移动端的适配跳转。
以前分享这个跳转的时候,我下意识的以为,这种跳转可以兼容任意缓存,比如 cos-html-cache 等。实际上发现,这种跳转只能兼容 wp super cache!原因是:只有 wp super cache 是分域名来储存缓存文件的,其他插件包括代码版都不区分域名,统统存到同一个路径,于是就出现了无限死循环跳转的窘迫,因为缓存文件是同一个,跳了之后还是 PC 的缓存,怎么跳都跳不出这个死循环!!(估计又有人看不懂了,不过无所谓,真正需要的时候肯定看得懂!)
言归正传,既然 wp super cache 可以兼容多域名网站,那代码版肯定也可以做到!测试了十来分钟,就搞定了!存放结构都和 wp-super-cache 一样!
下面分享一下详细步骤:
二、部署代码
<?php //原缓存路径拼接当前请求域名的文件夹,从而可以区分 m.zhang.ge 还是 zhang.ge,或是其它... define('CACHE_ROOT', dirname(__FILE__).'/cache/'.$_SERVER['HTTP_HOST']); define('CACHE_LIFE', 86400); //缓存文件的生命期,单位秒,86400 秒是一天 define('CACHE_SUFFIX','.html'); //缓存文件的扩展名,千万别用 .php .asp .jsp .pl 等等 //首页以 index.html 缓存,其他以【请求路径/index.html】缓存,如【1234.html/index.html】(同 wp super cache) if($_SERVER['REQUEST_URI'] == '/'){ $file_name = "index".CACHE_SUFFIX; $cache_dir = CACHE_ROOT; } else { $file_name = 'index.html'; //过滤带参数的地址,避免重复缓存浪费资源: $target_url = preg_replace('/(\/comment|page|#|\?|:).*$/','',$_SERVER['REQUEST_URI']); $cache_dir = CACHE_ROOT.$_SERVER['REQUEST_URI']; } //缓存文件存放路径 $cache_file = $cache_dir.'/'.$file_name; //缓存黑名单:已过滤 WP 搜索、订阅、地图等,若要添加更多请用分隔符|隔开: $Filter_list = '/(\?s=|feed|map|page|404|%|xml|txt|tag|author)/'; if($_SERVER['REQUEST_METHOD']=='GET' && !preg_match_all($Filter_list,$_SERVER['REQUEST_URI'],$matches)){ if(file_exists($cache_file) && time() - filemtime($cache_file) < CACHE_LIFE){ //如果缓存文件存在,并且没有过期,就把它读出来。 $fp = fopen($cache_file,'rb'); fpassthru($fp); fclose($fp); exit(); } elseif(!file_exists($cache_dir)){ if(!file_exists(CACHE_ROOT)){ mkdir(CACHE_ROOT,0777); chmod(CACHE_ROOT,0777); } mkdir($cache_dir,0777); chmod($cache_dir,0777); } function auto_cache($contents){ //回调函数,当程序结束时自动调用此函数 global $cache_file; $fp = fopen($cache_file,'wb'); fwrite($fp,$contents); fclose($fp); chmod($cache_file,0777); clean_old_cache(); //生成新缓存的同时,自动删除所有的老缓存。以节约空间。 return $contents; } function clean_old_cache(){ chdir(CACHE_ROOT); foreach (glob("*/*".CACHE_SUFFIX) as $file){ if(time()-filemtime($file)>CACHE_LIFE){ unlink($file); } } } ob_start('auto_cache'); //回调函数 auto_cache } else{ if(file_exists($cache_file)){ //file_exists() 函数检查文件或目录是否存在。 unlink($cache_file); //不是 GET 的请求就删除缓存文件。 } } ?>
以下是三种缓存机制中,缓存文件存放路径的对比:
①、原缓存代码存放路径为: /网站根目录/cache/请求路径前 2 位 MD5 值/MD5 字符串(很难区分是哪篇文章!) ②、#nginx 下开启 mod_rewrite 模式时,wp super cache 的缓存路径是: /网站根目录/wp-content/cache/supercache/请求域名/请求路径/index.html ③、我修改后的缓存代码存放路径则变成: /网站根目录/cache/请求域名/请求路径/index.html
部署方法还是和原代码一致:
将以上代码保存为 cache.php 上传到网站根目录,然后修改网站根目录的 index.php,在第一个<?php 后添加代码:
require('cache.php');
现在,只要刷新一下网站前台,就能在 cache 里面看到内容了,而且结构和 WP Super Cache 一致:
很明显,我参考了 WP Super Cache 的存放路径,请求域名的不同,存放位置也会不同,从而避免了多个域名请求到同一个缓存文件,造成跳转死循环的问题!(不得不说 WP Super Cache 想得很周到!)
当然,如果你想改成和 WP Super Cache 一样的存放路径,只需要将第二行代码如下修改即可:
define('CACHE_ROOT', dirname(__FILE__).'/wp-content/cache/'.$_SERVER['HTTP_HOST']);
代码中已加入缓存黑名单功能,如果想禁止某页面缓存,只要将该页面的关键字眼加入到如下代码,比如我要禁止缓存留言板,也就是 https://zhang.ge/liuyan,那么如下新增:
//使用分隔符 | 隔开新增的关键词即可: $Filter_list = '/(\?s=|feed|map|page|404|%|xml|txt|tag|author|liuyan)/';
最新补充:今早查看缓存时,大小居然高达 45M!进去看了下,只能惊叹那些无聊的小人,真特么多!发一个部分截图,你们感受下:
各种恶意访问,真是居心叵测!如果非常多,建议将关键词加入到缓存黑名单,避免缓存太多占空间。
三、更多补刀
①、找回 CDN 功能
用过 WP Super Cache 的朋友,应该知道这个插件还自带 CDN 功能,很全面!那么换成代码版,当然也不能漏掉这个实用功能!
正好歧路亡羊博客分享了水煮鱼的七牛 CDN 插件的代码版,直接拿来主义,然后强迫症缩进一下:
//水煮鱼的七牛 CDN 插件代码版 define('FocusCDNHost','https://zhang.ge'); //wordpress 网站网址 define('FocusCDNRemote','http://static.zhang.ge'); //cdn 域名 define('FocusCDNIncludes','wp-content,wp-includes'); //设置加速目录 define('FocusCDNExcludes','.php|.xml|.html|.po|.mo'); //设置文件白名单 define('FocusCDNRelative',''); function do_cdnrewrite_ob_start() { $rewriter = new FocusCDNRewriteWordpress(); $rewriter->register_as_output_buffer(); } add_action('template_redirect', 'do_cdnrewrite_ob_start'); class FocusCDNRewriteWordpress extends FocusCDNRewrite { function __construct() { $excl_tmp = FocusCDNExcludes; $excludes = array_map('trim', explode('|', $excl_tmp)); parent::__construct( FocusCDNHost, FocusCDNRemote, FocusCDNIncludes, $excludes, !!FocusCDNRelative ); } public function register_as_output_buffer() { if ($this->blog_url != FocusCDNRemote) { ob_start(array(&$this, 'rewrite')); } } } class FocusCDNRewrite { var $blog_url = null; var $cdn_url = null; var $include_dirs = null; var $excludes = array(); var $rootrelative = false; function __construct($blog_url, $cdn_url, $include_dirs, array $excludes, $root_relative) { $this->blog_url = $blog_url; $this->cdn_url = $cdn_url; $this->include_dirs = $include_dirs; $this->excludes = $excludes; $this->rootrelative = $root_relative; } protected function exclude_single(&$match) { foreach ($this->excludes as $badword) { if (stristr($match, $badword) != false) { return true; } } return false; } protected function rewrite_single(&$match) { if ($this->exclude_single($match[0])) { return $match[0]; } else { if (!$this->rootrelative || strstr($match[0], $this->blog_url)) { return str_replace($this->blog_url, $this->cdn_url, $match[0]); } else { return $this->cdn_url . $match[0]; } } } protected function include_dirs_to_pattern() { $input = explode(',', $this->include_dirs); if ($this->include_dirs == '' || count($input) < 1) { return 'wp\-content|wp\-includes'; } else { return implode('|', array_map('quotemeta', array_map('trim', $input))); } } public function rewrite(&$content) { $dirs = $this->include_dirs_to_pattern(); $regex = '#(?<=[(\"\'])'; $regex .= $this->rootrelative ? ('(?:'.quotemeta($this->blog_url).')?') : quotemeta($this->blog_url); $regex .= '/(?:((?:'.$dirs.')[^\"\')]+)|([^/\"\']+\.[^/\"\')]+))(?=[\"\')])#'; return preg_replace_callback($regex, array(&$this, 'rewrite_single'), $content); } }
根据实际情况修改以上代码的 2~3 行,最后粘贴到主题目录下的 functions.php 文件的最后一个?>之前即可。
如果和张戈博客一样,有一个 PC 主题,还有个移动主题,那么 2 个主题的 functions.php 都需要添加,但是要注意修改不同的域名:
比如 pc 主题 用 https://zhang.ge,而移动主题用 http://m.zhang.ge,总之依葫芦画瓢就行!!!
这样就实现了之前 WP Super Cache 的 CDN 功能。
②、找回 Mod_rewrite 模式
如果想要在 nginx 下实现 mod_rewrite 模式,需要在 nginx 下新增一些规则,这个在张戈博客已经分享过(相关文章)
开启这个模式的好处是,当存在静态缓存文件时,nginx 将直接调用缓存文件给浏览器,而不再需要经过 php-fpm 模块,从而加载速度成倍增长!
那本文分享的代码版能否实现这个功能呢?依然是那句话,有想法,技术都不是问题!
很简单,只要稍微修改一下之前分享的 nginx 下的 mod_rewrite 规则即可:
location / { #缓存规则开始! # 如果请求的文件已存在,直接返回 if (-f $request_filename) { break; } set $supercache_file ''; set $ihttp_host ''; set $supercache_uri $request_uri; if ($supercache_uri ~ ^(.+)$) { #请注意:下面这行代码的路径对应缓存代码中的 CACHE_ROOT 定义的路径: set $supercache_file /cache/$http_host$1/index${ihttp_host}.html; } # 只有当缓存文件存在时,才进行 rewrite if (-f $document_root$supercache_file) { #rewrite ^(.*)$ $supercache_file break; rewrite ^ $supercache_file last; } # 所有其他请求,转给 wordpress 处理 if (!-e $request_filename) { rewrite . /index.php last; } #缓存规则结束! #下面部分是 nginx 原有规则.... }
只要将以上代码中的开始到结束之间的代码,添加到 nginx 配置文件中 location / 模块原有规则之前即可。
保存之后,执行如下类似命令重载 nginx:
#先测试配置是否正确: /usr/local/nginx/sbin/nginx -t #若显示 success,则继续重载 nginx: /usr/local/nginx/sbin/nginx -s reload
完成以上操作,当有人访问到你的网站的缓存文件时,无需经过 index.php,直接由 nginx 判断并回应给用户,和之前 WP Super Cache 的机制一摸一样!
③、提交评论时清除文章缓存
这是张戈博客之前的文章就分享过的方法(相关文章),这次只要稍微修改下路径就可以用了!
//有人评论将自动删除已存在缓存 $post_data = get_post($post->ID, ARRAY_A); $slug = $post_data['post_name']; $cache_s = site_url()."/cache/".$_SERVER['HTTP_HOST']."/".$post->ID.".html/index.html"; $cache_sd = site_url()."/cache/".$_SERVER['HTTP_HOST']."/".$post->ID.".html"; $cache_p = site_url()."/cache/".$_SERVER['HTTP_HOST']."/".$slug."/index.html"; $cache_pd = site_url()."/cache/".$_SERVER['HTTP_HOST']."/".$slug; if (file_exists($cache_s)) { unlink($cache_s); rmdir($cache_sd); } if (file_exists($cache_p)) { unlink($cache_p); rmdir($cache_pd); }
修改主题目录下的 comment-ajax.php,将以上代码添加到以下代码之后即可实现提交评论时删除当前文章的缓存:
do_action('pre_comment_on_post', $comment_post_ID);
以上方法会比歧路亡羊博主分享的要实用一些,当然萝卜白菜各有所爱,喜欢哪个用哪个!值得注意的是,如果博客没有使用 ajax 评论,那么以上代码需要添加到 WP 根目录下的 wp-comments-post.php 文件的相应位置。
四、多后遗症
部署此功能之后,各种问题迎面而来,我也真是醉了!
①、若主题有登陆状态显示,那缓存之后,无论谁打开都显示已登录;
②、WordPress 原生评论框已登录状态将带入缓存当中,效果同上;
③、管理员回复评论也会发送邮件给管理员(①、②项处理 OK 之后发现的问题);
④、无法保存评论者信息,这个是开启缓存之后的诟病,之前已分享过变相解决办法(相关文章)。
对于①、②,很显然缓存的时候将用户登陆状态一起缓存了,导致任何人打开都是已登录,你醉了么?
解决办法:
针对①、找到主题显示状态的位置,修改登陆相关代码!比如知更鸟主题主题下的 time.php 和 login.php;
针对②、找到主题下的 comments.php 文件,删除状态判断代码,主体不同,可能会有所差异,粗略贴一下:
<!-- 需要删除的代码(开始) --> <?php if ( $user_ID ) : ?> <!-- 此处是注册用户已登录的相关代码 --> <?php elseif ( '' != $comment_author ): ?> <!-- 此处是曾经留过言的相关代码 --> <?php endif; ?> <!-- 下面则是位置用户的相关代码 --> <!-- 需要删除的代码(结束) --> <!-- 以下部分只要删除判断的第一行和最后一行的 php 部分,如下: --> <?php if ( ! $user_ID ): ?> <!-- 之间的代码请勿删除!!! --> <?php endif; ?>
看起来相当费解,我还是贴一下我最后修改的代码吧!仅供参考:
<?php if ('comments.php' == basename($_SERVER['SCRIPT_FILENAME'])) die ('Please do not load this page directly. Thanks!'); if (!empty($post->post_password)) { if ($_COOKIE['wp-postpass_' . COOKIEHASH] != $post->post_password) { // and it doesn't match the cookie ?> <p class="nocomments">必须输入密码,才能查看评论!</p> <?php return; } } $oddcomment = ''; ?> <?php if ('open' == $post->comment_status) : ?> <div id="respond"> <h3>给我留言</h3> <div class="cancel-comment-reply"> <small><?php cancel_comment_reply_link(); ?></small> </div> <?php if ( get_option('comment_registration') && !$user_ID ) : ?> <p><?php print '您必须'; ?><a href="<?php echo get_option('siteurl'); ?>/wp-login.php?redirect_to=<?php echo urlencode(get_permalink()); ?>"> [ 登录 ] </a>才能发表留言!</p> <?php else : ?> <!-- 需要删除的代码大概在这个位置 --> <form action="<?php echo get_option('siteurl'); ?>/wp-comments-post.php" method="post" id="commentform"> <div id="comment-author-info"> <p> <input type="text" name="author" id="author" class="commenttext" value="<?php echo $comment_author; ?>" size="22" tabindex="1" /> <label for="author">昵称<?php if ($req) echo " *"; ?></label> </p> <p> <input type="text" name="email" id="email" class="commenttext" value="<?php echo $comment_author_email; ?>" size="22" tabindex="2" /> <label for="email">邮箱<?php if ($req) echo " *"; ?></label> </p> <p> <input type="text" name="url" id="url" class="commenttext" value="<?php echo $comment_author_url; ?>" size="22" tabindex="3" /> <label for="url">网址</label> </p> <?php if ( '' == $comment_author )spam_protection_math();?> </div> <?php endif; ?> <div class="clear"></div> <p><textarea name="comment" id="comment" tabindex="4"></textarea></p> <div class="submitted"> <input class="submit" name="submit" type="submit" id="submit" tabindex="5" value="提交留言"/> <input class="reset" name="reset" type="reset" id="reset" tabindex="6" value="<?php esc_attr_e( '重写' ); ?>" /> <input type="checkbox" id="saveme" value="saveme" checked="checked" style="margin-left:20px;" /><label for="comment_mail_notify">记住我</label></p> <?php comment_id_fields(); ?> </div> <script type="text/javascript"> $(document).keypress(function(e){ if(e.ctrlKey && e.which == 13 || e.which == 10) { $(".submit").click(); document.body.focus(); } else if (e.shiftKey && e.which==13 || e.which == 10) { $(".submit").click(); } }) </script> <?php do_action('comment_form', $post->ID); ?> </form> <div class="clear"></div> </div> <?php endif;?> <?php if ($comments) : ?> <!-- 引用 --> <?php $numPingBacks = 0; $numComments = 0; foreach ($comments as $comment) if (get_comment_type() != "comment") $numPingBacks++; else $numComments++; ?> <h2 id="comments">共有 <?php $my_email = get_bloginfo ( 'admin_email' ); $str = "SELECT COUNT(*) FROM $wpdb->comments WHERE comment_post_ID = $post->ID AND comment_approved = '1' AND comment_type = '' AND comment_author_email"; $count_t = $post->comment_count; $count_v = $wpdb->get_var("$str != '$my_email'"); $count_h = $wpdb->get_var("$str = '$my_email'"); echo $count_t, " 条留言 访客:", $count_v, " 条 博主:", $count_h, " 条 "; ?> <?php if($numPingBacks>0) { ?>引用:<?php echo ' '.$numPingBacks.'';?><?php } ?> </h2> <ol class="commentlist"><?php wp_list_comments('type=comment&callback=mytheme_comment&end-callback=mytheme_end_comment'); ?></ol> <div class="navigation_c"> <div class="previous"><?php paginate_comments_links(); ?></div> </div> <?php else : ?> <?php if ('open' == $post->comment_status) : ?> <?php else : ?> <p class="nocomments">抱歉!评论已关闭.</p> <?php endif; ?> <?php endif; ?>
针对问题③,需要在发送邮件时先做一个判断,由于缓存无法取得用户登陆状态,所以我使用邮箱来判断是否发送:
if($to != '[email protected]'){ wp_mail( $to, $subject, $message, $headers ); }
如果评论者邮箱是博主自己的邮箱,则不发送邮件!由于每个主题的情况都不一样,所以这个方法只能参考,而无法通用!如果,你的博客用了代码版的 SMTP 功能,那么就可以使用这个方法!
更多相关问题请看续篇教程:
①、启用 WP Super Cache 纯代码版本之后的一些优化措施
②、php 平滑重启 nginx,彻底清除 WordPress 的静态缓存
五、写到最后
这篇文章是在我一边测试、一边记录的情况下完成的!本以为会很简单,结果状况百出,问题接踵而来!结果,此教程也变得异常复杂难懂!!!(写得比较匆忙,后续将持续更新发现的问题)
所以,在本文最后,张戈温馨提醒一下:
如果你博客用了多说,如果你博客没有什么已登录状态的判断代码,那么这个方法会比较简单!
如果你比较会折腾代码,也看懂了本文,那么这个方法依然适合你!
如果你不懂代码,而且还不容易接受新事物,那么请不要轻易去尝试本文分享的方法,否则也真是醉了。。。
早知道这么麻烦,我还是继续用 WP Super Cache 插件比较靠谱!想跃跃欲试的博友,最好先考虑清楚咯。。。。
我的七牛云就是用的代码实现的,水煮鱼的插件虽然很实用,但是真心无用功能太多。。一般博客用不到。。
现在只用七牛做图床。。
我用的是一个插件。
喔喔,感觉亲自做这个测试一样
太厉害了,博主!向博主靠近!
俺博客就用了个自适应的主题,专门搞个移动端网站麻烦
我的还是楼主帮我弄的呢。呵呵。
七牛配合WP Super Cache效果还是很不错的
做个缓存就OK了
看着博客成长 起来的,赞一个
上边第一个cache.php 好像用不了哦
一直正常使用中。
我使用的时候第21行出错,应该是分享的时候弄错了吧
出错了,检查下就好了。
我看了下文章,是多了一个空格代码&.nbsp;删除应该就可以了,应该是更新文章的时候转码了。
偶然的一次用手机打开博主的博客,发现博主的移动端是知更鸟的那个简约主题,但是域名确实二级域名,本以为是百度的siteapp移动端,原来不是的,这个手机站用二级域名是怎么做的
你看下这个:https://zhang.ge/3775.html
非常喜欢楼主的这个文章 有个问题清楼主指教一下
如果我不想按照域名来存放 文章的代码该怎么改?
原来是按MD5 挺乱的 我想按照文章名的POSTID来存放 比如你的文章中的图中的 直接以这种方式存放
../cache/1000/1000.html
../cache/2000/2000.html
../cache/3000/3000.html
../cache/4000/4000.html
该如何改动呢 ?
第三行如下修改,可以去掉域名文件夹。
ID来做文件夹,需要将如下行进行改造:
$_SERVER['REQUEST_URI']; 这个变量需要去掉后面的 .html,你自己折腾,不会的话就用 、cache/0000.html/index.html的形式也没啥问题,没必要纠结存放形式把?
谢谢 懂啦
已醉
不送
:sad: :!: :grin: :wink: :cry: :neutral: 啊啊啊啊啊啊啊啊啊
:???: 这是什么情况,被人盗用回复?
:twisted: 谁那么无聊,竟然盗用我的用户名和邮箱来回复,无语。PS:有什么办法解决吗?
前2天还有人盗用我的在我博客回复呢。。。都不用审核,打各种广告。
已经采取了排除措施,回头整理下。
不过用你的信息在我博客留言,这个就没办法阻止了。
上次也有人用我的信息在知更鸟那边发垃圾评论,头疼啊
Jager的文章就是帅气,用上了,效果不错,有个小地方要改下:
生成的缓存权限是777,把代码里面的0777改成0755会好点。
嗯,是这个理。
看着都好复杂呀!呵呵!
求问博主,这段代码缓存是怎么写入的,$content的值也就是网页的内容哪里来的呢。感谢 :smile:
http://www.php.net/manual/zh/function.ob-start.php 自问自答了。没搞过php,所以略有不懂。还是官方教程清楚
实在是抱歉,天天忙成狗,没时间搭理博客评论。。。自己找到了印象更深刻,感谢分享成果。
技术控啊,服了。
如果使用了cdn再使用这个插件做缓存,这样的效果是不是会更好一点。
本地也是缓存的话,1是对SEO更好,因为蜘蛛回源抓取快,2是外部访问更快。
博主 您好,看过您很多文章 都写得很不错。现在想请教一下,我一个网站有两个域名,当用另一个访问的时候,会产生相应的域名目录,但是主页 index.html 却是大小 0 byte,然后导致主页访问空白,请博主指教
兄弟你解决这个问题了么?
Warning: chmod(): No such file or directory in /www/wwwroot/mc.24bp.cn/wp-content/themes/FLY/cache.php on line 31
Warning: mkdir(): No such file or directory in /www/wwwroot/mc.24bp.cn/wp-content/themes/FLY/cache.php on line 33
Warning: chmod(): No such file or directory in /www/wwwroot/mc.24bp.cn/wp-content/themes/FLY/cache.php on line 34
博主显示这个怎么解决
cache.php放错地方了吧,应该放到网站根目录
请教一下,我开启缓存后,缩略图就显示不出来,这个要怎么调呢,主题自带的随机缩略图。谢谢
那个提交评论时删除页面缓存一直不成功。。是不是哪里不对,现在的ajax.php里只有do_action('pre_comment_on_post', $comment_post_ID);,是不是这个的问题