5.7. 使用资源

如果你的程序复杂一些的话,一定会用到额外的 css, js, gif, png, jpg … 这些文件统称为资源(Asset)。你的插件是放在 protected/Plugin 目录下的,这个目录下的文件是没法被访问的(出于安全的考虑,整个 protected 目录都是禁止访问的)。现在问题是,你的输出网页会引用这些 资源文件,但是你不能直接指向 protected/Plugin/… 目录,你需要使用 bzfshop 提供的 AssetManage 功能来 "发布资源" 才能让用户访问到。

大部分简单的 PHP 程序都是直接把资源放在某个目录下,然后你通过 "相对路径" 来访问它,比如 <img src="../img/aaa.gif" /> ,这里其实假设了 img 目录和你的代码在同一个目录。这种方式使用起来简单,但是非常的不安全。

bzfshop 设计是面向 CDN 运行的,CDN 会缓存你所有的静态文件。传统的方式如果你的静态文件发生了修改,你需要去 CDN 刷新这个文件,否则用户访问仍然是旧文件。bzfshop 的设计避免了这样的麻烦,AssetManage 会发现资源文件发生了修改,然后主动重新发布资源文件(换名发布),这样对 CDN 而言这就是一个新文件会自动加载这个新文件,完全不用去刷新 CDN。至于旧文件就扔在哪里不用管它好了,反正后面也不会引用到了。

[注意]

bzfshop 是面向 CDN 运行设计的,采用 AssetManage 管理资源。资源的任何修改都会触发重新 "换名发布",所以完全不需要做任何 CDN 的刷新操作。

在这章里面我们向你展示一个插件例子,插件有自己的 css, js 文件,插件的页面也完全是由自己的 css, js 渲染的。插件需要使用 AssetManage 发布自己的资源,这样用户才能访问得到。例子代码在这里 下载

5.7.1. 代码结构

图 5.8. 插件Example4目录结构

插件Example4目录结构

从上面的代码目录结构我们能看到增加了一个 Asset 目录,里面存放了页面用到的 css 和 js 文件。

现在请安装这个插件,然后访问 http://…/Example/Goods/View?goods_id=42079 你就能看到插件显示的页面,这个页面完全使用插件自带的 css 和 js ,和系统已有的 css,js 完全无关。

现在我们再强调一次我们的目录约定,插件下面建立不同的目录用于不同的用途,比如 Code 用于存放代码,Tpl 用于存放模板文件,Asset 用于存放资源文件。清晰的目录能够让别人一样就看出它的用途,不要把很多不相关的东西都放到一起以至于别人都不知道这是做什么的。

5.7.2. 资源发布

你的插件所带的资源(css, js, …)都存放在 protected/Plugin/你的插件目录 下面。由于 protected 目录本身是禁止访问的,所以你的资源在这里是无法访问的,你必须发布到某个可以访问的地方去。bzfshop 已经配置好了使用 src/asset 目录作为 "发布目标"(可以在配置文件里面修改),所以你的资源会被发布到这个目录下面。

现在再次打开 http://…/Example/Goods/View?goods_id=42079 ,右键 → 查看源代码,你会看到下面的内容(这是我的运行环境的路径,你的可能会有点不一样):

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">

    <!-- 让 IE 使用最新模式 -->
    <meta http-equiv="X-UA-Compatible" content="IE=edge"/>

    <title>插件演示:使用自己的CSS,JS</title>

    <!-- 指定360浏览器使用极速模式 -->
    <meta name="renderer" content="webkit"/>
    <!-- /指定360浏览器使用极速模式 -->

    <!-- 使用自己的 css -->
    <link href="http://192.168.2.101/PHPStorm/bzfshop-opensource/src/asset/6248FD5A-B19C-4936-9109-F52B71B81F1E/1.0.0/css/1388118660.example.css" type="text/css" rel="stylesheet">

</head>
<body>

<div class="navbar navbar-inverse navbar-static-top">
    <div class="navbar-inner">
        <div style="text-align:center;" class="container">
            <h5>【全国包邮】冬季新款时尚呢子大衣中长款毛呢料裙摆外套L175!四色可选,裙摆优雅的设计,非常修身时尚,优雅动人! <!-- 这里是输出结果 --></h5>
        </div>
    </div>
</div>

<!-- 网页主体内容 -->
<div id="main_body" class="container">

    <!-- 主体内容 row -->
    <div class="row" style="background-color: white;padding: 40px; 10px; text-align: center;">

        <button class="btn btn-large btn-success" onclick="example4_btn_test('【全国包邮】冬季新款时尚呢子大衣中长款毛呢料裙摆外套L175!四色可选,裙摆优雅的设计,非常修身时尚,优雅动人!');">点击测试 JS 功能
        </button>

    </div>
    <!-- /主体内容 row -->

    <div class="row" style="height: 60px;background-color: white;"></div>
</div>

<!-- 使用自己的 js -->
<script type="text/javascript" src="http://192.168.2.101/PHPStorm/bzfshop-opensource/src/asset/6248FD5A-B19C-4936-9109-F52B71B81F1E/1.0.0/js/1388119183.example.js"></script>

</body>
</html>

这里你能看到,css, js 来自于一个很长的链接 http://192.168.2.101/PHPStorm/bzfshop-opensource/src/asset/6248FD5A-B19C-4936-9109-F52B71B81F1E/1.0.0/js/1388119183.example.js 。从这里你能看出,资源发布在 src/asset 目录下面,路径为 6248FD5A-B19C-4936-9109-F52B71B81F1E1/1.0.02/js/1388119183.3example.js 。

  1. 这是插件的 uuid 值,对应 plugin_load.php 中的 protected static $pluginUniqueId = '6248FD5A-B19C-4936-9109-F52B71B81F1E'; 。不同的插件 uuid 值也不同,所以即使有多个插件有同样的 example.js 文件,相互之间也不会冲突
  2. 这是插件的版本号,对应于 config.php 中的 'version' ⇒ '1.0.0',所以同一个插件不同版本就算名字相同 example.js,相互之间也不会受到影响
  3. 这个是文件的 "最后修改时间的时间戳"。如果 example.js 文件发生修改,这个时间戳也会改变,于是 AssetManage 会重新发布 example.js 文件,并且名字变化了(因为时间戳变化了)。这就是 AssetManage 实现的 "换名发布"
[注意]

传统的资源版本控制一般是 /…/example.js?hash=1388119183 ,采用一个参数来使得 URL 不同从而迫使浏览器在资源发生变化的时候重新加载资源。bzfshop 本身也支持这种方式的 URL 发布,可以自己查看 AssetManage 的代码,里面有设置。我们之所以采用 1388119183.example.js 而不是 example.js?hash=1388119183 是因为国内很多 CDN 都很屎,他们对于这种带参数的静态资源会把它们认为是动态资源从而不做缓存,结果 CDN 就无效了。

下面我们来看看代码都做了哪些工作:

plugin_load.php. 

        private function doShopAction()
        {
            // 获取当前插件的根地址
            $currentPluginBasePath = dirname(__FILE__);

            // code 目录加入到 auto load 的路径中,这样系统就能自动做 class 加载
            SystemHelper::addAutoloadPath($currentPluginBasePath . '/shop/Code');

            // 增加 smarty 的搜索路径,这样才能找到我们的模板
            global $smarty;
            $smarty->addTemplateDir($currentPluginBasePath . '/shop/Tpl/');

            // 设置路由
            SystemHelper::addRouteMap(
                '/Example/Goods/View',
                'Controller\Example\Goods\View'
            );

            // 注册 Asset 模块
            \Core\Asset\ManagerHelper::registerModule(
                self::pluginGetUniqueId(),
                $this->pluginGetVersion(),
                $currentPluginBasePath . '/shop/Asset'
            );

            // 发布必要的资源文件,这样用户才能访问到我们的资源
            \Core\Asset\ManagerHelper::publishAsset(
                self::pluginGetUniqueId(),
                'css'
            );
            \Core\Asset\ManagerHelper::publishAsset(
                self::pluginGetUniqueId(),
                'js'
            );

            // 注册 smarty 使用的函数
            $smarty->registerPlugin(
                'function',
                'bzf_get_asset_url_example4',
                array($this, 'getAssetUrl')
            );

            return true;
        }

        /**
         * Smarty 中用来取得资源路径的方法
         *
         * @param array $paramArray
         * @param       $smarty
         *
         * @return string
         */
        public function getAssetUrl(array $paramArray, $smarty)
        {
            if (!isset($paramArray['asset'])) {
                return '';
            }

            return \Core\Asset\ManagerHelper::getAssetUrl(self::pluginGetUniqueId(), $paramArray['asset']);
        }

这是 doShopAction() 为 shop 设置运行环境的代码。这里增加了 Asset 管理的部分:

  1. 为插件注册 registerModule(),你可以想象,系统中有很多插件,每个插件可能都带有自己的 example.js ,我们怎么知道你想要引用哪个呢?就是通过这里注册插件的 self::pluginGetUniqueId() 来区分的,包括让 AssetManage 知道你的 Asset 对应的目录在哪里
  2. 由于前面已经用插件的 uuid 注册了 Asset,我们接下来发布 css, js 目录就可以了(所谓发布,就是 AssetManage 会把这些目录下面的文件复制到 "发布目录" 下,并且建立正确的目录结构,也就是你前面看到的那个很长一串的路径)
  3. 注册一个 smarty 函数(bzf_get_asset_url_example4)用于在 smarty 模板中引用我们需要的资源文件(见下面 模板文件说明)

资源发布成功之后,一般我们会在网页模板中引用资源,下面是对应的模板应用:

example4_goods_view.tpl. 

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">

    <!-- 让 IE 使用最新模式 -->
    <meta http-equiv="X-UA-Compatible" content="IE=edge"/>

    <title>插件演示:使用自己的CSS,JS</title>

    <!-- 指定360浏览器使用极速模式 -->
    <meta name="renderer" content="webkit"/>
    <!-- /指定360浏览器使用极速模式 -->

    <!-- 使用自己的 css -->
    <link href="{{bzf_get_asset_url_example4 asset='css/example.css'}}" type="text/css" rel="stylesheet">

</head>
<body>

<div class="navbar navbar-inverse navbar-static-top">
    <div class="navbar-inner">
        <div style="text-align:center;" class="container">
            <h5>{{$testResult|default}} <!-- 这里是输出结果 --></h5>
        </div>
    </div>
</div>

<!-- 网页主体内容 -->
<div id="main_body" class="container">

    <!-- 主体内容 row -->
    <div class="row" style="background-color: white;padding: 40px; 10px; text-align: center;">

        <button class="btn btn-large btn-success" onclick="example4_btn_test('{{$testResult|default}}');">点击测试 JS 功能
        </button>

    </div>
    <!-- /主体内容 row -->

    <div class="row" style="height: 60px;background-color: white;"></div>
</div>

<!-- 使用自己的 js -->
<script type="text/javascript" src="{{bzf_get_asset_url_example4 asset='js/example.js'}}"></script>

</body>
</html>

注意这里的调用 {{bzf_get_asset_url_example4 asset='css/example.css'}}bzf_get_asset_url_example4 是我们前面注册的函数名,我们通过调用这个方法就能取得 css/example.css 的实际路径,这样网页就能正确的引用对应的资源文件了。

[注意]

bzfshop 的 AssetManage 会自动检测资源的变化,并且对发生变化的资源做 "换名发布",所以一旦你修改了 css, js 文件,不需要做任何操作你的网页就自动会引用最新的 css, js 了。