mirror of
https://gitee.com/chinabugotech/hutool.git
synced 2025-05-09 23:51:34 +08:00
Merge remote-tracking branch 'origin/v6-dev' into v6-dev
This commit is contained in:
commit
a907a439f9
@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
-------------------------------------------------------------------------------------------------------------
|
-------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
# 6.0.0-M10 (2023-12-28)
|
# 6.0.0-M11 (2024-01-11)
|
||||||
|
|
||||||
### 计划实现
|
### 计划实现
|
||||||
* 【poi 】 Markdown相关(如HTML转换等),基于commonmark-java
|
* 【poi 】 Markdown相关(如HTML转换等),基于commonmark-java
|
||||||
|
@ -144,18 +144,18 @@ We provide the T-Shirt and Sweater with Hutool Logo, please visit the shop:
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.dromara.hutool</groupId>
|
<groupId>org.dromara.hutool</groupId>
|
||||||
<artifactId>hutool-all</artifactId>
|
<artifactId>hutool-all</artifactId>
|
||||||
<version>6.0.0-M10</version>
|
<version>6.0.0-M11</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
```
|
```
|
||||||
|
|
||||||
### 🍐Gradle
|
### 🍐Gradle
|
||||||
```
|
```
|
||||||
implementation 'org.dromara.hutool:hutool-all:6.0.0-M10'
|
implementation 'org.dromara.hutool:hutool-all:6.0.0-M11'
|
||||||
```
|
```
|
||||||
|
|
||||||
## 📥Download
|
## 📥Download
|
||||||
|
|
||||||
- [Maven Repo](https://repo1.maven.org/maven2/cn/hutool/hutool-all/6.0.0-M10/)
|
- [Maven Repo](https://repo1.maven.org/maven2/cn/hutool/hutool-all/6.0.0-M11/)
|
||||||
|
|
||||||
> 🔔️note:
|
> 🔔️note:
|
||||||
> Hutool 5.x supports JDK8+ and is not tested on Android platforms, and cannot guarantee that all tool classes or tool methods are available.
|
> Hutool 5.x supports JDK8+ and is not tested on Android platforms, and cannot guarantee that all tool classes or tool methods are available.
|
||||||
|
@ -139,21 +139,21 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.dromara.hutool</groupId>
|
<groupId>org.dromara.hutool</groupId>
|
||||||
<artifactId>hutool-all</artifactId>
|
<artifactId>hutool-all</artifactId>
|
||||||
<version>6.0.0-M10</version>
|
<version>6.0.0-M11</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
```
|
```
|
||||||
|
|
||||||
### 🍐Gradle
|
### 🍐Gradle
|
||||||
|
|
||||||
```
|
```
|
||||||
implementation 'org.dromara.hutool:hutool-all:6.0.0-M10'
|
implementation 'org.dromara.hutool:hutool-all:6.0.0-M11'
|
||||||
```
|
```
|
||||||
|
|
||||||
### 📥下载jar
|
### 📥下载jar
|
||||||
|
|
||||||
点击以下链接,下载`hutool-all-X.X.X.jar`即可:
|
点击以下链接,下载`hutool-all-X.X.X.jar`即可:
|
||||||
|
|
||||||
- [Maven中央库](https://repo1.maven.org/maven2/org/dromara/hutool/hutool-all/6.0.0-M10/)
|
- [Maven中央库](https://repo1.maven.org/maven2/org/dromara/hutool/hutool-all/6.0.0-M11/)
|
||||||
|
|
||||||
> 🔔️注意
|
> 🔔️注意
|
||||||
> Hutool 6.x支持JDK8+,对Android平台没有测试,不能保证所有工具类或工具方法可用。
|
> Hutool 6.x支持JDK8+,对Android平台没有测试,不能保证所有工具类或工具方法可用。
|
||||||
|
@ -1 +1 @@
|
|||||||
6.0.0-M10
|
6.0.0-M11
|
||||||
|
@ -1 +1 @@
|
|||||||
var version = '6.0.0-M10'
|
var version = '6.0.0-M11'
|
@ -21,7 +21,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>org.dromara.hutool</groupId>
|
<groupId>org.dromara.hutool</groupId>
|
||||||
<artifactId>hutool-parent</artifactId>
|
<artifactId>hutool-parent</artifactId>
|
||||||
<version>6.0.0-M10</version>
|
<version>6.0.0-M11</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<artifactId>hutool-all</artifactId>
|
<artifactId>hutool-all</artifactId>
|
||||||
|
@ -21,7 +21,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>org.dromara.hutool</groupId>
|
<groupId>org.dromara.hutool</groupId>
|
||||||
<artifactId>hutool-parent</artifactId>
|
<artifactId>hutool-parent</artifactId>
|
||||||
<version>6.0.0-M10</version>
|
<version>6.0.0-M11</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<artifactId>hutool-bom</artifactId>
|
<artifactId>hutool-bom</artifactId>
|
||||||
|
@ -21,7 +21,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>org.dromara.hutool</groupId>
|
<groupId>org.dromara.hutool</groupId>
|
||||||
<artifactId>hutool-parent</artifactId>
|
<artifactId>hutool-parent</artifactId>
|
||||||
<version>6.0.0-M10</version>
|
<version>6.0.0-M11</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<artifactId>hutool-core</artifactId>
|
<artifactId>hutool-core</artifactId>
|
||||||
|
@ -33,6 +33,7 @@ import org.dromara.hutool.core.util.ObjUtil;
|
|||||||
|
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.nio.file.FileSystem;
|
import java.nio.file.FileSystem;
|
||||||
import java.nio.file.*;
|
import java.nio.file.*;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@ -726,7 +727,7 @@ public class ZipUtil {
|
|||||||
* Gzip压缩处理
|
* Gzip压缩处理
|
||||||
*
|
*
|
||||||
* @param content 被压缩的字符串
|
* @param content 被压缩的字符串
|
||||||
* @param charset 编码
|
* @param charset 编码 {@link StandardCharsets#UTF_8}、 {@link CharsetUtil#UTF_8}
|
||||||
* @return 压缩后的字节流
|
* @return 压缩后的字节流
|
||||||
* @throws HutoolException IO异常
|
* @throws HutoolException IO异常
|
||||||
*/
|
*/
|
||||||
|
@ -13,7 +13,8 @@
|
|||||||
package org.dromara.hutool.core.lang.wrapper;
|
package org.dromara.hutool.core.lang.wrapper;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 简单包装对象
|
* 简单包装对象<br>
|
||||||
|
* 通过继承此类,可以直接使用被包装的对象,用于简化和统一封装。
|
||||||
*
|
*
|
||||||
* @param <T> 被包装对象类型
|
* @param <T> 被包装对象类型
|
||||||
* @author looly
|
* @author looly
|
||||||
|
@ -297,6 +297,7 @@ public class NumberParser {
|
|||||||
// issue#I79VS7
|
// issue#I79VS7
|
||||||
numberStr = StrUtil.subSuf(numberStr, 1);
|
numberStr = StrUtil.subSuf(numberStr, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final NumberFormat format = NumberFormat.getInstance(locale);
|
final NumberFormat format = NumberFormat.getInstance(locale);
|
||||||
if (format instanceof DecimalFormat) {
|
if (format instanceof DecimalFormat) {
|
||||||
|
@ -898,12 +898,12 @@ public class NumberUtil extends NumberValidator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Float、Double等有精度问题,转换为字符串后再转换
|
// Float、Double等有精度问题,转换为字符串后再转换
|
||||||
return toBigDecimal(number.toString());
|
return new BigDecimal(number.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 数字转{@link BigDecimal}<br>
|
* 数字转{@link BigDecimal}<br>
|
||||||
* null或""或空白符转换为0
|
* null或""或"NaN"或空白符转换为0
|
||||||
*
|
*
|
||||||
* @param numberStr 数字字符串
|
* @param numberStr 数字字符串
|
||||||
* @return {@link BigDecimal}
|
* @return {@link BigDecimal}
|
||||||
@ -927,7 +927,7 @@ public class NumberUtil extends NumberValidator {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 数字转{@link BigInteger}<br>
|
* 数字转{@link BigInteger}<br>
|
||||||
* null转换为0
|
* null或"NaN"转换为0
|
||||||
*
|
*
|
||||||
* @param number 数字
|
* @param number 数字
|
||||||
* @return {@link BigInteger}
|
* @return {@link BigInteger}
|
||||||
|
@ -15,6 +15,7 @@ package org.dromara.hutool.core.text;
|
|||||||
import org.dromara.hutool.core.array.ArrayUtil;
|
import org.dromara.hutool.core.array.ArrayUtil;
|
||||||
import org.dromara.hutool.core.func.FunctionPool;
|
import org.dromara.hutool.core.func.FunctionPool;
|
||||||
import org.dromara.hutool.core.text.placeholder.StrFormatter;
|
import org.dromara.hutool.core.text.placeholder.StrFormatter;
|
||||||
|
import org.dromara.hutool.core.text.split.SplitUtil;
|
||||||
import org.dromara.hutool.core.util.CharsetUtil;
|
import org.dromara.hutool.core.util.CharsetUtil;
|
||||||
|
|
||||||
import java.io.StringReader;
|
import java.io.StringReader;
|
||||||
@ -27,6 +28,13 @@ import java.util.Map;
|
|||||||
* 字符串工具类<br>
|
* 字符串工具类<br>
|
||||||
* 此工具主要针对单个字符串的操作
|
* 此工具主要针对单个字符串的操作
|
||||||
*
|
*
|
||||||
|
* <p>本工具类,v6.x进行了拆分。
|
||||||
|
* 字符串分割<strong>split</strong>参考:{@link SplitUtil} <br>
|
||||||
|
* 多字符串判空<strong>hasBlank</strong>参考:{@link ArrayUtil}
|
||||||
|
* </p>
|
||||||
|
* @see SplitUtil#split(CharSequence, CharSequence) 对字符串分割
|
||||||
|
* @see ArrayUtil#hasBlank(CharSequence...) 对多个字符串判空
|
||||||
|
*
|
||||||
* @author Looly
|
* @author Looly
|
||||||
*/
|
*/
|
||||||
public class StrUtil extends CharSequenceUtil implements StrPool {
|
public class StrUtil extends CharSequenceUtil implements StrPool {
|
||||||
|
@ -12,6 +12,8 @@
|
|||||||
|
|
||||||
package org.dromara.hutool.core.xml;
|
package org.dromara.hutool.core.xml;
|
||||||
|
|
||||||
|
import org.dromara.hutool.core.text.CharUtil;
|
||||||
|
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -30,6 +32,10 @@ public class XmlConstants {
|
|||||||
* 字符串常量:XML And 符转义 {@code "&" -> "&"}
|
* 字符串常量:XML And 符转义 {@code "&" -> "&"}
|
||||||
*/
|
*/
|
||||||
public static final String AMP = "&";
|
public static final String AMP = "&";
|
||||||
|
/**
|
||||||
|
* The Character '&'.
|
||||||
|
*/
|
||||||
|
public static final Character C_AMP = CharUtil.AMP;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 字符串常量:XML 双引号转义 {@code """ -> "\""}
|
* 字符串常量:XML 双引号转义 {@code """ -> "\""}
|
||||||
@ -40,17 +46,41 @@ public class XmlConstants {
|
|||||||
* 字符串常量:XML 单引号转义 {@code "&apos" -> "'"}
|
* 字符串常量:XML 单引号转义 {@code "&apos" -> "'"}
|
||||||
*/
|
*/
|
||||||
public static final String APOS = "'";
|
public static final String APOS = "'";
|
||||||
|
/**
|
||||||
|
* The Character '''.
|
||||||
|
*/
|
||||||
|
public static final Character C_APOS = CharUtil.SINGLE_QUOTE;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 字符串常量:XML 小于号转义 {@code "<" -> "<"}
|
* 字符串常量:XML 小于号转义 {@code "<" -> "<"}
|
||||||
*/
|
*/
|
||||||
public static final String LT = "<";
|
public static final String LT = "<";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Character '<'.
|
||||||
|
*/
|
||||||
|
public static final Character C_LT = '<';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 字符串常量:XML 大于号转义 {@code ">" -> ">"}
|
* 字符串常量:XML 大于号转义 {@code ">" -> ">"}
|
||||||
*/
|
*/
|
||||||
public static final String GT = ">";
|
public static final String GT = ">";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Character '>'.
|
||||||
|
*/
|
||||||
|
public static final Character C_GT = '>';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Character '!'.
|
||||||
|
*/
|
||||||
|
public static final Character C_BANG = '!';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Character '?'.
|
||||||
|
*/
|
||||||
|
public static final Character C_QUEST = '?';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 在XML中无效的字符 正则
|
* 在XML中无效的字符 正则
|
||||||
*/
|
*/
|
||||||
|
@ -0,0 +1,28 @@
|
|||||||
|
package org.dromara.hutool.core.math;
|
||||||
|
|
||||||
|
import org.dromara.hutool.core.lang.Console;
|
||||||
|
import org.junit.jupiter.api.Assertions;
|
||||||
|
import org.junit.jupiter.api.Disabled;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.text.DecimalFormat;
|
||||||
|
import java.text.NumberFormat;
|
||||||
|
import java.text.ParseException;
|
||||||
|
|
||||||
|
public class Issue3423Test {
|
||||||
|
@Test
|
||||||
|
public void toBigDecimalOfNaNTest() {
|
||||||
|
final BigDecimal naN = NumberUtil.toBigDecimal("NaN");
|
||||||
|
Assertions.assertEquals(BigDecimal.ZERO, naN);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Disabled
|
||||||
|
public void toBigDecimalOfNaNTest2() throws ParseException {
|
||||||
|
final NumberFormat format = NumberFormat.getInstance();
|
||||||
|
((DecimalFormat) format).setParseBigDecimal(true);
|
||||||
|
final Number naN = format.parse("NaN");
|
||||||
|
Console.log(naN.getClass());
|
||||||
|
}
|
||||||
|
}
|
@ -21,7 +21,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>org.dromara.hutool</groupId>
|
<groupId>org.dromara.hutool</groupId>
|
||||||
<artifactId>hutool-parent</artifactId>
|
<artifactId>hutool-parent</artifactId>
|
||||||
<version>6.0.0-M10</version>
|
<version>6.0.0-M11</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<artifactId>hutool-cron</artifactId>
|
<artifactId>hutool-cron</artifactId>
|
||||||
|
@ -31,6 +31,9 @@ public class CronConfig {
|
|||||||
*/
|
*/
|
||||||
protected boolean matchSecond;
|
protected boolean matchSecond;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构造
|
||||||
|
*/
|
||||||
public CronConfig(){
|
public CronConfig(){
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
package org.dromara.hutool.cron;
|
package org.dromara.hutool.cron;
|
||||||
|
|
||||||
import org.dromara.hutool.core.date.DateUnit;
|
import org.dromara.hutool.core.date.DateUnit;
|
||||||
|
import org.dromara.hutool.core.lang.Console;
|
||||||
import org.dromara.hutool.core.thread.ThreadUtil;
|
import org.dromara.hutool.core.thread.ThreadUtil;
|
||||||
import org.dromara.hutool.log.Log;
|
import org.dromara.hutool.log.Log;
|
||||||
|
|
||||||
@ -63,9 +64,14 @@ public class CronTimer extends Thread implements Serializable {
|
|||||||
//等待直到下一个时间点,如果被中断直接退出Timer
|
//等待直到下一个时间点,如果被中断直接退出Timer
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
//执行点,时间记录为执行开始的时间,而非结束时间
|
//执行点,时间记录为执行开始的时间,而非结束时间
|
||||||
thisTime = System.currentTimeMillis();
|
spawnLauncher(nextTime);
|
||||||
spawnLauncher(thisTime);
|
|
||||||
|
// issue#3460 采用叠加方式,确保正好是1分钟或1秒,避免sleep晚醒问题
|
||||||
|
// 此处无需校验,因为每次循环都是sleep与上触发点的时间差。
|
||||||
|
// 当上一次晚醒后,本次会减少sleep时间,保证误差在一个unit内,并不断修正。
|
||||||
|
thisTime = nextTime;
|
||||||
} else{
|
} else{
|
||||||
// 非正常时间重新计算(issue#1224@Github)
|
// 非正常时间重新计算(issue#1224@Github)
|
||||||
thisTime = System.currentTimeMillis();
|
thisTime = System.currentTimeMillis();
|
||||||
|
@ -19,7 +19,7 @@ import org.dromara.hutool.cron.task.InvokeTask;
|
|||||||
public class DeamonMainTest {
|
public class DeamonMainTest {
|
||||||
public static void main(final String[] args) {
|
public static void main(final String[] args) {
|
||||||
// 测试守护线程是否对作业线程有效
|
// 测试守护线程是否对作业线程有效
|
||||||
CronUtil.schedule("*/2 * * * * *", new InvokeTask("demo.org.dromara.hutool.cron.TestJob.doWhileTest"));
|
CronUtil.schedule("*/2 * * * * *", new InvokeTask("org.dromara.hutool.cron.demo.TestJob.doWhileTest"));
|
||||||
// 当为守护线程时,stop方法调用后doWhileTest里的循环输出将终止,表示作业线程正常结束
|
// 当为守护线程时,stop方法调用后doWhileTest里的循环输出将终止,表示作业线程正常结束
|
||||||
// 当非守护线程时,stop方法调用后,不再产生新的作业,原作业正常执行。
|
// 当非守护线程时,stop方法调用后,不再产生新的作业,原作业正常执行。
|
||||||
CronUtil.setMatchSecond(true);
|
CronUtil.setMatchSecond(true);
|
||||||
|
@ -14,6 +14,7 @@ package org.dromara.hutool.cron.demo;
|
|||||||
|
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import org.dromara.hutool.core.date.DateUtil;
|
||||||
import org.dromara.hutool.core.lang.Console;
|
import org.dromara.hutool.core.lang.Console;
|
||||||
import org.dromara.hutool.core.thread.ThreadUtil;
|
import org.dromara.hutool.core.thread.ThreadUtil;
|
||||||
|
|
||||||
@ -29,7 +30,7 @@ public class TestJob2 {
|
|||||||
* 执行定时任务内容
|
* 执行定时任务内容
|
||||||
*/
|
*/
|
||||||
public void doTest() {
|
public void doTest() {
|
||||||
Console.log("TestJob2.doTest开始执行……");
|
Console.log("TestJob2.doTest开始执行…… at [{}]", DateUtil.formatNow());
|
||||||
ThreadUtil.sleep(20, TimeUnit.SECONDS);
|
ThreadUtil.sleep(20, TimeUnit.SECONDS);
|
||||||
Console.log("延迟20s打印testJob2");
|
Console.log("延迟20s打印testJob2");
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
# demo.org.dromara.hutool.cron.TestJob.doTest = */1 * * * * *
|
# demo.org.dromara.hutool.cron.TestJob.doTest = */1 * * * * *
|
||||||
|
|
||||||
[org.dromara.hutool.cron.demo]=
|
[org.dromara.hutool.cron.demo]
|
||||||
# 6位表达式在秒匹配模式下可用,此处表示每秒执行一次
|
# 6位表达式在秒匹配模式下可用,此处表示每秒执行一次
|
||||||
# TestJob.doTest = */1 * * * * *
|
# TestJob.doTest = */1 * * * * *
|
||||||
# 5位表达式在分匹配模式下可用,此处表示每分钟执行一次
|
# 5位表达式在分匹配模式下可用,此处表示每分钟执行一次
|
||||||
|
@ -21,7 +21,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>org.dromara.hutool</groupId>
|
<groupId>org.dromara.hutool</groupId>
|
||||||
<artifactId>hutool-parent</artifactId>
|
<artifactId>hutool-parent</artifactId>
|
||||||
<version>6.0.0-M10</version>
|
<version>6.0.0-M11</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<artifactId>hutool-crypto</artifactId>
|
<artifactId>hutool-crypto</artifactId>
|
||||||
@ -31,7 +31,7 @@
|
|||||||
<properties>
|
<properties>
|
||||||
<Automatic-Module-Name>org.dromara.hutool.crypto</Automatic-Module-Name>
|
<Automatic-Module-Name>org.dromara.hutool.crypto</Automatic-Module-Name>
|
||||||
<!-- versions -->
|
<!-- versions -->
|
||||||
<bouncycastle.version>1.76</bouncycastle.version>
|
<bouncycastle.version>1.77</bouncycastle.version>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
|
@ -0,0 +1,104 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2024. looly(loolly@aliyun.com)
|
||||||
|
* Hutool is licensed under Mulan PSL v2.
|
||||||
|
* You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||||||
|
* You may obtain a copy of Mulan PSL v2 at:
|
||||||
|
* https://license.coscl.org.cn/MulanPSL2
|
||||||
|
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
||||||
|
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
||||||
|
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
||||||
|
* See the Mulan PSL v2 for more details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.dromara.hutool.crypto;
|
||||||
|
|
||||||
|
import org.bouncycastle.crypto.BufferedBlockCipher;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 密码接口,提供统一的API,用于兼容和统一JCE和BouncyCastle等库的操作
|
||||||
|
*
|
||||||
|
* @author Looly
|
||||||
|
* @since 6.0.0
|
||||||
|
*/
|
||||||
|
public interface Cipher {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取算法名称
|
||||||
|
*
|
||||||
|
* @return 算法名称
|
||||||
|
*/
|
||||||
|
String getAlgorithmName();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取块大小,当为Stream方式加密时返回0
|
||||||
|
*
|
||||||
|
* @return 块大小,-1表示非块加密
|
||||||
|
*/
|
||||||
|
int getBlockSize();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化模式和参数
|
||||||
|
*
|
||||||
|
* @param mode 模式,如加密模式或解密模式
|
||||||
|
* @param parameters Cipher所需参数,包括Key、Random、IV等信息
|
||||||
|
*/
|
||||||
|
void init(CipherMode mode, Parameters parameters);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据输入长度,获取输出长度,输出长度与算法相关<br>
|
||||||
|
* 输出长度只针对本次输入关联,即len长度的数据对应输出长度加doFinal的长度
|
||||||
|
*
|
||||||
|
* @param len 输入长度
|
||||||
|
* @return 输出长度,-1表示非块加密
|
||||||
|
*/
|
||||||
|
int getOutputSize(int len);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行运算,可以是加密运算或解密运算
|
||||||
|
*
|
||||||
|
* @param in 输入数据
|
||||||
|
* @param inOff 输入数据开始位置
|
||||||
|
* @param len 被处理数据长度
|
||||||
|
* @param out 输出数据
|
||||||
|
* @param outOff 输出数据开始位置
|
||||||
|
* @return 处理长度
|
||||||
|
*/
|
||||||
|
int process(byte[] in, int inOff, int len, byte[] out, int outOff);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理最后一块数据<br>
|
||||||
|
* 当{@link #process(byte[], int, int, byte[], int)}处理完数据后非完整块数据,此方法用于处理块中剩余的bytes<br>
|
||||||
|
* 如加密数据要求128bit,即16byes的整数,单数处理数据后为15bytes,此时根据padding方式不同,可填充剩余1byte为指定值(如填充0)<br>
|
||||||
|
* 当对数据进行分段加密时,需要首先多次执行process方法,在最后一块数据处理完后执行此方法。
|
||||||
|
*
|
||||||
|
* @param out 经过process执行过运算的结果数据
|
||||||
|
* @param outOff 数据处理开始位置
|
||||||
|
* @return 处理长度
|
||||||
|
*/
|
||||||
|
int doFinal(byte[] out, int outOff);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理数据,并返回最终结果
|
||||||
|
*
|
||||||
|
* @param in 输入数据
|
||||||
|
* @return 结果数据
|
||||||
|
*/
|
||||||
|
default byte[] processFinal(final byte[] in) {
|
||||||
|
final byte[] buf = new byte[getOutputSize(in.length)];
|
||||||
|
int len = process(in, 0, in.length, buf, 0);
|
||||||
|
len += doFinal(buf, len);
|
||||||
|
|
||||||
|
if (len == buf.length) {
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
return Arrays.copyOfRange(buf, 0, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cipher所需参数,包括Key、Random、IV等信息
|
||||||
|
*/
|
||||||
|
interface Parameters {
|
||||||
|
}
|
||||||
|
}
|
@ -24,19 +24,19 @@ public enum CipherMode {
|
|||||||
/**
|
/**
|
||||||
* 加密模式
|
* 加密模式
|
||||||
*/
|
*/
|
||||||
encrypt(Cipher.ENCRYPT_MODE),
|
ENCRYPT(Cipher.ENCRYPT_MODE),
|
||||||
/**
|
/**
|
||||||
* 解密模式
|
* 解密模式
|
||||||
*/
|
*/
|
||||||
decrypt(Cipher.DECRYPT_MODE),
|
DECRYPT(Cipher.DECRYPT_MODE),
|
||||||
/**
|
/**
|
||||||
* 包装模式
|
* 包装模式
|
||||||
*/
|
*/
|
||||||
wrap(Cipher.WRAP_MODE),
|
WRAP(Cipher.WRAP_MODE),
|
||||||
/**
|
/**
|
||||||
* 拆包模式
|
* 拆包模式
|
||||||
*/
|
*/
|
||||||
unwrap(Cipher.UNWRAP_MODE);
|
UNWRAP(Cipher.UNWRAP_MODE);
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1,136 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2023 looly(loolly@aliyun.com)
|
|
||||||
* Hutool is licensed under Mulan PSL v2.
|
|
||||||
* You can use this software according to the terms and conditions of the Mulan PSL v2.
|
|
||||||
* You may obtain a copy of Mulan PSL v2 at:
|
|
||||||
* https://license.coscl.org.cn/MulanPSL2
|
|
||||||
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
|
||||||
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
|
||||||
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
|
||||||
* See the Mulan PSL v2 for more details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.dromara.hutool.crypto;
|
|
||||||
|
|
||||||
import org.dromara.hutool.core.lang.wrapper.Wrapper;
|
|
||||||
|
|
||||||
import javax.crypto.Cipher;
|
|
||||||
import java.security.InvalidAlgorithmParameterException;
|
|
||||||
import java.security.InvalidKeyException;
|
|
||||||
import java.security.Key;
|
|
||||||
import java.security.SecureRandom;
|
|
||||||
import java.security.spec.AlgorithmParameterSpec;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@link Cipher}包装类,提供初始化模式等额外方法<br>
|
|
||||||
* 包装之后可提供自定义或默认的:
|
|
||||||
* <ul>
|
|
||||||
* <li>{@link AlgorithmParameterSpec}</li>
|
|
||||||
* <li>{@link SecureRandom}</li>
|
|
||||||
* </ul>
|
|
||||||
*
|
|
||||||
* @author looly
|
|
||||||
* @since 5.7.17
|
|
||||||
*/
|
|
||||||
public class CipherWrapper implements Wrapper<Cipher> {
|
|
||||||
|
|
||||||
private final Cipher cipher;
|
|
||||||
/**
|
|
||||||
* 算法参数
|
|
||||||
*/
|
|
||||||
private AlgorithmParameterSpec params;
|
|
||||||
/**
|
|
||||||
* 随机数生成器,可自定义随机数种子
|
|
||||||
*/
|
|
||||||
private SecureRandom random;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 构造
|
|
||||||
*
|
|
||||||
* @param algorithm 算法名称
|
|
||||||
*/
|
|
||||||
public CipherWrapper(final String algorithm) {
|
|
||||||
this(SecureUtil.createCipher(algorithm));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 构造
|
|
||||||
*
|
|
||||||
* @param cipher {@link Cipher}
|
|
||||||
*/
|
|
||||||
public CipherWrapper(final Cipher cipher) {
|
|
||||||
this.cipher = cipher;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取{@link AlgorithmParameterSpec}<br>
|
|
||||||
* 在某些算法中,需要特别的参数,例如在ECIES中,此处为IESParameterSpec
|
|
||||||
*
|
|
||||||
* @return {@link AlgorithmParameterSpec}
|
|
||||||
*/
|
|
||||||
public AlgorithmParameterSpec getParams() {
|
|
||||||
return this.params;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置 {@link AlgorithmParameterSpec},通常用于加盐或偏移向量
|
|
||||||
*
|
|
||||||
* @param params {@link AlgorithmParameterSpec}
|
|
||||||
* @return this
|
|
||||||
*/
|
|
||||||
public CipherWrapper setParams(final AlgorithmParameterSpec params) {
|
|
||||||
this.params = params;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置随机数生成器,可自定义随机数种子
|
|
||||||
*
|
|
||||||
* @param random 随机数生成器,可自定义随机数种子
|
|
||||||
* @return this
|
|
||||||
*/
|
|
||||||
public CipherWrapper setRandom(final SecureRandom random) {
|
|
||||||
this.random = random;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取被包装的{@link Cipher}
|
|
||||||
*
|
|
||||||
* @return {@link Cipher}
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public Cipher getRaw() {
|
|
||||||
return this.cipher;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 初始化{@link Cipher}为加密或者解密模式
|
|
||||||
*
|
|
||||||
* @param mode 模式,见{@link Cipher#ENCRYPT_MODE} 或 {@link Cipher#DECRYPT_MODE}
|
|
||||||
* @param key 密钥
|
|
||||||
* @return this
|
|
||||||
* @throws InvalidKeyException 无效key
|
|
||||||
* @throws InvalidAlgorithmParameterException 无效算法
|
|
||||||
*/
|
|
||||||
public CipherWrapper initMode(final int mode, final Key key)
|
|
||||||
throws InvalidKeyException, InvalidAlgorithmParameterException {
|
|
||||||
final Cipher cipher = this.cipher;
|
|
||||||
final AlgorithmParameterSpec params = this.params;
|
|
||||||
final SecureRandom random = this.random;
|
|
||||||
if (null != params) {
|
|
||||||
if (null != random) {
|
|
||||||
cipher.init(mode, key, params, random);
|
|
||||||
} else {
|
|
||||||
cipher.init(mode, key, params);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (null != random) {
|
|
||||||
cipher.init(mode, key, random);
|
|
||||||
} else {
|
|
||||||
cipher.init(mode, key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,155 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2024. looly(loolly@aliyun.com)
|
||||||
|
* Hutool is licensed under Mulan PSL v2.
|
||||||
|
* You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||||||
|
* You may obtain a copy of Mulan PSL v2 at:
|
||||||
|
* https://license.coscl.org.cn/MulanPSL2
|
||||||
|
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
||||||
|
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
||||||
|
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
||||||
|
* See the Mulan PSL v2 for more details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.dromara.hutool.crypto;
|
||||||
|
|
||||||
|
import org.dromara.hutool.core.lang.Assert;
|
||||||
|
import org.dromara.hutool.core.lang.wrapper.SimpleWrapper;
|
||||||
|
|
||||||
|
import javax.crypto.ShortBufferException;
|
||||||
|
import java.security.InvalidAlgorithmParameterException;
|
||||||
|
import java.security.InvalidKeyException;
|
||||||
|
import java.security.Key;
|
||||||
|
import java.security.SecureRandom;
|
||||||
|
import java.security.spec.AlgorithmParameterSpec;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 提供{@link javax.crypto.Cipher}的方法包装
|
||||||
|
*
|
||||||
|
* @author Looly
|
||||||
|
*/
|
||||||
|
public class JceCipher extends SimpleWrapper<javax.crypto.Cipher> implements Cipher {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构造
|
||||||
|
*
|
||||||
|
* @param algorithm 算法名称
|
||||||
|
*/
|
||||||
|
public JceCipher(final String algorithm) {
|
||||||
|
this(SecureUtil.createCipher(algorithm));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构造
|
||||||
|
*
|
||||||
|
* @param cipher {@link javax.crypto.Cipher},可以通过{@link javax.crypto.Cipher#getInstance(String)}创建
|
||||||
|
*/
|
||||||
|
public JceCipher(final javax.crypto.Cipher cipher) {
|
||||||
|
super(Assert.notNull(cipher));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getAlgorithmName() {
|
||||||
|
return this.raw.getAlgorithm();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getBlockSize() {
|
||||||
|
return this.raw.getBlockSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getOutputSize(final int len) {
|
||||||
|
return this.raw.getOutputSize(len);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init(final CipherMode mode, final Parameters parameters) {
|
||||||
|
Assert.isInstanceOf(JceParameters.class, parameters, "Only support JceParameters!");
|
||||||
|
|
||||||
|
try {
|
||||||
|
init(mode.getValue(), (JceParameters) parameters);
|
||||||
|
} catch (final InvalidAlgorithmParameterException | InvalidKeyException e) {
|
||||||
|
throw new CryptoException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行初始化参数操作
|
||||||
|
*
|
||||||
|
* @param mode 模式
|
||||||
|
* @param jceParameters {@link JceParameters}
|
||||||
|
* @throws InvalidAlgorithmParameterException 无效算法参数
|
||||||
|
* @throws InvalidKeyException 无效key
|
||||||
|
*/
|
||||||
|
public void init(final int mode, final JceParameters jceParameters) throws InvalidAlgorithmParameterException, InvalidKeyException {
|
||||||
|
final javax.crypto.Cipher cipher = this.raw;
|
||||||
|
if (null != jceParameters.parameterSpec) {
|
||||||
|
if (null != jceParameters.random) {
|
||||||
|
cipher.init(mode, jceParameters.key, jceParameters.parameterSpec, jceParameters.random);
|
||||||
|
} else {
|
||||||
|
cipher.init(mode, jceParameters.key, jceParameters.parameterSpec);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (null != jceParameters.random) {
|
||||||
|
cipher.init(mode, jceParameters.key, jceParameters.random);
|
||||||
|
} else {
|
||||||
|
cipher.init(mode, jceParameters.key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int process(final byte[] in, final int inOff, final int len, final byte[] out, final int outOff) {
|
||||||
|
try {
|
||||||
|
return this.raw.update(in, inOff, len, out, outOff);
|
||||||
|
} catch (final ShortBufferException e) {
|
||||||
|
throw new CryptoException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int doFinal(final byte[] out, final int outOff) {
|
||||||
|
try {
|
||||||
|
return this.raw.doFinal(out, outOff);
|
||||||
|
} catch (final Exception e) {
|
||||||
|
throw new CryptoException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] processFinal(final byte[] data) {
|
||||||
|
try {
|
||||||
|
return this.raw.doFinal(data);
|
||||||
|
} catch (final Exception e) {
|
||||||
|
throw new CryptoException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JCE的{@link AlgorithmParameterSpec} 参数包装
|
||||||
|
*/
|
||||||
|
public static class JceParameters implements Parameters {
|
||||||
|
private final Key key;
|
||||||
|
/**
|
||||||
|
* 算法参数
|
||||||
|
*/
|
||||||
|
private final AlgorithmParameterSpec parameterSpec;
|
||||||
|
/**
|
||||||
|
* 随机数生成器,可自定义随机数种子
|
||||||
|
*/
|
||||||
|
private final SecureRandom random;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构造
|
||||||
|
*
|
||||||
|
* @param key 密钥
|
||||||
|
* @param parameterSpec {@link AlgorithmParameterSpec}
|
||||||
|
* @param random 自定义随机数生成器
|
||||||
|
*/
|
||||||
|
public JceParameters(final Key key, final AlgorithmParameterSpec parameterSpec, final SecureRandom random) {
|
||||||
|
this.key = key;
|
||||||
|
this.parameterSpec = parameterSpec;
|
||||||
|
this.random = random;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -64,6 +64,7 @@ public class KeyUtil {
|
|||||||
public static final int DEFAULT_KEY_SIZE = 1024;
|
public static final int DEFAULT_KEY_SIZE = 1024;
|
||||||
|
|
||||||
// region ----- generateKey
|
// region ----- generateKey
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 生成 {@link SecretKey},仅用于对称加密和摘要算法密钥生成
|
* 生成 {@link SecretKey},仅用于对称加密和摘要算法密钥生成
|
||||||
*
|
*
|
||||||
@ -196,7 +197,24 @@ public class KeyUtil {
|
|||||||
}
|
}
|
||||||
// endregion
|
// endregion
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查{@link KeyPair} 是否为空,空的条件是:
|
||||||
|
* <ul>
|
||||||
|
* <li>keyPair本身为{@code null}</li>
|
||||||
|
* <li>{@link KeyPair#getPrivate()}和{@link KeyPair#getPublic()}都为{@code null}</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* @param keyPair 密钥对
|
||||||
|
* @return 是否为空
|
||||||
|
*/
|
||||||
// region ----- keyPair
|
// region ----- keyPair
|
||||||
|
public static boolean isEmpty(final KeyPair keyPair) {
|
||||||
|
if (null == keyPair) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return null != keyPair.getPrivate() || null != keyPair.getPublic();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 生成RSA私钥,仅用于非对称加密<br>
|
* 生成RSA私钥,仅用于非对称加密<br>
|
||||||
* 采用PKCS#8规范,此规范定义了私钥信息语法和加密私钥语法<br>
|
* 采用PKCS#8规范,此规范定义了私钥信息语法和加密私钥语法<br>
|
||||||
|
@ -54,5 +54,10 @@ public enum Mode {
|
|||||||
/**
|
/**
|
||||||
* Propagating Cipher Block
|
* Propagating Cipher Block
|
||||||
*/
|
*/
|
||||||
PCBC
|
PCBC,
|
||||||
|
/**
|
||||||
|
* GCM 全称为 Galois/Counter Mode。G是指GMAC,C是指CTR。
|
||||||
|
* 它在 CTR 加密的基础上增加 GMAC 的特性,解决了 CTR 不能对加密消息进行完整性校验的问题。
|
||||||
|
*/
|
||||||
|
GCM
|
||||||
}
|
}
|
||||||
|
@ -31,7 +31,7 @@ import java.util.Map;
|
|||||||
* 签名工具类<br>
|
* 签名工具类<br>
|
||||||
* 封装包括:
|
* 封装包括:
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>非堆成签名,签名算法支持见{@link SignAlgorithm}</li>
|
* <li>非对称签名,签名算法支持见{@link SignAlgorithm}</li>
|
||||||
* <li>对称签名,支持Map类型参数排序后签名</li>
|
* <li>对称签名,支持Map类型参数排序后签名</li>
|
||||||
* <li>摘要签名,支持Map类型参数排序后签名,签名方法见:{@link DigestAlgorithm}</li>
|
* <li>摘要签名,支持Map类型参数排序后签名,签名方法见:{@link DigestAlgorithm}</li>
|
||||||
* </ul>
|
* </ul>
|
||||||
|
@ -14,18 +14,13 @@ package org.dromara.hutool.crypto.asymmetric;
|
|||||||
|
|
||||||
import org.dromara.hutool.core.codec.binary.Base64;
|
import org.dromara.hutool.core.codec.binary.Base64;
|
||||||
import org.dromara.hutool.core.io.stream.FastByteArrayOutputStream;
|
import org.dromara.hutool.core.io.stream.FastByteArrayOutputStream;
|
||||||
import org.dromara.hutool.crypto.CipherWrapper;
|
import org.dromara.hutool.crypto.*;
|
||||||
import org.dromara.hutool.crypto.CryptoException;
|
|
||||||
import org.dromara.hutool.crypto.KeyUtil;
|
|
||||||
import org.dromara.hutool.crypto.SecureUtil;
|
|
||||||
import org.dromara.hutool.crypto.symmetric.SymmetricAlgorithm;
|
import org.dromara.hutool.crypto.symmetric.SymmetricAlgorithm;
|
||||||
|
|
||||||
import javax.crypto.BadPaddingException;
|
import javax.crypto.BadPaddingException;
|
||||||
import javax.crypto.Cipher;
|
import javax.crypto.Cipher;
|
||||||
import javax.crypto.IllegalBlockSizeException;
|
import javax.crypto.IllegalBlockSizeException;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.security.InvalidAlgorithmParameterException;
|
|
||||||
import java.security.InvalidKeyException;
|
|
||||||
import java.security.Key;
|
import java.security.Key;
|
||||||
import java.security.PrivateKey;
|
import java.security.PrivateKey;
|
||||||
import java.security.PublicKey;
|
import java.security.PublicKey;
|
||||||
@ -51,8 +46,7 @@ public class AsymmetricCrypto extends AbstractAsymmetricCrypto<AsymmetricCrypto>
|
|||||||
/**
|
/**
|
||||||
* Cipher负责完成加密或解密工作
|
* Cipher负责完成加密或解密工作
|
||||||
*/
|
*/
|
||||||
protected CipherWrapper cipherWrapper;
|
protected JceCipher cipher;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 加密的块大小
|
* 加密的块大小
|
||||||
*/
|
*/
|
||||||
@ -61,6 +55,15 @@ public class AsymmetricCrypto extends AbstractAsymmetricCrypto<AsymmetricCrypto>
|
|||||||
* 解密的块大小
|
* 解密的块大小
|
||||||
*/
|
*/
|
||||||
protected int decryptBlockSize = -1;
|
protected int decryptBlockSize = -1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 算法参数
|
||||||
|
*/
|
||||||
|
private AlgorithmParameterSpec algorithmParameterSpec;
|
||||||
|
/**
|
||||||
|
* 自定义随机数
|
||||||
|
*/
|
||||||
|
private SecureRandom random;
|
||||||
// ------------------------------------------------------------------ Constructor start
|
// ------------------------------------------------------------------ Constructor start
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -144,8 +147,8 @@ public class AsymmetricCrypto extends AbstractAsymmetricCrypto<AsymmetricCrypto>
|
|||||||
*/
|
*/
|
||||||
public AsymmetricCrypto(final String algorithm, final byte[] privateKey, final byte[] publicKey) {
|
public AsymmetricCrypto(final String algorithm, final byte[] privateKey, final byte[] publicKey) {
|
||||||
this(algorithm, //
|
this(algorithm, //
|
||||||
KeyUtil.generatePrivateKey(algorithm, privateKey), //
|
KeyUtil.generatePrivateKey(algorithm, privateKey), //
|
||||||
KeyUtil.generatePublicKey(algorithm, publicKey)//
|
KeyUtil.generatePublicKey(algorithm, publicKey)//
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -209,7 +212,7 @@ public class AsymmetricCrypto extends AbstractAsymmetricCrypto<AsymmetricCrypto>
|
|||||||
* @since 5.4.3
|
* @since 5.4.3
|
||||||
*/
|
*/
|
||||||
public AlgorithmParameterSpec getAlgorithmParameterSpec() {
|
public AlgorithmParameterSpec getAlgorithmParameterSpec() {
|
||||||
return this.cipherWrapper.getParams();
|
return this.algorithmParameterSpec;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -217,10 +220,11 @@ public class AsymmetricCrypto extends AbstractAsymmetricCrypto<AsymmetricCrypto>
|
|||||||
* 在某些算法中,需要特别的参数,例如在ECIES中,此处为IESParameterSpec
|
* 在某些算法中,需要特别的参数,例如在ECIES中,此处为IESParameterSpec
|
||||||
*
|
*
|
||||||
* @param algorithmParameterSpec {@link AlgorithmParameterSpec}
|
* @param algorithmParameterSpec {@link AlgorithmParameterSpec}
|
||||||
* @since 5.4.3
|
* @return this
|
||||||
*/
|
*/
|
||||||
public void setAlgorithmParameterSpec(final AlgorithmParameterSpec algorithmParameterSpec) {
|
public AsymmetricCrypto setAlgorithmParameterSpec(final AlgorithmParameterSpec algorithmParameterSpec) {
|
||||||
this.cipherWrapper.setParams(algorithmParameterSpec);
|
this.algorithmParameterSpec = algorithmParameterSpec;
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -231,7 +235,7 @@ public class AsymmetricCrypto extends AbstractAsymmetricCrypto<AsymmetricCrypto>
|
|||||||
* @since 5.7.17
|
* @since 5.7.17
|
||||||
*/
|
*/
|
||||||
public AsymmetricCrypto setRandom(final SecureRandom random) {
|
public AsymmetricCrypto setRandom(final SecureRandom random) {
|
||||||
this.cipherWrapper.setRandom(random);
|
this.random = random;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -249,7 +253,7 @@ public class AsymmetricCrypto extends AbstractAsymmetricCrypto<AsymmetricCrypto>
|
|||||||
final Key key = getKeyByType(keyType);
|
final Key key = getKeyByType(keyType);
|
||||||
lock.lock();
|
lock.lock();
|
||||||
try {
|
try {
|
||||||
final Cipher cipher = initMode(Cipher.ENCRYPT_MODE, key);
|
final JceCipher cipher = initMode(CipherMode.ENCRYPT, key);
|
||||||
|
|
||||||
if (this.encryptBlockSize < 0) {
|
if (this.encryptBlockSize < 0) {
|
||||||
// 在引入BC库情况下,自动获取块大小
|
// 在引入BC库情况下,自动获取块大小
|
||||||
@ -274,7 +278,7 @@ public class AsymmetricCrypto extends AbstractAsymmetricCrypto<AsymmetricCrypto>
|
|||||||
final Key key = getKeyByType(keyType);
|
final Key key = getKeyByType(keyType);
|
||||||
lock.lock();
|
lock.lock();
|
||||||
try {
|
try {
|
||||||
final Cipher cipher = initMode(Cipher.DECRYPT_MODE, key);
|
final JceCipher cipher = initMode(CipherMode.DECRYPT, key);
|
||||||
|
|
||||||
if (this.decryptBlockSize < 0) {
|
if (this.decryptBlockSize < 0) {
|
||||||
// 在引入BC库情况下,自动获取块大小
|
// 在引入BC库情况下,自动获取块大小
|
||||||
@ -301,7 +305,7 @@ public class AsymmetricCrypto extends AbstractAsymmetricCrypto<AsymmetricCrypto>
|
|||||||
* @since 5.4.3
|
* @since 5.4.3
|
||||||
*/
|
*/
|
||||||
public Cipher getCipher() {
|
public Cipher getCipher() {
|
||||||
return this.cipherWrapper.getRaw();
|
return this.cipher.getRaw();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -310,7 +314,7 @@ public class AsymmetricCrypto extends AbstractAsymmetricCrypto<AsymmetricCrypto>
|
|||||||
* @since 4.5.2
|
* @since 4.5.2
|
||||||
*/
|
*/
|
||||||
protected void initCipher() {
|
protected void initCipher() {
|
||||||
this.cipherWrapper = new CipherWrapper(this.algorithm);
|
this.cipher = new JceCipher(this.algorithm);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -346,9 +350,10 @@ public class AsymmetricCrypto extends AbstractAsymmetricCrypto<AsymmetricCrypto>
|
|||||||
* @throws BadPaddingException padding错误异常
|
* @throws BadPaddingException padding错误异常
|
||||||
* @throws IOException IO异常,不会被触发
|
* @throws IOException IO异常,不会被触发
|
||||||
*/
|
*/
|
||||||
|
@SuppressWarnings("resource")
|
||||||
private byte[] doFinalWithBlock(final byte[] data, final int maxBlockSize) throws IllegalBlockSizeException, BadPaddingException, IOException {
|
private byte[] doFinalWithBlock(final byte[] data, final int maxBlockSize) throws IllegalBlockSizeException, BadPaddingException, IOException {
|
||||||
final int dataLength = data.length;
|
final int dataLength = data.length;
|
||||||
@SuppressWarnings("resource") final FastByteArrayOutputStream out = new FastByteArrayOutputStream();
|
final FastByteArrayOutputStream out = new FastByteArrayOutputStream();
|
||||||
|
|
||||||
int offSet = 0;
|
int offSet = 0;
|
||||||
// 剩余长度
|
// 剩余长度
|
||||||
@ -367,15 +372,15 @@ public class AsymmetricCrypto extends AbstractAsymmetricCrypto<AsymmetricCrypto>
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 初始化{@link Cipher}的模式,如加密模式或解密模式
|
* 初始化{@link JceCipher}的模式,如加密模式或解密模式
|
||||||
*
|
*
|
||||||
* @param mode 模式,可选{@link Cipher#ENCRYPT_MODE}或者{@link Cipher#DECRYPT_MODE}
|
* @param mode 模式,可选{@link CipherMode#ENCRYPT}或者{@link CipherMode#DECRYPT}
|
||||||
* @param key 密钥
|
* @param key 密钥
|
||||||
* @return {@link Cipher}
|
* @return {@link JceCipher}
|
||||||
* @throws InvalidAlgorithmParameterException 异常算法错误
|
|
||||||
* @throws InvalidKeyException 异常KEY错误
|
|
||||||
*/
|
*/
|
||||||
private Cipher initMode(final int mode, final Key key) throws InvalidAlgorithmParameterException, InvalidKeyException {
|
private JceCipher initMode(final CipherMode mode, final Key key) {
|
||||||
return this.cipherWrapper.initMode(mode, key).getRaw();
|
final JceCipher cipher = this.cipher;
|
||||||
|
cipher.init(mode, new JceCipher.JceParameters(key, this.algorithmParameterSpec, this.random));
|
||||||
|
return cipher;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,234 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2024. looly(loolly@aliyun.com)
|
||||||
|
* Hutool is licensed under Mulan PSL v2.
|
||||||
|
* You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||||||
|
* You may obtain a copy of Mulan PSL v2 at:
|
||||||
|
* https://license.coscl.org.cn/MulanPSL2
|
||||||
|
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
||||||
|
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
||||||
|
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
||||||
|
* See the Mulan PSL v2 for more details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.dromara.hutool.crypto.bc;
|
||||||
|
|
||||||
|
import org.bouncycastle.crypto.*;
|
||||||
|
import org.bouncycastle.crypto.modes.AEADBlockCipher;
|
||||||
|
import org.dromara.hutool.core.lang.Assert;
|
||||||
|
import org.dromara.hutool.core.lang.wrapper.Wrapper;
|
||||||
|
import org.dromara.hutool.crypto.Cipher;
|
||||||
|
import org.dromara.hutool.crypto.CipherMode;
|
||||||
|
import org.dromara.hutool.crypto.CryptoException;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 基于BouncyCastle库封装的加密解密实现,包装包括:
|
||||||
|
* <ul>
|
||||||
|
* <li>{@link BufferedBlockCipher}</li>
|
||||||
|
* <li>{@link BlockCipher}</li>
|
||||||
|
* <li>{@link StreamCipher}</li>
|
||||||
|
* <li>{@link AEADBlockCipher}</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* @author Looly, changhr2013
|
||||||
|
*/
|
||||||
|
public class BCCipher implements Cipher, Wrapper<Object> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link BufferedBlockCipher},块加密,包含engine、mode、padding
|
||||||
|
*/
|
||||||
|
private BufferedBlockCipher bufferedBlockCipher;
|
||||||
|
/**
|
||||||
|
* {@link BlockCipher} 块加密,一般用于AES等对称加密
|
||||||
|
*/
|
||||||
|
private BlockCipher blockCipher;
|
||||||
|
/**
|
||||||
|
* {@link AEADBlockCipher}, 关联数据的认证加密(Authenticated Encryption with Associated Data)
|
||||||
|
*/
|
||||||
|
private AEADBlockCipher aeadBlockCipher;
|
||||||
|
/**
|
||||||
|
* {@link StreamCipher}
|
||||||
|
*/
|
||||||
|
private StreamCipher streamCipher;
|
||||||
|
|
||||||
|
// region ----- 构造
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构造
|
||||||
|
*
|
||||||
|
* @param bufferedBlockCipher {@link BufferedBlockCipher}
|
||||||
|
*/
|
||||||
|
public BCCipher(final BufferedBlockCipher bufferedBlockCipher) {
|
||||||
|
this.bufferedBlockCipher = Assert.notNull(bufferedBlockCipher);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构造
|
||||||
|
*
|
||||||
|
* @param blockCipher {@link BlockCipher}
|
||||||
|
*/
|
||||||
|
public BCCipher(final BlockCipher blockCipher) {
|
||||||
|
this.blockCipher = Assert.notNull(blockCipher);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构造
|
||||||
|
*
|
||||||
|
* @param aeadBlockCipher {@link AEADBlockCipher}
|
||||||
|
*/
|
||||||
|
public BCCipher(final AEADBlockCipher aeadBlockCipher) {
|
||||||
|
this.aeadBlockCipher = Assert.notNull(aeadBlockCipher);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构造
|
||||||
|
*
|
||||||
|
* @param streamCipher {@link StreamCipher}
|
||||||
|
*/
|
||||||
|
public BCCipher(final StreamCipher streamCipher) {
|
||||||
|
this.streamCipher = Assert.notNull(streamCipher);
|
||||||
|
}
|
||||||
|
// endregion
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getRaw() {
|
||||||
|
if (null != this.bufferedBlockCipher) {
|
||||||
|
return this.bufferedBlockCipher;
|
||||||
|
}
|
||||||
|
if (null != this.blockCipher) {
|
||||||
|
return this.blockCipher;
|
||||||
|
}
|
||||||
|
if (null != this.aeadBlockCipher) {
|
||||||
|
return this.aeadBlockCipher;
|
||||||
|
}
|
||||||
|
return this.streamCipher;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getAlgorithmName() {
|
||||||
|
if (null != this.bufferedBlockCipher) {
|
||||||
|
return this.bufferedBlockCipher.getUnderlyingCipher().getAlgorithmName();
|
||||||
|
}
|
||||||
|
if (null != this.blockCipher) {
|
||||||
|
return this.blockCipher.getAlgorithmName();
|
||||||
|
}
|
||||||
|
if (null != this.aeadBlockCipher) {
|
||||||
|
return this.aeadBlockCipher.getUnderlyingCipher().getAlgorithmName();
|
||||||
|
}
|
||||||
|
return this.streamCipher.getAlgorithmName();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getBlockSize() {
|
||||||
|
if (null != this.bufferedBlockCipher) {
|
||||||
|
return this.bufferedBlockCipher.getBlockSize();
|
||||||
|
}
|
||||||
|
if (null != this.blockCipher) {
|
||||||
|
return this.blockCipher.getBlockSize();
|
||||||
|
}
|
||||||
|
if (null != this.aeadBlockCipher) {
|
||||||
|
return this.aeadBlockCipher.getUnderlyingCipher().getBlockSize();
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init(final CipherMode mode, final Parameters parameters) {
|
||||||
|
Assert.isInstanceOf(BCParameters.class, parameters, "Only support BCParameters!");
|
||||||
|
|
||||||
|
final boolean forEncryption;
|
||||||
|
if (mode == CipherMode.ENCRYPT) {
|
||||||
|
forEncryption = true;
|
||||||
|
} else if (mode == CipherMode.DECRYPT) {
|
||||||
|
forEncryption = false;
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException("Invalid mode: " + mode.name());
|
||||||
|
}
|
||||||
|
final CipherParameters cipherParameters = ((BCParameters) parameters).parameters;
|
||||||
|
|
||||||
|
if (null != this.bufferedBlockCipher) {
|
||||||
|
this.bufferedBlockCipher.init(forEncryption, cipherParameters);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (null != this.blockCipher) {
|
||||||
|
this.blockCipher.init(forEncryption, cipherParameters);
|
||||||
|
}
|
||||||
|
if (null != this.aeadBlockCipher) {
|
||||||
|
this.aeadBlockCipher.init(forEncryption, cipherParameters);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.streamCipher.init(forEncryption, cipherParameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getOutputSize(final int len) {
|
||||||
|
if (null != this.bufferedBlockCipher) {
|
||||||
|
return this.bufferedBlockCipher.getOutputSize(len);
|
||||||
|
}
|
||||||
|
if (null != this.aeadBlockCipher) {
|
||||||
|
return this.aeadBlockCipher.getOutputSize(len);
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int process(final byte[] in, final int inOff, final int len, final byte[] out, final int outOff) {
|
||||||
|
if (null != this.bufferedBlockCipher) {
|
||||||
|
return this.bufferedBlockCipher.processBytes(in, inOff, len, out, outOff);
|
||||||
|
}
|
||||||
|
if (null != this.blockCipher) {
|
||||||
|
final byte[] subBytes;
|
||||||
|
if (inOff + len < in.length) {
|
||||||
|
subBytes = Arrays.copyOf(in, inOff + len);
|
||||||
|
} else {
|
||||||
|
subBytes = in;
|
||||||
|
}
|
||||||
|
return this.blockCipher.processBlock(subBytes, inOff, out, outOff);
|
||||||
|
}
|
||||||
|
if (null != this.aeadBlockCipher) {
|
||||||
|
return this.aeadBlockCipher.processBytes(in, inOff, len, out, outOff);
|
||||||
|
}
|
||||||
|
return this.streamCipher.processBytes(in, inOff, len, out, outOff);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int doFinal(final byte[] out, final int outOff) {
|
||||||
|
if (null != this.bufferedBlockCipher) {
|
||||||
|
try {
|
||||||
|
return this.bufferedBlockCipher.doFinal(out, outOff);
|
||||||
|
} catch (final InvalidCipherTextException e) {
|
||||||
|
throw new CryptoException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (null != this.aeadBlockCipher) {
|
||||||
|
try {
|
||||||
|
return this.aeadBlockCipher.doFinal(out, outOff);
|
||||||
|
} catch (final InvalidCipherTextException e) {
|
||||||
|
throw new CryptoException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* BouncyCastle库的{@link CipherParameters}封装
|
||||||
|
*
|
||||||
|
* @author Looly
|
||||||
|
*/
|
||||||
|
public static class BCParameters implements Parameters {
|
||||||
|
/**
|
||||||
|
* 算法的参数
|
||||||
|
*/
|
||||||
|
private final CipherParameters parameters;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构造
|
||||||
|
*
|
||||||
|
* @param parameters {@link CipherParameters}
|
||||||
|
*/
|
||||||
|
public BCParameters(final CipherParameters parameters) {
|
||||||
|
this.parameters = parameters;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -15,10 +15,19 @@ package org.dromara.hutool.crypto.bc;
|
|||||||
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
|
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
|
||||||
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
|
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
|
||||||
import org.bouncycastle.asn1.x9.X9ECParameters;
|
import org.bouncycastle.asn1.x9.X9ECParameters;
|
||||||
|
import org.bouncycastle.crypto.BlockCipher;
|
||||||
|
import org.bouncycastle.crypto.BufferedBlockCipher;
|
||||||
|
import org.bouncycastle.crypto.DefaultBufferedBlockCipher;
|
||||||
|
import org.bouncycastle.crypto.modes.*;
|
||||||
|
import org.bouncycastle.crypto.paddings.ISO10126d2Padding;
|
||||||
|
import org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher;
|
||||||
|
import org.bouncycastle.crypto.paddings.ZeroBytePadding;
|
||||||
import org.bouncycastle.crypto.params.ECDomainParameters;
|
import org.bouncycastle.crypto.params.ECDomainParameters;
|
||||||
import org.bouncycastle.jcajce.provider.asymmetric.util.ECUtil;
|
import org.bouncycastle.jcajce.provider.asymmetric.util.ECUtil;
|
||||||
import org.bouncycastle.jce.spec.ECParameterSpec;
|
import org.bouncycastle.jce.spec.ECParameterSpec;
|
||||||
import org.dromara.hutool.core.io.IORuntimeException;
|
import org.dromara.hutool.core.io.IORuntimeException;
|
||||||
|
import org.dromara.hutool.crypto.Mode;
|
||||||
|
import org.dromara.hutool.crypto.Padding;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.security.PrivateKey;
|
import java.security.PrivateKey;
|
||||||
@ -41,10 +50,10 @@ public class BCUtil {
|
|||||||
*/
|
*/
|
||||||
public static ECDomainParameters toDomainParams(final ECParameterSpec parameterSpec) {
|
public static ECDomainParameters toDomainParams(final ECParameterSpec parameterSpec) {
|
||||||
return new ECDomainParameters(
|
return new ECDomainParameters(
|
||||||
parameterSpec.getCurve(),
|
parameterSpec.getCurve(),
|
||||||
parameterSpec.getG(),
|
parameterSpec.getG(),
|
||||||
parameterSpec.getN(),
|
parameterSpec.getN(),
|
||||||
parameterSpec.getH());
|
parameterSpec.getH());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -67,10 +76,10 @@ public class BCUtil {
|
|||||||
*/
|
*/
|
||||||
public static ECDomainParameters toDomainParams(final X9ECParameters x9ECParameters) {
|
public static ECDomainParameters toDomainParams(final X9ECParameters x9ECParameters) {
|
||||||
return new ECDomainParameters(
|
return new ECDomainParameters(
|
||||||
x9ECParameters.getCurve(),
|
x9ECParameters.getCurve(),
|
||||||
x9ECParameters.getG(),
|
x9ECParameters.getG(),
|
||||||
x9ECParameters.getN(),
|
x9ECParameters.getN(),
|
||||||
x9ECParameters.getH()
|
x9ECParameters.getH()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -81,7 +90,7 @@ public class BCUtil {
|
|||||||
* @return PKCS#1格式私钥
|
* @return PKCS#1格式私钥
|
||||||
* @since 5.5.9
|
* @since 5.5.9
|
||||||
*/
|
*/
|
||||||
public static byte[] toPkcs1(final PrivateKey privateKey){
|
public static byte[] toPkcs1(final PrivateKey privateKey) {
|
||||||
final PrivateKeyInfo pkInfo = PrivateKeyInfo.getInstance(privateKey.getEncoded());
|
final PrivateKeyInfo pkInfo = PrivateKeyInfo.getInstance(privateKey.getEncoded());
|
||||||
try {
|
try {
|
||||||
return pkInfo.parsePrivateKey().toASN1Primitive().getEncoded();
|
return pkInfo.parsePrivateKey().toASN1Primitive().getEncoded();
|
||||||
@ -97,13 +106,53 @@ public class BCUtil {
|
|||||||
* @return PKCS#1格式公钥
|
* @return PKCS#1格式公钥
|
||||||
* @since 5.5.9
|
* @since 5.5.9
|
||||||
*/
|
*/
|
||||||
public static byte[] toPkcs1(final PublicKey publicKey){
|
public static byte[] toPkcs1(final PublicKey publicKey) {
|
||||||
final SubjectPublicKeyInfo spkInfo = SubjectPublicKeyInfo
|
final SubjectPublicKeyInfo spkInfo = SubjectPublicKeyInfo
|
||||||
.getInstance(publicKey.getEncoded());
|
.getInstance(publicKey.getEncoded());
|
||||||
try {
|
try {
|
||||||
return spkInfo.parsePublicKey().getEncoded();
|
return spkInfo.parsePublicKey().getEncoded();
|
||||||
} catch (final IOException e) {
|
} catch (final IOException e) {
|
||||||
throw new IORuntimeException(e);
|
throw new IORuntimeException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将{@link BlockCipher}包装为指定mode和padding的{@link BufferedBlockCipher}
|
||||||
|
*
|
||||||
|
* @param cipher {@link BlockCipher}
|
||||||
|
* @param mode 模式
|
||||||
|
* @param padding 补码方式
|
||||||
|
* @return {@link BufferedBlockCipher},无对应Cipher返回{@code null}
|
||||||
|
* @since 6.0.0
|
||||||
|
*/
|
||||||
|
public static BufferedBlockCipher wrap(BlockCipher cipher, final Mode mode, final Padding padding) {
|
||||||
|
switch (mode) {
|
||||||
|
case CBC:
|
||||||
|
cipher = CBCBlockCipher.newInstance(cipher);
|
||||||
|
break;
|
||||||
|
case CFB:
|
||||||
|
cipher = CFBBlockCipher.newInstance(cipher, cipher.getBlockSize() * 8);
|
||||||
|
break;
|
||||||
|
case CTR:
|
||||||
|
cipher = SICBlockCipher.newInstance(cipher);
|
||||||
|
break;
|
||||||
|
case OFB:
|
||||||
|
cipher = new OFBBlockCipher(cipher, cipher.getBlockSize() * 8);
|
||||||
|
case CTS:
|
||||||
|
return new CTSBlockCipher(cipher);
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (padding) {
|
||||||
|
case NoPadding:
|
||||||
|
return new DefaultBufferedBlockCipher(cipher);
|
||||||
|
case PKCS5Padding:
|
||||||
|
return new PaddedBufferedBlockCipher(cipher);
|
||||||
|
case ZeroPadding:
|
||||||
|
return new PaddedBufferedBlockCipher(cipher, new ZeroBytePadding());
|
||||||
|
case ISO10126Padding:
|
||||||
|
return new PaddedBufferedBlockCipher(cipher, new ISO10126d2Padding());
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -435,6 +435,9 @@ public class ECKeyUtil {
|
|||||||
* @since 5.5.9
|
* @since 5.5.9
|
||||||
*/
|
*/
|
||||||
public static ECPrivateKeyParameters decodePrivateKeyParams(final byte[] privateKeyBytes) {
|
public static ECPrivateKeyParameters decodePrivateKeyParams(final byte[] privateKeyBytes) {
|
||||||
|
if (null == privateKeyBytes) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
// 尝试D值
|
// 尝试D值
|
||||||
return toSm2PrivateParams(privateKeyBytes);
|
return toSm2PrivateParams(privateKeyBytes);
|
||||||
@ -468,6 +471,9 @@ public class ECKeyUtil {
|
|||||||
* @since 5.5.9
|
* @since 5.5.9
|
||||||
*/
|
*/
|
||||||
public static ECPublicKeyParameters decodePublicKeyParams(final byte[] publicKeyBytes) {
|
public static ECPublicKeyParameters decodePublicKeyParams(final byte[] publicKeyBytes) {
|
||||||
|
if(null == publicKeyBytes){
|
||||||
|
return null;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
// 尝试Q值
|
// 尝试Q值
|
||||||
return toSm2PublicParams(publicKeyBytes);
|
return toSm2PublicParams(publicKeyBytes);
|
||||||
|
@ -545,7 +545,7 @@ public class BCrypt {
|
|||||||
if (hashed_bytes.length != try_bytes.length) {
|
if (hashed_bytes.length != try_bytes.length) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
byte ret = 0;
|
int ret = 0;
|
||||||
for (int i = 0; i < try_bytes.length; i++)
|
for (int i = 0; i < try_bytes.length; i++)
|
||||||
ret |= hashed_bytes[i] ^ try_bytes[i];
|
ret |= hashed_bytes[i] ^ try_bytes[i];
|
||||||
return ret == 0;
|
return ret == 0;
|
||||||
|
@ -19,11 +19,29 @@ package org.dromara.hutool.crypto.digest;
|
|||||||
* @author Looly
|
* @author Looly
|
||||||
*/
|
*/
|
||||||
public enum DigestAlgorithm {
|
public enum DigestAlgorithm {
|
||||||
|
/**
|
||||||
|
* MD2
|
||||||
|
*/
|
||||||
MD2("MD2"),
|
MD2("MD2"),
|
||||||
|
/**
|
||||||
|
* MD5
|
||||||
|
*/
|
||||||
MD5("MD5"),
|
MD5("MD5"),
|
||||||
|
/**
|
||||||
|
* SHA-1
|
||||||
|
*/
|
||||||
SHA1("SHA-1"),
|
SHA1("SHA-1"),
|
||||||
|
/**
|
||||||
|
* SHA-256
|
||||||
|
*/
|
||||||
SHA256("SHA-256"),
|
SHA256("SHA-256"),
|
||||||
|
/**
|
||||||
|
* SHA-384
|
||||||
|
*/
|
||||||
SHA384("SHA-384"),
|
SHA384("SHA-384"),
|
||||||
|
/**
|
||||||
|
* SHA-512
|
||||||
|
*/
|
||||||
SHA512("SHA-512");
|
SHA512("SHA-512");
|
||||||
|
|
||||||
private final String value;
|
private final String value;
|
||||||
|
@ -253,7 +253,7 @@ public class Digester extends SimpleWrapper<MessageDigest> implements Serializab
|
|||||||
// 加盐在末尾,自动忽略空盐值
|
// 加盐在末尾,自动忽略空盐值
|
||||||
result = doDigest(data, this.salt);
|
result = doDigest(data, this.salt);
|
||||||
} else if (ArrayUtil.isNotEmpty(this.salt)) {
|
} else if (ArrayUtil.isNotEmpty(this.salt)) {
|
||||||
final MessageDigest digest = getRaw();
|
final MessageDigest digest = this.raw;
|
||||||
// 加盐在中间
|
// 加盐在中间
|
||||||
digest.update(data, 0, this.saltPosition);
|
digest.update(data, 0, this.saltPosition);
|
||||||
digest.update(this.salt);
|
digest.update(this.salt);
|
||||||
|
@ -27,7 +27,7 @@ import java.security.Provider;
|
|||||||
public class DigesterFactory {
|
public class DigesterFactory {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 创建工厂
|
* 创建工厂,只使用JDK提供的算法
|
||||||
*
|
*
|
||||||
* @param algorithm 算法
|
* @param algorithm 算法
|
||||||
* @return DigesterFactory
|
* @return DigesterFactory
|
||||||
|
@ -75,16 +75,4 @@ public class BCHMacEngine extends BCMacEngine {
|
|||||||
super(mac, params);
|
super(mac, params);
|
||||||
}
|
}
|
||||||
// ------------------------------------------------------------------------------------------- Constructor end
|
// ------------------------------------------------------------------------------------------- Constructor end
|
||||||
|
|
||||||
/**
|
|
||||||
* 初始化
|
|
||||||
*
|
|
||||||
* @param digest 摘要算法
|
|
||||||
* @param params 参数,例如密钥可以用{@link KeyParameter}
|
|
||||||
* @return this
|
|
||||||
* @see #init(Mac, CipherParameters)
|
|
||||||
*/
|
|
||||||
public BCHMacEngine init(final Digest digest, final CipherParameters params) {
|
|
||||||
return (BCHMacEngine) init(new HMac(digest), params);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,7 @@ package org.dromara.hutool.crypto.digest.mac;
|
|||||||
import org.bouncycastle.crypto.CipherParameters;
|
import org.bouncycastle.crypto.CipherParameters;
|
||||||
import org.bouncycastle.crypto.Mac;
|
import org.bouncycastle.crypto.Mac;
|
||||||
import org.bouncycastle.crypto.params.KeyParameter;
|
import org.bouncycastle.crypto.params.KeyParameter;
|
||||||
|
import org.dromara.hutool.core.lang.wrapper.SimpleWrapper;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* BouncyCastle的MAC算法实现引擎,使用{@link Mac} 实现摘要<br>
|
* BouncyCastle的MAC算法实现引擎,使用{@link Mac} 实现摘要<br>
|
||||||
@ -23,9 +24,7 @@ import org.bouncycastle.crypto.params.KeyParameter;
|
|||||||
* @author Looly
|
* @author Looly
|
||||||
* @since 5.8.0
|
* @since 5.8.0
|
||||||
*/
|
*/
|
||||||
public class BCMacEngine implements MacEngine {
|
public class BCMacEngine extends SimpleWrapper<Mac> implements MacEngine {
|
||||||
|
|
||||||
private Mac mac;
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------------------- Constructor start
|
// ------------------------------------------------------------------------------------------- Constructor start
|
||||||
/**
|
/**
|
||||||
@ -36,57 +35,46 @@ public class BCMacEngine implements MacEngine {
|
|||||||
* @since 5.8.0
|
* @since 5.8.0
|
||||||
*/
|
*/
|
||||||
public BCMacEngine(final Mac mac, final CipherParameters params) {
|
public BCMacEngine(final Mac mac, final CipherParameters params) {
|
||||||
init(mac, params);
|
super(initMac(mac, params));
|
||||||
}
|
}
|
||||||
// ------------------------------------------------------------------------------------------- Constructor end
|
// ------------------------------------------------------------------------------------------- Constructor end
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void update(final byte[] in, final int inOff, final int len) {
|
||||||
|
this.raw.update(in, inOff, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] doFinal() {
|
||||||
|
final byte[] result = new byte[getMacLength()];
|
||||||
|
this.raw.doFinal(result, 0);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void reset() {
|
||||||
|
this.raw.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getMacLength() {
|
||||||
|
return this.raw.getMacSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getAlgorithm() {
|
||||||
|
return this.raw.getAlgorithmName();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 初始化
|
* 初始化
|
||||||
*
|
*
|
||||||
* @param mac 摘要算法
|
* @param mac 摘要算法
|
||||||
* @param params 参数,例如密钥可以用{@link KeyParameter}
|
* @param params 参数,例如密钥可以用{@link KeyParameter}
|
||||||
* @return this
|
* @return this
|
||||||
* @since 5.8.0
|
|
||||||
*/
|
*/
|
||||||
public BCMacEngine init(final Mac mac, final CipherParameters params) {
|
private static Mac initMac(final Mac mac, final CipherParameters params) {
|
||||||
mac.init(params);
|
mac.init(params);
|
||||||
this.mac = mac;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获得 {@link Mac}
|
|
||||||
*
|
|
||||||
* @return {@link Mac}
|
|
||||||
*/
|
|
||||||
public Mac ipgetMac() {
|
|
||||||
return mac;
|
return mac;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void update(final byte[] in, final int inOff, final int len) {
|
|
||||||
this.mac.update(in, inOff, len);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public byte[] doFinal() {
|
|
||||||
final byte[] result = new byte[getMacLength()];
|
|
||||||
this.mac.doFinal(result, 0);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void reset() {
|
|
||||||
this.mac.reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getMacLength() {
|
|
||||||
return mac.getMacSize();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getAlgorithm() {
|
|
||||||
return this.mac.getAlgorithmName();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,6 @@ package org.dromara.hutool.crypto.digest.mac;
|
|||||||
import org.bouncycastle.crypto.BlockCipher;
|
import org.bouncycastle.crypto.BlockCipher;
|
||||||
import org.bouncycastle.crypto.CipherParameters;
|
import org.bouncycastle.crypto.CipherParameters;
|
||||||
import org.bouncycastle.crypto.Digest;
|
import org.bouncycastle.crypto.Digest;
|
||||||
import org.bouncycastle.crypto.Mac;
|
|
||||||
import org.bouncycastle.crypto.macs.CBCBlockCipherMac;
|
import org.bouncycastle.crypto.macs.CBCBlockCipherMac;
|
||||||
import org.bouncycastle.crypto.params.KeyParameter;
|
import org.bouncycastle.crypto.params.KeyParameter;
|
||||||
import org.bouncycastle.crypto.params.ParametersWithIV;
|
import org.bouncycastle.crypto.params.ParametersWithIV;
|
||||||
@ -97,16 +96,4 @@ public class CBCBlockCipherMacEngine extends BCMacEngine {
|
|||||||
public CBCBlockCipherMacEngine(final CBCBlockCipherMac mac, final CipherParameters params) {
|
public CBCBlockCipherMacEngine(final CBCBlockCipherMac mac, final CipherParameters params) {
|
||||||
super(mac, params);
|
super(mac, params);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 初始化
|
|
||||||
*
|
|
||||||
* @param cipher {@link BlockCipher}
|
|
||||||
* @param params 参数,例如密钥可以用{@link KeyParameter}
|
|
||||||
* @return this
|
|
||||||
* @see #init(Mac, CipherParameters)
|
|
||||||
*/
|
|
||||||
public CBCBlockCipherMacEngine init(final BlockCipher cipher, final CipherParameters params) {
|
|
||||||
return (CBCBlockCipherMacEngine) init(new CBCBlockCipherMac(cipher), params);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
|
|
||||||
package org.dromara.hutool.crypto.digest.mac;
|
package org.dromara.hutool.crypto.digest.mac;
|
||||||
|
|
||||||
|
import org.dromara.hutool.core.lang.wrapper.SimpleWrapper;
|
||||||
import org.dromara.hutool.crypto.CryptoException;
|
import org.dromara.hutool.crypto.CryptoException;
|
||||||
import org.dromara.hutool.crypto.KeyUtil;
|
import org.dromara.hutool.crypto.KeyUtil;
|
||||||
import org.dromara.hutool.crypto.SecureUtil;
|
import org.dromara.hutool.crypto.SecureUtil;
|
||||||
@ -23,18 +24,15 @@ import java.security.Key;
|
|||||||
import java.security.spec.AlgorithmParameterSpec;
|
import java.security.spec.AlgorithmParameterSpec;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 默认的HMAC算法实现引擎,使用{@link Mac} 实现摘要<br>
|
* JDK提供的的MAC算法实现引擎,使用{@link Mac} 实现摘要<br>
|
||||||
* 当引入BouncyCastle库时自动使用其作为Provider
|
* 当引入BouncyCastle库时自动使用其作为Provider
|
||||||
*
|
*
|
||||||
* @author Looly
|
* @author Looly
|
||||||
* @since 4.5.13
|
* @since 4.5.13
|
||||||
*/
|
*/
|
||||||
public class DefaultHMacEngine implements MacEngine {
|
public class JCEMacEngine extends SimpleWrapper<Mac> implements MacEngine {
|
||||||
|
|
||||||
private Mac mac;
|
|
||||||
|
|
||||||
// region ----- Constructor
|
// region ----- Constructor
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 构造
|
* 构造
|
||||||
*
|
*
|
||||||
@ -42,7 +40,7 @@ public class DefaultHMacEngine implements MacEngine {
|
|||||||
* @param key 密钥
|
* @param key 密钥
|
||||||
* @since 4.5.13
|
* @since 4.5.13
|
||||||
*/
|
*/
|
||||||
public DefaultHMacEngine(final String algorithm, final byte[] key) {
|
public JCEMacEngine(final String algorithm, final byte[] key) {
|
||||||
this(algorithm, (null == key) ? null : new SecretKeySpec(key, algorithm));
|
this(algorithm, (null == key) ? null : new SecretKeySpec(key, algorithm));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -53,7 +51,7 @@ public class DefaultHMacEngine implements MacEngine {
|
|||||||
* @param key 密钥
|
* @param key 密钥
|
||||||
* @since 4.5.13
|
* @since 4.5.13
|
||||||
*/
|
*/
|
||||||
public DefaultHMacEngine(final String algorithm, final Key key) {
|
public JCEMacEngine(final String algorithm, final Key key) {
|
||||||
this(algorithm, key, null);
|
this(algorithm, key, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -65,34 +63,39 @@ public class DefaultHMacEngine implements MacEngine {
|
|||||||
* @param spec {@link AlgorithmParameterSpec}
|
* @param spec {@link AlgorithmParameterSpec}
|
||||||
* @since 5.7.12
|
* @since 5.7.12
|
||||||
*/
|
*/
|
||||||
public DefaultHMacEngine(final String algorithm, final Key key, final AlgorithmParameterSpec spec) {
|
public JCEMacEngine(final String algorithm, final Key key, final AlgorithmParameterSpec spec) {
|
||||||
init(algorithm, key, spec);
|
super(initMac(algorithm, key, spec));
|
||||||
}
|
}
|
||||||
// endregion
|
// endregion
|
||||||
|
|
||||||
// region ----- init
|
@Override
|
||||||
|
public void update(final byte[] in) {
|
||||||
/**
|
this.raw.update(in);
|
||||||
* 初始化
|
|
||||||
*
|
|
||||||
* @param algorithm 算法
|
|
||||||
* @param key 密钥
|
|
||||||
* @return this
|
|
||||||
*/
|
|
||||||
public DefaultHMacEngine init(final String algorithm, final byte[] key) {
|
|
||||||
return init(algorithm, (null == key) ? null : new SecretKeySpec(key, algorithm));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
@Override
|
||||||
* 初始化
|
public void update(final byte[] in, final int inOff, final int len) {
|
||||||
*
|
this.raw.update(in, inOff, len);
|
||||||
* @param algorithm 算法
|
}
|
||||||
* @param key 密钥 {@link SecretKey}
|
|
||||||
* @return this
|
@Override
|
||||||
* @throws CryptoException Cause by IOException
|
public byte[] doFinal() {
|
||||||
*/
|
return this.raw.doFinal();
|
||||||
public DefaultHMacEngine init(final String algorithm, final Key key) {
|
}
|
||||||
return init(algorithm, key, null);
|
|
||||||
|
@Override
|
||||||
|
public void reset() {
|
||||||
|
this.raw.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getMacLength() {
|
||||||
|
return this.raw.getMacLength();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getAlgorithm() {
|
||||||
|
return this.raw.getAlgorithm();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -103,9 +106,9 @@ public class DefaultHMacEngine implements MacEngine {
|
|||||||
* @param spec {@link AlgorithmParameterSpec}
|
* @param spec {@link AlgorithmParameterSpec}
|
||||||
* @return this
|
* @return this
|
||||||
* @throws CryptoException Cause by IOException
|
* @throws CryptoException Cause by IOException
|
||||||
* @since 5.7.12
|
|
||||||
*/
|
*/
|
||||||
public DefaultHMacEngine init(final String algorithm, Key key, final AlgorithmParameterSpec spec) {
|
private static Mac initMac(final String algorithm, Key key, final AlgorithmParameterSpec spec) {
|
||||||
|
final Mac mac;
|
||||||
try {
|
try {
|
||||||
mac = SecureUtil.createMac(algorithm);
|
mac = SecureUtil.createMac(algorithm);
|
||||||
if (null == key) {
|
if (null == key) {
|
||||||
@ -119,46 +122,6 @@ public class DefaultHMacEngine implements MacEngine {
|
|||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
throw new CryptoException(e);
|
throw new CryptoException(e);
|
||||||
}
|
}
|
||||||
return this;
|
|
||||||
}
|
|
||||||
// endregion
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获得 {@link Mac}
|
|
||||||
*
|
|
||||||
* @return {@link Mac}
|
|
||||||
*/
|
|
||||||
public Mac getMac() {
|
|
||||||
return mac;
|
return mac;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void update(final byte[] in) {
|
|
||||||
this.mac.update(in);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void update(final byte[] in, final int inOff, final int len) {
|
|
||||||
this.mac.update(in, inOff, len);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public byte[] doFinal() {
|
|
||||||
return this.mac.doFinal();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void reset() {
|
|
||||||
this.mac.reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getMacLength() {
|
|
||||||
return mac.getMacLength();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getAlgorithm() {
|
|
||||||
return this.mac.getAlgorithm();
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -50,6 +50,6 @@ public class MacEngineFactory {
|
|||||||
// HmacSM3算法是BC库实现的,忽略加盐
|
// HmacSM3算法是BC库实现的,忽略加盐
|
||||||
return SmUtil.createHmacSm3Engine(key.getEncoded());
|
return SmUtil.createHmacSm3Engine(key.getEncoded());
|
||||||
}
|
}
|
||||||
return new DefaultHMacEngine(algorithm, key, spec);
|
return new JCEMacEngine(algorithm, key, spec);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,6 +22,21 @@
|
|||||||
* HMAC 可以与任何迭代散列函数捆绑使用。MD5 和 SHA-1 就是这种散列函数。HMAC 还可以使用一个用于计算和确认消息鉴别值的密钥。
|
* HMAC 可以与任何迭代散列函数捆绑使用。MD5 和 SHA-1 就是这种散列函数。HMAC 还可以使用一个用于计算和确认消息鉴别值的密钥。
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
|
* <pre>{@code
|
||||||
|
* MacEngineFactory
|
||||||
|
* ||(创建)
|
||||||
|
* MacEngine----------------(包装)-----------------> Mac
|
||||||
|
* _____|_______________ |
|
||||||
|
* / \ HMac
|
||||||
|
* JCEMacEngine BCMacEngine
|
||||||
|
* / \
|
||||||
|
* BCHMacEngine CBCBlockCipherMacEngine
|
||||||
|
* |
|
||||||
|
* SM4MacEngine
|
||||||
|
* }</pre>
|
||||||
|
*
|
||||||
|
* 通过MacEngine,封装支持了BouncyCastle和JCE实现的一些MAC算法,通过MacEngineFactory自动根据算法名称创建对应对象。
|
||||||
|
*
|
||||||
* @author Looly
|
* @author Looly
|
||||||
* @since 4.5.13
|
* @since 4.5.13
|
||||||
*/
|
*/
|
||||||
|
@ -21,6 +21,12 @@
|
|||||||
* Truncate:是一个函数,就是怎么截取加密后的串,并取加密后串的哪些字段组成一个数字。
|
* Truncate:是一个函数,就是怎么截取加密后的串,并取加密后串的哪些字段组成一个数字。
|
||||||
* </pre>
|
* </pre>
|
||||||
*
|
*
|
||||||
|
* 实现包括:
|
||||||
|
* <ul>
|
||||||
|
* <li>HMAC-based one-time passwords (HOTP) 基于HMAC算法一次性密码生成器</li>
|
||||||
|
* <li>time-based one-time passwords (TOTP) 基于时间戳算法的一次性密码生成器</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
* @author looly
|
* @author looly
|
||||||
*/
|
*/
|
||||||
package org.dromara.hutool.crypto.digest.otp;
|
package org.dromara.hutool.crypto.digest.otp;
|
||||||
|
@ -34,8 +34,6 @@ import java.io.IOException;
|
|||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.security.InvalidAlgorithmParameterException;
|
|
||||||
import java.security.InvalidKeyException;
|
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
import java.security.spec.AlgorithmParameterSpec;
|
import java.security.spec.AlgorithmParameterSpec;
|
||||||
import java.util.concurrent.locks.Lock;
|
import java.util.concurrent.locks.Lock;
|
||||||
@ -52,7 +50,15 @@ import java.util.concurrent.locks.ReentrantLock;
|
|||||||
public class SymmetricCrypto implements SymmetricEncryptor, SymmetricDecryptor, Serializable {
|
public class SymmetricCrypto implements SymmetricEncryptor, SymmetricDecryptor, Serializable {
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
private CipherWrapper cipherWrapper;
|
private JceCipher cipher;
|
||||||
|
/**
|
||||||
|
* 算法参数
|
||||||
|
*/
|
||||||
|
private AlgorithmParameterSpec algorithmParameterSpec;
|
||||||
|
/**
|
||||||
|
* 自定义随机数
|
||||||
|
*/
|
||||||
|
private SecureRandom random;
|
||||||
/**
|
/**
|
||||||
* SecretKey 负责保存对称密钥
|
* SecretKey 负责保存对称密钥
|
||||||
*/
|
*/
|
||||||
@ -157,7 +163,7 @@ public class SymmetricCrypto implements SymmetricEncryptor, SymmetricDecryptor,
|
|||||||
this.isZeroPadding = true;
|
this.isZeroPadding = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.cipherWrapper = new CipherWrapper(algorithm);
|
this.cipher = new JceCipher(algorithm);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -176,17 +182,17 @@ public class SymmetricCrypto implements SymmetricEncryptor, SymmetricDecryptor,
|
|||||||
* @return 加密或解密
|
* @return 加密或解密
|
||||||
*/
|
*/
|
||||||
public Cipher getCipher() {
|
public Cipher getCipher() {
|
||||||
return cipherWrapper.getRaw();
|
return cipher.getRaw();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 设置 {@link AlgorithmParameterSpec},通常用于加盐或偏移向量
|
* 设置{@link AlgorithmParameterSpec},通常用于加盐或偏移向量
|
||||||
*
|
*
|
||||||
* @param params {@link AlgorithmParameterSpec}
|
* @param algorithmParameterSpec {@link AlgorithmParameterSpec}
|
||||||
* @return 自身
|
* @return this
|
||||||
*/
|
*/
|
||||||
public SymmetricCrypto setParams(final AlgorithmParameterSpec params) {
|
public SymmetricCrypto setAlgorithmParameterSpec(final AlgorithmParameterSpec algorithmParameterSpec) {
|
||||||
this.cipherWrapper.setParams(params);
|
this.algorithmParameterSpec = algorithmParameterSpec;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -197,7 +203,7 @@ public class SymmetricCrypto implements SymmetricEncryptor, SymmetricDecryptor,
|
|||||||
* @return 自身
|
* @return 自身
|
||||||
*/
|
*/
|
||||||
public SymmetricCrypto setIv(final IvParameterSpec iv) {
|
public SymmetricCrypto setIv(final IvParameterSpec iv) {
|
||||||
return setParams(iv);
|
return setAlgorithmParameterSpec(iv);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -218,7 +224,7 @@ public class SymmetricCrypto implements SymmetricEncryptor, SymmetricDecryptor,
|
|||||||
* @since 5.7.17
|
* @since 5.7.17
|
||||||
*/
|
*/
|
||||||
public SymmetricCrypto setRandom(final SecureRandom random) {
|
public SymmetricCrypto setRandom(final SecureRandom random) {
|
||||||
this.cipherWrapper.setRandom(random);
|
this.random = random;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -245,9 +251,7 @@ public class SymmetricCrypto implements SymmetricEncryptor, SymmetricDecryptor,
|
|||||||
public SymmetricCrypto setMode(final CipherMode mode, final byte[] salt) {
|
public SymmetricCrypto setMode(final CipherMode mode, final byte[] salt) {
|
||||||
lock.lock();
|
lock.lock();
|
||||||
try {
|
try {
|
||||||
initMode(mode.getValue(), salt);
|
initMode(mode, salt);
|
||||||
} catch (final Exception e) {
|
|
||||||
throw new CryptoException(e);
|
|
||||||
} finally {
|
} finally {
|
||||||
lock.unlock();
|
lock.unlock();
|
||||||
}
|
}
|
||||||
@ -263,7 +267,7 @@ public class SymmetricCrypto implements SymmetricEncryptor, SymmetricDecryptor,
|
|||||||
* @since 5.6.8
|
* @since 5.6.8
|
||||||
*/
|
*/
|
||||||
public byte[] update(final byte[] data) {
|
public byte[] update(final byte[] data) {
|
||||||
final Cipher cipher = cipherWrapper.getRaw();
|
final Cipher cipher = this.cipher.getRaw();
|
||||||
lock.lock();
|
lock.lock();
|
||||||
try {
|
try {
|
||||||
return cipher.update(paddingDataWithZero(data, cipher.getBlockSize()));
|
return cipher.update(paddingDataWithZero(data, cipher.getBlockSize()));
|
||||||
@ -305,10 +309,8 @@ public class SymmetricCrypto implements SymmetricEncryptor, SymmetricDecryptor,
|
|||||||
byte[] result;
|
byte[] result;
|
||||||
lock.lock();
|
lock.lock();
|
||||||
try {
|
try {
|
||||||
final Cipher cipher = initMode(Cipher.ENCRYPT_MODE, salt);
|
final JceCipher cipher = initMode(CipherMode.ENCRYPT, salt);
|
||||||
result = cipher.doFinal(paddingDataWithZero(data, cipher.getBlockSize()));
|
result = cipher.processFinal(paddingDataWithZero(data, cipher.getBlockSize()));
|
||||||
} catch (final Exception e) {
|
|
||||||
throw new CryptoException(e);
|
|
||||||
} finally {
|
} finally {
|
||||||
lock.unlock();
|
lock.unlock();
|
||||||
}
|
}
|
||||||
@ -320,8 +322,8 @@ public class SymmetricCrypto implements SymmetricEncryptor, SymmetricDecryptor,
|
|||||||
CipherOutputStream cipherOutputStream = null;
|
CipherOutputStream cipherOutputStream = null;
|
||||||
lock.lock();
|
lock.lock();
|
||||||
try {
|
try {
|
||||||
final Cipher cipher = initMode(Cipher.ENCRYPT_MODE, null);
|
final JceCipher cipher = initMode(CipherMode.ENCRYPT, null);
|
||||||
cipherOutputStream = new CipherOutputStream(out, cipher);
|
cipherOutputStream = new CipherOutputStream(out, cipher.getRaw());
|
||||||
final long length = IoUtil.copy(data, cipherOutputStream);
|
final long length = IoUtil.copy(data, cipherOutputStream);
|
||||||
if (this.isZeroPadding) {
|
if (this.isZeroPadding) {
|
||||||
final int blockSize = cipher.getBlockSize();
|
final int blockSize = cipher.getBlockSize();
|
||||||
@ -359,9 +361,9 @@ public class SymmetricCrypto implements SymmetricEncryptor, SymmetricDecryptor,
|
|||||||
lock.lock();
|
lock.lock();
|
||||||
try {
|
try {
|
||||||
final byte[] salt = SaltMagic.getSalt(bytes);
|
final byte[] salt = SaltMagic.getSalt(bytes);
|
||||||
final Cipher cipher = initMode(Cipher.DECRYPT_MODE, salt);
|
final JceCipher cipher = initMode(CipherMode.DECRYPT, salt);
|
||||||
blockSize = cipher.getBlockSize();
|
blockSize = cipher.getBlockSize();
|
||||||
decryptData = cipher.doFinal(SaltMagic.getData(bytes));
|
decryptData = cipher.processFinal(SaltMagic.getData(bytes));
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
throw new CryptoException(e);
|
throw new CryptoException(e);
|
||||||
} finally {
|
} finally {
|
||||||
@ -376,8 +378,8 @@ public class SymmetricCrypto implements SymmetricEncryptor, SymmetricDecryptor,
|
|||||||
CipherInputStream cipherInputStream = null;
|
CipherInputStream cipherInputStream = null;
|
||||||
lock.lock();
|
lock.lock();
|
||||||
try {
|
try {
|
||||||
final Cipher cipher = initMode(Cipher.DECRYPT_MODE, null);
|
final JceCipher cipher = initMode(CipherMode.DECRYPT, null);
|
||||||
cipherInputStream = new CipherInputStream(data, cipher);
|
cipherInputStream = new CipherInputStream(data, cipher.getRaw());
|
||||||
if (this.isZeroPadding) {
|
if (this.isZeroPadding) {
|
||||||
final int blockSize = cipher.getBlockSize();
|
final int blockSize = cipher.getBlockSize();
|
||||||
if (blockSize > 0) {
|
if (blockSize > 0) {
|
||||||
@ -417,8 +419,8 @@ public class SymmetricCrypto implements SymmetricEncryptor, SymmetricDecryptor,
|
|||||||
*/
|
*/
|
||||||
private SymmetricCrypto initParams(final String algorithm, AlgorithmParameterSpec paramsSpec) {
|
private SymmetricCrypto initParams(final String algorithm, AlgorithmParameterSpec paramsSpec) {
|
||||||
if (null == paramsSpec) {
|
if (null == paramsSpec) {
|
||||||
byte[] iv = Opt.ofNullable(cipherWrapper)
|
byte[] iv = Opt.ofNullable(cipher)
|
||||||
.map(CipherWrapper::getRaw).map(Cipher::getIV).get();
|
.map(JceCipher::getRaw).map(Cipher::getIV).get();
|
||||||
|
|
||||||
// 随机IV
|
// 随机IV
|
||||||
if (StrUtil.startWithIgnoreCase(algorithm, "PBE")) {
|
if (StrUtil.startWithIgnoreCase(algorithm, "PBE")) {
|
||||||
@ -435,18 +437,16 @@ public class SymmetricCrypto implements SymmetricEncryptor, SymmetricDecryptor,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return setParams(paramsSpec);
|
return setAlgorithmParameterSpec(paramsSpec);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 初始化{@link Cipher}为加密或者解密模式
|
* 初始化{@link JceCipher}为加密或者解密模式
|
||||||
*
|
*
|
||||||
* @param mode 模式,见{@link Cipher#ENCRYPT_MODE} 或 {@link Cipher#DECRYPT_MODE}
|
* @param mode 模式,见{@link CipherMode#ENCRYPT} 或 {@link CipherMode#DECRYPT}
|
||||||
* @return {@link Cipher}
|
* @return {@link Cipher}
|
||||||
* @throws InvalidKeyException 无效key
|
|
||||||
* @throws InvalidAlgorithmParameterException 无效算法
|
|
||||||
*/
|
*/
|
||||||
private Cipher initMode(final int mode, final byte[] salt) throws InvalidKeyException, InvalidAlgorithmParameterException {
|
private JceCipher initMode(final CipherMode mode, final byte[] salt) {
|
||||||
SecretKey secretKey = this.secretKey;
|
SecretKey secretKey = this.secretKey;
|
||||||
if (null != salt) {
|
if (null != salt) {
|
||||||
// /issues#I6YWWD,提供OpenSSL格式兼容支持
|
// /issues#I6YWWD,提供OpenSSL格式兼容支持
|
||||||
@ -454,11 +454,15 @@ public class SymmetricCrypto implements SymmetricEncryptor, SymmetricDecryptor,
|
|||||||
final byte[][] keyAndIV = OpenSSLSaltParser.ofMd5(32, algorithm)
|
final byte[][] keyAndIV = OpenSSLSaltParser.ofMd5(32, algorithm)
|
||||||
.getKeyAndIV(secretKey.getEncoded(), salt);
|
.getKeyAndIV(secretKey.getEncoded(), salt);
|
||||||
secretKey = KeyUtil.generateKey(algorithm, keyAndIV[0]);
|
secretKey = KeyUtil.generateKey(algorithm, keyAndIV[0]);
|
||||||
if(ArrayUtil.isNotEmpty(keyAndIV[1])){
|
if (ArrayUtil.isNotEmpty(keyAndIV[1])) {
|
||||||
this.cipherWrapper.setParams(new IvParameterSpec(keyAndIV[1]));
|
setAlgorithmParameterSpec(new IvParameterSpec(keyAndIV[1]));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return this.cipherWrapper.initMode(mode, secretKey).getRaw();
|
|
||||||
|
final JceCipher cipher = this.cipher;
|
||||||
|
cipher.init(mode,
|
||||||
|
new JceCipher.JceParameters(secretKey, this.algorithmParameterSpec, this.random));
|
||||||
|
return cipher;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -0,0 +1,28 @@
|
|||||||
|
package org.dromara.hutool.crypto.bc;
|
||||||
|
|
||||||
|
import org.bouncycastle.crypto.BufferedBlockCipher;
|
||||||
|
import org.bouncycastle.crypto.DefaultBufferedBlockCipher;
|
||||||
|
import org.bouncycastle.crypto.engines.SM4Engine;
|
||||||
|
import org.bouncycastle.crypto.modes.CBCBlockCipher;
|
||||||
|
import org.bouncycastle.crypto.params.KeyParameter;
|
||||||
|
import org.dromara.hutool.core.util.ByteUtil;
|
||||||
|
import org.dromara.hutool.crypto.CipherMode;
|
||||||
|
import org.junit.jupiter.api.Assertions;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
public class BCCipherTest {
|
||||||
|
@Test
|
||||||
|
void sm4Test() {
|
||||||
|
final byte[] data = ByteUtil.toUtf8Bytes("我是测试Hutool的字符串00");
|
||||||
|
|
||||||
|
final BufferedBlockCipher blockCipher = new DefaultBufferedBlockCipher(CBCBlockCipher.newInstance(new SM4Engine()));
|
||||||
|
final BCCipher bcCipher = new BCCipher(blockCipher);
|
||||||
|
bcCipher.init(CipherMode.ENCRYPT, new BCCipher.BCParameters(new KeyParameter(ByteUtil.toUtf8Bytes("1234567890000000"))));
|
||||||
|
final byte[] encryptData = bcCipher.processFinal(data);
|
||||||
|
|
||||||
|
bcCipher.init(CipherMode.DECRYPT, new BCCipher.BCParameters(new KeyParameter(ByteUtil.toUtf8Bytes("1234567890000000"))));
|
||||||
|
final byte[] decryptData = bcCipher.processFinal(encryptData);
|
||||||
|
|
||||||
|
Assertions.assertArrayEquals(data, decryptData);
|
||||||
|
}
|
||||||
|
}
|
@ -135,9 +135,9 @@ public class SymmetricTest {
|
|||||||
final AES aes = new AES(Mode.CBC, Padding.PKCS5Padding, "0123456789ABHAEQ".getBytes(), "DYgjCEIMVrj2W9xN".getBytes());
|
final AES aes = new AES(Mode.CBC, Padding.PKCS5Padding, "0123456789ABHAEQ".getBytes(), "DYgjCEIMVrj2W9xN".getBytes());
|
||||||
|
|
||||||
// 加密为16进制表示
|
// 加密为16进制表示
|
||||||
aes.setMode(CipherMode.encrypt);
|
aes.setMode(CipherMode.ENCRYPT);
|
||||||
final String randomData = aes.updateHex(content.getBytes(StandardCharsets.UTF_8));
|
final String randomData = aes.updateHex(content.getBytes(StandardCharsets.UTF_8));
|
||||||
aes.setMode(CipherMode.encrypt);
|
aes.setMode(CipherMode.ENCRYPT);
|
||||||
final String randomData2 = aes.updateHex(content.getBytes(StandardCharsets.UTF_8));
|
final String randomData2 = aes.updateHex(content.getBytes(StandardCharsets.UTF_8));
|
||||||
Assertions.assertEquals(randomData2, randomData);
|
Assertions.assertEquals(randomData2, randomData);
|
||||||
Assertions.assertEquals(randomData, "cd0e3a249eaf0ed80c330338508898c4");
|
Assertions.assertEquals(randomData, "cd0e3a249eaf0ed80c330338508898c4");
|
||||||
|
@ -28,7 +28,7 @@
|
|||||||
### SQL相关工具(sql)
|
### SQL相关工具(sql)
|
||||||
提供SQL相关功能,包括SQL变量替换(NamedSql),通过对象完成SQL构建(SqlBuilder)等。
|
提供SQL相关功能,包括SQL变量替换(NamedSql),通过对象完成SQL构建(SqlBuilder)等。
|
||||||
|
|
||||||
`SqlSqlExecutor`提供SQL执行的静态方法。
|
`SqlExecutor`提供SQL执行的静态方法。
|
||||||
|
|
||||||
### 数据库元信息(meta)
|
### 数据库元信息(meta)
|
||||||
通过`MetaUtil`提供数据库表、字段等信息的读取操作。
|
通过`MetaUtil`提供数据库表、字段等信息的读取操作。
|
||||||
|
@ -21,7 +21,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>org.dromara.hutool</groupId>
|
<groupId>org.dromara.hutool</groupId>
|
||||||
<artifactId>hutool-parent</artifactId>
|
<artifactId>hutool-parent</artifactId>
|
||||||
<version>6.0.0-M10</version>
|
<version>6.0.0-M11</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<artifactId>hutool-db</artifactId>
|
<artifactId>hutool-db</artifactId>
|
||||||
@ -38,7 +38,7 @@
|
|||||||
<druid.version>1.2.21</druid.version>
|
<druid.version>1.2.21</druid.version>
|
||||||
<!-- 固定4.x -->
|
<!-- 固定4.x -->
|
||||||
<hikariCP.version>4.0.3</hikariCP.version>
|
<hikariCP.version>4.0.3</hikariCP.version>
|
||||||
<sqlite.version>3.43.0.0</sqlite.version>
|
<sqlite.version>3.44.1.0</sqlite.version>
|
||||||
<!-- 此处固定2.5.x,支持到JDK8 -->
|
<!-- 此处固定2.5.x,支持到JDK8 -->
|
||||||
<hsqldb.version>2.5.2</hsqldb.version>
|
<hsqldb.version>2.5.2</hsqldb.version>
|
||||||
</properties>
|
</properties>
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (c) 2023 looly(loolly@aliyun.com)
|
* Copyright (c) 2023-2024. looly(loolly@aliyun.com)
|
||||||
* Hutool is licensed under Mulan PSL v2.
|
* Hutool is licensed under Mulan PSL v2.
|
||||||
* You can use this software according to the terms and conditions of the Mulan PSL v2.
|
* You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||||||
* You may obtain a copy of Mulan PSL v2 at:
|
* You may obtain a copy of Mulan PSL v2 at:
|
||||||
@ -12,14 +12,15 @@
|
|||||||
|
|
||||||
package org.dromara.hutool.db;
|
package org.dromara.hutool.db;
|
||||||
|
|
||||||
|
import org.dromara.hutool.core.array.ArrayUtil;
|
||||||
import org.dromara.hutool.core.collection.iter.ArrayIter;
|
import org.dromara.hutool.core.collection.iter.ArrayIter;
|
||||||
import org.dromara.hutool.core.lang.Assert;
|
import org.dromara.hutool.core.lang.Assert;
|
||||||
import org.dromara.hutool.db.handler.ResultSetUtil;
|
import org.dromara.hutool.db.handler.ResultSetUtil;
|
||||||
import org.dromara.hutool.db.handler.RsHandler;
|
import org.dromara.hutool.db.handler.RsHandler;
|
||||||
import org.dromara.hutool.db.sql.SqlBuilder;
|
import org.dromara.hutool.db.sql.SqlBuilder;
|
||||||
import org.dromara.hutool.db.sql.SqlLog;
|
|
||||||
import org.dromara.hutool.db.sql.StatementBuilder;
|
import org.dromara.hutool.db.sql.StatementBuilder;
|
||||||
import org.dromara.hutool.db.sql.StatementWrapper;
|
import org.dromara.hutool.db.sql.StatementWrapper;
|
||||||
|
import org.dromara.hutool.db.sql.filter.SqlLogFilter;
|
||||||
|
|
||||||
import java.sql.*;
|
import java.sql.*;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
@ -87,7 +88,7 @@ public class StatementUtil {
|
|||||||
return StatementBuilder.of()
|
return StatementBuilder.of()
|
||||||
.setConnection(conn)
|
.setConnection(conn)
|
||||||
.setReturnGeneratedKey(returnGeneratedKey)
|
.setReturnGeneratedKey(returnGeneratedKey)
|
||||||
.setSqlLog(SqlLog.INSTANCE)
|
.setSqlFilter(SqlLogFilter.INSTANCE)
|
||||||
.setSql(sql)
|
.setSql(sql)
|
||||||
.setParams(params)
|
.setParams(params)
|
||||||
.build();
|
.build();
|
||||||
@ -120,29 +121,10 @@ public class StatementUtil {
|
|||||||
return StatementBuilder.of()
|
return StatementBuilder.of()
|
||||||
.setConnection(conn)
|
.setConnection(conn)
|
||||||
.setReturnGeneratedKey(false)
|
.setReturnGeneratedKey(false)
|
||||||
.setSqlLog(SqlLog.INSTANCE)
|
.setSqlFilter(SqlLogFilter.INSTANCE)
|
||||||
.setSql(sql)
|
.setSql(sql)
|
||||||
.buildForBatch(paramsBatch);
|
.setParams(ArrayUtil.ofArray(paramsBatch, Object.class))
|
||||||
}
|
.buildForBatch();
|
||||||
|
|
||||||
/**
|
|
||||||
* 创建批量操作的{@link PreparedStatement}
|
|
||||||
*
|
|
||||||
* @param conn 数据库连接
|
|
||||||
* @param sql SQL语句,使用"?"做为占位符
|
|
||||||
* @param fields 字段列表,用于获取对应值
|
|
||||||
* @param entities "?"对应参数批次列表
|
|
||||||
* @return {@link PreparedStatement}
|
|
||||||
* @since 4.6.7
|
|
||||||
*/
|
|
||||||
public static PreparedStatement prepareStatementForBatch(final Connection conn, final String sql,
|
|
||||||
final Iterable<String> fields, final Entity... entities) {
|
|
||||||
return StatementBuilder.of()
|
|
||||||
.setConnection(conn)
|
|
||||||
.setReturnGeneratedKey(false)
|
|
||||||
.setSqlLog(SqlLog.INSTANCE)
|
|
||||||
.setSql(sql)
|
|
||||||
.buildForBatch(fields, entities);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -158,7 +140,7 @@ public class StatementUtil {
|
|||||||
public static CallableStatement prepareCall(final Connection conn, final String sql, final Object... params) throws SQLException {
|
public static CallableStatement prepareCall(final Connection conn, final String sql, final Object... params) throws SQLException {
|
||||||
return StatementBuilder.of()
|
return StatementBuilder.of()
|
||||||
.setConnection(conn)
|
.setConnection(conn)
|
||||||
.setSqlLog(SqlLog.INSTANCE)
|
.setSqlFilter(SqlLogFilter.INSTANCE)
|
||||||
.setSql(sql)
|
.setSql(sql)
|
||||||
.setParams(params)
|
.setParams(params)
|
||||||
.buildForCall();
|
.buildForCall();
|
||||||
|
@ -53,21 +53,20 @@ public class AnsiSqlDialect implements Dialect {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PreparedStatement psForInsert(final Connection conn, final Entity entity) throws SQLException {
|
public PreparedStatement psForInsert(final Connection conn, final Entity entity) {
|
||||||
final SqlBuilder insert = SqlBuilder.of(quoteWrapper).insert(entity, this.dialectName());
|
final SqlBuilder insert = SqlBuilder.of(quoteWrapper).insert(entity, this.dialectName());
|
||||||
|
|
||||||
return StatementUtil.prepareStatement(conn, insert);
|
return StatementUtil.prepareStatement(conn, insert);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PreparedStatement psForInsertBatch(final Connection conn, final Entity... entities) throws SQLException {
|
public PreparedStatement psForInsertBatch(final Connection conn, final Entity... entities) {
|
||||||
if (ArrayUtil.isEmpty(entities)) {
|
if (ArrayUtil.isEmpty(entities)) {
|
||||||
throw new DbRuntimeException("Entities for batch insert is empty !");
|
throw new DbRuntimeException("Entities for batch insert is empty !");
|
||||||
}
|
}
|
||||||
// 批量,根据第一行数据结构生成SQL占位符
|
// 批量,根据第一行数据结构生成SQL占位符
|
||||||
final SqlBuilder insert = SqlBuilder.of(quoteWrapper).insert(entities[0], this.dialectName());
|
final SqlBuilder insert = SqlBuilder.of(quoteWrapper).insert(entities[0], this.dialectName());
|
||||||
final Set<String> fields = CollUtil.remove(entities[0].keySet(), StrUtil::isBlank);
|
return StatementUtil.prepareStatementForBatch(conn, insert.build(), entities);
|
||||||
return StatementUtil.prepareStatementForBatch(conn, insert.build(), fields, entities);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -100,12 +99,12 @@ public class AnsiSqlDialect implements Dialect {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PreparedStatement psForFind(final Connection conn, final Query query) throws SQLException {
|
public PreparedStatement psForFind(final Connection conn, final Query query) {
|
||||||
return psForPage(conn, query);
|
return psForPage(conn, query);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PreparedStatement psForPage(final Connection conn, final Query query) throws SQLException {
|
public PreparedStatement psForPage(final Connection conn, final Query query) {
|
||||||
Assert.notNull(query, "query must be not null !");
|
Assert.notNull(query, "query must be not null !");
|
||||||
if (ArrayUtil.hasBlank(query.getTableNames())) {
|
if (ArrayUtil.hasBlank(query.getTableNames())) {
|
||||||
throw new DbRuntimeException("Table name must be not empty !");
|
throw new DbRuntimeException("Table name must be not empty !");
|
||||||
@ -116,7 +115,7 @@ public class AnsiSqlDialect implements Dialect {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PreparedStatement psForPage(final Connection conn, SqlBuilder sqlBuilder, final Page page) throws SQLException {
|
public PreparedStatement psForPage(final Connection conn, SqlBuilder sqlBuilder, final Page page) {
|
||||||
// 根据不同数据库在查询SQL语句基础上包装其分页的语句
|
// 根据不同数据库在查询SQL语句基础上包装其分页的语句
|
||||||
if (null != page) {
|
if (null != page) {
|
||||||
sqlBuilder = wrapPageSql(sqlBuilder.orderBy(page.getOrders()), page);
|
sqlBuilder = wrapPageSql(sqlBuilder.orderBy(page.getOrders()), page);
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
|
|
||||||
package org.dromara.hutool.db.dialect.impl;
|
package org.dromara.hutool.db.dialect.impl;
|
||||||
|
|
||||||
|
import org.dromara.hutool.core.text.StrPool;
|
||||||
import org.dromara.hutool.core.text.StrUtil;
|
import org.dromara.hutool.core.text.StrUtil;
|
||||||
import org.dromara.hutool.db.Page;
|
import org.dromara.hutool.db.Page;
|
||||||
import org.dromara.hutool.db.dialect.DialectName;
|
import org.dromara.hutool.db.dialect.DialectName;
|
||||||
@ -25,6 +26,10 @@ import org.dromara.hutool.db.sql.SqlBuilder;
|
|||||||
public class OracleDialect extends AnsiSqlDialect {
|
public class OracleDialect extends AnsiSqlDialect {
|
||||||
private static final long serialVersionUID = 6122761762247483015L;
|
private static final long serialVersionUID = 6122761762247483015L;
|
||||||
|
|
||||||
|
private static final String DEFAULT_TABLE_ALIAS = "table_alias_";
|
||||||
|
private static final String DEFAULT_ROW_ALIAS = "row_";
|
||||||
|
private static final String DEFAULT_ROWNUM_ALIAS = "rownum_";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 检查字段值是否为Oracle自增字段,自增字段以`.nextval`结尾
|
* 检查字段值是否为Oracle自增字段,自增字段以`.nextval`结尾
|
||||||
*
|
*
|
||||||
@ -36,6 +41,9 @@ public class OracleDialect extends AnsiSqlDialect {
|
|||||||
return (value instanceof CharSequence) && StrUtil.endWithIgnoreCase(value.toString(), ".nextval");
|
return (value instanceof CharSequence) && StrUtil.endWithIgnoreCase(value.toString(), ".nextval");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构造
|
||||||
|
*/
|
||||||
public OracleDialect() {
|
public OracleDialect() {
|
||||||
//Oracle所有字段名用双引号包围,防止字段名或表名与系统关键字冲突
|
//Oracle所有字段名用双引号包围,防止字段名或表名与系统关键字冲突
|
||||||
//wrapper = new Wrapper('"');
|
//wrapper = new Wrapper('"');
|
||||||
@ -44,11 +52,27 @@ public class OracleDialect extends AnsiSqlDialect {
|
|||||||
@Override
|
@Override
|
||||||
protected SqlBuilder wrapPageSql(final SqlBuilder find, final Page page) {
|
protected SqlBuilder wrapPageSql(final SqlBuilder find, final Page page) {
|
||||||
final int[] startEnd = page.getStartEnd();
|
final int[] startEnd = page.getStartEnd();
|
||||||
|
|
||||||
|
// 检查别名,避免重名
|
||||||
|
final String sql = find.toString();
|
||||||
|
String tableAlias = DEFAULT_TABLE_ALIAS;
|
||||||
|
while (sql.contains(tableAlias)) {
|
||||||
|
tableAlias += StrPool.UNDERLINE;
|
||||||
|
}
|
||||||
|
String rowAlias = DEFAULT_ROW_ALIAS;
|
||||||
|
while (sql.contains(rowAlias)) {
|
||||||
|
rowAlias += StrPool.UNDERLINE;
|
||||||
|
}
|
||||||
|
String rownumAlias = DEFAULT_ROWNUM_ALIAS;
|
||||||
|
while (sql.contains(rownumAlias)) {
|
||||||
|
rownumAlias += StrPool.UNDERLINE;
|
||||||
|
}
|
||||||
|
|
||||||
return find
|
return find
|
||||||
.insertPreFragment("SELECT * FROM ( SELECT row_.*, rownum rownum_ from ( ")
|
.insertPreFragment("SELECT * FROM ( SELECT " + rowAlias + ".*, rownum " + rownumAlias + " from ( ")
|
||||||
.append(" ) row_ where rownum <= ").append(startEnd[1])//
|
.append(" ) row_ where rownum <= ").append(startEnd[1])//
|
||||||
.append(") table_alias_")//
|
.append(") ").append(tableAlias)//
|
||||||
.append(" where table_alias_.rownum_ > ").append(startEnd[0]);//
|
.append(" where ").append(tableAlias).append(".rownum_ > ").append(startEnd[0]);//
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -29,6 +29,9 @@ import java.sql.SQLException;
|
|||||||
public class PhoenixDialect extends AnsiSqlDialect {
|
public class PhoenixDialect extends AnsiSqlDialect {
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构造
|
||||||
|
*/
|
||||||
public PhoenixDialect() {
|
public PhoenixDialect() {
|
||||||
// wrapper = new Wrapper('"');
|
// wrapper = new Wrapper('"');
|
||||||
}
|
}
|
||||||
|
@ -21,15 +21,17 @@ import org.dromara.hutool.db.sql.QuoteWrapper;
|
|||||||
/**
|
/**
|
||||||
* SQLServer2012 方言
|
* SQLServer2012 方言
|
||||||
*
|
*
|
||||||
* @author loolly
|
* @author Looly
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
public class SqlServer2012Dialect extends AnsiSqlDialect {
|
public class SqlServer2012Dialect extends AnsiSqlDialect {
|
||||||
private static final long serialVersionUID = -37598166015777797L;
|
private static final long serialVersionUID = -37598166015777797L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构造
|
||||||
|
*/
|
||||||
public SqlServer2012Dialect() {
|
public SqlServer2012Dialect() {
|
||||||
//双引号和中括号适用,双引号更广泛
|
//双引号和中括号适用,双引号更广泛
|
||||||
quoteWrapper = new QuoteWrapper('"');
|
quoteWrapper = new QuoteWrapper('"');
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -39,10 +41,10 @@ public class SqlServer2012Dialect extends AnsiSqlDialect {
|
|||||||
find.append(" order by current_timestamp");
|
find.append(" order by current_timestamp");
|
||||||
}
|
}
|
||||||
return find.append(" offset ")
|
return find.append(" offset ")
|
||||||
.append(page.getStartPosition())//
|
.append(page.getStartPosition())//
|
||||||
.append(" row fetch next ")//row和rows同义词
|
.append(" row fetch next ")//row和rows同义词
|
||||||
.append(page.getPageSize())//
|
.append(page.getPageSize())//
|
||||||
.append(" row only");//
|
.append(" row only");//
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -17,12 +17,15 @@ import org.dromara.hutool.db.sql.QuoteWrapper;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* SqlLite3方言
|
* SqlLite3方言
|
||||||
* @author loolly
|
|
||||||
*
|
*
|
||||||
|
* @author Looly
|
||||||
*/
|
*/
|
||||||
public class Sqlite3Dialect extends AnsiSqlDialect{
|
public class Sqlite3Dialect extends AnsiSqlDialect {
|
||||||
private static final long serialVersionUID = -3527642408849291634L;
|
private static final long serialVersionUID = -3527642408849291634L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构造
|
||||||
|
*/
|
||||||
public Sqlite3Dialect() {
|
public Sqlite3Dialect() {
|
||||||
quoteWrapper = new QuoteWrapper('[', ']');
|
quoteWrapper = new QuoteWrapper('[', ']');
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,7 @@ import org.dromara.hutool.core.map.SafeConcurrentHashMap;
|
|||||||
import org.dromara.hutool.core.spi.SpiUtil;
|
import org.dromara.hutool.core.spi.SpiUtil;
|
||||||
import org.dromara.hutool.core.text.StrUtil;
|
import org.dromara.hutool.core.text.StrUtil;
|
||||||
import org.dromara.hutool.db.DbRuntimeException;
|
import org.dromara.hutool.db.DbRuntimeException;
|
||||||
|
import org.dromara.hutool.db.DbUtil;
|
||||||
import org.dromara.hutool.db.GlobalDbConfig;
|
import org.dromara.hutool.db.GlobalDbConfig;
|
||||||
import org.dromara.hutool.db.driver.DriverUtil;
|
import org.dromara.hutool.db.driver.DriverUtil;
|
||||||
import org.dromara.hutool.log.LogUtil;
|
import org.dromara.hutool.log.LogUtil;
|
||||||
@ -89,6 +90,7 @@ public class DSPool implements Closeable {
|
|||||||
*/
|
*/
|
||||||
public DSPool(final Setting setting, final DSFactory factory) {
|
public DSPool(final Setting setting, final DSFactory factory) {
|
||||||
this.setting = null != setting ? setting : GlobalDbConfig.createDbSetting();
|
this.setting = null != setting ? setting : GlobalDbConfig.createDbSetting();
|
||||||
|
DbUtil.setShowSqlGlobal(this.setting);
|
||||||
this.factory = null != factory ? factory : SpiUtil.loadFirstAvailable(DSFactory.class);
|
this.factory = null != factory ? factory : SpiUtil.loadFirstAvailable(DSFactory.class);
|
||||||
this.pool = new SafeConcurrentHashMap<>();
|
this.pool = new SafeConcurrentHashMap<>();
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,7 @@ package org.dromara.hutool.db.ds;
|
|||||||
|
|
||||||
import org.dromara.hutool.core.exception.CloneException;
|
import org.dromara.hutool.core.exception.CloneException;
|
||||||
import org.dromara.hutool.core.io.IoUtil;
|
import org.dromara.hutool.core.io.IoUtil;
|
||||||
import org.dromara.hutool.core.lang.wrapper.Wrapper;
|
import org.dromara.hutool.core.lang.wrapper.SimpleWrapper;
|
||||||
|
|
||||||
import javax.sql.DataSource;
|
import javax.sql.DataSource;
|
||||||
import java.io.Closeable;
|
import java.io.Closeable;
|
||||||
@ -34,9 +34,8 @@ import java.util.logging.Logger;
|
|||||||
* @author looly
|
* @author looly
|
||||||
* @since 4.3.2
|
* @since 4.3.2
|
||||||
*/
|
*/
|
||||||
public class DSWrapper implements Wrapper<DataSource>, DataSource, Closeable, Cloneable {
|
public class DSWrapper extends SimpleWrapper<DataSource> implements DataSource, Closeable, Cloneable {
|
||||||
|
|
||||||
private final DataSource ds;
|
|
||||||
private final String driver;
|
private final String driver;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -57,7 +56,7 @@ public class DSWrapper implements Wrapper<DataSource>, DataSource, Closeable, Cl
|
|||||||
* @param driver 数据库驱动类名
|
* @param driver 数据库驱动类名
|
||||||
*/
|
*/
|
||||||
public DSWrapper(final DataSource ds, final String driver) {
|
public DSWrapper(final DataSource ds, final String driver) {
|
||||||
this.ds = ds;
|
super(ds);
|
||||||
this.driver = driver;
|
this.driver = driver;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -70,65 +69,56 @@ public class DSWrapper implements Wrapper<DataSource>, DataSource, Closeable, Cl
|
|||||||
return this.driver;
|
return this.driver;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取原始的数据源
|
|
||||||
*
|
|
||||||
* @return 原始数据源
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public DataSource getRaw() {
|
|
||||||
return this.ds;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PrintWriter getLogWriter() throws SQLException {
|
public PrintWriter getLogWriter() throws SQLException {
|
||||||
return ds.getLogWriter();
|
return this.raw.getLogWriter();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setLogWriter(final PrintWriter out) throws SQLException {
|
public void setLogWriter(final PrintWriter out) throws SQLException {
|
||||||
ds.setLogWriter(out);
|
this.raw.setLogWriter(out);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setLoginTimeout(final int seconds) throws SQLException {
|
public void setLoginTimeout(final int seconds) throws SQLException {
|
||||||
ds.setLoginTimeout(seconds);
|
this.raw.setLoginTimeout(seconds);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getLoginTimeout() throws SQLException {
|
public int getLoginTimeout() throws SQLException {
|
||||||
return ds.getLoginTimeout();
|
return this.raw.getLoginTimeout();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Logger getParentLogger() throws SQLFeatureNotSupportedException {
|
public Logger getParentLogger() throws SQLFeatureNotSupportedException {
|
||||||
return ds.getParentLogger();
|
return this.raw.getParentLogger();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public <T> T unwrap(final Class<T> iface) throws SQLException {
|
public <T> T unwrap(final Class<T> iface) throws SQLException {
|
||||||
return ds.unwrap(iface);
|
return this.raw.unwrap(iface);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isWrapperFor(final Class<?> iface) throws SQLException {
|
public boolean isWrapperFor(final Class<?> iface) throws SQLException {
|
||||||
return ds.isWrapperFor(iface);
|
return this.raw.isWrapperFor(iface);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Connection getConnection() throws SQLException {
|
public Connection getConnection() throws SQLException {
|
||||||
return ds.getConnection();
|
return this.raw.getConnection();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Connection getConnection(final String username, final String password) throws SQLException {
|
public Connection getConnection(final String username, final String password) throws SQLException {
|
||||||
return ds.getConnection(username, password);
|
return this.raw.getConnection(username, password);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close() {
|
public void close() {
|
||||||
if (this.ds instanceof AutoCloseable) {
|
final DataSource ds = this.raw;
|
||||||
IoUtil.closeQuietly((AutoCloseable) this.ds);
|
if (ds instanceof AutoCloseable) {
|
||||||
|
IoUtil.closeQuietly((AutoCloseable) ds);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
107
hutool-db/src/main/java/org/dromara/hutool/db/sql/BoundSql.java
Normal file
107
hutool-db/src/main/java/org/dromara/hutool/db/sql/BoundSql.java
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2024. looly(loolly@aliyun.com)
|
||||||
|
* Hutool is licensed under Mulan PSL v2.
|
||||||
|
* You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||||||
|
* You may obtain a copy of Mulan PSL v2 at:
|
||||||
|
* https://license.coscl.org.cn/MulanPSL2
|
||||||
|
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
||||||
|
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
||||||
|
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
||||||
|
* See the Mulan PSL v2 for more details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.dromara.hutool.db.sql;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 参数绑定的SQL封装,用于表示SQL语句模板('?'表示参数占位符)和参数值的封装<br>
|
||||||
|
* SQL中的'?'占位符必须和params列表中的参数值一一对应
|
||||||
|
*
|
||||||
|
* @author Looly
|
||||||
|
*/
|
||||||
|
public class BoundSql {
|
||||||
|
|
||||||
|
private String sql;
|
||||||
|
private List<Object> params;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构造
|
||||||
|
*/
|
||||||
|
public BoundSql() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构造
|
||||||
|
*
|
||||||
|
* @param sql SQL语句,参数占位符使用'?'表示
|
||||||
|
* @param params 参数列表,每个参数对应一个'?'
|
||||||
|
*/
|
||||||
|
public BoundSql(final String sql, final List<Object> params) {
|
||||||
|
this.sql = sql;
|
||||||
|
this.params = params;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取SQL
|
||||||
|
*
|
||||||
|
* @return SQL
|
||||||
|
*/
|
||||||
|
public String getSql() {
|
||||||
|
return this.sql;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置SQL语句
|
||||||
|
*
|
||||||
|
* @param sql SQL语句
|
||||||
|
* @return this
|
||||||
|
*/
|
||||||
|
public BoundSql setSql(final String sql) {
|
||||||
|
this.sql = sql;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取参数列表,按照占位符顺序
|
||||||
|
*
|
||||||
|
* @return 参数列表
|
||||||
|
*/
|
||||||
|
public List<Object> getParams() {
|
||||||
|
return this.params;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取参数列表,按照占位符顺序
|
||||||
|
*
|
||||||
|
* @return 参数数组
|
||||||
|
*/
|
||||||
|
public Object[] getParamArray() {
|
||||||
|
return this.params.toArray(new Object[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置参数列表
|
||||||
|
*
|
||||||
|
* @param params 参数列表
|
||||||
|
* @return this
|
||||||
|
*/
|
||||||
|
public BoundSql setParams(final List<Object> params) {
|
||||||
|
this.params = params;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 增加参数
|
||||||
|
*
|
||||||
|
* @param paramValue 参数值
|
||||||
|
* @return this
|
||||||
|
*/
|
||||||
|
public BoundSql addParam(final Object paramValue){
|
||||||
|
if(null == this.params){
|
||||||
|
this.params = new ArrayList<>();
|
||||||
|
}
|
||||||
|
this.params.add(paramValue);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
@ -12,13 +12,11 @@
|
|||||||
|
|
||||||
package org.dromara.hutool.db.sql;
|
package org.dromara.hutool.db.sql;
|
||||||
|
|
||||||
|
import org.dromara.hutool.core.array.ArrayUtil;
|
||||||
import org.dromara.hutool.core.map.MapUtil;
|
import org.dromara.hutool.core.map.MapUtil;
|
||||||
import org.dromara.hutool.core.text.StrUtil;
|
import org.dromara.hutool.core.text.StrUtil;
|
||||||
import org.dromara.hutool.core.array.ArrayUtil;
|
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -33,12 +31,12 @@ import java.util.Map;
|
|||||||
* @author looly
|
* @author looly
|
||||||
* @since 4.0.10
|
* @since 4.0.10
|
||||||
*/
|
*/
|
||||||
public class NamedSql {
|
public class NamedSql extends BoundSql {
|
||||||
|
|
||||||
private static final char[] NAME_START_CHARS = {':', '@', '?'};
|
private static final char[] NAME_START_CHARS = {':', '@', '?'};
|
||||||
|
|
||||||
private String sql;
|
private final String namedSql;
|
||||||
private final List<Object> params;
|
private final Map<String, Object> paramMap;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 构造
|
* 构造
|
||||||
@ -47,35 +45,27 @@ public class NamedSql {
|
|||||||
* @param paramMap 名和参数的对应Map
|
* @param paramMap 名和参数的对应Map
|
||||||
*/
|
*/
|
||||||
public NamedSql(final String namedSql, final Map<String, Object> paramMap) {
|
public NamedSql(final String namedSql, final Map<String, Object> paramMap) {
|
||||||
this.params = new LinkedList<>();
|
this.namedSql = namedSql;
|
||||||
|
this.paramMap = paramMap;
|
||||||
parse(namedSql, paramMap);
|
parse(namedSql, paramMap);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取SQL
|
* 获取原始地带名称占位符的SQL语句
|
||||||
*
|
*
|
||||||
* @return SQL
|
* @return 名称占位符的SQL
|
||||||
*/
|
*/
|
||||||
public String getSql() {
|
public String getNamedSql() {
|
||||||
return this.sql;
|
return namedSql;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取参数列表,按照占位符顺序
|
* 获取原始参数名和参数值对应关系参数表
|
||||||
*
|
*
|
||||||
* @return 参数数组
|
* @return 参数名和参数值对应关系参数表
|
||||||
*/
|
*/
|
||||||
public Object[] getParams() {
|
public Map<String, Object> getParamMap() {
|
||||||
return this.params.toArray(new Object[0]);
|
return paramMap;
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取参数列表,按照占位符顺序
|
|
||||||
*
|
|
||||||
* @return 参数列表
|
|
||||||
*/
|
|
||||||
public List<Object> getParamList() {
|
|
||||||
return this.params;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -86,7 +76,7 @@ public class NamedSql {
|
|||||||
*/
|
*/
|
||||||
private void parse(final String namedSql, final Map<String, Object> paramMap) {
|
private void parse(final String namedSql, final Map<String, Object> paramMap) {
|
||||||
if (MapUtil.isEmpty(paramMap)) {
|
if (MapUtil.isEmpty(paramMap)) {
|
||||||
this.sql = namedSql;
|
setSql(namedSql);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -124,7 +114,7 @@ public class NamedSql {
|
|||||||
replaceVar(nameStartChar, name, sqlBuilder, paramMap);
|
replaceVar(nameStartChar, name, sqlBuilder, paramMap);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.sql = sqlBuilder.toString();
|
setSql(sqlBuilder.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -163,11 +153,11 @@ public class NamedSql {
|
|||||||
sqlBuilder.append(',');
|
sqlBuilder.append(',');
|
||||||
}
|
}
|
||||||
sqlBuilder.append('?');
|
sqlBuilder.append('?');
|
||||||
this.params.add(ArrayUtil.get(paramValue, i));
|
addParam(ArrayUtil.get(paramValue, i));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
sqlBuilder.append('?');
|
sqlBuilder.append('?');
|
||||||
this.params.add(paramValue);
|
addParam(paramValue);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// 无变量对应值,原样输出
|
// 无变量对应值,原样输出
|
||||||
|
@ -44,7 +44,7 @@ public class SqlExecutor {
|
|||||||
*/
|
*/
|
||||||
public static int execute(final Connection conn, final String sql, final Map<String, Object> paramMap) throws DbRuntimeException {
|
public static int execute(final Connection conn, final String sql, final Map<String, Object> paramMap) throws DbRuntimeException {
|
||||||
final NamedSql namedSql = new NamedSql(sql, paramMap);
|
final NamedSql namedSql = new NamedSql(sql, paramMap);
|
||||||
return execute(conn, namedSql.getSql(), namedSql.getParams());
|
return execute(conn, namedSql.getSql(), namedSql.getParamArray());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -125,7 +125,7 @@ public class SqlExecutor {
|
|||||||
*/
|
*/
|
||||||
public static Long executeForGeneratedKey(final Connection conn, final String sql, final Map<String, Object> paramMap) throws DbRuntimeException {
|
public static Long executeForGeneratedKey(final Connection conn, final String sql, final Map<String, Object> paramMap) throws DbRuntimeException {
|
||||||
final NamedSql namedSql = new NamedSql(sql, paramMap);
|
final NamedSql namedSql = new NamedSql(sql, paramMap);
|
||||||
return executeForGeneratedKey(conn, namedSql.getSql(), namedSql.getParams());
|
return executeForGeneratedKey(conn, namedSql.getSql(), namedSql.getParamArray());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -241,7 +241,7 @@ public class SqlExecutor {
|
|||||||
*/
|
*/
|
||||||
public static <T> T query(final Connection conn, final String sql, final RsHandler<T> rsh, final Map<String, Object> paramMap) throws DbRuntimeException {
|
public static <T> T query(final Connection conn, final String sql, final RsHandler<T> rsh, final Map<String, Object> paramMap) throws DbRuntimeException {
|
||||||
final NamedSql namedSql = new NamedSql(sql, paramMap);
|
final NamedSql namedSql = new NamedSql(sql, paramMap);
|
||||||
return query(conn, namedSql.getSql(), rsh, namedSql.getParams());
|
return query(conn, namedSql.getSql(), rsh, namedSql.getParamArray());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
package org.dromara.hutool.db.sql;
|
package org.dromara.hutool.db.sql;
|
||||||
|
|
||||||
import org.dromara.hutool.core.array.ArrayUtil;
|
import org.dromara.hutool.core.array.ArrayUtil;
|
||||||
|
import org.dromara.hutool.core.collection.ListUtil;
|
||||||
import org.dromara.hutool.core.collection.iter.ArrayIter;
|
import org.dromara.hutool.core.collection.iter.ArrayIter;
|
||||||
import org.dromara.hutool.core.convert.Convert;
|
import org.dromara.hutool.core.convert.Convert;
|
||||||
import org.dromara.hutool.core.lang.Assert;
|
import org.dromara.hutool.core.lang.Assert;
|
||||||
@ -21,10 +22,13 @@ import org.dromara.hutool.core.map.MapUtil;
|
|||||||
import org.dromara.hutool.core.text.StrUtil;
|
import org.dromara.hutool.core.text.StrUtil;
|
||||||
import org.dromara.hutool.db.DbRuntimeException;
|
import org.dromara.hutool.db.DbRuntimeException;
|
||||||
import org.dromara.hutool.db.Entity;
|
import org.dromara.hutool.db.Entity;
|
||||||
|
import org.dromara.hutool.db.sql.filter.SqlFilter;
|
||||||
|
|
||||||
import java.sql.*;
|
import java.sql.*;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@link PreparedStatement}构建器,构建结果为{@link StatementWrapper}
|
* {@link PreparedStatement}构建器,构建结果为{@link StatementWrapper}
|
||||||
@ -44,20 +48,19 @@ public class StatementBuilder implements Builder<StatementWrapper> {
|
|||||||
return new StatementBuilder();
|
return new StatementBuilder();
|
||||||
}
|
}
|
||||||
|
|
||||||
private SqlLog sqlLog;
|
private final BoundSql boundSql = new BoundSql();
|
||||||
private Connection connection;
|
private Connection connection;
|
||||||
private String sql;
|
|
||||||
private Object[] params;
|
|
||||||
private boolean returnGeneratedKey = true;
|
private boolean returnGeneratedKey = true;
|
||||||
|
private SqlFilter sqlFilter;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 设置SQL日志
|
* 设置SQL日志
|
||||||
*
|
*
|
||||||
* @param sqlLog {@link SqlLog}
|
* @param sqlFilter {@link SqlFilter}
|
||||||
* @return this
|
* @return this
|
||||||
*/
|
*/
|
||||||
public StatementBuilder setSqlLog(final SqlLog sqlLog) {
|
public StatementBuilder setSqlFilter(final SqlFilter sqlFilter) {
|
||||||
this.sqlLog = sqlLog;
|
this.sqlFilter = sqlFilter;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -79,7 +82,7 @@ public class StatementBuilder implements Builder<StatementWrapper> {
|
|||||||
* @return this
|
* @return this
|
||||||
*/
|
*/
|
||||||
public StatementBuilder setSql(final String sql) {
|
public StatementBuilder setSql(final String sql) {
|
||||||
this.sql = StrUtil.trim(sql);
|
this.boundSql.setSql(sql);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -90,7 +93,7 @@ public class StatementBuilder implements Builder<StatementWrapper> {
|
|||||||
* @return this
|
* @return this
|
||||||
*/
|
*/
|
||||||
public StatementBuilder setParams(final Object... params) {
|
public StatementBuilder setParams(final Object... params) {
|
||||||
this.params = params;
|
this.boundSql.setParams(ListUtil.of(params));
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -105,6 +108,11 @@ public class StatementBuilder implements Builder<StatementWrapper> {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建{@link StatementWrapper}
|
||||||
|
*
|
||||||
|
* @return {@link StatementWrapper},{@code null}表示不执行
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public StatementWrapper build() {
|
public StatementWrapper build() {
|
||||||
try {
|
try {
|
||||||
@ -117,48 +125,37 @@ public class StatementBuilder implements Builder<StatementWrapper> {
|
|||||||
/**
|
/**
|
||||||
* 创建批量操作的{@link StatementWrapper}
|
* 创建批量操作的{@link StatementWrapper}
|
||||||
*
|
*
|
||||||
* @param paramsBatch "?"对应参数批次列表
|
* @return {@link StatementWrapper},{@code null}表示不执行
|
||||||
* @return {@link StatementWrapper}
|
|
||||||
* @throws DbRuntimeException SQL异常
|
* @throws DbRuntimeException SQL异常
|
||||||
*/
|
*/
|
||||||
public StatementWrapper buildForBatch(final Iterable<Object[]> paramsBatch) throws DbRuntimeException {
|
public StatementWrapper buildForBatch() throws DbRuntimeException {
|
||||||
|
final String sql = this.boundSql.getSql();
|
||||||
Assert.notBlank(sql, "Sql String must be not blank!");
|
Assert.notBlank(sql, "Sql String must be not blank!");
|
||||||
|
final List<Object> paramsBatch = this.boundSql.getParams();
|
||||||
|
|
||||||
sqlLog.log(sql, paramsBatch);
|
sqlFilter.filter(this.connection, this.boundSql, this.returnGeneratedKey);
|
||||||
|
|
||||||
final StatementWrapper ps;
|
final StatementWrapper ps;
|
||||||
try {
|
try {
|
||||||
ps = StatementWrapper.of(connection.prepareStatement(sql));
|
ps = StatementWrapper.of(connection.prepareStatement(sql));
|
||||||
final Map<Integer, Integer> nullTypeMap = new HashMap<>();
|
final Map<Integer, Integer> nullTypeMap = new HashMap<>();
|
||||||
for (final Object[] params : paramsBatch) {
|
Set<String> keys = null;
|
||||||
ps.fillParams(new ArrayIter<>(params), nullTypeMap);
|
for (final Object params : paramsBatch) {
|
||||||
ps.addBatch();
|
if (null == params) {
|
||||||
}
|
continue;
|
||||||
} catch (final SQLException e) {
|
}
|
||||||
throw new DbRuntimeException(e);
|
if (ArrayUtil.isArray(params)) {
|
||||||
}
|
ps.fillParams(new ArrayIter<>(params), nullTypeMap);
|
||||||
return ps;
|
} else if (params instanceof Entity) {
|
||||||
}
|
final Entity entity = (Entity) params;
|
||||||
|
// 对于多Entity批量插入的情况,为防止数据不对齐,故按照首行提供键值对筛选。
|
||||||
/**
|
if(null == keys){
|
||||||
* 创建批量操作的{@link StatementWrapper}
|
keys = entity.keySet();
|
||||||
*
|
ps.fillParams(entity.values(), nullTypeMap);
|
||||||
* @param fields 字段列表,用于获取对应值
|
} else{
|
||||||
* @param entities "?"对应参数批次列表
|
ps.fillParams(MapUtil.valuesOfKeys(entity, keys), nullTypeMap);
|
||||||
* @return {@link StatementWrapper}
|
}
|
||||||
* @throws DbRuntimeException SQL异常
|
}
|
||||||
*/
|
|
||||||
public StatementWrapper buildForBatch(final Iterable<String> fields, final Entity... entities) throws DbRuntimeException {
|
|
||||||
Assert.notBlank(sql, "Sql String must be not blank!");
|
|
||||||
|
|
||||||
sqlLog.logForBatch(sql);
|
|
||||||
|
|
||||||
final StatementWrapper ps;
|
|
||||||
try {
|
|
||||||
ps = StatementWrapper.of(connection.prepareStatement(sql));
|
|
||||||
final Map<Integer, Integer> nullTypeMap = new HashMap<>();
|
|
||||||
for (final Entity entity : entities) {
|
|
||||||
ps.fillParams(MapUtil.valuesOfKeys(entity, fields), nullTypeMap);
|
|
||||||
ps.addBatch();
|
ps.addBatch();
|
||||||
}
|
}
|
||||||
} catch (final SQLException e) {
|
} catch (final SQLException e) {
|
||||||
@ -170,12 +167,15 @@ public class StatementBuilder implements Builder<StatementWrapper> {
|
|||||||
/**
|
/**
|
||||||
* 创建存储过程或函数调用的{@link StatementWrapper}
|
* 创建存储过程或函数调用的{@link StatementWrapper}
|
||||||
*
|
*
|
||||||
* @return StatementWrapper
|
* @return StatementWrapper,{@code null}表示不执行
|
||||||
* @since 6.0.0
|
* @since 6.0.0
|
||||||
*/
|
*/
|
||||||
public CallableStatement buildForCall() {
|
public CallableStatement buildForCall() {
|
||||||
|
final String sql = this.boundSql.getSql();
|
||||||
|
final Object[] params = this.boundSql.getParamArray();
|
||||||
Assert.notBlank(sql, "Sql String must be not blank!");
|
Assert.notBlank(sql, "Sql String must be not blank!");
|
||||||
sqlLog.log(sql, ArrayUtil.isEmpty(params) ? null : params);
|
|
||||||
|
sqlFilter.filter(this.connection, this.boundSql, this.returnGeneratedKey);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return (CallableStatement) StatementWrapper
|
return (CallableStatement) StatementWrapper
|
||||||
@ -190,20 +190,23 @@ public class StatementBuilder implements Builder<StatementWrapper> {
|
|||||||
/**
|
/**
|
||||||
* 构建{@link StatementWrapper}
|
* 构建{@link StatementWrapper}
|
||||||
*
|
*
|
||||||
* @return {@link StatementWrapper}
|
* @return {@link StatementWrapper},{@code null}表示不执行
|
||||||
* @throws SQLException SQL异常
|
* @throws SQLException SQL异常
|
||||||
*/
|
*/
|
||||||
private StatementWrapper _build() throws SQLException {
|
private StatementWrapper _build() throws SQLException {
|
||||||
|
String sql = this.boundSql.getSql();
|
||||||
|
Object[] params = this.boundSql.getParamArray();
|
||||||
Assert.notBlank(sql, "Sql String must be not blank!");
|
Assert.notBlank(sql, "Sql String must be not blank!");
|
||||||
|
|
||||||
if (ArrayUtil.isNotEmpty(params) && 1 == params.length && params[0] instanceof Map) {
|
if (ArrayUtil.isNotEmpty(params) && 1 == params.length && params[0] instanceof Map) {
|
||||||
// 检查参数是否为命名方式的参数
|
// 检查参数是否为命名方式的参数
|
||||||
final NamedSql namedSql = new NamedSql(sql, Convert.toMap(String.class, Object.class, params[0]));
|
final NamedSql namedSql = new NamedSql(sql, Convert.toMap(String.class, Object.class, params[0]));
|
||||||
sql = namedSql.getSql();
|
sql = namedSql.getSql();
|
||||||
params = namedSql.getParams();
|
params = namedSql.getParamArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
sqlLog.log(sql, ArrayUtil.isEmpty(params) ? null : params);
|
sqlFilter.filter(this.connection, this.boundSql, this.returnGeneratedKey);
|
||||||
|
|
||||||
final PreparedStatement ps;
|
final PreparedStatement ps;
|
||||||
if (returnGeneratedKey && StrUtil.startWithIgnoreCase(sql, "insert")) {
|
if (returnGeneratedKey && StrUtil.startWithIgnoreCase(sql, "insert")) {
|
||||||
// 插入默认返回主键
|
// 插入默认返回主键
|
||||||
|
@ -0,0 +1,34 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2024. looly(loolly@aliyun.com)
|
||||||
|
* Hutool is licensed under Mulan PSL v2.
|
||||||
|
* You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||||||
|
* You may obtain a copy of Mulan PSL v2 at:
|
||||||
|
* https://license.coscl.org.cn/MulanPSL2
|
||||||
|
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
||||||
|
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
||||||
|
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
||||||
|
* See the Mulan PSL v2 for more details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.dromara.hutool.db.sql.filter;
|
||||||
|
|
||||||
|
import org.dromara.hutool.db.sql.BoundSql;
|
||||||
|
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SQL拦截器
|
||||||
|
*/
|
||||||
|
public interface SqlFilter {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 过滤
|
||||||
|
*
|
||||||
|
* @param conn {@link Connection}
|
||||||
|
* @param boundSql {@link BoundSql},包含SQL语句和参数,
|
||||||
|
* 可通过{@link BoundSql#setSql(String)}和{@link BoundSql#setParams(List)} 自定义SQL和参数
|
||||||
|
* @param returnGeneratedKey 是否自动生成主键
|
||||||
|
*/
|
||||||
|
void filter(Connection conn, BoundSql boundSql, boolean returnGeneratedKey);
|
||||||
|
}
|
@ -0,0 +1,55 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2024. looly(loolly@aliyun.com)
|
||||||
|
* Hutool is licensed under Mulan PSL v2.
|
||||||
|
* You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||||||
|
* You may obtain a copy of Mulan PSL v2 at:
|
||||||
|
* https://license.coscl.org.cn/MulanPSL2
|
||||||
|
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
||||||
|
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
||||||
|
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
||||||
|
* See the Mulan PSL v2 for more details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.dromara.hutool.db.sql.filter;
|
||||||
|
|
||||||
|
import org.dromara.hutool.core.lang.Console;
|
||||||
|
import org.dromara.hutool.db.sql.BoundSql;
|
||||||
|
import org.dromara.hutool.db.sql.SqlLog;
|
||||||
|
|
||||||
|
import java.sql.Connection;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SQL打印拦截器
|
||||||
|
*
|
||||||
|
* @author Looly
|
||||||
|
*/
|
||||||
|
public class SqlLogFilter implements SqlFilter {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 单例
|
||||||
|
*/
|
||||||
|
public static final SqlLogFilter INSTANCE = new SqlLogFilter();
|
||||||
|
|
||||||
|
private final SqlLog sqlLog;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构造,使用默认SqlLog
|
||||||
|
*/
|
||||||
|
public SqlLogFilter() {
|
||||||
|
this(SqlLog.INSTANCE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构造
|
||||||
|
*
|
||||||
|
* @param sqlLog {@link SqlLog}
|
||||||
|
*/
|
||||||
|
public SqlLogFilter(final SqlLog sqlLog) {
|
||||||
|
this.sqlLog = sqlLog;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void filter(final Connection conn, final BoundSql boundSql, final boolean returnGeneratedKey) {
|
||||||
|
sqlLog.log(boundSql.getSql(), boundSql.getParams());
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,18 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2024. looly(loolly@aliyun.com)
|
||||||
|
* Hutool is licensed under Mulan PSL v2.
|
||||||
|
* You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||||||
|
* You may obtain a copy of Mulan PSL v2 at:
|
||||||
|
* https://license.coscl.org.cn/MulanPSL2
|
||||||
|
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
||||||
|
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
||||||
|
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
||||||
|
* See the Mulan PSL v2 for more details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 提供SQL过滤器封装
|
||||||
|
*
|
||||||
|
* @author Looly
|
||||||
|
*/
|
||||||
|
package org.dromara.hutool.db.sql.filter;
|
@ -36,8 +36,8 @@ public class NamedSqlTest {
|
|||||||
final NamedSql namedSql = new NamedSql(sql, paramMap);
|
final NamedSql namedSql = new NamedSql(sql, paramMap);
|
||||||
//未指定参数原样输出
|
//未指定参数原样输出
|
||||||
Assertions.assertEquals("select * from table where id=@id and name = ? and nickName = ?", namedSql.getSql());
|
Assertions.assertEquals("select * from table where id=@id and name = ? and nickName = ?", namedSql.getSql());
|
||||||
Assertions.assertEquals("张三", namedSql.getParams()[0]);
|
Assertions.assertEquals("张三", namedSql.getParamArray()[0]);
|
||||||
Assertions.assertEquals("小豆豆", namedSql.getParams()[1]);
|
Assertions.assertEquals("小豆豆", namedSql.getParamArray()[1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -54,9 +54,9 @@ public class NamedSqlTest {
|
|||||||
final NamedSql namedSql = new NamedSql(sql, paramMap);
|
final NamedSql namedSql = new NamedSql(sql, paramMap);
|
||||||
Assertions.assertEquals("select * from table where id=? and name = ? and nickName = ?", namedSql.getSql());
|
Assertions.assertEquals("select * from table where id=? and name = ? and nickName = ?", namedSql.getSql());
|
||||||
//指定了null参数的依旧替换,参数值为null
|
//指定了null参数的依旧替换,参数值为null
|
||||||
Assertions.assertNull(namedSql.getParams()[0]);
|
Assertions.assertNull(namedSql.getParamArray()[0]);
|
||||||
Assertions.assertEquals("张三", namedSql.getParams()[1]);
|
Assertions.assertEquals("张三", namedSql.getParamArray()[1]);
|
||||||
Assertions.assertEquals("小豆豆", namedSql.getParams()[2]);
|
Assertions.assertEquals("小豆豆", namedSql.getParamArray()[2]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -92,9 +92,9 @@ public class NamedSqlTest {
|
|||||||
|
|
||||||
final NamedSql namedSql = new NamedSql(sql, paramMap);
|
final NamedSql namedSql = new NamedSql(sql, paramMap);
|
||||||
Assertions.assertEquals("select * from user where id in (?,?,?)", namedSql.getSql());
|
Assertions.assertEquals("select * from user where id in (?,?,?)", namedSql.getSql());
|
||||||
Assertions.assertEquals(1, namedSql.getParams()[0]);
|
Assertions.assertEquals(1, namedSql.getParamArray()[0]);
|
||||||
Assertions.assertEquals(2, namedSql.getParams()[1]);
|
Assertions.assertEquals(2, namedSql.getParamArray()[1]);
|
||||||
Assertions.assertEquals(3, namedSql.getParams()[2]);
|
Assertions.assertEquals(3, namedSql.getParamArray()[2]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -21,7 +21,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>org.dromara.hutool</groupId>
|
<groupId>org.dromara.hutool</groupId>
|
||||||
<artifactId>hutool-parent</artifactId>
|
<artifactId>hutool-parent</artifactId>
|
||||||
<version>6.0.0-M10</version>
|
<version>6.0.0-M11</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<artifactId>hutool-extra</artifactId>
|
<artifactId>hutool-extra</artifactId>
|
||||||
@ -32,14 +32,14 @@
|
|||||||
<Automatic-Module-Name>org.dromara.hutool.extra</Automatic-Module-Name>
|
<Automatic-Module-Name>org.dromara.hutool.extra</Automatic-Module-Name>
|
||||||
<!-- versions -->
|
<!-- versions -->
|
||||||
<velocity.version>2.3</velocity.version>
|
<velocity.version>2.3</velocity.version>
|
||||||
<beetl.version>3.15.8.RELEASE</beetl.version>
|
<beetl.version>3.15.12.RELEASE</beetl.version>
|
||||||
<rythm.version>1.4.2</rythm.version>
|
<rythm.version>1.4.2</rythm.version>
|
||||||
<freemarker.version>2.3.32</freemarker.version>
|
<freemarker.version>2.3.32</freemarker.version>
|
||||||
<enjoy.version>5.1.3</enjoy.version>
|
<enjoy.version>5.1.3</enjoy.version>
|
||||||
<thymeleaf.version>3.1.2.RELEASE</thymeleaf.version>
|
<thymeleaf.version>3.1.2.RELEASE</thymeleaf.version>
|
||||||
<mail.version>1.6.2</mail.version>
|
<mail.version>1.6.2</mail.version>
|
||||||
<jsch.version>0.1.55</jsch.version>
|
<jsch.version>0.1.55</jsch.version>
|
||||||
<sshj.version>0.37.0</sshj.version>
|
<sshj.version>0.38.0</sshj.version>
|
||||||
<zxing.version>3.5.2</zxing.version>
|
<zxing.version>3.5.2</zxing.version>
|
||||||
<net.version>3.9.0</net.version>
|
<net.version>3.9.0</net.version>
|
||||||
<emoji-java.version>5.1.1</emoji-java.version>
|
<emoji-java.version>5.1.1</emoji-java.version>
|
||||||
@ -204,6 +204,14 @@
|
|||||||
<artifactId>slf4j-api</artifactId>
|
<artifactId>slf4j-api</artifactId>
|
||||||
<groupId>org.slf4j</groupId>
|
<groupId>org.slf4j</groupId>
|
||||||
</exclusion>
|
</exclusion>
|
||||||
|
<exclusion>
|
||||||
|
<artifactId>bcpkix-jdk18on</artifactId>
|
||||||
|
<groupId>org.bouncycastle</groupId>
|
||||||
|
</exclusion>
|
||||||
|
<exclusion>
|
||||||
|
<artifactId>bcprov-jdk18on</artifactId>
|
||||||
|
<groupId>org.bouncycastle</groupId>
|
||||||
|
</exclusion>
|
||||||
</exclusions>
|
</exclusions>
|
||||||
<optional>true</optional>
|
<optional>true</optional>
|
||||||
</dependency>
|
</dependency>
|
||||||
@ -407,7 +415,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.rnkrsoft.bopomofo4j</groupId>
|
<groupId>com.rnkrsoft.bopomofo4j</groupId>
|
||||||
<artifactId>bopomofo4j</artifactId>
|
<artifactId>bopomofo4j</artifactId>
|
||||||
<version>1.0.0</version>
|
<version>1.0.1</version>
|
||||||
<optional>true</optional>
|
<optional>true</optional>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
@ -488,7 +496,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.apache.commons</groupId>
|
<groupId>org.apache.commons</groupId>
|
||||||
<artifactId>commons-compress</artifactId>
|
<artifactId>commons-compress</artifactId>
|
||||||
<version>1.24.0</version>
|
<version>1.25.0</version>
|
||||||
<scope>compile</scope>
|
<scope>compile</scope>
|
||||||
<optional>true</optional>
|
<optional>true</optional>
|
||||||
</dependency>
|
</dependency>
|
||||||
@ -496,7 +504,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.github.oshi</groupId>
|
<groupId>com.github.oshi</groupId>
|
||||||
<artifactId>oshi-core</artifactId>
|
<artifactId>oshi-core</artifactId>
|
||||||
<version>6.4.5</version>
|
<version>6.4.10</version>
|
||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
<exclusions>
|
<exclusions>
|
||||||
<exclusion>
|
<exclusion>
|
||||||
|
@ -31,8 +31,11 @@ public class UserInfo implements Serializable{
|
|||||||
private final String USER_LANGUAGE;
|
private final String USER_LANGUAGE;
|
||||||
private final String USER_COUNTRY;
|
private final String USER_COUNTRY;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构造
|
||||||
|
*/
|
||||||
public UserInfo(){
|
public UserInfo(){
|
||||||
USER_NAME = fixPath(SystemUtil.get("user.name", false));
|
USER_NAME = SystemUtil.get("user.name", false);
|
||||||
USER_HOME = fixPath(SystemUtil.get("user.home", false));
|
USER_HOME = fixPath(SystemUtil.get("user.home", false));
|
||||||
USER_DIR = fixPath(SystemUtil.get("user.dir", false));
|
USER_DIR = fixPath(SystemUtil.get("user.dir", false));
|
||||||
JAVA_IO_TMPDIR = fixPath(SystemUtil.get("java.io.tmpdir", false));
|
JAVA_IO_TMPDIR = fixPath(SystemUtil.get("java.io.tmpdir", false));
|
||||||
|
@ -21,7 +21,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>org.dromara.hutool</groupId>
|
<groupId>org.dromara.hutool</groupId>
|
||||||
<artifactId>hutool-parent</artifactId>
|
<artifactId>hutool-parent</artifactId>
|
||||||
<version>6.0.0-M10</version>
|
<version>6.0.0-M11</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<artifactId>hutool-http</artifactId>
|
<artifactId>hutool-http</artifactId>
|
||||||
|
@ -21,7 +21,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>org.dromara.hutool</groupId>
|
<groupId>org.dromara.hutool</groupId>
|
||||||
<artifactId>hutool-parent</artifactId>
|
<artifactId>hutool-parent</artifactId>
|
||||||
<version>6.0.0-M10</version>
|
<version>6.0.0-M11</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<artifactId>hutool-json</artifactId>
|
<artifactId>hutool-json</artifactId>
|
||||||
@ -31,7 +31,7 @@
|
|||||||
<properties>
|
<properties>
|
||||||
<Automatic-Module-Name>org.dromara.hutool.json</Automatic-Module-Name>
|
<Automatic-Module-Name>org.dromara.hutool.json</Automatic-Module-Name>
|
||||||
<!-- versions -->
|
<!-- versions -->
|
||||||
<bouncycastle.version>1.76</bouncycastle.version>
|
<bouncycastle.version>1.77</bouncycastle.version>
|
||||||
<jjwt.version>0.12.3</jjwt.version>
|
<jjwt.version>0.12.3</jjwt.version>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
|
@ -29,6 +29,7 @@ import org.dromara.hutool.json.JSONTokener;
|
|||||||
import org.dromara.hutool.json.xml.JSONXMLUtil;
|
import org.dromara.hutool.json.xml.JSONXMLUtil;
|
||||||
import org.dromara.hutool.json.serialize.GlobalSerializeMapping;
|
import org.dromara.hutool.json.serialize.GlobalSerializeMapping;
|
||||||
import org.dromara.hutool.json.serialize.JSONSerializer;
|
import org.dromara.hutool.json.serialize.JSONSerializer;
|
||||||
|
import org.dromara.hutool.json.xml.ParseConfig;
|
||||||
|
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.Reader;
|
import java.io.Reader;
|
||||||
@ -170,7 +171,7 @@ public class JSONObjectMapper {
|
|||||||
final String jsonStr = StrUtil.trim(source);
|
final String jsonStr = StrUtil.trim(source);
|
||||||
if (StrUtil.startWith(jsonStr, '<')) {
|
if (StrUtil.startWith(jsonStr, '<')) {
|
||||||
// 可能为XML
|
// 可能为XML
|
||||||
JSONXMLUtil.toJSONObject(jsonObject, jsonStr, false);
|
JSONXMLUtil.toJSONObject(jsonStr, jsonObject, ParseConfig.of());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
mapFromTokener(new JSONTokener(StrUtil.trim(source), jsonObject.config()), jsonObject);
|
mapFromTokener(new JSONTokener(StrUtil.trim(source), jsonObject.config()), jsonObject);
|
||||||
|
@ -12,7 +12,9 @@
|
|||||||
|
|
||||||
package org.dromara.hutool.json.xml;
|
package org.dromara.hutool.json.xml;
|
||||||
|
|
||||||
|
import org.dromara.hutool.core.text.CharUtil;
|
||||||
import org.dromara.hutool.core.text.StrUtil;
|
import org.dromara.hutool.core.text.StrUtil;
|
||||||
|
import org.dromara.hutool.core.xml.XmlConstants;
|
||||||
import org.dromara.hutool.json.JSONException;
|
import org.dromara.hutool.json.JSONException;
|
||||||
import org.dromara.hutool.json.JSONObject;
|
import org.dromara.hutool.json.JSONObject;
|
||||||
import org.dromara.hutool.json.mapper.JSONValueMapper;
|
import org.dromara.hutool.json.mapper.JSONValueMapper;
|
||||||
@ -29,28 +31,31 @@ public class JSONXMLParser {
|
|||||||
* 转换XML为JSONObject
|
* 转换XML为JSONObject
|
||||||
* 转换过程中一些信息可能会丢失,JSON中无法区分节点和属性,相同的节点将被处理为JSONArray。
|
* 转换过程中一些信息可能会丢失,JSON中无法区分节点和属性,相同的节点将被处理为JSONArray。
|
||||||
*
|
*
|
||||||
* @param jo JSONObject
|
|
||||||
* @param xmlStr XML字符串
|
* @param xmlStr XML字符串
|
||||||
* @param keepStrings 如果为{@code true},则值保持String类型,不转换为数字或boolean
|
* @param jo JSONObject
|
||||||
|
* @param parseConfig 解析选项
|
||||||
* @throws JSONException 解析异常
|
* @throws JSONException 解析异常
|
||||||
*/
|
*/
|
||||||
public static void parseJSONObject(final JSONObject jo, final String xmlStr, final boolean keepStrings) throws JSONException {
|
public static void parseJSONObject(final String xmlStr, final JSONObject jo, final ParseConfig parseConfig) throws JSONException {
|
||||||
final XMLTokener x = new XMLTokener(xmlStr, jo.config());
|
final XMLTokener x = new XMLTokener(xmlStr, jo.config());
|
||||||
while (x.more() && x.skipPast("<")) {
|
while (x.more() && x.skipPast("<")) {
|
||||||
parse(x, jo, null, keepStrings);
|
parse(x, jo, null, parseConfig, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Scan the content following the named tag, attaching it to the context.
|
* 扫描XML内容,并解析到JSONObject中。
|
||||||
*
|
*
|
||||||
* @param x The XMLTokener containing the source string.
|
* @param x {@link XMLTokener}
|
||||||
* @param context The JSONObject that will include the new material.
|
* @param context {@link JSONObject}
|
||||||
* @param name The tag name.
|
* @param name 标签名,null表示从根标签开始解析
|
||||||
* @return true if the close tag is processed.
|
* @param parseConfig 解析选项
|
||||||
|
* @param currentNestingDepth 当前层级
|
||||||
|
* @return {@code true}表示解析完成
|
||||||
* @throws JSONException JSON异常
|
* @throws JSONException JSON异常
|
||||||
*/
|
*/
|
||||||
private static boolean parse(final XMLTokener x, final JSONObject context, final String name, final boolean keepStrings) throws JSONException {
|
private static boolean parse(final XMLTokener x, final JSONObject context, final String name,
|
||||||
|
final ParseConfig parseConfig, final int currentNestingDepth) throws JSONException {
|
||||||
final char c;
|
final char c;
|
||||||
int i;
|
int i;
|
||||||
final JSONObject jsonobject;
|
final JSONObject jsonobject;
|
||||||
@ -60,7 +65,7 @@ public class JSONXMLParser {
|
|||||||
|
|
||||||
token = x.nextToken();
|
token = x.nextToken();
|
||||||
|
|
||||||
if (token == JSONXMLUtil.BANG) {
|
if (token == XmlConstants.C_BANG) {
|
||||||
c = x.next();
|
c = x.next();
|
||||||
if (c == '-') {
|
if (c == '-') {
|
||||||
if (x.next() == '-') {
|
if (x.next() == '-') {
|
||||||
@ -86,19 +91,19 @@ public class JSONXMLParser {
|
|||||||
token = x.nextMeta();
|
token = x.nextMeta();
|
||||||
if (token == null) {
|
if (token == null) {
|
||||||
throw x.syntaxError("Missing '>' after '<!'.");
|
throw x.syntaxError("Missing '>' after '<!'.");
|
||||||
} else if (token == JSONXMLUtil.LT) {
|
} else if (token == XmlConstants.C_LT) {
|
||||||
i += 1;
|
i += 1;
|
||||||
} else if (token == JSONXMLUtil.GT) {
|
} else if (token == XmlConstants.C_GT) {
|
||||||
i -= 1;
|
i -= 1;
|
||||||
}
|
}
|
||||||
} while (i > 0);
|
} while (i > 0);
|
||||||
return false;
|
return false;
|
||||||
} else if (token == JSONXMLUtil.QUEST) {
|
} else if (token == XmlConstants.C_QUEST) {
|
||||||
|
|
||||||
// <?
|
// <?
|
||||||
x.skipPast("?>");
|
x.skipPast("?>");
|
||||||
return false;
|
return false;
|
||||||
} else if (token == JSONXMLUtil.SLASH) {
|
} else if (token == Character.valueOf(CharUtil.SLASH)) {
|
||||||
|
|
||||||
// Close tag </
|
// Close tag </
|
||||||
|
|
||||||
@ -109,7 +114,7 @@ public class JSONXMLParser {
|
|||||||
if (!token.equals(name)) {
|
if (!token.equals(name)) {
|
||||||
throw x.syntaxError("Mismatched " + name + " and " + token);
|
throw x.syntaxError("Mismatched " + name + " and " + token);
|
||||||
}
|
}
|
||||||
if (x.nextToken() != JSONXMLUtil.GT) {
|
if (x.nextToken() != XmlConstants.C_GT) {
|
||||||
throw x.syntaxError("Misshaped close tag");
|
throw x.syntaxError("Misshaped close tag");
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@ -123,6 +128,7 @@ public class JSONXMLParser {
|
|||||||
tagName = (String) token;
|
tagName = (String) token;
|
||||||
token = null;
|
token = null;
|
||||||
jsonobject = new JSONObject();
|
jsonobject = new JSONObject();
|
||||||
|
final boolean keepStrings = parseConfig.isKeepStrings();
|
||||||
for (; ; ) {
|
for (; ; ) {
|
||||||
if (token == null) {
|
if (token == null) {
|
||||||
token = x.nextToken();
|
token = x.nextToken();
|
||||||
@ -132,7 +138,7 @@ public class JSONXMLParser {
|
|||||||
if (token instanceof String) {
|
if (token instanceof String) {
|
||||||
string = (String) token;
|
string = (String) token;
|
||||||
token = x.nextToken();
|
token = x.nextToken();
|
||||||
if (token == JSONXMLUtil.EQ) {
|
if (token == Character.valueOf(CharUtil.EQUAL)) {
|
||||||
token = x.nextToken();
|
token = x.nextToken();
|
||||||
if (!(token instanceof String)) {
|
if (!(token instanceof String)) {
|
||||||
throw x.syntaxError("Missing value");
|
throw x.syntaxError("Missing value");
|
||||||
@ -143,9 +149,9 @@ public class JSONXMLParser {
|
|||||||
jsonobject.append(string, "");
|
jsonobject.append(string, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
} else if (token == JSONXMLUtil.SLASH) {
|
} else if (token == Character.valueOf(CharUtil.SLASH)) {
|
||||||
// Empty tag <.../>
|
// Empty tag <.../>
|
||||||
if (x.nextToken() != JSONXMLUtil.GT) {
|
if (x.nextToken() != XmlConstants.C_GT) {
|
||||||
throw x.syntaxError("Misshaped tag");
|
throw x.syntaxError("Misshaped tag");
|
||||||
}
|
}
|
||||||
if (!jsonobject.isEmpty()) {
|
if (!jsonobject.isEmpty()) {
|
||||||
@ -155,7 +161,7 @@ public class JSONXMLParser {
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
} else if (token == JSONXMLUtil.GT) {
|
} else if (token == XmlConstants.C_GT) {
|
||||||
// Content, between <...> and </...>
|
// Content, between <...> and </...>
|
||||||
for (; ; ) {
|
for (; ; ) {
|
||||||
token = x.nextContent();
|
token = x.nextContent();
|
||||||
@ -170,9 +176,16 @@ public class JSONXMLParser {
|
|||||||
jsonobject.append("content", keepStrings ? token : JSONValueMapper.toJsonValue(string));
|
jsonobject.append("content", keepStrings ? token : JSONValueMapper.toJsonValue(string));
|
||||||
}
|
}
|
||||||
|
|
||||||
} else if (token == JSONXMLUtil.LT) {
|
} else if (token == XmlConstants.C_LT) {
|
||||||
// Nested element
|
// Nested element
|
||||||
if (parse(x, jsonobject, tagName, keepStrings)) {
|
// issue#2748 of CVE-2022-45688
|
||||||
|
final int maxNestingDepth = parseConfig.getMaxNestingDepth();
|
||||||
|
if (maxNestingDepth > -1 && currentNestingDepth >= maxNestingDepth) {
|
||||||
|
throw x.syntaxError("Maximum nesting depth of " + maxNestingDepth + " reached");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Nested element
|
||||||
|
if (parse(x, jsonobject, tagName, parseConfig, currentNestingDepth + 1)) {
|
||||||
if (jsonobject.isEmpty()) {
|
if (jsonobject.isEmpty()) {
|
||||||
context.append(tagName, StrUtil.EMPTY);
|
context.append(tagName, StrUtil.EMPTY);
|
||||||
} else if (jsonobject.size() == 1 && jsonobject.get("content") != null) {
|
} else if (jsonobject.size() == 1 && jsonobject.get("content") != null) {
|
||||||
|
@ -12,7 +12,6 @@
|
|||||||
|
|
||||||
package org.dromara.hutool.json.xml;
|
package org.dromara.hutool.json.xml;
|
||||||
|
|
||||||
import org.dromara.hutool.core.text.CharUtil;
|
|
||||||
import org.dromara.hutool.json.JSONException;
|
import org.dromara.hutool.json.JSONException;
|
||||||
import org.dromara.hutool.json.JSONObject;
|
import org.dromara.hutool.json.JSONObject;
|
||||||
|
|
||||||
@ -25,51 +24,6 @@ import org.dromara.hutool.json.JSONObject;
|
|||||||
*/
|
*/
|
||||||
public class JSONXMLUtil {
|
public class JSONXMLUtil {
|
||||||
|
|
||||||
/**
|
|
||||||
* The Character '&'.
|
|
||||||
*/
|
|
||||||
public static final Character AMP = CharUtil.AMP;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The Character '''.
|
|
||||||
*/
|
|
||||||
public static final Character APOS = CharUtil.SINGLE_QUOTE;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The Character '!'.
|
|
||||||
*/
|
|
||||||
public static final Character BANG = '!';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The Character '='.
|
|
||||||
*/
|
|
||||||
public static final Character EQ = '=';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The Character '>'.
|
|
||||||
*/
|
|
||||||
public static final Character GT = '>';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The Character '<'.
|
|
||||||
*/
|
|
||||||
public static final Character LT = '<';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The Character '?'.
|
|
||||||
*/
|
|
||||||
public static final Character QUEST = '?';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The Character '"'.
|
|
||||||
*/
|
|
||||||
public static final Character QUOT = CharUtil.DOUBLE_QUOTES;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The Character '/'.
|
|
||||||
*/
|
|
||||||
public static final Character SLASH = CharUtil.SLASH;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 转换XML为JSONObject
|
* 转换XML为JSONObject
|
||||||
* 转换过程中一些信息可能会丢失,JSON中无法区分节点和属性,相同的节点将被处理为JSONArray。
|
* 转换过程中一些信息可能会丢失,JSON中无法区分节点和属性,相同的节点将被处理为JSONArray。
|
||||||
@ -80,7 +34,7 @@ public class JSONXMLUtil {
|
|||||||
* @throws JSONException Thrown if there is an errors while parsing the string
|
* @throws JSONException Thrown if there is an errors while parsing the string
|
||||||
*/
|
*/
|
||||||
public static JSONObject toJSONObject(final String string) throws JSONException {
|
public static JSONObject toJSONObject(final String string) throws JSONException {
|
||||||
return toJSONObject(string, false);
|
return toJSONObject(string, ParseConfig.of());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -89,13 +43,13 @@ public class JSONXMLUtil {
|
|||||||
* Content text may be placed in a "content" member. Comments, prologs, DTDs, and {@code <[ [ ]]>} are ignored.
|
* Content text may be placed in a "content" member. Comments, prologs, DTDs, and {@code <[ [ ]]>} are ignored.
|
||||||
* All values are converted as strings, for 1, 01, 29.0 will not be coerced to numbers but will instead be the exact value as seen in the XML document.
|
* All values are converted as strings, for 1, 01, 29.0 will not be coerced to numbers but will instead be the exact value as seen in the XML document.
|
||||||
*
|
*
|
||||||
* @param string The source string.
|
* @param string XML字符串
|
||||||
* @param keepStrings If true, then values will not be coerced into boolean or numeric values and will instead be left as strings
|
* @param parseConfig XML解析选项
|
||||||
* @return A JSONObject containing the structured data from the XML string.
|
* @return A JSONObject containing the structured data from the XML string.
|
||||||
* @throws JSONException Thrown if there is an errors while parsing the string
|
* @throws JSONException Thrown if there is an errors while parsing the string
|
||||||
*/
|
*/
|
||||||
public static JSONObject toJSONObject(final String string, final boolean keepStrings) throws JSONException {
|
public static JSONObject toJSONObject(final String string, final ParseConfig parseConfig) throws JSONException {
|
||||||
return toJSONObject(new JSONObject(), string, keepStrings);
|
return toJSONObject(string, new JSONObject(), parseConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -104,13 +58,13 @@ public class JSONXMLUtil {
|
|||||||
*
|
*
|
||||||
* @param jo JSONObject
|
* @param jo JSONObject
|
||||||
* @param xmlStr XML字符串
|
* @param xmlStr XML字符串
|
||||||
* @param keepStrings 如果为{@code true},则值保持String类型,不转换为数字或boolean
|
* @param parseConfig XML解析选项
|
||||||
* @return A JSONObject 解析后的JSON对象,与传入的jo为同一对象
|
* @return A JSONObject 解析后的JSON对象,与传入的jo为同一对象
|
||||||
* @throws JSONException 解析异常
|
* @throws JSONException 解析异常
|
||||||
* @since 5.3.1
|
* @since 5.3.1
|
||||||
*/
|
*/
|
||||||
public static JSONObject toJSONObject(final JSONObject jo, final String xmlStr, final boolean keepStrings) throws JSONException {
|
public static JSONObject toJSONObject(final String xmlStr, final JSONObject jo, final ParseConfig parseConfig) throws JSONException {
|
||||||
JSONXMLParser.parseJSONObject(jo, xmlStr, keepStrings);
|
JSONXMLParser.parseJSONObject(xmlStr, jo, parseConfig);
|
||||||
return jo;
|
return jo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,88 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2024. looly(loolly@aliyun.com)
|
||||||
|
* Hutool is licensed under Mulan PSL v2.
|
||||||
|
* You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||||||
|
* You may obtain a copy of Mulan PSL v2 at:
|
||||||
|
* https://license.coscl.org.cn/MulanPSL2
|
||||||
|
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
||||||
|
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
||||||
|
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
||||||
|
* See the Mulan PSL v2 for more details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.dromara.hutool.json.xml;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* XML解析为JSON的可选选项<br>
|
||||||
|
* 参考:https://github.com/stleary/JSON-java/blob/master/src/main/java/org/json/ParserConfiguration.java
|
||||||
|
*
|
||||||
|
* @author AylwardJ, Looly
|
||||||
|
*/
|
||||||
|
public class ParseConfig implements Serializable {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 默认最大嵌套深度
|
||||||
|
*/
|
||||||
|
public static final int DEFAULT_MAXIMUM_NESTING_DEPTH = 512;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建ParseConfig
|
||||||
|
*
|
||||||
|
* @return ParseConfig
|
||||||
|
*/
|
||||||
|
public static ParseConfig of() {
|
||||||
|
return new ParseConfig();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否保持值为String类型,如果为{@code false},则尝试转换为对应类型(numeric, boolean, string)
|
||||||
|
*/
|
||||||
|
private boolean keepStrings;
|
||||||
|
/**
|
||||||
|
* 最大嵌套深度,用于解析时限制解析层级,当大于这个层级时抛出异常,-1表示无限制
|
||||||
|
*/
|
||||||
|
private int maxNestingDepth = -1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否保持值为String类型,如果为{@code false},则尝试转换为对应类型(numeric, boolean, string)
|
||||||
|
*
|
||||||
|
* @return 是否保持值为String类型
|
||||||
|
*/
|
||||||
|
public boolean isKeepStrings() {
|
||||||
|
return keepStrings;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置是否保持值为String类型,如果为{@code false},则尝试转换为对应类型(numeric, boolean, string)
|
||||||
|
*
|
||||||
|
* @param keepStrings 是否保持值为String类型
|
||||||
|
* @return this
|
||||||
|
*/
|
||||||
|
public ParseConfig setKeepStrings(final boolean keepStrings) {
|
||||||
|
this.keepStrings = keepStrings;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取最大嵌套深度,用于解析时限制解析层级,当大于这个层级时抛出异常,-1表示无限制
|
||||||
|
*
|
||||||
|
* @return 最大嵌套深度
|
||||||
|
*/
|
||||||
|
public int getMaxNestingDepth() {
|
||||||
|
return maxNestingDepth;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置最大嵌套深度,用于解析时限制解析层级,当大于这个层级时抛出异常,-1表示无限制
|
||||||
|
*
|
||||||
|
* @param maxNestingDepth 最大嵌套深度
|
||||||
|
* @return this
|
||||||
|
*/
|
||||||
|
public ParseConfig setMaxNestingDepth(final int maxNestingDepth) {
|
||||||
|
this.maxNestingDepth = maxNestingDepth;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
@ -12,6 +12,8 @@
|
|||||||
|
|
||||||
package org.dromara.hutool.json.xml;
|
package org.dromara.hutool.json.xml;
|
||||||
|
|
||||||
|
import org.dromara.hutool.core.text.CharUtil;
|
||||||
|
import org.dromara.hutool.core.xml.XmlConstants;
|
||||||
import org.dromara.hutool.json.JSONConfig;
|
import org.dromara.hutool.json.JSONConfig;
|
||||||
import org.dromara.hutool.json.JSONException;
|
import org.dromara.hutool.json.JSONException;
|
||||||
import org.dromara.hutool.json.JSONTokener;
|
import org.dromara.hutool.json.JSONTokener;
|
||||||
@ -31,11 +33,11 @@ public class XMLTokener extends JSONTokener {
|
|||||||
|
|
||||||
static {
|
static {
|
||||||
entity = new java.util.HashMap<>(8);
|
entity = new java.util.HashMap<>(8);
|
||||||
entity.put("amp", JSONXMLUtil.AMP);
|
entity.put("amp", XmlConstants.C_AMP);
|
||||||
entity.put("apos", JSONXMLUtil.APOS);
|
entity.put("apos", XmlConstants.C_APOS);
|
||||||
entity.put("gt", JSONXMLUtil.GT);
|
entity.put("gt", XmlConstants.C_GT);
|
||||||
entity.put("lt", JSONXMLUtil.LT);
|
entity.put("lt", XmlConstants.C_LT);
|
||||||
entity.put("quot", JSONXMLUtil.QUOT);
|
entity.put("quot", CharUtil.DOUBLE_QUOTES);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -89,7 +91,7 @@ public class XMLTokener extends JSONTokener {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
if (c == '<') {
|
if (c == '<') {
|
||||||
return JSONXMLUtil.LT;
|
return XmlConstants.C_LT;
|
||||||
}
|
}
|
||||||
sb = new StringBuilder();
|
sb = new StringBuilder();
|
||||||
for (; ; ) {
|
for (; ; ) {
|
||||||
@ -175,17 +177,17 @@ public class XMLTokener extends JSONTokener {
|
|||||||
case 0:
|
case 0:
|
||||||
throw syntaxError("Misshaped meta tag");
|
throw syntaxError("Misshaped meta tag");
|
||||||
case '<':
|
case '<':
|
||||||
return JSONXMLUtil.LT;
|
return XmlConstants.C_LT;
|
||||||
case '>':
|
case '>':
|
||||||
return JSONXMLUtil.GT;
|
return XmlConstants.C_GT;
|
||||||
case '/':
|
case '/':
|
||||||
return JSONXMLUtil.SLASH;
|
return CharUtil.SLASH;
|
||||||
case '=':
|
case '=':
|
||||||
return JSONXMLUtil.EQ;
|
return CharUtil.EQUAL;
|
||||||
case '!':
|
case '!':
|
||||||
return JSONXMLUtil.BANG;
|
return XmlConstants.C_BANG;
|
||||||
case '?':
|
case '?':
|
||||||
return JSONXMLUtil.QUEST;
|
return XmlConstants.C_QUEST;
|
||||||
case '"':
|
case '"':
|
||||||
case '\'':
|
case '\'':
|
||||||
q = c;
|
q = c;
|
||||||
@ -242,15 +244,15 @@ public class XMLTokener extends JSONTokener {
|
|||||||
case '<':
|
case '<':
|
||||||
throw syntaxError("Misplaced '<'");
|
throw syntaxError("Misplaced '<'");
|
||||||
case '>':
|
case '>':
|
||||||
return JSONXMLUtil.GT;
|
return XmlConstants.C_GT;
|
||||||
case '/':
|
case '/':
|
||||||
return JSONXMLUtil.SLASH;
|
return CharUtil.SLASH;
|
||||||
case '=':
|
case '=':
|
||||||
return JSONXMLUtil.EQ;
|
return CharUtil.EQUAL;
|
||||||
case '!':
|
case '!':
|
||||||
return JSONXMLUtil.BANG;
|
return XmlConstants.C_BANG;
|
||||||
case '?':
|
case '?':
|
||||||
return JSONXMLUtil.QUEST;
|
return XmlConstants.C_QUEST;
|
||||||
|
|
||||||
// Quoted string
|
// Quoted string
|
||||||
|
|
||||||
|
@ -0,0 +1,18 @@
|
|||||||
|
package org.dromara.hutool.json.xml;
|
||||||
|
|
||||||
|
import org.dromara.hutool.core.text.StrUtil;
|
||||||
|
import org.dromara.hutool.json.JSONException;
|
||||||
|
import org.junit.jupiter.api.Assertions;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
public class Issue2748Test {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void toJSONObjectTest() {
|
||||||
|
final String s = StrUtil.repeat("<a>", 600);
|
||||||
|
|
||||||
|
Assertions.assertThrows(JSONException.class, () -> {
|
||||||
|
JSONXMLUtil.toJSONObject(s, ParseConfig.of().setMaxNestingDepth(512));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -21,7 +21,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>org.dromara.hutool</groupId>
|
<groupId>org.dromara.hutool</groupId>
|
||||||
<artifactId>hutool-parent</artifactId>
|
<artifactId>hutool-parent</artifactId>
|
||||||
<version>6.0.0-M10</version>
|
<version>6.0.0-M11</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<artifactId>hutool-log</artifactId>
|
<artifactId>hutool-log</artifactId>
|
||||||
@ -32,10 +32,10 @@
|
|||||||
<Automatic-Module-Name>org.dromara.hutool.log</Automatic-Module-Name>
|
<Automatic-Module-Name>org.dromara.hutool.log</Automatic-Module-Name>
|
||||||
<!-- versions -->
|
<!-- versions -->
|
||||||
<slf4j.version>2.0.9</slf4j.version>
|
<slf4j.version>2.0.9</slf4j.version>
|
||||||
<logback.version>1.4.13</logback.version>
|
<logback.version>1.4.14</logback.version>
|
||||||
<log4j.version>1.2.17</log4j.version>
|
<log4j.version>1.2.17</log4j.version>
|
||||||
<log4j2.version>2.20.0</log4j2.version>
|
<log4j2.version>2.20.0</log4j2.version>
|
||||||
<commons-logging.version>1.2</commons-logging.version>
|
<commons-logging.version>1.3.0</commons-logging.version>
|
||||||
<tinylog.version>1.3.6</tinylog.version>
|
<tinylog.version>1.3.6</tinylog.version>
|
||||||
<tinylog2.version>2.6.2</tinylog2.version>
|
<tinylog2.version>2.6.2</tinylog2.version>
|
||||||
<!-- 固定3.4.x,支持到jdk8 -->
|
<!-- 固定3.4.x,支持到jdk8 -->
|
||||||
|
@ -21,7 +21,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>org.dromara.hutool</groupId>
|
<groupId>org.dromara.hutool</groupId>
|
||||||
<artifactId>hutool-parent</artifactId>
|
<artifactId>hutool-parent</artifactId>
|
||||||
<version>6.0.0-M10</version>
|
<version>6.0.0-M11</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<artifactId>hutool-poi</artifactId>
|
<artifactId>hutool-poi</artifactId>
|
||||||
@ -55,6 +55,16 @@
|
|||||||
<artifactId>ofdrw-full</artifactId>
|
<artifactId>ofdrw-full</artifactId>
|
||||||
<version>2.2.4</version>
|
<version>2.2.4</version>
|
||||||
<scope>compile</scope>
|
<scope>compile</scope>
|
||||||
|
<exclusions>
|
||||||
|
<exclusion>
|
||||||
|
<artifactId>bcpkix-jdk15on</artifactId>
|
||||||
|
<groupId>org.bouncycastle</groupId>
|
||||||
|
</exclusion>
|
||||||
|
<exclusion>
|
||||||
|
<artifactId>bcprov-jdk15on</artifactId>
|
||||||
|
<groupId>org.bouncycastle</groupId>
|
||||||
|
</exclusion>
|
||||||
|
</exclusions>
|
||||||
<optional>true</optional>
|
<optional>true</optional>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
@ -21,7 +21,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>org.dromara.hutool</groupId>
|
<groupId>org.dromara.hutool</groupId>
|
||||||
<artifactId>hutool-parent</artifactId>
|
<artifactId>hutool-parent</artifactId>
|
||||||
<version>6.0.0-M10</version>
|
<version>6.0.0-M11</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<artifactId>hutool-setting</artifactId>
|
<artifactId>hutool-setting</artifactId>
|
||||||
|
@ -21,7 +21,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>org.dromara.hutool</groupId>
|
<groupId>org.dromara.hutool</groupId>
|
||||||
<artifactId>hutool-parent</artifactId>
|
<artifactId>hutool-parent</artifactId>
|
||||||
<version>6.0.0-M10</version>
|
<version>6.0.0-M11</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<artifactId>hutool-socket</artifactId>
|
<artifactId>hutool-socket</artifactId>
|
||||||
|
@ -21,7 +21,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>org.dromara.hutool</groupId>
|
<groupId>org.dromara.hutool</groupId>
|
||||||
<artifactId>hutool-parent</artifactId>
|
<artifactId>hutool-parent</artifactId>
|
||||||
<version>6.0.0-M10</version>
|
<version>6.0.0-M11</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<artifactId>hutool-swing</artifactId>
|
<artifactId>hutool-swing</artifactId>
|
||||||
|
@ -27,7 +27,7 @@ public class CaptchaUtil {
|
|||||||
* @param height 图片高
|
* @param height 图片高
|
||||||
* @return {@link LineCaptcha}
|
* @return {@link LineCaptcha}
|
||||||
*/
|
*/
|
||||||
public static LineCaptcha createLineCaptcha(final int width, final int height) {
|
public static LineCaptcha ofLineCaptcha(final int width, final int height) {
|
||||||
return new LineCaptcha(width, height);
|
return new LineCaptcha(width, height);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -40,7 +40,7 @@ public class CaptchaUtil {
|
|||||||
* @param lineCount 干扰线条数
|
* @param lineCount 干扰线条数
|
||||||
* @return {@link LineCaptcha}
|
* @return {@link LineCaptcha}
|
||||||
*/
|
*/
|
||||||
public static LineCaptcha createLineCaptcha(final int width, final int height, final int codeCount, final int lineCount) {
|
public static LineCaptcha ofLineCaptcha(final int width, final int height, final int codeCount, final int lineCount) {
|
||||||
return new LineCaptcha(width, height, codeCount, lineCount);
|
return new LineCaptcha(width, height, codeCount, lineCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -52,7 +52,7 @@ public class CaptchaUtil {
|
|||||||
* @return {@link CircleCaptcha}
|
* @return {@link CircleCaptcha}
|
||||||
* @since 3.2.3
|
* @since 3.2.3
|
||||||
*/
|
*/
|
||||||
public static CircleCaptcha createCircleCaptcha(final int width, final int height) {
|
public static CircleCaptcha ofCircleCaptcha(final int width, final int height) {
|
||||||
return new CircleCaptcha(width, height);
|
return new CircleCaptcha(width, height);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -66,7 +66,7 @@ public class CaptchaUtil {
|
|||||||
* @return {@link CircleCaptcha}
|
* @return {@link CircleCaptcha}
|
||||||
* @since 3.2.3
|
* @since 3.2.3
|
||||||
*/
|
*/
|
||||||
public static CircleCaptcha createCircleCaptcha(final int width, final int height, final int codeCount, final int circleCount) {
|
public static CircleCaptcha ofCircleCaptcha(final int width, final int height, final int codeCount, final int circleCount) {
|
||||||
return new CircleCaptcha(width, height, codeCount, circleCount);
|
return new CircleCaptcha(width, height, codeCount, circleCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -78,7 +78,7 @@ public class CaptchaUtil {
|
|||||||
* @return {@link ShearCaptcha}
|
* @return {@link ShearCaptcha}
|
||||||
* @since 3.2.3
|
* @since 3.2.3
|
||||||
*/
|
*/
|
||||||
public static ShearCaptcha createShearCaptcha(final int width, final int height) {
|
public static ShearCaptcha ofShearCaptcha(final int width, final int height) {
|
||||||
return new ShearCaptcha(width, height);
|
return new ShearCaptcha(width, height);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -92,7 +92,7 @@ public class CaptchaUtil {
|
|||||||
* @return {@link ShearCaptcha}
|
* @return {@link ShearCaptcha}
|
||||||
* @since 3.3.0
|
* @since 3.3.0
|
||||||
*/
|
*/
|
||||||
public static ShearCaptcha createShearCaptcha(final int width, final int height, final int codeCount, final int thickness) {
|
public static ShearCaptcha ofShearCaptcha(final int width, final int height, final int codeCount, final int thickness) {
|
||||||
return new ShearCaptcha(width, height, codeCount, thickness);
|
return new ShearCaptcha(width, height, codeCount, thickness);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -103,7 +103,7 @@ public class CaptchaUtil {
|
|||||||
* @param height 高
|
* @param height 高
|
||||||
* @return {@link GifCaptcha}
|
* @return {@link GifCaptcha}
|
||||||
*/
|
*/
|
||||||
public static GifCaptcha createGifCaptcha(final int width, final int height) {
|
public static GifCaptcha ofGifCaptcha(final int width, final int height) {
|
||||||
return new GifCaptcha(width, height);
|
return new GifCaptcha(width, height);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -115,7 +115,7 @@ public class CaptchaUtil {
|
|||||||
* @param codeCount 字符个数
|
* @param codeCount 字符个数
|
||||||
* @return {@link GifCaptcha}
|
* @return {@link GifCaptcha}
|
||||||
*/
|
*/
|
||||||
public static GifCaptcha createGifCaptcha(final int width, final int height, final int codeCount) {
|
public static GifCaptcha ofGifCaptcha(final int width, final int height, final int codeCount) {
|
||||||
return new GifCaptcha(width, height, codeCount);
|
return new GifCaptcha(width, height, codeCount);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,8 @@ package org.dromara.hutool.swing.captcha;
|
|||||||
|
|
||||||
import org.dromara.hutool.core.util.ObjUtil;
|
import org.dromara.hutool.core.util.ObjUtil;
|
||||||
import org.dromara.hutool.core.util.RandomUtil;
|
import org.dromara.hutool.core.util.RandomUtil;
|
||||||
|
import org.dromara.hutool.swing.captcha.generator.CodeGenerator;
|
||||||
|
import org.dromara.hutool.swing.captcha.generator.RandomGenerator;
|
||||||
import org.dromara.hutool.swing.img.color.ColorUtil;
|
import org.dromara.hutool.swing.img.color.ColorUtil;
|
||||||
import org.dromara.hutool.swing.img.GraphicsUtil;
|
import org.dromara.hutool.swing.img.GraphicsUtil;
|
||||||
|
|
||||||
@ -63,7 +65,19 @@ public class CircleCaptcha extends AbstractCaptcha {
|
|||||||
* @param interfereCount 验证码干扰元素个数
|
* @param interfereCount 验证码干扰元素个数
|
||||||
*/
|
*/
|
||||||
public CircleCaptcha(final int width, final int height, final int codeCount, final int interfereCount) {
|
public CircleCaptcha(final int width, final int height, final int codeCount, final int interfereCount) {
|
||||||
super(width, height, codeCount, interfereCount);
|
this(width, height, new RandomGenerator(codeCount), interfereCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构造
|
||||||
|
*
|
||||||
|
* @param width 图片宽
|
||||||
|
* @param height 图片高
|
||||||
|
* @param generator 验证码生成器
|
||||||
|
* @param interfereCount 验证码干扰元素个数
|
||||||
|
*/
|
||||||
|
public CircleCaptcha(final int width, final int height, final CodeGenerator generator, final int interfereCount) {
|
||||||
|
super(width, height, generator, interfereCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -16,6 +16,8 @@ package org.dromara.hutool.swing.captcha;
|
|||||||
import org.dromara.hutool.core.util.ObjUtil;
|
import org.dromara.hutool.core.util.ObjUtil;
|
||||||
import org.dromara.hutool.core.util.RandomUtil;
|
import org.dromara.hutool.core.util.RandomUtil;
|
||||||
import com.madgag.gif.fmsware.AnimatedGifEncoder;
|
import com.madgag.gif.fmsware.AnimatedGifEncoder;
|
||||||
|
import org.dromara.hutool.swing.captcha.generator.CodeGenerator;
|
||||||
|
import org.dromara.hutool.swing.captcha.generator.RandomGenerator;
|
||||||
|
|
||||||
import java.awt.AlphaComposite;
|
import java.awt.AlphaComposite;
|
||||||
import java.awt.Color;
|
import java.awt.Color;
|
||||||
@ -59,7 +61,29 @@ public class GifCaptcha extends AbstractCaptcha {
|
|||||||
* @param codeCount 验证码个数
|
* @param codeCount 验证码个数
|
||||||
*/
|
*/
|
||||||
public GifCaptcha(final int width, final int height, final int codeCount) {
|
public GifCaptcha(final int width, final int height, final int codeCount) {
|
||||||
super(width, height, codeCount, 10);
|
this(width, height, codeCount, 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param width 验证码宽度
|
||||||
|
* @param height 验证码高度
|
||||||
|
* @param codeCount 验证码个数
|
||||||
|
* @param interfereCount 干扰个数
|
||||||
|
*/
|
||||||
|
public GifCaptcha(final int width, final int height, final int codeCount, final int interfereCount) {
|
||||||
|
this(width, height, new RandomGenerator(codeCount), interfereCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构造
|
||||||
|
*
|
||||||
|
* @param width 图片宽
|
||||||
|
* @param height 图片高
|
||||||
|
* @param generator 验证码生成器
|
||||||
|
* @param interfereCount 验证码干扰元素个数
|
||||||
|
*/
|
||||||
|
public GifCaptcha(final int width, final int height, final CodeGenerator generator, final int interfereCount) {
|
||||||
|
super(width, height, generator, interfereCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -178,9 +202,9 @@ public class GifCaptcha extends AbstractCaptcha {
|
|||||||
g2d.setComposite(ac);
|
g2d.setComposite(ac);
|
||||||
g2d.setColor(fontColor[i]);
|
g2d.setColor(fontColor[i]);
|
||||||
g2d.drawOval(
|
g2d.drawOval(
|
||||||
RandomUtil.randomInt(width),
|
RandomUtil.randomInt(width),
|
||||||
RandomUtil.randomInt(height),
|
RandomUtil.randomInt(height),
|
||||||
RandomUtil.randomInt(5, 30), 5 + RandomUtil.randomInt(5, 30)
|
RandomUtil.randomInt(5, 30), 5 + RandomUtil.randomInt(5, 30)
|
||||||
);//绘制椭圆边框
|
);//绘制椭圆边框
|
||||||
g2d.drawString(words[i] + "", x + (font.getSize() + m) * i, y);
|
g2d.drawString(words[i] + "", x + (font.getSize() + m) * i, y);
|
||||||
}
|
}
|
||||||
@ -223,8 +247,8 @@ public class GifCaptcha extends AbstractCaptcha {
|
|||||||
max = 255;
|
max = 255;
|
||||||
}
|
}
|
||||||
return new Color(
|
return new Color(
|
||||||
RandomUtil.randomInt(min, max),
|
RandomUtil.randomInt(min, max),
|
||||||
RandomUtil.randomInt(min, max),
|
RandomUtil.randomInt(min, max),
|
||||||
RandomUtil.randomInt(min, max));
|
RandomUtil.randomInt(min, max));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,8 @@ package org.dromara.hutool.swing.captcha;
|
|||||||
|
|
||||||
import org.dromara.hutool.core.util.ObjUtil;
|
import org.dromara.hutool.core.util.ObjUtil;
|
||||||
import org.dromara.hutool.core.util.RandomUtil;
|
import org.dromara.hutool.core.util.RandomUtil;
|
||||||
|
import org.dromara.hutool.swing.captcha.generator.CodeGenerator;
|
||||||
|
import org.dromara.hutool.swing.captcha.generator.RandomGenerator;
|
||||||
import org.dromara.hutool.swing.img.color.ColorUtil;
|
import org.dromara.hutool.swing.img.color.ColorUtil;
|
||||||
import org.dromara.hutool.swing.img.GraphicsUtil;
|
import org.dromara.hutool.swing.img.GraphicsUtil;
|
||||||
|
|
||||||
@ -53,7 +55,19 @@ public class LineCaptcha extends AbstractCaptcha {
|
|||||||
* @param lineCount 干扰线条数
|
* @param lineCount 干扰线条数
|
||||||
*/
|
*/
|
||||||
public LineCaptcha(final int width, final int height, final int codeCount, final int lineCount) {
|
public LineCaptcha(final int width, final int height, final int codeCount, final int lineCount) {
|
||||||
super(width, height, codeCount, lineCount);
|
this(width, height, new RandomGenerator(codeCount), lineCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构造
|
||||||
|
*
|
||||||
|
* @param width 图片宽
|
||||||
|
* @param height 图片高
|
||||||
|
* @param generator 验证码生成器
|
||||||
|
* @param interfereCount 验证码干扰元素个数
|
||||||
|
*/
|
||||||
|
public LineCaptcha(final int width, final int height, final CodeGenerator generator, final int interfereCount) {
|
||||||
|
super(width, height, generator, interfereCount);
|
||||||
}
|
}
|
||||||
// -------------------------------------------------------------------- Constructor end
|
// -------------------------------------------------------------------- Constructor end
|
||||||
|
|
||||||
|
@ -14,6 +14,8 @@ package org.dromara.hutool.swing.captcha;
|
|||||||
|
|
||||||
import org.dromara.hutool.core.util.ObjUtil;
|
import org.dromara.hutool.core.util.ObjUtil;
|
||||||
import org.dromara.hutool.core.util.RandomUtil;
|
import org.dromara.hutool.core.util.RandomUtil;
|
||||||
|
import org.dromara.hutool.swing.captcha.generator.CodeGenerator;
|
||||||
|
import org.dromara.hutool.swing.captcha.generator.RandomGenerator;
|
||||||
import org.dromara.hutool.swing.img.color.ColorUtil;
|
import org.dromara.hutool.swing.img.color.ColorUtil;
|
||||||
import org.dromara.hutool.swing.img.GraphicsUtil;
|
import org.dromara.hutool.swing.img.GraphicsUtil;
|
||||||
|
|
||||||
@ -63,7 +65,19 @@ public class ShearCaptcha extends AbstractCaptcha {
|
|||||||
* @param thickness 干扰线宽度
|
* @param thickness 干扰线宽度
|
||||||
*/
|
*/
|
||||||
public ShearCaptcha(final int width, final int height, final int codeCount, final int thickness) {
|
public ShearCaptcha(final int width, final int height, final int codeCount, final int thickness) {
|
||||||
super(width, height, codeCount, thickness);
|
this(width, height, new RandomGenerator(codeCount), thickness);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构造
|
||||||
|
*
|
||||||
|
* @param width 图片宽
|
||||||
|
* @param height 图片高
|
||||||
|
* @param generator 验证码生成器
|
||||||
|
* @param interfereCount 验证码干扰元素个数
|
||||||
|
*/
|
||||||
|
public ShearCaptcha(final int width, final int height, final CodeGenerator generator, final int interfereCount) {
|
||||||
|
super(width, height, generator, interfereCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -30,7 +30,7 @@ public class CaptchaTest {
|
|||||||
@Test
|
@Test
|
||||||
public void lineCaptchaTest1() {
|
public void lineCaptchaTest1() {
|
||||||
// 定义图形验证码的长和宽
|
// 定义图形验证码的长和宽
|
||||||
final LineCaptcha lineCaptcha = CaptchaUtil.createLineCaptcha(200, 100);
|
final LineCaptcha lineCaptcha = CaptchaUtil.ofLineCaptcha(200, 100);
|
||||||
Assertions.assertNotNull(lineCaptcha.getCode());
|
Assertions.assertNotNull(lineCaptcha.getCode());
|
||||||
Assertions.assertTrue(lineCaptcha.verify(lineCaptcha.getCode()));
|
Assertions.assertTrue(lineCaptcha.verify(lineCaptcha.getCode()));
|
||||||
}
|
}
|
||||||
@ -39,7 +39,7 @@ public class CaptchaTest {
|
|||||||
@Disabled
|
@Disabled
|
||||||
public void lineCaptchaTest3() {
|
public void lineCaptchaTest3() {
|
||||||
// 定义图形验证码的长和宽
|
// 定义图形验证码的长和宽
|
||||||
final LineCaptcha lineCaptcha = CaptchaUtil.createLineCaptcha(200, 70, 4, 15);
|
final LineCaptcha lineCaptcha = CaptchaUtil.ofLineCaptcha(200, 70, 4, 15);
|
||||||
lineCaptcha.setBackground(Color.yellow);
|
lineCaptcha.setBackground(Color.yellow);
|
||||||
lineCaptcha.write("f:/test/captcha/tellow.png");
|
lineCaptcha.write("f:/test/captcha/tellow.png");
|
||||||
}
|
}
|
||||||
@ -48,7 +48,7 @@ public class CaptchaTest {
|
|||||||
@Disabled
|
@Disabled
|
||||||
public void lineCaptchaWithMathTest() {
|
public void lineCaptchaWithMathTest() {
|
||||||
// 定义图形验证码的长和宽
|
// 定义图形验证码的长和宽
|
||||||
final LineCaptcha lineCaptcha = CaptchaUtil.createLineCaptcha(200, 80);
|
final LineCaptcha lineCaptcha = CaptchaUtil.ofLineCaptcha(200, 80);
|
||||||
lineCaptcha.setGenerator(new MathGenerator());
|
lineCaptcha.setGenerator(new MathGenerator());
|
||||||
lineCaptcha.setTextAlpha(0.8f);
|
lineCaptcha.setTextAlpha(0.8f);
|
||||||
lineCaptcha.write("f:/captcha/math.png");
|
lineCaptcha.write("f:/captcha/math.png");
|
||||||
@ -59,7 +59,7 @@ public class CaptchaTest {
|
|||||||
public void lineCaptchaTest2() {
|
public void lineCaptchaTest2() {
|
||||||
|
|
||||||
// 定义图形验证码的长和宽
|
// 定义图形验证码的长和宽
|
||||||
final LineCaptcha lineCaptcha = CaptchaUtil.createLineCaptcha(200, 100);
|
final LineCaptcha lineCaptcha = CaptchaUtil.ofLineCaptcha(200, 100);
|
||||||
// LineCaptcha lineCaptcha = new LineCaptcha(200, 100, 4, 150);
|
// LineCaptcha lineCaptcha = new LineCaptcha(200, 100, 4, 150);
|
||||||
// 图形验证码写出,可以写出到文件,也可以写出到流
|
// 图形验证码写出,可以写出到文件,也可以写出到流
|
||||||
lineCaptcha.write("f:/captcha/line.png");
|
lineCaptcha.write("f:/captcha/line.png");
|
||||||
@ -79,7 +79,7 @@ public class CaptchaTest {
|
|||||||
public void circleCaptchaTest() {
|
public void circleCaptchaTest() {
|
||||||
|
|
||||||
// 定义图形验证码的长和宽
|
// 定义图形验证码的长和宽
|
||||||
final CircleCaptcha captcha = CaptchaUtil.createCircleCaptcha(200, 100, 4, 20);
|
final CircleCaptcha captcha = CaptchaUtil.ofCircleCaptcha(200, 100, 4, 20);
|
||||||
// CircleCaptcha captcha = new CircleCaptcha(200, 100, 4, 20);
|
// CircleCaptcha captcha = new CircleCaptcha(200, 100, 4, 20);
|
||||||
// 图形验证码写出,可以写出到文件,也可以写出到流
|
// 图形验证码写出,可以写出到文件,也可以写出到流
|
||||||
captcha.write("f:/captcha/circle.png");
|
captcha.write("f:/captcha/circle.png");
|
||||||
@ -92,7 +92,7 @@ public class CaptchaTest {
|
|||||||
public void shearCaptchaTest() {
|
public void shearCaptchaTest() {
|
||||||
|
|
||||||
// 定义图形验证码的长和宽
|
// 定义图形验证码的长和宽
|
||||||
final ShearCaptcha captcha = CaptchaUtil.createShearCaptcha(203, 100, 4, 4);
|
final ShearCaptcha captcha = CaptchaUtil.ofShearCaptcha(203, 100, 4, 4);
|
||||||
// ShearCaptcha captcha = new ShearCaptcha(200, 100, 4, 4);
|
// ShearCaptcha captcha = new ShearCaptcha(200, 100, 4, 4);
|
||||||
// 图形验证码写出,可以写出到文件,也可以写出到流
|
// 图形验证码写出,可以写出到文件,也可以写出到流
|
||||||
captcha.write("f:/captcha/shear.png");
|
captcha.write("f:/captcha/shear.png");
|
||||||
@ -116,7 +116,7 @@ public class CaptchaTest {
|
|||||||
@Disabled
|
@Disabled
|
||||||
public void ShearCaptchaWithMathTest() {
|
public void ShearCaptchaWithMathTest() {
|
||||||
// 定义图形验证码的长和宽
|
// 定义图形验证码的长和宽
|
||||||
final ShearCaptcha captcha = CaptchaUtil.createShearCaptcha(200, 45, 4, 4);
|
final ShearCaptcha captcha = CaptchaUtil.ofShearCaptcha(200, 45, 4, 4);
|
||||||
captcha.setGenerator(new MathGenerator());
|
captcha.setGenerator(new MathGenerator());
|
||||||
// ShearCaptcha captcha = new ShearCaptcha(200, 100, 4, 4);
|
// ShearCaptcha captcha = new ShearCaptcha(200, 100, 4, 4);
|
||||||
// 图形验证码写出,可以写出到文件,也可以写出到流
|
// 图形验证码写出,可以写出到文件,也可以写出到流
|
||||||
@ -128,7 +128,7 @@ public class CaptchaTest {
|
|||||||
@Test
|
@Test
|
||||||
@Disabled
|
@Disabled
|
||||||
public void GifCaptchaTest() {
|
public void GifCaptchaTest() {
|
||||||
final GifCaptcha captcha = CaptchaUtil.createGifCaptcha(200, 100, 4);
|
final GifCaptcha captcha = CaptchaUtil.ofGifCaptcha(200, 100, 4);
|
||||||
captcha.write("d:/test/gif_captcha.gif");
|
captcha.write("d:/test/gif_captcha.gif");
|
||||||
assert captcha.verify(captcha.getCode());
|
assert captcha.verify(captcha.getCode());
|
||||||
}
|
}
|
||||||
@ -136,7 +136,7 @@ public class CaptchaTest {
|
|||||||
@Test
|
@Test
|
||||||
@Disabled
|
@Disabled
|
||||||
public void bgTest(){
|
public void bgTest(){
|
||||||
final LineCaptcha captcha = CaptchaUtil.createLineCaptcha(200, 100, 4, 1);
|
final LineCaptcha captcha = CaptchaUtil.ofLineCaptcha(200, 100, 4, 1);
|
||||||
captcha.setBackground(Color.WHITE);
|
captcha.setBackground(Color.WHITE);
|
||||||
captcha.write("d:/test/test.jpg");
|
captcha.write("d:/test/test.jpg");
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,7 @@ public class CaptchaUtilTest {
|
|||||||
@Disabled
|
@Disabled
|
||||||
public void createTest() {
|
public void createTest() {
|
||||||
for(int i = 0; i < 1; i++) {
|
for(int i = 0; i < 1; i++) {
|
||||||
CaptchaUtil.createShearCaptcha(320, 240);
|
CaptchaUtil.ofShearCaptcha(320, 240);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
2
pom.xml
2
pom.xml
@ -20,7 +20,7 @@
|
|||||||
|
|
||||||
<groupId>org.dromara.hutool</groupId>
|
<groupId>org.dromara.hutool</groupId>
|
||||||
<artifactId>hutool-parent</artifactId>
|
<artifactId>hutool-parent</artifactId>
|
||||||
<version>6.0.0-M10</version>
|
<version>6.0.0-M11</version>
|
||||||
<name>hutool</name>
|
<name>hutool</name>
|
||||||
<description>
|
<description>
|
||||||
Hutool是一个功能丰富且易用的Java工具库,通过诸多实用工具类的使用,旨在帮助开发者快速、便捷地完成各类开发任务。这些封装的工具涵盖了字符串、数字、集合、编码、日期、文件、IO、加密、数据库JDBC、JSON、HTTP客户端等一系列操作,可以满足各种不同的开发需求。
|
Hutool是一个功能丰富且易用的Java工具库,通过诸多实用工具类的使用,旨在帮助开发者快速、便捷地完成各类开发任务。这些封装的工具涵盖了字符串、数字、集合、编码、日期、文件、IO、加密、数据库JDBC、JSON、HTTP客户端等一系列操作,可以满足各种不同的开发需求。
|
||||||
|
Loading…
x
Reference in New Issue
Block a user