🖋️ 前言
首先,为什么会有这篇文章?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();
Ansi.ansi()创建新的 ANSI 控制器对象;.fgBlue().bold()设置控制台输出的前景色为蓝色,且加粗文本;.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());
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-pack 的 vscjava 。
2.使用 split("\\.") 分割结果,以点号作为分隔符,所以我们预期得到两个部分:发布者名称和扩展名称,即 vscjava 和 vscode-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);
}
}
- 使用
.toLowerCase()将用户输入的大写转换为小写; - 继续使用
Desktop.getDesktop()获取系统桌面接口; - 最后调用
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…