maven hash 静态资源插件

这标题不知道怎么写才能表达到我想表达的意思。。。。

基于此文章(前端工程精粹(一):静态资源版本更新与缓存)的hash版本冗余更新静态资源

摘抄:

基于query的形式修改链接

  • 如果先覆盖index.html,后覆盖a.js,用户在这个时间间隙访问,会得到新的index.html配合旧的a.js的情况,从而出现错误的页面。

  • 如果先覆盖a.js,后覆盖index.html,用户在这个间隙访问,会得到旧的index.html配合新的a.js的情况,从而也出现了错误的页面。

  • 静态资源文件版本更新是“覆盖式”的,而页面需要通过修改query来更新,对于使用CDN缓存的web产品来说,还可能面临CDN缓存攻击的问题

对付这个问题,目前来说最优方案就是基于文件内容的hash版本冗余机制了。也就是说,我们希望工程师源码是这么写的:

但是线上代码是这样的:


目前百度也是这种做法,不过oschina还是停留在第一种方法。oschina的做法不是不好,只是觉得不优雅。这也是普遍做法。虽然理解一间公司需要盈利,但一间有创新的科技公司更受人尊重。既然这是一个程序员的网站,为何不尝试使用一些比较前沿的技术(这涉及的技术选型不谈),毕竟用户都是程序员,大家对bug接受能力还是大(也许)。

---------------------------------------我的分割线------------------------------------------------------

我们希望得到的是,修改了的文件的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-war-plugin对warSourceDirectory资源复制后就是打成war,不能在中间插入步骤,修改复制后的资源

  2. 对静态资源的hash冗余,然后修改使用这资源的文件(html)

第一个问题我是在package生命流程钱前将warSourceDirectory的资源复制出来修改,最后配置maven-war-plugin的warSourceDirectory指向这个复制出来的文件夹就可以.

第二个问题只能通过写maven插件来解决


先贴插件的代码

@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值肯定和未压缩之前的不同,但这不影响我们的需求。也可以修改yuicompressor-maven-plugin的pash,然后放hash插件之前,这样就是先压缩后计算hash。如果还是启用gzip的话,上述插件的代码是不支持的,还是建议先hash后压缩。

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

你可能感兴趣的:(maven,maven-plugin)