Compare commits

..
6 Commits
7 changed files with 313 additions and 31 deletions

No files matched your search

+17 -20
View File
@@ -1,4 +1,4 @@
1.7-SNAPSHOT
1.9-SNAPSHOT
此版本包含:
1. 压缩/解压 (`compress`)
@@ -13,24 +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.7 更新内容:
- 安全:修复 Zip-Slip 路径穿越漏洞,解压时校验条目路径并拒绝符号/硬链接条目
- 安全:AES 升级为 AES-256/GCM(随机 IV + 认证标签),RSA 实现真实混合加密
(RSA-2048/OAEP 包裹随机 AES-256 会话密钥),密钥文件限制为仅属主可读写
- 健壮性:加解密改为流式处理(支持大文件),临时文件 + 原子替换,中途失败不损坏原数据
- 修复:随机数上界 Integer.MAX_VALUE 溢出;ColorPrintlnPlus 格式码叠加(颜色+加粗等)
- 修复:getHWID 命令执行增加超时并并发排空输出流,Windows 改用 PowerShell CIM(wmic 回退),
修正 Linux 磁盘序列号命令,MAC 优先物理网卡
- 修复:getJarPath 经 URI 解析(正确处理空格与 URL 编码),临时目录改用唯一命名
- 工程:测试全部迁移至 JUnit 5(53 个用例),pom 补充元数据与插件版本,
版本号统一由 pom / MANIFEST Implementation-Version 提供
1.9 更新内容:
- 新增文件下载模块 (`file/download`)
- 支持单线程下载和多线程并行下载(基于 HTTP Range 请求)
- 多线程下载自动创建父目录,预分配文件空间
- 使用 Java HttpClient 标准库,支持自动重定向
在 1.6-SNAPSHOT 的基础上进行安全加固与缺陷修复。
在 1.8-SNAPSHOT 的基础上进行版本升级。
+44 -9
View File
@@ -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.7-SNAPSHOT.jar` 放入项目依赖中,或在 Releases 中下载最新版 JAR 包。
编译成功后,将 `target/gnu_java_utils_library-1.9-SNAPSHOT.jar` 放入项目依赖中,或在 Releases 中下载最新版 JAR 包。
**pom.xml 引入本地依赖:**
@@ -425,9 +460,9 @@ mvn test
<dependency>
<groupId>top.gtb520.java.gnu</groupId>
<artifactId>gnu_java_utils_library</artifactId>
<version>1.7-SNAPSHOT</version>
<version>1.9-SNAPSHOT</version>
<scope>system</scope>
<systemPath>${project.basedir}/lib/gnu_java_utils_library-1.7-SNAPSHOT.jar</systemPath>
<systemPath>${project.basedir}/lib/gnu_java_utils_library-1.9-SNAPSHOT.jar</systemPath>
</dependency>
</dependencies>
```
+1 -1
View File
@@ -6,7 +6,7 @@
<groupId>top.gtb520.java.gnu</groupId>
<artifactId>gnu_java_utils_library</artifactId>
<version>1.7-SNAPSHOT</version>
<version>1.9-SNAPSHOT</version>
<packaging>jar</packaging>
<name>GNU Java Utils Library</name>
@@ -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);
}
}
// 文件加解密操作
@@ -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<InputStream> 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<Void> 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<InputStream> 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;
}
}
}
@@ -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.7";
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";
@@ -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);
}
}
}