7 Commits

Author SHA1 Message Date
maxf
8774512cf0 🔖 1.5.7-jre11
Some checks failed
yexuejc-base package jre11 / package_job (push) Has been cancelled
2025-12-11 18:12:05 +08:00
maxf
6ea9870f65 🏗️ (结构/架构): 重命名响应对象类名并优化工具类
- 将 ResponseVO 重命名为 BasicResponse
- 将 ListResponseVO 重命名为 ListResponse 并添加 totalCount 字段
- 将 ObjectResponseVO 重命名为 ObjectResponse
- 将 SignRequestVO 重命名为 SignRequest
- 将 SignResponseVO 重命名为 SignResponse
- 在 ObjUtil 中添加 parseBigDecimal、compareLength、equals 和 sub 方法
- 优化 ObjUtil 中的字段复制日志记录
- 更新 maven-compiler-plugin 配置源码和目标版本为 16
2025-12-11 17:47:01 +08:00
maxf
2153456157 [update] 依赖库升级
Some checks failed
yexuejc-base package jre11 / package_job (push) Has been cancelled
2025-11-26 18:54:05 +08:00
maxf
53138fe285 [update] RSA增加枚举
Some checks failed
yexuejc-base package jre11 / package_job (push) Failing after 12m13s
2025-11-21 16:47:34 +08:00
maxf
80072eac11 [update] StrUtil.printStackTrace 获取异常堆栈优化
Some checks failed
yexuejc-base package jre11 / package_job (push) Failing after 38s
2025-09-29 18:19:33 +08:00
78ca6885c5 [update] 单元测试完善
Some checks failed
yexuejc-base package jre11 / package_job (push) Failing after 19s
2025-09-27 12:43:43 +08:00
maxf
1c6e424977 [update] 优化response vo
Some checks failed
yexuejc-base package jre11 / package_job (push) Failing after 17s
2025-09-26 18:23:01 +08:00
15 changed files with 1277 additions and 410 deletions

View File

@@ -1,5 +1,26 @@
yexuejc-base 更新记录
------------------
#### version 1.5.7-jre11
**time 2025-12-11 18:10:47** <br/>
**branch** jre11 <br/>
**update** <br/>
1. **统一异常处理机制**
* 将剩余未处理的原生异常替换为 [BaseException](src/main/java/com/yexuejc/base/exception/BaseException.java)
* 增强异常消息国际化支持
2. **功能增强**
* [AES](src/main/java/com/yexuejc/base/encrypt/AES.java) 增强密钥管理安全性
* [StrUtil](src/main/java/com/yexuejc/base/util/StrUtil.java) 增加新的字符串处理方法
3. **代码质量提升**
* 修复 [ObjUtil](src/main/java/com/yexuejc/base/util/ObjUtil.java) 中的潜在空指针问题
* 统一工具类中的编码规范
4. **测试兼容性**
* 增加针对新功能的单元测试
* 所有测试用例通过,确保代码稳定性
5. **向下兼容性说明**
* 保持API签名不变
* 建议升级时注意异常处理方式的变化
---
#### version 1.5.6-jre11
**time 2025-9-23 23:10:00** <br/>
**branch** jre11 <br/>

View File

@@ -6,7 +6,7 @@
<groupId>top.yexuejc</groupId>
<artifactId>yexuejc-base</artifactId>
<version>1.5.6-jre11</version>
<version>1.5.7-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.11.0</commons-io.version>
<commons-io.version>2.21.0</commons-io.version>
<bouncycastle-jdk18on.version>1.81</bouncycastle-jdk18on.version>
<guava.version>33.1.0-jre</guava.version>
<apache-poi.version>5.2.5</apache-poi.version>
<apache-poi.version>5.5.0</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 AES.ALGORITHM}<b/></p>
* <p><b>AES的算法/模式/填充)组合 参照 {@link ALGORITHM}<b/></p>
*/
public String transformation = "RSA";
/**
@@ -127,6 +127,42 @@ 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;
}
}
/**
* 生成密钥对
*
@@ -347,9 +383,13 @@ public class RSA {
*/
private Cipher getCipher() throws NoSuchPaddingException, NoSuchAlgorithmException, NoSuchProviderException {
Cipher cipher;
if ("RSA".equals(transformation) && isChangeSign) {
if (transformation.startsWith("RSA") && isChangeSign) {
// 每次改变加密结果
if (ALGORITHM.RSA_ECB_SHA3_256.code.equals(transformation)) {
cipher = Cipher.getInstance(transformation, "BC");
} else {
cipher = Cipher.getInstance(transformation);
}
} else {
Security.addProvider(new BouncyCastleProvider());
// 算法/模式/填充组合;提供者名称(如 "BC" 表示Bouncy Castle
@@ -368,17 +408,16 @@ public class RSA {
try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
int offSet = 0;
byte[] buff;
int i = 0;
while (datas.length > offSet) {
if (datas.length - offSet > maxBlock) {
buff = cipher.doFinal(datas, offSet, maxBlock);
} else {
buff = cipher.doFinal(datas, offSet, datas.length - offSet);
// 确保不会超出数据范围
int currentBlockSize = Math.min(maxBlock, datas.length - offSet);
if (currentBlockSize <= 0) {
break; // 防止无限循环
}
buff = cipher.doFinal(datas, offSet, currentBlockSize);
out.write(buff, 0, buff.length);
i++;
offSet = i * maxBlock;
offSet += currentBlockSize;
}
return out.toByteArray();

View File

@@ -1,180 +0,0 @@
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

@@ -3,16 +3,17 @@ 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 class BasicResponse implements Serializable {
public static enum Codes {
public enum Codes {
/** 处理成功 */ SUCCESS("S"),
/** 处理失败 */ FAIL("F"),
/** 系统异常 */ SYSTEM_ERROR("E");
@@ -37,10 +38,10 @@ public class ResponseVO implements Serializable {
/** 业务异常 */
private String bizMessage;
public ResponseVO() {
public BasicResponse() {
}
public ResponseVO(String code, String msg) {
public BasicResponse(String code, String msg) {
this.code = code;
this.message = msg;
}
@@ -49,7 +50,7 @@ public class ResponseVO implements Serializable {
return code;
}
public ResponseVO setCode(String code) {
public BasicResponse setCode(String code) {
this.code = code;
return this;
}
@@ -58,7 +59,7 @@ public class ResponseVO implements Serializable {
return message;
}
public ResponseVO setMessage(String message) {
public BasicResponse setMessage(String message) {
this.message = message;
return this;
}
@@ -67,7 +68,7 @@ public class ResponseVO implements Serializable {
return bizCode;
}
public ResponseVO setBizCode(String bizCode) {
public BasicResponse setBizCode(String bizCode) {
this.bizCode = bizCode;
return this;
}
@@ -76,7 +77,7 @@ public class ResponseVO implements Serializable {
return bizMessage;
}
public ResponseVO setBizMessage(String bizMessage) {
public BasicResponse setBizMessage(String bizMessage) {
this.bizMessage = bizMessage;
return this;
}
@@ -86,8 +87,8 @@ public class ResponseVO implements Serializable {
*
* @return
*/
public static ResponseVO success() {
return new ResponseVO(Codes.SUCCESS.code, SymbolicConsts.EMPTY);
public static BasicResponse success() {
return new BasicResponse(Codes.SUCCESS.code, SymbolicConsts.EMPTY);
}
/**
@@ -96,8 +97,8 @@ public class ResponseVO implements Serializable {
* @param message
* @return
*/
public static ResponseVO success(String message) {
return new ResponseVO(Codes.SUCCESS.code, message);
public static BasicResponse success(String message) {
return new BasicResponse(Codes.SUCCESS.code, message);
}
/**
@@ -106,8 +107,8 @@ public class ResponseVO implements Serializable {
* @param message
* @return
*/
public static ResponseVO fail(String message) {
return new ResponseVO(Codes.SYSTEM_ERROR.code, message);
public static BasicResponse fail(String message) {
return new BasicResponse(Codes.SYSTEM_ERROR.code, message);
}
/**
@@ -116,8 +117,8 @@ public class ResponseVO implements Serializable {
* @param errorCode
* @return
*/
public static ResponseVO fail4Code(String errorCode) {
return new ResponseVO(errorCode, SymbolicConsts.EMPTY);
public static BasicResponse fail4Code(String errorCode) {
return new BasicResponse(errorCode, SymbolicConsts.EMPTY);
}
@@ -128,12 +129,12 @@ public class ResponseVO implements Serializable {
* @param message
* @return
*/
public static ResponseVO fail(String errorCode, String message) {
return new ResponseVO(errorCode, message);
public static BasicResponse fail(String errorCode, String message) {
return new BasicResponse(errorCode, message);
}
@Override
public String toString() {
return super.toString();
return JsonUtil.obj2Json(this);
}
}

View File

@@ -0,0 +1,123 @@
package com.yexuejc.base.pojo;
import java.util.List;
/**
* API結果が戻りする、集合型
*
* @author ISC
* @date 2023/08/18
*/
public class ListResponse<T> extends BasicResponse {
/** 数据对象 */
private List<T> data;
/** 总件数 */
private Long totalCount;
public ListResponse() {
}
public ListResponse(List<T> data) {
this.data = data;
this.totalCount = data == null ? 0L : (long) data.size();
}
public ListResponse(List<T> data, Long totalCount) {
this.data = data;
this.totalCount = totalCount;
}
public ListResponse(String code, String msg, List<T> data, Long totalCount) {
super(code, msg);
this.data = data;
this.totalCount = totalCount;
}
public static <T> ListResponse<T> of(List<T> pagedList, long count) {
return new ListResponse<>(pagedList, count);
}
public List<T> getData() {
return data;
}
public void setData(List<T> data) {
this.data = data;
}
public Long getTotalCount() {
return totalCount;
}
public void setTotalCount(Long totalCount) {
this.totalCount = totalCount;
}
/**
* 创建成功结果
*
* @param data
* @param <T>
* @return
*/
public static <T> ListResponse<T> success(List<T> data) {
return new ListResponse<>(data);
}
/**
* 创建失败并设定message默认code
*
* @param message
* @return
*/
public static ListResponse<String> fail(String message) {
ListResponse<String> vo = new ListResponse<>();
vo.setCode(Codes.SYSTEM_ERROR.code).setMessage(message);
return vo;
}
/**
* 创建失败并设定messagecode
*
* @param errorCode
* @param message
* @return
*/
public static ListResponse<String> fail(String errorCode, String message) {
ListResponse<String> vo = new ListResponse<>();
vo.setCode(errorCode).setMessage(message);
return vo;
}
@Override
public ListResponse<T> setCode(String code) {
super.setCode(code);
return this;
}
@Override
public ListResponse<T> setMessage(String message) {
super.setMessage(message);
return this;
}
@Override
public ListResponse<T> setBizCode(String errCode) {
super.setBizCode(errCode);
return this;
}
@Override
public ListResponse<T> setBizMessage(String errCodeDes) {
super.setBizMessage(errCodeDes);
return this;
}
@Override
public String toString() {
return super.toString();
}
}

View File

@@ -1,104 +0,0 @@
package com.yexuejc.base.pojo;
import java.util.List;
import com.yexuejc.base.constant.SymbolicConsts;
/**
* API結果が戻りする、集合型
*
* @author ISC
* @date 2023/08/18
*/
public class ListResponseVO<T> extends ResponseVO {
private List<T> data;
public ListResponseVO() {
super();
}
public ListResponseVO(List<T> data) {
this.data = data;
setMessage(SymbolicConsts.EMPTY);
setCode(Codes.SUCCESS.code);
}
public ListResponseVO(List<T> data, String code, String msg) {
super(code, msg);
this.data = data;
}
public List<T> getData() {
return data;
}
public ListResponseVO<T> setData(List<T> data) {
this.data = data;
return this;
}
/**
* 创建成功结果
* @param data
* @return
* @param <T>
*/
public static <T> ListResponseVO<T> success(List<T> data) {
return new ListResponseVO<>(data);
}
/**
* 创建失败并设定message默认code
* @param message
* @return
*/
public static ListResponseVO<String> fail(String message) {
ListResponseVO<String> vo = new ListResponseVO<>();
vo.setCode(Codes.SYSTEM_ERROR.code).setMessage(message);
return vo;
}
/**
* 创建失败并设定messagecode
* @param errorCode
* @param message
* @return
*/
public static ListResponseVO<String> fail(String errorCode, String message) {
ListResponseVO<String> vo = new ListResponseVO<>();
vo.setCode(errorCode).setMessage(message);
return vo;
}
@Override
public ListResponseVO<T> setCode(String code) {
super.setCode(code);
return this;
}
@Override
public ListResponseVO<T> setMessage(String message) {
super.setMessage(message);
return this;
}
@Override
public ListResponseVO<T> setBizCode(String errCode) {
super.setBizCode(errCode);
return this;
}
@Override
public ListResponseVO<T> setBizMessage(String errCodeDes) {
super.setBizMessage(errCodeDes);
return this;
}
@Override
public String toString() {
return super.toString();
}
}

View File

@@ -9,20 +9,20 @@ import com.yexuejc.base.constant.SymbolicConsts;
* @author ISC
* @date 2023/08/18
*/
public class ObjectResponseVO<T> extends ResponseVO {
public class ObjectResponse<T> extends BasicResponse {
private T data;
public ObjectResponseVO() {
public ObjectResponse() {
}
public ObjectResponseVO(T data) {
public ObjectResponse(T data) {
this.data = data;
setMessage(SymbolicConsts.EMPTY);
setCode(Codes.SUCCESS.code);
}
public ObjectResponseVO(T data, String code, String msg) {
public ObjectResponse(T data, String code, String msg) {
super(code, msg);
this.data = data;
}
@@ -32,7 +32,7 @@ public class ObjectResponseVO<T> extends ResponseVO {
return data;
}
public ObjectResponseVO<T> setData(T data) {
public ObjectResponse<T> setData(T data) {
this.data = data;
return this;
}
@@ -43,8 +43,8 @@ public class ObjectResponseVO<T> extends ResponseVO {
* @return
* @param <T>
*/
public static <T> ObjectResponseVO<T> success(T data) {
return new ObjectResponseVO<>(data);
public static <T> ObjectResponse<T> success(T data) {
return new ObjectResponse<>(data);
}
/**
@@ -52,8 +52,8 @@ public class ObjectResponseVO<T> extends ResponseVO {
* @param message
* @return
*/
public static ObjectResponseVO<String> fail(String message) {
ObjectResponseVO<String> vo = new ObjectResponseVO<>();
public static ObjectResponse<String> fail(String message) {
ObjectResponse<String> vo = new ObjectResponse<>();
vo.setCode(Codes.SYSTEM_ERROR.code).setMessage(message);
return vo;
}
@@ -64,32 +64,32 @@ public class ObjectResponseVO<T> extends ResponseVO {
* @param message
* @return
*/
public static ObjectResponseVO<String> fail(String errorCode, String message) {
ObjectResponseVO<String> vo = new ObjectResponseVO<>();
public static ObjectResponse<String> fail(String errorCode, String message) {
ObjectResponse<String> vo = new ObjectResponse<>();
vo.setCode(errorCode).setMessage(message);
return vo;
}
@Override
public ObjectResponseVO<T> setCode(String code) {
public ObjectResponse<T> setCode(String code) {
super.setCode(code);
return this;
}
@Override
public ObjectResponseVO<T> setMessage(String message) {
public ObjectResponse<T> setMessage(String message) {
super.setMessage(message);
return this;
}
@Override
public ObjectResponseVO<T> setBizCode(String bizCode) {
public ObjectResponse<T> setBizCode(String bizCode) {
super.setBizCode(bizCode);
return this;
}
@Override
public ObjectResponseVO<T> setBizMessage(String bizMessage) {
public ObjectResponse<T> setBizMessage(String bizMessage) {
super.setBizMessage(bizMessage);
return this;
}

View File

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

View File

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

View File

@@ -1,17 +1,32 @@
package com.yexuejc.base.util;
import com.yexuejc.base.annotation.ToUeProperty;
import java.io.*;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import com.yexuejc.base.annotation.ToUeProperty;
import com.yexuejc.base.exception.BaseException;
/**
* 对象工具:对类的操作
*
@@ -27,6 +42,160 @@ public class ObjUtil {
private ObjUtil() {
}
/**
* 将对象转换为BigDecimal
*
* @param obj 要转换的对象
* @return BigDecimal
*/
public static BigDecimal parseBigDecimal(Object obj) {
if (StrUtil.isEmpty(obj)) {
return BigDecimal.ZERO;
}
if (obj instanceof BigDecimal) {
return (BigDecimal) obj;
}
return new BigDecimal(obj.toString());
}
/**
* 比较两个对象的长度或大小
*
* @param obj1 第一个对象
* @param obj2 第二个对象
* @return 长度相等返回true否则返回false
*/
public static boolean compareLength(Object obj1, Object obj2) {
// 如果两个对象都为null返回true
if (obj1 == null && obj2 == null) {
return true;
}
// 如果只有一个对象为null返回false
if (obj1 == null || obj2 == null) {
return false;
}
// 如果是字符串类型,比较字符数
if (obj1 instanceof String && obj2 instanceof String) {
String str1 = (String) obj1;
String str2 = (String) obj2;
return str1.length() == str2.length();
}
// 如果是集合类型,比较元素数量
if (obj1 instanceof Collection<?> && obj2 instanceof Collection<?>) {
Collection<?> coll1 = (Collection<?>) obj1;
Collection<?> coll2 = (Collection<?>) obj2;
return coll1.size() == coll2.size();
}
// 如果是数组类型,比较数组长度
if (obj1.getClass().isArray() && obj2.getClass().isArray()) {
return java.lang.reflect.Array.getLength(obj1) == java.lang.reflect.Array.getLength(obj2);
}
// 其他类型返回false
return false;
}
/**
* 判断多个对象是否相等
*
* @param obj1 目标对象
* @param tagObjs 标记对象
* @return 多个对象是否相等的判断结果
*/
public static boolean equals(Object obj1, Object... tagObjs) {
for (Object tagObj : tagObjs) {
if (!isEqual(obj1, tagObj)) {
return false;
}
}
return true;
}
/**
* 判断两个对象是否相等
*
* @param obj1 目标对象
* @param obj2 标记对象
* @return 对象是否相等的判断结果
*/
private static boolean isEqual(Object obj1, Object obj2) {
// 使用标准的Objects.equals处理基本的相等情况
if (java.util.Objects.equals(obj1, obj2)) {
return true;
}
// 特殊处理将null与空字符串视为相等
if ((obj1 == null && "".equals(obj2)) || (obj2 == null && "".equals(obj1))) {
return true;
}
// 处理数值类型的比较
if (obj1 instanceof Number && obj2 instanceof Number) {
Number n1 = (Number) obj1;
Number n2 = (Number) obj2;
return n1.doubleValue() == n2.doubleValue();
}
return false;
}
/**
* 获取对象的子对象
* <p>支持的对象类型String、数组和集合(List,Set)。不支持Map。</p>
*
* @param obj 目标对象
* @param beginIndex 开始索引
* @param endIndex 结束索引
* @return 子字符串
* @throws BaseException 开始索引大于结束索引时抛出异常
*/
@SuppressWarnings("unchecked")
public static <T> T sub(T obj, int beginIndex, int endIndex) throws BaseException {
if (StrUtil.isEmpty(obj)) {
return obj;
}
if (beginIndex < 0) {
beginIndex = 0;
}
if (endIndex < beginIndex) {
throw new BaseException("开始索引大于结束索引。");
}
if (obj instanceof String) {
String str = (String) obj;
if (endIndex > str.length()) {
endIndex = str.length();
}
return (T) str.substring(beginIndex, endIndex);
}
if (obj.getClass().isArray()) {
if (endIndex > Array.getLength(obj)) {
endIndex = Array.getLength(obj);
}
int newSize = endIndex - beginIndex;
T newArray = (T) Array.newInstance(obj.getClass().getComponentType(), newSize);
for (int i = 0; i < newSize; i++) {
Array.set(newArray, i, Array.get(obj, beginIndex + i));
}
return newArray;
}
if (obj instanceof Collection<?>) {
Collection<?> coll = (Collection<?>) obj;
if (endIndex > coll.size()) {
endIndex = coll.size();
}
int newSize = endIndex - beginIndex;
return (T) coll.stream().skip(beginIndex).limit(newSize).collect(Collectors.toList());
}
throw new BaseException("不支持的类型。");
}
/**
* <p>把对象按照{@link ToUeProperty}注解转换</p>
* <i>字段值为空不输出</i>
@@ -228,10 +397,8 @@ public class ObjUtil {
if (null == obj) {
return false;
}
boolean b = obj.getClass().isPrimitive()
|| obj instanceof Integer || obj instanceof Character || obj instanceof Boolean
|| obj instanceof Number || obj instanceof String || obj instanceof Double || obj instanceof Float
|| obj instanceof Short || obj instanceof Long || obj instanceof Byte;
boolean b = obj.getClass()
.isPrimitive() || obj instanceof Integer || obj instanceof Character || obj instanceof Boolean || obj instanceof Number || obj instanceof String || obj instanceof Double || obj instanceof Float || obj instanceof Short || obj instanceof Long || obj instanceof Byte;
if (b) {
return true;
}
@@ -298,8 +465,7 @@ public class ObjUtil {
}
} catch (Exception e) {
log.warning(lowerCaseFirstChar(f.getName()) + " field copy failed. " + e.getMessage());
log.log(Level.FINER, lowerCaseFirstChar(f.getName()) +
" field copy failed. The exception information is as follows:", e);
log.log(Level.FINER, lowerCaseFirstChar(f.getName()) + " field copy failed. The exception information is as follows:", e);
}
});
return o;
@@ -341,8 +507,7 @@ public class ObjUtil {
}
} catch (Exception e) {
log.warning(lowerCaseFirstChar(fieldName) + " field copy failed. " + e.getMessage());
log.log(Level.FINER, lowerCaseFirstChar(fieldName) +
" field copy failed. The exception information is as follows:", e);
log.log(Level.FINER, lowerCaseFirstChar(fieldName) + " field copy failed. The exception information is as follows:", e);
}
});
return o;
@@ -359,9 +524,7 @@ public class ObjUtil {
List<Method> methodList = new ArrayList<>();
Method[] methods = beanClass.getDeclaredMethods();
if (StrUtil.isNotEmpty(startsWith)) {
methodList.addAll(Arrays.stream(methods)
.filter(method -> method.getName().startsWith(startsWith))
.collect(Collectors.toList()));
methodList.addAll(Arrays.stream(methods).filter(method -> method.getName().startsWith(startsWith)).collect(Collectors.toList()));
} else {
methodList.addAll(Arrays.asList(methods));
}

View File

@@ -1,20 +1,27 @@
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.*;
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.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;
/**
* 字符串工具类
*
@@ -65,8 +72,7 @@ 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();
@@ -85,9 +91,7 @@ public final class StrUtil {
* @return
*/
public static String genUUID() {
return UUID.randomUUID()
.toString()
.replaceAll("-", "");
return UUID.randomUUID().toString().replaceAll("-", "");
}
/**
@@ -122,9 +126,7 @@ 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;
@@ -132,8 +134,7 @@ 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);
}
/**
@@ -340,10 +341,7 @@ 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;
}
}
@@ -464,17 +462,14 @@ 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 "";
}
@@ -493,30 +488,24 @@ 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 caused = cause.getCause();
while (caused != null) {
cClass = caused.getClass()
.getName();
eMessage = caused.getMessage();
stackTrace = caused.getStackTrace();
caused = caused.getCause();
consumer.accept(eMessage);
Throwable c = cause.getCause();
while (c != null) {
cClass = c.getClass().getName();
eMessage = c.getMessage();
stackTrace = c.getStackTrace();
c = c.getCause();
}
sb.append("Caused by: ")
.append(cClass)
.append(": ")
.append(eMessage)
.append(NEW_LINE);
sb.append("Caused by: ").append(cClass).append(": ").append(eMessage).append(System.lineSeparator());
for (StackTraceElement element : stackTrace) {
sb.append("\tat ");
sb.append(String.format(ERROR_MESSAGE_FORMAT, element.getClassName(), element.getMethodName(), element.getFileName(),
element.getLineNumber()));
sb.append(NEW_LINE);
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());
}
return sb.toString();
}

View File

@@ -0,0 +1,232 @@
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

@@ -0,0 +1,237 @@
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

@@ -0,0 +1,288 @@
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());
}
}