mirror of
https://gitee.com/jzsw-it/yexuejc-base.git
synced 2026-01-04 00:09:32 +08:00
Compare commits
1 Commits
2153456157
...
1.5.6-jre1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cffabb0150 |
6
pom.xml
6
pom.xml
@@ -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>
|
||||
|
||||
@@ -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();
|
||||
|
||||
180
src/main/java/com/yexuejc/base/http/Resps.java
Normal file
180
src/main/java/com/yexuejc/base/http/Resps.java
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
/**
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user