mirror of
				https://github.com/NotoChen/Jetbrains-Help.git
				synced 2025-10-31 17:08:30 +08:00 
			
		
		
		
	perf: 优化插件的刷新加载机制
This commit is contained in:
		
							parent
							
								
									b455aa58fc
								
							
						
					
					
						commit
						aef596ada2
					
				|  | @ -1,9 +1,7 @@ | |||
| package com.jetbrains.help; | ||||
| 
 | ||||
| import cn.hutool.core.collection.CollUtil; | ||||
| import cn.hutool.core.net.Ipv4Util; | ||||
| import cn.hutool.core.text.CharSequenceUtil; | ||||
| import cn.hutool.core.util.StrUtil; | ||||
| import cn.hutool.core.thread.ThreadUtil; | ||||
| import cn.hutool.extra.spring.SpringUtil; | ||||
| import com.jetbrains.help.context.*; | ||||
| import lombok.SneakyThrows; | ||||
|  | @ -13,14 +11,11 @@ import org.springframework.boot.autoconfigure.SpringBootApplication; | |||
| import org.springframework.boot.context.event.ApplicationReadyEvent; | ||||
| import org.springframework.context.annotation.Import; | ||||
| import org.springframework.context.event.EventListener; | ||||
| import org.springframework.scheduling.annotation.EnableScheduling; | ||||
| import org.springframework.scheduling.annotation.Scheduled; | ||||
| import org.springframework.scheduling.annotation.*; | ||||
| 
 | ||||
| import java.net.InetAddress; | ||||
| import java.util.Collection; | ||||
| import java.util.List; | ||||
| 
 | ||||
| @Slf4j | ||||
| @Slf4j(topic = "源项目入口") | ||||
| @EnableScheduling | ||||
| @Import(SpringUtil.class) | ||||
| @SpringBootApplication | ||||
|  | @ -41,15 +36,15 @@ public class JetbrainsHelpApplication { | |||
|         InetAddress localHost = InetAddress.getLocalHost(); | ||||
|         String address = CharSequenceUtil.format("http://{}:{}", localHost.getHostAddress(), SpringUtil.getProperty("server.port")); | ||||
|         String runSuccessWarn = "\n====================================================================================\n" + | ||||
|                 "=                        Jetbrains-Help Run Success~                               =\n" + | ||||
|                 "=                        address:" + address + "                            =\n" + | ||||
|                 "=                        Jetbrains-Help 启动成功~                                   =\n" + | ||||
|                 "=                        访问地址:" + address + "                            =\n" + | ||||
|                 "====================================================================================\n"; | ||||
|         log.info(runSuccessWarn); | ||||
|     } | ||||
| 
 | ||||
|     @Scheduled(cron = "0 0 12 * * ?") | ||||
|     public void refresh() { | ||||
|         PluginsContextHolder.refreshJsonFile(); | ||||
|         ThreadUtil.execute(PluginsContextHolder::refreshJsonFile); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  |  | |||
|  | @ -20,7 +20,7 @@ import java.security.cert.X509Certificate; | |||
| import java.security.interfaces.RSAPublicKey; | ||||
| import java.util.concurrent.CompletableFuture; | ||||
| 
 | ||||
| @Slf4j | ||||
| @Slf4j(topic = "代理上下文") | ||||
| @NoArgsConstructor(access = AccessLevel.PRIVATE) | ||||
| public class AgentContextHolder { | ||||
| 
 | ||||
|  | @ -33,18 +33,18 @@ public class AgentContextHolder { | |||
|     private static File jaNetfilterZipFile; | ||||
| 
 | ||||
|     public static void init() { | ||||
|         log.info("Agent context init loading..."); | ||||
|         log.info("初始化中..."); | ||||
|         jaNetfilterZipFile = FileTools.getFileOrCreat(JA_NETFILTER_FILE_PATH + ".zip"); | ||||
|         if (!FileTools.fileExists(JA_NETFILTER_FILE_PATH)) { | ||||
|             unzipJaNetfilter(); | ||||
|             if (!powerConfHasInit()) { | ||||
|                 log.info("Agent config init loading..."); | ||||
|                 log.info("配置初始化中..."); | ||||
|                 loadPowerConf(); | ||||
|                 zipJaNetfilter(); | ||||
|                 log.info("Agent config init success !"); | ||||
|                 log.info("配置初始化成功!"); | ||||
|             } | ||||
|         } | ||||
|         log.info("Agent context init success !"); | ||||
|         log.info("初始化成功!"); | ||||
|     } | ||||
| 
 | ||||
|     public static File jaNetfilterZipFile() { | ||||
|  | @ -57,7 +57,7 @@ public class AgentContextHolder { | |||
|         try { | ||||
|             powerConfStr = IoUtil.readUtf8(FileUtil.getInputStream(powerConfFile)); | ||||
|         } catch (IORuntimeException e) { | ||||
|             throw new IllegalArgumentException(CharSequenceUtil.format("{} File read failed", POWER_CONF_FILE_NAME), e); | ||||
|             throw new IllegalArgumentException(CharSequenceUtil.format("{} 文件读取失败!", POWER_CONF_FILE_NAME), e); | ||||
|         } | ||||
|         return CharSequenceUtil.containsAll(powerConfStr, "[Result]", "EQUAL,"); | ||||
|     } | ||||
|  | @ -68,7 +68,7 @@ public class AgentContextHolder { | |||
|                 .thenApply(AgentContextHolder::generatePowerConfigStr) | ||||
|                 .thenAccept(AgentContextHolder::overridePowerConfFileContent) | ||||
|                 .exceptionally(throwable -> { | ||||
|                     log.error("agent context init or refresh failed", throwable); | ||||
|                     log.error("配置初始化失败!", throwable); | ||||
|                     return null; | ||||
|                 }).join(); | ||||
|     } | ||||
|  | @ -94,7 +94,7 @@ public class AgentContextHolder { | |||
|         try { | ||||
|             FileUtil.writeString(configStr, powerConfFile, StandardCharsets.UTF_8); | ||||
|         } catch (IORuntimeException e) { | ||||
|             throw new IllegalArgumentException(CharSequenceUtil.format("{} File write failed", POWER_CONF_FILE_NAME), e); | ||||
|             throw new IllegalArgumentException(CharSequenceUtil.format("{} 文件写入失败!", POWER_CONF_FILE_NAME), e); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|  |  | |||
|  | @ -27,7 +27,7 @@ import java.security.cert.Certificate; | |||
| import java.security.cert.CertificateEncodingException; | ||||
| import java.security.cert.CertificateException; | ||||
| 
 | ||||
| @Slf4j | ||||
| @Slf4j(topic = "证书上下文") | ||||
| @NoArgsConstructor(access = AccessLevel.PRIVATE) | ||||
| public class CertificateContextHolder { | ||||
| 
 | ||||
|  | @ -45,20 +45,20 @@ public class CertificateContextHolder { | |||
|     private static File crtFile; | ||||
| 
 | ||||
|     public static void init() { | ||||
|         log.info("certificate context init loading..."); | ||||
|         log.info("初始化中..."); | ||||
|         rootKeyFile = FileTools.getFileOrCreat(ROOT_KEY_FILE_NAME); | ||||
|         if (!FileTools.fileExists(PRIVATE_KEY_FILE_NAME) | ||||
|                 || !FileTools.fileExists(PUBLIC_KEY_FILE_NAME) | ||||
|                 || !FileTools.fileExists(CET_FILE_NAME)) { | ||||
|             log.info("certificate context generate loading..."); | ||||
|             log.info("证书生成中..."); | ||||
|             generateCertificate(); | ||||
|             log.info("certificate context generate success!"); | ||||
|             log.info("证书生成成功!"); | ||||
|         } else { | ||||
|             privateKeyFile = FileTools.getFileOrCreat(PRIVATE_KEY_FILE_NAME); | ||||
|             publicKeyFile = FileTools.getFileOrCreat(PUBLIC_KEY_FILE_NAME); | ||||
|             crtFile = FileTools.getFileOrCreat(CET_FILE_NAME); | ||||
|         } | ||||
|         log.info("certificate context init success !"); | ||||
|         log.info("初始化成功!"); | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|  | @ -99,11 +99,11 @@ public class CertificateContextHolder { | |||
|             crtFile = FileTools.getFileOrCreat(CET_FILE_NAME); | ||||
|             PemUtil.writePemObject("CERTIFICATE", certificate.getEncoded(), FileUtil.getWriter(crtFile, StandardCharsets.UTF_8, false)); | ||||
|         } catch (OperatorCreationException e) { | ||||
|             throw new IllegalArgumentException("Certificate operator creation exception", e); | ||||
|             throw new IllegalArgumentException("证书运算符创建异常!", e); | ||||
|         } catch (CertificateEncodingException e) { | ||||
|             throw new IllegalArgumentException("The certificate encoding exception", e); | ||||
|             throw new IllegalArgumentException("证书编码异常", e); | ||||
|         } catch (CertificateException e) { | ||||
|             throw new IllegalArgumentException("The certificate read exception", e); | ||||
|             throw new IllegalArgumentException("证书读取异常", e); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|  |  | |||
|  | @ -24,7 +24,7 @@ import java.util.Set; | |||
| 
 | ||||
| import static cn.hutool.crypto.asymmetric.SignAlgorithm.SHA1withRSA; | ||||
| 
 | ||||
| @Slf4j | ||||
| @Slf4j(topic = "授权上下文") | ||||
| @NoArgsConstructor(access = AccessLevel.PRIVATE) | ||||
| public class LicenseContextHolder { | ||||
| 
 | ||||
|  | @ -52,7 +52,7 @@ public class LicenseContextHolder { | |||
|         try { | ||||
|             certBase64 = Base64.encode(certificate.getEncoded()); | ||||
|         } catch (CertificateEncodingException e) { | ||||
|             throw new IllegalArgumentException("Certificate extraction failed", e); | ||||
|             throw new IllegalArgumentException("证书编码异常", e); | ||||
|         } | ||||
|         return CharSequenceUtil.format("{}-{}-{}-{}", licenseId, licensePartBase64, signatureBase64, certBase64); | ||||
|     } | ||||
|  |  | |||
|  | @ -4,6 +4,7 @@ import cn.hutool.core.io.FileUtil; | |||
| import cn.hutool.core.io.IORuntimeException; | ||||
| import cn.hutool.core.io.IoUtil; | ||||
| import cn.hutool.core.text.CharSequenceUtil; | ||||
| import cn.hutool.core.util.StrUtil; | ||||
| import cn.hutool.http.HttpUtil; | ||||
| import cn.hutool.json.JSONUtil; | ||||
| import com.jetbrains.help.util.FileTools; | ||||
|  | @ -16,10 +17,10 @@ import java.io.File; | |||
| import java.io.IOException; | ||||
| import java.io.InputStream; | ||||
| import java.nio.charset.StandardCharsets; | ||||
| import java.util.List; | ||||
| import java.util.*; | ||||
| import java.util.concurrent.CompletableFuture; | ||||
| 
 | ||||
| @Slf4j | ||||
| @Slf4j(topic = "插件上下文") | ||||
| @NoArgsConstructor(access = AccessLevel.PRIVATE) | ||||
| public class PluginsContextHolder { | ||||
| 
 | ||||
|  | @ -36,20 +37,22 @@ public class PluginsContextHolder { | |||
|     private static File pluginsJsonFile; | ||||
| 
 | ||||
|     public static void init() { | ||||
|         log.info("Plugin context init loading..."); | ||||
|         log.info("初始化中..."); | ||||
|         pluginsJsonFile = FileTools.getFileOrCreat(PLUGIN_JSON_FILE_NAME); | ||||
| 
 | ||||
|         String pluginJsonArray; | ||||
|         try { | ||||
|             pluginJsonArray = IoUtil.readUtf8(FileUtil.getInputStream(pluginsJsonFile)); | ||||
|         } catch (IORuntimeException e) { | ||||
|             throw new IllegalArgumentException(CharSequenceUtil.format("{} File read failed", PLUGIN_JSON_FILE_NAME), e); | ||||
|             throw new IllegalArgumentException(CharSequenceUtil.format("{} 文件读取失败!", PLUGIN_JSON_FILE_NAME), e); | ||||
|         } | ||||
|         if (CharSequenceUtil.isBlank(pluginJsonArray) || !JSONUtil.isTypeJSON(pluginJsonArray)) { | ||||
|             pluginCacheList = new ArrayList<>(); | ||||
|             refreshJsonFile(); | ||||
|         } else { | ||||
|             pluginCacheList = JSONUtil.toList(pluginJsonArray, PluginCache.class); | ||||
|             log.info("Plugin context init success !"); | ||||
|             log.info("初始化成功!"); | ||||
|             refreshJsonFile(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|  | @ -58,26 +61,28 @@ public class PluginsContextHolder { | |||
|     } | ||||
| 
 | ||||
|     public static void refreshJsonFile() { | ||||
|         log.info("Init or Refresh plugin context from 'JetBrains.com' loading..."); | ||||
|         log.info("从'JetBrains.com'刷新中..."); | ||||
|         CompletableFuture | ||||
|                 .supplyAsync(PluginsContextHolder::pluginList) | ||||
|                 .thenApply(PluginsContextHolder::pluginListFilter) | ||||
|                 .thenApply(PluginsContextHolder::pluginConversion) | ||||
|                 .thenAccept(PluginsContextHolder::overrideJsonFile) | ||||
|                 .thenRun(() -> log.info("刷新成功!")) | ||||
|                 .exceptionally(throwable -> { | ||||
|                     log.error("Plugin context init or refresh failed", throwable); | ||||
|                     log.error("刷新失败!", throwable); | ||||
|                     return null; | ||||
|                 }); | ||||
|         log.info("Init or Refresh plugin context success !"); | ||||
|     } | ||||
| 
 | ||||
|     public static void overrideJsonFile(List<PluginCache> pluginCaches) { | ||||
|         log.info("源大小 => [{}], 新增大小 => [{}]", pluginCacheList.size(), pluginCaches.size()); | ||||
|         pluginCacheList.addAll(pluginCaches); | ||||
|         String jsonStr = JSONUtil.toJsonStr(pluginCacheList); | ||||
|         try { | ||||
|             FileUtil.writeString(jsonStr, pluginsJsonFile, StandardCharsets.UTF_8); | ||||
|             FileUtil.writeString(JSONUtil.formatJsonStr(jsonStr), pluginsJsonFile, StandardCharsets.UTF_8); | ||||
|             log.info("Json文件已覆写!"); | ||||
|         } catch (IORuntimeException e) { | ||||
|             throw new IllegalArgumentException(CharSequenceUtil.format("{} File write failed", PLUGIN_JSON_FILE_NAME), e); | ||||
|             throw new IllegalArgumentException(CharSequenceUtil.format("{} 文件写入失败!", PLUGIN_JSON_FILE_NAME), e); | ||||
|         } | ||||
| 
 | ||||
|     } | ||||
|  | @ -87,49 +92,58 @@ public class PluginsContextHolder { | |||
|                 .thenFunction(response -> { | ||||
|                     try (InputStream is = response.bodyStream()) { | ||||
|                         if (!response.isOk()) { | ||||
|                             throw new IllegalArgumentException(CharSequenceUtil.format("{} The request failed = {}", PLUGIN_LIST_URL, response)); | ||||
|                             throw new IllegalArgumentException(CharSequenceUtil.format("{} 请求失败! = {}", PLUGIN_LIST_URL, response)); | ||||
|                         } | ||||
|                         return IoUtil.readObj(is, PluginList.class); | ||||
|                         PluginList pluginList = JSONUtil.toBean(IoUtil.readUtf8(is), PluginList.class); | ||||
|                         log.info("获取大小 => [{}]", pluginList.getTotal()); | ||||
|                         return pluginList; | ||||
|                     } catch (IOException e) { | ||||
|                         throw new IllegalArgumentException(CharSequenceUtil.format("{} The request io read failed", PLUGIN_LIST_URL), e); | ||||
|                         throw new IllegalArgumentException(CharSequenceUtil.format("{} 请求IO读取失败!", PLUGIN_LIST_URL), e); | ||||
|                     } | ||||
|                 }); | ||||
|     } | ||||
| 
 | ||||
|     public static List<PluginList.Plugin> pluginListFilter(PluginList pluginList) { | ||||
|         return pluginList.getPlugins() | ||||
|         List<PluginList.Plugin> plugins = pluginList.getPlugins() | ||||
|                 .stream() | ||||
|                 .filter(plugin -> !PluginsContextHolder.pluginCacheList.contains(new PluginCache().setId(plugin.getId()))) | ||||
|                 .filter(plugin -> !CharSequenceUtil.equals(plugin.getPricingModel(), "FREE")) | ||||
|                 .toList(); | ||||
|         log.info("过滤后大小 => [{}]", plugins.size()); | ||||
|         return plugins; | ||||
|     } | ||||
| 
 | ||||
|     public static List<PluginCache> pluginConversion(List<PluginList.Plugin> pluginList) { | ||||
|         return pluginList | ||||
|         List<PluginCache> list = pluginList | ||||
|                 .stream() | ||||
|                 .parallel() | ||||
|                 .map(plugin -> { | ||||
|                     String productCode = pluginInfo(plugin.getId()).getPurchaseInfo().getProductCode(); | ||||
|                     String productCode = pluginInfo(plugin).getPurchaseInfo().getProductCode(); | ||||
|                     return new PluginCache() | ||||
|                             .setId(plugin.getId()) | ||||
|                             .setProductCode(productCode) | ||||
|                             .setName(plugin.getName()) | ||||
|                             .setPricingModel(plugin.getPricingModel()) | ||||
|                             .setIcon(PLUGIN_BASIC_URL + plugin.getIcon()) | ||||
|                             .setIcon(StrUtil.isNotBlank(plugin.getIcon()) ? PLUGIN_BASIC_URL + plugin.getIcon() : null) | ||||
|                             ; | ||||
|                 }) | ||||
|                 .toList(); | ||||
|         log.info("转换后大小 => [{}]", list.size()); | ||||
|         return list; | ||||
|     } | ||||
| 
 | ||||
|     public static PluginInfo pluginInfo(Long pluginId) { | ||||
|         return HttpUtil.createGet(PLUGIN_INFO_URL + pluginId) | ||||
|     public static PluginInfo pluginInfo(PluginList.Plugin plugin) { | ||||
|         return HttpUtil.createGet(PLUGIN_INFO_URL + plugin.getId()) | ||||
|                 .thenFunction(response -> { | ||||
|                     try (InputStream is = response.bodyStream()) { | ||||
|                         if (!response.isOk()) { | ||||
|                             throw new IllegalArgumentException(CharSequenceUtil.format("{} The request failed = {}", PLUGIN_INFO_URL, response)); | ||||
|                             throw new IllegalArgumentException(CharSequenceUtil.format("{} 请求失败! = {}", PLUGIN_INFO_URL, response)); | ||||
|                         } | ||||
|                         return IoUtil.readObj(is, PluginInfo.class); | ||||
|                         PluginInfo pluginInfo = JSONUtil.toBean(IoUtil.readUtf8(is), PluginInfo.class); | ||||
|                         log.info("已抓取 => ID = [{}], 名称 = [{}], Code = [{}]", pluginInfo.getId(), plugin.getName(), pluginInfo.getPurchaseInfo().getProductCode()); | ||||
|                         return pluginInfo; | ||||
|                     } catch (IOException e) { | ||||
|                         throw new IllegalArgumentException(CharSequenceUtil.format("{} The request io read failed", PLUGIN_LIST_URL), e); | ||||
|                         throw new IllegalArgumentException(CharSequenceUtil.format("{} 请求IO读取失败!", PLUGIN_LIST_URL), e); | ||||
|                     } | ||||
|                 }); | ||||
|     } | ||||
|  |  | |||
|  | @ -14,7 +14,7 @@ import lombok.extern.slf4j.Slf4j; | |||
| import java.io.File; | ||||
| import java.util.List; | ||||
| 
 | ||||
| @Slf4j | ||||
| @Slf4j(topic = "产品上下文") | ||||
| @NoArgsConstructor(access = AccessLevel.PRIVATE) | ||||
| public class ProductsContextHolder { | ||||
| 
 | ||||
|  | @ -23,20 +23,20 @@ public class ProductsContextHolder { | |||
|     private static List<ProductCache> productCacheList; | ||||
| 
 | ||||
|     public static void init() { | ||||
|         log.info("Product context init loading..."); | ||||
|         log.info("初始化中..."); | ||||
|         File productJsonFile = FileTools.getFileOrCreat(PRODUCT_JSON_FILE_NAME); | ||||
| 
 | ||||
|         String productJsonArray; | ||||
|         try { | ||||
|             productJsonArray = IoUtil.readUtf8(FileUtil.getInputStream(productJsonFile)); | ||||
|         } catch (IORuntimeException e) { | ||||
|             throw new IllegalArgumentException(CharSequenceUtil.format("{} File read failed !", PRODUCT_JSON_FILE_NAME), e); | ||||
|             throw new IllegalArgumentException(CharSequenceUtil.format("{} 文件读取失败!", PRODUCT_JSON_FILE_NAME), e); | ||||
|         } | ||||
|         if (CharSequenceUtil.isBlank(productJsonArray) || !JSONUtil.isTypeJSON(productJsonArray)) { | ||||
|             log.error("Jetbrains Product data does not exist !"); | ||||
|             log.error("产品数据不存在!"); | ||||
|         } else { | ||||
|             productCacheList = JSONUtil.toList(productJsonArray, ProductCache.class); | ||||
|             log.info("Product context init success !"); | ||||
|             log.info("初始化成功!"); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|  |  | |||
|  | @ -35,7 +35,7 @@ public interface FileTools { | |||
|                 try { | ||||
|                     FileUtil.writeFromStream(classPathResource.getInputStream(), classPathFile); | ||||
|                 } catch (Exception e) { | ||||
|                     throw new IllegalArgumentException(CharSequenceUtil.format("{} File read failed", classPathFile.getPath()), e); | ||||
|                     throw new IllegalArgumentException(CharSequenceUtil.format("{} 文件读取失败!", classPathFile.getPath()), e); | ||||
|                 } | ||||
|                 FileUtil.copy(classPathFile, file, true); | ||||
|             } | ||||
|  |  | |||
|  | @ -1,10 +1,8 @@ | |||
| ${AnsiColor.RED}     ██╗███████╗████████╗██████╗ ██████╗  █████╗ ██╗███╗   ██╗███████╗      ██╗  ██╗███████╗██╗     ██████╗ | ||||
| ${AnsiColor.CYAN}     ██║██╔════╝╚══██╔══╝██╔══██╗██╔══██╗██╔══██╗██║████╗  ██║██╔════╝      ██║  ██║██╔════╝██║     ██╔══██╗ | ||||
| ${AnsiColor.BRIGHT_YELLOW}     ██║█████╗     ██║   ██████╔╝██████╔╝███████║██║██╔██╗ ██║███████╗█████╗███████║█████╗  ██║     ██████╔╝ | ||||
| ${AnsiColor.GREEN}██   ██║██╔══╝     ██║   ██╔══██╗██╔══██╗██╔══██║██║██║╚██╗██║╚════██║╚════╝██╔══██║██╔══╝  ██║     ██╔═══╝ | ||||
| ${AnsiColor.BLUE}╚█████╔╝███████╗   ██║   ██████╔╝██║  ██║██║  ██║██║██║ ╚████║███████║      ██║  ██║███████╗███████╗██║ | ||||
| ${AnsiColor.MAGENTA} ╚════╝ ╚══════╝   ╚═╝   ╚═════╝ ╚═╝  ╚═╝╚═╝  ╚═╝╚═╝╚═╝  ╚═══╝╚══════╝      ╚═╝  ╚═╝╚══════╝╚══════╝╚═╝ | ||||
| 
 | ||||
|      ██╗███████╗████████╗██████╗ ██████╗  █████╗ ██╗███╗   ██╗███████╗      ██╗  ██╗███████╗██╗     ██████╗ | ||||
|      ██║██╔════╝╚══██╔══╝██╔══██╗██╔══██╗██╔══██╗██║████╗  ██║██╔════╝      ██║  ██║██╔════╝██║     ██╔══██╗ | ||||
|      ██║█████╗     ██║   ██████╔╝██████╔╝███████║██║██╔██╗ ██║███████╗█████╗███████║█████╗  ██║     ██████╔╝ | ||||
| ██   ██║██╔══╝     ██║   ██╔══██╗██╔══██╗██╔══██║██║██║╚██╗██║╚════██║╚════╝██╔══██║██╔══╝  ██║     ██╔═══╝ | ||||
| ╚█████╔╝███████╗   ██║   ██████╔╝██║  ██║██║  ██║██║██║ ╚████║███████║      ██║  ██║███████╗███████╗██║ | ||||
|  ╚════╝ ╚══════╝   ╚═╝   ╚═════╝ ╚═╝  ╚═╝╚═╝  ╚═╝╚═╝╚═╝  ╚═══╝╚══════╝      ╚═╝  ╚═╝╚══════╝╚══════╝╚═╝ | ||||
| 
 | ||||
| ${AnsiColor.BRIGHT_YELLOW} Spring Boot Version: ${spring-boot.version} | ||||
| 
 | ||||
| ${AnsiColor.BRIGHT_YELLOW} Spring Boot Version: ${spring-boot.version}${AnsiColor.DEFAULT} | ||||
							
								
								
									
										3200
									
								
								src/main/resources/external/data/plugin.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3200
									
								
								src/main/resources/external/data/plugin.json
									
									
									
									
										vendored
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
		Loading…
	
		Reference in New Issue
	
	Block a user
	 藏柏
						藏柏