diff --git a/CHANGELOG.md b/CHANGELOG.md index 6afff4a3c..9a6601673 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ ------------------------------------------------------------------------------------------------------------- -# 6.0.0-M10 (2023-12-28) +# 6.0.0-M11 (2024-01-11) ### 计划实现 * 【poi 】 Markdown相关(如HTML转换等),基于commonmark-java diff --git a/README-EN.md b/README-EN.md index c71a5d51f..20a0901ae 100755 --- a/README-EN.md +++ b/README-EN.md @@ -144,18 +144,18 @@ We provide the T-Shirt and Sweater with Hutool Logo, please visit the shop: org.dromara.hutool hutool-all - 6.0.0-M10 + 6.0.0-M11 ``` ### 🍐Gradle ``` -implementation 'org.dromara.hutool:hutool-all:6.0.0-M10' +implementation 'org.dromara.hutool:hutool-all:6.0.0-M11' ``` ## 📥Download -- [Maven Repo](https://repo1.maven.org/maven2/cn/hutool/hutool-all/6.0.0-M10/) +- [Maven Repo](https://repo1.maven.org/maven2/cn/hutool/hutool-all/6.0.0-M11/) > 🔔️note: > Hutool 5.x supports JDK8+ and is not tested on Android platforms, and cannot guarantee that all tool classes or tool methods are available. diff --git a/README.md b/README.md index b2cc82c74..f65dccc66 100755 --- a/README.md +++ b/README.md @@ -139,21 +139,21 @@ org.dromara.hutool hutool-all - 6.0.0-M10 + 6.0.0-M11 ``` ### 🍐Gradle ``` -implementation 'org.dromara.hutool:hutool-all:6.0.0-M10' +implementation 'org.dromara.hutool:hutool-all:6.0.0-M11' ``` ### 📥下载jar 点击以下链接,下载`hutool-all-X.X.X.jar`即可: -- [Maven中央库](https://repo1.maven.org/maven2/org/dromara/hutool/hutool-all/6.0.0-M10/) +- [Maven中央库](https://repo1.maven.org/maven2/org/dromara/hutool/hutool-all/6.0.0-M11/) > 🔔️注意 > Hutool 6.x支持JDK8+,对Android平台没有测试,不能保证所有工具类或工具方法可用。 diff --git a/bin/version.txt b/bin/version.txt index 76f9249ad..bdfaba25f 100644 --- a/bin/version.txt +++ b/bin/version.txt @@ -1 +1 @@ -6.0.0-M10 +6.0.0-M11 diff --git a/docs/js/version.js b/docs/js/version.js index 2221a2e15..afee3da9a 100755 --- a/docs/js/version.js +++ b/docs/js/version.js @@ -1 +1 @@ -var version = '6.0.0-M10' \ No newline at end of file +var version = '6.0.0-M11' \ No newline at end of file diff --git a/hutool-all/pom.xml b/hutool-all/pom.xml index d7a896e56..1317ca64f 100755 --- a/hutool-all/pom.xml +++ b/hutool-all/pom.xml @@ -21,7 +21,7 @@ org.dromara.hutool hutool-parent - 6.0.0-M10 + 6.0.0-M11 hutool-all diff --git a/hutool-bom/pom.xml b/hutool-bom/pom.xml index 231002101..411a1ff45 100755 --- a/hutool-bom/pom.xml +++ b/hutool-bom/pom.xml @@ -21,7 +21,7 @@ org.dromara.hutool hutool-parent - 6.0.0-M10 + 6.0.0-M11 hutool-bom diff --git a/hutool-core/pom.xml b/hutool-core/pom.xml index f83eb80fb..8c247ce36 100755 --- a/hutool-core/pom.xml +++ b/hutool-core/pom.xml @@ -21,7 +21,7 @@ org.dromara.hutool hutool-parent - 6.0.0-M10 + 6.0.0-M11 hutool-core diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/compress/ZipUtil.java b/hutool-core/src/main/java/org/dromara/hutool/core/compress/ZipUtil.java index 1afb561c8..a947041ac 100644 --- a/hutool-core/src/main/java/org/dromara/hutool/core/compress/ZipUtil.java +++ b/hutool-core/src/main/java/org/dromara/hutool/core/compress/ZipUtil.java @@ -33,6 +33,7 @@ import org.dromara.hutool.core.util.ObjUtil; import java.io.*; import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.nio.file.FileSystem; import java.nio.file.*; import java.util.ArrayList; @@ -726,7 +727,7 @@ public class ZipUtil { * Gzip压缩处理 * * @param content 被压缩的字符串 - * @param charset 编码 + * @param charset 编码 {@link StandardCharsets#UTF_8}、 {@link CharsetUtil#UTF_8} * @return 压缩后的字节流 * @throws HutoolException IO异常 */ diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/lang/wrapper/SimpleWrapper.java b/hutool-core/src/main/java/org/dromara/hutool/core/lang/wrapper/SimpleWrapper.java index c67ba52e7..a1d199af2 100644 --- a/hutool-core/src/main/java/org/dromara/hutool/core/lang/wrapper/SimpleWrapper.java +++ b/hutool-core/src/main/java/org/dromara/hutool/core/lang/wrapper/SimpleWrapper.java @@ -13,7 +13,8 @@ package org.dromara.hutool.core.lang.wrapper; /** - * 简单包装对象 + * 简单包装对象
+ * 通过继承此类,可以直接使用被包装的对象,用于简化和统一封装。 * * @param 被包装对象类型 * @author looly diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/math/NumberParser.java b/hutool-core/src/main/java/org/dromara/hutool/core/math/NumberParser.java index 9038c4685..3c8779b23 100644 --- a/hutool-core/src/main/java/org/dromara/hutool/core/math/NumberParser.java +++ b/hutool-core/src/main/java/org/dromara/hutool/core/math/NumberParser.java @@ -297,6 +297,7 @@ public class NumberParser { // issue#I79VS7 numberStr = StrUtil.subSuf(numberStr, 1); } + try { final NumberFormat format = NumberFormat.getInstance(locale); if (format instanceof DecimalFormat) { diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/math/NumberUtil.java b/hutool-core/src/main/java/org/dromara/hutool/core/math/NumberUtil.java index e81fd333e..561a86959 100644 --- a/hutool-core/src/main/java/org/dromara/hutool/core/math/NumberUtil.java +++ b/hutool-core/src/main/java/org/dromara/hutool/core/math/NumberUtil.java @@ -898,12 +898,12 @@ public class NumberUtil extends NumberValidator { } // Float、Double等有精度问题,转换为字符串后再转换 - return toBigDecimal(number.toString()); + return new BigDecimal(number.toString()); } /** * 数字转{@link BigDecimal}
- * null或""或空白符转换为0 + * null或""或"NaN"或空白符转换为0 * * @param numberStr 数字字符串 * @return {@link BigDecimal} @@ -927,7 +927,7 @@ public class NumberUtil extends NumberValidator { /** * 数字转{@link BigInteger}
- * null转换为0 + * null或"NaN"转换为0 * * @param number 数字 * @return {@link BigInteger} diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/text/StrUtil.java b/hutool-core/src/main/java/org/dromara/hutool/core/text/StrUtil.java index fb52e0b62..ebb26bae8 100644 --- a/hutool-core/src/main/java/org/dromara/hutool/core/text/StrUtil.java +++ b/hutool-core/src/main/java/org/dromara/hutool/core/text/StrUtil.java @@ -15,6 +15,7 @@ package org.dromara.hutool.core.text; import org.dromara.hutool.core.array.ArrayUtil; import org.dromara.hutool.core.func.FunctionPool; import org.dromara.hutool.core.text.placeholder.StrFormatter; +import org.dromara.hutool.core.text.split.SplitUtil; import org.dromara.hutool.core.util.CharsetUtil; import java.io.StringReader; @@ -27,6 +28,13 @@ import java.util.Map; * 字符串工具类
* 此工具主要针对单个字符串的操作 * + *

本工具类,v6.x进行了拆分。 + * 字符串分割split参考:{@link SplitUtil}   
+ * 多字符串判空hasBlank参考:{@link ArrayUtil} + *

+ * @see SplitUtil#split(CharSequence, CharSequence) 对字符串分割 + * @see ArrayUtil#hasBlank(CharSequence...) 对多个字符串判空 + * * @author Looly */ public class StrUtil extends CharSequenceUtil implements StrPool { diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/xml/XmlConstants.java b/hutool-core/src/main/java/org/dromara/hutool/core/xml/XmlConstants.java index 01977d2ed..b77d2af83 100755 --- a/hutool-core/src/main/java/org/dromara/hutool/core/xml/XmlConstants.java +++ b/hutool-core/src/main/java/org/dromara/hutool/core/xml/XmlConstants.java @@ -12,6 +12,8 @@ package org.dromara.hutool.core.xml; +import org.dromara.hutool.core.text.CharUtil; + import java.util.regex.Pattern; /** @@ -30,6 +32,10 @@ public class XmlConstants { * 字符串常量:XML And 符转义 {@code "&" -> "&"} */ public static final String AMP = "&"; + /** + * The Character '&'. + */ + public static final Character C_AMP = CharUtil.AMP; /** * 字符串常量:XML 双引号转义 {@code """ -> "\""} @@ -40,17 +46,41 @@ public class XmlConstants { * 字符串常量:XML 单引号转义 {@code "&apos" -> "'"} */ public static final String APOS = "'"; + /** + * The Character '''. + */ + public static final Character C_APOS = CharUtil.SINGLE_QUOTE; /** * 字符串常量:XML 小于号转义 {@code "<" -> "<"} */ public static final String LT = "<"; + /** + * The Character '<'. + */ + public static final Character C_LT = '<'; + /** * 字符串常量:XML 大于号转义 {@code ">" -> ">"} */ public static final String GT = ">"; + /** + * The Character '>'. + */ + public static final Character C_GT = '>'; + + /** + * The Character '!'. + */ + public static final Character C_BANG = '!'; + + /** + * The Character '?'. + */ + public static final Character C_QUEST = '?'; + /** * 在XML中无效的字符 正则 */ diff --git a/hutool-core/src/test/java/org/dromara/hutool/core/math/Issue3423Test.java b/hutool-core/src/test/java/org/dromara/hutool/core/math/Issue3423Test.java new file mode 100644 index 000000000..9579a300f --- /dev/null +++ b/hutool-core/src/test/java/org/dromara/hutool/core/math/Issue3423Test.java @@ -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()); + } +} diff --git a/hutool-cron/pom.xml b/hutool-cron/pom.xml index d7a4bd4f7..4c4f7350a 100755 --- a/hutool-cron/pom.xml +++ b/hutool-cron/pom.xml @@ -21,7 +21,7 @@ org.dromara.hutool hutool-parent - 6.0.0-M10 + 6.0.0-M11 hutool-cron diff --git a/hutool-cron/src/main/java/org/dromara/hutool/cron/CronConfig.java b/hutool-cron/src/main/java/org/dromara/hutool/cron/CronConfig.java index 47fbe1a6b..1df148ebf 100644 --- a/hutool-cron/src/main/java/org/dromara/hutool/cron/CronConfig.java +++ b/hutool-cron/src/main/java/org/dromara/hutool/cron/CronConfig.java @@ -31,6 +31,9 @@ public class CronConfig { */ protected boolean matchSecond; + /** + * 构造 + */ public CronConfig(){ } diff --git a/hutool-cron/src/main/java/org/dromara/hutool/cron/CronTimer.java b/hutool-cron/src/main/java/org/dromara/hutool/cron/CronTimer.java index 085f01818..3ad60d746 100644 --- a/hutool-cron/src/main/java/org/dromara/hutool/cron/CronTimer.java +++ b/hutool-cron/src/main/java/org/dromara/hutool/cron/CronTimer.java @@ -13,6 +13,7 @@ package org.dromara.hutool.cron; import org.dromara.hutool.core.date.DateUnit; +import org.dromara.hutool.core.lang.Console; import org.dromara.hutool.core.thread.ThreadUtil; import org.dromara.hutool.log.Log; @@ -63,9 +64,14 @@ public class CronTimer extends Thread implements Serializable { //等待直到下一个时间点,如果被中断直接退出Timer break; } + //执行点,时间记录为执行开始的时间,而非结束时间 - thisTime = System.currentTimeMillis(); - spawnLauncher(thisTime); + spawnLauncher(nextTime); + + // issue#3460 采用叠加方式,确保正好是1分钟或1秒,避免sleep晚醒问题 + // 此处无需校验,因为每次循环都是sleep与上触发点的时间差。 + // 当上一次晚醒后,本次会减少sleep时间,保证误差在一个unit内,并不断修正。 + thisTime = nextTime; } else{ // 非正常时间重新计算(issue#1224@Github) thisTime = System.currentTimeMillis(); diff --git a/hutool-cron/src/test/java/org/dromara/hutool/cron/demo/DeamonMainTest.java b/hutool-cron/src/test/java/org/dromara/hutool/cron/demo/DeamonMainTest.java index c7f982aff..40188baed 100644 --- a/hutool-cron/src/test/java/org/dromara/hutool/cron/demo/DeamonMainTest.java +++ b/hutool-cron/src/test/java/org/dromara/hutool/cron/demo/DeamonMainTest.java @@ -19,7 +19,7 @@ import org.dromara.hutool.cron.task.InvokeTask; public class DeamonMainTest { public static void main(final String[] args) { // 测试守护线程是否对作业线程有效 - CronUtil.schedule("*/2 * * * * *", new InvokeTask("demo.org.dromara.hutool.cron.TestJob.doWhileTest")); + CronUtil.schedule("*/2 * * * * *", new InvokeTask("org.dromara.hutool.cron.demo.TestJob.doWhileTest")); // 当为守护线程时,stop方法调用后doWhileTest里的循环输出将终止,表示作业线程正常结束 // 当非守护线程时,stop方法调用后,不再产生新的作业,原作业正常执行。 CronUtil.setMatchSecond(true); diff --git a/hutool-cron/src/test/java/org/dromara/hutool/cron/demo/TestJob2.java b/hutool-cron/src/test/java/org/dromara/hutool/cron/demo/TestJob2.java index 00a7e20d7..b3baf4197 100644 --- a/hutool-cron/src/test/java/org/dromara/hutool/cron/demo/TestJob2.java +++ b/hutool-cron/src/test/java/org/dromara/hutool/cron/demo/TestJob2.java @@ -14,6 +14,7 @@ package org.dromara.hutool.cron.demo; import java.util.concurrent.TimeUnit; +import org.dromara.hutool.core.date.DateUtil; import org.dromara.hutool.core.lang.Console; import org.dromara.hutool.core.thread.ThreadUtil; @@ -29,7 +30,7 @@ public class TestJob2 { * 执行定时任务内容 */ public void doTest() { - Console.log("TestJob2.doTest开始执行……"); + Console.log("TestJob2.doTest开始执行…… at [{}]", DateUtil.formatNow()); ThreadUtil.sleep(20, TimeUnit.SECONDS); Console.log("延迟20s打印testJob2"); } diff --git a/hutool-cron/src/test/resources/config/cron.setting b/hutool-cron/src/test/resources/config/cron.setting index 4e2860de9..62faca9fc 100644 --- a/hutool-cron/src/test/resources/config/cron.setting +++ b/hutool-cron/src/test/resources/config/cron.setting @@ -8,7 +8,7 @@ # demo.org.dromara.hutool.cron.TestJob.doTest = */1 * * * * * -[org.dromara.hutool.cron.demo]= +[org.dromara.hutool.cron.demo] # 6位表达式在秒匹配模式下可用,此处表示每秒执行一次 # TestJob.doTest = */1 * * * * * # 5位表达式在分匹配模式下可用,此处表示每分钟执行一次 diff --git a/hutool-crypto/pom.xml b/hutool-crypto/pom.xml index 0428e92fa..e82c73ae0 100755 --- a/hutool-crypto/pom.xml +++ b/hutool-crypto/pom.xml @@ -21,7 +21,7 @@ org.dromara.hutool hutool-parent - 6.0.0-M10 + 6.0.0-M11 hutool-crypto @@ -31,7 +31,7 @@ org.dromara.hutool.crypto - 1.76 + 1.77 diff --git a/hutool-crypto/src/main/java/org/dromara/hutool/crypto/Cipher.java b/hutool-crypto/src/main/java/org/dromara/hutool/crypto/Cipher.java new file mode 100644 index 000000000..6709cfb84 --- /dev/null +++ b/hutool-crypto/src/main/java/org/dromara/hutool/crypto/Cipher.java @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2024. looly(loolly@aliyun.com) + * Hutool is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * https://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, + * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, + * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ + +package org.dromara.hutool.crypto; + +import org.bouncycastle.crypto.BufferedBlockCipher; + +import java.util.Arrays; + +/** + * 密码接口,提供统一的API,用于兼容和统一JCE和BouncyCastle等库的操作 + * + * @author Looly + * @since 6.0.0 + */ +public interface Cipher { + + /** + * 获取算法名称 + * + * @return 算法名称 + */ + String getAlgorithmName(); + + /** + * 获取块大小,当为Stream方式加密时返回0 + * + * @return 块大小,-1表示非块加密 + */ + int getBlockSize(); + + /** + * 初始化模式和参数 + * + * @param mode 模式,如加密模式或解密模式 + * @param parameters Cipher所需参数,包括Key、Random、IV等信息 + */ + void init(CipherMode mode, Parameters parameters); + + /** + * 根据输入长度,获取输出长度,输出长度与算法相关
+ * 输出长度只针对本次输入关联,即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); + + /** + * 处理最后一块数据
+ * 当{@link #process(byte[], int, int, byte[], int)}处理完数据后非完整块数据,此方法用于处理块中剩余的bytes
+ * 如加密数据要求128bit,即16byes的整数,单数处理数据后为15bytes,此时根据padding方式不同,可填充剩余1byte为指定值(如填充0)
+ * 当对数据进行分段加密时,需要首先多次执行process方法,在最后一块数据处理完后执行此方法。 + * + * @param out 经过process执行过运算的结果数据 + * @param outOff 数据处理开始位置 + * @return 处理长度 + */ + int doFinal(byte[] out, int outOff); + + /** + * 处理数据,并返回最终结果 + * + * @param in 输入数据 + * @return 结果数据 + */ + default byte[] processFinal(final byte[] in) { + final byte[] buf = new byte[getOutputSize(in.length)]; + int len = process(in, 0, in.length, buf, 0); + len += doFinal(buf, len); + + if (len == buf.length) { + return buf; + } + return Arrays.copyOfRange(buf, 0, len); + } + + /** + * Cipher所需参数,包括Key、Random、IV等信息 + */ + interface Parameters { + } +} diff --git a/hutool-crypto/src/main/java/org/dromara/hutool/crypto/CipherMode.java b/hutool-crypto/src/main/java/org/dromara/hutool/crypto/CipherMode.java index c94eb2336..4e2317261 100644 --- a/hutool-crypto/src/main/java/org/dromara/hutool/crypto/CipherMode.java +++ b/hutool-crypto/src/main/java/org/dromara/hutool/crypto/CipherMode.java @@ -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); /** diff --git a/hutool-crypto/src/main/java/org/dromara/hutool/crypto/CipherWrapper.java b/hutool-crypto/src/main/java/org/dromara/hutool/crypto/CipherWrapper.java deleted file mode 100644 index 7a4077932..000000000 --- a/hutool-crypto/src/main/java/org/dromara/hutool/crypto/CipherWrapper.java +++ /dev/null @@ -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}包装类,提供初始化模式等额外方法
- * 包装之后可提供自定义或默认的: - * - * - * @author looly - * @since 5.7.17 - */ -public class CipherWrapper implements Wrapper { - - 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}
- * 在某些算法中,需要特别的参数,例如在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; - } -} diff --git a/hutool-crypto/src/main/java/org/dromara/hutool/crypto/JceCipher.java b/hutool-crypto/src/main/java/org/dromara/hutool/crypto/JceCipher.java new file mode 100644 index 000000000..11625d35c --- /dev/null +++ b/hutool-crypto/src/main/java/org/dromara/hutool/crypto/JceCipher.java @@ -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 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; + } + } +} diff --git a/hutool-crypto/src/main/java/org/dromara/hutool/crypto/KeyUtil.java b/hutool-crypto/src/main/java/org/dromara/hutool/crypto/KeyUtil.java index be4eb5a98..43784426e 100644 --- a/hutool-crypto/src/main/java/org/dromara/hutool/crypto/KeyUtil.java +++ b/hutool-crypto/src/main/java/org/dromara/hutool/crypto/KeyUtil.java @@ -64,6 +64,7 @@ public class KeyUtil { public static final int DEFAULT_KEY_SIZE = 1024; // region ----- generateKey + /** * 生成 {@link SecretKey},仅用于对称加密和摘要算法密钥生成 * @@ -196,7 +197,24 @@ public class KeyUtil { } // endregion + /** + * 检查{@link KeyPair} 是否为空,空的条件是: + *
    + *
  • keyPair本身为{@code null}
  • + *
  • {@link KeyPair#getPrivate()}和{@link KeyPair#getPublic()}都为{@code null}
  • + *
+ * + * @param keyPair 密钥对 + * @return 是否为空 + */ // region ----- keyPair + public static boolean isEmpty(final KeyPair keyPair) { + if (null == keyPair) { + return false; + } + return null != keyPair.getPrivate() || null != keyPair.getPublic(); + } + /** * 生成RSA私钥,仅用于非对称加密
* 采用PKCS#8规范,此规范定义了私钥信息语法和加密私钥语法
diff --git a/hutool-crypto/src/main/java/org/dromara/hutool/crypto/Mode.java b/hutool-crypto/src/main/java/org/dromara/hutool/crypto/Mode.java index 7ef557bae..87e592bfb 100644 --- a/hutool-crypto/src/main/java/org/dromara/hutool/crypto/Mode.java +++ b/hutool-crypto/src/main/java/org/dromara/hutool/crypto/Mode.java @@ -54,5 +54,10 @@ public enum Mode { /** * Propagating Cipher Block */ - PCBC + PCBC, + /** + * GCM 全称为 Galois/Counter Mode。G是指GMAC,C是指CTR。 + * 它在 CTR 加密的基础上增加 GMAC 的特性,解决了 CTR 不能对加密消息进行完整性校验的问题。 + */ + GCM } diff --git a/hutool-crypto/src/main/java/org/dromara/hutool/crypto/SignUtil.java b/hutool-crypto/src/main/java/org/dromara/hutool/crypto/SignUtil.java index b728bfc80..78ebbfc25 100644 --- a/hutool-crypto/src/main/java/org/dromara/hutool/crypto/SignUtil.java +++ b/hutool-crypto/src/main/java/org/dromara/hutool/crypto/SignUtil.java @@ -31,7 +31,7 @@ import java.util.Map; * 签名工具类
* 封装包括: *
    - *
  • 非堆成签名,签名算法支持见{@link SignAlgorithm}
  • + *
  • 非对称签名,签名算法支持见{@link SignAlgorithm}
  • *
  • 对称签名,支持Map类型参数排序后签名
  • *
  • 摘要签名,支持Map类型参数排序后签名,签名方法见:{@link DigestAlgorithm}
  • *
diff --git a/hutool-crypto/src/main/java/org/dromara/hutool/crypto/asymmetric/AsymmetricCrypto.java b/hutool-crypto/src/main/java/org/dromara/hutool/crypto/asymmetric/AsymmetricCrypto.java index b050f6bf8..5542d8395 100644 --- a/hutool-crypto/src/main/java/org/dromara/hutool/crypto/asymmetric/AsymmetricCrypto.java +++ b/hutool-crypto/src/main/java/org/dromara/hutool/crypto/asymmetric/AsymmetricCrypto.java @@ -14,18 +14,13 @@ package org.dromara.hutool.crypto.asymmetric; import org.dromara.hutool.core.codec.binary.Base64; import org.dromara.hutool.core.io.stream.FastByteArrayOutputStream; -import org.dromara.hutool.crypto.CipherWrapper; -import org.dromara.hutool.crypto.CryptoException; -import org.dromara.hutool.crypto.KeyUtil; -import org.dromara.hutool.crypto.SecureUtil; +import org.dromara.hutool.crypto.*; import org.dromara.hutool.crypto.symmetric.SymmetricAlgorithm; import javax.crypto.BadPaddingException; import javax.crypto.Cipher; import javax.crypto.IllegalBlockSizeException; import java.io.IOException; -import java.security.InvalidAlgorithmParameterException; -import java.security.InvalidKeyException; import java.security.Key; import java.security.PrivateKey; import java.security.PublicKey; @@ -51,8 +46,7 @@ public class AsymmetricCrypto extends AbstractAsymmetricCrypto /** * Cipher负责完成加密或解密工作 */ - protected CipherWrapper cipherWrapper; - + protected JceCipher cipher; /** * 加密的块大小 */ @@ -61,6 +55,15 @@ public class AsymmetricCrypto extends AbstractAsymmetricCrypto * 解密的块大小 */ protected int decryptBlockSize = -1; + + /** + * 算法参数 + */ + private AlgorithmParameterSpec algorithmParameterSpec; + /** + * 自定义随机数 + */ + private SecureRandom random; // ------------------------------------------------------------------ Constructor start /** @@ -144,8 +147,8 @@ public class AsymmetricCrypto extends AbstractAsymmetricCrypto */ public AsymmetricCrypto(final String algorithm, final byte[] privateKey, final byte[] publicKey) { this(algorithm, // - KeyUtil.generatePrivateKey(algorithm, privateKey), // - KeyUtil.generatePublicKey(algorithm, publicKey)// + KeyUtil.generatePrivateKey(algorithm, privateKey), // + KeyUtil.generatePublicKey(algorithm, publicKey)// ); } @@ -209,7 +212,7 @@ public class AsymmetricCrypto extends AbstractAsymmetricCrypto * @since 5.4.3 */ public AlgorithmParameterSpec getAlgorithmParameterSpec() { - return this.cipherWrapper.getParams(); + return this.algorithmParameterSpec; } /** @@ -217,10 +220,11 @@ public class AsymmetricCrypto extends AbstractAsymmetricCrypto * 在某些算法中,需要特别的参数,例如在ECIES中,此处为IESParameterSpec * * @param algorithmParameterSpec {@link AlgorithmParameterSpec} - * @since 5.4.3 + * @return this */ - public void setAlgorithmParameterSpec(final AlgorithmParameterSpec algorithmParameterSpec) { - this.cipherWrapper.setParams(algorithmParameterSpec); + public AsymmetricCrypto setAlgorithmParameterSpec(final AlgorithmParameterSpec algorithmParameterSpec) { + this.algorithmParameterSpec = algorithmParameterSpec; + return this; } /** @@ -231,7 +235,7 @@ public class AsymmetricCrypto extends AbstractAsymmetricCrypto * @since 5.7.17 */ public AsymmetricCrypto setRandom(final SecureRandom random) { - this.cipherWrapper.setRandom(random); + this.random = random; return this; } @@ -249,7 +253,7 @@ public class AsymmetricCrypto extends AbstractAsymmetricCrypto final Key key = getKeyByType(keyType); lock.lock(); try { - final Cipher cipher = initMode(Cipher.ENCRYPT_MODE, key); + final JceCipher cipher = initMode(CipherMode.ENCRYPT, key); if (this.encryptBlockSize < 0) { // 在引入BC库情况下,自动获取块大小 @@ -274,7 +278,7 @@ public class AsymmetricCrypto extends AbstractAsymmetricCrypto final Key key = getKeyByType(keyType); lock.lock(); try { - final Cipher cipher = initMode(Cipher.DECRYPT_MODE, key); + final JceCipher cipher = initMode(CipherMode.DECRYPT, key); if (this.decryptBlockSize < 0) { // 在引入BC库情况下,自动获取块大小 @@ -301,7 +305,7 @@ public class AsymmetricCrypto extends AbstractAsymmetricCrypto * @since 5.4.3 */ public Cipher getCipher() { - return this.cipherWrapper.getRaw(); + return this.cipher.getRaw(); } /** @@ -310,7 +314,7 @@ public class AsymmetricCrypto extends AbstractAsymmetricCrypto * @since 4.5.2 */ protected void initCipher() { - this.cipherWrapper = new CipherWrapper(this.algorithm); + this.cipher = new JceCipher(this.algorithm); } /** @@ -346,9 +350,10 @@ public class AsymmetricCrypto extends AbstractAsymmetricCrypto * @throws BadPaddingException padding错误异常 * @throws IOException IO异常,不会被触发 */ + @SuppressWarnings("resource") private byte[] doFinalWithBlock(final byte[] data, final int maxBlockSize) throws IllegalBlockSizeException, BadPaddingException, IOException { final int dataLength = data.length; - @SuppressWarnings("resource") final FastByteArrayOutputStream out = new FastByteArrayOutputStream(); + final FastByteArrayOutputStream out = new FastByteArrayOutputStream(); int offSet = 0; // 剩余长度 @@ -367,15 +372,15 @@ public class AsymmetricCrypto extends AbstractAsymmetricCrypto } /** - * 初始化{@link Cipher}的模式,如加密模式或解密模式 + * 初始化{@link JceCipher}的模式,如加密模式或解密模式 * - * @param mode 模式,可选{@link Cipher#ENCRYPT_MODE}或者{@link Cipher#DECRYPT_MODE} + * @param mode 模式,可选{@link CipherMode#ENCRYPT}或者{@link CipherMode#DECRYPT} * @param key 密钥 - * @return {@link Cipher} - * @throws InvalidAlgorithmParameterException 异常算法错误 - * @throws InvalidKeyException 异常KEY错误 + * @return {@link JceCipher} */ - private Cipher initMode(final int mode, final Key key) throws InvalidAlgorithmParameterException, InvalidKeyException { - return this.cipherWrapper.initMode(mode, key).getRaw(); + private JceCipher initMode(final CipherMode mode, final Key key) { + final JceCipher cipher = this.cipher; + cipher.init(mode, new JceCipher.JceParameters(key, this.algorithmParameterSpec, this.random)); + return cipher; } } diff --git a/hutool-crypto/src/main/java/org/dromara/hutool/crypto/bc/BCCipher.java b/hutool-crypto/src/main/java/org/dromara/hutool/crypto/bc/BCCipher.java new file mode 100644 index 000000000..3281675a1 --- /dev/null +++ b/hutool-crypto/src/main/java/org/dromara/hutool/crypto/bc/BCCipher.java @@ -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库封装的加密解密实现,包装包括: + *
    + *
  • {@link BufferedBlockCipher}
  • + *
  • {@link BlockCipher}
  • + *
  • {@link StreamCipher}
  • + *
  • {@link AEADBlockCipher}
  • + *
+ * + * @author Looly, changhr2013 + */ +public class BCCipher implements Cipher, Wrapper { + + /** + * {@link BufferedBlockCipher},块加密,包含engine、mode、padding + */ + private BufferedBlockCipher bufferedBlockCipher; + /** + * {@link BlockCipher} 块加密,一般用于AES等对称加密 + */ + private BlockCipher blockCipher; + /** + * {@link AEADBlockCipher}, 关联数据的认证加密(Authenticated Encryption with Associated Data) + */ + private AEADBlockCipher aeadBlockCipher; + /** + * {@link StreamCipher} + */ + private StreamCipher streamCipher; + + // region ----- 构造 + + /** + * 构造 + * + * @param bufferedBlockCipher {@link BufferedBlockCipher} + */ + public BCCipher(final BufferedBlockCipher bufferedBlockCipher) { + this.bufferedBlockCipher = Assert.notNull(bufferedBlockCipher); + } + + /** + * 构造 + * + * @param blockCipher {@link BlockCipher} + */ + public BCCipher(final BlockCipher blockCipher) { + this.blockCipher = Assert.notNull(blockCipher); + } + + /** + * 构造 + * + * @param aeadBlockCipher {@link AEADBlockCipher} + */ + public BCCipher(final AEADBlockCipher aeadBlockCipher) { + this.aeadBlockCipher = Assert.notNull(aeadBlockCipher); + } + + /** + * 构造 + * + * @param streamCipher {@link StreamCipher} + */ + public BCCipher(final StreamCipher streamCipher) { + this.streamCipher = Assert.notNull(streamCipher); + } + // endregion + + @Override + public Object getRaw() { + if (null != this.bufferedBlockCipher) { + return this.bufferedBlockCipher; + } + if (null != this.blockCipher) { + return this.blockCipher; + } + if (null != this.aeadBlockCipher) { + return this.aeadBlockCipher; + } + return this.streamCipher; + } + + @Override + public String getAlgorithmName() { + if (null != this.bufferedBlockCipher) { + return this.bufferedBlockCipher.getUnderlyingCipher().getAlgorithmName(); + } + if (null != this.blockCipher) { + return this.blockCipher.getAlgorithmName(); + } + if (null != this.aeadBlockCipher) { + return this.aeadBlockCipher.getUnderlyingCipher().getAlgorithmName(); + } + return this.streamCipher.getAlgorithmName(); + } + + @Override + public int getBlockSize() { + if (null != this.bufferedBlockCipher) { + return this.bufferedBlockCipher.getBlockSize(); + } + if (null != this.blockCipher) { + return this.blockCipher.getBlockSize(); + } + if (null != this.aeadBlockCipher) { + return this.aeadBlockCipher.getUnderlyingCipher().getBlockSize(); + } + return -1; + } + + @Override + public void init(final CipherMode mode, final Parameters parameters) { + Assert.isInstanceOf(BCParameters.class, parameters, "Only support BCParameters!"); + + final boolean forEncryption; + if (mode == CipherMode.ENCRYPT) { + forEncryption = true; + } else if (mode == CipherMode.DECRYPT) { + forEncryption = false; + } else { + throw new IllegalArgumentException("Invalid mode: " + mode.name()); + } + final CipherParameters cipherParameters = ((BCParameters) parameters).parameters; + + if (null != this.bufferedBlockCipher) { + this.bufferedBlockCipher.init(forEncryption, cipherParameters); + return; + } + if (null != this.blockCipher) { + this.blockCipher.init(forEncryption, cipherParameters); + } + if (null != this.aeadBlockCipher) { + this.aeadBlockCipher.init(forEncryption, cipherParameters); + return; + } + this.streamCipher.init(forEncryption, cipherParameters); + } + + @Override + public int getOutputSize(final int len) { + if (null != this.bufferedBlockCipher) { + return this.bufferedBlockCipher.getOutputSize(len); + } + if (null != this.aeadBlockCipher) { + return this.aeadBlockCipher.getOutputSize(len); + } + return -1; + } + + @Override + public int process(final byte[] in, final int inOff, final int len, final byte[] out, final int outOff) { + if (null != this.bufferedBlockCipher) { + return this.bufferedBlockCipher.processBytes(in, inOff, len, out, outOff); + } + if (null != this.blockCipher) { + final byte[] subBytes; + if (inOff + len < in.length) { + subBytes = Arrays.copyOf(in, inOff + len); + } else { + subBytes = in; + } + return this.blockCipher.processBlock(subBytes, inOff, out, outOff); + } + if (null != this.aeadBlockCipher) { + return this.aeadBlockCipher.processBytes(in, inOff, len, out, outOff); + } + return this.streamCipher.processBytes(in, inOff, len, out, outOff); + } + + @Override + public int doFinal(final byte[] out, final int outOff) { + if (null != this.bufferedBlockCipher) { + try { + return this.bufferedBlockCipher.doFinal(out, outOff); + } catch (final InvalidCipherTextException e) { + throw new CryptoException(e); + } + } + if (null != this.aeadBlockCipher) { + try { + return this.aeadBlockCipher.doFinal(out, outOff); + } catch (final InvalidCipherTextException e) { + throw new CryptoException(e); + } + } + return 0; + } + + /** + * BouncyCastle库的{@link CipherParameters}封装 + * + * @author Looly + */ + public static class BCParameters implements Parameters { + /** + * 算法的参数 + */ + private final CipherParameters parameters; + + /** + * 构造 + * + * @param parameters {@link CipherParameters} + */ + public BCParameters(final CipherParameters parameters) { + this.parameters = parameters; + } + } +} diff --git a/hutool-crypto/src/main/java/org/dromara/hutool/crypto/bc/BCUtil.java b/hutool-crypto/src/main/java/org/dromara/hutool/crypto/bc/BCUtil.java index f392fe8fe..9455bbb26 100644 --- a/hutool-crypto/src/main/java/org/dromara/hutool/crypto/bc/BCUtil.java +++ b/hutool-crypto/src/main/java/org/dromara/hutool/crypto/bc/BCUtil.java @@ -15,10 +15,19 @@ package org.dromara.hutool.crypto.bc; import org.bouncycastle.asn1.pkcs.PrivateKeyInfo; import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; import org.bouncycastle.asn1.x9.X9ECParameters; +import org.bouncycastle.crypto.BlockCipher; +import org.bouncycastle.crypto.BufferedBlockCipher; +import org.bouncycastle.crypto.DefaultBufferedBlockCipher; +import org.bouncycastle.crypto.modes.*; +import org.bouncycastle.crypto.paddings.ISO10126d2Padding; +import org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher; +import org.bouncycastle.crypto.paddings.ZeroBytePadding; import org.bouncycastle.crypto.params.ECDomainParameters; import org.bouncycastle.jcajce.provider.asymmetric.util.ECUtil; import org.bouncycastle.jce.spec.ECParameterSpec; import org.dromara.hutool.core.io.IORuntimeException; +import org.dromara.hutool.crypto.Mode; +import org.dromara.hutool.crypto.Padding; import java.io.IOException; import java.security.PrivateKey; @@ -41,10 +50,10 @@ public class BCUtil { */ public static ECDomainParameters toDomainParams(final ECParameterSpec parameterSpec) { return new ECDomainParameters( - parameterSpec.getCurve(), - parameterSpec.getG(), - parameterSpec.getN(), - parameterSpec.getH()); + parameterSpec.getCurve(), + parameterSpec.getG(), + parameterSpec.getN(), + parameterSpec.getH()); } /** @@ -67,10 +76,10 @@ public class BCUtil { */ public static ECDomainParameters toDomainParams(final X9ECParameters x9ECParameters) { return new ECDomainParameters( - x9ECParameters.getCurve(), - x9ECParameters.getG(), - x9ECParameters.getN(), - x9ECParameters.getH() + x9ECParameters.getCurve(), + x9ECParameters.getG(), + x9ECParameters.getN(), + x9ECParameters.getH() ); } @@ -81,7 +90,7 @@ public class BCUtil { * @return PKCS#1格式私钥 * @since 5.5.9 */ - public static byte[] toPkcs1(final PrivateKey privateKey){ + public static byte[] toPkcs1(final PrivateKey privateKey) { final PrivateKeyInfo pkInfo = PrivateKeyInfo.getInstance(privateKey.getEncoded()); try { return pkInfo.parsePrivateKey().toASN1Primitive().getEncoded(); @@ -97,13 +106,53 @@ public class BCUtil { * @return PKCS#1格式公钥 * @since 5.5.9 */ - public static byte[] toPkcs1(final PublicKey publicKey){ + public static byte[] toPkcs1(final PublicKey publicKey) { final SubjectPublicKeyInfo spkInfo = SubjectPublicKeyInfo - .getInstance(publicKey.getEncoded()); + .getInstance(publicKey.getEncoded()); try { return spkInfo.parsePublicKey().getEncoded(); } catch (final IOException e) { throw new IORuntimeException(e); } } + + /** + * 将{@link BlockCipher}包装为指定mode和padding的{@link BufferedBlockCipher} + * + * @param cipher {@link BlockCipher} + * @param mode 模式 + * @param padding 补码方式 + * @return {@link BufferedBlockCipher},无对应Cipher返回{@code null} + * @since 6.0.0 + */ + public static BufferedBlockCipher wrap(BlockCipher cipher, final Mode mode, final Padding padding) { + switch (mode) { + case CBC: + cipher = CBCBlockCipher.newInstance(cipher); + break; + case CFB: + cipher = CFBBlockCipher.newInstance(cipher, cipher.getBlockSize() * 8); + break; + case CTR: + cipher = SICBlockCipher.newInstance(cipher); + break; + case OFB: + cipher = new OFBBlockCipher(cipher, cipher.getBlockSize() * 8); + case CTS: + return new CTSBlockCipher(cipher); + } + + switch (padding) { + case NoPadding: + return new DefaultBufferedBlockCipher(cipher); + case PKCS5Padding: + return new PaddedBufferedBlockCipher(cipher); + case ZeroPadding: + return new PaddedBufferedBlockCipher(cipher, new ZeroBytePadding()); + case ISO10126Padding: + return new PaddedBufferedBlockCipher(cipher, new ISO10126d2Padding()); + } + + return null; + } } diff --git a/hutool-crypto/src/main/java/org/dromara/hutool/crypto/bc/ECKeyUtil.java b/hutool-crypto/src/main/java/org/dromara/hutool/crypto/bc/ECKeyUtil.java index 040c614c4..864d88824 100644 --- a/hutool-crypto/src/main/java/org/dromara/hutool/crypto/bc/ECKeyUtil.java +++ b/hutool-crypto/src/main/java/org/dromara/hutool/crypto/bc/ECKeyUtil.java @@ -435,6 +435,9 @@ public class ECKeyUtil { * @since 5.5.9 */ public static ECPrivateKeyParameters decodePrivateKeyParams(final byte[] privateKeyBytes) { + if (null == privateKeyBytes) { + return null; + } try { // 尝试D值 return toSm2PrivateParams(privateKeyBytes); @@ -468,6 +471,9 @@ public class ECKeyUtil { * @since 5.5.9 */ public static ECPublicKeyParameters decodePublicKeyParams(final byte[] publicKeyBytes) { + if(null == publicKeyBytes){ + return null; + } try { // 尝试Q值 return toSm2PublicParams(publicKeyBytes); diff --git a/hutool-crypto/src/main/java/org/dromara/hutool/crypto/digest/BCrypt.java b/hutool-crypto/src/main/java/org/dromara/hutool/crypto/digest/BCrypt.java index ed4b73991..23a4e545c 100644 --- a/hutool-crypto/src/main/java/org/dromara/hutool/crypto/digest/BCrypt.java +++ b/hutool-crypto/src/main/java/org/dromara/hutool/crypto/digest/BCrypt.java @@ -545,7 +545,7 @@ public class BCrypt { if (hashed_bytes.length != try_bytes.length) { return false; } - byte ret = 0; + int ret = 0; for (int i = 0; i < try_bytes.length; i++) ret |= hashed_bytes[i] ^ try_bytes[i]; return ret == 0; diff --git a/hutool-crypto/src/main/java/org/dromara/hutool/crypto/digest/DigestAlgorithm.java b/hutool-crypto/src/main/java/org/dromara/hutool/crypto/digest/DigestAlgorithm.java index b7907094f..a2f114303 100644 --- a/hutool-crypto/src/main/java/org/dromara/hutool/crypto/digest/DigestAlgorithm.java +++ b/hutool-crypto/src/main/java/org/dromara/hutool/crypto/digest/DigestAlgorithm.java @@ -19,11 +19,29 @@ package org.dromara.hutool.crypto.digest; * @author Looly */ public enum DigestAlgorithm { + /** + * MD2 + */ MD2("MD2"), + /** + * MD5 + */ MD5("MD5"), + /** + * SHA-1 + */ SHA1("SHA-1"), + /** + * SHA-256 + */ SHA256("SHA-256"), + /** + * SHA-384 + */ SHA384("SHA-384"), + /** + * SHA-512 + */ SHA512("SHA-512"); private final String value; diff --git a/hutool-crypto/src/main/java/org/dromara/hutool/crypto/digest/Digester.java b/hutool-crypto/src/main/java/org/dromara/hutool/crypto/digest/Digester.java index d17e1a87b..7af85dcee 100644 --- a/hutool-crypto/src/main/java/org/dromara/hutool/crypto/digest/Digester.java +++ b/hutool-crypto/src/main/java/org/dromara/hutool/crypto/digest/Digester.java @@ -253,7 +253,7 @@ public class Digester extends SimpleWrapper implements Serializab // 加盐在末尾,自动忽略空盐值 result = doDigest(data, this.salt); } else if (ArrayUtil.isNotEmpty(this.salt)) { - final MessageDigest digest = getRaw(); + final MessageDigest digest = this.raw; // 加盐在中间 digest.update(data, 0, this.saltPosition); digest.update(this.salt); diff --git a/hutool-crypto/src/main/java/org/dromara/hutool/crypto/digest/DigesterFactory.java b/hutool-crypto/src/main/java/org/dromara/hutool/crypto/digest/DigesterFactory.java index eec919580..f51bdf4ed 100644 --- a/hutool-crypto/src/main/java/org/dromara/hutool/crypto/digest/DigesterFactory.java +++ b/hutool-crypto/src/main/java/org/dromara/hutool/crypto/digest/DigesterFactory.java @@ -27,7 +27,7 @@ import java.security.Provider; public class DigesterFactory { /** - * 创建工厂 + * 创建工厂,只使用JDK提供的算法 * * @param algorithm 算法 * @return DigesterFactory diff --git a/hutool-crypto/src/main/java/org/dromara/hutool/crypto/digest/mac/BCHMacEngine.java b/hutool-crypto/src/main/java/org/dromara/hutool/crypto/digest/mac/BCHMacEngine.java index 358987432..15d58da73 100644 --- a/hutool-crypto/src/main/java/org/dromara/hutool/crypto/digest/mac/BCHMacEngine.java +++ b/hutool-crypto/src/main/java/org/dromara/hutool/crypto/digest/mac/BCHMacEngine.java @@ -75,16 +75,4 @@ public class BCHMacEngine extends BCMacEngine { super(mac, params); } // ------------------------------------------------------------------------------------------- Constructor end - - /** - * 初始化 - * - * @param digest 摘要算法 - * @param params 参数,例如密钥可以用{@link KeyParameter} - * @return this - * @see #init(Mac, CipherParameters) - */ - public BCHMacEngine init(final Digest digest, final CipherParameters params) { - return (BCHMacEngine) init(new HMac(digest), params); - } } diff --git a/hutool-crypto/src/main/java/org/dromara/hutool/crypto/digest/mac/BCMacEngine.java b/hutool-crypto/src/main/java/org/dromara/hutool/crypto/digest/mac/BCMacEngine.java index 4e95d69f4..92e27103e 100644 --- a/hutool-crypto/src/main/java/org/dromara/hutool/crypto/digest/mac/BCMacEngine.java +++ b/hutool-crypto/src/main/java/org/dromara/hutool/crypto/digest/mac/BCMacEngine.java @@ -15,6 +15,7 @@ package org.dromara.hutool.crypto.digest.mac; import org.bouncycastle.crypto.CipherParameters; import org.bouncycastle.crypto.Mac; import org.bouncycastle.crypto.params.KeyParameter; +import org.dromara.hutool.core.lang.wrapper.SimpleWrapper; /** * BouncyCastle的MAC算法实现引擎,使用{@link Mac} 实现摘要
@@ -23,9 +24,7 @@ import org.bouncycastle.crypto.params.KeyParameter; * @author Looly * @since 5.8.0 */ -public class BCMacEngine implements MacEngine { - - private Mac mac; +public class BCMacEngine extends SimpleWrapper implements MacEngine { // ------------------------------------------------------------------------------------------- Constructor start /** @@ -36,57 +35,46 @@ public class BCMacEngine implements MacEngine { * @since 5.8.0 */ public BCMacEngine(final Mac mac, final CipherParameters params) { - init(mac, params); + super(initMac(mac, params)); } // ------------------------------------------------------------------------------------------- Constructor end + @Override + public void update(final byte[] in, final int inOff, final int len) { + this.raw.update(in, inOff, len); + } + + @Override + public byte[] doFinal() { + final byte[] result = new byte[getMacLength()]; + this.raw.doFinal(result, 0); + return result; + } + + @Override + public void reset() { + this.raw.reset(); + } + + @Override + public int getMacLength() { + return this.raw.getMacSize(); + } + + @Override + public String getAlgorithm() { + return this.raw.getAlgorithmName(); + } + /** * 初始化 * * @param mac 摘要算法 * @param params 参数,例如密钥可以用{@link KeyParameter} * @return this - * @since 5.8.0 */ - public BCMacEngine init(final Mac mac, final CipherParameters params) { + private static Mac initMac(final Mac mac, final CipherParameters params) { mac.init(params); - this.mac = mac; - return this; - } - - /** - * 获得 {@link Mac} - * - * @return {@link Mac} - */ - public Mac ipgetMac() { return mac; } - - @Override - public void update(final byte[] in, final int inOff, final int len) { - this.mac.update(in, inOff, len); - } - - @Override - public byte[] doFinal() { - final byte[] result = new byte[getMacLength()]; - this.mac.doFinal(result, 0); - return result; - } - - @Override - public void reset() { - this.mac.reset(); - } - - @Override - public int getMacLength() { - return mac.getMacSize(); - } - - @Override - public String getAlgorithm() { - return this.mac.getAlgorithmName(); - } } diff --git a/hutool-crypto/src/main/java/org/dromara/hutool/crypto/digest/mac/CBCBlockCipherMacEngine.java b/hutool-crypto/src/main/java/org/dromara/hutool/crypto/digest/mac/CBCBlockCipherMacEngine.java index ed243ef40..352a06dfe 100644 --- a/hutool-crypto/src/main/java/org/dromara/hutool/crypto/digest/mac/CBCBlockCipherMacEngine.java +++ b/hutool-crypto/src/main/java/org/dromara/hutool/crypto/digest/mac/CBCBlockCipherMacEngine.java @@ -15,7 +15,6 @@ package org.dromara.hutool.crypto.digest.mac; import org.bouncycastle.crypto.BlockCipher; import org.bouncycastle.crypto.CipherParameters; import org.bouncycastle.crypto.Digest; -import org.bouncycastle.crypto.Mac; import org.bouncycastle.crypto.macs.CBCBlockCipherMac; import org.bouncycastle.crypto.params.KeyParameter; import org.bouncycastle.crypto.params.ParametersWithIV; @@ -97,16 +96,4 @@ public class CBCBlockCipherMacEngine extends BCMacEngine { public CBCBlockCipherMacEngine(final CBCBlockCipherMac mac, final CipherParameters params) { super(mac, params); } - - /** - * 初始化 - * - * @param cipher {@link BlockCipher} - * @param params 参数,例如密钥可以用{@link KeyParameter} - * @return this - * @see #init(Mac, CipherParameters) - */ - public CBCBlockCipherMacEngine init(final BlockCipher cipher, final CipherParameters params) { - return (CBCBlockCipherMacEngine) init(new CBCBlockCipherMac(cipher), params); - } } diff --git a/hutool-crypto/src/main/java/org/dromara/hutool/crypto/digest/mac/DefaultHMacEngine.java b/hutool-crypto/src/main/java/org/dromara/hutool/crypto/digest/mac/JCEMacEngine.java similarity index 62% rename from hutool-crypto/src/main/java/org/dromara/hutool/crypto/digest/mac/DefaultHMacEngine.java rename to hutool-crypto/src/main/java/org/dromara/hutool/crypto/digest/mac/JCEMacEngine.java index d14aa1c68..2b3695c0e 100644 --- a/hutool-crypto/src/main/java/org/dromara/hutool/crypto/digest/mac/DefaultHMacEngine.java +++ b/hutool-crypto/src/main/java/org/dromara/hutool/crypto/digest/mac/JCEMacEngine.java @@ -12,6 +12,7 @@ package org.dromara.hutool.crypto.digest.mac; +import org.dromara.hutool.core.lang.wrapper.SimpleWrapper; import org.dromara.hutool.crypto.CryptoException; import org.dromara.hutool.crypto.KeyUtil; import org.dromara.hutool.crypto.SecureUtil; @@ -23,18 +24,15 @@ import java.security.Key; import java.security.spec.AlgorithmParameterSpec; /** - * 默认的HMAC算法实现引擎,使用{@link Mac} 实现摘要
+ * JDK提供的的MAC算法实现引擎,使用{@link Mac} 实现摘要
* 当引入BouncyCastle库时自动使用其作为Provider * * @author Looly * @since 4.5.13 */ -public class DefaultHMacEngine implements MacEngine { - - private Mac mac; +public class JCEMacEngine extends SimpleWrapper implements MacEngine { // region ----- Constructor - /** * 构造 * @@ -42,7 +40,7 @@ public class DefaultHMacEngine implements MacEngine { * @param key 密钥 * @since 4.5.13 */ - public DefaultHMacEngine(final String algorithm, final byte[] key) { + public JCEMacEngine(final String algorithm, final byte[] key) { this(algorithm, (null == key) ? null : new SecretKeySpec(key, algorithm)); } @@ -53,7 +51,7 @@ public class DefaultHMacEngine implements MacEngine { * @param key 密钥 * @since 4.5.13 */ - public DefaultHMacEngine(final String algorithm, final Key key) { + public JCEMacEngine(final String algorithm, final Key key) { this(algorithm, key, null); } @@ -65,34 +63,39 @@ public class DefaultHMacEngine implements MacEngine { * @param spec {@link AlgorithmParameterSpec} * @since 5.7.12 */ - public DefaultHMacEngine(final String algorithm, final Key key, final AlgorithmParameterSpec spec) { - init(algorithm, key, spec); + public JCEMacEngine(final String algorithm, final Key key, final AlgorithmParameterSpec spec) { + super(initMac(algorithm, key, spec)); } // endregion - // region ----- init - - /** - * 初始化 - * - * @param algorithm 算法 - * @param key 密钥 - * @return this - */ - public DefaultHMacEngine init(final String algorithm, final byte[] key) { - return init(algorithm, (null == key) ? null : new SecretKeySpec(key, algorithm)); + @Override + public void update(final byte[] in) { + this.raw.update(in); } - /** - * 初始化 - * - * @param algorithm 算法 - * @param key 密钥 {@link SecretKey} - * @return this - * @throws CryptoException Cause by IOException - */ - public DefaultHMacEngine init(final String algorithm, final Key key) { - return init(algorithm, key, null); + @Override + public void update(final byte[] in, final int inOff, final int len) { + this.raw.update(in, inOff, len); + } + + @Override + public byte[] doFinal() { + return this.raw.doFinal(); + } + + @Override + public void reset() { + this.raw.reset(); + } + + @Override + public int getMacLength() { + return this.raw.getMacLength(); + } + + @Override + public String getAlgorithm() { + return this.raw.getAlgorithm(); } /** @@ -103,9 +106,9 @@ public class DefaultHMacEngine implements MacEngine { * @param spec {@link AlgorithmParameterSpec} * @return this * @throws CryptoException Cause by IOException - * @since 5.7.12 */ - public DefaultHMacEngine init(final String algorithm, Key key, final AlgorithmParameterSpec spec) { + private static Mac initMac(final String algorithm, Key key, final AlgorithmParameterSpec spec) { + final Mac mac; try { mac = SecureUtil.createMac(algorithm); if (null == key) { @@ -119,46 +122,6 @@ public class DefaultHMacEngine implements MacEngine { } catch (final Exception e) { throw new CryptoException(e); } - return this; - } - // endregion - - /** - * 获得 {@link Mac} - * - * @return {@link Mac} - */ - public Mac getMac() { return mac; } - - @Override - public void update(final byte[] in) { - this.mac.update(in); - } - - @Override - public void update(final byte[] in, final int inOff, final int len) { - this.mac.update(in, inOff, len); - } - - @Override - public byte[] doFinal() { - return this.mac.doFinal(); - } - - @Override - public void reset() { - this.mac.reset(); - } - - @Override - public int getMacLength() { - return mac.getMacLength(); - } - - @Override - public String getAlgorithm() { - return this.mac.getAlgorithm(); - } } diff --git a/hutool-crypto/src/main/java/org/dromara/hutool/crypto/digest/mac/MacEngineFactory.java b/hutool-crypto/src/main/java/org/dromara/hutool/crypto/digest/mac/MacEngineFactory.java index 8f353c4ec..ea22423db 100644 --- a/hutool-crypto/src/main/java/org/dromara/hutool/crypto/digest/mac/MacEngineFactory.java +++ b/hutool-crypto/src/main/java/org/dromara/hutool/crypto/digest/mac/MacEngineFactory.java @@ -50,6 +50,6 @@ public class MacEngineFactory { // HmacSM3算法是BC库实现的,忽略加盐 return SmUtil.createHmacSm3Engine(key.getEncoded()); } - return new DefaultHMacEngine(algorithm, key, spec); + return new JCEMacEngine(algorithm, key, spec); } } diff --git a/hutool-crypto/src/main/java/org/dromara/hutool/crypto/digest/mac/package-info.java b/hutool-crypto/src/main/java/org/dromara/hutool/crypto/digest/mac/package-info.java index c163dd2f8..0ca816ff1 100644 --- a/hutool-crypto/src/main/java/org/dromara/hutool/crypto/digest/mac/package-info.java +++ b/hutool-crypto/src/main/java/org/dromara/hutool/crypto/digest/mac/package-info.java @@ -22,6 +22,21 @@ * HMAC 可以与任何迭代散列函数捆绑使用。MD5 和 SHA-1 就是这种散列函数。HMAC 还可以使用一个用于计算和确认消息鉴别值的密钥。 *

* + *
{@code
+ *         MacEngineFactory
+ *               ||(创建)
+ *           MacEngine----------------(包装)-----------------> Mac
+ *          _____|_______________                                |
+ *         /                     \                              HMac
+ *  JCEMacEngine             BCMacEngine
+ *                            /        \
+ *                   BCHMacEngine  CBCBlockCipherMacEngine
+ *                                          |
+ *                                     SM4MacEngine
+ * }
+ * + * 通过MacEngine,封装支持了BouncyCastle和JCE实现的一些MAC算法,通过MacEngineFactory自动根据算法名称创建对应对象。 + * * @author Looly * @since 4.5.13 */ diff --git a/hutool-crypto/src/main/java/org/dromara/hutool/crypto/digest/otp/package-info.java b/hutool-crypto/src/main/java/org/dromara/hutool/crypto/digest/otp/package-info.java index 29e540aac..695306155 100644 --- a/hutool-crypto/src/main/java/org/dromara/hutool/crypto/digest/otp/package-info.java +++ b/hutool-crypto/src/main/java/org/dromara/hutool/crypto/digest/otp/package-info.java @@ -21,6 +21,12 @@ * Truncate:是一个函数,就是怎么截取加密后的串,并取加密后串的哪些字段组成一个数字。 * * + * 实现包括: + *
    + *
  • HMAC-based one-time passwords (HOTP) 基于HMAC算法一次性密码生成器
  • + *
  • time-based one-time passwords (TOTP) 基于时间戳算法的一次性密码生成器
  • + *
+ * * @author looly */ package org.dromara.hutool.crypto.digest.otp; diff --git a/hutool-crypto/src/main/java/org/dromara/hutool/crypto/symmetric/SymmetricCrypto.java b/hutool-crypto/src/main/java/org/dromara/hutool/crypto/symmetric/SymmetricCrypto.java index 38cea58af..a96eb09d6 100644 --- a/hutool-crypto/src/main/java/org/dromara/hutool/crypto/symmetric/SymmetricCrypto.java +++ b/hutool-crypto/src/main/java/org/dromara/hutool/crypto/symmetric/SymmetricCrypto.java @@ -34,8 +34,6 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.Serializable; -import java.security.InvalidAlgorithmParameterException; -import java.security.InvalidKeyException; import java.security.SecureRandom; import java.security.spec.AlgorithmParameterSpec; import java.util.concurrent.locks.Lock; @@ -52,7 +50,15 @@ import java.util.concurrent.locks.ReentrantLock; public class SymmetricCrypto implements SymmetricEncryptor, SymmetricDecryptor, Serializable { private static final long serialVersionUID = 1L; - private CipherWrapper cipherWrapper; + private JceCipher cipher; + /** + * 算法参数 + */ + private AlgorithmParameterSpec algorithmParameterSpec; + /** + * 自定义随机数 + */ + private SecureRandom random; /** * SecretKey 负责保存对称密钥 */ @@ -157,7 +163,7 @@ public class SymmetricCrypto implements SymmetricEncryptor, SymmetricDecryptor, this.isZeroPadding = true; } - this.cipherWrapper = new CipherWrapper(algorithm); + this.cipher = new JceCipher(algorithm); return this; } @@ -176,17 +182,17 @@ public class SymmetricCrypto implements SymmetricEncryptor, SymmetricDecryptor, * @return 加密或解密 */ public Cipher getCipher() { - return cipherWrapper.getRaw(); + return cipher.getRaw(); } /** - * 设置 {@link AlgorithmParameterSpec},通常用于加盐或偏移向量 + * 设置{@link AlgorithmParameterSpec},通常用于加盐或偏移向量 * - * @param params {@link AlgorithmParameterSpec} - * @return 自身 + * @param algorithmParameterSpec {@link AlgorithmParameterSpec} + * @return this */ - public SymmetricCrypto setParams(final AlgorithmParameterSpec params) { - this.cipherWrapper.setParams(params); + public SymmetricCrypto setAlgorithmParameterSpec(final AlgorithmParameterSpec algorithmParameterSpec) { + this.algorithmParameterSpec = algorithmParameterSpec; return this; } @@ -197,7 +203,7 @@ public class SymmetricCrypto implements SymmetricEncryptor, SymmetricDecryptor, * @return 自身 */ public SymmetricCrypto setIv(final IvParameterSpec iv) { - return setParams(iv); + return setAlgorithmParameterSpec(iv); } /** @@ -218,7 +224,7 @@ public class SymmetricCrypto implements SymmetricEncryptor, SymmetricDecryptor, * @since 5.7.17 */ public SymmetricCrypto setRandom(final SecureRandom random) { - this.cipherWrapper.setRandom(random); + this.random = random; return this; } @@ -245,9 +251,7 @@ public class SymmetricCrypto implements SymmetricEncryptor, SymmetricDecryptor, public SymmetricCrypto setMode(final CipherMode mode, final byte[] salt) { lock.lock(); try { - initMode(mode.getValue(), salt); - } catch (final Exception e) { - throw new CryptoException(e); + initMode(mode, salt); } finally { lock.unlock(); } @@ -263,7 +267,7 @@ public class SymmetricCrypto implements SymmetricEncryptor, SymmetricDecryptor, * @since 5.6.8 */ public byte[] update(final byte[] data) { - final Cipher cipher = cipherWrapper.getRaw(); + final Cipher cipher = this.cipher.getRaw(); lock.lock(); try { return cipher.update(paddingDataWithZero(data, cipher.getBlockSize())); @@ -305,10 +309,8 @@ public class SymmetricCrypto implements SymmetricEncryptor, SymmetricDecryptor, byte[] result; lock.lock(); try { - final Cipher cipher = initMode(Cipher.ENCRYPT_MODE, salt); - result = cipher.doFinal(paddingDataWithZero(data, cipher.getBlockSize())); - } catch (final Exception e) { - throw new CryptoException(e); + final JceCipher cipher = initMode(CipherMode.ENCRYPT, salt); + result = cipher.processFinal(paddingDataWithZero(data, cipher.getBlockSize())); } finally { lock.unlock(); } @@ -320,8 +322,8 @@ public class SymmetricCrypto implements SymmetricEncryptor, SymmetricDecryptor, CipherOutputStream cipherOutputStream = null; lock.lock(); try { - final Cipher cipher = initMode(Cipher.ENCRYPT_MODE, null); - cipherOutputStream = new CipherOutputStream(out, cipher); + final JceCipher cipher = initMode(CipherMode.ENCRYPT, null); + cipherOutputStream = new CipherOutputStream(out, cipher.getRaw()); final long length = IoUtil.copy(data, cipherOutputStream); if (this.isZeroPadding) { final int blockSize = cipher.getBlockSize(); @@ -359,9 +361,9 @@ public class SymmetricCrypto implements SymmetricEncryptor, SymmetricDecryptor, lock.lock(); try { final byte[] salt = SaltMagic.getSalt(bytes); - final Cipher cipher = initMode(Cipher.DECRYPT_MODE, salt); + final JceCipher cipher = initMode(CipherMode.DECRYPT, salt); blockSize = cipher.getBlockSize(); - decryptData = cipher.doFinal(SaltMagic.getData(bytes)); + decryptData = cipher.processFinal(SaltMagic.getData(bytes)); } catch (final Exception e) { throw new CryptoException(e); } finally { @@ -376,8 +378,8 @@ public class SymmetricCrypto implements SymmetricEncryptor, SymmetricDecryptor, CipherInputStream cipherInputStream = null; lock.lock(); try { - final Cipher cipher = initMode(Cipher.DECRYPT_MODE, null); - cipherInputStream = new CipherInputStream(data, cipher); + final JceCipher cipher = initMode(CipherMode.DECRYPT, null); + cipherInputStream = new CipherInputStream(data, cipher.getRaw()); if (this.isZeroPadding) { final int blockSize = cipher.getBlockSize(); if (blockSize > 0) { @@ -417,8 +419,8 @@ public class SymmetricCrypto implements SymmetricEncryptor, SymmetricDecryptor, */ private SymmetricCrypto initParams(final String algorithm, AlgorithmParameterSpec paramsSpec) { if (null == paramsSpec) { - byte[] iv = Opt.ofNullable(cipherWrapper) - .map(CipherWrapper::getRaw).map(Cipher::getIV).get(); + byte[] iv = Opt.ofNullable(cipher) + .map(JceCipher::getRaw).map(Cipher::getIV).get(); // 随机IV if (StrUtil.startWithIgnoreCase(algorithm, "PBE")) { @@ -435,18 +437,16 @@ public class SymmetricCrypto implements SymmetricEncryptor, SymmetricDecryptor, } } - return setParams(paramsSpec); + return setAlgorithmParameterSpec(paramsSpec); } /** - * 初始化{@link Cipher}为加密或者解密模式 + * 初始化{@link JceCipher}为加密或者解密模式 * - * @param mode 模式,见{@link Cipher#ENCRYPT_MODE} 或 {@link Cipher#DECRYPT_MODE} + * @param mode 模式,见{@link CipherMode#ENCRYPT} 或 {@link CipherMode#DECRYPT} * @return {@link Cipher} - * @throws InvalidKeyException 无效key - * @throws InvalidAlgorithmParameterException 无效算法 */ - private Cipher initMode(final int mode, final byte[] salt) throws InvalidKeyException, InvalidAlgorithmParameterException { + private JceCipher initMode(final CipherMode mode, final byte[] salt) { SecretKey secretKey = this.secretKey; if (null != salt) { // /issues#I6YWWD,提供OpenSSL格式兼容支持 @@ -454,11 +454,15 @@ public class SymmetricCrypto implements SymmetricEncryptor, SymmetricDecryptor, final byte[][] keyAndIV = OpenSSLSaltParser.ofMd5(32, algorithm) .getKeyAndIV(secretKey.getEncoded(), salt); secretKey = KeyUtil.generateKey(algorithm, keyAndIV[0]); - if(ArrayUtil.isNotEmpty(keyAndIV[1])){ - this.cipherWrapper.setParams(new IvParameterSpec(keyAndIV[1])); + if (ArrayUtil.isNotEmpty(keyAndIV[1])) { + setAlgorithmParameterSpec(new IvParameterSpec(keyAndIV[1])); } } - return this.cipherWrapper.initMode(mode, secretKey).getRaw(); + + final JceCipher cipher = this.cipher; + cipher.init(mode, + new JceCipher.JceParameters(secretKey, this.algorithmParameterSpec, this.random)); + return cipher; } /** diff --git a/hutool-crypto/src/test/java/org/dromara/hutool/crypto/bc/BCCipherTest.java b/hutool-crypto/src/test/java/org/dromara/hutool/crypto/bc/BCCipherTest.java new file mode 100644 index 000000000..b614c1ad5 --- /dev/null +++ b/hutool-crypto/src/test/java/org/dromara/hutool/crypto/bc/BCCipherTest.java @@ -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); + } +} diff --git a/hutool-crypto/src/test/java/org/dromara/hutool/crypto/symmetric/SymmetricTest.java b/hutool-crypto/src/test/java/org/dromara/hutool/crypto/symmetric/SymmetricTest.java index bfb6d7d2f..7a939c9ee 100644 --- a/hutool-crypto/src/test/java/org/dromara/hutool/crypto/symmetric/SymmetricTest.java +++ b/hutool-crypto/src/test/java/org/dromara/hutool/crypto/symmetric/SymmetricTest.java @@ -135,9 +135,9 @@ public class SymmetricTest { final AES aes = new AES(Mode.CBC, Padding.PKCS5Padding, "0123456789ABHAEQ".getBytes(), "DYgjCEIMVrj2W9xN".getBytes()); // 加密为16进制表示 - aes.setMode(CipherMode.encrypt); + aes.setMode(CipherMode.ENCRYPT); final String randomData = aes.updateHex(content.getBytes(StandardCharsets.UTF_8)); - aes.setMode(CipherMode.encrypt); + aes.setMode(CipherMode.ENCRYPT); final String randomData2 = aes.updateHex(content.getBytes(StandardCharsets.UTF_8)); Assertions.assertEquals(randomData2, randomData); Assertions.assertEquals(randomData, "cd0e3a249eaf0ed80c330338508898c4"); diff --git a/hutool-db/README.md b/hutool-db/README.md index 603876838..97d7fd3bb 100644 --- a/hutool-db/README.md +++ b/hutool-db/README.md @@ -28,7 +28,7 @@ ### SQL相关工具(sql) 提供SQL相关功能,包括SQL变量替换(NamedSql),通过对象完成SQL构建(SqlBuilder)等。 -`SqlSqlExecutor`提供SQL执行的静态方法。 +`SqlExecutor`提供SQL执行的静态方法。 ### 数据库元信息(meta) 通过`MetaUtil`提供数据库表、字段等信息的读取操作。 diff --git a/hutool-db/pom.xml b/hutool-db/pom.xml index bab350310..36cfd6d7a 100755 --- a/hutool-db/pom.xml +++ b/hutool-db/pom.xml @@ -21,7 +21,7 @@ org.dromara.hutool hutool-parent - 6.0.0-M10 + 6.0.0-M11 hutool-db @@ -38,7 +38,7 @@ 1.2.21 4.0.3 - 3.43.0.0 + 3.44.1.0 2.5.2 diff --git a/hutool-db/src/main/java/org/dromara/hutool/db/StatementUtil.java b/hutool-db/src/main/java/org/dromara/hutool/db/StatementUtil.java index 449109869..679faed72 100644 --- a/hutool-db/src/main/java/org/dromara/hutool/db/StatementUtil.java +++ b/hutool-db/src/main/java/org/dromara/hutool/db/StatementUtil.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 looly(loolly@aliyun.com) + * Copyright (c) 2023-2024. looly(loolly@aliyun.com) * Hutool is licensed under Mulan PSL v2. * You can use this software according to the terms and conditions of the Mulan PSL v2. * You may obtain a copy of Mulan PSL v2 at: @@ -12,14 +12,15 @@ package org.dromara.hutool.db; +import org.dromara.hutool.core.array.ArrayUtil; import org.dromara.hutool.core.collection.iter.ArrayIter; import org.dromara.hutool.core.lang.Assert; import org.dromara.hutool.db.handler.ResultSetUtil; import org.dromara.hutool.db.handler.RsHandler; import org.dromara.hutool.db.sql.SqlBuilder; -import org.dromara.hutool.db.sql.SqlLog; import org.dromara.hutool.db.sql.StatementBuilder; import org.dromara.hutool.db.sql.StatementWrapper; +import org.dromara.hutool.db.sql.filter.SqlLogFilter; import java.sql.*; import java.util.Collection; @@ -87,7 +88,7 @@ public class StatementUtil { return StatementBuilder.of() .setConnection(conn) .setReturnGeneratedKey(returnGeneratedKey) - .setSqlLog(SqlLog.INSTANCE) + .setSqlFilter(SqlLogFilter.INSTANCE) .setSql(sql) .setParams(params) .build(); @@ -120,29 +121,10 @@ public class StatementUtil { return StatementBuilder.of() .setConnection(conn) .setReturnGeneratedKey(false) - .setSqlLog(SqlLog.INSTANCE) + .setSqlFilter(SqlLogFilter.INSTANCE) .setSql(sql) - .buildForBatch(paramsBatch); - } - - /** - * 创建批量操作的{@link PreparedStatement} - * - * @param conn 数据库连接 - * @param sql SQL语句,使用"?"做为占位符 - * @param fields 字段列表,用于获取对应值 - * @param entities "?"对应参数批次列表 - * @return {@link PreparedStatement} - * @since 4.6.7 - */ - public static PreparedStatement prepareStatementForBatch(final Connection conn, final String sql, - final Iterable fields, final Entity... entities) { - return StatementBuilder.of() - .setConnection(conn) - .setReturnGeneratedKey(false) - .setSqlLog(SqlLog.INSTANCE) - .setSql(sql) - .buildForBatch(fields, entities); + .setParams(ArrayUtil.ofArray(paramsBatch, Object.class)) + .buildForBatch(); } /** @@ -158,7 +140,7 @@ public class StatementUtil { public static CallableStatement prepareCall(final Connection conn, final String sql, final Object... params) throws SQLException { return StatementBuilder.of() .setConnection(conn) - .setSqlLog(SqlLog.INSTANCE) + .setSqlFilter(SqlLogFilter.INSTANCE) .setSql(sql) .setParams(params) .buildForCall(); diff --git a/hutool-db/src/main/java/org/dromara/hutool/db/dialect/impl/AnsiSqlDialect.java b/hutool-db/src/main/java/org/dromara/hutool/db/dialect/impl/AnsiSqlDialect.java index fdc6ff605..64bb94fe2 100644 --- a/hutool-db/src/main/java/org/dromara/hutool/db/dialect/impl/AnsiSqlDialect.java +++ b/hutool-db/src/main/java/org/dromara/hutool/db/dialect/impl/AnsiSqlDialect.java @@ -53,21 +53,20 @@ public class AnsiSqlDialect implements Dialect { } @Override - public PreparedStatement psForInsert(final Connection conn, final Entity entity) throws SQLException { + public PreparedStatement psForInsert(final Connection conn, final Entity entity) { final SqlBuilder insert = SqlBuilder.of(quoteWrapper).insert(entity, this.dialectName()); return StatementUtil.prepareStatement(conn, insert); } @Override - public PreparedStatement psForInsertBatch(final Connection conn, final Entity... entities) throws SQLException { + public PreparedStatement psForInsertBatch(final Connection conn, final Entity... entities) { if (ArrayUtil.isEmpty(entities)) { throw new DbRuntimeException("Entities for batch insert is empty !"); } // 批量,根据第一行数据结构生成SQL占位符 final SqlBuilder insert = SqlBuilder.of(quoteWrapper).insert(entities[0], this.dialectName()); - final Set fields = CollUtil.remove(entities[0].keySet(), StrUtil::isBlank); - return StatementUtil.prepareStatementForBatch(conn, insert.build(), fields, entities); + return StatementUtil.prepareStatementForBatch(conn, insert.build(), entities); } @Override @@ -100,12 +99,12 @@ public class AnsiSqlDialect implements Dialect { } @Override - public PreparedStatement psForFind(final Connection conn, final Query query) throws SQLException { + public PreparedStatement psForFind(final Connection conn, final Query query) { return psForPage(conn, query); } @Override - public PreparedStatement psForPage(final Connection conn, final Query query) throws SQLException { + public PreparedStatement psForPage(final Connection conn, final Query query) { Assert.notNull(query, "query must be not null !"); if (ArrayUtil.hasBlank(query.getTableNames())) { throw new DbRuntimeException("Table name must be not empty !"); @@ -116,7 +115,7 @@ public class AnsiSqlDialect implements Dialect { } @Override - public PreparedStatement psForPage(final Connection conn, SqlBuilder sqlBuilder, final Page page) throws SQLException { + public PreparedStatement psForPage(final Connection conn, SqlBuilder sqlBuilder, final Page page) { // 根据不同数据库在查询SQL语句基础上包装其分页的语句 if (null != page) { sqlBuilder = wrapPageSql(sqlBuilder.orderBy(page.getOrders()), page); diff --git a/hutool-db/src/main/java/org/dromara/hutool/db/dialect/impl/OracleDialect.java b/hutool-db/src/main/java/org/dromara/hutool/db/dialect/impl/OracleDialect.java index 06f822a46..de67b8612 100644 --- a/hutool-db/src/main/java/org/dromara/hutool/db/dialect/impl/OracleDialect.java +++ b/hutool-db/src/main/java/org/dromara/hutool/db/dialect/impl/OracleDialect.java @@ -12,6 +12,7 @@ package org.dromara.hutool.db.dialect.impl; +import org.dromara.hutool.core.text.StrPool; import org.dromara.hutool.core.text.StrUtil; import org.dromara.hutool.db.Page; import org.dromara.hutool.db.dialect.DialectName; @@ -25,6 +26,10 @@ import org.dromara.hutool.db.sql.SqlBuilder; public class OracleDialect extends AnsiSqlDialect { private static final long serialVersionUID = 6122761762247483015L; + private static final String DEFAULT_TABLE_ALIAS = "table_alias_"; + private static final String DEFAULT_ROW_ALIAS = "row_"; + private static final String DEFAULT_ROWNUM_ALIAS = "rownum_"; + /** * 检查字段值是否为Oracle自增字段,自增字段以`.nextval`结尾 * @@ -36,6 +41,9 @@ public class OracleDialect extends AnsiSqlDialect { return (value instanceof CharSequence) && StrUtil.endWithIgnoreCase(value.toString(), ".nextval"); } + /** + * 构造 + */ public OracleDialect() { //Oracle所有字段名用双引号包围,防止字段名或表名与系统关键字冲突 //wrapper = new Wrapper('"'); @@ -44,11 +52,27 @@ public class OracleDialect extends AnsiSqlDialect { @Override protected SqlBuilder wrapPageSql(final SqlBuilder find, final Page page) { final int[] startEnd = page.getStartEnd(); + + // 检查别名,避免重名 + final String sql = find.toString(); + String tableAlias = DEFAULT_TABLE_ALIAS; + while (sql.contains(tableAlias)) { + tableAlias += StrPool.UNDERLINE; + } + String rowAlias = DEFAULT_ROW_ALIAS; + while (sql.contains(rowAlias)) { + rowAlias += StrPool.UNDERLINE; + } + String rownumAlias = DEFAULT_ROWNUM_ALIAS; + while (sql.contains(rownumAlias)) { + rownumAlias += StrPool.UNDERLINE; + } + return find - .insertPreFragment("SELECT * FROM ( SELECT row_.*, rownum rownum_ from ( ") - .append(" ) row_ where rownum <= ").append(startEnd[1])// - .append(") table_alias_")// - .append(" where table_alias_.rownum_ > ").append(startEnd[0]);// + .insertPreFragment("SELECT * FROM ( SELECT " + rowAlias + ".*, rownum " + rownumAlias + " from ( ") + .append(" ) row_ where rownum <= ").append(startEnd[1])// + .append(") ").append(tableAlias)// + .append(" where ").append(tableAlias).append(".rownum_ > ").append(startEnd[0]);// } @Override diff --git a/hutool-db/src/main/java/org/dromara/hutool/db/dialect/impl/PhoenixDialect.java b/hutool-db/src/main/java/org/dromara/hutool/db/dialect/impl/PhoenixDialect.java index ebfa95146..0d9966ab0 100644 --- a/hutool-db/src/main/java/org/dromara/hutool/db/dialect/impl/PhoenixDialect.java +++ b/hutool-db/src/main/java/org/dromara/hutool/db/dialect/impl/PhoenixDialect.java @@ -29,6 +29,9 @@ import java.sql.SQLException; public class PhoenixDialect extends AnsiSqlDialect { private static final long serialVersionUID = 1L; + /** + * 构造 + */ public PhoenixDialect() { // wrapper = new Wrapper('"'); } diff --git a/hutool-db/src/main/java/org/dromara/hutool/db/dialect/impl/SqlServer2012Dialect.java b/hutool-db/src/main/java/org/dromara/hutool/db/dialect/impl/SqlServer2012Dialect.java index fd98c9ebf..b879b0195 100644 --- a/hutool-db/src/main/java/org/dromara/hutool/db/dialect/impl/SqlServer2012Dialect.java +++ b/hutool-db/src/main/java/org/dromara/hutool/db/dialect/impl/SqlServer2012Dialect.java @@ -21,15 +21,17 @@ import org.dromara.hutool.db.sql.QuoteWrapper; /** * SQLServer2012 方言 * - * @author loolly - * + * @author Looly */ public class SqlServer2012Dialect extends AnsiSqlDialect { private static final long serialVersionUID = -37598166015777797L; + /** + * 构造 + */ public SqlServer2012Dialect() { //双引号和中括号适用,双引号更广泛 - quoteWrapper = new QuoteWrapper('"'); + quoteWrapper = new QuoteWrapper('"'); } @Override @@ -39,10 +41,10 @@ public class SqlServer2012Dialect extends AnsiSqlDialect { find.append(" order by current_timestamp"); } return find.append(" offset ") - .append(page.getStartPosition())// - .append(" row fetch next ")//row和rows同义词 - .append(page.getPageSize())// - .append(" row only");// + .append(page.getStartPosition())// + .append(" row fetch next ")//row和rows同义词 + .append(page.getPageSize())// + .append(" row only");// } @Override diff --git a/hutool-db/src/main/java/org/dromara/hutool/db/dialect/impl/Sqlite3Dialect.java b/hutool-db/src/main/java/org/dromara/hutool/db/dialect/impl/Sqlite3Dialect.java index d8209e501..fc9217b2f 100644 --- a/hutool-db/src/main/java/org/dromara/hutool/db/dialect/impl/Sqlite3Dialect.java +++ b/hutool-db/src/main/java/org/dromara/hutool/db/dialect/impl/Sqlite3Dialect.java @@ -17,12 +17,15 @@ import org.dromara.hutool.db.sql.QuoteWrapper; /** * SqlLite3方言 - * @author loolly * + * @author Looly */ -public class Sqlite3Dialect extends AnsiSqlDialect{ +public class Sqlite3Dialect extends AnsiSqlDialect { private static final long serialVersionUID = -3527642408849291634L; + /** + * 构造 + */ public Sqlite3Dialect() { quoteWrapper = new QuoteWrapper('[', ']'); } diff --git a/hutool-db/src/main/java/org/dromara/hutool/db/ds/DSPool.java b/hutool-db/src/main/java/org/dromara/hutool/db/ds/DSPool.java index 20cc1aa0b..90cef3077 100644 --- a/hutool-db/src/main/java/org/dromara/hutool/db/ds/DSPool.java +++ b/hutool-db/src/main/java/org/dromara/hutool/db/ds/DSPool.java @@ -19,6 +19,7 @@ import org.dromara.hutool.core.map.SafeConcurrentHashMap; import org.dromara.hutool.core.spi.SpiUtil; import org.dromara.hutool.core.text.StrUtil; import org.dromara.hutool.db.DbRuntimeException; +import org.dromara.hutool.db.DbUtil; import org.dromara.hutool.db.GlobalDbConfig; import org.dromara.hutool.db.driver.DriverUtil; import org.dromara.hutool.log.LogUtil; @@ -89,6 +90,7 @@ public class DSPool implements Closeable { */ public DSPool(final Setting setting, final DSFactory factory) { this.setting = null != setting ? setting : GlobalDbConfig.createDbSetting(); + DbUtil.setShowSqlGlobal(this.setting); this.factory = null != factory ? factory : SpiUtil.loadFirstAvailable(DSFactory.class); this.pool = new SafeConcurrentHashMap<>(); } diff --git a/hutool-db/src/main/java/org/dromara/hutool/db/ds/DSWrapper.java b/hutool-db/src/main/java/org/dromara/hutool/db/ds/DSWrapper.java index 1109de772..2490387ca 100644 --- a/hutool-db/src/main/java/org/dromara/hutool/db/ds/DSWrapper.java +++ b/hutool-db/src/main/java/org/dromara/hutool/db/ds/DSWrapper.java @@ -14,7 +14,7 @@ package org.dromara.hutool.db.ds; import org.dromara.hutool.core.exception.CloneException; import org.dromara.hutool.core.io.IoUtil; -import org.dromara.hutool.core.lang.wrapper.Wrapper; +import org.dromara.hutool.core.lang.wrapper.SimpleWrapper; import javax.sql.DataSource; import java.io.Closeable; @@ -34,9 +34,8 @@ import java.util.logging.Logger; * @author looly * @since 4.3.2 */ -public class DSWrapper implements Wrapper, DataSource, Closeable, Cloneable { +public class DSWrapper extends SimpleWrapper implements DataSource, Closeable, Cloneable { - private final DataSource ds; private final String driver; /** @@ -57,7 +56,7 @@ public class DSWrapper implements Wrapper, DataSource, Closeable, Cl * @param driver 数据库驱动类名 */ public DSWrapper(final DataSource ds, final String driver) { - this.ds = ds; + super(ds); this.driver = driver; } @@ -70,65 +69,56 @@ public class DSWrapper implements Wrapper, DataSource, Closeable, Cl return this.driver; } - /** - * 获取原始的数据源 - * - * @return 原始数据源 - */ - @Override - public DataSource getRaw() { - return this.ds; - } - @Override public PrintWriter getLogWriter() throws SQLException { - return ds.getLogWriter(); + return this.raw.getLogWriter(); } @Override public void setLogWriter(final PrintWriter out) throws SQLException { - ds.setLogWriter(out); + this.raw.setLogWriter(out); } @Override public void setLoginTimeout(final int seconds) throws SQLException { - ds.setLoginTimeout(seconds); + this.raw.setLoginTimeout(seconds); } @Override public int getLoginTimeout() throws SQLException { - return ds.getLoginTimeout(); + return this.raw.getLoginTimeout(); } @Override public Logger getParentLogger() throws SQLFeatureNotSupportedException { - return ds.getParentLogger(); + return this.raw.getParentLogger(); } @Override public T unwrap(final Class iface) throws SQLException { - return ds.unwrap(iface); + return this.raw.unwrap(iface); } @Override public boolean isWrapperFor(final Class iface) throws SQLException { - return ds.isWrapperFor(iface); + return this.raw.isWrapperFor(iface); } @Override public Connection getConnection() throws SQLException { - return ds.getConnection(); + return this.raw.getConnection(); } @Override public Connection getConnection(final String username, final String password) throws SQLException { - return ds.getConnection(username, password); + return this.raw.getConnection(username, password); } @Override public void close() { - if (this.ds instanceof AutoCloseable) { - IoUtil.closeQuietly((AutoCloseable) this.ds); + final DataSource ds = this.raw; + if (ds instanceof AutoCloseable) { + IoUtil.closeQuietly((AutoCloseable) ds); } } diff --git a/hutool-db/src/main/java/org/dromara/hutool/db/sql/BoundSql.java b/hutool-db/src/main/java/org/dromara/hutool/db/sql/BoundSql.java new file mode 100644 index 000000000..1801c08c7 --- /dev/null +++ b/hutool-db/src/main/java/org/dromara/hutool/db/sql/BoundSql.java @@ -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语句模板('?'表示参数占位符)和参数值的封装
+ * SQL中的'?'占位符必须和params列表中的参数值一一对应 + * + * @author Looly + */ +public class BoundSql { + + private String sql; + private List params; + + /** + * 构造 + */ + public BoundSql() {} + + /** + * 构造 + * + * @param sql SQL语句,参数占位符使用'?'表示 + * @param params 参数列表,每个参数对应一个'?' + */ + public BoundSql(final String sql, final List 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 getParams() { + return this.params; + } + + /** + * 获取参数列表,按照占位符顺序 + * + * @return 参数数组 + */ + public Object[] getParamArray() { + return this.params.toArray(new Object[0]); + } + + /** + * 设置参数列表 + * + * @param params 参数列表 + * @return this + */ + public BoundSql setParams(final List 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; + } +} diff --git a/hutool-db/src/main/java/org/dromara/hutool/db/sql/NamedSql.java b/hutool-db/src/main/java/org/dromara/hutool/db/sql/NamedSql.java index 68b763f8e..9ba5354a8 100644 --- a/hutool-db/src/main/java/org/dromara/hutool/db/sql/NamedSql.java +++ b/hutool-db/src/main/java/org/dromara/hutool/db/sql/NamedSql.java @@ -12,13 +12,11 @@ package org.dromara.hutool.db.sql; +import org.dromara.hutool.core.array.ArrayUtil; import org.dromara.hutool.core.map.MapUtil; import org.dromara.hutool.core.text.StrUtil; -import org.dromara.hutool.core.array.ArrayUtil; import java.util.Collection; -import java.util.LinkedList; -import java.util.List; import java.util.Map; /** @@ -33,12 +31,12 @@ import java.util.Map; * @author looly * @since 4.0.10 */ -public class NamedSql { +public class NamedSql extends BoundSql { private static final char[] NAME_START_CHARS = {':', '@', '?'}; - private String sql; - private final List params; + private final String namedSql; + private final Map paramMap; /** * 构造 @@ -47,35 +45,27 @@ public class NamedSql { * @param paramMap 名和参数的对应Map */ public NamedSql(final String namedSql, final Map paramMap) { - this.params = new LinkedList<>(); + this.namedSql = namedSql; + this.paramMap = paramMap; parse(namedSql, paramMap); } /** - * 获取SQL + * 获取原始地带名称占位符的SQL语句 * - * @return SQL + * @return 名称占位符的SQL */ - public String getSql() { - return this.sql; + public String getNamedSql() { + return namedSql; } /** - * 获取参数列表,按照占位符顺序 + * 获取原始参数名和参数值对应关系参数表 * - * @return 参数数组 + * @return 参数名和参数值对应关系参数表 */ - public Object[] getParams() { - return this.params.toArray(new Object[0]); - } - - /** - * 获取参数列表,按照占位符顺序 - * - * @return 参数列表 - */ - public List getParamList() { - return this.params; + public Map getParamMap() { + return paramMap; } /** @@ -86,7 +76,7 @@ public class NamedSql { */ private void parse(final String namedSql, final Map paramMap) { if (MapUtil.isEmpty(paramMap)) { - this.sql = namedSql; + setSql(namedSql); return; } @@ -124,7 +114,7 @@ public class NamedSql { replaceVar(nameStartChar, name, sqlBuilder, paramMap); } - this.sql = sqlBuilder.toString(); + setSql(sqlBuilder.toString()); } /** @@ -163,11 +153,11 @@ public class NamedSql { sqlBuilder.append(','); } sqlBuilder.append('?'); - this.params.add(ArrayUtil.get(paramValue, i)); + addParam(ArrayUtil.get(paramValue, i)); } } else { sqlBuilder.append('?'); - this.params.add(paramValue); + addParam(paramValue); } } else { // 无变量对应值,原样输出 diff --git a/hutool-db/src/main/java/org/dromara/hutool/db/sql/SqlExecutor.java b/hutool-db/src/main/java/org/dromara/hutool/db/sql/SqlExecutor.java index a4245d661..90f6361f8 100644 --- a/hutool-db/src/main/java/org/dromara/hutool/db/sql/SqlExecutor.java +++ b/hutool-db/src/main/java/org/dromara/hutool/db/sql/SqlExecutor.java @@ -44,7 +44,7 @@ public class SqlExecutor { */ public static int execute(final Connection conn, final String sql, final Map paramMap) throws DbRuntimeException { final NamedSql namedSql = new NamedSql(sql, paramMap); - return execute(conn, namedSql.getSql(), namedSql.getParams()); + return execute(conn, namedSql.getSql(), namedSql.getParamArray()); } /** @@ -125,7 +125,7 @@ public class SqlExecutor { */ public static Long executeForGeneratedKey(final Connection conn, final String sql, final Map paramMap) throws DbRuntimeException { final NamedSql namedSql = new NamedSql(sql, paramMap); - return executeForGeneratedKey(conn, namedSql.getSql(), namedSql.getParams()); + return executeForGeneratedKey(conn, namedSql.getSql(), namedSql.getParamArray()); } /** @@ -241,7 +241,7 @@ public class SqlExecutor { */ public static T query(final Connection conn, final String sql, final RsHandler rsh, final Map paramMap) throws DbRuntimeException { final NamedSql namedSql = new NamedSql(sql, paramMap); - return query(conn, namedSql.getSql(), rsh, namedSql.getParams()); + return query(conn, namedSql.getSql(), rsh, namedSql.getParamArray()); } /** diff --git a/hutool-db/src/main/java/org/dromara/hutool/db/sql/StatementBuilder.java b/hutool-db/src/main/java/org/dromara/hutool/db/sql/StatementBuilder.java index 521342b75..91d6d0845 100644 --- a/hutool-db/src/main/java/org/dromara/hutool/db/sql/StatementBuilder.java +++ b/hutool-db/src/main/java/org/dromara/hutool/db/sql/StatementBuilder.java @@ -13,6 +13,7 @@ package org.dromara.hutool.db.sql; import org.dromara.hutool.core.array.ArrayUtil; +import org.dromara.hutool.core.collection.ListUtil; import org.dromara.hutool.core.collection.iter.ArrayIter; import org.dromara.hutool.core.convert.Convert; import org.dromara.hutool.core.lang.Assert; @@ -21,10 +22,13 @@ import org.dromara.hutool.core.map.MapUtil; import org.dromara.hutool.core.text.StrUtil; import org.dromara.hutool.db.DbRuntimeException; import org.dromara.hutool.db.Entity; +import org.dromara.hutool.db.sql.filter.SqlFilter; import java.sql.*; import java.util.HashMap; +import java.util.List; import java.util.Map; +import java.util.Set; /** * {@link PreparedStatement}构建器,构建结果为{@link StatementWrapper} @@ -44,20 +48,19 @@ public class StatementBuilder implements Builder { return new StatementBuilder(); } - private SqlLog sqlLog; + private final BoundSql boundSql = new BoundSql(); private Connection connection; - private String sql; - private Object[] params; private boolean returnGeneratedKey = true; + private SqlFilter sqlFilter; /** * 设置SQL日志 * - * @param sqlLog {@link SqlLog} + * @param sqlFilter {@link SqlFilter} * @return this */ - public StatementBuilder setSqlLog(final SqlLog sqlLog) { - this.sqlLog = sqlLog; + public StatementBuilder setSqlFilter(final SqlFilter sqlFilter) { + this.sqlFilter = sqlFilter; return this; } @@ -79,7 +82,7 @@ public class StatementBuilder implements Builder { * @return this */ public StatementBuilder setSql(final String sql) { - this.sql = StrUtil.trim(sql); + this.boundSql.setSql(sql); return this; } @@ -90,7 +93,7 @@ public class StatementBuilder implements Builder { * @return this */ public StatementBuilder setParams(final Object... params) { - this.params = params; + this.boundSql.setParams(ListUtil.of(params)); return this; } @@ -105,6 +108,11 @@ public class StatementBuilder implements Builder { return this; } + /** + * 构建{@link StatementWrapper} + * + * @return {@link StatementWrapper},{@code null}表示不执行 + */ @Override public StatementWrapper build() { try { @@ -117,48 +125,37 @@ public class StatementBuilder implements Builder { /** * 创建批量操作的{@link StatementWrapper} * - * @param paramsBatch "?"对应参数批次列表 - * @return {@link StatementWrapper} + * @return {@link StatementWrapper},{@code null}表示不执行 * @throws DbRuntimeException SQL异常 */ - public StatementWrapper buildForBatch(final Iterable paramsBatch) throws DbRuntimeException { + public StatementWrapper buildForBatch() throws DbRuntimeException { + final String sql = this.boundSql.getSql(); Assert.notBlank(sql, "Sql String must be not blank!"); + final List paramsBatch = this.boundSql.getParams(); - sqlLog.log(sql, paramsBatch); + sqlFilter.filter(this.connection, this.boundSql, this.returnGeneratedKey); final StatementWrapper ps; try { ps = StatementWrapper.of(connection.prepareStatement(sql)); final Map nullTypeMap = new HashMap<>(); - for (final Object[] params : paramsBatch) { - ps.fillParams(new ArrayIter<>(params), nullTypeMap); - ps.addBatch(); - } - } catch (final SQLException e) { - throw new DbRuntimeException(e); - } - return ps; - } - - /** - * 创建批量操作的{@link StatementWrapper} - * - * @param fields 字段列表,用于获取对应值 - * @param entities "?"对应参数批次列表 - * @return {@link StatementWrapper} - * @throws DbRuntimeException SQL异常 - */ - public StatementWrapper buildForBatch(final Iterable 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 nullTypeMap = new HashMap<>(); - for (final Entity entity : entities) { - ps.fillParams(MapUtil.valuesOfKeys(entity, fields), nullTypeMap); + Set keys = null; + for (final Object params : paramsBatch) { + if (null == params) { + continue; + } + if (ArrayUtil.isArray(params)) { + ps.fillParams(new ArrayIter<>(params), nullTypeMap); + } else if (params instanceof Entity) { + final Entity entity = (Entity) params; + // 对于多Entity批量插入的情况,为防止数据不对齐,故按照首行提供键值对筛选。 + if(null == keys){ + keys = entity.keySet(); + ps.fillParams(entity.values(), nullTypeMap); + } else{ + ps.fillParams(MapUtil.valuesOfKeys(entity, keys), nullTypeMap); + } + } ps.addBatch(); } } catch (final SQLException e) { @@ -170,12 +167,15 @@ public class StatementBuilder implements Builder { /** * 创建存储过程或函数调用的{@link StatementWrapper} * - * @return StatementWrapper + * @return StatementWrapper,{@code null}表示不执行 * @since 6.0.0 */ public CallableStatement buildForCall() { + final String sql = this.boundSql.getSql(); + final Object[] params = this.boundSql.getParamArray(); Assert.notBlank(sql, "Sql String must be not blank!"); - sqlLog.log(sql, ArrayUtil.isEmpty(params) ? null : params); + + sqlFilter.filter(this.connection, this.boundSql, this.returnGeneratedKey); try { return (CallableStatement) StatementWrapper @@ -190,20 +190,23 @@ public class StatementBuilder implements Builder { /** * 构建{@link StatementWrapper} * - * @return {@link StatementWrapper} + * @return {@link StatementWrapper},{@code null}表示不执行 * @throws SQLException SQL异常 */ private StatementWrapper _build() throws SQLException { + String sql = this.boundSql.getSql(); + Object[] params = this.boundSql.getParamArray(); Assert.notBlank(sql, "Sql String must be not blank!"); if (ArrayUtil.isNotEmpty(params) && 1 == params.length && params[0] instanceof Map) { // 检查参数是否为命名方式的参数 - final NamedSql namedSql = new NamedSql(sql, Convert.toMap(String.class, Object.class, params[0])); + final NamedSql namedSql = new NamedSql(sql, Convert.toMap(String.class, Object.class, params[0])); sql = namedSql.getSql(); - params = namedSql.getParams(); + params = namedSql.getParamArray(); } - sqlLog.log(sql, ArrayUtil.isEmpty(params) ? null : params); + sqlFilter.filter(this.connection, this.boundSql, this.returnGeneratedKey); + final PreparedStatement ps; if (returnGeneratedKey && StrUtil.startWithIgnoreCase(sql, "insert")) { // 插入默认返回主键 diff --git a/hutool-db/src/main/java/org/dromara/hutool/db/sql/filter/SqlFilter.java b/hutool-db/src/main/java/org/dromara/hutool/db/sql/filter/SqlFilter.java new file mode 100644 index 000000000..5a06a5445 --- /dev/null +++ b/hutool-db/src/main/java/org/dromara/hutool/db/sql/filter/SqlFilter.java @@ -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); +} diff --git a/hutool-db/src/main/java/org/dromara/hutool/db/sql/filter/SqlLogFilter.java b/hutool-db/src/main/java/org/dromara/hutool/db/sql/filter/SqlLogFilter.java new file mode 100644 index 000000000..53ec08480 --- /dev/null +++ b/hutool-db/src/main/java/org/dromara/hutool/db/sql/filter/SqlLogFilter.java @@ -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()); + } +} diff --git a/hutool-db/src/main/java/org/dromara/hutool/db/sql/filter/package-info.java b/hutool-db/src/main/java/org/dromara/hutool/db/sql/filter/package-info.java new file mode 100644 index 000000000..e1ee60e95 --- /dev/null +++ b/hutool-db/src/main/java/org/dromara/hutool/db/sql/filter/package-info.java @@ -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; diff --git a/hutool-db/src/test/java/org/dromara/hutool/db/NamedSqlTest.java b/hutool-db/src/test/java/org/dromara/hutool/db/NamedSqlTest.java index bbee47be0..0253facb3 100644 --- a/hutool-db/src/test/java/org/dromara/hutool/db/NamedSqlTest.java +++ b/hutool-db/src/test/java/org/dromara/hutool/db/NamedSqlTest.java @@ -36,8 +36,8 @@ public class NamedSqlTest { final NamedSql namedSql = new NamedSql(sql, paramMap); //未指定参数原样输出 Assertions.assertEquals("select * from table where id=@id and name = ? and nickName = ?", namedSql.getSql()); - Assertions.assertEquals("张三", namedSql.getParams()[0]); - Assertions.assertEquals("小豆豆", namedSql.getParams()[1]); + Assertions.assertEquals("张三", namedSql.getParamArray()[0]); + Assertions.assertEquals("小豆豆", namedSql.getParamArray()[1]); } @Test @@ -54,9 +54,9 @@ public class NamedSqlTest { final NamedSql namedSql = new NamedSql(sql, paramMap); Assertions.assertEquals("select * from table where id=? and name = ? and nickName = ?", namedSql.getSql()); //指定了null参数的依旧替换,参数值为null - Assertions.assertNull(namedSql.getParams()[0]); - Assertions.assertEquals("张三", namedSql.getParams()[1]); - Assertions.assertEquals("小豆豆", namedSql.getParams()[2]); + Assertions.assertNull(namedSql.getParamArray()[0]); + Assertions.assertEquals("张三", namedSql.getParamArray()[1]); + Assertions.assertEquals("小豆豆", namedSql.getParamArray()[2]); } @Test @@ -92,9 +92,9 @@ public class NamedSqlTest { final NamedSql namedSql = new NamedSql(sql, paramMap); Assertions.assertEquals("select * from user where id in (?,?,?)", namedSql.getSql()); - Assertions.assertEquals(1, namedSql.getParams()[0]); - Assertions.assertEquals(2, namedSql.getParams()[1]); - Assertions.assertEquals(3, namedSql.getParams()[2]); + Assertions.assertEquals(1, namedSql.getParamArray()[0]); + Assertions.assertEquals(2, namedSql.getParamArray()[1]); + Assertions.assertEquals(3, namedSql.getParamArray()[2]); } @Test diff --git a/hutool-extra/pom.xml b/hutool-extra/pom.xml index 6231d4545..37b93a30a 100755 --- a/hutool-extra/pom.xml +++ b/hutool-extra/pom.xml @@ -21,7 +21,7 @@ org.dromara.hutool hutool-parent - 6.0.0-M10 + 6.0.0-M11 hutool-extra @@ -32,14 +32,14 @@ org.dromara.hutool.extra 2.3 - 3.15.8.RELEASE + 3.15.12.RELEASE 1.4.2 2.3.32 5.1.3 3.1.2.RELEASE 1.6.2 0.1.55 - 0.37.0 + 0.38.0 3.5.2 3.9.0 5.1.1 @@ -204,6 +204,14 @@ slf4j-api org.slf4j + + bcpkix-jdk18on + org.bouncycastle + + + bcprov-jdk18on + org.bouncycastle + true @@ -407,7 +415,7 @@ com.rnkrsoft.bopomofo4j bopomofo4j - 1.0.0 + 1.0.1 true @@ -488,7 +496,7 @@ org.apache.commons commons-compress - 1.24.0 + 1.25.0 compile true @@ -496,7 +504,7 @@ com.github.oshi oshi-core - 6.4.5 + 6.4.10 provided diff --git a/hutool-extra/src/main/java/org/dromara/hutool/extra/management/UserInfo.java b/hutool-extra/src/main/java/org/dromara/hutool/extra/management/UserInfo.java index a3293e8da..0e4786986 100644 --- a/hutool-extra/src/main/java/org/dromara/hutool/extra/management/UserInfo.java +++ b/hutool-extra/src/main/java/org/dromara/hutool/extra/management/UserInfo.java @@ -31,8 +31,11 @@ public class UserInfo implements Serializable{ private final String USER_LANGUAGE; private final String USER_COUNTRY; + /** + * 构造 + */ public UserInfo(){ - USER_NAME = fixPath(SystemUtil.get("user.name", false)); + USER_NAME = SystemUtil.get("user.name", false); USER_HOME = fixPath(SystemUtil.get("user.home", false)); USER_DIR = fixPath(SystemUtil.get("user.dir", false)); JAVA_IO_TMPDIR = fixPath(SystemUtil.get("java.io.tmpdir", false)); diff --git a/hutool-http/pom.xml b/hutool-http/pom.xml index 1955090ce..41686d9f7 100755 --- a/hutool-http/pom.xml +++ b/hutool-http/pom.xml @@ -21,7 +21,7 @@ org.dromara.hutool hutool-parent - 6.0.0-M10 + 6.0.0-M11 hutool-http diff --git a/hutool-json/pom.xml b/hutool-json/pom.xml index f80f9801c..dbbedfa36 100755 --- a/hutool-json/pom.xml +++ b/hutool-json/pom.xml @@ -21,7 +21,7 @@ org.dromara.hutool hutool-parent - 6.0.0-M10 + 6.0.0-M11 hutool-json @@ -31,7 +31,7 @@ org.dromara.hutool.json - 1.76 + 1.77 0.12.3 diff --git a/hutool-json/src/main/java/org/dromara/hutool/json/mapper/JSONObjectMapper.java b/hutool-json/src/main/java/org/dromara/hutool/json/mapper/JSONObjectMapper.java index ba6508dae..a102aa19f 100644 --- a/hutool-json/src/main/java/org/dromara/hutool/json/mapper/JSONObjectMapper.java +++ b/hutool-json/src/main/java/org/dromara/hutool/json/mapper/JSONObjectMapper.java @@ -29,6 +29,7 @@ import org.dromara.hutool.json.JSONTokener; import org.dromara.hutool.json.xml.JSONXMLUtil; import org.dromara.hutool.json.serialize.GlobalSerializeMapping; import org.dromara.hutool.json.serialize.JSONSerializer; +import org.dromara.hutool.json.xml.ParseConfig; import java.io.InputStream; import java.io.Reader; @@ -170,7 +171,7 @@ public class JSONObjectMapper { final String jsonStr = StrUtil.trim(source); if (StrUtil.startWith(jsonStr, '<')) { // 可能为XML - JSONXMLUtil.toJSONObject(jsonObject, jsonStr, false); + JSONXMLUtil.toJSONObject(jsonStr, jsonObject, ParseConfig.of()); return; } mapFromTokener(new JSONTokener(StrUtil.trim(source), jsonObject.config()), jsonObject); diff --git a/hutool-json/src/main/java/org/dromara/hutool/json/xml/JSONXMLParser.java b/hutool-json/src/main/java/org/dromara/hutool/json/xml/JSONXMLParser.java index addf574d7..524829c4a 100644 --- a/hutool-json/src/main/java/org/dromara/hutool/json/xml/JSONXMLParser.java +++ b/hutool-json/src/main/java/org/dromara/hutool/json/xml/JSONXMLParser.java @@ -12,7 +12,9 @@ package org.dromara.hutool.json.xml; +import org.dromara.hutool.core.text.CharUtil; import org.dromara.hutool.core.text.StrUtil; +import org.dromara.hutool.core.xml.XmlConstants; import org.dromara.hutool.json.JSONException; import org.dromara.hutool.json.JSONObject; import org.dromara.hutool.json.mapper.JSONValueMapper; @@ -29,28 +31,31 @@ public class JSONXMLParser { * 转换XML为JSONObject * 转换过程中一些信息可能会丢失,JSON中无法区分节点和属性,相同的节点将被处理为JSONArray。 * - * @param jo JSONObject * @param xmlStr XML字符串 - * @param keepStrings 如果为{@code true},则值保持String类型,不转换为数字或boolean + * @param jo JSONObject + * @param parseConfig 解析选项 * @throws JSONException 解析异常 */ - public static void parseJSONObject(final JSONObject jo, final String xmlStr, final boolean keepStrings) throws JSONException { + public static void parseJSONObject(final String xmlStr, final JSONObject jo, final ParseConfig parseConfig) throws JSONException { final XMLTokener x = new XMLTokener(xmlStr, jo.config()); while (x.more() && x.skipPast("<")) { - parse(x, jo, null, keepStrings); + parse(x, jo, null, parseConfig, 0); } } /** - * Scan the content following the named tag, attaching it to the context. + * 扫描XML内容,并解析到JSONObject中。 * - * @param x The XMLTokener containing the source string. - * @param context The JSONObject that will include the new material. - * @param name The tag name. - * @return true if the close tag is processed. + * @param x {@link XMLTokener} + * @param context {@link JSONObject} + * @param name 标签名,null表示从根标签开始解析 + * @param parseConfig 解析选项 + * @param currentNestingDepth 当前层级 + * @return {@code true}表示解析完成 * @throws JSONException JSON异常 */ - private static boolean parse(final XMLTokener x, final JSONObject context, final String name, final boolean keepStrings) throws JSONException { + private static boolean parse(final XMLTokener x, final JSONObject context, final String name, + final ParseConfig parseConfig, final int currentNestingDepth) throws JSONException { final char c; int i; final JSONObject jsonobject; @@ -60,7 +65,7 @@ public class JSONXMLParser { token = x.nextToken(); - if (token == JSONXMLUtil.BANG) { + if (token == XmlConstants.C_BANG) { c = x.next(); if (c == '-') { if (x.next() == '-') { @@ -86,19 +91,19 @@ public class JSONXMLParser { token = x.nextMeta(); if (token == null) { throw x.syntaxError("Missing '>' after ' 0); return false; - } else if (token == JSONXMLUtil.QUEST) { + } else if (token == XmlConstants.C_QUEST) { // "); return false; - } else if (token == JSONXMLUtil.SLASH) { + } else if (token == Character.valueOf(CharUtil.SLASH)) { // Close tag - if (x.nextToken() != JSONXMLUtil.GT) { + if (x.nextToken() != XmlConstants.C_GT) { throw x.syntaxError("Misshaped tag"); } if (!jsonobject.isEmpty()) { @@ -155,7 +161,7 @@ public class JSONXMLParser { } return false; - } else if (token == JSONXMLUtil.GT) { + } else if (token == XmlConstants.C_GT) { // Content, between <...> and for (; ; ) { token = x.nextContent(); @@ -170,9 +176,16 @@ public class JSONXMLParser { jsonobject.append("content", keepStrings ? token : JSONValueMapper.toJsonValue(string)); } - } else if (token == JSONXMLUtil.LT) { + } else if (token == XmlConstants.C_LT) { // Nested element - if (parse(x, jsonobject, tagName, keepStrings)) { + // issue#2748 of CVE-2022-45688 + final int maxNestingDepth = parseConfig.getMaxNestingDepth(); + if (maxNestingDepth > -1 && currentNestingDepth >= maxNestingDepth) { + throw x.syntaxError("Maximum nesting depth of " + maxNestingDepth + " reached"); + } + + // Nested element + if (parse(x, jsonobject, tagName, parseConfig, currentNestingDepth + 1)) { if (jsonobject.isEmpty()) { context.append(tagName, StrUtil.EMPTY); } else if (jsonobject.size() == 1 && jsonobject.get("content") != null) { diff --git a/hutool-json/src/main/java/org/dromara/hutool/json/xml/JSONXMLUtil.java b/hutool-json/src/main/java/org/dromara/hutool/json/xml/JSONXMLUtil.java index 13de4624a..083a2a56a 100644 --- a/hutool-json/src/main/java/org/dromara/hutool/json/xml/JSONXMLUtil.java +++ b/hutool-json/src/main/java/org/dromara/hutool/json/xml/JSONXMLUtil.java @@ -12,7 +12,6 @@ package org.dromara.hutool.json.xml; -import org.dromara.hutool.core.text.CharUtil; import org.dromara.hutool.json.JSONException; import org.dromara.hutool.json.JSONObject; @@ -25,51 +24,6 @@ import org.dromara.hutool.json.JSONObject; */ public class JSONXMLUtil { - /** - * The Character '&'. - */ - public static final Character AMP = CharUtil.AMP; - - /** - * The Character '''. - */ - public static final Character APOS = CharUtil.SINGLE_QUOTE; - - /** - * The Character '!'. - */ - public static final Character BANG = '!'; - - /** - * The Character '='. - */ - public static final Character EQ = '='; - - /** - * The Character '>'. - */ - public static final Character GT = '>'; - - /** - * The Character '<'. - */ - public static final Character LT = '<'; - - /** - * The Character '?'. - */ - public static final Character QUEST = '?'; - - /** - * The Character '"'. - */ - public static final Character QUOT = CharUtil.DOUBLE_QUOTES; - - /** - * The Character '/'. - */ - public static final Character SLASH = CharUtil.SLASH; - /** * 转换XML为JSONObject * 转换过程中一些信息可能会丢失,JSON中无法区分节点和属性,相同的节点将被处理为JSONArray。 @@ -80,7 +34,7 @@ public class JSONXMLUtil { * @throws JSONException Thrown if there is an errors while parsing the string */ public static JSONObject toJSONObject(final String string) throws JSONException { - return toJSONObject(string, false); + return toJSONObject(string, ParseConfig.of()); } /** @@ -89,13 +43,13 @@ public class JSONXMLUtil { * Content text may be placed in a "content" member. Comments, prologs, DTDs, and {@code <[ [ ]]>} are ignored. * All values are converted as strings, for 1, 01, 29.0 will not be coerced to numbers but will instead be the exact value as seen in the XML document. * - * @param string The source string. - * @param keepStrings If true, then values will not be coerced into boolean or numeric values and will instead be left as strings + * @param string XML字符串 + * @param parseConfig XML解析选项 * @return A JSONObject containing the structured data from the XML string. * @throws JSONException Thrown if there is an errors while parsing the string */ - public static JSONObject toJSONObject(final String string, final boolean keepStrings) throws JSONException { - return toJSONObject(new JSONObject(), string, keepStrings); + public static JSONObject toJSONObject(final String string, final ParseConfig parseConfig) throws JSONException { + return toJSONObject(string, new JSONObject(), parseConfig); } /** @@ -104,13 +58,13 @@ public class JSONXMLUtil { * * @param jo JSONObject * @param xmlStr XML字符串 - * @param keepStrings 如果为{@code true},则值保持String类型,不转换为数字或boolean + * @param parseConfig XML解析选项 * @return A JSONObject 解析后的JSON对象,与传入的jo为同一对象 * @throws JSONException 解析异常 * @since 5.3.1 */ - public static JSONObject toJSONObject(final JSONObject jo, final String xmlStr, final boolean keepStrings) throws JSONException { - JSONXMLParser.parseJSONObject(jo, xmlStr, keepStrings); + public static JSONObject toJSONObject(final String xmlStr, final JSONObject jo, final ParseConfig parseConfig) throws JSONException { + JSONXMLParser.parseJSONObject(xmlStr, jo, parseConfig); return jo; } diff --git a/hutool-json/src/main/java/org/dromara/hutool/json/xml/ParseConfig.java b/hutool-json/src/main/java/org/dromara/hutool/json/xml/ParseConfig.java new file mode 100644 index 000000000..d1fd7b33c --- /dev/null +++ b/hutool-json/src/main/java/org/dromara/hutool/json/xml/ParseConfig.java @@ -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的可选选项
+ * 参考: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; + } +} diff --git a/hutool-json/src/main/java/org/dromara/hutool/json/xml/XMLTokener.java b/hutool-json/src/main/java/org/dromara/hutool/json/xml/XMLTokener.java index 3569fba5f..d3eb34bb8 100644 --- a/hutool-json/src/main/java/org/dromara/hutool/json/xml/XMLTokener.java +++ b/hutool-json/src/main/java/org/dromara/hutool/json/xml/XMLTokener.java @@ -12,6 +12,8 @@ package org.dromara.hutool.json.xml; +import org.dromara.hutool.core.text.CharUtil; +import org.dromara.hutool.core.xml.XmlConstants; import org.dromara.hutool.json.JSONConfig; import org.dromara.hutool.json.JSONException; import org.dromara.hutool.json.JSONTokener; @@ -31,11 +33,11 @@ public class XMLTokener extends JSONTokener { static { entity = new java.util.HashMap<>(8); - entity.put("amp", JSONXMLUtil.AMP); - entity.put("apos", JSONXMLUtil.APOS); - entity.put("gt", JSONXMLUtil.GT); - entity.put("lt", JSONXMLUtil.LT); - entity.put("quot", JSONXMLUtil.QUOT); + entity.put("amp", XmlConstants.C_AMP); + entity.put("apos", XmlConstants.C_APOS); + entity.put("gt", XmlConstants.C_GT); + entity.put("lt", XmlConstants.C_LT); + entity.put("quot", CharUtil.DOUBLE_QUOTES); } /** @@ -89,7 +91,7 @@ public class XMLTokener extends JSONTokener { return null; } if (c == '<') { - return JSONXMLUtil.LT; + return XmlConstants.C_LT; } sb = new StringBuilder(); for (; ; ) { @@ -175,17 +177,17 @@ public class XMLTokener extends JSONTokener { case 0: throw syntaxError("Misshaped meta tag"); case '<': - return JSONXMLUtil.LT; + return XmlConstants.C_LT; case '>': - return JSONXMLUtil.GT; + return XmlConstants.C_GT; case '/': - return JSONXMLUtil.SLASH; + return CharUtil.SLASH; case '=': - return JSONXMLUtil.EQ; + return CharUtil.EQUAL; case '!': - return JSONXMLUtil.BANG; + return XmlConstants.C_BANG; case '?': - return JSONXMLUtil.QUEST; + return XmlConstants.C_QUEST; case '"': case '\'': q = c; @@ -242,15 +244,15 @@ public class XMLTokener extends JSONTokener { case '<': throw syntaxError("Misplaced '<'"); case '>': - return JSONXMLUtil.GT; + return XmlConstants.C_GT; case '/': - return JSONXMLUtil.SLASH; + return CharUtil.SLASH; case '=': - return JSONXMLUtil.EQ; + return CharUtil.EQUAL; case '!': - return JSONXMLUtil.BANG; + return XmlConstants.C_BANG; case '?': - return JSONXMLUtil.QUEST; + return XmlConstants.C_QUEST; // Quoted string diff --git a/hutool-json/src/test/java/org/dromara/hutool/json/xml/Issue2748Test.java b/hutool-json/src/test/java/org/dromara/hutool/json/xml/Issue2748Test.java new file mode 100644 index 000000000..8093101cd --- /dev/null +++ b/hutool-json/src/test/java/org/dromara/hutool/json/xml/Issue2748Test.java @@ -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("", 600); + + Assertions.assertThrows(JSONException.class, () -> { + JSONXMLUtil.toJSONObject(s, ParseConfig.of().setMaxNestingDepth(512)); + }); + } +} diff --git a/hutool-log/pom.xml b/hutool-log/pom.xml index db948322f..f27b2477c 100755 --- a/hutool-log/pom.xml +++ b/hutool-log/pom.xml @@ -21,7 +21,7 @@ org.dromara.hutool hutool-parent - 6.0.0-M10 + 6.0.0-M11 hutool-log @@ -32,10 +32,10 @@ org.dromara.hutool.log 2.0.9 - 1.4.13 + 1.4.14 1.2.17 2.20.0 - 1.2 + 1.3.0 1.3.6 2.6.2 diff --git a/hutool-poi/pom.xml b/hutool-poi/pom.xml index 01f1c32b6..5448ddeeb 100755 --- a/hutool-poi/pom.xml +++ b/hutool-poi/pom.xml @@ -21,7 +21,7 @@ org.dromara.hutool hutool-parent - 6.0.0-M10 + 6.0.0-M11 hutool-poi @@ -55,6 +55,16 @@ ofdrw-full 2.2.4 compile + + + bcpkix-jdk15on + org.bouncycastle + + + bcprov-jdk15on + org.bouncycastle + + true
diff --git a/hutool-setting/pom.xml b/hutool-setting/pom.xml index 613367242..da628335f 100755 --- a/hutool-setting/pom.xml +++ b/hutool-setting/pom.xml @@ -21,7 +21,7 @@ org.dromara.hutool hutool-parent - 6.0.0-M10 + 6.0.0-M11 hutool-setting diff --git a/hutool-socket/pom.xml b/hutool-socket/pom.xml index c259cf477..d6e763c4f 100755 --- a/hutool-socket/pom.xml +++ b/hutool-socket/pom.xml @@ -21,7 +21,7 @@ org.dromara.hutool hutool-parent - 6.0.0-M10 + 6.0.0-M11 hutool-socket diff --git a/hutool-swing/pom.xml b/hutool-swing/pom.xml index 185754e1c..e0e0783d9 100755 --- a/hutool-swing/pom.xml +++ b/hutool-swing/pom.xml @@ -21,7 +21,7 @@ org.dromara.hutool hutool-parent - 6.0.0-M10 + 6.0.0-M11 hutool-swing diff --git a/hutool-swing/src/main/java/org/dromara/hutool/swing/captcha/CaptchaUtil.java b/hutool-swing/src/main/java/org/dromara/hutool/swing/captcha/CaptchaUtil.java index 395e8cc62..9acac1258 100644 --- a/hutool-swing/src/main/java/org/dromara/hutool/swing/captcha/CaptchaUtil.java +++ b/hutool-swing/src/main/java/org/dromara/hutool/swing/captcha/CaptchaUtil.java @@ -27,7 +27,7 @@ public class CaptchaUtil { * @param height 图片高 * @return {@link LineCaptcha} */ - public static LineCaptcha createLineCaptcha(final int width, final int height) { + public static LineCaptcha ofLineCaptcha(final int width, final int height) { return new LineCaptcha(width, height); } @@ -40,7 +40,7 @@ public class CaptchaUtil { * @param lineCount 干扰线条数 * @return {@link LineCaptcha} */ - public static LineCaptcha createLineCaptcha(final int width, final int height, final int codeCount, final int lineCount) { + public static LineCaptcha ofLineCaptcha(final int width, final int height, final int codeCount, final int lineCount) { return new LineCaptcha(width, height, codeCount, lineCount); } @@ -52,7 +52,7 @@ public class CaptchaUtil { * @return {@link CircleCaptcha} * @since 3.2.3 */ - public static CircleCaptcha createCircleCaptcha(final int width, final int height) { + public static CircleCaptcha ofCircleCaptcha(final int width, final int height) { return new CircleCaptcha(width, height); } @@ -66,7 +66,7 @@ public class CaptchaUtil { * @return {@link CircleCaptcha} * @since 3.2.3 */ - public static CircleCaptcha createCircleCaptcha(final int width, final int height, final int codeCount, final int circleCount) { + public static CircleCaptcha ofCircleCaptcha(final int width, final int height, final int codeCount, final int circleCount) { return new CircleCaptcha(width, height, codeCount, circleCount); } @@ -78,7 +78,7 @@ public class CaptchaUtil { * @return {@link ShearCaptcha} * @since 3.2.3 */ - public static ShearCaptcha createShearCaptcha(final int width, final int height) { + public static ShearCaptcha ofShearCaptcha(final int width, final int height) { return new ShearCaptcha(width, height); } @@ -92,7 +92,7 @@ public class CaptchaUtil { * @return {@link ShearCaptcha} * @since 3.3.0 */ - public static ShearCaptcha createShearCaptcha(final int width, final int height, final int codeCount, final int thickness) { + public static ShearCaptcha ofShearCaptcha(final int width, final int height, final int codeCount, final int thickness) { return new ShearCaptcha(width, height, codeCount, thickness); } @@ -103,7 +103,7 @@ public class CaptchaUtil { * @param height 高 * @return {@link GifCaptcha} */ - public static GifCaptcha createGifCaptcha(final int width, final int height) { + public static GifCaptcha ofGifCaptcha(final int width, final int height) { return new GifCaptcha(width, height); } @@ -115,7 +115,7 @@ public class CaptchaUtil { * @param codeCount 字符个数 * @return {@link GifCaptcha} */ - public static GifCaptcha createGifCaptcha(final int width, final int height, final int codeCount) { + public static GifCaptcha ofGifCaptcha(final int width, final int height, final int codeCount) { return new GifCaptcha(width, height, codeCount); } } diff --git a/hutool-swing/src/main/java/org/dromara/hutool/swing/captcha/CircleCaptcha.java b/hutool-swing/src/main/java/org/dromara/hutool/swing/captcha/CircleCaptcha.java index d650f0016..57bbbe604 100644 --- a/hutool-swing/src/main/java/org/dromara/hutool/swing/captcha/CircleCaptcha.java +++ b/hutool-swing/src/main/java/org/dromara/hutool/swing/captcha/CircleCaptcha.java @@ -14,6 +14,8 @@ package org.dromara.hutool.swing.captcha; import org.dromara.hutool.core.util.ObjUtil; import org.dromara.hutool.core.util.RandomUtil; +import org.dromara.hutool.swing.captcha.generator.CodeGenerator; +import org.dromara.hutool.swing.captcha.generator.RandomGenerator; import org.dromara.hutool.swing.img.color.ColorUtil; import org.dromara.hutool.swing.img.GraphicsUtil; @@ -63,7 +65,19 @@ public class CircleCaptcha extends AbstractCaptcha { * @param interfereCount 验证码干扰元素个数 */ public CircleCaptcha(final int width, final int height, final int codeCount, final int interfereCount) { - super(width, height, codeCount, interfereCount); + this(width, height, new RandomGenerator(codeCount), interfereCount); + } + + /** + * 构造 + * + * @param width 图片宽 + * @param height 图片高 + * @param generator 验证码生成器 + * @param interfereCount 验证码干扰元素个数 + */ + public CircleCaptcha(final int width, final int height, final CodeGenerator generator, final int interfereCount) { + super(width, height, generator, interfereCount); } @Override diff --git a/hutool-swing/src/main/java/org/dromara/hutool/swing/captcha/GifCaptcha.java b/hutool-swing/src/main/java/org/dromara/hutool/swing/captcha/GifCaptcha.java index 9af10552e..a63aec5ce 100644 --- a/hutool-swing/src/main/java/org/dromara/hutool/swing/captcha/GifCaptcha.java +++ b/hutool-swing/src/main/java/org/dromara/hutool/swing/captcha/GifCaptcha.java @@ -16,6 +16,8 @@ package org.dromara.hutool.swing.captcha; import org.dromara.hutool.core.util.ObjUtil; import org.dromara.hutool.core.util.RandomUtil; import com.madgag.gif.fmsware.AnimatedGifEncoder; +import org.dromara.hutool.swing.captcha.generator.CodeGenerator; +import org.dromara.hutool.swing.captcha.generator.RandomGenerator; import java.awt.AlphaComposite; import java.awt.Color; @@ -59,7 +61,29 @@ public class GifCaptcha extends AbstractCaptcha { * @param codeCount 验证码个数 */ public GifCaptcha(final int width, final int height, final int codeCount) { - super(width, height, codeCount, 10); + this(width, height, codeCount, 10); + } + + /** + * @param width 验证码宽度 + * @param height 验证码高度 + * @param codeCount 验证码个数 + * @param interfereCount 干扰个数 + */ + public GifCaptcha(final int width, final int height, final int codeCount, final int interfereCount) { + this(width, height, new RandomGenerator(codeCount), interfereCount); + } + + /** + * 构造 + * + * @param width 图片宽 + * @param height 图片高 + * @param generator 验证码生成器 + * @param interfereCount 验证码干扰元素个数 + */ + public GifCaptcha(final int width, final int height, final CodeGenerator generator, final int interfereCount) { + super(width, height, generator, interfereCount); } /** @@ -178,9 +202,9 @@ public class GifCaptcha extends AbstractCaptcha { g2d.setComposite(ac); g2d.setColor(fontColor[i]); g2d.drawOval( - RandomUtil.randomInt(width), - RandomUtil.randomInt(height), - RandomUtil.randomInt(5, 30), 5 + RandomUtil.randomInt(5, 30) + RandomUtil.randomInt(width), + RandomUtil.randomInt(height), + RandomUtil.randomInt(5, 30), 5 + RandomUtil.randomInt(5, 30) );//绘制椭圆边框 g2d.drawString(words[i] + "", x + (font.getSize() + m) * i, y); } @@ -223,8 +247,8 @@ public class GifCaptcha extends AbstractCaptcha { max = 255; } return new Color( - RandomUtil.randomInt(min, max), - RandomUtil.randomInt(min, max), - RandomUtil.randomInt(min, max)); + RandomUtil.randomInt(min, max), + RandomUtil.randomInt(min, max), + RandomUtil.randomInt(min, max)); } } diff --git a/hutool-swing/src/main/java/org/dromara/hutool/swing/captcha/LineCaptcha.java b/hutool-swing/src/main/java/org/dromara/hutool/swing/captcha/LineCaptcha.java index 37458a8c0..022de5e65 100644 --- a/hutool-swing/src/main/java/org/dromara/hutool/swing/captcha/LineCaptcha.java +++ b/hutool-swing/src/main/java/org/dromara/hutool/swing/captcha/LineCaptcha.java @@ -14,6 +14,8 @@ package org.dromara.hutool.swing.captcha; import org.dromara.hutool.core.util.ObjUtil; import org.dromara.hutool.core.util.RandomUtil; +import org.dromara.hutool.swing.captcha.generator.CodeGenerator; +import org.dromara.hutool.swing.captcha.generator.RandomGenerator; import org.dromara.hutool.swing.img.color.ColorUtil; import org.dromara.hutool.swing.img.GraphicsUtil; @@ -53,7 +55,19 @@ public class LineCaptcha extends AbstractCaptcha { * @param lineCount 干扰线条数 */ public LineCaptcha(final int width, final int height, final int codeCount, final int lineCount) { - super(width, height, codeCount, lineCount); + this(width, height, new RandomGenerator(codeCount), lineCount); + } + + /** + * 构造 + * + * @param width 图片宽 + * @param height 图片高 + * @param generator 验证码生成器 + * @param interfereCount 验证码干扰元素个数 + */ + public LineCaptcha(final int width, final int height, final CodeGenerator generator, final int interfereCount) { + super(width, height, generator, interfereCount); } // -------------------------------------------------------------------- Constructor end diff --git a/hutool-swing/src/main/java/org/dromara/hutool/swing/captcha/ShearCaptcha.java b/hutool-swing/src/main/java/org/dromara/hutool/swing/captcha/ShearCaptcha.java index 0e08863d5..cd7d35137 100644 --- a/hutool-swing/src/main/java/org/dromara/hutool/swing/captcha/ShearCaptcha.java +++ b/hutool-swing/src/main/java/org/dromara/hutool/swing/captcha/ShearCaptcha.java @@ -14,6 +14,8 @@ package org.dromara.hutool.swing.captcha; import org.dromara.hutool.core.util.ObjUtil; import org.dromara.hutool.core.util.RandomUtil; +import org.dromara.hutool.swing.captcha.generator.CodeGenerator; +import org.dromara.hutool.swing.captcha.generator.RandomGenerator; import org.dromara.hutool.swing.img.color.ColorUtil; import org.dromara.hutool.swing.img.GraphicsUtil; @@ -63,7 +65,19 @@ public class ShearCaptcha extends AbstractCaptcha { * @param thickness 干扰线宽度 */ public ShearCaptcha(final int width, final int height, final int codeCount, final int thickness) { - super(width, height, codeCount, thickness); + this(width, height, new RandomGenerator(codeCount), thickness); + } + + /** + * 构造 + * + * @param width 图片宽 + * @param height 图片高 + * @param generator 验证码生成器 + * @param interfereCount 验证码干扰元素个数 + */ + public ShearCaptcha(final int width, final int height, final CodeGenerator generator, final int interfereCount) { + super(width, height, generator, interfereCount); } @Override diff --git a/hutool-swing/src/test/java/org/dromara/hutool/swing/captcha/CaptchaTest.java b/hutool-swing/src/test/java/org/dromara/hutool/swing/captcha/CaptchaTest.java index 81362a26b..1bc990670 100644 --- a/hutool-swing/src/test/java/org/dromara/hutool/swing/captcha/CaptchaTest.java +++ b/hutool-swing/src/test/java/org/dromara/hutool/swing/captcha/CaptchaTest.java @@ -30,7 +30,7 @@ public class CaptchaTest { @Test public void lineCaptchaTest1() { // 定义图形验证码的长和宽 - final LineCaptcha lineCaptcha = CaptchaUtil.createLineCaptcha(200, 100); + final LineCaptcha lineCaptcha = CaptchaUtil.ofLineCaptcha(200, 100); Assertions.assertNotNull(lineCaptcha.getCode()); Assertions.assertTrue(lineCaptcha.verify(lineCaptcha.getCode())); } @@ -39,7 +39,7 @@ public class CaptchaTest { @Disabled public void lineCaptchaTest3() { // 定义图形验证码的长和宽 - final LineCaptcha lineCaptcha = CaptchaUtil.createLineCaptcha(200, 70, 4, 15); + final LineCaptcha lineCaptcha = CaptchaUtil.ofLineCaptcha(200, 70, 4, 15); lineCaptcha.setBackground(Color.yellow); lineCaptcha.write("f:/test/captcha/tellow.png"); } @@ -48,7 +48,7 @@ public class CaptchaTest { @Disabled public void lineCaptchaWithMathTest() { // 定义图形验证码的长和宽 - final LineCaptcha lineCaptcha = CaptchaUtil.createLineCaptcha(200, 80); + final LineCaptcha lineCaptcha = CaptchaUtil.ofLineCaptcha(200, 80); lineCaptcha.setGenerator(new MathGenerator()); lineCaptcha.setTextAlpha(0.8f); lineCaptcha.write("f:/captcha/math.png"); @@ -59,7 +59,7 @@ public class CaptchaTest { public void lineCaptchaTest2() { // 定义图形验证码的长和宽 - final LineCaptcha lineCaptcha = CaptchaUtil.createLineCaptcha(200, 100); + final LineCaptcha lineCaptcha = CaptchaUtil.ofLineCaptcha(200, 100); // LineCaptcha lineCaptcha = new LineCaptcha(200, 100, 4, 150); // 图形验证码写出,可以写出到文件,也可以写出到流 lineCaptcha.write("f:/captcha/line.png"); @@ -79,7 +79,7 @@ public class CaptchaTest { public void circleCaptchaTest() { // 定义图形验证码的长和宽 - final CircleCaptcha captcha = CaptchaUtil.createCircleCaptcha(200, 100, 4, 20); + final CircleCaptcha captcha = CaptchaUtil.ofCircleCaptcha(200, 100, 4, 20); // CircleCaptcha captcha = new CircleCaptcha(200, 100, 4, 20); // 图形验证码写出,可以写出到文件,也可以写出到流 captcha.write("f:/captcha/circle.png"); @@ -92,7 +92,7 @@ public class CaptchaTest { public void shearCaptchaTest() { // 定义图形验证码的长和宽 - final ShearCaptcha captcha = CaptchaUtil.createShearCaptcha(203, 100, 4, 4); + final ShearCaptcha captcha = CaptchaUtil.ofShearCaptcha(203, 100, 4, 4); // ShearCaptcha captcha = new ShearCaptcha(200, 100, 4, 4); // 图形验证码写出,可以写出到文件,也可以写出到流 captcha.write("f:/captcha/shear.png"); @@ -116,7 +116,7 @@ public class CaptchaTest { @Disabled public void ShearCaptchaWithMathTest() { // 定义图形验证码的长和宽 - final ShearCaptcha captcha = CaptchaUtil.createShearCaptcha(200, 45, 4, 4); + final ShearCaptcha captcha = CaptchaUtil.ofShearCaptcha(200, 45, 4, 4); captcha.setGenerator(new MathGenerator()); // ShearCaptcha captcha = new ShearCaptcha(200, 100, 4, 4); // 图形验证码写出,可以写出到文件,也可以写出到流 @@ -128,7 +128,7 @@ public class CaptchaTest { @Test @Disabled public void GifCaptchaTest() { - final GifCaptcha captcha = CaptchaUtil.createGifCaptcha(200, 100, 4); + final GifCaptcha captcha = CaptchaUtil.ofGifCaptcha(200, 100, 4); captcha.write("d:/test/gif_captcha.gif"); assert captcha.verify(captcha.getCode()); } @@ -136,7 +136,7 @@ public class CaptchaTest { @Test @Disabled public void bgTest(){ - final LineCaptcha captcha = CaptchaUtil.createLineCaptcha(200, 100, 4, 1); + final LineCaptcha captcha = CaptchaUtil.ofLineCaptcha(200, 100, 4, 1); captcha.setBackground(Color.WHITE); captcha.write("d:/test/test.jpg"); } diff --git a/hutool-swing/src/test/java/org/dromara/hutool/swing/captcha/CaptchaUtilTest.java b/hutool-swing/src/test/java/org/dromara/hutool/swing/captcha/CaptchaUtilTest.java index 2920d721c..77917d974 100644 --- a/hutool-swing/src/test/java/org/dromara/hutool/swing/captcha/CaptchaUtilTest.java +++ b/hutool-swing/src/test/java/org/dromara/hutool/swing/captcha/CaptchaUtilTest.java @@ -21,7 +21,7 @@ public class CaptchaUtilTest { @Disabled public void createTest() { for(int i = 0; i < 1; i++) { - CaptchaUtil.createShearCaptcha(320, 240); + CaptchaUtil.ofShearCaptcha(320, 240); } } } diff --git a/pom.xml b/pom.xml index 6666b0cb9..8c8c95408 100755 --- a/pom.xml +++ b/pom.xml @@ -20,7 +20,7 @@ org.dromara.hutool hutool-parent - 6.0.0-M10 + 6.0.0-M11 hutool Hutool是一个功能丰富且易用的Java工具库,通过诸多实用工具类的使用,旨在帮助开发者快速、便捷地完成各类开发任务。这些封装的工具涵盖了字符串、数字、集合、编码、日期、文件、IO、加密、数据库JDBC、JSON、HTTP客户端等一系列操作,可以满足各种不同的开发需求。