静态资源文件自动压缩并替换成压缩版本(大型网站优化技术)

news/2024/5/10 15:01:17/文章来源:https://blog.csdn.net/weixin_34056162/article/details/85768532
原文:静态资源文件自动压缩并替换成压缩版本(大型网站优化技术)

  这一次,我总结和分享一项大型网站优化技术,那就是在项目中自动压缩静态资源文件(css、js),并让网站自动加载压缩后的资源文件。当然,这项技术在雅虎35条前端优化建议里也有记载,但它那只是给出一个理论的方案而已,并且采用的是外部压缩工具去压缩,而在我的项目中,是直接通过自己的程序自动化去压缩所有css、js文件,然后让页面直接加载所压缩后的资源,接下来直接进入主题。

  本次实验使用的是PHP脚本语言,版本是PHP5.6,是在LINUX下搭建的环境(网上搭建无论是搭建LAMP还是LNMP的教程都五花八门乱七八糟,下次我会总结和分享如何在LINUX下搭建服务器环境的博文,而且搭建的环境必须一次性搭建成功的)。所选用的框架是CI框架,所使用的模板是Smarty模板引擎。当然了,这些只是我所使用的环境而已,如果你是PHP开发者,假如你要测试下这次实验,那么,我建议你的PHP版本选用5.4以上,至于框架用什么都是可以的。而如果你不是PHP开发者(你是JSP或者是ASP开发者或者是其他开发者),那么你理解好这一思路后,完全可以在自己熟悉的语言里进行实验测试。

   一、原理图

  首先我画一张思路图,便于大家先理解。

  首先是资源压缩原理图:

  

 

  接着是资源文件替换的原理图:

  

  假如大家认真理解并且看懂这两张原理图的话,基本上也就掌握了我所分享的思路。假如还是不能理解的话,接下来我会结合代码,对以上原理图的每一步进行详细讲解。

  二、思路详细分析

  1.首先是调用该压缩的方法,你可以把该方法放在网站所要加载的公共类的地方,例如每次访问网站都会调用该压缩方法进行压缩。当然,这个只是在开发环境才会每次都调用,如果是线上的环境,在你的网站发一次新版本的时候,调用一次用来生成压缩版的静态资源就可以了。

 1 class MY_Controller extends CI_Controller {
 2     public function __construct() {
 3         parent::__construct();
 4 
 5         //压缩jscss资源文件
 6         $this->compressResHandle();
 7     }
 8     /**
 9      * 压缩js、css资源文件(优化)
10      * @return [type] [description]
11      */
12     private function compressResHandle() {
13         $this->load->library('ResMinifier');
14         //压缩指定文件夹下的资源文件
15         $this->resminifier->compressRes();
16     }
17 }

  2.接着就调用了 ResMinifier类里的 compressRes方法。在这里我先附上 ResMinifier这个类的代码,然后方便一步步进行分析讲解

  1 <?php 
  2 defined('BASEPATH') OR exit('No direct script access allowed');
  3 /**
  4  * 资源压缩类
  5  */
  6 class ResMinifier {
  7     /** 需要压缩的资源目录*/
  8     public $compressResDir = ['css', 'js'];
  9     /** 忽略压缩的路径,例如此处是js/icon开头的路径忽略压缩*/
 10     public $compressResIngorePrefix = ['js/icon'];
 11     /** 资源根目录*/
 12     public $resRootDir;
 13     /** 资源版本文件路径*/
 14     private $resStatePath;
 15 
 16     public function __construct() {
 17         $this->resRootDir = WEBROOT . 'www/';
 18         $this->resStatePath = WEBROOT . 'www/resState.php';
 19     }
 20 
 21     public function compressRes() {
 22         //获取存放版本的资源文件
 23         $resState = $this->getResState();
 24         $count = 0;
 25 
 26         //开始遍历需要压缩的资源目录
 27         foreach ($this->compressResDir as $resDir) {
 28             foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($this->resRootDir . $resDir , FilesystemIterator::SKIP_DOTS)) as $file) {
 29                 //获取该资源文件的绝对路径
 30                 $filePath = str_replace('\\', '/', $file->getRealPath());
 31                 //获取文件相对路径
 32                 $object = substr($filePath, strlen($this->resRootDir));
 33                 //计算文件的版本号
 34                 $state = $this->_getResStateVersion($filePath);
 35 
 36                 //获取文件的几个参数值
 37                 if (true !== $this->getObjectInfo($object, $minObject, $needCompress, $state, $extension)) {
 38                     continue;
 39                 }
 40 
 41                 //压缩文件的绝对路径
 42                 $minFilePath = str_replace('\\', '/', $this->resRootDir. $minObject);
 43 
 44                 //************此处p判断是最重要部分之一*****************//
 45                 //判断文件是否存在且已经改动过
 46                 if (isset($resState[$object]) && $resState[$object] == $state && isset($resState[$minObject]) && file_exists($minFilePath)) {
 47                     continue;
 48                 }
 49 
 50                 //确保/www/min/目录可写
 51                 $this->_ensureWritableDir(dirname($minFilePath));
 52 
 53                 if ($needCompress) {
 54                     $this->compressResFileAndSave($filePath, $minFilePath);
 55                 } else {
 56                     copy($filePath, $minFilePath);
 57                 }
 58 
 59 
 60                 $resState[$object] = $state;
 61                 $resState[$minObject] = '';
 62                 $count++;
 63 
 64                 if ($count == 50) {
 65                     $this->_saveResState($resState);
 66                     $count = 0;
 67                 }
 68 
 69             }
 70         }
 71         if($count) $this->_saveResState($resState);
 72     }
 73 
 74     public function getObjectInfo($object, &$minObject, &$needCompress, &$state, &$extension) {
 75         //获取资源绝对路径
 76         $filePath = $this->resRootDir . $object;
 77         //判断资源是否存在
 78         if (!file_exists($filePath)) return "资源文件不存在{$filePath}";
 79         //版本号
 80         $state = $this-> _getResStateVersion($filePath);
 81         //文件名后缀
 82         $extension = pathinfo($filePath, PATHINFO_EXTENSION);
 83         //是否要压缩
 84         $needCompress = true;
 85 
 86         //判断资源文件是否是以 .min.css或者.min.js结尾的
 87         //此类结尾一般都是已压缩过,例如jquery.min.js,就不必再压缩了
 88         if (str_end_with($object, '.min.'.$extension, true)) {
 89             //压缩后的资源存放路径,放在 /www/min/ 目录下
 90             $minObject = 'min/'.substr($object, 0, strlen($object) - strlen($extension) - 4) . $state .'.'. $extension;
 91             $needCompress = false;
 92         } else if (in_array($extension, $this->compressResDir)) {
 93             //此处是需要压缩的文件目录
 94             $minObject = 'min/'.substr($object, 0, strlen($object) - strlen($extension)) . $state . '.' . $extension;
 95             //看看是否是忽略的路径前缀
 96             foreach ($this->compressResIngorePrefix as $v) {
 97                 if (str_start_with($object, $v, true)) {
 98                     $needCompress = false;
 99                 }
100             }
101         } else {
102             $minObject = 'min/'.$object;
103             $needCompress = false;
104         }
105         return true;
106     }
107 
108 
109     /**
110      * 获取存放资源版本的文件
111      * 它是放在一个数组里
112      * $resState = array(
113      *         '文件路径' => '对应的版本号',
114      *         '文件路径' => '对应的版本号',
115      *         '文件路径' => '对应的版本号',
116      *     );
117      * @return [type] [description]
118      */
119     public function getResState() {
120         if (file_exists($this->resStatePath)) {
121             require $this->resStatePath;
122             return $resState;
123         }
124         return [];
125     }
126 
127     /**
128      * 计算文件的版本号,这个是根据计算文件MD5散列值得到版本号
129      * 只要文件内容改变了,所计算得到的散列值就会不一样
130      * 用于判断资源文件是否有改动过
131      * @param  [type] $filePath [description]
132      * @return [type]           [description]
133      */
134     public function _getResStateVersion($filePath) {
135         return base_convert(crc32(md5_file($filePath)), 10, 36);
136     }
137 
138     /**
139      * 确保目录可写
140      * @param  [type] $dir [description]
141      * @return [type]      [description]
142      */
143     private function _ensureWritableDir($dir) {
144         if (!file_exists($dir)) {
145             @mkdir($dir, 0777, true);
146             @chmod($dir, 0777);
147         } else if (!is_writable($dir)) {
148             @chmod($dir, 0777);
149             if (!is_writable($dir)) {
150                 show_error('目录'.$dir.'不可写');
151             }
152         }
153     }
154 
155     /**
156      * 将压缩后的资源文件写入到/www/min/下去
157      * @param  [type] $filePath    [description]
158      * @param  [type] $minFilePath [description]
159      * @return [type]              [description]
160      */
161     private function compressResFileAndSave($filePath, $minFilePath) {
162         if (!file_put_contents($minFilePath, $this->compressResFile($filePath))) {
163 
164             //$CI->exceptions->show_exception("写入文件{$minFilePath}失败");
165             show_error("写入文件{$minFilePath}失败", -1);
166         }
167     }
168 
169     /**
170      * 压缩资源文件
171      * @param  [type] $filePath [description]
172      * @return [type]           [description]
173      */
174     private function compressResFile($filePath) {
175         $extension = strtolower(pathinfo($filePath, PATHINFO_EXTENSION));
176         if ($extension === 'js') {
177             require_once 'JShrink/Minifier.php';
178             return \JShrink\Minifier::minify(file_get_contents($filePath));
179         } else if ($extension ==='css') {
180             $content = file_get_contents($filePath);
181             $content = preg_replace('!/\*[^*]*\*+([^/][^*]*\*+)*/!', '', $content);
182             $content = str_replace(["\r\n", "\r", "\n"], '', $content);
183             $content = preg_replace('/([{}),;:>])\s+/', '$1', $content);
184             $content = preg_replace('/\s+([{}),;:>])/', '$1', $content);
185             $content = str_replace(';}', '}', $content);
186             return $content;
187         } else {
188             //$CI->exceptions->show_exception("不支持压缩{extension}文件[$filePath]");
189             show_error("不支持压缩{extension}文件[$filePath]", -1);
190 
191         }
192     }
193 
194     private function _saveResState($resState) {
195         ksort($resState);
196         $content = "<?php\n\n\$resState = array(\n";
197         foreach ($resState as $k => $v) {
198             $content .= "\t '$k' => '$v',\n";
199         }
200         $content .= ");\n\n";
201         file_put_contents($this->resStatePath, $content); 
202     }
203 
204 }
点击打开 资源压缩类

  整个类大部分代码我都加了注释,方便大家快速理解。这里我也会对每一行代码进行解说。

  (1)

/** 需要压缩的资源目录*/public $compressResDir = ['css', 'js'];/** 忽略压缩的路径,例如此处是js/icon开头的路径忽略压缩*/public $compressResIngorePrefix = ['js/icon'];/** 资源根目录*/public $resRootDir;/** 资源版本文件路径*/private $resStatePath;public function __construct() {$this->resRootDir = WEBROOT . 'www/';$this->resStatePath = WEBROOT . 'www/resState.php';}

  $compressResDir变量是需要压缩的资源目录,假如你有新的处理目录,可以在此变量里假如新的目录名即可处理。附上我测试项目的目录图

  $compressResIngorePrefix 忽略被压缩的路径的路径前部分是该数组变量的字符串,例如 有一个资源路径为 js/icon/bg.js或者是js/icon_index.js或者是js/icon.header.js,假如在该数组中加入了 js/icon这个字符串,那么资源路径为js/icon开头的都会被忽略掉,也就是直接跳过,不用压缩。(因为资源文件里总有一些是不需要压缩的嘛)

  $resRootDir存放资源根目录的

  $resStatePath 这个是资源版本文件路径

  (2)进入compressRes() 方法,我们先分析前面这一段代码

public function compressRes() {//获取存放版本的资源文件$resState = $this->getResState();$count = 0;  

-------------------------------调用getResState() 讲解start------------------------------------------------------------- 

  这里首先是调用 $this->getResState() 方法来获取存放版本的资源文件,此处先跳到该方法看看是如何写的,其实就是包含该文件,然后返回里面存放版本号的数组,我们看注释可以知道该文件里存放版本号的格式(顺便附上图让大家看看)

 

 

   /*** 获取存放资源版本的文件* 它是放在一个数组里* $resState = array(*         '文件路径' => '对应的版本号',*         '文件路径' => '对应的版本号',*         '文件路径' => '对应的版本号',*     );* @return [type] [description]*/public function getResState() {if (file_exists($this->resStatePath)) {require $this->resStatePath;return $resState;}return [];}

 

  (资源版本文件截图:)

 

-------------------------------调用getResState() 讲解end------------------------------------------------------------- 

 

  接着看compressRes()里的这一段代码 

 

//开始遍历需要压缩的资源目录foreach ($this->compressResDir as $resDir) {foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($this->resRootDir . $resDir , FilesystemIterator::SKIP_DOTS)) as $file) {//获取该资源文件的绝对路径$filePath = str_replace('\\', '/', $file->getRealPath());//获取文件相对路径$object = substr($filePath, strlen($this->resRootDir));//计算文件的版本号$state = $this->_getResStateVersion($filePath);

 

  第一个遍历的是js和css目录 第二个遍历是将js目录或者css目录里的文件都变成路径形式,

  例如获取文件的绝对路径 $filePath 的值是这样子的:

    /usr/local/apache2/htdocs/project/www/css/home/index.css

  而文件的相对路径$object是这样子的 :

    css/home/index.css

  这里就开始调用$this->_getResStateVersion($filePath)来计算文件的版本号

 

-------------------------------调用_getResStateVersion($filePath) 讲解start------------------------------------------------------------- 

/*** 计算文件的版本号,这个是根据计算文件MD5散列值得到版本号* 只要文件内容改变了,所计算得到的散列值就会不一样* 用于判断资源文件是否有改动过* @param  [type] $filePath [description]* @return [type]           [description]*/public function _getResStateVersion($filePath) {return base_convert(crc32(md5_file($filePath)), 10, 36);}

-------------------------------调用_getResStateVersion($filePath) 讲解end-------------------------------------------------------------   

 

  或者到版本号后,再看下一段代码,这里开始调用$this->getObjectInfo()方法,这里获取到压缩文件的相对路径$minObject,是否需要压缩$needCompress,版本号$state,文件后缀$extension。

             //获取文件的几个参数值if (true !== $this->getObjectInfo($object, $minObject, $needCompress, $state, $extension)) {continue;}

 

-------------------------------调用$this->getObjectInfo() 讲解start-------------------------------------------------------------  

   /*** 获取资源文件相关信息* @param  [type] $object       资源文件路径 (www/css/home/index.css)* @param  [type] $minObject    压缩资源文件路径 (www/min/css/home/index.ae123a.css)* @param  [type] $needCompress 是否需要压缩* @param  [type] $state        文件版本号* @param  [type] $extension    文件名后缀* @return [type]               [description]*/public function getObjectInfo($object, &$minObject, &$needCompress, &$state, &$extension) {//获取资源绝对路径$filePath = $this->resRootDir . $object;//判断资源是否存在if (!file_exists($filePath)) return "资源文件不存在{$filePath}";//版本号$state = $this-> _getResStateVersion($filePath);//文件名后缀$extension = pathinfo($filePath, PATHINFO_EXTENSION);//是否要压缩$needCompress = true;//判断资源文件是否是以 .min.css或者.min.js结尾的//此类结尾一般都是已压缩过,例如jquery.min.js,就不必再压缩了if (str_end_with($object, '.min.'.$extension, true)) {//压缩后的资源存放路径,放在 /www/min/ 目录下$minObject = 'min/'.substr($object, 0, strlen($object) - strlen($extension) - 4) . $state .'.'. $extension;$needCompress = false;} else if (in_array($extension, $this->compressResDir)) {//此处是需要压缩的文件目录$minObject = 'min/'.substr($object, 0, strlen($object) - strlen($extension)) . $state . '.' . $extension;//看看是否是忽略的路径前缀foreach ($this->compressResIngorePrefix as $v) {if (str_start_with($object, $v, true)) {$needCompress = false;}}} else {$minObject = 'min/'.$object;$needCompress = false;}return true;}

 

  这个方法里的每一行代码基本上都有注释了,所以就不一句句进行讲解了,这里主要看下面的判断部分:

    if (str_end_with($object, '.min.'.$extension, true)) 这个判断是比较资源文件路径字串后面部分是否以 .min.$extension 结尾,例如是 jquery.min.js,这种文件本来就是
压缩过的文件,所以就不用再进行压缩处理了, $minObject 这个变量存放的是压缩后的资源文件路径。
  此处附上str_end_with()函数的代码:
/*** 判断 subject 是否以 search结尾, 参数指定是否忽略大小写* @param  [type]  $subject     [description]* @param  [type]  $search      [description]* @param  boolean $ignore_case [description]* @return [type]               [description]*/function str_end_with($subject, $search, $ignore_case = false) {$len2 = strlen($search);if (0 === $len2) return true;$len1 = strlen($subject);if ($len2 > $len1) return false;if ($ignore_case) {return 0 === strcmp(substr($subject, $len1 - $len2), $search);} else {return 0 === strcasecmp(substr($subject, $len1 - $len2), $search);}}
 

  if (in_array($extension, $this->compressResDir),这个判断就是是否是需要处理的两个目录里的。

  然后里面的foreach ($this->compressResIngorePrefix as $v) { if (str_start_with($object, $v, true)) { $needCompress = false; } } 

这个是判断是否是以$this->compressResIngorePrefix属性定义的前面部分字串开头的路径,是的话就忽略压缩该资源文件。

  判断到最后else 就是说明该资源文件不需要压缩了,最后是返回$minObject,$needCompress,$state,$extension这四个变量。

-------------------------------调用$this->getObjectInfo() 讲解end------------------------------------------------------------- 

 

  到这里继续回来看 compressRes()方法里面的代码    

                //压缩文件的绝对路径$minFilePath = str_replace('\\', '/', $this->resRootDir. $minObject);//************此处p判断是最重要部分之一*****************////判断文件是否存在且已经改动过if (isset($resState[$object]) && $resState[$object] == $state && isset($resState[$minObject]) && file_exists($minFilePath)) {continue;}    

 

  这段代码首先是拼接出压缩文件的绝对路径,

  接着下面这个判断是关键的部分,通过这个判断就可以知道该资源文件是否被改动过,如果改动过的话,就重新对该资源文件进行压缩,假如没改动过,就继续处理下一个资源文件。看这里的判断:isset($resState[$object]) && $resState[$object] == $state,这个判断就是判断该文件路径是否存在  并且文件中对应的版本号和计算出的版本号是否还一致;isset($resState[$minObject]) && file_exists($minFilePath),这个是判断压缩文件路径是否存在,并且该压缩文件是否真实存在目录中。

 

  看下一段代码,如果能走到这一部分,说明目前的这个资源文件是被改动过的(代码修改过),那么此时就对文件进行压缩操作了

                //确保/www/min/目录可写$this->_ensureWritableDir(dirname($minFilePath));if ($needCompress) {$this->compressResFileAndSave($filePath, $minFilePath);} else {copy($filePath, $minFilePath);}

$this->_ensureWritableDir(),此方法是要保证新创建的www/min目录是可写的,这里附上代码:

 

-------------------------------调用$this->_ensureWritableDir() 讲解start------------------------------------------------------------- 

   /*** 确保目录可写* @param  [type] $dir [description]* @return [type]      [description]*/private function _ensureWritableDir($dir) {if (!file_exists($dir)) {@mkdir($dir, 0777, true);@chmod($dir, 0777);} else if (!is_writable($dir)) {@chmod($dir, 0777);if (!is_writable($dir)) {show_error('目录'.$dir.'不可写');}}}

-------------------------------调用$this->_ensureWritableDir() 讲解end------------------------------------------------------------- 

  

  if ($needCompress),这个判断资源文件是否需要压缩,需要的话调用$this->compressResFileAndSave($filePath, $minFilePath);不需要的话,直接复制文件到压缩文件路径 copy($filePath, $minFilePath);

 

  先看$this->compressResFileAndSave()

-------------------------------调用$this->compressResFileAndSave() 讲解start-------------------------------------------------------------

    /*** 将压缩后的资源文件写入到/www/min/下去* @param  [type] $filePath    [description]* @param  [type] $minFilePath [description]* @return [type]              [description]*/private function compressResFileAndSave($filePath, $minFilePath) {if (!file_put_contents($minFilePath, $this->compressResFile($filePath))) {//$CI->exceptions->show_exception("写入文件{$minFilePath}失败");show_error("写入文件{$minFilePath}失败", -1);}}/*** 压缩资源文件* @param  [type] $filePath [description]* @return [type]           [description]*/private function compressResFile($filePath) {$extension = strtolower(pathinfo($filePath, PATHINFO_EXTENSION));if ($extension === 'js') {require_once 'JShrink/Minifier.php';return \JShrink\Minifier::minify(file_get_contents($filePath));} else if ($extension ==='css') {$content = file_get_contents($filePath);$content = preg_replace('!/\*[^*]*\*+([^/][^*]*\*+)*/!', '', $content);$content = str_replace(["\r\n", "\r", "\n"], '', $content);$content = preg_replace('/([{}),;:>])\s+/', '$1', $content);$content = preg_replace('/\s+([{}),;:>])/', '$1', $content);$content = str_replace(';}', '}', $content);return $content;} else {//$CI->exceptions->show_exception("不支持压缩{extension}文件[$filePath]");show_error("不支持压缩{extension}文件[$filePath]", -1);}} 

  先压缩,再将压缩后的内容写入到 压缩文件路径里去。

  我们先看下这个压缩方法:

    $this->compressResFile($filePath); 此方法中分两类压缩,第一类时对js文件进行压缩,第二类的对css文件进行压缩。先说js压缩,这里是调用一个JShrink的类,它

一个用来压缩js文件的PHP类,百度可以找到,调用这个类的minify()这个方法就可以压缩了;而css的压缩利用正则替换来压缩,把那些空格换行什么的都去掉。到此就压缩成功

了,然后再将压缩后的资源写入到对应的压缩文件路径里去。

 

-------------------------------调用$this->compressResFileAndSave() 讲解end-------------------------------------------------------------

  

  接着继续看compressRes()这个方法里的代码,这里开始就是保存新的版本号到$resState数组里 $object=>$state,还有就是新的压缩路径$minObject,而这里$count++的作用是,当这个循环50次就将 $resState这个数组写入一次到 resState.php文件里,这里是出于严谨考虑而已,如果你不加这个 $count的处理这部分也可以,最后写入一次就行了。

 

                $resState[$object] = $state;$resState[$minObject] = '';$count++;if ($count == 50) {$this->_saveResState($resState);$count = 0;}}}if($count) $this->_saveResState($resState);         

 

  这里看$this->_saveResState($resState),这个方法就是将$resState数组写入到resState.php文件里去的方法。

 

-------------------------------调用$this->_saveResState($resState) 讲解start-------------------------------------------------------------

    private function _saveResState($resState) {ksort($resState);$content = "<?php\n\n\$resState = array(\n";foreach ($resState as $k => $v) {$content .= "\t '$k' => '$v',\n";}$content .= ");\n\n";file_put_contents($this->resStatePath, $content); }    

-------------------------------调用$this->_saveResState($resState) 讲解end------------------------------------------------------------- 

   处理完后,看看所生成的文件,这里一个文件会有多个版本,旧版本没有删除掉,在开发环境下删不删除都没问题,这里为何不删除旧版本的压缩文件,这就涉及到在更新多个应用服务器代码时所要注意的问题里。在此我就多讲解一点吧,简单地举个例子吧,一般大型项目中的静态资源和模板文件是部署在不同的机器集群上的,上线的过程中,静态资源和页面文件的部署时间间隔可能会非常长,对于一个大型互联网应用来说即使在一个很小的时间间隔内,都有可能出现新用户访问,假如旧版本的静态资源删除了,但新版本的静态资源还没部署完成,那么用户就加载不到该静态资源,结果可想而知,所以,一般情况下我们会保留旧版本的静态资源,然后等所有一些部署完成了,再通过一定的脚本删除掉也没关系,其实,这些不必删除也是可以的,你想想,一个项目发一次版本,才会调用一次资源文件压缩方法,它只会对修改过的文件进行生成新版本号的静态文件而已。这些就看个人的做法了。

  我们可以打开看看,下面这个就是压缩后的文件的代码了,文件原大小为16K,压缩后大概少了5K,现在是11K,压缩比大概是2/3,假如在大型项目中,一个复杂点的页面会有很大的静态资源文件要加载,通过此方法,大大地提高了加载的速度。(可能有些朋友觉得压缩个几K或者十几K算什么,完全可以忽略,其实我想说的是,当你在大型项目中优化项目的时候,能够减少几K的代码,也给网站的性能提高了一大截)

 

  到此,资源压缩处理就分析完毕了。其实,有一定基础的朋友,可以直接看我分享的那个代码就可以了,假如理解不了,再看我上面这一步步的分析讲解,我是处于能看来到此博客的朋友,无论技术是好或者是稍弱,都能看懂,所以才对代码一步步地进行分析讲解。(希望各位多多支持小弟)

 -------------------------------------------------------------------------------------------------------------------------

  3. 接下来就是讲解如何替换压缩后的资源文件了。

  这个到Home.php

 1 <?php
 2 defined('BASEPATH') OR exit('No direct script access allowed');
 3 
 4 class Home extends MY_Controller {
 5     public function index() {
 6         $this->smartyData['test'] = 111;
 7         //这个默认是加载 www/css/home/index.css文件
 8         $this->addResLink('index.css');
 9         //这个默认是加载www/js/jquery.all.min.js文件
10         $this->addResLink('/jquery.all.min.js');
11         //这个默认是加载www/js/index.js文件
12         $this->addResLink('index.js');
13         $this->displayView('home/index.tpl');
14     }
15 }

  上面有加载三个资源文件,我们先看看$this->addResLink();这个方法,这个方法放在My_Controller.php里:

    /*** 资源路径* @param [type] $filePath [description]*/protected function addResLink($filePath) {list($filePath, $query) = explode('?', $filePath . '?');$extension = strtolower(pathinfo($filePath, PATHINFO_EXTENSION));foreach ($this->_resLink as $v) {if (false === array_search($filePath, $this->_resLink[$extension])) {$this->_resLink[$extension][] = $query == null ? $filePath : $filePath .'?'. $query;}}return $this;}

  这里主要是判断了资源文件是css还是js,然后将其存放在 $this->_resLink这个属性里。

  那么此处我就先附上My_Controller.php这个父类的所有代码吧

  1 <?php
  2 defined('BASEPATH') OR exit('No direct script access allowed');
  3 
  4 class MY_Controller extends CI_Controller {
  5     public function __construct() {
  6         parent::__construct();
  7 
  8         //压缩jscss资源文件
  9         $this->compressResHandle();
 10     }
 11 
 12     //==========================使用SMARTY模板引擎================================//
 13     /* Smarty母版页文件路径 */
 14     protected $masterPage = 'default.tpl';
 15     /* 视图文件路径*/
 16     protected $smartyView;
 17     /* 要赋值给smarty视图的数据*/
 18     protected $smartyData = [];
 19     /* 资源文件*/
 20     protected $_resLink = ['js'=>[], 'css'=>[]];
 21 
 22     /**
 23      * 使用母版页输出一个视图
 24      * @return [type] [description]
 25      */
 26     protected function displayView($viewName = null, $masterPage = null) {
 27         //为空则选用默认母版
 28         if ($masterPage == null) $masterPage = $this->masterPage;
 29         //获取视图的输出内容
 30         $viewContent = $this->_fetchView($this->smartyData, $viewName, $masterPage);
 31 
 32         $output = '';
 33         
 34         //添加css Link
 35         foreach ($this->_resLink['css'] as $v) {
 36             $output .= res_link($v);
 37         }
 38 
 39         //内容部分
 40         $output .= $viewContent;
 41         //尾部添加js 链接
 42         foreach ($this->_resLink['js'] as $v) {
 43             $output .= res_link($v);
 44         }
 45         //发送最终输出结果以及服务器的 HTTP 头到浏览器
 46         
 47         $this->output->_display($output);
 48         return $output;
 49     }
 50 
 51     private function _fetchView($smartyData, &$viewName, &$masterPage) {
 52         if ($viewName == null) $viewName = $this->smartyView;
 53 
 54         if (empty($this->smarty)) {
 55             require_once SMARTY_DIR.'Smarty.class.php';
 56             $this->smarty = new Smarty();
 57             $this->smarty->setCompileDir(APPPATH . 'cache/');
 58             $this->smarty->setCacheDir(APPPATH . 'cache/');
 59         }
 60 
 61         //设置视图真实路径
 62         $this->_getViewDir(true, $viewName, $masterPage, $templateDir);
 63 
 64         foreach ($smartyData as $k => $v) {
 65             $this->smarty->assign($k, $v);
 66         }
 67 
 68         if (empty($masterPage)) {
 69             return $this->smarty->fetch($viewName);
 70         } else {
 71             $this->smarty->assign('VIEW_MAIN', $viewName);
 72             return $this->smarty->fetch($masterPage);
 73         }
 74     }
 75 
 76     /**
 77      * 资源路径
 78      * @param [type] $filePath [description]
 79      */
 80     protected function addResLink($filePath) {
 81         list($filePath, $query) = explode('?', $filePath . '?');
 82         $extension = strtolower(pathinfo($filePath, PATHINFO_EXTENSION));
 83         foreach ($this->_resLink as $v) {
 84             if (false === array_search($filePath, $this->_resLink[$extension])) {
 85                 $this->_resLink[$extension][] = $query == null ? $filePath : $filePath .'?'. $query;
 86             }
 87         }
 88 
 89         return $this;
 90     }
 91 
 92     private function _getViewDir($setTemplateDir, &$viewName, &$masterPage = null, &$templateDir) {
 93         if ('/' === $viewName[0]) $viewName = substr($viewName, 1);
 94 
 95         //是否使用模板,有,则路由到 /views/master_page/*****.tpl下去
 96         if ($masterPage) {
 97             $masterPage = '/' === $masterPage[0] ? substr($masterPage, 1) : ('master_page' .'/'. $masterPage);
 98         }
 99 
100         //是否设置模板目录
101         if ($setTemplateDir) {
102             $templateDir = VIEWPATH;
103             $this->smarty->setTemplateDir($templateDir);
104         }
105     }
106 
107     /**
108      * 压缩js、css资源文件(优化)
109      * @return [type] [description]
110      */
111     private function compressResHandle() {
112         $this->load->library('ResMinifier');
113         //压缩指定文件夹下的资源文件
114         $this->resminifier->compressRes();
115     }
116 }
点击打开 My_Controller.php

   打印出来 $this->_resLink这个属性的结构是这样子的:

Array
([js] => Array([0] => /jquery.all.min.js[1] => index.js)[css] => Array([0] => index.css))

  再回到Home.php里面调用 $this->displayView('home/index.tpl');

  我们看这个方法:

     /*** 使用母版页输出一个视图* @return [type] [description]*/protected function displayView($viewName = null, $masterPage = null) {//为空则选用默认母版if ($masterPage == null) $masterPage = $this->masterPage;//获取视图的输出内容$viewContent = $this->_fetchView($this->smartyData, $viewName, $masterPage);$output = '';//添加css Linkforeach ($this->_resLink['css'] as $v) {$output .= res_link($v);}//内容部分$output .= $viewContent;//尾部添加js 链接foreach ($this->_resLink['js'] as $v) {$output .= res_link($v);}//发送最终输出结果以及服务器的 HTTP 头到浏览器$this->output->_display($output);return $output;}private function _fetchView($smartyData, &$viewName, &$masterPage) {if ($viewName == null) $viewName = $this->smartyView;if (empty($this->smarty)) {require_once SMARTY_DIR.'Smarty.class.php';$this->smarty = new Smarty();$this->smarty->setCompileDir(APPPATH . 'cache/');$this->smarty->setCacheDir(APPPATH . 'cache/');}//设置视图真实路径$this->_getViewDir(true, $viewName, $masterPage, $templateDir);foreach ($smartyData as $k => $v) {$this->smarty->assign($k, $v);}if (empty($masterPage)) {return $this->smarty->fetch($viewName);} else {$this->smarty->assign('VIEW_MAIN', $viewName);return $this->smarty->fetch($masterPage);}}

  这一段代码没有一部分就是调用了Smarty模板引擎的内容,这个有关Smarty的知识我就不讲了,大家可以自己百度,这里主要讲 res_link() 这个函数,就是通过这个函数来进行资源文件替换的。先看这个函数的代码:

    /*** 输出 HttpHead 中的资源连接。 css/js 自动判断真实路径* @param  string  文件路径* @return string      */function res_link($file) {$file = res_path($file, $extension);if ($extension === 'css') {return '<link rel="stylesheet" type="text/css" href="' . $file . '"/>';} else if ($extension === 'js') {return '<script type="text/javascript" src="'.$file.'"></script>';} else {return false;}}

   此处最重要就是 res_path() 函数了,这个函数能自动路由资源的真实路径 。例如:index.css = > css/home/index.css

  该函数最重要的一个功能是替换资源的压缩版本。

  直接看代码:

   /*** 智能路由资源真实路径* @param  string      路径* @param  string      扩展名* @return string       真实路径*/function res_path($file, &$extension) {//检查是否存在查询字符串list($file, $query) = explode('?', $file . '?');//取得扩展名$extension = strtolower(pathinfo($file, PATHINFO_EXTENSION));//
        $file = str_replace('\\', '/', $file);//取得当前控制器名global $class;if ($class == null) exit('can not get class name');$className = strtolower($class);//此处的规则是这样://例如,如果不加 / ,Home控制器对应的格式是: index.css,那么 此处的路径会变成css/home/index.css//假如有 / ,控制器的格式可以是 /main.css,那么此处的路径会变成 css/main.css(公用的css类)if ('/' !== $file[0]) {//index.css => css/home/index.css$object = $extension .'/'. $className .'/' . $file;} else {// /css/main.css 或者 /main.css => css/main.css$object = substr($file, 1);//若object是 main.css ,则自动加上 扩展名目录 => css/main.cssif (0 !== strncasecmp($extension, $object, strlen($extension))) {$object = $extension . '/' . $object;}}//资源真实路径$filepath = WEBROOT.'www/'.$object;//替换压缩版本,这部分逻辑与文件压缩逻辑对应if (in_array($extension, array('css', 'js'))) {if(!str_start_with($object, 'min/') && file_exists(APPPATH.'libraries/ResMinifier.php')) {require_once APPPATH.'libraries/ResMinifier.php';$resminifier = new ResMinifier();//获取存放资源版本的文件的数组变量$resState = $resminifier->getResState();//计算得到当前文件版本号$state = $resminifier->_getResStateVersion($filepath);//判断该版本号是否存在if (isset($resState[$object])) {//判断是否是.min.css或.min.js结尾if (str_end_with($object, '.min.'.$extension)) {//将版本号拼接上去,然后得到min的文件路径$minObject = 'min/'.substr($object, 0, strlen($object) - strlen($extension) - 4) . $state . '.' . $extension;} else {//将版本号拼接上去,然后得到min的文件路径$minObject = 'min/'.substr($object, 0, strlen($object) - strlen($extension)) . $state . '.' . $extension;}//判断min的路径是否存在在$resState里面if (isset($resState[$minObject])) {$object = $minObject;$query = '';}} }$file = RES_BASE_URL . $object;}return ($query == null) ? $file : ($file .'?'. $query);}

  代码基本上都给了注释,方便大家容易去理解,前面一部分是智能路径css、js资源的路径,后面一部分是替换压缩版本,这一部分的逻辑其实和资源压缩那里的逻辑基本一样,就是通过资源文件路径,进行判断和处理,最后得到资源的压缩版本的路径,最后就将资源的压缩版本的路径返回去,放在'<link rel="stylesheet" type="text/css" href="' . $file . '"/>'里面。这样  ,就成功地将资源文件路径替换成了压缩版本的资源文件路径,并且在模板输出时,输出的是压缩后的资源文件。

  到此,资源替换的内容就到此讲解完毕。而整一项技术也分析到此。

  三、总结

  在这里我集中地附上本博文讲解中的几个文件代码:

  Home.php

 1 <?php
 2 defined('BASEPATH') OR exit('No direct script access allowed');
 3 
 4 class Home extends MY_Controller {
 5     public function index() {
 6         $this->smartyData['test'] = 111;
 7         //这个默认是加载 www/css/home/index.css文件
 8         $this->addResLink('index.css');
 9         //这个默认是加载www/js/jquery.all.min.js文件
10         $this->addResLink('/jquery.all.min.js');
11         //这个默认是加载www/js/index.js文件
12         $this->addResLink('index.js');
13         $this->displayView('home/index.tpl');
14     }
15 }
点击打开

  My_Controller.php

  1 <?php
  2 defined('BASEPATH') OR exit('No direct script access allowed');
  3 
  4 class MY_Controller extends CI_Controller {
  5     public function __construct() {
  6         parent::__construct();
  7 
  8         //压缩jscss资源文件
  9         $this->compressResHandle();
 10     }
 11 
 12     //==========================使用SMARTY模板引擎================================//
 13     /* Smarty母版页文件路径 */
 14     protected $masterPage = 'default.tpl';
 15     /* 视图文件路径*/
 16     protected $smartyView;
 17     /* 要赋值给smarty视图的数据*/
 18     protected $smartyData = [];
 19     /* 资源文件*/
 20     protected $_resLink = ['js'=>[], 'css'=>[]];
 21 
 22     /**
 23      * 使用母版页输出一个视图
 24      * @return [type] [description]
 25      */
 26     protected function displayView($viewName = null, $masterPage = null) {
 27         //为空则选用默认母版
 28         if ($masterPage == null) $masterPage = $this->masterPage;
 29         //获取视图的输出内容
 30         $viewContent = $this->_fetchView($this->smartyData, $viewName, $masterPage);
 31 
 32         $output = '';
 33         
 34         //添加css Link
 35         foreach ($this->_resLink['css'] as $v) {
 36             $output .= res_link($v);
 37         }
 38 
 39         //内容部分
 40         $output .= $viewContent;
 41         //尾部添加js 链接
 42         foreach ($this->_resLink['js'] as $v) {
 43             $output .= res_link($v);
 44         }
 45         //发送最终输出结果以及服务器的 HTTP 头到浏览器
 46         
 47         $this->output->_display($output);
 48         return $output;
 49     }
 50 
 51     private function _fetchView($smartyData, &$viewName, &$masterPage) {
 52         if ($viewName == null) $viewName = $this->smartyView;
 53 
 54         if (empty($this->smarty)) {
 55             require_once SMARTY_DIR.'Smarty.class.php';
 56             $this->smarty = new Smarty();
 57             $this->smarty->setCompileDir(APPPATH . 'cache/');
 58             $this->smarty->setCacheDir(APPPATH . 'cache/');
 59         }
 60 
 61         //设置视图真实路径
 62         $this->_getViewDir(true, $viewName, $masterPage, $templateDir);
 63 
 64         foreach ($smartyData as $k => $v) {
 65             $this->smarty->assign($k, $v);
 66         }
 67 
 68         if (empty($masterPage)) {
 69             return $this->smarty->fetch($viewName);
 70         } else {
 71             $this->smarty->assign('VIEW_MAIN', $viewName);
 72             return $this->smarty->fetch($masterPage);
 73         }
 74     }
 75 
 76     /**
 77      * 资源路径
 78      * @param [type] $filePath [description]
 79      */
 80     protected function addResLink($filePath) {
 81         list($filePath, $query) = explode('?', $filePath . '?');
 82         $extension = strtolower(pathinfo($filePath, PATHINFO_EXTENSION));
 83         foreach ($this->_resLink as $v) {
 84             if (false === array_search($filePath, $this->_resLink[$extension])) {
 85                 $this->_resLink[$extension][] = $query == null ? $filePath : $filePath .'?'. $query;
 86             }
 87         }
 88 
 89         return $this;
 90     }
 91 
 92     private function _getViewDir($setTemplateDir, &$viewName, &$masterPage = null, &$templateDir) {
 93         if ('/' === $viewName[0]) $viewName = substr($viewName, 1);
 94 
 95         //是否使用模板,有,则路由到 /views/master_page/*****.tpl下去
 96         if ($masterPage) {
 97             $masterPage = '/' === $masterPage[0] ? substr($masterPage, 1) : ('master_page' .'/'. $masterPage);
 98         }
 99 
100         //是否设置模板目录
101         if ($setTemplateDir) {
102             $templateDir = VIEWPATH;
103             $this->smarty->setTemplateDir($templateDir);
104         }
105     }
106 
107     /**
108      * 压缩js、css资源文件(优化)
109      * @return [type] [description]
110      */
111     private function compressResHandle() {
112         $this->load->library('ResMinifier');
113         //压缩指定文件夹下的资源文件
114         $this->resminifier->compressRes();
115     }
116 }
点击打开

  ResMinifier.php

  1 <?php 
  2 defined('BASEPATH') OR exit('No direct script access allowed');
  3 /**
  4  * 资源压缩类
  5  */
  6 class ResMinifier {
  7     /** 需要压缩的资源目录*/
  8     public $compressResDir = ['css', 'js'];
  9     /** 忽略压缩的路径,例如此处是js/icon开头的路径忽略压缩*/
 10     public $compressResIngorePrefix = ['js/icon'];
 11     /** 资源根目录*/
 12     public $resRootDir;
 13     /** 资源版本文件路径*/
 14     private $resStatePath;
 15 
 16     public function __construct() {
 17         $this->resRootDir = WEBROOT . 'www/';
 18         $this->resStatePath = WEBROOT . 'www/resState.php';
 19     }
 20 
 21     public function compressRes() {
 22         //获取存放版本的资源文件
 23         $resState = $this->getResState();
 24         $count = 0;
 25 
 26         //开始遍历需要压缩的资源目录
 27         foreach ($this->compressResDir as $resDir) {
 28             foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($this->resRootDir . $resDir , FilesystemIterator::SKIP_DOTS)) as $file) {
 29                 //获取该资源文件的绝对路径
 30                 $filePath = str_replace('\\', '/', $file->getRealPath());
 31 
 32                 //获取文件相对路径
 33                 $object = substr($filePath, strlen($this->resRootDir));
 34 
 35                 //计算文件的版本号
 36                 $state = $this->_getResStateVersion($filePath);
 37 
 38                 //获取文件的几个参数值
 39                 if (true !== $this->getObjectInfo($object, $minObject, $needCompress, $state, $extension)) {
 40                     continue;
 41                 }
 42 
 43                 //压缩文件的绝对路径
 44                 $minFilePath = str_replace('\\', '/', $this->resRootDir. $minObject);
 45 
 46                 //************此处p判断是最重要部分之一*****************//
 47                 //判断文件是否存在且已经改动过
 48                 if (isset($resState[$object]) && $resState[$object] == $state && isset($resState[$minObject]) && file_exists($minFilePath)) {
 49                     continue;
 50                 }
 51 
 52                 //确保/www/min/目录可写
 53                 $this->_ensureWritableDir(dirname($minFilePath));
 54 
 55                 if ($needCompress) {
 56                     $this->compressResFileAndSave($filePath, $minFilePath);
 57                 } else {
 58                     copy($filePath, $minFilePath);
 59                 }
 60 
 61 
 62                 $resState[$object] = $state;
 63                 $resState[$minObject] = '';
 64                 $count++;
 65 
 66                 if ($count == 50) {
 67                     $this->_saveResState($resState);
 68                     $count = 0;
 69                 }
 70 
 71             }
 72         }
 73         if($count) $this->_saveResState($resState);
 74     }
 75 
 76     /**
 77      * 获取资源文件相关信息
 78      * @param  [type] $object       资源文件路径 (www/css/home/index.css)
 79      * @param  [type] $minObject    压缩资源文件路径 (www/min/css/home/index.ae123a.css)
 80      * @param  [type] $needCompress 是否需要压缩
 81      * @param  [type] $state        文件版本号
 82      * @param  [type] $extension    文件名后缀
 83      * @return [type]               [description]
 84      */
 85     public function getObjectInfo($object, &$minObject, &$needCompress, &$state, &$extension) {
 86         //获取资源绝对路径
 87         $filePath = $this->resRootDir . $object;
 88         //判断资源是否存在
 89         if (!file_exists($filePath)) return "资源文件不存在{$filePath}";
 90         //版本号
 91         $state = $this-> _getResStateVersion($filePath);
 92         //文件名后缀
 93         $extension = pathinfo($filePath, PATHINFO_EXTENSION);
 94         //是否要压缩
 95         $needCompress = true;
 96 
 97         //判断资源文件是否是以 .min.css或者.min.js结尾的
 98         //此类结尾一般都是已压缩过,例如jquery.min.js,就不必再压缩了
 99         if (str_end_with($object, '.min.'.$extension, true)) {
100             //压缩后的资源存放路径,放在 /www/min/ 目录下
101             $minObject = 'min/'.substr($object, 0, strlen($object) - strlen($extension) - 4) . $state .'.'. $extension;
102             $needCompress = false;
103         } else if (in_array($extension, $this->compressResDir)) {
104             //此处是需要压缩的文件目录
105             $minObject = 'min/'.substr($object, 0, strlen($object) - strlen($extension)) . $state . '.' . $extension;
106             //看看是否是忽略的路径前缀
107             foreach ($this->compressResIngorePrefix as $v) {
108                 if (str_start_with($object, $v, true)) {
109                     $needCompress = false;
110                 }
111             }
112         } else {
113             $minObject = 'min/'.$object;
114             $needCompress = false;
115         }
116         return true;
117     }
118 
119 
120     /**
121      * 获取存放资源版本的文件
122      * 它是放在一个数组里
123      * $resState = array(
124      *         '文件路径' => '对应的版本号',
125      *         '文件路径' => '对应的版本号',
126      *         '文件路径' => '对应的版本号',
127      *     );
128      * @return [type] [description]
129      */
130     public function getResState() {
131         if (file_exists($this->resStatePath)) {
132             require $this->resStatePath;
133             return $resState;
134         }
135         return [];
136     }
137 
138     /**
139      * 计算文件的版本号,这个是根据计算文件MD5散列值得到版本号
140      * 只要文件内容改变了,所计算得到的散列值就会不一样
141      * 用于判断资源文件是否有改动过
142      * @param  [type] $filePath [description]
143      * @return [type]           [description]
144      */
145     public function _getResStateVersion($filePath) {
146         return base_convert(crc32(md5_file($filePath)), 10, 36);
147     }
148 
149     /**
150      * 确保目录可写
151      * @param  [type] $dir [description]
152      * @return [type]      [description]
153      */
154     private function _ensureWritableDir($dir) {
155         if (!file_exists($dir)) {
156             @mkdir($dir, 0777, true);
157             @chmod($dir, 0777);
158         } else if (!is_writable($dir)) {
159             @chmod($dir, 0777);
160             if (!is_writable($dir)) {
161                 show_error('目录'.$dir.'不可写');
162             }
163         }
164     }
165 
166     /**
167      * 将压缩后的资源文件写入到/www/min/下去
168      * @param  [type] $filePath    [description]
169      * @param  [type] $minFilePath [description]
170      * @return [type]              [description]
171      */
172     private function compressResFileAndSave($filePath, $minFilePath) {
173         if (!file_put_contents($minFilePath, $this->compressResFile($filePath))) {
174 
175             //$CI->exceptions->show_exception("写入文件{$minFilePath}失败");
176             show_error("写入文件{$minFilePath}失败", -1);
177         }
178     }
179 
180     /**
181      * 压缩资源文件
182      * @param  [type] $filePath [description]
183      * @return [type]           [description]
184      */
185     private function compressResFile($filePath) {
186         $extension = strtolower(pathinfo($filePath, PATHINFO_EXTENSION));
187         if ($extension === 'js') {
188             require_once 'JShrink/Minifier.php';
189             return \JShrink\Minifier::minify(file_get_contents($filePath));
190         } else if ($extension ==='css') {
191             $content = file_get_contents($filePath);
192             $content = preg_replace('!/\*[^*]*\*+([^/][^*]*\*+)*/!', '', $content);
193             $content = str_replace(["\r\n", "\r", "\n"], '', $content);
194             $content = preg_replace('/([{}),;:>])\s+/', '$1', $content);
195             $content = preg_replace('/\s+([{}),;:>])/', '$1', $content);
196             $content = str_replace(';}', '}', $content);
197             return $content;
198         } else {
199             //$CI->exceptions->show_exception("不支持压缩{extension}文件[$filePath]");
200             show_error("不支持压缩{extension}文件[$filePath]", -1);
201 
202         }
203     }
204 
205     private function _saveResState($resState) {
206         ksort($resState);
207         $content = "<?php\n\n\$resState = array(\n";
208         foreach ($resState as $k => $v) {
209             $content .= "\t '$k' => '$v',\n";
210         }
211         $content .= ");\n\n";
212         file_put_contents($this->resStatePath, $content); 
213     }
214 
215 }
点击打开

  Common.php

  1 <?php 
  2     /**
  3      * 输出 HttpHead 中的资源连接。 css/js 自动判断真实路径
  4      * @param  string  文件路径
  5      * @return string      
  6      */
  7     function res_link($file) {
  8         $file = res_path($file, $extension);
  9 
 10         if ($extension === 'css') {
 11            return '<link rel="stylesheet" type="text/css" href="' . $file . '"/>';
 12         } else if ($extension === 'js') {
 13             return '<script type="text/javascript" src="'.$file.'"></script>';
 14         } else {
 15             return false;
 16         }
 17     }
 18 
 19     /**
 20      * 智能路由资源真实路径
 21      * @param  string      路径
 22      * @param  string      扩展名
 23      * @return string       真实路径
 24      */
 25     function res_path($file, &$extension) {
 26         //检查是否存在查询字符串
 27         list($file, $query) = explode('?', $file . '?');
 28         //取得扩展名
 29         $extension = strtolower(pathinfo($file, PATHINFO_EXTENSION));
 30         //
 31         $file = str_replace('\\', '/', $file);
 32         //取得当前控制器名
 33         global $class;
 34         if ($class == null) exit('can not get class name');
 35         $className = strtolower($class);
 36 
 37         //此处的规则是这样:
 38         //例如,如果不加 / ,Home控制器对应的格式是: index.css,那么 此处的路径会变成css/home/index.css
 39         //假如有 / ,控制器的格式可以是 /main.css,那么此处的路径会变成 css/main.css(公用的css类)
 40         if ('/' !== $file[0]) {
 41             //index.css => css/home/index.css
 42             $object = $extension .'/'. $className .'/' . $file;
 43         } else {
 44             // /css/main.css 或者 /main.css => css/main.css
 45             $object = substr($file, 1);
 46 
 47             //若object是 main.css ,则自动加上 扩展名目录 => css/main.css
 48             if (0 !== strncasecmp($extension, $object, strlen($extension))) {
 49                 $object = $extension . '/' . $object;
 50             }
 51         }
 52         //资源真实路径
 53         $filepath = WEBROOT.'www/'.$object;
 54         
 55         //替换压缩版本,这部分逻辑与文件压缩逻辑对应
 56         if (in_array($extension, array('css', 'js'))) {
 57             if(!str_start_with($object, 'min/') && file_exists(APPPATH.'libraries/ResMinifier.php')) {
 58                 require_once APPPATH.'libraries/ResMinifier.php';
 59                 $resminifier = new ResMinifier();
 60                 //获取存放资源版本的文件的数组变量
 61                 $resState = $resminifier->getResState();
 62                 //计算得到当前文件版本号
 63                 $state = $resminifier->_getResStateVersion($filepath);
 64                 //判断该版本号是否存在
 65                 if (isset($resState[$object])) {
 66                     //判断是否是.min.css或.min.js结尾
 67                     if (str_end_with($object, '.min.'.$extension)) {
 68                         //将版本号拼接上去,然后得到min的文件路径
 69                         $minObject = 'min/'.substr($object, 0, strlen($object) - strlen($extension) - 4) . $state . '.' . $extension;
 70                     } else {
 71                         //将版本号拼接上去,然后得到min的文件路径
 72                         $minObject = 'min/'.substr($object, 0, strlen($object) - strlen($extension)) . $state . '.' . $extension;
 73                     }
 74                     //判断min的路径是否存在在$resState里面
 75                      if (isset($resState[$minObject])) {
 76                         $object = $minObject;
 77                         $query = '';
 78                      }
 79                 } 
 80 
 81             }
 82             
 83             $file = RES_BASE_URL . $object;
 84         }
 85 
 86         return ($query == null) ? $file : ($file .'?'. $query);
 87 
 88     }
 89 
 90     /**
 91      * 判断 subject 是否以 search开头, 参数指定是否忽略大小写
 92      * @param  [type]  $subject     [description]
 93      * @param  [type]  $search      [description]
 94      * @param  boolean $ignore_case [description]
 95      * @return [type]               [description]
 96      */
 97     function str_start_with($subject, $search, $ignore_case = false) {
 98         $len2 = strlen($search);
 99         if (0 === $len2) return true;
100         $len1 = strlen($subject);
101         if ($len1 < $len2) return false;
102         if ($ignore_case) {
103             return 0 === strncmp($subject, $search, $len2);
104         } else {
105             return 0 === strncasecmp($subject, $search, $len2);
106         }
107     }
108 
109     /**
110      * 判断 subject 是否以 search结尾, 参数指定是否忽略大小写
111      * @param  [type]  $subject     [description]
112      * @param  [type]  $search      [description]
113      * @param  boolean $ignore_case [description]
114      * @return [type]               [description]
115      */
116     function str_end_with($subject, $search, $ignore_case = false) {
117         $len2 = strlen($search);
118         if (0 === $len2) return true;
119         $len1 = strlen($subject);
120         if ($len2 > $len1) return false;
121         if ($ignore_case) {
122             return 0 === strcmp(substr($subject, $len1 - $len2), $search);
123         } else {
124             return 0 === strcasecmp(substr($subject, $len1 - $len2), $search);
125         }
126     }
点击打开

  $resState.php(里面的代码是自动生成的)

 1 <?php
 2 
 3 $resState = array(
 4      'css/home/index.css' => 'gwy933',
 5      'js/echarts-all.min.js' => 'wqrf1c',
 6      'js/home/index.js' => 's2z6f5',
 7      'js/icon.js' => 'pgcyih',
 8      'js/icon_home.js' => 'zhl9iu',
 9      'js/ion.rangeSlider.min.js' => 'akq381',
10      'js/jquery-ui-autocomplete.js' => '8nzacv',
11      'js/jquery-ui.min.js' => 'i6tw8z',
12      'js/jquery.all.min.js' => 'd2w76v',
13      'js/jquery.city.js' => 'toxdrf',
14      'js/jquery.easydropdown.min.js' => '2ni3i0',
15      'js/jquery.matrix.js' => '3vrqkk',
16      'js/jquery.mobile.all.min.js' => 'ernu7r',
17      'js/jquery.qrcode.min.js' => 'yuhnsj',
18      'js/jquery.tinyscrollbar.min.js' => 'oakk3c',
19      'js/mobiscroll.custom.min.js' => 'kn8h2e',
20      'js/store.min.js' => 'n50jwr',
21      'js/swiper.animate1.0.2.min.js' => 'mm27zc',
22      'js/swiper.min.js' => 'jicwhh',
23      'min/css/home/index.6a4e83eb.css' => '',
24      'min/css/home/index.gwy933.css' => '',
25      'min/css/home/index.puzbnf.css' => '',
26      'min/css/home/index.thv8x7.css' => '',
27      'min/js/echarts-all.76025ee0.js' => '',
28      'min/js/echarts-all.wqrf1c.js' => '',
29      'min/js/home/index.65363d41.js' => '',
30      'min/js/home/index.s2z6f5.js' => '',
31      'min/js/icon.5bbd4db9.js' => '',
32      'min/js/icon.pgcyih.js' => '',
33      'min/js/icon_home.7fe74076.js' => '',
34      'min/js/icon_home.zhl9iu.js' => '',
35      'min/js/ion.rangeSlider.261d8ed1.js' => '',
36      'min/js/ion.rangeSlider.akq381.js' => '',
37      'min/js/jquery-ui-autocomplete.1f3bb62f.js' => '',
38      'min/js/jquery-ui-autocomplete.8nzacv.js' => '',
39      'min/js/jquery-ui.418e9683.js' => '',
40      'min/js/jquery-ui.i6tw8z.js' => '',
41      'min/js/jquery.all.2f248267.js' => '',
42      'min/js/jquery.all.d2w76v.js' => '',
43      'min/js/jquery.city.6b036feb.js' => '',
44      'min/js/jquery.city.toxdrf.js' => '',
45      'min/js/jquery.easydropdown.2ni3i0.js' => '',
46      'min/js/jquery.easydropdown.98fa138.js' => '',
47      'min/js/jquery.matrix.3vrqkk.js' => '',
48      'min/js/jquery.matrix.dfe2a44.js' => '',
49      'min/js/jquery.mobile.all.3539ebb7.js' => '',
50      'min/js/jquery.mobile.all.ernu7r.js' => '',
51      'min/js/jquery.qrcode.7d9738b3.js' => '',
52      'min/js/jquery.qrcode.yuhnsj.js' => '',
53      'min/js/jquery.tinyscrollbar.578e4cb8.js' => '',
54      'min/js/jquery.tinyscrollbar.oakk3c.js' => '',
55      'min/js/mobiscroll.custom.4a684f66.js' => '',
56      'min/js/mobiscroll.custom.kn8h2e.js' => '',
57      'min/js/store.536545cb.js' => '',
58      'min/js/store.n50jwr.js' => '',
59      'min/js/swiper.4650ad75.js' => '',
60      'min/js/swiper.animate1.0.2.517f82e8.js' => '',
61      'min/js/swiper.animate1.0.2.mm27zc.js' => '',
62      'min/js/swiper.jicwhh.js' => '',
63 );
点击打开

  另外附上JShrink这个PHP类的链接给大家下载 http://pan.baidu.com/s/1gd12JT5

  要是大家还是觉得不够OK的话,我直接将这个实验项目打包供大家下载下来学习和了解:http://pan.baidu.com/s/1o6OUYPO

    四、结语

  最后我来分享我们线上项目的具体实现方案:

  我们的项目分线上环境、开发环境和测试环境,在开发和测试环境中,我们每一次访问都会调用压缩文件的接口,然后再对生成的资源文件的大小是要做判断的,如果压缩后文件过小,就要求将该资源文件的代码合并到其他资源文件里去,以此减少不必要的HTTP请求(因为文件太小,资源的下载时间远远小于HTTP请求响应所消耗的时间);另一个是图片的处理,所有图片都要经过压缩才能通过(例如在:https://tinypng.com/  这个网站去压缩图片),在PC端,如果是小图标的话,使用图片合并的方式进行优化,详情可参考本人的这篇博文:http://www.cnblogs.com/it-cen/p/4618954.html    而在wap端的图片处理采用的是base64编码方式来处理图片,详情可以参考本人的这篇博文:http://www.cnblogs.com/it-cen/p/4624939.html  ,当页面输出时,会使用redis来缓存页面(为啥用内存来缓存而不是采用页面缓存,这个以后再分享给大家)。如果是线上环境,每发一次版本,才会调用一下资源文件压缩这个接口,并且线上的静态资源(css、js、图片)是存放在阿里云的OSS里的,与我们的应用服务器是分开的。这是我们线上项目的一部分优化解决方案,当然了,还有更多优化技术,我会在以后一一总结和分享出来,方便大家一起学习和交流。

  本次博文就分享到此,谢谢阅览此博文的朋友们。

 

 

 

  如果此博文中有哪里讲得让人难以理解,欢迎留言交流,若有讲解错的地方欢迎指出。

  

  如果您觉得您能在此博文学到了新知识,请为我顶一个,如文章中有解释错的地方,欢迎指出。

 

  互相学习,共同进步!

 

  

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.luyixian.cn/news_show_814481.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

临阵磨枪,血拼季网站优化的最后三板斧

2019独角兽企业重金招聘Python工程师标准>>> 【编者按】本文作者 Dennis Callaghan 目前就职于 Callaghan&Partners。本文中&#xff0c;作者分享了购物高峰期提高网站性能的三个建议&#xff0c;帮助电商网站解决性能难题&#xff0c;提升用户体验&#xff0c;…

用户访问网站的完整流程

在浏览器输入想要访问的域名之后&#xff0c;浏览器会进行域名解析获得IP地址&#xff0c;在经过TCP的连接&#xff0c;实现数据的传输就会有两种报文&#xff0c;及请求报文和响应报文。最终才能实现通信。因此想要实现通信&#xff0c;就得先弄懂DNS的解析原理以及TCP连接通道…

网站定位之---根据IP获得区域

记得以前做一个培训机构网站时候需要定位&#xff0c;那时候用的搜狐的api&#xff0c;不是很精准。 demo:https://github.com/dunitian/LoTCodeBase/tree/master/NetCode/3.常用技能/06.Position/01.IPToPosition 后来做电商的时候用的新浪的api&#xff0c;感觉还行&#xff…

网站收录工具(php导航自动收录源码)_让百度快速收录网站的办法(简单可行)...

让百度快速收录网站最直接的方式是把网站链接提交给百度&#xff0c;能让蜘蛛快速发现页面而不是等着百度蜘蛛自己发现&#xff0c;收录慢不说&#xff0c;如果网站结构有问题&#xff0c;可能还抓取不到某些页面&#xff0c;从而不收录。百度提交网站收录的方式有快速收录和普…

java支付宝开发-02-手机网站支付

源码已上传github&#xff0c;欢迎专注&#xff1a;https://github.com/shirayner/alipay-wap 一、基础部分 1.手机网站支付产品介绍 1.1 阅读官方介绍&#xff1a; 手机网站支付产品介绍 1.2 看完官方的介绍之后&#xff0c;可以做出如下总结&#xff1a; 通过接入此接口&…

大型网站系统架构的演化

2019独角兽企业重金招聘Python工程师标准>>> 前言 一个成熟的大型网站&#xff08;如淘宝、京东等&#xff09;的系统架构并不是开始设计就具备完整的高性能、高可用、安全等特性&#xff0c;它总是随着用户量的增加&#xff0c;业务功能的扩展逐渐演变完善的&#…

可以发外链的网站_SEO优化就是写文章发外链吗?到底该怎么优化一个网站呢?...

点击箭头处“蓝色字”&#xff0c;关注我们哦&#xff01;&#xff01;在某个群里有很多“大侠”对SEO优化很是有误解&#xff0c;有的人说优化就是使劲的发文章&#xff0c;把外链做到极致就可以了&#xff0c;也有的人对优化不屑一顾&#xff0c;觉得优化没技术含量&#xff…

网站输入正确账号密码页面刷新一下_对某钓鱼网站的一次渗透测试

文章作者&#xff1a;Mochazz文章来源&#xff1a;Mochazz的博客原文链接&#xff1a;https://mochazz.github.io/2019/07/01/对某钓鱼网站的一次渗透测试/周末在某个CTF群偶然看到这个钓鱼网站&#xff1a;http://gggggg.cn (声明&#xff1a;本文中出现的域名、IP均被替换&am…

免密登录_以Airbnb为例,用selenium免密登录网站

大家好&#xff0c;这篇文章介绍了如何通过selenium利用cookie免密登陆网站&#xff08;第一次写文章 &#xff09;zzh2027/zzh2027.github.io​github.comfrom selenium import webdriver from selenium.webdriver.common.keys import Keys## 模拟键盘用的包 from selenium.we…

SEO流量定义,什么是搜索引擎优化策略营销?

SEO流量定义&#xff0c;什么是搜索引擎优化策略营销&#xff1f; 搜索引擎优化&#xff08;SEO&#xff09;看起来非常简单。你挑选几个关键词&#xff0c;然后瞧&#xff01;您的网页针对搜索引擎优化进行了优化 还没。 很多人都明白SEO的基本原则&#xff0c;但在过去的十年…

疯狂的多域名富关键词网站优化策略

疯狂的多域名富关键词网站优化策略 SEO中一个被很多人看重的排名因素就是根域名含有关键词&#xff0c;中文也一样&#xff0c;就是含有拼音&#xff0c;在相同的条件下&#xff0c;显然根域名含有关键词就有较好的排名&#xff0c;有一些SEO.er利用搜索引擎的这个排名策略&…

实时搜索SEO如何应对

实时搜索(Real-time Search)已经大势所趋&#xff0c;不是SEO不明白&#xff0c;是互联网变化太快&#xff0c;每天都有新名词&#xff0c;每天都有新主题&#xff0c;搜索引擎如何为用户及时的返回丰富又有意义的即时结果&#xff0c;Google推出Caffeine也正是适应这种趋势&am…

国内优秀的免费素材网站大比拼

奇遇网络专业提供seo优化服务&#xff0c;seo优化外包&#xff0c;免费建站模板&#xff0c;免费wordpress模板&#xff0c;DIVCSS模板,几千种免费网页模板&#xff0c;收集最好最全的html5模板及css3模板,html5是目前最前瞻的网页技术,本站为大家提供免费技术交流平台。 国内优…

ASP.NET新建解决方案和网站

新建解决方案和网站 1.打开visual studio&#xff0c;选择新建项目&#xff0c;然后选择解决方案。 2.解决方案建好之后&#xff0c;右键点击解决方案选择新建网站。注意&#xff0c;路径。我在solution1文件夹下面又建了一个site文件夹。 3.右键点击site选择添加新项。 好了&a…

网站架构演化阶段

网站架构演化阶段 网站架构需要工程师从全局把握一个网站的前台、后台&#xff0c;要知道什么地方会有瓶颈&#xff0c; 对网站的可扩展性、安全性等需要有一个成熟的把握。 下面说的几个阶段可以了解网站架构演化。 1&#xff09;单机部署 最简单的情况&#xff0c;可以把各个…

IOS-5个可以帮你优化App的优秀网站

也许现在有一款App可以提供所有你需要的&#xff0c;你不需要的&#xff0c;或者你可以想象到的内容。但是&#xff0c;有多少App真的可以不仅满足需求而且还能提供很好的用户体验呢? 相信很多APP并没有这样的能力。有一些APP的设计特别烂&#xff0c;以至于用户很难去完成一些…

安工大计算机作业提交网站,安工大教务处教务系统:http://jwc.ahut.edu.cn

各有关学院&#xff1a;根据教育部高等教育教学评估中心和中国工程教育专业认证协会联合发布《关于接受2020年工程教育认证申请的通告》(工程教育认证通告〔2019〕第3号)(附件1)文件精神&#xff0c;2020年工程教育专业认证申请工作现已开始。请各有关学院根据工程教育认证标准…

20点提高网站访问速度缩短网页加载时间

网友上网都不喜欢用太多的时间等待网页的打开&#xff0c;等待的越长&#xff0c;用户可能会直接关闭网页&#xff0c;这样就会损失很多流量!其次&#xff0c;关键字的排名与网页的打开速度也有关系&#xff0c;这个主要体现搜索引擎对用户体验度上&#xff0c;用户体验度好&am…

网站管理助手 mysql_巧用网站管理助手Mysql数据库损坏修复方法

站访问提示mysql错误:Table XXX is marked as crashed and should be repaired修复方法如下&#xff1a;一、管理助手方式&#xff1a;打开管理助手&#xff0c;打开mysql数据库选项&#xff0c;双击需要修复的数据库&#xff0c;点击修复按钮修复。如果没有修复成功&#xff0…

网站运营

网站如何经营&#xff0c;流量如何拉动&#xff0c;点击率如何获取&#xff0c;这些问题似乎从互联网诞生之日就出现了。然而关于它们的答案总是在不断更新。如今&#xff0c;传统媒体报业都在经受新媒体的冲击和挑战&#xff0c;如何去适应一个全新的&#xff0c;高度互联&…