121 Commits

Author SHA1 Message Date
e8274b1beb 添加 TODO,不放行全部域名,通过配置文件配置允许访问的域名。 2023-12-06 18:14:29 +08:00
3b1c15335b 升级 Spring Boot 版本。 2023-12-06 18:13:58 +08:00
81cefdca83 修改排序的 bug。 2023-12-06 18:13:34 +08:00
86fcd7a255 plusone-commons 中新增 BaseRuntimeException 作为基础运行时异常,原 BaseException 作为基础检查型异常。 2023-10-18 11:09:55 +08:00
15ed0e2ec6 升级 Spring Boot 版本。 2023-10-18 11:09:26 +08:00
3bb5397c49 修改 UnsupportedMenuTypeException 的错误码 2023-09-10 16:47:53 +08:00
b69983077a plusone-validator 修改了方法名 2023-09-10 16:47:30 +08:00
7f8a37803a 修改菜单树的构建 2023-09-09 18:41:00 +08:00
8414fcb010 删除 MenuUtil 2023-09-09 18:40:25 +08:00
b91ab22354 plusone-commons 修改了 IWithIntCode 的包名 2023-09-09 18:39:13 +08:00
00c5ed651e 修改异常 2023-09-09 18:38:44 +08:00
b25d7589c2 使用 UnifiedResponse 替代 RestfulResult 2023-09-09 18:38:09 +08:00
208d7c93c9 plusone-commons 修改了 BaseException 2023-09-09 18:19:58 +08:00
b870684fea 静态方法不需要 final 2023-09-09 18:18:01 +08:00
9582cc7187 plusone-commons 修改了 IWithCode 和 IWithIntCode 的包名 2023-09-09 18:17:17 +08:00
a30e5bcd12 升级依赖 2023-09-09 18:14:41 +08:00
1c02b37442 优化代码。 2023-07-16 03:28:17 +08:00
68f6ce5b8f Merge branch 'dev' of http://zhouxy.xyz:3000/ZhouXY108/plusone-admin into dev 2023-07-07 15:34:45 +08:00
bd42da6777 优化 MenuViewObject。 2023-07-07 15:33:49 +08:00
c4e7b8fd26 格式化代码。 2023-06-29 02:03:24 +08:00
e34efdfc56 去除 TODO 注释。 2023-06-29 01:44:28 +08:00
7077b84317 修改 MenuType。 2023-06-29 01:44:06 +08:00
79466d180f 优化代码。 2023-06-29 01:43:43 +08:00
dcb86f9100 标识过时方法。 2023-06-29 01:43:08 +08:00
e972899fbb IWithCode 相关接口的使用调整。 2023-06-29 01:41:55 +08:00
aa7f10008f NumberUtil 改为 Numbers。 2023-06-29 01:39:43 +08:00
b32dac3b8a ValidatableStringRecord 移动到 plusone-commons 并优化。 2023-06-29 01:37:35 +08:00
2f07ce0e5f Merge branch 'dev' of http://zhouxy.xyz:3000/ZhouXY108/plusone-admin into dev 2023-06-29 01:32:42 +08:00
30f67dfa18 领域事件相关建模。 2023-06-29 01:28:14 +08:00
87942d708a 正则相关的操作由 RegexUtil 负责。 2023-06-29 01:27:14 +08:00
d05fcbfc9c 更新 Spring Boot 版本。 2023-06-29 01:26:39 +08:00
363460fe96 调整枚举类。 2023-06-29 01:26:17 +08:00
f83a05e429 改用 ValueSet 的静态工厂方法。 2023-05-16 17:41:29 +08:00
92896a0345 Merge branch 'dev' of http://zhouxy.xyz:3000/ZhouXY108/plusone-admin into dev 2023-05-16 17:39:21 +08:00
ac3fdae2d0 改用 ValueSet 的静态工厂方法。 2023-05-16 17:39:09 +08:00
8dcf8eadac 修改注释。 2023-05-16 17:38:37 +08:00
39d8eac578 删除多余的导入。 2023-04-28 23:42:15 +08:00
3858aa16e1 更新 Spring Boot 版本。 2023-04-28 23:41:15 +08:00
4d3d0f7cc7 commit. 2023-04-20 21:29:23 +08:00
17b5b55d59 添加些许规约。 2023-04-19 19:24:00 +08:00
30f29bb4b3 更改 Password 实现。 2023-04-19 06:05:02 +08:00
104bffb0e8 Merge branch 'dev' of http://zhouxy.xyz:3000/ZhouXY108/plusone-admin into dev 2023-04-16 04:33:31 +08:00
62607dc0a4 plusone-exception-handler 的代码调整。 2023-04-16 00:54:59 +08:00
fe03b6da4d 静态工厂方法添加 @StaticFactoryMethod 注解。 2023-04-16 00:16:11 +08:00
0f145e383e 重构 FastDFSFile。 2023-04-16 00:08:36 +08:00
89584d2a48 重构代码。 2023-04-16 00:08:35 +08:00
f3017e90c0 修改错别字。 2023-04-16 00:08:35 +08:00
f1d16808c6 添加常量。 2023-04-16 00:08:35 +08:00
8460b9da29 修改部分 jsr305 注解的使用。 2023-04-16 00:08:04 +08:00
19fc97362c 更改 Exception Handler 版本。 2023-04-16 00:06:36 +08:00
d91f818c96 Merge remote-tracking branch 'origin/main' into main 2023-04-15 22:06:58 +08:00
65d77f35c1 静态工厂方法添加 @StaticFactoryMethod 注解。 2023-04-15 14:01:46 +08:00
b14c03fc32 静态工厂方法添加 @StaticFactoryMethod 注解。 2023-04-15 14:00:20 +08:00
ba34fa4a2b 重构 FastDFSFile。 2023-04-15 13:38:00 +08:00
b015f5d1c4 重构代码。 2023-04-15 13:37:44 +08:00
6335fa03b1 Merge branch 'dev' of http://zhouxy.xyz:3000/ZhouXY108/plusone-admin into dev 2023-04-15 12:26:02 +08:00
0e0d6f1808 plusone-commons 做了调整。 2023-04-15 05:24:03 +08:00
5215fded9c 修改错别字。 2023-04-06 16:21:54 +08:00
c30cca8f6a 添加常量。 2023-04-04 18:53:38 +08:00
06ffc8d858 避免实例化。 2023-04-04 00:32:25 +08:00
60b4f18e8c 更改 hutool 版本。 2023-04-04 00:27:53 +08:00
2b282039ad 更新 plusone-validator 和 plusone-exception-handler。 2023-04-04 00:24:31 +08:00
ea7a8ee6a0 Merge pull request '整合多次更改' (#1) from dev into main
Reviewed-on: http://zhouxy.xyz:3000/ZhouXY108/plusone-admin/pulls/1
2023-03-29 18:54:03 +08:00
67313938e1 提交代码。 2023-03-28 15:41:15 +08:00
27be582bfb 升级 Spring Boot 版本。 2023-03-28 15:40:49 +08:00
288ce9689f postgresql:42.3.8 依赖 checker-qual:3.5.0,将 guava 的版本更改为 30.1-jre,避免依赖冲突。 2023-03-21 15:52:38 +08:00
71edaa60a4 整理依赖。 2023-03-17 18:46:39 +08:00
39cd57e675 使用原生哈希算法。使用自己实现的随机盐生成方法。 2023-03-17 18:46:06 +08:00
7a44c43402 使用原生 UUID。 2023-03-17 18:44:51 +08:00
55395ed327 自己实现随机字符串的生成。 2023-03-17 18:44:33 +08:00
956da350ed 更改 plusone-exception-handler 版本。 2023-03-17 18:43:24 +08:00
78cdded667 int 数组的值总和可能大于 int 的最大值,使用 long 类型的变量存储其结果。 2023-03-17 18:42:43 +08:00
cc3c4be8de 升级 hutool。 2023-03-14 18:10:02 +08:00
1e31e1f327 使用 Integer 接收前端的参数,而不是枚举类。 2023-03-14 10:51:11 +08:00
993ba7fa7b 枚举类不允许继承。 2023-03-12 13:44:28 +08:00
8ae0faa04d 优化正则的使用。 2023-03-12 13:42:08 +08:00
920971c640 Merge branch 'dev' of http://zhouxy.xyz:3000/ZhouXY108/plusone-admin into dev 2023-03-12 13:29:15 +08:00
5f1cb36235 AccountInfo 的构建使用 Builder 模式。 2023-03-12 13:29:07 +08:00
b4bccfe663 升级 Spring Boot 到 2.7.9。 2023-03-12 13:28:39 +08:00
dc09022919 update hutool to 5.8.14 2023-03-08 19:48:17 +08:00
f0b6014e77 整理代码。 2023-03-08 17:42:04 +08:00
19557734ed Update Spring Boot to 2.7.9. 2023-03-03 21:02:03 +08:00
ff54aca271 Merge branch 'dev' of http://zhouxy.xyz:3000/ZhouXY108/plusone-admin into dev 2023-02-26 13:49:09 +08:00
5d9e6ae5b3 修改 Optional.map 方法的错误使用。 2023-02-26 13:49:03 +08:00
5c3b31bb18 更新方法名。 2023-02-25 02:00:12 +08:00
33d3d86393 Merge branch 'dev' into main 2023-02-25 00:32:34 +08:00
a0b16afde2 plusone-commons 重构,代码随之重构调整。 2023-02-24 17:30:18 +08:00
9293ba6817 20230219 2023-02-19 04:21:39 +08:00
92dbc613d2 统一空格。 2023-02-18 11:00:40 +08:00
1b551eeb4c Merge branch 'dev' into main 2023-02-17 15:57:05 +08:00
5b80f70373 添加 guava 依赖。 2023-02-17 15:29:51 +08:00
cf068566a1 Merge branch 'dev' of http://zhouxy.xyz:3000/ZhouXY108/plusone-admin into dev 2023-02-17 11:25:07 +08:00
0212020dcf 修改领域模型。 2023-02-17 11:24:55 +08:00
699ba2394d 更新 fastdfs-client-java 的版本。 2023-02-16 20:30:05 +08:00
5ad6980bf7 Merge branch 'feature/jdbc-next' into dev 2023-02-16 15:57:22 +08:00
bac7a007e6 将 find 方法的返回值改为 Optional<T>。 2023-02-16 15:42:30 +08:00
02d918d0a4 添加工厂方法的重载。 2023-02-16 15:03:41 +08:00
f70e9575ea 改造 exists 相关方法。 2023-02-16 14:35:10 +08:00
cbc2711540 重构 DAO 基础代码。 2023-02-11 20:34:16 +08:00
960a4d4ef3 重构代码,调整配置的加载。 2023-02-10 13:34:08 +08:00
1dbd00ccef 修改 FastDFS 的集成方式。 2023-02-09 15:49:05 +08:00
44f2067f20 plusone-exception-handler 0.0.7-SNAPSHOT 暂时有 bug,用回 0.0.6-SNAPSHOT。 2023-02-09 15:45:24 +08:00
cc44c3ddbd 修改类名。 2023-02-09 15:44:00 +08:00
2de86dcc4a 添加 TODO。 2023-02-09 00:54:58 +08:00
7c522cae1d 测试时的配置文件。 2023-02-09 00:40:37 +08:00
117390c5ae 集成 FastDFS。 2023-02-09 00:37:06 +08:00
cec72ba7f9 进一步完善 JdbcEntityDaoSupport 和 AssertResult 的 API,使构建 Repository 的实现更容易。 2023-02-09 00:35:17 +08:00
d01db60309 使用原先 ExceptionHandler 共享 ExceptionInfoHolder 的方式。 2023-02-09 00:32:12 +08:00
151a33cad4 调整配置文件。优化代码格式。 2023-02-01 09:46:57 +08:00
1611bf38e8 Merge branch 'dev' into main 2023-01-28 17:37:12 +08:00
6d7517f778 调整配置文件。 2023-01-28 17:36:35 +08:00
c73c722b87 更新依赖;解决依赖冲突;调整、重构代码。 2023-01-28 12:40:02 +08:00
2c967a4c0d 注册时不要求填写性别与头像。 2022-12-30 17:51:25 +08:00
90da53bf2b AccountLoginException 没必要输出太多信息,创建一个拦截器专门处理。 2022-12-29 17:51:54 +08:00
7462b47655 升级 Spring Boot 到 2.7.7。 2022-12-28 16:57:26 +08:00
17273b0bc6 Merge pull request 'dev' (#3) from dev into master
Reviewed-on: http://zhouxy.xyz:3000/ZhouXY108/plusone-admin/pulls/3
2022-12-17 22:55:06 +08:00
46a72e63c0 删除 TestController。 2022-12-15 11:38:22 +08:00
ad8a6da44d 2022/12/14 2022-12-14 14:42:56 +08:00
d398800ce4 重构,升级异常拦截器为 0.0.5-SNAPSHOT。 2022-12-14 14:30:54 +08:00
71438901a0 重构。 2022-12-14 13:41:57 +08:00
2d02899372 Merge pull request 'dev' (#1) from dev into master
Reviewed-on: http://zhouxy.xyz:3000/ZhouXY108/plusone-admin/pulls/1
2022-12-10 03:46:53 +08:00
156 changed files with 2672 additions and 1418 deletions

View File

@@ -33,4 +33,4 @@ indent_size=4
indent_size=2 indent_size=2
[*.java] [*.java]
indent_size = 4 indent_size=4

5
.gitignore vendored
View File

@@ -32,3 +32,8 @@ build/
### VS Code ### ### VS Code ###
.vscode/ .vscode/
log/
### bak ###
*.bak
*-secret.yaml

View File

@@ -19,3 +19,10 @@
目前项目还没完成,开发中…… 目前项目还没完成,开发中……
相关的文档和介绍完善中…… 相关的文档和介绍完善中……
## 编码规约
### 关于 null
1. 方法默认参数不为 `null`**可以**在 Javadoc 中对参数进行说明,如“...(must not be {@code null}[ or empty string])”。方法内部**必须**做参数校验,如使用 Assert 等工具类。
2. 如果参数允许为空,**必须**为参数添加 `@Nullable` 注解,并在 Javadoc 中对参数进行说明,如“...(may be {@code null})”。
3. 除极特殊的情况,方法默认**不返回 `null`**,如可能返回一个对象表示值的缺失,则**必须**使用 `Optional`**绝不在返回类型为 Optional 的方法中返回 `null`**。
4. 在极其特殊的情况下,方法需要返回 `null`**必须**给方法加上 `@Nullable` 注解,并在 Javadoc 中详细说明。

View File

@@ -222,6 +222,10 @@
{ {
"code": 4040201, "code": 4040201,
"description": "不支持的 PrincipalType" "description": "不支持的 PrincipalType"
},
{
"code": 4040209,
"description": "不支持的菜单类型"
} }
] ]
}, },

View File

@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <project xmlns="https://maven.apache.org/POM/4.0.0" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="https://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<artifactId>plusone-basic</artifactId> <artifactId>plusone-basic</artifactId>

View File

@@ -1,11 +1,13 @@
package xyz.zhouxy.plusone.exception.handler; package xyz.zhouxy.plusone.exception.handler;
import javax.annotation.Nonnull;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import xyz.zhouxy.plusone.constant.ErrorCodeConsts; import xyz.zhouxy.plusone.commons.exception.handler.AllExceptionHandler;
import xyz.zhouxy.plusone.exception.handler.BaseExceptionHandler.ExceptionInfoHolder; import xyz.zhouxy.plusone.commons.exception.handler.BaseExceptionHandler.ExceptionInfoHolder;
/** /**
* AllExceptionHandlerConfig * AllExceptionHandlerConfig
@@ -15,7 +17,7 @@ import xyz.zhouxy.plusone.exception.handler.BaseExceptionHandler.ExceptionInfoHo
public class AllExceptionHandlerConfig { public class AllExceptionHandlerConfig {
@Bean @Bean
AllExceptionHandler getAllExceptionHandler(ExceptionInfoHolder exceptionInfoHolder) { AllExceptionHandler getAllExceptionHandler(@Nonnull ExceptionInfoHolder exceptionInfoHolder) {
return new AllExceptionHandler(ErrorCodeConsts.DEFAULT_ERROR_CODE, exceptionInfoHolder); return new AllExceptionHandler(exceptionInfoHolder);
} }
} }

View File

@@ -1,5 +1,7 @@
package xyz.zhouxy.plusone.exception.handler; package xyz.zhouxy.plusone.exception.handler;
import javax.annotation.Nonnull;
import org.springframework.context.support.DefaultMessageSourceResolvable; import org.springframework.context.support.DefaultMessageSourceResolvable;
import org.springframework.core.Ordered; import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order; import org.springframework.core.annotation.Order;
@@ -11,8 +13,8 @@ import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice; import org.springframework.web.bind.annotation.RestControllerAdvice;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import xyz.zhouxy.plusone.constant.ErrorCodeConsts; import xyz.zhouxy.plusone.commons.exception.handler.BaseExceptionHandler;
import xyz.zhouxy.plusone.util.RestfulResult; import xyz.zhouxy.plusone.commons.util.UnifiedResponse;
/** /**
* 默认异常的处理器 * 默认异常的处理器
@@ -41,14 +43,13 @@ import xyz.zhouxy.plusone.util.RestfulResult;
@Order(Ordered.LOWEST_PRECEDENCE - 1) @Order(Ordered.LOWEST_PRECEDENCE - 1)
@Slf4j @Slf4j
public class DefaultExceptionHandler extends BaseExceptionHandler { public class DefaultExceptionHandler extends BaseExceptionHandler {
public DefaultExceptionHandler(@Nonnull ExceptionInfoHolder exceptionInfoHolder) {
public DefaultExceptionHandler(ExceptionInfoHolder exceptionInfoHolder) { super(exceptionInfoHolder);
super(ErrorCodeConsts.DEFAULT_ERROR_CODE, exceptionInfoHolder);
set(IllegalArgumentException.class, 4010000, "格式错误", HttpStatus.FORBIDDEN); set(IllegalArgumentException.class, 4010000, "格式错误", HttpStatus.FORBIDDEN);
set(DataAccessException.class, 6030000, "数据库错误", HttpStatus.INTERNAL_SERVER_ERROR, true); set(DataAccessException.class, 6030000, "数据库错误", HttpStatus.INTERNAL_SERVER_ERROR, true);
set(MethodArgumentNotValidException.class, set(MethodArgumentNotValidException.class,
4040401, 4040401,
e -> ((MethodArgumentNotValidException) e).getAllErrors() e -> e.getAllErrors()
.stream() .stream()
.map(DefaultMessageSourceResolvable::getDefaultMessage) .map(DefaultMessageSourceResolvable::getDefaultMessage)
.toList() .toList()
@@ -61,7 +62,7 @@ public class DefaultExceptionHandler extends BaseExceptionHandler {
DataAccessException.class, DataAccessException.class,
MethodArgumentNotValidException.class MethodArgumentNotValidException.class
}) })
public ResponseEntity<RestfulResult> handleException(Exception e) { public ResponseEntity<UnifiedResponse> handleException(Exception e) {
log.error(e.getMessage(), e); log.error(e.getMessage(), e);
return buildExceptionResponse(e); return buildExceptionResponse(e);
} }

View File

@@ -0,0 +1,29 @@
package xyz.zhouxy.plusone.exception.handler;
import javax.annotation.Nonnull;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import lombok.extern.slf4j.Slf4j;
import xyz.zhouxy.plusone.commons.exception.handler.BaseExceptionHandler;
import xyz.zhouxy.plusone.commons.util.UnifiedResponse;
import xyz.zhouxy.plusone.exception.SysException;
@RestControllerAdvice
@Slf4j
public class SysExceptionHandler extends BaseExceptionHandler {
public SysExceptionHandler(@Nonnull ExceptionInfoHolder exceptionInfoHolder) {
super(exceptionInfoHolder);
}
@ExceptionHandler({ SysException.class })
public ResponseEntity<UnifiedResponse> handleException(@Nonnull Exception e) {
log.error(e.getMessage(), e);
HttpStatus httpStatus = getHttpStatus(e);
return new ResponseEntity<>(UnifiedResponse.error(getErrorCode(e), "系统错误"), httpStatus);
}
}

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <project xmlns="https://maven.apache.org/POM/4.0.0" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="https://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<artifactId>plusone-basic</artifactId> <artifactId>plusone-basic</artifactId>
@@ -31,20 +31,25 @@
<artifactId>spring-webmvc</artifactId> <artifactId>spring-webmvc</artifactId>
</dependency> </dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
</dependency>
<dependency> <dependency>
<groupId>xyz.zhouxy.plusone</groupId> <groupId>xyz.zhouxy.plusone</groupId>
<artifactId>plusone-commons</artifactId> <artifactId>plusone-commons</artifactId>
<version>0.0.1-SNAPSHOT</version> <version>0.1.0-SNAPSHOT</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>xyz.zhouxy.plusone</groupId> <groupId>xyz.zhouxy.plusone</groupId>
<artifactId>plusone-validator</artifactId> <artifactId>plusone-validator</artifactId>
<version>0.0.1-SNAPSHOT</version> <version>0.1.3-SNAPSHOT</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>xyz.zhouxy.plusone</groupId> <groupId>xyz.zhouxy.plusone</groupId>
<artifactId>plusone-exception-handler</artifactId> <artifactId>plusone-exception-handler</artifactId>
<version>0.0.4-SNAPSHOT</version> <version>0.0.9-SNAPSHOT</version>
</dependency> </dependency>
</dependencies> </dependencies>

View File

@@ -2,4 +2,10 @@ package xyz.zhouxy.plusone.constant;
public class ErrorCodeConsts { public class ErrorCodeConsts {
public static final int DEFAULT_ERROR_CODE = 9999999; public static final int DEFAULT_ERROR_CODE = 9999999;
public static final int DEFAULT_SYS_ERROR_CODE = 5000000;
public static final int DEFAULT_BIZ_ERROR_CODE = 4000000;
private ErrorCodeConsts() {
throw new IllegalStateException("Utility class");
}
} }

View File

@@ -0,0 +1,39 @@
package xyz.zhouxy.plusone.exception;
import xyz.zhouxy.plusone.commons.exception.BaseRuntimeException;
/**
* 业务异常
*
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
*/
public class BizException extends BaseRuntimeException {
@java.io.Serial
private static final long serialVersionUID = -5524759033245815405L;
public static final String DEFAULT_ERROR_CODE = "4000000";
public BizException(String code, String msg) {
super(code, msg);
}
public BizException(String code, Throwable cause) {
super(code, cause);
}
public BizException(String code, String msg, Throwable cause) {
super(code, msg, cause);
}
public static BizException of(String msg) {
return new BizException(DEFAULT_ERROR_CODE, msg);
}
public static BizException of(Throwable cause) {
return new BizException(DEFAULT_ERROR_CODE, cause);
}
public static BizException of(String msg, Throwable cause) {
return new BizException(DEFAULT_ERROR_CODE, msg, cause);
}
}

View File

@@ -6,20 +6,15 @@ import org.springframework.web.bind.annotation.ResponseStatus;
/** /**
* 需要时,当查询数据不存在时抛出的异常 * 需要时,当查询数据不存在时抛出的异常
* *
* <p>
* 暂时先这样,后续完善异常体系时可能会更改。
* </p>
*
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a> * @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
* @see xyz.zhouxy.plusone.util.AssertResult
*/ */
@ResponseStatus(HttpStatus.NOT_FOUND) @ResponseStatus(HttpStatus.NOT_FOUND)
public class DataNotExistException extends PlusoneException { public class DataNotExistException extends BizException {
@java.io.Serial @java.io.Serial
private static final long serialVersionUID = 6536955800679703111L; private static final long serialVersionUID = 6536955800679703111L;
public static final int ERROR_CODE = 4110100; public static final String ERROR_CODE = "4110100";
public DataNotExistException() { public DataNotExistException() {
super(ERROR_CODE, "数据不存在"); super(ERROR_CODE, "数据不存在");

View File

@@ -4,28 +4,27 @@ import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.ResponseStatus;
/** /**
* 需要时当数据操作的行数不符合预期时抛出的异常 * 需要时当数据操作的结果不符合预期时抛出的异常
* *
* <p> * <p>
* 暂时先这样后续完善异常体系时可能会更改 * 暂时先这样后续完善异常体系时可能会更改
* </p>
* *
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a> * @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
* @see xyz.zhouxy.plusone.util.AssertResult * @see xyz.zhouxy.plusone.util.AssertResult
*/ */
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public class DataOperationNumberException extends PlusoneException { public class DataOperationResultException extends SysException {
@java.io.Serial @java.io.Serial
private static final long serialVersionUID = -9220765735990318186L; private static final long serialVersionUID = -9220765735990318186L;
public static final int ERROR_CODE = 4110200; public static final String ERROR_CODE = "4110200";
public DataOperationNumberException() { public DataOperationResultException() {
super(ERROR_CODE, "数据操作的行数不符合预期"); super(ERROR_CODE, "数据操作结果不符合预期");
} }
public DataOperationNumberException(String message) { public DataOperationResultException(String message) {
super(ERROR_CODE, message); super(ERROR_CODE, message);
} }
} }

View File

@@ -0,0 +1,34 @@
package xyz.zhouxy.plusone.exception;
import xyz.zhouxy.plusone.commons.exception.BaseRuntimeException;
public class SysException extends BaseRuntimeException {
@java.io.Serial
private static final long serialVersionUID = 8821240827443168118L;
public static final String DEFAULT_ERROR_CODE = "5000000";
public SysException(String code, String msg) {
super(code, msg);
}
public SysException(String code, Throwable cause) {
super(code, cause);
}
public SysException(String code, String msg, Throwable cause) {
super(code, msg, cause);
}
public static SysException of(String msg) {
return new SysException(DEFAULT_ERROR_CODE, msg);
}
public static SysException of(Throwable cause) {
return new SysException(DEFAULT_ERROR_CODE, cause);
}
public static SysException of(String msg, Throwable cause) {
return new SysException(DEFAULT_ERROR_CODE, msg, cause);
}
}

View File

@@ -9,33 +9,33 @@ import org.springframework.web.bind.annotation.ResponseStatus;
* @author ZhouXY * @author ZhouXY
*/ */
@ResponseStatus(HttpStatus.BAD_REQUEST) @ResponseStatus(HttpStatus.BAD_REQUEST)
public class UserOperationException extends PlusoneException { public class UserOperationException extends BizException {
@java.io.Serial @java.io.Serial
private static final long serialVersionUID = 4371055414421991940L; private static final long serialVersionUID = 4371055414421991940L;
public static final int DEFAULT_ERROR_CODE = 4040600; public static final String DEFAULT_ERROR_CODE = "4040600";
public UserOperationException(String msg) { public UserOperationException(String code, String msg) {
super(DEFAULT_ERROR_CODE, msg);
}
public UserOperationException(String msg, Throwable cause) {
super(DEFAULT_ERROR_CODE, msg, cause);
}
public UserOperationException(int code, String msg) {
super(code, msg); super(code, msg);
} }
public UserOperationException(int code, Throwable cause) { public UserOperationException(String code, Throwable cause) {
super(code, cause); super(code, cause);
} }
public UserOperationException(int code, String msg, Throwable cause) { public UserOperationException(String code, String msg, Throwable cause) {
super(code, msg, cause); super(code, msg, cause);
} }
public static UserOperationException of(String msg) {
return new UserOperationException(DEFAULT_ERROR_CODE, msg);
}
public static UserOperationException of(String msg, Throwable cause) {
return new UserOperationException(DEFAULT_ERROR_CODE, msg, cause);
}
/** /**
* 无效的操作 * 无效的操作
* *
@@ -52,7 +52,7 @@ public class UserOperationException extends PlusoneException {
* @return 异常对象 * @return 异常对象
*/ */
public static UserOperationException invalidOperation(String msg) { public static UserOperationException invalidOperation(String msg) {
return new UserOperationException(4040604, msg); return new UserOperationException("4040604", msg);
} }
} }

View File

@@ -1,10 +1,10 @@
package xyz.zhouxy.plusone.util; package xyz.zhouxy.plusone.util;
import xyz.zhouxy.plusone.exception.DataNotExistException; import xyz.zhouxy.plusone.exception.DataNotExistException;
import xyz.zhouxy.plusone.exception.DataOperationNumberException; import xyz.zhouxy.plusone.exception.DataOperationResultException;
import java.util.Objects; import java.util.Objects;
import java.util.function.Supplier;
/** /**
* 对数据库执行结果进行判断 * 对数据库执行结果进行判断
@@ -17,51 +17,34 @@ public final class AssertResult {
throw new IllegalStateException("Utility class"); throw new IllegalStateException("Utility class");
} }
public static void update(boolean expression) { public static <E extends Throwable> void isTrue(boolean condition, Supplier<E> e) throws E {
if (!expression) { if (!condition) {
throw new DataOperationNumberException(); throw e.get();
} }
} }
public static void update(boolean expression, String message) { public static <T> void equals(T result, T expectedValue) {
if (!expression) { isTrue(Objects.equals(result, expectedValue), DataOperationResultException::new);
throw new DataOperationNumberException(message);
}
} }
public static void update(Object i, int expectedValue) { public static <T> void equals(T result, T expectedValue, String msgTemplate, Object... args) {
if (!Objects.equals(i, expectedValue)) { isTrue(!Objects.equals(result, expectedValue),
throw new DataOperationNumberException(); () -> new DataOperationResultException(String.format(msgTemplate, args)));
}
} }
public static void update(Object i, int expectedValue, String format) { public static void updateOneRow(int i) {
if (!Objects.equals(i, expectedValue)) { equals(i, 1);
throw new DataOperationNumberException(String.format(format, i));
}
} }
public static void exist(boolean expression) { public static void updateOneRow(int i, String format, Object... args) {
if (!expression) { equals(i, 1, format, args);
throw new DataNotExistException();
}
}
public static void exist(boolean expression, String message) {
if (!expression) {
throw new DataNotExistException(message);
}
} }
public static void nonNull(Object obj) { public static void nonNull(Object obj) {
if (Objects.isNull(obj)) { isTrue(Objects.nonNull(obj), DataNotExistException::new);
throw new DataNotExistException();
}
} }
public static void nonNull(Object obj, String message) { public static void nonNull(Object obj, String message) {
if (Objects.isNull(obj)) { isTrue(Objects.nonNull(obj), () -> new DataNotExistException(message));
throw new DataNotExistException(message);
}
} }
} }

View File

@@ -1,9 +1,5 @@
package xyz.zhouxy.plusone.util; package xyz.zhouxy.plusone.util;
import java.util.Objects;
import java.util.regex.Pattern;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
/** /**
@@ -27,17 +23,4 @@ public class PlusoneStrUtil {
return StringUtils.hasLength(value) ? value : EMPTY_STR; return StringUtils.hasLength(value) ? value : EMPTY_STR;
} }
public static String checkString(String value, String regex, String message) {
Assert.notNull(value, message);
Assert.isTrue(Pattern.matches(regex, value), message);
return value;
}
public static String checkStringNullable(String value, String regex, String message) {
return Objects.nonNull(value) ? checkString(value, regex, message) : null;
}
public static String checkStringOrDefault(String value, String regex, String message) {
return StringUtils.hasText(value) ? checkString(value, regex, message) : "";
}
} }

View File

@@ -0,0 +1,18 @@
package xyz.zhouxy.plusone.util;
import java.security.SecureRandom;
public final class RandomUtil {
private RandomUtil() {
throw new IllegalStateException("Utility class");
}
public static String randomStr(char[] sourceCharacters, int length) {
SecureRandom random = new SecureRandom();
char[] result = new char[length];
for (int i = 0; i < length; i++) {
result[i] = sourceCharacters[random.nextInt(sourceCharacters.length)];
}
return String.valueOf(result);
}
}

View File

@@ -0,0 +1,23 @@
package xyz.zhouxy.plusone.util;
import java.util.Arrays;
import org.junit.jupiter.api.Test;
import lombok.extern.slf4j.Slf4j;
@Slf4j
class RandomUtilTests {
@Test
void testRandom() {
String[] s = new String[20];
for (int i = 0; i < 20; i++) {
s[i] = RandomUtil.randomStr(
"0123456789qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM~`!@#$%^&*()_-+={[\\|/:;\"',.<>?]}"
.toCharArray(),
28);
}
log.info("{}", Arrays.toString(s));
}
}

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <project xmlns="https://maven.apache.org/POM/4.0.0" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="https://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>xyz.zhouxy</groupId> <groupId>xyz.zhouxy</groupId>
@@ -15,11 +15,6 @@
<groupId>xyz.zhouxy</groupId> <groupId>xyz.zhouxy</groupId>
</dependency> </dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-core</artifactId>
</dependency>
<dependency> <dependency>
<groupId>org.projectlombok</groupId> <groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId> <artifactId>lombok</artifactId>
@@ -28,12 +23,10 @@
<dependency> <dependency>
<groupId>com.fasterxml.jackson.core</groupId> <groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId> <artifactId>jackson-core</artifactId>
<version>2.13.4</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.fasterxml.jackson.core</groupId> <groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId> <artifactId>jackson-annotations</artifactId>
<version>2.13.4</version> </dependency>
</dependency>
</dependencies> </dependencies>
</project> </project>

View File

@@ -1,32 +1,33 @@
package xyz.zhouxy.plusone.constant; package xyz.zhouxy.plusone.constant;
import xyz.zhouxy.plusone.util.Enumeration; import java.util.Collection;
import xyz.zhouxy.plusone.util.EnumerationValuesHolder;
import javax.annotation.Nonnull;
import xyz.zhouxy.plusone.commons.util.Enumeration;
/** /**
* 实体状态 * 实体状态
* *
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a> * @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
*/ */
public class EntityStatus extends Enumeration<EntityStatus> { public final class EntityStatus extends Enumeration<EntityStatus> {
private EntityStatus(int value, String name) { private EntityStatus(int id, @Nonnull String name) {
super(value, name); super(id, name);
} }
// 常量 // 常量
public static final EntityStatus AVAILABLE = new EntityStatus(0, "正常"); public static final EntityStatus AVAILABLE = new EntityStatus(0, "正常");
public static final EntityStatus DISABLED = new EntityStatus(1, "禁用"); public static final EntityStatus DISABLED = new EntityStatus(1, "禁用");
private static final EnumerationValuesHolder<EntityStatus> ENUMERATION_VALUES = new EnumerationValuesHolder<>( private static final ValueSet<EntityStatus> VALUE_SET = ValueSet.of(AVAILABLE, DISABLED);
new EntityStatus[] { AVAILABLE, DISABLED });
public static EntityStatus of(int value) { public static EntityStatus of(int id) {
return ENUMERATION_VALUES.get(value); return VALUE_SET.get(id);
} }
@Override public static Collection<EntityStatus> constants() {
public String toString() { return VALUE_SET.getValues();
return "EntityStatus" + super.toString();
} }
} }

View File

@@ -1,27 +0,0 @@
package xyz.zhouxy.plusone.constant;
/**
* 正则表达式常量
*
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
*/
public final class RegexConsts {
public static final String DATE = "^\\d{4}-\\d{2}-\\d{2}";
public static final String PASSWORD = "^(?=.*\\d)(?=.*[a-z])(?=.*[A-Z])[\\w\\\\!#$%&'*\\+\\-/=?^`{|}~@\\(\\)\\[\\]\",\\.;':><]{8,32}$";
public static final String CAPTCHA = "^[0-9A-Za-z]{4,6}$";
public static final String EMAIL = "^\\w+([-+.]\\w+)*@[a-zA-Z0-9][-a-zA-Z0-9]{0,62}(\\.[a-zA-Z0-9][-a-zA-Z0-9]{0,62})*(\\.(?![0-9]+$)[a-zA-Z0-9][-0-9A-Za-z]{0,62})$";
public static final String MOBILE_PHONE = "^(13[0-9]|14[01456879]|15[0-35-9]|16[2567]|17[0-8]|18[0-9]|19[0-35-9])\\d{8}$";
public static final String USERNAME = "^[\\da-zA-Z_.@\\\\]{4,36}$";
public static final String NICKNAME = "^[\\da-zA-Z_.@\\\\]{4,36}$";
private RegexConsts() {
throw new IllegalStateException("Utility class");
}
}

View File

@@ -1,6 +1,7 @@
package xyz.zhouxy.plusone.domain; package xyz.zhouxy.plusone.domain;
import cn.hutool.core.lang.UUID; import java.util.UUID;
import lombok.Getter; import lombok.Getter;
/** /**
@@ -16,11 +17,11 @@ import lombok.Getter;
*/ */
@Getter @Getter
public abstract class DomainEvent { public abstract class DomainEvent {
private String identifier; private final String identifier;
private long happenedAt; private final long happenedAt;
protected DomainEvent() { protected DomainEvent() {
this.identifier = UUID.randomUUID().toString(true); this.identifier = UUID.randomUUID().toString();
this.happenedAt = System.currentTimeMillis(); this.happenedAt = System.currentTimeMillis();
} }
} }

View File

@@ -5,6 +5,8 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import xyz.zhouxy.plusone.commons.annotation.Overridable;
/** /**
* 实体 * 实体
* *
@@ -26,7 +28,29 @@ public abstract class Entity<ID extends Serializable> {
public abstract Optional<ID> getId(); public abstract Optional<ID> getId();
protected void addDomainEvent(DomainEvent domainEvent) { protected final void addDomainEvent(DomainEvent domainEvent) {
domainEvents.add(domainEvent); domainEvents.add(domainEvent);
} }
public final List<DomainEvent> getDomainEvents() {
return List.copyOf(domainEvents);
}
@Override
@Overridable
public boolean equals(Object obj) {
return super.equals(obj);
}
@Override
@Overridable
public int hashCode() {
return super.hashCode();
}
@Override
@Overridable
public String toString() {
return super.toString();
}
} }

View File

@@ -0,0 +1,8 @@
package xyz.zhouxy.plusone.domain;
public interface IDomainEventPublisher {
void publish(DomainEvent domainEvent);
<T extends DomainEvent> void subsrcibe(IDomainEventSubscriber<T> subscriber);
}

View File

@@ -0,0 +1,11 @@
package xyz.zhouxy.plusone.domain;
/**
* 领域事件消费者
*/
public interface IDomainEventSubscriber<T extends DomainEvent> {
void handleEvent(final T event);
Class<T> subscribedToEventType();
}

View File

@@ -1,6 +1,7 @@
package xyz.zhouxy.plusone.domain; package xyz.zhouxy.plusone.domain;
import java.io.Serializable; import java.io.Serializable;
import java.util.Optional;
/** /**
* Repository 基础接口 * Repository 基础接口
@@ -10,7 +11,7 @@ import java.io.Serializable;
*/ */
public interface IRepository<T extends AggregateRoot<ID>, ID extends Serializable> { public interface IRepository<T extends AggregateRoot<ID>, ID extends Serializable> {
T find(ID id); Optional<T> find(ID id);
T save(T entity); T save(T entity);

View File

@@ -1,34 +0,0 @@
package xyz.zhouxy.plusone.domain;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonValue;
/**
* 带校验的字符串值对象
*
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
*/
public abstract class ValidatableStringRecord implements IValueObject {
protected String value;
protected final String format;
protected ValidatableStringRecord(String format) {
this.format = format;
}
@JsonIgnore
protected boolean isValid() {
return value.matches(format);
}
@JsonValue
public String value() {
return value;
}
@Override
public String toString() {
return value;
}
}

View File

@@ -1,36 +0,0 @@
package xyz.zhouxy.plusone.util;
import java.util.regex.Pattern;
public class RegexUtil {
private RegexUtil() {
throw new IllegalStateException("Utility class");
}
public static boolean matches(CharSequence input, String regex) {
return Pattern.matches(regex, input);
}
public static boolean matchesOr(CharSequence input, String... regexs) {
boolean isMatched;
for (var regex : regexs) {
isMatched = Pattern.matches(regex, input);
if (isMatched) {
return true;
}
}
return false;
}
public static boolean matchesAnd(CharSequence input, String... regexs) {
boolean isMatched;
for (var regex : regexs) {
isMatched = Pattern.matches(regex, input);
if (!isMatched) {
return false;
}
}
return true;
}
}

View File

@@ -1,5 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <project xmlns="https://maven.apache.org/POM/4.0.0"
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>xyz.zhouxy</groupId> <groupId>xyz.zhouxy</groupId>
@@ -84,6 +86,11 @@
<groupId>com.tencentcloudapi</groupId> <groupId>com.tencentcloudapi</groupId>
<artifactId>tencentcloud-sdk-java-sms</artifactId> <artifactId>tencentcloud-sdk-java-sms</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.csource</groupId>
<artifactId>fastdfs-client-java</artifactId>
</dependency>
</dependencies> </dependencies>
</project> </project>

View File

@@ -4,7 +4,8 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import xyz.zhouxy.plusone.exception.handler.BaseExceptionHandler.ExceptionInfoHolder; import xyz.zhouxy.plusone.constant.ErrorCodeConsts;
import xyz.zhouxy.plusone.commons.exception.handler.BaseExceptionHandler.ExceptionInfoHolder;
@Configuration @Configuration
public class PlusoneExceptionHandlerConfig { public class PlusoneExceptionHandlerConfig {
@@ -12,6 +13,6 @@ public class PlusoneExceptionHandlerConfig {
@Bean @Bean
@ConditionalOnMissingBean @ConditionalOnMissingBean
ExceptionInfoHolder exceptionInfoHolder() { ExceptionInfoHolder exceptionInfoHolder() {
return new ExceptionInfoHolder(); return new ExceptionInfoHolder(ErrorCodeConsts.DEFAULT_ERROR_CODE);
} }
} }

View File

@@ -1,8 +1,12 @@
package xyz.zhouxy.plusone.jdbc; package xyz.zhouxy.plusone.jdbc;
import org.springframework.jdbc.core.namedparam.BeanPropertySqlParameterSource; import org.springframework.jdbc.core.namedparam.BeanPropertySqlParameterSource;
import org.springframework.jdbc.core.namedparam.SqlParameterSource;
import org.springframework.lang.Nullable; import org.springframework.lang.Nullable;
import xyz.zhouxy.plusone.commons.base.IWithCode;
import xyz.zhouxy.plusone.commons.base.IWithIntCode;
/** /**
* 扩展了 {@link BeanPropertySqlParameterSource},在将 POJO 转换为 * 扩展了 {@link BeanPropertySqlParameterSource},在将 POJO 转换为
* {@link org.springframework.jdbc.core.namedparam.SqlParameterSource} 时, * {@link org.springframework.jdbc.core.namedparam.SqlParameterSource} 时,
@@ -17,17 +21,23 @@ import org.springframework.lang.Nullable;
*/ */
public class BeanPropertyParamSource extends BeanPropertySqlParameterSource { public class BeanPropertyParamSource extends BeanPropertySqlParameterSource {
public BeanPropertyParamSource(Object object) { public BeanPropertyParamSource(Object object) {
super(object); super(object);
} }
@Override @Override
@Nullable @Nullable
public Object getValue(String paramName) throws IllegalArgumentException { public Object getValue(String paramName) throws IllegalArgumentException {
Object value = super.getValue(paramName); Object value = super.getValue(paramName);
if (value instanceof Enum) { if (value instanceof Enum<?> e) {
return ((Enum<?>) value).ordinal(); if (value instanceof IWithCode<?> c) {
} return c.getCode();
return value; }
} if (value instanceof IWithIntCode c) {
return c.getCode();
}
return e.ordinal();
}
return value;
}
} }

View File

@@ -4,69 +4,72 @@ import java.io.Serializable;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.List; import java.util.List;
import java.util.Optional;
import java.util.stream.Stream; import java.util.stream.Stream;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import org.springframework.jdbc.core.ResultSetExtractor; import org.springframework.jdbc.core.ResultSetExtractor;
import org.springframework.jdbc.core.RowMapper; import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.jdbc.core.namedparam.SqlParameterSource; import org.springframework.jdbc.core.namedparam.SqlParameterSource;
import xyz.zhouxy.plusone.domain.Entity; import xyz.zhouxy.plusone.domain.Entity;
public abstract class JdbcEntityDaoSupport<T extends Entity<ID>, ID extends Serializable> { public abstract class JdbcEntityDaoSupport<T extends Entity<ID>, ID extends Serializable>
protected final NamedParameterJdbcTemplate jdbc; extends PlusoneJdbcDaoSupport {
protected RowMapper<T> rowMapper; protected RowMapper<T> rowMapper;
protected ResultSetExtractor<T> resultSetExtractor; protected ResultSetExtractor<Optional<T>> resultSetExtractor;
protected JdbcEntityDaoSupport(@Nonnull NamedParameterJdbcTemplate namedParameterJdbcTemplate) { protected JdbcEntityDaoSupport(@Nonnull NamedParameterJdbcTemplate namedParameterJdbcTemplate) {
this.jdbc = namedParameterJdbcTemplate; super(namedParameterJdbcTemplate);
this.rowMapper = (ResultSet rs, int rowNum) -> mapRow(rs); this.rowMapper = (ResultSet rs, int rowNum) -> mapRow(rs);
this.resultSetExtractor = (ResultSet rs) -> rs.next() ? mapRow(rs) : null; this.resultSetExtractor = (ResultSet rs) -> rs.next() ? Optional.of(mapRow(rs)) : Optional.empty();
} }
protected final T queryForObject(String sql) { protected final Optional<T> queryForObject(String sql) {
return this.jdbc.query(sql, this.resultSetExtractor); return queryForObject(sql, this.resultSetExtractor);
} }
protected final T queryForObject(String sql, SqlParameterSource paramSource) { protected final Optional<T> queryForObject(String sql, SqlParameterSource paramSource) {
return this.jdbc.query(sql, paramSource, this.resultSetExtractor); return queryForObject(sql, paramSource, this.resultSetExtractor);
}
protected final Optional<T> queryForObject(String sql, String paramName, Object value) {
return queryForObject(sql, new MapSqlParameterSource(paramName, value), this.resultSetExtractor);
} }
protected final List<T> queryForList(String sql) { protected final List<T> queryForList(String sql) {
return this.jdbc.query(sql, this.rowMapper); return queryForList(sql, this.rowMapper);
} }
protected final List<T> queryForList(String sql, SqlParameterSource parameterSource) { protected final List<T> queryForList(String sql, SqlParameterSource parameterSource) {
return this.jdbc.query(sql, parameterSource, this.rowMapper); return queryForList(sql, parameterSource, this.rowMapper);
}
protected final List<T> queryForList(String sql, String paramName, Object value) {
return queryForList(sql, new MapSqlParameterSource(paramName, value), this.rowMapper);
} }
protected final Stream<T> queryForStream(String sql, SqlParameterSource parameterSource) { protected final Stream<T> queryForStream(String sql, SqlParameterSource parameterSource) {
return this.jdbc.queryForStream(sql, parameterSource, this.rowMapper); return queryForStream(sql, parameterSource, this.rowMapper);
} }
protected final <E> Stream<E> queryForStream(String sql, SqlParameterSource parameterSource, Class<E> elementType) { protected final Stream<T> queryForStream(String sql, String paramName, Object value) {
return this.jdbc.queryForList(sql, parameterSource, elementType).stream(); return queryForStream(sql, new MapSqlParameterSource(paramName, value), this.rowMapper);
}
protected final boolean queryExists(String sql, SqlParameterSource parameterSource) {
Boolean isExists = this.jdbc.query(sql, parameterSource, ResultSet::next);
return Boolean.TRUE.equals(isExists);
}
protected final int update(String sql, SqlParameterSource parameterSource) {
return this.jdbc.update(sql, parameterSource);
} }
protected abstract T mapRow(ResultSet rs) throws SQLException; protected abstract T mapRow(ResultSet rs) throws SQLException;
@Deprecated
protected void setRowMapper(@Nonnull RowMapper<T> rowMapper) { protected void setRowMapper(@Nonnull RowMapper<T> rowMapper) {
this.rowMapper = rowMapper; this.rowMapper = rowMapper;
} }
protected void setResultSetExtractor(@Nonnull ResultSetExtractor<T> resultSetExtractor) { @Deprecated
protected void setResultSetExtractor(@Nonnull ResultSetExtractor<Optional<T>> resultSetExtractor) {
this.resultSetExtractor = resultSetExtractor; this.resultSetExtractor = resultSetExtractor;
} }
} }

View File

@@ -1,13 +1,13 @@
package xyz.zhouxy.plusone.jdbc; package xyz.zhouxy.plusone.jdbc;
import org.springframework.context.ApplicationContext;
import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import xyz.zhouxy.plusone.spring.SpringContextHolder; import xyz.zhouxy.plusone.spring.SpringContextHolder;
/** /**
* 全局单例,由 Spring 装配好的对象。 * 通过静态方法获取 Spring 容器中的 {@link JdbcTemplate} 和
* 可通过静态方法获取 Spring 容器中的 {@link JdbcTemplate} 和
* {@link NamedParameterJdbcTemplate} 对象。 * {@link NamedParameterJdbcTemplate} 对象。
* *
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a> * @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
@@ -16,15 +16,19 @@ import xyz.zhouxy.plusone.spring.SpringContextHolder;
*/ */
public final class JdbcFactory { public final class JdbcFactory {
private static final ApplicationContext SPRING_APPLICATION_CONTEXT = SpringContextHolder.getContext();
private static final JdbcTemplate JDBC_TEMPLATE = SPRING_APPLICATION_CONTEXT.getBean(JdbcTemplate.class);
private static final NamedParameterJdbcTemplate NAMED_PARAMETER_JDBC_TEMPLATE = SPRING_APPLICATION_CONTEXT.getBean(NamedParameterJdbcTemplate.class);
private JdbcFactory() { private JdbcFactory() {
throw new IllegalStateException("Utility class"); throw new IllegalStateException("Utility class");
} }
public static JdbcTemplate getJdbcTemplate() { public static JdbcTemplate getJdbcTemplate() {
return SpringContextHolder.getContext().getBean(JdbcTemplate.class); return JDBC_TEMPLATE;
} }
public static NamedParameterJdbcTemplate getNamedParameterJdbcTemplate() { public static NamedParameterJdbcTemplate getNamedParameterJdbcTemplate() {
return SpringContextHolder.getContext().getBean(NamedParameterJdbcTemplate.class); return NAMED_PARAMETER_JDBC_TEMPLATE;
} }
} }

View File

@@ -1,12 +1,16 @@
package xyz.zhouxy.plusone.jdbc; package xyz.zhouxy.plusone.jdbc;
import java.io.Serializable; import java.io.Serializable;
import java.util.Optional;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.jdbc.core.namedparam.SqlParameterSource; import org.springframework.jdbc.core.namedparam.SqlParameterSource;
import com.google.common.base.Preconditions;
import xyz.zhouxy.plusone.commons.annotation.Overridable;
import xyz.zhouxy.plusone.domain.AggregateRoot; import xyz.zhouxy.plusone.domain.AggregateRoot;
import xyz.zhouxy.plusone.domain.IRepository; import xyz.zhouxy.plusone.domain.IRepository;
@@ -18,35 +22,33 @@ public abstract class JdbcRepositorySupport<T extends AggregateRoot<ID>, ID exte
super(namedParameterJdbcTemplate); super(namedParameterJdbcTemplate);
} }
@Overridable
protected abstract void doDelete(@Nonnull T entity); protected abstract void doDelete(@Nonnull T entity);
protected abstract T doFindById(@Nonnull ID id); @Overridable
protected abstract Optional<T> doFindById(@Nonnull ID id);
@Overridable
protected abstract T doInsert(@Nonnull T entity); protected abstract T doInsert(@Nonnull T entity);
@Overridable
protected abstract T doUpdate(@Nonnull T entity); protected abstract T doUpdate(@Nonnull T entity);
@Override @Override
public final void delete(T entity) { public final void delete(T entity) {
if (entity == null) { Preconditions.checkArgument(entity != null, "Cannot delete null.");
throw new IllegalArgumentException("Cannot delete null.");
}
doDelete(entity); doDelete(entity);
} }
@Override @Override
public final T find(ID id) { public final Optional<T> find(ID id) {
if (id == null) { Preconditions.checkArgument(id != null, "Id cannot be null.");
throw new IllegalArgumentException("Id cannot be null.");
}
return doFindById(id); return doFindById(id);
} }
@Override @Override
public final T save(T entity) { public final T save(T entity) {
if (entity == null) { Preconditions.checkArgument(entity != null, "Cannot save null.");
throw new IllegalArgumentException("Cannot save null.");
}
return entity.getId().isPresent() ? doUpdate(entity) : doInsert(entity); return entity.getId().isPresent() ? doUpdate(entity) : doInsert(entity);
} }

View File

@@ -0,0 +1,173 @@
package xyz.zhouxy.plusone.jdbc;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.function.Function;
import java.util.stream.Stream;
import javax.annotation.Nonnull;
import org.springframework.jdbc.core.ResultSetExtractor;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.jdbc.core.namedparam.SqlParameterSource;
import org.springframework.util.CollectionUtils;
import xyz.zhouxy.plusone.commons.util.Numbers;
import xyz.zhouxy.plusone.exception.DataOperationResultException;
public abstract class PlusoneJdbcDaoSupport {
protected final NamedParameterJdbcTemplate jdbc;
protected PlusoneJdbcDaoSupport(@Nonnull NamedParameterJdbcTemplate namedParameterJdbcTemplate) {
this.jdbc = namedParameterJdbcTemplate;
}
protected final <T> T queryForObject(String sql, ResultSetExtractor<T> resultSetExtractor) {
return this.jdbc.query(sql, resultSetExtractor);
}
protected final <T> T queryForObject(String sql, SqlParameterSource paramSource,
ResultSetExtractor<T> resultSetExtractor) {
return this.jdbc.query(sql, paramSource, resultSetExtractor);
}
protected final <T> T queryForObject(String sql, String paramName, Object value,
ResultSetExtractor<T> resultSetExtractor) {
return this.jdbc.query(sql, new MapSqlParameterSource(paramName, value), resultSetExtractor);
}
protected final <T> List<T> queryForList(String sql, RowMapper<T> rowMapper) {
return this.jdbc.query(sql, rowMapper);
}
protected final <T> List<T> queryForList(String sql, SqlParameterSource parameterSource, RowMapper<T> rowMapper) {
return this.jdbc.query(sql, parameterSource, rowMapper);
}
protected final <T> List<T> queryForList(String sql, String paramName, Object value, RowMapper<T> rowMapper) {
return this.jdbc.query(sql, new MapSqlParameterSource(paramName, value), rowMapper);
}
protected final <T> Stream<T> queryForStream(String sql, SqlParameterSource parameterSource,
RowMapper<T> rowMapper) {
return this.jdbc.queryForStream(sql, parameterSource, rowMapper);
}
protected final <T> Stream<T> queryForStream(String sql, String paramName, Object value, RowMapper<T> rowMapper) {
return this.jdbc.queryForStream(sql, new MapSqlParameterSource(paramName, value), rowMapper);
}
protected final <T> Stream<T> queryForStream(String sql, SqlParameterSource parameterSource, Class<T> elementType) {
return this.jdbc.queryForList(sql, parameterSource, elementType).stream();
}
protected final <T> Stream<T> queryForStream(String sql, String paramName, Object value, Class<T> elementType) {
return queryForStream(sql, new MapSqlParameterSource(paramName, value), elementType);
}
protected final boolean queryForBool(String sql, SqlParameterSource parameterSource) {
Boolean result = this.jdbc.queryForObject(sql, parameterSource, Boolean.TYPE);
return Boolean.TRUE.equals(result);
}
protected final boolean queryForBool(String sql, String paramName, Object value) {
return queryForBool(sql, new MapSqlParameterSource(paramName, value));
}
protected final int update(String sql, SqlParameterSource parameterSource) {
return this.jdbc.update(sql, parameterSource);
}
protected final int update(String sql, String paramName, Object value) {
return update(sql, new MapSqlParameterSource(paramName, value));
}
protected final long batchUpdate(String sql, SqlParameterSource[] batchArgs) {
int[] i = this.jdbc.batchUpdate(sql, batchArgs);
return Numbers.sum(i);
}
protected final <T> long batchUpdate(String sql, Stream<T> c,
@Nonnull Function<T, SqlParameterSource> paramSourceBuilder) {
int[] i = this.jdbc.batchUpdate(sql, buildSqlParameterSourceArray(c, paramSourceBuilder));
return Numbers.sum(i);
}
protected final <T> long batchUpdate(String sql, Collection<T> c,
@Nonnull Function<T, SqlParameterSource> paramSourceBuilder) {
int[] i = this.jdbc.batchUpdate(sql, buildSqlParameterSourceArray(c, paramSourceBuilder));
return Numbers.sum(i);
}
protected static <T> void assertResultEquals(T result, T expectedValue) {
if (!Objects.equals(result, expectedValue)) {
throw new DataOperationResultException();
}
}
protected static <T> void assertResultEquals(T result, T expectedValue, Function<T, String> errMsg) {
if (!Objects.equals(result, expectedValue)) {
throw new DataOperationResultException(errMsg.apply(result));
}
}
protected static <T> void assertResultEquals(T result, T expectedValue, String msgTemplate, Object... args) {
if (!Objects.equals(result, expectedValue)) {
throw new DataOperationResultException(String.format(msgTemplate, args));
}
}
protected static <T, E extends Throwable> void assertResultEqualsOrThrow(T result, T expectedValue,
Function<T, E> e) throws E {
if (!Objects.equals(result, expectedValue)) {
throw e.apply(result);
}
}
protected static void assertUpdateOneRow(int result) {
assertResultEquals(result, 1);
}
protected static void assertUpdateOneRow(int result, Function<Integer, String> errMsg) {
assertResultEquals(result, 1, errMsg);
}
protected static void assertUpdateOneRow(int result, String msgTemplate, Object... args) {
assertResultEquals(result, 1, msgTemplate, args);
}
protected static <E extends Throwable> void assertUpdateOneRowOrThrow(int result, Function<Integer, E> e)
throws E {
assertResultEqualsOrThrow(result, 1, e);
}
protected static <T> SqlParameterSource[] buildSqlParameterSourceArray(
T[] c,
@Nonnull Function<T, SqlParameterSource> paramSourceBuilder) {
if (c == null || c.length == 0) {
return new SqlParameterSource[] {};
}
return buildSqlParameterSourceArray(Arrays.stream(c), paramSourceBuilder);
}
protected static <T> SqlParameterSource[] buildSqlParameterSourceArray(
Collection<T> c,
@Nonnull Function<T, SqlParameterSource> paramSourceBuilder) {
if (CollectionUtils.isEmpty(c)) {
return new SqlParameterSource[] {};
}
return buildSqlParameterSourceArray(c.stream(), paramSourceBuilder);
}
protected static <T> SqlParameterSource[] buildSqlParameterSourceArray(
Stream<T> stream,
@Nonnull Function<T, SqlParameterSource> paramSourceBuilder) {
Objects.requireNonNull(stream);
Objects.requireNonNull(paramSourceBuilder);
return stream.map(paramSourceBuilder).toArray(SqlParameterSource[]::new);
}
}

View File

@@ -8,7 +8,7 @@ import java.sql.SQLException;
* *
* <p> * <p>
* 通过在 {@link #map(ResultSet)} 中配置 {@link ResultSet} 到对象的映射, * 通过在 {@link #map(ResultSet)} 中配置 {@link ResultSet} 到对象的映射,
* 可将 {@link #rowMapper(ResultSet, int)} 的方法用, * 可将 {@link #rowMapper(ResultSet, int)} 的方法用,
* 直接当成 {@link org.springframework.jdbc.core.RowMapper} 对象传给 * 直接当成 {@link org.springframework.jdbc.core.RowMapper} 对象传给
* {@link org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate} * {@link org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate}
* 的查询方法, * 的查询方法,

View File

@@ -1,6 +1,5 @@
package xyz.zhouxy.plusone.mail; package xyz.zhouxy.plusone.mail;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
@@ -15,11 +14,10 @@ import org.springframework.mail.javamail.JavaMailSender;
@Configuration @Configuration
@EnableConfigurationProperties(PlusoneMailProperties.class) @EnableConfigurationProperties(PlusoneMailProperties.class)
@ConditionalOnClass(MailService.class) @ConditionalOnClass(MailService.class)
@EnableAutoConfiguration
public class PlusoneMailAutoConfiguration { public class PlusoneMailAutoConfiguration {
@Bean @Bean
public MailService mailService(JavaMailSender mailSender, PlusoneMailProperties mailProperties) { MailService mailService(JavaMailSender mailSender, PlusoneMailProperties mailProperties) {
MailMessageFactory mailMessageFactory = new MailMessageFactory(mailProperties); MailMessageFactory mailMessageFactory = new MailMessageFactory(mailProperties);
return new SimpleMailService(mailSender, mailMessageFactory); return new SimpleMailService(mailSender, mailMessageFactory);
} }

View File

@@ -0,0 +1,92 @@
package xyz.zhouxy.plusone.oss;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.List;
import java.util.Objects;
import org.csource.common.MyException;
import org.csource.fastdfs.ClientGlobal;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import xyz.zhouxy.plusone.oss.FastDFSProperties.ConnectionPool;
@Configuration
@EnableConfigurationProperties(FastDFSProperties.class)
@ConditionalOnClass(FastDFSUtil.class)
public class FastDFSAutoConfig {
@Bean
@SuppressWarnings("all")
FastDFSUtil fastDFSUtil(FastDFSProperties props) throws IOException, FastDFSException {
List<String> trackerServerStrList = props.getTrackerServers();
if (CollectionUtils.isEmpty(trackerServerStrList)) {
throw new FastDFSException(
String.format("configure item %s is required - ", ClientGlobal.PROP_KEY_TRACKER_SERVERS));
}
try {
InetSocketAddress[] trackerServers = trackerServerStrList.stream()
.map(trackerServer -> {
String[] hostPort = trackerServer.trim().split(":");
String host = hostPort[0].trim();
int port = Integer.parseInt(hostPort[1].trim());
return new InetSocketAddress(host, port);
})
.toArray(InetSocketAddress[]::new);
ClientGlobal.initByTrackers(trackerServers);
var connectTimeoutInSecondsConf = props.getConnectTimeoutInSeconds();
if (connectTimeoutInSecondsConf != null) {
ClientGlobal.setG_connect_timeout(connectTimeoutInSecondsConf * 1000);
}
var networkTimeoutInSecondsConf = props.getNetworkTimeoutInSeconds();
if (networkTimeoutInSecondsConf != null) {
ClientGlobal.setG_network_timeout(networkTimeoutInSecondsConf * 1000);
}
var charsetConf = props.getCharset();
if (StringUtils.hasText(charsetConf)) {
ClientGlobal.setG_charset(charsetConf);
}
var httpAntiStealTokenConf = props.getHttpAntiStealToken();
if (httpAntiStealTokenConf != null) {
ClientGlobal.setG_anti_steal_token(httpAntiStealTokenConf);
}
var httpSecretKeyConf = props.getHttpSecretKey();
if (StringUtils.hasText(httpSecretKeyConf)) {
ClientGlobal.setG_secret_key(httpSecretKeyConf);
}
var httpTrackerHttpPortConf = props.getHttpTrackerHttpPort();
if (httpTrackerHttpPortConf != null) {
ClientGlobal.setG_tracker_http_port(httpTrackerHttpPortConf);
}
ConnectionPool connectionPool = props.getConnectionPool();
var poolEnabled = Objects.nonNull(connectionPool)
&& Boolean.TRUE.equals(connectionPool.getEnabled());
if (poolEnabled) {
var poolMaxCountPerEntry = connectionPool.getMaxCountPerEntry();
if (poolMaxCountPerEntry != null) {
ClientGlobal.g_connection_pool_max_count_per_entry = poolMaxCountPerEntry;
}
var poolMaxIdleTime = connectionPool.getMaxIdleTime();
if (poolMaxIdleTime != null) {
ClientGlobal.g_connection_pool_max_idle_time = poolMaxIdleTime * 1000;
}
var poolMaxWaitTimeInMS = connectionPool.getMaxWaitTimeInMs();
if (poolMaxWaitTimeInMS != null) {
ClientGlobal.g_connection_pool_max_wait_time_in_ms = poolMaxWaitTimeInMS;
}
}
return new FastDFSUtil();
} catch (MyException e) {
throw new FastDFSException(e);
}
}
}

View File

@@ -0,0 +1,29 @@
package xyz.zhouxy.plusone.oss;
/**
* 封装 FastDFS 的 {@link org.csource.common.MyException}
*
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
*/
public class FastDFSException extends Exception {
private static final long serialVersionUID = 7871031982887742468L;
public FastDFSException() {
}
public FastDFSException(String message) {
super(message);
}
public FastDFSException(Throwable cause) {
super(cause);
}
public FastDFSException(String message, Throwable cause) {
super(message, cause);
}
public FastDFSException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}

View File

@@ -0,0 +1,121 @@
package xyz.zhouxy.plusone.oss;
import java.util.List;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties("fastdfs")
public class FastDFSProperties {
private Integer connectTimeoutInSeconds;
private Integer networkTimeoutInSeconds;
private String charset;
private Boolean httpAntiStealToken;
private String httpSecretKey;
private Integer httpTrackerHttpPort;
private List<String> trackerServers;
private ConnectionPool connectionPool;
public Integer getConnectTimeoutInSeconds() {
return connectTimeoutInSeconds;
}
public void setConnectTimeoutInSeconds(Integer connectTimeoutInSeconds) {
this.connectTimeoutInSeconds = connectTimeoutInSeconds;
}
public Integer getNetworkTimeoutInSeconds() {
return networkTimeoutInSeconds;
}
public void setNetworkTimeoutInSeconds(Integer networkTimeoutInSeconds) {
this.networkTimeoutInSeconds = networkTimeoutInSeconds;
}
public String getCharset() {
return charset;
}
public void setCharset(String charset) {
this.charset = charset;
}
public Boolean getHttpAntiStealToken() {
return httpAntiStealToken;
}
public void setHttpAntiStealToken(Boolean httpAntiStealToken) {
this.httpAntiStealToken = httpAntiStealToken;
}
public String getHttpSecretKey() {
return httpSecretKey;
}
public void setHttpSecretKey(String httpSecretKey) {
this.httpSecretKey = httpSecretKey;
}
public Integer getHttpTrackerHttpPort() {
return httpTrackerHttpPort;
}
public void setHttpTrackerHttpPort(Integer httpTrackerHttpPort) {
this.httpTrackerHttpPort = httpTrackerHttpPort;
}
public List<String> getTrackerServers() {
return trackerServers;
}
public void setTrackerServers(List<String> trackerServers) {
this.trackerServers = trackerServers;
}
public ConnectionPool getConnectionPool() {
return connectionPool;
}
public void setConnectionPool(ConnectionPool connectionPool) {
this.connectionPool = connectionPool;
}
public static class ConnectionPool {
private Boolean enabled;
private Integer maxCountPerEntry;
private Integer maxIdleTime;
private Integer maxWaitTimeInMs;
public Boolean getEnabled() {
return enabled;
}
public void setEnabled(Boolean enabled) {
this.enabled = enabled;
}
public Integer getMaxCountPerEntry() {
return maxCountPerEntry;
}
public void setMaxCountPerEntry(Integer maxCountPerEntry) {
this.maxCountPerEntry = maxCountPerEntry;
}
public Integer getMaxIdleTime() {
return maxIdleTime;
}
public void setMaxIdleTime(Integer maxIdleTime) {
this.maxIdleTime = maxIdleTime;
}
public Integer getMaxWaitTimeInMs() {
return maxWaitTimeInMs;
}
public void setMaxWaitTimeInMs(Integer maxWaitTimeInMs) {
this.maxWaitTimeInMs = maxWaitTimeInMs;
}
}
}

View File

@@ -0,0 +1,193 @@
package xyz.zhouxy.plusone.oss;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.Objects;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import org.csource.common.MyException;
import org.csource.common.NameValuePair;
import org.csource.fastdfs.FileInfo;
import org.csource.fastdfs.StorageClient;
import org.csource.fastdfs.StorageServer;
import org.csource.fastdfs.TrackerClient;
import org.csource.fastdfs.TrackerServer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.io.Files;
import lombok.Getter;
import xyz.zhouxy.plusone.commons.annotation.StaticFactoryMethod;
import xyz.zhouxy.plusone.exception.SysException;
public class FastDFSUtil {
private final TrackerServer trackerServer;
private final StorageServer storageServer;
private static final Logger logger = LoggerFactory.getLogger(FastDFSUtil.class);
FastDFSUtil() throws IOException, MyException {
TrackerClient trackerClient = new TrackerClient();
this.trackerServer = trackerClient.getTrackerServer();
this.storageServer = trackerClient.getStoreStorage(trackerServer);
}
/**
* 上传文件到 Fast DFS失败将抛异常
*
* @param file 文件信息
* @return 2 elements string array if success:<br>
* <ul>
* <li>results[0]: the group name to store the file</li>
* </ul>
* <ul>
* <li>results[1]: the new created filename</li>
* </ul>
* return null if fail
* @throws FastDFSException
*/
public String[] upload(FastDFSFile file) throws FastDFSException {
logger.info("File Name: {}, File Length: {}", file.getFileName(), file.getContent().length);
NameValuePair[] metaList = new NameValuePair[1];
metaList[0] = new NameValuePair("author", file.getAuthor());
long startTime = System.currentTimeMillis();
StorageClient storageClient = null;
String[] uploadResults = null;
try {
storageClient = new StorageClient(this.trackerServer, this.storageServer);
uploadResults = storageClient.upload_file(file.getContent(), file.getExt(), metaList);
if (uploadResults == null) {
throw new FastDFSException("upload file fail, error code: " + storageClient.getErrorCode());
}
logger.info("Upload file successfully!!! group_name: {}, remoteFileName: {}, time used: {} ms",
uploadResults[0], uploadResults[1], System.currentTimeMillis() - startTime);
} catch (IOException e) {
throw new FastDFSException("IO Exception when uploadind the file:" + file.getFileName(), e);
} catch (MyException e) {
throw new FastDFSException(e);
}
return uploadResults;
}
public FileInfo getFile(String groupName, String remoteFileName) throws FastDFSException {
try {
StorageClient storageClient = new StorageClient(this.trackerServer, this.storageServer);
return storageClient.get_file_info(groupName, remoteFileName);
} catch (IOException e) {
throw new FastDFSException("IO Exception: Get File from Fast DFS failed", e);
} catch (Exception e) {
throw new FastDFSException("Non IO Exception: Get File from Fast DFS failed", e);
}
}
public InputStream downFile(String groupName, String remoteFileName) throws FastDFSException {
try {
StorageClient storageClient = new StorageClient(this.trackerServer, this.storageServer);
byte[] fileByte = storageClient.download_file(groupName, remoteFileName);
InputStream ins = new ByteArrayInputStream(fileByte);
return ins;
} catch (IOException e) {
throw new FastDFSException("IO Exception: Get File from Fast DFS failed", e);
} catch (Exception e) {
throw new FastDFSException("Non IO Exception: Get File from Fast DFS failed", e);
}
}
public void deleteFile(String groupName, String remoteFileName) throws FastDFSException {
StorageClient storageClient = new StorageClient(this.trackerServer, this.storageServer);
try {
int i = storageClient.delete_file(groupName, remoteFileName);
if (i == 0) {
logger.info("Delete file SUCCESSFULLY!!!");
} else {
throw new FastDFSException("Delete file failed, error code is: " + i);
}
} catch (IOException | MyException e) {
throw new FastDFSException(e);
}
}
public static final class FastDFSFile {
@Getter
private final String fileName;
private final byte[] content;
@Getter
private final String ext;
@Getter
private final String md5;
@Getter
private final String author;
private FastDFSFile(@Nonnull File file, @Nullable String author) throws IOException {
this.fileName = file.getName();
this.content = Files.toByteArray(file);
this.ext = Files.getFileExtension(fileName);
this.md5 = md5Hex(content);
this.author = author;
}
private FastDFSFile(@Nonnull String fileName, @Nonnull byte[] content, @Nullable String author) {
this.fileName = fileName;
this.content = content;
this.ext = Files.getFileExtension(fileName);
this.md5 = md5Hex(content);
this.author = author;
}
@Nonnull
@StaticFactoryMethod(FastDFSFile.class)
public static FastDFSFile of(@Nonnull File file) throws IOException {
return new FastDFSFile(file, null);
}
@Nonnull
@StaticFactoryMethod(FastDFSFile.class)
public static FastDFSFile of(@Nonnull File file, @Nullable String author) throws IOException {
return new FastDFSFile(file, author);
}
@Nonnull
@StaticFactoryMethod(FastDFSFile.class)
public static FastDFSFile of(@Nonnull String fileName, @Nonnull byte[] content) {
return new FastDFSFile(fileName, content, null);
}
@Nonnull
@StaticFactoryMethod(FastDFSFile.class)
public static FastDFSFile of(@Nonnull String fileName, @Nonnull byte[] content, @Nullable String author) {
return new FastDFSFile(fileName, content, author);
}
public byte[] getContent() {
return Arrays.copyOf(content, content.length);
}
@Nonnull
private static String md5Hex(byte[] data) {
try {
MessageDigest messageDigest = MessageDigest.getInstance("MD5");
messageDigest.update(data);
byte[] result = messageDigest.digest();
var sha512Hex = new BigInteger(1, result).toString(16);
return Objects.requireNonNull(sha512Hex);
} catch (NoSuchAlgorithmException e) {
throw SysException.of(e);
}
}
}
}

View File

@@ -11,17 +11,12 @@ import org.springframework.context.annotation.Configuration;
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a> * @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
*/ */
@Configuration @Configuration
@EnableConfigurationProperties(value = { @EnableConfigurationProperties(SmsProperties.class)
SmsProperties.class,
SmsCredentialProperties.class,
SmsClientProperties.class,
SmsHttpProperties.class,
SmsProxyProperties.class})
@ConditionalOnClass(SmsService.class) @ConditionalOnClass(SmsService.class)
public class PlusoneSmsAutoConfiguration { public class PlusoneSmsAutoConfiguration {
@Bean @Bean
public SmsService smsService(SmsProperties smsProperties) { SmsService smsService(SmsProperties smsProperties) {
return new TencentSmsServiceImpl(smsProperties); return new TencentSmsServiceImpl(smsProperties);
} }
} }

View File

@@ -4,14 +4,11 @@ import java.util.Map;
import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.ConfigurationProperties;
import lombok.Data;
/** /**
* SMS 相关参数 * SMS 相关参数
* *
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a> * @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
*/ */
@Data
@ConfigurationProperties("plusone.sms") @ConfigurationProperties("plusone.sms")
public class SmsProperties { public class SmsProperties {
private String region; private String region;
@@ -19,33 +16,138 @@ public class SmsProperties {
private SmsClientProperties client; private SmsClientProperties client;
private String appId; private String appId;
private Map<String, String> templates; private Map<String, String> templates;
}
@Data public String getRegion() {
@ConfigurationProperties("plusone.sms.credential") return region;
class SmsCredentialProperties { }
private String secretId;
private String secretKey;
}
@Data public void setRegion(String region) {
@ConfigurationProperties("plusone.sms.client") this.region = region;
class SmsClientProperties { }
private String signMethod;
private SmsHttpProperties http;
}
@Data public SmsCredentialProperties getCredential() {
@ConfigurationProperties("plusone.sms.client.http") return credential;
class SmsHttpProperties { }
private SmsProxyProperties proxy;
private String reqMethod; public void setCredential(SmsCredentialProperties credential) {
private Integer connTimeout; this.credential = credential;
} }
public SmsClientProperties getClient() {
return client;
}
public void setClient(SmsClientProperties client) {
this.client = client;
}
public String getAppId() {
return appId;
}
public void setAppId(String appId) {
this.appId = appId;
}
public Map<String, String> getTemplates() {
return templates;
}
public void setTemplates(Map<String, String> templates) {
this.templates = templates;
}
public static class SmsCredentialProperties {
private String secretId;
private String secretKey;
public String getSecretId() {
return secretId;
}
public void setSecretId(String secretId) {
this.secretId = secretId;
}
public String getSecretKey() {
return secretKey;
}
public void setSecretKey(String secretKey) {
this.secretKey = secretKey;
}
}
public static class SmsClientProperties {
private String signMethod;
private SmsHttpProperties http;
public String getSignMethod() {
return signMethod;
}
public void setSignMethod(String signMethod) {
this.signMethod = signMethod;
}
public SmsHttpProperties getHttp() {
return http;
}
public void setHttp(SmsHttpProperties http) {
this.http = http;
}
}
public static class SmsHttpProperties {
private SmsProxyProperties proxy;
private String reqMethod;
private Integer connTimeout;
public SmsProxyProperties getProxy() {
return proxy;
}
public void setProxy(SmsProxyProperties proxy) {
this.proxy = proxy;
}
public String getReqMethod() {
return reqMethod;
}
public void setReqMethod(String reqMethod) {
this.reqMethod = reqMethod;
}
public Integer getConnTimeout() {
return connTimeout;
}
public void setConnTimeout(Integer connTimeout) {
this.connTimeout = connTimeout;
}
}
public static class SmsProxyProperties {
private String host;
private Integer port;
public String getHost() {
return host;
}
public void setHost(String host) {
this.host = host;
}
public Integer getPort() {
return port;
}
public void setPort(Integer port) {
this.port = port;
}
}
@Data
@ConfigurationProperties("plusone.sms.client.http.proxy")
class SmsProxyProperties {
private String host;
private Integer port;
} }

View File

@@ -1,5 +1,7 @@
package xyz.zhouxy.plusone.sms; package xyz.zhouxy.plusone.sms;
import org.springframework.stereotype.Service;
import com.tencentcloudapi.common.Credential; import com.tencentcloudapi.common.Credential;
import com.tencentcloudapi.common.exception.TencentCloudSDKException; import com.tencentcloudapi.common.exception.TencentCloudSDKException;
import com.tencentcloudapi.common.profile.ClientProfile; import com.tencentcloudapi.common.profile.ClientProfile;
@@ -8,11 +10,11 @@ import com.tencentcloudapi.sms.v20210111.SmsClient;
import com.tencentcloudapi.sms.v20210111.models.SendSmsRequest; import com.tencentcloudapi.sms.v20210111.models.SendSmsRequest;
import com.tencentcloudapi.sms.v20210111.models.SendSmsResponse; import com.tencentcloudapi.sms.v20210111.models.SendSmsResponse;
import org.springframework.stereotype.Service;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import xyz.zhouxy.plusone.constant.ErrorCodeConsts; import xyz.zhouxy.plusone.exception.SysException;
import xyz.zhouxy.plusone.exception.PlusoneException; import xyz.zhouxy.plusone.sms.SmsProperties.SmsCredentialProperties;
import xyz.zhouxy.plusone.sms.SmsProperties.SmsHttpProperties;
import xyz.zhouxy.plusone.sms.SmsProperties.SmsProxyProperties;
/** /**
* 使用腾讯 SMS 服务 * 使用腾讯 SMS 服务
@@ -66,7 +68,7 @@ public class TencentSmsServiceImpl implements SmsService {
} catch (TencentCloudSDKException e) { } catch (TencentCloudSDKException e) {
log.error(e.getMessage(), e); log.error(e.getMessage(), e);
throw new PlusoneException(ErrorCodeConsts.DEFAULT_ERROR_CODE, e); throw SysException.of(e);
} }
} }

View File

@@ -4,15 +4,13 @@ import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
public class SpringContextHolder { public enum SpringContextHolder {
INSTANCE
;
private ApplicationContext context; private ApplicationContext context;
private static final SpringContextHolder INSTANCE = new SpringContextHolder();
private SpringContextHolder() {
}
public static ApplicationContext getContext() { public static ApplicationContext getContext() {
return INSTANCE.context; return INSTANCE.context;
} }

View File

@@ -1,34 +0,0 @@
package xyz.zhouxy.plusone.sql;
import java.util.Objects;
import org.apache.ibatis.jdbc.AbstractSQL;
/**
* 扩展 MyBatis 中的 SQL 语句构造器
*
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
*/
public class SQL extends AbstractSQL<SQL> {
public SQL SET_IF(boolean condition, String sets) {
return condition ? SET(sets) : getSelf();
}
public SQL SET_IF_NOT_NULL(Object param, String sets) {
return Objects.nonNull(param) ? SET(sets) : getSelf();
}
public SQL WHERE_IF(boolean condition, String sqlCondition) {
return condition ? WHERE(sqlCondition) : getSelf();
}
public SQL WHERE_IF_NOT_NULL(Object param, String sqlCondition) {
return Objects.nonNull(param) ? WHERE(sqlCondition) : getSelf();
}
@Override
public SQL getSelf() {
return this;
}
}

View File

@@ -0,0 +1,16 @@
package xyz.zhouxy.plusone.validator;
import javax.annotation.Nonnull;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import xyz.zhouxy.plusone.commons.exception.handler.BaseExceptionHandler;
@RestControllerAdvice
public class InvalidInputExceptionHandler extends BaseExceptionHandler {
public InvalidInputExceptionHandler(@Nonnull ExceptionInfoHolder exceptionInfoHolder) {
super(exceptionInfoHolder);
set(InvalidInputException.class, InvalidInputException.ERROR_CODE, "无效的用户输入");
}
}

View File

@@ -12,6 +12,7 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration @Configuration
public class WebCorsConfig implements WebMvcConfigurer { public class WebCorsConfig implements WebMvcConfigurer {
// TODO 不放行全部,通过配置文件配置允许访问的域名
@Override @Override
public void addCorsMappings(CorsRegistry registry) { public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**") registry.addMapping("/**")

View File

@@ -0,0 +1,4 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
xyz.zhouxy.plusone.sms.PlusoneSmsAutoConfiguration,\
xyz.zhouxy.plusone.mail.PlusoneMailAutoConfiguration,\
xyz.zhouxy.plusone.oss.FastDFSAutoConfig

View File

@@ -1,14 +1,16 @@
package xyz.zhouxy.plusone.validatortest; package xyz.zhouxy.plusone.validatortest;
import java.util.regex.Pattern;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Data; import lombok.Data;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import xyz.zhouxy.plusone.constant.RegexConsts; import xyz.zhouxy.plusone.commons.constant.PatternConsts;
import xyz.zhouxy.plusone.validator.validator2.BaseValidator2; import xyz.zhouxy.plusone.validator.BaseValidator;
class BaseValidator2Test { class BaseValidatorTest {
@Test @Test
void testValid() { void testValid() {
@@ -27,17 +29,18 @@ class LoginCommand {
private boolean rememberMe; private boolean rememberMe;
} }
class LoginCommandValidator extends BaseValidator2<LoginCommand> { class LoginCommandValidator extends BaseValidator<LoginCommand> {
public static final LoginCommandValidator INSTANCE = new LoginCommandValidator(); public static final LoginCommandValidator INSTANCE = new LoginCommandValidator();
private LoginCommandValidator() { private LoginCommandValidator() {
ruleFor(LoginCommand::getAccount) ruleForString(LoginCommand::getAccount)
.notNull("邮箱地址不能为空") .notNull("邮箱地址不能为空")
.matchesOr(new String[] { RegexConsts.EMAIL, RegexConsts.MOBILE_PHONE }, value -> new RuntimeException('"' + value + "\" 不是邮箱地址或手机号")); .matchesOne(new Pattern[] { PatternConsts.EMAIL, PatternConsts.MOBILE_PHONE },
ruleFor(LoginCommand::getPwd) value -> new RuntimeException('"' + value + "\" 不是邮箱地址或手机号"));
ruleForString(LoginCommand::getPwd)
.notNull("密码不能为空") .notNull("密码不能为空")
.notEmpty("密码不能为空") .notEmpty("密码不能为空")
.matches(RegexConsts.PASSWORD, "密码格式错误"); .matches(PatternConsts.PASSWORD, "密码格式错误");
} }
} }

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <project xmlns="https://maven.apache.org/POM/4.0.0" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="https://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<artifactId>plusone</artifactId> <artifactId>plusone</artifactId>
@@ -7,9 +7,7 @@
<version>1.0.0-SNAPSHOT</version> <version>1.0.0-SNAPSHOT</version>
</parent> </parent>
<groupId>xyz.zhouxy</groupId>
<artifactId>plusone-basic</artifactId> <artifactId>plusone-basic</artifactId>
<version>1.0.0-SNAPSHOT</version>
<packaging>pom</packaging> <packaging>pom</packaging>

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <project xmlns="https://maven.apache.org/POM/4.0.0" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="https://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<artifactId>plusone</artifactId> <artifactId>plusone</artifactId>
@@ -7,18 +7,11 @@
<version>1.0.0-SNAPSHOT</version> <version>1.0.0-SNAPSHOT</version>
</parent> </parent>
<groupId>xyz.zhouxy</groupId>
<artifactId>plusone-start</artifactId> <artifactId>plusone-start</artifactId>
<version>1.0.0-SNAPSHOT</version>
<name>plusone-start</name> <name>plusone-start</name>
<description>参考 DDD 落地的脚手架</description> <description>参考 DDD 落地的脚手架</description>
<properties>
<spring-boot.version>2.7.6</spring-boot.version>
</properties>
<dependencies> <dependencies>
<dependency> <dependency>

View File

@@ -3,14 +3,10 @@ package xyz.zhouxy.plusone;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
import lombok.extern.slf4j.Slf4j;
@SpringBootApplication @SpringBootApplication
@Slf4j
public class PlusoneApplication { public class PlusoneApplication {
public static void main(String[] args) { public static void main(String[] args) {
log.debug("Plusone started!");
SpringApplication.run(PlusoneApplication.class, args); SpringApplication.run(PlusoneApplication.class, args);
} }

View File

@@ -1,12 +0,0 @@
package xyz.zhouxy.plusone;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class TestController {
@RequestMapping("/test")
public String test() throws Exception {
throw new Exception();
}
}

View File

@@ -1,34 +1,32 @@
{ {"properties": [
"properties": [ {
{ "name": "plusone.application.name",
"name": "plusone.application.name", "type": "java.lang.String",
"type": "java.lang.String", "description": "A description for 'plusone.application.name'"
"description": "A description for 'plusone.application.name'" },
}, {
{ "name": "plusone.server.port",
"name": "plusone.server.port", "type": "java.lang.Integer",
"type": "java.lang.Integer", "description": "A description for 'plusone.server.port'"
"description": "A description for 'plusone.server.port'" },
}, {
{ "name": "plusone.debug",
"name": "plusone.debug", "type": "java.lang.Boolean",
"type": "java.lang.Boolean", "description": "A description for 'plusone.debug'"
"description": "A description for 'plusone.debug'" },
}, {
{ "name": "plusone.mail.host",
"name": "plusone.mail.host", "type": "java.lang.String",
"type": "java.lang.String", "description": "A description for 'plusone.mail.host'"
"description": "A description for 'plusone.mail.host'" },
}, {
{ "name": "plusone.mail.password",
"name": "plusone.mail.password", "type": "java.lang.String",
"type": "java.lang.String", "description": "A description for 'plusone.mail.password'"
"description": "A description for 'plusone.mail.password'" },
}, {
{ "name": "plusone.exception.handle-all-exception",
"name": "plusone.exception.handle-all-exception", "type": "java.lang.Boolean",
"type": "java.lang.Boolean", "description": "A description for 'plusone.exception.handle-all-exception'"
"description": "A description for 'plusone.exception.handle-all-exception'" }
} ]}
]
}

View File

@@ -0,0 +1,78 @@
spring:
# redis配置
redis:
# Redis数据库索引默认为0
database: 1
# Redis服务器地址
host: 127.0.0.1
# Redis服务器连接端口
port: 6379
# Redis服务器连接密码默认为空
# password:
# 连接超时时间
timeout: 10s
lettuce:
pool:
# 连接池最大连接数
max-active: 200
# 连接池最大阻塞等待时间(使用负值表示没有限制)
max-wait: -1ms
# 连接池中的最大空闲连接
max-idle: 10
# 连接池中的最小空闲连接
min-idle: 0
# 数据库
datasource:
url: jdbc:postgresql://localhost:5432/plusone
username: plusone
password: XXXXXXXXXXXXXXXX
plusone:
application:
name: plusone
server:
port: 8108
debug: true
# 短信发送相关参数
sms:
region: ap-guangzhou
credential:
secret-id: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
secret-key: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
client:
# SDK 默认用 TC3-HMAC-SHA256 进行签名,非必要请不要修改这个字段
sign-method: HmacSHA256
http:
# proxy:
# host: xxx
# port: xxx
req-method: POST
conn-timeout: 60
app-id: 1111111111
templates:
'[code]': 0000000
# 邮件发送相关参数
mail:
host: smtp.163.com
#设置邮件发送者
from: example@163.com
password: XXXXXXXXXXXXXXXX
# 异常拦截机制是否拦截所有异常
exception:
handle-all-exception: false
fastdfs:
connect_timeout_in_seconds: 5
network_timeout_in_seconds: 30
charset: UTF-8
http_anti_steal_token: false
http_secret_key: FastDFS1234567890
http_tracker_http_port: 80
tracker_servers: 10.0.11.201:22122,10.0.11.202:22122,10.0.11.203:22122
connection_pool:
enabled: true
max_count_per_entry: 500
max_idle_time: 3600
max_wait_time_in_ms: 1000

View File

@@ -1,79 +1,16 @@
spring:
profiles:
active: public
plusone: plusone:
application:
name: plusone
server:
port: 8108
debug: true
# 短信发送相关参数
sms:
region: ap-guangzhou
credential:
secret-id: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
secret-key: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
client:
# SDK 默认用 TC3-HMAC-SHA256 进行签名,非必要请不要修改这个字段
sign-method: HmacSHA256
http:
# proxy:
# host: xxx
# port: xxx
req-method: POST
conn-timeout: 60
app-id: 1111111111
templates:
code: 0000000
# 邮件发送相关参数 # 邮件发送相关参数
mail: mail:
host: smtp.163.com
#设置邮件发送者
from: example@163.com
password: XXXXXXXXXXXXXXXX
subject: subject:
code: Plusone '[code]': Plusone
template: template:
code: 【Plusone】验证码%s10分钟内有效请勿泄露。 '[code]': 【Plusone】验证码%s10分钟内有效请勿泄露。
# 异常拦截机制是否拦截所有异常
exception:
handle-all-exception: false
spring:
# redis配置
redis:
# Redis数据库索引默认为0
database: 1
# Redis服务器地址
host: 127.0.0.1
# Redis服务器连接端口
port: 6379
# Redis服务器连接密码默认为空
# password:
# 连接超时时间
timeout: 10s
lettuce:
pool:
# 连接池最大连接数
max-active: 200
# 连接池最大阻塞等待时间(使用负值表示没有限制)
max-wait: -1ms
# 连接池中的最大空闲连接
max-idle: 10
# 连接池中的最小空闲连接
min-idle: 0
# 数据库
datasource:
url: jdbc:postgresql://localhost:5432/plusone
username: plusone
password: XXXXXXXXXXXXXXXX
# 日志配置 # 日志配置
logging: logging:
file:
name: ${user.home}/logs/${plusone.application.name}.log
level: level:
root: INFO root: info
xyz.zhouxy.plusone: DEBUG '[xyz.zhouxy.plusone]': debug
org.springframework.jdbc.core: DEBUG
xyz.zhouxy.plusone.system.application.query: DEBUG

View File

@@ -10,23 +10,23 @@
</encoder> </encoder>
</appender> </appender>
<!-- <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<encoder> <encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %5level ${PID:- } - [%15.15t] %-50.50logger : %msg%n</pattern> <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %5level ${PID:- } - [%15.15t] %-50.50logger : %msg%n</pattern>
<charset>utf-8</charset> <charset>utf-8</charset>
</encoder> </encoder>
<file>log/output.log</file> <file>log/plusone.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy"> <rollingPolicy class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy">
<fileNamePattern>log/output.log.%i</fileNamePattern> <fileNamePattern>log/plusone.log.%i</fileNamePattern>
</rollingPolicy> </rollingPolicy>
<triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy"> <triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
<MaxFileSize>20MB</MaxFileSize> <MaxFileSize>20MB</MaxFileSize>
</triggeringPolicy> </triggeringPolicy>
</appender> --> </appender>
<root level="INFO"> <root level="INFO">
<appender-ref ref="CONSOLE" /> <appender-ref ref="CONSOLE" />
<!-- <appender-ref ref="FILE" /> --> <appender-ref ref="FILE" />
</root> </root>
</configuration> </configuration>

View File

@@ -0,0 +1,33 @@
package xyz.zhouxy.plusone;
import java.util.Collections;
import java.util.List;
import javax.annotation.Resource;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import lombok.extern.slf4j.Slf4j;
import xyz.zhouxy.plusone.system.application.query.AccountQueries;
import xyz.zhouxy.plusone.system.application.query.params.AccountQueryParams;
import xyz.zhouxy.plusone.system.application.query.result.AccountOverview;
@Slf4j
@SpringBootTest(classes = PlusoneApplication.class)
class AccountQueriesTests {
@Resource
AccountQueries accountQueries;
@Test
void testQueryPage() {
AccountQueryParams queryParams = new AccountQueryParams();
// queryParams.setOrderBy(List.of("email", "mobile_phone", "id"));
queryParams.setSize(20);
queryParams.setPageNum(1L);
queryParams.setOrderBy(Collections.emptyList());
List<AccountOverview> l = accountQueries.queryAccountOverview(queryParams);
log.info("l: {}", l);
}
}

View File

@@ -0,0 +1,29 @@
package xyz.zhouxy.plusone;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import javax.annotation.Resource;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import lombok.extern.slf4j.Slf4j;
import xyz.zhouxy.plusone.oss.FastDFSException;
import xyz.zhouxy.plusone.oss.FastDFSUtil;
import xyz.zhouxy.plusone.oss.FastDFSUtil.FastDFSFile;
@SpringBootTest(classes = PlusoneApplication.class)
@Slf4j
class FastDFSTests {
@Resource
FastDFSUtil fastDFSUtil;
@Test
void testOSS() throws FileNotFoundException, IOException, FastDFSException {
String[] upload = fastDFSUtil.upload(FastDFSFile.of(new File("D:\\ZhouXY\\Desktop\\666.png")));
log.info(String.join("/", upload));
}
}

View File

@@ -5,14 +5,14 @@ import java.io.ObjectStreamClass;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import xyz.zhouxy.plusone.exception.PlusoneException; import xyz.zhouxy.plusone.exception.*;
@Slf4j @Slf4j
class SerialTests { class SerialTests {
@Test @Test
void testSerialVersionUID() { void testSerialVersionUID() {
var cl = PlusoneException.class; var cl = SysException.class;
var c = ObjectStreamClass.lookup(cl); var c = ObjectStreamClass.lookup(cl);
var uid = c.getSerialVersionUID(); var uid = c.getSerialVersionUID();
log.info("\n @java.io.Serial" + log.info("\n @java.io.Serial" +

View File

@@ -1,5 +1,7 @@
package xyz.zhouxy.plusone; package xyz.zhouxy.plusone;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import javax.annotation.Resource; import javax.annotation.Resource;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@@ -25,6 +27,7 @@ class TestAop {
command.setPrincipal("Code108"); command.setPrincipal("Code108");
command.setPassword("2333"); command.setPassword("2333");
command.setRememberMe(false); command.setRememberMe(false);
assertNotNull(service);
LoginInfoViewObject loginInfo = service.loginByPassword(command); LoginInfoViewObject loginInfo = service.loginByPassword(command);
System.err.println(loginInfo); System.err.println(loginInfo);
} }

View File

@@ -0,0 +1,16 @@
spring:
profiles:
active: secret
plusone:
# 邮件发送相关参数
mail:
subject:
'[code]': Plusone
template:
'[code]': 【Plusone】验证码%s10分钟内有效请勿泄露。
# 日志配置
logging:
level:
root: info
'[xyz.zhouxy.plusone]': debug

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <project xmlns="https://maven.apache.org/POM/4.0.0" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="https://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<artifactId>plusone-system</artifactId> <artifactId>plusone-system</artifactId>

View File

@@ -0,0 +1,54 @@
package xyz.zhouxy.plusone.system.application.common.exception;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
import xyz.zhouxy.plusone.exception.BizException;
@ResponseStatus(HttpStatus.BAD_REQUEST)
public class AccountLoginException extends BizException {
@java.io.Serial
private static final long serialVersionUID = -3040996790739138556L;
private static final String DEFAULT_ERR_CODE = "4030000";
private AccountLoginException() {
super(DEFAULT_ERR_CODE, "用户登录异常");
}
private AccountLoginException(String code, String message) {
super(code, message);
}
public static AccountLoginException accountNotExistException() {
return accountNotExistException("用户账户不存在");
}
public static AccountLoginException accountNotExistException(String msg) {
return new AccountLoginException("4030101", msg);
}
public static AccountLoginException otpErrorException() {
return otpErrorException("验证码错误");
}
public static AccountLoginException otpErrorException(String msg) {
return new AccountLoginException("4030501", msg);
}
public static AccountLoginException otpNotExistsException() {
return otpNotExistsException("验证码不存在或已过期");
}
public static AccountLoginException otpNotExistsException(String msg) {
return new AccountLoginException("4030502", msg);
}
public static AccountLoginException passwordErrorException() {
return passwordErrorException("用户密码错误");
}
public static AccountLoginException passwordErrorException(String msg) {
return new AccountLoginException("4030200", msg);
}
}

View File

@@ -0,0 +1,57 @@
package xyz.zhouxy.plusone.system.application.common.exception;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
import xyz.zhouxy.plusone.exception.BizException;
@ResponseStatus(HttpStatus.BAD_REQUEST)
public class AccountRegisterException extends BizException {
@java.io.Serial
private static final long serialVersionUID = 7580245181633370195L;
public AccountRegisterException() {
this("4020000", "用户注册错误");
}
public AccountRegisterException(String message) {
this("4020000", message);
}
public AccountRegisterException(Throwable cause) {
super("4020000", cause);
}
public AccountRegisterException(String code, String message) {
super(code, message);
}
public AccountRegisterException(String code, Throwable cause) {
super(code, cause);
}
public static AccountRegisterException emailOrMobilePhoneRequiredException() {
return new AccountRegisterException("4020300", "邮箱和手机号应至少绑定一个");
}
public static AccountRegisterException usernameAlreadyExists(String username) {
return new AccountRegisterException("4020400", String.format("用户名 %s 已存在", username));
}
public static AccountRegisterException emailAlreadyExists(String value) {
return new AccountRegisterException("4020500", String.format("邮箱 %s 已存在", value));
}
public static AccountRegisterException mobilePhoneAlreadyExists(String value) {
return new AccountRegisterException("4020600", String.format("手机号 %s 已存在", value));
}
public static AccountRegisterException codeErrorException() {
return new AccountRegisterException("4020701", "校验码错误");
}
public static AccountRegisterException codeNotExistsException() {
return new AccountRegisterException("4020702", "校验码不存在或已过期");
}
}

View File

@@ -1,4 +1,4 @@
package xyz.zhouxy.plusone.system.application.exception; package xyz.zhouxy.plusone.system.application.common.exception;
import xyz.zhouxy.plusone.validator.InvalidInputException; import xyz.zhouxy.plusone.validator.InvalidInputException;
@@ -7,11 +7,13 @@ public class UnsupportedMenuTypeException extends InvalidInputException {
@java.io.Serial @java.io.Serial
private static final long serialVersionUID = -769169844015637730L; private static final long serialVersionUID = -769169844015637730L;
public static final String ERROR_CODE = "4040209";
public UnsupportedMenuTypeException() { public UnsupportedMenuTypeException() {
this("不支持的菜单类型"); this("不支持的菜单类型");
} }
public UnsupportedMenuTypeException(String message) { public UnsupportedMenuTypeException(String message) {
super(message); super(ERROR_CODE, message);
} }
} }

View File

@@ -0,0 +1,32 @@
package xyz.zhouxy.plusone.system.application.common.exception;
import xyz.zhouxy.plusone.exception.BizException;
/**
* 不支持的 Principal 类型出现时抛出的异常
*
* @author <a href="https://zhouxy.xyz:3000/ZhouXY108">ZhouXY</a>
*/
public class UnsupportedPrincipalTypeException extends BizException {
private static final long serialVersionUID = 5207757290868470762L;
public static final String ERR_CODE = "4040201";
private static final String DEFAULT_ERROR_MSG = "不支持的 PrincipalType";
public UnsupportedPrincipalTypeException() {
super(ERR_CODE, DEFAULT_ERROR_MSG);
}
public UnsupportedPrincipalTypeException(String msg) {
super(ERR_CODE, msg);
}
public UnsupportedPrincipalTypeException(Throwable cause) {
super(ERR_CODE, cause);
}
public UnsupportedPrincipalTypeException(String msg, Throwable cause) {
super(ERR_CODE, msg, cause);
}
}

View File

@@ -0,0 +1,26 @@
package xyz.zhouxy.plusone.system.application.common.exception.handler;
import java.util.Objects;
import javax.annotation.Nonnull;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import xyz.zhouxy.plusone.commons.exception.handler.BaseExceptionHandler;
import xyz.zhouxy.plusone.commons.util.UnifiedResponse;
import xyz.zhouxy.plusone.system.application.common.exception.AccountLoginException;
@RestControllerAdvice
public class AccountLoginExceptionHandler extends BaseExceptionHandler {
public AccountLoginExceptionHandler(@Nonnull ExceptionInfoHolder exceptionInfoHolder) {
super(exceptionInfoHolder);
}
@ExceptionHandler({ AccountLoginException.class })
public ResponseEntity<UnifiedResponse> handleException(Exception e) {
return buildExceptionResponse(Objects.requireNonNull(e));
}
}

View File

@@ -1,4 +1,6 @@
package xyz.zhouxy.plusone.system.application.exception.handler; package xyz.zhouxy.plusone.system.application.common.exception.handler;
import javax.annotation.Nonnull;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
@@ -6,17 +8,16 @@ import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice; import org.springframework.web.bind.annotation.RestControllerAdvice;
import cn.dev33.satoken.exception.DisableServiceException; import cn.dev33.satoken.exception.DisableServiceException;
import cn.dev33.satoken.exception.SameTokenInvalidException;
import cn.dev33.satoken.exception.NotBasicAuthException; import cn.dev33.satoken.exception.NotBasicAuthException;
import cn.dev33.satoken.exception.NotLoginException; import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.exception.NotPermissionException; import cn.dev33.satoken.exception.NotPermissionException;
import cn.dev33.satoken.exception.NotRoleException; import cn.dev33.satoken.exception.NotRoleException;
import cn.dev33.satoken.exception.NotSafeException; import cn.dev33.satoken.exception.NotSafeException;
import cn.dev33.satoken.exception.SaTokenException; import cn.dev33.satoken.exception.SaTokenException;
import cn.dev33.satoken.exception.SameTokenInvalidException;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import xyz.zhouxy.plusone.constant.ErrorCodeConsts; import xyz.zhouxy.plusone.commons.exception.handler.BaseExceptionHandler;
import xyz.zhouxy.plusone.exception.handler.BaseExceptionHandler; import xyz.zhouxy.plusone.commons.util.UnifiedResponse;
import xyz.zhouxy.plusone.util.RestfulResult;
/** /**
* Sa-Token 异常处理器 * Sa-Token 异常处理器
@@ -27,8 +28,9 @@ import xyz.zhouxy.plusone.util.RestfulResult;
@Slf4j @Slf4j
public class SaTokenExceptionHandler extends BaseExceptionHandler { public class SaTokenExceptionHandler extends BaseExceptionHandler {
public SaTokenExceptionHandler(ExceptionInfoHolder exceptionInfoHolder) {
super(ErrorCodeConsts.DEFAULT_ERROR_CODE, exceptionInfoHolder); public SaTokenExceptionHandler(@Nonnull ExceptionInfoHolder exceptionInfoHolder) {
super(exceptionInfoHolder);
set(NotPermissionException.class, 4030103, "会话未能通过权限认证", HttpStatus.FORBIDDEN); set(NotPermissionException.class, 4030103, "会话未能通过权限认证", HttpStatus.FORBIDDEN);
set(NotRoleException.class, 4030103, "会话未能通过角色认证", HttpStatus.FORBIDDEN); set(NotRoleException.class, 4030103, "会话未能通过角色认证", HttpStatus.FORBIDDEN);
set(DisableServiceException.class, 4030202, "账号指定服务已被封禁", HttpStatus.FORBIDDEN); set(DisableServiceException.class, 4030202, "账号指定服务已被封禁", HttpStatus.FORBIDDEN);
@@ -37,7 +39,7 @@ public class SaTokenExceptionHandler extends BaseExceptionHandler {
set(NotSafeException.class, 4020300, "会话未能通过二级认证", HttpStatus.UNAUTHORIZED); set(NotSafeException.class, 4020300, "会话未能通过二级认证", HttpStatus.UNAUTHORIZED);
set(NotLoginException.class, set(NotLoginException.class,
4020400, 4020400,
e -> switch (((NotLoginException) e).getType()) { e -> switch (e.getType()) {
case NotLoginException.NOT_TOKEN -> "未提供 Token"; case NotLoginException.NOT_TOKEN -> "未提供 Token";
case NotLoginException.INVALID_TOKEN -> "Token 无效"; case NotLoginException.INVALID_TOKEN -> "Token 无效";
case NotLoginException.TOKEN_TIMEOUT -> "Token 已过期"; case NotLoginException.TOKEN_TIMEOUT -> "Token 已过期";
@@ -50,7 +52,7 @@ public class SaTokenExceptionHandler extends BaseExceptionHandler {
} }
@ExceptionHandler(SaTokenException.class) @ExceptionHandler(SaTokenException.class)
public ResponseEntity<RestfulResult> handleSaTokenException(SaTokenException e) { public ResponseEntity<UnifiedResponse> handleSaTokenException(SaTokenException e) {
log.error(e.getMessage(), e); log.error(e.getMessage(), e);
return buildExceptionResponse(e); return buildExceptionResponse(e);
} }

View File

@@ -0,0 +1,138 @@
package xyz.zhouxy.plusone.system.application.common.model;
import java.util.Map;
import java.util.Optional;
import java.util.OptionalInt;
import java.util.OptionalLong;
import java.util.OptionalDouble;
import java.util.concurrent.ConcurrentHashMap;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import xyz.zhouxy.plusone.exception.BizException;
public class AuthenticationInfo<T> {
/** token 值 */
private final String token;
/** 此 token 对应的LoginId未登录时不会构建此类的实例 */
private final T loginId;
/** 账号类型 */
private final String loginType;
/** 登录设备类型 */
private final String deviceType;
/** 自定义数据 */
@Nonnull
private final Map<String, Object> metadata;
private AuthenticationInfo(String token, T loginId, String loginType, String deviceType,
@Nonnull Map<String, Object> metadata) {
this.token = token;
this.loginId = loginId;
this.loginType = loginType;
this.deviceType = deviceType;
this.metadata = metadata;
}
public String getToken() {
return token;
}
public T getLoginId() {
return loginId;
}
public String getLoginType() {
return loginType;
}
public String getDeviceType() {
return deviceType;
}
private Object getObjFromMetadata(String key) {
if (this.metadata.containsKey(key)) {
return this.metadata.get(key);
}
throw BizException.of(String.format("不存在 key 为 \"%s\"的元数据。", key));
}
public OptionalDouble getMetadataAsDouble(String key) {
Object valObj = getObjFromMetadata(key);
if (valObj instanceof Double val) {
return OptionalDouble.of(val.doubleValue());
}
return OptionalDouble.empty();
}
public OptionalLong getMetadataAsLong(String key) {
Object valObj = getObjFromMetadata(key);
if (valObj instanceof Long val) {
return OptionalLong.of(val.longValue());
}
return OptionalLong.empty();
}
public OptionalInt getMetadataAsInt(String key) {
Object valObj = getObjFromMetadata(key);
if (valObj instanceof Integer val) {
return OptionalInt.of(val.intValue());
}
return OptionalInt.empty();
}
public Optional<String> getMetadataAsString(String key) {
Object valObj = getObjFromMetadata(key);
if (valObj instanceof String val) {
return Optional.of(val);
}
return Optional.empty();
}
@SuppressWarnings("unchecked")
public <C> Optional<C> getMetadata(String key, Class<C> type) {
Object valObj = getObjFromMetadata(key);
if (valObj == null || !type.isAssignableFrom(valObj.getClass())) {
return Optional.empty();
}
return Optional.of((C) valObj);
}
// ==========================================
// builder
public static <T> Builder<T> builder(String token, T loginId, String loginType, String deviceType) {
return new Builder<>(token, loginId, loginType, deviceType);
}
public static final class Builder<T> {
private final String token;
private final T loginId;
private final String loginType;
private final String deviceType;
@Nonnull
private final Map<String, Object> meta = new ConcurrentHashMap<>();
private Builder(String token, T loginId, String loginType, String deviceType) {
this.token = token;
this.loginId = loginId;
this.loginType = loginType;
this.deviceType = deviceType;
}
public <C> Builder<T> putMetadata(String key, @Nullable C value) {
this.meta.put(key, value);
return this;
}
public AuthenticationInfo<T> build() {
return new AuthenticationInfo<>(token, loginId, loginType, deviceType, meta);
}
}
}

View File

@@ -0,0 +1,21 @@
package xyz.zhouxy.plusone.system.application.common.model;
public class PlusoneContext {
private static final ThreadLocal<AuthenticationInfo<Long>> context = new ThreadLocal<>();
public static void setContext(AuthenticationInfo<Long> value) {
context.set(value);
}
public static AuthenticationInfo<Long> getContext() {
return context.get();
}
public static void remove() {
context.remove();
}
private PlusoneContext() {
throw new IllegalStateException("Utility class");
}
}

View File

@@ -1,61 +0,0 @@
package xyz.zhouxy.plusone.system.application.common.util;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import xyz.zhouxy.plusone.domain.IWithOrderNumber;
import xyz.zhouxy.plusone.system.application.query.result.MenuViewObject;
import xyz.zhouxy.plusone.system.domain.model.menu.Menu.MenuType;
/**
* 菜单工具类
*
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
*/
public class MenuUtil {
private MenuUtil() {
throw new IllegalStateException("Utility class");
}
/**
* 构建菜单树
*
* @param allMenus 菜单列表
* @return 菜单树
*/
public static List<MenuViewObject> buildMenuTree(Collection<MenuViewObject> allMenus) {
// 先排序,保证添加到 rootMenus 中的顺序,以及 addChild 添加的子菜单的顺序
allMenus = allMenus.stream()
.sorted(Comparator.comparing(IWithOrderNumber::getOrderNumber))
.toList();
// 一级菜单
List<MenuViewObject> rootMenus = new ArrayList<>();
// key: 菜单 id; value: 菜单对象. 方便根据 id 查找相应对象。
Map<Long, MenuViewObject> menuListMap = new HashMap<>();
for (var menu : allMenus) {
// 添加 MENU_LIST 到 map 中,方便后面调用对象的方法
if (menu.getType() == MenuType.MENU_LIST.ordinal()) {
menuListMap.put(menu.getId(), menu);
}
// 一级菜单
if (menu.getParentId() == 0) {
rootMenus.add(menu);
}
}
for (var menu : allMenus) {
var parent = menuListMap.getOrDefault(menu.getParentId(), null);
// 父菜单存在于 map 中,调用父菜单的 addChild 方法将当前菜单添加为父菜单的子菜单。
if (parent != null) {
parent.addChild(menu);
}
}
return rootMenus;
}
}

View File

@@ -1,18 +1,20 @@
package xyz.zhouxy.plusone.system.application.common.util; package xyz.zhouxy.plusone.system.application.common.util;
import java.util.regex.Pattern;
import lombok.Getter; import lombok.Getter;
import xyz.zhouxy.plusone.constant.RegexConsts; import xyz.zhouxy.plusone.commons.constant.PatternConsts;
public enum PrincipalType { public enum PrincipalType {
EMAIL(RegexConsts.EMAIL), EMAIL(PatternConsts.EMAIL),
MOBILE_PHONE(RegexConsts.MOBILE_PHONE), MOBILE_PHONE(PatternConsts.MOBILE_PHONE),
USERNAME(RegexConsts.USERNAME) USERNAME(PatternConsts.USERNAME)
; ;
@Getter @Getter
private final String regex; private final Pattern regex;
PrincipalType(String regex) { PrincipalType(Pattern regex) {
this.regex = regex; this.regex = regex;
} }
} }

View File

@@ -2,11 +2,11 @@ package xyz.zhouxy.plusone.system.application.common.util;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import xyz.zhouxy.plusone.system.application.common.exception.UnsupportedPrincipalTypeException;
import xyz.zhouxy.plusone.system.domain.model.account.Email; import xyz.zhouxy.plusone.system.domain.model.account.Email;
import xyz.zhouxy.plusone.system.domain.model.account.MobilePhone; import xyz.zhouxy.plusone.system.domain.model.account.MobilePhone;
import xyz.zhouxy.plusone.system.domain.model.account.Principal; import xyz.zhouxy.plusone.system.domain.model.account.Principal;
import xyz.zhouxy.plusone.system.domain.model.account.Username; import xyz.zhouxy.plusone.system.domain.model.account.Username;
import xyz.zhouxy.plusone.validator.InvalidInputException;
/** /**
* 根据字面值,判断并生成 {@link Principal} 值对象。 * 根据字面值,判断并生成 {@link Principal} 值对象。
@@ -16,7 +16,7 @@ import xyz.zhouxy.plusone.validator.InvalidInputException;
* @see Username * @see Username
* @see Email * @see Email
* @see MobilePhone * @see MobilePhone
* @see InvalidInputException * @see UnsupportedPrincipalTypeException
*/ */
public class PrincipalUtil { public class PrincipalUtil {
@@ -30,11 +30,11 @@ public class PrincipalUtil {
} }
PrincipalType[] principalTypes = PrincipalType.values(); PrincipalType[] principalTypes = PrincipalType.values();
for (var principalType : principalTypes) { for (var principalType : principalTypes) {
if (principal.matches(principalType.getRegex())) { if (principalType.getRegex().matcher(principal).matches()) {
return principalType; return principalType;
} }
} }
throw InvalidInputException.unsupportedPrincipalTypeException(); throw new UnsupportedPrincipalTypeException();
} }
public static Principal getPrincipal(@Nullable String principal) { public static Principal getPrincipal(@Nullable String principal) {
@@ -48,7 +48,7 @@ public class PrincipalUtil {
if (principalType == PrincipalType.USERNAME) { if (principalType == PrincipalType.USERNAME) {
return Username.of(principal); return Username.of(principal);
} }
throw InvalidInputException.unsupportedPrincipalTypeException(); throw new UnsupportedPrincipalTypeException();
} }
public static Principal getEmailOrMobilePhone(@Nullable String principal) { public static Principal getEmailOrMobilePhone(@Nullable String principal) {
@@ -59,6 +59,6 @@ public class PrincipalUtil {
if (principalType == PrincipalType.MOBILE_PHONE) { if (principalType == PrincipalType.MOBILE_PHONE) {
return MobilePhone.of(principal); return MobilePhone.of(principal);
} }
throw InvalidInputException.unsupportedPrincipalTypeException("输入邮箱地址或手机号"); throw new UnsupportedPrincipalTypeException("输入邮箱地址或手机号");
} }
} }

View File

@@ -1,30 +0,0 @@
package xyz.zhouxy.plusone.system.application.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import xyz.zhouxy.plusone.system.application.service.AdminLogoutService;
import xyz.zhouxy.plusone.util.RestfulResult;
/**
* Admin 账号登出
*
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
*/
@RestController
@RequestMapping("logout")
public class AdminLogoutController {
private final AdminLogoutService service;
public AdminLogoutController(AdminLogoutService service) {
this.service = service;
}
@GetMapping
public RestfulResult execute() {
service.execute();
return RestfulResult.success("注销成功");
}
}

View File

@@ -1,38 +0,0 @@
package xyz.zhouxy.plusone.system.application.exception;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
import xyz.zhouxy.plusone.exception.PlusoneException;
@ResponseStatus(HttpStatus.BAD_REQUEST)
public class AccountLoginException extends PlusoneException {
@java.io.Serial
private static final long serialVersionUID = -3040996790739138556L;
private static final int DEFAULT_ERR_CODE = 4030000;
private AccountLoginException() {
super(DEFAULT_ERR_CODE, "用户登录异常");
}
private AccountLoginException(int code, String message) {
super(code, message);
}
public static AccountLoginException accountNotExistException() {
return new AccountLoginException(4030101, "用户账户不存在");
}
public static AccountLoginException otpErrorException() {
return new AccountLoginException(4030501, "验证码错误");
}
public static AccountLoginException otpNotExistsException() {
return new AccountLoginException(4030502, "验证码不存在或已过期");
}
public static AccountLoginException passwordErrorException() {
return new AccountLoginException(4030200, "用户密码错误");
}
}

View File

@@ -1,57 +0,0 @@
package xyz.zhouxy.plusone.system.application.exception;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
import xyz.zhouxy.plusone.exception.PlusoneException;
@ResponseStatus(HttpStatus.BAD_REQUEST)
public class AccountRegisterException extends PlusoneException {
@java.io.Serial
private static final long serialVersionUID = 7580245181633370195L;
public AccountRegisterException() {
this(4020000, "用户注册错误");
}
public AccountRegisterException(String message) {
this(4020000, message);
}
public AccountRegisterException(Throwable cause) {
super(4020000, cause);
}
public AccountRegisterException(int code, String message) {
super(code, message);
}
public AccountRegisterException(int code, Throwable cause) {
super(code, cause);
}
public static AccountRegisterException emailOrMobilePhoneRequiredException() {
return new AccountRegisterException(4020300, "邮箱和手机号应至少绑定一个");
}
public static AccountRegisterException usernameAlreadyExists(String username) {
return new AccountRegisterException(4020400, String.format("用户名 %s 已存在", username));
}
public static AccountRegisterException emailAlreadyExists(String value) {
return new AccountRegisterException(4020500, String.format("邮箱 %s 已存在", value));
}
public static AccountRegisterException mobilePhoneAlreadyExists(String value) {
return new AccountRegisterException(4020600, String.format("手机号 %s 已存在", value));
}
public static AccountRegisterException codeErrorException() {
return new AccountRegisterException(4020701, "校验码错误");
}
public static AccountRegisterException codeNotExistsException() {
return new AccountRegisterException(4020702, "校验码不存在或已过期");
}
}

View File

@@ -2,12 +2,14 @@ package xyz.zhouxy.plusone.system.application.query;
import java.util.List; import java.util.List;
import javax.annotation.Nonnull;
import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Mapper;
import xyz.zhouxy.plusone.commons.util.PageDTO;
import xyz.zhouxy.plusone.system.application.query.params.AccountQueryParams; import xyz.zhouxy.plusone.system.application.query.params.AccountQueryParams;
import xyz.zhouxy.plusone.system.application.query.result.AccountDetails; import xyz.zhouxy.plusone.system.application.query.result.AccountDetails;
import xyz.zhouxy.plusone.system.application.query.result.AccountOverview; import xyz.zhouxy.plusone.system.application.query.result.AccountOverview;
import xyz.zhouxy.plusone.util.PageDTO;
/** /**
* 账号信息查询器 * 账号信息查询器
@@ -23,6 +25,7 @@ public interface AccountQueries {
return PageDTO.of(content, total); return PageDTO.of(content, total);
} }
@Nonnull
List<AccountOverview> queryAccountOverview(AccountQueryParams queryParams); List<AccountOverview> queryAccountOverview(AccountQueryParams queryParams);
long count(AccountQueryParams queryParams); long count(AccountQueryParams queryParams);

View File

@@ -2,12 +2,12 @@ package xyz.zhouxy.plusone.system.application.query;
import java.util.List; import java.util.List;
import org.apache.ibatis.jdbc.SQL;
import org.springframework.jdbc.core.BeanPropertyRowMapper; import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.namedparam.BeanPropertySqlParameterSource; import org.springframework.jdbc.core.namedparam.BeanPropertySqlParameterSource;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import xyz.zhouxy.plusone.sql.SQL;
import xyz.zhouxy.plusone.system.application.query.params.DictQueryParams; import xyz.zhouxy.plusone.system.application.query.params.DictQueryParams;
import xyz.zhouxy.plusone.system.application.query.result.DictOverview; import xyz.zhouxy.plusone.system.application.query.result.DictOverview;
@@ -26,14 +26,21 @@ public class DictQueries {
} }
public List<DictOverview> queryDictOverviewList(DictQueryParams queryParams) { public List<DictOverview> queryDictOverviewList(DictQueryParams queryParams) {
String sql = new SQL() String sql = new SQL() {
.SELECT("id", "dict_type", "dict_label", {
"created_by", "create_time", "updated_by", "update_time", "count") SELECT("id", "dict_type", "dict_label",
.FROM("view_sys_dict_overview") "created_by", "create_time", "updated_by", "update_time", "count");
.WHERE_IF(queryParams.getDictType() != null, "dict_type LIKE '%:dictType%'") FROM("view_sys_dict_overview");
.WHERE_IF(queryParams.getDictLabel() != null, "dict_label LIKE '%:dictLabel%'") if (queryParams.getDictType() != null) {
.toString(); WHERE("dict_type LIKE '%:dictType%'");
}
if (queryParams.getDictLabel() != null) {
WHERE("dict_label LIKE '%:dictLabel%'");
}
}
}.toString();
return this.jdbcTemplate return this.jdbcTemplate
.query(sql, new BeanPropertySqlParameterSource(queryParams), new BeanPropertyRowMapper<>(DictOverview.class)); .query(sql, new BeanPropertySqlParameterSource(queryParams),
new BeanPropertyRowMapper<>(DictOverview.class));
} }
} }

View File

@@ -7,7 +7,7 @@ import org.springframework.jdbc.core.namedparam.BeanPropertySqlParameterSource;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.stereotype.Repository; import org.springframework.stereotype.Repository;
import xyz.zhouxy.plusone.sql.SQL; import org.apache.ibatis.jdbc.SQL;
import xyz.zhouxy.plusone.system.application.query.params.RoleQueryParams; import xyz.zhouxy.plusone.system.application.query.params.RoleQueryParams;
import xyz.zhouxy.plusone.system.application.query.result.RoleOverview; import xyz.zhouxy.plusone.system.application.query.result.RoleOverview;
@@ -35,30 +35,47 @@ public class RoleQueries {
* @return 查询结果 * @return 查询结果
*/ */
public List<RoleOverview> query(RoleQueryParams params) { public List<RoleOverview> query(RoleQueryParams params) {
String b = new SQL() var sql = new SQL() {
.SELECT("id") {
.FROM("sys_role") SELECT("a.id AS id", "a.name AS name", "a.identifier AS identifier", "a.status AS status");
.WHERE_IF_NOT_NULL(params.getId(), "id = :id") FROM("sys_role AS a", "(" +
.WHERE_IF_NOT_NULL(params.getName(), "name = :name") new SQL() {
.WHERE_IF_NOT_NULL(params.getIdentifier(), "identifier = :identifier") {
.WHERE_IF_NOT_NULL(params.getStatus(), "status = :status") SELECT("id");
.WHERE_IF_NOT_NULL(params.getCreateTimeStart(), "create_time >= :createTimeStart") FROM("sys_role");
.WHERE_IF_NOT_NULL(params.getCreateTimeEnd(), "create_time < :createTimeEnd") if (null != params.getId()) {
.WHERE_IF_NOT_NULL(params.getUpdateTimeStart(), "update_time >= :updateTimeStart") WHERE("id = :id");
.WHERE_IF_NOT_NULL(params.getUpdateTimeEnd(), "update_time < :updateTimeEnd") }
.LIMIT(params.getSize()) if (null != params.getName()) {
.OFFSET(params.getOffset()) WHERE("name = :name");
.toString(); }
var sql = """ if (null != params.getIdentifier()) {
SELECT a.id AS id, a.name AS name, a.identifier AS identifier, a.status AS status WHERE("identifier = :identifier");
FROM sys_role AS a, ( }
""" + b + """ if (null != params.getStatus()) {
) AS b WHERE("status = :status");
WHERE a.id = b.id }
"""; if (null != params.getCreateTimeStart()) {
WHERE("create_time >= :createTimeStart");
}
if (null != params.getCreateTimeEnd()) {
WHERE("create_time < :createTimeEnd");
}
if (null != params.getUpdateTimeStart()) {
WHERE("update_time >= :updateTimeStart");
}
if (null != params.getUpdateTimeEnd()) {
WHERE("update_time < :updateTimeEnd");
}
LIMIT(params.getSize());
OFFSET(params.getOffset());
}
}.toString() + ") AS b");
WHERE("a.id = b.id");
}
}.toString();
return this.jdbcTemplate return this.jdbcTemplate
.query(sql, new BeanPropertySqlParameterSource(params), .query(sql, new BeanPropertySqlParameterSource(params),
new BeanPropertyRowMapper<>(RoleOverview.class)); new BeanPropertyRowMapper<>(RoleOverview.class));
} }
} }

View File

@@ -5,14 +5,14 @@ import java.time.LocalDate;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import lombok.ToString; import lombok.ToString;
import xyz.zhouxy.plusone.util.PagingAndSortingQueryParams; import xyz.zhouxy.plusone.commons.util.PagingAndSortingQueryParams;
/** /**
* 账号信息查询参数 * 账号信息查询参数
* *
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a> * @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
*/ */
@ToString @ToString(callSuper = true)
public class AccountQueryParams extends PagingAndSortingQueryParams { public class AccountQueryParams extends PagingAndSortingQueryParams {
public AccountQueryParams() { public AccountQueryParams() {
@@ -29,7 +29,6 @@ public class AccountQueryParams extends PagingAndSortingQueryParams {
"update_time"); "update_time");
} }
// TODO【添加】 注解参数校验
private @Getter @Setter Long id; private @Getter @Setter Long id;
private @Getter @Setter String username; private @Getter @Setter String username;
private @Getter @Setter String email; private @Getter @Setter String email;

View File

@@ -4,7 +4,7 @@ import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import lombok.ToString; import lombok.ToString;
import lombok.experimental.Accessors; import lombok.experimental.Accessors;
import xyz.zhouxy.plusone.util.PagingAndSortingQueryParams; import xyz.zhouxy.plusone.commons.util.PagingAndSortingQueryParams;
/** /**
* 数据字典查询参数 * 数据字典查询参数

View File

@@ -5,7 +5,7 @@ import java.time.LocalDate;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import lombok.ToString; import lombok.ToString;
import xyz.zhouxy.plusone.util.PagingAndSortingQueryParams; import xyz.zhouxy.plusone.commons.util.PagingAndSortingQueryParams;
/** /**
* 角色信息查询参数 * 角色信息查询参数

View File

@@ -2,9 +2,10 @@ package xyz.zhouxy.plusone.system.application.query.result;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Comparator; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.SortedSet;
import java.util.TreeSet;
import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude;
@@ -81,18 +82,18 @@ public class MenuViewObject implements IWithOrderNumber {
List<Action> actions; List<Action> actions;
// MENU_LIST // MENU_LIST
List<MenuViewObject> children; SortedSet<MenuViewObject> children;
public void addChild(MenuViewObject child) { public void addChild(MenuViewObject child) {
if (this.children == null) { if (this.children == null) {
this.children = new ArrayList<>(); this.children = new TreeSet<>();
} }
this.children.add(child); this.children.add(child);
} }
public void addChildren(Collection<MenuViewObject> children) { public void addChildren(Collection<MenuViewObject> children) {
if (this.children == null) { if (this.children == null) {
this.children = new ArrayList<>(); this.children = new TreeSet<>();
} }
this.children.addAll(children); this.children.addAll(children);
} }
@@ -109,7 +110,7 @@ public class MenuViewObject implements IWithOrderNumber {
viewObject.icon = menu.getIcon(); viewObject.icon = menu.getIcon();
viewObject.hidden = menu.isHidden(); viewObject.hidden = menu.isHidden();
viewObject.orderNumber = menu.getOrderNumber(); viewObject.orderNumber = menu.getOrderNumber();
viewObject.status = menu.getStatus().getValue(); viewObject.status = menu.getStatus().getId();
viewObject.remarks = menu.getRemarks(); viewObject.remarks = menu.getRemarks();
if (viewObject.type == MenuType.MENU_ITEM.ordinal()) { if (viewObject.type == MenuType.MENU_ITEM.ordinal()) {
viewObject.component = menu.getComponent(); viewObject.component = menu.getComponent();
@@ -121,11 +122,33 @@ public class MenuViewObject implements IWithOrderNumber {
} }
public List<MenuViewObject> getChildren() { public List<MenuViewObject> getChildren() {
return Objects.nonNull(this.children) return this.children == null || this.children.isEmpty()
? this.children ? Collections.emptyList()
.stream() : new ArrayList<>(this.children);
.sorted(Comparator.comparing(IWithOrderNumber::getOrderNumber)) }
.toList()
: null; @Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((id == null) ? 0 : id.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
MenuViewObject other = (MenuViewObject) obj;
if (id == null) {
if (other.id != null)
return false;
} else if (!id.equals(other.id))
return false;
return true;
} }
} }

View File

@@ -1,7 +1,5 @@
package xyz.zhouxy.plusone.system.application.service; package xyz.zhouxy.plusone.system.application.service;
import xyz.zhouxy.plusone.system.constant.AuthLogic;
import java.util.List; import java.util.List;
import javax.annotation.Resource; import javax.annotation.Resource;
@@ -10,12 +8,16 @@ import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import cn.dev33.satoken.stp.StpLogic; import cn.dev33.satoken.stp.StpLogic;
import xyz.zhouxy.plusone.system.application.common.exception.AccountLoginException;
import xyz.zhouxy.plusone.system.application.common.exception.UnsupportedPrincipalTypeException;
import xyz.zhouxy.plusone.system.application.common.util.PrincipalUtil; import xyz.zhouxy.plusone.system.application.common.util.PrincipalUtil;
import xyz.zhouxy.plusone.system.application.query.AccountQueries; import xyz.zhouxy.plusone.system.application.query.AccountQueries;
import xyz.zhouxy.plusone.system.application.query.result.AccountDetails; import xyz.zhouxy.plusone.system.application.query.result.AccountDetails;
import xyz.zhouxy.plusone.system.application.query.result.MenuViewObject; import xyz.zhouxy.plusone.system.application.query.result.MenuViewObject;
import xyz.zhouxy.plusone.system.application.service.command.ChangePasswordByOtpCommand;
import xyz.zhouxy.plusone.system.application.service.command.ChangePasswordCommand; import xyz.zhouxy.plusone.system.application.service.command.ChangePasswordCommand;
import xyz.zhouxy.plusone.system.application.service.command.ChangePasswordWithoutLoginCommand; import xyz.zhouxy.plusone.system.application.service.command.ChangePasswordWithoutLoginCommand;
import xyz.zhouxy.plusone.system.constant.AuthLogic;
import xyz.zhouxy.plusone.system.domain.model.account.Account; import xyz.zhouxy.plusone.system.domain.model.account.Account;
import xyz.zhouxy.plusone.system.domain.model.account.AccountRepository; import xyz.zhouxy.plusone.system.domain.model.account.AccountRepository;
import xyz.zhouxy.plusone.system.domain.model.account.Email; import xyz.zhouxy.plusone.system.domain.model.account.Email;
@@ -23,14 +25,14 @@ import xyz.zhouxy.plusone.system.domain.model.account.MobilePhone;
import xyz.zhouxy.plusone.system.domain.model.account.Principal; import xyz.zhouxy.plusone.system.domain.model.account.Principal;
/** /**
* 账号查询本身相关信息 * 账号对当前帐号进行操作
* *
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a> * @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
*/ */
@Service @Service
public class AccountContextService { public class AccountContextService {
private final static StpLogic adminAuthLogic = AuthLogic.adminAuthLogic; private static final StpLogic adminAuthLogic = AuthLogic.adminAuthLogic;
@Resource @Resource
private AccountQueries accountQueries; private AccountQueries accountQueries;
@@ -47,6 +49,11 @@ public class AccountContextService {
return accountQueries.queryAccountDetails(accountId); return accountQueries.queryAccountDetails(accountId);
} }
public void logout() {
adminAuthLogic.checkLogin();
adminAuthLogic.logout();
}
public List<MenuViewObject> getMenuTree() { public List<MenuViewObject> getMenuTree() {
adminAuthLogic.checkLogin(); adminAuthLogic.checkLogin();
long accountId = adminAuthLogic.getLoginIdAsLong(); long accountId = adminAuthLogic.getLoginIdAsLong();
@@ -56,7 +63,8 @@ public class AccountContextService {
@Transactional @Transactional
public void changePassword(ChangePasswordCommand command) { public void changePassword(ChangePasswordCommand command) {
adminAuthLogic.checkLogin(); adminAuthLogic.checkLogin();
Account account = accountRepository.find(adminAuthLogic.getLoginIdAsLong()); Account account = accountRepository.find(adminAuthLogic.getLoginIdAsLong())
.orElseThrow(() -> AccountLoginException.accountNotExistException("当前所登录的账号不存在"));
account.checkPassword(command.getPassword()); account.checkPassword(command.getPassword());
account.changePassword(command.getNewPassword(), command.getPasswordConfirmation()); account.changePassword(command.getNewPassword(), command.getPasswordConfirmation());
accountRepository.save(account); accountRepository.save(account);
@@ -68,12 +76,28 @@ public class AccountContextService {
String principal = command.getAccount(); String principal = command.getAccount();
Principal emailOrMobilePhone = PrincipalUtil.getEmailOrMobilePhone(principal); Principal emailOrMobilePhone = PrincipalUtil.getEmailOrMobilePhone(principal);
Account account = emailOrMobilePhone instanceof Email Account account = (emailOrMobilePhone instanceof Email
? accountRepository.findByEmail((Email) emailOrMobilePhone) ? accountRepository.findByEmail((Email) emailOrMobilePhone)
: accountRepository.findByMobilePhone((MobilePhone) emailOrMobilePhone); : accountRepository.findByMobilePhone((MobilePhone) emailOrMobilePhone))
.orElseThrow(() -> AccountLoginException.accountNotExistException("当前所登录的账号不存在"));
account.checkPassword(command.getOldPassword()); account.checkPassword(command.getOldPassword());
account.changePassword(command.getNewPassword(), command.getPasswordConfirmation()); account.changePassword(command.getNewPassword(), command.getPasswordConfirmation());
accountRepository.save(account); accountRepository.save(account);
adminAuthLogic.logout(); adminAuthLogic.logout();
} }
@Transactional
public void changePasswordByOtp(ChangePasswordByOtpCommand command) {
var principal = command.getAccount();
boolean accountExists = switch (command.getPrincipalType()) {
case EMAIL -> accountRepository.existsEmail(Email.of(principal));
case MOBILE_PHONE -> accountRepository.existsMobilePhone(MobilePhone.of(principal));
default -> throw new UnsupportedPrincipalTypeException("输入邮箱地址或手机号");
};
if (accountExists) {
throw AccountLoginException.accountNotExistException();
}
mailAndSmsVerifyService.checkOtp(principal, command.getOtp());
}
} }

View File

@@ -14,7 +14,9 @@ import org.springframework.util.Assert;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PathVariable;
import xyz.zhouxy.plusone.system.application.exception.AccountRegisterException; import xyz.zhouxy.plusone.commons.util.PageDTO;
import xyz.zhouxy.plusone.exception.DataNotExistException;
import xyz.zhouxy.plusone.system.application.common.exception.AccountRegisterException;
import xyz.zhouxy.plusone.system.application.query.AccountQueries; import xyz.zhouxy.plusone.system.application.query.AccountQueries;
import xyz.zhouxy.plusone.system.application.query.params.AccountQueryParams; import xyz.zhouxy.plusone.system.application.query.params.AccountQueryParams;
import xyz.zhouxy.plusone.system.application.query.result.AccountDetails; import xyz.zhouxy.plusone.system.application.query.result.AccountDetails;
@@ -24,11 +26,12 @@ import xyz.zhouxy.plusone.system.application.service.command.UpdateAccountComman
import xyz.zhouxy.plusone.system.domain.model.account.Account; import xyz.zhouxy.plusone.system.domain.model.account.Account;
import xyz.zhouxy.plusone.system.domain.model.account.AccountInfo; import xyz.zhouxy.plusone.system.domain.model.account.AccountInfo;
import xyz.zhouxy.plusone.system.domain.model.account.AccountRepository; import xyz.zhouxy.plusone.system.domain.model.account.AccountRepository;
import xyz.zhouxy.plusone.system.domain.model.account.AccountStatus;
import xyz.zhouxy.plusone.system.domain.model.account.Email; import xyz.zhouxy.plusone.system.domain.model.account.Email;
import xyz.zhouxy.plusone.system.domain.model.account.MobilePhone; import xyz.zhouxy.plusone.system.domain.model.account.MobilePhone;
import xyz.zhouxy.plusone.system.domain.model.account.Sex;
import xyz.zhouxy.plusone.system.domain.model.account.Username; import xyz.zhouxy.plusone.system.domain.model.account.Username;
import xyz.zhouxy.plusone.util.AssertResult; import xyz.zhouxy.plusone.util.AssertResult;
import xyz.zhouxy.plusone.util.PageDTO;
/** /**
* 账号管理 * 账号管理
@@ -66,9 +69,13 @@ public class AccountManagementService {
mobilePhone, mobilePhone,
command.getPassword(), command.getPassword(),
command.getPasswordConfirmation(), command.getPasswordConfirmation(),
command.getStatus(), AccountStatus.of(command.getStatus()),
command.getRoleRefs(), command.getRoleRefs(),
AccountInfo.of(command.getNickname(), command.getAvatar(), command.getSex()), AccountInfo.builder()
.nickname(command.getNickname())
.avatar(command.getAvatar())
.sex(Sex.of(command.getSex()))
.build(),
adminAuthLogic.getLoginIdAsLong()); adminAuthLogic.getLoginIdAsLong());
accountRepository.save(account); accountRepository.save(account);
} }
@@ -76,18 +83,17 @@ public class AccountManagementService {
public void deleteAccounts(List<Long> ids) { public void deleteAccounts(List<Long> ids) {
Account accountToDelete; Account accountToDelete;
for (var id : ids) { for (var id : ids) {
accountToDelete = accountRepository.find(id); accountToDelete = accountRepository.find(id)
AssertResult.nonNull(accountToDelete); .orElseThrow(() -> new DataNotExistException("该账号不存在"));
accountRepository.delete(accountToDelete); accountRepository.delete(accountToDelete);
} }
} }
public void updateAccountInfo(Long id, @Valid UpdateAccountCommand command) { public void updateAccountInfo(Long id, @Valid UpdateAccountCommand command) {
Assert.isTrue(Objects.equals(id, command.getId()), "参数错误: id 不匹配"); Assert.isTrue(Objects.equals(id, command.getId()), "参数错误: id 不匹配");
Account account = accountRepository.find(id); Account account = accountRepository.find(id)
AssertResult.nonNull(account, "该账号不存在"); .orElseThrow(() -> new DataNotExistException("该账号不存在"));
account.setAccountInfo(command.getNickname(), command.getAvatar(), command.getSex()); account.setAccountInfo(command.getNickname(), command.getAvatar(), Sex.of(command.getSex()));
account.setUpdatedBy(adminAuthLogic.getLoginIdAsLong());
accountRepository.save(account); accountRepository.save(account);
} }
@@ -96,6 +102,13 @@ public class AccountManagementService {
return accountQueries.queryAccountOverviewPage(queryParams); return accountQueries.queryAccountOverviewPage(queryParams);
} }
/**
* 查询账号详细信息,如果查不到将抛出 {@link DataNotExistException}。
*
* @param accountId 账号 id
* @return 账号信息
* @throws DataNotExistException 查询不到数据时抛出异常
*/
@Transactional(propagation = Propagation.SUPPORTS) @Transactional(propagation = Propagation.SUPPORTS)
public AccountDetails queryAccountDetails(@PathVariable("accountId") Long accountId) { public AccountDetails queryAccountDetails(@PathVariable("accountId") Long accountId) {
var accountDetails = accountQueries.queryAccountDetails(accountId); var accountDetails = accountQueries.queryAccountDetails(accountId);

View File

@@ -5,10 +5,11 @@ import static xyz.zhouxy.plusone.system.constant.AuthLogic.adminAuthLogic;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import xyz.zhouxy.plusone.exception.InvalidInputException; import cn.hutool.core.lang.Assert;
import xyz.zhouxy.plusone.system.application.common.exception.AccountLoginException;
import xyz.zhouxy.plusone.system.application.common.exception.UnsupportedPrincipalTypeException;
import xyz.zhouxy.plusone.system.application.common.util.PrincipalType; import xyz.zhouxy.plusone.system.application.common.util.PrincipalType;
import xyz.zhouxy.plusone.system.application.common.util.PrincipalUtil; import xyz.zhouxy.plusone.system.application.common.util.PrincipalUtil;
import xyz.zhouxy.plusone.system.application.exception.AccountLoginException;
import xyz.zhouxy.plusone.system.application.query.AccountQueries; import xyz.zhouxy.plusone.system.application.query.AccountQueries;
import xyz.zhouxy.plusone.system.application.query.result.LoginInfoViewObject; import xyz.zhouxy.plusone.system.application.query.result.LoginInfoViewObject;
import xyz.zhouxy.plusone.system.application.service.command.LoginByOtpCommand; import xyz.zhouxy.plusone.system.application.service.command.LoginByOtpCommand;
@@ -17,7 +18,6 @@ import xyz.zhouxy.plusone.system.domain.model.account.Account;
import xyz.zhouxy.plusone.system.domain.model.account.AccountRepository; import xyz.zhouxy.plusone.system.domain.model.account.AccountRepository;
import xyz.zhouxy.plusone.system.domain.model.account.Email; import xyz.zhouxy.plusone.system.domain.model.account.Email;
import xyz.zhouxy.plusone.system.domain.model.account.MobilePhone; import xyz.zhouxy.plusone.system.domain.model.account.MobilePhone;
import xyz.zhouxy.plusone.system.domain.model.account.Principal;
import xyz.zhouxy.plusone.system.domain.model.account.Username; import xyz.zhouxy.plusone.system.domain.model.account.Username;
import xyz.zhouxy.plusone.validator.ValidateDto; import xyz.zhouxy.plusone.validator.ValidateDto;
@@ -43,62 +43,43 @@ public class AdminLoginService {
@ValidateDto @ValidateDto
public LoginInfoViewObject loginByPassword(LoginByPasswordCommand command) { public LoginInfoViewObject loginByPassword(LoginByPasswordCommand command) {
Principal principal = PrincipalUtil.getPrincipal(command.getPrincipal()); var principal = command.getPrincipal();
Account account; Account account = (switch (command.getPrincipalType()) {
if (principal instanceof Email) { case USERNAME -> accountRepository.findByUsername(Username.of(principal));
account = accountRepository.findByEmail((Email) principal); case EMAIL -> accountRepository.findByEmail(Email.of(principal));
} else if (principal instanceof MobilePhone) { case MOBILE_PHONE -> accountRepository.findByMobilePhone(MobilePhone.of(principal));
account = accountRepository.findByMobilePhone((MobilePhone) principal); }).orElseThrow(AccountLoginException::accountNotExistException);
} else { var isPasswordCorrect = account.checkPassword(command.getPassword());
account = accountRepository.findByUsername((Username) principal); Assert.isTrue(isPasswordCorrect, AccountLoginException::passwordErrorException);
}
if (account == null) {
throw AccountLoginException.accountNotExistException();
}
@SuppressWarnings("null")
boolean isPasswordCorrect = account.checkPassword(command.getPassword());
if (!isPasswordCorrect) {
throw AccountLoginException.passwordErrorException();
}
adminAuthLogic.login(account.getId().orElseThrow(), command.isRememberMe()); adminAuthLogic.login(account.getId().orElseThrow(), command.isRememberMe());
var accountDetails = accountQueries.queryAccountDetails(account.getId().orElseThrow()); var accountDetails = accountQueries.queryAccountDetails(account.getId().orElseThrow());
return LoginInfoViewObject.of(adminAuthLogic.getTokenValue(), accountDetails); return LoginInfoViewObject.of(adminAuthLogic.getTokenValue(), accountDetails);
} }
@ValidateDto @ValidateDto
public LoginInfoViewObject loginByOtp(LoginByOtpCommand command) { public LoginInfoViewObject loginByOtp(LoginByOtpCommand command) {
String principal = command.getPrincipal(); var principal = command.getPrincipal();
PrincipalType principalType = PrincipalUtil.getPrincipalType(principal); Account account = (switch (command.getPrincipalType()) {
String otp = command.getOtp(); case EMAIL -> accountRepository.findByEmail(Email.of(principal));
boolean rememberMe = command.isRememberMe(); case MOBILE_PHONE -> accountRepository.findByMobilePhone(MobilePhone.of(principal));
default -> throw new UnsupportedPrincipalTypeException("输入邮箱地址或手机号");
}).orElseThrow(AccountLoginException::accountNotExistException);
Account account; mailAndSmsVerifyService.checkOtp(principal, command.getOtp());
if (principalType == PrincipalType.EMAIL) {
account = accountRepository.findByEmail(Email.of(principal));
} else if (principalType == PrincipalType.MOBILE_PHONE) {
account = accountRepository.findByMobilePhone(MobilePhone.of(principal));
} else {
throw InvalidInputException.unsupportedPrincipalTypeException("输入邮箱地址或手机号");
}
if (account == null) { adminAuthLogic.login(account.getId().orElseThrow(), command.isRememberMe());
throw AccountLoginException.accountNotExistException();
}
mailAndSmsVerifyService.checkOtp(principal, otp);
adminAuthLogic.login(account.getId().orElseThrow(), rememberMe);
var accountDetails = accountQueries.queryAccountDetails(account.getId().orElseThrow()); var accountDetails = accountQueries.queryAccountDetails(account.getId().orElseThrow());
return LoginInfoViewObject.of(adminAuthLogic.getTokenValue(), accountDetails); return LoginInfoViewObject.of(adminAuthLogic.getTokenValue(), accountDetails);
} }
public void sendOtp(String principal) { public void sendOtp(String principal) {
Principal emailOrMobilePhone = PrincipalUtil.getEmailOrMobilePhone(principal); PrincipalType principalType = PrincipalUtil.getPrincipalType(principal);
if (emailOrMobilePhone instanceof Email) { if (principalType == PrincipalType.EMAIL) {
mailAndSmsVerifyService.sendOtpToEmail((Email) emailOrMobilePhone); mailAndSmsVerifyService.sendOtpToEmail(Email.of(principal));
} else { } else {
mailAndSmsVerifyService.sendOtpToMobilePhone((MobilePhone) emailOrMobilePhone); mailAndSmsVerifyService.sendOtpToMobilePhone(MobilePhone.of(principal));
} }
} }
} }

View File

@@ -1,19 +0,0 @@
package xyz.zhouxy.plusone.system.application.service;
import static xyz.zhouxy.plusone.system.constant.AuthLogic.adminAuthLogic;
import org.springframework.stereotype.Service;
/**
* Admin 账号登出
*
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
*/
@Service
public class AdminLogoutService {
public void execute() {
adminAuthLogic.checkLogin();
adminAuthLogic.logout();
}
}

View File

@@ -10,6 +10,7 @@ import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import xyz.zhouxy.plusone.exception.DataNotExistException;
import xyz.zhouxy.plusone.system.application.query.DictQueries; import xyz.zhouxy.plusone.system.application.query.DictQueries;
import xyz.zhouxy.plusone.system.application.query.params.DictQueryParams; import xyz.zhouxy.plusone.system.application.query.params.DictQueryParams;
import xyz.zhouxy.plusone.system.application.query.result.DictOverview; import xyz.zhouxy.plusone.system.application.query.result.DictOverview;
@@ -45,21 +46,21 @@ public class DictManagementService {
public void deleteDicts(List<Long> ids) { public void deleteDicts(List<Long> ids) {
Dict dictToDelete; Dict dictToDelete;
for (Long id : ids) { for (Long id : ids) {
dictToDelete = dictRepository.find(id); dictToDelete = dictRepository.find(id).orElseThrow(DataNotExistException::new);
dictRepository.delete(dictToDelete); dictRepository.delete(dictToDelete);
} }
} }
public void updateDict(Long id, @Valid UpdateDictCommand command) { public void updateDict(Long id, @Valid UpdateDictCommand command) {
Assert.isTrue(Objects.equals(id, command.getId()), "id 不匹配"); Assert.isTrue(Objects.equals(id, command.getId()), "id 不匹配");
Dict dictToUpdate = dictRepository.find(command.getId()); Dict dictToUpdate = dictRepository.find(command.getId()).orElseThrow(DataNotExistException::new);
dictToUpdate.updateDict(command.getDictType(), command.getDictLabel(), command.getKeyLabelMap()); dictToUpdate.updateDict(command.getDictType(), command.getDictLabel(), command.getKeyLabelMap());
dictRepository.save(dictToUpdate); dictRepository.save(dictToUpdate);
} }
@Transactional(propagation = Propagation.SUPPORTS) @Transactional(propagation = Propagation.SUPPORTS)
public Dict findDictDetails(Long dictId) { public Dict findDictDetails(Long dictId) {
return dictRepository.find(dictId); return dictRepository.find(dictId).orElseThrow(DataNotExistException::new);
} }
@Transactional(propagation = Propagation.SUPPORTS) @Transactional(propagation = Propagation.SUPPORTS)

View File

@@ -9,8 +9,8 @@ import org.springframework.util.Assert;
import cn.hutool.core.util.RandomUtil; import cn.hutool.core.util.RandomUtil;
import xyz.zhouxy.plusone.mail.MailService; import xyz.zhouxy.plusone.mail.MailService;
import xyz.zhouxy.plusone.sms.SmsService; import xyz.zhouxy.plusone.sms.SmsService;
import xyz.zhouxy.plusone.system.application.exception.AccountLoginException; import xyz.zhouxy.plusone.system.application.common.exception.AccountLoginException;
import xyz.zhouxy.plusone.system.application.exception.AccountRegisterException; import xyz.zhouxy.plusone.system.application.common.exception.AccountRegisterException;
import xyz.zhouxy.plusone.system.domain.model.account.AccountRepository; import xyz.zhouxy.plusone.system.domain.model.account.AccountRepository;
import xyz.zhouxy.plusone.system.domain.model.account.Email; import xyz.zhouxy.plusone.system.domain.model.account.Email;
import xyz.zhouxy.plusone.system.domain.model.account.MobilePhone; import xyz.zhouxy.plusone.system.domain.model.account.MobilePhone;

View File

@@ -1,7 +1,6 @@
package xyz.zhouxy.plusone.system.application.service; package xyz.zhouxy.plusone.system.application.service;
import java.util.Comparator; import java.util.Collection;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
@@ -13,8 +12,10 @@ import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import xyz.zhouxy.plusone.domain.IWithOrderNumber; import xyz.zhouxy.plusone.commons.util.MoreCollections;
import xyz.zhouxy.plusone.system.application.exception.UnsupportedMenuTypeException; import xyz.zhouxy.plusone.constant.EntityStatus;
import xyz.zhouxy.plusone.exception.DataNotExistException;
import xyz.zhouxy.plusone.system.application.common.exception.UnsupportedMenuTypeException;
import xyz.zhouxy.plusone.system.application.query.result.MenuViewObject; import xyz.zhouxy.plusone.system.application.query.result.MenuViewObject;
import xyz.zhouxy.plusone.system.application.service.command.CreateMenuCommand; import xyz.zhouxy.plusone.system.application.service.command.CreateMenuCommand;
import xyz.zhouxy.plusone.system.application.service.command.UpdateMenuCommand; import xyz.zhouxy.plusone.system.application.service.command.UpdateMenuCommand;
@@ -22,7 +23,6 @@ import xyz.zhouxy.plusone.system.domain.model.menu.Menu;
import xyz.zhouxy.plusone.system.domain.model.menu.MenuConstructor; import xyz.zhouxy.plusone.system.domain.model.menu.MenuConstructor;
import xyz.zhouxy.plusone.system.domain.model.menu.MenuRepository; import xyz.zhouxy.plusone.system.domain.model.menu.MenuRepository;
import xyz.zhouxy.plusone.system.domain.service.MenuService; import xyz.zhouxy.plusone.system.domain.service.MenuService;
import xyz.zhouxy.plusone.util.AssertResult;
/** /**
* 菜单管理 * 菜单管理
@@ -66,7 +66,7 @@ public class MenuManagementService {
command.getIcon(), command.getIcon(),
command.getHidden(), command.getHidden(),
command.getOrderNumber(), command.getOrderNumber(),
command.getStatus(), EntityStatus.of(command.getStatus()),
command.getRemarks()); command.getRemarks());
} }
@@ -79,7 +79,7 @@ public class MenuManagementService {
command.getIcon(), command.getIcon(),
command.getHidden(), command.getHidden(),
command.getOrderNumber(), command.getOrderNumber(),
command.getStatus(), EntityStatus.of(command.getStatus()),
command.getComponent(), command.getComponent(),
command.getResource(), command.getResource(),
command.getCache(), command.getCache(),
@@ -88,15 +88,16 @@ public class MenuManagementService {
// ==================== delete ==================== // ==================== delete ====================
public void deleteMenu(Long id) { public void deleteMenu(Long id) {
Menu menuToDelete = menuRepository.find(id); Menu menuToDelete = menuRepository.find(id)
AssertResult.nonNull(menuToDelete); .orElseThrow(DataNotExistException::new);
menuRepository.delete(menuToDelete); menuRepository.delete(menuToDelete);
} }
// ==================== update ==================== // ==================== update ====================
public void updateMenu(Long id, @Valid UpdateMenuCommand command) { public void updateMenu(Long id, @Valid UpdateMenuCommand command) {
Assert.isTrue(Objects.equals(id, command.getId()), "id 不匹配"); Assert.isTrue(Objects.equals(id, command.getId()), "id 不匹配");
Menu menuToUpdate = menuRepository.find(command.getId()); Menu menuToUpdate = menuRepository.find(command.getId())
.orElseThrow(DataNotExistException::new);
menuToUpdate.updateMenuInfo( menuToUpdate.updateMenuInfo(
command.getMenuType(), command.getMenuType(),
command.getParentId(), command.getParentId(),
@@ -106,7 +107,7 @@ public class MenuManagementService {
command.getIcon(), command.getIcon(),
command.getHidden(), command.getHidden(),
command.getOrderNumber(), command.getOrderNumber(),
command.getStatus(), EntityStatus.of(command.getStatus()),
command.getComponent(), command.getComponent(),
command.getResource(), command.getResource(),
command.getCache(), command.getCache(),
@@ -118,7 +119,7 @@ public class MenuManagementService {
@Transactional(propagation = Propagation.SUPPORTS) @Transactional(propagation = Propagation.SUPPORTS)
public MenuViewObject findById(Long id) { public MenuViewObject findById(Long id) {
var menu = menuRepository.find(id); var menu = menuRepository.find(id);
return MenuViewObject.of(menu); return MenuViewObject.of(menu.orElseThrow(DataNotExistException::new));
} }
@Transactional(propagation = Propagation.SUPPORTS) @Transactional(propagation = Propagation.SUPPORTS)
@@ -137,38 +138,33 @@ public class MenuManagementService {
@Transactional(propagation = Propagation.SUPPORTS) @Transactional(propagation = Propagation.SUPPORTS)
public List<MenuViewObject> buildMenuTree(List<MenuViewObject> menus) { public List<MenuViewObject> buildMenuTree(List<MenuViewObject> menus) {
List<MenuViewObject> rootMenus = menus // 创建并填充 Map
.stream() final Map<Long, MenuViewObject> menuMap = MoreCollections.toHashMap(menus, MenuViewObject::getId);
.filter(menu -> Objects.equals(menu.getParentId(), 0L))
.toList();
Map<Long, MenuViewObject> allMenus = new HashMap<>();
for (var item : menus) {
allMenus.put(item.getId(), item);
}
for (MenuViewObject menu : menus) { for (MenuViewObject menu : menus) {
long parentId = menu.getParentId(); long parentId = menu.getParentId();
while (parentId != 0 && !allMenus.containsKey(parentId)) { if (parentId != 0L) {
MenuViewObject parent = findById(parentId); while (!menuMap.containsKey(parentId)) {
if (parent == null) { MenuViewObject parent = findById(parentId);
break; if (parent == null) {
break;
}
menuMap.put(parent.getId(), parent);
parentId = parent.getParentId();
} }
allMenus.put(parent.getId(), parent);
parentId = parent.getParentId();
} }
} }
for (var menu : allMenus.values()) { Collection<MenuViewObject> allMenus = menuMap.values();
var parent = allMenus.getOrDefault(menu.getParentId(), null); for (var menu : allMenus) {
var parent = menuMap.get(menu.getParentId());
if (parent != null) { if (parent != null) {
parent.addChild(menu); parent.addChild(menu);
} }
} }
return rootMenus return allMenus.stream()
.stream() .filter(menu -> (menu != null) && Objects.equals(menu.getParentId(), 0L))
.sorted(Comparator.comparing(IWithOrderNumber::getOrderNumber)) .sorted()
.toList(); .toList();
} }
} }

View File

@@ -5,10 +5,10 @@ import java.util.Set;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import xyz.zhouxy.plusone.exception.InvalidInputException; import xyz.zhouxy.plusone.system.application.common.exception.AccountRegisterException;
import xyz.zhouxy.plusone.system.application.common.exception.UnsupportedPrincipalTypeException;
import xyz.zhouxy.plusone.system.application.common.util.PrincipalType; import xyz.zhouxy.plusone.system.application.common.util.PrincipalType;
import xyz.zhouxy.plusone.system.application.common.util.PrincipalUtil; import xyz.zhouxy.plusone.system.application.common.util.PrincipalUtil;
import xyz.zhouxy.plusone.system.application.exception.AccountRegisterException;
import xyz.zhouxy.plusone.system.application.service.command.RegisterAccountCommand; import xyz.zhouxy.plusone.system.application.service.command.RegisterAccountCommand;
import xyz.zhouxy.plusone.system.domain.model.account.Account; import xyz.zhouxy.plusone.system.domain.model.account.Account;
import xyz.zhouxy.plusone.system.domain.model.account.AccountInfo; import xyz.zhouxy.plusone.system.domain.model.account.AccountInfo;
@@ -69,7 +69,7 @@ public class RegisterAccountService {
throw AccountRegisterException.emailAlreadyExists(mobilePhone.value()); throw AccountRegisterException.emailAlreadyExists(mobilePhone.value());
} }
} else { } else {
throw InvalidInputException.unsupportedPrincipalTypeException(); throw new UnsupportedPrincipalTypeException();
} }
verifyService.checkCode(emailOrMobilePhone, command.getCode()); verifyService.checkCode(emailOrMobilePhone, command.getCode());
@@ -78,10 +78,12 @@ public class RegisterAccountService {
Username.of(username), Username.of(username),
email, email,
mobilePhone, mobilePhone,
Password.newPassword(command.getPassword(), command.getPasswordConfirmation()), Password.newPassword(command.getPassword(), command.getReenteredPassword()),
AccountStatus.AVAILABLE, AccountStatus.AVAILABLE,
Set.of(DEFAULT_ROLE_ID), Set.of(DEFAULT_ROLE_ID),
AccountInfo.of(command.getNickname(), command.getAvatar(), command.getSex())); AccountInfo.builder()
.nickname(command.getNickname())
.build());
accountRepository.save(accountToSave); accountRepository.save(accountToSave);
} }
@@ -92,7 +94,7 @@ public class RegisterAccountService {
} else if (principalType == PrincipalType.MOBILE_PHONE) { } else if (principalType == PrincipalType.MOBILE_PHONE) {
verifyService.sendCodeToMobilePhone(MobilePhone.of(principal)); verifyService.sendCodeToMobilePhone(MobilePhone.of(principal));
} else { } else {
throw InvalidInputException.unsupportedPrincipalTypeException(); throw new UnsupportedPrincipalTypeException();
} }
} }
} }

View File

@@ -10,6 +10,8 @@ import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import xyz.zhouxy.plusone.constant.EntityStatus;
import xyz.zhouxy.plusone.exception.DataNotExistException;
import xyz.zhouxy.plusone.system.application.query.RoleQueries; import xyz.zhouxy.plusone.system.application.query.RoleQueries;
import xyz.zhouxy.plusone.system.application.query.params.RoleQueryParams; import xyz.zhouxy.plusone.system.application.query.params.RoleQueryParams;
import xyz.zhouxy.plusone.system.application.query.result.RoleOverview; import xyz.zhouxy.plusone.system.application.query.result.RoleOverview;
@@ -53,7 +55,7 @@ public class RoleManagementService {
Role roleToCreate = Role.newInstance( Role roleToCreate = Role.newInstance(
command.getName(), command.getName(),
command.getIdentifier(), command.getIdentifier(),
command.getStatus(), EntityStatus.of(command.getStatus()),
command.getRemarks(), command.getRemarks(),
menuRefs, menuRefs,
permissionRefs); permissionRefs);
@@ -62,11 +64,11 @@ public class RoleManagementService {
public void updateRole(@Valid UpdateRoleCommand command) { public void updateRole(@Valid UpdateRoleCommand command) {
Long roleId = command.getId(); Long roleId = command.getId();
Role roleToUpdate = _roleRepository.find(roleId); Role roleToUpdate = _roleRepository.find(roleId).orElseThrow(DataNotExistException::new);
roleToUpdate.update( roleToUpdate.update(
command.getName(), command.getName(),
command.getIdentifier(), command.getIdentifier(),
command.getStatus(), EntityStatus.of(command.getStatus()),
command.getRemarks(), command.getRemarks(),
Set.copyOf(_menuRepository.findByIdIn(command.getMenus())), Set.copyOf(_menuRepository.findByIdIn(command.getMenus())),
Set.copyOf(_menuRepository.findPermissionsByIdIn(command.getPermissions()))); Set.copyOf(_menuRepository.findPermissionsByIdIn(command.getPermissions())));
@@ -74,7 +76,7 @@ public class RoleManagementService {
} }
public void delete(Long id) { public void delete(Long id) {
Role role = _roleRepository.find(id); Role role = _roleRepository.find(id).orElseThrow(DataNotExistException::new);
_roleRepository.delete(role); _roleRepository.delete(role);
} }
@@ -85,7 +87,7 @@ public class RoleManagementService {
@Transactional(propagation = Propagation.SUPPORTS) @Transactional(propagation = Propagation.SUPPORTS)
public Role findById(Long id) { public Role findById(Long id) {
return _roleRepository.find(id); return _roleRepository.find(id).orElseThrow(DataNotExistException::new);
} }
@Transactional(propagation = Propagation.SUPPORTS) @Transactional(propagation = Propagation.SUPPORTS)

View File

@@ -0,0 +1,11 @@
package xyz.zhouxy.plusone.system.application.service.command;
import lombok.Data;
import xyz.zhouxy.plusone.system.application.common.util.PrincipalType;
@Data
public class ChangePasswordByOtpCommand {
String account;
String otp;
PrincipalType principalType;
}

View File

@@ -10,10 +10,8 @@ import javax.validation.constraints.Pattern;
import org.hibernate.validator.constraints.URL; import org.hibernate.validator.constraints.URL;
import lombok.Data; import lombok.Data;
import xyz.zhouxy.plusone.constant.RegexConsts; import xyz.zhouxy.plusone.commons.constant.RegexConsts;
import xyz.zhouxy.plusone.domain.ICommand; import xyz.zhouxy.plusone.domain.ICommand;
import xyz.zhouxy.plusone.system.domain.model.account.AccountStatus;
import xyz.zhouxy.plusone.system.domain.model.account.Sex;
/** /**
* 创建账号命令 * 创建账号命令
@@ -41,7 +39,7 @@ public class CreateAccountCommand implements ICommand {
String passwordConfirmation; String passwordConfirmation;
@NotNull @NotNull
AccountStatus status; Integer status;
Set<Long> roleRefs; Set<Long> roleRefs;
@@ -52,5 +50,5 @@ public class CreateAccountCommand implements ICommand {
@URL @URL
String avatar; String avatar;
Sex sex; Integer sex;
} }

View File

@@ -5,7 +5,6 @@ import javax.validation.constraints.NotNull;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import xyz.zhouxy.plusone.constant.EntityStatus;
import xyz.zhouxy.plusone.domain.ICommand; import xyz.zhouxy.plusone.domain.ICommand;
import xyz.zhouxy.plusone.system.domain.model.menu.Menu.MenuType; import xyz.zhouxy.plusone.system.domain.model.menu.Menu.MenuType;
@@ -42,7 +41,7 @@ public class CreateMenuCommand implements ICommand {
private Integer orderNumber; private Integer orderNumber;
@NotNull @NotNull
private EntityStatus status; private Integer status;
private String component; private String component;

View File

@@ -6,7 +6,6 @@ import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull; import javax.validation.constraints.NotNull;
import lombok.Data; import lombok.Data;
import xyz.zhouxy.plusone.constant.EntityStatus;
import xyz.zhouxy.plusone.domain.ICommand; import xyz.zhouxy.plusone.domain.ICommand;
/** /**
@@ -23,7 +22,7 @@ public class CreateRoleCommand implements ICommand {
String identifier; String identifier;
@NotNull @NotNull
EntityStatus status; Integer status;
String remarks; String remarks;
Set<Long> menus; Set<Long> menus;

View File

@@ -2,6 +2,7 @@ package xyz.zhouxy.plusone.system.application.service.command;
import lombok.Data; import lombok.Data;
import xyz.zhouxy.plusone.domain.ICommand; import xyz.zhouxy.plusone.domain.ICommand;
import xyz.zhouxy.plusone.system.application.common.util.PrincipalType;
/** /**
* 登录命令 * 登录命令
@@ -14,6 +15,7 @@ public class LoginByOtpCommand implements ICommand {
String principal; // 邮箱地址 / 手机号 String principal; // 邮箱地址 / 手机号
String otp; // 密码 String otp; // 密码
boolean rememberMe; // 记住我 boolean rememberMe; // 记住我
PrincipalType principalType;
// 进入登陆界面时或刷新验证码时,前端发送图形验证码的请求,后端生成 captcha 并暂存到 redis 中key 为 UUID将图形和 uuid 响应给前端。 // 进入登陆界面时或刷新验证码时,前端发送图形验证码的请求,后端生成 captcha 并暂存到 redis 中key 为 UUID将图形和 uuid 响应给前端。
// String uuid; // 校验码的 key // String uuid; // 校验码的 key

Some files were not shown because too many files have changed in this diff Show More