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
|
||||
|
@ -144,18 +144,18 @@ We provide the T-Shirt and Sweater with Hutool Logo, please visit the shop:
|
||||
<dependency>
|
||||
<groupId>org.dromara.hutool</groupId>
|
||||
<artifactId>hutool-all</artifactId>
|
||||
<version>6.0.0-M10</version>
|
||||
<version>6.0.0-M11</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
### 🍐Gradle
|
||||
```
|
||||
implementation 'org.dromara.hutool:hutool-all:6.0.0-M10'
|
||||
implementation 'org.dromara.hutool:hutool-all:6.0.0-M11'
|
||||
```
|
||||
|
||||
## 📥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:
|
||||
> 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>
|
||||
<groupId>org.dromara.hutool</groupId>
|
||||
<artifactId>hutool-all</artifactId>
|
||||
<version>6.0.0-M10</version>
|
||||
<version>6.0.0-M11</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
### 🍐Gradle
|
||||
|
||||
```
|
||||
implementation 'org.dromara.hutool:hutool-all:6.0.0-M10'
|
||||
implementation 'org.dromara.hutool:hutool-all:6.0.0-M11'
|
||||
```
|
||||
|
||||
### 📥下载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平台没有测试,不能保证所有工具类或工具方法可用。
|
||||
|
@ -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>
|
||||
<groupId>org.dromara.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>6.0.0-M10</version>
|
||||
<version>6.0.0-M11</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-all</artifactId>
|
||||
|
@ -21,7 +21,7 @@
|
||||
<parent>
|
||||
<groupId>org.dromara.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>6.0.0-M10</version>
|
||||
<version>6.0.0-M11</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-bom</artifactId>
|
||||
|
@ -21,7 +21,7 @@
|
||||
<parent>
|
||||
<groupId>org.dromara.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>6.0.0-M10</version>
|
||||
<version>6.0.0-M11</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-core</artifactId>
|
||||
|
@ -33,6 +33,7 @@ import org.dromara.hutool.core.util.ObjUtil;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.FileSystem;
|
||||
import java.nio.file.*;
|
||||
import java.util.ArrayList;
|
||||
@ -726,7 +727,7 @@ public class ZipUtil {
|
||||
* Gzip压缩处理
|
||||
*
|
||||
* @param content 被压缩的字符串
|
||||
* @param charset 编码
|
||||
* @param charset 编码 {@link StandardCharsets#UTF_8}、 {@link CharsetUtil#UTF_8}
|
||||
* @return 压缩后的字节流
|
||||
* @throws HutoolException IO异常
|
||||
*/
|
||||
|
@ -13,7 +13,8 @@
|
||||
package org.dromara.hutool.core.lang.wrapper;
|
||||
|
||||
/**
|
||||
* 简单包装对象
|
||||
* 简单包装对象<br>
|
||||
* 通过继承此类,可以直接使用被包装的对象,用于简化和统一封装。
|
||||
*
|
||||
* @param <T> 被包装对象类型
|
||||
* @author looly
|
||||
|
@ -297,6 +297,7 @@ public class NumberParser {
|
||||
// issue#I79VS7
|
||||
numberStr = StrUtil.subSuf(numberStr, 1);
|
||||
}
|
||||
|
||||
try {
|
||||
final NumberFormat format = NumberFormat.getInstance(locale);
|
||||
if (format instanceof DecimalFormat) {
|
||||
|
@ -898,12 +898,12 @@ public class NumberUtil extends NumberValidator {
|
||||
}
|
||||
|
||||
// Float、Double等有精度问题,转换为字符串后再转换
|
||||
return toBigDecimal(number.toString());
|
||||
return new BigDecimal(number.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* 数字转{@link BigDecimal}<br>
|
||||
* null或""或空白符转换为0
|
||||
* null或""或"NaN"或空白符转换为0
|
||||
*
|
||||
* @param numberStr 数字字符串
|
||||
* @return {@link BigDecimal}
|
||||
@ -927,7 +927,7 @@ public class NumberUtil extends NumberValidator {
|
||||
|
||||
/**
|
||||
* 数字转{@link BigInteger}<br>
|
||||
* null转换为0
|
||||
* null或"NaN"转换为0
|
||||
*
|
||||
* @param number 数字
|
||||
* @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.func.FunctionPool;
|
||||
import org.dromara.hutool.core.text.placeholder.StrFormatter;
|
||||
import org.dromara.hutool.core.text.split.SplitUtil;
|
||||
import org.dromara.hutool.core.util.CharsetUtil;
|
||||
|
||||
import java.io.StringReader;
|
||||
@ -27,6 +28,13 @@ import java.util.Map;
|
||||
* 字符串工具类<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
|
||||
*/
|
||||
public class StrUtil extends CharSequenceUtil implements StrPool {
|
||||
|
@ -12,6 +12,8 @@
|
||||
|
||||
package org.dromara.hutool.core.xml;
|
||||
|
||||
import org.dromara.hutool.core.text.CharUtil;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
@ -30,6 +32,10 @@ public class XmlConstants {
|
||||
* 字符串常量:XML And 符转义 {@code "&" -> "&"}
|
||||
*/
|
||||
public static final String AMP = "&";
|
||||
/**
|
||||
* The Character '&'.
|
||||
*/
|
||||
public static final Character C_AMP = CharUtil.AMP;
|
||||
|
||||
/**
|
||||
* 字符串常量:XML 双引号转义 {@code """ -> "\""}
|
||||
@ -40,17 +46,41 @@ public class XmlConstants {
|
||||
* 字符串常量:XML 单引号转义 {@code "&apos" -> "'"}
|
||||
*/
|
||||
public static final String APOS = "'";
|
||||
/**
|
||||
* The Character '''.
|
||||
*/
|
||||
public static final Character C_APOS = CharUtil.SINGLE_QUOTE;
|
||||
|
||||
/**
|
||||
* 字符串常量:XML 小于号转义 {@code "<" -> "<"}
|
||||
*/
|
||||
public static final String LT = "<";
|
||||
|
||||
/**
|
||||
* The Character '<'.
|
||||
*/
|
||||
public static final Character C_LT = '<';
|
||||
|
||||
/**
|
||||
* 字符串常量:XML 大于号转义 {@code ">" -> ">"}
|
||||
*/
|
||||
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中无效的字符 正则
|
||||
*/
|
||||
|
@ -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>
|
||||
<groupId>org.dromara.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>6.0.0-M10</version>
|
||||
<version>6.0.0-M11</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-cron</artifactId>
|
||||
|
@ -31,6 +31,9 @@ public class CronConfig {
|
||||
*/
|
||||
protected boolean matchSecond;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*/
|
||||
public CronConfig(){
|
||||
}
|
||||
|
||||
|
@ -13,6 +13,7 @@
|
||||
package org.dromara.hutool.cron;
|
||||
|
||||
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.log.Log;
|
||||
|
||||
@ -63,9 +64,14 @@ public class CronTimer extends Thread implements Serializable {
|
||||
//等待直到下一个时间点,如果被中断直接退出Timer
|
||||
break;
|
||||
}
|
||||
|
||||
//执行点,时间记录为执行开始的时间,而非结束时间
|
||||
thisTime = System.currentTimeMillis();
|
||||
spawnLauncher(thisTime);
|
||||
spawnLauncher(nextTime);
|
||||
|
||||
// issue#3460 采用叠加方式,确保正好是1分钟或1秒,避免sleep晚醒问题
|
||||
// 此处无需校验,因为每次循环都是sleep与上触发点的时间差。
|
||||
// 当上一次晚醒后,本次会减少sleep时间,保证误差在一个unit内,并不断修正。
|
||||
thisTime = nextTime;
|
||||
} else{
|
||||
// 非正常时间重新计算(issue#1224@Github)
|
||||
thisTime = System.currentTimeMillis();
|
||||
|
@ -19,7 +19,7 @@ import org.dromara.hutool.cron.task.InvokeTask;
|
||||
public class DeamonMainTest {
|
||||
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方法调用后,不再产生新的作业,原作业正常执行。
|
||||
CronUtil.setMatchSecond(true);
|
||||
|
@ -14,6 +14,7 @@ package org.dromara.hutool.cron.demo;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.dromara.hutool.core.date.DateUtil;
|
||||
import org.dromara.hutool.core.lang.Console;
|
||||
import org.dromara.hutool.core.thread.ThreadUtil;
|
||||
|
||||
@ -29,7 +30,7 @@ public class TestJob2 {
|
||||
* 执行定时任务内容
|
||||
*/
|
||||
public void doTest() {
|
||||
Console.log("TestJob2.doTest开始执行……");
|
||||
Console.log("TestJob2.doTest开始执行…… at [{}]", DateUtil.formatNow());
|
||||
ThreadUtil.sleep(20, TimeUnit.SECONDS);
|
||||
Console.log("延迟20s打印testJob2");
|
||||
}
|
||||
|
@ -8,7 +8,7 @@
|
||||
|
||||
# demo.org.dromara.hutool.cron.TestJob.doTest = */1 * * * * *
|
||||
|
||||
[org.dromara.hutool.cron.demo]=
|
||||
[org.dromara.hutool.cron.demo]
|
||||
# 6位表达式在秒匹配模式下可用,此处表示每秒执行一次
|
||||
# TestJob.doTest = */1 * * * * *
|
||||
# 5位表达式在分匹配模式下可用,此处表示每分钟执行一次
|
||||
|
@ -21,7 +21,7 @@
|
||||
<parent>
|
||||
<groupId>org.dromara.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>6.0.0-M10</version>
|
||||
<version>6.0.0-M11</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-crypto</artifactId>
|
||||
@ -31,7 +31,7 @@
|
||||
<properties>
|
||||
<Automatic-Module-Name>org.dromara.hutool.crypto</Automatic-Module-Name>
|
||||
<!-- versions -->
|
||||
<bouncycastle.version>1.76</bouncycastle.version>
|
||||
<bouncycastle.version>1.77</bouncycastle.version>
|
||||
</properties>
|
||||
|
||||
<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;
|
||||
|
||||
// region ----- generateKey
|
||||
|
||||
/**
|
||||
* 生成 {@link SecretKey},仅用于对称加密和摘要算法密钥生成
|
||||
*
|
||||
@ -196,7 +197,24 @@ public class KeyUtil {
|
||||
}
|
||||
// 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
|
||||
public static boolean isEmpty(final KeyPair keyPair) {
|
||||
if (null == keyPair) {
|
||||
return false;
|
||||
}
|
||||
return null != keyPair.getPrivate() || null != keyPair.getPublic();
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成RSA私钥,仅用于非对称加密<br>
|
||||
* 采用PKCS#8规范,此规范定义了私钥信息语法和加密私钥语法<br>
|
||||
|
@ -54,5 +54,10 @@ public enum Mode {
|
||||
/**
|
||||
* 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>
|
||||
* 封装包括:
|
||||
* <ul>
|
||||
* <li>非堆成签名,签名算法支持见{@link SignAlgorithm}</li>
|
||||
* <li>非对称签名,签名算法支持见{@link SignAlgorithm}</li>
|
||||
* <li>对称签名,支持Map类型参数排序后签名</li>
|
||||
* <li>摘要签名,支持Map类型参数排序后签名,签名方法见:{@link DigestAlgorithm}</li>
|
||||
* </ul>
|
||||
|
@ -14,18 +14,13 @@ package org.dromara.hutool.crypto.asymmetric;
|
||||
|
||||
import org.dromara.hutool.core.codec.binary.Base64;
|
||||
import org.dromara.hutool.core.io.stream.FastByteArrayOutputStream;
|
||||
import org.dromara.hutool.crypto.CipherWrapper;
|
||||
import org.dromara.hutool.crypto.CryptoException;
|
||||
import org.dromara.hutool.crypto.KeyUtil;
|
||||
import org.dromara.hutool.crypto.SecureUtil;
|
||||
import org.dromara.hutool.crypto.*;
|
||||
import org.dromara.hutool.crypto.symmetric.SymmetricAlgorithm;
|
||||
|
||||
import javax.crypto.BadPaddingException;
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.IllegalBlockSizeException;
|
||||
import java.io.IOException;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.Key;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
@ -51,8 +46,7 @@ public class AsymmetricCrypto extends AbstractAsymmetricCrypto<AsymmetricCrypto>
|
||||
/**
|
||||
* Cipher负责完成加密或解密工作
|
||||
*/
|
||||
protected CipherWrapper cipherWrapper;
|
||||
|
||||
protected JceCipher cipher;
|
||||
/**
|
||||
* 加密的块大小
|
||||
*/
|
||||
@ -61,6 +55,15 @@ public class AsymmetricCrypto extends AbstractAsymmetricCrypto<AsymmetricCrypto>
|
||||
* 解密的块大小
|
||||
*/
|
||||
protected int decryptBlockSize = -1;
|
||||
|
||||
/**
|
||||
* 算法参数
|
||||
*/
|
||||
private AlgorithmParameterSpec algorithmParameterSpec;
|
||||
/**
|
||||
* 自定义随机数
|
||||
*/
|
||||
private SecureRandom random;
|
||||
// ------------------------------------------------------------------ Constructor start
|
||||
|
||||
/**
|
||||
@ -144,8 +147,8 @@ public class AsymmetricCrypto extends AbstractAsymmetricCrypto<AsymmetricCrypto>
|
||||
*/
|
||||
public AsymmetricCrypto(final String algorithm, final byte[] privateKey, final byte[] publicKey) {
|
||||
this(algorithm, //
|
||||
KeyUtil.generatePrivateKey(algorithm, privateKey), //
|
||||
KeyUtil.generatePublicKey(algorithm, publicKey)//
|
||||
KeyUtil.generatePrivateKey(algorithm, privateKey), //
|
||||
KeyUtil.generatePublicKey(algorithm, publicKey)//
|
||||
);
|
||||
}
|
||||
|
||||
@ -209,7 +212,7 @@ public class AsymmetricCrypto extends AbstractAsymmetricCrypto<AsymmetricCrypto>
|
||||
* @since 5.4.3
|
||||
*/
|
||||
public AlgorithmParameterSpec getAlgorithmParameterSpec() {
|
||||
return this.cipherWrapper.getParams();
|
||||
return this.algorithmParameterSpec;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -217,10 +220,11 @@ public class AsymmetricCrypto extends AbstractAsymmetricCrypto<AsymmetricCrypto>
|
||||
* 在某些算法中,需要特别的参数,例如在ECIES中,此处为IESParameterSpec
|
||||
*
|
||||
* @param algorithmParameterSpec {@link AlgorithmParameterSpec}
|
||||
* @since 5.4.3
|
||||
* @return this
|
||||
*/
|
||||
public void setAlgorithmParameterSpec(final AlgorithmParameterSpec algorithmParameterSpec) {
|
||||
this.cipherWrapper.setParams(algorithmParameterSpec);
|
||||
public AsymmetricCrypto setAlgorithmParameterSpec(final AlgorithmParameterSpec algorithmParameterSpec) {
|
||||
this.algorithmParameterSpec = algorithmParameterSpec;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -231,7 +235,7 @@ public class AsymmetricCrypto extends AbstractAsymmetricCrypto<AsymmetricCrypto>
|
||||
* @since 5.7.17
|
||||
*/
|
||||
public AsymmetricCrypto setRandom(final SecureRandom random) {
|
||||
this.cipherWrapper.setRandom(random);
|
||||
this.random = random;
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -249,7 +253,7 @@ public class AsymmetricCrypto extends AbstractAsymmetricCrypto<AsymmetricCrypto>
|
||||
final Key key = getKeyByType(keyType);
|
||||
lock.lock();
|
||||
try {
|
||||
final Cipher cipher = initMode(Cipher.ENCRYPT_MODE, key);
|
||||
final JceCipher cipher = initMode(CipherMode.ENCRYPT, key);
|
||||
|
||||
if (this.encryptBlockSize < 0) {
|
||||
// 在引入BC库情况下,自动获取块大小
|
||||
@ -274,7 +278,7 @@ public class AsymmetricCrypto extends AbstractAsymmetricCrypto<AsymmetricCrypto>
|
||||
final Key key = getKeyByType(keyType);
|
||||
lock.lock();
|
||||
try {
|
||||
final Cipher cipher = initMode(Cipher.DECRYPT_MODE, key);
|
||||
final JceCipher cipher = initMode(CipherMode.DECRYPT, key);
|
||||
|
||||
if (this.decryptBlockSize < 0) {
|
||||
// 在引入BC库情况下,自动获取块大小
|
||||
@ -301,7 +305,7 @@ public class AsymmetricCrypto extends AbstractAsymmetricCrypto<AsymmetricCrypto>
|
||||
* @since 5.4.3
|
||||
*/
|
||||
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
|
||||
*/
|
||||
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 IOException IO异常,不会被触发
|
||||
*/
|
||||
@SuppressWarnings("resource")
|
||||
private byte[] doFinalWithBlock(final byte[] data, final int maxBlockSize) throws IllegalBlockSizeException, BadPaddingException, IOException {
|
||||
final int dataLength = data.length;
|
||||
@SuppressWarnings("resource") final FastByteArrayOutputStream out = new FastByteArrayOutputStream();
|
||||
final FastByteArrayOutputStream out = new FastByteArrayOutputStream();
|
||||
|
||||
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 密钥
|
||||
* @return {@link Cipher}
|
||||
* @throws InvalidAlgorithmParameterException 异常算法错误
|
||||
* @throws InvalidKeyException 异常KEY错误
|
||||
* @return {@link JceCipher}
|
||||
*/
|
||||
private Cipher initMode(final int mode, final Key key) throws InvalidAlgorithmParameterException, InvalidKeyException {
|
||||
return this.cipherWrapper.initMode(mode, key).getRaw();
|
||||
private JceCipher initMode(final CipherMode mode, final Key key) {
|
||||
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.x509.SubjectPublicKeyInfo;
|
||||
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.jcajce.provider.asymmetric.util.ECUtil;
|
||||
import org.bouncycastle.jce.spec.ECParameterSpec;
|
||||
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.security.PrivateKey;
|
||||
@ -41,10 +50,10 @@ public class BCUtil {
|
||||
*/
|
||||
public static ECDomainParameters toDomainParams(final ECParameterSpec parameterSpec) {
|
||||
return new ECDomainParameters(
|
||||
parameterSpec.getCurve(),
|
||||
parameterSpec.getG(),
|
||||
parameterSpec.getN(),
|
||||
parameterSpec.getH());
|
||||
parameterSpec.getCurve(),
|
||||
parameterSpec.getG(),
|
||||
parameterSpec.getN(),
|
||||
parameterSpec.getH());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -67,10 +76,10 @@ public class BCUtil {
|
||||
*/
|
||||
public static ECDomainParameters toDomainParams(final X9ECParameters x9ECParameters) {
|
||||
return new ECDomainParameters(
|
||||
x9ECParameters.getCurve(),
|
||||
x9ECParameters.getG(),
|
||||
x9ECParameters.getN(),
|
||||
x9ECParameters.getH()
|
||||
x9ECParameters.getCurve(),
|
||||
x9ECParameters.getG(),
|
||||
x9ECParameters.getN(),
|
||||
x9ECParameters.getH()
|
||||
);
|
||||
}
|
||||
|
||||
@ -81,7 +90,7 @@ public class BCUtil {
|
||||
* @return PKCS#1格式私钥
|
||||
* @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());
|
||||
try {
|
||||
return pkInfo.parsePrivateKey().toASN1Primitive().getEncoded();
|
||||
@ -97,13 +106,53 @@ public class BCUtil {
|
||||
* @return PKCS#1格式公钥
|
||||
* @since 5.5.9
|
||||
*/
|
||||
public static byte[] toPkcs1(final PublicKey publicKey){
|
||||
public static byte[] toPkcs1(final PublicKey publicKey) {
|
||||
final SubjectPublicKeyInfo spkInfo = SubjectPublicKeyInfo
|
||||
.getInstance(publicKey.getEncoded());
|
||||
.getInstance(publicKey.getEncoded());
|
||||
try {
|
||||
return spkInfo.parsePublicKey().getEncoded();
|
||||
} catch (final IOException 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
|
||||
*/
|
||||
public static ECPrivateKeyParameters decodePrivateKeyParams(final byte[] privateKeyBytes) {
|
||||
if (null == privateKeyBytes) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
// 尝试D值
|
||||
return toSm2PrivateParams(privateKeyBytes);
|
||||
@ -468,6 +471,9 @@ public class ECKeyUtil {
|
||||
* @since 5.5.9
|
||||
*/
|
||||
public static ECPublicKeyParameters decodePublicKeyParams(final byte[] publicKeyBytes) {
|
||||
if(null == publicKeyBytes){
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
// 尝试Q值
|
||||
return toSm2PublicParams(publicKeyBytes);
|
||||
|
@ -545,7 +545,7 @@ public class BCrypt {
|
||||
if (hashed_bytes.length != try_bytes.length) {
|
||||
return false;
|
||||
}
|
||||
byte ret = 0;
|
||||
int ret = 0;
|
||||
for (int i = 0; i < try_bytes.length; i++)
|
||||
ret |= hashed_bytes[i] ^ try_bytes[i];
|
||||
return ret == 0;
|
||||
|
@ -19,11 +19,29 @@ package org.dromara.hutool.crypto.digest;
|
||||
* @author Looly
|
||||
*/
|
||||
public enum DigestAlgorithm {
|
||||
/**
|
||||
* MD2
|
||||
*/
|
||||
MD2("MD2"),
|
||||
/**
|
||||
* MD5
|
||||
*/
|
||||
MD5("MD5"),
|
||||
/**
|
||||
* SHA-1
|
||||
*/
|
||||
SHA1("SHA-1"),
|
||||
/**
|
||||
* SHA-256
|
||||
*/
|
||||
SHA256("SHA-256"),
|
||||
/**
|
||||
* SHA-384
|
||||
*/
|
||||
SHA384("SHA-384"),
|
||||
/**
|
||||
* SHA-512
|
||||
*/
|
||||
SHA512("SHA-512");
|
||||
|
||||
private final String value;
|
||||
|
@ -253,7 +253,7 @@ public class Digester extends SimpleWrapper<MessageDigest> implements Serializab
|
||||
// 加盐在末尾,自动忽略空盐值
|
||||
result = doDigest(data, 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(this.salt);
|
||||
|
@ -27,7 +27,7 @@ import java.security.Provider;
|
||||
public class DigesterFactory {
|
||||
|
||||
/**
|
||||
* 创建工厂
|
||||
* 创建工厂,只使用JDK提供的算法
|
||||
*
|
||||
* @param algorithm 算法
|
||||
* @return DigesterFactory
|
||||
|
@ -75,16 +75,4 @@ public class BCHMacEngine extends BCMacEngine {
|
||||
super(mac, params);
|
||||
}
|
||||
// ------------------------------------------------------------------------------------------- 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.Mac;
|
||||
import org.bouncycastle.crypto.params.KeyParameter;
|
||||
import org.dromara.hutool.core.lang.wrapper.SimpleWrapper;
|
||||
|
||||
/**
|
||||
* BouncyCastle的MAC算法实现引擎,使用{@link Mac} 实现摘要<br>
|
||||
@ -23,9 +24,7 @@ import org.bouncycastle.crypto.params.KeyParameter;
|
||||
* @author Looly
|
||||
* @since 5.8.0
|
||||
*/
|
||||
public class BCMacEngine implements MacEngine {
|
||||
|
||||
private Mac mac;
|
||||
public class BCMacEngine extends SimpleWrapper<Mac> implements MacEngine {
|
||||
|
||||
// ------------------------------------------------------------------------------------------- Constructor start
|
||||
/**
|
||||
@ -36,57 +35,46 @@ public class BCMacEngine implements MacEngine {
|
||||
* @since 5.8.0
|
||||
*/
|
||||
public BCMacEngine(final Mac mac, final CipherParameters params) {
|
||||
init(mac, params);
|
||||
super(initMac(mac, params));
|
||||
}
|
||||
// ------------------------------------------------------------------------------------------- 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 params 参数,例如密钥可以用{@link KeyParameter}
|
||||
* @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);
|
||||
this.mac = mac;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得 {@link Mac}
|
||||
*
|
||||
* @return {@link Mac}
|
||||
*/
|
||||
public Mac ipgetMac() {
|
||||
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.CipherParameters;
|
||||
import org.bouncycastle.crypto.Digest;
|
||||
import org.bouncycastle.crypto.Mac;
|
||||
import org.bouncycastle.crypto.macs.CBCBlockCipherMac;
|
||||
import org.bouncycastle.crypto.params.KeyParameter;
|
||||
import org.bouncycastle.crypto.params.ParametersWithIV;
|
||||
@ -97,16 +96,4 @@ public class CBCBlockCipherMacEngine extends BCMacEngine {
|
||||
public CBCBlockCipherMacEngine(final CBCBlockCipherMac mac, final CipherParameters 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;
|
||||
|
||||
import org.dromara.hutool.core.lang.wrapper.SimpleWrapper;
|
||||
import org.dromara.hutool.crypto.CryptoException;
|
||||
import org.dromara.hutool.crypto.KeyUtil;
|
||||
import org.dromara.hutool.crypto.SecureUtil;
|
||||
@ -23,18 +24,15 @@ import java.security.Key;
|
||||
import java.security.spec.AlgorithmParameterSpec;
|
||||
|
||||
/**
|
||||
* 默认的HMAC算法实现引擎,使用{@link Mac} 实现摘要<br>
|
||||
* JDK提供的的MAC算法实现引擎,使用{@link Mac} 实现摘要<br>
|
||||
* 当引入BouncyCastle库时自动使用其作为Provider
|
||||
*
|
||||
* @author Looly
|
||||
* @since 4.5.13
|
||||
*/
|
||||
public class DefaultHMacEngine implements MacEngine {
|
||||
|
||||
private Mac mac;
|
||||
public class JCEMacEngine extends SimpleWrapper<Mac> implements MacEngine {
|
||||
|
||||
// region ----- Constructor
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
@ -42,7 +40,7 @@ public class DefaultHMacEngine implements MacEngine {
|
||||
* @param key 密钥
|
||||
* @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));
|
||||
}
|
||||
|
||||
@ -53,7 +51,7 @@ public class DefaultHMacEngine implements MacEngine {
|
||||
* @param key 密钥
|
||||
* @since 4.5.13
|
||||
*/
|
||||
public DefaultHMacEngine(final String algorithm, final Key key) {
|
||||
public JCEMacEngine(final String algorithm, final Key key) {
|
||||
this(algorithm, key, null);
|
||||
}
|
||||
|
||||
@ -65,34 +63,39 @@ public class DefaultHMacEngine implements MacEngine {
|
||||
* @param spec {@link AlgorithmParameterSpec}
|
||||
* @since 5.7.12
|
||||
*/
|
||||
public DefaultHMacEngine(final String algorithm, final Key key, final AlgorithmParameterSpec spec) {
|
||||
init(algorithm, key, spec);
|
||||
public JCEMacEngine(final String algorithm, final Key key, final AlgorithmParameterSpec spec) {
|
||||
super(initMac(algorithm, key, spec));
|
||||
}
|
||||
// endregion
|
||||
|
||||
// region ----- init
|
||||
|
||||
/**
|
||||
* 初始化
|
||||
*
|
||||
* @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) {
|
||||
this.raw.update(in);
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化
|
||||
*
|
||||
* @param algorithm 算法
|
||||
* @param key 密钥 {@link SecretKey}
|
||||
* @return this
|
||||
* @throws CryptoException Cause by IOException
|
||||
*/
|
||||
public DefaultHMacEngine init(final String algorithm, final Key key) {
|
||||
return init(algorithm, key, null);
|
||||
@Override
|
||||
public void update(final byte[] in, final int inOff, final int len) {
|
||||
this.raw.update(in, inOff, len);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] doFinal() {
|
||||
return this.raw.doFinal();
|
||||
}
|
||||
|
||||
@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}
|
||||
* @return this
|
||||
* @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 {
|
||||
mac = SecureUtil.createMac(algorithm);
|
||||
if (null == key) {
|
||||
@ -119,46 +122,6 @@ public class DefaultHMacEngine implements MacEngine {
|
||||
} catch (final Exception e) {
|
||||
throw new CryptoException(e);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
// endregion
|
||||
|
||||
/**
|
||||
* 获得 {@link Mac}
|
||||
*
|
||||
* @return {@link Mac}
|
||||
*/
|
||||
public Mac getMac() {
|
||||
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库实现的,忽略加盐
|
||||
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 还可以使用一个用于计算和确认消息鉴别值的密钥。
|
||||
* </p>
|
||||
*
|
||||
* <pre>{@code
|
||||
* MacEngineFactory
|
||||
* ||(创建)
|
||||
* MacEngine----------------(包装)-----------------> Mac
|
||||
* _____|_______________ |
|
||||
* / \ HMac
|
||||
* JCEMacEngine BCMacEngine
|
||||
* / \
|
||||
* BCHMacEngine CBCBlockCipherMacEngine
|
||||
* |
|
||||
* SM4MacEngine
|
||||
* }</pre>
|
||||
*
|
||||
* 通过MacEngine,封装支持了BouncyCastle和JCE实现的一些MAC算法,通过MacEngineFactory自动根据算法名称创建对应对象。
|
||||
*
|
||||
* @author Looly
|
||||
* @since 4.5.13
|
||||
*/
|
||||
|
@ -21,6 +21,12 @@
|
||||
* Truncate:是一个函数,就是怎么截取加密后的串,并取加密后串的哪些字段组成一个数字。
|
||||
* </pre>
|
||||
*
|
||||
* 实现包括:
|
||||
* <ul>
|
||||
* <li>HMAC-based one-time passwords (HOTP) 基于HMAC算法一次性密码生成器</li>
|
||||
* <li>time-based one-time passwords (TOTP) 基于时间戳算法的一次性密码生成器</li>
|
||||
* </ul>
|
||||
*
|
||||
* @author looly
|
||||
*/
|
||||
package org.dromara.hutool.crypto.digest.otp;
|
||||
|
@ -34,8 +34,6 @@ import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.Serializable;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.SecureRandom;
|
||||
import java.security.spec.AlgorithmParameterSpec;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
@ -52,7 +50,15 @@ import java.util.concurrent.locks.ReentrantLock;
|
||||
public class SymmetricCrypto implements SymmetricEncryptor, SymmetricDecryptor, Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private CipherWrapper cipherWrapper;
|
||||
private JceCipher cipher;
|
||||
/**
|
||||
* 算法参数
|
||||
*/
|
||||
private AlgorithmParameterSpec algorithmParameterSpec;
|
||||
/**
|
||||
* 自定义随机数
|
||||
*/
|
||||
private SecureRandom random;
|
||||
/**
|
||||
* SecretKey 负责保存对称密钥
|
||||
*/
|
||||
@ -157,7 +163,7 @@ public class SymmetricCrypto implements SymmetricEncryptor, SymmetricDecryptor,
|
||||
this.isZeroPadding = true;
|
||||
}
|
||||
|
||||
this.cipherWrapper = new CipherWrapper(algorithm);
|
||||
this.cipher = new JceCipher(algorithm);
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -176,17 +182,17 @@ public class SymmetricCrypto implements SymmetricEncryptor, SymmetricDecryptor,
|
||||
* @return 加密或解密
|
||||
*/
|
||||
public Cipher getCipher() {
|
||||
return cipherWrapper.getRaw();
|
||||
return cipher.getRaw();
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置 {@link AlgorithmParameterSpec},通常用于加盐或偏移向量
|
||||
* 设置{@link AlgorithmParameterSpec},通常用于加盐或偏移向量
|
||||
*
|
||||
* @param params {@link AlgorithmParameterSpec}
|
||||
* @return 自身
|
||||
* @param algorithmParameterSpec {@link AlgorithmParameterSpec}
|
||||
* @return this
|
||||
*/
|
||||
public SymmetricCrypto setParams(final AlgorithmParameterSpec params) {
|
||||
this.cipherWrapper.setParams(params);
|
||||
public SymmetricCrypto setAlgorithmParameterSpec(final AlgorithmParameterSpec algorithmParameterSpec) {
|
||||
this.algorithmParameterSpec = algorithmParameterSpec;
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -197,7 +203,7 @@ public class SymmetricCrypto implements SymmetricEncryptor, SymmetricDecryptor,
|
||||
* @return 自身
|
||||
*/
|
||||
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
|
||||
*/
|
||||
public SymmetricCrypto setRandom(final SecureRandom random) {
|
||||
this.cipherWrapper.setRandom(random);
|
||||
this.random = random;
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -245,9 +251,7 @@ public class SymmetricCrypto implements SymmetricEncryptor, SymmetricDecryptor,
|
||||
public SymmetricCrypto setMode(final CipherMode mode, final byte[] salt) {
|
||||
lock.lock();
|
||||
try {
|
||||
initMode(mode.getValue(), salt);
|
||||
} catch (final Exception e) {
|
||||
throw new CryptoException(e);
|
||||
initMode(mode, salt);
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
@ -263,7 +267,7 @@ public class SymmetricCrypto implements SymmetricEncryptor, SymmetricDecryptor,
|
||||
* @since 5.6.8
|
||||
*/
|
||||
public byte[] update(final byte[] data) {
|
||||
final Cipher cipher = cipherWrapper.getRaw();
|
||||
final Cipher cipher = this.cipher.getRaw();
|
||||
lock.lock();
|
||||
try {
|
||||
return cipher.update(paddingDataWithZero(data, cipher.getBlockSize()));
|
||||
@ -305,10 +309,8 @@ public class SymmetricCrypto implements SymmetricEncryptor, SymmetricDecryptor,
|
||||
byte[] result;
|
||||
lock.lock();
|
||||
try {
|
||||
final Cipher cipher = initMode(Cipher.ENCRYPT_MODE, salt);
|
||||
result = cipher.doFinal(paddingDataWithZero(data, cipher.getBlockSize()));
|
||||
} catch (final Exception e) {
|
||||
throw new CryptoException(e);
|
||||
final JceCipher cipher = initMode(CipherMode.ENCRYPT, salt);
|
||||
result = cipher.processFinal(paddingDataWithZero(data, cipher.getBlockSize()));
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
@ -320,8 +322,8 @@ public class SymmetricCrypto implements SymmetricEncryptor, SymmetricDecryptor,
|
||||
CipherOutputStream cipherOutputStream = null;
|
||||
lock.lock();
|
||||
try {
|
||||
final Cipher cipher = initMode(Cipher.ENCRYPT_MODE, null);
|
||||
cipherOutputStream = new CipherOutputStream(out, cipher);
|
||||
final JceCipher cipher = initMode(CipherMode.ENCRYPT, null);
|
||||
cipherOutputStream = new CipherOutputStream(out, cipher.getRaw());
|
||||
final long length = IoUtil.copy(data, cipherOutputStream);
|
||||
if (this.isZeroPadding) {
|
||||
final int blockSize = cipher.getBlockSize();
|
||||
@ -359,9 +361,9 @@ public class SymmetricCrypto implements SymmetricEncryptor, SymmetricDecryptor,
|
||||
lock.lock();
|
||||
try {
|
||||
final byte[] salt = SaltMagic.getSalt(bytes);
|
||||
final Cipher cipher = initMode(Cipher.DECRYPT_MODE, salt);
|
||||
final JceCipher cipher = initMode(CipherMode.DECRYPT, salt);
|
||||
blockSize = cipher.getBlockSize();
|
||||
decryptData = cipher.doFinal(SaltMagic.getData(bytes));
|
||||
decryptData = cipher.processFinal(SaltMagic.getData(bytes));
|
||||
} catch (final Exception e) {
|
||||
throw new CryptoException(e);
|
||||
} finally {
|
||||
@ -376,8 +378,8 @@ public class SymmetricCrypto implements SymmetricEncryptor, SymmetricDecryptor,
|
||||
CipherInputStream cipherInputStream = null;
|
||||
lock.lock();
|
||||
try {
|
||||
final Cipher cipher = initMode(Cipher.DECRYPT_MODE, null);
|
||||
cipherInputStream = new CipherInputStream(data, cipher);
|
||||
final JceCipher cipher = initMode(CipherMode.DECRYPT, null);
|
||||
cipherInputStream = new CipherInputStream(data, cipher.getRaw());
|
||||
if (this.isZeroPadding) {
|
||||
final int blockSize = cipher.getBlockSize();
|
||||
if (blockSize > 0) {
|
||||
@ -417,8 +419,8 @@ public class SymmetricCrypto implements SymmetricEncryptor, SymmetricDecryptor,
|
||||
*/
|
||||
private SymmetricCrypto initParams(final String algorithm, AlgorithmParameterSpec paramsSpec) {
|
||||
if (null == paramsSpec) {
|
||||
byte[] iv = Opt.ofNullable(cipherWrapper)
|
||||
.map(CipherWrapper::getRaw).map(Cipher::getIV).get();
|
||||
byte[] iv = Opt.ofNullable(cipher)
|
||||
.map(JceCipher::getRaw).map(Cipher::getIV).get();
|
||||
|
||||
// 随机IV
|
||||
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}
|
||||
* @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;
|
||||
if (null != salt) {
|
||||
// /issues#I6YWWD,提供OpenSSL格式兼容支持
|
||||
@ -454,11 +454,15 @@ public class SymmetricCrypto implements SymmetricEncryptor, SymmetricDecryptor,
|
||||
final byte[][] keyAndIV = OpenSSLSaltParser.ofMd5(32, algorithm)
|
||||
.getKeyAndIV(secretKey.getEncoded(), salt);
|
||||
secretKey = KeyUtil.generateKey(algorithm, keyAndIV[0]);
|
||||
if(ArrayUtil.isNotEmpty(keyAndIV[1])){
|
||||
this.cipherWrapper.setParams(new IvParameterSpec(keyAndIV[1]));
|
||||
if (ArrayUtil.isNotEmpty(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());
|
||||
|
||||
// 加密为16进制表示
|
||||
aes.setMode(CipherMode.encrypt);
|
||||
aes.setMode(CipherMode.ENCRYPT);
|
||||
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));
|
||||
Assertions.assertEquals(randomData2, randomData);
|
||||
Assertions.assertEquals(randomData, "cd0e3a249eaf0ed80c330338508898c4");
|
||||
|
@ -28,7 +28,7 @@
|
||||
### SQL相关工具(sql)
|
||||
提供SQL相关功能,包括SQL变量替换(NamedSql),通过对象完成SQL构建(SqlBuilder)等。
|
||||
|
||||
`SqlSqlExecutor`提供SQL执行的静态方法。
|
||||
`SqlExecutor`提供SQL执行的静态方法。
|
||||
|
||||
### 数据库元信息(meta)
|
||||
通过`MetaUtil`提供数据库表、字段等信息的读取操作。
|
||||
|
@ -21,7 +21,7 @@
|
||||
<parent>
|
||||
<groupId>org.dromara.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>6.0.0-M10</version>
|
||||
<version>6.0.0-M11</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-db</artifactId>
|
||||
@ -38,7 +38,7 @@
|
||||
<druid.version>1.2.21</druid.version>
|
||||
<!-- 固定4.x -->
|
||||
<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 -->
|
||||
<hsqldb.version>2.5.2</hsqldb.version>
|
||||
</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.
|
||||
* 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:
|
||||
@ -12,14 +12,15 @@
|
||||
|
||||
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.lang.Assert;
|
||||
import org.dromara.hutool.db.handler.ResultSetUtil;
|
||||
import org.dromara.hutool.db.handler.RsHandler;
|
||||
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.StatementWrapper;
|
||||
import org.dromara.hutool.db.sql.filter.SqlLogFilter;
|
||||
|
||||
import java.sql.*;
|
||||
import java.util.Collection;
|
||||
@ -87,7 +88,7 @@ public class StatementUtil {
|
||||
return StatementBuilder.of()
|
||||
.setConnection(conn)
|
||||
.setReturnGeneratedKey(returnGeneratedKey)
|
||||
.setSqlLog(SqlLog.INSTANCE)
|
||||
.setSqlFilter(SqlLogFilter.INSTANCE)
|
||||
.setSql(sql)
|
||||
.setParams(params)
|
||||
.build();
|
||||
@ -120,29 +121,10 @@ public class StatementUtil {
|
||||
return StatementBuilder.of()
|
||||
.setConnection(conn)
|
||||
.setReturnGeneratedKey(false)
|
||||
.setSqlLog(SqlLog.INSTANCE)
|
||||
.setSqlFilter(SqlLogFilter.INSTANCE)
|
||||
.setSql(sql)
|
||||
.buildForBatch(paramsBatch);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建批量操作的{@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);
|
||||
.setParams(ArrayUtil.ofArray(paramsBatch, Object.class))
|
||||
.buildForBatch();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -158,7 +140,7 @@ public class StatementUtil {
|
||||
public static CallableStatement prepareCall(final Connection conn, final String sql, final Object... params) throws SQLException {
|
||||
return StatementBuilder.of()
|
||||
.setConnection(conn)
|
||||
.setSqlLog(SqlLog.INSTANCE)
|
||||
.setSqlFilter(SqlLogFilter.INSTANCE)
|
||||
.setSql(sql)
|
||||
.setParams(params)
|
||||
.buildForCall();
|
||||
|
@ -53,21 +53,20 @@ public class AnsiSqlDialect implements Dialect {
|
||||
}
|
||||
|
||||
@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());
|
||||
|
||||
return StatementUtil.prepareStatement(conn, insert);
|
||||
}
|
||||
|
||||
@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)) {
|
||||
throw new DbRuntimeException("Entities for batch insert is empty !");
|
||||
}
|
||||
// 批量,根据第一行数据结构生成SQL占位符
|
||||
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(), fields, entities);
|
||||
return StatementUtil.prepareStatementForBatch(conn, insert.build(), entities);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -100,12 +99,12 @@ public class AnsiSqlDialect implements Dialect {
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
|
||||
@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 !");
|
||||
if (ArrayUtil.hasBlank(query.getTableNames())) {
|
||||
throw new DbRuntimeException("Table name must be not empty !");
|
||||
@ -116,7 +115,7 @@ public class AnsiSqlDialect implements Dialect {
|
||||
}
|
||||
|
||||
@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语句基础上包装其分页的语句
|
||||
if (null != page) {
|
||||
sqlBuilder = wrapPageSql(sqlBuilder.orderBy(page.getOrders()), page);
|
||||
|
@ -12,6 +12,7 @@
|
||||
|
||||
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.db.Page;
|
||||
import org.dromara.hutool.db.dialect.DialectName;
|
||||
@ -25,6 +26,10 @@ import org.dromara.hutool.db.sql.SqlBuilder;
|
||||
public class OracleDialect extends AnsiSqlDialect {
|
||||
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`结尾
|
||||
*
|
||||
@ -36,6 +41,9 @@ public class OracleDialect extends AnsiSqlDialect {
|
||||
return (value instanceof CharSequence) && StrUtil.endWithIgnoreCase(value.toString(), ".nextval");
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*/
|
||||
public OracleDialect() {
|
||||
//Oracle所有字段名用双引号包围,防止字段名或表名与系统关键字冲突
|
||||
//wrapper = new Wrapper('"');
|
||||
@ -44,11 +52,27 @@ public class OracleDialect extends AnsiSqlDialect {
|
||||
@Override
|
||||
protected SqlBuilder wrapPageSql(final SqlBuilder find, final Page page) {
|
||||
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
|
||||
.insertPreFragment("SELECT * FROM ( SELECT row_.*, rownum rownum_ from ( ")
|
||||
.append(" ) row_ where rownum <= ").append(startEnd[1])//
|
||||
.append(") table_alias_")//
|
||||
.append(" where table_alias_.rownum_ > ").append(startEnd[0]);//
|
||||
.insertPreFragment("SELECT * FROM ( SELECT " + rowAlias + ".*, rownum " + rownumAlias + " from ( ")
|
||||
.append(" ) row_ where rownum <= ").append(startEnd[1])//
|
||||
.append(") ").append(tableAlias)//
|
||||
.append(" where ").append(tableAlias).append(".rownum_ > ").append(startEnd[0]);//
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -29,6 +29,9 @@ import java.sql.SQLException;
|
||||
public class PhoenixDialect extends AnsiSqlDialect {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*/
|
||||
public PhoenixDialect() {
|
||||
// wrapper = new Wrapper('"');
|
||||
}
|
||||
|
@ -21,15 +21,17 @@ import org.dromara.hutool.db.sql.QuoteWrapper;
|
||||
/**
|
||||
* SQLServer2012 方言
|
||||
*
|
||||
* @author loolly
|
||||
*
|
||||
* @author Looly
|
||||
*/
|
||||
public class SqlServer2012Dialect extends AnsiSqlDialect {
|
||||
private static final long serialVersionUID = -37598166015777797L;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*/
|
||||
public SqlServer2012Dialect() {
|
||||
//双引号和中括号适用,双引号更广泛
|
||||
quoteWrapper = new QuoteWrapper('"');
|
||||
quoteWrapper = new QuoteWrapper('"');
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -39,10 +41,10 @@ public class SqlServer2012Dialect extends AnsiSqlDialect {
|
||||
find.append(" order by current_timestamp");
|
||||
}
|
||||
return find.append(" offset ")
|
||||
.append(page.getStartPosition())//
|
||||
.append(" row fetch next ")//row和rows同义词
|
||||
.append(page.getPageSize())//
|
||||
.append(" row only");//
|
||||
.append(page.getStartPosition())//
|
||||
.append(" row fetch next ")//row和rows同义词
|
||||
.append(page.getPageSize())//
|
||||
.append(" row only");//
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -17,12 +17,15 @@ import org.dromara.hutool.db.sql.QuoteWrapper;
|
||||
|
||||
/**
|
||||
* SqlLite3方言
|
||||
* @author loolly
|
||||
*
|
||||
* @author Looly
|
||||
*/
|
||||
public class Sqlite3Dialect extends AnsiSqlDialect{
|
||||
public class Sqlite3Dialect extends AnsiSqlDialect {
|
||||
private static final long serialVersionUID = -3527642408849291634L;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*/
|
||||
public Sqlite3Dialect() {
|
||||
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.text.StrUtil;
|
||||
import org.dromara.hutool.db.DbRuntimeException;
|
||||
import org.dromara.hutool.db.DbUtil;
|
||||
import org.dromara.hutool.db.GlobalDbConfig;
|
||||
import org.dromara.hutool.db.driver.DriverUtil;
|
||||
import org.dromara.hutool.log.LogUtil;
|
||||
@ -89,6 +90,7 @@ public class DSPool implements Closeable {
|
||||
*/
|
||||
public DSPool(final Setting setting, final DSFactory factory) {
|
||||
this.setting = null != setting ? setting : GlobalDbConfig.createDbSetting();
|
||||
DbUtil.setShowSqlGlobal(this.setting);
|
||||
this.factory = null != factory ? factory : SpiUtil.loadFirstAvailable(DSFactory.class);
|
||||
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.io.IoUtil;
|
||||
import org.dromara.hutool.core.lang.wrapper.Wrapper;
|
||||
import org.dromara.hutool.core.lang.wrapper.SimpleWrapper;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.io.Closeable;
|
||||
@ -34,9 +34,8 @@ import java.util.logging.Logger;
|
||||
* @author looly
|
||||
* @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;
|
||||
|
||||
/**
|
||||
@ -57,7 +56,7 @@ public class DSWrapper implements Wrapper<DataSource>, DataSource, Closeable, Cl
|
||||
* @param driver 数据库驱动类名
|
||||
*/
|
||||
public DSWrapper(final DataSource ds, final String driver) {
|
||||
this.ds = ds;
|
||||
super(ds);
|
||||
this.driver = driver;
|
||||
}
|
||||
|
||||
@ -70,65 +69,56 @@ public class DSWrapper implements Wrapper<DataSource>, DataSource, Closeable, Cl
|
||||
return this.driver;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取原始的数据源
|
||||
*
|
||||
* @return 原始数据源
|
||||
*/
|
||||
@Override
|
||||
public DataSource getRaw() {
|
||||
return this.ds;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PrintWriter getLogWriter() throws SQLException {
|
||||
return ds.getLogWriter();
|
||||
return this.raw.getLogWriter();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLogWriter(final PrintWriter out) throws SQLException {
|
||||
ds.setLogWriter(out);
|
||||
this.raw.setLogWriter(out);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLoginTimeout(final int seconds) throws SQLException {
|
||||
ds.setLoginTimeout(seconds);
|
||||
this.raw.setLoginTimeout(seconds);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLoginTimeout() throws SQLException {
|
||||
return ds.getLoginTimeout();
|
||||
return this.raw.getLoginTimeout();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Logger getParentLogger() throws SQLFeatureNotSupportedException {
|
||||
return ds.getParentLogger();
|
||||
return this.raw.getParentLogger();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T unwrap(final Class<T> iface) throws SQLException {
|
||||
return ds.unwrap(iface);
|
||||
return this.raw.unwrap(iface);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isWrapperFor(final Class<?> iface) throws SQLException {
|
||||
return ds.isWrapperFor(iface);
|
||||
return this.raw.isWrapperFor(iface);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Connection getConnection() throws SQLException {
|
||||
return ds.getConnection();
|
||||
return this.raw.getConnection();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Connection getConnection(final String username, final String password) throws SQLException {
|
||||
return ds.getConnection(username, password);
|
||||
return this.raw.getConnection(username, password);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
if (this.ds instanceof AutoCloseable) {
|
||||
IoUtil.closeQuietly((AutoCloseable) this.ds);
|
||||
final DataSource ds = this.raw;
|
||||
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;
|
||||
|
||||
import org.dromara.hutool.core.array.ArrayUtil;
|
||||
import org.dromara.hutool.core.map.MapUtil;
|
||||
import org.dromara.hutool.core.text.StrUtil;
|
||||
import org.dromara.hutool.core.array.ArrayUtil;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
@ -33,12 +31,12 @@ import java.util.Map;
|
||||
* @author looly
|
||||
* @since 4.0.10
|
||||
*/
|
||||
public class NamedSql {
|
||||
public class NamedSql extends BoundSql {
|
||||
|
||||
private static final char[] NAME_START_CHARS = {':', '@', '?'};
|
||||
|
||||
private String sql;
|
||||
private final List<Object> params;
|
||||
private final String namedSql;
|
||||
private final Map<String, Object> paramMap;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
@ -47,35 +45,27 @@ public class NamedSql {
|
||||
* @param paramMap 名和参数的对应Map
|
||||
*/
|
||||
public NamedSql(final String namedSql, final Map<String, Object> paramMap) {
|
||||
this.params = new LinkedList<>();
|
||||
this.namedSql = namedSql;
|
||||
this.paramMap = paramMap;
|
||||
parse(namedSql, paramMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取SQL
|
||||
* 获取原始地带名称占位符的SQL语句
|
||||
*
|
||||
* @return SQL
|
||||
* @return 名称占位符的SQL
|
||||
*/
|
||||
public String getSql() {
|
||||
return this.sql;
|
||||
public String getNamedSql() {
|
||||
return namedSql;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取参数列表,按照占位符顺序
|
||||
* 获取原始参数名和参数值对应关系参数表
|
||||
*
|
||||
* @return 参数数组
|
||||
* @return 参数名和参数值对应关系参数表
|
||||
*/
|
||||
public Object[] getParams() {
|
||||
return this.params.toArray(new Object[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取参数列表,按照占位符顺序
|
||||
*
|
||||
* @return 参数列表
|
||||
*/
|
||||
public List<Object> getParamList() {
|
||||
return this.params;
|
||||
public Map<String, Object> getParamMap() {
|
||||
return paramMap;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -86,7 +76,7 @@ public class NamedSql {
|
||||
*/
|
||||
private void parse(final String namedSql, final Map<String, Object> paramMap) {
|
||||
if (MapUtil.isEmpty(paramMap)) {
|
||||
this.sql = namedSql;
|
||||
setSql(namedSql);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -124,7 +114,7 @@ public class NamedSql {
|
||||
replaceVar(nameStartChar, name, sqlBuilder, paramMap);
|
||||
}
|
||||
|
||||
this.sql = sqlBuilder.toString();
|
||||
setSql(sqlBuilder.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -163,11 +153,11 @@ public class NamedSql {
|
||||
sqlBuilder.append(',');
|
||||
}
|
||||
sqlBuilder.append('?');
|
||||
this.params.add(ArrayUtil.get(paramValue, i));
|
||||
addParam(ArrayUtil.get(paramValue, i));
|
||||
}
|
||||
} else {
|
||||
sqlBuilder.append('?');
|
||||
this.params.add(paramValue);
|
||||
addParam(paramValue);
|
||||
}
|
||||
} 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 {
|
||||
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 {
|
||||
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 {
|
||||
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;
|
||||
|
||||
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.convert.Convert;
|
||||
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.db.DbRuntimeException;
|
||||
import org.dromara.hutool.db.Entity;
|
||||
import org.dromara.hutool.db.sql.filter.SqlFilter;
|
||||
|
||||
import java.sql.*;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* {@link PreparedStatement}构建器,构建结果为{@link StatementWrapper}
|
||||
@ -44,20 +48,19 @@ public class StatementBuilder implements Builder<StatementWrapper> {
|
||||
return new StatementBuilder();
|
||||
}
|
||||
|
||||
private SqlLog sqlLog;
|
||||
private final BoundSql boundSql = new BoundSql();
|
||||
private Connection connection;
|
||||
private String sql;
|
||||
private Object[] params;
|
||||
private boolean returnGeneratedKey = true;
|
||||
private SqlFilter sqlFilter;
|
||||
|
||||
/**
|
||||
* 设置SQL日志
|
||||
*
|
||||
* @param sqlLog {@link SqlLog}
|
||||
* @param sqlFilter {@link SqlFilter}
|
||||
* @return this
|
||||
*/
|
||||
public StatementBuilder setSqlLog(final SqlLog sqlLog) {
|
||||
this.sqlLog = sqlLog;
|
||||
public StatementBuilder setSqlFilter(final SqlFilter sqlFilter) {
|
||||
this.sqlFilter = sqlFilter;
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -79,7 +82,7 @@ public class StatementBuilder implements Builder<StatementWrapper> {
|
||||
* @return this
|
||||
*/
|
||||
public StatementBuilder setSql(final String sql) {
|
||||
this.sql = StrUtil.trim(sql);
|
||||
this.boundSql.setSql(sql);
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -90,7 +93,7 @@ public class StatementBuilder implements Builder<StatementWrapper> {
|
||||
* @return this
|
||||
*/
|
||||
public StatementBuilder setParams(final Object... params) {
|
||||
this.params = params;
|
||||
this.boundSql.setParams(ListUtil.of(params));
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -105,6 +108,11 @@ public class StatementBuilder implements Builder<StatementWrapper> {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建{@link StatementWrapper}
|
||||
*
|
||||
* @return {@link StatementWrapper},{@code null}表示不执行
|
||||
*/
|
||||
@Override
|
||||
public StatementWrapper build() {
|
||||
try {
|
||||
@ -117,48 +125,37 @@ public class StatementBuilder implements Builder<StatementWrapper> {
|
||||
/**
|
||||
* 创建批量操作的{@link StatementWrapper}
|
||||
*
|
||||
* @param paramsBatch "?"对应参数批次列表
|
||||
* @return {@link StatementWrapper}
|
||||
* @return {@link StatementWrapper},{@code null}表示不执行
|
||||
* @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!");
|
||||
final List<Object> paramsBatch = this.boundSql.getParams();
|
||||
|
||||
sqlLog.log(sql, paramsBatch);
|
||||
sqlFilter.filter(this.connection, this.boundSql, this.returnGeneratedKey);
|
||||
|
||||
final StatementWrapper ps;
|
||||
try {
|
||||
ps = StatementWrapper.of(connection.prepareStatement(sql));
|
||||
final Map<Integer, Integer> nullTypeMap = new HashMap<>();
|
||||
for (final Object[] params : paramsBatch) {
|
||||
ps.fillParams(new ArrayIter<>(params), nullTypeMap);
|
||||
ps.addBatch();
|
||||
}
|
||||
} catch (final SQLException e) {
|
||||
throw new DbRuntimeException(e);
|
||||
}
|
||||
return ps;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建批量操作的{@link StatementWrapper}
|
||||
*
|
||||
* @param fields 字段列表,用于获取对应值
|
||||
* @param entities "?"对应参数批次列表
|
||||
* @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);
|
||||
Set<String> keys = null;
|
||||
for (final Object params : paramsBatch) {
|
||||
if (null == params) {
|
||||
continue;
|
||||
}
|
||||
if (ArrayUtil.isArray(params)) {
|
||||
ps.fillParams(new ArrayIter<>(params), nullTypeMap);
|
||||
} else if (params instanceof Entity) {
|
||||
final Entity entity = (Entity) params;
|
||||
// 对于多Entity批量插入的情况,为防止数据不对齐,故按照首行提供键值对筛选。
|
||||
if(null == keys){
|
||||
keys = entity.keySet();
|
||||
ps.fillParams(entity.values(), nullTypeMap);
|
||||
} else{
|
||||
ps.fillParams(MapUtil.valuesOfKeys(entity, keys), nullTypeMap);
|
||||
}
|
||||
}
|
||||
ps.addBatch();
|
||||
}
|
||||
} catch (final SQLException e) {
|
||||
@ -170,12 +167,15 @@ public class StatementBuilder implements Builder<StatementWrapper> {
|
||||
/**
|
||||
* 创建存储过程或函数调用的{@link StatementWrapper}
|
||||
*
|
||||
* @return StatementWrapper
|
||||
* @return StatementWrapper,{@code null}表示不执行
|
||||
* @since 6.0.0
|
||||
*/
|
||||
public CallableStatement buildForCall() {
|
||||
final String sql = this.boundSql.getSql();
|
||||
final Object[] params = this.boundSql.getParamArray();
|
||||
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 {
|
||||
return (CallableStatement) StatementWrapper
|
||||
@ -190,20 +190,23 @@ public class StatementBuilder implements Builder<StatementWrapper> {
|
||||
/**
|
||||
* 构建{@link StatementWrapper}
|
||||
*
|
||||
* @return {@link StatementWrapper}
|
||||
* @return {@link StatementWrapper},{@code null}表示不执行
|
||||
* @throws SQLException SQL异常
|
||||
*/
|
||||
private StatementWrapper _build() throws SQLException {
|
||||
String sql = this.boundSql.getSql();
|
||||
Object[] params = this.boundSql.getParamArray();
|
||||
Assert.notBlank(sql, "Sql String must be not blank!");
|
||||
|
||||
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();
|
||||
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;
|
||||
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);
|
||||
//未指定参数原样输出
|
||||
Assertions.assertEquals("select * from table where id=@id and name = ? and nickName = ?", namedSql.getSql());
|
||||
Assertions.assertEquals("张三", namedSql.getParams()[0]);
|
||||
Assertions.assertEquals("小豆豆", namedSql.getParams()[1]);
|
||||
Assertions.assertEquals("张三", namedSql.getParamArray()[0]);
|
||||
Assertions.assertEquals("小豆豆", namedSql.getParamArray()[1]);
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -54,9 +54,9 @@ public class NamedSqlTest {
|
||||
final NamedSql namedSql = new NamedSql(sql, paramMap);
|
||||
Assertions.assertEquals("select * from table where id=? and name = ? and nickName = ?", namedSql.getSql());
|
||||
//指定了null参数的依旧替换,参数值为null
|
||||
Assertions.assertNull(namedSql.getParams()[0]);
|
||||
Assertions.assertEquals("张三", namedSql.getParams()[1]);
|
||||
Assertions.assertEquals("小豆豆", namedSql.getParams()[2]);
|
||||
Assertions.assertNull(namedSql.getParamArray()[0]);
|
||||
Assertions.assertEquals("张三", namedSql.getParamArray()[1]);
|
||||
Assertions.assertEquals("小豆豆", namedSql.getParamArray()[2]);
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -92,9 +92,9 @@ public class NamedSqlTest {
|
||||
|
||||
final NamedSql namedSql = new NamedSql(sql, paramMap);
|
||||
Assertions.assertEquals("select * from user where id in (?,?,?)", namedSql.getSql());
|
||||
Assertions.assertEquals(1, namedSql.getParams()[0]);
|
||||
Assertions.assertEquals(2, namedSql.getParams()[1]);
|
||||
Assertions.assertEquals(3, namedSql.getParams()[2]);
|
||||
Assertions.assertEquals(1, namedSql.getParamArray()[0]);
|
||||
Assertions.assertEquals(2, namedSql.getParamArray()[1]);
|
||||
Assertions.assertEquals(3, namedSql.getParamArray()[2]);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -21,7 +21,7 @@
|
||||
<parent>
|
||||
<groupId>org.dromara.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>6.0.0-M10</version>
|
||||
<version>6.0.0-M11</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-extra</artifactId>
|
||||
@ -32,14 +32,14 @@
|
||||
<Automatic-Module-Name>org.dromara.hutool.extra</Automatic-Module-Name>
|
||||
<!-- versions -->
|
||||
<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>
|
||||
<freemarker.version>2.3.32</freemarker.version>
|
||||
<enjoy.version>5.1.3</enjoy.version>
|
||||
<thymeleaf.version>3.1.2.RELEASE</thymeleaf.version>
|
||||
<mail.version>1.6.2</mail.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>
|
||||
<net.version>3.9.0</net.version>
|
||||
<emoji-java.version>5.1.1</emoji-java.version>
|
||||
@ -204,6 +204,14 @@
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
<groupId>org.slf4j</groupId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<artifactId>bcpkix-jdk18on</artifactId>
|
||||
<groupId>org.bouncycastle</groupId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<artifactId>bcprov-jdk18on</artifactId>
|
||||
<groupId>org.bouncycastle</groupId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
@ -407,7 +415,7 @@
|
||||
<dependency>
|
||||
<groupId>com.rnkrsoft.bopomofo4j</groupId>
|
||||
<artifactId>bopomofo4j</artifactId>
|
||||
<version>1.0.0</version>
|
||||
<version>1.0.1</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
@ -488,7 +496,7 @@
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-compress</artifactId>
|
||||
<version>1.24.0</version>
|
||||
<version>1.25.0</version>
|
||||
<scope>compile</scope>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
@ -496,7 +504,7 @@
|
||||
<dependency>
|
||||
<groupId>com.github.oshi</groupId>
|
||||
<artifactId>oshi-core</artifactId>
|
||||
<version>6.4.5</version>
|
||||
<version>6.4.10</version>
|
||||
<scope>provided</scope>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
|
@ -31,8 +31,11 @@ public class UserInfo implements Serializable{
|
||||
private final String USER_LANGUAGE;
|
||||
private final String USER_COUNTRY;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*/
|
||||
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_DIR = fixPath(SystemUtil.get("user.dir", false));
|
||||
JAVA_IO_TMPDIR = fixPath(SystemUtil.get("java.io.tmpdir", false));
|
||||
|
@ -21,7 +21,7 @@
|
||||
<parent>
|
||||
<groupId>org.dromara.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>6.0.0-M10</version>
|
||||
<version>6.0.0-M11</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-http</artifactId>
|
||||
|
@ -21,7 +21,7 @@
|
||||
<parent>
|
||||
<groupId>org.dromara.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>6.0.0-M10</version>
|
||||
<version>6.0.0-M11</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-json</artifactId>
|
||||
@ -31,7 +31,7 @@
|
||||
<properties>
|
||||
<Automatic-Module-Name>org.dromara.hutool.json</Automatic-Module-Name>
|
||||
<!-- versions -->
|
||||
<bouncycastle.version>1.76</bouncycastle.version>
|
||||
<bouncycastle.version>1.77</bouncycastle.version>
|
||||
<jjwt.version>0.12.3</jjwt.version>
|
||||
</properties>
|
||||
|
||||
|
@ -29,6 +29,7 @@ import org.dromara.hutool.json.JSONTokener;
|
||||
import org.dromara.hutool.json.xml.JSONXMLUtil;
|
||||
import org.dromara.hutool.json.serialize.GlobalSerializeMapping;
|
||||
import org.dromara.hutool.json.serialize.JSONSerializer;
|
||||
import org.dromara.hutool.json.xml.ParseConfig;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.Reader;
|
||||
@ -170,7 +171,7 @@ public class JSONObjectMapper {
|
||||
final String jsonStr = StrUtil.trim(source);
|
||||
if (StrUtil.startWith(jsonStr, '<')) {
|
||||
// 可能为XML
|
||||
JSONXMLUtil.toJSONObject(jsonObject, jsonStr, false);
|
||||
JSONXMLUtil.toJSONObject(jsonStr, jsonObject, ParseConfig.of());
|
||||
return;
|
||||
}
|
||||
mapFromTokener(new JSONTokener(StrUtil.trim(source), jsonObject.config()), jsonObject);
|
||||
|
@ -12,7 +12,9 @@
|
||||
|
||||
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.xml.XmlConstants;
|
||||
import org.dromara.hutool.json.JSONException;
|
||||
import org.dromara.hutool.json.JSONObject;
|
||||
import org.dromara.hutool.json.mapper.JSONValueMapper;
|
||||
@ -29,28 +31,31 @@ public class JSONXMLParser {
|
||||
* 转换XML为JSONObject
|
||||
* 转换过程中一些信息可能会丢失,JSON中无法区分节点和属性,相同的节点将被处理为JSONArray。
|
||||
*
|
||||
* @param jo JSONObject
|
||||
* @param xmlStr XML字符串
|
||||
* @param keepStrings 如果为{@code true},则值保持String类型,不转换为数字或boolean
|
||||
* @param jo JSONObject
|
||||
* @param parseConfig 解析选项
|
||||
* @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());
|
||||
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 context The JSONObject that will include the new material.
|
||||
* @param name The tag name.
|
||||
* @return true if the close tag is processed.
|
||||
* @param x {@link XMLTokener}
|
||||
* @param context {@link JSONObject}
|
||||
* @param name 标签名,null表示从根标签开始解析
|
||||
* @param parseConfig 解析选项
|
||||
* @param currentNestingDepth 当前层级
|
||||
* @return {@code true}表示解析完成
|
||||
* @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;
|
||||
int i;
|
||||
final JSONObject jsonobject;
|
||||
@ -60,7 +65,7 @@ public class JSONXMLParser {
|
||||
|
||||
token = x.nextToken();
|
||||
|
||||
if (token == JSONXMLUtil.BANG) {
|
||||
if (token == XmlConstants.C_BANG) {
|
||||
c = x.next();
|
||||
if (c == '-') {
|
||||
if (x.next() == '-') {
|
||||
@ -86,19 +91,19 @@ public class JSONXMLParser {
|
||||
token = x.nextMeta();
|
||||
if (token == null) {
|
||||
throw x.syntaxError("Missing '>' after '<!'.");
|
||||
} else if (token == JSONXMLUtil.LT) {
|
||||
} else if (token == XmlConstants.C_LT) {
|
||||
i += 1;
|
||||
} else if (token == JSONXMLUtil.GT) {
|
||||
} else if (token == XmlConstants.C_GT) {
|
||||
i -= 1;
|
||||
}
|
||||
} while (i > 0);
|
||||
return false;
|
||||
} else if (token == JSONXMLUtil.QUEST) {
|
||||
} else if (token == XmlConstants.C_QUEST) {
|
||||
|
||||
// <?
|
||||
x.skipPast("?>");
|
||||
return false;
|
||||
} else if (token == JSONXMLUtil.SLASH) {
|
||||
} else if (token == Character.valueOf(CharUtil.SLASH)) {
|
||||
|
||||
// Close tag </
|
||||
|
||||
@ -109,7 +114,7 @@ public class JSONXMLParser {
|
||||
if (!token.equals(name)) {
|
||||
throw x.syntaxError("Mismatched " + name + " and " + token);
|
||||
}
|
||||
if (x.nextToken() != JSONXMLUtil.GT) {
|
||||
if (x.nextToken() != XmlConstants.C_GT) {
|
||||
throw x.syntaxError("Misshaped close tag");
|
||||
}
|
||||
return true;
|
||||
@ -123,6 +128,7 @@ public class JSONXMLParser {
|
||||
tagName = (String) token;
|
||||
token = null;
|
||||
jsonobject = new JSONObject();
|
||||
final boolean keepStrings = parseConfig.isKeepStrings();
|
||||
for (; ; ) {
|
||||
if (token == null) {
|
||||
token = x.nextToken();
|
||||
@ -132,7 +138,7 @@ public class JSONXMLParser {
|
||||
if (token instanceof String) {
|
||||
string = (String) token;
|
||||
token = x.nextToken();
|
||||
if (token == JSONXMLUtil.EQ) {
|
||||
if (token == Character.valueOf(CharUtil.EQUAL)) {
|
||||
token = x.nextToken();
|
||||
if (!(token instanceof String)) {
|
||||
throw x.syntaxError("Missing value");
|
||||
@ -143,9 +149,9 @@ public class JSONXMLParser {
|
||||
jsonobject.append(string, "");
|
||||
}
|
||||
|
||||
} else if (token == JSONXMLUtil.SLASH) {
|
||||
} else if (token == Character.valueOf(CharUtil.SLASH)) {
|
||||
// Empty tag <.../>
|
||||
if (x.nextToken() != JSONXMLUtil.GT) {
|
||||
if (x.nextToken() != XmlConstants.C_GT) {
|
||||
throw x.syntaxError("Misshaped tag");
|
||||
}
|
||||
if (!jsonobject.isEmpty()) {
|
||||
@ -155,7 +161,7 @@ public class JSONXMLParser {
|
||||
}
|
||||
return false;
|
||||
|
||||
} else if (token == JSONXMLUtil.GT) {
|
||||
} else if (token == XmlConstants.C_GT) {
|
||||
// Content, between <...> and </...>
|
||||
for (; ; ) {
|
||||
token = x.nextContent();
|
||||
@ -170,9 +176,16 @@ public class JSONXMLParser {
|
||||
jsonobject.append("content", keepStrings ? token : JSONValueMapper.toJsonValue(string));
|
||||
}
|
||||
|
||||
} else if (token == JSONXMLUtil.LT) {
|
||||
} else if (token == XmlConstants.C_LT) {
|
||||
// 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()) {
|
||||
context.append(tagName, StrUtil.EMPTY);
|
||||
} else if (jsonobject.size() == 1 && jsonobject.get("content") != null) {
|
||||
|
@ -12,7 +12,6 @@
|
||||
|
||||
package org.dromara.hutool.json.xml;
|
||||
|
||||
import org.dromara.hutool.core.text.CharUtil;
|
||||
import org.dromara.hutool.json.JSONException;
|
||||
import org.dromara.hutool.json.JSONObject;
|
||||
|
||||
@ -25,51 +24,6 @@ import org.dromara.hutool.json.JSONObject;
|
||||
*/
|
||||
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
|
||||
* 转换过程中一些信息可能会丢失,JSON中无法区分节点和属性,相同的节点将被处理为JSONArray。
|
||||
@ -80,7 +34,7 @@ public class JSONXMLUtil {
|
||||
* @throws JSONException Thrown if there is an errors while parsing the string
|
||||
*/
|
||||
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.
|
||||
* 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 keepStrings If true, then values will not be coerced into boolean or numeric values and will instead be left as strings
|
||||
* @param string XML字符串
|
||||
* @param parseConfig XML解析选项
|
||||
* @return A JSONObject containing the structured data from the XML 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 {
|
||||
return toJSONObject(new JSONObject(), string, keepStrings);
|
||||
public static JSONObject toJSONObject(final String string, final ParseConfig parseConfig) throws JSONException {
|
||||
return toJSONObject(string, new JSONObject(), parseConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -104,13 +58,13 @@ public class JSONXMLUtil {
|
||||
*
|
||||
* @param jo JSONObject
|
||||
* @param xmlStr XML字符串
|
||||
* @param keepStrings 如果为{@code true},则值保持String类型,不转换为数字或boolean
|
||||
* @param parseConfig XML解析选项
|
||||
* @return A JSONObject 解析后的JSON对象,与传入的jo为同一对象
|
||||
* @throws JSONException 解析异常
|
||||
* @since 5.3.1
|
||||
*/
|
||||
public static JSONObject toJSONObject(final JSONObject jo, final String xmlStr, final boolean keepStrings) throws JSONException {
|
||||
JSONXMLParser.parseJSONObject(jo, xmlStr, keepStrings);
|
||||
public static JSONObject toJSONObject(final String xmlStr, final JSONObject jo, final ParseConfig parseConfig) throws JSONException {
|
||||
JSONXMLParser.parseJSONObject(xmlStr, jo, parseConfig);
|
||||
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;
|
||||
|
||||
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.JSONException;
|
||||
import org.dromara.hutool.json.JSONTokener;
|
||||
@ -31,11 +33,11 @@ public class XMLTokener extends JSONTokener {
|
||||
|
||||
static {
|
||||
entity = new java.util.HashMap<>(8);
|
||||
entity.put("amp", JSONXMLUtil.AMP);
|
||||
entity.put("apos", JSONXMLUtil.APOS);
|
||||
entity.put("gt", JSONXMLUtil.GT);
|
||||
entity.put("lt", JSONXMLUtil.LT);
|
||||
entity.put("quot", JSONXMLUtil.QUOT);
|
||||
entity.put("amp", XmlConstants.C_AMP);
|
||||
entity.put("apos", XmlConstants.C_APOS);
|
||||
entity.put("gt", XmlConstants.C_GT);
|
||||
entity.put("lt", XmlConstants.C_LT);
|
||||
entity.put("quot", CharUtil.DOUBLE_QUOTES);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -89,7 +91,7 @@ public class XMLTokener extends JSONTokener {
|
||||
return null;
|
||||
}
|
||||
if (c == '<') {
|
||||
return JSONXMLUtil.LT;
|
||||
return XmlConstants.C_LT;
|
||||
}
|
||||
sb = new StringBuilder();
|
||||
for (; ; ) {
|
||||
@ -175,17 +177,17 @@ public class XMLTokener extends JSONTokener {
|
||||
case 0:
|
||||
throw syntaxError("Misshaped meta tag");
|
||||
case '<':
|
||||
return JSONXMLUtil.LT;
|
||||
return XmlConstants.C_LT;
|
||||
case '>':
|
||||
return JSONXMLUtil.GT;
|
||||
return XmlConstants.C_GT;
|
||||
case '/':
|
||||
return JSONXMLUtil.SLASH;
|
||||
return CharUtil.SLASH;
|
||||
case '=':
|
||||
return JSONXMLUtil.EQ;
|
||||
return CharUtil.EQUAL;
|
||||
case '!':
|
||||
return JSONXMLUtil.BANG;
|
||||
return XmlConstants.C_BANG;
|
||||
case '?':
|
||||
return JSONXMLUtil.QUEST;
|
||||
return XmlConstants.C_QUEST;
|
||||
case '"':
|
||||
case '\'':
|
||||
q = c;
|
||||
@ -242,15 +244,15 @@ public class XMLTokener extends JSONTokener {
|
||||
case '<':
|
||||
throw syntaxError("Misplaced '<'");
|
||||
case '>':
|
||||
return JSONXMLUtil.GT;
|
||||
return XmlConstants.C_GT;
|
||||
case '/':
|
||||
return JSONXMLUtil.SLASH;
|
||||
return CharUtil.SLASH;
|
||||
case '=':
|
||||
return JSONXMLUtil.EQ;
|
||||
return CharUtil.EQUAL;
|
||||
case '!':
|
||||
return JSONXMLUtil.BANG;
|
||||
return XmlConstants.C_BANG;
|
||||
case '?':
|
||||
return JSONXMLUtil.QUEST;
|
||||
return XmlConstants.C_QUEST;
|
||||
|
||||
// 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>
|
||||
<groupId>org.dromara.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>6.0.0-M10</version>
|
||||
<version>6.0.0-M11</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-log</artifactId>
|
||||
@ -32,10 +32,10 @@
|
||||
<Automatic-Module-Name>org.dromara.hutool.log</Automatic-Module-Name>
|
||||
<!-- versions -->
|
||||
<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>
|
||||
<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>
|
||||
<tinylog2.version>2.6.2</tinylog2.version>
|
||||
<!-- 固定3.4.x,支持到jdk8 -->
|
||||
|
@ -21,7 +21,7 @@
|
||||
<parent>
|
||||
<groupId>org.dromara.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>6.0.0-M10</version>
|
||||
<version>6.0.0-M11</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-poi</artifactId>
|
||||
@ -55,6 +55,16 @@
|
||||
<artifactId>ofdrw-full</artifactId>
|
||||
<version>2.2.4</version>
|
||||
<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>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
@ -21,7 +21,7 @@
|
||||
<parent>
|
||||
<groupId>org.dromara.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>6.0.0-M10</version>
|
||||
<version>6.0.0-M11</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-setting</artifactId>
|
||||
|
@ -21,7 +21,7 @@
|
||||
<parent>
|
||||
<groupId>org.dromara.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>6.0.0-M10</version>
|
||||
<version>6.0.0-M11</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-socket</artifactId>
|
||||
|
@ -21,7 +21,7 @@
|
||||
<parent>
|
||||
<groupId>org.dromara.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>6.0.0-M10</version>
|
||||
<version>6.0.0-M11</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-swing</artifactId>
|
||||
|
@ -27,7 +27,7 @@ public class CaptchaUtil {
|
||||
* @param height 图片高
|
||||
* @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);
|
||||
}
|
||||
|
||||
@ -40,7 +40,7 @@ public class CaptchaUtil {
|
||||
* @param lineCount 干扰线条数
|
||||
* @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);
|
||||
}
|
||||
|
||||
@ -52,7 +52,7 @@ public class CaptchaUtil {
|
||||
* @return {@link CircleCaptcha}
|
||||
* @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);
|
||||
}
|
||||
|
||||
@ -66,7 +66,7 @@ public class CaptchaUtil {
|
||||
* @return {@link CircleCaptcha}
|
||||
* @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);
|
||||
}
|
||||
|
||||
@ -78,7 +78,7 @@ public class CaptchaUtil {
|
||||
* @return {@link ShearCaptcha}
|
||||
* @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);
|
||||
}
|
||||
|
||||
@ -92,7 +92,7 @@ public class CaptchaUtil {
|
||||
* @return {@link ShearCaptcha}
|
||||
* @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);
|
||||
}
|
||||
|
||||
@ -103,7 +103,7 @@ public class CaptchaUtil {
|
||||
* @param height 高
|
||||
* @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);
|
||||
}
|
||||
|
||||
@ -115,7 +115,7 @@ public class CaptchaUtil {
|
||||
* @param codeCount 字符个数
|
||||
* @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);
|
||||
}
|
||||
}
|
||||
|
@ -14,6 +14,8 @@ package org.dromara.hutool.swing.captcha;
|
||||
|
||||
import org.dromara.hutool.core.util.ObjUtil;
|
||||
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.GraphicsUtil;
|
||||
|
||||
@ -63,7 +65,19 @@ public class CircleCaptcha extends AbstractCaptcha {
|
||||
* @param 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
|
||||
|
@ -16,6 +16,8 @@ package org.dromara.hutool.swing.captcha;
|
||||
import org.dromara.hutool.core.util.ObjUtil;
|
||||
import org.dromara.hutool.core.util.RandomUtil;
|
||||
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.Color;
|
||||
@ -59,7 +61,29 @@ public class GifCaptcha extends AbstractCaptcha {
|
||||
* @param 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.setColor(fontColor[i]);
|
||||
g2d.drawOval(
|
||||
RandomUtil.randomInt(width),
|
||||
RandomUtil.randomInt(height),
|
||||
RandomUtil.randomInt(5, 30), 5 + RandomUtil.randomInt(5, 30)
|
||||
RandomUtil.randomInt(width),
|
||||
RandomUtil.randomInt(height),
|
||||
RandomUtil.randomInt(5, 30), 5 + RandomUtil.randomInt(5, 30)
|
||||
);//绘制椭圆边框
|
||||
g2d.drawString(words[i] + "", x + (font.getSize() + m) * i, y);
|
||||
}
|
||||
@ -223,8 +247,8 @@ public class GifCaptcha extends AbstractCaptcha {
|
||||
max = 255;
|
||||
}
|
||||
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.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.GraphicsUtil;
|
||||
|
||||
@ -53,7 +55,19 @@ public class LineCaptcha extends AbstractCaptcha {
|
||||
* @param 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
|
||||
|
||||
|
@ -14,6 +14,8 @@ package org.dromara.hutool.swing.captcha;
|
||||
|
||||
import org.dromara.hutool.core.util.ObjUtil;
|
||||
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.GraphicsUtil;
|
||||
|
||||
@ -63,7 +65,19 @@ public class ShearCaptcha extends AbstractCaptcha {
|
||||
* @param 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
|
||||
|
@ -30,7 +30,7 @@ public class CaptchaTest {
|
||||
@Test
|
||||
public void lineCaptchaTest1() {
|
||||
// 定义图形验证码的长和宽
|
||||
final LineCaptcha lineCaptcha = CaptchaUtil.createLineCaptcha(200, 100);
|
||||
final LineCaptcha lineCaptcha = CaptchaUtil.ofLineCaptcha(200, 100);
|
||||
Assertions.assertNotNull(lineCaptcha.getCode());
|
||||
Assertions.assertTrue(lineCaptcha.verify(lineCaptcha.getCode()));
|
||||
}
|
||||
@ -39,7 +39,7 @@ public class CaptchaTest {
|
||||
@Disabled
|
||||
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.write("f:/test/captcha/tellow.png");
|
||||
}
|
||||
@ -48,7 +48,7 @@ public class CaptchaTest {
|
||||
@Disabled
|
||||
public void lineCaptchaWithMathTest() {
|
||||
// 定义图形验证码的长和宽
|
||||
final LineCaptcha lineCaptcha = CaptchaUtil.createLineCaptcha(200, 80);
|
||||
final LineCaptcha lineCaptcha = CaptchaUtil.ofLineCaptcha(200, 80);
|
||||
lineCaptcha.setGenerator(new MathGenerator());
|
||||
lineCaptcha.setTextAlpha(0.8f);
|
||||
lineCaptcha.write("f:/captcha/math.png");
|
||||
@ -59,7 +59,7 @@ public class CaptchaTest {
|
||||
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.write("f:/captcha/line.png");
|
||||
@ -79,7 +79,7 @@ public class CaptchaTest {
|
||||
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);
|
||||
// 图形验证码写出,可以写出到文件,也可以写出到流
|
||||
captcha.write("f:/captcha/circle.png");
|
||||
@ -92,7 +92,7 @@ public class CaptchaTest {
|
||||
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);
|
||||
// 图形验证码写出,可以写出到文件,也可以写出到流
|
||||
captcha.write("f:/captcha/shear.png");
|
||||
@ -116,7 +116,7 @@ public class CaptchaTest {
|
||||
@Disabled
|
||||
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());
|
||||
// ShearCaptcha captcha = new ShearCaptcha(200, 100, 4, 4);
|
||||
// 图形验证码写出,可以写出到文件,也可以写出到流
|
||||
@ -128,7 +128,7 @@ public class CaptchaTest {
|
||||
@Test
|
||||
@Disabled
|
||||
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");
|
||||
assert captcha.verify(captcha.getCode());
|
||||
}
|
||||
@ -136,7 +136,7 @@ public class CaptchaTest {
|
||||
@Test
|
||||
@Disabled
|
||||
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.write("d:/test/test.jpg");
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ public class CaptchaUtilTest {
|
||||
@Disabled
|
||||
public void createTest() {
|
||||
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>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>6.0.0-M10</version>
|
||||
<version>6.0.0-M11</version>
|
||||
<name>hutool</name>
|
||||
<description>
|
||||
Hutool是一个功能丰富且易用的Java工具库,通过诸多实用工具类的使用,旨在帮助开发者快速、便捷地完成各类开发任务。这些封装的工具涵盖了字符串、数字、集合、编码、日期、文件、IO、加密、数据库JDBC、JSON、HTTP客户端等一系列操作,可以满足各种不同的开发需求。
|
||||
|
Loading…
x
Reference in New Issue
Block a user