Merge remote-tracking branch 'origin/v6-dev' into v6-dev

This commit is contained in:
VampireAchao 2024-01-14 13:07:49 +08:00
commit a907a439f9
88 changed files with 1579 additions and 688 deletions

View File

@ -3,7 +3,7 @@
------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------
# 6.0.0-M10 (2023-12-28) # 6.0.0-M11 (2024-01-11)
### 计划实现 ### 计划实现
* 【poi 】 Markdown相关如HTML转换等基于commonmark-java * 【poi 】 Markdown相关如HTML转换等基于commonmark-java

View File

@ -144,18 +144,18 @@ We provide the T-Shirt and Sweater with Hutool Logo, please visit the shop
<dependency> <dependency>
<groupId>org.dromara.hutool</groupId> <groupId>org.dromara.hutool</groupId>
<artifactId>hutool-all</artifactId> <artifactId>hutool-all</artifactId>
<version>6.0.0-M10</version> <version>6.0.0-M11</version>
</dependency> </dependency>
``` ```
### 🍐Gradle ### 🍐Gradle
``` ```
implementation 'org.dromara.hutool:hutool-all:6.0.0-M10' implementation 'org.dromara.hutool:hutool-all:6.0.0-M11'
``` ```
## 📥Download ## 📥Download
- [Maven Repo](https://repo1.maven.org/maven2/cn/hutool/hutool-all/6.0.0-M10/) - [Maven Repo](https://repo1.maven.org/maven2/cn/hutool/hutool-all/6.0.0-M11/)
> 🔔note: > 🔔note:
> Hutool 5.x supports JDK8+ and is not tested on Android platforms, and cannot guarantee that all tool classes or tool methods are available. > Hutool 5.x supports JDK8+ and is not tested on Android platforms, and cannot guarantee that all tool classes or tool methods are available.

View File

@ -139,21 +139,21 @@
<dependency> <dependency>
<groupId>org.dromara.hutool</groupId> <groupId>org.dromara.hutool</groupId>
<artifactId>hutool-all</artifactId> <artifactId>hutool-all</artifactId>
<version>6.0.0-M10</version> <version>6.0.0-M11</version>
</dependency> </dependency>
``` ```
### 🍐Gradle ### 🍐Gradle
``` ```
implementation 'org.dromara.hutool:hutool-all:6.0.0-M10' implementation 'org.dromara.hutool:hutool-all:6.0.0-M11'
``` ```
### 📥下载jar ### 📥下载jar
点击以下链接,下载`hutool-all-X.X.X.jar`即可: 点击以下链接,下载`hutool-all-X.X.X.jar`即可:
- [Maven中央库](https://repo1.maven.org/maven2/org/dromara/hutool/hutool-all/6.0.0-M10/) - [Maven中央库](https://repo1.maven.org/maven2/org/dromara/hutool/hutool-all/6.0.0-M11/)
> 🔔️注意 > 🔔️注意
> Hutool 6.x支持JDK8+对Android平台没有测试不能保证所有工具类或工具方法可用。 > Hutool 6.x支持JDK8+对Android平台没有测试不能保证所有工具类或工具方法可用。

View File

@ -1 +1 @@
6.0.0-M10 6.0.0-M11

View File

@ -1 +1 @@
var version = '6.0.0-M10' var version = '6.0.0-M11'

View File

@ -21,7 +21,7 @@
<parent> <parent>
<groupId>org.dromara.hutool</groupId> <groupId>org.dromara.hutool</groupId>
<artifactId>hutool-parent</artifactId> <artifactId>hutool-parent</artifactId>
<version>6.0.0-M10</version> <version>6.0.0-M11</version>
</parent> </parent>
<artifactId>hutool-all</artifactId> <artifactId>hutool-all</artifactId>

View File

@ -21,7 +21,7 @@
<parent> <parent>
<groupId>org.dromara.hutool</groupId> <groupId>org.dromara.hutool</groupId>
<artifactId>hutool-parent</artifactId> <artifactId>hutool-parent</artifactId>
<version>6.0.0-M10</version> <version>6.0.0-M11</version>
</parent> </parent>
<artifactId>hutool-bom</artifactId> <artifactId>hutool-bom</artifactId>

View File

@ -21,7 +21,7 @@
<parent> <parent>
<groupId>org.dromara.hutool</groupId> <groupId>org.dromara.hutool</groupId>
<artifactId>hutool-parent</artifactId> <artifactId>hutool-parent</artifactId>
<version>6.0.0-M10</version> <version>6.0.0-M11</version>
</parent> </parent>
<artifactId>hutool-core</artifactId> <artifactId>hutool-core</artifactId>

View File

@ -33,6 +33,7 @@ import org.dromara.hutool.core.util.ObjUtil;
import java.io.*; import java.io.*;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.FileSystem; import java.nio.file.FileSystem;
import java.nio.file.*; import java.nio.file.*;
import java.util.ArrayList; import java.util.ArrayList;
@ -726,7 +727,7 @@ public class ZipUtil {
* Gzip压缩处理 * Gzip压缩处理
* *
* @param content 被压缩的字符串 * @param content 被压缩的字符串
* @param charset 编码 * @param charset 编码 {@link StandardCharsets#UTF_8} {@link CharsetUtil#UTF_8}
* @return 压缩后的字节流 * @return 压缩后的字节流
* @throws HutoolException IO异常 * @throws HutoolException IO异常
*/ */

View File

@ -13,7 +13,8 @@
package org.dromara.hutool.core.lang.wrapper; package org.dromara.hutool.core.lang.wrapper;
/** /**
* 简单包装对象 * 简单包装对象<br>
* 通过继承此类可以直接使用被包装的对象用于简化和统一封装
* *
* @param <T> 被包装对象类型 * @param <T> 被包装对象类型
* @author looly * @author looly

View File

@ -297,6 +297,7 @@ public class NumberParser {
// issue#I79VS7 // issue#I79VS7
numberStr = StrUtil.subSuf(numberStr, 1); numberStr = StrUtil.subSuf(numberStr, 1);
} }
try { try {
final NumberFormat format = NumberFormat.getInstance(locale); final NumberFormat format = NumberFormat.getInstance(locale);
if (format instanceof DecimalFormat) { if (format instanceof DecimalFormat) {

View File

@ -898,12 +898,12 @@ public class NumberUtil extends NumberValidator {
} }
// FloatDouble等有精度问题转换为字符串后再转换 // FloatDouble等有精度问题转换为字符串后再转换
return toBigDecimal(number.toString()); return new BigDecimal(number.toString());
} }
/** /**
* 数字转{@link BigDecimal}<br> * 数字转{@link BigDecimal}<br>
* null或""空白符转换为0 * null或"""NaN"空白符转换为0
* *
* @param numberStr 数字字符串 * @param numberStr 数字字符串
* @return {@link BigDecimal} * @return {@link BigDecimal}
@ -927,7 +927,7 @@ public class NumberUtil extends NumberValidator {
/** /**
* 数字转{@link BigInteger}<br> * 数字转{@link BigInteger}<br>
* null转换为0 * null"NaN"转换为0
* *
* @param number 数字 * @param number 数字
* @return {@link BigInteger} * @return {@link BigInteger}

View File

@ -15,6 +15,7 @@ package org.dromara.hutool.core.text;
import org.dromara.hutool.core.array.ArrayUtil; import org.dromara.hutool.core.array.ArrayUtil;
import org.dromara.hutool.core.func.FunctionPool; import org.dromara.hutool.core.func.FunctionPool;
import org.dromara.hutool.core.text.placeholder.StrFormatter; import org.dromara.hutool.core.text.placeholder.StrFormatter;
import org.dromara.hutool.core.text.split.SplitUtil;
import org.dromara.hutool.core.util.CharsetUtil; import org.dromara.hutool.core.util.CharsetUtil;
import java.io.StringReader; import java.io.StringReader;
@ -27,6 +28,13 @@ import java.util.Map;
* 字符串工具类<br> * 字符串工具类<br>
* 此工具主要针对单个字符串的操作 * 此工具主要针对单个字符串的操作
* *
* <p>本工具类v6.x进行了拆分
* 字符串分割<strong>split</strong>参考{@link SplitUtil} &nbsp;&nbsp;<br>
* 多字符串判空<strong>hasBlank</strong>参考{@link ArrayUtil}
* </p>
* @see SplitUtil#split(CharSequence, CharSequence) 对字符串分割
* @see ArrayUtil#hasBlank(CharSequence...) 对多个字符串判空
*
* @author Looly * @author Looly
*/ */
public class StrUtil extends CharSequenceUtil implements StrPool { public class StrUtil extends CharSequenceUtil implements StrPool {

View File

@ -12,6 +12,8 @@
package org.dromara.hutool.core.xml; package org.dromara.hutool.core.xml;
import org.dromara.hutool.core.text.CharUtil;
import java.util.regex.Pattern; import java.util.regex.Pattern;
/** /**
@ -30,6 +32,10 @@ public class XmlConstants {
* 字符串常量XML And 符转义 {@code "&amp;" -> "&"} * 字符串常量XML And 符转义 {@code "&amp;" -> "&"}
*/ */
public static final String AMP = "&amp;"; public static final String AMP = "&amp;";
/**
* The Character '&amp;'.
*/
public static final Character C_AMP = CharUtil.AMP;
/** /**
* 字符串常量XML 双引号转义 {@code "&quot;" -> "\""} * 字符串常量XML 双引号转义 {@code "&quot;" -> "\""}
@ -40,17 +46,41 @@ public class XmlConstants {
* 字符串常量XML 单引号转义 {@code "&apos" -> "'"} * 字符串常量XML 单引号转义 {@code "&apos" -> "'"}
*/ */
public static final String APOS = "&apos;"; public static final String APOS = "&apos;";
/**
* The Character '''.
*/
public static final Character C_APOS = CharUtil.SINGLE_QUOTE;
/** /**
* 字符串常量XML 小于号转义 {@code "&lt;" -> "<"} * 字符串常量XML 小于号转义 {@code "&lt;" -> "<"}
*/ */
public static final String LT = "&lt;"; public static final String LT = "&lt;";
/**
* The Character '&lt;'.
*/
public static final Character C_LT = '<';
/** /**
* 字符串常量XML 大于号转义 {@code "&gt;" -> ">"} * 字符串常量XML 大于号转义 {@code "&gt;" -> ">"}
*/ */
public static final String GT = "&gt;"; public static final String GT = "&gt;";
/**
* The Character '&gt;'.
*/
public static final Character C_GT = '>';
/**
* The Character '!'.
*/
public static final Character C_BANG = '!';
/**
* The Character '?'.
*/
public static final Character C_QUEST = '?';
/** /**
* 在XML中无效的字符 正则 * 在XML中无效的字符 正则
*/ */

View File

@ -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());
}
}

View File

@ -21,7 +21,7 @@
<parent> <parent>
<groupId>org.dromara.hutool</groupId> <groupId>org.dromara.hutool</groupId>
<artifactId>hutool-parent</artifactId> <artifactId>hutool-parent</artifactId>
<version>6.0.0-M10</version> <version>6.0.0-M11</version>
</parent> </parent>
<artifactId>hutool-cron</artifactId> <artifactId>hutool-cron</artifactId>

View File

@ -31,6 +31,9 @@ public class CronConfig {
*/ */
protected boolean matchSecond; protected boolean matchSecond;
/**
* 构造
*/
public CronConfig(){ public CronConfig(){
} }

View File

@ -13,6 +13,7 @@
package org.dromara.hutool.cron; package org.dromara.hutool.cron;
import org.dromara.hutool.core.date.DateUnit; import org.dromara.hutool.core.date.DateUnit;
import org.dromara.hutool.core.lang.Console;
import org.dromara.hutool.core.thread.ThreadUtil; import org.dromara.hutool.core.thread.ThreadUtil;
import org.dromara.hutool.log.Log; import org.dromara.hutool.log.Log;
@ -63,9 +64,14 @@ public class CronTimer extends Thread implements Serializable {
//等待直到下一个时间点如果被中断直接退出Timer //等待直到下一个时间点如果被中断直接退出Timer
break; break;
} }
//执行点时间记录为执行开始的时间而非结束时间 //执行点时间记录为执行开始的时间而非结束时间
thisTime = System.currentTimeMillis(); spawnLauncher(nextTime);
spawnLauncher(thisTime);
// issue#3460 采用叠加方式确保正好是1分钟或1秒避免sleep晚醒问题
// 此处无需校验因为每次循环都是sleep与上触发点的时间差
// 当上一次晚醒后本次会减少sleep时间保证误差在一个unit内并不断修正
thisTime = nextTime;
} else{ } else{
// 非正常时间重新计算issue#1224@Github // 非正常时间重新计算issue#1224@Github
thisTime = System.currentTimeMillis(); thisTime = System.currentTimeMillis();

View File

@ -19,7 +19,7 @@ import org.dromara.hutool.cron.task.InvokeTask;
public class DeamonMainTest { public class DeamonMainTest {
public static void main(final String[] args) { public static void main(final String[] args) {
// 测试守护线程是否对作业线程有效 // 测试守护线程是否对作业线程有效
CronUtil.schedule("*/2 * * * * *", new InvokeTask("demo.org.dromara.hutool.cron.TestJob.doWhileTest")); CronUtil.schedule("*/2 * * * * *", new InvokeTask("org.dromara.hutool.cron.demo.TestJob.doWhileTest"));
// 当为守护线程时stop方法调用后doWhileTest里的循环输出将终止表示作业线程正常结束 // 当为守护线程时stop方法调用后doWhileTest里的循环输出将终止表示作业线程正常结束
// 当非守护线程时stop方法调用后不再产生新的作业原作业正常执行 // 当非守护线程时stop方法调用后不再产生新的作业原作业正常执行
CronUtil.setMatchSecond(true); CronUtil.setMatchSecond(true);

View File

@ -14,6 +14,7 @@ package org.dromara.hutool.cron.demo;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import org.dromara.hutool.core.date.DateUtil;
import org.dromara.hutool.core.lang.Console; import org.dromara.hutool.core.lang.Console;
import org.dromara.hutool.core.thread.ThreadUtil; import org.dromara.hutool.core.thread.ThreadUtil;
@ -29,7 +30,7 @@ public class TestJob2 {
* 执行定时任务内容 * 执行定时任务内容
*/ */
public void doTest() { public void doTest() {
Console.log("TestJob2.doTest开始执行……"); Console.log("TestJob2.doTest开始执行…… at [{}]", DateUtil.formatNow());
ThreadUtil.sleep(20, TimeUnit.SECONDS); ThreadUtil.sleep(20, TimeUnit.SECONDS);
Console.log("延迟20s打印testJob2"); Console.log("延迟20s打印testJob2");
} }

View File

@ -8,7 +8,7 @@
# demo.org.dromara.hutool.cron.TestJob.doTest = */1 * * * * * # demo.org.dromara.hutool.cron.TestJob.doTest = */1 * * * * *
[org.dromara.hutool.cron.demo]= [org.dromara.hutool.cron.demo]
# 6位表达式在秒匹配模式下可用此处表示每秒执行一次 # 6位表达式在秒匹配模式下可用此处表示每秒执行一次
# TestJob.doTest = */1 * * * * * # TestJob.doTest = */1 * * * * *
# 5位表达式在分匹配模式下可用此处表示每分钟执行一次 # 5位表达式在分匹配模式下可用此处表示每分钟执行一次

View File

@ -21,7 +21,7 @@
<parent> <parent>
<groupId>org.dromara.hutool</groupId> <groupId>org.dromara.hutool</groupId>
<artifactId>hutool-parent</artifactId> <artifactId>hutool-parent</artifactId>
<version>6.0.0-M10</version> <version>6.0.0-M11</version>
</parent> </parent>
<artifactId>hutool-crypto</artifactId> <artifactId>hutool-crypto</artifactId>
@ -31,7 +31,7 @@
<properties> <properties>
<Automatic-Module-Name>org.dromara.hutool.crypto</Automatic-Module-Name> <Automatic-Module-Name>org.dromara.hutool.crypto</Automatic-Module-Name>
<!-- versions --> <!-- versions -->
<bouncycastle.version>1.76</bouncycastle.version> <bouncycastle.version>1.77</bouncycastle.version>
</properties> </properties>
<dependencies> <dependencies>

View File

@ -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所需参数包括KeyRandomIV等信息
*/
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所需参数包括KeyRandomIV等信息
*/
interface Parameters {
}
}

View File

@ -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);
/** /**

View File

@ -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;
}
}

View File

@ -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;
}
}
}

View File

@ -64,6 +64,7 @@ public class KeyUtil {
public static final int DEFAULT_KEY_SIZE = 1024; public static final int DEFAULT_KEY_SIZE = 1024;
// region ----- generateKey // region ----- generateKey
/** /**
* 生成 {@link SecretKey}仅用于对称加密和摘要算法密钥生成 * 生成 {@link SecretKey}仅用于对称加密和摘要算法密钥生成
* *
@ -196,7 +197,24 @@ public class KeyUtil {
} }
// endregion // endregion
/**
* 检查{@link KeyPair} 是否为空空的条件是
* <ul>
* <li>keyPair本身为{@code null}</li>
* <li>{@link KeyPair#getPrivate()}{@link KeyPair#getPublic()}都为{@code null}</li>
* </ul>
*
* @param keyPair 密钥对
* @return 是否为空
*/
// region ----- keyPair // region ----- keyPair
public static boolean isEmpty(final KeyPair keyPair) {
if (null == keyPair) {
return false;
}
return null != keyPair.getPrivate() || null != keyPair.getPublic();
}
/** /**
* 生成RSA私钥仅用于非对称加密<br> * 生成RSA私钥仅用于非对称加密<br>
* 采用PKCS#8规范此规范定义了私钥信息语法和加密私钥语法<br> * 采用PKCS#8规范此规范定义了私钥信息语法和加密私钥语法<br>

View File

@ -54,5 +54,10 @@ public enum Mode {
/** /**
* Propagating Cipher Block * Propagating Cipher Block
*/ */
PCBC PCBC,
/**
* GCM 全称为 Galois/Counter ModeG是指GMACC是指CTR
* 它在 CTR 加密的基础上增加 GMAC 的特性解决了 CTR 不能对加密消息进行完整性校验的问题
*/
GCM
} }

View File

@ -31,7 +31,7 @@ import java.util.Map;
* 签名工具类<br> * 签名工具类<br>
* 封装包括 * 封装包括
* <ul> * <ul>
* <li>堆成签名签名算法支持见{@link SignAlgorithm}</li> * <li>对称签名签名算法支持见{@link SignAlgorithm}</li>
* <li>对称签名支持Map类型参数排序后签名</li> * <li>对称签名支持Map类型参数排序后签名</li>
* <li>摘要签名支持Map类型参数排序后签名签名方法见{@link DigestAlgorithm}</li> * <li>摘要签名支持Map类型参数排序后签名签名方法见{@link DigestAlgorithm}</li>
* </ul> * </ul>

View File

@ -14,18 +14,13 @@ package org.dromara.hutool.crypto.asymmetric;
import org.dromara.hutool.core.codec.binary.Base64; import org.dromara.hutool.core.codec.binary.Base64;
import org.dromara.hutool.core.io.stream.FastByteArrayOutputStream; import org.dromara.hutool.core.io.stream.FastByteArrayOutputStream;
import org.dromara.hutool.crypto.CipherWrapper; import org.dromara.hutool.crypto.*;
import org.dromara.hutool.crypto.CryptoException;
import org.dromara.hutool.crypto.KeyUtil;
import org.dromara.hutool.crypto.SecureUtil;
import org.dromara.hutool.crypto.symmetric.SymmetricAlgorithm; import org.dromara.hutool.crypto.symmetric.SymmetricAlgorithm;
import javax.crypto.BadPaddingException; import javax.crypto.BadPaddingException;
import javax.crypto.Cipher; import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException; import javax.crypto.IllegalBlockSizeException;
import java.io.IOException; import java.io.IOException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.Key; import java.security.Key;
import java.security.PrivateKey; import java.security.PrivateKey;
import java.security.PublicKey; import java.security.PublicKey;
@ -51,8 +46,7 @@ public class AsymmetricCrypto extends AbstractAsymmetricCrypto<AsymmetricCrypto>
/** /**
* Cipher负责完成加密或解密工作 * Cipher负责完成加密或解密工作
*/ */
protected CipherWrapper cipherWrapper; protected JceCipher cipher;
/** /**
* 加密的块大小 * 加密的块大小
*/ */
@ -61,6 +55,15 @@ public class AsymmetricCrypto extends AbstractAsymmetricCrypto<AsymmetricCrypto>
* 解密的块大小 * 解密的块大小
*/ */
protected int decryptBlockSize = -1; protected int decryptBlockSize = -1;
/**
* 算法参数
*/
private AlgorithmParameterSpec algorithmParameterSpec;
/**
* 自定义随机数
*/
private SecureRandom random;
// ------------------------------------------------------------------ Constructor start // ------------------------------------------------------------------ Constructor start
/** /**
@ -144,8 +147,8 @@ public class AsymmetricCrypto extends AbstractAsymmetricCrypto<AsymmetricCrypto>
*/ */
public AsymmetricCrypto(final String algorithm, final byte[] privateKey, final byte[] publicKey) { public AsymmetricCrypto(final String algorithm, final byte[] privateKey, final byte[] publicKey) {
this(algorithm, // this(algorithm, //
KeyUtil.generatePrivateKey(algorithm, privateKey), // KeyUtil.generatePrivateKey(algorithm, privateKey), //
KeyUtil.generatePublicKey(algorithm, publicKey)// KeyUtil.generatePublicKey(algorithm, publicKey)//
); );
} }
@ -209,7 +212,7 @@ public class AsymmetricCrypto extends AbstractAsymmetricCrypto<AsymmetricCrypto>
* @since 5.4.3 * @since 5.4.3
*/ */
public AlgorithmParameterSpec getAlgorithmParameterSpec() { public AlgorithmParameterSpec getAlgorithmParameterSpec() {
return this.cipherWrapper.getParams(); return this.algorithmParameterSpec;
} }
/** /**
@ -217,10 +220,11 @@ public class AsymmetricCrypto extends AbstractAsymmetricCrypto<AsymmetricCrypto>
* 在某些算法中需要特别的参数例如在ECIES中此处为IESParameterSpec * 在某些算法中需要特别的参数例如在ECIES中此处为IESParameterSpec
* *
* @param algorithmParameterSpec {@link AlgorithmParameterSpec} * @param algorithmParameterSpec {@link AlgorithmParameterSpec}
* @since 5.4.3 * @return this
*/ */
public void setAlgorithmParameterSpec(final AlgorithmParameterSpec algorithmParameterSpec) { public AsymmetricCrypto setAlgorithmParameterSpec(final AlgorithmParameterSpec algorithmParameterSpec) {
this.cipherWrapper.setParams(algorithmParameterSpec); this.algorithmParameterSpec = algorithmParameterSpec;
return this;
} }
/** /**
@ -231,7 +235,7 @@ public class AsymmetricCrypto extends AbstractAsymmetricCrypto<AsymmetricCrypto>
* @since 5.7.17 * @since 5.7.17
*/ */
public AsymmetricCrypto setRandom(final SecureRandom random) { public AsymmetricCrypto setRandom(final SecureRandom random) {
this.cipherWrapper.setRandom(random); this.random = random;
return this; return this;
} }
@ -249,7 +253,7 @@ public class AsymmetricCrypto extends AbstractAsymmetricCrypto<AsymmetricCrypto>
final Key key = getKeyByType(keyType); final Key key = getKeyByType(keyType);
lock.lock(); lock.lock();
try { try {
final Cipher cipher = initMode(Cipher.ENCRYPT_MODE, key); final JceCipher cipher = initMode(CipherMode.ENCRYPT, key);
if (this.encryptBlockSize < 0) { if (this.encryptBlockSize < 0) {
// 在引入BC库情况下自动获取块大小 // 在引入BC库情况下自动获取块大小
@ -274,7 +278,7 @@ public class AsymmetricCrypto extends AbstractAsymmetricCrypto<AsymmetricCrypto>
final Key key = getKeyByType(keyType); final Key key = getKeyByType(keyType);
lock.lock(); lock.lock();
try { try {
final Cipher cipher = initMode(Cipher.DECRYPT_MODE, key); final JceCipher cipher = initMode(CipherMode.DECRYPT, key);
if (this.decryptBlockSize < 0) { if (this.decryptBlockSize < 0) {
// 在引入BC库情况下自动获取块大小 // 在引入BC库情况下自动获取块大小
@ -301,7 +305,7 @@ public class AsymmetricCrypto extends AbstractAsymmetricCrypto<AsymmetricCrypto>
* @since 5.4.3 * @since 5.4.3
*/ */
public Cipher getCipher() { public Cipher getCipher() {
return this.cipherWrapper.getRaw(); return this.cipher.getRaw();
} }
/** /**
@ -310,7 +314,7 @@ public class AsymmetricCrypto extends AbstractAsymmetricCrypto<AsymmetricCrypto>
* @since 4.5.2 * @since 4.5.2
*/ */
protected void initCipher() { protected void initCipher() {
this.cipherWrapper = new CipherWrapper(this.algorithm); this.cipher = new JceCipher(this.algorithm);
} }
/** /**
@ -346,9 +350,10 @@ public class AsymmetricCrypto extends AbstractAsymmetricCrypto<AsymmetricCrypto>
* @throws BadPaddingException padding错误异常 * @throws BadPaddingException padding错误异常
* @throws IOException IO异常不会被触发 * @throws IOException IO异常不会被触发
*/ */
@SuppressWarnings("resource")
private byte[] doFinalWithBlock(final byte[] data, final int maxBlockSize) throws IllegalBlockSizeException, BadPaddingException, IOException { private byte[] doFinalWithBlock(final byte[] data, final int maxBlockSize) throws IllegalBlockSizeException, BadPaddingException, IOException {
final int dataLength = data.length; final int dataLength = data.length;
@SuppressWarnings("resource") final FastByteArrayOutputStream out = new FastByteArrayOutputStream(); final FastByteArrayOutputStream out = new FastByteArrayOutputStream();
int offSet = 0; int offSet = 0;
// 剩余长度 // 剩余长度
@ -367,15 +372,15 @@ public class AsymmetricCrypto extends AbstractAsymmetricCrypto<AsymmetricCrypto>
} }
/** /**
* 初始化{@link Cipher}的模式如加密模式或解密模式 * 初始化{@link JceCipher}的模式如加密模式或解密模式
* *
* @param mode 模式可选{@link Cipher#ENCRYPT_MODE}或者{@link Cipher#DECRYPT_MODE} * @param mode 模式可选{@link CipherMode#ENCRYPT}或者{@link CipherMode#DECRYPT}
* @param key 密钥 * @param key 密钥
* @return {@link Cipher} * @return {@link JceCipher}
* @throws InvalidAlgorithmParameterException 异常算法错误
* @throws InvalidKeyException 异常KEY错误
*/ */
private Cipher initMode(final int mode, final Key key) throws InvalidAlgorithmParameterException, InvalidKeyException { private JceCipher initMode(final CipherMode mode, final Key key) {
return this.cipherWrapper.initMode(mode, key).getRaw(); final JceCipher cipher = this.cipher;
cipher.init(mode, new JceCipher.JceParameters(key, this.algorithmParameterSpec, this.random));
return cipher;
} }
} }

View File

@ -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}块加密包含enginemodepadding
*/
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;
}
}
}

View File

@ -15,10 +15,19 @@ package org.dromara.hutool.crypto.bc;
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo; import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.asn1.x9.X9ECParameters; import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.crypto.BlockCipher;
import org.bouncycastle.crypto.BufferedBlockCipher;
import org.bouncycastle.crypto.DefaultBufferedBlockCipher;
import org.bouncycastle.crypto.modes.*;
import org.bouncycastle.crypto.paddings.ISO10126d2Padding;
import org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher;
import org.bouncycastle.crypto.paddings.ZeroBytePadding;
import org.bouncycastle.crypto.params.ECDomainParameters; import org.bouncycastle.crypto.params.ECDomainParameters;
import org.bouncycastle.jcajce.provider.asymmetric.util.ECUtil; import org.bouncycastle.jcajce.provider.asymmetric.util.ECUtil;
import org.bouncycastle.jce.spec.ECParameterSpec; import org.bouncycastle.jce.spec.ECParameterSpec;
import org.dromara.hutool.core.io.IORuntimeException; import org.dromara.hutool.core.io.IORuntimeException;
import org.dromara.hutool.crypto.Mode;
import org.dromara.hutool.crypto.Padding;
import java.io.IOException; import java.io.IOException;
import java.security.PrivateKey; import java.security.PrivateKey;
@ -41,10 +50,10 @@ public class BCUtil {
*/ */
public static ECDomainParameters toDomainParams(final ECParameterSpec parameterSpec) { public static ECDomainParameters toDomainParams(final ECParameterSpec parameterSpec) {
return new ECDomainParameters( return new ECDomainParameters(
parameterSpec.getCurve(), parameterSpec.getCurve(),
parameterSpec.getG(), parameterSpec.getG(),
parameterSpec.getN(), parameterSpec.getN(),
parameterSpec.getH()); parameterSpec.getH());
} }
/** /**
@ -67,10 +76,10 @@ public class BCUtil {
*/ */
public static ECDomainParameters toDomainParams(final X9ECParameters x9ECParameters) { public static ECDomainParameters toDomainParams(final X9ECParameters x9ECParameters) {
return new ECDomainParameters( return new ECDomainParameters(
x9ECParameters.getCurve(), x9ECParameters.getCurve(),
x9ECParameters.getG(), x9ECParameters.getG(),
x9ECParameters.getN(), x9ECParameters.getN(),
x9ECParameters.getH() x9ECParameters.getH()
); );
} }
@ -81,7 +90,7 @@ public class BCUtil {
* @return PKCS#1格式私钥 * @return PKCS#1格式私钥
* @since 5.5.9 * @since 5.5.9
*/ */
public static byte[] toPkcs1(final PrivateKey privateKey){ public static byte[] toPkcs1(final PrivateKey privateKey) {
final PrivateKeyInfo pkInfo = PrivateKeyInfo.getInstance(privateKey.getEncoded()); final PrivateKeyInfo pkInfo = PrivateKeyInfo.getInstance(privateKey.getEncoded());
try { try {
return pkInfo.parsePrivateKey().toASN1Primitive().getEncoded(); return pkInfo.parsePrivateKey().toASN1Primitive().getEncoded();
@ -97,13 +106,53 @@ public class BCUtil {
* @return PKCS#1格式公钥 * @return PKCS#1格式公钥
* @since 5.5.9 * @since 5.5.9
*/ */
public static byte[] toPkcs1(final PublicKey publicKey){ public static byte[] toPkcs1(final PublicKey publicKey) {
final SubjectPublicKeyInfo spkInfo = SubjectPublicKeyInfo final SubjectPublicKeyInfo spkInfo = SubjectPublicKeyInfo
.getInstance(publicKey.getEncoded()); .getInstance(publicKey.getEncoded());
try { try {
return spkInfo.parsePublicKey().getEncoded(); return spkInfo.parsePublicKey().getEncoded();
} catch (final IOException e) { } catch (final IOException e) {
throw new IORuntimeException(e); throw new IORuntimeException(e);
} }
} }
/**
* {@link BlockCipher}包装为指定mode和padding的{@link BufferedBlockCipher}
*
* @param cipher {@link BlockCipher}
* @param mode 模式
* @param padding 补码方式
* @return {@link BufferedBlockCipher}无对应Cipher返回{@code null}
* @since 6.0.0
*/
public static BufferedBlockCipher wrap(BlockCipher cipher, final Mode mode, final Padding padding) {
switch (mode) {
case CBC:
cipher = CBCBlockCipher.newInstance(cipher);
break;
case CFB:
cipher = CFBBlockCipher.newInstance(cipher, cipher.getBlockSize() * 8);
break;
case CTR:
cipher = SICBlockCipher.newInstance(cipher);
break;
case OFB:
cipher = new OFBBlockCipher(cipher, cipher.getBlockSize() * 8);
case CTS:
return new CTSBlockCipher(cipher);
}
switch (padding) {
case NoPadding:
return new DefaultBufferedBlockCipher(cipher);
case PKCS5Padding:
return new PaddedBufferedBlockCipher(cipher);
case ZeroPadding:
return new PaddedBufferedBlockCipher(cipher, new ZeroBytePadding());
case ISO10126Padding:
return new PaddedBufferedBlockCipher(cipher, new ISO10126d2Padding());
}
return null;
}
} }

View File

@ -435,6 +435,9 @@ public class ECKeyUtil {
* @since 5.5.9 * @since 5.5.9
*/ */
public static ECPrivateKeyParameters decodePrivateKeyParams(final byte[] privateKeyBytes) { public static ECPrivateKeyParameters decodePrivateKeyParams(final byte[] privateKeyBytes) {
if (null == privateKeyBytes) {
return null;
}
try { try {
// 尝试D值 // 尝试D值
return toSm2PrivateParams(privateKeyBytes); return toSm2PrivateParams(privateKeyBytes);
@ -468,6 +471,9 @@ public class ECKeyUtil {
* @since 5.5.9 * @since 5.5.9
*/ */
public static ECPublicKeyParameters decodePublicKeyParams(final byte[] publicKeyBytes) { public static ECPublicKeyParameters decodePublicKeyParams(final byte[] publicKeyBytes) {
if(null == publicKeyBytes){
return null;
}
try { try {
// 尝试Q值 // 尝试Q值
return toSm2PublicParams(publicKeyBytes); return toSm2PublicParams(publicKeyBytes);

View File

@ -545,7 +545,7 @@ public class BCrypt {
if (hashed_bytes.length != try_bytes.length) { if (hashed_bytes.length != try_bytes.length) {
return false; return false;
} }
byte ret = 0; int ret = 0;
for (int i = 0; i < try_bytes.length; i++) for (int i = 0; i < try_bytes.length; i++)
ret |= hashed_bytes[i] ^ try_bytes[i]; ret |= hashed_bytes[i] ^ try_bytes[i];
return ret == 0; return ret == 0;

View File

@ -19,11 +19,29 @@ package org.dromara.hutool.crypto.digest;
* @author Looly * @author Looly
*/ */
public enum DigestAlgorithm { public enum DigestAlgorithm {
/**
* MD2
*/
MD2("MD2"), MD2("MD2"),
/**
* MD5
*/
MD5("MD5"), MD5("MD5"),
/**
* SHA-1
*/
SHA1("SHA-1"), SHA1("SHA-1"),
/**
* SHA-256
*/
SHA256("SHA-256"), SHA256("SHA-256"),
/**
* SHA-384
*/
SHA384("SHA-384"), SHA384("SHA-384"),
/**
* SHA-512
*/
SHA512("SHA-512"); SHA512("SHA-512");
private final String value; private final String value;

View File

@ -253,7 +253,7 @@ public class Digester extends SimpleWrapper<MessageDigest> implements Serializab
// 加盐在末尾自动忽略空盐值 // 加盐在末尾自动忽略空盐值
result = doDigest(data, this.salt); result = doDigest(data, this.salt);
} else if (ArrayUtil.isNotEmpty(this.salt)) { } else if (ArrayUtil.isNotEmpty(this.salt)) {
final MessageDigest digest = getRaw(); final MessageDigest digest = this.raw;
// 加盐在中间 // 加盐在中间
digest.update(data, 0, this.saltPosition); digest.update(data, 0, this.saltPosition);
digest.update(this.salt); digest.update(this.salt);

View File

@ -27,7 +27,7 @@ import java.security.Provider;
public class DigesterFactory { public class DigesterFactory {
/** /**
* 创建工厂 * 创建工厂只使用JDK提供的算法
* *
* @param algorithm 算法 * @param algorithm 算法
* @return DigesterFactory * @return DigesterFactory

View File

@ -75,16 +75,4 @@ public class BCHMacEngine extends BCMacEngine {
super(mac, params); super(mac, params);
} }
// ------------------------------------------------------------------------------------------- Constructor end // ------------------------------------------------------------------------------------------- Constructor end
/**
* 初始化
*
* @param digest 摘要算法
* @param params 参数例如密钥可以用{@link KeyParameter}
* @return this
* @see #init(Mac, CipherParameters)
*/
public BCHMacEngine init(final Digest digest, final CipherParameters params) {
return (BCHMacEngine) init(new HMac(digest), params);
}
} }

View File

@ -15,6 +15,7 @@ package org.dromara.hutool.crypto.digest.mac;
import org.bouncycastle.crypto.CipherParameters; import org.bouncycastle.crypto.CipherParameters;
import org.bouncycastle.crypto.Mac; import org.bouncycastle.crypto.Mac;
import org.bouncycastle.crypto.params.KeyParameter; import org.bouncycastle.crypto.params.KeyParameter;
import org.dromara.hutool.core.lang.wrapper.SimpleWrapper;
/** /**
* BouncyCastle的MAC算法实现引擎使用{@link Mac} 实现摘要<br> * BouncyCastle的MAC算法实现引擎使用{@link Mac} 实现摘要<br>
@ -23,9 +24,7 @@ import org.bouncycastle.crypto.params.KeyParameter;
* @author Looly * @author Looly
* @since 5.8.0 * @since 5.8.0
*/ */
public class BCMacEngine implements MacEngine { public class BCMacEngine extends SimpleWrapper<Mac> implements MacEngine {
private Mac mac;
// ------------------------------------------------------------------------------------------- Constructor start // ------------------------------------------------------------------------------------------- Constructor start
/** /**
@ -36,57 +35,46 @@ public class BCMacEngine implements MacEngine {
* @since 5.8.0 * @since 5.8.0
*/ */
public BCMacEngine(final Mac mac, final CipherParameters params) { public BCMacEngine(final Mac mac, final CipherParameters params) {
init(mac, params); super(initMac(mac, params));
} }
// ------------------------------------------------------------------------------------------- Constructor end // ------------------------------------------------------------------------------------------- Constructor end
@Override
public void update(final byte[] in, final int inOff, final int len) {
this.raw.update(in, inOff, len);
}
@Override
public byte[] doFinal() {
final byte[] result = new byte[getMacLength()];
this.raw.doFinal(result, 0);
return result;
}
@Override
public void reset() {
this.raw.reset();
}
@Override
public int getMacLength() {
return this.raw.getMacSize();
}
@Override
public String getAlgorithm() {
return this.raw.getAlgorithmName();
}
/** /**
* 初始化 * 初始化
* *
* @param mac 摘要算法 * @param mac 摘要算法
* @param params 参数例如密钥可以用{@link KeyParameter} * @param params 参数例如密钥可以用{@link KeyParameter}
* @return this * @return this
* @since 5.8.0
*/ */
public BCMacEngine init(final Mac mac, final CipherParameters params) { private static Mac initMac(final Mac mac, final CipherParameters params) {
mac.init(params); mac.init(params);
this.mac = mac;
return this;
}
/**
* 获得 {@link Mac}
*
* @return {@link Mac}
*/
public Mac ipgetMac() {
return mac; return mac;
} }
@Override
public void update(final byte[] in, final int inOff, final int len) {
this.mac.update(in, inOff, len);
}
@Override
public byte[] doFinal() {
final byte[] result = new byte[getMacLength()];
this.mac.doFinal(result, 0);
return result;
}
@Override
public void reset() {
this.mac.reset();
}
@Override
public int getMacLength() {
return mac.getMacSize();
}
@Override
public String getAlgorithm() {
return this.mac.getAlgorithmName();
}
} }

View File

@ -15,7 +15,6 @@ package org.dromara.hutool.crypto.digest.mac;
import org.bouncycastle.crypto.BlockCipher; import org.bouncycastle.crypto.BlockCipher;
import org.bouncycastle.crypto.CipherParameters; import org.bouncycastle.crypto.CipherParameters;
import org.bouncycastle.crypto.Digest; import org.bouncycastle.crypto.Digest;
import org.bouncycastle.crypto.Mac;
import org.bouncycastle.crypto.macs.CBCBlockCipherMac; import org.bouncycastle.crypto.macs.CBCBlockCipherMac;
import org.bouncycastle.crypto.params.KeyParameter; import org.bouncycastle.crypto.params.KeyParameter;
import org.bouncycastle.crypto.params.ParametersWithIV; import org.bouncycastle.crypto.params.ParametersWithIV;
@ -97,16 +96,4 @@ public class CBCBlockCipherMacEngine extends BCMacEngine {
public CBCBlockCipherMacEngine(final CBCBlockCipherMac mac, final CipherParameters params) { public CBCBlockCipherMacEngine(final CBCBlockCipherMac mac, final CipherParameters params) {
super(mac, params); super(mac, params);
} }
/**
* 初始化
*
* @param cipher {@link BlockCipher}
* @param params 参数例如密钥可以用{@link KeyParameter}
* @return this
* @see #init(Mac, CipherParameters)
*/
public CBCBlockCipherMacEngine init(final BlockCipher cipher, final CipherParameters params) {
return (CBCBlockCipherMacEngine) init(new CBCBlockCipherMac(cipher), params);
}
} }

View File

@ -12,6 +12,7 @@
package org.dromara.hutool.crypto.digest.mac; package org.dromara.hutool.crypto.digest.mac;
import org.dromara.hutool.core.lang.wrapper.SimpleWrapper;
import org.dromara.hutool.crypto.CryptoException; import org.dromara.hutool.crypto.CryptoException;
import org.dromara.hutool.crypto.KeyUtil; import org.dromara.hutool.crypto.KeyUtil;
import org.dromara.hutool.crypto.SecureUtil; import org.dromara.hutool.crypto.SecureUtil;
@ -23,18 +24,15 @@ import java.security.Key;
import java.security.spec.AlgorithmParameterSpec; import java.security.spec.AlgorithmParameterSpec;
/** /**
* 默认的HMAC算法实现引擎使用{@link Mac} 实现摘要<br> * JDK提供的的MAC算法实现引擎使用{@link Mac} 实现摘要<br>
* 当引入BouncyCastle库时自动使用其作为Provider * 当引入BouncyCastle库时自动使用其作为Provider
* *
* @author Looly * @author Looly
* @since 4.5.13 * @since 4.5.13
*/ */
public class DefaultHMacEngine implements MacEngine { public class JCEMacEngine extends SimpleWrapper<Mac> implements MacEngine {
private Mac mac;
// region ----- Constructor // region ----- Constructor
/** /**
* 构造 * 构造
* *
@ -42,7 +40,7 @@ public class DefaultHMacEngine implements MacEngine {
* @param key 密钥 * @param key 密钥
* @since 4.5.13 * @since 4.5.13
*/ */
public DefaultHMacEngine(final String algorithm, final byte[] key) { public JCEMacEngine(final String algorithm, final byte[] key) {
this(algorithm, (null == key) ? null : new SecretKeySpec(key, algorithm)); this(algorithm, (null == key) ? null : new SecretKeySpec(key, algorithm));
} }
@ -53,7 +51,7 @@ public class DefaultHMacEngine implements MacEngine {
* @param key 密钥 * @param key 密钥
* @since 4.5.13 * @since 4.5.13
*/ */
public DefaultHMacEngine(final String algorithm, final Key key) { public JCEMacEngine(final String algorithm, final Key key) {
this(algorithm, key, null); this(algorithm, key, null);
} }
@ -65,34 +63,39 @@ public class DefaultHMacEngine implements MacEngine {
* @param spec {@link AlgorithmParameterSpec} * @param spec {@link AlgorithmParameterSpec}
* @since 5.7.12 * @since 5.7.12
*/ */
public DefaultHMacEngine(final String algorithm, final Key key, final AlgorithmParameterSpec spec) { public JCEMacEngine(final String algorithm, final Key key, final AlgorithmParameterSpec spec) {
init(algorithm, key, spec); super(initMac(algorithm, key, spec));
} }
// endregion // endregion
// region ----- init @Override
public void update(final byte[] in) {
/** this.raw.update(in);
* 初始化
*
* @param algorithm 算法
* @param key 密钥
* @return this
*/
public DefaultHMacEngine init(final String algorithm, final byte[] key) {
return init(algorithm, (null == key) ? null : new SecretKeySpec(key, algorithm));
} }
/** @Override
* 初始化 public void update(final byte[] in, final int inOff, final int len) {
* this.raw.update(in, inOff, len);
* @param algorithm 算法 }
* @param key 密钥 {@link SecretKey}
* @return this @Override
* @throws CryptoException Cause by IOException public byte[] doFinal() {
*/ return this.raw.doFinal();
public DefaultHMacEngine init(final String algorithm, final Key key) { }
return init(algorithm, key, null);
@Override
public void reset() {
this.raw.reset();
}
@Override
public int getMacLength() {
return this.raw.getMacLength();
}
@Override
public String getAlgorithm() {
return this.raw.getAlgorithm();
} }
/** /**
@ -103,9 +106,9 @@ public class DefaultHMacEngine implements MacEngine {
* @param spec {@link AlgorithmParameterSpec} * @param spec {@link AlgorithmParameterSpec}
* @return this * @return this
* @throws CryptoException Cause by IOException * @throws CryptoException Cause by IOException
* @since 5.7.12
*/ */
public DefaultHMacEngine init(final String algorithm, Key key, final AlgorithmParameterSpec spec) { private static Mac initMac(final String algorithm, Key key, final AlgorithmParameterSpec spec) {
final Mac mac;
try { try {
mac = SecureUtil.createMac(algorithm); mac = SecureUtil.createMac(algorithm);
if (null == key) { if (null == key) {
@ -119,46 +122,6 @@ public class DefaultHMacEngine implements MacEngine {
} catch (final Exception e) { } catch (final Exception e) {
throw new CryptoException(e); throw new CryptoException(e);
} }
return this;
}
// endregion
/**
* 获得 {@link Mac}
*
* @return {@link Mac}
*/
public Mac getMac() {
return mac; return mac;
} }
@Override
public void update(final byte[] in) {
this.mac.update(in);
}
@Override
public void update(final byte[] in, final int inOff, final int len) {
this.mac.update(in, inOff, len);
}
@Override
public byte[] doFinal() {
return this.mac.doFinal();
}
@Override
public void reset() {
this.mac.reset();
}
@Override
public int getMacLength() {
return mac.getMacLength();
}
@Override
public String getAlgorithm() {
return this.mac.getAlgorithm();
}
} }

View File

@ -50,6 +50,6 @@ public class MacEngineFactory {
// HmacSM3算法是BC库实现的忽略加盐 // HmacSM3算法是BC库实现的忽略加盐
return SmUtil.createHmacSm3Engine(key.getEncoded()); return SmUtil.createHmacSm3Engine(key.getEncoded());
} }
return new DefaultHMacEngine(algorithm, key, spec); return new JCEMacEngine(algorithm, key, spec);
} }
} }

View File

@ -22,6 +22,21 @@
* HMAC 可以与任何迭代散列函数捆绑使用MD5 SHA-1 就是这种散列函数HMAC 还可以使用一个用于计算和确认消息鉴别值的密钥 * HMAC 可以与任何迭代散列函数捆绑使用MD5 SHA-1 就是这种散列函数HMAC 还可以使用一个用于计算和确认消息鉴别值的密钥
* </p> * </p>
* *
* <pre>{@code
* MacEngineFactory
* ||(创建)
* MacEngine----------------包装-----------------> Mac
* _____|_______________ |
* / \ HMac
* JCEMacEngine BCMacEngine
* / \
* BCHMacEngine CBCBlockCipherMacEngine
* |
* SM4MacEngine
* }</pre>
*
* 通过MacEngine封装支持了BouncyCastle和JCE实现的一些MAC算法通过MacEngineFactory自动根据算法名称创建对应对象
*
* @author Looly * @author Looly
* @since 4.5.13 * @since 4.5.13
*/ */

View File

@ -21,6 +21,12 @@
* Truncate是一个函数就是怎么截取加密后的串并取加密后串的哪些字段组成一个数字 * Truncate是一个函数就是怎么截取加密后的串并取加密后串的哪些字段组成一个数字
* </pre> * </pre>
* *
* 实现包括
* <ul>
* <li>HMAC-based one-time passwords (HOTP) 基于HMAC算法一次性密码生成器</li>
* <li>time-based one-time passwords (TOTP) 基于时间戳算法的一次性密码生成器</li>
* </ul>
*
* @author looly * @author looly
*/ */
package org.dromara.hutool.crypto.digest.otp; package org.dromara.hutool.crypto.digest.otp;

View File

@ -34,8 +34,6 @@ import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.io.Serializable; import java.io.Serializable;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.SecureRandom; import java.security.SecureRandom;
import java.security.spec.AlgorithmParameterSpec; import java.security.spec.AlgorithmParameterSpec;
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.Lock;
@ -52,7 +50,15 @@ import java.util.concurrent.locks.ReentrantLock;
public class SymmetricCrypto implements SymmetricEncryptor, SymmetricDecryptor, Serializable { public class SymmetricCrypto implements SymmetricEncryptor, SymmetricDecryptor, Serializable {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
private CipherWrapper cipherWrapper; private JceCipher cipher;
/**
* 算法参数
*/
private AlgorithmParameterSpec algorithmParameterSpec;
/**
* 自定义随机数
*/
private SecureRandom random;
/** /**
* SecretKey 负责保存对称密钥 * SecretKey 负责保存对称密钥
*/ */
@ -157,7 +163,7 @@ public class SymmetricCrypto implements SymmetricEncryptor, SymmetricDecryptor,
this.isZeroPadding = true; this.isZeroPadding = true;
} }
this.cipherWrapper = new CipherWrapper(algorithm); this.cipher = new JceCipher(algorithm);
return this; return this;
} }
@ -176,17 +182,17 @@ public class SymmetricCrypto implements SymmetricEncryptor, SymmetricDecryptor,
* @return 加密或解密 * @return 加密或解密
*/ */
public Cipher getCipher() { public Cipher getCipher() {
return cipherWrapper.getRaw(); return cipher.getRaw();
} }
/** /**
* 设置 {@link AlgorithmParameterSpec}通常用于加盐或偏移向量 * 设置{@link AlgorithmParameterSpec}通常用于加盐或偏移向量
* *
* @param params {@link AlgorithmParameterSpec} * @param algorithmParameterSpec {@link AlgorithmParameterSpec}
* @return 自身 * @return this
*/ */
public SymmetricCrypto setParams(final AlgorithmParameterSpec params) { public SymmetricCrypto setAlgorithmParameterSpec(final AlgorithmParameterSpec algorithmParameterSpec) {
this.cipherWrapper.setParams(params); this.algorithmParameterSpec = algorithmParameterSpec;
return this; return this;
} }
@ -197,7 +203,7 @@ public class SymmetricCrypto implements SymmetricEncryptor, SymmetricDecryptor,
* @return 自身 * @return 自身
*/ */
public SymmetricCrypto setIv(final IvParameterSpec iv) { public SymmetricCrypto setIv(final IvParameterSpec iv) {
return setParams(iv); return setAlgorithmParameterSpec(iv);
} }
/** /**
@ -218,7 +224,7 @@ public class SymmetricCrypto implements SymmetricEncryptor, SymmetricDecryptor,
* @since 5.7.17 * @since 5.7.17
*/ */
public SymmetricCrypto setRandom(final SecureRandom random) { public SymmetricCrypto setRandom(final SecureRandom random) {
this.cipherWrapper.setRandom(random); this.random = random;
return this; return this;
} }
@ -245,9 +251,7 @@ public class SymmetricCrypto implements SymmetricEncryptor, SymmetricDecryptor,
public SymmetricCrypto setMode(final CipherMode mode, final byte[] salt) { public SymmetricCrypto setMode(final CipherMode mode, final byte[] salt) {
lock.lock(); lock.lock();
try { try {
initMode(mode.getValue(), salt); initMode(mode, salt);
} catch (final Exception e) {
throw new CryptoException(e);
} finally { } finally {
lock.unlock(); lock.unlock();
} }
@ -263,7 +267,7 @@ public class SymmetricCrypto implements SymmetricEncryptor, SymmetricDecryptor,
* @since 5.6.8 * @since 5.6.8
*/ */
public byte[] update(final byte[] data) { public byte[] update(final byte[] data) {
final Cipher cipher = cipherWrapper.getRaw(); final Cipher cipher = this.cipher.getRaw();
lock.lock(); lock.lock();
try { try {
return cipher.update(paddingDataWithZero(data, cipher.getBlockSize())); return cipher.update(paddingDataWithZero(data, cipher.getBlockSize()));
@ -305,10 +309,8 @@ public class SymmetricCrypto implements SymmetricEncryptor, SymmetricDecryptor,
byte[] result; byte[] result;
lock.lock(); lock.lock();
try { try {
final Cipher cipher = initMode(Cipher.ENCRYPT_MODE, salt); final JceCipher cipher = initMode(CipherMode.ENCRYPT, salt);
result = cipher.doFinal(paddingDataWithZero(data, cipher.getBlockSize())); result = cipher.processFinal(paddingDataWithZero(data, cipher.getBlockSize()));
} catch (final Exception e) {
throw new CryptoException(e);
} finally { } finally {
lock.unlock(); lock.unlock();
} }
@ -320,8 +322,8 @@ public class SymmetricCrypto implements SymmetricEncryptor, SymmetricDecryptor,
CipherOutputStream cipherOutputStream = null; CipherOutputStream cipherOutputStream = null;
lock.lock(); lock.lock();
try { try {
final Cipher cipher = initMode(Cipher.ENCRYPT_MODE, null); final JceCipher cipher = initMode(CipherMode.ENCRYPT, null);
cipherOutputStream = new CipherOutputStream(out, cipher); cipherOutputStream = new CipherOutputStream(out, cipher.getRaw());
final long length = IoUtil.copy(data, cipherOutputStream); final long length = IoUtil.copy(data, cipherOutputStream);
if (this.isZeroPadding) { if (this.isZeroPadding) {
final int blockSize = cipher.getBlockSize(); final int blockSize = cipher.getBlockSize();
@ -359,9 +361,9 @@ public class SymmetricCrypto implements SymmetricEncryptor, SymmetricDecryptor,
lock.lock(); lock.lock();
try { try {
final byte[] salt = SaltMagic.getSalt(bytes); final byte[] salt = SaltMagic.getSalt(bytes);
final Cipher cipher = initMode(Cipher.DECRYPT_MODE, salt); final JceCipher cipher = initMode(CipherMode.DECRYPT, salt);
blockSize = cipher.getBlockSize(); blockSize = cipher.getBlockSize();
decryptData = cipher.doFinal(SaltMagic.getData(bytes)); decryptData = cipher.processFinal(SaltMagic.getData(bytes));
} catch (final Exception e) { } catch (final Exception e) {
throw new CryptoException(e); throw new CryptoException(e);
} finally { } finally {
@ -376,8 +378,8 @@ public class SymmetricCrypto implements SymmetricEncryptor, SymmetricDecryptor,
CipherInputStream cipherInputStream = null; CipherInputStream cipherInputStream = null;
lock.lock(); lock.lock();
try { try {
final Cipher cipher = initMode(Cipher.DECRYPT_MODE, null); final JceCipher cipher = initMode(CipherMode.DECRYPT, null);
cipherInputStream = new CipherInputStream(data, cipher); cipherInputStream = new CipherInputStream(data, cipher.getRaw());
if (this.isZeroPadding) { if (this.isZeroPadding) {
final int blockSize = cipher.getBlockSize(); final int blockSize = cipher.getBlockSize();
if (blockSize > 0) { if (blockSize > 0) {
@ -417,8 +419,8 @@ public class SymmetricCrypto implements SymmetricEncryptor, SymmetricDecryptor,
*/ */
private SymmetricCrypto initParams(final String algorithm, AlgorithmParameterSpec paramsSpec) { private SymmetricCrypto initParams(final String algorithm, AlgorithmParameterSpec paramsSpec) {
if (null == paramsSpec) { if (null == paramsSpec) {
byte[] iv = Opt.ofNullable(cipherWrapper) byte[] iv = Opt.ofNullable(cipher)
.map(CipherWrapper::getRaw).map(Cipher::getIV).get(); .map(JceCipher::getRaw).map(Cipher::getIV).get();
// 随机IV // 随机IV
if (StrUtil.startWithIgnoreCase(algorithm, "PBE")) { if (StrUtil.startWithIgnoreCase(algorithm, "PBE")) {
@ -435,18 +437,16 @@ public class SymmetricCrypto implements SymmetricEncryptor, SymmetricDecryptor,
} }
} }
return setParams(paramsSpec); return setAlgorithmParameterSpec(paramsSpec);
} }
/** /**
* 初始化{@link Cipher}为加密或者解密模式 * 初始化{@link JceCipher}为加密或者解密模式
* *
* @param mode 模式{@link Cipher#ENCRYPT_MODE} {@link Cipher#DECRYPT_MODE} * @param mode 模式{@link CipherMode#ENCRYPT} {@link CipherMode#DECRYPT}
* @return {@link Cipher} * @return {@link Cipher}
* @throws InvalidKeyException 无效key
* @throws InvalidAlgorithmParameterException 无效算法
*/ */
private Cipher initMode(final int mode, final byte[] salt) throws InvalidKeyException, InvalidAlgorithmParameterException { private JceCipher initMode(final CipherMode mode, final byte[] salt) {
SecretKey secretKey = this.secretKey; SecretKey secretKey = this.secretKey;
if (null != salt) { if (null != salt) {
// /issues#I6YWWD提供OpenSSL格式兼容支持 // /issues#I6YWWD提供OpenSSL格式兼容支持
@ -454,11 +454,15 @@ public class SymmetricCrypto implements SymmetricEncryptor, SymmetricDecryptor,
final byte[][] keyAndIV = OpenSSLSaltParser.ofMd5(32, algorithm) final byte[][] keyAndIV = OpenSSLSaltParser.ofMd5(32, algorithm)
.getKeyAndIV(secretKey.getEncoded(), salt); .getKeyAndIV(secretKey.getEncoded(), salt);
secretKey = KeyUtil.generateKey(algorithm, keyAndIV[0]); secretKey = KeyUtil.generateKey(algorithm, keyAndIV[0]);
if(ArrayUtil.isNotEmpty(keyAndIV[1])){ if (ArrayUtil.isNotEmpty(keyAndIV[1])) {
this.cipherWrapper.setParams(new IvParameterSpec(keyAndIV[1])); setAlgorithmParameterSpec(new IvParameterSpec(keyAndIV[1]));
} }
} }
return this.cipherWrapper.initMode(mode, secretKey).getRaw();
final JceCipher cipher = this.cipher;
cipher.init(mode,
new JceCipher.JceParameters(secretKey, this.algorithmParameterSpec, this.random));
return cipher;
} }
/** /**

View File

@ -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);
}
}

View File

@ -135,9 +135,9 @@ public class SymmetricTest {
final AES aes = new AES(Mode.CBC, Padding.PKCS5Padding, "0123456789ABHAEQ".getBytes(), "DYgjCEIMVrj2W9xN".getBytes()); final AES aes = new AES(Mode.CBC, Padding.PKCS5Padding, "0123456789ABHAEQ".getBytes(), "DYgjCEIMVrj2W9xN".getBytes());
// 加密为16进制表示 // 加密为16进制表示
aes.setMode(CipherMode.encrypt); aes.setMode(CipherMode.ENCRYPT);
final String randomData = aes.updateHex(content.getBytes(StandardCharsets.UTF_8)); final String randomData = aes.updateHex(content.getBytes(StandardCharsets.UTF_8));
aes.setMode(CipherMode.encrypt); aes.setMode(CipherMode.ENCRYPT);
final String randomData2 = aes.updateHex(content.getBytes(StandardCharsets.UTF_8)); final String randomData2 = aes.updateHex(content.getBytes(StandardCharsets.UTF_8));
Assertions.assertEquals(randomData2, randomData); Assertions.assertEquals(randomData2, randomData);
Assertions.assertEquals(randomData, "cd0e3a249eaf0ed80c330338508898c4"); Assertions.assertEquals(randomData, "cd0e3a249eaf0ed80c330338508898c4");

View File

@ -28,7 +28,7 @@
### SQL相关工具sql ### SQL相关工具sql
提供SQL相关功能包括SQL变量替换NamedSql通过对象完成SQL构建SqlBuilder等。 提供SQL相关功能包括SQL变量替换NamedSql通过对象完成SQL构建SqlBuilder等。
`SqlSqlExecutor`提供SQL执行的静态方法。 `SqlExecutor`提供SQL执行的静态方法。
### 数据库元信息meta ### 数据库元信息meta
通过`MetaUtil`提供数据库表、字段等信息的读取操作。 通过`MetaUtil`提供数据库表、字段等信息的读取操作。

View File

@ -21,7 +21,7 @@
<parent> <parent>
<groupId>org.dromara.hutool</groupId> <groupId>org.dromara.hutool</groupId>
<artifactId>hutool-parent</artifactId> <artifactId>hutool-parent</artifactId>
<version>6.0.0-M10</version> <version>6.0.0-M11</version>
</parent> </parent>
<artifactId>hutool-db</artifactId> <artifactId>hutool-db</artifactId>
@ -38,7 +38,7 @@
<druid.version>1.2.21</druid.version> <druid.version>1.2.21</druid.version>
<!-- 固定4.x --> <!-- 固定4.x -->
<hikariCP.version>4.0.3</hikariCP.version> <hikariCP.version>4.0.3</hikariCP.version>
<sqlite.version>3.43.0.0</sqlite.version> <sqlite.version>3.44.1.0</sqlite.version>
<!-- 此处固定2.5.x支持到JDK8 --> <!-- 此处固定2.5.x支持到JDK8 -->
<hsqldb.version>2.5.2</hsqldb.version> <hsqldb.version>2.5.2</hsqldb.version>
</properties> </properties>

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2023 looly(loolly@aliyun.com) * Copyright (c) 2023-2024. looly(loolly@aliyun.com)
* Hutool is licensed under Mulan PSL v2. * Hutool is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2. * You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at: * You may obtain a copy of Mulan PSL v2 at:
@ -12,14 +12,15 @@
package org.dromara.hutool.db; package org.dromara.hutool.db;
import org.dromara.hutool.core.array.ArrayUtil;
import org.dromara.hutool.core.collection.iter.ArrayIter; import org.dromara.hutool.core.collection.iter.ArrayIter;
import org.dromara.hutool.core.lang.Assert; import org.dromara.hutool.core.lang.Assert;
import org.dromara.hutool.db.handler.ResultSetUtil; import org.dromara.hutool.db.handler.ResultSetUtil;
import org.dromara.hutool.db.handler.RsHandler; import org.dromara.hutool.db.handler.RsHandler;
import org.dromara.hutool.db.sql.SqlBuilder; import org.dromara.hutool.db.sql.SqlBuilder;
import org.dromara.hutool.db.sql.SqlLog;
import org.dromara.hutool.db.sql.StatementBuilder; import org.dromara.hutool.db.sql.StatementBuilder;
import org.dromara.hutool.db.sql.StatementWrapper; import org.dromara.hutool.db.sql.StatementWrapper;
import org.dromara.hutool.db.sql.filter.SqlLogFilter;
import java.sql.*; import java.sql.*;
import java.util.Collection; import java.util.Collection;
@ -87,7 +88,7 @@ public class StatementUtil {
return StatementBuilder.of() return StatementBuilder.of()
.setConnection(conn) .setConnection(conn)
.setReturnGeneratedKey(returnGeneratedKey) .setReturnGeneratedKey(returnGeneratedKey)
.setSqlLog(SqlLog.INSTANCE) .setSqlFilter(SqlLogFilter.INSTANCE)
.setSql(sql) .setSql(sql)
.setParams(params) .setParams(params)
.build(); .build();
@ -120,29 +121,10 @@ public class StatementUtil {
return StatementBuilder.of() return StatementBuilder.of()
.setConnection(conn) .setConnection(conn)
.setReturnGeneratedKey(false) .setReturnGeneratedKey(false)
.setSqlLog(SqlLog.INSTANCE) .setSqlFilter(SqlLogFilter.INSTANCE)
.setSql(sql) .setSql(sql)
.buildForBatch(paramsBatch); .setParams(ArrayUtil.ofArray(paramsBatch, Object.class))
} .buildForBatch();
/**
* 创建批量操作的{@link PreparedStatement}
*
* @param conn 数据库连接
* @param sql SQL语句使用"?"做为占位符
* @param fields 字段列表用于获取对应值
* @param entities "?"对应参数批次列表
* @return {@link PreparedStatement}
* @since 4.6.7
*/
public static PreparedStatement prepareStatementForBatch(final Connection conn, final String sql,
final Iterable<String> fields, final Entity... entities) {
return StatementBuilder.of()
.setConnection(conn)
.setReturnGeneratedKey(false)
.setSqlLog(SqlLog.INSTANCE)
.setSql(sql)
.buildForBatch(fields, entities);
} }
/** /**
@ -158,7 +140,7 @@ public class StatementUtil {
public static CallableStatement prepareCall(final Connection conn, final String sql, final Object... params) throws SQLException { public static CallableStatement prepareCall(final Connection conn, final String sql, final Object... params) throws SQLException {
return StatementBuilder.of() return StatementBuilder.of()
.setConnection(conn) .setConnection(conn)
.setSqlLog(SqlLog.INSTANCE) .setSqlFilter(SqlLogFilter.INSTANCE)
.setSql(sql) .setSql(sql)
.setParams(params) .setParams(params)
.buildForCall(); .buildForCall();

View File

@ -53,21 +53,20 @@ public class AnsiSqlDialect implements Dialect {
} }
@Override @Override
public PreparedStatement psForInsert(final Connection conn, final Entity entity) throws SQLException { public PreparedStatement psForInsert(final Connection conn, final Entity entity) {
final SqlBuilder insert = SqlBuilder.of(quoteWrapper).insert(entity, this.dialectName()); final SqlBuilder insert = SqlBuilder.of(quoteWrapper).insert(entity, this.dialectName());
return StatementUtil.prepareStatement(conn, insert); return StatementUtil.prepareStatement(conn, insert);
} }
@Override @Override
public PreparedStatement psForInsertBatch(final Connection conn, final Entity... entities) throws SQLException { public PreparedStatement psForInsertBatch(final Connection conn, final Entity... entities) {
if (ArrayUtil.isEmpty(entities)) { if (ArrayUtil.isEmpty(entities)) {
throw new DbRuntimeException("Entities for batch insert is empty !"); throw new DbRuntimeException("Entities for batch insert is empty !");
} }
// 批量根据第一行数据结构生成SQL占位符 // 批量根据第一行数据结构生成SQL占位符
final SqlBuilder insert = SqlBuilder.of(quoteWrapper).insert(entities[0], this.dialectName()); final SqlBuilder insert = SqlBuilder.of(quoteWrapper).insert(entities[0], this.dialectName());
final Set<String> fields = CollUtil.remove(entities[0].keySet(), StrUtil::isBlank); return StatementUtil.prepareStatementForBatch(conn, insert.build(), entities);
return StatementUtil.prepareStatementForBatch(conn, insert.build(), fields, entities);
} }
@Override @Override
@ -100,12 +99,12 @@ public class AnsiSqlDialect implements Dialect {
} }
@Override @Override
public PreparedStatement psForFind(final Connection conn, final Query query) throws SQLException { public PreparedStatement psForFind(final Connection conn, final Query query) {
return psForPage(conn, query); return psForPage(conn, query);
} }
@Override @Override
public PreparedStatement psForPage(final Connection conn, final Query query) throws SQLException { public PreparedStatement psForPage(final Connection conn, final Query query) {
Assert.notNull(query, "query must be not null !"); Assert.notNull(query, "query must be not null !");
if (ArrayUtil.hasBlank(query.getTableNames())) { if (ArrayUtil.hasBlank(query.getTableNames())) {
throw new DbRuntimeException("Table name must be not empty !"); throw new DbRuntimeException("Table name must be not empty !");
@ -116,7 +115,7 @@ public class AnsiSqlDialect implements Dialect {
} }
@Override @Override
public PreparedStatement psForPage(final Connection conn, SqlBuilder sqlBuilder, final Page page) throws SQLException { public PreparedStatement psForPage(final Connection conn, SqlBuilder sqlBuilder, final Page page) {
// 根据不同数据库在查询SQL语句基础上包装其分页的语句 // 根据不同数据库在查询SQL语句基础上包装其分页的语句
if (null != page) { if (null != page) {
sqlBuilder = wrapPageSql(sqlBuilder.orderBy(page.getOrders()), page); sqlBuilder = wrapPageSql(sqlBuilder.orderBy(page.getOrders()), page);

View File

@ -12,6 +12,7 @@
package org.dromara.hutool.db.dialect.impl; package org.dromara.hutool.db.dialect.impl;
import org.dromara.hutool.core.text.StrPool;
import org.dromara.hutool.core.text.StrUtil; import org.dromara.hutool.core.text.StrUtil;
import org.dromara.hutool.db.Page; import org.dromara.hutool.db.Page;
import org.dromara.hutool.db.dialect.DialectName; import org.dromara.hutool.db.dialect.DialectName;
@ -25,6 +26,10 @@ import org.dromara.hutool.db.sql.SqlBuilder;
public class OracleDialect extends AnsiSqlDialect { public class OracleDialect extends AnsiSqlDialect {
private static final long serialVersionUID = 6122761762247483015L; private static final long serialVersionUID = 6122761762247483015L;
private static final String DEFAULT_TABLE_ALIAS = "table_alias_";
private static final String DEFAULT_ROW_ALIAS = "row_";
private static final String DEFAULT_ROWNUM_ALIAS = "rownum_";
/** /**
* 检查字段值是否为Oracle自增字段自增字段以`.nextval`结尾 * 检查字段值是否为Oracle自增字段自增字段以`.nextval`结尾
* *
@ -36,6 +41,9 @@ public class OracleDialect extends AnsiSqlDialect {
return (value instanceof CharSequence) && StrUtil.endWithIgnoreCase(value.toString(), ".nextval"); return (value instanceof CharSequence) && StrUtil.endWithIgnoreCase(value.toString(), ".nextval");
} }
/**
* 构造
*/
public OracleDialect() { public OracleDialect() {
//Oracle所有字段名用双引号包围防止字段名或表名与系统关键字冲突 //Oracle所有字段名用双引号包围防止字段名或表名与系统关键字冲突
//wrapper = new Wrapper('"'); //wrapper = new Wrapper('"');
@ -44,11 +52,27 @@ public class OracleDialect extends AnsiSqlDialect {
@Override @Override
protected SqlBuilder wrapPageSql(final SqlBuilder find, final Page page) { protected SqlBuilder wrapPageSql(final SqlBuilder find, final Page page) {
final int[] startEnd = page.getStartEnd(); final int[] startEnd = page.getStartEnd();
// 检查别名避免重名
final String sql = find.toString();
String tableAlias = DEFAULT_TABLE_ALIAS;
while (sql.contains(tableAlias)) {
tableAlias += StrPool.UNDERLINE;
}
String rowAlias = DEFAULT_ROW_ALIAS;
while (sql.contains(rowAlias)) {
rowAlias += StrPool.UNDERLINE;
}
String rownumAlias = DEFAULT_ROWNUM_ALIAS;
while (sql.contains(rownumAlias)) {
rownumAlias += StrPool.UNDERLINE;
}
return find return find
.insertPreFragment("SELECT * FROM ( SELECT row_.*, rownum rownum_ from ( ") .insertPreFragment("SELECT * FROM ( SELECT " + rowAlias + ".*, rownum " + rownumAlias + " from ( ")
.append(" ) row_ where rownum <= ").append(startEnd[1])// .append(" ) row_ where rownum <= ").append(startEnd[1])//
.append(") table_alias_")// .append(") ").append(tableAlias)//
.append(" where table_alias_.rownum_ > ").append(startEnd[0]);// .append(" where ").append(tableAlias).append(".rownum_ > ").append(startEnd[0]);//
} }
@Override @Override

View File

@ -29,6 +29,9 @@ import java.sql.SQLException;
public class PhoenixDialect extends AnsiSqlDialect { public class PhoenixDialect extends AnsiSqlDialect {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
/**
* 构造
*/
public PhoenixDialect() { public PhoenixDialect() {
// wrapper = new Wrapper('"'); // wrapper = new Wrapper('"');
} }

View File

@ -21,15 +21,17 @@ import org.dromara.hutool.db.sql.QuoteWrapper;
/** /**
* SQLServer2012 方言 * SQLServer2012 方言
* *
* @author loolly * @author Looly
*
*/ */
public class SqlServer2012Dialect extends AnsiSqlDialect { public class SqlServer2012Dialect extends AnsiSqlDialect {
private static final long serialVersionUID = -37598166015777797L; private static final long serialVersionUID = -37598166015777797L;
/**
* 构造
*/
public SqlServer2012Dialect() { public SqlServer2012Dialect() {
//双引号和中括号适用双引号更广泛 //双引号和中括号适用双引号更广泛
quoteWrapper = new QuoteWrapper('"'); quoteWrapper = new QuoteWrapper('"');
} }
@Override @Override
@ -39,10 +41,10 @@ public class SqlServer2012Dialect extends AnsiSqlDialect {
find.append(" order by current_timestamp"); find.append(" order by current_timestamp");
} }
return find.append(" offset ") return find.append(" offset ")
.append(page.getStartPosition())// .append(page.getStartPosition())//
.append(" row fetch next ")//row和rows同义词 .append(" row fetch next ")//row和rows同义词
.append(page.getPageSize())// .append(page.getPageSize())//
.append(" row only");// .append(" row only");//
} }
@Override @Override

View File

@ -17,12 +17,15 @@ import org.dromara.hutool.db.sql.QuoteWrapper;
/** /**
* SqlLite3方言 * SqlLite3方言
* @author loolly
* *
* @author Looly
*/ */
public class Sqlite3Dialect extends AnsiSqlDialect{ public class Sqlite3Dialect extends AnsiSqlDialect {
private static final long serialVersionUID = -3527642408849291634L; private static final long serialVersionUID = -3527642408849291634L;
/**
* 构造
*/
public Sqlite3Dialect() { public Sqlite3Dialect() {
quoteWrapper = new QuoteWrapper('[', ']'); quoteWrapper = new QuoteWrapper('[', ']');
} }

View File

@ -19,6 +19,7 @@ import org.dromara.hutool.core.map.SafeConcurrentHashMap;
import org.dromara.hutool.core.spi.SpiUtil; import org.dromara.hutool.core.spi.SpiUtil;
import org.dromara.hutool.core.text.StrUtil; import org.dromara.hutool.core.text.StrUtil;
import org.dromara.hutool.db.DbRuntimeException; import org.dromara.hutool.db.DbRuntimeException;
import org.dromara.hutool.db.DbUtil;
import org.dromara.hutool.db.GlobalDbConfig; import org.dromara.hutool.db.GlobalDbConfig;
import org.dromara.hutool.db.driver.DriverUtil; import org.dromara.hutool.db.driver.DriverUtil;
import org.dromara.hutool.log.LogUtil; import org.dromara.hutool.log.LogUtil;
@ -89,6 +90,7 @@ public class DSPool implements Closeable {
*/ */
public DSPool(final Setting setting, final DSFactory factory) { public DSPool(final Setting setting, final DSFactory factory) {
this.setting = null != setting ? setting : GlobalDbConfig.createDbSetting(); this.setting = null != setting ? setting : GlobalDbConfig.createDbSetting();
DbUtil.setShowSqlGlobal(this.setting);
this.factory = null != factory ? factory : SpiUtil.loadFirstAvailable(DSFactory.class); this.factory = null != factory ? factory : SpiUtil.loadFirstAvailable(DSFactory.class);
this.pool = new SafeConcurrentHashMap<>(); this.pool = new SafeConcurrentHashMap<>();
} }

View File

@ -14,7 +14,7 @@ package org.dromara.hutool.db.ds;
import org.dromara.hutool.core.exception.CloneException; import org.dromara.hutool.core.exception.CloneException;
import org.dromara.hutool.core.io.IoUtil; import org.dromara.hutool.core.io.IoUtil;
import org.dromara.hutool.core.lang.wrapper.Wrapper; import org.dromara.hutool.core.lang.wrapper.SimpleWrapper;
import javax.sql.DataSource; import javax.sql.DataSource;
import java.io.Closeable; import java.io.Closeable;
@ -34,9 +34,8 @@ import java.util.logging.Logger;
* @author looly * @author looly
* @since 4.3.2 * @since 4.3.2
*/ */
public class DSWrapper implements Wrapper<DataSource>, DataSource, Closeable, Cloneable { public class DSWrapper extends SimpleWrapper<DataSource> implements DataSource, Closeable, Cloneable {
private final DataSource ds;
private final String driver; private final String driver;
/** /**
@ -57,7 +56,7 @@ public class DSWrapper implements Wrapper<DataSource>, DataSource, Closeable, Cl
* @param driver 数据库驱动类名 * @param driver 数据库驱动类名
*/ */
public DSWrapper(final DataSource ds, final String driver) { public DSWrapper(final DataSource ds, final String driver) {
this.ds = ds; super(ds);
this.driver = driver; this.driver = driver;
} }
@ -70,65 +69,56 @@ public class DSWrapper implements Wrapper<DataSource>, DataSource, Closeable, Cl
return this.driver; return this.driver;
} }
/**
* 获取原始的数据源
*
* @return 原始数据源
*/
@Override
public DataSource getRaw() {
return this.ds;
}
@Override @Override
public PrintWriter getLogWriter() throws SQLException { public PrintWriter getLogWriter() throws SQLException {
return ds.getLogWriter(); return this.raw.getLogWriter();
} }
@Override @Override
public void setLogWriter(final PrintWriter out) throws SQLException { public void setLogWriter(final PrintWriter out) throws SQLException {
ds.setLogWriter(out); this.raw.setLogWriter(out);
} }
@Override @Override
public void setLoginTimeout(final int seconds) throws SQLException { public void setLoginTimeout(final int seconds) throws SQLException {
ds.setLoginTimeout(seconds); this.raw.setLoginTimeout(seconds);
} }
@Override @Override
public int getLoginTimeout() throws SQLException { public int getLoginTimeout() throws SQLException {
return ds.getLoginTimeout(); return this.raw.getLoginTimeout();
} }
@Override @Override
public Logger getParentLogger() throws SQLFeatureNotSupportedException { public Logger getParentLogger() throws SQLFeatureNotSupportedException {
return ds.getParentLogger(); return this.raw.getParentLogger();
} }
@Override @Override
public <T> T unwrap(final Class<T> iface) throws SQLException { public <T> T unwrap(final Class<T> iface) throws SQLException {
return ds.unwrap(iface); return this.raw.unwrap(iface);
} }
@Override @Override
public boolean isWrapperFor(final Class<?> iface) throws SQLException { public boolean isWrapperFor(final Class<?> iface) throws SQLException {
return ds.isWrapperFor(iface); return this.raw.isWrapperFor(iface);
} }
@Override @Override
public Connection getConnection() throws SQLException { public Connection getConnection() throws SQLException {
return ds.getConnection(); return this.raw.getConnection();
} }
@Override @Override
public Connection getConnection(final String username, final String password) throws SQLException { public Connection getConnection(final String username, final String password) throws SQLException {
return ds.getConnection(username, password); return this.raw.getConnection(username, password);
} }
@Override @Override
public void close() { public void close() {
if (this.ds instanceof AutoCloseable) { final DataSource ds = this.raw;
IoUtil.closeQuietly((AutoCloseable) this.ds); if (ds instanceof AutoCloseable) {
IoUtil.closeQuietly((AutoCloseable) ds);
} }
} }

View 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;
}
}

View File

@ -12,13 +12,11 @@
package org.dromara.hutool.db.sql; package org.dromara.hutool.db.sql;
import org.dromara.hutool.core.array.ArrayUtil;
import org.dromara.hutool.core.map.MapUtil; import org.dromara.hutool.core.map.MapUtil;
import org.dromara.hutool.core.text.StrUtil; import org.dromara.hutool.core.text.StrUtil;
import org.dromara.hutool.core.array.ArrayUtil;
import java.util.Collection; import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.Map; import java.util.Map;
/** /**
@ -33,12 +31,12 @@ import java.util.Map;
* @author looly * @author looly
* @since 4.0.10 * @since 4.0.10
*/ */
public class NamedSql { public class NamedSql extends BoundSql {
private static final char[] NAME_START_CHARS = {':', '@', '?'}; private static final char[] NAME_START_CHARS = {':', '@', '?'};
private String sql; private final String namedSql;
private final List<Object> params; private final Map<String, Object> paramMap;
/** /**
* 构造 * 构造
@ -47,35 +45,27 @@ public class NamedSql {
* @param paramMap 名和参数的对应Map * @param paramMap 名和参数的对应Map
*/ */
public NamedSql(final String namedSql, final Map<String, Object> paramMap) { public NamedSql(final String namedSql, final Map<String, Object> paramMap) {
this.params = new LinkedList<>(); this.namedSql = namedSql;
this.paramMap = paramMap;
parse(namedSql, paramMap); parse(namedSql, paramMap);
} }
/** /**
* 获取SQL * 获取原始地带名称占位符的SQL语句
* *
* @return SQL * @return 名称占位符的SQL
*/ */
public String getSql() { public String getNamedSql() {
return this.sql; return namedSql;
} }
/** /**
* 获取参数列表按照占位符顺序 * 获取原始参数名和参数值对应关系参数表
* *
* @return 参数数组 * @return 参数名和参数值对应关系参数表
*/ */
public Object[] getParams() { public Map<String, Object> getParamMap() {
return this.params.toArray(new Object[0]); return paramMap;
}
/**
* 获取参数列表按照占位符顺序
*
* @return 参数列表
*/
public List<Object> getParamList() {
return this.params;
} }
/** /**
@ -86,7 +76,7 @@ public class NamedSql {
*/ */
private void parse(final String namedSql, final Map<String, Object> paramMap) { private void parse(final String namedSql, final Map<String, Object> paramMap) {
if (MapUtil.isEmpty(paramMap)) { if (MapUtil.isEmpty(paramMap)) {
this.sql = namedSql; setSql(namedSql);
return; return;
} }
@ -124,7 +114,7 @@ public class NamedSql {
replaceVar(nameStartChar, name, sqlBuilder, paramMap); replaceVar(nameStartChar, name, sqlBuilder, paramMap);
} }
this.sql = sqlBuilder.toString(); setSql(sqlBuilder.toString());
} }
/** /**
@ -163,11 +153,11 @@ public class NamedSql {
sqlBuilder.append(','); sqlBuilder.append(',');
} }
sqlBuilder.append('?'); sqlBuilder.append('?');
this.params.add(ArrayUtil.get(paramValue, i)); addParam(ArrayUtil.get(paramValue, i));
} }
} else { } else {
sqlBuilder.append('?'); sqlBuilder.append('?');
this.params.add(paramValue); addParam(paramValue);
} }
} else { } else {
// 无变量对应值原样输出 // 无变量对应值原样输出

View File

@ -44,7 +44,7 @@ public class SqlExecutor {
*/ */
public static int execute(final Connection conn, final String sql, final Map<String, Object> paramMap) throws DbRuntimeException { public static int execute(final Connection conn, final String sql, final Map<String, Object> paramMap) throws DbRuntimeException {
final NamedSql namedSql = new NamedSql(sql, paramMap); final NamedSql namedSql = new NamedSql(sql, paramMap);
return execute(conn, namedSql.getSql(), namedSql.getParams()); return execute(conn, namedSql.getSql(), namedSql.getParamArray());
} }
/** /**
@ -125,7 +125,7 @@ public class SqlExecutor {
*/ */
public static Long executeForGeneratedKey(final Connection conn, final String sql, final Map<String, Object> paramMap) throws DbRuntimeException { public static Long executeForGeneratedKey(final Connection conn, final String sql, final Map<String, Object> paramMap) throws DbRuntimeException {
final NamedSql namedSql = new NamedSql(sql, paramMap); final NamedSql namedSql = new NamedSql(sql, paramMap);
return executeForGeneratedKey(conn, namedSql.getSql(), namedSql.getParams()); return executeForGeneratedKey(conn, namedSql.getSql(), namedSql.getParamArray());
} }
/** /**
@ -241,7 +241,7 @@ public class SqlExecutor {
*/ */
public static <T> T query(final Connection conn, final String sql, final RsHandler<T> rsh, final Map<String, Object> paramMap) throws DbRuntimeException { public static <T> T query(final Connection conn, final String sql, final RsHandler<T> rsh, final Map<String, Object> paramMap) throws DbRuntimeException {
final NamedSql namedSql = new NamedSql(sql, paramMap); final NamedSql namedSql = new NamedSql(sql, paramMap);
return query(conn, namedSql.getSql(), rsh, namedSql.getParams()); return query(conn, namedSql.getSql(), rsh, namedSql.getParamArray());
} }
/** /**

View File

@ -13,6 +13,7 @@
package org.dromara.hutool.db.sql; package org.dromara.hutool.db.sql;
import org.dromara.hutool.core.array.ArrayUtil; import org.dromara.hutool.core.array.ArrayUtil;
import org.dromara.hutool.core.collection.ListUtil;
import org.dromara.hutool.core.collection.iter.ArrayIter; import org.dromara.hutool.core.collection.iter.ArrayIter;
import org.dromara.hutool.core.convert.Convert; import org.dromara.hutool.core.convert.Convert;
import org.dromara.hutool.core.lang.Assert; import org.dromara.hutool.core.lang.Assert;
@ -21,10 +22,13 @@ import org.dromara.hutool.core.map.MapUtil;
import org.dromara.hutool.core.text.StrUtil; import org.dromara.hutool.core.text.StrUtil;
import org.dromara.hutool.db.DbRuntimeException; import org.dromara.hutool.db.DbRuntimeException;
import org.dromara.hutool.db.Entity; import org.dromara.hutool.db.Entity;
import org.dromara.hutool.db.sql.filter.SqlFilter;
import java.sql.*; import java.sql.*;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set;
/** /**
* {@link PreparedStatement}构建器构建结果为{@link StatementWrapper} * {@link PreparedStatement}构建器构建结果为{@link StatementWrapper}
@ -44,20 +48,19 @@ public class StatementBuilder implements Builder<StatementWrapper> {
return new StatementBuilder(); return new StatementBuilder();
} }
private SqlLog sqlLog; private final BoundSql boundSql = new BoundSql();
private Connection connection; private Connection connection;
private String sql;
private Object[] params;
private boolean returnGeneratedKey = true; private boolean returnGeneratedKey = true;
private SqlFilter sqlFilter;
/** /**
* 设置SQL日志 * 设置SQL日志
* *
* @param sqlLog {@link SqlLog} * @param sqlFilter {@link SqlFilter}
* @return this * @return this
*/ */
public StatementBuilder setSqlLog(final SqlLog sqlLog) { public StatementBuilder setSqlFilter(final SqlFilter sqlFilter) {
this.sqlLog = sqlLog; this.sqlFilter = sqlFilter;
return this; return this;
} }
@ -79,7 +82,7 @@ public class StatementBuilder implements Builder<StatementWrapper> {
* @return this * @return this
*/ */
public StatementBuilder setSql(final String sql) { public StatementBuilder setSql(final String sql) {
this.sql = StrUtil.trim(sql); this.boundSql.setSql(sql);
return this; return this;
} }
@ -90,7 +93,7 @@ public class StatementBuilder implements Builder<StatementWrapper> {
* @return this * @return this
*/ */
public StatementBuilder setParams(final Object... params) { public StatementBuilder setParams(final Object... params) {
this.params = params; this.boundSql.setParams(ListUtil.of(params));
return this; return this;
} }
@ -105,6 +108,11 @@ public class StatementBuilder implements Builder<StatementWrapper> {
return this; return this;
} }
/**
* 构建{@link StatementWrapper}
*
* @return {@link StatementWrapper}{@code null}表示不执行
*/
@Override @Override
public StatementWrapper build() { public StatementWrapper build() {
try { try {
@ -117,48 +125,37 @@ public class StatementBuilder implements Builder<StatementWrapper> {
/** /**
* 创建批量操作的{@link StatementWrapper} * 创建批量操作的{@link StatementWrapper}
* *
* @param paramsBatch "?"对应参数批次列表 * @return {@link StatementWrapper}{@code null}表示不执行
* @return {@link StatementWrapper}
* @throws DbRuntimeException SQL异常 * @throws DbRuntimeException SQL异常
*/ */
public StatementWrapper buildForBatch(final Iterable<Object[]> paramsBatch) throws DbRuntimeException { public StatementWrapper buildForBatch() throws DbRuntimeException {
final String sql = this.boundSql.getSql();
Assert.notBlank(sql, "Sql String must be not blank!"); Assert.notBlank(sql, "Sql String must be not blank!");
final List<Object> paramsBatch = this.boundSql.getParams();
sqlLog.log(sql, paramsBatch); sqlFilter.filter(this.connection, this.boundSql, this.returnGeneratedKey);
final StatementWrapper ps; final StatementWrapper ps;
try { try {
ps = StatementWrapper.of(connection.prepareStatement(sql)); ps = StatementWrapper.of(connection.prepareStatement(sql));
final Map<Integer, Integer> nullTypeMap = new HashMap<>(); final Map<Integer, Integer> nullTypeMap = new HashMap<>();
for (final Object[] params : paramsBatch) { Set<String> keys = null;
ps.fillParams(new ArrayIter<>(params), nullTypeMap); for (final Object params : paramsBatch) {
ps.addBatch(); if (null == params) {
} continue;
} catch (final SQLException e) { }
throw new DbRuntimeException(e); if (ArrayUtil.isArray(params)) {
} ps.fillParams(new ArrayIter<>(params), nullTypeMap);
return ps; } else if (params instanceof Entity) {
} final Entity entity = (Entity) params;
// 对于多Entity批量插入的情况为防止数据不对齐故按照首行提供键值对筛选
/** if(null == keys){
* 创建批量操作的{@link StatementWrapper} keys = entity.keySet();
* ps.fillParams(entity.values(), nullTypeMap);
* @param fields 字段列表用于获取对应值 } else{
* @param entities "?"对应参数批次列表 ps.fillParams(MapUtil.valuesOfKeys(entity, keys), nullTypeMap);
* @return {@link StatementWrapper} }
* @throws DbRuntimeException SQL异常 }
*/
public StatementWrapper buildForBatch(final Iterable<String> fields, final Entity... entities) throws DbRuntimeException {
Assert.notBlank(sql, "Sql String must be not blank!");
sqlLog.logForBatch(sql);
final StatementWrapper ps;
try {
ps = StatementWrapper.of(connection.prepareStatement(sql));
final Map<Integer, Integer> nullTypeMap = new HashMap<>();
for (final Entity entity : entities) {
ps.fillParams(MapUtil.valuesOfKeys(entity, fields), nullTypeMap);
ps.addBatch(); ps.addBatch();
} }
} catch (final SQLException e) { } catch (final SQLException e) {
@ -170,12 +167,15 @@ public class StatementBuilder implements Builder<StatementWrapper> {
/** /**
* 创建存储过程或函数调用的{@link StatementWrapper} * 创建存储过程或函数调用的{@link StatementWrapper}
* *
* @return StatementWrapper * @return StatementWrapper{@code null}表示不执行
* @since 6.0.0 * @since 6.0.0
*/ */
public CallableStatement buildForCall() { public CallableStatement buildForCall() {
final String sql = this.boundSql.getSql();
final Object[] params = this.boundSql.getParamArray();
Assert.notBlank(sql, "Sql String must be not blank!"); Assert.notBlank(sql, "Sql String must be not blank!");
sqlLog.log(sql, ArrayUtil.isEmpty(params) ? null : params);
sqlFilter.filter(this.connection, this.boundSql, this.returnGeneratedKey);
try { try {
return (CallableStatement) StatementWrapper return (CallableStatement) StatementWrapper
@ -190,20 +190,23 @@ public class StatementBuilder implements Builder<StatementWrapper> {
/** /**
* 构建{@link StatementWrapper} * 构建{@link StatementWrapper}
* *
* @return {@link StatementWrapper} * @return {@link StatementWrapper}{@code null}表示不执行
* @throws SQLException SQL异常 * @throws SQLException SQL异常
*/ */
private StatementWrapper _build() throws SQLException { private StatementWrapper _build() throws SQLException {
String sql = this.boundSql.getSql();
Object[] params = this.boundSql.getParamArray();
Assert.notBlank(sql, "Sql String must be not blank!"); Assert.notBlank(sql, "Sql String must be not blank!");
if (ArrayUtil.isNotEmpty(params) && 1 == params.length && params[0] instanceof Map) { if (ArrayUtil.isNotEmpty(params) && 1 == params.length && params[0] instanceof Map) {
// 检查参数是否为命名方式的参数 // 检查参数是否为命名方式的参数
final NamedSql namedSql = new NamedSql(sql, Convert.toMap(String.class, Object.class, params[0])); final NamedSql namedSql = new NamedSql(sql, Convert.toMap(String.class, Object.class, params[0]));
sql = namedSql.getSql(); sql = namedSql.getSql();
params = namedSql.getParams(); params = namedSql.getParamArray();
} }
sqlLog.log(sql, ArrayUtil.isEmpty(params) ? null : params); sqlFilter.filter(this.connection, this.boundSql, this.returnGeneratedKey);
final PreparedStatement ps; final PreparedStatement ps;
if (returnGeneratedKey && StrUtil.startWithIgnoreCase(sql, "insert")) { if (returnGeneratedKey && StrUtil.startWithIgnoreCase(sql, "insert")) {
// 插入默认返回主键 // 插入默认返回主键

View File

@ -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);
}

View File

@ -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());
}
}

View File

@ -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;

View File

@ -36,8 +36,8 @@ public class NamedSqlTest {
final NamedSql namedSql = new NamedSql(sql, paramMap); final NamedSql namedSql = new NamedSql(sql, paramMap);
//未指定参数原样输出 //未指定参数原样输出
Assertions.assertEquals("select * from table where id=@id and name = ? and nickName = ?", namedSql.getSql()); Assertions.assertEquals("select * from table where id=@id and name = ? and nickName = ?", namedSql.getSql());
Assertions.assertEquals("张三", namedSql.getParams()[0]); Assertions.assertEquals("张三", namedSql.getParamArray()[0]);
Assertions.assertEquals("小豆豆", namedSql.getParams()[1]); Assertions.assertEquals("小豆豆", namedSql.getParamArray()[1]);
} }
@Test @Test
@ -54,9 +54,9 @@ public class NamedSqlTest {
final NamedSql namedSql = new NamedSql(sql, paramMap); final NamedSql namedSql = new NamedSql(sql, paramMap);
Assertions.assertEquals("select * from table where id=? and name = ? and nickName = ?", namedSql.getSql()); Assertions.assertEquals("select * from table where id=? and name = ? and nickName = ?", namedSql.getSql());
//指定了null参数的依旧替换参数值为null //指定了null参数的依旧替换参数值为null
Assertions.assertNull(namedSql.getParams()[0]); Assertions.assertNull(namedSql.getParamArray()[0]);
Assertions.assertEquals("张三", namedSql.getParams()[1]); Assertions.assertEquals("张三", namedSql.getParamArray()[1]);
Assertions.assertEquals("小豆豆", namedSql.getParams()[2]); Assertions.assertEquals("小豆豆", namedSql.getParamArray()[2]);
} }
@Test @Test
@ -92,9 +92,9 @@ public class NamedSqlTest {
final NamedSql namedSql = new NamedSql(sql, paramMap); final NamedSql namedSql = new NamedSql(sql, paramMap);
Assertions.assertEquals("select * from user where id in (?,?,?)", namedSql.getSql()); Assertions.assertEquals("select * from user where id in (?,?,?)", namedSql.getSql());
Assertions.assertEquals(1, namedSql.getParams()[0]); Assertions.assertEquals(1, namedSql.getParamArray()[0]);
Assertions.assertEquals(2, namedSql.getParams()[1]); Assertions.assertEquals(2, namedSql.getParamArray()[1]);
Assertions.assertEquals(3, namedSql.getParams()[2]); Assertions.assertEquals(3, namedSql.getParamArray()[2]);
} }
@Test @Test

View File

@ -21,7 +21,7 @@
<parent> <parent>
<groupId>org.dromara.hutool</groupId> <groupId>org.dromara.hutool</groupId>
<artifactId>hutool-parent</artifactId> <artifactId>hutool-parent</artifactId>
<version>6.0.0-M10</version> <version>6.0.0-M11</version>
</parent> </parent>
<artifactId>hutool-extra</artifactId> <artifactId>hutool-extra</artifactId>
@ -32,14 +32,14 @@
<Automatic-Module-Name>org.dromara.hutool.extra</Automatic-Module-Name> <Automatic-Module-Name>org.dromara.hutool.extra</Automatic-Module-Name>
<!-- versions --> <!-- versions -->
<velocity.version>2.3</velocity.version> <velocity.version>2.3</velocity.version>
<beetl.version>3.15.8.RELEASE</beetl.version> <beetl.version>3.15.12.RELEASE</beetl.version>
<rythm.version>1.4.2</rythm.version> <rythm.version>1.4.2</rythm.version>
<freemarker.version>2.3.32</freemarker.version> <freemarker.version>2.3.32</freemarker.version>
<enjoy.version>5.1.3</enjoy.version> <enjoy.version>5.1.3</enjoy.version>
<thymeleaf.version>3.1.2.RELEASE</thymeleaf.version> <thymeleaf.version>3.1.2.RELEASE</thymeleaf.version>
<mail.version>1.6.2</mail.version> <mail.version>1.6.2</mail.version>
<jsch.version>0.1.55</jsch.version> <jsch.version>0.1.55</jsch.version>
<sshj.version>0.37.0</sshj.version> <sshj.version>0.38.0</sshj.version>
<zxing.version>3.5.2</zxing.version> <zxing.version>3.5.2</zxing.version>
<net.version>3.9.0</net.version> <net.version>3.9.0</net.version>
<emoji-java.version>5.1.1</emoji-java.version> <emoji-java.version>5.1.1</emoji-java.version>
@ -204,6 +204,14 @@
<artifactId>slf4j-api</artifactId> <artifactId>slf4j-api</artifactId>
<groupId>org.slf4j</groupId> <groupId>org.slf4j</groupId>
</exclusion> </exclusion>
<exclusion>
<artifactId>bcpkix-jdk18on</artifactId>
<groupId>org.bouncycastle</groupId>
</exclusion>
<exclusion>
<artifactId>bcprov-jdk18on</artifactId>
<groupId>org.bouncycastle</groupId>
</exclusion>
</exclusions> </exclusions>
<optional>true</optional> <optional>true</optional>
</dependency> </dependency>
@ -407,7 +415,7 @@
<dependency> <dependency>
<groupId>com.rnkrsoft.bopomofo4j</groupId> <groupId>com.rnkrsoft.bopomofo4j</groupId>
<artifactId>bopomofo4j</artifactId> <artifactId>bopomofo4j</artifactId>
<version>1.0.0</version> <version>1.0.1</version>
<optional>true</optional> <optional>true</optional>
</dependency> </dependency>
<dependency> <dependency>
@ -488,7 +496,7 @@
<dependency> <dependency>
<groupId>org.apache.commons</groupId> <groupId>org.apache.commons</groupId>
<artifactId>commons-compress</artifactId> <artifactId>commons-compress</artifactId>
<version>1.24.0</version> <version>1.25.0</version>
<scope>compile</scope> <scope>compile</scope>
<optional>true</optional> <optional>true</optional>
</dependency> </dependency>
@ -496,7 +504,7 @@
<dependency> <dependency>
<groupId>com.github.oshi</groupId> <groupId>com.github.oshi</groupId>
<artifactId>oshi-core</artifactId> <artifactId>oshi-core</artifactId>
<version>6.4.5</version> <version>6.4.10</version>
<scope>provided</scope> <scope>provided</scope>
<exclusions> <exclusions>
<exclusion> <exclusion>

View File

@ -31,8 +31,11 @@ public class UserInfo implements Serializable{
private final String USER_LANGUAGE; private final String USER_LANGUAGE;
private final String USER_COUNTRY; private final String USER_COUNTRY;
/**
* 构造
*/
public UserInfo(){ public UserInfo(){
USER_NAME = fixPath(SystemUtil.get("user.name", false)); USER_NAME = SystemUtil.get("user.name", false);
USER_HOME = fixPath(SystemUtil.get("user.home", false)); USER_HOME = fixPath(SystemUtil.get("user.home", false));
USER_DIR = fixPath(SystemUtil.get("user.dir", false)); USER_DIR = fixPath(SystemUtil.get("user.dir", false));
JAVA_IO_TMPDIR = fixPath(SystemUtil.get("java.io.tmpdir", false)); JAVA_IO_TMPDIR = fixPath(SystemUtil.get("java.io.tmpdir", false));

View File

@ -21,7 +21,7 @@
<parent> <parent>
<groupId>org.dromara.hutool</groupId> <groupId>org.dromara.hutool</groupId>
<artifactId>hutool-parent</artifactId> <artifactId>hutool-parent</artifactId>
<version>6.0.0-M10</version> <version>6.0.0-M11</version>
</parent> </parent>
<artifactId>hutool-http</artifactId> <artifactId>hutool-http</artifactId>

View File

@ -21,7 +21,7 @@
<parent> <parent>
<groupId>org.dromara.hutool</groupId> <groupId>org.dromara.hutool</groupId>
<artifactId>hutool-parent</artifactId> <artifactId>hutool-parent</artifactId>
<version>6.0.0-M10</version> <version>6.0.0-M11</version>
</parent> </parent>
<artifactId>hutool-json</artifactId> <artifactId>hutool-json</artifactId>
@ -31,7 +31,7 @@
<properties> <properties>
<Automatic-Module-Name>org.dromara.hutool.json</Automatic-Module-Name> <Automatic-Module-Name>org.dromara.hutool.json</Automatic-Module-Name>
<!-- versions --> <!-- versions -->
<bouncycastle.version>1.76</bouncycastle.version> <bouncycastle.version>1.77</bouncycastle.version>
<jjwt.version>0.12.3</jjwt.version> <jjwt.version>0.12.3</jjwt.version>
</properties> </properties>

View File

@ -29,6 +29,7 @@ import org.dromara.hutool.json.JSONTokener;
import org.dromara.hutool.json.xml.JSONXMLUtil; import org.dromara.hutool.json.xml.JSONXMLUtil;
import org.dromara.hutool.json.serialize.GlobalSerializeMapping; import org.dromara.hutool.json.serialize.GlobalSerializeMapping;
import org.dromara.hutool.json.serialize.JSONSerializer; import org.dromara.hutool.json.serialize.JSONSerializer;
import org.dromara.hutool.json.xml.ParseConfig;
import java.io.InputStream; import java.io.InputStream;
import java.io.Reader; import java.io.Reader;
@ -170,7 +171,7 @@ public class JSONObjectMapper {
final String jsonStr = StrUtil.trim(source); final String jsonStr = StrUtil.trim(source);
if (StrUtil.startWith(jsonStr, '<')) { if (StrUtil.startWith(jsonStr, '<')) {
// 可能为XML // 可能为XML
JSONXMLUtil.toJSONObject(jsonObject, jsonStr, false); JSONXMLUtil.toJSONObject(jsonStr, jsonObject, ParseConfig.of());
return; return;
} }
mapFromTokener(new JSONTokener(StrUtil.trim(source), jsonObject.config()), jsonObject); mapFromTokener(new JSONTokener(StrUtil.trim(source), jsonObject.config()), jsonObject);

View File

@ -12,7 +12,9 @@
package org.dromara.hutool.json.xml; package org.dromara.hutool.json.xml;
import org.dromara.hutool.core.text.CharUtil;
import org.dromara.hutool.core.text.StrUtil; import org.dromara.hutool.core.text.StrUtil;
import org.dromara.hutool.core.xml.XmlConstants;
import org.dromara.hutool.json.JSONException; import org.dromara.hutool.json.JSONException;
import org.dromara.hutool.json.JSONObject; import org.dromara.hutool.json.JSONObject;
import org.dromara.hutool.json.mapper.JSONValueMapper; import org.dromara.hutool.json.mapper.JSONValueMapper;
@ -29,28 +31,31 @@ public class JSONXMLParser {
* 转换XML为JSONObject * 转换XML为JSONObject
* 转换过程中一些信息可能会丢失JSON中无法区分节点和属性相同的节点将被处理为JSONArray * 转换过程中一些信息可能会丢失JSON中无法区分节点和属性相同的节点将被处理为JSONArray
* *
* @param jo JSONObject
* @param xmlStr XML字符串 * @param xmlStr XML字符串
* @param keepStrings 如果为{@code true}则值保持String类型不转换为数字或boolean * @param jo JSONObject
* @param parseConfig 解析选项
* @throws JSONException 解析异常 * @throws JSONException 解析异常
*/ */
public static void parseJSONObject(final JSONObject jo, final String xmlStr, final boolean keepStrings) throws JSONException { public static void parseJSONObject(final String xmlStr, final JSONObject jo, final ParseConfig parseConfig) throws JSONException {
final XMLTokener x = new XMLTokener(xmlStr, jo.config()); final XMLTokener x = new XMLTokener(xmlStr, jo.config());
while (x.more() && x.skipPast("<")) { while (x.more() && x.skipPast("<")) {
parse(x, jo, null, keepStrings); parse(x, jo, null, parseConfig, 0);
} }
} }
/** /**
* Scan the content following the named tag, attaching it to the context. * 扫描XML内容并解析到JSONObject中
* *
* @param x The XMLTokener containing the source string. * @param x {@link XMLTokener}
* @param context The JSONObject that will include the new material. * @param context {@link JSONObject}
* @param name The tag name. * @param name 标签名null表示从根标签开始解析
* @return true if the close tag is processed. * @param parseConfig 解析选项
* @param currentNestingDepth 当前层级
* @return {@code true}表示解析完成
* @throws JSONException JSON异常 * @throws JSONException JSON异常
*/ */
private static boolean parse(final XMLTokener x, final JSONObject context, final String name, final boolean keepStrings) throws JSONException { private static boolean parse(final XMLTokener x, final JSONObject context, final String name,
final ParseConfig parseConfig, final int currentNestingDepth) throws JSONException {
final char c; final char c;
int i; int i;
final JSONObject jsonobject; final JSONObject jsonobject;
@ -60,7 +65,7 @@ public class JSONXMLParser {
token = x.nextToken(); token = x.nextToken();
if (token == JSONXMLUtil.BANG) { if (token == XmlConstants.C_BANG) {
c = x.next(); c = x.next();
if (c == '-') { if (c == '-') {
if (x.next() == '-') { if (x.next() == '-') {
@ -86,19 +91,19 @@ public class JSONXMLParser {
token = x.nextMeta(); token = x.nextMeta();
if (token == null) { if (token == null) {
throw x.syntaxError("Missing '>' after '<!'."); throw x.syntaxError("Missing '>' after '<!'.");
} else if (token == JSONXMLUtil.LT) { } else if (token == XmlConstants.C_LT) {
i += 1; i += 1;
} else if (token == JSONXMLUtil.GT) { } else if (token == XmlConstants.C_GT) {
i -= 1; i -= 1;
} }
} while (i > 0); } while (i > 0);
return false; return false;
} else if (token == JSONXMLUtil.QUEST) { } else if (token == XmlConstants.C_QUEST) {
// <? // <?
x.skipPast("?>"); x.skipPast("?>");
return false; return false;
} else if (token == JSONXMLUtil.SLASH) { } else if (token == Character.valueOf(CharUtil.SLASH)) {
// Close tag </ // Close tag </
@ -109,7 +114,7 @@ public class JSONXMLParser {
if (!token.equals(name)) { if (!token.equals(name)) {
throw x.syntaxError("Mismatched " + name + " and " + token); throw x.syntaxError("Mismatched " + name + " and " + token);
} }
if (x.nextToken() != JSONXMLUtil.GT) { if (x.nextToken() != XmlConstants.C_GT) {
throw x.syntaxError("Misshaped close tag"); throw x.syntaxError("Misshaped close tag");
} }
return true; return true;
@ -123,6 +128,7 @@ public class JSONXMLParser {
tagName = (String) token; tagName = (String) token;
token = null; token = null;
jsonobject = new JSONObject(); jsonobject = new JSONObject();
final boolean keepStrings = parseConfig.isKeepStrings();
for (; ; ) { for (; ; ) {
if (token == null) { if (token == null) {
token = x.nextToken(); token = x.nextToken();
@ -132,7 +138,7 @@ public class JSONXMLParser {
if (token instanceof String) { if (token instanceof String) {
string = (String) token; string = (String) token;
token = x.nextToken(); token = x.nextToken();
if (token == JSONXMLUtil.EQ) { if (token == Character.valueOf(CharUtil.EQUAL)) {
token = x.nextToken(); token = x.nextToken();
if (!(token instanceof String)) { if (!(token instanceof String)) {
throw x.syntaxError("Missing value"); throw x.syntaxError("Missing value");
@ -143,9 +149,9 @@ public class JSONXMLParser {
jsonobject.append(string, ""); jsonobject.append(string, "");
} }
} else if (token == JSONXMLUtil.SLASH) { } else if (token == Character.valueOf(CharUtil.SLASH)) {
// Empty tag <.../> // Empty tag <.../>
if (x.nextToken() != JSONXMLUtil.GT) { if (x.nextToken() != XmlConstants.C_GT) {
throw x.syntaxError("Misshaped tag"); throw x.syntaxError("Misshaped tag");
} }
if (!jsonobject.isEmpty()) { if (!jsonobject.isEmpty()) {
@ -155,7 +161,7 @@ public class JSONXMLParser {
} }
return false; return false;
} else if (token == JSONXMLUtil.GT) { } else if (token == XmlConstants.C_GT) {
// Content, between <...> and </...> // Content, between <...> and </...>
for (; ; ) { for (; ; ) {
token = x.nextContent(); token = x.nextContent();
@ -170,9 +176,16 @@ public class JSONXMLParser {
jsonobject.append("content", keepStrings ? token : JSONValueMapper.toJsonValue(string)); jsonobject.append("content", keepStrings ? token : JSONValueMapper.toJsonValue(string));
} }
} else if (token == JSONXMLUtil.LT) { } else if (token == XmlConstants.C_LT) {
// Nested element // Nested element
if (parse(x, jsonobject, tagName, keepStrings)) { // issue#2748 of CVE-2022-45688
final int maxNestingDepth = parseConfig.getMaxNestingDepth();
if (maxNestingDepth > -1 && currentNestingDepth >= maxNestingDepth) {
throw x.syntaxError("Maximum nesting depth of " + maxNestingDepth + " reached");
}
// Nested element
if (parse(x, jsonobject, tagName, parseConfig, currentNestingDepth + 1)) {
if (jsonobject.isEmpty()) { if (jsonobject.isEmpty()) {
context.append(tagName, StrUtil.EMPTY); context.append(tagName, StrUtil.EMPTY);
} else if (jsonobject.size() == 1 && jsonobject.get("content") != null) { } else if (jsonobject.size() == 1 && jsonobject.get("content") != null) {

View File

@ -12,7 +12,6 @@
package org.dromara.hutool.json.xml; package org.dromara.hutool.json.xml;
import org.dromara.hutool.core.text.CharUtil;
import org.dromara.hutool.json.JSONException; import org.dromara.hutool.json.JSONException;
import org.dromara.hutool.json.JSONObject; import org.dromara.hutool.json.JSONObject;
@ -25,51 +24,6 @@ import org.dromara.hutool.json.JSONObject;
*/ */
public class JSONXMLUtil { public class JSONXMLUtil {
/**
* The Character '&amp;'.
*/
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 '&gt;'.
*/
public static final Character GT = '>';
/**
* The Character '&lt;'.
*/
public static final Character LT = '<';
/**
* The Character '?'.
*/
public static final Character QUEST = '?';
/**
* The Character '"'.
*/
public static final Character QUOT = CharUtil.DOUBLE_QUOTES;
/**
* The Character '/'.
*/
public static final Character SLASH = CharUtil.SLASH;
/** /**
* 转换XML为JSONObject * 转换XML为JSONObject
* 转换过程中一些信息可能会丢失JSON中无法区分节点和属性相同的节点将被处理为JSONArray * 转换过程中一些信息可能会丢失JSON中无法区分节点和属性相同的节点将被处理为JSONArray
@ -80,7 +34,7 @@ public class JSONXMLUtil {
* @throws JSONException Thrown if there is an errors while parsing the string * @throws JSONException Thrown if there is an errors while parsing the string
*/ */
public static JSONObject toJSONObject(final String string) throws JSONException { public static JSONObject toJSONObject(final String string) throws JSONException {
return toJSONObject(string, false); return toJSONObject(string, ParseConfig.of());
} }
/** /**
@ -89,13 +43,13 @@ public class JSONXMLUtil {
* Content text may be placed in a "content" member. Comments, prologs, DTDs, and {@code <[ [ ]]>} are ignored. * Content text may be placed in a "content" member. Comments, prologs, DTDs, and {@code <[ [ ]]>} are ignored.
* All values are converted as strings, for 1, 01, 29.0 will not be coerced to numbers but will instead be the exact value as seen in the XML document. * All values are converted as strings, for 1, 01, 29.0 will not be coerced to numbers but will instead be the exact value as seen in the XML document.
* *
* @param string The source string. * @param string XML字符串
* @param keepStrings If true, then values will not be coerced into boolean or numeric values and will instead be left as strings * @param parseConfig XML解析选项
* @return A JSONObject containing the structured data from the XML string. * @return A JSONObject containing the structured data from the XML string.
* @throws JSONException Thrown if there is an errors while parsing the string * @throws JSONException Thrown if there is an errors while parsing the string
*/ */
public static JSONObject toJSONObject(final String string, final boolean keepStrings) throws JSONException { public static JSONObject toJSONObject(final String string, final ParseConfig parseConfig) throws JSONException {
return toJSONObject(new JSONObject(), string, keepStrings); return toJSONObject(string, new JSONObject(), parseConfig);
} }
/** /**
@ -104,13 +58,13 @@ public class JSONXMLUtil {
* *
* @param jo JSONObject * @param jo JSONObject
* @param xmlStr XML字符串 * @param xmlStr XML字符串
* @param keepStrings 如果为{@code true}则值保持String类型不转换为数字或boolean * @param parseConfig XML解析选项
* @return A JSONObject 解析后的JSON对象与传入的jo为同一对象 * @return A JSONObject 解析后的JSON对象与传入的jo为同一对象
* @throws JSONException 解析异常 * @throws JSONException 解析异常
* @since 5.3.1 * @since 5.3.1
*/ */
public static JSONObject toJSONObject(final JSONObject jo, final String xmlStr, final boolean keepStrings) throws JSONException { public static JSONObject toJSONObject(final String xmlStr, final JSONObject jo, final ParseConfig parseConfig) throws JSONException {
JSONXMLParser.parseJSONObject(jo, xmlStr, keepStrings); JSONXMLParser.parseJSONObject(xmlStr, jo, parseConfig);
return jo; return jo;
} }

View File

@ -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;
}
}

View File

@ -12,6 +12,8 @@
package org.dromara.hutool.json.xml; package org.dromara.hutool.json.xml;
import org.dromara.hutool.core.text.CharUtil;
import org.dromara.hutool.core.xml.XmlConstants;
import org.dromara.hutool.json.JSONConfig; import org.dromara.hutool.json.JSONConfig;
import org.dromara.hutool.json.JSONException; import org.dromara.hutool.json.JSONException;
import org.dromara.hutool.json.JSONTokener; import org.dromara.hutool.json.JSONTokener;
@ -31,11 +33,11 @@ public class XMLTokener extends JSONTokener {
static { static {
entity = new java.util.HashMap<>(8); entity = new java.util.HashMap<>(8);
entity.put("amp", JSONXMLUtil.AMP); entity.put("amp", XmlConstants.C_AMP);
entity.put("apos", JSONXMLUtil.APOS); entity.put("apos", XmlConstants.C_APOS);
entity.put("gt", JSONXMLUtil.GT); entity.put("gt", XmlConstants.C_GT);
entity.put("lt", JSONXMLUtil.LT); entity.put("lt", XmlConstants.C_LT);
entity.put("quot", JSONXMLUtil.QUOT); entity.put("quot", CharUtil.DOUBLE_QUOTES);
} }
/** /**
@ -89,7 +91,7 @@ public class XMLTokener extends JSONTokener {
return null; return null;
} }
if (c == '<') { if (c == '<') {
return JSONXMLUtil.LT; return XmlConstants.C_LT;
} }
sb = new StringBuilder(); sb = new StringBuilder();
for (; ; ) { for (; ; ) {
@ -175,17 +177,17 @@ public class XMLTokener extends JSONTokener {
case 0: case 0:
throw syntaxError("Misshaped meta tag"); throw syntaxError("Misshaped meta tag");
case '<': case '<':
return JSONXMLUtil.LT; return XmlConstants.C_LT;
case '>': case '>':
return JSONXMLUtil.GT; return XmlConstants.C_GT;
case '/': case '/':
return JSONXMLUtil.SLASH; return CharUtil.SLASH;
case '=': case '=':
return JSONXMLUtil.EQ; return CharUtil.EQUAL;
case '!': case '!':
return JSONXMLUtil.BANG; return XmlConstants.C_BANG;
case '?': case '?':
return JSONXMLUtil.QUEST; return XmlConstants.C_QUEST;
case '"': case '"':
case '\'': case '\'':
q = c; q = c;
@ -242,15 +244,15 @@ public class XMLTokener extends JSONTokener {
case '<': case '<':
throw syntaxError("Misplaced '<'"); throw syntaxError("Misplaced '<'");
case '>': case '>':
return JSONXMLUtil.GT; return XmlConstants.C_GT;
case '/': case '/':
return JSONXMLUtil.SLASH; return CharUtil.SLASH;
case '=': case '=':
return JSONXMLUtil.EQ; return CharUtil.EQUAL;
case '!': case '!':
return JSONXMLUtil.BANG; return XmlConstants.C_BANG;
case '?': case '?':
return JSONXMLUtil.QUEST; return XmlConstants.C_QUEST;
// Quoted string // Quoted string

View File

@ -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));
});
}
}

View File

@ -21,7 +21,7 @@
<parent> <parent>
<groupId>org.dromara.hutool</groupId> <groupId>org.dromara.hutool</groupId>
<artifactId>hutool-parent</artifactId> <artifactId>hutool-parent</artifactId>
<version>6.0.0-M10</version> <version>6.0.0-M11</version>
</parent> </parent>
<artifactId>hutool-log</artifactId> <artifactId>hutool-log</artifactId>
@ -32,10 +32,10 @@
<Automatic-Module-Name>org.dromara.hutool.log</Automatic-Module-Name> <Automatic-Module-Name>org.dromara.hutool.log</Automatic-Module-Name>
<!-- versions --> <!-- versions -->
<slf4j.version>2.0.9</slf4j.version> <slf4j.version>2.0.9</slf4j.version>
<logback.version>1.4.13</logback.version> <logback.version>1.4.14</logback.version>
<log4j.version>1.2.17</log4j.version> <log4j.version>1.2.17</log4j.version>
<log4j2.version>2.20.0</log4j2.version> <log4j2.version>2.20.0</log4j2.version>
<commons-logging.version>1.2</commons-logging.version> <commons-logging.version>1.3.0</commons-logging.version>
<tinylog.version>1.3.6</tinylog.version> <tinylog.version>1.3.6</tinylog.version>
<tinylog2.version>2.6.2</tinylog2.version> <tinylog2.version>2.6.2</tinylog2.version>
<!-- 固定3.4.x支持到jdk8 --> <!-- 固定3.4.x支持到jdk8 -->

View File

@ -21,7 +21,7 @@
<parent> <parent>
<groupId>org.dromara.hutool</groupId> <groupId>org.dromara.hutool</groupId>
<artifactId>hutool-parent</artifactId> <artifactId>hutool-parent</artifactId>
<version>6.0.0-M10</version> <version>6.0.0-M11</version>
</parent> </parent>
<artifactId>hutool-poi</artifactId> <artifactId>hutool-poi</artifactId>
@ -55,6 +55,16 @@
<artifactId>ofdrw-full</artifactId> <artifactId>ofdrw-full</artifactId>
<version>2.2.4</version> <version>2.2.4</version>
<scope>compile</scope> <scope>compile</scope>
<exclusions>
<exclusion>
<artifactId>bcpkix-jdk15on</artifactId>
<groupId>org.bouncycastle</groupId>
</exclusion>
<exclusion>
<artifactId>bcprov-jdk15on</artifactId>
<groupId>org.bouncycastle</groupId>
</exclusion>
</exclusions>
<optional>true</optional> <optional>true</optional>
</dependency> </dependency>
</dependencies> </dependencies>

View File

@ -21,7 +21,7 @@
<parent> <parent>
<groupId>org.dromara.hutool</groupId> <groupId>org.dromara.hutool</groupId>
<artifactId>hutool-parent</artifactId> <artifactId>hutool-parent</artifactId>
<version>6.0.0-M10</version> <version>6.0.0-M11</version>
</parent> </parent>
<artifactId>hutool-setting</artifactId> <artifactId>hutool-setting</artifactId>

View File

@ -21,7 +21,7 @@
<parent> <parent>
<groupId>org.dromara.hutool</groupId> <groupId>org.dromara.hutool</groupId>
<artifactId>hutool-parent</artifactId> <artifactId>hutool-parent</artifactId>
<version>6.0.0-M10</version> <version>6.0.0-M11</version>
</parent> </parent>
<artifactId>hutool-socket</artifactId> <artifactId>hutool-socket</artifactId>

View File

@ -21,7 +21,7 @@
<parent> <parent>
<groupId>org.dromara.hutool</groupId> <groupId>org.dromara.hutool</groupId>
<artifactId>hutool-parent</artifactId> <artifactId>hutool-parent</artifactId>
<version>6.0.0-M10</version> <version>6.0.0-M11</version>
</parent> </parent>
<artifactId>hutool-swing</artifactId> <artifactId>hutool-swing</artifactId>

View File

@ -27,7 +27,7 @@ public class CaptchaUtil {
* @param height 图片高 * @param height 图片高
* @return {@link LineCaptcha} * @return {@link LineCaptcha}
*/ */
public static LineCaptcha createLineCaptcha(final int width, final int height) { public static LineCaptcha ofLineCaptcha(final int width, final int height) {
return new LineCaptcha(width, height); return new LineCaptcha(width, height);
} }
@ -40,7 +40,7 @@ public class CaptchaUtil {
* @param lineCount 干扰线条数 * @param lineCount 干扰线条数
* @return {@link LineCaptcha} * @return {@link LineCaptcha}
*/ */
public static LineCaptcha createLineCaptcha(final int width, final int height, final int codeCount, final int lineCount) { public static LineCaptcha ofLineCaptcha(final int width, final int height, final int codeCount, final int lineCount) {
return new LineCaptcha(width, height, codeCount, lineCount); return new LineCaptcha(width, height, codeCount, lineCount);
} }
@ -52,7 +52,7 @@ public class CaptchaUtil {
* @return {@link CircleCaptcha} * @return {@link CircleCaptcha}
* @since 3.2.3 * @since 3.2.3
*/ */
public static CircleCaptcha createCircleCaptcha(final int width, final int height) { public static CircleCaptcha ofCircleCaptcha(final int width, final int height) {
return new CircleCaptcha(width, height); return new CircleCaptcha(width, height);
} }
@ -66,7 +66,7 @@ public class CaptchaUtil {
* @return {@link CircleCaptcha} * @return {@link CircleCaptcha}
* @since 3.2.3 * @since 3.2.3
*/ */
public static CircleCaptcha createCircleCaptcha(final int width, final int height, final int codeCount, final int circleCount) { public static CircleCaptcha ofCircleCaptcha(final int width, final int height, final int codeCount, final int circleCount) {
return new CircleCaptcha(width, height, codeCount, circleCount); return new CircleCaptcha(width, height, codeCount, circleCount);
} }
@ -78,7 +78,7 @@ public class CaptchaUtil {
* @return {@link ShearCaptcha} * @return {@link ShearCaptcha}
* @since 3.2.3 * @since 3.2.3
*/ */
public static ShearCaptcha createShearCaptcha(final int width, final int height) { public static ShearCaptcha ofShearCaptcha(final int width, final int height) {
return new ShearCaptcha(width, height); return new ShearCaptcha(width, height);
} }
@ -92,7 +92,7 @@ public class CaptchaUtil {
* @return {@link ShearCaptcha} * @return {@link ShearCaptcha}
* @since 3.3.0 * @since 3.3.0
*/ */
public static ShearCaptcha createShearCaptcha(final int width, final int height, final int codeCount, final int thickness) { public static ShearCaptcha ofShearCaptcha(final int width, final int height, final int codeCount, final int thickness) {
return new ShearCaptcha(width, height, codeCount, thickness); return new ShearCaptcha(width, height, codeCount, thickness);
} }
@ -103,7 +103,7 @@ public class CaptchaUtil {
* @param height * @param height
* @return {@link GifCaptcha} * @return {@link GifCaptcha}
*/ */
public static GifCaptcha createGifCaptcha(final int width, final int height) { public static GifCaptcha ofGifCaptcha(final int width, final int height) {
return new GifCaptcha(width, height); return new GifCaptcha(width, height);
} }
@ -115,7 +115,7 @@ public class CaptchaUtil {
* @param codeCount 字符个数 * @param codeCount 字符个数
* @return {@link GifCaptcha} * @return {@link GifCaptcha}
*/ */
public static GifCaptcha createGifCaptcha(final int width, final int height, final int codeCount) { public static GifCaptcha ofGifCaptcha(final int width, final int height, final int codeCount) {
return new GifCaptcha(width, height, codeCount); return new GifCaptcha(width, height, codeCount);
} }
} }

View File

@ -14,6 +14,8 @@ package org.dromara.hutool.swing.captcha;
import org.dromara.hutool.core.util.ObjUtil; import org.dromara.hutool.core.util.ObjUtil;
import org.dromara.hutool.core.util.RandomUtil; import org.dromara.hutool.core.util.RandomUtil;
import org.dromara.hutool.swing.captcha.generator.CodeGenerator;
import org.dromara.hutool.swing.captcha.generator.RandomGenerator;
import org.dromara.hutool.swing.img.color.ColorUtil; import org.dromara.hutool.swing.img.color.ColorUtil;
import org.dromara.hutool.swing.img.GraphicsUtil; import org.dromara.hutool.swing.img.GraphicsUtil;
@ -63,7 +65,19 @@ public class CircleCaptcha extends AbstractCaptcha {
* @param interfereCount 验证码干扰元素个数 * @param interfereCount 验证码干扰元素个数
*/ */
public CircleCaptcha(final int width, final int height, final int codeCount, final int interfereCount) { public CircleCaptcha(final int width, final int height, final int codeCount, final int interfereCount) {
super(width, height, codeCount, interfereCount); this(width, height, new RandomGenerator(codeCount), interfereCount);
}
/**
* 构造
*
* @param width 图片宽
* @param height 图片高
* @param generator 验证码生成器
* @param interfereCount 验证码干扰元素个数
*/
public CircleCaptcha(final int width, final int height, final CodeGenerator generator, final int interfereCount) {
super(width, height, generator, interfereCount);
} }
@Override @Override

View File

@ -16,6 +16,8 @@ package org.dromara.hutool.swing.captcha;
import org.dromara.hutool.core.util.ObjUtil; import org.dromara.hutool.core.util.ObjUtil;
import org.dromara.hutool.core.util.RandomUtil; import org.dromara.hutool.core.util.RandomUtil;
import com.madgag.gif.fmsware.AnimatedGifEncoder; import com.madgag.gif.fmsware.AnimatedGifEncoder;
import org.dromara.hutool.swing.captcha.generator.CodeGenerator;
import org.dromara.hutool.swing.captcha.generator.RandomGenerator;
import java.awt.AlphaComposite; import java.awt.AlphaComposite;
import java.awt.Color; import java.awt.Color;
@ -59,7 +61,29 @@ public class GifCaptcha extends AbstractCaptcha {
* @param codeCount 验证码个数 * @param codeCount 验证码个数
*/ */
public GifCaptcha(final int width, final int height, final int codeCount) { public GifCaptcha(final int width, final int height, final int codeCount) {
super(width, height, codeCount, 10); this(width, height, codeCount, 10);
}
/**
* @param width 验证码宽度
* @param height 验证码高度
* @param codeCount 验证码个数
* @param interfereCount 干扰个数
*/
public GifCaptcha(final int width, final int height, final int codeCount, final int interfereCount) {
this(width, height, new RandomGenerator(codeCount), interfereCount);
}
/**
* 构造
*
* @param width 图片宽
* @param height 图片高
* @param generator 验证码生成器
* @param interfereCount 验证码干扰元素个数
*/
public GifCaptcha(final int width, final int height, final CodeGenerator generator, final int interfereCount) {
super(width, height, generator, interfereCount);
} }
/** /**
@ -178,9 +202,9 @@ public class GifCaptcha extends AbstractCaptcha {
g2d.setComposite(ac); g2d.setComposite(ac);
g2d.setColor(fontColor[i]); g2d.setColor(fontColor[i]);
g2d.drawOval( g2d.drawOval(
RandomUtil.randomInt(width), RandomUtil.randomInt(width),
RandomUtil.randomInt(height), RandomUtil.randomInt(height),
RandomUtil.randomInt(5, 30), 5 + RandomUtil.randomInt(5, 30) RandomUtil.randomInt(5, 30), 5 + RandomUtil.randomInt(5, 30)
);//绘制椭圆边框 );//绘制椭圆边框
g2d.drawString(words[i] + "", x + (font.getSize() + m) * i, y); g2d.drawString(words[i] + "", x + (font.getSize() + m) * i, y);
} }
@ -223,8 +247,8 @@ public class GifCaptcha extends AbstractCaptcha {
max = 255; max = 255;
} }
return new Color( return new Color(
RandomUtil.randomInt(min, max), RandomUtil.randomInt(min, max),
RandomUtil.randomInt(min, max), RandomUtil.randomInt(min, max),
RandomUtil.randomInt(min, max)); RandomUtil.randomInt(min, max));
} }
} }

View File

@ -14,6 +14,8 @@ package org.dromara.hutool.swing.captcha;
import org.dromara.hutool.core.util.ObjUtil; import org.dromara.hutool.core.util.ObjUtil;
import org.dromara.hutool.core.util.RandomUtil; import org.dromara.hutool.core.util.RandomUtil;
import org.dromara.hutool.swing.captcha.generator.CodeGenerator;
import org.dromara.hutool.swing.captcha.generator.RandomGenerator;
import org.dromara.hutool.swing.img.color.ColorUtil; import org.dromara.hutool.swing.img.color.ColorUtil;
import org.dromara.hutool.swing.img.GraphicsUtil; import org.dromara.hutool.swing.img.GraphicsUtil;
@ -53,7 +55,19 @@ public class LineCaptcha extends AbstractCaptcha {
* @param lineCount 干扰线条数 * @param lineCount 干扰线条数
*/ */
public LineCaptcha(final int width, final int height, final int codeCount, final int lineCount) { public LineCaptcha(final int width, final int height, final int codeCount, final int lineCount) {
super(width, height, codeCount, lineCount); this(width, height, new RandomGenerator(codeCount), lineCount);
}
/**
* 构造
*
* @param width 图片宽
* @param height 图片高
* @param generator 验证码生成器
* @param interfereCount 验证码干扰元素个数
*/
public LineCaptcha(final int width, final int height, final CodeGenerator generator, final int interfereCount) {
super(width, height, generator, interfereCount);
} }
// -------------------------------------------------------------------- Constructor end // -------------------------------------------------------------------- Constructor end

View File

@ -14,6 +14,8 @@ package org.dromara.hutool.swing.captcha;
import org.dromara.hutool.core.util.ObjUtil; import org.dromara.hutool.core.util.ObjUtil;
import org.dromara.hutool.core.util.RandomUtil; import org.dromara.hutool.core.util.RandomUtil;
import org.dromara.hutool.swing.captcha.generator.CodeGenerator;
import org.dromara.hutool.swing.captcha.generator.RandomGenerator;
import org.dromara.hutool.swing.img.color.ColorUtil; import org.dromara.hutool.swing.img.color.ColorUtil;
import org.dromara.hutool.swing.img.GraphicsUtil; import org.dromara.hutool.swing.img.GraphicsUtil;
@ -63,7 +65,19 @@ public class ShearCaptcha extends AbstractCaptcha {
* @param thickness 干扰线宽度 * @param thickness 干扰线宽度
*/ */
public ShearCaptcha(final int width, final int height, final int codeCount, final int thickness) { public ShearCaptcha(final int width, final int height, final int codeCount, final int thickness) {
super(width, height, codeCount, thickness); this(width, height, new RandomGenerator(codeCount), thickness);
}
/**
* 构造
*
* @param width 图片宽
* @param height 图片高
* @param generator 验证码生成器
* @param interfereCount 验证码干扰元素个数
*/
public ShearCaptcha(final int width, final int height, final CodeGenerator generator, final int interfereCount) {
super(width, height, generator, interfereCount);
} }
@Override @Override

View File

@ -30,7 +30,7 @@ public class CaptchaTest {
@Test @Test
public void lineCaptchaTest1() { public void lineCaptchaTest1() {
// 定义图形验证码的长和宽 // 定义图形验证码的长和宽
final LineCaptcha lineCaptcha = CaptchaUtil.createLineCaptcha(200, 100); final LineCaptcha lineCaptcha = CaptchaUtil.ofLineCaptcha(200, 100);
Assertions.assertNotNull(lineCaptcha.getCode()); Assertions.assertNotNull(lineCaptcha.getCode());
Assertions.assertTrue(lineCaptcha.verify(lineCaptcha.getCode())); Assertions.assertTrue(lineCaptcha.verify(lineCaptcha.getCode()));
} }
@ -39,7 +39,7 @@ public class CaptchaTest {
@Disabled @Disabled
public void lineCaptchaTest3() { public void lineCaptchaTest3() {
// 定义图形验证码的长和宽 // 定义图形验证码的长和宽
final LineCaptcha lineCaptcha = CaptchaUtil.createLineCaptcha(200, 70, 4, 15); final LineCaptcha lineCaptcha = CaptchaUtil.ofLineCaptcha(200, 70, 4, 15);
lineCaptcha.setBackground(Color.yellow); lineCaptcha.setBackground(Color.yellow);
lineCaptcha.write("f:/test/captcha/tellow.png"); lineCaptcha.write("f:/test/captcha/tellow.png");
} }
@ -48,7 +48,7 @@ public class CaptchaTest {
@Disabled @Disabled
public void lineCaptchaWithMathTest() { public void lineCaptchaWithMathTest() {
// 定义图形验证码的长和宽 // 定义图形验证码的长和宽
final LineCaptcha lineCaptcha = CaptchaUtil.createLineCaptcha(200, 80); final LineCaptcha lineCaptcha = CaptchaUtil.ofLineCaptcha(200, 80);
lineCaptcha.setGenerator(new MathGenerator()); lineCaptcha.setGenerator(new MathGenerator());
lineCaptcha.setTextAlpha(0.8f); lineCaptcha.setTextAlpha(0.8f);
lineCaptcha.write("f:/captcha/math.png"); lineCaptcha.write("f:/captcha/math.png");
@ -59,7 +59,7 @@ public class CaptchaTest {
public void lineCaptchaTest2() { public void lineCaptchaTest2() {
// 定义图形验证码的长和宽 // 定义图形验证码的长和宽
final LineCaptcha lineCaptcha = CaptchaUtil.createLineCaptcha(200, 100); final LineCaptcha lineCaptcha = CaptchaUtil.ofLineCaptcha(200, 100);
// LineCaptcha lineCaptcha = new LineCaptcha(200, 100, 4, 150); // LineCaptcha lineCaptcha = new LineCaptcha(200, 100, 4, 150);
// 图形验证码写出可以写出到文件也可以写出到流 // 图形验证码写出可以写出到文件也可以写出到流
lineCaptcha.write("f:/captcha/line.png"); lineCaptcha.write("f:/captcha/line.png");
@ -79,7 +79,7 @@ public class CaptchaTest {
public void circleCaptchaTest() { public void circleCaptchaTest() {
// 定义图形验证码的长和宽 // 定义图形验证码的长和宽
final CircleCaptcha captcha = CaptchaUtil.createCircleCaptcha(200, 100, 4, 20); final CircleCaptcha captcha = CaptchaUtil.ofCircleCaptcha(200, 100, 4, 20);
// CircleCaptcha captcha = new CircleCaptcha(200, 100, 4, 20); // CircleCaptcha captcha = new CircleCaptcha(200, 100, 4, 20);
// 图形验证码写出可以写出到文件也可以写出到流 // 图形验证码写出可以写出到文件也可以写出到流
captcha.write("f:/captcha/circle.png"); captcha.write("f:/captcha/circle.png");
@ -92,7 +92,7 @@ public class CaptchaTest {
public void shearCaptchaTest() { public void shearCaptchaTest() {
// 定义图形验证码的长和宽 // 定义图形验证码的长和宽
final ShearCaptcha captcha = CaptchaUtil.createShearCaptcha(203, 100, 4, 4); final ShearCaptcha captcha = CaptchaUtil.ofShearCaptcha(203, 100, 4, 4);
// ShearCaptcha captcha = new ShearCaptcha(200, 100, 4, 4); // ShearCaptcha captcha = new ShearCaptcha(200, 100, 4, 4);
// 图形验证码写出可以写出到文件也可以写出到流 // 图形验证码写出可以写出到文件也可以写出到流
captcha.write("f:/captcha/shear.png"); captcha.write("f:/captcha/shear.png");
@ -116,7 +116,7 @@ public class CaptchaTest {
@Disabled @Disabled
public void ShearCaptchaWithMathTest() { public void ShearCaptchaWithMathTest() {
// 定义图形验证码的长和宽 // 定义图形验证码的长和宽
final ShearCaptcha captcha = CaptchaUtil.createShearCaptcha(200, 45, 4, 4); final ShearCaptcha captcha = CaptchaUtil.ofShearCaptcha(200, 45, 4, 4);
captcha.setGenerator(new MathGenerator()); captcha.setGenerator(new MathGenerator());
// ShearCaptcha captcha = new ShearCaptcha(200, 100, 4, 4); // ShearCaptcha captcha = new ShearCaptcha(200, 100, 4, 4);
// 图形验证码写出可以写出到文件也可以写出到流 // 图形验证码写出可以写出到文件也可以写出到流
@ -128,7 +128,7 @@ public class CaptchaTest {
@Test @Test
@Disabled @Disabled
public void GifCaptchaTest() { public void GifCaptchaTest() {
final GifCaptcha captcha = CaptchaUtil.createGifCaptcha(200, 100, 4); final GifCaptcha captcha = CaptchaUtil.ofGifCaptcha(200, 100, 4);
captcha.write("d:/test/gif_captcha.gif"); captcha.write("d:/test/gif_captcha.gif");
assert captcha.verify(captcha.getCode()); assert captcha.verify(captcha.getCode());
} }
@ -136,7 +136,7 @@ public class CaptchaTest {
@Test @Test
@Disabled @Disabled
public void bgTest(){ public void bgTest(){
final LineCaptcha captcha = CaptchaUtil.createLineCaptcha(200, 100, 4, 1); final LineCaptcha captcha = CaptchaUtil.ofLineCaptcha(200, 100, 4, 1);
captcha.setBackground(Color.WHITE); captcha.setBackground(Color.WHITE);
captcha.write("d:/test/test.jpg"); captcha.write("d:/test/test.jpg");
} }

View File

@ -21,7 +21,7 @@ public class CaptchaUtilTest {
@Disabled @Disabled
public void createTest() { public void createTest() {
for(int i = 0; i < 1; i++) { for(int i = 0; i < 1; i++) {
CaptchaUtil.createShearCaptcha(320, 240); CaptchaUtil.ofShearCaptcha(320, 240);
} }
} }
} }

View File

@ -20,7 +20,7 @@
<groupId>org.dromara.hutool</groupId> <groupId>org.dromara.hutool</groupId>
<artifactId>hutool-parent</artifactId> <artifactId>hutool-parent</artifactId>
<version>6.0.0-M10</version> <version>6.0.0-M11</version>
<name>hutool</name> <name>hutool</name>
<description> <description>
Hutool是一个功能丰富且易用的Java工具库通过诸多实用工具类的使用旨在帮助开发者快速、便捷地完成各类开发任务。这些封装的工具涵盖了字符串、数字、集合、编码、日期、文件、IO、加密、数据库JDBC、JSON、HTTP客户端等一系列操作可以满足各种不同的开发需求。 Hutool是一个功能丰富且易用的Java工具库通过诸多实用工具类的使用旨在帮助开发者快速、便捷地完成各类开发任务。这些封装的工具涵盖了字符串、数字、集合、编码、日期、文件、IO、加密、数据库JDBC、JSON、HTTP客户端等一系列操作可以满足各种不同的开发需求。