1 Commits

Author SHA1 Message Date
maxf
cffabb0150 🔖 1.5.6-jre11 2025-12-11 18:09:12 +08:00
10 changed files with 248 additions and 912 deletions

View File

@@ -6,7 +6,7 @@
<groupId>top.yexuejc</groupId>
<artifactId>yexuejc-base</artifactId>
<version>1.5.4-jre11</version>
<version>1.5.6-jre11</version>
<name>yexuejc-base</name>
<url>https://github.com/yexuejc/yexuejc-base</url>
@@ -51,10 +51,10 @@
<validation-api.version>3.0.2</validation-api.version>
<jjwt.version>0.12.5</jjwt.version>
<commons-io.version>2.21.0</commons-io.version>
<commons-io.version>2.11.0</commons-io.version>
<bouncycastle-jdk18on.version>1.81</bouncycastle-jdk18on.version>
<guava.version>33.1.0-jre</guava.version>
<apache-poi.version>5.5.0</apache-poi.version>
<apache-poi.version>5.2.5</apache-poi.version>
<jackson.version>2.17.0</jackson.version>
<zip4j.version>2.11.4</zip4j.version>
</properties>

View File

@@ -93,7 +93,7 @@ public class RSA {
* <li>NoPadding无填充需数据长度对齐</li>
* </p>
* <hr>
* <p><b>AES的算法/模式/填充)组合 参照 {@link ALGORITHM}<b/></p>
* <p><b>AES的算法/模式/填充)组合 参照 {@link AES.ALGORITHM}<b/></p>
*/
public String transformation = "RSA";
/**
@@ -127,42 +127,6 @@ public class RSA {
*/
public final SignAlgorithm signAlgorithm = SignAlgorithm.SHA256withRSA;
/**
* 加密模式
*/
// @formatter:off
public enum ALGORITHM {
// 模式 填充 说明
// RSA/ECB/PKCS1Padding PKCS#1 v1.5 传统填充方式,兼容性好,但可能存在安全风险(如选择密文攻击)。
RSA_ECB_PKCS1Padding("RSA/ECB/PKCS1Padding"),
// RSA/ECB/OAEPWithSHA-1AndMGF1Padding OAEP (SHA-1) 比 PKCS1Padding 更安全,但 SHA-1 已不推荐。
RSA_ECB_SHA_1("RSA/ECB/OAEPWithSHA-1AndMGF1Padding"),
// RSA/ECB/OAEPWithSHA-224AndMGF1Padding OAEP (SHA-224) 比 PKCS1Padding 更安全,但 SHA-224 已不推荐。
RSA_ECB_SHA_224("RSA/ECB/OAEPWithSHA-224AndMGF1Padding"),
// RSA/ECB/OAEPWithSHA-256AndMGF1Padding OAEP (SHA-256) 推荐,安全性更高。
RSA_ECB_SHA_256("RSA/ECB/OAEPWithSHA-256AndMGF1Padding"),
// RSA/ECB/OAEPWithSHA-384AndMGF1Padding OAEP (SHA-384) 更高安全性,但性能稍差。
RSA_ECB_SHA_384("RSA/ECB/OAEPWithSHA-384AndMGF1Padding"),
// RSA/ECB/OAEPWithSHA-512AndMGF1Padding OAEP (SHA-512) 最高安全性,但计算开销大。
RSA_ECB_SHA_512("RSA/ECB/OAEPWithSHA-512AndMGF1Padding"),
// RSA/ECB/OAEPWithSHA3-256AndMGF1Padding OAEP (SHA3-256) 2021 年新增,与 SHA-256 相同但性能更高Bouncy Castle 支持
RSA_ECB_SHA3_256("RSA/ECB/OAEPWithSHA3-256AndMGF1Padding"),
// RSA/None/NoPadding 无 仅用于特殊场景,Bouncy Castle 支持
RSA_None_NoPadding("RSA/None/NoPadding"),
// RSA/None/PKCS1Padding PKCS#1 v1.5 Bouncy Castle 支持
RSA_None_PKCS1Padding("RSA/None/PKCS1Padding"),
// RSA/None/OAEPPadding OAEP (SHA-1) Bouncy Castle 支持
RSA_None_OAEPPadding("RSA/None/OAEPPadding"),
// RSA/None/ISO9796-1Padding ISO9796-1 旧标准,不推荐,Bouncy Castle 支持
RSA_None_ISO9796("RSA/None/ISO9796-1Padding"),
;
public final String code;
ALGORITHM(String code) {
this.code = code;
}
}
/**
* 生成密钥对
*
@@ -383,13 +347,9 @@ public class RSA {
*/
private Cipher getCipher() throws NoSuchPaddingException, NoSuchAlgorithmException, NoSuchProviderException {
Cipher cipher;
if (transformation.startsWith("RSA") && isChangeSign) {
if ("RSA".equals(transformation) && isChangeSign) {
// 每次改变加密结果
if (ALGORITHM.RSA_ECB_SHA3_256.code.equals(transformation)) {
cipher = Cipher.getInstance(transformation, "BC");
} else {
cipher = Cipher.getInstance(transformation);
}
cipher = Cipher.getInstance(transformation);
} else {
Security.addProvider(new BouncyCastleProvider());
// 算法/模式/填充组合;提供者名称(如 "BC" 表示Bouncy Castle
@@ -408,16 +368,17 @@ public class RSA {
try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
int offSet = 0;
byte[] buff;
int i = 0;
while (datas.length > offSet) {
// 确保不会超出数据范围
int currentBlockSize = Math.min(maxBlock, datas.length - offSet);
if (currentBlockSize <= 0) {
break; // 防止无限循环
if (datas.length - offSet > maxBlock) {
buff = cipher.doFinal(datas, offSet, maxBlock);
} else {
buff = cipher.doFinal(datas, offSet, datas.length - offSet);
}
buff = cipher.doFinal(datas, offSet, currentBlockSize);
out.write(buff, 0, buff.length);
offSet += currentBlockSize;
i++;
offSet = i * maxBlock;
}
return out.toByteArray();

View File

@@ -0,0 +1,180 @@
package com.yexuejc.base.http;
import com.yexuejc.base.constant.RespConsts;
import com.yexuejc.base.util.JsonUtil;
import java.io.Serializable;
/**
* 网络请求统一返回类
*
* @PackageName: com.yexuejc.util.base
* @Description: 网络请求统一返回类
* @author: maxf
* @date: 2017/12/27 16:33
*/
public class Resps<T> implements Serializable {
/**
* 状态
*/
private String code;
/**
* md5码
*/
private String sign;
/**
* 内容
*/
private T data;
/**
* 消息
*/
private String[] msg;
public Resps() {
}
/**
* 多个消息
*
* @param code
* @param msg
*/
public Resps(String code, String[] msg) {
this.code = code;
this.msg = msg;
}
/**
* 单个消息
*
* @param code
* @param msg
*/
public Resps(String code, String msg) {
this.code = code;
this.msg = new String[]{msg};
}
public Resps<T> setSucc(T t) {
this.data = t;
return this;
}
public Resps<T> setSucc(T t, String msg) {
this.setMsg(new String[]{msg});
this.setData(t);
return this;
}
public Resps<T> setSucc(T t, String[] msg) {
this.setData(t);
this.setMsg(msg);
return this;
}
public Resps setErr(String code, String[] msg) {
this.setCode(code);
this.setMsg(msg);
return this;
}
public static Resps success(String code, String msg) {
return new Resps(code, msg);
}
public static Resps success(String code, String[] msg) {
return new Resps(code, msg);
}
public static Resps success(String[] msg) {
return new Resps(RespConsts.CODE_SUCCESS, msg);
}
public static Resps success(String msg) {
return new Resps(RespConsts.CODE_SUCCESS, msg);
}
public static Resps success() {
return new Resps(RespConsts.CODE_SUCCESS, RespConsts.MSG_SUCCESS_OPERATE);
}
public static Resps error() {
return new Resps(RespConsts.CODE_ERROR, RespConsts.MSG_ERROT_OPERATE);
}
public static Resps error(String msg) {
return new Resps(RespConsts.CODE_ERROR, msg);
}
public static Resps error(String[] msg) {
return new Resps(RespConsts.CODE_ERROR, msg);
}
public static Resps error(String code, String msg) {
return new Resps(code, msg);
}
public static Resps error(String code, String[] msg) {
return new Resps(code, msg);
}
public static Resps fail() {
return new Resps(RespConsts.CODE_FAIL, RespConsts.MSG_FAIL_OPERATE);
}
public static Resps fail(String msg) {
return new Resps(RespConsts.CODE_FAIL, msg);
}
public static Resps fail(String[] msg) {
return new Resps(RespConsts.CODE_FAIL, msg);
}
public static Resps fail(String code, String msg) {
return new Resps(code, msg);
}
public static Resps fail(String code, String[] msg) {
return new Resps(code, msg);
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
public String[] getMsg() {
return msg;
}
public void setMsg(String[] msg) {
this.msg = msg;
}
public String getSign() {
return sign;
}
public void setSign(String sign) {
this.sign = sign;
}
@Override
public String toString() {
return JsonUtil.obj2Json(this);
}
}

View File

@@ -1,16 +1,16 @@
package com.yexuejc.base.pojo;
import java.io.Serializable;
import com.yexuejc.base.util.JsonUtil;
import java.io.Serializable;
/**
* 解密前的请求参数格式
*
* @author: maxf
* @date: 2018/5/12 14:52
*/
public class SignRequestVO implements Serializable {
public class ParamsPO implements Serializable {
private static final long serialVersionUID = 9171765814642105098L;
/**

View File

@@ -3,17 +3,16 @@ package com.yexuejc.base.pojo;
import java.io.Serializable;
import com.yexuejc.base.constant.SymbolicConsts;
import com.yexuejc.base.util.JsonUtil;
/**
* 网络请求统一返回类
* API結果が戻りする
*
* @author ISC
* @date 2023/08/18
*/
public class ResponseVO implements Serializable {
public enum Codes {
public static enum Codes {
/** 处理成功 */ SUCCESS("S"),
/** 处理失败 */ FAIL("F"),
/** 系统异常 */ SYSTEM_ERROR("E");
@@ -135,6 +134,6 @@ public class ResponseVO implements Serializable {
@Override
public String toString() {
return JsonUtil.obj2Json(this);
return super.toString();
}
}

View File

@@ -1,58 +0,0 @@
package com.yexuejc.base.pojo;
/**
* 签名返回
*
* @author maxiaofeng
* @date 2025/9/25 17:35
*/
public class SignResponseVO<T> extends ObjectResponseVO<T> {
/**
* 签名
*/
private String sign;
public String getSign() {
return sign;
}
public SignResponseVO<T> setSign(String sign) {
this.sign = sign;
return this;
}
@Override
public SignResponseVO<T> setData(T data) {
super.setData(data);
return this;
}
@Override
public SignResponseVO<T> setMessage(String message) {
super.setMessage(message);
return this;
}
@Override
public SignResponseVO<T> setCode(String code) {
super.setCode(code);
return this;
}
@Override
public SignResponseVO<T> setBizCode(String bizCode) {
super.setBizCode(bizCode);
return this;
}
@Override
public SignResponseVO<T> setBizMessage(String message) {
super.setBizMessage(message);
return this;
}
@Override
public String toString() {
return super.toString();
}
}

View File

@@ -1,27 +1,20 @@
package com.yexuejc.base.util;
import com.yexuejc.base.constant.ExpCode;
import com.yexuejc.base.exception.BaseException;
import java.lang.reflect.Array;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import java.util.*;
import java.util.function.Consumer;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.yexuejc.base.constant.ExpCode;
import com.yexuejc.base.exception.BaseException;
/**
* 字符串工具类
*
@@ -32,12 +25,12 @@ import com.yexuejc.base.exception.BaseException;
*/
public final class StrUtil {
private static final Logger logger = Logger.getLogger(StrUtil.class.getName());
private StrUtil() {
}
private static final char[] HEX_CHAR = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
// ThreadLocal缓存MessageDigest实例提高性能
private static final ThreadLocal<MessageDigest> MD5_DIGEST = ThreadLocal.withInitial(() -> {
try {
@@ -46,7 +39,7 @@ public final class StrUtil {
throw new RuntimeException(new BaseException(e, ExpCode.MD5_ALGORITHM_UNAVAILABLE));
}
});
private static final ThreadLocal<MessageDigest> SHA256_DIGEST = ThreadLocal.withInitial(() -> {
try {
return MessageDigest.getInstance("SHA-256");
@@ -54,7 +47,7 @@ public final class StrUtil {
throw new RuntimeException(new BaseException(e, ExpCode.SHA256_ALGORITHM_UNAVAILABLE));
}
});
// 使用SecureRandom替代Random提高安全性
private static final ThreadLocal<SecureRandom> SECURE_RANDOM = ThreadLocal.withInitial(SecureRandom::new);
@@ -72,7 +65,8 @@ public final class StrUtil {
return ((Optional<?>) obj).isEmpty();
} else if (obj instanceof CharSequence) {
return ((CharSequence) obj).length() == 0;
} else if (obj.getClass().isArray()) {
} else if (obj.getClass()
.isArray()) {
return Array.getLength(obj) == 0;
} else if (obj instanceof Collection) {
return ((Collection<?>) obj).isEmpty();
@@ -91,7 +85,9 @@ public final class StrUtil {
* @return
*/
public static String genUUID() {
return UUID.randomUUID().toString().replaceAll("-", "");
return UUID.randomUUID()
.toString()
.replaceAll("-", "");
}
/**
@@ -126,7 +122,9 @@ public final class StrUtil {
* @return
*/
public static String genNum() {
int hashCode = UUID.randomUUID().toString().hashCode();
int hashCode = UUID.randomUUID()
.toString()
.hashCode();
StringBuilder num = new StringBuilder();
if (hashCode < 0) {
hashCode = -hashCode;
@@ -134,7 +132,8 @@ public final class StrUtil {
} else {
num.append("1");
}
return num.append(String.format("%010d", hashCode)).substring(0, 8);
return num.append(String.format("%010d", hashCode))
.substring(0, 8);
}
/**
@@ -184,13 +183,13 @@ public final class StrUtil {
* @return SHA256值
*/
public static String toSHA256(final String str) {
return toSHA(str, "SHA-256");
return toSHA(str, "SHA-256");
}
/**
* SHA加密
*
* @param str 要加密的字符串
* @param str 要加密的字符串
* @param algorithm 算法名称
* @return SHA值
*/
@@ -341,7 +340,10 @@ public final class StrUtil {
for (String key : keys) {
Object value = sortedParams.get(key);
if (isNotEmpty(key) && isNotEmpty(value)) {
content.append(index == 0 ? "" : "&").append(key).append("=").append(value);
content.append(index == 0 ? "" : "&")
.append(key)
.append("=")
.append(value);
++index;
}
}
@@ -462,14 +464,17 @@ public final class StrUtil {
if (cause != null) {
StringBuilder msg = new StringBuilder();
if (isNotEmpty(cause.getMessage())) {
msg.append(cause.getMessage()).append(NEW_LINE);
msg.append(cause.getMessage())
.append(NEW_LINE);
}
String causedMsg = printStackTrace(cause, (eMessage) -> {
if (isNotEmpty(eMessage)) {
msg.append(eMessage).append(NEW_LINE);
msg.append(eMessage)
.append(NEW_LINE);
}
});
return msg.append(causedMsg).toString();
return msg.append(causedMsg)
.toString();
}
return "";
}
@@ -488,24 +493,30 @@ public final class StrUtil {
private static String printStackTrace(Throwable cause, Consumer<String> consumer) {
if (cause != null) {
String err = "\tat %s.%s(%s.java:%d)";
StringBuilder sb = new StringBuilder();
String cClass = cause.getClass().getName();
String cClass = cause.getClass()
.getName();
String eMessage = cause.getMessage();
StackTraceElement[] stackTrace = cause.getStackTrace();
Throwable c = cause.getCause();
while (c != null) {
cClass = c.getClass().getName();
eMessage = c.getMessage();
stackTrace = c.getStackTrace();
c = c.getCause();
Throwable caused = cause.getCause();
while (caused != null) {
cClass = caused.getClass()
.getName();
eMessage = caused.getMessage();
stackTrace = caused.getStackTrace();
caused = caused.getCause();
consumer.accept(eMessage);
}
sb.append("Caused by: ").append(cClass).append(": ").append(eMessage).append(System.lineSeparator());
sb.append("Caused by: ")
.append(cClass)
.append(": ")
.append(eMessage)
.append(NEW_LINE);
for (StackTraceElement element : stackTrace) {
String className = element.getClassName();
String simpleClassName = className.substring(className.lastIndexOf('.') + 1);
sb.append(String.format(err, className, element.getMethodName(), simpleClassName, element.getLineNumber()))
.append(System.lineSeparator());
sb.append("\tat ");
sb.append(String.format(ERROR_MESSAGE_FORMAT, element.getClassName(), element.getMethodName(), element.getFileName(),
element.getLineNumber()));
sb.append(NEW_LINE);
}
return sb.toString();
}

View File

@@ -1,232 +0,0 @@
package com.yexuejc.base.encrypt;
import com.yexuejc.base.exception.BaseException;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Assertions;
import java.nio.charset.StandardCharsets;
import static org.junit.jupiter.api.Assertions.*;
/**
* AES加密增强测试类 - 验证优化后的安全性和功能
*
* @author optimization
* @date 2025/09/23
*/
@DisplayName("AES加密增强测试")
class AESEnhancedTest {
private AES aes;
@BeforeEach
void setUp() {
aes = AES.builder();
}
@Test
@DisplayName("测试密钥和IV必须设置")
void testKeyAndIvRequired() {
// 测试未设置密钥时抛出异常
IllegalStateException exception = assertThrows(IllegalStateException.class, () -> {
aes.encrypt("test data");
});
assertTrue(exception.getMessage().contains("AES密钥未设置"));
}
@Test
@DisplayName("测试密钥长度验证")
void testKeyLengthValidation() {
// 测试无效密钥长度
IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> {
aes.setKey("short");
});
assertTrue(exception.getMessage().contains("AES密钥长度必须是16、24或32字节"));
// 测试有效密钥长度
assertDoesNotThrow(() -> {
aes.setKey("1234567890123456"); // 16字节
aes.setKey("123456789012345678901234"); // 24字节
aes.setKey("12345678901234567890123456789012"); // 32字节
});
}
@Test
@DisplayName("测试IV长度验证")
void testIvLengthValidation() {
// 设置有效密钥
aes.setKey("1234567890123456");
// 测试无效IV长度
IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> {
aes.setIv("short");
});
assertTrue(exception.getMessage().contains("AES初始向量长度必须是16字节"));
// 测试有效IV长度
assertDoesNotThrow(() -> {
aes.setIv("1234567890123456"); // 16字节
});
}
@Test
@DisplayName("测试输入参数验证")
void testInputValidation() {
aes.setKey("1234567890123456")
.setIv("1234567890123456")
.setAlgorithm(AES.ALGORITHM.AES_CBC_PKCS5Padding);
// 测试空输入
IllegalArgumentException encryptException = assertThrows(IllegalArgumentException.class, () -> {
aes.encrypt(null);
});
assertTrue(encryptException.getMessage().contains("加密数据不能为空"));
IllegalArgumentException decryptException = assertThrows(IllegalArgumentException.class, () -> {
aes.decrypt(null);
});
assertTrue(decryptException.getMessage().contains("解密数据不能为空"));
}
@Test
@DisplayName("测试字符集设置验证")
void testCharsetValidation() {
IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> {
aes.setCharset(null);
});
assertTrue(exception.getMessage().contains("字符集不能为空"));
// 测试有效字符集设置
assertDoesNotThrow(() -> {
aes.setCharset(StandardCharsets.UTF_8);
});
}
@Test
@DisplayName("测试ECB模式不需要IV")
void testECBModeWithoutIV() throws BaseException {
String data = "Hello World!";
aes.setKey("1234567890123456")
.setAlgorithm(AES.ALGORITHM.AES_ECB_PKCS5Padding);
// ECB模式不需要IV
String encrypted = aes.encrypt(data);
assertNotNull(encrypted);
assertFalse(encrypted.isEmpty());
String decrypted = aes.decrypt(encrypted);
assertEquals(data, decrypted);
}
@Test
@DisplayName("测试完整的加密解密流程")
void testCompleteEncryptDecryptFlow() throws BaseException {
String originalData = "这是一个测试字符串包含中文!";
aes.setKey("1234567890123456")
.setIv("1234567890123456")
.setAlgorithm(AES.ALGORITHM.AES_CBC_PKCS5Padding)
.setCharset(StandardCharsets.UTF_8);
String encrypted = aes.encrypt(originalData);
assertNotNull(encrypted);
assertNotEquals(originalData, encrypted);
String decrypted = aes.decrypt(encrypted);
assertEquals(originalData, decrypted);
}
@Test
@DisplayName("测试不同AES算法模式")
void testDifferentAlgorithms() throws BaseException {
String data = "Test Data";
String key = "1234567890123456";
String iv = "1234567890123456";
AES.ALGORITHM[] algorithms = {
AES.ALGORITHM.AES_CBC_PKCS5Padding,
AES.ALGORITHM.AES_OFB_PKCS5Padding,
AES.ALGORITHM.AES_ECB_PKCS5Padding
};
for (AES.ALGORITHM algorithm : algorithms) {
AES testAes = AES.builder()
.setKey(key)
.setAlgorithm(algorithm);
if (!algorithm.code.contains("ECB")) {
testAes.setIv(iv);
}
String encrypted = testAes.encrypt(data);
String decrypted = testAes.decrypt(encrypted);
assertEquals(data, decrypted, "Algorithm " + algorithm.code + " failed");
}
}
@Test
@DisplayName("测试建造者模式")
void testBuilderPattern() {
AES aes1 = AES.builder();
AES aes2 = AES.builder();
// 现在返回不同的实例,避免状态共享
assertNotNull(aes1);
assertNotNull(aes2);
assertNotSame(aes1, aes2); // 确认不是单例
}
@Test
@DisplayName("测试解密异常处理")
void testDecryptErrorHandling() {
aes.setKey("1234567890123456")
.setIv("1234567890123456")
.setAlgorithm(AES.ALGORITHM.AES_CBC_PKCS5Padding);
// 测试无效Base64数据
BaseException exception = assertThrows(BaseException.class, () -> {
aes.decrypt("invalid_base64_data");
});
assertNotNull(exception.getErrorCode());
}
@Test
@DisplayName("测试不同密钥长度")
void testDifferentKeyLengths() throws BaseException {
String data = "Test Data";
String iv = "1234567890123456";
// 测试16字节密钥
AES aes16 = AES.builder()
.setKey("1234567890123456")
.setIv(iv)
.setAlgorithm(AES.ALGORITHM.AES_CBC_PKCS5Padding);
String encrypted16 = aes16.encrypt(data);
String decrypted16 = aes16.decrypt(encrypted16);
assertEquals(data, decrypted16);
// 测试24字节密钥
AES aes24 = AES.builder()
.setKey("123456789012345678901234")
.setIv(iv)
.setAlgorithm(AES.ALGORITHM.AES_CBC_PKCS5Padding);
String encrypted24 = aes24.encrypt(data);
String decrypted24 = aes24.decrypt(encrypted24);
assertEquals(data, decrypted24);
// 测试32字节密钥
AES aes32 = AES.builder()
.setKey("12345678901234567890123456789012")
.setIv(iv)
.setAlgorithm(AES.ALGORITHM.AES_CBC_PKCS5Padding);
String encrypted32 = aes32.encrypt(data);
String decrypted32 = aes32.decrypt(encrypted32);
assertEquals(data, decrypted32);
}
}

View File

@@ -1,237 +0,0 @@
package com.yexuejc.base.util;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.DisplayName;
import java.util.Arrays;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
/**
* ObjUtil增强测试类 - 验证优化后的异常处理和功能
*
* @author optimization
* @date 2025/09/23
*/
@DisplayName("ObjUtil增强测试")
class ObjUtilEnhancedTest {
@Test
@DisplayName("测试基本字段复制功能")
void testBasicFieldCopy() throws Exception {
SourceObject source = new SourceObject();
source.setName("Test Name");
source.setAge(25);
source.setEmail("test@example.com");
TargetObject target = ObjUtil.copy(source, TargetObject.class, null, null);
assertNotNull(target);
assertEquals(source.getName(), target.getName());
assertEquals(source.getAge(), target.getAge());
assertEquals(source.getEmail(), target.getEmail());
}
@Test
@DisplayName("测试包含字段过滤")
void testIncludeFieldFiltering() throws Exception {
SourceObject source = new SourceObject();
source.setName("Test Name");
source.setAge(25);
source.setEmail("test@example.com");
List<String> includeFields = Arrays.asList("name", "age");
TargetObject target = ObjUtil.copy(source, TargetObject.class, includeFields, null);
assertNotNull(target);
assertEquals(source.getName(), target.getName());
assertEquals(source.getAge(), target.getAge());
assertNull(target.getEmail()); // 不在包含列表中
}
@Test
@DisplayName("测试排除字段过滤")
void testExcludeFieldFiltering() throws Exception {
SourceObject source = new SourceObject();
source.setName("Test Name");
source.setAge(25);
source.setEmail("test@example.com");
List<String> excludeFields = Arrays.asList("email");
TargetObject target = ObjUtil.copy(source, TargetObject.class, null, excludeFields);
assertNotNull(target);
assertEquals(source.getName(), target.getName());
assertEquals(source.getAge(), target.getAge());
assertNull(target.getEmail()); // 被排除
}
@Test
@DisplayName("测试使用Setter方法复制")
void testCopyWithSetter() throws Exception {
SourceObject source = new SourceObject();
source.setName("Test Name");
source.setAge(25);
TargetObjectWithSetter target = ObjUtil.copy(source, TargetObjectWithSetter.class, true);
assertNotNull(target);
assertEquals(source.getName(), target.getName());
assertEquals(source.getAge(), target.getAge());
}
@Test
@DisplayName("测试字段不匹配的处理")
void testFieldMismatchHandling() throws Exception {
SourceObjectWithExtraField source = new SourceObjectWithExtraField();
source.setName("Test Name");
source.setAge(25);
source.setExtraField("Extra Value");
// 目标类没有extraField字段
TargetObject target = ObjUtil.copy(source, TargetObject.class, null, null);
assertNotNull(target);
assertEquals(source.getName(), target.getName());
assertEquals(source.getAge(), target.getAge());
// extraField应该被忽略不会导致异常
}
@Test
@DisplayName("测试空源对象处理")
void testNullSourceHandling() {
assertThrows(NullPointerException.class, () -> {
ObjUtil.copy(null, TargetObject.class, null, null);
});
}
@Test
@DisplayName("测试获取所有字段功能")
void testGetAllFields() {
List<java.lang.reflect.Field> fields = ObjUtil.getAllFields(ChildObject.class);
assertNotNull(fields);
assertTrue(fields.size() >= 3); // 至少包含child字段和父类的两个字段
// 验证包含父类字段
boolean hasParentField1 = fields.stream().anyMatch(f -> "parentField1".equals(f.getName()));
boolean hasParentField2 = fields.stream().anyMatch(f -> "parentField2".equals(f.getName()));
boolean hasChildField = fields.stream().anyMatch(f -> "childField".equals(f.getName()));
assertTrue(hasParentField1);
assertTrue(hasParentField2);
assertTrue(hasChildField);
}
@Test
@DisplayName("测试获取所有Getter方法功能")
void testGetAllGetterMethods() {
List<java.lang.reflect.Method> methods = ObjUtil.getAllGetterMethods(ChildObject.class, "get");
assertNotNull(methods);
assertTrue(methods.size() >= 3); // 至少包含三个getter方法
// 验证包含父类方法
boolean hasGetParentField1 = methods.stream().anyMatch(m -> "getParentField1".equals(m.getName()));
boolean hasGetParentField2 = methods.stream().anyMatch(m -> "getParentField2".equals(m.getName()));
boolean hasGetChildField = methods.stream().anyMatch(m -> "getChildField".equals(m.getName()));
assertTrue(hasGetParentField1);
assertTrue(hasGetParentField2);
assertTrue(hasGetChildField);
}
@Test
@DisplayName("测试首字母小写工具方法")
void testLowerCaseFirstChar() {
assertEquals("test", ObjUtil.lowerCaseFirstChar("Test"));
assertEquals("tEST", ObjUtil.lowerCaseFirstChar("TEST"));
assertEquals("", ObjUtil.lowerCaseFirstChar(""));
assertNull(ObjUtil.lowerCaseFirstChar(null));
assertEquals("a", ObjUtil.lowerCaseFirstChar("A"));
}
@Test
@DisplayName("测试目标类没有默认构造函数的情况")
void testTargetClassWithoutDefaultConstructor() {
SourceObject source = new SourceObject();
source.setName("Test");
assertThrows(Exception.class, () -> {
ObjUtil.copy(source, ClassWithoutDefaultConstructor.class, null, null);
});
}
// 测试用的内部类
static class SourceObject {
private String name;
private Integer age;
private String email;
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public Integer getAge() { return age; }
public void setAge(Integer age) { this.age = age; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
}
static class SourceObjectWithExtraField extends SourceObject {
private String extraField;
public String getExtraField() { return extraField; }
public void setExtraField(String extraField) { this.extraField = extraField; }
}
static class TargetObject {
private String name;
private Integer age;
private String email;
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public Integer getAge() { return age; }
public void setAge(Integer age) { this.age = age; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
}
static class TargetObjectWithSetter {
private String name;
private Integer age;
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public Integer getAge() { return age; }
public void setAge(Integer age) { this.age = age; }
}
static class ParentObject {
private String parentField1;
private String parentField2;
public String getParentField1() { return parentField1; }
public void setParentField1(String parentField1) { this.parentField1 = parentField1; }
public String getParentField2() { return parentField2; }
public void setParentField2(String parentField2) { this.parentField2 = parentField2; }
}
static class ChildObject extends ParentObject {
private String childField;
public String getChildField() { return childField; }
public void setChildField(String childField) { this.childField = childField; }
}
static class ClassWithoutDefaultConstructor {
private String name;
public ClassWithoutDefaultConstructor(String name) {
this.name = name;
}
public String getName() { return name; }
}
}

View File

@@ -1,288 +0,0 @@
package com.yexuejc.base.util;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.RepeatedTest;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import static org.junit.jupiter.api.Assertions.*;
/**
* StrUtil增强测试类 - 验证优化后的性能和线程安全性
*
* @author optimization
* @date 2025/09/23
*/
@DisplayName("StrUtil增强测试")
class StrUtilEnhancedTest {
@Test
@DisplayName("测试MD5计算的一致性")
void testMD5Consistency() {
String input = "Hello World!";
String expected = "ed076287532e86365e841e92bfc50d8c"; // 已知MD5值
String result1 = StrUtil.toMD5(input);
String result2 = StrUtil.toMD5(input);
assertEquals(expected, result1);
assertEquals(result1, result2);
}
@Test
@DisplayName("测试SHA256计算的一致性")
void testSHA256Consistency() {
String input = "Hello World!";
String expected = "7f83b1657ff1fc53b92dc18148a1d65dfc2d4b1fa3d677284addd200126d9069"; // 已知SHA256值
String result1 = StrUtil.toSHA256(input);
String result2 = StrUtil.toSHA256(input);
assertEquals(expected, result1);
assertEquals(result1, result2);
}
@Test
@DisplayName("测试MD5处理null值")
void testMD5WithNull() {
assertNull(StrUtil.toMD5(null));
}
@Test
@DisplayName("测试SHA256处理null值")
void testSHA256WithNull() {
assertNull(StrUtil.toSHA256(null));
}
@Test
@DisplayName("测试不支持的SHA算法")
void testUnsupportedSHAAlgorithm() {
String result = StrUtil.toSHA("test", "INVALID_ALGORITHM");
assertNull(result);
}
@Test
@DisplayName("测试中文字符的MD5计算")
void testMD5WithChinese() {
String input = "你好世界";
String result = StrUtil.toMD5(input);
assertNotNull(result);
assertEquals(32, result.length()); // MD5长度应为32
assertTrue(result.matches("[a-f0-9]+"));
}
@Test
@DisplayName("测试中文字符的SHA256计算")
void testSHA256WithChinese() {
String input = "你好世界";
String result = StrUtil.toSHA256(input);
assertNotNull(result);
assertEquals(64, result.length()); // SHA256长度应为64
assertTrue(result.matches("[a-f0-9]+"));
}
@Test
@DisplayName("测试长字符串的MD5计算")
void testMD5WithLongString() {
StringBuilder longString = new StringBuilder();
for (int i = 0; i < 10000; i++) {
longString.append("abcdefghij");
}
String result = StrUtil.toMD5(longString.toString());
assertNotNull(result);
assertEquals(32, result.length());
}
@RepeatedTest(10)
@DisplayName("测试SecureRandom生成的随机性")
void testCodeIdRandomness() {
String id = "12345678901234567890123456789012"; // 32位ID
String coded1 = StrUtil.codeId(id);
String coded2 = StrUtil.codeId(id);
assertEquals(64, coded1.length());
assertEquals(64, coded2.length());
// 由于使用SecureRandom两次编码结果应该不同
assertNotEquals(coded1, coded2);
// 解码后应该得到原始ID
String decoded1 = StrUtil.decodeId(coded1);
String decoded2 = StrUtil.decodeId(coded2);
assertEquals(id, decoded1);
assertEquals(id, decoded2);
}
@Test
@DisplayName("测试codeId处理无效输入")
void testCodeIdWithInvalidInput() {
// 测试null
assertNull(StrUtil.codeId(null));
// 测试长度不是32的字符串
String shortId = "short";
assertEquals(shortId, StrUtil.codeId(shortId));
String longId = "this_is_a_very_long_string_that_exceeds_32_characters";
assertEquals(longId, StrUtil.codeId(longId));
}
@Test
@DisplayName("测试decodeId处理无效输入")
void testDecodeIdWithInvalidInput() {
// 测试null
assertNull(StrUtil.decodeId(null));
// 测试长度不是64的字符串
String shortCoded = "short";
assertEquals(shortCoded, StrUtil.decodeId(shortCoded));
String longCoded = "this_is_a_very_long_string_that_exceeds_64_characters_and_should_be_returned_as_is";
assertEquals(longCoded, StrUtil.decodeId(longCoded));
}
@Test
@DisplayName("测试线程安全性 - MD5计算")
void testMD5ThreadSafety() throws InterruptedException {
final int threadCount = 20;
final int iterationsPerThread = 100;
final String testString = "Thread Safety Test";
final String expectedMD5 = StrUtil.toMD5(testString);
ExecutorService executor = Executors.newFixedThreadPool(threadCount);
AtomicBoolean hasError = new AtomicBoolean(false);
for (int i = 0; i < threadCount; i++) {
executor.submit(() -> {
for (int j = 0; j < iterationsPerThread; j++) {
String result = StrUtil.toMD5(testString);
if (!expectedMD5.equals(result)) {
hasError.set(true);
break;
}
}
});
}
executor.shutdown();
assertTrue(executor.awaitTermination(10, TimeUnit.SECONDS));
assertFalse(hasError.get(), "MD5计算在多线程环境下出现不一致结果");
}
@Test
@DisplayName("测试线程安全性 - SHA256计算")
void testSHA256ThreadSafety() throws InterruptedException {
final int threadCount = 20;
final int iterationsPerThread = 100;
final String testString = "Thread Safety Test";
final String expectedSHA256 = StrUtil.toSHA256(testString);
ExecutorService executor = Executors.newFixedThreadPool(threadCount);
AtomicBoolean hasError = new AtomicBoolean(false);
for (int i = 0; i < threadCount; i++) {
executor.submit(() -> {
for (int j = 0; j < iterationsPerThread; j++) {
String result = StrUtil.toSHA256(testString);
if (!expectedSHA256.equals(result)) {
hasError.set(true);
break;
}
}
});
}
executor.shutdown();
assertTrue(executor.awaitTermination(10, TimeUnit.SECONDS));
assertFalse(hasError.get(), "SHA256计算在多线程环境下出现不一致结果");
}
@Test
@DisplayName("测试线程安全性 - codeId生成")
void testCodeIdThreadSafety() throws InterruptedException {
final int threadCount = 10;
final int iterationsPerThread = 50;
final String testId = "12345678901234567890123456789012";
ExecutorService executor = Executors.newFixedThreadPool(threadCount);
Set<String> results = new HashSet<>();
AtomicBoolean hasError = new AtomicBoolean(false);
for (int i = 0; i < threadCount; i++) {
executor.submit(() -> {
for (int j = 0; j < iterationsPerThread; j++) {
String coded = StrUtil.codeId(testId);
String decoded = StrUtil.decodeId(coded);
if (!testId.equals(decoded)) {
hasError.set(true);
break;
}
synchronized (results) {
results.add(coded);
}
}
});
}
executor.shutdown();
assertTrue(executor.awaitTermination(10, TimeUnit.SECONDS));
assertFalse(hasError.get(), "codeId在多线程环境下解码失败");
// 验证生成的编码都是唯一的由于使用SecureRandom
assertEquals(threadCount * iterationsPerThread, results.size(),
"生成的编码应该都是唯一的");
}
@Test
@DisplayName("测试性能 - MD5计算")
void testMD5Performance() {
String testString = "Performance test string with some content to hash";
long startTime = System.nanoTime();
for (int i = 0; i < 10000; i++) {
StrUtil.toMD5(testString);
}
long endTime = System.nanoTime();
long duration = endTime - startTime;
// 确保性能在合理范围内(这里只是确保能完成,具体时间取决于硬件)
assertTrue(duration > 0, "MD5计算应该花费一些时间");
// 可以添加更具体的性能断言,但要考虑不同硬件的差异
System.out.println("MD5 10000次计算耗时: " + duration / 1_000_000 + "ms");
}
@Test
@DisplayName("测试边界值")
void testBoundaryValues() {
// 测试空字符串
String emptyResult = StrUtil.toMD5("");
assertNotNull(emptyResult);
assertEquals(32, emptyResult.length());
// 测试单字符
String singleCharResult = StrUtil.toMD5("a");
assertNotNull(singleCharResult);
assertEquals(32, singleCharResult.length());
// 测试特殊字符
String specialCharResult = StrUtil.toMD5("!@#$%^&*()");
assertNotNull(specialCharResult);
assertEquals(32, specialCharResult.length());
}
}