private Mono<Void> loadNetworkPlugin(HttpClient client, NetworkPluginProperties properties) {
        String url = properties.getUrl();
        if (StringUtil.isBlank(url)) {
            return Mono.error(new IllegalArgumentException("The plugin URL must not be blank"));
        }
        URI uri;
        try {
            uri = URI.create(url);
        } catch (Exception e) {
            return Mono.error(new IllegalArgumentException(
                    "Invalid plugin URL: "
                            + url,
                    e));
        }
        String fileName = Paths.get(uri.getPath())
                .getFileName()
                .toString();
        boolean isJavaPlugin;
        PluginType type = properties.getType();
        String newFileName;
        if (type == PluginType.AUTO) {
            isJavaPlugin = fileName.endsWith(".jar");
            boolean isJsPlugin = !isJavaPlugin && fileName.endsWith(".js");
            if (!isJavaPlugin && !isJsPlugin) {
                return Mono.error(new IllegalArgumentException(
                        "Cannot detect the plugin type from the URL: "
                                + url
                                + ". The URL must end with \".jar\" for Java plugins or \".js\" for JavaScript plugins"));
            }
            newFileName = EXTERNAL_PLUGIN_ARCHIVE_NAME_PREFIX + fileName;
        } else {
            isJavaPlugin = type == PluginType.JAVA;
            newFileName = fileName.endsWith(".jar") || fileName.endsWith(".js")
                    ? EXTERNAL_PLUGIN_ARCHIVE_NAME_PREFIX + fileName
                    : EXTERNAL_PLUGIN_ARCHIVE_NAME_PREFIX + fileName + (isJavaPlugin
                            ? ".jar"
                            : ".js");
        }
        Path path = pluginDir.resolve(newFileName);
        File file = path.toFile();
        if (file.exists()) {
            if (properties.isUseLocalCache()) {
                // No need to load cached network plugins here
                // because the plugin manager will load them as local plugins
                return Mono.empty();
            } else {
                file.delete();
            }
        }
        NetworkPluginProperties.DownloadProperties downloadProperties = properties.getDownload();
        return client.followRedirect(true)
                .responseTimeout(Duration.ofMillis(downloadProperties.getTimeoutMillis()))
                .request(downloadProperties.getHttpMethod()
                        .getMethod())
                .uri(uri)
                .responseSingle((response, bodyMono) -> {
                    int code = response.status()
                            .code();
                    return HttpStatusClass.SUCCESS.contains(code)
                            ? bodyMono
                            : Mono.error(new RuntimeException(
                                    "Expected a success code, but got: "
                                            + code));
                })
                .doOnError(t -> LOGGER.error("Failed to download the plugin with the URL: "
                        + url, t))
                .doOnSuccess(buffer -> {
                    LOGGER.info("Downloaded the plugin with the URL: "
                            + url);
                    if (isJavaPlugin) {
                        if (!allowSaveJavaPlugins) {
                            file.deleteOnExit();
                        }
                    } else if (isJsEnabled && !allowSaveJsPlugins) {
                        file.deleteOnExit();
                    }
                    try {
                        Files.createDirectories(pluginDir);
                    } catch (IOException e) {
                        throw new InputOutputException(
                                "Failed to create the plugin directory: "
                                        + pluginDir,
                                e);
                    }
                    FileUtil.write(file, buffer);
                    if (isJavaPlugin) {
                        ZipFile zipFile;
                        try {
                            zipFile = new ZipFile(file);
                        } catch (IOException e) {
                            throw new InputOutputException(
                                    "Failed to new a zip file from the file: "
                                            + path,
                                    e);
                        }
                        loadJavaPluginOrCloseFile(zipFile);
                    } else {
                        if (isJsEnabled) {
                            String script = ByteBufUtil.readString(buffer);
                            loadJsPlugin(script, path);
                        }
                    }
                })
                .doOnSubscribe(subscription -> LOGGER.info("Downloading the plugin with the URL: "
                        + url))
                .then();
    }
@implNote Start plugins after all beans are ready so that plugins can get any bean when
          starting.
turms-server-common/src/main/java/im/turms/server/common/infra/plugin/PluginManager.java