From 87f2fec4d98f3efbc7c168828495022b5961f565 Mon Sep 17 00:00:00 2001 From: database-mysql Date: Mon, 2 Nov 2026 22:10:25 +0800 Subject: [PATCH] =?UTF-8?q?update/add:=20=E6=96=B0=E5=A2=9E=E6=96=87?= =?UTF-8?q?=E4=BB=B6=E4=B8=8B=E8=BD=BD=E6=96=B9=E6=B3=95;?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- INFO.md | 29 +-- README.md | 53 ++++- pom.xml | 2 +- .../java/gnu/java/utils/library/main.java | 11 ++ .../library/utils/file/download/file.java | 181 ++++++++++++++++++ .../java/gnu/java/utils/library/version.java | 2 +- .../library/utils/file/downloadfile.java | 58 ++++++ 7 files changed, 313 insertions(+), 23 deletions(-) create mode 100644 src/main/java/top/gtb520/java/gnu/java/utils/library/utils/file/download/file.java create mode 100644 src/test/java/top/gtb520/java/gnu/java/utils/library/utils/file/downloadfile.java diff --git a/INFO.md b/INFO.md index e01c9f2..cac6d4d 100644 --- a/INFO.md +++ b/INFO.md @@ -1,4 +1,4 @@ -1.8-SNAPSHOT +1.9-SNAPSHOT 此版本包含: 1. 压缩/解压 (`compress`) @@ -13,16 +13,21 @@ 4.1 AES-256/GCM 加密 4.2 RSA-2048/OAEP 混合加密 4.3 DES 加密(已废弃,仅兼容旧版本) -5. 随机生成器 (`random`) -6. 硬件标识生成器 (`hwid`) -7. 版本信息 (`version`) -8. 运行时信息 (`runtime`) - 8.1 获取运行时临时目录 - 8.2 新建临时目录 - 8.3 获取当前运行的 JAR 文件路径 +5. 文件下载 (`file/download`) + 5.1 单线程下载 + 5.2 多线程下载(HTTP Range 分块并行下载,支持断点续传) +6. 随机生成器 (`random`) +7. 硬件标识生成器 (`hwid`) +8. 版本信息 (`version`) +9. 运行时信息 (`runtime`) + 9.1 获取运行时临时目录 + 9.2 新建临时目录 + 9.3 获取当前运行的 JAR 文件路径 -1.8 更新内容: -- 版本号升级为 1.8-SNAPSHOT(pom / version 常量 / README 引用同步) -- 构建与测试验证通过:mvn clean verify(JUnit 5 共 53 个用例全部通过) +1.9 更新内容: +- 新增文件下载模块 (`file/download`) +- 支持单线程下载和多线程并行下载(基于 HTTP Range 请求) +- 多线程下载自动创建父目录,预分配文件空间 +- 使用 Java HttpClient 标准库,支持自动重定向 -在 1.7-SNAPSHOT 的基础上进行版本升级。 \ No newline at end of file +在 1.8-SNAPSHOT 的基础上进行版本升级。 \ No newline at end of file diff --git a/README.md b/README.md index a0eabb9..6b2631b 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # GNU Java Utils Library -一个轻量级 Java 工具库,提供压缩/解压、终端彩色输出、文件创建与管理、文件加密/解密、随机数生成、硬件标识等功能。 +一个轻量级 Java 工具库,提供压缩/解压、终端彩色输出、文件创建与管理、文件加密/解密、文件下载(单线程/多线程)、随机数生成、硬件标识等功能。 > 注:此项目/仓库已同步到 Github:[https://github.com/databasemysql233-debug/gnu_java_utils_library.git](https://github.com/databasemysql233-debug/gnu_java_utils_library.git) > 原项目/仓库链接:[https://git.opensource-studio.info/database-mysql/gnu_java_utils_library.git](https://git.opensource-studio.info/database-mysql/gnu_java_utils_library.git) @@ -23,7 +23,9 @@ src/main/java/top/gtb520/java/gnu/java/utils/library/ │ └── compress.java # 压缩/解压工具类 ├── file/ │ ├── CreateFile.java # 文件创建/保存工具类 - │ └── EncryptionAndDecryption.java # 文件加密/解密工具类 + │ ├── EncryptionAndDecryption.java # 文件加密/解密工具类 + │ └── download/ + │ └── file.java # 文件下载工具类(单线程/多线程) ├── print/ │ ├── echo.java # 终端彩色输出工具类 │ └── ColorNameEnum.java # 颜色名称枚举 @@ -273,7 +275,40 @@ File keyFile = keyDir.resolve("data.txt.key").toFile(); File decrypted = EncryptionAndDecryption.DecryptionFile(file, EncryptionType.AES, keyFile); ``` -### 5. 随机生成器 (`random`) +### 5. 文件下载 (`file/download`) + +支持单线程和多线程文件下载,多线程模式通过 HTTP `Range` 请求分块并行下载,自动创建父目录。 + +| 方法 | 说明 | 参数 | +|-------------------------------------------------------|---------------------------------------|-------------------------------------------------------------| +| `file.DownloadFile(URL, destFilePanth)` | 单线程下载文件 | `URL` 文件地址,`destFilePanth` 保存路径 | +| `file.DownloadFile(URL, destFilePanth, threadNum)` | 多线程下载文件(支持断点续传) | `URL` 文件地址,`destFilePanth` 保存路径,`threadNum` 线程数枚举 | + +**线程数枚举 (`ThreadNum`):** + +| 枚举值 | 线程数 | +|-------------------|------| +| `THREAD_NUM_II` | 2 | +| `THREAD_NUM_IV` | 4 | +| `THREAD_NUM_VI` | 6 | +| `THREAD_NUM_VIII` | 8 | +| `THREAD_NUM_X` | 10 | + +> 多线程下载要求服务器支持 `Accept-Ranges: bytes`(HTTP 206 响应),否则将抛出 `RuntimeException`。 +> 入参为 `null` 时抛出 `IllegalArgumentException`;HTTP 请求失败时抛出 `RuntimeException`。 + +```java +import top.gtb520.java.gnu.java.utils.library.utils.file.download.file; +import top.gtb520.java.gnu.java.utils.library.utils.file.download.file.ThreadNum; + +// 单线程下载 +file.DownloadFile("https://example.com/file.zip", "/path/to/save/file.zip"); + +// 多线程下载(4 线程) +file.DownloadFile("https://example.com/file.zip", "/path/to/save/file.zip", ThreadNum.THREAD_NUM_IV); +``` + +### 6. 随机生成器 (`random`) | 方法 | 说明 | 参数 | |---------------------------------------------|--------------|------------------------------| @@ -289,7 +324,7 @@ String str = defaults.randomString(16); // 生成 16 位随机字符串( > `random(start, end)` 结果包含两端;`randomString(length)` 的长度不能为负数,否则抛出 `IllegalArgumentException`。 -### 6. 硬件标识生成器 (`hwid`) +### 7. 硬件标识生成器 (`hwid`) 组合 CPU、主板、MAC 地址、硬盘序列号等信息,通过 SHA-256 生成唯一的硬件标识符(HWID)。支持 Windows、macOS、Linux 及 Android 平台。 @@ -307,7 +342,7 @@ System.out.println("HWID: " + hwid); > 单项硬件信息获取失败时自动跳过该组件(如无序列号的硬件);全部失败时返回 `"UNKNOWN_HWID"`。 > MAC 地址优先取非回环、非虚拟网卡,避免虚拟网卡导致 HWID 漂移。 -### 7. 版本信息 (`version`) +### 8. 版本信息 (`version`) 提供项目版本信息及编译时 Java/OpenJDK 版本常量。 @@ -338,7 +373,7 @@ main.version.getOpenJDKRuntimeEnvironment(); // OpenJDK 运行时环境 main.version.getOpenJDK64BitServerVM(); // OpenJDK 64-Bit Server VM ``` -### 8. 运行时信息 (`runtime`) +### 9. 运行时信息 (`runtime`) 获取当前 Java 运行时环境信息,包括 JAR 路径、JVM 版本、位数等。 @@ -416,7 +451,7 @@ mvn test ## 使用 -编译成功后,将 `target/gnu_java_utils_library-1.8-SNAPSHOT.jar` 放入项目依赖中,或在 Releases 中下载最新版 JAR 包。 +编译成功后,将 `target/gnu_java_utils_library-1.9-SNAPSHOT.jar` 放入项目依赖中,或在 Releases 中下载最新版 JAR 包。 **pom.xml 引入本地依赖:** @@ -425,9 +460,9 @@ mvn test top.gtb520.java.gnu gnu_java_utils_library - 1.8-SNAPSHOT + 1.9-SNAPSHOT system - ${project.basedir}/lib/gnu_java_utils_library-1.8-SNAPSHOT.jar + ${project.basedir}/lib/gnu_java_utils_library-1.9-SNAPSHOT.jar ``` diff --git a/pom.xml b/pom.xml index 7809a2d..eefddde 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ top.gtb520.java.gnu gnu_java_utils_library - 1.8-SNAPSHOT + 1.9-SNAPSHOT jar GNU Java Utils Library diff --git a/src/main/java/top/gtb520/java/gnu/java/utils/library/main.java b/src/main/java/top/gtb520/java/gnu/java/utils/library/main.java index 3d5e950..d51793a 100644 --- a/src/main/java/top/gtb520/java/gnu/java/utils/library/main.java +++ b/src/main/java/top/gtb520/java/gnu/java/utils/library/main.java @@ -2,6 +2,7 @@ package top.gtb520.java.gnu.java.utils.library; import top.gtb520.java.gnu.java.utils.library.utils.print.ColorNameEnum; import top.gtb520.java.gnu.java.utils.library.utils.compress.CompressPNG.PNGThreshold; +import top.gtb520.java.gnu.java.utils.library.utils.file.download.file.ThreadNum; import java.io.File; import java.nio.file.Path; @@ -153,6 +154,16 @@ public interface main { static boolean StreamSaveFile(File file, Path TargePath, String FileName) { return top.gtb520.java.gnu.java.utils.library.utils.file.CreateFile.StreamSaveFile(file, TargePath, FileName); } + + // 下载文件,单线程 + static void DownloadFile(String URL, String destFilePanth) throws Exception { + top.gtb520.java.gnu.java.utils.library.utils.file.download.file.DownloadFile(URL, destFilePanth); + } + + // 下载文件,多线程 + static void DownloadFile(String URL, String destFilePanth, ThreadNum threadNum) throws Exception { + top.gtb520.java.gnu.java.utils.library.utils.file.download.file.DownloadFile(URL, destFilePanth, threadNum); + } } // 文件加解密操作 diff --git a/src/main/java/top/gtb520/java/gnu/java/utils/library/utils/file/download/file.java b/src/main/java/top/gtb520/java/gnu/java/utils/library/utils/file/download/file.java new file mode 100644 index 0000000..03cac6c --- /dev/null +++ b/src/main/java/top/gtb520/java/gnu/java/utils/library/utils/file/download/file.java @@ -0,0 +1,181 @@ +package top.gtb520.java.gnu.java.utils.library.utils.file.download; + +import java.io.InputStream; +import java.io.RandomAccessFile; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.time.Duration; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +public class file { + + /** + * 单线程下载文件 + * + * @param URL 文件地址 + * @param destFilePanth 下载文件保存路径 + */ + public static void DownloadFile(String URL, String destFilePanth) throws Exception { + if (URL == null || destFilePanth == null) { + throw new IllegalArgumentException("URL 和保存路径不能为 null"); + } + + Path destPath = Paths.get(destFilePanth); + // 确保父目录存在 + if (destPath.getParent() != null) { + Files.createDirectories(destPath.getParent()); + } + + HttpClient client = HttpClient.newBuilder() + .followRedirects(HttpClient.Redirect.ALWAYS) + .connectTimeout(Duration.ofSeconds(30)) + .build(); + + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(URL)) + .GET() + .build(); + + HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofInputStream()); + + if (response.statusCode() != 200) { + throw new RuntimeException("下载失败,HTTP 状态码: " + response.statusCode()); + } + + try (InputStream in = response.body()) { + Files.copy(in, destPath, java.nio.file.StandardCopyOption.REPLACE_EXISTING); + } + } + + /** + * 多线程下载文件 + * + * @param URL 文件地址 + * @param destFilePanth 下载文件保存路径 + * @param threadNum 线程数 + * */ + public static void DownloadFile(String URL, String destFilePanth, ThreadNum threadNum) throws Exception { + if (URL == null || destFilePanth == null) { + throw new IllegalArgumentException("URL 和保存路径不能为 null"); + } + + Path destPath = Paths.get(destFilePanth); + // 确保父目录存在 + if (destPath.getParent() != null) { + Files.createDirectories(destPath.getParent()); + } + + HttpClient client = HttpClient.newBuilder() + .followRedirects(HttpClient.Redirect.ALWAYS) + .connectTimeout(Duration.ofSeconds(30)) + .build(); + + // 获取文件大小 + HttpRequest headRequest = HttpRequest.newBuilder() + .uri(URI.create(URL)) + .method("HEAD", HttpRequest.BodyPublishers.noBody()) + .build(); + + HttpResponse headResponse = client.send(headRequest, HttpResponse.BodyHandlers.discarding()); + + if (headResponse.statusCode() != 200) { + throw new RuntimeException("获取文件信息失败,HTTP 状态码: " + headResponse.statusCode()); + } + + long fileSize = headResponse.headers().firstValueAsLong("content-length") + .orElseThrow(() -> new RuntimeException("无法获取文件大小")); + + int numThreads = threadNum.getNum(); + long blockSize = fileSize / numThreads; + + // 创建临时文件占位 + try (RandomAccessFile raf = new RandomAccessFile(destPath.toFile(), "rw")) { + raf.setLength(fileSize); + } + + CountDownLatch latch = new CountDownLatch(numThreads); + ExecutorService executor = Executors.newFixedThreadPool(numThreads); + + for (int i = 0; i < numThreads; i++) { + long start = i * blockSize; + long end = (i == numThreads - 1) ? fileSize - 1 : (start + blockSize - 1); + + executor.submit(() -> { + try { + downloadBlock(client, URL, destPath.toFile(), start, end); + } catch (Exception e) { + throw new RuntimeException("下载块失败 [" + start + "-" + end + "]: " + e.getMessage(), e); + } finally { + latch.countDown(); + } + }); + } + + latch.await(); + executor.shutdown(); + } + + /** + * 下载指定字节范围的数据块 + * + * @param client HTTP 客户端 + * @param url 文件 URL + * @param destFile 目标文件 + * @param start 起始字节位置 + * @param end 结束字节位置 + */ + private static void downloadBlock(HttpClient client, String url, java.io.File destFile, + long start, long end) throws Exception { + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(url)) + .header("Range", "bytes=" + start + "-" + end) + .GET() + .build(); + + HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofInputStream()); + + if (response.statusCode() != 206) { + throw new RuntimeException("服务器不支持断点续传,HTTP 状态码: " + response.statusCode()); + } + + try (InputStream in = response.body(); + RandomAccessFile raf = new RandomAccessFile(destFile, "rw")) { + + raf.seek(start); + + byte[] buffer = new byte[8192]; + int bytesRead; + while ((bytesRead = in.read(buffer)) != -1) { + raf.write(buffer, 0, bytesRead); + } + } + } + + /** + * 线程数枚举 + * */ + public enum ThreadNum { + THREAD_NUM_II(2), + THREAD_NUM_IV(4), + THREAD_NUM_VI(6), + THREAD_NUM_VIII(8), + THREAD_NUM_X(10); + + private final int num; + + ThreadNum(int num) { + this.num = num; + } + + public int getNum() { + return num; + } + } +} diff --git a/src/main/java/top/gtb520/java/gnu/java/utils/library/version.java b/src/main/java/top/gtb520/java/gnu/java/utils/library/version.java index 5078ab7..3e39238 100644 --- a/src/main/java/top/gtb520/java/gnu/java/utils/library/version.java +++ b/src/main/java/top/gtb520/java/gnu/java/utils/library/version.java @@ -3,7 +3,7 @@ package top.gtb520.java.gnu.java.utils.library; public class version { // 项目的版本信息,不代表实时运行环境 // 与 pom.xml 保持一致;构建 JAR 时优先读取 MANIFEST 的 Implementation-Version - public static final String VERSION = "1.8"; + public static final String VERSION = "1.9"; public static final String JAVA_VERSION = "25"; public static final String OpenJDK_VERSION = "25.0.0.0.1"; public static final String OpenJDK_Runtime_Environment = "25.0.0.0.1+36-GA"; diff --git a/src/test/java/top/gtb520/java/gnu/java/utils/library/utils/file/downloadfile.java b/src/test/java/top/gtb520/java/gnu/java/utils/library/utils/file/downloadfile.java new file mode 100644 index 0000000..729a2e0 --- /dev/null +++ b/src/test/java/top/gtb520/java/gnu/java/utils/library/utils/file/downloadfile.java @@ -0,0 +1,58 @@ +package top.gtb520.java.gnu.java.utils.library.utils.file; + +import org.junit.jupiter.api.Test; +import top.gtb520.java.gnu.java.utils.library.utils.file.download.file; + +import top.gtb520.java.gnu.java.utils.library.utils.file.download.file.ThreadNum; + +import java.io.File; + +public class downloadfile { + @Test + void download_single() { + // 获取当前运行目录 + String currentDirectory = System.getProperty("user.dir"); + + // 文件URL + String fileURL = "http://server-1.database-mysql.com:5525/resource_repository/Java/Linux/openjdk-25.0.2_linux-x64_bin.tar.gz"; + + try { + // 创建目录 + File directory = new File(currentDirectory + "/test_download"); + if (!directory.exists()) { + directory.mkdirs(); + } + System.out.println("目录: " + currentDirectory); + System.out.println("下载目录: " + directory); + + file.DownloadFile(fileURL, directory + "/openjdk-25.0.2_linux-x64_bin.tar.gz"); + } catch (Exception e) { + System.out.println("抛出异常: \n" + e.getMessage()); + throw new RuntimeException(e); + } + } + + @Test + void download_thread() { + // 获取当前运行目录 + String currentDirectory = System.getProperty("user.dir"); + + // 文件URL + String fileURL = "http://server-1.database-mysql.com:5525/resource_repository/Java/Linux/java_composer.tar.gz"; + + try { + // 创建目录 + File directory = new File(currentDirectory + "/test_download"); + if (!directory.exists()) { + directory.mkdirs(); + } + System.out.println("目录: " + currentDirectory); + System.out.println("下载目录: " + directory); + + file.DownloadFile(fileURL, directory + "/java_composer.tar.gz", ThreadNum.THREAD_NUM_X); + } catch (Exception e) { + System.out.println("抛出异常: \n" + e.getMessage()); + throw new RuntimeException(e); + } + } +}