# pan-common
**Repository Path**: apanlh/pan-common
## Basic Information
- **Project Name**: pan-common
- **Description**: pan-common 是一个功能丰富的Java工具类库,宗旨让开发人员更专注于业务逻辑,简化常见业务编程任务,显著提高开发效率 。
主要功能涵盖:HTTP 客户端、FTP 客户端、高效IO/NIO工具、摘要算法、对称加密、非对称加密、签名验证、日期时间、集合、MAP、字符串、数组、比较、反射、脱敏、编码、随机数、ID生成。JAVA工具库,JAVA工具包,Java 快速开发工具集
- **Primary Language**: Unknown
- **License**: MulanPSL-2.0
- **Default Branch**: master
- **Homepage**: https://gitee.com/apanlh/pan-common
- **GVP Project**: No
## Statistics
- **Stars**: 35
- **Forks**: 1
- **Created**: 2023-08-31
- **Last Updated**: 2026-03-27
## Categories & Tags
**Categories**: Uncategorized
**Tags**: Java, java工具包, Java工具类包, Java包, java工具类
## README
# pan-common 工具库





##
[](https://www.murphysec.com/console/report/1749267682312421376/2033229391950004224)
## 📚 文档导航
- [**首页**](#pan-common-工具库)
- [**核心功能**](#核心功能)
- [**安装说明**](#安装说明)
- [**API文档**](#api文档查看)
- [**工具类/示例**](#工具类示例)
- [基础工具](#1-基础工具-base)
- [时间工具](#2-时间工具-date)
- [加密解密工具](#3-加密解密工具-algorithm)
- [网络工具](#4-网络工具-net)
- [反射工具](#5-反射工具-reflection)
- [缓存工具](#6-缓存工具-cache)
- [配置工具](#7-资源配置工具-setting)
- [文件工具](#8-文件工具-file)
- [IO工具](#9-io工具-io)
- [随机工具](#10-随机工具-random)
- [验证工具](#11-验证工具-valid)
- [系统工具](#12-系统工具-sys)
- [线程工具](#13-线程工具-thread)
- [日志工具](#14-日志工具-log)
- [编码工具](#15-编码工具-encode)
- [脱敏工具](#16-脱敏工具-desensitize)
- [图片工具](#17-图片工具-image)
- [ID生成工具](#18-id生成工具-id)
- [树结构工具](#19-树结构工具-tree)
- [单位工具](#20-单位工具-unit)
- [Spring工具](#21-Spring相关)
- [数据交换工具](#22-数据交换-dataformat)
- [**贡献指南**](#贡献)
## 🏠 首页
### 📝 简介
**pan-common** 是一个功能丰富的Java工具库,由独立开发者精心打造,旨在让开发人员更专注于业务逻辑,简化常见编程任务,显著提高开发效率
- ### 注意:
- 因Servlet版本变动,所以区分版本(javax.servlet -> jakarta.servlet)
- 2.x版本专门给SpringBoot2.x使用(如果使用到Spring工具类情况下)
- 如Springboot是3.x或以上请用3.x版本工具包
- 工具类内部实现方式无任何变化,无需变更任何方法
- ### 性能测试:
- 会陆续的把工具类的性能数据放出(基于JMH)
### 🔥 主要特点
- **简单易用**:提供简洁而强大的API,降低编码复杂度,无需查阅文档即可上手
- **功能全面**:覆盖日常开发 90% 以上的通用场景
- **持续迭代**:兼容 JDK8~21,积极更新内部工具类方法,保持技术前瞻性
- **性能优先**:会持续做JMH测试,并且对比三方库工具类或原生写法,持续做性能优化
- **模块化设计**:各工具职责清晰,可独立使用
### 🎯 设计理念
- **降低学习成本**:API方法命名直观,无需查阅文档即可上手,方法名遵循“动词+名词”规范
- **零侵入、轻依赖**:核心工具类不依赖任何第三方库,可独立使用;对需要特定库的功能(如国密、Redis)则通过内部依赖检查机制,如无依赖则进行依赖缺失提示,同时保持工具类整体轻量性
- **性能优先**:针对高频调用场景进行针对性优化,通过内置缓存、减少冗余计算等方式,兼顾代码简洁并提高执行效率
## 🚀核心功能
### 🌐 网络与 IO 操作
- **HTTP 客户端**:轻量级封装,支持 HttpURLConnection 和 JDK 11+ HttpClient 双实现,后期可拓展其他客户端实现提供链式构建、虚拟线程、拦截器、耗时统计、文件上传等功能,无需引入额外依赖
- **FTP 客户端**:基于 Apache Commons Net 封装,简化 FTP 命令,支持连接池、目录操作、文件上传下载与删除
- **IP 工具**:IP 地址解析、IPv4/IPv6 验证、整型与字符串互转
- **IO 工具**:高效流复制、读写,支持字节流和字符流,自动资源释放,避免内存泄漏
- **FileIO 工具**:基于 NIO 的零拷贝文件读写,支持大文件的行迭代读取
- **配置工具**:从 classpath、文件系统、JAR 包中加载 properties 和 YAML 配置,支持自定义字符编码和占位符处理
- **文件工具**:提供文件/目录创建、拷贝、移动、删除、递归遍历、MIME 类型获取等常用操作
### 🔐 安全加密
- **摘要算法**:MD5、SHA-1/224/256/384/512、SHA3-224/256/384/512、HMac系列、国密SM3
- **对称加密**:AES、DES、3DES、国密SM4,支持ECB/CBC/CFB/OFB/CTR等模式及多种填充方式
- **非对称加密**:RSA、国密SM2,支持密钥对生成、加密解密、密钥导出(Hex/Base64)
- **签名验证**:JWT生成与验证,支持HMAC256/384/512,自动处理异常并返回状态码
### 📅 日期时间
- **日期解析**:内置40+常见格式(含中文),通过特征路由实现高性能自动识别,支持LocalDateTime/LocalDate/LocalTime互转
- **日期处理**:提供丰富的日期计算(偏移、差值、比较)、格式化、时间段判断、生日计算、人性化描述等功能
### 📦 数据处理
- **集合工具**:简化集合创建、去重、过滤、分组、集合运算(交并差)、分块批处理
- **Map工具**:多种Map创建、比较、过滤、值查找,支持函数式初始化
- **字符串工具**:拼接、分割、格式化、驼峰/下划线转换、相似度计算、特殊字符清理
- **数组工具**:查找、复制、合并、插入、类型转换(String[] → 基本类型数组)
- **校验工具**:统一参数处理(ValidParam), 一键参数校验(空值、数字、字母、全角等),身份证、正则验证
- **比较工具**:统一Equals处理(Eq类) API,支持字符串、集合、Map、数组、枚举的相等判断,自动推断类型匹配
### 🎨 实用工具
- **反射工具**:优化反射性能,内置字段、方法、构造器、注解缓存,支持 final 字段修改,简化反射编程
- **随机工具**:生成随机数、随机码(数字/字母混合)
- **编码工具**:Base64、十六进制、URL编解码、Unicode转义
- **脱敏工具**:内置8种脱敏规则(姓名、手机、身份证、银行卡、邮箱、地址等),支持注解驱动对象脱敏
- **图片工具**:图片缩放、裁剪、旋转(待完善)
- **ID生成工具**:UUID(含简洁版)、雪花算法(Snowflake)
- **单位工具**:数据大小解析(如"1.5MB"转字节)、字节可读格式化(如"1.5 KB")
[🔝 返回顶部](#pan-common-工具库)
## 📦安装说明
- ### 1️⃣ Maven
在项目中的pom.xml中添加以下内容即可引用
```maven
com.gitee.apanlh
apanlh-common
2.0.3
```
- ### 2️⃣ Gradle
```gradle
implementation group: 'com.gitee.apanlh', name: 'apanlh-common', version: '2.0.3'
```
- ### 3️⃣ 下载JAR
[点击此处下载-Maven](https://repo1.maven.org/maven2/com/gitee/apanlh/apanlh-common/2.0.3/) **点击apanlh-common-x.x.x.jar进行下载**
[🔝 返回顶部](#pan-common-工具库)
## 📚API文档查看
- **Javadoc文档** [点击此处查看-API文档](https://apidoc.gitee.com/apanlh/pan-common)
[🔝 返回顶部](#pan-common-工具库)
# 🔧工具类示例
## 1. 基础工具 (base)
### 1.1 字符串工具 (StringUtils)
**功能**:提供基础字符串操作,涵盖拼接、分割、格式化、判断、替换、转换、清理等常用功能,减少重复代码,提升开发效率
**大部分方法对 JDK 8+ 做了性能优化(如 replace 在 JDK 9+ 使用原生方法)**
#### 代码示例
```java
// 1. 拼接与追加
String str = StringUtils.append("Hello", " ", "World"); // "Hello World"
String str2 = StringUtils.append(false, "World", "Hello "); // "Hello World"(开头追加)
// 2. 包含判断
boolean has = StringUtils.contains("abcdefg", "cd"); // true
boolean any = StringUtils.containsAny("abc", "d", "b"); // true
boolean all = StringUtils.containsAll("abc", "a", "b"); // true
boolean inArr = StringUtils.arrayContainsAny(new String[]{"a","b"}, "b"); // true
// 3. 字符串比较
boolean eq = StringUtils.eq("abc", "abc"); // true
boolean eqAny = StringUtils.eqAny("abc", "def", "abc"); // true
boolean eqEnum= StringUtils.eqAny("SUCCESS", Status.SUCCESS); // true(枚举name比较)
// 4. 格式化(占位符 {})
String formatted = StringUtils.format("Hello, {}!", "World"); // "Hello, World!"
// 5. 驼峰与下划线互转
String hump = StringUtils.lineToHump("user_name"); // "userName"
String line = StringUtils.humpToLine("userName"); // "USER_NAME"(默认大写)
String line2 = StringUtils.humpToLine("userName", true); // "user_name"(小写)
// 6. 判空(含null、空串、仅空格、"null")
boolean empty = StringUtils.isEmpty(" "); // true
boolean empty2 = StringUtils.isEmpty("null"); // true
// 7. 替换
String replaced = StringUtils.replace("aabbcc", "bb", "dd"); // "aaddcc"
// 按顺序占位符替换
String ordered = StringUtils.replaceByOrder("{} is {}", "{}", "{}", "Java", "great"); // "Java is great"
// 8. 移除前后缀
String noPrefix = StringUtils.removePrefix("prefix_name", "prefix_"); // "name"
String noSuffix = StringUtils.removeSuffix("name_suffix", "_suffix"); // "name"
// 9. 分割
String[] parts = StringUtils.split("a,b,c", ","); // ["a","b","c"]
String[] parts2 = StringUtils.split("a,b,", ",", true); // ["a","b"](剔除末尾空串)
List list = StringUtils.splitToList("a,b,c", ","); // ["a","b","c"]
// 10. 截取
String sub = StringUtils.subStr("HelloWorld", 2, 5); // "llo"
// 11. 大小写转换
String lower = StringUtils.lowerFirst("Name"); // "name"
String upper = StringUtils.upperFirst("name"); // "Name"
String allLower = StringUtils.lowerCase("AbC"); // "abc"
String allUpper = StringUtils.upperCase("AbC"); // "ABC"
// 12. 相似度计算
float ratio = StringUtils.similarityRatio("hello", "hallo"); // 约 80.0f
// 13. 清理特殊字符(空格、换行、缩进、全角空格等)
String cleaned = StringUtils.cleanSpecial(" abc \t\n"); // "abc"
String cleaned2 = StringUtils.cleanSpecial(" 全角空格 ", true); // "全角空格"(清理全角符号)
// 14. 自定义过滤字符
String filtered = StringUtils.filter("a1b2c3", c -> Character.isDigit(c)); // "123"
// 15. 前缀忽略大小写匹配
boolean match = StringUtils.startWithIgnoreCase("HelloWorld", "hello"); // true // "abc"
```
### 1.2 数组工具 (ArrayUtils)
**功能**:提供数组操作的常用方法,如查找、判空、连接、复制、合并、插入、比较、类型转换等
#### 代码示例
```java
// 创建数组(基础类型或对象)
int[] intArr = {1, 2, 3, 4, 5};
String[] strArr = {"a", "b", "c"};
// 查找元素
int found = ArrayUtils.find(intArr, 3); // 返回值 3
String foundStr = ArrayUtils.find(strArr, "b"); // 返回 "b"
int index = ArrayUtils.indexOf(intArr, 3); // 返回 2
int lastIndex = ArrayUtils.lastIndexOf(intArr, 3); // 返回 2(只有一个)
int binaryIndex = ArrayUtils.binarySearch(intArr, 3); // 二分查找,返回 2
// 判空
boolean empty = ArrayUtils.isEmpty(intArr); // false
// 连接成字符串
String joined = ArrayUtils.join(intArr, ","); // "1,2,3,4,5"
String joinedStr = ArrayUtils.join(strArr, "-"); // "a-b-c"
// 复制/扩容
int[] copied = ArrayUtils.copy(intArr, 10); // 新数组长度10,前5个为原值,后5个为0
// 合并数组
int[] more = {6, 7};
int[] concated = ArrayUtils.concat(intArr, more); // {1,2,3,4,5,6,7}
// 插入元素
int[] inserted = ArrayUtils.insert(intArr, 2, 99, 100); // 在索引2处插入99,100 => {1,2,99,100,3,4,5}
// 比较数组
int[] another = {1, 2, 3, 4, 5};
boolean equal = ArrayUtils.eq(intArr, another); // true
// 转换为字符串表示
String arrStr = ArrayUtils.toString(intArr); // "[1,2,3,4,5]"
// 类型转换(String[] → 基本类型数组)
String[] numStrs = {"1", "2", "3"};
int[] ints = ArrayUtils.toInt(numStrs); // {1,2,3}
boolean[] bools = ArrayUtils.toBoolean(new String[]{"true", "false"}); // {true,false}
long[] longs = ArrayUtils.toLong(numStrs); // {1L,2L,3L}
```
### 1.3 集合工具 (CollUtils)
**功能**:提供集合操作覆盖集合的创建、转换、过滤、分组、比较、分块、批处理等常见场景
#### 代码示例
```java
// 1. 创建集合
List list = CollUtils.newArrayList("a", "b", "c");
Set set = CollUtils.newHashSet("a", "b", "c");
List beanList = CollUtils.newArrayList(bean1, bean2, bean3);
// 2. 去重
List distinct = CollUtils.distinct(list); // 按元素去重
List distinctBean = CollUtils.distinct(beanList, Bean::getName); // 按字段去重
// 3. 过滤(改变原集合)
CollUtils.filter(list, item -> item.startsWith("a")); // 移除不匹配的元素
// 4. 查找符合条件的元素
List found = CollUtils.find(list, item -> Eq.str("b", item)); // 返回所有匹配的元素
// 查找并映射为自定义值
List mapped = CollUtils.findCall(list, item -> item.equals("b") ? "1" : "0");
// 5. 提取字段值组成新列表
List names = CollUtils.toList(beanList, Bean::getName); // 提取 name 字段
// 6. 集合比较
boolean isEqual = CollUtils.eq(list1, list2); // 内容相同(忽略顺序)
boolean isFieldEqual = CollUtils.eq(beanList1, beanList2, Bean::getName); // 按字段比较
boolean isOrderedEqual = CollUtils.eqByOrder(list1, list2); // 顺序和内容均相同
// 7. 分组
Map> group = CollUtils.groupBy(list, item -> item.startsWith("a"));
// 8. 集合运算
Set intersection = CollUtils.intersection(set1, set2); // 交集
Set union = CollUtils.union(set1, set2); // 并集
Set difference = CollUtils.difference(set1, set2); // 差集(在set1不在set2)
// 9. 分块
List> partitions = CollUtils.partitionList(list); // 自动平衡分块
List> fixedPartitions = CollUtils.partitionList(list, 10); // 每块最多10个元素
// 10. 排序(会改变原集合)
CollUtils.sort(beanList, Bean::getName, Sort.ASC); // 按字段升序
// 11. 批处理
// 同步批处理:将list按每批10个元素执行任务
CollUtils.batchProcess(list, 10, () -> {
// 例如批量插入数据库
});
// 异步批处理(基于CompletableFuture)
CollUtils.batchProcessAsync(list, 10, () -> {
// 异步执行的任务
});
```
### 1.4 映射工具 (MapUtils)
**功能**:提供Map操作的常用方法,如创建、比较、过滤、转换等
#### 代码示例
```java
// 创建各种类型的Map
Map hashMap = MapUtils.newHashMap(); // HashMap
Map linkedMap = MapUtils.newLinkedHashMap(); // LinkedHashMap
Map concurrentMap = MapUtils.newConcurrentHashMap(); // ConcurrentHashMap
Map treeMap = MapUtils.newTreeMap(); // TreeMap
Map enumMap = MapUtils.newEnumMap(DayOfWeek.class); // EnumMap
// 带初始数据的创建
Map map = MapUtils.newHashMap(newMap -> {
newMap.put("a", 1);
newMap.put("b", 2);
});
// 比较Map
Map map1 = MapUtils.newHashMap(m -> m.put("a", 1));
Map map2 = MapUtils.newHashMap(m -> m.put("a", 1));
boolean eqValue = MapUtils.eqByValue(map1, map2); // true(只比较值)
boolean eqKeyValue = MapUtils.eqByKeyValue(map1, map2); // true(比较键和值)
boolean eqOrder = MapUtils.eqByOrder(map1, map2); // true(顺序严格)
// 过滤元素
Map data = MapUtils.newHashMap(m -> {
m.put("a", 1);
m.put("b", 2);
m.put("c", 3);
});
MapUtils.filter(data, 2); // 删除值为2的条目
MapUtils.filter(data, (value) -> value > 1); // 删除值大于1的条目
// 函数式创建
Map source = MapUtils.newHashMap(m -> {
m.put("x", 100);
m.put("y", 200);
m.put("z", 100);
});
Map keys = MapUtils.getKeysByValue(source, 100); // 返回 {"x":100, "z":100}
// 判空
boolean empty = MapUtils.isEmpty(source); // false
// 转换为其他集合
List list = MapUtils.toArrayList(source); // [100,200,100]
Map strMap = MapUtils.toHashMap("a", "b", "c"); // {"0":"a","1":"b","2":"c"}
```
### 1.5 比较工具 (Eq)
**功能**:提供统一、简洁的对象比较API,支持字符串、集合、Map、数组、枚举等多种类型的相等判断,并支持自动类型匹配、回调等高级功能
#### 代码示例
```java
// 字符串比较
boolean eq1 = Eq.str("abc", "abc"); // true
boolean eqAny = Eq.strAny("abc", "def", "abc", "ghi"); // true
// 对象比较
boolean objEq = Eq.object(user1, user2); // 调用equals方法
boolean objAny = Eq.objectAny(target, opt1, opt2, opt3);
// 集合比较
List list1 = CollUtils.newArrayList("a", "b");
List list2 = CollUtils.newArrayList("a", "b");
boolean collEq = Eq.collection(list1, list2); // true
boolean collOrder = Eq.collectionByOrder(list1, list2); // true(严格顺序)
// Map比较
Map map1 = MapUtils.newHashMap(m -> m.put("a", 1));
Map map2 = MapUtils.newHashMap(m -> m.put("a", 1));
boolean mapVal = Eq.mapValue(map1, map2); // true(只比较值)
boolean mapKeyVal = Eq.mapByKeyValue(map1, map2); // true(比较键和值)
boolean mapOrder = Eq.mapByOrder(map1, map2); // true(严格顺序)
// 数组比较
int[] arr1 = {1, 2, 3};
int[] arr2 = {1, 2, 3};
boolean arrEq = Eq.array(arr1, arr2); // true
// 枚举比较
DayOfWeek monday = DayOfWeek.MONDAY;
boolean enumEq = Eq.enums(monday, DayOfWeek.MONDAY); // true
boolean enumAny = Eq.enumsAny(monday, DayOfWeek.SUNDAY, DayOfWeek.MONDAY); // true
// 自动类型匹配(根据参数类型自动选择合适的比较方法)
Object val1 = "abc";
Object val2 = "abc";
boolean auto = Eq.autoEq(val1, val2); // true
// 条件回调
String result = Eq.call(value, expected, "matched"); // 如果相等返回"matched",否则null
Eq.callVoid(value, expected, () -> Dev.log("matched")); // 如果相等执行回调
```
### 1.6 选择器工具 (ChooseEq)
**功能**:提供链式的值匹配选择器,支持显式比较和固定源两种模式,短路返回第一个匹配结果,可简化多分支条件判断
#### 代码示例
```java
// 示例1:显式比较模式(两个值都不固定)
String role = "admin";
String result = ChooseEq.create()
.when(role, "admin", "管理员")
.when(role, "user", "普通用户")
.end("访客"); // 无匹配时返回默认值
// 示例2:显式比较模式 + 延迟计算(值由函数提供)
Integer value = ChooseEq.create()
.when(role, "admin", () -> computeAdminValue())
.when(role, "user", () -> computeUserValue())
.end(() -> 0);
// 示例3:固定源模式(源对象固定,只比较目标)
Integer code = ChooseEq.create("SUCCESS")
.when("SUCCESS", 200)
.when("FAIL", 500)
.end(404);
// 示例4:固定源模式 + 无返回值动作(仅执行操作)
ChooseEq.create("EVENT_A")
.when("EVENT_A", () -> handleEventA())
.when("EVENT_B", () -> handleEventB())
.endVoid(() -> defaultHandler()); // 无匹配时执行默认动作
```
### 1.7 短路顺序选择器 (ChooseFirst)
**功能**:提供链式的短路条件选择器,按顺序匹配条件,一旦找到第一个满足的条件即返回对应的值(或执行动作),后续条件支持直接加载值和延迟计算(FuncCall),可替代冗长的 if-elseif-else 语句
#### 代码示例
```java
// 示例1:直接调用(适合常量,无延迟)
String result = ChooseFirst.create(条件1, "值1")
.when(条件2, "值2")
.when(条件3, "值3")
.end("默认值");
// 示例2:延迟计算(使用 FuncCall,值在匹配时才计算)
Integer value = ChooseFirst.create(条件1, () -> computeExpensiveValue())
.when(条件2, () -> anotherExpensiveValue())
.end(() -> defaultValue);
// 示例3:仅执行动作(无返回值)
ChooseFirst.create(条件1, () -> action1())
.when(条件2, () -> action2())
.endVoid(() -> defaultAction());
// 示例4:空选择器后添加分支
ChooseFirst selector = ChooseFirst.create()
.when(条件1, 100)
.when(条件2, 200)
.when(条件3, 300)
.end(400); // 若无匹配返回 400
```
### 1.8 单例工具 (SingletonUtils)
**功能**:提供基于缓存的单例对象获取工具,支持无参构造函数和有参构造函数,自动缓存已创建的实例,避免重复创建内部使用弱引用缓存,内存友好
**注意**:缓存键由类名和参数值拼接而成,因此不同参数会得到不同实例适用于需要全局唯一实例的场景
#### 代码示例
```java
// 定义测试类
public class User {
private String name;
public User() {}
public User(String name) { this.name = name; }
// getter/setter...
}
// 获取无参构造的单例
User user1 = SingletonUtils.get(User.class);
User user2 = SingletonUtils.get(User.class);
Dev.log(user1 == user2); // true
// 获取有参构造的单例(参数相同则返回同一实例)
User user3 = SingletonUtils.get(User.class, "张三");
User user4 = SingletonUtils.get(User.class, "张三");
Dev.log(user3 == user4); // true
// 不同参数返回不同实例
User user5 = SingletonUtils.get(User.class, "李四");
Dev.log(user3 == user5); // false
```
### 1.9 迭代器工具 (IteratorUtils)
**功能**:提供丰富的迭代器操作工具,支持集合、数组、Map、枚举等多种数据结构的遍历,提供可中断遍历、带索引遍历、重复循环、获取元素等多种高级功能,简化迭代器使用的复杂性
#### 代码示例
```java
// 准备数据
List list = CollUtils.newArrayList("A", "B", "C", "D");
Map map = MapUtils.newHashMap(m -> {
m.put("x", 1);
m.put("y", 2);
m.put("z", 3);
});
// 1. 集合遍历(无返回值)
IteratorUtils.collection(list, (item, iterator) -> {
Dev.log(item);
// 可操作 iterator 进行 remove 等
});
// 2. 可中断的集合遍历(返回 false 停止)
IteratorUtils.collectionBreak(list, (item, iterator) -> {
Dev.log(item);
return !"C".equals(item); // 遇到 C 停止
});
// 3. 数组遍历
String[] array = {"a", "b", "c"};
IteratorUtils.array(array, item -> Dev.log(item));
IteratorUtils.array(array, (item, index) -> Dev.log(index + ": " + item));
// 4. Map EntrySet 遍历
IteratorUtils.entrySet(map, (key, value, iterator) ->
Dev.log(key + "=" + value)
);
IteratorUtils.entrySetBreak(map, (key, value) -> {
Dev.log(key + "=" + value);
return !key.equals("y"); // 遇到 key 为 y 时停止
});
// 5. Map KeySet 遍历
IteratorUtils.keySet(map, key -> Dev.log(key));
// 6. Map Values 遍历
IteratorUtils.values(map, value -> Dev.log(value));
// 7. 获取集合中的指定元素、首元素、尾元素
String elem = IteratorUtils.getElement(list, 2); // "C"
String first = IteratorUtils.getFirst(list); // "A"
String last = IteratorUtils.getLast(list); // "D"
// 8. 重复循环遍历(满足条件则从头开始继续)
List numbers = CollUtils.newArrayList(1, 2, 3);
IteratorUtils.repeatLoop(numbers, item -> {
Dev.log(item);
return item < 3; // 当 item=3 时返回 false 停止,否则继续(到末尾后从头开始)
});
// 9. 枚举遍历
Enumeration enumeration = Collections.enumeration(list);
IteratorUtils.array(enumeration, item -> Dev.log(item));
```
### 1.10 范围工具 (RangeUtils)
**功能**:提供简洁的区间判断工具,支持各种开闭区间组合,适用于数字、日期等可比较类型的范围判断
#### 代码示例
```java
// 数值区间判断
int value = 5;
boolean inOpen = RangeUtils.open(1, 10, value); // (1,10) → true
boolean inClosed = RangeUtils.closed(1, 5, value); // [1,5] → true
boolean inClosedOpen = RangeUtils.closedOpen(1, 5, value); // [1,5) → false (5不在内)
boolean inOpenClosed = RangeUtils.openClosed(1, 5, value); // (1,5] → true
// 单侧区间
boolean lessThan = RangeUtils.leftOpen(10, value); // (-∞,10) → true
boolean lessEqual = RangeUtils.leftClose(5, value); // (-∞,5] → true
boolean greaterThan = RangeUtils.rightOpen(3, value); // (3,+∞) → true
boolean greaterEqual = RangeUtils.rightClose(5, value); // [5,+∞) → true
// 日期区间判断
LocalDate today = LocalDate.now();
LocalDate start = today.minusDays(1);
LocalDate end = today.plusDays(1);
boolean inDateRange = RangeUtils.closed(start, end, today); // true
```
### 1.11 大数字工具 (BigDecimalUtils)
**功能**:提供 BigDecimal 的常用操作,包括数值转换、四舍五入、截断、加减乘除、取余、比较等,支持多种舍入模式,简化高精度数值处理
#### 代码示例
```java
// 数值转换(默认截断两位小数)
BigDecimal bd1 = BigDecimalUtils.toTruncate(123.456789); // 123.45
String str1 = BigDecimalUtils.toTruncateStr(123.456789); // "123.45"
String str2 = BigDecimalUtils.toTruncateRemovePoint(123.45); // "12345"(去除小数点)
// 四舍五入(默认两位小数)
BigDecimal roundUp = BigDecimalUtils.toRoundUp(123.451); // 123.46(向上取整)
BigDecimal halfUp = BigDecimalUtils.toHalfUp(123.455); // 123.46(四舍五入)
BigDecimal halfDown = BigDecimalUtils.toHalfDown(123.455); // 123.45(五舍六入)
// 算术运算
BigDecimal sum = BigDecimalUtils.plus(10.5, 20.3); // 30.8
BigDecimal diff = BigDecimalUtils.minus(50, 12.5); // 37.5
BigDecimal product = BigDecimalUtils.multiply(3, 4.5); // 13.5
BigDecimal quotient = BigDecimalUtils.division(10, 3); // 3.33(除法,默认截断两位小数)
BigDecimal remainder = BigDecimalUtils.module(10, 3); // 1(取余)
// 自定义精度和舍入模式
BigDecimal quotient2 = BigDecimalUtils.division(10, 3, 4, RoundingMode.HALF_UP); // 3.3333
// 数值比较
int cmp1 = BigDecimalUtils.compare(123.45, 123.45); // 0(相等)
int cmp2 = BigDecimalUtils.compare(100, "200"); // -1(100 < 200)
int cmp3 = BigDecimalUtils.compare("300.5", 300); // 1(300.5 > 300)
```
### 1.12 类型转换工具 (ClassConvertUtils)
**功能**:提供丰富且高效的类型转换方法,涵盖 JavaBean 与 Map 互转、基本类型与字符串互转、数组类型转换、强制类型转换等,简化日常开发中的类型处理
#### 代码示例
```java
// 1. Bean 转 Map
User user = new User("张三", 25);
Map map = ClassConvertUtils.toMap(user);
Map strMap = ClassConvertUtils.toMapString(user);
// 2. Map 转 Bean
Map params = new HashMap<>();
params.put("name", "李四");
params.put("age", 30);
User newUser = ClassConvertUtils.toBean(params, User.class);
// 3. 基础类型转换
int i = ClassConvertUtils.toInt("123");
long l = ClassConvertUtils.toLong("123456");
boolean b = ClassConvertUtils.toBoolean("yes"); // true
boolean b2 = ClassConvertUtils.toBoolean(1); // true
// 4. 数组转换
byte[] bytes = "Hello".getBytes();
char[] chars = ClassConvertUtils.toChar(bytes); // 转为 char[]
byte[] back = ClassConvertUtils.toByte(chars); // 转回 byte[]
// 5. 强制类型转换
Object obj = "test";
String str = ClassConvertUtils.castString(obj); // 等同于 (String) obj
List list = ClassConvertUtils.castList(obj); // 假设 obj 是 List
```
### 1.13 类工具 (ClassUtils)
**功能**:提供类操作的常用方法,包括类名获取、类加载、包扫描、类关系判断、代理类处理等,内置缓存优化类加载性能
#### 代码示例
```java
// 获取类名(简单名、全限定名、包名)
Class> clazz = User.class;
String simpleName = ClassUtils.getSimpleName(clazz); // "User"
String fullName = ClassUtils.getName(clazz); // "com.example.User"
String packageName = ClassUtils.getPackageName(clazz); // "com.example"
String lowerFirst = ClassUtils.getSimpleName(clazz, true); // "user"
// 加载类(带缓存)
Class> loaded = ClassUtils.forName("java.lang.String");
// 判断类关系
boolean related = ClassUtils.isRelated(List.class, ArrayList.class); // true
boolean sub = ClassUtils.isSubclassOrSame(Collection.class, ArrayList.class); // true
boolean equal = ClassUtils.eq(String.class, Integer.class); // false
// 获取调用者类
Class> caller = ClassUtils.getClassOfCall(); // 返回调用此方法的类
// 获取参数类型数组
Object[] args = {"abc", 123};
Class>[] argTypes = ClassUtils.getClasses(args); // [String.class, Integer.class]
// 扫描指定包下的所有类
List> classes = ClassUtils.getPackageClasses("com.example.service", true);
// 从集合中获取类
List users = ...;
Class> userClass = ClassUtils.getClass(users); // 返回 User.class
```
### 1.14 类型数据工具 (ClassTypeUtils)
**功能**:提供类型判断和转换,涵盖基本类型、包装类型、数组类型、集合类型、流类型等的识别,以及基本类型与包装类型之间的相互转换常用于反射、泛型处理、数据校验等场景
#### 代码示例
```java
// 1. 基本类型与包装类型判断
boolean isPrimitive = ClassTypeUtils.isPrimitive(int.class); // true
boolean isBasic = ClassTypeUtils.isBasicDataType(Integer.class); // true(包装类也算基本数据类型)
Class> basic = ClassTypeUtils.convertBasicType(Integer.class); // int.class
Class> wrapper = ClassTypeUtils.convertWrapperType(int.class); // Integer.class
// 2. 数组类型判断
int[] intArray = new int[10];
ClassTypeUtils.isArray(intArray); // true
ClassTypeUtils.isArrayInt(intArray); // true
ClassTypeUtils.isArrayByte(new byte[5]); // true
// 3. 常见类型判断
ClassTypeUtils.isString("hello"); // true
ClassTypeUtils.isCollection(new ArrayList<>()); // true
ClassTypeUtils.isMap(new HashMap<>()); // true
ClassTypeUtils.isFile(new File("test.txt")); // true
ClassTypeUtils.isInputStream(System.in); // true
// 4. 类型兼容性判断(isAssignableFrom 的封装)
boolean assignable = ClassTypeUtils.isAssignableFrom(Number.class, Integer.class); // true
```
[🔝 返回顶部](#pan-common-工具库)
## 2. 时间工具 (date)
### 2.1 日期工具 (DateUtils)
**功能**:提供时间工具操作的常用方法,如格式化、解析、计算等
#### 代码示例
- 所有示例基于假设的当前时间 **2025-03-13 14:30:45**(用于说明)实际使用时请以系统当前时间为准
**1. 获取当前时间属性(now前缀)**
```java
// 获取当前日期时间字符串
String now = DateUtils.now();
// 输出示例:2025-03-13 14:30:45
// 获取当前日期,格式 yyyy年MM月dd日
String nowFormatted = DateUtils.now("yyyy年MM月dd日");
// 输出示例:2025年03月13日
// 将当前日期设置为 09:05:10
String customTime = DateUtils.nowWithTime(9, 5, 10);
// 输出示例:2025-03-13 09:05:10
// 获取当前 Date 对象
Date date = DateUtils.nowDate();
// 获取秒级时间戳
long timestampSec = DateUtils.nowTimestamp();
// 输出示例:1741852845
// 获取毫秒级时间戳
long timestampMillis = DateUtils.nowTimestampMillis();
// 输出示例:1741852845000
// 获取当前小时
int hour = DateUtils.nowHour();
// 输出示例:14
// 获取当前分钟
int minute = DateUtils.nowMinute();
// 输出示例:30
// 获取当前秒
int second = DateUtils.nowSecond();
// 输出示例:45
// 获取当前天数
int day = DateUtils.nowDay();
// 输出示例:13
// 获取当前星期几
int dayOfWeek = DateUtils.nowDayOfWeek();
// 输出示例:4(假设2025-03-13是周四)
// 获取当前月份
int month = DateUtils.nowMonth();
// 输出示例:3
// 获取当前季度
int quarter = DateUtils.nowQuarter();
// 输出示例:1(1-3月为第一季度)
// 获取本季度起始月份
int firstMonth = DateUtils.nowFirstMonthOfQuarter();
// 输出示例:1(第一季度起始1月)
// 获取当前年份
int year = DateUtils.nowYear();
// 输出示例:2025
```
**2. 范围生成 (nowRangeOf前缀)**
```java
// 获取当前小时的起止时间
String hourRange = DateUtils.nowRangeOfHours();
// 输出示例:2025-03-13 14:00:00~2025-03-13 14:59:59
// 获取2小时后的整点小时范围
String futureHourRange = DateUtils.nowRangeOfHours(2);
// 输出示例:2025-03-13 16:00:00~2025-03-13 16:59:59
// 获取1小时前的小时范围,仅输出时间部分
String pastHourRange = DateUtils.nowRangeOfHours(-1, "HH:mm:ss");
// 输出示例:13:00:00~13:59:59
// 获取未来3天的日期列表(包含今天)
List futureDays = DateUtils.nowRangeOfDays(3);
// 输出示例:["2025-03-16","2025-03-15","2025-03-14","2025-03-13"]
// 获取过去2天的日期列表(自定义格式 MM/dd)
List pastDays = DateUtils.nowRangeOfDays(-2, "MM/dd");
// 输出示例:["03/11","03/12","03/13"]
// 获取从当前月到一月的月份列表
List months = DateUtils.nowRangeOfMonths();
// 输出示例:[3,2,1](假设当前为3月)
// 获取升序月份列表(一月到当前月)
List monthsAsc = DateUtils.nowRangeOfMonths(false);
// 输出示例:[1,2,3]
// 获取季度列表
List quarters = DateUtils.nowRangeOfQuarters();
// 输出示例:[3,2,1](假设当前为第3季度)
```
**3. 时间单位转换 (convert前缀)**
```java
// 将 90 分钟转换为小时
long hours = DateUtils.convertTime(90, TimeUnit.MINUTES, TimeUnit.HOURS);
// 输出示例:1
// 将 90 分钟精确转换为小时
BigDecimal exactHours = DateUtils.convertTimeExact(90, TimeUnit.MINUTES, TimeUnit.HOURS);
// 输出示例:1.50
// 2 小时转分钟
long minutes = DateUtils.convertHoursToMinutes(2);
// 输出示例:120
// 2 小时转秒
long seconds = DateUtils.convertHoursToSeconds(2);
// 输出示例:7200
// 2 小时转毫秒
long millis = DateUtils.convertHoursToMillis(2);
// 输出示例:7200000
// 5 分钟转秒
long secs = DateUtils.convertMinutesToSeconds(5);
// 输出示例:300
// 5 分钟转毫秒
long ms = DateUtils.convertMinutesToMillis(5);
// 输出示例:300000
// 90 分钟转小时
long hrs = DateUtils.convertMinutesToHours(90);
// 输出示例:1
// 30 秒转毫秒
long ms2 = DateUtils.convertSecondsToMillis(30);
// 输出示例:30000
// 130 秒转分钟
long mins = DateUtils.convertSecondsToMinutes(130);
// 输出示例:2
// 150000 毫秒转分钟
long min = DateUtils.convertMillisToMinutes(150000);
// 输出示例:2
// 2500 毫秒转秒
long sec = DateUtils.convertMillisToSeconds(2500);
// 输出示例:2
```
**4. 已过时间 (elapsed前缀)**
```java
// 获取从当天00:00:00到当前时间经过的分钟数
long minutesPassed = DateUtils.elapsedMinutesOfDay();
// 获取从当天00:00:00到当前时间经过的秒数
long secondsPassed = DateUtils.elapsedSecondsOfDay();
// 获取从当天00:00:00到当前时间经过的毫秒数
long millisPassed = DateUtils.elapsedMillisOfDay();
```
**5. 剩余时间 (remaining前缀)**
```java
// 计算从当前时间到目标时间的剩余毫秒数(可为负)
LocalDateTime target = LocalDateTime.of(2025, 3, 13, 18, 0);
long remaining = DateUtils.remainingMillis(target);
// 输出示例:12555000(正数表示未来)
// 计算从当前时间到目标时间的剩余毫秒数(可为负) 目标时间字符串
long remaining2 = DateUtils.remainingMillis("2025-03-13 18:00:00");
// 输出示例:12555000
// 获取从当前时间到当天最后一刻的剩余毫秒数
long remainingToday = DateUtils.remainingMillisOfDay();
// 输出示例:(24*3600 - 52245) * 1000 = 34155000
// 指定 UTC 时区
long remainingUTC = DateUtils.remainingMillisOfDay(ZoneId.of("UTC"));
// 获取从当前时间到本月最后一天最后一刻的剩余毫秒数
long remainingMonth = DateUtils.remainingMillisOfMonth();
// 输出示例:根据3月31日计算
```
**6. 比较方法 (compare前缀) 所有方法都会截断到秒比较**
```java
// 比较两个LocalDateTime对象(当前时间截断到秒)
LocalDateTime t1 = LocalDateTime.of(2025, 3, 13, 10, 0);
LocalDateTime t2 = LocalDateTime.of(2025, 3, 13, 12, 0);
int compare = DateUtils.compare(t1, t2);
// 输出示例:-1 (t1 < t2)
// 比较两个LocalDate对象
LocalDate d1 = LocalDate.of(2025, 3, 13);
LocalDate d2 = LocalDate.of(2025, 3, 14);
int compare = DateUtils.compare(d1, d2);
// 输出示例:-1
// 比较两个LocalTime对象(当前时间截断到秒)
LocalTime tm1 = LocalTime.of(10, 30);
LocalTime tm2 = LocalTime.of(14, 30);
int compare = DateUtils.compare(tm1, tm2);
// 输出示例:-1
// 比较当前时间与指定(当前时间截断到秒)
LocalDateTime future = LocalDateTime.of(2025, 3, 13, 18, 0);
int cmpNow = DateUtils.compareNow(future);
// 输出示例:-1(当前时间14:30 < 18:00)
LocalDate tomorrow = LocalDate.of(2025, 3, 14);
int cmpDate = DateUtils.compareNow(tomorrow);
// 输出示例:-1
LocalTime evening = LocalTime.of(18, 0);
int cmpTimeNow = DateUtils.compareNow(evening);
// 输出示例:-1
// 比较当前时间与日期时间字符串
int cmpStr = DateUtils.compareNowDateTime("2025-03-13 18:00:00");
// 输出示例:-1
// 比较当前日期与日期字符串
int cmpStrDate = DateUtils.compareNowDate("2025-03-14");
// 输出示例:-1
// 比较当前时间与时间字符串
int cmpStrTime = DateUtils.compareNowTime("18:00:00");
// 输出示例:-1
// 比较两个日期时间字符串
int cmpDT = DateUtils.compareDateTime("2025-03-13 10:00", "2025-03-13 12:00");
// 输出示例:-1
// 自定义解析格式比较两个日期时间字符串
int cmpDT2 = DateUtils.compareDateTime("2025/03/13 10:00", "2025/03/13 12:00", "yyyy/MM/dd HH:mm");
// 输出示例:-1
// 比较两个日期字符串(默认 yyyy-MM-dd)
int cmpD = DateUtils.compareDate("2025-03-13", "2025-03-14");
// 输出示例:-1
// 自定义解析格式比较两个日期字符串
int cmpD2 = DateUtils.compareDate("2025/03/13", "2025/03/14", "yyyy/MM/dd");
// 输出示例:-1
// 比较两个时间字符串(默认 HH:mm:ss)
int cmpT = DateUtils.compareTime("10:30:00", "14:30:00");
// 输出示例:-1
// 自定义解析格式比较两个时间字符串
int cmpT2 = DateUtils.compareTime("10:30", "14:30", "HH:mm");
// 输出示例:-1
```
**7. 格式化 (format前缀)**
```java
// 将常见格式的日期时间字符串转换为目标格式
String formatted = DateUtils.formatDateTime("2025-03-15 14:30:45", "yyyy年MM月dd日 HH:mm");
// 输出示例:2025年03月15日 14:30
// 自定义源格式和目标格式
String formatted2 = DateUtils.formatDateTime("2025/03/15 14:30", "yyyy/MM/dd HH:mm", "yyyy-MM-dd HH:mm:ss");
// 输出示例:2025-03-15 14:30:00
// 格式化日期字符串(默认源格式 yyyy-MM-dd)
String formattedDate = DateUtils.formatDate("2025-03-15", "yyyy年MM月dd日");
// 输出示例:2025年03月15日
// 自定义源格式和目标格式格式化日期
String formattedDate2 = DateUtils.formatDate("2025/03/15", "yyyy/MM/dd", "yyyy-MM-dd");
// 输出示例:2025-03-15
// 格式化时间字符串(默认源格式 HH:mm:ss)
String formattedTime = DateUtils.formatTime("14:30:45", "HH时mm分");
// 输出示例:14时30分
// 自定义源格式和目标格式格式化时间
String formattedTime2 = DateUtils.formatTime("14:30", "HH:mm", "HH:mm:ss");
// 输出示例:14:30:00
// 将Period格式化为中文描述
Period period = Period.of(1, 2, 3);
String desc = DateUtils.formatPeriod(period);
// 输出示例:1年2月3天
// 将ISO-8601周期字符串格式化为中文
String desc = DateUtils.formatPeriod("P1Y2M3D");
// 输出示例:1年2月3天
// 将 Duration 格式化为中文描述
Duration duration = Duration.ofHours(2).plusMinutes(30);
String desc = DateUtils.formatDuration(duration);
// 输出示例:2小时30分
// 人性化日期描述-将日期时间描述为“刚刚”、“X分钟前”等
LocalDateTime past = LocalDateTime.now().minusMinutes(5);
String human = DateUtils.formatHumanize(past);
// 输出示例:5分钟前
// 人性化日期描述
LocalDate yesterday = LocalDate.now().minusDays(1);
String humanDate = DateUtils.formatHumanize(yesterday);
// 输出示例:昨天
// 人性化日期描述字符串版本,自动识别
String humanStr = DateUtils.formatHumanize("2025-03-12 10:00:00");
// 输出示例:昨天(假设今天2025-03-13)
```
**8. 缺失日期查找**
```java
// 找出指定月份内缺失的日期
List existing = Arrays.asList("2025-03-01", "2025-03-03");
List missing = DateUtils.findMissingDateOfMonth(existing, "yyyy-MM-dd");
// missing 包含 2025-03-02, 2025-03-04...2025-03-31
```
**9. 区间判断 (isInRange前缀)**
```java
// 判断目标时间是否在指定时间区间内(包含边界,支持跨午夜)
LocalTime target = LocalTime.of(23, 30);
LocalTime start = LocalTime.of(22, 0);
LocalTime end = LocalTime.of(2, 0);
boolean in = DateUtils.isInRange(target, start, end);
// 输出示例:true(跨午夜区间)
// 判断目标日期是否在指定日期区间内
LocalDate target = LocalDate.of(2025, 6, 1);
LocalDate start = LocalDate.of(2025, 1, 1);
LocalDate end = LocalDate.of(2025, 12, 31);
boolean inDate = DateUtils.isInRange(target, start, end);
// 输出示例:true
// 判断目标日期时间是否在指定区间内
LocalDateTime target = LocalDateTime.of(2025, 1, 1, 14, 30);
LocalDateTime start = LocalDateTime.of(2025, 1, 1, 10, 0);
LocalDateTime end = LocalDateTime.of(2025, 1, 1, 18, 0);
boolean inDateTime = DateUtils.isInRange(target, start, end);
// 输出示例:true
// 日期时间区间判断
boolean inStr = DateUtils.isInRange("2025-01-01 14:30:00", "2025-01-01 10:00:00", "2025-01-01 18:00:00");
// 输出示例:true
// 日期字符串是否在区间内
boolean inDateStr = DateUtils.isDateInRange("2025-06-01", "2025-01-01", "2025-12-31");
// 输出示例:true
// 判断时间字符串是否在区间内(支持跨午夜)
boolean inTimeStr = DateUtils.isTimeInRange("01:30:00", "22:00:00", "02:00:00");
// 输出示例:true
// 判断指定日期时间是否在当前时间到指定结束时间之间
boolean nowIn = DateUtils.isNowInRange("2025-03-13 16:00:00", "2025-03-13 18:00:00");
// 输出示例:true(假设当前14:30,目标16:00在14:30-18:00之间)
// 判断指定日期是否在当前日期到指定结束日期之间
boolean nowDateIn = DateUtils.isNowDateInRange("2025-03-15", "2025-03-20");
// 输出示例:true(当前日期2025-03-13在区间内)
// 判断当前小时是否在指定小时区间内
boolean inHour = DateUtils.isNowHourInRange(9, 18);
// 输出示例:true(当前14点在9-18之间)
```
**10. 日期关系判断**
```java
// 判断两个日期字符串是否同一天
boolean sameDay = DateUtils.isSameDay("2025-03-13 10:00", "2025-03-13 22:30");
// 输出示例:true
// 判断两个日期是否在同一月
boolean sameMonth = DateUtils.isSameMonth("2025-03-01", "2025-03-31");
// 输出示例:true
// 判断两个日期是否在同一年
boolean sameYear = DateUtils.isSameYear("2025-01-01", "2025-12-31");
// 输出示例:true
// 判断给定日期是否为今天
boolean today = DateUtils.isToday("2025-03-13");
// 输出示例:true
// 判断是否为昨天
boolean yesterday = DateUtils.isYesterday("2025-03-12");
// 输出示例:true
// 判断是否为明天
boolean tomorrow = DateUtils.isTomorrow("2025-03-14");
// 输出示例:true
// 判断是否在当前周内(周一至周日)
boolean thisWeek = DateUtils.isThisWeek("2025-03-10");
// 输出示例:true(假设本周一为2025-03-10)
// 判断是否在当前月内
boolean thisMonth = DateUtils.isThisMonth("2025-03-01");
// 输出示例:true
// 判断是否在当前年内
boolean thisYear = DateUtils.isThisYear("2025-12-31");
// 输出示例:true
// 判断今天是否为生日
boolean birthday = DateUtils.isBirthday("1990-03-13");
// 输出示例:true
// 判断是否过去时间
LocalDateTime past = LocalDateTime.of(2025, 3, 13, 10, 0);
boolean pastFlag = DateUtils.isPast(past);
// 输出示例:true(10:00 < 14:30)
// 判断是否过去日期
LocalDate pastDate = LocalDate.of(2025, 3, 12);
boolean pastDateFlag = DateUtils.isPast(pastDate);
// 输出示例:true
// 判断日期时间字符串是否为过去
boolean pastStr = DateUtils.isPast("2025-03-13 10:00:00");
// 输出示例:true
// 判断日期字符串是否为过去
boolean datePast = DateUtils.isDatePast("2025-03-12");
// 输出示例:true
// 判断是否未来时间
LocalDateTime future = LocalDateTime.of(2025, 3, 13, 18, 0);
boolean futureFlag = DateUtils.isFuture(future);
// 输出示例:true
// 判断是否未来日期
LocalDate futureDate = LocalDate.of(2025, 3, 14);
boolean futureDateFlag = DateUtils.isFuture(futureDate);
// 输出示例:true
// 判断日期时间字符串是否为未来
boolean futureStr = DateUtils.isFuture("2025-03-13 18:00:00");
// 输出示例:true
// 判断日期字符串是否为未来
boolean dateFuture = DateUtils.isDateFuture("2025-03-14");
// 输出示例:true
```
**11. 过期判断(isExpired前缀)**
```java
// 判断时间戳是否已超过指定时长
long timestamp = System.currentTimeMillis() - 5 * 60 * 1000; // 5分钟前
boolean expired = DateUtils.isExpired(timestamp, 10, TimeUnit.MINUTES);
// 输出示例:false(5分钟 < 10分钟)
// 分钟数与当前时间对比已超过指定时长
boolean expiredMin = DateUtils.isExpiredMinutes(timestamp, 10);
// 小时数与当前时间对比已超过指定时长
boolean expiredHour = DateUtils.isExpiredHours(timestamp, 1);
// 天数与当前时间对比已超过指定时长
boolean expiredDay = DateUtils.isExpiredDays(timestamp, 1);
```
**13. 基础判断**
```java
// 判断当前年是否为闰年
boolean leap = DateUtils.isLeapYear();
// 判断指定日期所在年是否为闰年
boolean leapDate = DateUtils.isLeapYear("2024-03-01");
// 输出示例:true
// 判断指定年份是否为闰年
boolean leapYear = DateUtils.isLeapYear(2024);
// 输出示例:true
// 判断今天是否为工作日(周一至周五)
boolean workDay = DateUtils.isWorkDay();
```
**14. 偏移方法(offset前缀)**
```java
// 对LocalDateTime/LocalDate/LocalTime对象进行偏移
LocalDateTime base = LocalDateTime.of(2025, 3, 13, 14, 30);
LocalDate baseDate = LocalDate.of(2025, 3, 13);
LocalTime baseTime = LocalTime.of(13, 14, 30);
LocalDateTime result = DateUtils.offset(base/baseDate/baseTime, 2, ChronoUnit.HOURS);
// 输出示例:2025-03-13T16:30
// 偏移LocalDateTime/LocalDate/LocalTime对并自定义格式化返回字符串
String resultStr = DateUtils.offset(base/baseDate/baseTime, -1, ChronoUnit.DAYS, "yyyy-MM-dd");
// 输出示例:2025-03-12
// 基于当前时间偏移,返回默认格式字符串
String after2Hours = DateUtils.offsetNow(2, ChronoUnit.HOURS);
// 输出示例:2025-03-13 16:30:45
// 基于当前时间偏移,自定义格式
String after1Day = DateUtils.offsetNow(1, ChronoUnit.DAYS, "yyyy-MM-dd");
// 输出示例:2025-03-14
// 基于当前时间偏移指定秒/分钟/小时/天/周/月/年
String after10Sec = DateUtils.offsetNowSeconds(10);
//String after10Sec = DateUtils.offsetNowMinutes(10);
// offsetNowXXX
// 输出示例:2025-03-13 14:30:55
// 基于当前时间偏移指定秒/分钟/小时/天/周/月/年,自定义格式
String after10SecFmt = DateUtils.offsetNowSeconds(10, "HH:mm:ss");
// 输出示例:14:30:55
// 对日期时间字符串偏移,返回默认格式
String resultDT = DateUtils.offsetDateTime("2025-03-13 14:30", 2, ChronoUnit.HOURS);
// 输出示例:2025-03-13 16:30:00
// 对日期时间字符串偏移,自定义格式
String resultDTFmt = DateUtils.offsetDateTime("2025-03-13 14:30", 2, ChronoUnit.HOURS, "HH:mm:ss");
// 输出示例:16:30:00
// 对日期时间字符串偏移,自定义格式,指定偏移单位
String resultDTFmt = DateUtils.offsetDateTime("2025-03-13 14:30", 2, ChronoUnit.HOURS, "HH:mm:ss");
// 输出示例:16:30:00
// 对日期时间字符串偏移,分钟/小时/天数/周/月偏移
String minResult = DateUtils.offsetDateTimeMinutes("2025-03-13 14:30", 5);
// 输出示例:2025-03-13 14:35:00
// 对日期时间字符串偏移,分钟/小时/天数/周/月偏移,自定义格式
String minResultFmt = DateUtils.offsetDateTimeMinutes("2025-03-13 14:30", 5, "HH:mm");
// 输出示例:14:35
// 对日期字符串偏移,返回默认日期格式,指定单位
String dateResult = DateUtils.offsetDate("2025-03-13", 2, ChronoUnit.DAYS);
// 输出示例:2025-03-15
// 对日期字符串偏移,自定义格式,指定单位
String dateResultFmt = DateUtils.offsetDate("2025-03-13", 2, ChronoUnit.DAYS, "MM/dd");
// 输出示例:03/15
// 日期天数/周/月份偏移
String daysResult = DateUtils.offsetDateDays("2025-03-13", 2);
// 输出示例:2025-03-15
// 日期天数/周/月份偏移,自定义格式
String daysResultFmt = DateUtils.offsetDateDays("2025-03-13", 2, "MM/dd");
// 输出示例:03/15
// 对时间字符串偏移,返回默认时间格式,自定义格式
String timeResult = DateUtils.offsetTime("14:30:00", 2, ChronoUnit.HOURS);
// 输出示例:16:30:00
// 对时间字符串偏移,指定单位,自定义格式
String timeResultFmt = DateUtils.offsetTime("14:30:00", 2, ChronoUnit.HOURS, "HH:mm");
// 输出示例:16:30
// 偏移月后取该月第一天(开始时间)
String firstDay = DateUtils.offsetNowMonthsOfFirstDay(1);
// 输出示例:2025-04-01 00:00:00
// 偏移月后取该月最后一天(结束时间)
String lastDay = DateUtils.offsetNowMonthsOfLastDay(1);
// 输出示例:2025-04-30 23:59:59
```
**15. 差值计算 (diff前缀)**
```java
// 计算两个 LocalDateTime/LocalDate/LocalTime之间的差值,指定单位
LocalDateTime start = LocalDateTime.of(2025, 3, 13, 10, 0);
LocalDateTime end = LocalDateTime.of(2025, 3, 13, 12, 30);
long diffHours = DateUtils.diff(start, end, ChronoUnit.HOURS);
// 输出示例:2
// 字符串版本,自动识别字符串时间类型(LocalDateTime/LocalDate/LocalTime),指定对比单位
long diffMinutes = DateUtils.diff("2025-03-13 14:30:00", "2025-03-13 15:00:00", ChronoUnit.MINUTES);
// 输出示例:30
// 毫秒差/秒差/分钟差/小时差/天数差/周数差/年份差
long millis = DateUtils.diffMillis("2025-03-13 14:30:00", "2025-03-13 14:30:05");
// 输出示例:5000
long secs = DateUtils.diffSeconds("2025-03-13 14:30:00", "2025-03-13 14:30:30");
// 输出示例:30
long mins = DateUtils.diffMinutes("2025-03-13 14:30:00", "2025-03-13 15:00:00");
// 输出示例:30
int hrs = DateUtils.diffHours("2025-03-13 10:00:00", "2025-03-13 14:30:00");
// 输出示例:4
int days = DateUtils.diffDays("2025-03-10", "2025-03-13");
// 输出示例:3
int weeks = DateUtils.diffWeeks("2025-03-01", "2025-03-15");
// 输出示例:2
int months = DateUtils.diffMonths("2025-01-01", "2025-03-01");
// 输出示例:2
int years = DateUtils.diffYears("2000-01-01", "2025-01-01");
// 输出示例:25
// 两个日期之间的差值返回Period对象
Period period = DateUtils.diffPeriod(LocalDate.of(2020, 1, 15), LocalDate.of(2025, 3, 13));
// 输出示例:period: P5Y1M28D (5年1个月28天)
// 两个日期之间的差值返回Period字符串
Period period2 = DateUtils.diffPeriod("2020-01-15", "2025-03-13");
// 输出示例:period: P5Y1M28D (5年1个月28天)
// 工作日差值
// 计算两个日期之间的工作日天数(排除周末)
int workDays = DateUtils.diffWorkDays(LocalDate.of(2025, 3, 10), LocalDate.of(2025, 3, 14));
// 输出示例:5(周一至周五)
int workDays2 = DateUtils.diffWorkDays("2025-03-10", "2025-03-14");
// 输出示例:5
```
**16. 开始时间 (startOf前缀)**
```java
// 获取当前日期的开始时间
String startToday = DateUtils.startOfDay();
// 输出示例:2025-03-13 00:00:00
// 获取指定日期的开始时间,可自定义格式
String start = DateUtils.startOfDay("2025-03-13 10:30");
// 输出示例:2025-03-13 00:00:00
String start = DateUtils.startOfDay("2025-03-13 10:30", "yyyy-MM-dd HH:mm:ss");
// 输出示例:2025-03-13 00:00:00
LocalDate ld = LocalDate.of(2025, 3, 13);
String startLd = DateUtils.startOfDay(ld, "yyyy-MM-dd HH:mm:ss");
// 将当前日期可指定时分秒,可自定自定义格式
String customStart = DateUtils.startOfDayWithTime(9, 30, 0);
// 输出示例:2025-03-13 09:30:00
String customStartFmt = DateUtils.startOfDayWithTime(9, 30, 0, "HH:mm:ss");
// 输出示例:09:30:00
// 获取当前日期所在周的周一(开始时间)
String startWeek = DateUtils.startOfWeek();
// 假设今天是2025-03-13(周四),则周一为2025-03-10 00:00:00
// 获取指定日期所在周的周一的开始时间,可自定义格式
String startWeek2 = DateUtils.startOfWeek("2025-03-13");
// 输出示例:2025-03-10 00:00:00
// 获取指定日期所在周的周一的开始时间,指定时分秒,可自定义格式
String mondayTime = DateUtils.startOfWeekWithTime(9, 0, 0);
// 输出示例:2025-03-10 09:00:00
// 当前月份第一天开始时间
String startMonth = DateUtils.startOfMonth();
// 输出示例:2025-03-01 00:00:00
// 指定月份第一天,可自定义格式
String startMonth2 = DateUtils.startOfMonth("2025-03-13");
// 输出示例:2025-03-01 00:00:00
// 获取本月第一天,指定时分秒,可自定义格式
String monthStartTime = DateUtils.startOfMonthWithTime(10, 30, 0);
// 输出示例:2025-03-01 10:30:00
// 当前季度第一天开始时间
String startQuarter = DateUtils.startOfQuarter();
// 输出示例:假设当前3月,则输出2025-01-01 00:00:00
// 指定日期所在季度第一天,可自定义格式
String startQuarter2 = DateUtils.startOfQuarter("2025-03-13");
// 输出示例:2025-01-01 00:00:00
// 指定日期所在季度第一天,指定时分秒,可自定义格式
String quarterStartTime = DateUtils.startOfQuarterWithTime(9, 0, 0);
// 输出示例:2025-01-01 09:00:00
// 指定年份第一天,可自定义格式
String startYear = DateUtils.startOfYear(2025);
// 输出示例:2025-01-01 00:00:00
// 获取日期年份,返回该年第一天,可自定义格式
String startYear2 = DateUtils.startOfYear("2025-03-13");
// 输出示例:2025-01-01 00:00:00
```
**17. 结束时间 (endOf前缀)**
```java
// 当前日期结束时间
String endToday = DateUtils.endOfDay();
// 输出示例:2025-03-13 23:59:59
// 指定日期结束时间,可自定义格式
String end = DateUtils.endOfDay("2025-03-13");
// 输出示例:2025-03-13 23:59:59
String endFmt = DateUtils.endOfDay("2025-03-13", "yyyy-MM-dd HH:mm:ss");
// 当前日期的指定时分秒,可自定义格式
String customEnd = DateUtils.endOfDayWithTime(23, 30, 0);
// 输出示例:2025-03-13 23:30:00
String customEndFmt = DateUtils.endOfDayWithTime(23, 30, 0, "HH:mm");
// 输出示例:23:30
// 当前日期所在周的周日(结束时间)
String endWeek = DateUtils.endOfWeek();
// 假设2025-03-13是周四,则周日为2025-03-16 23:59:59
// 指定日期所在周的周日
String endWeek2 = DateUtils.endOfWeek("2025-03-13");
// 输出示例:2025-03-16 23:59:59
String endWeekFmt = DateUtils.endOfWeek("2025-03-13", "yyyy-MM-dd");
// 获取本周日,指定时分秒,可自定义格式
String sundayTime = DateUtils.endOfWeekWithTime(23, 30, 0);
String sundayTimeFmt = DateUtils.endOfWeekWithTime(23, 30, 0, "HH:mm");
// 输出示例:23:30
// 本月最后一天结束时间
String endMonth = DateUtils.endOfMonth();
// 输出示例:2025-03-31 23:59:59
// 指定日期所在月最后一天
String endMonth2 = DateUtils.endOfMonth("2025-03-13");
// 输出示例:2025-03-31 23:59:59
String endMonthFmt = DateUtils.endOfMonth("2025-03-13", "yyyy-MM-dd");
// 输出示例:2025-03-31
// 本月最后一天的指定时分秒
String monthEndTime = DateUtils.endOfMonthWithTime(23, 30, 0);
// 输出示例:2025-03-31 23:30:00
String monthEndTimeFmt = DateUtils.endOfMonthWithTime(23, 30, 0, "HH:mm");
// 当前季度最后一天结束时间
String endQuarter = DateUtils.endOfQuarter();
// 假设当前3月,则输出2025-03-31 23:59:59
// 指定日期所在季度最后一天
String endQuarter2 = DateUtils.endOfQuarter("2025-03-13");
// 输出示例:2025-03-31 23:59:59
String endQuarterFmt = DateUtils.endOfQuarter("2025-03-13", "yyyy-MM-dd");
// 输出示例:2025-03-31
// 本季度最后一天的指定时分秒
String quarterEndTime = DateUtils.endOfQuarterWithTime(23, 30, 0);
// 输出示例:2025-03-31 23:30:00
String quarterEndTimeFmt = DateUtils.endOfQuarterWithTime(23, 30, 0, "HH:mm");
// 输出示例:23:30
// 指定年份最后一天结束时间
String endYear = DateUtils.endOfYear(2025);
// 输出示例:2025-12-31 23:59:59
String endYearFmt = DateUtils.endOfYear(2025, "yyyy-MM-dd");
// 输出示例:2025-12-31
// 从日期中提取年份,返回该年最后一天
String endYear2 = DateUtils.endOfYear("2025-03-13");
// 输出示例:2025-12-31 23:59:59
String endYear2Fmt = DateUtils.endOfYear("2025-03-13", "yyyy-MM-dd");
```
**18. 上一个日期相关 (next前缀)**
```java
// 获取指定日期之后的下一个指定星期几
String nextMonday = DateUtils.nextDayOfWeek("2025-03-13", DayOfWeek.MONDAY);
// 输出示例:2025-03-17
// 获取指定日期之后的下一个工作日(周一至周五)返回(yyyy-MM-dd)格式
String nextWork = DateUtils.nextWorkDay("2025-03-14"); // 周五
// 输出示例:2025-03-17(周一)
// 计算距离下次生日还有多少天
int daysToBirthday = DateUtils.nextBirthdayOfDay("1990-03-15");
// 输出示例:假设今天2025-03-13,输出2
```
**19. 下一个日期相关 (previous前缀)**
```java
// 获取指定日期之前的上一个指定星期几
String prevFriday = DateUtils.previousDayOfWeek("2025-03-13", DayOfWeek.FRIDAY);
// 输出示例:2025-03-07
// 获取上个月的第一天(默认格式),也可自定义格式
String prevFirst = DateUtils.previousMonthFirstDay();
// 输出示例:假设当前3月,输出2025-02-01 00:00:00
String prevFirstFmt = DateUtils.previousMonthFirstDay("yyyy-MM-dd");
// 输出示例:2025-02-01
// 获取上个月的最后一天,可自定义格式
String prevLast = DateUtils.previousMonthLastDay();
// 输出示例:2025-02-28 23:59:59
String prevLastFmt = DateUtils.previousMonthLastDay("yyyy-MM-dd");
// 输出示例:2025-02-28
```
**20. 时间截断 (truncateTo前缀)**
```java
// 将日期时间字符串截断到指定单位,可自定义格式
String s = truncateDateTimeTo("2026-03-15 17:00:00", ChronoUnit.WEEKS);
// 输出示例:2026-03-09 00:00:00
// 将日期时间字符串截断到指定单位,可自定义格式
String s2 = truncateDateTo("2026-03-15", ChronoUnit.WEEKS);
// 输出示例:2026-03-09
// 将日期时间字符串截断到指定单位,可自定义格式
String s1 = truncateTimeTo("10:00:00", ChronoUnit.DAYS);
// 输出示例:00:00:00
```
**21. 其他独立方法**
```java
// 计算年龄(周岁)
int age = DateUtils.age("2000-01-01");
// 假设今天2025-03-13,输出25
// 获取星期几的数字(1-7)
int dow = DateUtils.dayOfWeek("2025-03-13");
// 输出示例:4(假设是周四)
// 获取日期在一年中的周数(ISO标准)
int week = DateUtils.weekOfYear("2025-03-13");
// 输出示例:11
```
### 2.2 日期解析工具 (DateParserUtils)
**功能**:提供更灵活、高性能的日期时间解析功能,支持常见格式自动识别、特征路由优化解析性能,并兼容中文日期时间格式
**自动识别:** 内置 40+ 种常见日期时间格式,包括 ISO、横线、斜杠、点、下划线、中文、无分隔符紧凑格式等
#### 代码示例
```java
// 1.解析为 LocalDateTime(自动识别格式)
// 横线分隔 + 时间
LocalDateTime dt1 = DateParserUtils.toLocalDateTime("2025-03-13 14:30:45");
// 输出:2025-03-13T14:30:45
// ISO 格式
LocalDateTime dt2 = DateParserUtils.toLocalDateTime("2025-03-13T14:30:45");
// 输出:2025-03-13T14:30:45
// 中文格式
LocalDateTime dt3 = DateParserUtils.toLocalDateTime("2025年3月13日 14时30分45秒");
// 输出:2025-03-13T14:30:45
// 无分隔符紧凑格式(14位)
LocalDateTime dt4 = DateParserUtils.toLocalDateTime("20250313143045");
// 输出:2025-03-13T14:30:45
// 2.解析为 LocalDate(自动识别日期部分)
LocalDate d1 = DateParserUtils.toLocalDate("2025-03-13");
LocalDate d2 = DateParserUtils.toLocalDate("2025/03/13 14:30"); // 自动提取日期
LocalDate d3 = DateParserUtils.toLocalDate("2025年3月13日");
// 输出:2025-03-13
// 3.解析为 LocalTime(自动识别时间部分)
LocalTime t1 = DateParserUtils.toLocalTime("14:30:45");
LocalTime t2 = DateParserUtils.toLocalTime("2025-03-13 14:30"); // 自动提取时间
LocalTime t3 = DateParserUtils.toLocalTime("14时30分");
// 输出:14:30(秒缺省为0)
// 4.转换为 Date(自动识别并组合)
// 纯日期 → 当天 00:00:00
Date date1 = DateParserUtils.parseDate("2025-03-13");
// 纯时间 → 使用当前日期组合
Date date2 = DateParserUtils.parseDate("14:30:45");
// 日期时间 → 完整时间
Date date3 = DateParserUtils.parseDate("2025-03-13 14:30:45");
// 5.时间戳与 LocalDateTime/LocalDate 互转
long timestamp = System.currentTimeMillis();
LocalDateTime ldt = DateParserUtils.toLocalDateTime(timestamp);
LocalDate ld = DateParserUtils.toLocalDate(timestamp);
long seconds = DateParserUtils.toTimestamp(ldt);
long millis = DateParserUtils.toTimestampMillis(ld);
// 宽松解析
LocalDateTime nowTime = DateParserUtils.parseLocalDateTime("2025-03-13");
LocalDateTime nowTime = DateParserUtils.parseLocalTime("2025-03-13 00:00:00");
LocalDateTime nowTime = DateParserUtils.parseLocalDate("2025-03-13 00:00:00");
```
[🔝 返回顶部](#pan-common-工具库)
## 3. 加密解密工具 (algorithm)
### 3.1 摘要算法 (DigestUtils)
**功能**:提供一套完整、易用且高度安全的摘要算法工具类,涵盖 MD5、SHA-1、SHA-2 全系列(224/256/384/512)、SHA-3 全系列(224/256/384/512)、HMac(MD5/SHA1/SHA224/SHA256/SHA384/SHA512)以及国密 SM3 算法。
可以使用一致的API完成所有摘要类型对字符串、字节数组、文件、输入流的摘要计算,并直接获取原始字节、Hex 或 Base64(支持标准/URL安全/MIME 等多种变体)输出。
支持大数据流式处理能力,同时提供加盐摘要和摘要验证功能,满足应用对数据完整性校验需求
**核心特性:**
- 算法丰富
- 经典算法:MD5、SHA-1、SHA-224/256/384/512
- 现代算法:SHA3-224/256/384/512
- 国密算法:SM3(完全遵循国家密码管理局标准)
- 消息认证码:HMac-MD5、HMac-SHA1、HMac-SHA224/256/384/512
- 流式大文件处理
- 提供 update/digest 分块更新接口,可对超大文件或网络流进行摘要计算,内存占用恒定(默认 8KB 缓冲区)
- 内置 digest(File) 和 digest(InputStream) 便捷方法,自动完成流式处理
- HMac 支持:
- 支持带密钥的 HMac 摘要,可自动生成安全随机密钥或传入自定义密钥
- 提供 getKey()、getKeyBytes()、getMacLength() 等方法方便密钥管理
- 摘要验证:
- 内置 verify 一系列方法,用于比对原文与摘要是否匹配
- 支持文件与文件、文件与摘要字符串、字节数组与摘要字符串等多种比对形式 自动忽略 Hex/Base64 字符串的大小写
- 统一工厂类`DigestUtils`
**注意事项:**
- 密钥长度(HMac):
- HMac 算法对密钥长度没有硬性限制,但为了达到最佳安全性,建议密钥长度不小于摘要输出长度。过短或过长的密钥会被底层自动处理,但推荐使用 getMacLength() 获取推荐密钥长度并生成相应长度的随机密钥。
- 自动生成的密钥可通过 getKeyBytes() 获取,便于存储。
- 依赖:
- SHA-3 和 SM3 算法需要引入 Bouncy Castle 依赖(如 bcprov-jdk18on),工具类会自动检测并在无依赖时抛出异常。
- MD5、SHA-1、SHA-2 及 HMac 算法基于 JDK 原生实现,无需额外依赖。
- 加盐摘要:
- 提供的 digestSalt 方法采用盐值前置(salt + content)的简单拼接方式,适用于大多数场景。若需盐值置于其他位置,可自行组合后调用 digest。
- 文件摘要:
- digest(File) 方法会自动关闭文件流,无需调用者管理。
- digest(InputStream) 方法不会关闭输入流,调用者需自行管理流的关闭(详见方法注释)。
#### 代码示例
```java
// ========== 1. 获取摘要实例 ==========
// 通过 DigestUtils 工厂类获取
MD5 md5 = DigestUtils.mD5(); // MD5
SHA sha256 = DigestUtils.shaWith256(); // SHA-256
SHA3 sha3 = DigestUtils.sha3With256(); // SHA3-256
SM3 sm3 = DigestUtils.sm3(); // 国密 SM3
// HMac 摘要(默认生成随机密钥)
HMac hmacMd5 = DigestUtils.hmacWithMd5(); // HMac-MD5
HMac hmacSha256 = DigestUtils.hmacWithSha256(); // HMac-SHA256
// 自定义密钥的 HMac
byte[] key = "mySecretKey".getBytes(StandardCharsets.UTF_8);
HMac hmac = DigestUtils.hmac(key, DigestType.HMAC_SHA256);
// ========== 2.基础摘要计算 ==========
String data = "Hello, 世界!";
// 获取摘要字节数组
byte[] digestBytes = md5.digest(data);
// 获取 Hex 字符串(默认小写)
String hex = sha256.digestToHex(data);
String hexUpper = sha256.digestToHex(data, false); // 大写
// 获取 Base64 字符串(默认标准 RFC4648)
String base64 = sm3.digestToBase64Str(data);
String base64Url = sm3.digestToBase64Str(data, Base64Type.RFC4648_URLSAFE); // URL 安全
// ========== 3.流式更新(大文件/网络流) ==========
// 对大文件进行 SHA-256 摘要计算(内存友好)
SHA sha256 = DigestUtils.shaWith256();
try (InputStream in = new FileInputStream("large.dat")) {
byte[] buffer = new byte[8192];
int len;
while ((len = in.read(buffer)) != -1) {
sha256.update(buffer, 0, len); // 分块更新
}
byte[] digest = sha256.digest(); // 完成计算,实例自动重置
String hex = sha256.digestToHex(); // 或直接获取 Hex 字符串
}
// 使用便捷方法直接对文件计算摘要
byte[] fileDigest = sha256.digest(new File("large.dat"));
String fileHex = sha256.digestToHex(); // 注意:digest(File) 会重置实例,之后可直接调用 digestToHex()
// ========== 4.加盐摘要 ==========
byte[] salt = "randomSalt".getBytes(StandardCharsets.UTF_8);
String password = "userPassword";
// 加盐摘要(盐值前置)
byte[] saltedHash = md5.digestSalt(password, salt);
String saltedHex = md5.digestSaltToHex(password, salt);
String saltedBase64 = md5.digestSaltToBase64Str(password, salt);
// ========== 5.摘要文件摘要与验证验证 ==========
File file1 = new File("data.txt");
File file2 = new File("data_copy.txt");
// 计算文件摘要
String file1Hex = md5.digestToHex(file1); // 自动流式处理
// 验证两个文件摘要是否一致
boolean filesMatch = md5.verify(file1, file2);
// 验证文件与已知摘要字符串
String knownDigest = "e10adc3949ba59abbe56e057f20f883e"; // 示例 MD5
boolean isValid = md5.verifyHex(file1, knownDigest); // 忽略大小写
// ========== 6.HMAC示例 ==========
// 使用默认生成的密钥
HMac hmac = DigestUtils.hmacWithSha256();
byte[] key = hmac.getKeyBytes(); // 获取密钥,需保存用于解密验证
byte[] mac = hmac.digest("message".getBytes());
String macHex = hmac.digestToHex("message");
// 使用自定义密钥重新创建 HMac 实例进行验证
HMac hmac2 = DigestUtils.hmac(key, DigestType.HMAC_SHA256);
boolean valid = hmac2.verify("message".getBytes(), mac); // true
// ========== 7.摘要验证(多种形式) ==========
String plain = "Hello";
String hexDigest = md5.digestToHex(plain);
String base64Digest = md5.digestToBase64Str(plain);
// 验证原文与 Hex 摘要
boolean valid1 = md5.verifyHex(plain, hexDigest);
boolean valid2 = md5.verifyHex(plain, hexDigest.toUpperCase()); // 忽略大小写
// 验证原文与 Base64 摘要
boolean valid3 = md5.verifyBase64(plain, base64Digest);
// 验证字节数组与摘要字节数组
byte[] digestBytes = md5.digest(plain);
boolean valid4 = md5.verify(plain.getBytes(), digestBytes);
```
**常见问题**
- Q: 摘要算法的输出长度是多少?
- A: 不同算法输出长度不同:
- MD5:16 字节(128 位)
- SHA-1:20 字节(160 位)
- SHA-224:28 字节
- SHA-256:32 字节
- SHA-384:48 字节
- SHA-512:64 字节
- SHA3-224:28 字节
- SHA3-256:32 字节
- SHA3-384:48 字节
- SHA3-512:64 字节
- SM3:32 字节
- HMac:与对应摘要算法相同
- 可通过 getDigestLength() 获取。
- Q: 为什么 HMac 需要密钥?
- A: HMac 是一种带密钥的消息认证码,用于验证消息的完整性和真实性。只有拥有相同密钥的双方才能计算出相同的 MAC 值,防止消息被篡改或伪造。
- Q: 加盐摘要的盐值需要存储吗?
- A: 是的,盐值必须与摘要结果一起存储(通常以明文形式),因为在验证时需要相同的盐值重新计算摘要。盐值不需要保密,但必须是随机且唯一的。
- Q: 如何选择摘要算法?
- A: 对于一般完整性校验,推荐使用 SHA-256 或 SHA-3-256。对于密码存储,应使用加盐摘要(如 digestSalt)或专业的密码哈希算法(如 bcrypt、PBKDF2)。MD5 和 SHA-1 已不再安全,仅用于与遗留系统兼容。
### 3.2 对称加密
**功能**:一套完整的对称加密工具类,覆盖 AES、DES、DESede、SM4(国密)以及 PBE(基于口令的加密)等主流算法。
所有实现均支持多种工作模式(ECB、CBC、CFB、OFB、CTR 等)和填充方式(PKCS5Padding、PKCS7Padding、NoPadding等)
。内置密钥生成工具,支持从字节数组或字符串构建加密器,并提供便捷的加解密方法,支持流式处理能力,可高效地加密/解密大文件(GB 级别)。
**核心特性:**
- 算法丰富:支持 AES(128/192/256位)、DES(64位)、DESede(192位)、SM4(128位国密)以及多种 PBE 算法(基于 SHA/MD5 的系列变体)
- 模式/填充可配置:支持 ECB、CBC、CFB、OFB、CTR 等分组模式,以及 PKCS5Padding、PKCS7Padding、NoPadding、ZeroPadding 等填充方式,满足各类兼容性需求
- 密钥/IV 管理:可自定义密钥和初始向量,内置 KeyUtils 生成指定长度的安全随机密钥;加密器实例可获取密钥、IV 的原始字节、Hex 和 Base64 表示,便于存储和传输
- 多种输入输出:加密/解密支持字节数组、字符串、Hex 字符串、Base64 字符串,且 Base64 支持标准、URL 安全、MIME 等多种变体
- 流式大文件处理:内置对流式加密/解密支持,可安全处理超大文件(如日志、数据库备份)
- 国密支持:基于 Bouncy Castle 提供 SM4 算法实现,符合国家密码管理局标准
- PBE 支持:内置 PBKDF2 实现,支持自定义盐值和迭代次数,将用户口令安全地转换为加密密钥
**注意事项:**
-
- 密钥长度
- AES:16字节(128位)、24字节(192位)、32字节(256位)
- DES:8字节(64位)
- DESede:24字节(192位)
- SM4:16字节(128位)
- PBE:口令长度任意,内部通过 PBKDF2 生成符合要求的密钥
- 初始向量(IV)
- ECB 模式不需要 IV,传入 null 即可
- CBC/CFB/OFB/CTR 模式需要 IV,长度必须与算法块大小一致(AES/SM4 为 16字节,DES/DESede 为 8字节)
- 若未指定 IV,构造器会自动生成随机 IV(可通过 getIv() 获取),确保每次加密的随机性
- 推荐使用 `KeyUtils.generate128()` 生成
- 填充选择
- 对于需要填充的模式(如 ECB、CBC),推荐使用 PKCS5Padding 或 PKCS7Padding(二者在 Java 中通常等价)
- NoPadding 要求输入数据长度恰好为块大小的整数倍,否则抛出异常,适用于流模式或已对齐的数据
- ZeroPadding 是一种简单但不安全的填充方式(无法区分真实数据与填充零),仅用于与遗留系统兼容
- PBE 安全性
- 盐值应至少 8 字节,推荐使用 16 字节;迭代次数建议不低于 1000(根据性能可适当提高,如 10000)
- 盐值和迭代次数必须与密文一起存储,解密时需提供相同参数
- 依赖
- SM4 和部分算法需要引入 Bouncy Castle 依赖,工具会自动检测
#### 代码示例
```java
// ========== AES 加密 ==========
// ========== ECB 模式,随机生成 128 位密钥 ==========
AES aesEcb = AES.createEcb();
byte[] cipherBytes = aesEcb.encrypt("Hello World".getBytes(StandardCharsets.UTF_8));
String cipherHex = aesEcb.encryptToHex("Hello World");
String cipherBase64 = aesEcb.encryptToBase64Str("Hello World");
// 解密
byte[] plainBytes = aesEcb.decrypt(cipherBytes);
String plainText = aesEcb.decryptFromHexStr(cipherHex);
System.out.println(plainText); // 输出 "Hello World"
// ========== CBC 模式,指定密钥和 IV ==========
byte[] key = KeyUtils.generate128(); // 16字节密钥
byte[] iv = KeyUtils.generate128(); // 16字节 IV
AES aesCbc = new AES(key, iv); // 默认 CBC/PKCS5Padding
byte[] encrypted = aesCbc.encrypt("Sensitive Data".getBytes());
byte[] decrypted = aesCbc.decrypt(encrypted);
// 获取密钥和 IV 的编码表示
String keyHex = aesCbc.getKeyToHex();
String ivBase64 = aesCbc.getIvToBase64Str();
// ========== 自定义模式与填充 ==========
AES aesCfb = new AES(key, iv, AlgorithmMode.CFB, AlgorithmPadding.NO_PADDING); // CFB 流模式无需填充
// ========== DES 加密 ==========
// DES:ECB 模式,随机 64 位密钥
DES des = DES.createEcb();
String encryptedDes = des.encryptToBase64Str("secret");
// DESede (3DES):CBC 模式,密钥 192 位,IV 64 位
byte[] desKey = KeyUtils.generate192(); // 24字节
byte[] desIv = KeyUtils.generate64(); // 8字节
DESede desEde = DESede.createCbc(desKey, desIv);
byte[] decryptedDes = desEde.decryptFromBase64(encryptedDes);
// ========== SM4 加密 ==========
// ECB 模式,随机 128 位密钥
SM4 sm4 = SM4.createEcb();
String sm4Cipher = sm4.encryptToHex("国密算法测试");
String sm4Plain = sm4.decryptFromHexStr(sm4Cipher);
// CBC 模式,指定密钥和 IV
byte[] sm4Key = KeyUtils.generate128();
byte[] sm4Iv = KeyUtils.generate128();
SM4 sm4Cbc = new SM4(sm4Key, sm4Iv); // 默认 CBC/PKCS5Padding
byte[] encrypted = sm4Cbc.encrypt("敏感数据".getBytes());
// ========== PBE 加密 ==========
// 最简单用法:自动生成盐值,迭代 100 次,使用 SHA-128-AES-CBC 算法
PBE pbe = PBE.create("userPassword");
byte[] pbeEncrypted = pbe.encrypt("sensitive data".getBytes());
byte[] pbeDecrypted = pbe.decrypt(pbeEncrypted);
System.out.println(new String(pbeDecrypted)); // 输出 "sensitive data"
// 获取盐值和迭代次数(需存储以便解密)
byte[] salt = pbe.getSalt();
int iteration = pbe.getIterationCount();
// 自定义盐值、迭代次数和算法
byte[] customSalt = KeyUtils.generate128();
PBE pbe2 = new PBE("userPassword", customSalt, 1000, PBEAlgorithms.SHA256_256_AES_CBC);
// ========== 大文件流式加密/解密 ==========
AES aes = new AES(); // 随机密钥
File srcFile = new File("/path/to/large.dat");
File encFile = new File("/path/to/large.enc");
File decFile = new File("/path/to/large.dec");
// 加密文件
aes.encrypt(srcFile, encFile);
// 解密文件
aes.decrypt(encFile, decFile);
// 使用 InputStream/OutputStream 自定义控制
try (InputStream in = new FileInputStream(srcFile);
OutputStream out = new FileOutputStream(encFile)) {
aes.encrypt(in, out, false); // 不自动关闭流,由 try-with-resources 管理
}
// ========== 密钥/IV 的编码与存储 ==========
AES aes = new AES(key, iv);
byte[] rawKey = aes.getKey();
String keyBase64 = aes.getKeyToBase64Str(); // 用于存储或传输
String ivHex = aes.getIvToHex();
// 从存储的字符串重建
byte[] restoredKey = Base64Utils.decode(keyBase64);
byte[] restoredIv = HexUtils.decode(ivHex);
AES aesRestored = new AES(restoredKey, restoredIv);
```
**常见问题**
- Q: 加密结果长度为什么会比原文长?
- A: 对于需要填充的模式(如 ECB、CBC),当数据长度不是块大小的整数倍时,会添加填充字节。即使数据长度恰好是块大小整数倍,某些填充模式(如 PKCS5)也会添加一个完整的填充块,因此密文长度总是块大小的整数倍。
- Q: 如何选择合适的模式和填充?
- A: 一般场景推荐使用 CBC 模式 + PKCS5Padding,安全性较高且兼容性好。对于需要并行处理或随机访问的数据流,可使用 CTR 或 CFB 模式。若需与遗留系统兼容,可能需要使用 ECB 模式(注意:ECB 模式对相同明文块产生相同密文块,存在信息泄露风险,不推荐用于新系统)。
- Q: PBE 的迭代次数越大越好吗?
- A: 迭代次数越大,破解成本越高,但加解密速度越慢。建议在安全性与性能之间取得平衡,一般 1000~10000 次较为常见。对于高安全性要求,可酌情提高。
- Q: 如何确保 IV 的安全性?
- A: IV 不需要保密,但必须是不可预测的(随机)。对于同一密钥,每次加密应使用不同的 IV(尤其是在 CBC 模式下)。工具类在未指定 IV 时会自动生成随机 IV,满足安全要求。IV 可与密文一起存储(通常前置)。
- Q: 流式加密支持多大的文件?
- A: 流式加密使用固定大小的缓冲区(默认 8KB),内存占用恒定,因此理论上可处理任意大小的文件(受磁盘空间和操作系统限制)。
### 3.3 非对称加密
**功能**:完整易用且高度安全的非对称加密工具类,
涵盖`RSA`和国密`SM2`算法。
支持密钥对`生成`、`加密`、`解密`、`签名`、`验签`,以及多种格式的密钥导入导出
。国密实现依赖BouncyCastle库。可以轻易完成对数据加密、解密、签名、验签及密钥管理,满足完整性校验和身份认证的需求
**核心特性:**
- RSA 支持
- 可生成指定长度的 RSA 密钥对(默认 1024 位,支持 2048、4096 等)
- 支持从字节数组、Base64/Hex 字符串、模数与指数等方式加载公钥/私钥
- 签名算法支持 SHA1withRSA、SHA256withRSA、SHA384withRSA、SHA512withRSA 等(默认 SHA256withRSA)
- SM2(国密)支持
- 完全遵循国家密码管理局标准,默认使用 sm2p256v1 曲线
- 实现国密 SM2 算法,支持 C1C2C3 和 C1C3C2 两种加密模式,可从原始 Q 值/D 值、X.509/PKCS#8 编码、OpenSSH 格式等加载密钥
- 签名完全符合国密标准,支持用户自定义 ID(默认 "1234567812345678"),并可选择是否使用随机数(默认启用)
- 可从原始 Q 值/D 值、X.509/PKCS#8 编码、OpenSSH 格式、Java 标准 PublicKey/PrivateKey 等多种方式加载密钥
- 密钥编码与导出:
- AsymmetricKey 类可统一存储和传递密钥对,便于缓存和序列化
- 统一编码
- 统一使用 UTF-8 编码处理字符串,确保跨平台一致性
**注意事项:**
- 依赖:
- SM2 算法依赖 Bouncy Castle,使用前请确保已引入相关依赖,工具类会自动检测并加载。
- 算法字符串
- RSA 加解密需指定完整转换字符串(如 "RSA/ECB/PKCS1Padding"),签名算法单独指定(如 "SHA256withRSA")。
- SM2 加解密使用内置引擎,无需指定算法字符串,但需通过 SM2Mode 选择加密顺序。
- RSA 密钥长度:
- 密钥长度至少 1024 位,推荐使用 2048 位或更高以保证安全性。
- 加密块大小自动计算,与填充方式相关(默认使用 PKCS1Padding),如需使用 OAEP 填充,请通过构造参数指定完整算法字符串(如 "RSA/ECB/OAEPWithSHA-256AndMGF1Padding")。
- SM2 密钥要求:
- 私钥 D 值为 32 字节无符号大端整数,工具类会自动处理 BigInteger 到字节数组的转换。
- 公钥 Q 值支持压缩和未压缩格式,默认导出为未压缩(以 0x04 开头)。
- 用户 ID 可为 null,此时签名/验签将使用 SM2 默认 ID("1234567812345678")。
#### RSA代码示例
```java
// 1. 生成默认 1024 位 RSA 密钥对
// 默认 1024 位 RSA 密钥对
RSA rsa = new RSA();
byte[] publicKey = rsa.getPublicEncode();
byte[] privateKey = rsa.getPrivateEncode();
// 指定密钥长度(如 2048 位)
RSA rsa2048 = new RSA(2048);
// 2. 使用已有密钥加密/解密
RSA rsa = new RSA(publicKey, privateKey); // 同时传入公私钥用于加解密
byte[] plain = "Hello RSA".getBytes(StandardCharsets.UTF_8);
byte[] cipher = rsa.encrypt(plain);
byte[] decrypted = rsa.decrypt(cipher);
System.out.println(new String(decrypted)); // 输出 "Hello RSA"
// 3. 从字符串加载密钥(Base64/Hex 自动识别)
// 从 Base64 字符串加载
String pubBase64 = rsa.getPublicEncodeToBase64Str();
String priBase64 = rsa.getPrivateEncodeToBase64Str();
RSA rsa2 = new RSA(pubBase64, priBase64);
// 从模数和指数还原(仅公钥)
BigInteger modulus = rsa.getModulus();
BigInteger pubExp = rsa.getPublicExponent();
RSA rsa3 = new RSA(modulus, pubExp, null); // 第三个参数为私钥指数,可 null
// 从 X.509/PKCS#8 字节数组加载
byte[] pubBytes = rsa.getPublicEncode();
byte[] priBytes = rsa.getPrivateEncode();
RSA rsa4 = new RSA(pubBytes, priBytes);
// 4. 签名/验签
RSA rsa = new RSA(publicKey, privateKey); // 需同时有公私钥
byte[] data = "待签名数据".getBytes(StandardCharsets.UTF_8);
// 签名
byte[] signature = rsa.sign(data);
String signHex = rsa.signToHex(data);
String signBase64 = rsa.signToBase64Str(data);
// 验签
boolean valid1 = rsa.verify(data, signature);
boolean valid2 = rsa.verifyFromHex(data, signHex);
boolean valid3 = rsa.verifyFromBase64Str(data, signBase64);
System.out.println("验签结果: " + valid1);
// 指定签名算法(默认 SHA256withRSA)
RSA rsaPss = new RSA(publicKey, privateKey, "RSA/ECB/PKCS1Padding", "SHA512withRSA");
byte[] sigPss = rsaPss.sign(data);
// 5.自定义加密算法(如 OAEP)
RSA rsaOaep = new RSA(publicKey, privateKey, "RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
byte[] cipherOaep = rsaOaep.encrypt(plain);
byte[] plainOaep = rsaOaep.decrypt(cipherOaep);
// 6. 导出密钥为不同格式
byte[] pubEncoded = rsa.getPublicEncode();
String pubHex = rsa.getPublicEncodeToHex();
String pubBase64Str = rsa.getPublicEncodeToBase64Str();
byte[] pubBase64Bytes = rsa.getPublicEncodeToBase64(); // Base64 字节数组
```
#### SM2代码示例
```java
// 1. 生成密钥对 默认 C1C3C2 模式
SM2 sm2 = SM2.create();
byte[] publicKey = sm2.getPublicEncode();
byte[] privateKey = sm2.getPrivateEncode();
// 指定加密模式(C1C2C3)
SM2 sm2C1C2C3 = SM2.create(SM2Mode.C1C2C3);
// 2. 使用已有密钥加密/解密(C1C3C2 模式)
SM2 sm2Enc = SM2.create(publicKey, null); // 仅公钥用于加密
SM2 sm2Dec = SM2.create(null, privateKey); // 仅私钥用于解密
byte[] plain = "Hello SM2".getBytes(StandardCharsets.UTF_8);
byte[] cipher = sm2Enc.encrypt(plain);
byte[] decrypted = sm2Dec.decrypt(cipher);
System.out.println(new String(decrypted)); // 输出 "Hello SM2"
// 字符串便捷方法
String cipherHex = sm2Enc.encryptToHex("Hello SM2");
String plainStr = sm2Dec.decryptFromHexStr(cipherHex);
// 3. 从原始 Q 值/D 值加载
ECPoint q = sm2.getEcPoint();
BigInteger d = sm2.getD();
// 仅公钥加密
SM2 sm2ByQ = SM2.create(q, null);
// 仅私钥解密
SM2 sm2ByD = SM2.create(null, d);
// 4. 签名/验签
SM2 sm2 = SM2.create(publicKey, privateKey); // 同时有公私钥
byte[] data = "待签名数据".getBytes();
// 签名(默认使用随机数)
byte[] signature = sm2.sign(data);
String signHex = sm2.signToHex(data);
// 验签
boolean valid = sm2.verify(data, signature);
boolean validFromHex = sm2.verifyFromHex(data, signHex);
System.out.println("验签结果: " + valid);
// 自定义用户 ID 和关闭随机数(确定性签名)
byte[] customUserId = "CustomUser".getBytes();
SM2 sm2Custom = new SM2(publicKey, privateKey, SM2Mode.C1C3C2, customUserId, false);
byte[] sig2 = sm2Custom.sign(data);
boolean valid2 = sm2Custom.verify(data, sig2);
// 5. 导出密钥
String pubBase64 = sm2.getPublicEncodeToBase64Str();
String priHex = sm2.getPrivateEncodeToHex();
byte[] pubBytes = sm2.getPublicEncode();
// 6. 密钥对对象(AsymmetricKey)使用
AsymmetricKey keyPair = sm2.createKeyObject(); // 默认 Base64 编码
// 或指定编码格式:true 为十六进制,false 为 Base64
AsymmetricKey keyPairHex = sm2.createKeyObject(true);
// 从存储的字符串还原
String storedPub = keyPair.getPublicKeyStr();
String storedPri = keyPair.getPrivateKeyStr();
keyPair.decode(); // 将字符串解码为字节数组
byte[] pub = keyPair.getPublicKey();
byte[] pri = keyPair.getPrivateKey();
```
**常见问题**
- Q: RSA 加密块大小如何计算?
- A: 加密块大小 = 密钥字节数 - 11(PKCS1Padding),解密块大小 = 密钥字节数。例如 2048 位密钥(256 字节)加密块为 245 字节。如需加密更大数据,请采用流式处理或混合加密(对称密钥 + RSA 加密密钥)。
- Q: SM2 签名时用户 ID 的作用?
- A: 用户 ID 是 SM2 签名标准的一部分,用于区分不同应用场景。默认值 "1234567812345678" 为国密推荐值,若与外部系统对接,请确认双方使用相同的用户 ID。
- Q: 为什么解密时需要使用正确的密钥?
- A: 非对称加密的安全性基于密钥配对,只有配对的私钥才能解密公钥加密的数据,反之亦然。请确保加密和解密使用同一密钥对。
- Q: 如何选择合适的 RSA 签名算法?
- A: 推荐使用 SHA256withRSA 或更强的 SHA384withRSA/SHA512withRSA。SHA1withRSA 已不推荐用于新系统。PSS 填充(如 RSASSA-PSS)可提供更高安全性,但需确保双方支持。
- Q: SM2 加密模式 C1C2C3 和 C1C3C2 有何区别?
- A: 两者是国密标准中规定的两种密文格式,区别在于 C2(加密数据)和 C3(哈希值)的顺序。通常推荐使用 C1C3C2,因为它与多数主流实现兼容。
### 3.4 签名验证 (JwtUtils)
**功能**:提供 JWT(JSON Web Token)的生成与验证工具,支持 HMAC256/384/512 签名算法可自定义载荷、过期时间及附加声明,验证时返回封装的状态对象,便于业务层处理
**设计说明:**
- 内部使用 com.auth0.jwt 库,所有异常统一捕获并转换为 JwtObject 状态码,同时抛出 JwtException 以便上层捕获
- 状态码定义:
-
- 500100:校验成功
- 500101:空指针异常(非法访问)
- 500102:签名不一致
- 500103:载荷不一致
- 500104:token 已过期
- 500105:token 格式错误
- 500106:算法不匹配
- 500200:其他内部错误
- 默认过期时间为 7 天
**注意事项:**
- TODO 未来会进行重构(提供更强大或无依赖模式)
- 依赖 java-jwt 库,使用前请确保引入(com.auth0:java-jwt)
- 签名密钥需妥善保管,建议使用环境变量或配置中心管理
- 校验时需传入与生成时完全相同的 Algorithm 实例和载荷字符串,否则验证失败
#### 代码示例
```java
// ========== 创建算法实例 ==========
Algorithm hmac256 = JwtUtils.createHmac256("secret");
Algorithm hmac384 = JwtUtils.createHmac384("secret");
Algorithm hmac512 = JwtUtils.createHmac512("secret");
// ========== 生成 JWT ==========
String token = JwtUtils.sign("user123", hmac256); // 默认7天过期
// 自定义过期时间(毫秒)
String token2 = JwtUtils.sign("user123", hmac256, 3600000L); // 1小时
// 携带额外 claims
Map claims = new HashMap<>();
claims.put("role", "admin");
claims.put("dept", "sales");
String token3 = JwtUtils.sign("user123", hmac256, 3600000L, claims);
// ========== 验证 JWT ==========
JwtObject result = JwtUtils.verifyJwt(token, "user123", hmac256);
if (result.isVerifyFlag()) {
// 校验成功
} else {
// 校验失败,result.getSignCode() 可获取具体错误码
}
// 验证带 claims 的 token
JwtObject result2 = JwtUtils.verifyJwt(token3, "user123", hmac256, claims);
// ========== 解析时间 ==========
LocalDateTime iat = JwtUtils.getIatTimeOfDateTime(token);
LocalDateTime exp = JwtUtils.getExpTimeOfDateTime(token);
```
### 3.5 密钥工具 (KeyUtils)
**功能**:提供对称和非对称密钥的生成、编码、解码、长度获取及密钥对生成等操作,基于安全随机数生成器,支持多种密钥长度和编码格式(十六进制、Base64)
**核心特性**:
- **密钥生成**:支持 40~2048 位多种长度的随机密钥生成,可直接返回字节数组、十六进制字符串或 Base64 字符串
- **编码/解码**:提供密钥在十六进制和 Base64 格式之间的双向转换,自动识别输入格式(十六进制或 Base64 URL-safe)
- **密钥长度计算**:可从字符串或字节数组计算密钥的实际位数(bit)
- **密钥对生成**:支持指定算法、Provider、密钥长度和算法参数生成非对称密钥对(如 RSA)
**部分代码示例**:
```java
// 1. 生成随机密钥(对称加密常用)
byte[] key128 = KeyUtils.generate128(); // 128位密钥(16字节)
String key128Hex = KeyUtils.generate128ToHexStr(); // 128位十六进制字符串
String key128Base64 = KeyUtils.generate128ToBase64Str();// 128位 Base64 字符串
// 2. 生成自定义长度密钥
byte[] key256 = KeyUtils.generate(32); // 256位密钥(32字节)
String keyHex = KeyUtils.generateToHexStr(16); // 128位十六进制
String keyBase64 = KeyUtils.generateToBase64Str(24); // 192位 Base64
// 3. 编码/解码密钥
byte[] rawKey = KeyUtils.generate128();
String hex = KeyUtils.encode(rawKey); // 转为十六进制(默认)
String base64 = KeyUtils.encode(rawKey, true); // 转为 Base64
byte[] decoded = KeyUtils.decode(hex); // 从十六进制解码
byte[] decoded2 = KeyUtils.decode(base64); // 从 Base64 解码(自动识别)
// 4. 生成 RSA 密钥对
KeyPair rsaKeyPair = KeyUtils.generateKeyPair("RSA", 2048);
PublicKey pub = rsaKeyPair.getPublic();
PrivateKey pri = rsaKeyPair.getPrivate();
```
[🔝 返回顶部](#pan-common-工具库)
## 4. 网络工具 (net)
### 4.1 HTTP客户端 (HttpClientBuilder)
**简介:**
- 提供功能强大的HTTP客户端构建器,大幅简化开发请求构建与发送流程灵活切换底层实现(HttpURLConnection / JDK 11+ HttpClient),原生支持虚拟线程,兼具高性能与低资源消耗
**核心特性**
- 轻量级封装,无需引入任何依赖
- 请求方法:GET、POST、PUT、DELETE等
- 参数形式:Query参数、Path变量、Form表单、FormData(支持文件上传)、JSON
- 自定义请求头认证:支持 Basic、Bearer、自定义认证头,可任意添加请求头
- 丰富配置: 自定义字符集、连接超时、读取超时设置,请求/响应日志控制(支持不同日志级别)
- 拦截器机制: 请求前/后拦截器、响应拦截器
- 客户端灵活适配:支持 HttpURLConnection 和 JDK 11 HttpClient 两种底层,预留扩展客户端(如 OkHttp)接口可根据环境(如 JDK 版本、性能需求)灵活选择,而无需更换 API
- 资源自动释放:响应流会在首次读取后自动关闭,无论正常或异常情况,均无需手动关闭,有效防止资源泄漏
- 线程安全说明:HttpClientBuilder 本身非线程安全,每个请求应创建新的构建器实例
- 文件上传增强:FormDataFileResource 可用于自定义文件名、内容类型等
- 支持虚拟线程:当使用JDK_11_HTTP_CLIENT且运行在JDK21+时,会自动启用虚拟线程执行器,无需额外配置
- 耗时统计: 自动统计各阶段耗时(连接、写入、响应等待、读取等)
**TODO 未来逐步开放高级特性**
- 代理认证
- 重试机制
- 性能监控上报支持集群
- 异步任务获取(支持虚拟线程)
#### 代码示例
```java
// 目前支持默认基于HttpURLConnection及JDK11HttpClient,推荐切换成JDK11HttpClient
// 1. 基础GET请求(查询参数)
HttpResponse response = HttpClientBuilder.builder("https://api.example.com/users")
.withHeader("X-API-Key", "your-api-key")
.withQueryParams("page", 1)
.withQueryParams("size", 20)
.build();
String result = response.getStr(); // 获取响应字符串
// 2. POST JSON请求(对象自动序列化)
User user = new User("张三", 25);
HttpResponse postResp = HttpClientBuilder.builder("https://api.example.com/users", HttpMethod.POST)
.withBodyJson(user)
.build();
User createdUser = postResp.getBean(User.class); // 解析JSON为对象
// 3. POST表单提交
HttpResponse formResp = HttpClientBuilder.builder("https://httpbin.org/post", HttpMethod.POST)
.withBodyForm(map -> {
map.put("username", "admin");
map.put("password", "123456");
})
.build();
// 4. 文件上传(单文件)
File file = new File("/path/to/file.txt");
HttpResponse uploadResp = HttpClientBuilder.builder("https://api.example.com/upload", HttpMethod.POST)
.withBodyFormData("file", file) // 指定表单字段名
.build();
// 5. Path变量(RESTful风格)
HttpResponse pathResp = HttpClientBuilder.builder("https://api.example.com/users/{id}", HttpMethod.GET)
.withPathVariable("id", 123) // 自动替换URL中的{id}
.build();
// 6. 自定义认证(Basic)
HttpResponse authResp = HttpClientBuilder.builder("https://api.example.com/secure", HttpMethod.GET)
.withAuth(HttpAuth.basic("username", "password")) // 自动添加Authorization: Basic xxx
.build();
// 7. 自定义认证(Bearer)
HttpResponse tokenResp = HttpClientBuilder.builder("https://api.example.com/protected", HttpMethod.GET)
.withAuth(HttpAuth.bearer("your-jwt-token"))
.build();
// 8. 自定义认证(自定义头)
HttpResponse customAuthResp = HttpClientBuilder.builder("https://api.example.com/custom", HttpMethod.GET)
.withAuth(HttpAuth.custom("X-Custom-Token", "token-value"))
.build();
// 9. 设置超时和字符集
HttpResponse timeoutResp = HttpClientBuilder.builder("https://api.example.com/slow", HttpMethod.GET)
.withConnectTimeout(5000) // 连接超时5秒
.withReadTimeout(10000) // 读取超时10秒
.withCharset("GBK") // 使用GBK编码
.build();
// 10. 日志控制(级别、开关)
HttpResponse logResp = HttpClientBuilder.builder("https://api.example.com/log", HttpMethod.GET)
.withLogLevel(LogLevel.INFO) // 设置日志级别为INFO
.withErrorLogLevel(LogLevel.ERROR) // 错误响应使用ERROR级别
.disableRequestLog() // 关闭请求日志
.disableResponseLog() // 关闭响应日志
.build();
// 11. 拦截器(请求前、请求后、响应)
HttpResponse interceptedResp = HttpClientBuilder.builder("https://api.example.com/intercept", HttpMethod.GET)
.withRequestPreInterceptor((request, client, config) -> {
Dev.log("请求前(参数未解析): " + request.getUrl());
})
.withRequestInterceptor((request, client, config) -> {
Dev.log("请求后(参数已解析): " + request.getUrl());
})
.withResponseInterceptor(response -> {
Dev.log("响应状态码: " + response.getCode());
return response; // 可修改或替换响应
})
.build();
// 12. 启用耗时统计
HttpResponse monitorResp = HttpClientBuilder.builder("https://api.example.com/monitor", HttpMethod.GET)
.enableTimer() // 启用耗时统计
.build();
Dev.log(monitorResp.getTimerStr()); // 输出耗时统计字符串
// 13. 选择不同的HTTP客户端实现(JDK11 HttpClient)默认基于HttpURLConnection
HttpResponse jdk11Resp = HttpClientBuilder.builder("https://api.example.com/jdk11", HttpMethod.GET)
.withClientType(HttpClientType.JDK_11_HTTP_CLIENT) // 使用JDK11 HttpClient(支持虚拟线程)
.build();
// 14. 链路追踪ID(日志中标识)
HttpResponse traceResp = HttpClientBuilder.builder("https://api.example.com/trace", HttpMethod.GET)
.withLogTraceId(123456L) // 设置追踪ID,日志中会打印
.build();
// 15. 获取响应详细信息
HttpResponse detailResp = HttpClientBuilder.builder("https://api.example.com/detail", HttpMethod.GET)
.build();
int statusCode = detailResp.getCode(); // HTTP状态码
String statusMsg = detailResp.getMessage(); // 状态消息
String errorMsg = detailResp.getErrorMsg(); // 错误信息(如果有)
boolean isSuccess = detailResp.isSuc(); // 是否2xx成功
HttpResponseHeader headers = detailResp.getHttpResponseHeader(); // 响应头
String contentType = headers.get("Content-Type");
String data = detailResp.getStr(); // 获取字符串
User bean = detailResp.getBean(User.class); // 仅限JSON默认序列化为UserBean
Long getTraceId = detailResp.getTraceId(); // 获取链路id
byte[] data = detailResp.getByte(); // 获取原始字节
InputStream in = detailResp.getInputStream(); // 获取输入流(手动读取)
List userList = detailResp.getBeanList(User.class); // 解析为List
```
### 4.2 FTP客户端 (Ftp)
**简介:**
- 提供简洁易用的 FTP 客户端,基于 Apache Commons Net 封装,支持连接管理、目录操作、文件上传下载与删除,以及丰富的配置项
**核心特性**
- 灵活认证:支持匿名登录、用户名密码登录
- 目录操作:切换工作目录、获取当前目录、列出目录及文件、判断文件是否存在
- 文件操作:上传(字节数组 / InputStream)、下载(返回 byte[])、删除单个或多个文件
- 批量操作:支持批量删除、批量检查文件是否存在
- 配置丰富:可自定义编码、超时(连接、命令、数据传输)、缓冲区大小、文件类型(二进制/ASCII)等
- 自动资源管理:实现 Closeable,支持 try-with-resources 自动释放连接
**注意事项**
- Ftp 实现了 Closeable,务必在使用后关闭(推荐 try-with-resources)
- 所有操作均可能抛出 FtpException 或其子类,建议捕获处理
- 上传下载操作会自动关闭传入的 InputStream,无需手动关闭
- 目录切换操作是有状态的,会影响后续相对路径的操作
#### 代码示例
**1. 连接FTP服务器并执行操作**
```java
// 创建 FTP 客户端(匿名登录,默认端口 21)
try (Ftp ftp = Ftp.create("192.168.1.100")) {
// 获取命令执行器(连接会自动建立)
FtpCommandExecutor executor = ftp.getExecutor();
// 切换目录
executor.getDirPathExecutor().changeWorkingDirectory("/pub");
// 上传文件
byte[] data = "Hello FTP".getBytes();
executor.getFileExecutor().upload("test.txt", data);
// 下载文件
byte[] downloaded = executor.getFileExecutor().download("test.txt");
// 列出当前目录下的文件
FTPFile[] files = executor.getDirFileExecutor().getFiles();
// 删除文件
executor.getFileExecutor().delFile("test.txt");
}
```
**2. 创建 FTP 实例**
```java
// 匿名登录(默认端口 21)
Ftp ftp = Ftp.create("ftp.example.com");
// 指定端口匿名登录
Ftp ftp = Ftp.create("ftp.example.com", 2121);
// 用户名密码登录
Ftp ftp = Ftp.create("ftp.example.com", 21, "user", "pass");
// 自定义配置
FtpConfig config = FtpConfig.create()
.setConnectMode(FtpConnectMode.PASSIVE)
.setEncoding("GBK")
.setGlobalTimeOut(60000);
Ftp ftp = Ftp.create("ftp.example.com", 21, "user", "pass", config);
```
**3. 目录操作 (FtpCommandDirectoryPath / FtpCommandDirectoryFile)**
```java
FtpCommandDirectoryPath dirPath = executor.getDirPathExecutor();
FtpCommandDirectoryFile dirFile = executor.getDirFileExecutor();
// 切换工作目录
dirPath.changeWorkingDirectory("/path/to/dir");
// 获取当前目录
String current = dirPath.getCurrentPath();
// 获取当前目录下所有子目录名
String[] subDirs = dirPath.getDirectoryPathAll();
// 列出当前目录所有文件
FTPFile[] files = dirFile.getFiles();
// 列出指定目录的文件
FTPFile[] filesIn = dirFile.getFiles("/another/path");
// 判断文件是否存在
boolean exists = dirFile.isExists("file.txt");
// 批量检查文件是否存在(返回缺失的索引)
int[] missing = dirFile.isExists("/target", Arrays.asList("a.txt", "b.txt"));
// 判断是否全部存在
boolean allExist = dirFile.isAllExists("/target", new String[]{"a.txt", "b.txt"});
```
**4. 文件操作 (FtpCommandFileExecutor)**
```java
FtpCommandFileExecutor fileExec = executor.getFileExecutor();
// 上传字节数组到当前目录
fileExec.upload("test.txt", "Hello".getBytes());
// 上传到指定目录
fileExec.upload("/remote/path", "test.txt", inputStream);
// 下载文件(返回字节数组)
byte[] content = fileExec.download("test.txt");
// 从指定目录下载
byte[] content = fileExec.download("/remote/path", "test.txt");
// 删除当前目录下的文件
fileExec.delFile("test.txt");
// 删除指定目录下的文件
fileExec.delFile("/remote/path", "test.txt");
// 批量删除文件
fileExec.delFiles(new String[]{"a.txt", "b.txt"});
fileExec.delFiles("/remote/path", Arrays.asList("a.txt", "b.txt"));
```
**5. 命令执行与状态 (FtpCommand)**
```java
FtpCommand cmd = executor.getCommand();
// 执行原始 FTP 命令
String[] result = cmd.doCommandAsArray("LIST", null);
// 获取上次命令执行详情
String details = cmd.getCommandDetails();
```
[🔝 返回顶部](#pan-common-工具库)
## 5. 反射工具 (reflection)
### 5.1 反射工具 (ReflectionUtils)
**核心特性**
- 字段操作:获取字段、读取/设置字段值(支持按字段名或 Field 对象)、批量获取字段值、支持静态字段与 final 字段修改
- 方法操作:获取方法、调用方法(支持参数匹配)、判断静态方法
- 构造器操作:获取构造器、创建实例(支持参数传递)
- 注解处理:判断类/字段/方法是否存在指定注解、获取注解、支持“与条件”和“或条件”查找带注解的字段或方法
- 高性能缓存:大幅度优化性能,避免重复 JDK 反射开销,所有缓存均为线程安全,可在多线程环境下使用
#### 代码示例
```java
// 获取字段并读取值
User user = new User("张三", 25);
Field field = ReflectionUtils.getField(User.class, "name");
String name = (String) ReflectionUtils.getFieldValue(field, user);
// 或直接通过字段名读取
Object name2 = ReflectionUtils.getFieldValue(user, "name");
// 设置字段值
ReflectionUtils.setFieldValue(user, "age", 30);
// 批量获取字段值
List