文档章节

maven hash 静态资源插件

NestleCaau
 NestleCaau
发布于 2015/12/01 16:57
字数 1024
阅读 124
收藏 0

基于此文章(前端工程精粹(一):静态资源版本更新与缓存)实现的maven插件

我们希望得到的是,修改了的文件的hash码是改变了,并且与现在和未修改前的文件的hash码不重复.Hash码并且越短越好.

MD5生成的16进制长度是32位,SHA-256的更长,是64位.最后确定是选CRC32(介绍) ,不过看到JDK(8) 里面还有个Adler32(介绍).

理论上来讲,CRC64的碰撞概率大约是每18×10^18个CRC码出现一次。这里(链接)有人测试过CRC32的碰撞概率,数据显示是1820W数据,冲突数量是38638个,还是比较可观。还有Adler32也可以选择,不过保守点还是选CRC32.

使用大致流程是

  1. 用maven-resources-plugin复制warSourceDirectory的资源到临时目录

  2. 计算静态资源的hash值重命名,然后修改引用这资源的htm/jsp文件



先贴插件的代码

@Mojo(name = "hash", defaultPhase = LifecyclePhase.VALIDATE)
public class HashResourceMojo extends AbstractMojo {

   @Parameter(defaultValue = "${project.build.directory}/prepareWarSource")
   private File warSourceDirectory;

   @Parameter
   private String[] excludes;

   @Parameter
   private String[] includes;

   @Parameter
   private String[] includesHtml;

   @Parameter
   private String[] excludesHtml;

   @Parameter(defaultValue = "UTF-8")
   private String encode;

   @Parameter(defaultValue = "CRC32")
   private String algorithm;

   public void execute() throws MojoExecutionException {
   Map<String, String> resourceMap = checksumResource();
   Pattern pattern = Pattern.compile("<(link|script)\\b[^<]*(src|href)=[\"'](?<uri>[\\w-\\./]+?)[\"'][^<]*>");
   List<File> files = getFiles(warSourceDirectory, getIncludesHtml(), excludesHtml);
   int processFileCount = 0;
   for (File file : files) {
      try {
         String source = FileUtils.readFileToString(file, encode);
         StringBuilder sb = new StringBuilder();
         int count = 0;
         while (true) {
            Matcher matcher = pattern.matcher(source);
            if (matcher.find()) {
               String uri = matcher.group("uri");
               File uriFile = new File(warSourceDirectory, uri);
               String absPath = convertToWebPath(uriFile);
               if (resourceMap.containsKey(absPath)) {
                  String hashFile = resourceMap.get(absPath);
                  int index = absPath.lastIndexOf("/");
                  sb.append(source.substring(0, matcher.start("uri"))).append(absPath.substring(0, index + 1)).append(hashFile);
                  count++;
               } else {
                  sb.append(source.substring(0, matcher.end("uri")));
               }
               source = source.substring(matcher.end("uri"));
            } else {
               break;
            }
         }
         sb.append(source);
         if (count > 0) {
            processFileCount++;
            FileUtils.write(file, sb.toString(), encode);
            getLog().info(convertToWebPath(file) + " change " + count + " uri");
         }
      } catch (IOException e) {
         throw new RuntimeException(e);
      }
   }
   getLog().info("Hash " + resourceMap.keySet().size() + " files");
   getLog().info("Process " + processFileCount + " files");
}


private Map<String, String> checksumResource() {
   List<File> files = getFiles(warSourceDirectory, getIncludes(), excludes);
   return files.parallelStream().map(file -> {
      String[] str = new String[2];
      FileReader in = null;
      try {
         in = new FileReader(file);
         byte[] bytes = IOUtils.toByteArray(in);
         IOUtils.closeQuietly(in);
         Checksum checksum = getChecksum();
         checksum.reset();
         checksum.update(bytes, 0, bytes.length);
         String hexCode = Long.toHexString(checksum.getValue());
         String fileName = file.getName();
         int suffixIndex = fileName.lastIndexOf(".");
         String hashFileName = fileName.substring(0, suffixIndex) + "_" + hexCode + fileName.substring(suffixIndex);

         File outFile = new File(file.getParent(), hashFileName);
         if (file.renameTo(outFile)) {
            String srcPath = convertToWebPath(file);
            str[0] = srcPath;
            str[1] = hashFileName;
         } else {
            throw new RuntimeException("File rename failed");
         }
      } catch (IOException e) {
         throw new RuntimeException(e);
      } finally {
         IOUtils.closeQuietly(in);
      }
      return str;
   }).collect(Collectors.toMap(obj -> obj[0], obj -> obj[1]));
}

private List<File> getFiles(File parent, String[] in, String[] ex) {
   DirectoryScanner scanner = new DirectoryScanner();
   scanner.setBasedir(parent);
   scanner.setIncludes(in);
   scanner.setExcludes(ex);
   scanner.scan();
   return Arrays.stream(scanner.getIncludedFiles()).parallel().map(fileName -> new File(parent, fileName)).collect(Collectors.toList());
}

private String convertToWebPath(File file) throws IOException {
   return file.getCanonicalPath().replace(warSourceDirectory.getPath(), "").replace("\\", "/");
}

public String[] getIncludes() {
   if (includes == null || includes.length < 1) {
      return new String[]{"**/*.js", "**/*.css"};
   }
   return includes;
}

public String[] getIncludesHtml() {
   if (includesHtml == null || includesHtml.length < 1) {
      return new String[]{"**/*.jsp", "**/*.html"};
   }
   return includesHtml;
}

private Checksum getChecksum() {
   Checksum checksum;
   if (algorithm.equalsIgnoreCase("Adler32")) {
      checksum = new Adler32();
   } else if (algorithm.equalsIgnoreCase("CRC32")) {
      checksum = new CRC32();
   } else {
      throw new IllegalArgumentException("Algorithm value is " + algorithm);
   }
   return checksum;
}


难就难在写正则表达式,这正则试过几条数据都没有问题.生成环境也试过OK。不过就不适用带EL表达的uri,可自行改造。

项目的pom的配置

<plugins>
    <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-resources-plugin</artifactId>
        <version>2.7</version>
        <executions>
            <execution>
                <phase>validate</phase>
                <goals>
                    <goal>copy-resources</goal>
                </goals>
                <configuration>
                    <outputDirectory>${project.build.directory}/prepareWarSource</outputDirectory>
                    <resources>
                        <resource>
                            <directory>${basedir}/webapp</directory>
                        </resource>
                    </resources>
                </configuration>
            </execution>
        </executions>
    </plugin>
    <plugin>
        <groupId>com.chaodongyue.maven</groupId><!--自己写的插件-->
        <artifactId>hashresource-maven-plugin</artifactId><
        <version>1.0</version>
        <executions>
            <execution>
                <goals>
                    <goal>hash</goal>
                </goals>
                <configuration>
                    <warSourceDirectory>${project.build.directory}/prepareWarSource</warSourceDirectory>
                    <includesHtml>
                        <include>**/*.jsp</include>
                        <include>**/*.html</include>
                        <include>/WEB-INF/tags/*.tag</include>
                    </includesHtml>
                </configuration>
            </execution>
        </executions>
    </plugin>
    <!--压缩js和css
    <plugin>
        <groupId>net.alchim31.maven</groupId>
        <artifactId>yuicompressor-maven-plugin</artifactId>
        <version>1.5.1</version>
        <executions>
            <execution>
                <goals>
                    <goal>compress</goal>
                </goals>
            </execution>
        </executions>
        <configuration>
            <gzip>true</gzip>
            <encoding>UTF-8</encoding>
            <nosuffix>true</nosuffix>
            <jswarn>false</jswarn>
            <warSourceDirectory>${project.build.directory}/prepareWarSource</warSourceDirectory>
            <webappDirectory>${project.build.directory}/prepareWarSource</webappDirectory>
            <includes>
                <include>**/*.js</include>
                <include>**/*.css</include>
            </includes>
        </configuration>
    </plugin>
    -->
    <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-war-plugin</artifactId>
        <configuration>
            <warSourceDirectory>${project.build.directory}/prepareWarSource</warSourceDirectory>
        </configuration>
    </plugin>
</plugins>

在validate阶段,将原本web根目录下的文件全部复制到prepareWarSource目录下,然后在对js,css,jsp等进行hash处理。注意顺序,应该将maven-resources-plugin摆在hash插件之前。最后可以对hash之后的文件进行压缩。

压缩后的文件的hash值肯定和未压缩之前的不同,但这不影响我们的需求。如果启用gzip的话,要先hash后压缩。

最后maven-war-plugin设置warSourceDirectory打包的源文件夹的路径就可以

© 著作权归作者所有

NestleCaau
粉丝 3
博文 3
码字总数 2118
作品 0
广州
程序员
私信 提问
加载中

评论(2)

L
LeiYaoShun
博主能发一份源码给我吗,最近也在解决这个问题
s
silence_zx
博主能发一份源码给我吗,最近也在解决这个问题。zhuxiang134@gmail.com,谢谢。
使用gulp解决Web项目中静态资源版本更新与缓存

现在大部分项目还没做到完全的静态分离,想做到完全静态分离,后端和前端人员配比也相差不大。对于中小型公司,一般都前端工程师做出静态页面和相应的动画效果,页面的渲染还都是由后端工程师...

无忌
2017/06/19
541
0
基于grunt的前端构建

在「grunt的初次使用」的基础上,这一篇继续对grunt进行探索研究。这一次不再使用php进行include静态文件,而是在html里面引入。然后主要将grunt用于两个大的方向,一个是用于开发期间,一个...

cobish
2016/04/05
41
0
前端构建工具-fis3使用入门

FIS3 是面向前端的工程构建工具。解决前端工程中性能优化、资源加载(异步、同步、按需、预加载、依赖管理、合并、内嵌)、模块化开发、自动化工具、开发规范、代码部署等问题。 官网地址是:...

renfufei
2017/07/10
0
0
Springboot+thymeleaf+mybatis 报Error resolving template [index], template might not exist的异常

出现异常原因: 1、静态资源没有进行拷贝到jar中或者war中 2、@RequestMapping("***")不能加 "/" 解决: 项目是Maven项目,在POM文件中添加静态资源编译,进行资源文件拷贝 <build> <plugins......

嘿嘿嘿IT
05/18
15
0
grunt添加版本号

为了上线之后用户能使用到最新的静态资源,大部分人会使用添加时间戳来清掉缓存,类似于下面这样的代码。读过张云龙的「大公司里怎样开发和部署前端代码」,意识这种方法有几个弊端。一则是每...

cobish
2016/04/05
2.8K
0

没有更多内容

加载失败,请刷新页面

加载更多

《JAVA核心知识》学习笔记 (21. JAVA 算法)

21. JAVA 算法

Shingfi
26分钟前
4
0
redis 命令

redis 秒杀用到的 原子锁 :$redis->decr('jili_reward_goods_stock_' . $gifts_id) redis 秒杀用到的原子锁在秒杀过程中库存量增加 $redis->incrBy('key1', 10); redis 键查看重复:$redis-......

小小小壮
26分钟前
4
0
像智能手机一样管理云端应用:阿里云联合微软全球首发开放应用模型(OAM)

2019 年 10 月 17 日上午 9 点 15 分,阿里巴巴合伙人、阿里云智能基础产品事业部总经理蒋江伟在 QCon 上海《基于云架构的研发模式演进》主题演讲中,正式宣布: “今天,我们同微软联合发布...

阿里巴巴云原生
35分钟前
4
0
SpringBoot配置数据源

默认数据源 Springboot默认支持4种数据源类型,定义在 org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration 中,分别是: org.apache.tomcat.jdbc.pool.DataSource......

Gx_ww
38分钟前
4
0
Java应用在docker环境配置容器健康检查

在《极速体验docker容器健康》一文已体验了docker容器健康检查功能,今天就来给java应用的容器加入健康检查,使应用的状态随时都可以被监控和查看。 实战环境信息 操作系统:macOS Catalina ...

程序员欣宸
40分钟前
4
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部