diff --git a/src/programming/java/工具箱/Spring Boot JAR 瘦身与加密.md b/src/programming/java/工具箱/Spring Boot JAR 瘦身与加密.md
new file mode 100644
index 0000000..e16e128
--- /dev/null
+++ b/src/programming/java/工具箱/Spring Boot JAR 瘦身与加密.md
@@ -0,0 +1,1225 @@
+---
+icon: bi:arrows-expand
+date: 2025-05-13
+category:
+ - 实用工具
+ - JAVA
+ - SpringBoot
+ - JAR
+tag:
+ - JAR包瘦身
+title: Spring Boot JAR 瘦身与加密
+---
+
+Spring Boot JAR 瘦身与加密:构建安全高效的部署方案
+
+# Spring Boot JAR 瘦身与加密:构建安全高效的部署方案
+
+在 Spring Boot 应用程序部署过程中,我们常常面临两个主要挑战:
+
+1. **JAR 包体积过大**:Spring Boot 应用打包时会将所有依赖一起打包,导致最终 JAR 文件臃肿
+2. **代码安全性问题**:部署到客户环境或公开场合的 JAR 包可能被反编译,造成核心业务逻辑泄露
+
+为了解决这些问题,本文将介绍一套完整的解决方案,包括 JAR 包瘦身和 JAR 包加密两部分,以及配套的自定义类加载器,实现高效安全的 Spring Boot 应用部署。
+
+## 整体方案设计
+
+该方案由两个主要项目组成:
+
+1. **spring-boot-jar-slim-encrypt**:用于将 Spring Boot 应用 JAR 包瘦身和加密
+2. **spring-boot-custom-classloader**:用于加载第三方JAR
+
+### 工作流程
+
+```
+┌────────────────────┐
+│ 原始Spring Boot │
+│ JAR包 │
+└──────────┬─────────┘
+ │
+ ▼
+┌────────────────────┐ ┌────────────────────┐
+│ spring-boot-jar- │ │ │
+│ slim-encrypt工具 ├───►│ 提取依赖到libs目录 │
+└──────────┬─────────┘ └────────────────────┘
+ │
+ ▼
+┌────────────────────┐
+│ 瘦身后的JAR包 │
+└──────────┬─────────┘
+ │
+ ▼
+┌────────────────────┐
+│ XJar加密处理 │
+└──────────┬─────────┘
+ │
+ ▼
+┌────────────────────┐
+│ 加密后的JAR包 │
+│ (.xjar) │
+└──────────┬─────────┘
+ │
+ │ 部署
+ ▼
+┌─────────────────────────────────────────┐
+│ 运行时环境 │
+│ ┌─────────────────┐ ┌───────────────┐ │
+│ │ 加密JAR (.xjar) │ │ 提取的依赖库 │ │
+│ └────────┬────────┘ └───────┬───────┘ │
+│ │ │ │
+│ │ │ │
+│ ▼ ▼ │
+│ ┌─────────────────────────────────┐ │
+│ │ PlainTextClassLoader │ │
+│ │ (自定义类加载器) │ │
+│ └─────────────┬─────────────┬─────┘ │
+│ │ │ │
+│ ▼ ▼ │
+│ ┌─────────────────┐ ┌───────────────┐ │
+│ │ 解密JAR内容 │ │ 加载外部依赖 │ │
+│ └─────────────────┘ └───────────────┘ │
+│ │
+└─────────────────────────────────────────┘
+```
+
+1. 使用 spring-boot-jar-slim-encrypt 工具提取原始 JAR 包中的依赖
+2. 将依赖库单独存储在 libs 目录
+3. 对精简后的 JAR 包进行加密
+4. 使用自定义类加载器加载外部依赖
+5. 使用xJar进行解密
+
+## 项目一:spring-boot-custom-classloader
+
+这是一个自定义类加载器项目,它实现了类加载器:
+
+### 1. JarClassLoader 接口
+
+```java
+package com.mangmang;
+
+import java.io.File;
+
+public interface JarClassLoader {
+ String JAR_EXTENSION = ".jar";
+
+ /**
+ * 从指定目录加载所有JAR文件
+ *
+ * @param jarDir 包含要加载的JAR文件的目录路径
+ * @throws IllegalArgumentException 如果jarDir为null或不存在
+ */
+ default void loadJar(String jarDir) {
+ if (jarDir == null || jarDir.trim().isEmpty()) {
+ throw new IllegalArgumentException("JAR目录路径不能为空");
+ }
+
+ File directory = new File(jarDir);
+ if (!directory.exists() || !directory.isDirectory()) {
+ throw new IllegalArgumentException("指定路径不是有效目录: " + jarDir);
+ }
+
+ File[] jarFiles = directory.listFiles(this::isJarFile);
+ if (jarFiles == null) {
+ return;
+ }
+
+ for (File jarFile : jarFiles) {
+ System.out.println("加载 》" + jarFile.getName());
+ scanJarFile(jarFile);
+ }
+ }
+
+ /**
+ * 递归扫描文件或目录以查找JAR文件
+ *
+ * @param file 要扫描的文件或目录
+ * @throws IllegalArgumentException 如果file为null
+ */
+ default void scanJarFile(File file) {
+ if (file == null) {
+ throw new IllegalArgumentException("文件不能为null");
+ }
+
+ if (!file.exists()) {
+ return;
+ }
+
+ if (isJarFile(file)) {
+ addJARFile(file);
+ } else if (file.isDirectory()) {
+ File[] files = file.listFiles();
+ if (files != null) {
+ for (File f : files) {
+ scanJarFile(f);
+ }
+ }
+ }
+ }
+
+ /**
+ * 检查文件是否为JAR文件
+ */
+ default boolean isJarFile(File file) {
+ return file.isFile() && file.getName().endsWith(JAR_EXTENSION);
+ }
+
+ /**
+ * 将JAR文件添加到类加载器
+ *
+ * @param jar 要添加的JAR文件
+ * @throws IllegalArgumentException 如果jar为null或不是有效的JAR文件
+ */
+ void addJARFile(File jar);
+}
+```
+
+这是一个接口,定义了 JAR 文件加载的核心方法:
+
+- `loadJar(String jarDir)`: 从指定目录加载所有 JAR 文件
+- `scanJarFile(File file)`: 递归扫描文件或目录查找 JAR 文件
+- `isJarFile(File file)`: 检查文件是否为 JAR 文件
+- `addJARFile(File jar)`: 将 JAR 文件添加到类加载器
+
+### 2. PlainTextClassLoader 实现
+
+```java
+package com.mangmang;
+
+import org.springframework.boot.context.event.ApplicationStartingEvent;
+import org.springframework.context.ApplicationListener;
+import org.springframework.lang.NonNull;
+
+import java.io.File;
+import java.lang.management.ManagementFactory;
+import java.lang.reflect.Method;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * 明文类加载器
+ *
+ * 该类实现了自定义类加载器接口和Spring应用启动事件监听器接口。
+ * 主要功能是在应用启动时,从JVM启动参数中查找指定路径的JAR文件并加载。
+ * 通过反射机制将JAR文件动态添加到当前线程的类加载器中,实现运行时加载额外的类库。
+ *
+ *
+ * 使用方法:
+ * 1. 在JVM启动参数中添加 -Dexternal.jars.path=你的JAR文件目录路径
+ * 2. 系统将自动加载该目录下所有的JAR文件
+ *
+ *
+ * 示例:
+ * java -Dexternal.jars.path.path=/path/to/jars -jar your-application.jar
+ *
+ */
+public class PlainTextClassLoader implements JarClassLoader, ApplicationListener {
+
+ private final String findPath = "external.jars.path"; // 查找路径的键名
+ private final ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); // 获取当前线程的类加载器
+ private final List jarFiles = new ArrayList<>(); // 存储已加载的JAR文件列表
+
+ /**
+ * 构造函数
+ *
+ * 在初始化时执行以下操作:
+ * 1. 设置当前线程的类加载器
+ * 2. 输出启动日志信息
+ * 3. 从JVM启动参数中检索包含"external.jars.path.path"的参数
+ * 4. 提取路径值并调用loadJar方法加载指定目录下的JAR文件
+ *
+ */
+ public PlainTextClassLoader() {
+ // 设置当前线程的类加载器
+ Thread.currentThread().setContextClassLoader(classLoader);
+
+ // 打印启动信息
+ System.out.println("启动自定义明文类加载器");
+
+ // 查找并加载外部JAR文件
+ loadExternalJarsFromSystemProperties();
+ }
+
+ /**
+ * 从系统属性中查找并加载外部JAR文件
+ */
+ private void loadExternalJarsFromSystemProperties() {
+ List inputArguments = ManagementFactory.getRuntimeMXBean().getInputArguments();
+
+ // 查找包含指定路径参数的启动参数
+ inputArguments.stream()
+ .filter(arg -> arg.contains(findPath))
+ .map(this::extractPathFromArgument)
+ .filter(Objects::nonNull)
+ .forEach(this::loadJar);
+ }
+
+ /**
+ * 从JVM参数中提取路径值
+ *
+ * @param argument JVM启动参数
+ * @return 提取的路径值,如果提取失败则返回null
+ */
+ private String extractPathFromArgument(String argument) {
+ String prefix = "-D" + findPath + "=";
+ if (argument.startsWith(prefix)) {
+ String path = argument.replace(prefix, "");
+ if (!path.isEmpty()) {
+ return path;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * 处理应用程序启动事件
+ *
+ * 当Spring应用启动时会触发此方法。
+ * 目前该方法为空实现,可以在此添加应用启动时需要执行的代码。
+ *
+ *
+ * @param event Spring应用启动事件对象
+ */
+
+ @Override
+ public void onApplicationEvent(@NonNull ApplicationStartingEvent event) {
+ // 应用程序启动事件的处理方法,目前为空
+ }
+
+ /**
+ * 将JAR文件添加到类加载器
+ *
+ * 通过反射机制调用URLClassLoader的addURL方法,将指定的JAR文件URL添加到当前类加载器。
+ * 添加成功后,JAR文件中的类可以被当前JVM加载和使用。
+ * 同时将已加载的JAR文件记录到jarFiles列表中。
+ *
+ *
+ * @param jar 要添加到类加载器的JAR文件对象
+ * @throws RuntimeException 如果添加过程中发生任何异常,将抛出RuntimeException
+ */
+ @Override
+ public void addJARFile(File jar) {
+ if (jar == null) {
+ throw new IllegalArgumentException("JAR文件不能为null");
+ }
+
+ try {
+ addUrlToClassLoader(jar);
+ jarFiles.add(jar);
+ System.out.println(jarFiles);
+ } catch (Exception e) {
+ throw new RuntimeException("添加JAR文件到类加载器失败: " + jar.getName(), e);
+ }
+ }
+
+ /**
+ * 通过反射将JAR文件URL添加到类加载器
+ *
+ * @param jar 要添加的JAR文件
+ * @throws Exception 如果反射操作失败
+ */
+ private void addUrlToClassLoader(File jar) throws Exception {
+ Method addUrlMethod = URLClassLoader.class.getDeclaredMethod("addURL", URL.class);
+ if (!addUrlMethod.isAccessible()) {
+ addUrlMethod.setAccessible(true);
+ }
+ URL jarUrl = jar.toURI().toURL();
+ addUrlMethod.invoke(classLoader, jarUrl);
+ }
+```
+
+这是一个明文类加载器,实现了 `JarClassLoader` 接口和 Spring 的 `ApplicationListener` 接口,用于在 Spring Boot 应用启动时加载外部 JAR 文件:
+
+主要特点:
+
+- 在 Spring Boot 应用启动时自动执行
+- 通过 JVM 参数 `-Dexternal.jars.path=你的JAR文件目录路径` 指定外部 JAR 文件目录
+- 使用反射机制将 JAR 文件 URL 添加到当前线程的类加载器中
+
+使用示例:
+
+```bash
+java -Dexternal.jars.path=/path/to/jars -jar your-application.jar
+```
+
+### 3. MANIFEAT.MF
+
+>src/main/resources/META-INF/spring.factories
+
+```text
+org.springframework.context.ApplicationListener=\
+com.mangmang.PlainTextClassLoader
+```
+
+### 4. pom.xml
+
+```xml
+
+ 4.0.0
+
+ spring-boot-custom-classloader
+ jar
+
+ spring-boot-custom-classloader
+ http://maven.apache.org
+
+
+ UTF-8
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter
+ 2.6.7
+
+
+
+
+```
+
+## 项目二:spring-boot-jar-slim-encrypt
+
+这个工具用于压缩和加密 Spring Boot JAR 文件,主要包含以下两个核心类:
+
+### 1. JarUtil 工具类
+
+```java
+package com.mangmang;
+
+import cn.hutool.core.collection.CollectionUtil;
+import cn.hutool.core.io.FileUtil;
+import io.xjar.XCryptos;
+import io.xjar.XEncryption;
+import org.apache.commons.compress.utils.IOUtils;
+import org.apache.commons.compress.utils.Sets;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+import java.util.jar.JarOutputStream;
+
+public class JarUtil {
+ /**
+ * 表示UTF-8字符编码的常量。
+ * 该变量用于在各种文件操作中强制使用UTF-8编码,
+ * 确保在整个应用程序中一致地处理文本数据。
+ */
+ private static final String UTF_8 = "UTF-8";
+ /**
+ * 常量BOOT_INF_LIB表示JAR文件中通常存储依赖库的默认目录路径。
+ * 该路径主要用于在压缩或排除等操作中识别和处理库文件。
+ */
+ private static final String BOOT_INF_LIB = "BOOT-INF/lib";
+ /**
+ * 用于标识JAR(Java归档)文件的文件扩展名。
+ * 该常量表示JAR文件的标准扩展名,通常用于
+ * 文件过滤、命名或在目录或归档中识别JAR文件的操作。
+ */
+ private static final String JAR_EXTENSION = ".jar";
+ /**
+ * 定义在管理JAR过程中生成的需求文件的后缀,
+ * 特别是在处理依赖项或排除项时使用。
+ * 该字符串用作特定的文件名模式,用于保存与特定服务
+ * 相关的排除依赖项或其他需求的列表。
+ * 默认值为"-requirements.txt"。
+ */
+ private static final String REQUIREMENTS_SUFFIX = "-requirements.txt";
+ /**
+ * 预定义的、不可修改的特定jar文件名集合,被视为
+ * "安全"或"始终包含"的文件。这些jar文件通常在
+ * 处理或压缩操作中免于排除过滤。
+ * 该集合包含以下jar标识符:
+ * - "spring"
+ * - "logback-core"
+ * - "tomcat"
+ * 该变量用于根据jar文件名决定是否包含特定jar文件的操作中。
+ * 它作为应用程序关键或必要jar的白名单。
+ */
+ private static final Set WHITE_LIST_JARS = Sets.newHashSet("spring", "logback-core", "tomcat");
+
+ /**
+ * 通过排除和包含指定的条目来压缩给定的源JAR文件,并将结果写入目标JAR文件。
+ * 它处理源JAR的条目,应用排除和包含规则,还可以将某些条目提取到指定的目录中。
+ * 在此过程中创建一个临时文件,成功完成后将其重命名为目标JAR文件。
+ *
+ * @param serviceName 正在处理的服务名称,主要用于日志记录和创建其他相关文件。
+ * @param sourceJar 要压缩的源JAR文件。
+ * @param includes 指定应保留哪些条目的包含模式集合。可能会自动添加额外的默认包含项。
+ * @param exclusions 指定应排除哪些条目的排除模式集合。
+ * @param targetJar 将写入压缩JAR的文件。
+ * @param libDir 某些被排除的条目可能被提取到的目录(如适用)。
+ */
+ public static void compress(String serviceName, File sourceJar, Set includes, Set exclusions, File targetJar, String libDir) {
+ includes.addAll(WHITE_LIST_JARS);
+ File tempJar = new File(targetJar.getAbsolutePath() + ".tmp");
+ Set excludedJars = new HashSet<>();
+
+ if (processJarEntries(sourceJar, tempJar, includes, exclusions, libDir, excludedJars)) {
+ finalizeCompression(serviceName, targetJar, tempJar, excludedJars, libDir);
+ } else {
+ boolean delete = tempJar.delete();
+ System.out.println("删除临时文件:{" + delete + "}");
+ }
+ }
+
+/**
+ * 处理源JAR文件中的条目以生成临时JAR文件,
+ * 同时根据包含和排除规则过滤条目。如果需要,
+ * 还会将指定的JAR条目提取到库目录中。
+ *
+ * @param sourceJar 要处理的源JAR文件
+ * @param tempJar 要创建的临时JAR文件
+ * @param includes 定义要包含的条目的模式集合
+ * @param exclusions 定义要排除的条目的模式集合
+ * @param libDir 特定JAR应该被提取到的目录,如果不需要提取则为null
+ * @param excludedJars 用于存储被排除的JAR条目名称的集合
+ * @return 如果处理成功完成则返回true,否则返回false
+ */
+ private static boolean processJarEntries(File sourceJar, File tempJar, Set includes,
+ Set exclusions, String libDir, Set excludedJars) {
+ try (JarFile jar = new JarFile(sourceJar);
+ JarOutputStream tempJarStream = new JarOutputStream(Files.newOutputStream(tempJar.toPath()))) {
+ for (Enumeration entries = jar.entries(); entries.hasMoreElements(); ) {
+ JarEntry entry = entries.nextElement();
+ String entryName = entry.getName();
+
+ if (shouldExcludeEntry(entryName, includes, exclusions)) {
+ if (libDir != null && !libDir.isEmpty()) {
+ extractJarToLib(jar, entry, libDir, excludedJars);
+ }
+ continue;
+ }
+
+ copyEntryToJar(jar, entry, tempJarStream);
+ }
+ return true;
+ } catch (Exception ex) {
+ System.out.println("处理异常:" + ex.getMessage());
+ return false;
+ }
+ }
+
+ /**
+ * 根据预定义的标准确定是否应排除特定的jar条目。
+ * 该方法评估条目是否属于BOOT-INF/lib目录,是否具有".jar"扩展名,
+ * 以及是否不满足由includes和exclusions集合定义的包含/排除条件。
+ *
+ * @param entryName 要检查的jar条目名称
+ * @param includes jar包含条件的集合
+ * @param exclusions jar排除条件的集合
+ * @return 如果应排除该条目则返回true,否则返回false
+ */
+ private static boolean shouldExcludeEntry(String entryName, Set includes, Set exclusions) {
+ if (!entryName.startsWith(BOOT_INF_LIB)) {
+ return false;
+ }
+ String jarName = entryName.substring(entryName.lastIndexOf("/") + 1);
+ return jarName.endsWith(JAR_EXTENSION) && !isWhiteJar(jarName, includes, exclusions);
+ }
+
+ /**
+ * 从JAR文件中提取指定的JAR条目到指定的库目录。
+ * 如果条目对应于JAR文件且在库目录中尚不存在,
+ * 则将其复制到该目录,并将其名称添加到被排除的JAR集合中。
+ *
+ * @param jar 包含要提取的条目的JAR文件
+ * @param entry 要提取的JAR条目
+ * @param libDir 提取的JAR文件将被复制到的目录
+ * @param excludedJars 处理过程中被排除的JAR文件名的集合
+ * @throws IOException 如果在从文件系统读取或写入时发生I/O错误
+ */
+ private static void extractJarToLib(JarFile jar, JarEntry entry, String libDir,
+ Set excludedJars) throws IOException {
+ String jarName = entry.getName().substring(entry.getName().lastIndexOf("/") + 1);
+ File outputFile = new File(libDir, jarName);
+
+ if (!outputFile.exists()) {
+ FileUtil.touch(outputFile);
+ }
+
+ try (InputStream input = jar.getInputStream(entry);
+ FileOutputStream output = new FileOutputStream(outputFile)) {
+ IOUtils.copy(input, output);
+ excludedJars.add(jarName);
+ System.out.println("Excluding: " + outputFile.getAbsolutePath());
+ }
+ }
+
+ /**
+ * 将单个{@link JarEntry}从源{@link JarFile}复制到目标{@link JarOutputStream}。
+ *
+ * @param jar 包含要复制的条目的源{@link JarFile}
+ * @param entry 要复制的{@link JarEntry}
+ * @param output 将写入条目的目标{@link JarOutputStream}
+ * @throws IOException 如果在复制过程中发生I/O错误
+ */
+ private static void copyEntryToJar(JarFile jar, JarEntry entry, JarOutputStream output) throws IOException {
+ try (InputStream input = jar.getInputStream(entry)) {
+ output.putNextEntry(entry);
+ IOUtils.copy(input, output);
+ }
+ }
+
+ /**
+ * 通过处理目标和临时JAR文件完成压缩过程,
+ * 并可选择将排除的JAR列表写入需求文件。
+ *
+ * @param serviceName 与压缩过程关联的服务名称
+ * @param targetJar 要创建或更新的目标JAR文件
+ * @param tempJar 压缩过程中使用的临时JAR文件
+ * @param excludedJars 压缩过程中排除的JAR文件名的集合
+ * @param libDir 存储库文件的目录
+ */
+ private static void finalizeCompression(String serviceName, File targetJar, File tempJar, Set excludedJars, String libDir) {
+ boolean deleteTarget = targetJar.delete();
+ System.out.println("删除目标文件结果:" + deleteTarget);
+ boolean rename = tempJar.renameTo(targetJar);
+ System.out.println("临时文件重命名结果:" + rename);
+
+ if (CollectionUtil.isNotEmpty(excludedJars)) {
+ File requirementsFile = new File(libDir, serviceName + REQUIREMENTS_SUFFIX);
+ FileUtil.writeLines(excludedJars, requirementsFile, UTF_8);
+ }
+ }
+
+ /**
+ * 确定给定的jar文件名是否匹配任何指定的包含模式
+ * 且不是排除集的一部分。
+ *
+ * @param jarName 要检查的jar文件名
+ * @param includes 表示包含模式的字符串集合;如果jar名称包含
+ * 这些模式中的任何一个,则被视为匹配
+ * @param exclusions 表示要排除的jar名称的字符串集合;如果jar名称
+ * 存在于此集合中,则被视为排除
+ * @return 如果jar名称匹配任何包含模式且不是
+ * 排除集的一部分,则返回{@code true},否则返回{@code false}
+ */
+ private static boolean isWhiteJar(String jarName, Set includes, Set exclusions) {
+ if (exclusions.contains(jarName)) {
+ return false;
+ }
+ for (String include : includes) {
+ if (jarName.contains(include)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * 使用提供的密码将指定的原始JAR文件加密为加密的JAR文件。
+ * 支持包含和排除模式,用于选择性地加密JAR中的文件条目。
+ *
+ * @param rawFile 要加密的输入JAR文件
+ * @param xjarFile 输出的加密JAR文件
+ * @param pass 用于加密的密码,如果为空则生成默认密码
+ * @param includes 指定要包含在加密中的文件条目的包含模式数组
+ * @param excludes 指定要从加密中排除的文件条目的排除模式数组
+ * @throws Exception 如果在加密过程中发生错误
+ */
+ public static void encrypt(File rawFile, File xjarFile, String pass, String[] includes, String[] excludes) throws Exception {
+ XEncryption xe = XCryptos.encryption().from(rawFile.getAbsolutePath());
+ xe.use((pass == null || pass.trim().isEmpty() || pass.startsWith("默认")) ? "0755isa" : pass);
+ if (includes != null) {
+ for (String include : includes) {
+ xe.include(include);
+ }
+ }
+ if (excludes != null) {
+ for (String exclude : excludes) {
+ xe.exclude(exclude);
+ }
+ }
+ xe.to(xjarFile);
+ }
+}
+```
+
+这个工具类提供了两个主要功能:
+
+#### JAR 包压缩功能
+
+`compress` 方法实现了 JAR 瘦身功能:
+
+- 根据包含列表和排除列表过滤 JAR 中的依赖
+- 将被排除的依赖提取到指定的库目录
+- 生成一个记录排除依赖的需求文件
+- 创建一个只包含必要依赖的精简 JAR 文件
+
+#### JAR 包加密功能
+
+`encrypt` 方法利用 XJar 库实现了 JAR 加密:
+
+- 支持指定加密密码
+- 通过包含和排除模式选择性地加密 JAR 中的内容
+- 生成加密后的 XJar 文件
+
+### 2. 主应用
+
+```java
+package com.mangmang;
+
+import cn.hutool.core.io.FileUtil;
+import org.dom4j.Document;
+import org.dom4j.Element;
+import org.dom4j.io.SAXReader;
+import org.xml.sax.SAXException;
+
+import java.io.File;
+import java.util.*;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.stream.Collectors;
+
+/**
+ *
+ * SpringBoot JAR文件压缩和加密工具的主类。
+ *
+ * 此类提供了一个基于XJar加密技术的框架,用于压缩和加密JAR文件。
+ * 它支持根据XML配置文件中的规则包含或排除特定的依赖项,并将结果保存为加密的JAR文件。
+ * 程序从原始JAR文件开始,根据需要压缩它们,然后应用加密。
+ *
+ */
+public class SpringBootJarSlimEncryptApplication {
+
+ /**
+ * 类的Logger实例,用于记录程序执行过程中的各种级别的日志信息。
+ */
+ private static final Logger LOGGER = Logger.getLogger(SpringBootJarSlimEncryptApplication.class.getName());
+
+ /**
+ * 包含程序所有配置常量的内部静态类。
+ * 这些常量定义了输入/输出目录、加密设置和其他程序所需的各种配置参数。
+ */
+ private static class Config {
+ /**
+ * 存储依赖项XML文件的目录路径。
+ * 默认为"./xml/",可通过系统属性"xml.dir"覆盖。
+ */
+ static final String DEPENDENCY_XML_DIR = System.getProperty("xml.dir", "./config/xml/");
+
+ /**
+ * 存储原始JAR文件的目录路径。
+ * 默认为"./rawJars/",可通过系统属性"raw.dir"覆盖。
+ */
+ static final String RAW_JAR_DIR = System.getProperty("raw.dir", "./config/rawJars/");
+
+ /**
+ * 存储压缩后JAR文件的目录路径。
+ * 默认为"./compressJars/",可通过系统属性"compress.dir"覆盖。
+ */
+ static final String COMPRESS_JAR_DIR = System.getProperty("compress.dir", "./config/compressJars/");
+
+ /**
+ * 存储提取的库文件的目录路径。
+ * 默认为"./libs/",可通过系统属性"libs.dir"覆盖。
+ */
+ static final String LIB_DIR = System.getProperty("libs.dir", "./config/libs/");
+
+ /**
+ * 存储加密后的XJar文件的目录路径。
+ * 默认为"./xJars/",可通过系统属性"xjar.dir"覆盖。
+ */
+ static final String X_JAR_DIR = System.getProperty("xjar.dir", "./config/xJars/");
+
+ /**
+ * 控制是否启用压缩功能的标志。
+ * 默认为true,可通过系统属性"compress.enable"覆盖。
+ */
+ static final boolean COMPRESS_ENABLED = Boolean.parseBoolean(System.getProperty("compress.enable", "true"));
+
+ /**
+ * 定义要包含在XJar加密中的文件模式数组。
+ * 这些文件将在加密过程中被加密。
+ */
+ static final String[] X_JAR_INCLUDES = new String[]{"/com/mangmang/**", "*.yaml", "*.yml", "mapper/**.xml"};
+
+ /**
+ * 定义要从XJar加密中排除的文件模式数组。
+ * 这些文件在加密过程中将保持未加密状态。
+ */
+ static final String[] X_JAR_EXCLUDES = new String[]{"/com/mangmang/pinyin/**"};
+
+ /**
+ * 用于X_JAR文件加密的密码。
+ */
+ static final String ENCRYPTION_PASSWORD = "0755isa";
+
+ /**
+ * 包含要排除的依赖项列表的XML文件的名称。
+ */
+ static final String EXCLUSIONS_XML = "config/exclusions.xml";
+
+ /**
+ * 包含要包含的依赖项列表的XML文件的名称。
+ */
+ static final String INCLUDES_XML = "config/includes.xml";
+ }
+ /**
+ * 应用程序的主入口点。
+ * 4. 处理所有服务,根据需要进行压缩和加密
+ *
+ *
+ * @param args 命令行参数,当前未使用
+ */
+ public static void main(String[] args) {
+ try {
+ //1. 确保所有必需的目录存在
+ ensureDirectoriesExist();
+ //2. 查找所有原始JAR服务
+ Set serviceList = findAllRawJarServices();
+ //3. 从XML配置文件加载排除和包含的JAR
+ Set exclusionJars = loadJarsFromXml(new File(Config.DEPENDENCY_XML_DIR + File.separator + Config.EXCLUSIONS_XML));
+ Set includedJars = loadJarsFromXml(new File(Config.DEPENDENCY_XML_DIR + File.separator + Config.INCLUDES_XML));
+ processAllServices(serviceList, includedJars, exclusionJars);
+ } catch (Exception e) {
+ LOGGER.log(Level.SEVERE, "处理JAR文件过程中发生错误", e);
+ }
+ }
+
+ /**
+ * 处理提供的服务列表,对每个服务应用包含和排除规则,然后处理它们。
+ *
+ * 对于列表中的每个服务,此方法会调用{@link #processService}方法,
+ * 并记录任何可能发生的错误。
+ *
+ *
+ * @param serviceList 要处理的服务名称集合
+ * @param includedJars 定义要包含的JAR的规则集合
+ * @param exclusionJars 定义要排除的JAR的规则集合
+ */
+ private static void processAllServices(Set serviceList, Set includedJars, Set exclusionJars) {
+ for (String service : serviceList) {
+ LOGGER.info("开始处理" + service);
+ try {
+ processService(service, includedJars, exclusionJars);
+ } catch (Exception e) {
+ LOGGER.log(Level.SEVERE, "处理服务 " + service + " 时发生错误", e);
+ }
+ }
+ }
+
+ /**
+ * 处理单个服务,应用压缩(如果启用)和加密操作。
+ *
+ * 根据配置,此方法将执行以下操作之一:
+ * - 如果启用了压缩:压缩原始JAR文件,然后加密压缩后的JAR
+ * - 如果禁用了压缩:直接加密原始JAR文件
+ *
+ *
+ * @param service 要处理的服务的名称
+ * @param includedJars 要包含在压缩JAR中的JAR文件集合
+ * @param exclusionJars 要从压缩JAR中排除的JAR文件集合
+ * @throws Exception 如果在处理过程中发生错误
+ */
+ private static void processService(String service, Set includedJars, Set exclusionJars) throws Exception {
+ File rawJarFile = new File(Config.RAW_JAR_DIR + File.separator + service + ".jar");
+ File xjarFile = new File(Config.X_JAR_DIR + File.separator + service + ".xjar");
+
+ if (Config.COMPRESS_ENABLED) {
+ File compressedJarFile = new File(Config.COMPRESS_JAR_DIR + File.separator + service + "-compress.jar");
+ JarUtil.compress(service, rawJarFile, includedJars, exclusionJars, compressedJarFile, Config.LIB_DIR);
+ JarUtil.encrypt(compressedJarFile, xjarFile, Config.ENCRYPTION_PASSWORD, Config.X_JAR_INCLUDES, Config.X_JAR_EXCLUDES);
+ if (xjarFile.exists()) {
+ LOGGER.info("压缩并加密" + service + "成功");
+ }
+ } else {
+ JarUtil.encrypt(rawJarFile, xjarFile, Config.ENCRYPTION_PASSWORD, Config.X_JAR_INCLUDES, Config.X_JAR_EXCLUDES);
+ if (xjarFile.exists()) {
+ LOGGER.info("加密" + service + "成功");
+ }
+ }
+ }
+
+
+
+ /**
+ * 查找RAW_JAR_DIR目录中的所有JAR文件,并返回不带.jar扩展名的服务名称集合。
+ *
+ * 此方法扫描配置的原始JAR目录,查找所有以.jar结尾的文件,
+ * 然后从文件名中删除.jar扩展名以获取服务名称。
+ *
+ *
+ * @return 原始JAR目录中找到的服务名称的集合(不带.jar扩展名)
+ */
+ private static Set findAllRawJarServices() {
+ File dir = new File(Config.RAW_JAR_DIR);
+ File[] files = dir.listFiles();
+ if (files == null) {
+ return Collections.emptySet();
+ }
+
+ return Arrays.stream(files)
+ .filter(file -> file.getName().endsWith(".jar"))
+ .map(file -> file.getName().replace(".jar", ""))
+ .collect(Collectors.toSet());
+ }
+
+ /**
+ * 确保所有必需的目录存在,如果不存在则创建它们。
+ *
+ * 此方法检查配置中定义的所有目录,并在必要时创建它们。
+ * 这些目录包括:
+ * - 依赖项XML目录
+ * - 原始JAR目录
+ * - 压缩JAR目录
+ * - 库目录
+ * - X_JAR目录
+ *
+ */
+ private static void ensureDirectoriesExist() {
+ String[] dirs = {
+ Config.DEPENDENCY_XML_DIR,
+ Config.RAW_JAR_DIR,
+ Config.COMPRESS_JAR_DIR,
+ Config.LIB_DIR,
+ Config.X_JAR_DIR
+ };
+
+ for (String dir : dirs) {
+ File file = new File(dir);
+ if (!file.exists()) {
+ FileUtil.mkdir(file);
+ }
+ }
+ }
+
+
+ /**
+ * 从XML文件中加载JAR依赖项列表。
+ *
+ * 此方法解析指定的XML文件,查找依赖项元素,并提取artifactId和可选的version,
+ * 以构建JAR文件名列表。
+ *
+ *
+ * @param xmlFile 包含依赖项列表的XML文件
+ * @return 从XML文件中提取的JAR名称集合
+ * @throws SAXException 如果在解析XML时发生错误
+ */
+ private static Set loadJarsFromXml(File xmlFile) throws SAXException {
+ Set jars = new HashSet<>();
+ if (!xmlFile.exists()) {
+ return jars;
+ }
+
+ SAXReader saxReader = new SAXReader();
+ saxReader.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
+ saxReader.setEncoding("UTF-8");
+
+ try {
+ Document document = saxReader.read(xmlFile);
+ Element rootElement = document.getRootElement();
+
+ if (!rootElement.hasContent()) {
+ return jars;
+ }
+
+ List dependencies = rootElement.elements("dependency");
+ if (dependencies.isEmpty()) {
+ return jars;
+ }
+
+ for (Element element : dependencies) {
+ Element artifactId = element.element("artifactId");
+ String artifactIdText = artifactId.getText();
+ Element version = element.element("version");
+
+ String jarName;
+ if (Objects.nonNull(version)) {
+ String versionText = version.getText();
+ jarName = artifactIdText + "-" + versionText + ".jar";
+ } else {
+ jarName = artifactIdText;
+ }
+ jars.add(jarName);
+ }
+ } catch (Exception e) {
+ LOGGER.log(Level.WARNING, "解析XML文件 " + xmlFile.getName() + " 时发生错误", e);
+ }
+
+ return jars;
+ }
+}
+```
+
+这是工具的主应用类,提供了完整的命令行接口来处理 JAR 文件的压缩和加密:
+
+主要功能:
+
+- 通过配置常量定义输入/输出目录、加密设置等
+- 支持从 XML 配置文件加载要包含和排除的依赖项
+- 批量处理多个 JAR 文件
+- 支持通过系统属性覆盖默认配置
+
+主要配置参数:
+
+- `xml.dir`: 依赖项 XML 文件目录(默认: `./config/xml/`)
+- `raw.dir`: 原始 JAR 文件目录(默认: `./config/rawJars/`)
+- `compress.dir`: 压缩后 JAR 文件目录(默认: `./config/compressJars/`)
+- `libs.dir`: 提取的库文件目录(默认: `./config/libs/`)
+- `xjar.dir`: 加密后的 XJar 文件目录(默认: `./config/xJars/`)
+- `compress.enable`: 是否启用压缩功能(默认: `true`)
+
+### 3. pom.xml
+
+```xml
+
+ 4.0.0
+
+ spring-boot-jar-slim-encrypt
+ jar
+
+ spring-boot-jar-slim-encrypt
+ http://maven.apache.org
+
+
+ UTF-8
+
+
+
+
+
+ jitpack.io
+ https://jitpack.io
+
+
+
+
+
+
+ com.github.core-lib
+ xjar
+ 4.0.0
+
+
+ org.dom4j
+ dom4j
+ 2.1.4
+
+
+ cn.hutool
+ hutool-core
+ 5.8.25
+
+
+ org.apache.commons
+ commons-lang3
+ 3.0
+
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-jar-plugin
+ 3.2.0
+
+
+
+
+
+
+ com.mangmang.SpringBootJarSlimEncryptApplication
+
+
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-assembly-plugin
+ 3.3.0
+
+
+ make-assembly
+
+
+ package
+
+
+ single
+
+
+
+
+
+ jar-with-dependencies
+
+
+
+
+ com.mangmang.SpringBootJarSlimEncryptApplication
+
+
+
+
+
+
+
+
+```
+
+
+
+## 使用步骤
+
+### 步骤 1: 集成自定义类加载器
+
+在你的 Spring Boot 项目中添加自定义类加载器依赖:
+
+```xml
+
+ com.mangmang
+ spring-boot-custom-classloader
+ 1.0.0
+
+```
+
+### 步骤 2: 构建 Spring Boot 应用
+
+正常构建你的 Spring Boot 应用:
+
+```bash
+mvn clean package
+```
+
+### 步骤 3: 配置依赖排除和包含规则
+
+>默认路径为根目录下的./config/xml/
+
+创建两个 XML 文件以定义要排除和包含的依赖项:
+
+**exclusions.xml**(要排除的依赖):
+
+```xml
+
+
+
+
+ cn.hutool
+ hutool-all
+ 5.8.26
+
+
+```
+
+**includes.xml**(要保留的依赖):
+
+```xml
+
+
+
+ com.fasterxml.jackson.core
+ jackson-annotations
+ 2.10.3
+
+
+```
+
+### 步骤 4: 执行 JAR 瘦身和加密工具
+
+将你的 Spring Boot JAR 文件放入 `./config/rawJars/` 目录,然后运行瘦身加密工具:
+
+```bash
+java -jar spring-boot-jar-slim-encrypt.jar
+```
+
+你可以通过系统属性覆盖默认配置:
+
+```bash
+java -Dcompress.enable=true -Dlibs.dir=/custom/libs/path -jar spring-boot-jar-slim-encrypt.jar
+```
+
+### 步骤 5: 部署和运行
+
+#### 1. 精简JAR启动指令
+```shell
+java -Dexternal.jars.path.path=/path/to/jars -jar your-application.jar
+```
+
+#### 2. 加密JAR启动指令
+1. 需要进入xjar.dir(默认: `./config/xJars/`)对应目录
+2. 使用go build xjar.go 编译
+3. 启动win10示例
+ ```powershell
+ .\xjar.exe java "-Dexternal.jars.path=..\libs" -jar .\spring-boot-thin-launcher-1.0-SNAPSHOT.xjar
+ ```
+4. linux 示例
+ ```shell
+ ./xjar java -Xms256m -Xmx1024m -Dexternal.jars.path=./libs -jar /path/to/encrypted.jar
+ ```
+
+## 技术原理解析
+
+### JAR 瘦身原理
+
+1. 扫描 Spring Boot JAR 中的 `BOOT-INF/lib` 目录
+2. 根据配置的排除和包含规则过滤依赖
+3. 将被排除的依赖提取到外部目录
+4. 创建一个不包含被排除依赖的新 JAR 文件
+
+### JAR 加密原理
+
+1. 使用 XJar 库实现 JAR 文件内容的加密
+2. 只加密指定的文件模式(如 Java 类文件、配置文件等)
+3. 避免加密某些需要保持明文的资源
+
+### 自定义类加载器原理
+
+1. 在 Spring Boot 应用启动时初始化自定义类加载器
+2. 扫描指定目录下的 JAR 文件
+3. 使用反射机制将 JAR 文件 URL 添加到当前类加载器
+
+## 方案优势
+
+1. **减小 JAR 体积**:将大型依赖库外置,显著减小主 JAR 文件体积
+2. **提高安全性**:通过加密保护核心业务逻辑和敏感配置
+3. **灵活配置**:支持通过 XML 配置和系统属性灵活控制瘦身和加密过程
+4. **无缝集成**:与 Spring Boot 应用无缝集成,无需修改应用代码
+
+## 注意事项
+
+1. 确保加密密码安全保存,丢失密码将导致无法运行加密的 JAR
+2. 测试瘦身后的应用,确保所有需要的依赖都能正确加载
+3. 部署时必须将提取的依赖库和加密后的 JAR 一起部署
+4. 启动应用时必须指定外部库路径参数
+
+## 结论
+
+通过结合 JAR 瘦身、JAR 加密和自定义类加载器,我们成功构建了一套完整的 Spring Boot 应用优化和保护方案。这不仅有效减小了部署包的体积,还提高了应用的安全性,为企业级 Spring Boot 应用的部署提供了一种实用的解决方案。
+
+在实际应用中,可以根据具体需求调整配置参数,以达到最佳的平衡点。例如,可以根据应用规模和安全需求调整要排除的依赖和加密的文件模式。
+
+这套工具实现了将原本臃肿的 Spring Boot 应用拆分为核心加密 JAR 和外部依赖库的方案,使得应用部署更加灵活,也为应用分发和更新提供了更多可能性。
+
+------
+
+*注:本方案适用于需要保护核心业务逻辑或减小部署包体积的 Spring Boot 应用。对于简单应用或开源项目,可能不必使用这么复杂的方案。*
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/programming/java/工具箱/SpringBoot打包体积优化.md b/src/programming/java/工具箱/SpringBoot打包体积优化.md
deleted file mode 100644
index 4f21359..0000000
--- a/src/programming/java/工具箱/SpringBoot打包体积优化.md
+++ /dev/null
@@ -1,616 +0,0 @@
----
-icon: bi:arrows-expand
-date: 2025-05-13
-category:
- - 实用工具
- - JAVA
- - SpringBoot
- - JAR
-tag:
- - JAR包瘦身
-title: SpringBoot打包体积优化.md
----
-
-SpringBoot打包体积优化
-
-
-# Spring Boot JAR 瘦身与加密:构建安全高效的部署方案
-
-在 Spring Boot 应用程序部署过程中,我们常常面临两个主要挑战:
-
-1. **JAR 包体积过大**:Spring Boot 应用打包时会将所有依赖一起打包,导致最终 JAR 文件臃肿
-2. **代码安全性问题**:部署到客户环境或公开场合的 JAR 包可能被反编译,造成核心业务逻辑泄露
-
-为了解决这些问题,本文将介绍一套完整的解决方案,包括 JAR 包瘦身和 JAR 包加密两部分,以及配套的自定义类加载器,实现高效安全的
-Spring Boot 应用部署。
-
-## 整体方案设计
-
-该方案由两个主要项目组成:
-
-1. **spring-boot-jar-slim-encrypt**:用于将 Spring Boot 应用 JAR 包瘦身和加密
-2. **spring-boot-custom-classloader**:用于加载瘦身和加密后的 JAR 包
-
-### 工作流程
-
-```
-┌────────────────────┐
-│ 原始Spring Boot │
-│ JAR包 │
-└──────────┬─────────┘
- │
- ▼
-┌────────────────────┐ ┌────────────────────┐
-│ spring-boot-jar- │ │ │
-│ slim-encrypt工具 ├───►│ 提取依赖到libs目录 │
-└──────────┬─────────┘ └────────────────────┘
- │
- ▼
-┌────────────────────┐
-│ 瘦身后的JAR包 │
-└──────────┬─────────┘
- │
- ▼
-┌────────────────────┐
-│ XJar加密处理 │
-└──────────┬─────────┘
- │
- ▼
-┌────────────────────┐
-│ 加密后的JAR包 │
-│ (.xjar) │
-└──────────┬─────────┘
- │
- │ 部署
- ▼
-┌─────────────────────────────────────────┐
-│ 运行时环境 │
-│ ┌─────────────────┐ ┌───────────────┐ │
-│ │ 加密JAR (.xjar) │ │ 提取的依赖库 │ │
-│ └────────┬────────┘ └───────┬───────┘ │
-│ │ │ │
-│ │ │ │
-│ ▼ ▼ │
-│ ┌─────────────────────────────────┐ │
-│ │ PlainTextClassLoader │ │
-│ │ (自定义类加载器) │ │
-│ └─────────────┬─────────────┬─────┘ │
-│ │ │ │
-│ ▼ ▼ │
-│ ┌─────────────────┐ ┌───────────────┐ │
-│ │ 解密JAR内容 │ │ 加载外部依赖 │ │
-│ └─────────────────┘ └───────────────┘ │
-│ │
-└─────────────────────────────────────────┘
-```
-
-1. 使用 spring-boot-jar-slim-encrypt 工具提取原始 JAR 包中的依赖
-2. 将依赖库单独存储在 libs 目录
-3. 对精简后的 JAR 包进行加密
-4. 使用自定义类加载器加载外部依赖和解密 JAR 包
-
-## 项目一:spring-boot-custom-classloader
-
-这是一个自定义类加载器项目,它实现了两种类加载器:
-
-### 1. JarClassLoader 接口
-
-```java
-package com.mangmang;
-
-import java.io.File;
-
-public interface JarClassLoader {
- String JAR_EXTENSION = ".jar";
-
- /**
- * 从指定目录加载所有JAR文件
- *
- * @param jarDir 包含要加载的JAR文件的目录路径
- * @throws IllegalArgumentException 如果jarDir为null或不存在
- */
- default void loadJar(String jarDir) {
- if (jarDir == null || jarDir.trim().isEmpty()) {
- throw new IllegalArgumentException("JAR目录路径不能为空");
- }
-
- File directory = new File(jarDir);
- if (!directory.exists() || !directory.isDirectory()) {
- throw new IllegalArgumentException("指定路径不是有效目录: " + jarDir);
- }
-
- File[] jarFiles = directory.listFiles(this::isJarFile);
- if (jarFiles == null) {
- return;
- }
-
- for (File jarFile : jarFiles) {
- System.out.println("加载 》" + jarFile.getName());
- scanJarFile(jarFile);
- }
- }
-
- /**
- * 递归扫描文件或目录以查找JAR文件
- *
- * @param file 要扫描的文件或目录
- * @throws IllegalArgumentException 如果file为null
- */
- default void scanJarFile(File file) {
- if (file == null) {
- throw new IllegalArgumentException("文件不能为null");
- }
-
- if (!file.exists()) {
- return;
- }
-
- if (isJarFile(file)) {
- addJARFile(file);
- } else if (file.isDirectory()) {
- File[] files = file.listFiles();
- if (files != null) {
- for (File f : files) {
- scanJarFile(f);
- }
- }
- }
- }
-
- /**
- * 检查文件是否为JAR文件
- */
- default boolean isJarFile(File file) {
- return file.isFile() && file.getName().endsWith(JAR_EXTENSION);
- }
-
- /**
- * 将JAR文件添加到类加载器
- *
- * @param jar 要添加的JAR文件
- * @throws IllegalArgumentException 如果jar为null或不是有效的JAR文件
- */
- void addJARFile(File jar);
-}
-
-```
-
-这是一个接口,定义了 JAR 文件加载的核心方法:
-
-- `loadJar(String jarDir)`: 从指定目录加载所有 JAR 文件
-- `scanJarFile(File file)`: 递归扫描文件或目录查找 JAR 文件
-- `isJarFile(File file)`: 检查文件是否为 JAR 文件
-- `addJARFile(File jar)`: 将 JAR 文件添加到类加载器
-
-### 2. PlainTextClassLoader 实现
-
-```java
-package com.mangmang;
-
-import org.springframework.boot.context.event.ApplicationStartingEvent;
-import org.springframework.context.ApplicationListener;
-import org.springframework.lang.NonNull;
-
-import java.io.File;
-import java.lang.management.ManagementFactory;
-import java.lang.reflect.Method;
-import java.net.URL;
-import java.net.URLClassLoader;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Objects;
-
-/**
- * 明文类加载器
- *
- * 该类实现了自定义类加载器接口和Spring应用启动事件监听器接口。
- * 主要功能是在应用启动时,从JVM启动参数中查找指定路径的JAR文件并加载。
- * 通过反射机制将JAR文件动态添加到当前线程的类加载器中,实现运行时加载额外的类库。
- *
- *
- * 使用方法:
- * 1. 在JVM启动参数中添加 -Dexternal.jars.path=你的JAR文件目录路径
- * 2. 系统将自动加载该目录下所有的JAR文件
- *
- *
- * 示例:
- * java -Dexternal.jars.path.path=/path/to/jars -jar your-application.jar
- *
- */
-public class PlainTextClassLoader implements JarClassLoader, ApplicationListener {
-
- private final String findPath = "external.jars.path"; // 查找路径的键名
- private final ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); // 获取当前线程的类加载器
- private final List jarFiles = new ArrayList<>(); // 存储已加载的JAR文件列表
-
- /**
- * 构造函数
- *
- * 在初始化时执行以下操作:
- * 1. 设置当前线程的类加载器
- * 2. 输出启动日志信息
- * 3. 从JVM启动参数中检索包含"external.jars.path.path"的参数
- * 4. 提取路径值并调用loadJar方法加载指定目录下的JAR文件
- *
- */
- public PlainTextClassLoader() {
- // 设置当前线程的类加载器
- Thread.currentThread().setContextClassLoader(classLoader);
-
- // 打印启动信息
- System.out.println("启动自定义明文类加载器");
-
- // 查找并加载外部JAR文件
- loadExternalJarsFromSystemProperties();
- }
-
- /**
- * 从系统属性中查找并加载外部JAR文件
- */
- private void loadExternalJarsFromSystemProperties() {
- List inputArguments = ManagementFactory.getRuntimeMXBean().getInputArguments();
-
- // 查找包含指定路径参数的启动参数
- inputArguments.stream()
- .filter(arg -> arg.contains(findPath))
- .map(this::extractPathFromArgument)
- .filter(Objects::nonNull)
- .forEach(this::loadJar);
- }
-
- /**
- * 从JVM参数中提取路径值
- *
- * @param argument JVM启动参数
- * @return 提取的路径值,如果提取失败则返回null
- */
- private String extractPathFromArgument(String argument) {
- String prefix = "-D" + findPath + "=";
- if (argument.startsWith(prefix)) {
- String path = argument.replace(prefix, "");
- if (!path.isEmpty()) {
- return path;
- }
- }
- return null;
- }
-
- /**
- * 处理应用程序启动事件
- *
- * 当Spring应用启动时会触发此方法。
- * 目前该方法为空实现,可以在此添加应用启动时需要执行的代码。
- *
- *
- * @param event Spring应用启动事件对象
- */
-
- @Override
- public void onApplicationEvent(@NonNull ApplicationStartingEvent event) {
- // 应用程序启动事件的处理方法,目前为空
- }
-
- /**
- * 将JAR文件添加到类加载器
- *
- * 通过反射机制调用URLClassLoader的addURL方法,将指定的JAR文件URL添加到当前类加载器。
- * 添加成功后,JAR文件中的类可以被当前JVM加载和使用。
- * 同时将已加载的JAR文件记录到jarFiles列表中。
- *
- *
- * @param jar 要添加到类加载器的JAR文件对象
- * @throws RuntimeException 如果添加过程中发生任何异常,将抛出RuntimeException
- */
- @Override
- public void addJARFile(File jar) {
- if (jar == null) {
- throw new IllegalArgumentException("JAR文件不能为null");
- }
-
- try {
- addUrlToClassLoader(jar);
- jarFiles.add(jar);
- System.out.println(jarFiles);
- } catch (Exception e) {
- throw new RuntimeException("添加JAR文件到类加载器失败: " + jar.getName(), e);
- }
- }
-
- /**
- * 通过反射将JAR文件URL添加到类加载器
- *
- * @param jar 要添加的JAR文件
- * @throws Exception 如果反射操作失败
- */
- private void addUrlToClassLoader(File jar) throws Exception {
- Method addUrlMethod = URLClassLoader.class.getDeclaredMethod("addURL", URL.class);
- if (!addUrlMethod.isAccessible()) {
- addUrlMethod.setAccessible(true);
- }
- URL jarUrl = jar.toURI().toURL();
- addUrlMethod.invoke(classLoader, jarUrl);
- }
-```
-
-这是一个明文类加载器,实现了 `JarClassLoader` 接口和 Spring 的 `ApplicationListener` 接口,用于在
-Spring Boot 应用启动时加载外部 JAR 文件:
-
-主要特点:
-
-- 在 Spring Boot 应用启动时自动执行
-- 通过 JVM 参数 `-Dexternal.jars.path=你的JAR文件目录路径` 指定外部 JAR 文件目录
-- 使用反射机制将 JAR 文件 URL 添加到当前线程的类加载器中
-
-使用示例:
-
-```bash
-java -Dexternal.jars.path=/path/to/jars -jar your-application.jar
-```
-
-## 项目二:spring-boot-jar-slim-encrypt
-
-这个工具用于压缩和加密 Spring Boot JAR 文件,主要包含以下两个核心类:
-
-### 1. JarUtil 工具类
-
-```java
-// 路径:spring-boot-jar-slim-encrypt/src/main/java/com/mangmang/JarUtil.java
-package com.sunri;
-
-import cn.hutool.core.collection.CollectionUtil;
-import cn.hutool.core.io.FileUtil;
-import io.xjar.XCryptos;
-import io.xjar.XEncryption;
-import org.apache.commons.compress.utils.IOUtils;
-import org.apache.commons.compress.utils.Sets;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.nio.file.Files;
-import java.util.Enumeration;
-import java.util.HashSet;
-import java.util.Set;
-import java.util.jar.JarEntry;
-import java.util.jar.JarFile;
-import java.util.jar.JarOutputStream;
-
-public class JarUtil {
- /**
- * 表示UTF-8字符编码的常量。
- * 该变量用于在各种文件操作中强制使用UTF-8编码,
- * 确保在整个应用程序中一致地处理文本数据。
- */
- private static final String UTF_8 = "UTF-8";
- /**
- * 常量BOOT_INF_LIB表示JAR文件中通常存储依赖库的默认目录路径。
- * 该路径主要用于在压缩或排除等操作中识别和处理库文件。
- */
- private static final String BOOT_INF_LIB = "BOOT-INF/lib";
- /**
- * 用于标识JAR(Java归档)文件的文件扩展名。
- * 该常量表示JAR文件的标准扩展名,通常用于
- * 文件过滤、命名或在目录或归档中识别JAR文件的操作。
- */
- private static final String JAR_EXTENSION = ".jar";
- /**
- * 定义在管理JAR过程中生成的需求文件的后缀,
- * 特别是在处理依赖项或排除项时使用。
- * 该字符串用作特定的文件名模式,用于保存与特定服务
- * 相关的排除依赖项或其他需求的列表。
- * 默认值为"-requirements.txt"。
- */
- private static final String REQUIREMENTS_SUFFIX = "-requirements.txt";
- /**
- * 预定义的、不可修改的特定jar文件名集合,被视为
- * "安全"或"始终包含"的文件。这些jar文件通常在
- * 处理或压缩操作中免于排除过滤。
- * 该集合包含以下jar标识符:
- * - "spring"
- * - "logback-core"
- * - "tomcat"
- * 该变量用于根据jar文件名决定是否包含特定jar文件的操作中。
- * 它作为应用程序关键或必要jar的白名单。
- */
- private static final Set WHITE_LIST_JARS = Sets.newHashSet("spring", "logback-core", "tomcat");
-
- /**
- * 通过排除和包含指定的条目来压缩给定的源JAR文件,并将结果写入目标JAR文件。
- * 它处理源JAR的条目,应用排除和包含规则,还可以将某些条目提取到指定的目录中。
- * 在此过程中创建一个临时文件,成功完成后将其重命名为目标JAR文件。
- *
- * @param serviceName 正在处理的服务名称,主要用于日志记录和创建其他相关文件。
- * @param sourceJar 要压缩的源JAR文件。
- * @param includes 指定应保留哪些条目的包含模式集合。可能会自动添加额外的默认包含项。
- * @param exclusions 指定应排除哪些条目的排除模式集合。
- * @param targetJar 将写入压缩JAR的文件。
- * @param libDir 某些被排除的条目可能被提取到的目录(如适用)。
- */
- public static void compress(String serviceName, File sourceJar, Set includes, Set exclusions, File targetJar, String libDir) {
- includes.addAll(WHITE_LIST_JARS);
- File tempJar = new File(targetJar.getAbsolutePath() + ".tmp");
- Set excludedJars = new HashSet<>();
-
- if (processJarEntries(sourceJar, tempJar, includes, exclusions, libDir, excludedJars)) {
- finalizeCompression(serviceName, targetJar, tempJar, excludedJars, libDir);
- } else {
- boolean delete = tempJar.delete();
- System.out.println("删除临时文件:{" + delete + "}");
- }
- }
-
- /**
- * 处理源JAR文件中的条目以生成临时JAR文件,
- * 同时根据包含和排除规则过滤条目。如果需要,
- * 还会将指定的JAR条目提取到库目录中。
- *
- * @param sourceJar 要处理的源JAR文件
- * @param tempJar
-```
-
-这个工具类提供了两个主要功能:
-
-#### JAR 包压缩功能
-
-`compress` 方法实现了 JAR 瘦身功能:
-
-- 根据包含列表和排除列表过滤 JAR 中的依赖
-- 将被排除的依赖提取到指定的库目录
-- 生成一个记录排除依赖的需求文件
-- 创建一个只包含必要依赖的精简 JAR 文件
-
-#### JAR 包加密功能
-
-`encrypt` 方法利用 XJar 库实现了 JAR 加密:
-
-- 支持指定加密密码
-- 通过包含和排除模式选择性地加密 JAR 中的内容
-- 生成加密后的 XJar 文件
-
-### 2. SpringBootJarSlimEncryptApplication 主应用
-
-```java
-// 路径:spring-boot-jar-slim-encrypt/src/main/java/com/mangmang/SpringBootJarSlimEncryptApplication.java
-```
-
-这是工具的主应用类,提供了完整的命令行接口来处理 JAR 文件的压缩和加密:
-
-主要功能:
-
-- 通过配置常量定义输入/输出目录、加密设置等
-- 支持从 XML 配置文件加载要包含和排除的依赖项
-- 批量处理多个 JAR 文件
-- 支持通过系统属性覆盖默认配置
-
-主要配置参数:
-
-- `xml.dir`: 依赖项 XML 文件目录(默认: `./config/xml/`)
-- `raw.dir`: 原始 JAR 文件目录(默认: `./config/rawJars/`)
-- `compress.dir`: 压缩后 JAR 文件目录(默认: `./config/compressJars/`)
-- `libs.dir`: 提取的库文件目录(默认: `./config/libs/`)
-- `xjar.dir`: 加密后的 XJar 文件目录(默认: `./config/xJars/`)
-- `compress.enable`: 是否启用压缩功能(默认: `true`)
-
-## 使用步骤
-
-### 步骤 1: 集成自定义类加载器
-
-在你的 Spring Boot 项目中添加自定义类加载器依赖:
-
-```xml
-
-
- com.mangmang
- spring-boot-custom-classloader
- 1.0.0
-
-```
-
-### 步骤 2: 构建 Spring Boot 应用
-
-正常构建你的 Spring Boot 应用:
-
-```bash
-mvn clean package
-```
-
-### 步骤 3: 配置依赖排除和包含规则
-
-创建两个 XML 文件以定义要排除和包含的依赖项:
-
-**exclusions.xml**(要排除的依赖):
-
-```xml
-
-
-
- spring-boot-starter-web
- 2.7.0
-
-
-
-```
-
-**includes.xml**(要保留的依赖):
-
-```xml
-
-
-
- spring-boot-custom-classloader
-
-
-
-```
-
-### 步骤 4: 执行 JAR 瘦身和加密工具
-
-将你的 Spring Boot JAR 文件放入 `./config/rawJars/` 目录,然后运行瘦身加密工具:
-
-```bash
-java -jar spring-boot-jar-slim-encrypt.jar
-```
-
-你可以通过系统属性覆盖默认配置:
-
-```bash
-java -Dcompress.enable=true -Dlibs.dir=/custom/libs/path -jar spring-boot-jar-slim-encrypt.jar
-```
-
-### 步骤 5: 部署和运行
-
-部署加密后的 JAR 文件和提取的库文件:
-
-```bash
-java -Dexternal.jars.path=/path/to/libs -jar your-application.xjar
-```
-
-## 技术原理解析
-
-### JAR 瘦身原理
-
-1. 扫描 Spring Boot JAR 中的 `BOOT-INF/lib` 目录
-2. 根据配置的排除和包含规则过滤依赖
-3. 将被排除的依赖提取到外部目录
-4. 创建一个不包含被排除依赖的新 JAR 文件
-
-### JAR 加密原理
-
-1. 使用 XJar 库实现 JAR 文件内容的加密
-2. 只加密指定的文件模式(如 Java 类文件、配置文件等)
-3. 避免加密某些需要保持明文的资源
-
-### 自定义类加载器原理
-
-1. 在 Spring Boot 应用启动时初始化自定义类加载器
-2. 扫描指定目录下的 JAR 文件
-3. 使用反射机制将 JAR 文件 URL 添加到当前类加载器
-
-## 方案优势
-
-1. **减小 JAR 体积**:将大型依赖库外置,显著减小主 JAR 文件体积
-2. **提高安全性**:通过加密保护核心业务逻辑和敏感配置
-3. **灵活配置**:支持通过 XML 配置和系统属性灵活控制瘦身和加密过程
-4. **无缝集成**:与 Spring Boot 应用无缝集成,无需修改应用代码
-
-## 注意事项
-
-1. 确保加密密码安全保存,丢失密码将导致无法运行加密的 JAR
-2. 测试瘦身后的应用,确保所有需要的依赖都能正确加载
-3. 部署时必须将提取的依赖库和加密后的 JAR 一起部署
-4. 启动应用时必须指定外部库路径参数
-
-## 结论
-
-通过结合 JAR 瘦身、JAR 加密和自定义类加载器,我们成功构建了一套完整的 Spring Boot 应用优化和保护方案。这不仅有效减小了部署包的体积,还提高了应用的安全性,为企业级
-Spring Boot 应用的部署提供了一种实用的解决方案。
-
-在实际应用中,可以根据具体需求调整配置参数,以达到最佳的平衡点。例如,可以根据应用规模和安全需求调整要排除的依赖和加密的文件模式。
-
-这套工具实现了将原本臃肿的 Spring Boot 应用拆分为核心加密 JAR 和外部依赖库的方案,使得应用部署更加灵活,也为应用分发和更新提供了更多可能性。
-
-------
-
-*注:本方案适用于需要保护核心业务逻辑或减小部署包体积的 Spring Boot
-应用。对于简单应用或开源项目,可能不必使用这么复杂的方案。*
\ No newline at end of file
diff --git a/src/programming/java/工具箱/WSL2.md b/src/programming/java/工具箱/WSL2.md
new file mode 100644
index 0000000..e6f782f
--- /dev/null
+++ b/src/programming/java/工具箱/WSL2.md
@@ -0,0 +1,558 @@
+---
+icon: bi:arrows-expand
+date: 2025-05-08
+category:
+ - win10
+tag:
+ - wsl
+ - http
+title: WSL2
+---
+
+# WSL2完全配置指南:从安装到实用工具
+Windows Subsystem for Linux (WSL2) 为Windows用户提供了无需双系统或虚拟机就能运行Linux环境的能力。本文将全面介绍WSL2的安装、配置和优化过程,包括网络设置、常用工具安装和问题排查等内容。
+
+## 安装WSL2
+
+1. **启用WSL功能**: 打开控制面板,启用"Windows Subsystem for Linux"和"虚拟机平台"功能,然后重启电脑。
+
+2. **检查WSL版本**:
+
+ ```powershell
+ wsl --status
+ wsl --update
+ ```
+
+3. **设置默认为WSL2**:
+
+ ```powershell
+ wsl --set-default-version 2
+ ```
+
+4. **查看可用的Linux发行版**:
+
+ ```powershell
+ wsl --list --online
+ ```
+
+5. **安装Linux发行版**:
+
+ ```powershell
+ # 安装默认版本(Ubuntu)
+ wsl --install
+
+ # 或安装指定版本
+ wsl --install -d Ubuntu-24.04
+ ```
+
+6. **设置用户名和密码**,安装完成后系统会提示设置。
+
+7. **更新系统**:
+
+ ```bash
+ sudo apt update && sudo apt upgrade
+ ```
+
+8. **验证安装**:
+
+ ```powershell
+ wsl -l -v
+ ```
+
+## 网络配置
+
+### 配置DNS
+
+WSL2使用的DNS服务器在`/etc/resolv.conf`文件中设置,为防止每次启动重置,需要进行以下配置:
+
+1. 创建`/etc/wsl.conf`文件:
+
+ ```bash
+ [network]
+ generateResolvConf = false
+ ```
+
+2. 删除原链接文件:
+
+ ```bash
+ rm /etc/resolv.conf
+ ```
+
+3. 创建新配置:
+
+ ```bash
+ vi /etc/resolv.conf
+ # 添加内容
+ nameserver 114.114.114.114
+ ```
+
+4. 重启WSL:
+
+ ```bash
+ exit
+ wsl --shutdown
+ wsl
+ ```
+
+5. 测试网络:
+
+ ```bash
+ ping www.baidu.com
+ ```
+
+### 配置桥接网络
+
+1. **开启Hyper-V**后执行以下命令:
+
+ ```powershell
+ Get-NetAdapter
+ New-VMSwitch -SwitchName "VETH" -NetAdapterName "以太网" -AllowManagementOS $True
+ ```
+
+2. 创建`.wslconfig`配置文件:
+
+ ```powershell
+ cd ~
+ New-Item .wslconfig
+ notepad .\.wslconfig
+ ```
+
+3. 添加以下内容:
+
+ ```
+ [wsl2]
+ networkingMode=bridged
+ vmSwitch=VETH
+ ipv6=true
+ ```
+
+### 配置网络代理
+
+1. 关闭自动更新DNS:
+
+ ```bash
+ #/etc/wsl.conf
+ [network]
+ generateResolvConf = false
+ ```
+
+2. 添加以下脚本至`.bashrc`或`.zshrc`:
+
+ ```bash
+ vi ~/.bashrc
+ # 添加代理配置
+ export hostip=10.6.212.22 # 替换为你的代理IP
+ export hostport=7890 # 替换为你的代理端口
+ alias proxy='
+ export HTTPS_PROXY="http://${hostip}:${hostport}";
+ export HTTP_PROXY="http://${hostip}:${hostport}";
+ export ALL_PROXY="http://${hostip}:${hostport}";
+ echo -e "Acquire::http::Proxy \"http://${hostip}:${hostport}\";" | sudo tee -a /etc/apt/apt.conf.d/proxy.conf > /dev/null;
+ echo -e "Acquire::https::Proxy \"http://${hostip}:${hostport}\";" | sudo tee -a /etc/apt/apt.conf.d/proxy.conf > /dev/null;
+ '
+ alias unproxy='
+ unset HTTPS_PROXY;
+ unset HTTP_PROXY;
+ unset ALL_PROXY;
+ sudo sed -i -e '/Acquire::http::Proxy/d' /etc/apt/apt.conf.d/proxy.conf;
+ sudo sed -i -e '/Acquire::https::Proxy/d' /etc/apt/apt.conf.d/proxy.conf;
+ '
+ ```
+
+3. 执行命令启用/禁用代理:
+
+ ```bash
+ # 启用代理
+ proxy
+
+ # 禁用代理
+ unproxy
+ ```
+
+4. 固定DNS配置:
+
+ ```bash
+ sudo rm /etc/resolv.conf
+ sudo bash -c 'echo "nameserver 8.8.8.8" > /etc/resolv.conf'
+ sudo bash -c 'echo "[network]" > /etc/wsl.conf'
+ sudo bash -c 'echo "generateResolvConf = false" >> /etc/wsl.conf'
+ sudo chattr +i /etc/resolv.conf
+ ```
+
+5. 测试代理:
+
+ ```bash
+ curl -vv google.com
+ ```
+
+## 基础功能配置
+
+### 开启SSH服务
+
+1. 安装SSH服务:
+
+ ```bash
+ sudo apt update && sudo apt upgrade
+ sudo apt-get install openssh-server
+ ```
+
+2. 修改SSH配置:
+
+ ```bash
+ sudo vi /etc/ssh/sshd_config
+ ```
+
+ 找到`PasswordAuthentication`行,确保设置为`yes`
+
+3. 启动并设置开机自启:
+
+ ```bash
+ sudo systemctl status ssh
+ sudo systemctl start ssh
+ sudo systemctl enable ssh
+ ```
+
+### 安装桌面环境
+
+```bash
+sudo apt update
+sudo apt install ubuntu-desktop
+# 安装远程桌面服务
+sudo apt-get install xrdp
+sudo systemctl start xrdp
+sudo systemctl enable xrdp
+```
+
+### 映射Windows目录至WSL
+
+```bash
+# 创建挂载点
+sudo mkdir /mnt/z
+# 挂载Windows目录
+sudo mount -t drvfs C:/User/xxx/Desktop/挂载文件 /mnt/z
+```
+
+## 系统管理
+
+### 查看端口
+
+查询端口占用有两种常用方法:
+
+1. 使用`netstat`:
+
+ ```bash
+ sudo apt-get install net-tools
+ sudo netstat -tunlp | grep 端口号
+ ```
+
+2. 使用`lsof`:
+
+ ```bash
+ sudo apt-get install lsof
+ sudo lsof -i:端口号
+ ```
+
+### 修改主机名
+
+1. 使用`hostnamectl`命令:
+
+ ```bash
+ sudo hostnamectl set-hostname 新主机名
+ ```
+
+2. 修改配置文件:
+
+ ```bash
+ sudo vi /etc/hostname
+ sudo vi /etc/hosts
+ ```
+
+ 在hosts文件中将`127.0.1.1`对应的旧主机名替换为新主机名
+
+3. 重启系统(可选):
+
+ ```bash
+ sudo reboot
+ ```
+
+## 开发环境配置
+
+### IDEA中文乱码修复
+
+1. 安装语言包:
+
+ ```bash
+ sudo apt install language-pack-zh-hans
+ ```
+
+2. 配置语言环境:
+
+ ```bash
+ sudo dpkg-reconfigure locales
+ # 选择en_US.UTF-8和zh_CN.UTF-8,并将zh_CN.UTF-8设为默认
+ ```
+
+3. 安装字体工具:
+
+ ```bash
+ sudo apt install fontconfig
+ ```
+
+4. 配置Windows字体:
+
+ ```bash
+ sudo vi /etc/fonts/local.conf
+ ```
+
+ 添加内容:
+
+ ```xml
+
+
+
+ /mnt/c/Windows/Fonts
+
+ ```
+
+5. 刷新字体缓存:
+
+ ```bash
+ fc-cache -f -v
+ ```
+
+6. 重启WSL:
+
+ ```bash
+ wsl --shutdown
+ ```
+
+### IDEA配置输入法
+
+1. 安装fcitx输入法:
+
+ ```bash
+ sudo apt install fcitx dbus-x11 im-config fcitx-sunpinyin
+ ```
+
+2. 编辑`/etc/locale.gen`:
+
+ ```bash
+ vi /etc/locale.gen
+ # 取消注释行:zh_CN.UTF-8
+ ```
+
+3. 配置环境变量:
+
+ ```bash
+ vi ~/.profile
+ # 添加内容
+ export GTK_IM_MODULE=fcitx
+ export QT_IM_MODULE=fcitx
+ export XMODIFIERS=@im=fcitx
+ export DefaultIMModule=fcitx
+ fcitx-autostart &>/dev/null
+ ```
+
+4. 更新配置:
+
+ ```bash
+ source ~/.profile
+ ```
+
+5. 配置快捷键:
+
+ ```bash
+ fcitx-config-gtk3
+ ```
+
+6. IDEA支持:编辑`idea.sh`启动脚本,添加:
+
+ ```properties
+ export XMODIFIERS=@im=fcitx
+ export QT_IM_MODULE=fcitx
+ ```
+
+### 安装Docker
+
+1. 更新系统包:
+
+ ```bash
+ sudo apt update
+ ```
+
+2. 安装依赖:
+
+ ```bash
+ sudo apt install ca-certificates curl gnupg lsb-release
+ ```
+
+3. 添加Docker官方GPG密钥:
+
+ ```bash
+ sudo mkdir -p /etc/apt/keyrings
+ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
+ ```
+
+4. 添加Docker APT源:
+
+ ```bash
+ echo \
+ "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
+ $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
+ ```
+
+5. 更新包列表:
+
+ ```bash
+ sudo apt update
+ ```
+
+6. 安装Docker引擎:
+
+ ```bash
+ sudo apt install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
+ ```
+
+7. 启动并验证:
+
+ ```bash
+ sudo systemctl start docker
+ sudo systemctl enable docker
+ sudo docker --version
+ ```
+
+### 安装1Panel
+
+一键安装:
+
+```bash
+curl -sSL https://resource.fit2cloud.com/1panel/package/quick_start.sh -o quick_start.sh && sudo bash quick_start.sh
+```
+
+查看管理员密码:
+
+```bash
+sudo 1pctl user-info
+```
+
+### 安装SVN
+
+```bash
+sudo apt update
+sudo apt install subversion
+svn --version # 验证安装
+```
+
+## 问题记录
+
+### SSH连接异常
+
+症状:SSH服务启动失败,出现以下错误:
+
+```
+error: Bind to port 22 on 0.0.0.0 failed: Address already in use.
+fatal: Missing privilege separation directory: /run/sshd
+```
+
+解决方法:
+
+1. 检查端口占用:
+
+ ```bash
+ sudo lsof -i:22
+ ```
+
+2. 终止占用进程:
+
+ ```bash
+ sudo kill
+ ```
+
+3. 创建缺失目录:
+
+ ```bash
+ sudo mkdir -p /run/sshd
+ sudo chmod 0755 /run/sshd
+ ```
+
+4. 重启SSH服务:
+
+ ```bash
+ sudo systemctl restart ssh
+ ```
+
+## 常用指令
+
+```powershell
+# 列出可用的Linux发行版
+wsl --list --online
+
+# 列出已安装的发行版
+wsl --list --verbose # 或 wsl -l -v
+
+# 设置WSL版本
+wsl --set-version <发行版名称> <版本号>
+
+# 设置默认WSL版本
+wsl --set-default-version <版本号>
+
+# 设置默认Linux发行版
+wsl --set-default <发行版名称>
+
+# 运行特定发行版
+wsl --distribution <发行版名称> --user <用户名>
+
+# 更新WSL
+wsl --update
+
+# 检查WSL状态
+wsl --status
+
+# 检查WSL版本
+wsl --version
+
+# 以特定用户身份运行
+wsl --user <用户名>
+
+# 卸载Linux发行版
+wsl --unregister <发行版名称>
+
+# 标识IP地址
+wsl hostname -I # 返回WSL2 IP地址
+ip route show | grep -i default | awk '{ print $3}' # 返回Windows主机IP
+
+# 更改默认用户
+<发行版名称> config --default-user <用户名>
+```
+
+## 实用软件安装
+
+### 安装适用于 Linux 的 Google Chrome
+
+```bash
+cd /tmp
+wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
+sudo apt install --fix-missing ./google-chrome-stable_current_amd64.deb
+```
+
+启动命令:`google-chrome`
+
+### 安装 VLC
+
+```bash
+sudo apt install vlc -y
+```
+
+启动命令:`vlc`
+
+### 安装 X11 应用
+
+```bash
+sudo apt install x11-apps -y
+```
+
+启动命令示例:`xcalc`、`xclock`、`xeyes`
+
+------
+
+通过本文的配置指南,你可以构建一个功能完善的WSL2环境,满足日常开发、学习和娱乐需求。WSL2的灵活性使得Windows用户无需切换操作系统就能享受Linux的强大功能,是开发人员的理想工具。
\ No newline at end of file
diff --git a/src/programming/java/工具箱/assert/sshkeys_create-8409f453e6780ca1a8db3ce33c74240b.png b/src/programming/java/工具箱/assert/sshkeys_create-8409f453e6780ca1a8db3ce33c74240b.png
new file mode 100644
index 0000000..c585b23
Binary files /dev/null and b/src/programming/java/工具箱/assert/sshkeys_create-8409f453e6780ca1a8db3ce33c74240b.png differ
diff --git a/src/programming/java/工具箱/assert/sshkeys_list-bff1a324894abbdc3ab8f61c49bb63d5.png b/src/programming/java/工具箱/assert/sshkeys_list-bff1a324894abbdc3ab8f61c49bb63d5.png
new file mode 100644
index 0000000..8132a32
Binary files /dev/null and b/src/programming/java/工具箱/assert/sshkeys_list-bff1a324894abbdc3ab8f61c49bb63d5.png differ
diff --git a/src/programming/java/工具箱/assert/sshkeys_show-a14cdfb89475debed237bfded2bd9848.png b/src/programming/java/工具箱/assert/sshkeys_show-a14cdfb89475debed237bfded2bd9848.png
new file mode 100644
index 0000000..b5e9db5
Binary files /dev/null and b/src/programming/java/工具箱/assert/sshkeys_show-a14cdfb89475debed237bfded2bd9848.png differ
diff --git a/src/programming/java/工具箱/gitee-ssh.md b/src/programming/java/工具箱/gitee-ssh.md
new file mode 100644
index 0000000..f092375
--- /dev/null
+++ b/src/programming/java/工具箱/gitee-ssh.md
@@ -0,0 +1,190 @@
+---
+icon: bi:arrows-expand
+date: 2025-05-08
+category:
+ - 实用工具
+tag:
+ - gitee
+ - 码云
+title: Gitee SSH
+---
+
+在Gitee上设置SSH公钥:完整指南
+
+
+# 在Gitee上设置SSH公钥:完整指南
+
+## 引言
+
+在使用Git进行代码管理时,SSH协议为开发者提供了一种安全且便捷的仓库访问方式。相比HTTPS协议,SSH协议不需要每次操作都输入用户名和密码,大大提高了开发效率。对于经常使用Gitee平台的Java开发者来说,正确配置SSH公钥是日常工作流程中的重要一环。本文将详细介绍如何在Gitee平台上生成并设置SSH公钥,帮助您实现更高效的代码管理。
+
+## SSH公钥的基本概念
+
+在深入了解操作步骤前,我们先来理解一下SSH公钥认证的基本原理:
+
+- SSH认证基于非对称加密,包含一对密钥:私钥(保存在本地)和公钥(上传至服务器)
+- 私钥必须安全保管,不可泄露;公钥则可以自由分享
+- 当您使用SSH协议访问仓库时,Gitee会验证您的身份,确认您是否拥有与已注册公钥匹配的私钥
+
+## 详细操作步骤
+
+### 1. 生成SSH密钥对
+
+首先,我们需要生成SSH密钥对。对于Windows用户,建议使用**Windows PowerShell**或**Git Bash**执行以下命令(注意:Windows的命令提示符中没有`cat`和`ls`命令)。
+
+```bash
+ssh-keygen -t ed25519 -C "Gitee SSH Key"
+```
+
+参数说明:
+- `-t`:指定密钥类型,这里使用更安全的ed25519算法
+- `-C`:添加注释,便于识别密钥的用途
+
+执行命令后,系统会提示您输入保存密钥的位置和密码短语。一般情况下,直接按三次回车键即可使用默认设置:
+
+```bash
+Generating public/private ed25519 key pair.
+Enter file in which to save the key (/home/git/.ssh/id_ed25519):
+Enter passphrase (empty for no passphrase):
+Enter same passphrase again:
+Your identification has been saved in /home/git/.ssh/id_ed25519
+Your public key has been saved in /home/git/.ssh/id_ed25519.pub
+```
+
+这样我们就成功生成了两个文件:
+- `id_ed25519`:私钥文件(保密,不要分享)
+- `id_ed25519.pub`:公钥文件(需要上传到Gitee)
+
+### 2. 获取公钥内容
+
+执行以下命令查看生成的SSH公钥内容:
+
+```bash
+cat ~/.ssh/id_ed25519.pub
+```
+
+命令会输出类似以下格式的公钥内容:
+
+```
+ssh-ed25519 AAAA***5B Gitee SSH Key
+```
+
+请完整复制这段输出的内容,包括开头的`ssh-ed25519`和末尾的注释`Gitee SSH Key`。
+
+### 3. 将公钥添加到Gitee账户
+
+获取到公钥内容后,我们需要将其添加到Gitee账户中:
+
+1. 登录Gitee账户,点击右上角头像
+2. 选择「个人设置」->「安全设置」->「SSH 公钥」
+3. 点击「添加公钥」按钮
+4. 在表单中填入公钥标题(自定义,方便识别)和公钥内容
+5. 输入Gitee账户密码进行验证
+6. 点击「添加」按钮完成操作
+
+
+
+### 4. 验证SSH连接
+
+添加完成后,可以通过以下命令测试SSH连接是否配置成功:
+
+```bash
+ssh -T git@gitee.com
+```
+
+如果配置正确,您将看到以下输出,其中USERNAME是您的Gitee用户名:
+
+```bash
+Hi USERNAME! You've successfully authenticated, but GITEE.COM does not provide shell access.
+```
+
+看到这个提示就说明SSH公钥已经成功配置,您可以开始使用SSH协议来操作Gitee上的仓库了。
+
+## SSH公钥的管理
+
+在Gitee平台上,您可以方便地管理已添加的SSH公钥:
+
+1. 浏览公钥列表:通过「个人设置」->「安全设置」->「SSH 公钥」页面查看所有已添加的SSH公钥
+2. 查看公钥详情:点击具体的公钥可以查看其详细信息
+3. 删除公钥:当某个SSH公钥不再需要时,可以直接删除它
+
+
+
+
+
+## 账户SSH公钥vs仓库SSH公钥
+
+在Gitee平台上,存在两种不同类型的SSH公钥,理解它们的区别对正确使用Git操作非常重要:
+
+### 账户SSH公钥
+
+- 与您的Gitee账户绑定
+- 当您的账户对仓库有推送/拉取权限时,可以使用这个公钥进行对应操作
+- 验证时会显示您的用户名:`Hi USERNAME! You've successfully authenticated...`
+- 适用于日常开发过程中的代码提交、拉取等操作
+
+### 仓库SSH公钥(部署公钥)
+
+- 仅针对特定仓库,与账户无关
+- 仅具有仓库的**拉取**权限,无法进行推送操作
+- 验证时显示为匿名用户:`Hi Anonymous! You've successfully authenticated...`
+- 主要用于部署环境(如生产服务器)拉取代码,避免在服务器上存储个人账户凭证
+
+> 注意:如果您需要设置仓库的部署公钥,请参考Gitee帮助文档中的"添加部署公钥"章节。
+
+## 实用技巧与常见问题
+
+### 使用不同的SSH密钥访问不同的Git服务
+
+如果您同时使用多个Git平台(如Gitee、GitHub、GitLab等),可以为每个平台生成不同的SSH密钥对,并在SSH配置文件中指定使用规则。
+
+1. 为不同平台生成不同的密钥,例如:
+ ```bash
+ ssh-keygen -t ed25519 -C "Gitee" -f ~/.ssh/gitee_id_ed25519
+ ssh-keygen -t ed25519 -C "GitHub" -f ~/.ssh/github_id_ed25519
+ ```
+
+2. 创建或编辑`~/.ssh/config`文件:
+ ```
+ # Gitee平台
+ Host gitee.com
+ HostName gitee.com
+ PreferredAuthentications publickey
+ IdentityFile ~/.ssh/gitee_id_ed25519
+
+ # GitHub平台
+ Host github.com
+ HostName github.com
+ PreferredAuthentications publickey
+ IdentityFile ~/.ssh/github_id_ed25519
+ ```
+
+### SSH连接故障排查
+
+如果在验证SSH连接时遇到问题,可以尝试以下步骤:
+
+1. 使用`-v`参数获取详细信息:
+ ```bash
+ ssh -vT git@gitee.com
+ ```
+
+2. 检查SSH密钥权限(Unix/Linux/macOS系统):
+ ```bash
+ chmod 700 ~/.ssh
+ chmod 600 ~/.ssh/id_ed25519
+ chmod 644 ~/.ssh/id_ed25519.pub
+ ```
+
+3. 确认SSH代理正在运行:
+ ```bash
+ eval $(ssh-agent -s)
+ ssh-add ~/.ssh/id_ed25519
+ ```
+
+## 总结
+
+正确设置和使用SSH公钥对于Java开发者在Gitee平台上进行高效的代码管理至关重要。通过本文介绍的步骤,您可以轻松生成SSH密钥对、将公钥添加到Gitee账户,并开始使用SSH协议访问您的代码仓库。这不仅提高了安全性,还简化了您的日常工作流程,避免了重复输入用户名和密码的繁琐过程。
+
+对于团队开发和持续集成/持续部署(CI/CD)环境,了解账户SSH公钥和仓库部署公钥的区别尤为重要,这有助于您根据不同场景选择最合适的认证方式,确保代码安全的同时提高开发和部署效率。
+
+希望本指南能帮助您在Gitee平台上更加顺畅地使用Git进行项目管理和协作开发。如果在实际操作中遇到任何问题,欢迎参考Gitee的官方帮助文档获取更多支持。
\ No newline at end of file