8 Commits
dev ... dev

Author SHA1 Message Date
829a7ed798 docs: 更新项目描述信息 2025-10-24 17:13:36 +08:00
f492d5d62e docs: 重命名文档文件名 2025-10-24 17:10:17 +08:00
159a7769dc docs: 修改文档目录 2025-10-24 17:02:57 +08:00
9ab92ce471 docs: 完善项目文档 [!9 (gitee)]
修改包描述(package-info.java)
修改 README.md
添加 docs 文件夹,包含各部分功能的介绍
2025-10-24 03:18:37 +00:00
8dfb3ff694 refactor!: 将 Ref 类从 base 包移动到 util 2025-10-23 16:59:04 +08:00
264717eb62 docs: 修复 UnifiedResponses 的 javadoc 错误 2025-10-23 16:26:11 +08:00
ba38175d93 refactor!: 优化 PagingAndSortingQueryParams (!7 @Gitee)
`PagingAndSortingQueryParams` 的构造器中必须传入一个 `PagingParamsBuilder`,
构建分页参数时使用此 `PagingParamsBuilder`。

只要子类确保同一场景下共用一个  `PagingParamsBuilder`  实例,就不会导致白名单重复校验。
2025-10-23 08:17:17 +00:00
b8c666a023 test: 使用 lombok 简化 TreeBuilder 的测试代码 2025-10-22 17:50:09 +08:00
21 changed files with 737 additions and 487 deletions

255
README.md
View File

@@ -1,246 +1,31 @@
## 一、annotation - 注解
### 1. StaticFactoryMethod
标识静态工厂方法。 *《Effective Java》***Item1** 建议考虑用静态工厂方法替换构造器, 因而考虑有一个注解可以标记一下静态工厂方法,以和其它方法进行区分
# Plusone Commons
## 1. 简介
Plusone Commons 是一个 Java 工具类库,提供了一系列实用的类和方法,用于简化开发
### 2. ReaderMethod 和 WriterMethod
分别标识读方法(如 getter或写方法如 setter
一开始是为了补充日常开发中guava 认为不需要,而我又用得上的工具,所以需要结合 guava 使用。后面也包含了一些从日常工作与学习中抽离出来可以通用的东西。
最早是写了一个集合类,为了方便判断使用读写锁时,哪些情况下使用读锁,哪些情况下使用写锁
Plusone Commons 的工具类不追求“大而全”,而是只提供相对需要的部分功能
### 3. UnsupportedOperation
标识该方法不被支持或没有实现,将抛出 `UnsupportedOperationException`。 为了方便在使用时,不需要点进源码,就能知道该方法没有实现。
> 未来一些不够“通用”的组件会迁移到更合适的模块中。
### 4. Virtual
Java 非 final 的实例方法,对应 C++/C# 中的虚方法,允许被子类覆写。 Virtual 注解旨在设计父类时,强调该方法父类虽然有默认实现,但子类可以根据自己的需要覆写
## 2. 安装
项目基于 OpenJDK 8 和 maven 构建
### 5. ValueObject
标记一个类,表示其作为值对象,区别于 Entity。
项目目前暂未发布到 maven 中央仓库,使用时需克隆代码到本地,并安装到本地仓库,然后才能在项目中引入依赖。
## 二、base - 基础组件
### 1. Ref
`Ref` 包装了一个值,表示对该值的应用。
## 3. 功能
详细功能说明请查阅文档:
灵感来自于 C# 的 ref 参数修饰符。C# 允许通过以下方式,将值返回给调用端:
```C#
void Method(ref int refArgument)
{
refArgument = refArgument + 44;
}
+ [文档地址](/plusone-commons/docs)
int number = 1;
Method(ref number);
Console.WriteLine(number); // Output: 45
```
`Ref` 使 Java 可以达到类似的效果,如:
```java
void method(Ref<Integer> refArgument) {
refArgument.transformValue(i -> i + 44);
}
## 4. 代码仓库
项目仓库一共建了三个:
Ref<Integer> number = Ref.of(1);
method(number);
System.out.println(number.getValue()); // Output: 45
```
当一个方法需要产生多个结果时,无法有多个返回值,可以使用 `Ref` 作为参数传入,方法内部修改 `Ref` 的值。 调用方在调用方法之后,使用 `getValue()` 获取结果。
```java
String method(Ref<Integer> intRefArgument, Ref<String> strRefArgument) {
intRefArgument.transformValue(i -> i + 44);
strRefArgument.setValue("Hello " + strRefArgument.getValue());
return "Return string";
}
+ [GitHub](https://github.com/ZhouXY108/plusone-commons)
+ [gitee](https://gitee.com/zhouxy108/plusone-commons)
+ [自建 Gitea 仓库](http://gitea.zhouxy.xyz/plusone/plusone-commons)
Ref<Integer> number = Ref.of(1);
Ref<String> str = Ref.of("Java");
String result = method(number, str);
System.out.println(number.getValue()); // Output: 45
System.out.println(str.getValue()); // Output: Hello Java
System.out.println(result); // Output: Return string
```
### 2. IWithCode
类似于枚举这样的类型,通常需要设置固定的码值表示对应的含义。 可实现 `IWithCode`、`IWithIntCode`、`IWithLongCode`,便于在需要的地方对这些接口的实现进行处理。
欢迎在 GitHub 和 gitee 上通过 issue 反馈使用过程中发现的问题和建议,也接受善意的 PR。
## 三、collection - 集合
### 1. CollectionTools
集合工具类
## 四、constant - 常量
### 1. 正则常量
`RegexConsts` 包含常见正则表达式;`PatternConsts` 包含对应的 `Pattern` 对象
## 五、exception - 异常
### 1. IMultiTypesException - 多类型异常
异常在不同场景下被抛出,可以用不同的枚举值,表示不同的场景类型。
异常实现 `IMultiTypesException` 的 `IMultiTypesException#getType` 方法,返回对应的场景类型。
表示场景类型的枚举实现 `IMultiTypesException.IExceptionType`,其中的工厂方法用于创建对应类型的异常。
```java
public final class LoginException
extends RuntimeException
implements IMultiTypesException<LoginException.Type> {
private static final long serialVersionUID = 881293090625085616L;
private final Type type;
private LoginException(@Nonnull Type type, @Nonnull String message) {
super(message);
this.type = type;
}
private LoginException(@Nonnull Type type, @Nonnull Throwable cause) {
super(cause);
this.type = type;
}
private LoginException(@Nonnull Type type,
@Nonnull String message,
@Nonnull Throwable cause) {
super(message, cause);
this.type = type;
}
@Override
public @Nonnull Type getType() {
return this.type;
}
// ...
public enum Type implements IExceptionType<String>, IExceptionFactory<LoginException> {
DEFAULT("00", "当前会话未登录"),
NOT_TOKEN("10", "未提供token"),
INVALID_TOKEN("20", "token无效"),
TOKEN_TIMEOUT("30", "token已过期"),
BE_REPLACED("40", "token已被顶下线"),
KICK_OUT("50", "token已被踢下线"),
;
@Nonnull
private final String code;
@Nonnull
private final String defaultMessage;
Type(@Nonnull String code, @Nonnull String defaultMessage) {
this.code = code;
this.defaultMessage = defaultMessage;
}
@Override
public @Nonnull String getCode() {
return code;
}
@Override
public @Nonnull String getDefaultMessage() {
return defaultMessage;
}
@Override
public @Nonnull LoginException create() {
return new LoginException(this, this.defaultMessage);
}
@Override
public @Nonnull LoginException create(String message) {
return new LoginException(this, message);
}
@Override
public @Nonnull LoginException create(Throwable cause) {
return new LoginException(this, cause);
}
@Override
public @Nonnull LoginException create(String message, Throwable cause) {
return new LoginException(this, message, cause);
}
}
}
```
使用时,可以使用这种方式创建并抛出异常:
```java
throw LoginException.Type.TOKEN_TIMEOUT.create();
```
### 2. 业务异常
预设常见的业务异常。可继承 `BizException` 自定义业务异常。
### 3. 系统异常
预设常见的系统异常。可继承 `SysException` 自定义系统异常。
## 六、function - 函数式编程
### 1. PredicateTools
`PredicateTools` 用于 `Predicate` 的相关操作。
### 2. Functional interfaces
补充可能用得上的函数式接口:
| Group | FunctionalInterface | method |
| ------------- | -------------------- | -------------------------------- |
| UnaryOperator | BoolUnaryOperator | boolean applyAsBool (boolean) |
| UnaryOperator | CharUnaryOperator | char applyAsChar(char) |
| Throwing | Executable | void execute() throws E |
| Throwing | ThrowingConsumer | void accept(T) throws E |
| Throwing | ThrowingFunction | R apply(T) throws E |
| Throwing | ThrowingPredicate | boolean test(T) throws E |
| Throwing | ThrowingSupplier | T get() throws E |
| Optional | OptionalSupplier | Optional<T> get() throws E |
| Optional | ToOptionalBiFunction | Optional<R> apply(T,U) |
| Optional | ToOptionalFunction | Optional<R> apply(T) |
## 七、model - 业务建模组件
包含业务建模可能用到的性别、身份证等元素,也包含数据传输对象,如分页查询参数、响应结果、分页结果等。
### 数据传输对象
#### 1. 分页
分页组件由 `PagingAndSortingQueryParams` 作为入参, 因为分页必须伴随着排序,不然可能出现同一个对象重复出现在不同页,有的对象不被查询到的情况, 所以分页查询的入参必须包含排序条件。
用户可继承 `PagingAndSortingQueryParams` 构建自己的分页查询入参,需在构造器中调用 `PagingAndSortingQueryParams` 的构造器,传入一个 Map 作为白名单, key 是供前端指定用于排序的**属性名**value 是对应数据库中的**字段名**,只有在白名单中指定的属性名才允许作为排序条件。
`PagingAndSortingQueryParams` 包含三个主要的属性:
- **size** - 每页显示的记录数
- **pageNum** - 当前页码
- **orderBy** - 排序条件
其中 `orderBy` 是一个 List可以指定多个排序条件每个排序条件是一个字符串 格式为“**属性名-ASC**”或“**属性名-DESC**”,分别表示升序和降序。
比如前端传入的 orderBy 为 ["name-ASC","age-DESC"],意味着要按 name 进行升序name 相同的情况下则按 age 进行降序。
使用时调用 `PagingAndSortingQueryParams#buildPagingParams()` 方法获取分页参数 `PagingParams`。
分页结果可以存放到 `PageResult` 中,作为出参。
#### 2. UnifiedResponse
`UnifiedResponse` 对返回给前端的数据进行封装,包含 `code`、`message`、`data。`
`UnifiedResponses` 是 `UnifiedResponse` 的工厂类。用于快速构建 `UnifiedResponse` 对象,默认的成功代码为 `2000000`。
用户可以继承 `UnifiedResponses` 实现自己的工厂类,自定义 SUCCESS_CODE 和 DEFAULT_SUCCESS_MSG以及工厂方法。如下所示
```java
// 自定义工厂类
public static class CustomUnifiedResponses extends UnifiedResponses {
public static final String SUCCESS_CODE = "000";
public static final String DEFAULT_SUCCESS_MSG = "成功";
public static <T> UnifiedResponse<T> success() {
return of(SUCCESS_CODE, DEFAULT_SUCCESS_MSG);
}
public static <T> UnifiedResponse<T> success(@Nullable String message) {
return of(SUCCESS_CODE, message);
}
public static <T> UnifiedResponse<T> success(@Nullable String message, @Nullable T data) {
return of(SUCCESS_CODE, message, data);
}
private CustomUnifiedResponses() {
super();
}
}
// 使用自定义工厂类
CustomUnifiedResponses.success("查询成功", userList); // 状态码为 000
```
见 [issue#22 @Gitea](http://gitea.zhouxy.xyz/plusone/plusone-commons/issues/22)
## 八、time - 时间 API
### 1. 季度
模仿 JDK 的 `java.time.Month` 和 `java.time.YearMonth` 实现 `Quarter`、`YearQuarter`,对季度进行建模。
## 九、util - 工具类
包含树构建器(`TreeBuilder`)、断言工具(`AssertTools`、ID 生成器(`IdGenerator`)及其它实用工具类。
## 5. 许可
项目使用 [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0) 开源,相关声明请参阅 `NOTICE` 文件。

View File

@@ -0,0 +1,8 @@
## 1. 注解
|注解|说明|
|--|--|
| **StaticFactoryMethod** | **标识静态工厂方法***《Effective Java》***Item1** 建议考虑用静态工厂方法替换构造器, 因而考虑有一个注解可以标记一下静态工厂方法,以和其它方法进行区分。|
| **ReaderMethod** / **WriterMethod** | **分别标识读方法(如 getter或写方法如 setter**<br>*最早是写了一个集合类,为了方便判断使用读写锁时,哪些情况下使用读锁,哪些情况下使用写锁。*|
| **UnsupportedOperation** | **标识该方法不被支持或没有实现**,将抛出 `UnsupportedOperationException`。 为了方便在使用时,不需要点进源码,就能知道该方法没有实现。|
| **Virtual** | Java 非 final 的实例方法,对应 C++/C# 中的虚方法,允许被子类覆写。 **Virtual 注解旨在设计父类时,强调该方法父类虽然有默认实现,但子类可以根据自己的需要覆写**。|
| **ValueObject** | 标记一个类,表示其作为**值对象**,区别于 Entity。|

View File

@@ -0,0 +1,39 @@
## 2. 集合
### 2.1. CollectionTools
简单的集合工具类,包含判空等常用方法。
### 2.2. MapModifier
Map 修改器。封装一系列对 Map 数据的修改操作,修改 Map 的数据。可以用于 Map 的数据初始化等操作。
```java
// MapModifier
MapModifier<String, Object> modifier = new MapModifier<String, Object>()
.putAll(commonProperties)
.put("username", "Ben")
.put("accountStatus", LOCKED);
// 从 Supplier 中获取 Map并修改数据
Map<String, Object> map = modifier.getAndModify(HashMap::new);
// 可以灵活使用不同 Map 类型的不同构造器
Map<String, Object> map = modifier.getAndModify(() -> new HashMap<>(8));
Map<String, Object> map = modifier.getAndModify(() -> new HashMap<>(anotherMap));
Map<String, Object> map = modifier.getAndModify(TreeMap::new);
Map<String, Object> map = modifier.getAndModify(ConcurrentHashMap::new);
// 修改已有的 Map
modifier.modify(map);
// 创建一个有初始化数据的不可变的 Map
Map<String, Object> map = modifier.getUnmodifiableMap();
// 链式调用创建并初始化数据
Map<String, Object> map = new MapModifier<String, Object>()
.putAll(commonProperties)
.put("username", "Ben")
.put("accountStatus", LOCKED)
.getAndModify(HashMap::new);
```

View File

@@ -0,0 +1,118 @@
## 3. 异常
### 3.1. 业务异常
|异常|描述|
|---|---|
|`BizException`|**业务异常**<br>*用户可继承 `BizException` 自定义业务异常。*|
`RequestParamsException`|**用户请求参数错误**|
|» » `InvalidInputException`|**用户输入内容非法**<br>00 - **DEFAULT** (用户输入内容非法)<br>01 - **CONTAINS_ILLEGAL_AND_MALICIOUS_LINKS** (包含非法恶意跳转链接)<br>02 - **CONTAINS_ILLEGAL_WORDS** (包含违禁敏感词)<br>03 - **PICTURE_CONTAINS_ILLEGAL_INFORMATION** (图片包含违禁信息)<br>04 - **INFRINGE_COPYRIGHT** (文件侵犯版权)|
### 3.2. 系统异常
|异常|描述|
|---|---|
|`SysException`|**系统异常**(表示技术异常)<br>*用户可继承 `SysException` 自定义系统异常。*|
`DataOperationResultException`|**数据操作的结果不符合预期**|
`NoAvailableMacFoundException`|**无法找到可访问的 Mac 地址**|
### 3.3. 其它异常
|异常|描述|
|---|---|
|`DataNotExistsException`|**数据不存在异常**|
|`ParsingFailureException`|**数据解析异常**<br>00 - **DEFAULT** (解析失败)<br>10 - **NUMBER_PARSING_FAILURE** (数字转换失败)<br>20 - **DATE_TIME_PARSING_FAILURE** (时间解析失败)<br>30 - **JSON_PARSING_FAILURE** (JSON 解析失败)<br>40 - **XML_PARSING_FAILURE** (XML 解析失败)|
### 3.4. 多类型异常
异常在不同场景下被抛出,可以用不同的枚举值,表示不同的场景类型。
异常实现 `IMultiTypesException``getType` 方法,返回对应的场景类型。
枚举实现 `IExceptionType` 接口,表示不同的异常场景。也可以实现 `IExceptionFactory`,用于创建对应场景的异常。
```java
public final class LoginException
extends RuntimeException
implements IMultiTypesException<LoginException.Type> {
private static final long serialVersionUID = 881293090625085616L;
private final Type type;
private LoginException(@Nonnull Type type, @Nonnull String message) {
super(message);
this.type = type;
}
private LoginException(@Nonnull Type type, @Nonnull Throwable cause) {
super(cause);
this.type = type;
}
private LoginException(@Nonnull Type type,
@Nonnull String message,
@Nonnull Throwable cause) {
super(message, cause);
this.type = type;
}
@Override
public @Nonnull Type getType() {
return this.type;
}
// ...
public enum Type implements IExceptionType<String>, IExceptionFactory<LoginException> {
DEFAULT("00", "当前会话未登录"),
NOT_TOKEN("10", "未提供token"),
INVALID_TOKEN("20", "token无效"),
TOKEN_TIMEOUT("30", "token已过期"),
BE_REPLACED("40", "token已被顶下线"),
KICK_OUT("50", "token已被踢下线"),
;
@Nonnull
private final String code;
@Nonnull
private final String defaultMessage;
Type(@Nonnull String code, @Nonnull String defaultMessage) {
this.code = code;
this.defaultMessage = defaultMessage;
}
@Override
public @Nonnull String getCode() {
return code;
}
@Override
public @Nonnull String getDefaultMessage() {
return defaultMessage;
}
@Override
public @Nonnull LoginException create() {
return new LoginException(this, this.defaultMessage);
}
@Override
public @Nonnull LoginException create(@Nonnull String message) {
return new LoginException(this, message);
}
@Override
public @Nonnull LoginException create(@Nonnull Throwable cause) {
return new LoginException(this, cause);
}
@Override
public @Nonnull LoginException create(@Nonnull String message, @Nonnull Throwable cause) {
return new LoginException(this, message, cause);
}
}
}
```
使用时,可以使用这种方式创建并抛出异常:
```java
throw LoginException.Type.TOKEN_TIMEOUT.create();
```

View File

@@ -0,0 +1,22 @@
## 4. - 函数式编程
### 4.1. PredicateTools
`PredicateTools` 用于 `Predicate` 的相关操作。
### 4.2. Functional interfaces
补充可能用得上的函数式接口:
| Group | FunctionalInterface | method |
| --- | --- | --- |
| UnaryOperator | **BoolUnaryOperator** | boolean applyAsBool (boolean) |
| UnaryOperator | **CharUnaryOperator** | char applyAsChar(char) |
| Throwing | **Executable** | void execute() throws E |
| Throwing | **ThrowingConsumer** | void accept(T) throws E |
| Throwing | **ThrowingFunction** | R apply(T) throws E |
| Throwing | **ThrowingPredicate** | boolean test(T) throws E |
| Throwing | **ThrowingSupplier** | T get() throws E |
| Optional | **OptionalSupplier** | Optional<T> get() throws E |
| Optional | **ToOptionalBiFunction** | Optional<R> apply(T,U) |
| Optional | **ToOptionalFunction** | Optional<R> apply(T) |

View File

@@ -0,0 +1,100 @@
## 5. 数据模型
### 5.1. 业务模型
| | 类型 | 描述 |
| --- | --- | --- |
| 接口 | `IDCardNumber` | 身份证号 |
| 值对象 | » `Chinese2ndGenIDCardNumber` | 中国二代居民身份证号 |
| 值对象 | `SemVer` | 语义化版本号 |
| 值对象(枚举)| `Gender` | 性别 |
| ~~值对象(抽象类)~~| ~~`ValidatableStringRecord`~~ | ~~带校验的字符串值对象~~ |
### 5.2. 数据传输对象
#### 5.2.1. 分页查询
`PagingAndSortingQueryParams` (分页排序查询参数)
`PagingAndSortingQueryParams` 包含三个主要的属性:
- **size** - 每页显示的记录数
- **pageNum** - 当前页码
- **orderBy** - 排序条件
分页必须伴随着排序,不然可能出现同一个对象重复出现在不同页,有的对象不被查询到的情况。
其中 `orderBy` 是一个 `List<String>`,可以指定多个排序条件,每个排序条件是一个字符串, 格式为“**属性名-ASC**”或“**属性名-DESC**”,分别表示升序和降序。
例如当 `orderBy` 的值为 `["name-ASC","age-DESC"]`,意味着要按 `name` 进行升序排列,`name` 相同的情况下则按 `age` 进行降序排列。
用户可继承 `PagingAndSortingQueryParams` 构建自己的分页查询入参,子类需在构造器中调用 `PagingAndSortingQueryParams` 的构造器,传入一个 `PagingParamsBuilder` 用于构建分页参数。同一场景下,复用一个 {@link PagingParamsBuilder} 实例即可。
构建 `PagingParamsBuilder` 时,需传入一个 `Map` 作为可排序字段的白名单,`key` 是供前端指定用于排序的**属性名**`value` 是对应数据库中的**字段名**,只有在白名单中指定的属性名才允许作为排序条件。
```java
@ToString(callSuper = true)
class AccountQueryParams extends PagingAndSortingQueryParams {
private static final Map<String, String> PROPERTY_COLUMN_MAP = ImmutableMap.<String, String>builder()
.put("id", "id")
.put("username", "username")
.build();
private static final PagingParamsBuilder PAGING_PARAMS_BUILDER = PagingAndSortingQueryParams
.pagingParamsBuilder(20, 100, PROPERTY_COLUMN_MAP);
public AccountQueryParams() {
super(PAGING_PARAMS_BUILDER);
}
private @Getter @Setter Long id;
private @Getter @Setter String username;
private @Getter @Setter String email;
private @Getter @Setter Integer status;
}
```
使用时调用 `PagingAndSortingQueryParams#buildPagingParams()` 方法获取分页参数 `PagingParams`。分页结果可以存放到 `PageResult` 中,作为出参。
```java
public PageResult<AccountVO> queryPage(AccountQueryParams params) {
// 获取分页参数
PagingParams pagingParams = params.buildPagingParams();
// 从 params 获取字段查询条件,从 pagingParams 获取分页条件,查询一页数据
List<AccountVO> list = accountQueries.queryAccountList(params, pagingParams);
// 查询总记录数
long count = accountQueries.countAccount(params);
// 返回分页结果
return PageResult.of(list, count);
}
```
#### 5.2.2. UnifiedResponse
`UnifiedResponse` 对返回给前端的数据进行封装,包含 `code``message``data`
`UnifiedResponses``UnifiedResponse` 的工厂类。用于快速构建 `UnifiedResponse` 对象,默认的成功代码为 `2000000`
用户可以继承 `UnifiedResponses` 实现自己的工厂类,自定义 SUCCESS_CODE 和 DEFAULT_SUCCESS_MSG以及工厂方法。如下所示
```java
// 自定义工厂类
public static class CustomUnifiedResponses extends UnifiedResponses {
public static final String SUCCESS_CODE = "000";
public static final String DEFAULT_SUCCESS_MSG = "成功";
public static <T> UnifiedResponse<T> success() {
return of(SUCCESS_CODE, DEFAULT_SUCCESS_MSG);
}
public static <T> UnifiedResponse<T> success(@Nullable String message) {
return of(SUCCESS_CODE, message);
}
public static <T> UnifiedResponse<T> success(@Nullable String message, @Nullable T data) {
return of(SUCCESS_CODE, message, data);
}
private CustomUnifiedResponses() {
super();
}
}
// 使用自定义工厂类
CustomUnifiedResponses.success("查询成功", userList); // 状态码为 000
```
> 见 [issue#22 @Gitea](http://gitea.zhouxy.xyz/plusone/plusone-commons/issues/22)

View File

@@ -0,0 +1,24 @@
## 6. 时间 API
### 6.1. 季度
模仿 JDK 的 `java.time.Month``java.time.YearMonth` 实现 `xyz.zhouxy.plusone.commons.time.Quarter``xyz.zhouxy.plusone.commons.timeYearQuarter`,对季度进行建模。
*这两个类的代码修改后,也提交给了 **hutool**。见 gitee
上的 [pr#1324](https://gitee.com/chinabugotech/hutool/pulls/1324)*。
### 6.2. DateTimeTools
`xyz.zhouxy.plusone.commons.util.DateTimeTools` 提供了包含 Java 旧的时间 API 和 `java.time` API 在内的日期时间的常用操作。
### 6.3. JodaTimeTools
`xyz.zhouxy.plusone.commons.util.JodaTimeTools` 提供了 JodaTime 和 `java.time` API 相互转换的工具方法:
- `toJodaInstant`
- `toJavaInstant`
- `toJodaDateTime`
- `toZonedDateTime`
- `toJodaLocalDateTime`
- `toJavaLocalDateTime`
- `toJavaZone`
- `toJodaZone`

View File

@@ -0,0 +1,275 @@
## 7. 工具类
### 7.1. 数组工具ArrayTools
| 方法 | 描述 |
| --- | --- |
| `isEmpty` | 判断数组是否为空 |
| `isNotEmpty` | 判断数组是否不为空 |
| `isAllElementsNotNull` | 判断数组中所有元素是否不为空 |
| `concat` | 拼接数组 |
| `repeat` | 重复数组中的元素 |
| `fill` | 填充数组 |
| `indexOf` | 获取元素在数组中的索引 |
| `lastIndexOf` | 获取元素最后出现在数组中的索引 |
| `contains` | 判断数组中是否包含某个元素 |
### 7.2. 断言工具AssertTools
`AssertTools` 不封装过多判断逻辑,鼓励充分使用项目中的工具类对数据进行判断:
```java
AssertTools.checkArgument(StringUtils.hasText(str), "The argument cannot be blank.");
AssertTools.checkState(ArrayUtils.isNotEmpty(result), "The result cannot be empty.");
AssertTools.checkCondition(!CollectionUtils.isEmpty(roles),
() -> new InvalidInputException("The roles cannot be empty."));
AssertTools.checkCondition(RegexTools.matches(email, PatternConsts.EMAIL),
"must be a well-formed email address");
```
### 7.3. 枚举工具
#### ~~7.3.1 枚举类Enumeration已废弃~~
~~`Enumeration` 的实现来自于 .net 社区。因为 C# 本身的枚举不带行为,所以继承自 `Enumeration` 类,以实现带行为的枚举常量。~~
**~~但 Java 的枚举可以带行为,故大多数情况下不需要这种设计。~~**
#### 7.3.2 Enum 工具类EnumTools
用于枚举的 ordinal 和枚举值的转换等操作。
由于不推荐使用枚举的 ordinal**故大多数方法已废弃**。更推荐的实现是枚举实现 `IWithCode` 之类的接口,在枚举中提供枚举值和枚举码的转换。
### 7.4. ID 生成器
#### 7.4.1. ID 生成器IdGenerator
- 提供了 `UUID` 相关的方法
| 方法 | 描述 |
| --- | --- |
| newUuid | 获取新的 `UUID` |
| uuidString | 获取新的 UUID 字符串 |
| simpleUuidString | 获取新的 UUID 字符串(无连接符) |
| toSimpleString | 将 `UUID` 转换为无连接符的字符串 |
- 使用 `IdWorker` *(来自 **Seata** 的雪花算法的变种)* 生成分布式唯一 ID
#### 7.4.2. IdWorker
来自 [Apache Seata](https://seata.apache.org/) 的 [`org.apache.seata.common.util.IdWorker`](https://github.com/apache/incubator-seata/blob/2.x/common/src/main/java/org/apache/seata/common/util/IdWorker.java),是雪花算法的变种。
详细介绍参考以下文章:
- [Seata基于改良版雪花算法的分布式UUID生成器分析](https://seata.apache.org/zh-cn/blog/seata-analysis-UUID-generator)
- [关于新版雪花算法的答疑](https://seata.apache.org/zh-cn/blog/seata-snowflake-explain)
- [在开源项目中看到一个改良版的雪花算法,现在它是你的了。](https://juejin.cn/post/7264387737276203065)
- [关于若干读者,阅读“改良版雪花算法”后提出的几个共性问题的回复。](https://juejin.cn/post/7265516484029743138)
#### 7.4.3. SnowflakeIdGenerator
`SnowflakeIdGenerator` 是原版的雪花算法的实现
### 7.5. 树构建器TreeBuilder
`TreeBuilder` 是一个树构建器,用于将列表数据构建为树结构。
`TreeBuilder` 构造器的入参:
- **identityGetter**: 从节点中获取其标识的逻辑
- **parentIdentityGetter**: 获取父节点标识的逻辑
- **addChild**: 添加子节点的逻辑
- **defaultComparator**: 默认的 Comparator用于排序
> **注意:`TreeBuilder` 的 `buildTree` 方法,会直接更改列表中的节点。设计初衷是将查询到的列表,构建成为树结构之后直接返回给前端,如果需要,请在调用之前做深拷贝,然后再将深拷贝的结果作为入参传入。**
以下示例演示 `TreeBuilder` 的使用:
#### 7.5.1. 处理相对复杂的 entity
假设有如下的实体类:
```java
@AllArgsConstructor(access = AccessLevel.PROTECTED)
@EqualsAndHashCode
@ToString
class Menu implements Serializable {
protected final @Getter String parentMenuCode;
protected final @Getter String menuCode;
protected final @Getter String title;
protected final @Getter int orderNum;
private static final long serialVersionUID = 20240917181424L;
}
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
class MenuItem extends Menu {
private final @Getter String url;
private MenuItem(String parentMenuCode, String menuCode, String title, String url, int orderNum) {
super(parentMenuCode, menuCode, title, orderNum);
this.url = url;
}
static MenuItem of(String parentMenuCode, String menuCode, String title, String url, int orderNum) {
return new MenuItem(parentMenuCode, menuCode, title, url, orderNum);
}
static MenuItem of(String menuCode, String title, String url, int orderNum) {
return new MenuItem(null, menuCode, title, url, orderNum);
}
private static final long serialVersionUID = 20240917181910L;
}
@ToString(callSuper = true)
class MenuList extends Menu {
private List<Menu> children;
private MenuList(String parentMenuCode, String menuCode, String title, int orderNum) {
super(parentMenuCode, menuCode, title, orderNum);
}
static MenuList of(String parentMenuCode, String menuCode, String title, int orderNum) {
return new MenuList(parentMenuCode, menuCode, title, orderNum);
}
static MenuList of(String menuCode, String title, int orderNum) {
return new MenuList(null, menuCode, title, orderNum);
}
@SuppressWarnings("unused")
static MenuList of(String menuCode, String title, Iterable<Menu> children, int orderNum) {
return of(null, menuCode, title, children, orderNum);
}
static MenuList of(String parentMenuCode, String menuCode, String title, Iterable<Menu> children,
int orderNum) {
final MenuList instance = of(parentMenuCode, menuCode, title, orderNum);
children.forEach(instance::addChild);
return instance;
}
public void addChild(Menu child) {
if (this.children == null) {
this.children = Lists.newArrayList();
}
this.children.add(child);
}
private static final long serialVersionUID = 20240917181917L;
}
```
其中,`Menu` 表示菜单节点,其子类 `MenuItem` 表示菜单项,在树中作为叶子节点,另一子类 `MenuList` 表示菜单列表,其子菜单放在 `children` 字段中。`MenuList` 提供了 `addChild` 方法用于将子菜单添加到 `children` 中。
使用以下方式构建并使用 `TreeBuilder`
```java
// 创建 TreeBuilder
TreeBuilder<Menu, MenuList, String> treeBuilder = new TreeBuilder<>(
// getMenuCode 方法获取节点标识
Menu::getMenuCode,
// getParentMenuCode 方法获取父节点标识,如果父节点不存在,返回 Optional.empty()
menu -> Optional.ofNullable(menu.getParentMenuCode()),
// addChild 方法用于将子节点添加到父节点的 children 中
MenuList::addChild,
// 默认的 Comparator使用 orderNum 进行排序
Comparator.comparing(Menu::getOrderNum));
// 需要的话进行深拷贝
List<Menu> clonedMenus = menus.stream().map(ObjectUtil::clone).collect(Collectors.toList());
// 按照创建时设置的逻辑,构建树结构
List<Menu> result = treeBuilder.buildTree(clonedMenus);
```
#### 7.5.2. 处理 POJO
`TreeBuilder` 也可以处理 POJO只需要自定义 `TreeBuilder` 所需的入参即可。
```java
// POJO
@Data
class Menu implements Serializable {
private final String parentMenuCode;
private final String menuCode;
private final String title;
private final int orderNum;
private final String url;
private List<Menu> children;
private static final long serialVersionUID = 1298482252210272617L;
}
```
使用以下方式构建并使用 `TreeBuilder`
```java
// 创建 TreeBuilder
TreeBuilder<Menu, MenuList, String> treeBuilder = new TreeBuilder<>(
// getMenuCode 方法获取节点标识
Menu::getMenuCode,
// getParentMenuCode 方法获取父节点标识,如果父节点不存在,返回 Optional.empty()
menu -> Optional.ofNullable(menu.getParentMenuCode()),
// 自定义 addChild 逻辑
(menuList, child) -> {
List<Menu> children = menuList.getChildren();
if (children == null) {
children = Lists.newArrayList();
menuList.setChildren(children);
}
children.add(child);
},
// 默认的 Comparator使用 orderNum 进行排序
Comparator.comparing(Menu::getOrderNum));
// 按照创建时设置的逻辑,构建树结构
List<Menu> result = treeBuilder.buildTree(clonedMenus);
```
### 7.6. Ref
`Ref` 包装了一个值,表示对该值的应用。
C# 中允许通过 ref 参数修饰符,将值返回给调用端:
```csharp
void Method(ref int refArgument)
{
refArgument = refArgument + 44;
}
int number = 1;
Method(ref number);
Console.WriteLine(number); // Output: 45
```
`Ref` 使 Java 可以达到类似的效果,如:
```java
void method(Ref<Integer> refArgument) {
refArgument.transformValue(i -> i + 44);
}
Ref<Integer> number = Ref.of(1);
method(number);
System.out.println(number.getValue()); // Output: 45
```
当一个方法需要产生多个结果时,无法有多个返回值,可以使用 `Ref` 作为参数传入,方法内部修改 `Ref` 的值。 调用方在调用方法之后,使用 `getValue()` 获取结果。
```java
String method(Ref<Integer> intRefArgument, Ref<String> strRefArgument) {
intRefArgument.transformValue(i -> i + 44);
strRefArgument.setValue("Hello " + strRefArgument.getValue());
return "Return string";
}
Ref<Integer> number = Ref.of(1);
Ref<String> str = Ref.of("Java");
String result = method(number, str);
System.out.println(number.getValue()); // Output: 45
System.out.println(str.getValue()); // Output: Hello Java
System.out.println(result); // Output: Return string
```
### 7.7 其它工具类
- **`BigDecimals`**: BigDecimal 工具
- **`Numbers`**: 数字工具
- **`OptionalTools`**: Optional 工具
- **`RandomTools`**: 随机工具
- **`RegexTools`**: 正则工具
- **`StringTools`**: 字符串工具
- **`ZipTools`**: zip 工具

View File

@@ -0,0 +1,9 @@
## 8. 其它内容
### 8.1. IWithCode
对于类似枚举这样的类型,通常需要设置固定的码值表示对应的含义。 可实现 `IWithCode``IWithIntCode``IWithLongCode`,便于在需要的地方对这些接口的实现进行处理。
### 8.2. 正则常量
`RegexConsts` 包含常见正则表达式;`PatternConsts` 包含对应的 `Pattern` 对象

View File

@@ -13,7 +13,7 @@
<artifactId>plusone-commons</artifactId>
<description>
常见工具集,结合 guava 使用。
Plusone Commons 是一个 Java 工具类库,提供了一系列实用的类和方法,用于简化开发。结合 guava 使用。
</description>
<properties>

View File

@@ -15,44 +15,7 @@
*/
/**
* <h2>注解</h2>
*
* <h3>
* 1. {@link StaticFactoryMethod}
* </h3>
* <p>
* 标识<b>静态工厂方法</b>。
* 《Effective Java》的 Item1 建议考虑用静态工厂方法替换构造器,
* 因而考虑有一个注解可以标记一下静态工厂方法,以和其它方法进行区分。
*
* <h3>
* 2. {@link ReaderMethod} 和 {@link WriterMethod}
* </h3>
* <p>
* 分别标识<b>读方法</b>(如 getter或<b>写方法</b>(如 setter
*
* <p>
* 最早是写了一个集合类,为了方便判断使用读写锁时,哪些情况下使用读锁,哪些情况下使用写锁。
*
* <h3>
* 3. {@link UnsupportedOperation}
* </h3>
* <p>
* 标识该方法不被支持或没有实现,将抛出 {@link UnsupportedOperationException}。
* 为了方便在使用时,不需要点进源码,就能知道该方法没有实现。
*
* <h3>
* 4. {@link Virtual}
* </h3>
* <p>
* Java 非 final 的实例方法,对应 C++/C&num; 中的虚方法,允许被子类覆写。
* {@link Virtual} 注解旨在设计父类时,强调该方法父类虽然有默认实现,但子类可以根据自己的需要覆写。
*
* <h3>
* 5. {@link ValueObject}
* </h3>
* <p>
* 标记一个类,表示其作为值对象,区别于 Entity。
* 注解
*
* @author ZhouXY108 <luquanlion@outlook.com>
*/

View File

@@ -15,56 +15,7 @@
*/
/**
* <h2>基础组件</h2>
*
* <h3>1. Ref</h3>
* <p>
* {@link Ref} 包装了一个值,表示对该值的应用。
*
* <p>灵感来自于 C&num; 的 {@code ref} 参数修饰符。C&num; 允许通过以下方式,将值返回给调用端:</p>
* <pre>
* void Method(ref int refArgument)
* {
* refArgument = refArgument + 44;
* }
*
* int number = 1;
* Method(ref number);
* Console.WriteLine(number); // Output: 45
* </pre>
* {@link Ref} 使 Java 可以达到类似的效果,如:
* <pre>
* void method(Ref&lt;Integer&gt; refArgument) {
* refArgument.transformValue(i -&gt; i + 44);
* }
*
* Ref&lt;Integer&gt; number = Ref.of(1);
* method(number);
* System.out.println(number.getValue()); // Output: 45
* </pre>
* <p>
* 当一个方法需要产生多个结果时,无法有多个返回值,可以使用 {@link Ref} 作为参数传入,方法内部修改 {@link Ref} 的值。
* 调用方在调用方法之后,使用 {@code getValue()} 获取结果。
*
* <pre>
* String method(Ref&lt;Integer&gt; intRefArgument, Ref&lt;String&gt; strRefArgument) {
* intRefArgument.transformValue(i -&gt; i + 44);
* strRefArgument.setValue("Hello " + strRefArgument.getValue());
* return "Return string";
* }
*
* Ref&lt;Integer&gt; number = Ref.of(1);
* Ref&lt;String&gt; str = Ref.of("Java");
* String result = method(number, str);
* System.out.println(number.getValue()); // Output: 45
* System.out.println(str.getValue()); // Output: Hello Java
* System.out.println(result); // Output: Return string
* </pre>
*
* <h3>2. IWithCode</h3>
* <p>
* 类似于枚举这样的类型,通常需要设置固定的码值表示对应的含义。
* 可实现 {@link IWithCode}、{@link IWithIntCode}、{@link IWithLongCode},便于在需要的地方对这些接口的实现进行处理。
* 基础内容
*
* @author ZhouXY108 <luquanlion@outlook.com>
*/
@@ -73,4 +24,5 @@
package xyz.zhouxy.plusone.commons.base;
import javax.annotation.ParametersAreNonnullByDefault;
import javax.annotation.CheckReturnValue;

View File

@@ -15,12 +15,7 @@
*/
/**
* <h2>集合</h2>
*
* <h3>
* 1. {@link CollectionTools}
* </h3>
* 集合工具类
* 集合相关工具
*
* @author ZhouXY108 <luquanlion@outlook.com>
*/

View File

@@ -15,115 +15,8 @@
*/
/**
* <h2>异常</h2>
*
* <h3>1. {@link IMultiTypesException} - 多类型异常</h3>
* <p>
* 异常在不同场景下被抛出,可以用不同的枚举值,表示不同的场景类型。
*
* <p>
* 异常实现 {@link IMultiTypesException} 的 {@link IMultiTypesException#getType} 方法,返回对应的场景类型。
*
* <p>
* 表示场景类型的枚举实现 {@link IMultiTypesException.IExceptionType},各个枚举值本身就是该场景的异常的工厂实例,
* 使用其中的工厂方法用于创建对应类型的异常。
*
* <pre>
* public final class LoginException
* extends RuntimeException
* implements IMultiTypesException&lt;LoginException, LoginException.Type, String&gt; {
* private static final long serialVersionUID = 881293090625085616L;
* private final Type type;
* private LoginException(&#64;Nonnull Type type, &#64;Nonnull String message) {
* super(message);
* this.type = type;
* }
*
* private LoginException(&#64;Nonnull Type type, &#64;Nonnull Throwable cause) {
* super(cause);
* this.type = type;
* }
*
* private LoginException(&#64;Nonnull Type type,
* &#64;Nonnull String message,
* &#64;Nonnull Throwable cause) {
* super(message, cause);
* this.type = type;
* }
*
* &#64;Override
* public &#64;Nonnull Type getType() {
* return this.type;
* }
*
* // ...
*
* public enum Type implements IExceptionType&lt;LoginException, String&gt; {
* DEFAULT("00", "当前会话未登录"),
* NOT_TOKEN("10", "未提供token"),
* INVALID_TOKEN("20", "token无效"),
* TOKEN_TIMEOUT("30", "token已过期"),
* BE_REPLACED("40", "token已被顶下线"),
* KICK_OUT("50", "token已被踢下线"),
* ;
*
* &#64;Nonnull
* private final String code;
* &#64;Nonnull
* private final String defaultMessage;
*
* Type(&#64;Nonnull String code, &#64;Nonnull String defaultMessage) {
* this.code = code;
* this.defaultMessage = defaultMessage;
* }
*
* &#64;Override
* public &#64;Nonnull String getCode() {
* return code;
* }
*
* &#64;Override
* public &#64;Nonnull String getDefaultMessage() {
* return defaultMessage;
* }
*
* &#64;Override
* public &#64;Nonnull LoginException create() {
* return new LoginException(this, this.defaultMessage);
* }
*
* &#64;Override
* public &#64;Nonnull LoginException create(String message) {
* return new LoginException(this, message);
* }
*
* &#64;Override
* public &#64;Nonnull LoginException create(Throwable cause) {
* return new LoginException(this, cause);
* }
*
* &#64;Override
* public &#64;Nonnull LoginException create(String message, Throwable cause) {
* return new LoginException(this, message, cause);
* }
* }
* }
* </pre>
*
* 使用时,可以使用这种方式创建并抛出异常:
* <pre>
* throw LoginException.Type.TOKEN_TIMEOUT.create();
* </pre>
*
* <h3>2. 业务异常</h3>
* 预设常见的业务异常。可继承 {@link BizException} 自定义业务异常。
*
* <h3>3. 系统异常</h3>
* 预设常见的系统异常。可继承 {@link SysException} 自定义系统异常。
* 包含常见的业务异常与系统异常,以及异常相关的工具
*
* @author ZhouXY108 <luquanlion@outlook.com>
*/
package xyz.zhouxy.plusone.commons.exception;
import xyz.zhouxy.plusone.commons.exception.business.*;
import xyz.zhouxy.plusone.commons.exception.system.*;

View File

@@ -35,8 +35,64 @@ import xyz.zhouxy.plusone.commons.util.StringTools;
* 分页排序查询参数
*
* <p>
* 根据传入的 {@code size} 和 {@code pageNum}
* 提供 {@code getOffset} 方法计算 SQL 语句中 {@code offset} 的值。
* 包含三个主要的属性:
* <ul>
* <li>size - 每页显示的记录数</li>
* <li>pageNum - 当前页码</li>
* <li>orderBy - 排序条件</li>
* </ul>
*
* <p>
* 分页必须伴随着排序,不然可能出现同一个对象重复出现在不同页,有的对象不被查询到的情况。
*
* <p>
* 其中 {@code orderBy} 是一个 {@code List&lt;String&gt;},可以指定多个排序条件。
* 每个排序条件是一个字符串, 格式为“属性名-ASC”或“属性名-DESC”分别表示升序和降序。
* 例如,当 {@code orderBy} 的值为 {@code ["name-ASC","age-DESC"]}
* 意味着要按 {@code name} 进行升序排列,{@code name} 相同的情况下则按 {@code age} 进行降序排列。
*
* <p>
* 用户可继承 {@link PagingAndSortingQueryParams} 构建自己的分页查询入参,
* 子类需在构造器中调用 {@link PagingAndSortingQueryParams} 的构造器,
* 传入一个 {@link PagingParamsBuilder} 用于构建分页参数。
* 同一场景下,复用一个 {@link PagingParamsBuilder} 实例即可。
*
* <p>
* 构建 {@link PagingParamsBuilder} 时,需传入一个 {@code Map} 作为可排序字段的白名单,
* {@code key} 是供前端指定用于排序的属性名,{@code value} 是对应数据库中的字段名。
* 只有在此白名单中的属性名才允许用于排序。
*
* <pre>
* class AccountQueryParams extends PagingAndSortingQueryParams {
* private static final Map&lt;String, String&gt; PROPERTY_COLUMN_MAP = ImmutableMap.&lt;String, String&gt;builder()
* .put("id", "id")
* .put("username", "username")
* .build();
* private static final PagingParamsBuilder PAGING_PARAMS_BUILDER = PagingAndSortingQueryParams
* .pagingParamsBuilder(20, 100, PROPERTY_COLUMN_MAP);
*
* public AccountQueryParams() {
* // 所有的 AccountQueryParams 复用同一个 PagingParamsBuilder 实例
* super(PAGING_PARAMS_BUILDER);
* }
*
* private @Getter @Setter Long id;
* private @Getter @Setter String username;
* private @Getter @Setter String email;
* private @Getter @Setter Integer status;
* }
*
* public PageResult&lt;AccountVO&gt; queryPage(AccountQueryParams params) {
* // 获取分页参数
* PagingParams pagingParams = params.buildPagingParams();
* // 从 params 获取字段查询条件,从 pagingParams 获取分页条件,查询一页数据
* List&lt;AccountVO&gt; list = accountQueries.queryAccountList(params, pagingParams);
* // 查询总记录数
* long count = accountQueries.countAccount(params);
* // 返回分页结果
* return PageResult.of(list, count);
* }
* </pre>
*
* @author ZhouXY108 <luquanlion@outlook.com>
* @see PagingParams
@@ -44,10 +100,23 @@ import xyz.zhouxy.plusone.commons.util.StringTools;
*/
public class PagingAndSortingQueryParams {
private final PagingParamsBuilder pagingParamsBuilder;
private Integer size;
private Long pageNum;
private List<String> orderBy;
/**
* 创建一个 {@code PagingAndSortingQueryParams} 实例
*
* @param pagingParamsBuilder
* 分页参数构造器。
* 通过 {@link #pagingParamsBuilder(int, int, Map)} 创建,同一场景下只需要共享同一个实例。
*/
public PagingAndSortingQueryParams(PagingParamsBuilder pagingParamsBuilder) {
this.pagingParamsBuilder = pagingParamsBuilder;
}
// Setters
/**
@@ -88,11 +157,31 @@ public class PagingAndSortingQueryParams {
+ "]";
}
protected static PagingParamsBuilder pagingParamsBuilder(
/**
* 创建一个分页参数构造器
*
* @param defaultSize 默认每页大小
* @param maxSize 最大每页大小
* @param sortableProperties
* 可排序属性。
* key 是供前端指定用于排序的属性名value 是对应数据库中的字段名。
* 只有在此白名单中的属性名才允许用于排序。
* @return 分页参数构造器
*/
public static PagingParamsBuilder pagingParamsBuilder(
int defaultSize, int maxSize, Map<String, String> sortableProperties) {
return new PagingParamsBuilder(defaultSize, maxSize, sortableProperties);
}
/**
* 根据当前查询参数,构建分页参数
*
* @return 分页参数
*/
public PagingParams buildPagingParams() {
return this.pagingParamsBuilder.buildPagingParams(this);
}
/**
* 可排序属性
*/
@@ -103,7 +192,7 @@ public class PagingAndSortingQueryParams {
private final String sqlSnippet;
SortableProperty(String propertyName, String columnName, String orderType) {
private SortableProperty(String propertyName, String columnName, String orderType) {
this.propertyName = propertyName;
this.columnName = columnName;
checkArgument("ASC".equalsIgnoreCase(orderType) || "DESC".equalsIgnoreCase(orderType));

View File

@@ -33,15 +33,15 @@ import javax.annotation.Nullable;
* public static final String SUCCESS_CODE = "000";
* public static final String DEFAULT_SUCCESS_MSG = "成功";
*
* public static <T> UnifiedResponse<T> success() {
* public static &lt;T&gt; UnifiedResponse&lt;T&gt; success() {
* return of(SUCCESS_CODE, DEFAULT_SUCCESS_MSG);
* }
*
* public static <T> UnifiedResponse<T> success(@Nullable String message) {
* public static &lt;T&gt; UnifiedResponse&lt;T&gt; success(@Nullable String message) {
* return of(SUCCESS_CODE, message);
* }
*
* public static <T> UnifiedResponse<T> success(@Nullable String message, @Nullable T data) {
* public static &lt;T&gt; UnifiedResponse&lt;T&gt; success(@Nullable String message, @Nullable T data) {
* return of(SUCCESS_CODE, message, data);
* }
*

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package xyz.zhouxy.plusone.commons.base;
package xyz.zhouxy.plusone.commons.util;
import java.util.Objects;
import java.util.function.Consumer;

View File

@@ -17,7 +17,8 @@
/**
* <h2>工具类</h2>
* <p>
* 包含树构建器({@link TreeBuilder})、断言工具({@link AssertTools})、ID 生成器({@link IdGenerator})及其它实用工具类。
* 包含树构建器({@link TreeBuilder})、断言工具({@link AssertTools})、
* ID 生成器({@link IdGenerator})及其它实用工具类。
*
* @author ZhouXY108 <luquanlion@outlook.com>
*/

View File

@@ -234,6 +234,10 @@ class AccountQueryParams extends PagingAndSortingQueryParams {
private static final PagingParamsBuilder PAGING_PARAMS_BUILDER = PagingAndSortingQueryParams
.pagingParamsBuilder(20, 100, PROPERTY_COLUMN_MAP);
public AccountQueryParams() {
super(PAGING_PARAMS_BUILDER);
}
private @Getter @Setter Long id;
private @Getter @Setter String username;
private @Getter @Setter String email;
@@ -248,10 +252,6 @@ class AccountQueryParams extends PagingAndSortingQueryParams {
}
return this.createTimeEnd.plusDays(1);
}
public PagingParams buildPagingParams() {
return PAGING_PARAMS_BUILDER.buildPagingParams(this);
}
}
@Data

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package xyz.zhouxy.plusone.commons.base;
package xyz.zhouxy.plusone.commons.util;
import static org.junit.jupiter.api.Assertions.*;

View File

@@ -36,7 +36,10 @@ import com.google.common.collect.Lists;
import com.google.gson.Gson;
import cn.hutool.core.util.ObjectUtil;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.ToString;
@SuppressWarnings("null")
@@ -61,7 +64,7 @@ class TreeBuilderTests {
private final TreeBuilder<Menu, MenuList, String> treeBuilder = new TreeBuilder<>(
Menu::getMenuCode,
menu -> Optional.ofNullable(menu.parentMenuCode),
menu -> Optional.ofNullable(menu.getParentMenuCode()),
MenuList::addChild,
Comparator.comparing(Menu::getOrderNum));
@@ -134,45 +137,23 @@ class TreeBuilderTests {
((MenuList) menuMap.get("C1")).children);
}
@ToString
@AllArgsConstructor(access = AccessLevel.PROTECTED)
@EqualsAndHashCode
@ToString
private abstract static class Menu implements Serializable {
protected final String parentMenuCode;
protected final String menuCode;
protected final String title;
protected final int orderNum;
public Menu(String parentMenuCode, String menuCode, String title, int orderNum) {
this.parentMenuCode = parentMenuCode;
this.menuCode = menuCode;
this.title = title;
this.orderNum = orderNum;
}
public String getMenuCode() {
return menuCode;
}
public String getParentMenuCode() {
return parentMenuCode;
}
public String getTitle() {
return title;
}
public int getOrderNum() {
return orderNum;
}
protected final @Getter String parentMenuCode;
protected final @Getter String menuCode;
protected final @Getter String title;
protected final @Getter int orderNum;
private static final long serialVersionUID = 20240917181424L;
}
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
private static final class MenuItem extends Menu {
private final String url;
private final @Getter String url;
private MenuItem(String parentMenuCode, String menuCode, String title, String url, int orderNum) {
super(parentMenuCode, menuCode, title, orderNum);
@@ -187,10 +168,6 @@ class TreeBuilderTests {
return new MenuItem(null, menuCode, title, url, orderNum);
}
public String getUrl() {
return url;
}
private static final long serialVersionUID = 20240917181910L;
}