通过 Java 获取 Visual Studio Code 的扩展

148 阅读5分钟

🖋️ 前言

首先,为什么会有这篇文章?VS Code 明明点击‘安装’就好了,废老鼻子劲手动下载扩展?不脱裤子放屁吗?确实,在某些场景下,我们可能真的‘点不了’ ——可能是公司网络限制、可能是VS Code卡Bug、也可能是我们需要安装特定版本的插件。于是,它就应运而生了。

💻 开发环境

  • Intelij IDEA IDE
  • Open JDK 21
  • Jansi 2.4.1

相关链接:

www.jetbrains.com/idea/
adoptium.net/zhCN/temuri… central.sonatype.com/artifact/or…

👨‍💻 教程

1.先导入库: 确保你包含了以下的库:

// 控制台美化
import org.fusesource.jansi.Ansi; 
// 启动桌面应用程序
import java.awt.Desktop;
// 对 URI 的解析、验证和操作
import java.net.URI;
// 发送 HTTP 请求并接收响应
import java.net.http.HttpClient;
// 构建 HTTP 请求对象
import java.net.http.HttpRequest;
// 表示 HTTP 响应信息
import java.net.http.HttpResponse;
// 设置超时时间间隔
import java.time.Duration;
import java.util.Scanner;

2.获取用户的 URL 地址:

  • 为了读取用户的输入的 URL ,我们需要用 Scanner 库来读取用户信息:
 System.out.print(Ansi.ansi().fgBlue().bold().a("\n输入 Vsix URL → ").reset());
 // 调用 scanner.nextLine() 方法等待用户输入一行文本,并将其存储在 userUrl 变量中。
 String userUrl = scanner.nextLine();
  1. Ansi.ansi() 创建新的 ANSI 控制器对象;
  2. .fgBlue().bold() 设置控制台输出的前景色为蓝色,且加粗文本;
  3. .reset() 重置控制台样式到默认状态。
  • 为了防止用户输入无效的 URL ,我们需要验证是否有效:
// 检查 userUrl 是否为 null 或者是否为空字符串
if (userUrl == null || userUrl.isEmpty()) { 
    System.out.println(Ansi.ansi().fgRed().bold().a("\n<输入 URL>").reset()); 
    return null; 
}
  • getUserInputUrl 方法:
private static String getUserInputUrl(Scanner scanner) {
    System.out.print(Ansi.ansi().fgBlue().bold().a("\n输入 Vsix URL → ").reset());
    String userUrl = scanner.nextLine();
    if (userUrl == null || userUrl.isEmpty()) {
        System.out.println(Ansi.ansi().fgRed().bold().a("\n<输入 URL>").reset());
        return null;
    }
    return userUrl;
}

3.检查 URL 是否真实:

  • 先创建 URI 对象并检查主机名起:
// new URI(userUrl) 将用户输入的字符串转换为 URI 对象
URI uri = new URI(userUrl);
System.out.println(Ansi.ansi().fgCyan().bold().a("\n<验证并请求 URL>").reset());
// 检查 uri.getHost() 是否为空或是否以 "marketplace.visualstudio.com" 结尾
if (uri.getHost() == null ||
!uri.getHost().endsWith("marketplace.visualstudio.com")) {
    System.out.println(Ansi.ansi().fgRed().bold().a("<无效的 Vsix URL>").reset());
    return false;
}
  • 接着我们继续验证,看看用户的 URL 是否是真存在:
// 创建 HttpClient 并发送 HTTP 请求
HttpClient client = HttpClient.newBuilder()
        .connectTimeout(Duration.ofSeconds(10))
        .build();
// 构建一个 HttpRequest 对象,指定使用 GET 方法,设置 User-Agent 头和请求超时时间
HttpRequest request = HttpRequest.newBuilder(uri)
        .GET()
        .setHeader("User-Agent", "Mozilla/5.0")
        .timeout(Duration.ofSeconds(10))
        .build();

HttpResponse<Void> response = client.send(request, HttpResponse.BodyHandlers.discarding());
  1. User-Agent 头:建议得有,否则很有可能被服务器限制/屏蔽了,这里 Mozilla/5.0 表示这是一个基于 Mozilla 的浏览器来访问数据,可以写得再复杂一点。
  • 获取状态码:
// 状态码 200 代表请求成功
if (response.statusCode() == 200) {
    System.out.println(Ansi.ansi().fgGreen().bold().a("\n<URL 请求成功>").reset());
    return true;
} else {
    System.out.println(Ansi.ansi().fgRed().bold().a("\n<请求失败 ( " + response.statusCode() + ")>").reset());
    return false;
}
  • validateUrl 方法:
private static boolean validateUrl(String userUrl) {
    try {
        URI uri = new URI(userUrl);
        System.out.println(Ansi.ansi().fgCyan().bold().a("\n<验证并请求 URL>").reset());
        if (uri.getHost() == null || !uri.getHost().endsWith("marketplace.visualstudio.com")) {
            System.out.println(Ansi.ansi().fgRed().bold().a("<无效的 Vsix URL>").reset());
            return false;
        }

        HttpClient client = HttpClient.newBuilder()
                .connectTimeout(Duration.ofSeconds(10))
                .build();
        HttpRequest request = HttpRequest.newBuilder(uri)
                .GET()
                .setHeader("User-Agent", "Mozilla/5.0")
                .timeout(Duration.ofSeconds(10))
                .build();

        HttpResponse<Void> response = client.send(request, HttpResponse.BodyHandlers.discarding());

        if (response.statusCode() == 200) {
            System.out.println(Ansi.ansi().fgGreen().bold().a("\n<URL 请求成功>").reset());
            return true;
        } else {
            System.out.println(Ansi.ansi().fgRed().bold().a("\n<请求失败 ( " + response.statusCode() + ")>").reset());
            return false;
        }
    } catch (Exception e) {
        System.out.println(Ansi.ansi().fgRed().bold().a("\n<验证失败>").reset());
    }
    return false;
}

4.获取版本号:

  • 提示用户输入:
System.out.print(Ansi.ansi().fgBlue().bold().a("\nVsix 版本 → ").reset());
String userVersion = scanner.nextLine();
  • 检查版本是否有效:
if (userVersion == null || userVersion.isEmpty()) {
    System.out.println(Ansi.ansi().fgRed().bold().a("\n<输入版本号>").reset());
    return null;
}

这两个步骤和 获取用户的 URL 地址 差不多,自行上划查看。

  • getExtensionVersion 方法:
private static String getExtensionVersion(Scanner scanner) {
    System.out.print(Ansi.ansi().fgBlue().bold().a("\nVsix 版本 → ").reset());
    String userVersion = scanner.nextLine();
    if (userVersion == null || userVersion.isEmpty()) {
        System.out.println(Ansi.ansi().fgRed().bold().a("\n<输入版本号>").reset());
        return null;
    }
  创建 URI 对象并检查查询参数  return userVersion;
}

5.提取下载链接的信息:

  • 创建 URI 对象并检查查询参数:
URI uri = new URI(userUrl);
// 获取 URL 的查询部分 uri.getQuery()
String query = uri.getQuery();
// 检查查询部分是否为空或不包含 "itemName=" 参数
if (query == null || !query.contains("itemName=")) {
    return null;
}
  • 接着,我们还要解析查询参数:
String itemName = query.split("itemName=")[1];
String[] parts = itemName.split("\\.");
// 如果分割后的数组长度不是 2(即格式不正确),则返回 null
if (parts.length != 2) {
    return null;
}

1.使用 split("itemName=") 分割查询字符串,获取它后面的内容,例如:itemName=vscjava.vscode-java-packvscjava

2.使用 split("\\.") 分割结果,以点号作为分隔符,所以我们预期得到两个部分:发布者名称和扩展名称,即 vscjavavscode-java-pack

  • 返回数组
return new String[]{parts[0], parts[1]};
  • extractPublisherAndExtension 方法:
private static String[] extractPublisherAndExtension(String userUrl) {
    try {
        URI uri = new URI(userUrl);
        String query = uri.getQuery();
        if (query == null || !query.contains("itemName=")) {
            return null;
        }
        String itemName = query.split("itemName=")[1];
        String[] parts = itemName.split("\.");
        if (parts.length != 2) {
            return null;
        }
        return new String[]{parts[0], parts[1]};
    } catch (Exception e) {
        System.out.println(Ansi.ansi().fgRed().bold().a("\n<验证 URL 失败>").reset());
        return null;
    }
}

6.生成下载链接:

  • 打印下载链接:
System.out.println(Ansi.ansi().fgGreen().bold().a("\n<已构建下载链接>").reset());
String downloadUrl = String.format(
// 拼接字符串
"https://marketplace.visualstudio.com/_apis/public/gallery/publishers/%s/vsextensions/%s/%s/vspackage",
        publisher, extensionName, userVersion);
System.out.println(downloadUrl + "\n");
  • 询问用户是否下载:
System.out.println(Ansi.ansi().fgBlue().bold().a("下载 " + extensionName + " 吗? [是 Y][否 N]").reset());

try (Scanner scanner = new Scanner(System.in)) {
    String choice = scanner.nextLine().toLowerCase();
    // 如果用户选了 Y/y
    if (choice.equals("y")) {
        try {
            Desktop desktop = Desktop.getDesktop();
            desktop.browse(new URI(downloadUrl));
            // 打印信息
            System.out.println(Ansi.ansi().fgGreen().bold().a("\n<操作成功完成>").reset());
        } catch (Exception e) {
            System.out.println(Ansi.ansi().fgRed().bold().a("\n<操作失败>").reset());
        }
    } else {
        // 退出程序
        System.exit(0);
    }
}
  1. 使用 .toLowerCase() 将用户输入的大写转换为小写;
  2. 继续使用 Desktop.getDesktop() 获取系统桌面接口;
  3. 最后调用 desktop.browse(new URI(downloadUrl)) 使用系统默认浏览器打开指定的 URI。
  • generateDownloadLink 方法:
private static void generateDownloadLink(String publisher, String extensionName, String userVersion) {
    System.out.println(Ansi.ansi().fgGreen().bold().a("\n<已构建下载链接>").reset());
    String downloadUrl = String.format(
            "https://marketplace.visualstudio.com/_apis/public/gallery/publishers/%s/vsextensions/%s/%s/vspackage",
            publisher, extensionName, userVersion);
    System.out.println(downloadUrl + "\n");
    System.out.println(Ansi.ansi().fgBlue().bold().a("下载 " + extensionName + " 吗? [是 Y][否 N]").reset());

    try (Scanner scanner = new Scanner(System.in)) {
        String choice = scanner.nextLine().toLowerCase();
        if (choice.equals("y")) {
            try {
                Desktop desktop = Desktop.getDesktop();
                desktop.browse(new URI(downloadUrl));
                System.out.println(Ansi.ansi().fgGreen().bold().a("\n<操作成功完成>").reset());
            } catch (Exception e) {
                System.out.println(Ansi.ansi().fgRed().bold().a("\n<操作失败>").reset());
            }
        } else {
            System.exit(0);
        }
    }
}

7.加载主类 main:

  • 逐步引导用户完成 VS Code 扩展的下载操作:
public static void main(String[] args) {
    try (Scanner scanner = new Scanner(System.in)) {
        String userUrl = getUserInputUrl(scanner);
        if (userUrl == null) {
            return;
        }

        String[] extractedInfo = extractPublisherAndExtension(userUrl);
        if (extractedInfo == null) {
            return;
        }
        String publisher = extractedInfo[0];
        String extensionName = extractedInfo[1];

        if (validateUrl(userUrl)) {
            String userVersion = getExtensionVersion(scanner);
            if (userVersion != null) {
                generateDownloadLink(publisher, extensionName, userVersion);
            }
        }
    }
}

流程图

flowchart TD
    A[开始] --> B[获取用户输入的 Marketplace URL]
    B --> C{URL 是否为空?}
    C -- 是 --> D[结束程序]
    C -- 否 --> E[解析发布者和扩展名]
    E --> F{解析是否成功?}
    F -- 否 --> D
    F -- 是 --> G[验证 URL 是否有效]
    G --> H{验证是否通过?}
    H -- 否 --> D
    H -- 是 --> I[获取用户指定的版本号]
    I --> J{版本号是否有效?}
    J -- 否 --> D
    J -- 是 --> K[生成下载链接]
    K --> L[结束]

📚 总结

  • 它能完美做到以下功能:

    1.分析链接否有效;

    2.解析链接并拼接;

    3.生成链接并下载。

  • 如果你对这个项目感兴趣的话,可以看看我的 GitHub 项目,喜欢请点 Star 支持一下~ github.com/lg841226/Vs…