资源版本管理的优化 - 自动化构建资源版本

  • 作者:KK

  • 发表日期:2016.11.06


优化定义的方式

其实定义资源时应该是这样的:

return [
	//其它配置
	'aliases' => include(__DIR__ . '/resource.php'),
	
	
	/*如果有自定义的其它别名就这样嘛:
	'aliases' => yii\helpers\ArrayHelper::merge(
		[
			'@自定义其它别名1' => 111,
			'@自定义其它别名2' => 222,
		],
		include(__DIR__ . '/resource.php')
	),*/
];

然后在resouce.php里独立定义资源:

return [
	'@css.a' => '/a.css',
	'@css.b' => '/b.css',
	'@js.a' => '/a.js',
];

实现版本更新

开发一个Yii控制台action,比如我的是build/update-resource-aliases,则这个程序是这样的:

namespace app\commands;

use Yii;
use DOMDocument;

/**
 * 构建控制器
 */
class BuildController extends \yii\console\Controller{
	/**
	 * 构建后的输出资源
	 */
	public $output = '@app/config/resource.php';
	
	public function options($actionID){
		return ['output'];
	}
	
	/**
	 * 构建静态资源版本
	 */
	public function actionUpdateResourceAliases(){
		$this->stdout('开始构建静态资源文件' . PHP_EOL);
		
		$configFile = Yii::getAlias('@app/config/resource.php'); //复杂环境下可以考虑将这个作为参数传入,并定义默认值,与outptu区分开来
		$resourceConfig = file_get_contents($configFile);
		$resourceList = include($configFile);

		$webPath = Yii::getAlias('@app/web'); //改成你的实际web目录
		foreach($resourceList as $key => $resource){
			$trueResource = $resource[0] == '@' ? Yii::getAlias($resource) : $resource;
			
			$urlInfo = parse_url($trueResource);
			if(isset($urlInfo['scheme'])){
				//如果是http://xxx.com/yy.css 就不处理了
				continue;
			}
			
			$resourceFile = $webPath . $urlInfo['path'];
			if(!file_exists($resourceFile)){
				throw new \Exception('资源别名 ' . $key . ' 所指的文件 ' . $resourceFile . ' 不存在');
			}
			$resourceMd5 = md5_file($resourceFile);
			$resourceContent = str_replace($resource, $resource . '?v=' . $resourceMd5, $resourceContent);
		}

		file_put_contents(Yii::getAlias($this->output), $resourceContent);
		
		$this->stdout('静态资源文件构建完毕' . PHP_EOL);
	}
}

其中可以看到其实我在v后面追加的并不是数字版本号,而是文件MD5,这是为了降低CDN攻击的风险,我觉得中小项目一般这样就已经足够了,大项目的话人才济济,他们懂得用生成md5文件名(不是参数值)等方式去优化这块,不必担心,但是如果中小项目也玩生成md5的文件名就小题大作了,首先确认你们项目使用了CDN服务再说吧,创业公司那么多,估计你的项目十有八九没用到CDN,用了也没几个人攻击你们项目的资源,除非你们有很大的商业价值


自动化执行更新

以上定义了控制器和方法对资源进行更新后,其实还没达到完全的自动,这里有3个方案可选:

  1. 半自动,每次上线前,运行一下yii build/update-resource-aliases这样就更新了版本

  2. 上线的钩子将代码拉到线上目录后做多一件事就是运行这个构建命令,并将构建后的内容用svn commit -m "自动提交消息" app/config/resource.php这样将它提交到仓库

  3. 简单学一下gulp,部署一个任务watch所有JS、CSS有变就用Node.Js执行php yii build/update-resource-aliases,其实就是调用子程序,熟练的程序员应该很清楚这种原理,流水线简单的项目我比较推荐这个

  4. 在持续集成中添加构建后的步骤,在这个步骤中自动调用命令进行更新,实际上我以前带的项目是用这个的,虽然持续集成平台为我们的流水线添加了一个环节,但这是很多标准化项目其实都有的流程,还可以做更多其它事情,所以我顺便在集成过程中构建了资源版本


后话

由于还是有一大部分项目未做前后分离开发,所以有就了这些解决方案的存在,其实也并非天下所有项目都适合做前后分离

我现在带的项目99%都是前后分离的了,所以在前后分离过程中,反而是使用gulpgulp-rev这些插件在程序员保存文件时就完成自动更新版本的

而重的要并不是怎么定义资源别名和通过控制台命令更新,而是这种思想很重要,咱们完全有各种各样的办法能让资源版本得到自动更新