From 932b89b3d44a38df5e570b36ba43d9ee6480ce11 Mon Sep 17 00:00:00 2001 From: StarBlues Date: Sat, 3 Jun 2023 18:27:20 +0800 Subject: [PATCH] =?UTF-8?q?=E6=94=AF=E6=8C=81jar-outer=E3=80=81zip-outer?= =?UTF-8?q?=E7=B1=BB=E5=9E=8B=E5=B8=A6=E4=BE=9D=E8=B5=96=E4=B8=94=E4=B8=BA?= =?UTF-8?q?=E5=8E=8B=E7=BC=A9=E5=8C=85=E4=B8=8A=E4=BC=A0=E6=8F=92=E4=BB=B6?= =?UTF-8?q?=E7=9A=84=E6=96=B9=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- README_zh.md | 2 +- .../bootstrap/ConfigurePluginEnvironment.java | 2 +- .../starblues/core/DefaultPluginManager.java | 116 +++++++++-------- .../gitee/starblues/core/PluginManager.java | 12 +- .../AutoIntegrationConfiguration.java | 4 +- .../DefaultIntegrationConfiguration.java | 8 +- .../operator/DefaultPluginOperator.java | 29 +++-- .../starblues/utils/PluginFileUtils.java | 119 +++++++++++------- update.md | 2 +- 10 files changed, 185 insertions(+), 111 deletions(-) diff --git a/README.md b/README.md index 8293811..769d299 100644 --- a/README.md +++ b/README.md @@ -48,6 +48,6 @@ The framework can be developed in the `spring-boot` project plugin, plugin can b - [spring-brick-example](https://gitee.com/starblues/springboot-plugin-framework-example) ### Contact -QQ: 859570617(**After you like the framework, you can enter the group. Please note the Gitee/GitHub nickname before entering the group**) +QQ: 859570617(QQ Group 1 is full), 536825438(QQ Group 2)(**After you like the framework, you can enter the group. Please note the Gitee/GitHub nickname before entering the group**) diff --git a/README_zh.md b/README_zh.md index fcd08f2..a4b0103 100644 --- a/README_zh.md +++ b/README_zh.md @@ -57,7 +57,7 @@ - [spring-brick 功能测试+案例](https://gitee.com/starblues/springboot-plugin-framework-example) ### 交流 | Contact -QQ交流群: 859570617 +QQ交流群: 859570617(群1, 已满), 536825438(群2) ### 支持 | Support 如果您觉得框架使用起来不错的, 或者想支持本框架继续维护, 开源不易, 您可以通过如下方式进行支持: diff --git a/spring-brick-bootstrap/src/main/java/com/gitee/starblues/bootstrap/ConfigurePluginEnvironment.java b/spring-brick-bootstrap/src/main/java/com/gitee/starblues/bootstrap/ConfigurePluginEnvironment.java index 3a05409..0f53317 100644 --- a/spring-brick-bootstrap/src/main/java/com/gitee/starblues/bootstrap/ConfigurePluginEnvironment.java +++ b/spring-brick-bootstrap/src/main/java/com/gitee/starblues/bootstrap/ConfigurePluginEnvironment.java @@ -40,7 +40,7 @@ import java.util.Map; * * @author starBlues * @since 3.0.0 - * @version 3.1.1 + * @version 3.1.2 */ public class ConfigurePluginEnvironment { private final Logger logger = LoggerFactory.getLogger(ConfigurePluginEnvironment.class); diff --git a/spring-brick/src/main/java/com/gitee/starblues/core/DefaultPluginManager.java b/spring-brick/src/main/java/com/gitee/starblues/core/DefaultPluginManager.java index 00f63cd..8c0a471 100644 --- a/spring-brick/src/main/java/com/gitee/starblues/core/DefaultPluginManager.java +++ b/spring-brick/src/main/java/com/gitee/starblues/core/DefaultPluginManager.java @@ -175,49 +175,77 @@ public class DefaultPluginManager implements PluginManager{ } @Override - public PluginInfo parse(Path pluginPath) throws PluginException { + public PluginInsideInfo parse(Path pluginPath) throws PluginException { + if(pluginPath == null){ + throw new PluginException("解析文件不能为 null"); + } PluginInsideInfo pluginInsideInfo = loadFromPath(pluginPath); if(pluginInsideInfo == null){ throw new PluginException("非法插件包: " + pluginPath); } pluginInsideInfo.setPluginState(PluginState.PARSED); - return pluginInsideInfo.toPluginInfo(); + return pluginInsideInfo; } @Override - public synchronized PluginInfo load(Path pluginPath, boolean unpackPlugin) throws PluginException { - Assert.isNotNull(pluginPath, "参数pluginPath不能为空"); - String sourcePluginPath = pluginPath.toString(); - try { + public PluginInsideInfo scanParse(Path pluginPath) throws PluginException { + if(pluginPath == null){ + throw new PluginException("解析文件不能为 null"); + } + List scanList = new ArrayList<>(1); + scanList.add(pluginPath.toString()); + List scanPluginPaths = provider.getPluginScanner().scan(scanList); + for (Path scanPluginPath : scanPluginPaths) { // 解析插件 + try { + PluginInsideInfo pluginInsideInfo = loadFromPath(scanPluginPath); + if(pluginInsideInfo != null){ + return pluginInsideInfo; + } + } catch (Exception e){ + // 忽略 + } + } + return null; + } - PluginInfo pluginInfo = parse(pluginPath); + @Override + public synchronized PluginInsideInfo load(Path pluginPath, boolean unpackPlugin) throws PluginException { + Assert.isNotNull(pluginPath, "参数pluginPath不能为空"); + Path sourcePluginPath = pluginPath; + File unpackPluginFile = null; + try { + if(unpackPlugin){ + unpackPluginFile = PluginFileUtils.decompressZip(pluginPath.toString(), configuration.uploadTempPath()); + pluginPath = unpackPluginFile.toPath(); + } + // 拷贝插件到root目录 + pluginPath = copyPluginToPluginRootDir(pluginPath); + PluginInsideInfo pluginInsideInfo = scanParse(pluginPath); + if(pluginInsideInfo == null){ + pluginListenerFactory.loadFailure(sourcePluginPath, new PluginException("Not found PluginInsideInfo")); + throw new PluginException("非法插件包: " + sourcePluginPath); + } // 检查是否存在当前插件 - PluginInsideInfo plugin = getPlugin(pluginInfo.getPluginId()); + PluginInsideInfo plugin = getPlugin(pluginInsideInfo.getPluginId()); if(plugin != null){ // 已经存在该插件 throw new PluginException("加载插件包[" + pluginPath + "]失败. 已经存在该插件: " + MsgUtils.getPluginUnique(plugin.getPluginDescriptor())); } - if(configuration.isProd()){ - // 如果为生产环境, 则拷贝插件 - pluginPath = copyPlugin(pluginPath, unpackPlugin); - } - // 加载插件 - PluginInsideInfo pluginInsideInfo = loadPlugin(pluginPath, true); - if(pluginInsideInfo != null){ - PluginInfo pluginInfoFace = pluginInsideInfo.toPluginInfo(); - pluginListenerFactory.loadSuccess(pluginInfoFace); - return pluginInfoFace; - } else { - pluginListenerFactory.loadFailure(pluginPath, new PluginException("Not found PluginInsideInfo")); - return null; - } + pluginInsideInfo = loadPlugin(Paths.get(pluginInsideInfo.getPluginPath()), false); + PluginInfo pluginInfoFace = pluginInsideInfo.toPluginInfo(); + pluginListenerFactory.loadSuccess(pluginInfoFace); + return pluginInsideInfo; } catch (Throwable e) { PluginException pluginException = PluginException.getPluginException(e, () -> { throw new PluginException("插件包加载失败: " + sourcePluginPath, e); }); - pluginListenerFactory.loadFailure(pluginPath, pluginException); + pluginListenerFactory.loadFailure(sourcePluginPath, pluginException); + if(unpackPluginFile != null){ + FileUtils.deleteQuietly(unpackPluginFile); + } + FileUtils.deleteQuietly(pluginPath.toFile()); throw pluginException; } } @@ -402,6 +430,12 @@ public class DefaultPluginManager implements PluginManager{ LogUtils.info(log, wrapperInside.getPluginDescriptor(), "卸载成功"); } + /** + * 加载插件信息 + * @param pluginPath 插件路径 + * @param resolvePath 是否直接解析路径 + * @return 插件内部细腻些 + */ protected PluginInsideInfo loadPlugin(Path pluginPath, boolean resolvePath) { if(resolvePath){ Path sourcePluginPath = pluginPath; @@ -455,19 +489,18 @@ public class DefaultPluginManager implements PluginManager{ /** * 拷贝插件文件到插件根目录 * @param pluginPath 源插件文件路径 - * @param unpackPlugin 是否解压插件包. 只有为压缩类型包才可解压 * @return 拷贝后的插件路径 * @throws IOException IO 异常 */ - protected Path copyPlugin(Path pluginPath, boolean unpackPlugin) throws IOException { + protected Path copyPluginToPluginRootDir(Path pluginPath) throws IOException { if(configuration.isDev()){ + // 开发环境不拷贝 return pluginPath; } File targetFile = pluginPath.toFile(); if(!targetFile.exists()) { throw new PluginException("不存在插件文件: " + pluginPath); } - String targetFileName = targetFile.getName(); // 先判断当前插件文件是否在插件目录中 File pluginRootDir = null; for (String dir : pluginRootDirs) { @@ -477,38 +510,22 @@ public class DefaultPluginManager implements PluginManager{ break; } } - String resolvePluginFileName = unpackPlugin ? PluginFileUtils.getFileName(targetFile) : targetFileName; + String fileName = targetFile.getName(); Path resultPath = null; if(pluginRootDir != null){ - // 在根目录中存在 - if(targetFile.isFile() && unpackPlugin){ - // 需要解压, 检查解压后的文件名称是否存在同名文件 - checkExistFile(pluginRootDir, resolvePluginFileName); - String unpackPluginPath = FilesUtils.joiningFilePath(pluginRootDir.getPath(), resolvePluginFileName); - PluginFileUtils.decompressZip(targetFile.getPath(), unpackPluginPath); - resultPath = Paths.get(unpackPluginPath); - PluginFileUtils.deleteFile(targetFile); - } else { - resultPath = targetFile.toPath(); - } + // 在根目录中存在, 检查是否存在同名文件 + checkExistFile(pluginRootDir, fileName); } else { // 不在插件目录 File pluginFile = pluginPath.toFile(); pluginRootDir = new File(getDefaultPluginRoot()); File pluginRootDirFile = new File(getDefaultPluginRoot()); // 检查是否存在同名文件 - checkExistFile(pluginRootDirFile, resolvePluginFileName); - targetFile = Paths.get(FilesUtils.joiningFilePath(pluginRootDir.getPath(), resolvePluginFileName)).toFile(); + checkExistFile(pluginRootDirFile, fileName); + targetFile = Paths.get(FilesUtils.joiningFilePath(pluginRootDir.getPath(), fileName)).toFile(); if(pluginFile.isFile()){ - if(unpackPlugin){ - // 需要解压 - String unpackPluginPath = FilesUtils.joiningFilePath(pluginRootDir.getPath(), resolvePluginFileName); - PluginFileUtils.decompressZip(pluginFile.getPath(), unpackPluginPath); - resultPath = Paths.get(unpackPluginPath); - } else { - FileUtils.copyFile(pluginFile, targetFile); - resultPath = targetFile.toPath(); - } + FileUtils.copyFile(pluginFile, targetFile); + resultPath = targetFile.toPath(); } else { FileUtils.copyDirectory(pluginFile, targetFile); resultPath = targetFile.toPath(); @@ -660,5 +677,4 @@ public class DefaultPluginManager implements PluginManager{ log.warn(warn.toString()); } - } diff --git a/spring-brick/src/main/java/com/gitee/starblues/core/PluginManager.java b/spring-brick/src/main/java/com/gitee/starblues/core/PluginManager.java index 7f453d1..3f41aad 100644 --- a/spring-brick/src/main/java/com/gitee/starblues/core/PluginManager.java +++ b/spring-brick/src/main/java/com/gitee/starblues/core/PluginManager.java @@ -61,7 +61,15 @@ public interface PluginManager { * @return 解析的插件信息 * @throws PluginException 插件异常 */ - PluginInfo parse(Path pluginPath) throws PluginException; + PluginInsideInfo parse(Path pluginPath) throws PluginException; + + /** + * 从某个目录扫描解析首个插件 + * @param pluginPath 插件路径 + * @return 解析的插件信息 + * @throws PluginException 插件异常 + */ + PluginInsideInfo scanParse(Path pluginPath) throws PluginException; /** * 根据具体插件路径来加载插件. @@ -70,7 +78,7 @@ public interface PluginManager { * @return 加载的插件信息 * @throws PluginException 插件异常 */ - PluginInfo load(Path pluginPath, boolean unpackPlugin) throws PluginException; + PluginInsideInfo load(Path pluginPath, boolean unpackPlugin) throws PluginException; /** * 卸载加载插件 diff --git a/spring-brick/src/main/java/com/gitee/starblues/integration/AutoIntegrationConfiguration.java b/spring-brick/src/main/java/com/gitee/starblues/integration/AutoIntegrationConfiguration.java index b0847e3..883887d 100644 --- a/spring-brick/src/main/java/com/gitee/starblues/integration/AutoIntegrationConfiguration.java +++ b/spring-brick/src/main/java/com/gitee/starblues/integration/AutoIntegrationConfiguration.java @@ -34,7 +34,7 @@ import java.util.Set; * * @author starBlues * @since 3.0.0 - * @version 3.0.3 + * @version 3.1.2 */ @EqualsAndHashCode(callSuper = true) @Component @@ -73,7 +73,7 @@ public class AutoIntegrationConfiguration extends DefaultIntegrationConfiguratio /** * 上传的插件所存储的临时目录 */ - @Value("${uploadTempPath:temp}") + @Value("${uploadTempPath:}") private String uploadTempPath; /** diff --git a/spring-brick/src/main/java/com/gitee/starblues/integration/DefaultIntegrationConfiguration.java b/spring-brick/src/main/java/com/gitee/starblues/integration/DefaultIntegrationConfiguration.java index bd06974..59370c3 100644 --- a/spring-brick/src/main/java/com/gitee/starblues/integration/DefaultIntegrationConfiguration.java +++ b/spring-brick/src/main/java/com/gitee/starblues/integration/DefaultIntegrationConfiguration.java @@ -20,6 +20,7 @@ import com.gitee.starblues.common.Constants; import com.gitee.starblues.integration.decrypt.DecryptConfiguration; import com.gitee.starblues.utils.Assert; +import java.io.File; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -30,13 +31,16 @@ import java.util.Set; * * @author starBlues * @since 3.0.0 - * @version 3.0.1 + * @version 3.1.2 */ public abstract class DefaultIntegrationConfiguration implements IntegrationConfiguration{ public static final String DEFAULT_PLUGIN_REST_PATH_PREFIX = "plugins"; public static final Boolean DEFAULT_ENABLE_PLUGIN_ID_REST_PATH_PREFIX = Boolean.TRUE; + private static final String DEFAULT_TEMP_FILE = + new File(System.getProperty("java.io.tmpdir"), "spring-brick-temp").getPath(); + @Override public Boolean enable() { return Boolean.TRUE; @@ -51,7 +55,7 @@ public abstract class DefaultIntegrationConfiguration implements IntegrationConf @Override public String uploadTempPath(){ - return "temp"; + return DEFAULT_TEMP_FILE; } @Override diff --git a/spring-brick/src/main/java/com/gitee/starblues/integration/operator/DefaultPluginOperator.java b/spring-brick/src/main/java/com/gitee/starblues/integration/operator/DefaultPluginOperator.java index 07b3208..9bf851c 100644 --- a/spring-brick/src/main/java/com/gitee/starblues/integration/operator/DefaultPluginOperator.java +++ b/spring-brick/src/main/java/com/gitee/starblues/integration/operator/DefaultPluginOperator.java @@ -54,8 +54,8 @@ import java.util.stream.Collectors; /** * 默认的插件操作者 * @author starBlues - * @version 3.0.0 - * @since 3.1.2 + * @since 3.0.0 + * @version 3.1.2 */ public class DefaultPluginOperator implements PluginOperator { protected final Logger log = LoggerFactory.getLogger(this.getClass()); @@ -153,7 +153,7 @@ public class DefaultPluginOperator implements PluginOperator { @Override public PluginInfo parse(Path pluginPath) throws PluginException { - return pluginManager.parse(pluginPath); + return toPluginInfo(pluginManager.parse(pluginPath)); } @Override @@ -168,7 +168,7 @@ public class DefaultPluginOperator implements PluginOperator { @Override public PluginInfo load(Path pluginPath, boolean unpackPlugin) throws PluginException { - return pluginManager.load(pluginPath, unpackPlugin); + return toPluginInfo(pluginManager.load(pluginPath, unpackPlugin)); } @Override @@ -288,9 +288,13 @@ public class DefaultPluginOperator implements PluginOperator { } finally { IOUtils.closeQuietly(inputStream); } + File unpackPluginFile = tempFile; try { // 解析该插件包 - PluginInfo uploadPluginInfo = parse(tempFilePath); + if(isUnpackPluginFile){ + unpackPluginFile = PluginFileUtils.decompressZip(tempFile.toString(), configuration.uploadTempPath()); + } + PluginInfo uploadPluginInfo = pluginManager.scanParse(unpackPluginFile.toPath()); if(uploadPluginInfo == null){ Exception exception = new Exception(pluginFileName + " 文件校验失败"); verifyFailureDelete(tempFilePath, exception); @@ -306,12 +310,12 @@ public class DefaultPluginOperator implements PluginOperator { backupPlugin(oldPluginPath, "upload"); } // 然后进入更新模式 - pluginInfo = pluginManager.upgrade(tempFilePath, isUnpackPluginFile); + pluginInfo = pluginManager.upgrade(unpackPluginFile.toPath(), false); // 删除旧插件包 FileUtils.delete(oldPluginPath.toFile()); } else { // 不存在则进入安装插件模式 - pluginInfo = pluginManager.install(tempFilePath, isUnpackPluginFile); + pluginInfo = pluginManager.install(unpackPluginFile.toPath(), false); } return pluginInfo; } catch (Exception e){ @@ -319,8 +323,8 @@ public class DefaultPluginOperator implements PluginOperator { verifyFailureDelete(tempFilePath, e); throw e; } finally { - // 删除临时文件 - tempFile.deleteOnExit(); + // 删除解压文件 + FileUtils.deleteQuietly(unpackPluginFile); } } @@ -450,5 +454,12 @@ public class DefaultPluginOperator implements PluginOperator { FileUtils.forceMkdir(file); } + private PluginInfo toPluginInfo(PluginInsideInfo pluginInsideInfo){ + if(pluginInsideInfo == null){ + return null; + } + return pluginInsideInfo.toPluginInfo(); + } + } diff --git a/spring-brick/src/main/java/com/gitee/starblues/utils/PluginFileUtils.java b/spring-brick/src/main/java/com/gitee/starblues/utils/PluginFileUtils.java index d3ee784..acbd9a0 100644 --- a/spring-brick/src/main/java/com/gitee/starblues/utils/PluginFileUtils.java +++ b/spring-brick/src/main/java/com/gitee/starblues/utils/PluginFileUtils.java @@ -45,7 +45,7 @@ import java.util.zip.ZipFile; * * @author starBlues * @since 3.0.0 - * @version 3.1.0 + * @version 3.1.2 */ public final class PluginFileUtils { @@ -124,76 +124,110 @@ public final class PluginFileUtils { /** * 得到文件名称 * @param file 原始文件 + * @param includeSuffix 是否包含后缀 * @return String */ - public static String getFileName(File file){ - String fileName = file.getName(); - if(!file.exists() | file.isDirectory()){ - return fileName; + public static String getFileName(File file, boolean includeSuffix){ + if(file == null){ + return null; } - return getFileName(fileName); + return getFileName(file.getName(), includeSuffix); } /** - * 得到文件名称 - * @param fileName 原始文件名称. 比如: file.txt + * 获取不包含文件后缀的文件名称 + * @param fileName 文件名称 * @return String */ public static String getFileName(String fileName){ + return getFileName(fileName, false); + } + + /** + * 得到文件名称 + * @param fileName 原始文件名称. 比如: file.txt , 则返回 file + * @param includeSuffix 是否包含后缀 + * @return String + */ + public static String getFileName(String fileName, boolean includeSuffix){ if(ObjectUtils.isEmpty(fileName)){ return fileName; } - if(fileName.lastIndexOf(FILE_POINT) > 0){ - return fileName.substring(0, fileName.lastIndexOf(FILE_POINT)); + if(includeSuffix){ + return fileName; + } + int i = fileName.lastIndexOf(FILE_POINT); + if(i > 0){ + return fileName.substring(0, i); } else { return fileName; } } - public static Manifest getManifest(InputStream inputStream) throws IOException { - Manifest manifest = new Manifest(); - Attributes attributes = manifest.getMainAttributes(); - List lines = IOUtils.readLines(inputStream, PackageStructure.CHARSET_NAME); - for (String line : lines) { - String[] split = line.split(":"); - if(split.length == 2){ - String key = split[0]; - String value = split[1]; - attributes.putValue(trim(key), trim(value)); + /** + * 创建随机文件 + * @param parentPath 父目录 + * @param isFile 是否为文件,不为文件就创建目录 + * @param fileSuffix 文件后缀 + * @return 创建的File + * @throws Exception 创建异常 + */ + public static File createRandomFile(String parentPath, boolean isFile, String fileSuffix) throws Exception{ + if(isFile){ + if(ObjectUtils.isEmpty(fileSuffix)){ + fileSuffix = ""; + } else { + if(!fileSuffix.startsWith(".")){ + fileSuffix = "." + fileSuffix; + } } + File file = new File(parentPath, String.valueOf(System.currentTimeMillis()) + fileSuffix); + if(file.createNewFile()){ + return file; + } else { + throw new IOException("Cannot create file '" + file + "'."); + } + } else { + File file = new File(parentPath, String.valueOf(System.currentTimeMillis())); + FileUtils.forceMkdir(file); + return file; } - return manifest; - } - - private static String trim(String value){ - if(ObjectUtils.isEmpty(value)){ - return value; - } - return value.trim(); } - public static void deleteFile(File file) throws IOException { - if(file == null || !file.exists()){ - return; - } - if(file.isDirectory()){ - FileUtils.deleteDirectory(file); - } else { - FileUtils.delete(file); - } + /** + * 判断是否能解压 + * @param zipPath zip路径 + * @return boolean + */ + public static boolean canDecompressZip(String zipPath){ + return ResourceUtils.isZip(zipPath) || ResourceUtils.isJar(zipPath); } - public static void decompressZip(String zipPath, String targetDir) throws IOException { + /** + * 解压zip 文件 + * @param zipPath zip 文件 + * @param targetDir 目标目录 + * @return 解压后的目录 + * @throws IOException 解压异常 + */ + public static File decompressZip(String zipPath, String targetDir) throws IOException { File zipFile = new File(zipPath); - if(!ResourceUtils.isZip(zipPath) && !ResourceUtils.isJar(zipPath)){ + if(!canDecompressZip(zipPath)){ throw new IOException("文件[" + zipFile.getName() + "]非压缩包, 不能解压"); } - File targetDirFile = new File(targetDir); + File targetDirFile = new File(targetDir, getFileName(zipFile, false)); if(!targetDirFile.exists()){ if(!targetDirFile.mkdirs()){ throw new IOException("创建目录异常: " + targetDir); } + targetDir = targetDirFile.getAbsolutePath(); + } else { + // 存在相同目录 + targetDirFile = new File(targetDir, getFileName(zipFile, false) + + "_" + System.currentTimeMillis()); + FileUtils.forceMkdir(targetDirFile); + targetDir = targetDirFile.getAbsolutePath(); } try (ZipFile zip = new ZipFile(zipFile, Charset.forName(PackageStructure.CHARSET_NAME))) { Enumeration zipEnumeration = zip.entries(); @@ -205,7 +239,8 @@ public final class PluginFileUtils { String currentTargetPath = FilesUtils.joiningFilePath(targetDir, currentZipPath); //判断路径是否存在,不存在则创建文件路径 if (zipEntry.isDirectory()) { - FileUtils.forceMkdir(new File(currentTargetPath)); + File file = new File(currentTargetPath); + FileUtils.forceMkdir(file); continue; } else { FilesUtils.createFile(currentTargetPath); @@ -221,9 +256,9 @@ public final class PluginFileUtils { } } } + return targetDirFile; } - private static void resolveDecompressPluginType(InputStream inputStream, OutputStream outputStream) throws IOException{ Manifest manifest = new Manifest(inputStream); Attributes mainAttributes = manifest.getMainAttributes(); diff --git a/update.md b/update.md index c4da9ac..3a97877 100644 --- a/update.md +++ b/update.md @@ -3,4 +3,4 @@ 3. 【修复】修复插件在运行状态无法加载类的问题 4. 【修复】修复对同一个插件包重复安装时,源插件包被系统占用的问题 5. 【修复】修复`jar-outer`、`zip-outer`类型插件卸载时依赖文件被占用问题 -6. 【新增】新增`jar-outer`、`zip-outer`带依赖,且为压缩包的上传安装 【暂未完成】 \ No newline at end of file +6. 【新增】支持`jar-outer`、`zip-outer`类型带依赖且为压缩包上传插件的方式 \ No newline at end of file -- Gitee