zoukankan      html  css  js  c++  java
  • 版本号的管理

    一、面临的问题

    出于性能优化的考虑,通常资源服务器会对静态资源的HTTP响应首部添加Expires 或者Cache-Control: max-age设置失效时间,如下图:

    这样,在失效时间到达之前,浏览器会使用缓存文件而不用重新发送HTTP请求。这就引起另一个问题:失效时间还未到,但是我们有新功能上线,如何通知浏览器弃用缓存,重新发送HTTP请求获取最新的文件?

    二、版本号的用途

    URL有变化时,浏览器总是会重新发送HTTP请求尝试获取最新的文件。利用浏览器的这一特性,我们可以为静态资源的URL添加版本号来更新浏览器缓存。生成版本号,业界有两种常用方案:

    方案一、用更新时间当做版本号,如下:

    <script type="text/javascript" src="m139.core.pack.js?v=20130825"></script>

    方案二、根据文件内容进行 hash 运算得到版本号,如下:

    <script type="text/javascript" src="m139.core.pack.js?v=opMnaQdlocvGACU1GtgYoQ%3D%3D"></script>

    方案一的优势为实现简单,缺点显而易见:

    (1)需要手动管理版本号

    (2)无法解决CDN 缓存攻击。什么是CDN缓存攻击?黑客可提前猜测到你的下一个版本号,20130825的下一个版本很可能是20130826,提前访问你的静态资源,让你的CDN缓存旧文件。这样当真正的用户请求资源时,永远返回的都是过期的文件。

    由于以上缺陷,139邮箱web2.0采用方案二生成版本号

    三、139邮箱版本号的生成与使用

    1)依赖软件

    JdkAntNode 之所以需要Jdk是因为Ant需要Jdk,所以新同学来了首先装软件,配置环境变量。

    2)生成过程

    1Ant的入口脚本build.xml

    <project default="m2012_pack">
    
        <target name="m2012_pack" >
    
            <property name="base" location="./" />
    
            <property name="out" location="./target" />
    
            <property name="resource_m2012" location="${out}/images.139cm.com/m2012" />
    
     
    
            <!-- 创建文件夹 -->
    
            <mkdir dir="${out}"></mkdir>
    
     
    
            <!-- 执行合并js -->
    
            <antcall target="pack_js"></antcall>
    
     
    
            <!-- 执行合并css -->
    
            <subant target="">
    
              <fileset dir="./" includes="concatcss.xml"/>
    
            </subant>
    
     
    
            <!-- 执行提取文件版本号 -->
    
            <subant target="">
    
              <fileset dir="./" includes="createFileVersion.xml"/>
    
            </subant>
    
     
    
             <!-- 复制js文件 -->
    
             <copydir src="${base}/js/"
    
               dest="${resource_m2012}/js/"
    
               includes="**/**.js"
    
               excludes=""
    
             />
    
     
    
             <!-- 复制css/image/html/flash文件 -->
    
             <copydir src="${base}/css/"
    
               dest="${resource_m2012}/css/"
    
               includes="**/**.css,"
    
               excludes=""
    
             />
    
             <copydir src="${base}/images/"
    
               dest="${resource_m2012}/images/"
    
               excludes=""
    
             />
    
       <!--
    
       小工具文件夹包删除
    
       <copydir src="${base}/controlupdate/"
    
               dest="${resource_m2012}/controlupdate/"
    
               excludes=""
    
             />
    
       -->
    
             <copydir src="${base}/component/"
    
               dest="${resource_m2012}/component/"
    
               excludes=""
    
             />
    
             <copydir src="${base}/flash/"
    
               dest="${resource_m2012}/flash/"
    
               includes="**/**"
    
               excludes=""
    
             />
    
             <!-- 信纸文件夹有图片 -->
    
             <copydir src="${base}/html/"
    
               dest="${resource_m2012}/html/"
    
               excludes=""
    
             />
    
     
    
     <delete file="${resource_m2012}/html/set/feature_meal_config.js" />
    
     <delete file="${resource_m2012}/js/config.*.js" />
    
     
    
             <!-- 复制conf文件 -->
    
             <copydir src="${base}/conf/"
    
               dest="${resource_m2012}/conf/"
    
               excludes=""
    
             />
    
     
    
            <!-- 压缩js -->
    
            <echo>开始压缩脚本</echo>
    
            <antcall target="min_js"></antcall>
    
     
    
            <echo>生成样式引用图片的版本号</echo>
    
            <antcall target="replace_image_version"></antcall>
    
        </target>
    
     
    
        <!-- 子任务:合并js -->
    
         <target name="pack_js">
    
            <subant target="">
    
                <fileset dir="./js/" includes="**.pack.js.xml"/>
    
            </subant>
    
         </target>
    
     
    
        <!-- 子任务:压缩js -->
    
        <target name="min_js">
    
            <subant target="">
    
                <fileset dir="./" includes="jsmin.xml"/>
    
            </subant>
    
        </target>
    
     
    
        <!-- 子任务:替换css里的图片文件版本号 -->
    
        <target name="replace_image_version">
    
          <exec executable="node">
    
            <arg value="${base}/build/lib/replaceCSSImageVersion.js" />
    
            <arg value="--csspath=${resource_m2012}/css" />
    
          </exec>
    
        </target>
    
    </project>

    注意,合并文件任务在提取版本号任务之前,我们是正对合并后的文件内容生成版本号,原因很简单:我们在页面上引入的本来就是合并后的文件。

    2Ant的生成版本号脚本createFileVersion.xml

    <?xml version="1.0"?>
    <project name="BuildCssProject" default="createFileVersion">
      <!-- 执行任务的nodejs程序 -->
      <property name="path.concatExec" location="./build/lib/createFileVersion.js" />
      <property name="thisPath" location="./" />
      <target name="createFileVersion">
        <echo>正在生成文件版本号</echo>
        <apply executable="node" failonerror="true">
          <!-- 要生成版本号的文件,可根据需求配置 -->
          <fileset dir="./" includes="js/packs/**/**.js,css/**/**.css" />
          <arg path="${path.concatExec}" />
          <arg line="--output=${thisPath}/conf/config.10086.cn.js,${thisPath}/conf/config.10086ts.cn.js,${thisPath}/conf/config.10086rd.cn.js" />
        </apply>
      </target>
    
    </project>

    看脚本我们知道,Ant调用了Node命令执行了JS脚本createFileVersion.js:

    /*
     * 根据文件md5值生成版本号
     */
    var fs = require("fs");
    var crypto = require("crypto"); //用来做MD5 和 Base64
    
    var argvs = {
        //输入输出文件必须都是utf-8编码
        "--output": ""//这个参数指定输出的配置文件,可以是以逗号隔开的多个文件
    };
    
    process.argv.forEach(function (item) {
        for (var p in argvs) {
            if (item.indexOf(p + "=") == 0) {
                argvs[p] = item.split("=")[1];
            }
        }
    });
    
    var filePath = process.argv[process.argv.length - 1];
    
    var md5 = getFileMD5(filePath);
    md5 = encodeURIComponent(md5);
    var fileName = getFileName(filePath);
    
    var configFiles = argvs["--output"].split(",");
    //预计会有研发线、测试线、生产线3个配置文件
    configFiles.forEach(function (confFile) {
        writeConfigJSON(confFile, fileName, md5);
    });
    
    function getMD5(data) {
        var hash = crypto.createHash("md5");
        hash.update(data);    
        var md5Base64 = hash.digest("base64");
        return md5Base64;
    }
    function getFileMD5(file) {
        var data = fs.readFileSync(file);
        return getMD5(data);
    }
    
    
    function writeConfigJSON(confFile,sourceFileName,md5) {
        var reg = ///<fileConfig>([sS]+?)</fileConfig>/;
        var text = fs.readFileSync(confFile).toString();
        var m = text.match(reg);
        var isReplace = false;
        if (m) {
            isReplace = true;
        }
    
        if (isReplace) {
            try {
                var Config_FileVersion;
                eval(m[1]);
                if (!Config_FileVersion) {
                    throw "goto catch";
                }
            } catch (e) {
                throw "eval <fileConfig> js error from" + confFile + "
    +++++
    " + m[1];
            }
        } else {
            Config_FileVersion = {};
        }
        Config_FileVersion["defaults"] = new Date().toISOString();
        //替换资源文件版本号
        Config_FileVersion[sourceFileName] = md5;
    
        var newConfString = "//<fileConfig>
    var Config_FileVersion = " + JSON.stringify(Config_FileVersion, "", 4) + "
    //</fileConfig>";
    
        if (isReplace) {
            text = text.replace(reg, newConfString);
        } else {
            text += "
    
    " + newConfString;
        }
    
        fs.writeFileSync(confFile, text);
        console.log("get file version " + sourceFileName + ":" + md5);
        /*
        //<fileConfig>
        var Config_FileVersion = {
            "defaults": "20130605_randomnum",//默认全部资源版本号
            "index.html.pack.js": "asdasdasdas_1",//单独文件版本号,视自动化构建配置而定 md5_手动修改版本号数字
            "compose.html.pack.js": "czxcascasca_1"
        }
        //</fileConfig>
        */
    }
    
    function getFileName(full) {
        return full.match(/[^/\]+$/)[0];
    }
    
    /*
    process.argv.forEach(function (item) {
        for(var p in argvs){
            if (item.indexOf(p + "=") == 0) {
                argvs[p] = item.split("=")[1];
            }
        }
    });
    
    if (!argvs["--input"] || !argvs["--output"]) {
        console.log("no input argvs:--input --output");
    } else {
        concatFile(argvs["--input"], argvs["--output"]);
    }
    */

    看脚本可知,我们生成的版本号其实是文件的MD5值的base64编码。

    3、那么我们生成的版本号保存在哪里?看我们的配置文件:

     

    Node会将生成的版本号以JS对象的形式保存在配置文件config.10086.cn.js底部。

    版本号的使用

    如何利用配置文件中的版本号请求静态资源?index.html上定义了公共方法loadScript

    function loadScript(path, _doc, charset, rootPath) {
    
        //fixed proxy.htm
    
        if (path === "jquery.js") {
    
            if (_doc && _doc.location.pathname.indexOf("proxy.htm") > -1) {
    
                return;
    
            }
    
        }
    
        if (_doc && !_doc.location.pathname.match(//m2012.+?/) && !/http:|/m2012/.test(path) && !_doc.location.pathname.match(//mpost2014.+?/)) { //旧版调用
    
            loadScriptM2011(path, _doc, charset, rootPath);
    
            return;
    
        } else {
    
            if (path.indexOf(".js") > -1) {
    
                var jsVersion = getResourceVersion(path) || "20130612";
    
     
    
                if (window.location.href.indexOf("reload=true") > -1) {
    
                    jsVersion += "_reload";
    
                }
    
                if (path.indexOf("/") == -1) {
    
                    if (rootPath) {
    
                        path = rootPath + path;
    
                    } else {
    
                        var base = "/m2012/js";
    
                        if (path.indexOf("pack.js") > -1) {
    
                            base += "/packs";
    
                        }
    
                        path = base + "/" + path;
    
                    }
    
                }
    
                if (path.indexOf("?") == -1) {
    
                    path += "?v=" + jsVersion;
    
                }
    
                if (path.indexOf("http://") == -1 && path.indexOf("/") == 0) {
    
                    path = m2012ResourceDomain + path;
    
                }
    
            }
    
            if (!charset && path.indexOf("/m2012/") > -1) {
    
                charset = "utf-8";
    
            }
    
        }
    
        (_doc || document).write("<script jsonload='0' onload='this.setAttribute("jsonload","1")' " + (charset ? "charset="" + charset + "" " : "") + " type="text/javascript" src="" + path + ""></" + "script>");
    
    }

    getResourceVersion

    function getResourceVersion(path) {
    
        if (window.Config_FileVersion) {
    
            var fileName = path.match(/[^/\]*$/)[0];
    
            return Config_FileVersion[fileName] || Config_FileVersion["defaults"];
    
        } else {
    
            return "";
    
        }
    
    }

    为了保证脚本的执行顺序,我们使用document.write写入script标签,同时又可利用浏览器的并发特性。由于邮箱采用的是IFrame架构,所以其他功能模块的IFrame页面可通过top.loadScript载入脚本。从而保证了整站引入脚本方式的统一。

     

    四、参考资料

    http://fex.baidu.com/blog/2014/04/fis-static-resource-management/

    http://fex.baidu.com/blog/2014/03/fis-optimize/

     

  • 相关阅读:
    C++标准转换运算符(2)
    C++标准转换运算符(1)
    未能加载视图状态。正在向其中加载视图状态的控件树必须与前一请求期间用于……
    我的第一篇博客
    C语言C语言程序
    C语言基本运算符
    C语言流程控制
    C语言关键字、标识(zhi)符、注释
    msado15.dll版本引发的离奇故障
    mySQL错误: The used table type doesn't support FULLTEXT indexes
  • 原文地址:https://www.cnblogs.com/hellohuman/p/3980633.html
Copyright © 2011-2022 走看看