docs: 完善项目文档 [!9 (gitee)]
修改包描述(package-info.java) 修改 README.md 添加 docs 文件夹,包含各部分功能的介绍
This commit is contained in:
8
docs/1_annotation.md
Normal file
8
docs/1_annotation.md
Normal 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。|
|
||||
39
docs/2_collection.md
Normal file
39
docs/2_collection.md
Normal 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);
|
||||
```
|
||||
118
docs/3_exception.md
Normal file
118
docs/3_exception.md
Normal 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();
|
||||
```
|
||||
|
||||
|
||||
22
docs/4_function.md
Normal file
22
docs/4_function.md
Normal 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) |
|
||||
100
docs/5_model.md
Normal file
100
docs/5_model.md
Normal 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)
|
||||
24
docs/6_time.md
Normal file
24
docs/6_time.md
Normal 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`
|
||||
275
docs/7_other_tools.md
Normal file
275
docs/7_other_tools.md
Normal 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 工具
|
||||
9
docs/8_others.md
Normal file
9
docs/8_others.md
Normal file
@@ -0,0 +1,9 @@
|
||||
## 8. 其它内容
|
||||
|
||||
### 8.1. IWithCode
|
||||
|
||||
对于类似枚举这样的类型,通常需要设置固定的码值表示对应的含义。 可实现 `IWithCode`、`IWithIntCode`、`IWithLongCode`,便于在需要的地方对这些接口的实现进行处理。
|
||||
|
||||
### 8.2. 正则常量
|
||||
|
||||
`RegexConsts` 包含常见正则表达式;`PatternConsts` 包含对应的 `Pattern` 对象
|
||||
Reference in New Issue
Block a user