Explorar o código

v2.0.0 修复打spring-boot jar包时,依赖乱序的问题

JustryDeng %!s(int64=4) %!d(string=hai) anos
pai
achega
fce708701c

+ 1 - 1
class-winter-core/dependency-reduced-pom.xml

@@ -3,7 +3,7 @@
   <parent>
     <artifactId>class-winter</artifactId>
     <groupId>com.idea-aedi</groupId>
-    <version>1.2.3</version>
+    <version>2.0.0</version>
   </parent>
   <modelVersion>4.0.0</modelVersion>
   <artifactId>class-winter-core</artifactId>

+ 1 - 1
class-winter-core/pom.xml

@@ -5,7 +5,7 @@
     <parent>
         <groupId>com.idea-aedi</groupId>
         <artifactId>class-winter</artifactId>
-        <version>1.2.3</version>
+        <version>2.0.0</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 

+ 7 - 2
class-winter-core/src/main/java/winter/com/ideaaedi/classwinter/executor/EncryptExecutor.java

@@ -14,6 +14,7 @@ import winter.com.ideaaedi.classwinter.util.Cache;
 import winter.com.ideaaedi.classwinter.util.Constant;
 import winter.com.ideaaedi.classwinter.util.EncryptUtil;
 import winter.com.ideaaedi.classwinter.util.ExceptionUtil;
+import winter.com.ideaaedi.classwinter.util.FileOrderSupport;
 import winter.com.ideaaedi.classwinter.util.IOUtil;
 import winter.com.ideaaedi.classwinter.util.JarUtil;
 import winter.com.ideaaedi.classwinter.util.JavaagentCmdArgs;
@@ -21,6 +22,7 @@ import winter.com.ideaaedi.classwinter.util.JavassistUtil;
 import winter.com.ideaaedi.classwinter.util.Logger;
 import winter.com.ideaaedi.classwinter.util.Pair;
 import winter.com.ideaaedi.classwinter.util.PathUtil;
+import winter.com.ideaaedi.classwinter.util.SimpleFileOrderSupport;
 import winter.com.ideaaedi.classwinter.util.StrUtil;
 
 import java.io.File;
@@ -61,6 +63,7 @@ public class EncryptExecutor {
         CLASS_WINTER_FILES.add(Constant.class.getName().replace(".", "/") + Constant.CLASS_SUFFIX);
         CLASS_WINTER_FILES.add(EncryptUtil.class.getName().replace(".", "/") + Constant.CLASS_SUFFIX);
         CLASS_WINTER_FILES.add(ExceptionUtil.class.getName().replace(".", "/") + Constant.CLASS_SUFFIX);
+        CLASS_WINTER_FILES.add(FileOrderSupport.class.getName().replace(".", "/") + Constant.CLASS_SUFFIX);
         CLASS_WINTER_FILES.add(IOUtil.class.getName().replace(".", "/") + Constant.CLASS_SUFFIX);
         CLASS_WINTER_FILES.add(JarUtil.class.getName().replace(".", "/") + Constant.CLASS_SUFFIX);
         CLASS_WINTER_FILES.add(JavaagentCmdArgs.class.getName().replace(".", "/") + Constant.CLASS_SUFFIX);
@@ -68,6 +71,7 @@ public class EncryptExecutor {
         CLASS_WINTER_FILES.add(Logger.class.getName().replace(".", "/") + Constant.CLASS_SUFFIX);
         CLASS_WINTER_FILES.add(Pair.class.getName().replace(".", "/") + Constant.CLASS_SUFFIX);
         CLASS_WINTER_FILES.add(PathUtil.class.getName().replace(".", "/") + Constant.CLASS_SUFFIX);
+        CLASS_WINTER_FILES.add(SimpleFileOrderSupport.class.getName().replace(".", "/") + Constant.CLASS_SUFFIX);
         CLASS_WINTER_FILES.add(StrUtil.class.getName().replace(".", "/") + Constant.CLASS_SUFFIX);
         CLASS_WINTER_FILES.add(Reverses.class.getName().replace(".", "/") + Constant.CLASS_SUFFIX);
         CLASS_WINTER_FILES.add(Reverses.class.getName().replace(".", "/").replace("Reverses", "Reverses$1") + Constant.CLASS_SUFFIX);
@@ -195,7 +199,7 @@ public class EncryptExecutor {
         Logger.debug(EncryptExecutor.class, "Generate seal -> " + Constant.SEAL);
         
         // step0. 解压jar到jarRootDir下
-        JarUtil.unJarWar(originJarOrWar, targetRootDir);
+        List<String> filePathList = JarUtil.unJarWar(originJarOrWar, targetRootDir);
         showProcess("step00", "step11", "un-jar-war");
         
         // step1. jar中的某些lib可能也需要加密,这里先(将那些需要加密的lib)进行解压,(解压到对应的临时目录,以便后面进行加密处理)
@@ -241,7 +245,7 @@ public class EncryptExecutor {
         
         // step10. 压缩targetRootDir目录为绝对文件路径名为generatedJarWar的jar(or war)包
         String generatedJarWar = generateJarWarPath(this.originIsJar);
-        JarUtil.doJarWar(targetRootDir, generatedJarWar);
+        JarUtil.doJarWar(targetRootDir, generatedJarWar, new SimpleFileOrderSupport(filePathList));
         showProcess("step10", "step11", "generate-jar-war-path");
         
         // step11. 删除临时目录
@@ -502,6 +506,7 @@ public class EncryptExecutor {
             return;
         }
         libJarAndTmpDirMap.forEach((jarFilePath, tmpDirPath) -> {
+            // lib下的jar都不是spring-boot jar,都是些普通jar,不需要排序,即:不需要使用三个参数的方法
             JarUtil.doJarWar(tmpDirPath, jarFilePath);
             // 删除临时目录
             IOUtil.delete(new File(tmpDirPath));

+ 38 - 0
class-winter-core/src/main/java/winter/com/ideaaedi/classwinter/util/FileOrderSupport.java

@@ -0,0 +1,38 @@
+package winter.com.ideaaedi.classwinter.util;
+
+import java.io.File;
+import java.util.Objects;
+
+/**
+ * 文件排序支持
+ *
+ * @author JustryDeng
+ * @since 2021/11/7 17:15:23
+ */
+public interface FileOrderSupport {
+    
+    /** 默认的排序序号 */
+    int DEFAULT_FILE_ORDER = Integer.MAX_VALUE;
+    
+    /**
+     * 获取文件的排序序号
+     *
+     * @param file
+     *            文件
+     * @return  文件的排序序号
+     */
+    default int obtainFileOrder(File file) {
+        Objects.requireNonNull(file, "file cannot be null.");
+        Integer order = fileOrder(file);
+        return order == null ? DEFAULT_FILE_ORDER : order;
+    }
+    
+    /**
+     * 获取文件的排序序号
+     *
+     * @param file
+     *            文件
+     * @return  文件的排序序号
+     */
+    Integer fileOrder(File file);
+}

+ 42 - 6
class-winter-core/src/main/java/winter/com/ideaaedi/classwinter/util/JarUtil.java

@@ -11,11 +11,16 @@ import java.io.OutputStream;
 import java.io.UnsupportedEncodingException;
 import java.net.URLDecoder;
 import java.nio.charset.StandardCharsets;
+import java.nio.file.attribute.FileTime;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Comparator;
 import java.util.Enumeration;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.Objects;
+import java.util.stream.Collectors;
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipFile;
 import java.util.zip.ZipOutputStream;
@@ -31,6 +36,13 @@ public final class JarUtil {
     /** 打包时需要忽略的(可能由操作系统产生的)文件 */
     private static final String[] IGNORE_FILE_SUFFIX = {".DS_Store", "Thumbs.db"};
     
+    /**
+     * @see JarUtil#doJarWar(String, String, FileOrderSupport)
+     */
+    public static String doJarWar(String srcDir, String targetJarOrWar) {
+        return doJarWar(srcDir, targetJarOrWar, null);
+    }
+    
     /**
      * 把目录压缩成jar(or war)
      *
@@ -38,12 +50,19 @@ public final class JarUtil {
      *            需要打包的目录(如 /tmp/demo-1.0.0/)
      * @param targetJarOrWar
      *            打包出的jar/war文件路径(如 /tmp/abc.jar)
+     * @param fileOrderSupport
+     *            文件排序序号提供者<br>
+     *            注:部分jar包会对jar包内的条目有顺序要求(如:spring-boot的jar包的lib目录下,lib包的顺序要和pom.xml中的声明顺序保持一致),
+     *                此时就可以使用此字段来实现排序了。
      * @return  打包出的jar/war文件路径
      */
-    public static String doJarWar(String srcDir, String targetJarOrWar) {
+    public static String doJarWar(String srcDir, String targetJarOrWar, FileOrderSupport fileOrderSupport) {
         File jarDirFile = new File(srcDir);
         // 枚举jarDir下的所有文件以及目录
         List<File> files = IOUtil.listSubFile(jarDirFile, 0);
+        if (fileOrderSupport != null) {
+            files = files.stream().sorted(Comparator.comparing(fileOrderSupport::obtainFileOrder)).collect(Collectors.toList());
+        }
         ZipOutputStream zos = null;
         OutputStream out = null;
         try {
@@ -65,7 +84,8 @@ public final class JarUtil {
                 // 目录,添加一个目录entry
                 if (file.isDirectory()) {
                     ZipEntry zipEntry = new ZipEntry(fileName + Constant.LINUX_FILE_SEPARATOR);
-                    zipEntry.setTime(System.currentTimeMillis());
+                    zipEntry.setTime(file.lastModified());
+                    zipEntry.setLastModifiedTime(FileTime.fromMillis(file.lastModified()));
                     zos.putNextEntry(zipEntry);
                     zos.closeEntry();
                 }
@@ -75,8 +95,9 @@ public final class JarUtil {
                     ZipEntry ze = new ZipEntry(fileName);
                     ze.setMethod(ZipEntry.STORED);
                     ze.setSize(bytes.length);
-                    ze.setTime(System.currentTimeMillis());
                     ze.setCrc(IOUtil.computeCrc32(bytes));
+                    ze.setTime(file.lastModified());
+                    ze.setLastModifiedTime(FileTime.fromMillis(file.lastModified()));
                     zos.putNextEntry(ze);
                     zos.write(bytes);
                     zos.closeEntry();
@@ -84,7 +105,8 @@ public final class JarUtil {
                 // 其它文件直接写入
                 else {
                     ZipEntry zipEntry = new ZipEntry(fileName);
-                    zipEntry.setTime(System.currentTimeMillis());
+                    zipEntry.setTime(file.lastModified());
+                    zipEntry.setLastModifiedTime(FileTime.fromMillis(file.lastModified()));
                     zos.putNextEntry(zipEntry);
                     byte[] bytes = IOUtil.toBytes(file);
                     zos.write(bytes);
@@ -123,7 +145,7 @@ public final class JarUtil {
      *            注:当entry对应jar或者war中的目录时,那么其值形如 BOOT-INF/classes/
      *            <br/>
      *            注:当entry对应jar或者war中的文件时,那么其值形如 BOOT-INF/classes/com/example/ssm/author/JustryDeng.class
-     * @return  解压出来的文件(包含目录)的完整路径
+     * @return  (按压缩文件中条目的顺序)解压出来的(有序的)文件(包含目录)的完整路径
      */
     public static <T extends Collection<String>> List<String> unJarWar(String jarWarPath, String targetDir,
                                                                        boolean delOldTargetDirIfAlreadyExist,
@@ -136,6 +158,9 @@ public final class JarUtil {
         guarantyDirExist(target);
         
         ZipFile zipFile = null;
+        // 此集合中的file会保留上一次修改时间,不会被当前解压操作覆盖
+        List<String> storeLastModifiedTimeList = new ArrayList<>();
+        Map<String, Long> filePthAndLastModifiedTimeMap = new HashMap<>(64);
         try {
             zipFile = new ZipFile(new File(jarWarPath));
             ZipEntry entry;
@@ -152,6 +177,9 @@ public final class JarUtil {
                 if (entry.isDirectory()) {
                     targetFile = new File(target, entryName);
                     guarantyDirExist(targetFile);
+                    String absolutePath = targetFile.getAbsolutePath();
+                    storeLastModifiedTimeList.add(absolutePath);
+                    filePthAndLastModifiedTimeMap.put(absolutePath, entry.getLastModifiedTime().toMillis());
                 } else {
                     // 有时遍历时,文件先于文件夹出来,所以也得保证目录存在
                     int lastSeparatorIndex = entryName.lastIndexOf(Constant.LINUX_FILE_SEPARATOR);
@@ -162,7 +190,10 @@ public final class JarUtil {
                     targetFile = new File(target, entryName);
                     byte[] bytes = IOUtil.toBytes(zipFile.getInputStream(entry));
                     IOUtil.toFile(bytes, targetFile, true);
-                    list.add(targetFile.getAbsolutePath());
+                    String absolutePath = targetFile.getAbsolutePath();
+                    list.add(absolutePath);
+                    storeLastModifiedTimeList.add(absolutePath);
+                    filePthAndLastModifiedTimeMap.put(absolutePath, entry.getLastModifiedTime().toMillis());
                 }
             }
         } catch (IOException e) {
@@ -170,6 +201,11 @@ public final class JarUtil {
         } finally {
             IOUtil.close(zipFile);
         }
+        for (String filePath : storeLastModifiedTimeList) {
+            Long lastModifiedTime = filePthAndLastModifiedTimeMap.get(filePath);
+            //noinspection ResultOfMethodCallIgnored
+            new File(filePath).setLastModified(lastModifiedTime == null ? System.currentTimeMillis() : lastModifiedTime);
+        }
         return list;
     }
     

+ 2 - 2
class-winter-core/src/main/java/winter/com/ideaaedi/classwinter/util/JavassistUtil.java

@@ -84,9 +84,9 @@ public final class JavassistUtil {
                     try {
                         ctMethod.insertBefore(
                                 "System.out.println(\"\\n " + TIPS +" \\n\");"
-                                + "\nSystem.exit(-1);"
-                                // 运行时,这一行内容不会被输出的
+                                // 也输出印章信息吧
                                 + "\nSystem.out.println(\"" + Constant.SEAL +"\");"
+                                + "\nSystem.exit(-1);"
                         );
                     } catch (CannotCompileException e) {
                         if ("no method body".equals(e.getMessage())) {

+ 0 - 125
class-winter-core/src/main/java/winter/com/ideaaedi/classwinter/util/RsaUtil.java

@@ -1,125 +0,0 @@
-package winter.com.ideaaedi.classwinter.util;
-
-import winter.com.ideaaedi.classwinter.author.JustryDeng;
-import winter.com.ideaaedi.classwinter.exception.ClassWinterException;
-
-import javax.crypto.Cipher;
-import java.nio.charset.StandardCharsets;
-import java.security.KeyFactory;
-import java.security.KeyPair;
-import java.security.KeyPairGenerator;
-import java.security.NoSuchAlgorithmException;
-import java.security.SecureRandom;
-import java.security.interfaces.RSAPrivateKey;
-import java.security.interfaces.RSAPublicKey;
-import java.security.spec.PKCS8EncodedKeySpec;
-import java.security.spec.X509EncodedKeySpec;
-import java.util.Base64;
-import java.util.Objects;
-
-/**
- * RSA 3072加解密
- *
- * @author {@link JustryDeng}
- * @since 2021/6/5 11:08:40
- */
-public class RsaUtil {
-    
-    /**
-     * 随机生成密钥对
-     *
-     * @return left-公钥;right-私钥
-     */
-    public static Pair<String, String> genKeyPair() {
-        KeyPairGenerator keyPairGen;
-        try {
-            // KeyPairGenerator类用于生成公钥和私钥对,基于RSA算法生成对象
-            keyPairGen = KeyPairGenerator.getInstance("RSA");
-        } catch (NoSuchAlgorithmException e) {
-            throw new ClassWinterException(e);
-        }
-        // 初始化密钥对生成器,密钥大小为3072位
-        keyPairGen.initialize(3072, new SecureRandom());
-        // 生成一个密钥对,保存在keyPair中
-        KeyPair keyPair = keyPairGen.generateKeyPair();
-        // 得到私钥
-        RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
-        // 得到公钥
-        RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
-        // 得到私钥字符串
-        String publicKeyString = new String(Base64.getEncoder().encode(publicKey.getEncoded()), StandardCharsets.UTF_8);
-        String privateKeyString = new String(Base64.getEncoder().encode((privateKey.getEncoded())), StandardCharsets.UTF_8);
-        return Pair.of(publicKeyString, privateKeyString);
-    }
-    
-    /**
-     * RSA公钥加密
-     *
-     * @param str
-     *            要加密的字符串
-     * @param publicKey
-     *            公钥
-     *
-     * @return 密文
-     */
-    public static String encrypt(String str, String publicKey) {
-        Objects.requireNonNull(str, "str cannot be empty.");
-        Objects.requireNonNull(publicKey, "publicKey cannot be empty.");
-        //base64编码的公钥
-        byte[] decoded = Base64.getDecoder().decode(publicKey);
-        try {
-            RSAPublicKey pubKey = (RSAPublicKey) KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(decoded));
-            //RSA加密
-            Cipher cipher = Cipher.getInstance("RSA");
-            cipher.init(Cipher.ENCRYPT_MODE, pubKey);
-            return Base64.getEncoder().encodeToString(cipher.doFinal(str.getBytes(StandardCharsets.UTF_8)));
-        } catch (Exception e) {
-            throw new ClassWinterException(e);
-        }
-    }
-    
-    /**
-     * RSA私钥解密
-     *
-     * @param cipherText
-     *            密文
-     * @param privateKey
-     *            私钥
-     *
-     * @return 明文
-     */
-    public static String decrypt(String cipherText, String privateKey) {
-        Objects.requireNonNull(cipherText, "cipherText cannot be empty.");
-        Objects.requireNonNull(privateKey, "privateKey cannot be empty.");
-        // 64位解码加密后的字符串
-        byte[] inputByte = Base64.getDecoder().decode(cipherText.getBytes(StandardCharsets.UTF_8));
-        // base64编码的私钥
-        byte[] decoded = Base64.getDecoder().decode(privateKey);
-        try {
-            RSAPrivateKey priKey = (RSAPrivateKey) KeyFactory.getInstance("RSA").generatePrivate(new PKCS8EncodedKeySpec(decoded));
-            //RSA解密
-            Cipher cipher = Cipher.getInstance("RSA");
-            cipher.init(Cipher.DECRYPT_MODE, priKey);
-            return new String(cipher.doFinal(inputByte), StandardCharsets.UTF_8);
-        } catch (Exception e) {
-           throw new ClassWinterException(e);
-        }
-    }
-    
-    /**
-     * test
-     */
-    public static void main(String[] args) throws Exception {
-        //生成公钥和私钥
-        Pair<String, String> keyPair = genKeyPair();
-        //加密字符串
-        String message = "hello JustryDeng";
-
-        System.out.println("随机生成的公钥为: " + keyPair.getLeft());
-        System.out.println("随机生成的私钥为: " + keyPair.getRight());
-        String cipherText = encrypt(message, keyPair.getLeft());
-        System.out.println(message + "\t加密后的字符串为: " + cipherText);
-        String plainText = decrypt(cipherText, keyPair.getRight());
-        System.out.println("还原后的字符串为: " + plainText);
-    }
-}

+ 31 - 0
class-winter-core/src/main/java/winter/com/ideaaedi/classwinter/util/SimpleFileOrderSupport.java

@@ -0,0 +1,31 @@
+package winter.com.ideaaedi.classwinter.util;
+
+import java.io.File;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 简单的文件排序提供者
+ *
+ * @author JustryDeng
+ * @since 2021/11/7 17:31:27
+ */
+public class SimpleFileOrderSupport implements FileOrderSupport {
+    
+    private final Map<File, Integer> fileOrderInfo = new HashMap<>(64);
+    
+    public SimpleFileOrderSupport(List<String> filePathList) {
+        if (filePathList == null || filePathList.size() == 0) {
+            return;
+        }
+        for (int i = 0; i < filePathList.size(); i++) {
+            fileOrderInfo.put(new File(filePathList.get(i)), i);
+        }
+    }
+    
+    @Override
+    public Integer fileOrder(File file) {
+        return fileOrderInfo.get(file);
+    }
+}

+ 1 - 1
class-winter-maven-plugin/pom.xml

@@ -5,7 +5,7 @@
     <parent>
         <groupId>com.idea-aedi</groupId>
         <artifactId>class-winter</artifactId>
-        <version>1.2.3</version>
+        <version>2.0.0</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 

+ 1 - 1
pom.xml

@@ -9,7 +9,7 @@
     </modules>
     <groupId>com.idea-aedi</groupId>
     <artifactId>class-winter</artifactId>
-    <version>1.2.3</version>
+    <version>2.0.0</version>
 
     <!-- 配置项目的基础信息 -->
     <name>class-winter</name>