docs: 完善项目文档 [!9 (gitee)]
修改包描述(package-info.java) 修改 README.md 添加 docs 文件夹,包含各部分功能的介绍
This commit is contained in:
255
README.md
255
README.md
@@ -1,246 +1,31 @@
|
|||||||
## 一、annotation - 注解
|
# Plusone Commons
|
||||||
### 1. StaticFactoryMethod
|
## 1. 简介
|
||||||
标识静态工厂方法。 *《Effective Java》* 的 **Item1** 建议考虑用静态工厂方法替换构造器, 因而考虑有一个注解可以标记一下静态工厂方法,以和其它方法进行区分。
|
Plusone Commons 是一个 Java 工具类库,提供了一系列实用的类和方法,用于简化开发。
|
||||||
|
|
||||||
### 2. ReaderMethod 和 WriterMethod
|
一开始是为了补充日常开发中,guava 认为不需要,而我又用得上的工具,所以需要结合 guava 使用。后面也包含了一些从日常工作与学习中抽离出来可以通用的东西。
|
||||||
分别标识读方法(如 getter)或写方法(如 setter)。
|
|
||||||
|
|
||||||
最早是写了一个集合类,为了方便判断使用读写锁时,哪些情况下使用读锁,哪些情况下使用写锁。
|
Plusone Commons 的工具类不追求“大而全”,而是只提供相对需要的部分功能。
|
||||||
|
|
||||||
### 3. UnsupportedOperation
|
> 未来一些不够“通用”的组件会迁移到更合适的模块中。
|
||||||
标识该方法不被支持或没有实现,将抛出 `UnsupportedOperationException`。 为了方便在使用时,不需要点进源码,就能知道该方法没有实现。
|
|
||||||
|
|
||||||
### 4. Virtual
|
## 2. 安装
|
||||||
Java 非 final 的实例方法,对应 C++/C# 中的虚方法,允许被子类覆写。 Virtual 注解旨在设计父类时,强调该方法父类虽然有默认实现,但子类可以根据自己的需要覆写。
|
项目基于 OpenJDK 8 和 maven 构建。
|
||||||
|
|
||||||
### 5. ValueObject
|
项目目前暂未发布到 maven 中央仓库,使用时需克隆代码到本地,并安装到本地仓库,然后才能在项目中引入依赖。
|
||||||
标记一个类,表示其作为值对象,区别于 Entity。
|
|
||||||
|
|
||||||
## 二、base - 基础组件
|
## 3. 功能
|
||||||
### 1. Ref
|
详细功能说明请查阅文档:
|
||||||
`Ref` 包装了一个值,表示对该值的应用。
|
|
||||||
|
|
||||||
灵感来自于 C# 的 ref 参数修饰符。C# 允许通过以下方式,将值返回给调用端:
|
+ [gitee 文档地址](https://gitee.com/zhouxy108/plusone-commons/tree/dev/docs)
|
||||||
```C#
|
|
||||||
void Method(ref int refArgument)
|
|
||||||
{
|
|
||||||
refArgument = refArgument + 44;
|
|
||||||
}
|
|
||||||
|
|
||||||
int number = 1;
|
## 4. 代码仓库
|
||||||
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);
|
+ [GitHub](https://github.com/ZhouXY108/plusone-commons)
|
||||||
method(number);
|
+ [gitee](https://gitee.com/zhouxy108/plusone-commons)
|
||||||
System.out.println(number.getValue()); // Output: 45
|
+ [自建 Gitea 仓库](http://gitea.zhouxy.xyz/plusone/plusone-commons)
|
||||||
```
|
|
||||||
当一个方法需要产生多个结果时,无法有多个返回值,可以使用 `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);
|
欢迎在 GitHub 和 gitee 上通过 issue 反馈使用过程中发现的问题和建议,也接受善意的 PR。
|
||||||
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`,便于在需要的地方对这些接口的实现进行处理。
|
|
||||||
|
|
||||||
## 三、collection - 集合
|
## 5. 许可
|
||||||
### 1. CollectionTools
|
项目使用 [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0) 开源,相关声明请参阅 `NOTICE` 文件。
|
||||||
集合工具类
|
|
||||||
|
|
||||||
## 四、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`)及其它实用工具类。
|
|
||||||
|
|||||||
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` 对象
|
||||||
@@ -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# 中的虚方法,允许被子类覆写。
|
|
||||||
* {@link Virtual} 注解旨在设计父类时,强调该方法父类虽然有默认实现,但子类可以根据自己的需要覆写。
|
|
||||||
*
|
|
||||||
* <h3>
|
|
||||||
* 5. {@link ValueObject}
|
|
||||||
* </h3>
|
|
||||||
* <p>
|
|
||||||
* 标记一个类,表示其作为值对象,区别于 Entity。
|
|
||||||
*
|
*
|
||||||
* @author ZhouXY108 <luquanlion@outlook.com>
|
* @author ZhouXY108 <luquanlion@outlook.com>
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -15,12 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <h2>集合</h2>
|
* 集合相关工具
|
||||||
*
|
|
||||||
* <h3>
|
|
||||||
* 1. {@link CollectionTools}
|
|
||||||
* </h3>
|
|
||||||
* 集合工具类
|
|
||||||
*
|
*
|
||||||
* @author ZhouXY108 <luquanlion@outlook.com>
|
* @author ZhouXY108 <luquanlion@outlook.com>
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -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<LoginException, LoginException.Type, String> {
|
|
||||||
* 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<LoginException, String> {
|
|
||||||
* 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);
|
|
||||||
* }
|
|
||||||
* }
|
|
||||||
* }
|
|
||||||
* </pre>
|
|
||||||
*
|
|
||||||
* 使用时,可以使用这种方式创建并抛出异常:
|
|
||||||
* <pre>
|
|
||||||
* throw LoginException.Type.TOKEN_TIMEOUT.create();
|
|
||||||
* </pre>
|
|
||||||
*
|
|
||||||
* <h3>2. 业务异常</h3>
|
|
||||||
* 预设常见的业务异常。可继承 {@link BizException} 自定义业务异常。
|
|
||||||
*
|
|
||||||
* <h3>3. 系统异常</h3>
|
|
||||||
* 预设常见的系统异常。可继承 {@link SysException} 自定义系统异常。
|
|
||||||
*
|
*
|
||||||
* @author ZhouXY108 <luquanlion@outlook.com>
|
* @author ZhouXY108 <luquanlion@outlook.com>
|
||||||
*/
|
*/
|
||||||
package xyz.zhouxy.plusone.commons.exception;
|
package xyz.zhouxy.plusone.commons.exception;
|
||||||
|
|
||||||
import xyz.zhouxy.plusone.commons.exception.business.*;
|
|
||||||
import xyz.zhouxy.plusone.commons.exception.system.*;
|
|
||||||
|
|||||||
@@ -17,7 +17,8 @@
|
|||||||
/**
|
/**
|
||||||
* <h2>工具类</h2>
|
* <h2>工具类</h2>
|
||||||
* <p>
|
* <p>
|
||||||
* 包含树构建器({@link TreeBuilder})、断言工具({@link AssertTools})、ID 生成器({@link IdGenerator})及其它实用工具类。
|
* 包含树构建器({@link TreeBuilder})、断言工具({@link AssertTools})、
|
||||||
|
* ID 生成器({@link IdGenerator})及其它实用工具类。
|
||||||
*
|
*
|
||||||
* @author ZhouXY108 <luquanlion@outlook.com>
|
* @author ZhouXY108 <luquanlion@outlook.com>
|
||||||
*/
|
*/
|
||||||
|
|||||||
Reference in New Issue
Block a user