# 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 工具库 ![Maven Central Version](https://img.shields.io/maven-central/v/com.gitee.apanlh/apanlh-common) ![Static Badge](https://img.shields.io/badge/最新2.x版本-2.0.3-blue) ![JDK Version](https://img.shields.io/badge/支持JDK-8%20%E2%86%92%2021-blue) ![Static Badge](https://img.shields.io/badge/华为代码健康度-A-green) ![Static Badge](https://img.shields.io/badge/license-MulanPSL2-blue) ## [![Security Status](https://www.murphysec.com/platform3/v31/badge/2033229391950004224.svg)](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 values = ReflectionUtils.getFieldsValue(user); Map valueMap = ReflectionUtils.getFieldsValueToMap(user); // 调用方法 Method method = ReflectionUtils.getMethod(User.class, "getName"); String result = (String) ReflectionUtils.invoke(MethodInvokeBuilder.builder(user, "getName")); // 创建实例 User newUser = ReflectionUtils.newInstance(User.class); User userWithArgs = ReflectionUtils.newInstance(User.class, "李四", 28); // 注解判断与查找 boolean hasAnno = ReflectionUtils.hasClassAnnotation(User.class, MyAnnotation.class); Method[] methodsWithAnno = ReflectionUtils.findMethodAnnotation(User.class, MyAnnotation.class); List fieldsWithAnyAnno = ReflectionUtils.findFieldAnnotationAny(User.class, MyAnno1.class, MyAnno2.class); // 修改 static final 字段 Field staticFinalField = ReflectionUtils.getField(SomeClass.class, "CONST"); ReflectionUtils.setFieldStaticFinal(staticFinalField, newValue); ``` ### 5.2 拷贝工具 (CopyUtils) **功能**:基于 Spring CGLIB BeanCopier 实现的高性能对象拷贝工具,支持单对象和列表拷贝,内置缓存机制提升拷贝效率同时提供灵活的配置选项,支持忽略字段、自定义转换器等高级特性 **注意事项** - 依赖 spring-core 库,使用前请确保已引入相关依赖(CGLIB 包含在 spring-core 中) #### 代码示例 ```java // 1. 基本单对象拷贝(源对象 → 目标类) User user = new User("张三", 25); UserVo userVo = CopyUtils.copy(user, UserVo.class); // 2. 拷贝到已存在的目标对象 UserVo target = new UserVo(); CopyUtils.copy(user, target); // 3. 忽略字段拷贝 CopyConfig config = CopyConfig.create("password", "salt"); // 忽略 password 和 salt 字段 UserVo vo = CopyUtils.copy(user, UserVo.class, config); // 4. 自定义转换器(例如处理日期格式化) CopyConfig configWithConverter = CopyConfig.create() .withConverter(new CopyConverter(config) { // 可继承 CopyConverter 或实现 Converter @Override public Object convert(Object value, Class fieldType, Object setterName) { if (value instanceof Date) { return ((Date) value).getTime(); } return super.convert(value, fieldType, setterName); } }); UserVo converted = CopyUtils.copy(user, UserVo.class, configWithConverter); // 5. 列表拷贝 List userList = Arrays.asList(user1, user2); List voList = CopyUtils.copyList(userList, UserVo.class); // 6. 带配置的列表拷贝 List voList2 = CopyUtils.copyList(userList, UserVo.class, config); ``` [🔝 返回顶部](#pan-common-工具库) ## 6. 缓存工具 (cache) ### 6.1 本地缓存 (CacheUtils) **功能**:提供本地缓存解决方案,支持多种缓存淘汰策略,内置线程安全控制,满足不同场景下的缓存需求 **核心特性**: - 多种缓存策略:支持弱引用缓存、FIFO、LIFO、LFU、LRU、LRU-K、LRU-2Q 及定时缓存 - 线程安全:基于 ReentrantReadWriteLock实现高并发安全访问 - 容量控制:可为缓存设置最大容量,超出时自动淘汰 - 过期机制:仅定时缓存支持自定义过期时间,自动清理过期数据 - 统计信息:部分缓存实现提供命中次数统计 **缓存类型详解:** - 弱引用缓存: Cache 基于 WeakHashMap,JVM 回收 临时缓存,无需手动清理 - CacheFifo: 先进先出 对数据时效性要求不高的场景 - CacheLifo: 后进先出(栈) 需要“最新数据优先”的场景 - CacheLfu: 最少使用频率淘汰 热点数据访问频繁的场景 - CacheLru: 最近最少使用淘汰 最常见的缓存策略,平衡性好 - CacheLruK: 基于访问次数,达到K次才进入LRU 避免一次性数据污染缓存 - CacheLruTwoQ: Two queues 算法 兼顾 FIFO 和 LRU 的优点 - CacheTimer:基于过期时间,自动清理,数据有明确生命周期的场景 **线程安全说明:** - Cache 类:若底层使用 ConcurrentHashMap,则利用其并发特性;若使用其他 Map,则通过读写锁保证线程安全 - 其他缓存类(如 CacheFifo、CacheLru 等)内部通过 ReentrantLock 或读写锁实现线程安全 #### 代码示例 ```java // 1. 创建默认弱引用缓存(WeakHashMap) Cache cache = CacheUtils.cache(); // 2. 创建指定初始容量的弱引用缓存 Cache cacheWithSize = CacheUtils.cache(128); // 3. 创建 FIFO 缓存(先进先出),默认容量256 CacheFifo fifo = CacheUtils.fifo(); // 4. 创建指定容量的 FIFO 缓存 CacheFifo fifoWithCapacity = CacheUtils.fifo(500); // 5. 创建 LIFO 缓存(后进先出,栈),默认无限容量 CacheLifo lifo = CacheUtils.lifo(); // 6. 创建指定容量的 LIFO 缓存 CacheLifo lifoWithCapacity = CacheUtils.lifo(100); // 7. 创建 LFU 缓存(最少使用频率淘汰),默认容量256 CacheLfu lfu = CacheUtils.lfu(); // 8. 创建 LRU 缓存(最近最少使用淘汰),默认容量256 CacheLru lru = CacheUtils.lru(); // 9. 创建 LRU-K 缓存(解决缓存污染),可配置历史队列容量、LRU容量和K值 CacheLruK lruK = CacheUtils.lruk(200, 100, 3); // 10. 创建 LRU-2Q 缓存(Two queues),可配置A1和A2队列容量 CacheLruTwoQ lru2 = CacheUtils.lru2(150, 150); // 11. 创建定时缓存(支持过期时间) CacheTimer timerCache = new CacheTimer<>(); // 或通过其他构造函数定制 timerCache.put("key", "value", CacheTimer.ExpireTime.ONE_MINUTES); // 1分钟后过期 String value = timerCache.get("key"); ``` ### 6.2 Redis缓存 **📖 简介** - 传统 Redis 工具需要手动拼接 key 字符串、重复获取 RedisTemplate 实例,代码分散且易出错本项目提供一种**声明式**的 Redis 键管理方案:在类中定义静态常量作为 Redis key,框架在 Spring 启动时自动注入 RedisTemplate,之后即可直接通过常量调用 Redis 操作,简单、安全、可维护 **📖功能** - 提供声明式、类型安全的 Redis 键管理及操作封装通过注解与构建器,将 Redis key 的定义集中管理,并在 Spring 容器启动时自动注入 RedisTemplate,使静态常量可直接执行 Redis 命令,大幅简化日常开发中的 Redis 使用 **设计理念** - 传统 Redis 工具往往需要手动拼接 key 字符串、重复获取 RedisTemplate 实例,代码分散且易出错 - 本工具通过 @RegisterRedisKey 标记类,在类中以静态常量形式声明 Redis key,利用 RedisKeyBuilder 链式构建并指定数据类型(如 RedisString、RedisHash 等) - 容器启动时自动扫描注解类为每个静态字段注入 RedisTemplate,此后可直接通过常量调用 Redis 操作方法(如 PHONE_CODE.set("value")),无需再关心 key 拼接和模板获取 **核心特性** - **声明式键管理**:所有 Redis key 集中定义 - **自动注入**:Spring 启动时自动为静态常量注入 RedisTemplate - **链式构建**:通过 `RedisKeyBuilder` 以链式方式定义 key 层级,支持自定义分隔符(默认使用:) - **多数据类型支持**:内置 `RedisString`、`RedisHash`、`RedisList`、`RedisSet`、`RedisZSet`、`RedisGeo`、`RedisHyperLogLog`、`RedisStream` - **类型安全**:每个常量绑定具体数据类型,IDE 智能提示 - **自动前缀**:调用方法时自动追加已定义的层级前缀 - **重复键检测**:启动时检查 key 重复,防止意外覆盖 **使用步骤** - 添加依赖(确保项目中已引入 spring-data-redis 及相关配置) - 配置 RedisTemplate 在 Spring 配置中定义名为 "redisTemplate" 的 RedisTemplate Bean(本工具默认使用此名称) - 定义 Key 类 创建类并使用 @RegisterRedisKey 注解标记,在类中通过 RedisKeyBuilder 定义静态常量 - 扫描包:@RegisterRedisKey 注解本身带有 @Component,会被 Spring 自动扫描(需确保所在包被 @ComponentScan 覆盖) **数据类型与操作** - 每个数据类型封装了对应的 Redis 命令例如: - RedisString:set、get、increment、decrement、setIfAbsent 等 - RedisHash:set(支持对象)、get、increment、size、delete 等 - RedisList:leftPush、rightPush、leftPop、rightPop、range 等 - RedisSet、RedisZSet 等均提供对应操作 **具体方法可参考对应类的 API 文档** #### 代码示例 **1. 定义 Key 类** ```java @RegisterRedisKey public class SmsRedisKey { /** 短信验证码,有效期 3 分钟 */ public static final RedisString PHONE_CODE = RedisKeyBuilder.builder().withKeys("sms", "code").buildString(); /** 短信发送次数计数器 */ public static final RedisString PHONE_COUNT = RedisKeyBuilder.builder().withKeys("sms", "count").buildString(); } ``` **withKeys("sms", "code") 将生成 key "sms:code"(分隔符可自定义)** **buildString() 返回 RedisString 实例,后续可调用其提供的字符串操作方法** **2. 业务代码中使用** ```java public void service() { // 所有方法均自动拼接完整 key,无需手动处理前缀 // 存入验证码(自动过期 3 分钟) SmsRedisKey.PHONE_CODE.setForSeconds("13800138000", "123456", 180); // 读取验证码 String code = SmsRedisKey.PHONE_CODE.get("13800138000", String.class); // 递增发送次数 Long count = SmsRedisKey.PHONE_COUNT.increment("13800138000"); } ``` [🔝 返回顶部](#pan-common-工具库) ## 7. 资源/配置工具 (setting) ### 7.1 配置加载器 (ConfigLoader) **功能**:提供配置文件加载的功能,多种配置加载如:properties和yaml格式 #### 代码示例 ```java // 配置加载 Properties properties = ConfigLoader.loadProperties("config.properties"); Map yaml = ConfigLoader.loadYaml("config.yml"); ``` ### 7.2 属性工具 (PropertiesUtils) **功能**:提供属性文件操作的功能 #### 代码示例 ```java // 属性操作 Properties properties = PropertiesUtils.load("config.properties"); String value = PropertiesUtils.getString(properties, "key", "default"); ``` ### 7.3 资源工具 (ResourceUtils) **功能**:提供属性文件操作的功能 #### 代码示例 ```java // 资源操作 URL url = ResourceUtils.getUrl("classpath:config.properties"); InputStream input = ResourceUtils.getInput(url); BufferedReader reader = ResourceUtils.getReader(url); ``` [🔝 返回顶部](#pan-common-工具库) ## 8. 文件工具 (file) ### 8.1 文件工具 (FileUtils) **功能**:提供文件与目录的常用操作,覆盖创建、删除、拷贝、移动、信息获取、遍历、行处理等,简化文件系统交互 #### 代码示例 **1. 创建目录与文件** ```java // 创建单级目录 FileUtils.createDirectory("/tmp/mydir"); // 创建多级目录(父目录不存在时一并创建) FileUtils.createDirectors("/tmp/a/b/c"); // 创建文件(目录不存在则自动创建) FileUtils.create("/tmp/data", "test.txt"); // 覆盖已存在的文件 FileUtils.create("/tmp/data", "test.txt", true); // 检查目录是否存在 boolean exists = FileUtils.existPath("/tmp/data"); // 检查文件是否存在,若不存在则创建 boolean created = FileUtils.existFile("/tmp/data", "config.properties", true); ``` **2. 拷贝与移动** ```java // 文件拷贝(源 → 目标) File copied = FileUtils.copy("/home/source.txt", "/backup/target.txt"); // 文件移动 File moved = FileUtils.move("/home/source.txt", "/archive/target.txt"); ``` **3. 删除** ```java // 删除单个文件或空目录 FileUtils.del("/tmp/old.log"); // 递归删除目录及其所有内容 FileUtils.del(new File("/tmp/dir")); // 删除文件的前 3 行(适用于小文件) FileUtils.delLine("/tmp/access.log", 3); ``` **4. 存在检查/自动创建** ```java // 判断是否为 class 文件 boolean isClass = FileUtils.isClassFile("Bean.class"); // true // 检查目录是否存在 boolean exists = FileUtils.existPath("/tmp/data"); // 检查文件是否存在,若不存在则创建 boolean created = FileUtils.existFile("/tmp/data", "config.properties", true); ``` **5. 文件信息获取** ```java // 获取文件后缀(不含点) String suffix = FileUtils.getSuffix("image.jpg"); // "jpg" String suffixWithDot = FileUtils.getSuffix("image.jpg", true); // ".jpg" // 获取文件的 MIME 类型 String contentType = FileUtils.getContentType("/tmp/file.pdf"); // 获取文本文件行数 long lines = FileUtils.getTotalLine("/tmp/log.txt"); // 列出目录下所有文件 List files = FileUtils.getFiles("/tmp", FileMatchType.FILE); // 仅文件 List names = FileUtils.getFilesName("/tmp", FileMatchType.ALL); // 所有条目名称 // 根据字节数组判断图片类型 byte[] imageBytes = ...; String ext = FileUtils.getExtendOfPhoto(imageBytes); // "png", "jpg" 等 ``` **6. 路径与文件名处理** ```java // 标准化路径分隔符(确保末尾带分隔符) String path = FileUtils.reviseFilePath("/usr/local"); // "/usr/local/" // 从全路径中提取文件名 String name = FileUtils.subName("/var/log/app.log"); // "app.log" String nameNoSuffix = FileUtils.subName("/var/log/app.log", true); // "app" // 修复文件名(去除开头的分隔符) String fixedName = FileUtils.reviseFileName("\\data.txt"); // "data.txt" ``` [🔝 返回顶部](#pan-common-工具库) ## 9. IO工具 (io) ### 9.1 IO工具 (IOUtils) **功能**:提供简洁高效的IO流操作工具,覆盖流的读取、写入、复制、关闭等常见场景,自动管理资源释放,减少样板代码支持字节流与字符流,内置缓冲区优化,并统一异常处理 #### 代码示例 **1. 获取读取器 (getReader)** ```java // 从文件获取 BufferedReader(默认 UTF-8,8K 缓冲) BufferedReader reader = IOUtils.getReader(new File("/tmp/data.txt")); // 从输入流获取,指定编码和缓冲区大小 InputStream is = new FileInputStream("/tmp/data.txt"); BufferedReader reader2 = IOUtils.getReader(is, "GBK", 4096); // 快捷获取 UTF-8 读取器 BufferedReader utf8Reader = IOUtils.getReaderUtf8(is); ``` **2. 流复制 (copy)** ```java // 将输入流内容复制到输出流(默认 8K 缓冲,自动关闭输入流) IOUtils.copy(inputStream, outputStream); // 自定义缓冲区大小,并选择关闭输入/输出流 IOUtils.copy(inputStream, outputStream, 8192, true, false); ``` **3. 写入数据 (write)** ```java // 将字符串写入输出流(默认 UTF-8,自动关闭输出流) IOUtils.write("Hello World", outputStream); // 指定编码和缓冲区,并选择是否关闭输出流 IOUtils.write("你好", outputStream, "GBK", 4096, true); // 将字节数组写入输出流 byte[] data = {1, 2, 3}; IOUtils.write(data, outputStream); // 将输入流内容写入 StringBuilder StringBuilder sb = new StringBuilder(); IOUtils.write(inputStream, sb, "UTF-8"); // 将字符读取器内容写入 StringBuffer StringBuffer sbuf = new StringBuffer(); IOUtils.write(reader, sbuf); ``` **4. 读取数据 (read / readString)** ```java // 读取输入流为字节数组(自动关闭输入流) byte[] bytes = IOUtils.read(inputStream); // 读取输入流为字符串(默认 UTF-8) String str = IOUtils.readString(inputStream); // 指定编码和缓冲区,并选择是否关闭输入流 String str2 = IOUtils.readString(inputStream, "GBK", 4096, true); // 从字符读取器读取字符串 String str3 = IOUtils.readString(reader); ``` **5.资源关闭 (close)** ```java // 关闭单个 Closeable 资源 IOUtils.close(reader); // 关闭多个 Closeable 资源(按顺序) IOUtils.close(inputStream, outputStream); // 关闭 AutoCloseable 资源(如 JDK 11+ HttpClient) IOUtils.close(autoCloseable); // 关闭进程 IOUtils.close(process); // 断开 HttpURLConnection IOUtils.close(httpURLConnection); ``` **6. 辅助方法** ```java // 字节数组转 ByteArrayInputStream ByteArrayInputStream bais = IOUtils.toByteInput(bytes); // 字节数组转 ByteArrayOutputStream(默认 8K 缓冲) ByteArrayOutputStream baos = IOUtils.toByteOutput(bytes); ``` ### 9.2 文件IO工具 (FileIOUtils) **功能**:提供基于 NIO 的高效文件流操作,封装文件输入/输出流的获取、复制、读写等常见任务,支持零拷贝写入、自动资源管理、行迭代读取,适用于大文件处理、流式传输等场景 **核心特性**: - 流获取:便捷获取 FileInputStream、FileOutputStream、BufferedWriter,支持追加/覆盖模式 - 流复制:支持 InputStream → FileOutputStream、FileInputStream → FileOutputStream 的复制,可自定义缓冲区大小及流关闭控制 - 高效写入:支持字符串、字节数组、输入流直接写入文件,自动创建不存在的目录和文件;内置零拷贝(FileChannel)写入优化 - 文件读取:将文件读为字节数组、字符串,或按行迭代读取(ReadLineIterator),支持自定义字符编码和缓冲区 - 行迭代器:ReadLineIterator 实现 Iterator,可逐行遍历文件,自动管理资源,避免一次性加载大文件 - 异常统一:将 IO 异常封装为运行时异常(如 StreamReadException),减少业务代码的 try-catch 负担 #### 代码示例 **1. 获取文件流** ```java // 获取文件输入流(需手动关闭) FileInputStream fis = FileIOUtils.getInput("/tmp/data.txt"); // 获取文件输出流(覆盖模式) FileOutputStream fos = FileIOUtils.getOutput("/tmp/out.txt"); // 获取文件输出流(追加模式) FileOutputStream fosAppend = FileIOUtils.getOutput("/tmp/out.txt", true); // 获取缓冲写入器(字符流) BufferedWriter writer = FileIOUtils.getWriter("/tmp/log.txt", true); // 追加 ``` **2. 复制文件流** ```java // 将 InputStream 复制到 FileOutputStream(默认 8K 缓冲,自动关闭输入流) long copied = FileIOUtils.copy(inputStream, fileOutputStream); // 自定义缓冲区大小,并选择关闭输出流 FileIOUtils.copy(inputStream, fos, 8192, true, false); // 使用 FileInputStream 直接复制(零拷贝优化) FileIOUtils.copy(fis, fos, -1); // -1 表示自动选择最优方式 ``` **3. 写入文件** ```java // 将字符串写入文件(UTF-8,覆盖模式,自动创建目录) FileIOUtils.writeToFile("Hello World", new File("/tmp/test.txt")); // 指定编码和追加模式 FileIOUtils.writeToFile("你好", new File("/tmp/test.txt"), "GBK", true); // 字节数组写入 byte[] data = {1, 2, 3}; FileIOUtils.writeToFile(data, new File("/tmp/data.bin")); // 输入流写入 FileIOUtils.writeToFile(inputStream, new File("/tmp/upload.dat")); // 使用 FileOutputStream 直接写入(可指定缓冲区、写入模式) FileIOUtils.write("content", fos, 4096, true, FileWriteMode.ZERO_COPY); ``` **4. 读取文件** ```java // 读取为字节数组 byte[] bytes = FileIOUtils.read("/tmp/data.bin"); // 读取为字符串(默认 UTF-8) String text = FileIOUtils.readString("/tmp/data.txt"); // 指定编码和缓冲区 String gbkText = FileIOUtils.readString("/tmp/gbk.txt", "GBK", 8192); ``` **5. 行迭代读取(ReadLineIterator)** ```java // 使用 try-with-resources 自动关闭 try (ReadLineIterator it = FileIOUtils.readLine("/tmp/large.log")) { while (it.hasNext()) { String line = it.next(); // 处理每一行 } } // 获取所有行(小文件适用) List lines = FileIOUtils.readLines("/tmp/data.txt"); ``` **6. 复制与写入高级选项** ```java // 使用 FileChannel 直接复制 FileChannel outChannel = fos.getChannel(); long written = FileIOUtils.copy(inputStream, outChannel, 8192, true, false); // 写入字节数组到 FileChannel(零拷贝模式) FileIOUtils.write(bytes, outChannel, 4096, true, FileWriteMode.ZERO_COPY); ``` [🔝 返回顶部](#pan-common-工具库) ## 10. 随机工具 (random) ### 10.1 随机工具 (RandomUtils) **功能**:提供随机数生成工具,支持整数、长整数、浮点数、布尔值、字节数组等常见类型内置线程本地随机数(ThreadLocalRandom)和安全随机数(SecureRandom),可满足普通测试、数据生成以及加密安全等不同场景的需求 #### 代码示例 ```java // 1. 获取随机数生成器 // 获取当前线程的 ThreadLocalRandom 实例(高效) ThreadLocalRandom threadRandom = RandomUtils.getRandomOfThread(); // 获取 SecureRandom 实例(安全) SecureRandom secureRandom = RandomUtils.getRandomOfSecure(); // 使用自定义种子创建 SecureRandom SecureRandom seededRandom = RandomUtils.getRandomOfSecure(seedBytes); // 2. 生成随机整数 // 生成 [0, 100] 范围内的随机整数(包含 100) int randInt = RandomUtils.randomInt(100); // 生成 [10, 20] 范围内的随机整数(包含 10 和 20) int randIntRange = RandomUtils.randomInt(10, 20); // 3. 生成随机长整数 // 生成 [0, 100000] 范围内的随机长整数 long randLong = RandomUtils.randomLong(100000L); // 生成 [1000, 2000] 范围内的随机长整数 long randLongRange = RandomUtils.randomLong(1000L, 2000L); // 4. 生成随机浮点数 // 生成 [0, 10) 范围内的随机 double(包含 0,不包含 10) double randDouble = RandomUtils.randomDouble(10.0); // 生成 [2.5, 8.5) 范围内的随机 double double randDoubleRange = RandomUtils.randomDouble(2.5, 8.5); // 生成 [0, 5) 范围内的随机 float float randFloat = RandomUtils.randomFloat(5.0f); // 生成 [1.0, 3.0) 范围内的随机 float float randFloatRange = RandomUtils.randomFloat(1.0f, 3.0f); // 5. 生成随机布尔值 boolean flag = RandomUtils.randomBoolean(); // true 或 false // 6. 生成随机字节数组 // 生成 16 字节(128 位)的随机字节数组(使用线程本地随机数) byte[] key16 = RandomUtils.randomBytes(16); // 生成 32 字节(256 位)的随机字节数组,使用安全随机数(适用于密钥等) byte[] key32 = RandomUtils.randomBytes(32, true); ``` ### 10.2 随机码工具 (RandomCodeUtils) **功能**:提供用于生成随机字符串的工具,支持纯数字和字母数字混合两种模式适用于验证码、密码、邀请码等需要随机字符串的场景 #### 代码示例 ```java // ========== 纯数字随机码 ========== String code = RandomCodeUtils.randomNumeric(); // 默认6位纯数字 String code4 = RandomCodeUtils.randomNumeric(4); // 4位纯数字 // ========== 字母数字混合随机码 ========== String mixed = RandomCodeUtils.randomAlphanumeric(); // 默认6位字母数字混合 String mixed8 = RandomCodeUtils.randomAlphanumeric(8); // 8位字母数字混合 // ========== 自定义字符集随机码 ========== String custom = RandomCodeUtils.random(6, "ABCDEFG".toCharArray()); // 使用字符数组 String custom2 = RandomCodeUtils.random(10, "0123456789abcdef"); // 使用字符串 ``` [🔝 返回顶部](#pan-common-工具库) ## 11. 验证工具 (valid) ### 11.1 参数验证 (ValidParam) **功能**:提供统一、便捷的参数验证方法,涵盖空值、空字符串、空集合、数字、字母、全角符号等多种校验场景,简化业务代码中的判断逻辑 #### 代码示例 ```java // ========== 必填验证 ========== boolean valid = ValidParam.required("name", "age"); // 多个字符串全非空 boolean valid2 = ValidParam.requiredContainsSpace("hello"); // 检查不含空格 boolean validMap = ValidParam.requiredMapByValue(map); // Map所有value非空 boolean validBean = ValidParam.requiredBean(user); // JavaBean所有属性非空 // ========== 空值判断 ========== boolean isNull = ValidParam.isNull(obj); // 单个对象是否为null boolean allNull = ValidParam.isNull(obj1, obj2, obj3); // 是否全部为null boolean notNull = ValidParam.isNotNull(obj); // 单个对象是否非null boolean allNotNull = ValidParam.isNotNull(obj1, obj2); // 是否全部非null // ========== 空集合/数组/字符串 ========== boolean empty = ValidParam.isEmpty(str); // 字符串为空(含null、"null"、空格) boolean emptyArr = ValidParam.isEmpty(arr); // 数组为空 boolean emptyColl = ValidParam.isEmpty(list); // 集合为空 boolean emptyMap = ValidParam.isEmpty(map); // Map为空 boolean notEmpty = ValidParam.isNotEmpty(str); // 非空 // ========== 类型与字符判断 ========== boolean isNum = ValidParam.isNumber("12345"); // 是否全数字 boolean isNumeric = ValidParam.isNumeric("-123"); // 是否数值(含负号) boolean isLetter = ValidParam.isLetter('A'); // 是否为字母 boolean isUpper = ValidParam.isUpperCaseLetter('A'); // 是否大写字母 boolean isLower = ValidParam.isLowerCaseLetter('a'); // 是否小写字母 boolean hasSpace = ValidParam.isContainsSpace("hello world"); // 是否包含空格 boolean hasFullWidth = ValidParam.isFullWidthCharacter("hello"); // 是否包含全角字符 ``` ### 11.2 身份证验证 (IdcardUtils) **功能**:提供中国大陆居民身份证号码的校验、转换和信息提取工具支持 15 位和 18 位身份证的合法性校验,并可提取出生日期、性别、年龄、省份、星座等信息 **设计说明:** - 校验规则严格遵循国家标准 GB 11643-1999,包括加权因子和校验码算法 - 省份代码基于国家标准,覆盖所有省、直辖市、自治区及特别行政区 - 年龄计算基于当前日期,使用 DateUtils.age 方法 #### 代码示例 ```java // ========== 身份证校验 ========== boolean isValid = IdcardUtils.valid("11010119900307663X"); // true boolean isValid15 = IdcardUtils.valid15("110101900307663"); // true boolean isValid18 = IdcardUtils.valid18("11010119900307663X"); // true // ========== 格式转换 ========== String idCard18 = IdcardUtils.idCard15To18("110101900307663"); // 11010119900307663X String idCard15 = IdcardUtils.idCard18To15("11010119900307663X"); // 110101900307663 // ========== 信息提取 ========== String province = IdcardUtils.getProvince("11010119900307663X"); // 北京 String birth = IdcardUtils.getBirth("11010119900307663X"); // 1990-03-07 String year = IdcardUtils.getYear("11010119900307663X"); // 1990 String month = IdcardUtils.getMonth("11010119900307663X"); // 03 String day = IdcardUtils.getDay("11010119900307663X"); // 07 String gender = IdcardUtils.getGender("11010119900307663X"); // 男 Integer age = IdcardUtils.getAge("11010119900307663X"); // 当前年龄 String zodiac = IdcardUtils.getZodiac("11010119900307663X"); // 双鱼座 ``` ### 11.3 正则验证 (RegexUtils) **功能**:提供常用正则表达式验证工具,涵盖手机号、数字、字母、IP、邮箱、身份证、URL、强密码等多种格式校验,简化字符串格式验证代码 **设计说明:** - 手机号正则覆盖 13、14、15、16、17、18、19 号段,且严格为 11 位 - 强密码规则:一级要求包含大写、小写、数字、特殊字符,二级要求包含大小写字母(至少一种)、数字、特殊字符,长度均为 8-20 位 - 部分方法(如身份证、IP)直接委托给对应工具类(IdcardUtils、IpUtils) **注意事项**: - 手机号验证仅适用于中国大陆手机号码,且不包含前导的国际区号(如 +86) - 图片后缀验证仅检查文件名末尾是否为常见图片格式,不验证文件内容 - 强密码中的特殊字符集为 !@#$%^&*().,,可根据需要调整 #### 代码示例 ```java // ========== 基础类型验证 ========== boolean isNum = RegexUtils.isNumeric("12345"); // 是否纯数字 boolean isFloat = RegexUtils.isFloat("3.14"); // 是否浮点数 boolean isEnglish = RegexUtils.isEnglish("abc"); // 是否纯英文字母 boolean isNumEnglish = RegexUtils.isNumEnglish("abc123"); // 是否字母数字组合 boolean isNumEnglishLine = RegexUtils.isNumEnglishLine("abc_123"); // 是否字母数字下划线 boolean isNumEnglishLineTag = RegexUtils.isNumEnglishLineTag("abc-123"); // 是否字母数字下划线横杠 // ========== 常用格式验证 ========== boolean isPhone = RegexUtils.isPhone("13800138000"); // 手机号 boolean isIpv4 = RegexUtils.isIpV4("192.168.1.1"); // IPv4 地址 boolean isPostalCode = RegexUtils.isPostalCode("100000"); // 邮政编码 boolean isUrl = RegexUtils.isUrl("https://example.com"); // URL boolean isEmail = RegexUtils.isEmail("user@example.com"); // 邮箱 boolean isChinese = RegexUtils.isChinese("中文"); // 是否全中文 boolean isImage = RegexUtils.isImage("photo.jpg"); // 图片文件名 // ========== 身份证验证(委托) ========== boolean isIdcard = RegexUtils.isIdcard("11010119900307663X"); // 15/18位身份证 boolean isIdcard15 = RegexUtils.isIdcard15("110101900307663"); boolean isIdcard18 = RegexUtils.isIdcard18("11010119900307663X"); // ========== 强密码验证 ========== boolean strong1 = RegexUtils.isStrongPwd("Abc123!@#"); // 大写+小写+数字+特殊字符 boolean strong2 = RegexUtils.isSecondStrongPwd("Abc123!@#"); // 大小写+数字+特殊字符(大小写任选) ``` [🔝 返回顶部](#pan-common-工具库) ## 12. 系统工具 (sys) ### 12.1 系统工具 (SystemUtils) **功能**:提供系统相关的功能,如获取系统信息、环境变量等 #### 代码示例 ```java TODO ``` ### 12.2 运行计时器 (RunTimer) **功能**:提供简单易用的代码块执行时间测量工具,支持任务命名、开始计时、停止计时、耗时获取和日志输出,便于性能分析和调试 **注意事项**: - 计时器非线程安全,建议在每个待测量的代码块中新建实例,避免多线程干扰 - 调用 printLog() 前若未调用 stopTime(),会自动调用一次以完成计时 #### 代码示例 ```java // ========== 创建计时器 ========== RunTimer timer = RunTimer.create(); // 任务名默认为调用方法名 RunTimer namedTimer = RunTimer.create("查询数据库"); // 指定任务名 // ========== 计时控制 ========== timer.startTime(); // 开始计时(构造时自动开始,可省略) // 执行需要计时的代码... Thread.sleep(500); // 模拟耗时操作 Long elapsed = timer.stopTime(); // 停止计时,返回耗时(毫秒) // ========== 获取结果 ========== long cost = timer.getStopTime(); // 获取已记录的耗时 String report = timer.printTime(); // 生成耗时描述字符串 // ========== 日志输出 ========== timer.printLog(); // 输出 INFO 日志:任务名:xxx的运行时间为:xxx(ms) // ========== 重置复用 ========== timer.clearTimer(); // 清空开始和结束时间 timer.startTime(); // 再次开始计时 // ... timer.stopTime(); timer.printLog(); ``` [🔝 返回顶部](#pan-common-工具库) ## 13. 线程工具 (thread) ### 13.1 线程池工具 (ThreadPoolExecutorUtils) **功能**:提供线程池操作的功能 #### 代码示例 ```java TODO ``` ### 13.2 线程睡眠工具 (Sleep) **功能**:提供线程睡眠的简化封装,支持毫秒和秒级休眠,自动处理中断异常并恢复中断状态,避免繁琐的 try-catch 代码 #### 代码示例 ```java // 毫秒级睡眠 Sleep.mills(500); // 休眠 500 毫秒 // 毫秒 + 纳秒级睡眠 Sleep.mills(1000, 500_000); // 休眠 1 秒 500 微秒 // 秒级睡眠 Sleep.seconds(3); // 休眠 3 秒 ``` [🔝 返回顶部](#pan-common-工具库) ## 14. 日志工具 (log) ### 14.1 日志工具 (Log) **功能**:提供基于 SLF4J 的日志门面封装,简化日志获取与使用,内置 Logger 缓存,支持通过堆栈自动推断调用类,提供统一的日志级别枚举及写入方法 **核心特性**: - 自动推断调用类:Log.get() 无需传入 Class,自动通过堆栈获取调用者类名,避免手动传入的繁琐 - 内置缓存:内部缓存已创建的 Logger 实例,避免重复创建开销 - 日志级别判断:提供 isInfoEnabled()、isDebugEnabled() 等快捷方法,便于在输出前检查级别 - 枚举日志级别:使用 LogLevel 枚举统一管理日志级别,支持通过 write(LogLevel, String) 和 write(LogLevel, String, Throwable) 按级别输出 - 获取当前级别:level() 方法可获取当前 Logger 的有效日志级别 #### 代码示例 ```java // ========== 获取 Logger ========== Logger logger = Log.get(); // 自动推断调用类 Logger loggerForClass = Log.get(MyClass.class); // 指定类 // ========== 日志级别判断 ========== if (Log.isDebugEnabled()) { // 执行调试信息构建 } // ========== 输出日志(使用枚举) ========== Log.write(LogLevel.INFO, "应用启动成功"); Log.write(LogLevel.ERROR, "发生错误", new RuntimeException("test")); // ========== 获取当前日志级别 ========== LogLevel currentLevel = Log.level(); // 返回 INFO/DEBUG 等 ``` [🔝 返回顶部](#pan-common-工具库) ## 15. 编码工具 (encode) ### 15.1 Base64工具 (_Base64Utils_) **功能**:封装 Java 标准库中的 Base64 功能,提供简洁易用的 API,支持标准 Base64、URL 安全 Base64 和 MIME Base64 三种编码类型适用于二进制数据与文本的相互转换、URL 参数传递、邮件传输等场景 **核心特性**: - 多类型支持 - RFC4648:标准 Base64(包含 + 和 /) - RFC4648_URLSAFE:URL 安全 Base64(使用 - 和 _ 替代) - RFC2045:MIME Base64(每行 76 字符,以 \r\n 换行) - 轻量封装:直接调用 JDK 的 Base64 类,无额外依赖,性能无损 **注意事项**: - URL 安全编码适用于在 URL 或文件名中传输数据,避免 + 和 / 被转义 - MIME 编码适用于邮件等需要换行的场景,解码时会忽略换行符 #### 代码示例 ```java // ========== 编码为 byte[] ========== byte[] encodedBytes = Base64Utils.encode("Hello World".getBytes()); // 标准 byte[] encodedBytes2 = Base64Utils.encode("Hello", Base64Type.RFC4648_URLSAFE); // ========== 编码为 String ========== String encodedStr = Base64Utils.encodeToStr("Hello World"); // 标准 String encodedStr2 = Base64Utils.encodeToStr("Hello", Base64Type.RFC2045); // ========== 解码为 byte[] ========== byte[] decodedBytes = Base64Utils.decode(encodedStr); // 标准 byte[] decodedBytes2 = Base64Utils.decode(encodedStr, Base64Type.RFC4648_URLSAFE); // ========== 解码为 String ========== String decodedStr = Base64Utils.decodeToStr(encodedStr); // 标准 String decodedStr2 = Base64Utils.decodeToStr(encodedStr, Base64Type.RFC2045); ``` ### 15.2 十六进制工具 (HexUtils) **功能**:提供十六进制与字节数组、字符串、整型等数据格式之间的高效转换支持大小写控制,内置字符映射缓存,优化解码性能 **核心特性**: - 双向转换:支持将字节数组、字符串、整型、长整型编码为十六进制字符串,同时支持将十六进制字符串解码回原格式 - 解码优化:解码时使用预计算的字符映射数组,避免重复计算 - 轻量实现:基于位运算实现,无额外依赖 #### 代码示例 ```java // ========== 基本类型编码 ========== String hexChar = HexUtils.encode('A'); // 字符 'A' → "41" String hexInt = HexUtils.encode(255); // 255 → "ff" String hexLong = HexUtils.encode(123456789L); // 123456789L → "75bcd15" // ========== 字符串编码 ========== String hexStr = HexUtils.encode("Hello"); // "Hello" → "48656c6c6f" (小写) String hexStrUpper = HexUtils.encode("Hello", false); // "Hello" → "48656C6C6F" (大写) // ========== 字节数组编码 ========== byte[] bytes = "World".getBytes(); String hexBytes = HexUtils.encode(bytes); // 字节数组 → 十六进制字符串 char[] hexChars = HexUtils.encodeToChar(bytes); // 字节数组 → 十六进制字符数组 // ========== 解码为字节数组 ========== byte[] decodedBytes = HexUtils.decode("48656c6c6f"); // 十六进制 → 字节数组 // ========== 解码为字符串 ========== String decodedStr = HexUtils.decodeToStr("48656c6c6f"); // 十六进制 → "Hello" // ========== 解码为长整型 ========== Long longVal = HexUtils.decodeToLong("75bcd15"); // 十六进制 → 123456789L Long longVal2 = HexUtils.decodeToLong("0x75bcd15"); // 支持 "0x" 前缀 ``` ### 15.3 字符串编码工具 (StrEncodeUtils) **功能**:提供字符串与字节数组在常用字符集(如 UTF-8)之间的转换,以及 URL 编码/解码、Unicode 转义序列处理,简化字符编码操作 **核心特性**: - UTF-8 转换:支持字符串与字节数组的双向转换,默认 UTF-8,可指定其他编码 - URL 编解码:对字符串进行 application/x-www-form-urlencoded 格式编码/解码,默认 UTF-8 - Unicode 转义:将字符串转换为 Unicode 转义序列(如 \u4e2d\u6587),并支持反向解析 #### 代码示例 ```java // ========== UTF-8 编码转换 ========== String str = "Hello 世界"; byte[] bytes = StrEncodeUtils.utf8EncodeToBytes(str); // 字符串 → UTF-8 字节数组 byte[] gbkBytes = StrEncodeUtils.utf8EncodeToBytes(str, "GBK"); // 使用指定编码 String fromBytes = StrEncodeUtils.utf8EncodeToStr(bytes); // 字节数组 → UTF-8 字符串 String fromGbk = StrEncodeUtils.utf8EncodeToStr(gbkBytes, "GBK"); // ========== URL 编解码 ========== String urlPart = "name=张三&age=18"; String encoded = StrEncodeUtils.urlEncode(urlPart); // name%3D%E5%BC%A0%E4%B8%89%26age%3D18 String decoded = StrEncodeUtils.urlDecode(encoded); // 还原为原字符串 // 指定 GBK 编码 String encodedGbk = StrEncodeUtils.urlEncode(urlPart, "GBK"); String decodedGbk = StrEncodeUtils.urlDecode(encodedGbk, "GBK"); // ========== Unicode 转义处理 ========== String chinese = "中文"; String unicode = StrEncodeUtils.unicodeEncode(chinese); // \u4e2d\u6587 String original = StrEncodeUtils.unicodeDecode(unicode); // 中文 // 混合普通字符和转义序列也可正确解析 String mixed = "ABC\\u4e2d\\u6587DEF"; String decodedMixed = StrEncodeUtils.unicodeDecode(mixed); // ABC中文DEF ``` [🔝 返回顶部](#pan-common-工具库) ## 16. 脱敏工具 (desensitize) ### 16.1 脱敏工具 (DesensitizeUtils) **功能**:提供灵活的数据脱敏能力,支持对单个字符串、JavaBean 对象、对象列表进行脱敏处理内置多种常见数据类型的脱敏规则(如姓名、手机号、身份证、银行卡、邮箱、地址等),并支持自定义脱敏规则 通过注解 `@Desensitize` 和 `@DesensitizeObject` 可方便地对对象字段进行声明式脱敏 **核心特性**: - 内置丰富规则:预定义 `8` 种脱敏类型(姓名、企业名、手机/固话、银行卡、身份证、邮箱、结构化地址、非结构化地址),覆盖绝大多数业务场景 - 注解驱动:在 JavaBean 字段上使用 `@Desensitize` 指定脱敏类型,使用 `@DesensitizeObject` 标记嵌套对象,实现深层脱敏 - 自定义规则:实现 DesensitizeRule 接口,可注册自定义脱敏逻辑,并通过 CUSTOM 类型或直接传入规则实例使用 #### 代码示例 ```java // ========== 字符串直接脱敏(快捷方法) ========== String name = DesensitizeUtils.userName("张三"); // "*三" String phone = DesensitizeUtils.phone("13811547561"); // "138*****561" String idCard = DesensitizeUtils.idCard("11010119900307663X"); // "110101********663X" String email = DesensitizeUtils.email("test@example.com"); // "te**@example.com" String bankCard = DesensitizeUtils.bankCard("6228480012345678"); // "6228********5678" String address = DesensitizeUtils.address("山东省济南市市中区泺源大街8-3-201"); // 结构化地址 String unstructured = DesensitizeUtils.unstructuredAddress("南街胡同口"); // 非结构化地址 String enterprise = DesensitizeUtils.enterpriseName("青岛金星化工厂"); // "青岛***化工厂" // ========== 使用注解对 JavaBean 脱敏 ========== public class User { @Desensitize(type = DesensitizeType.USER_NAME) private String name; @Desensitize(type = DesensitizeType.PHONE) private String phone; @DesensitizeObject private Address address; // 嵌套对象也会递归脱敏 // getter/setter... } User user = new User("张三", "13811547561", address); DesensitizeUtils.desensitize(user); // user.name 变为 "*三", user.phone 变为 "138*****561" // ========== 列表脱敏 ========== List userList = ...; DesensitizeUtils.desensitize(userList); // ========== 自定义规则脱敏 ========== // 实现 DesensitizeRule 接口 public class MyRule implements DesensitizeRule { @Override public String desensitize(String text) { return text.replaceAll("\\d", "*"); } } // 方式一:通过注解指定自定义规则类 @Desensitize(type = DesensitizeType.CUSTOM, customRule = MyRule.class) private String customField; // 方式二:直接传入规则实例 String result = DesensitizeUtils.desensitize("abc123", new MyRule()); // "abc***" DesensitizeUtils.desensitize(userList, new MyRule()); // 对整个列表应用自定义规则 ``` [🔝 返回顶部](#pan-common-工具库) ## 17. 图片工具 (image) ### 17.1 图片工具 (ImageUtils) **功能**:提供图片操作的功能,如缩放、裁剪、旋转等 #### 代码示例 ```java TODO ``` ### 17.2 验证码工具 (ImageCodeUtils) **功能**:提供验证码生成的功能 #### 代码示例 ```java TODO ``` ### 17.3 二维码工具 (ImageQrCodeUtils) **功能**:提供二维码生成和解析的功能 #### 代码示例 ```java TODO ``` [🔝 返回顶部](#pan-common-工具库) ## 18. ID生成工具 (id) ### 18.1 ID工具 (IdUtils) **功能**:提供支持标准UUID、简洁UUID以及雪花算法(Snowflake)ID,满足不同场景对ID唯一性、有序性及可读性的需求 **核心特性**: - UUID生成:支持标准格式(含连字符)和无连字符的简洁格式 - 雪花算法ID:基于Twitter Snowflake算法实现,生成全局唯一、趋势递增的64位长整型ID,支持自定义机器节点和数据中心标识 #### 代码示例 ```java // ========== UUID 生成 ========== String uuid = IdUtils.getUuid(); // 标准格式:550e8400-e29b-41d4-a716-446655440000 String simpleUuid = IdUtils.getSimpleUuid(); // 无连字符:550e8400e29b41d4a716446655440000 // ========== 雪花算法 ID ========== long snowId = IdUtils.snowFlakeId(); // 默认机器节点5,数据中心31 String snowIdStr = IdUtils.snowFlakeIdStr(); // 字符串形式 // 指定机器节点和数据中心(0-31) long customSnowId = IdUtils.snowFlakeId(1L, 1L); String customSnowIdStr = IdUtils.snowFlakeIdStr(2L, 3L); // 解析雪花ID的生成时间 String date = IdUtils.getSnowFlakeIdDate(snowId); // 默认格式:yyyy-MM-dd HH:mm:ss String customDate = IdUtils.getSnowFlakeIdDate(snowId, "yyyy/MM/dd HH:mm"); // 自定义格式 ``` [🔝 返回顶部](#pan-common-工具库) ## 19. 树结构工具 (tree) ### 19.1 树工具 (TreeUtils) **功能**:供将平铺列表转换为树形结构的能力,支持通过注解标记节点ID、父ID及字段别名,可自定义输出深度和排序,适用于菜单、组织架构、分类等树形数据的构建 **核心特性**: - 注解驱动:使用 @TreeId 标记节点唯一标识字段,@TreeParentId 标记父节点标识字段,@TreeAlias 自定义输出字段名 - 灵活输出:默认返回包含 children 子节点列表的树形 Map 结构,可自定义深度(0或负数表示无限深度),默认递归所有层级,也可通过参数限制最大深度 - 排序支持:可选择按节点ID升序排序,保证输出顺序 - 类型安全:基于反射和泛型,适用于任意 JavaBean 类型 **注解说明:** - @TreeId:标记字段为节点唯一标识 alias:指定输出时的字段别名,默认使用字段名 - @TreeParentId:标记字段为父节点标识 无属性 - @TreeAlias:自定义字段输出别名(可附加在任意字段上) alias:指定别名 #### 代码示例 ```java // 1.定义节点实体类 public class Menu { @TreeId(alias = "id") private Integer menuId; @TreeParentId private Integer parentId; @TreeAlias(alias = "name") private String menuName; private Integer sort; // getter/setter... }; // 2.构建树形结构 List menuList = ...; // 从数据库查询得到平铺列表 // 默认构建(无限深度,不排序) List> tree = TreeUtils.create(menuList); // 按ID升序排序,无限深度 List> sortedTree = TreeUtils.create(menuList, true); // 限制最大深度为3层,并按ID排序 List> limitedTree = TreeUtils.create(menuList, 3, true); // 3.输出结果示例 /* [ { "id": 1, "name": "系统管理", "sort": 1, "children": [ { "id": 2, "name": "用户管理", "sort": 2, "children": [] } ] } ] */ ``` [🔝 返回顶部](#pan-common-工具库) ## 20. 单位工具 (unit) ### 20.1 数据大小工具 (DataSize) **功能**:提供数据大小(字节、KB、MB、GB、TB、PB)的解析与单位转换能力支持从带单位的字符串(如 "1024KB")解析为字节数,并可灵活转换为任意目标单位,适用于文件大小、存储容量等场景 #### 代码示例 ```java // ========== 解析并转换为指定单位 ========== DataSize size = DataSize.transform("1024KB", DataSizeUnit.MB); Long mb = size.getLength(); // 1 String result = size.toString(); // "1MB" // ========== 解析并转换为字节(默认) ========== DataSize bytes = DataSize.transform("1.5MB"); Long byteCount = bytes.getLength(); // 1572864 (1.5 * 1024 * 1024) String byteStr = bytes.toString(); // "1572864B" // ========== 链式转换 ========== DataSize size2 = DataSize.create("2048KB") .transform(DataSizeUnit.MB); // 先解析为KB,再转换为MB Long mb2 = size2.getLength(); // 2 // ========== 获取转换后的数值或字符串 ========== DataSize gbSize = DataSize.transform("1073741824B", DataSizeUnit.GB); gbSize.getLength(); // 1 gbSize.toString(); // "1GB" ``` ### 21.2 字节可读性工具 (ByteReadable) **功能**:将字节大小转换为可读的字符串格式(如 1.5 KB、2.3 MB、4.1 GB、7.8 TB、9.2 PB)适用于展示文件大小、内存占用、网络流量等场景,提升数据可读性 **核心特性**: - 多种输入:支持 String、int、long、BigDecimal 形式的字节数输入 - 全单位覆盖:自动根据字节数选择最合适的单位(B、KB、MB、GB、TB、PB),并保留一位小数(四舍五入) #### 代码示例 ```java // ========== 转换为可读字符串 ========== String r1 = ByteReadable.convert(1024); // "1.0 KB" String r2 = ByteReadable.convert(1536); // "1.5 KB" String r3 = ByteReadable.convert(1048576); // "1.0 MB" String r4 = ByteReadable.convert(1073741824L); // "1.0 GB" String r5 = ByteReadable.convert(1099511627776L); // "1.0 TB" String r6 = ByteReadable.convert("1125899906842624"); // "1.0 PB" // ========== 使用 BigDecimal 输入 ========== BigDecimal size = new BigDecimal("3221225472"); String r7 = ByteReadable.convert(size); // "3.0 GB" ``` [🔝 返回顶部](#pan-common-工具库) ## 21. Spring相关 ### 21.1 通用请求对象 (RequestVO) **功能**: 提供统一的请求参数封装对象,无需在`Spring`中手动注入配置即可使用参数解析器,将不同 Content-Type(如 JSON、表单、FormData、XML、文本、文件流)的请求参数统一转换为 RequestVO,简化 Controller 层参数获取内置分页对象、请求头、文件信息等,适用于 RESTful API 及传统 Web 应用 **核心特性**: - 多类型支持:自动识别:路径解析(PathVariable)、 JSON、表单、FormData(含文件)、XML、纯文本、文件流等请求格式 - 统一参数访问:通过 get(String key)、getStr(String key)、getInt(String key) 等方法获取参数,无需关心原始格式 - 对象转换:支持将请求参数直接转换为 JavaBean(toBean(Class)),JSON 格式自动反序列化 - 分页封装:内置 Pager 对象,自动解析分页参数 - 文件处理:对于 FormData 请求,自动封装为 WebFile 对象,便于获取上传文件信息 - XML 支持:可获取 XML 格式的请求体,并转换为 JavaBean - 嵌套 JSON 访问:支持点号语法(如 user.address.city)访问嵌套 JSON 对象的属性 - 注解驱动:在 Controller 方法参数上使用 @RequestParamVo 标记,自动注入 RequestVO 实例 #### 代码示例 ```java @RestController public class UserController { @PostMapping("/user") public ResultVO createUser(@RequestParamVo RequestVO vo) { // 获取请求头 RequestHeader header = vo.getHeader(); String token = header.get("Authorization"); String userAgent = header.get("User-Agent"); // 获取纯文本内容 String text = vo.getText(); // 获取原始请求体字符串 String body = vo.getBody(); // 获取数组参数,例如 URL: /array?tags=java,spring&ids=1,2,3 String[] tags = vo.getArrayStr("tags"); Integer[] ids = vo.getArrayInt("ids"); // 获取单个参数,自动解析form和Json格式 String name = vo.getStr("name"); Integer age = vo.getInt("age"); // 转换为 JavaBean,自动解析form和Json格式 User user = vo.toBean(User.class); // 获取分页信息 Pager pager = vo.getPage(); int page = pager.getPage(); int limit = pager.getLimit(); // 获取上传文件(适用于 FormData) WebFile file = vo.getWebFile(); if (file != null) { byte[] content = file.getContent(); String fileName = file.getFileName(); } // 转换 JSON 数组为 List // 请求体为 JSON 数组,如 [{"name":"张三"},{"name":"李四"}] List users = vo.toListBean(User.class); // 获取JSON嵌套对象中某个单一属性 String city = vo.getJsonObject("user.address.city", String.class); // 获取原始 XML 字符串 String xml = vo.getXml(); // 将 XML 转换为 JavaBean(默认根节点 ) MyXmlBean bean = vo.getXml(MyXmlBean.class); // 自定义根节点别名(如 ) MyXmlBean bean2 = vo.getXml(MyXmlBean.class, "root"); return ResultVO.suc(user); } } ``` ### 21.2 通用响应对象 (ResultVO) **功能**:提供统一且规范的 API 响应结果封装,包含状态码、消息提示、业务数据及分页信息快速构建成功或失败响应 **核心特性**: - 标准字段:code(状态码)、msg(消息)、data(数据)、pager(分页信息) - 静态方法:提供丰富的 suc() 和 fail() 重载,支持自定义状态码、消息、数据和分页 - JSON 空值忽略:使用 @JsonInclude(Include.NON_EMPTY) 注解,data 和 pager 为空时不参与序列化,保持响应简洁 - 分页支持:可附带 Pager 对象,方便分页查询结果返回 - 类型安全:支持任意类型的数据承载,类型安全 #### 代码示例 **成功响应 (suc)** ```java // 无数据成功(默认状态码 200,消息 "success") ResultVO res1 = ResultVO.suc(); // 自定义消息 ResultVO res2 = ResultVO.suc("操作成功"); // 带数据 ResultVO res3 = ResultVO.suc(user); // 带数据和分页 Pager pager = new Pager(1, 10, 1, 10); ResultVO> res4 = ResultVO.suc(userList, pager); // 自定义状态码和消息 ResultVO res5 = ResultVO.suc(200, "自定义成功"); // 自定义状态码、消息和数据 ResultVO res6 = ResultVO.suc(200, "登录成功", user); ``` **失败响应 (fail)** ```java // 默认失败(状态码 500,消息 "error") ResultVO err1 = ResultVO.fail(); // 自定义消息 ResultVO err2 = ResultVO.fail("参数错误"); // 带错误数据 ResultVO> err3 = ResultVO.fail(errorMap); // 自定义状态码和消息 ResultVO err4 = ResultVO.fail(400, "请求无效"); // 使用预定义异常枚举 ResultVO err5 = ResultVO.fail(ExcMessage.PARAM_MISSING); ``` **综合示例** ```java @RestController @RequestMapping("/api/users") public class UserController { @GetMapping("/{id}") public ResultVO getUser(@PathVariable Long id) { User user = userService.findById(id); if (user == null) { return ResultVO.fail(404, "用户不存在"); } return ResultVO.suc(user); } @GetMapping public ResultVO> listUsers(@RequestParam(defaultValue = "1") int page, @RequestParam(defaultValue = "10") int limit) { PageResult pageResult = userService.list(page, limit); Pager pager = new Pager(pageResult.getPage(), pageResult.getLimit(), pageResult.getTotal(), pageResult.getTotalPages()); return ResultVO.suc(pageResult.getData(), pager); } @PostMapping public ResultVO createUser(@RequestBody User user) { User created = userService.create(user); return ResultVO.suc(201, "创建成功", created); } } ``` ### 21.3 上下文工具 (BeanContextUtils) **功能**:提供 Spring 应用上下文和 BeanFactory 的静态访问,支持获取 Spring 容器中的 Bean、当前激活的 Profile 以及 AOP 代理对象通过实现 ApplicationContextAware 和 BeanFactoryPostProcessor,在容器启动时自动注入上下文,确保在任何地方均可便捷获取 Spring 管理的 Bean **核心特性**: - 静态获取 Bean:支持按名称、类型或名称+类型获取 Bean,避免频繁注入 ApplicationContext - 环境信息获取:获取当前激活的 Profile 列表或第一个 Profile - AOP 代理支持:通过 AopContext.currentProxy() 获取当前类的 AOP 代理对象(需开启 exposeProxy) - 自动注入:无需手动配置,Spring 启动时自动注册 #### 代码示例 ```java // 1. 获取 Spring 管理的 Bean UserService userService = BeanContextUtils.getBean(UserService.class); // 2. 按名称获取 Bean Object bean = BeanContextUtils.getBean("userService"); // 3. 获取当前激活的 Profile String[] activeProfiles = BeanContextUtils.getActiveProfiles(); String activeProfile = BeanContextUtils.getActiveProfile(); // 4. 获取 AOP 代理对象(需在配置中开启 exposeProxy:@EnableAspectJAutoProxy(exposeProxy = true)) UserService proxy = BeanContextUtils.getAopProxy(); ``` [🔝 返回顶部](#pan-common-工具库) ## 22. 数据交换 (dataformat) ### 22.1 JSON (JsonUtils) **功能**:提供基于 Fastjson 的 JSON 解析与生成工具,支持 JavaBean、List、Map 与 JSON 字符串之间的双向转换,以及 JSON 节点提取、格式校验等常用操作 **注意事项:** - 底层基于 Fastjson 1.x 实现,需确保项目已引入 fastjson 依赖 #### 代码示例 ```java // 解析为 JavaBean User user = JsonUtils.toBean("{\"name\":\"张三\",\"age\":25}", User.class); // 解析为 List List userList = JsonUtils.toList("[{\"name\":\"张三\"},{\"name\":\"李四\"}]", User.class); // 解析为 Map Map map = JsonUtils.toMap("{\"key\":\"value\"}"); Map orderedMap = JsonUtils.toMapOrder(json); // 保持原顺序 Map sortedMap = JsonUtils.toSortMap(json); // 按键排序 // 解析为 List List> listMap = JsonUtils.toListMap(json); // 使用 TypeReference 解析复杂泛型 Result> result = JsonUtils.toBean(json, new TypeReference>>(){}); // 对象转 JSON 字符串 String json = JsonUtils.toJson(user); // List 转 JSON 数组(带格式) List users = Arrays.asList(user1, user2); StringBuilder formatted = JsonUtils.toJsonSplit(users, ",\n", true); // 元素间加逗号和换行 // List 转 JSON 字节数组 byte[] bytes = JsonUtils.toBytes(users); // 完整 JSON 数组 byte[] singleBytes = JsonUtils.toSingleBeanBytes(users); // 每行一个 JSON 对象(换行分隔) // 获取 JSON 节点值 JSONObject obj = JsonUtils.get("{\"a\":1,\"b\":2}"); String nodeValue = JsonUtils.getNode(json, "user.name"); // 校验 JSON 合法性 boolean isValid = JsonUtils.hasJson("{\"name\":\"张三\"}"); // true boolean isInvalid = JsonUtils.hasJson("abc"); // false ``` ### 22.2 XML (XStreamUtils) **功能**:基于 XStream 的 XML 与 JavaBean 转换工具,支持通过注解配置别名和 CDATA 节,提供简洁的 API 实现对象与 XML 的双向转换,并支持 XML 片段提取 #### 代码示例 ```java // XML 转 JavaBean String xml = "张三25"; User user = XStreamUtils.toBean(xml, User.class); // 指定编码(如 GBK) User user2 = XStreamUtils.toBean(xml, User.class, "GBK"); // JavaBean 转 XML User user = new User("李四", 30); String xml = XStreamUtils.toXml(user); // List 转 XML List list = Arrays.asList(user1, user2); String xmlList = XStreamUtils.toXml(list); // 提取 XML 片段并反转义 String fullXml = "张三"; String dataXml = XStreamUtils.filterXml(fullXml, "data"); // 返回 "张三" // 实体类注解示例: @XStreamAlias("user") public class User { @XStreamAlias("name") private String name; @XStreamAlias("age") private Integer age; @XStreamCDATA @XStreamAlias("remark") private String remark; // 该字段在 XML 中会用 CDATA 包裹 } ``` [🔝 返回顶部](#pan-common-工具库) ## 🤝贡献 - ### 🐞 **反馈或建议** ​ 如果发现了BUG,或者有新的功能建议,欢迎在 [Issues](https://gitee.com/apanlh/pan-common/issues) 中提出欢迎社区的每一位贡献者一同改进和完善该工具库 [🔝 返回顶部](#pan-common-工具库) ## 📧 联系方式 - **作者:** Pan - **邮箱:** [582084583@qq.com](mailto:582084583@qq.com) ## 💖 支持 如果觉得该工具类不错,可以点击一下右上角的 ⭐ 小星星,感谢(*^▽^*)😄,希望它能在你的项目中发挥重要作用😊😊 [🔝 返回顶部](#pan-common-工具库)