first commit.
This commit is contained in:
34
plusone-basic/plusone-basic-application/pom.xml
Normal file
34
plusone-basic/plusone-basic-application/pom.xml
Normal file
@@ -0,0 +1,34 @@
|
||||
<?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">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<artifactId>plusone-basic</artifactId>
|
||||
<groupId>xyz.zhouxy</groupId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
<artifactId>plusone-basic-application</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>xyz.zhouxy</groupId>
|
||||
<artifactId>plusone-basic-common</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>xyz.zhouxy</groupId>
|
||||
<artifactId>plusone-basic-domain</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>xyz.zhouxy</groupId>
|
||||
<artifactId>plusone-basic-infrastructure</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 防止XSS攻击的过滤插件 -->
|
||||
<dependency>
|
||||
<groupId>net.dreamlu</groupId>
|
||||
<artifactId>mica-xss</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
@@ -0,0 +1,17 @@
|
||||
package xyz.zhouxy.plusone.exception.config;
|
||||
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import xyz.zhouxy.plusone.exception.handler.BaseExceptionHandler.ExceptionInfoHolder;
|
||||
|
||||
@Configuration
|
||||
public class PlusoneExceptionHandlerConfig {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
ExceptionInfoHolder exceptionInfoHolder() {
|
||||
return new ExceptionInfoHolder();
|
||||
}
|
||||
}
|
@@ -0,0 +1,29 @@
|
||||
package xyz.zhouxy.plusone.exception.handler;
|
||||
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
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.util.RestfulResult;
|
||||
|
||||
/**
|
||||
* 处理所有异常的处理器
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
*/
|
||||
@ConditionalOnProperty(prefix = "plusone.exception", name = "handle-all-exception", havingValue = "true")
|
||||
@RestControllerAdvice
|
||||
@Slf4j
|
||||
public class AllExceptionHandler extends BaseExceptionHandler {
|
||||
protected AllExceptionHandler(ExceptionInfoHolder exceptionInfoHolder) {
|
||||
super(exceptionInfoHolder);
|
||||
}
|
||||
|
||||
@ExceptionHandler(Exception.class)
|
||||
public ResponseEntity<RestfulResult> handleException(Throwable e) {
|
||||
log.error(e.getMessage(), e);
|
||||
return this.buildExceptionResponse(e);
|
||||
}
|
||||
}
|
@@ -0,0 +1,63 @@
|
||||
package xyz.zhouxy.plusone.exception.handler;
|
||||
|
||||
import org.springframework.context.support.DefaultMessageSourceResolvable;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.dao.DataAccessException;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.MethodArgumentNotValidException;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import xyz.zhouxy.plusone.util.RestfulResult;
|
||||
|
||||
/**
|
||||
* 默认异常的处理器
|
||||
*
|
||||
* <p>
|
||||
* 对 {@link IllegalArgumentException} 异常做了写默认处理。
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* 将异常信息返回给前端时,隐藏掉数据库异常信息中关于数据库结构、SQL 语句的描述。
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* 为了避免因为 {@link AllExceptionHandler} 而失效,
|
||||
* {@code Order} 设置为了 {@value Ordered.LOWEST_PRECEDENCE - 1}。
|
||||
* 一般 Spring Bean 的 Order 应该是 Ordered.LOWEST_PRECEDENCE,
|
||||
* 所以如果想让自定义的异常处理器与本处理器有冲突,希望覆盖本处理器的行为,
|
||||
* 需要添加 {@link Order} 注解,将 order 修改为比 {@code Ordered.LOWEST_PRECEDENCE - 1}
|
||||
* 更小的值,
|
||||
* 如 0 或者 1,甚至是负数。
|
||||
* </p>
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
*/
|
||||
@RestControllerAdvice
|
||||
@Order(Ordered.LOWEST_PRECEDENCE - 1)
|
||||
@Slf4j
|
||||
public class DefaultExceptionHandler extends BaseExceptionHandler {
|
||||
|
||||
public DefaultExceptionHandler(ExceptionInfoHolder exceptionInfoHolder) {
|
||||
super(exceptionInfoHolder);
|
||||
set(IllegalArgumentException.class, 4010000, "格式错误", HttpStatus.FORBIDDEN);
|
||||
set(DataAccessException.class, 6030000, "数据库错误", HttpStatus.INTERNAL_SERVER_ERROR, true);
|
||||
set(MethodArgumentNotValidException.class,
|
||||
4040401,
|
||||
e -> ((MethodArgumentNotValidException) e).getAllErrors()
|
||||
.stream()
|
||||
.map(DefaultMessageSourceResolvable::getDefaultMessage)
|
||||
.toList()
|
||||
.toString(),
|
||||
HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
|
||||
@ExceptionHandler(Exception.class)
|
||||
public ResponseEntity<RestfulResult> handleException(Exception e) {
|
||||
log.error(e.getMessage(), e);
|
||||
return buildExceptionResponse(e);
|
||||
}
|
||||
}
|
46
plusone-basic/plusone-basic-common/pom.xml
Normal file
46
plusone-basic/plusone-basic-common/pom.xml
Normal file
@@ -0,0 +1,46 @@
|
||||
<?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">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<artifactId>plusone-basic</artifactId>
|
||||
<groupId>xyz.zhouxy</groupId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
<artifactId>plusone-basic-common</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-core</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-tx</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-webmvc</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>xyz.zhouxy.plusone</groupId>
|
||||
<artifactId>plusone-commons</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>xyz.zhouxy.plusone</groupId>
|
||||
<artifactId>plusone-exception-handler</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
@@ -0,0 +1,31 @@
|
||||
package xyz.zhouxy.plusone.exception;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
|
||||
/**
|
||||
* 需要时,当查询数据不存在时抛出的异常
|
||||
*
|
||||
* <p>
|
||||
* 暂时先这样,后续完善异常体系时可能会更改。
|
||||
* </p>
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
* @see xyz.zhouxy.plusone.util.AssertResult
|
||||
*/
|
||||
@ResponseStatus(HttpStatus.NOT_FOUND)
|
||||
public class DataNotExistException extends PlusoneException {
|
||||
|
||||
@java.io.Serial
|
||||
private static final long serialVersionUID = 6536955800679703111L;
|
||||
|
||||
public static final int ERROR_CODE = 4110100;
|
||||
|
||||
public DataNotExistException() {
|
||||
super(ERROR_CODE, "数据不存在");
|
||||
}
|
||||
|
||||
public DataNotExistException(String message) {
|
||||
super(ERROR_CODE, message);
|
||||
}
|
||||
}
|
@@ -0,0 +1,31 @@
|
||||
package xyz.zhouxy.plusone.exception;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
|
||||
/**
|
||||
* 需要时,当数据操作的行数不符合预期时抛出的异常
|
||||
*
|
||||
* <p>
|
||||
* 暂时先这样,后续完善异常体系时可能会更改。
|
||||
* </p>
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
* @see xyz.zhouxy.plusone.util.AssertResult
|
||||
*/
|
||||
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||
public class DataOperationNumberException extends PlusoneException {
|
||||
|
||||
@java.io.Serial
|
||||
private static final long serialVersionUID = -9220765735990318186L;
|
||||
|
||||
public static final int ERROR_CODE = 4110200;
|
||||
|
||||
public DataOperationNumberException() {
|
||||
super(ERROR_CODE, "数据操作的行数不符合预期");
|
||||
}
|
||||
|
||||
public DataOperationNumberException(String message) {
|
||||
super(ERROR_CODE, message);
|
||||
}
|
||||
}
|
@@ -0,0 +1,56 @@
|
||||
package xyz.zhouxy.plusone.exception;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
|
||||
/**
|
||||
* 4040200 - 无效的用户输入
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
*/
|
||||
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||
public class InvalidInputException extends PlusoneException {
|
||||
|
||||
@java.io.Serial
|
||||
private static final long serialVersionUID = 7956661913360059670L;
|
||||
|
||||
public static final int ERROR_CODE = 4040200;
|
||||
|
||||
private InvalidInputException(int code, String msg) {
|
||||
super(code, msg);
|
||||
}
|
||||
|
||||
private InvalidInputException(int code, Throwable cause) {
|
||||
super(code, cause);
|
||||
}
|
||||
|
||||
private InvalidInputException(int code, String msg, Throwable cause) {
|
||||
super(code, msg, cause);
|
||||
}
|
||||
|
||||
public InvalidInputException(String msg) {
|
||||
this(ERROR_CODE, msg);
|
||||
}
|
||||
|
||||
public InvalidInputException(Throwable cause) {
|
||||
this(ERROR_CODE, cause);
|
||||
}
|
||||
|
||||
public InvalidInputException(String msg, Throwable cause) {
|
||||
this(ERROR_CODE, msg, cause);
|
||||
}
|
||||
|
||||
/**
|
||||
* 不支持的 Principal 类型出现时抛出的异常
|
||||
*/
|
||||
public static InvalidInputException unsupportedPrincipalTypeException() {
|
||||
return unsupportedPrincipalTypeException("不支持的 PrincipalType");
|
||||
}
|
||||
|
||||
/**
|
||||
* 不支持的 Principal 类型出现时抛出的异常
|
||||
*/
|
||||
public static InvalidInputException unsupportedPrincipalTypeException(String message) {
|
||||
return new InvalidInputException(4040201, message);
|
||||
}
|
||||
}
|
@@ -0,0 +1,58 @@
|
||||
package xyz.zhouxy.plusone.exception;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
|
||||
/**
|
||||
* 4040600 - 用户操作异常
|
||||
*
|
||||
* @author ZhouXY
|
||||
*/
|
||||
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||
public class UserOperationException extends PlusoneException {
|
||||
|
||||
@java.io.Serial
|
||||
private static final long serialVersionUID = 4371055414421991940L;
|
||||
|
||||
public static final int DEFAULT_ERROR_CODE = 4040600;
|
||||
|
||||
public UserOperationException(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);
|
||||
}
|
||||
|
||||
public UserOperationException(int code, Throwable cause) {
|
||||
super(code, cause);
|
||||
}
|
||||
|
||||
public UserOperationException(int code, String msg, Throwable cause) {
|
||||
super(code, msg, cause);
|
||||
}
|
||||
|
||||
/**
|
||||
* 无效的操作
|
||||
*
|
||||
* @return 异常对象
|
||||
*/
|
||||
public static UserOperationException invalidOperation() {
|
||||
return invalidOperation("无效的操作");
|
||||
}
|
||||
|
||||
/**
|
||||
* 无效的操作
|
||||
*
|
||||
* @param msg 异常信息
|
||||
* @return 异常对象
|
||||
*/
|
||||
public static UserOperationException invalidOperation(String msg) {
|
||||
return new UserOperationException(4040604, msg);
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,67 @@
|
||||
package xyz.zhouxy.plusone.util;
|
||||
|
||||
|
||||
import xyz.zhouxy.plusone.exception.DataNotExistException;
|
||||
import xyz.zhouxy.plusone.exception.DataOperationNumberException;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* 对数据库执行结果进行判断
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
*/
|
||||
public final class AssertResult {
|
||||
|
||||
private AssertResult() {
|
||||
throw new IllegalStateException("Utility class");
|
||||
}
|
||||
|
||||
public static void update(boolean expression) {
|
||||
if (!expression) {
|
||||
throw new DataOperationNumberException();
|
||||
}
|
||||
}
|
||||
|
||||
public static void update(boolean expression, String message) {
|
||||
if (!expression) {
|
||||
throw new DataOperationNumberException(message);
|
||||
}
|
||||
}
|
||||
|
||||
public static void update(Object i, int expectedValue) {
|
||||
if (!Objects.equals(i, expectedValue)) {
|
||||
throw new DataOperationNumberException();
|
||||
}
|
||||
}
|
||||
|
||||
public static void update(Object i, int expectedValue, String format) {
|
||||
if (!Objects.equals(i, expectedValue)) {
|
||||
throw new DataOperationNumberException(String.format(format, i));
|
||||
}
|
||||
}
|
||||
|
||||
public static void exist(boolean expression) {
|
||||
if (!expression) {
|
||||
throw new DataNotExistException();
|
||||
}
|
||||
}
|
||||
|
||||
public static void exist(boolean expression, String message) {
|
||||
if (!expression) {
|
||||
throw new DataNotExistException(message);
|
||||
}
|
||||
}
|
||||
|
||||
public static void nonNull(Object obj) {
|
||||
if (Objects.isNull(obj)) {
|
||||
throw new DataNotExistException();
|
||||
}
|
||||
}
|
||||
|
||||
public static void nonNull(Object obj, String message) {
|
||||
if (Objects.isNull(obj)) {
|
||||
throw new DataNotExistException(message);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,43 @@
|
||||
package xyz.zhouxy.plusone.util;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* 字符串工具类
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
*/
|
||||
public class PlusoneStrUtil {
|
||||
|
||||
private PlusoneStrUtil() {
|
||||
throw new IllegalStateException("Utility class");
|
||||
}
|
||||
|
||||
public static final String EMPTY_STR = "";
|
||||
|
||||
public static String getTextOrEmpty(String value) {
|
||||
return StringUtils.hasText(value) ? value : EMPTY_STR;
|
||||
}
|
||||
|
||||
public static String getStrOrEmpty(String value) {
|
||||
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) : "";
|
||||
}
|
||||
}
|
39
plusone-basic/plusone-basic-domain/pom.xml
Normal file
39
plusone-basic/plusone-basic-domain/pom.xml
Normal file
@@ -0,0 +1,39 @@
|
||||
<?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">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>xyz.zhouxy</groupId>
|
||||
<artifactId>plusone-basic</artifactId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<artifactId>plusone-basic-domain</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<artifactId>plusone-basic-common</artifactId>
|
||||
<groupId>xyz.zhouxy</groupId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-core</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-core</artifactId>
|
||||
<version>2.13.4</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-annotations</artifactId>
|
||||
<version>2.13.4</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
@@ -0,0 +1,32 @@
|
||||
package xyz.zhouxy.plusone.constant;
|
||||
|
||||
import xyz.zhouxy.plusone.util.Enumeration;
|
||||
import xyz.zhouxy.plusone.util.EnumerationValuesHolder;
|
||||
|
||||
/**
|
||||
* 实体状态
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
*/
|
||||
public class EntityStatus extends Enumeration<EntityStatus> {
|
||||
|
||||
private EntityStatus(int value, String name) {
|
||||
super(value, name);
|
||||
}
|
||||
|
||||
// 常量
|
||||
public static final EntityStatus AVAILABLE = new EntityStatus(0, "正常");
|
||||
public static final EntityStatus DISABLED = new EntityStatus(1, "禁用");
|
||||
|
||||
private static final EnumerationValuesHolder<EntityStatus> ENUMERATION_VALUES = new EnumerationValuesHolder<>(
|
||||
new EntityStatus[] { AVAILABLE, DISABLED });
|
||||
|
||||
public static EntityStatus of(int value) {
|
||||
return ENUMERATION_VALUES.get(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "EntityStatus" + super.toString();
|
||||
}
|
||||
}
|
@@ -0,0 +1,27 @@
|
||||
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");
|
||||
}
|
||||
}
|
@@ -0,0 +1,17 @@
|
||||
package xyz.zhouxy.plusone.domain;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 聚合根
|
||||
*
|
||||
* <p>
|
||||
* 由 Repository 负责整个聚合根的查询、保存、删除。
|
||||
* </p>
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
* @see Entity
|
||||
* @see IRepository
|
||||
*/
|
||||
public abstract class AggregateRoot<ID extends Serializable> extends Entity<ID> {
|
||||
}
|
@@ -0,0 +1,26 @@
|
||||
package xyz.zhouxy.plusone.domain;
|
||||
|
||||
import cn.hutool.core.lang.UUID;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 领域事件
|
||||
*
|
||||
* <p>
|
||||
* <b>根据所使用的消息机制的不同,可能需要继承其它类,或实现 {@link java.io.Serializable} 接口</b>
|
||||
* </p>
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
* @see Entity
|
||||
* @see IEventHandler
|
||||
*/
|
||||
@Getter
|
||||
public abstract class DomainEvent {
|
||||
private String identifier;
|
||||
private long happenedAt;
|
||||
|
||||
protected DomainEvent() {
|
||||
this.identifier = UUID.randomUUID().toString(true);
|
||||
this.happenedAt = System.currentTimeMillis();
|
||||
}
|
||||
}
|
@@ -0,0 +1,32 @@
|
||||
package xyz.zhouxy.plusone.domain;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* 实体
|
||||
*
|
||||
* <p>
|
||||
* DDD 中的实体,带有 ID。
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* 维护一个 {@link DomainEvent} 的列表,持久化时可将其中的领域事件进行发布。
|
||||
* </p>
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
* @see IValueObject
|
||||
* @see AggregateRoot
|
||||
*/
|
||||
public abstract class Entity<ID extends Serializable> {
|
||||
|
||||
private final List<DomainEvent> domainEvents = new ArrayList<>();
|
||||
|
||||
public abstract Optional<ID> getId();
|
||||
|
||||
protected void addDomainEvent(DomainEvent domainEvent) {
|
||||
domainEvents.add(domainEvent);
|
||||
}
|
||||
}
|
@@ -0,0 +1,10 @@
|
||||
package xyz.zhouxy.plusone.domain;
|
||||
|
||||
/**
|
||||
* 命令
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
* @see ICommandHandler
|
||||
*/
|
||||
public interface ICommand {
|
||||
}
|
@@ -0,0 +1,12 @@
|
||||
package xyz.zhouxy.plusone.domain;
|
||||
|
||||
/**
|
||||
* 命令处理器
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
* @see ICommand
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface ICommandHandler<T extends ICommand> {
|
||||
void handle(T command);
|
||||
}
|
@@ -0,0 +1,12 @@
|
||||
package xyz.zhouxy.plusone.domain;
|
||||
|
||||
/**
|
||||
* 事件处理器
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
* @see DomainEvent
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface IEventHandler<E extends DomainEvent> {
|
||||
void handle(E command);
|
||||
}
|
@@ -0,0 +1,21 @@
|
||||
package xyz.zhouxy.plusone.domain;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* Repository 基础接口
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
* @see AggregateRoot
|
||||
*/
|
||||
public interface IRepository<T extends AggregateRoot<ID>, ID extends Serializable> {
|
||||
|
||||
T find(ID id);
|
||||
|
||||
T save(T entity);
|
||||
|
||||
void delete(T entity);
|
||||
|
||||
boolean exists(ID id);
|
||||
|
||||
}
|
@@ -0,0 +1,9 @@
|
||||
package xyz.zhouxy.plusone.domain;
|
||||
|
||||
/**
|
||||
* 值对象
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
*/
|
||||
public interface IValueObject {
|
||||
}
|
@@ -0,0 +1,10 @@
|
||||
package xyz.zhouxy.plusone.domain;
|
||||
|
||||
/**
|
||||
* 带可读的 label 属性
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
*/
|
||||
public interface IWithLabel {
|
||||
String getLabel();
|
||||
}
|
@@ -0,0 +1,33 @@
|
||||
package xyz.zhouxy.plusone.domain;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* 带 orderNumber 字段,可用来排序
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
*/
|
||||
public interface IWithOrderNumber extends Comparable<IWithOrderNumber> {
|
||||
|
||||
int getOrderNumber();
|
||||
|
||||
@Override
|
||||
default int compareTo(IWithOrderNumber that) {
|
||||
return new OrderNumberComparator<IWithOrderNumber>().compare(this, that);
|
||||
}
|
||||
|
||||
class OrderNumberComparator<T extends Comparable<T>>
|
||||
implements Comparator<IWithOrderNumber> {
|
||||
@Override
|
||||
public int compare(IWithOrderNumber a, IWithOrderNumber b) {
|
||||
if (Objects.equals(a, b)) {
|
||||
return 0;
|
||||
}
|
||||
if (a == null) {
|
||||
return -1;
|
||||
}
|
||||
return (b == null) ? 1 : Integer.compare(a.getOrderNumber(), b.getOrderNumber());
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,10 @@
|
||||
package xyz.zhouxy.plusone.domain;
|
||||
|
||||
/**
|
||||
* 带版本号
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
*/
|
||||
public interface IWithVersion {
|
||||
long getVersion();
|
||||
}
|
@@ -0,0 +1,34 @@
|
||||
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;
|
||||
}
|
||||
}
|
@@ -0,0 +1,36 @@
|
||||
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;
|
||||
}
|
||||
}
|
95
plusone-basic/plusone-basic-infrastructure/pom.xml
Normal file
95
plusone-basic/plusone-basic-infrastructure/pom.xml
Normal file
@@ -0,0 +1,95 @@
|
||||
<?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">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>xyz.zhouxy</groupId>
|
||||
<artifactId>plusone-basic</artifactId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<artifactId>plusone-basic-infrastructure</artifactId>
|
||||
|
||||
<dependencies>
|
||||
|
||||
<dependency>
|
||||
<groupId>xyz.zhouxy</groupId>
|
||||
<artifactId>plusone-basic-common</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>xyz.zhouxy</groupId>
|
||||
<artifactId>plusone-basic-domain</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-aop</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.baomidou</groupId>
|
||||
<artifactId>mybatis-plus-boot-starter</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-jdbc</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-redis</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-mail</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-validation</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-devtools</artifactId>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.postgresql</groupId>
|
||||
<artifactId>postgresql</artifactId>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-spring-boot-starter</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-dao-redis-jackson</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-autoconfigure</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-configuration-processor</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.tencentcloudapi</groupId>
|
||||
<artifactId>tencentcloud-sdk-java-sms</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
@@ -0,0 +1,14 @@
|
||||
package xyz.zhouxy.plusone.config;
|
||||
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.PropertySource;
|
||||
|
||||
/**
|
||||
* Plusone 配置。加载 plusone.properties 文件
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
*/
|
||||
@Configuration
|
||||
@PropertySource(value = {"classpath:conf/plusone.properties"}, encoding = "utf-8")
|
||||
public class PlusoneConfig {
|
||||
}
|
@@ -0,0 +1,41 @@
|
||||
package xyz.zhouxy.plusone.infrastructure.pojo;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
|
||||
/**
|
||||
* 实体的持久化对象
|
||||
*
|
||||
* <p>
|
||||
* <i>由于本项目目前是手动实现 Repository,直接将实体持久化,
|
||||
* 查询结果也是直接实例化为 Entity,故暂时不使用 PO。</i>
|
||||
* </p>
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
@ToString
|
||||
public abstract class AbstractEntityPO {
|
||||
protected Long createdBy;
|
||||
protected LocalDateTime createTime;
|
||||
protected Long updatedBy;
|
||||
protected LocalDateTime updateTime;
|
||||
|
||||
public abstract Long getId();
|
||||
|
||||
public void auditFields(Long updatedBy, LocalDateTime updateTime) {
|
||||
this.updatedBy = updatedBy;
|
||||
this.updateTime = updateTime;
|
||||
}
|
||||
|
||||
public void auditFields(Long createdBy, LocalDateTime createTime, Long updatedBy, LocalDateTime updateTime) {
|
||||
this.createdBy = createdBy;
|
||||
this.createTime = createTime;
|
||||
this.updatedBy = updatedBy;
|
||||
this.updateTime = updateTime;
|
||||
}
|
||||
}
|
@@ -0,0 +1,33 @@
|
||||
package xyz.zhouxy.plusone.jdbc;
|
||||
|
||||
import org.springframework.jdbc.core.namedparam.BeanPropertySqlParameterSource;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* 扩展了 {@link BeanPropertySqlParameterSource},在将 POJO 转换为
|
||||
* {@link org.springframework.jdbc.core.namedparam.SqlParameterSource} 时,
|
||||
* 使用 {@link Enum#ordinal()} 将枚举转换成整数。
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
*
|
||||
* @see SqlParameterSource
|
||||
* @see BeanPropertySqlParameterSource
|
||||
* @see org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate
|
||||
* @see Enum
|
||||
*/
|
||||
public class BeanPropertyParamSource extends BeanPropertySqlParameterSource {
|
||||
|
||||
public BeanPropertyParamSource(Object object) {
|
||||
super(object);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public Object getValue(String paramName) throws IllegalArgumentException {
|
||||
Object value = super.getValue(paramName);
|
||||
if (value instanceof Enum) {
|
||||
return ((Enum<?>) value).ordinal();
|
||||
}
|
||||
return value;
|
||||
}
|
||||
}
|
@@ -0,0 +1,51 @@
|
||||
package xyz.zhouxy.plusone.jdbc;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.jdbc.core.convert.JdbcCustomConversions;
|
||||
import org.springframework.data.jdbc.repository.QueryMappingConfiguration;
|
||||
import org.springframework.data.jdbc.repository.config.AbstractJdbcConfiguration;
|
||||
import org.springframework.data.jdbc.repository.config.DefaultQueryMappingConfiguration;
|
||||
|
||||
import xyz.zhouxy.plusone.jdbc.converter.EnumToOrdinalConverter;
|
||||
|
||||
/**
|
||||
* JDBC 配置
|
||||
*
|
||||
* <p>
|
||||
* 配置了类型转换器
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* 这里声明了 {@link DefaultQueryMappingConfiguration} 的实例为 Spring bean。
|
||||
* 在其它配置类中注入该 bean,就可以向其中添加
|
||||
* {@link org.springframework.jdbc.core.RowMapper}
|
||||
* 供 Spring Data JDBC 查询时使用。如下所示:
|
||||
*
|
||||
* <pre>
|
||||
* {@code @Configuration}
|
||||
* class CustomConfig {
|
||||
* public CustomConfig(DefaultQueryMappingConfiguration rowMappers) {
|
||||
* rowMappers.registerRowMapper(Person.class, new PersonRowMapper());
|
||||
* }
|
||||
* }
|
||||
* </pre>
|
||||
* </p>
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
*/
|
||||
@Configuration
|
||||
public class JdbcConfig extends AbstractJdbcConfiguration {
|
||||
|
||||
@Override
|
||||
public JdbcCustomConversions jdbcCustomConversions() {
|
||||
return new JdbcCustomConversions(List.of(EnumToOrdinalConverter.INSTANCE));
|
||||
}
|
||||
|
||||
@Bean
|
||||
QueryMappingConfiguration rowMappers() {
|
||||
return new DefaultQueryMappingConfiguration();
|
||||
}
|
||||
}
|
@@ -0,0 +1,68 @@
|
||||
package xyz.zhouxy.plusone.jdbc;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.List;
|
||||
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.NamedParameterJdbcTemplate;
|
||||
import org.springframework.jdbc.core.namedparam.SqlParameterSource;
|
||||
|
||||
import xyz.zhouxy.plusone.domain.Entity;
|
||||
|
||||
public abstract class JdbcEntityDaoSupport<T extends Entity<ID>, ID extends Serializable> {
|
||||
protected final NamedParameterJdbcTemplate jdbc;
|
||||
|
||||
protected RowMapper<T> rowMapper;
|
||||
protected ResultSetExtractor<T> resultSetExtractor;
|
||||
|
||||
protected JdbcEntityDaoSupport(@Nonnull NamedParameterJdbcTemplate namedParameterJdbcTemplate) {
|
||||
this.jdbc = namedParameterJdbcTemplate;
|
||||
this.rowMapper = (ResultSet rs, int rowNum) -> mapRow(rs);
|
||||
this.resultSetExtractor = (ResultSet rs) -> rs.next() ? mapRow(rs) : null;
|
||||
}
|
||||
|
||||
protected final T queryForObject(String sql) {
|
||||
return this.jdbc.query(sql, this.resultSetExtractor);
|
||||
}
|
||||
|
||||
protected final T queryForObject(String sql, SqlParameterSource paramSource) {
|
||||
return this.jdbc.query(sql, paramSource, this.resultSetExtractor);
|
||||
}
|
||||
|
||||
protected final List<T> queryForList(String sql) {
|
||||
return this.jdbc.query(sql, this.rowMapper);
|
||||
}
|
||||
|
||||
protected final List<T> queryForList(String sql, SqlParameterSource parameterSource) {
|
||||
return this.jdbc.query(sql, parameterSource, this.rowMapper);
|
||||
}
|
||||
|
||||
protected final Stream<T> queryForStream(String sql, SqlParameterSource parameterSource) {
|
||||
return this.jdbc.queryForStream(sql, parameterSource, this.rowMapper);
|
||||
}
|
||||
|
||||
protected final <E> Stream<E> queryForStream(String sql, SqlParameterSource parameterSource, Class<E> elementType) {
|
||||
return this.jdbc.queryForList(sql, parameterSource, elementType).stream();
|
||||
}
|
||||
|
||||
protected final boolean queryExists(String sql, SqlParameterSource parameterSource) {
|
||||
Boolean isExists = this.jdbc.query(sql, parameterSource, ResultSet::next);
|
||||
return Boolean.TRUE.equals(isExists);
|
||||
}
|
||||
|
||||
protected abstract T mapRow(ResultSet rs) throws SQLException;
|
||||
|
||||
protected void setRowMapper(@Nonnull RowMapper<T> rowMapper) {
|
||||
this.rowMapper = rowMapper;
|
||||
}
|
||||
|
||||
protected void setResultSetExtractor(@Nonnull ResultSetExtractor<T> resultSetExtractor) {
|
||||
this.resultSetExtractor = resultSetExtractor;
|
||||
}
|
||||
}
|
@@ -0,0 +1,30 @@
|
||||
package xyz.zhouxy.plusone.jdbc;
|
||||
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
|
||||
|
||||
import xyz.zhouxy.plusone.spring.SpringContextHolder;
|
||||
|
||||
/**
|
||||
* 全局单例,由 Spring 装配好的对象。
|
||||
* 可通过静态方法获取 Spring 容器中的 {@link JdbcTemplate} 和
|
||||
* {@link NamedParameterJdbcTemplate} 对象。
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
* @see JdbcTemplate
|
||||
* @see NamedParameterJdbcTemplate
|
||||
*/
|
||||
public final class JdbcFactory {
|
||||
|
||||
private JdbcFactory() {
|
||||
throw new IllegalStateException("Utility class");
|
||||
}
|
||||
|
||||
public static JdbcTemplate getJdbcTemplate() {
|
||||
return SpringContextHolder.getContext().getBean(JdbcTemplate.class);
|
||||
}
|
||||
|
||||
public static NamedParameterJdbcTemplate getNamedParameterJdbcTemplate() {
|
||||
return SpringContextHolder.getContext().getBean(NamedParameterJdbcTemplate.class);
|
||||
}
|
||||
}
|
@@ -0,0 +1,58 @@
|
||||
package xyz.zhouxy.plusone.jdbc;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
|
||||
import org.springframework.jdbc.core.namedparam.SqlParameterSource;
|
||||
|
||||
import xyz.zhouxy.plusone.domain.AggregateRoot;
|
||||
import xyz.zhouxy.plusone.domain.IRepository;
|
||||
|
||||
public abstract class JdbcRepositorySupport<T extends AggregateRoot<ID>, ID extends Serializable>
|
||||
extends JdbcEntityDaoSupport<T, ID>
|
||||
implements IRepository<T, ID> {
|
||||
|
||||
protected JdbcRepositorySupport(@Nonnull NamedParameterJdbcTemplate namedParameterJdbcTemplate) {
|
||||
super(namedParameterJdbcTemplate);
|
||||
}
|
||||
|
||||
protected abstract void doDelete(@Nonnull T entity);
|
||||
|
||||
protected abstract T doFindById(@Nonnull ID id);
|
||||
|
||||
protected abstract T doInsert(@Nonnull T entity);
|
||||
|
||||
protected abstract T doUpdate(@Nonnull T entity);
|
||||
|
||||
@Override
|
||||
public final void delete(T entity) {
|
||||
if (entity == null) {
|
||||
throw new IllegalArgumentException("Cannot delete null.");
|
||||
}
|
||||
doDelete(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final T find(ID id) {
|
||||
if (id == null) {
|
||||
throw new IllegalArgumentException("Id cannot be null.");
|
||||
}
|
||||
return doFindById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final T save(T entity) {
|
||||
if (entity == null) {
|
||||
throw new IllegalArgumentException("Cannot save null.");
|
||||
}
|
||||
return entity.getId().isPresent() ? doUpdate(entity) : doInsert(entity);
|
||||
}
|
||||
|
||||
protected abstract SqlParameterSource generateParamSource(ID id, @Nonnull T entity);
|
||||
|
||||
protected final SqlParameterSource generateParamSource(@Nonnull T entity) {
|
||||
return generateParamSource(entity.getId().orElseThrow(), entity);
|
||||
}
|
||||
}
|
@@ -0,0 +1,45 @@
|
||||
package xyz.zhouxy.plusone.jdbc.common;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
|
||||
/**
|
||||
* 查询结果处理
|
||||
*
|
||||
* <p>
|
||||
* 通过在 {@link #map(ResultSet)} 中配置 {@link ResultSet} 到对象的映射,
|
||||
* 可将 {@link #rowMapper(ResultSet, int)} 的方法应用,
|
||||
* 直接当成 {@link org.springframework.jdbc.core.RowMapper} 对象传给
|
||||
* {@link org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate}
|
||||
* 的查询方法,
|
||||
* 或者在 Spring Data JDBC 的配置中使用。
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* 在
|
||||
* {@link org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate}
|
||||
* 的 query(String, java.util.Map, ResultSetExtractor)
|
||||
* 和 query(String, SqlParameterSource, ResultSetExtractor)
|
||||
* 两个方法执行时,ResultSetExtractor 中需要执行一次 {@link ResultSet#next()} 判断是否查询到数据,
|
||||
* 如果查询到数据再执行查询结果的实例化。
|
||||
* {@link #resultSetExtractor(ResultSet)} 封装了这个过程,
|
||||
* 和 {@link #rowMapper(ResultSet, int)}一样,
|
||||
* 直接把方法引用当成 {@link org.springframework.jdbc.core.ResultSetExtractor} 的对象即可。
|
||||
* </p>
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
* @see org.springframework.data.jdbc.repository.config.DefaultQueryMappingConfiguration
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface ResultMapper<T> {
|
||||
|
||||
T map(ResultSet resultSet) throws SQLException;
|
||||
|
||||
default T rowMapper(ResultSet resultSet, int rowNum) throws SQLException {
|
||||
return map(resultSet);
|
||||
}
|
||||
|
||||
default T resultSetExtractor(ResultSet resultSet) throws SQLException {
|
||||
return resultSet.next() ? map(resultSet) : null;
|
||||
}
|
||||
}
|
@@ -0,0 +1,30 @@
|
||||
package xyz.zhouxy.plusone.jdbc.common;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
|
||||
import org.springframework.jdbc.core.BeanPropertyRowMapper;
|
||||
import org.springframework.jdbc.core.RowMapper;
|
||||
|
||||
public class SimpleResultMapper<T> implements ResultMapper<T> {
|
||||
|
||||
private final RowMapper<T> rowMapper;
|
||||
|
||||
public SimpleResultMapper(RowMapper<T> rowMapper) {
|
||||
this.rowMapper = rowMapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T map(ResultSet resultSet) throws SQLException {
|
||||
return rowMapper.mapRow(resultSet, 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public T rowMapper(ResultSet resultSet, int rowNum) throws SQLException {
|
||||
return this.rowMapper.mapRow(resultSet, rowNum);
|
||||
}
|
||||
|
||||
public static <T> SimpleResultMapper<T> of(Class<T> clazz) {
|
||||
return new SimpleResultMapper<>(new BeanPropertyRowMapper<>(clazz));
|
||||
}
|
||||
}
|
@@ -0,0 +1,20 @@
|
||||
package xyz.zhouxy.plusone.jdbc.converter;
|
||||
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.data.convert.WritingConverter;
|
||||
|
||||
/**
|
||||
* 枚举序号转换器
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
*/
|
||||
@WritingConverter
|
||||
public enum EnumToOrdinalConverter implements Converter<Enum<?>, Integer> {
|
||||
INSTANCE
|
||||
;
|
||||
|
||||
@Override
|
||||
public Integer convert(Enum<?> source) {
|
||||
return source.ordinal();
|
||||
}
|
||||
}
|
@@ -0,0 +1,44 @@
|
||||
package xyz.zhouxy.plusone.jdbc.converter;
|
||||
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.data.convert.ReadingConverter;
|
||||
|
||||
/**
|
||||
* 序号枚举转换器
|
||||
* <p>
|
||||
* 向 {@link org.springframework.data.jdbc.core.convert.JdbcCustomConversions}
|
||||
* 中添加类型转换器,
|
||||
* 如:
|
||||
*
|
||||
* <pre>
|
||||
* return new JdbcCustomConversions(List.of(new OrdinalToEnumConverter<SystemStatus>()));
|
||||
* </pre>
|
||||
* </p>
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
*
|
||||
* @see Converter
|
||||
* @see org.springframework.data.jdbc.core.convert.JdbcCustomConversions
|
||||
*/
|
||||
@ReadingConverter
|
||||
public class OrdinalToEnumConverter<E extends Enum<E>> implements Converter<Integer, Enum<E>> {
|
||||
|
||||
private final Class<E> type;
|
||||
private final E[] constants;
|
||||
|
||||
public OrdinalToEnumConverter(Class<E> type) {
|
||||
this.type = type;
|
||||
this.constants = type.getEnumConstants();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Enum<E> convert(Integer ordinal) {
|
||||
try {
|
||||
return constants[ordinal];
|
||||
} catch (ArrayIndexOutOfBoundsException exception) {
|
||||
throw new IllegalArgumentException(
|
||||
String.format("Cannot convert %d to %s by ordinal value.", ordinal, type.getSimpleName()),
|
||||
exception);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,31 @@
|
||||
package xyz.zhouxy.plusone.mail;
|
||||
|
||||
import org.springframework.mail.SimpleMailMessage;
|
||||
|
||||
/**
|
||||
* 构建 {@link SimpleMailMessage}
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
*/
|
||||
public class MailMessageFactory {
|
||||
private final PlusoneMailProperties mailProperties;
|
||||
|
||||
public MailMessageFactory(PlusoneMailProperties mailProperties) {
|
||||
this.mailProperties = mailProperties;
|
||||
}
|
||||
|
||||
public SimpleMailMessage getCodeMailMessage(String code, String to) {
|
||||
String subject = mailProperties.getSubject().get("code");
|
||||
String content = String.format(mailProperties.getTemplate().get("code"), code);
|
||||
return getSimpleMailMessage(to, subject, content);
|
||||
}
|
||||
|
||||
public SimpleMailMessage getSimpleMailMessage(String to, String subject, String content) {
|
||||
SimpleMailMessage simpleMailMessage = new SimpleMailMessage();
|
||||
simpleMailMessage.setFrom(mailProperties.getFrom());
|
||||
simpleMailMessage.setTo(to);
|
||||
simpleMailMessage.setSubject(subject);
|
||||
simpleMailMessage.setText(content);
|
||||
return simpleMailMessage;
|
||||
}
|
||||
}
|
@@ -0,0 +1,17 @@
|
||||
package xyz.zhouxy.plusone.mail;
|
||||
|
||||
import javax.mail.MessagingException;
|
||||
|
||||
/**
|
||||
* 邮件服务
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
*/
|
||||
public interface MailService {
|
||||
|
||||
void sendSimpleMail(String to, String subject, String content);
|
||||
|
||||
void sendHtmlMail(String to, String subject, String content) throws MessagingException;
|
||||
|
||||
void sendCodeMail(String code, String to);
|
||||
}
|
@@ -0,0 +1,26 @@
|
||||
package xyz.zhouxy.plusone.mail;
|
||||
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
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.mail.javamail.JavaMailSender;
|
||||
|
||||
/**
|
||||
* 读取配置文件,实例化 {@link MailService} 对象后,放到 Spring 容器中。
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
*/
|
||||
@Configuration
|
||||
@EnableConfigurationProperties(PlusoneMailProperties.class)
|
||||
@ConditionalOnClass(MailService.class)
|
||||
@EnableAutoConfiguration
|
||||
public class PlusoneMailAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
public MailService mailService(JavaMailSender mailSender, PlusoneMailProperties mailProperties) {
|
||||
MailMessageFactory mailMessageFactory = new MailMessageFactory(mailProperties);
|
||||
return new SimpleMailService(mailSender, mailMessageFactory);
|
||||
}
|
||||
}
|
@@ -0,0 +1,16 @@
|
||||
package xyz.zhouxy.plusone.mail;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@ConfigurationProperties("plusone.mail")
|
||||
public class PlusoneMailProperties {
|
||||
private String from;
|
||||
private Map<String, String> subject;
|
||||
private Map<String, String> template;
|
||||
}
|
@@ -0,0 +1,57 @@
|
||||
package xyz.zhouxy.plusone.mail;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.mail.MailException;
|
||||
import org.springframework.mail.SimpleMailMessage;
|
||||
import org.springframework.mail.javamail.JavaMailSender;
|
||||
|
||||
import javax.mail.MessagingException;
|
||||
|
||||
/**
|
||||
* 邮件服务。<b>不能发送 HTML 邮件。</b>
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
*/
|
||||
@Slf4j
|
||||
public class SimpleMailService implements MailService {
|
||||
|
||||
private final JavaMailSender mailSender;
|
||||
private final MailMessageFactory mailMessageFactory;
|
||||
|
||||
public SimpleMailService(JavaMailSender mailSender, MailMessageFactory mailMessageFactory) {
|
||||
this.mailSender = mailSender;
|
||||
this.mailMessageFactory = mailMessageFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送文本邮件
|
||||
*/
|
||||
@Override
|
||||
public void sendSimpleMail(String to, String subject, String content) {
|
||||
SimpleMailMessage message = mailMessageFactory.getSimpleMailMessage(to, subject, content);
|
||||
try {
|
||||
mailSender.send(message);
|
||||
log.debug("简单邮件已经发送。");
|
||||
} catch (MailException e) {
|
||||
log.error("发送简单邮件时发生异常!", e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendHtmlMail(String to, String subject, String content) throws MessagingException {
|
||||
throw new UnsupportedOperationException("暂不支持");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendCodeMail(String code, String to) {
|
||||
SimpleMailMessage message = mailMessageFactory.getCodeMailMessage(code, to);
|
||||
try {
|
||||
mailSender.send(message);
|
||||
log.debug("简单邮件已经发送。");
|
||||
} catch (MailException e) {
|
||||
log.error("发送简单邮件时发生异常!", e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,17 @@
|
||||
package xyz.zhouxy.plusone.mybatis;
|
||||
|
||||
import org.apache.ibatis.session.SqlSessionFactory;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration
|
||||
@EnableAutoConfiguration
|
||||
public class MyBatisAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
MybatisUtil mybatisUtil(SqlSessionFactory sqlSessionFactory) {
|
||||
return MybatisUtil.getInstance()
|
||||
.setSqlSessionFactory(sqlSessionFactory);
|
||||
}
|
||||
}
|
@@ -0,0 +1,19 @@
|
||||
package xyz.zhouxy.plusone.mybatis;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
|
||||
import com.baomidou.mybatisplus.extension.plugins.inner.BlockAttackInnerInterceptor;
|
||||
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration
|
||||
public class MyBatisPlusConfig {
|
||||
@Bean
|
||||
public MybatisPlusInterceptor mybatisPlusInterceptor() {
|
||||
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
|
||||
interceptor.addInnerInterceptor(new BlockAttackInnerInterceptor());
|
||||
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
|
||||
return interceptor;
|
||||
}
|
||||
}
|
@@ -0,0 +1,28 @@
|
||||
package xyz.zhouxy.plusone.mybatis;
|
||||
|
||||
import org.apache.ibatis.session.SqlSessionFactory;
|
||||
|
||||
public final class MybatisUtil {
|
||||
|
||||
private SqlSessionFactory sqlSessionFactory;
|
||||
|
||||
private MybatisUtil() {
|
||||
}
|
||||
|
||||
MybatisUtil setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
|
||||
this.sqlSessionFactory = sqlSessionFactory;
|
||||
return this;
|
||||
}
|
||||
|
||||
private static final class Holder {
|
||||
private static final MybatisUtil INSTANCE = new MybatisUtil();
|
||||
}
|
||||
|
||||
public static MybatisUtil getInstance() {
|
||||
return Holder.INSTANCE;
|
||||
}
|
||||
|
||||
public static SqlSessionFactory getSqlSessionFactory() {
|
||||
return MybatisUtil.getInstance().sqlSessionFactory;
|
||||
}
|
||||
}
|
@@ -0,0 +1,42 @@
|
||||
package xyz.zhouxy.plusone.redis;
|
||||
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.data.redis.core.ValueOperations;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* 使用 Redis 存放临时字符串
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
*/
|
||||
@Repository
|
||||
public class RedisStrCacheDAO implements StrCacheDAO {
|
||||
|
||||
private final StringRedisTemplate template;
|
||||
|
||||
public RedisStrCacheDAO(StringRedisTemplate template) {
|
||||
this.template = template;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValue(String key, String value, long timeout, TimeUnit unit) {
|
||||
ValueOperations<String, String> ops = template.opsForValue();
|
||||
ops.set(key, value, timeout, unit);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getValue(String key) {
|
||||
ValueOperations<String, String> ops = this.template.opsForValue();
|
||||
return ops.get(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getValueAndDelete(String key) {
|
||||
ValueOperations<String, String> ops = this.template.opsForValue();
|
||||
String value = ops.get(key);
|
||||
template.delete(key);
|
||||
return value;
|
||||
}
|
||||
}
|
@@ -0,0 +1,17 @@
|
||||
package xyz.zhouxy.plusone.redis;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* 字符串缓存接口
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
*/
|
||||
public interface StrCacheDAO {
|
||||
|
||||
void setValue(String key, String value, long timeout, TimeUnit unit);
|
||||
|
||||
String getValue(String key);
|
||||
|
||||
String getValueAndDelete(String key);
|
||||
}
|
@@ -0,0 +1,27 @@
|
||||
package xyz.zhouxy.plusone.sms;
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* SMS 相关配置
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
*/
|
||||
@Configuration
|
||||
@EnableConfigurationProperties(value = {
|
||||
SmsProperties.class,
|
||||
SmsCredentialProperties.class,
|
||||
SmsClientProperties.class,
|
||||
SmsHttpProperties.class,
|
||||
SmsProxyProperties.class})
|
||||
@ConditionalOnClass(SmsService.class)
|
||||
public class PlusoneSmsAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
public SmsService smsService(SmsProperties smsProperties) {
|
||||
return new TencentSmsServiceImpl(smsProperties);
|
||||
}
|
||||
}
|
@@ -0,0 +1,51 @@
|
||||
package xyz.zhouxy.plusone.sms;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* SMS 相关参数
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
*/
|
||||
@Data
|
||||
@ConfigurationProperties("plusone.sms")
|
||||
public class SmsProperties {
|
||||
private String region;
|
||||
private SmsCredentialProperties credential;
|
||||
private SmsClientProperties client;
|
||||
private String appId;
|
||||
private Map<String, String> templates;
|
||||
}
|
||||
|
||||
@Data
|
||||
@ConfigurationProperties("plusone.sms.credential")
|
||||
class SmsCredentialProperties {
|
||||
private String secretId;
|
||||
private String secretKey;
|
||||
}
|
||||
|
||||
@Data
|
||||
@ConfigurationProperties("plusone.sms.client")
|
||||
class SmsClientProperties {
|
||||
private String signMethod;
|
||||
private SmsHttpProperties http;
|
||||
}
|
||||
|
||||
@Data
|
||||
@ConfigurationProperties("plusone.sms.client.http")
|
||||
class SmsHttpProperties {
|
||||
private SmsProxyProperties proxy;
|
||||
private String reqMethod;
|
||||
private Integer connTimeout;
|
||||
}
|
||||
|
||||
@Data
|
||||
@ConfigurationProperties("plusone.sms.client.http.proxy")
|
||||
class SmsProxyProperties {
|
||||
private String host;
|
||||
private Integer port;
|
||||
}
|
@@ -0,0 +1,13 @@
|
||||
package xyz.zhouxy.plusone.sms;
|
||||
|
||||
/**
|
||||
* SMS 服务
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
*/
|
||||
public interface SmsService {
|
||||
|
||||
void sendCodeMessage(String code, String phoneNumber);
|
||||
|
||||
void sendCodeMessage(String signName, String code, String phoneNumber);
|
||||
}
|
@@ -0,0 +1,145 @@
|
||||
package xyz.zhouxy.plusone.sms;
|
||||
|
||||
import com.tencentcloudapi.common.Credential;
|
||||
import com.tencentcloudapi.common.exception.TencentCloudSDKException;
|
||||
import com.tencentcloudapi.common.profile.ClientProfile;
|
||||
import com.tencentcloudapi.common.profile.HttpProfile;
|
||||
import com.tencentcloudapi.sms.v20210111.SmsClient;
|
||||
import com.tencentcloudapi.sms.v20210111.models.SendSmsRequest;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* 使用腾讯 SMS 服务
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
*/
|
||||
@Service
|
||||
@Slf4j
|
||||
public class TencentSmsServiceImpl implements SmsService {
|
||||
|
||||
private final SmsProperties properties;
|
||||
private final Credential credential;
|
||||
|
||||
public TencentSmsServiceImpl(SmsProperties properties) {
|
||||
this.properties = properties;
|
||||
SmsCredentialProperties smsCredential = properties.getCredential();
|
||||
this.credential = new Credential(smsCredential.getSecretId(), smsCredential.getSecretKey());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendCodeMessage(String code, String phoneNumber) {
|
||||
sendCodeMessage("ZhouXY", code, phoneNumber);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendCodeMessage(String signName, String code, String phoneNumber) {
|
||||
String[] phoneNums = {"+86" + phoneNumber};
|
||||
sendMessage(signName, "code", phoneNums, code, "10");
|
||||
}
|
||||
|
||||
public void sendMessage(String signName, String action, String[] phoneNumberSet, String... templateParamSet) {
|
||||
try {
|
||||
|
||||
SmsClient client = getClient();
|
||||
SendSmsRequest req = getSendSmsRequest(signName,
|
||||
properties.getTemplates().get(action),
|
||||
phoneNumberSet,
|
||||
templateParamSet);
|
||||
|
||||
/*
|
||||
* 通过 client 对象调用 SendSms 方法发起请求。注意请求方法名与请求对象是对应的 返回的 res 是一个
|
||||
* SendSmsResponse 类的实例,与请求对象对应
|
||||
*/
|
||||
// var res = client.SendSms(req);
|
||||
client.SendSms(req);
|
||||
|
||||
// 输出json格式的字符串回包
|
||||
// System.out.println(SendSmsResponse.toJsonString(res));
|
||||
|
||||
// 也可以取出单个值,你可以通过官网接口文档或跳转到response对象的定义处查看返回字段的定义
|
||||
// System.out.println(res.getRequestId());
|
||||
|
||||
} catch (TencentCloudSDKException e) {
|
||||
log.error(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private SendSmsRequest getSendSmsRequest(String signName,
|
||||
String templateId,
|
||||
String[] phoneNumberSet,
|
||||
String... templateParamSet) {
|
||||
SendSmsRequest req = new SendSmsRequest();
|
||||
|
||||
String sdkAppId = properties.getAppId();
|
||||
req.setSmsSdkAppId(sdkAppId);
|
||||
req.setSignName(signName);
|
||||
|
||||
/* 国际/港澳台短信 SenderId: 国内短信填空,默认未开通,如需开通请联系 [sms helper] */
|
||||
String senderId = "";
|
||||
req.setSenderId(senderId);
|
||||
|
||||
/* 用户的 session 内容: 可以携带用户侧 ID 等上下文信息,server 会原样返回 */
|
||||
// String sessionContext = "xxx";
|
||||
// req.setSessionContext(sessionContext);
|
||||
|
||||
/* 短信号码扩展号: 默认未开通,如需开通请联系 [sms helper] */
|
||||
String extendCode = "";
|
||||
req.setExtendCode(extendCode);
|
||||
|
||||
/* 模板 ID: 必须填写已审核通过的模板 ID。模板ID可登录 [短信控制台] 查看 */
|
||||
req.setTemplateId(templateId);
|
||||
|
||||
/*
|
||||
* 下发手机号码,采用 E.164 标准,+[国家或地区码][手机号] 示例如:+8613711112222, 其中前面有一个+号
|
||||
* ,86为国家码,13711112222为手机号,最多不要超过200个手机号
|
||||
*/
|
||||
req.setPhoneNumberSet(phoneNumberSet);
|
||||
|
||||
/* 模板参数: 若无模板参数,则设置为空 */
|
||||
req.setTemplateParamSet(templateParamSet);
|
||||
return req;
|
||||
}
|
||||
|
||||
private SmsClient getClient() {
|
||||
String region = properties.getRegion();
|
||||
ClientProfile clientProfile = getClientProfile();
|
||||
return new SmsClient(credential, region, clientProfile);
|
||||
}
|
||||
|
||||
private ClientProfile getClientProfile() {
|
||||
HttpProfile httpProfile = getHttpProfile();
|
||||
/*
|
||||
* 非必要步骤: 实例化一个客户端配置对象,可以指定超时时间等配置
|
||||
*/
|
||||
ClientProfile clientProfile = new ClientProfile();
|
||||
String signMethod = properties.getClient().getSignMethod();
|
||||
if (signMethod != null) {
|
||||
clientProfile.setSignMethod(signMethod);
|
||||
} else {
|
||||
clientProfile.setSignMethod("HmacSHA256");
|
||||
}
|
||||
clientProfile.setHttpProfile(httpProfile);
|
||||
return clientProfile;
|
||||
}
|
||||
|
||||
private HttpProfile getHttpProfile() {
|
||||
HttpProfile httpProfile = new HttpProfile();
|
||||
SmsHttpProperties smsHttp = properties.getClient().getHttp();
|
||||
if (smsHttp != null) {
|
||||
if (smsHttp.getReqMethod() != null) {
|
||||
httpProfile.setReqMethod(smsHttp.getReqMethod());
|
||||
}
|
||||
if (smsHttp.getConnTimeout() != null) {
|
||||
httpProfile.setConnTimeout(60);
|
||||
}
|
||||
SmsProxyProperties proxy = smsHttp.getProxy();
|
||||
if (proxy != null && proxy.getHost() != null && proxy.getPort() != null) {// 设置代理
|
||||
httpProfile.setProxyHost(proxy.getHost());
|
||||
httpProfile.setProxyPort(proxy.getPort());
|
||||
}
|
||||
}
|
||||
return httpProfile;
|
||||
}
|
||||
}
|
@@ -0,0 +1,28 @@
|
||||
package xyz.zhouxy.plusone.spring;
|
||||
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
public class SpringContextHolder {
|
||||
|
||||
private ApplicationContext context;
|
||||
|
||||
private static final SpringContextHolder INSTANCE = new SpringContextHolder();
|
||||
|
||||
private SpringContextHolder() {
|
||||
}
|
||||
|
||||
public static ApplicationContext getContext() {
|
||||
return INSTANCE.context;
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class SpringContextHolderConfig {
|
||||
@Bean(name = "springContextHolder")
|
||||
SpringContextHolder getSpringContextHolder(ApplicationContext context) {
|
||||
SpringContextHolder.INSTANCE.context = context;
|
||||
return SpringContextHolder.INSTANCE;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,34 @@
|
||||
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;
|
||||
}
|
||||
}
|
@@ -0,0 +1,72 @@
|
||||
package xyz.zhouxy.plusone.validator;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import xyz.zhouxy.plusone.exception.InvalidInputException;
|
||||
|
||||
/**
|
||||
* 校验器
|
||||
*
|
||||
* <p>可以使用以下方式初始化一个校验器:</p>
|
||||
*
|
||||
* <pre>
|
||||
* BaseValidator<Integer> validator = new BaseValidator<>() {
|
||||
* {
|
||||
* ruleFor(value -> Objects.nonNull(value), "value 不能为空");
|
||||
* ruleFor(value -> (value >= 0 && value <= 500), "value 应在 [0, 500] 内");
|
||||
* }
|
||||
* };
|
||||
* </pre>
|
||||
*
|
||||
* <p>也可以通过继承本类,定义一个校验器(可使用单例模式)。</p>
|
||||
*
|
||||
* <p>
|
||||
* 然后通过校验器的 {@link #validate} 方法,或
|
||||
* {@link ValidateUtil#validate(Object, Validator)} 对指定对象进行校验。
|
||||
* </p>
|
||||
*
|
||||
* <pre>
|
||||
* ValidateUtil.validate(255, validator);
|
||||
* </pre>
|
||||
*
|
||||
* <pre>
|
||||
* validator.validate(666);
|
||||
* </pre>
|
||||
* </p>
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
* @see IValidateRequired
|
||||
* @see ValidateUtil
|
||||
* @see Validator
|
||||
*/
|
||||
public abstract class BaseValidator<T> {
|
||||
|
||||
private final List<RuleInfo<T>> rules = new ArrayList<>();
|
||||
|
||||
protected BaseValidator() {
|
||||
}
|
||||
|
||||
protected final void ruleFor(Predicate<T> rule, String errorMessage) {
|
||||
this.rules.add(new RuleInfo<>(rule, errorMessage));
|
||||
}
|
||||
|
||||
public void validate(T obj) {
|
||||
this.rules.forEach((RuleInfo<T> ruleInfo) -> {
|
||||
if (!ruleInfo.rule.test(obj)) {
|
||||
throw new InvalidInputException(ruleInfo.message);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected static class RuleInfo<T> {
|
||||
Predicate<T> rule;
|
||||
String message;
|
||||
|
||||
public RuleInfo(Predicate<T> rule, String message) {
|
||||
this.rule = rule;
|
||||
this.message = message;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,129 @@
|
||||
package xyz.zhouxy.plusone.validator;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import cn.hutool.core.exceptions.ValidateException;
|
||||
import lombok.AllArgsConstructor;
|
||||
import xyz.zhouxy.plusone.constant.RegexConsts;
|
||||
import xyz.zhouxy.plusone.util.RegexUtil;
|
||||
|
||||
public abstract class BaseValidator2<T> {
|
||||
|
||||
private List<ValidValueHolder<T, ?>> hs = new ArrayList<>();
|
||||
|
||||
protected final <R> ValidValueHolder<T, R> ruleFor(Function<T, R> getter) {
|
||||
ValidValueHolder<T, R> validValueHolder = new ValidValueHolder<>(getter);
|
||||
hs.add(validValueHolder);
|
||||
return validValueHolder;
|
||||
}
|
||||
|
||||
public void validate(T obj) {
|
||||
for (var holder : hs) {
|
||||
var value = holder.getter.apply(obj);
|
||||
for (var rule : holder.rules) {
|
||||
if (!rule.condition.test(value)) {
|
||||
throw new ValidateException(rule.errMsg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ValidValueHolder<T, R> {
|
||||
Function<T, R> getter;
|
||||
|
||||
List<RuleInfo<Object>> rules = new ArrayList<>();
|
||||
|
||||
public ValidValueHolder(Function<T, R> getter) {
|
||||
this.getter = getter;
|
||||
}
|
||||
|
||||
private void addRule(Predicate<Object> condition, String errMsg) {
|
||||
this.rules.add(new RuleInfo<>(condition, errMsg));
|
||||
}
|
||||
|
||||
public ValidValueHolder<T, R> nonNull(String errMsg) {
|
||||
addRule(Objects::nonNull, errMsg);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ValidValueHolder<T, R> nonEmpty(String errMsg) {
|
||||
addRule(value -> {
|
||||
if (value == null) {
|
||||
return false;
|
||||
}
|
||||
if (value instanceof Collection) {
|
||||
return ((Collection<?>) value).isEmpty();
|
||||
}
|
||||
if (value instanceof String) {
|
||||
return ((String) value).isEmpty();
|
||||
}
|
||||
return false;
|
||||
}, errMsg);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ValidValueHolder<T, R> size(int min, int max, String errMsg) {
|
||||
addRule(value -> {
|
||||
if (value == null) {
|
||||
return false;
|
||||
}
|
||||
if (value instanceof Collection) {
|
||||
int size = ((Collection<?>) value).size();
|
||||
return size >= min && size <= max;
|
||||
}
|
||||
return true;
|
||||
}, errMsg);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ValidValueHolder<T, R> length(int min, int max, String errMsg) {
|
||||
addRule(value -> {
|
||||
if (value == null) {
|
||||
return false;
|
||||
}
|
||||
if (value instanceof String) {
|
||||
int length = ((String) value).length();
|
||||
return length >= min && length <= max;
|
||||
}
|
||||
return true;
|
||||
}, errMsg);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ValidValueHolder<T, R> matches(String regex, String errMsg) {
|
||||
addRule(input -> RegexUtil.matches(regex, (String) input), errMsg);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ValidValueHolder<T, R> matchesOr(String[] regexs, String errMsg) {
|
||||
addRule(input -> RegexUtil.matchesOr((String) input, regexs), errMsg);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ValidValueHolder<T, R> matchesAnd(String[] regexs, String errMsg) {
|
||||
addRule(input -> RegexUtil.matchesAnd((String) input, regexs), errMsg);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ValidValueHolder<T, R> email(String errMsg) {
|
||||
return matches(RegexConsts.EMAIL, errMsg);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public ValidValueHolder<T, R> and(Predicate<R> condition, String errMsg) {
|
||||
addRule((Predicate<Object>) condition, errMsg);
|
||||
return this;
|
||||
}
|
||||
|
||||
@AllArgsConstructor
|
||||
static final class RuleInfo<R> {
|
||||
Predicate<R> condition;
|
||||
String errMsg;
|
||||
}
|
||||
}
|
@@ -0,0 +1,12 @@
|
||||
package xyz.zhouxy.plusone.validator;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface DtoValidator {
|
||||
Class<?> value();
|
||||
}
|
@@ -0,0 +1,13 @@
|
||||
package xyz.zhouxy.plusone.validator;
|
||||
|
||||
/**
|
||||
* 自带校验方法,校验不通过时直接抛异常。
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
*
|
||||
* @see ValidateUtil
|
||||
* @see BaseValidator
|
||||
*/
|
||||
public interface IValidateRequired {
|
||||
void validate();
|
||||
}
|
@@ -0,0 +1,42 @@
|
||||
package xyz.zhouxy.plusone.validator;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.aspectj.lang.annotation.Before;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Aspect
|
||||
@Component
|
||||
class ValidateDtosConfig {
|
||||
|
||||
final Map<Class<?>, BaseValidator<?>> validatorMap = new ConcurrentHashMap<>();
|
||||
|
||||
ValidateDtosConfig(ApplicationContext context) {
|
||||
Map<String, Object> beans = context.getBeansWithAnnotation(DtoValidator.class);
|
||||
for (var validator : beans.values()) {
|
||||
Class<?> targetClass = validator.getClass().getAnnotation(DtoValidator.class).value();
|
||||
this.validatorMap.put(targetClass, (BaseValidator<?>) validator);
|
||||
}
|
||||
}
|
||||
|
||||
@Before("@annotation(ValidateDto) && args(dto)")
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> void doValidateDto(T dto) {
|
||||
BaseValidator<T> validator = (BaseValidator<T>) this.validatorMap.get(dto.getClass());
|
||||
if (validator != null) {
|
||||
validator.validate(dto);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface ValidateDto {
|
||||
}
|
@@ -0,0 +1,29 @@
|
||||
package xyz.zhouxy.plusone.validator;
|
||||
|
||||
/**
|
||||
* 校验工具类
|
||||
* <p>
|
||||
* 对 {@link IValidateRequired} 的实现类对象进行校验
|
||||
* </p>
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
*
|
||||
* @see BaseValidator
|
||||
* @see Validator
|
||||
* @see IValidateRequired
|
||||
*/
|
||||
public class ValidateUtil {
|
||||
private ValidateUtil() {
|
||||
throw new IllegalStateException("Utility class");
|
||||
}
|
||||
|
||||
public static void validate(Object obj) {
|
||||
if (obj instanceof IValidateRequired) {
|
||||
((IValidateRequired) obj).validate();
|
||||
}
|
||||
}
|
||||
|
||||
public static <T> void validate(T obj, BaseValidator<T> validator) {
|
||||
validator.validate(obj);
|
||||
}
|
||||
}
|
@@ -0,0 +1,42 @@
|
||||
package xyz.zhouxy.plusone.validator;
|
||||
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/**
|
||||
* 校验器
|
||||
*
|
||||
* <p>
|
||||
* 可以使用以下方式初始化一个校验器:
|
||||
* </p>
|
||||
*
|
||||
* <pre>
|
||||
* var validator = new Validator<Integer>()
|
||||
* .addRule(value -> Objects.nonNull(value), "value 不能为空")
|
||||
* .addRule(value -> (value >= 0 && value <= 500), "value 应在 [0, 500] 内");
|
||||
* </pre>
|
||||
*
|
||||
* <p>
|
||||
* 然后通过校验器的 {@link #validate} 方法,或
|
||||
* {@link ValidateUtil#validate(Object, Validator)} 对指定对象进行校验。
|
||||
* </p>
|
||||
*
|
||||
* <pre>
|
||||
* validator.validate(666);
|
||||
* </pre>
|
||||
*
|
||||
* <pre>
|
||||
* ValidateUtil.validate(255, validator);
|
||||
* </pre>
|
||||
* </p>
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
* @see IValidateRequired
|
||||
* @see ValidateUtil
|
||||
* @see BaseValidator
|
||||
*/
|
||||
public final class Validator<T> extends BaseValidator<T> {
|
||||
public final Validator<T> addRule(final Predicate<T> rule, final String errorMessage) {
|
||||
ruleFor(rule, errorMessage);
|
||||
return this;
|
||||
}
|
||||
}
|
@@ -0,0 +1,23 @@
|
||||
package xyz.zhouxy.plusone.web.config;
|
||||
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.servlet.config.annotation.CorsRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
/**
|
||||
* 跨域配置
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
*/
|
||||
@Configuration
|
||||
public class WebCorsConfig implements WebMvcConfigurer {
|
||||
|
||||
@Override
|
||||
public void addCorsMappings(CorsRegistry registry) {
|
||||
registry.addMapping("/**")
|
||||
.allowedOriginPatterns("*")
|
||||
.allowCredentials(true)
|
||||
.allowedMethods("GET", "POST", "OPTIONS", "PUT", "DELETE", "PATCH")
|
||||
.maxAge(3600);
|
||||
}
|
||||
}
|
@@ -0,0 +1,55 @@
|
||||
package xyz.zhouxy.plusone.web.config;
|
||||
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.core.convert.converter.ConverterFactory;
|
||||
import org.springframework.format.FormatterRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
/**
|
||||
* Spring MVC 配置。
|
||||
*
|
||||
* <p>
|
||||
* 将与前后端交互的数据中的枚举转换为其整数值
|
||||
* </p>
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
*/
|
||||
@Configuration
|
||||
public class WebEnumMapConfig implements WebMvcConfigurer {
|
||||
@Override
|
||||
public void addFormatters(FormatterRegistry registry) {
|
||||
registry.addConverterFactory(new IntToEnumConverterFactory());
|
||||
registry.addConverterFactory(new StringToEnumConverterFactory());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class IntToEnumConverterFactory implements ConverterFactory<Integer, Enum<?>> {
|
||||
@Override
|
||||
public <T extends Enum<?>> Converter<Integer, T> getConverter(Class<T> targetType) {
|
||||
return (Integer source) -> {
|
||||
try {
|
||||
T[] values = targetType.getEnumConstants();
|
||||
return values[source];
|
||||
} catch (IndexOutOfBoundsException e) {
|
||||
throw new EnumConstantNotPresentException(targetType, Integer.toString(source));
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class StringToEnumConverterFactory implements ConverterFactory<String, Enum<?>> {
|
||||
@Override
|
||||
public <T extends Enum<?>> Converter<String, T> getConverter(Class<T> targetType) {
|
||||
return (String source) -> {
|
||||
int index = Integer.parseInt(source);
|
||||
try {
|
||||
T[] values = targetType.getEnumConstants();
|
||||
return values[index];
|
||||
} catch (IndexOutOfBoundsException e) {
|
||||
throw new EnumConstantNotPresentException(targetType, Integer.toString(index));
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
@@ -0,0 +1,31 @@
|
||||
spring.application.name=${plusone.application.name}
|
||||
|
||||
spring.mail.host=${plusone.mail.host}
|
||||
spring.mail.username=${plusone.mail.from}
|
||||
spring.mail.password=${plusone.mail.password}
|
||||
spring.mail.default-encoding=UTF-8
|
||||
spring.mail.properties.mail.debug=${plusone.debug}
|
||||
|
||||
server.port=${plusone.server.port}
|
||||
|
||||
mybatis-plus.global-config.banner=false
|
||||
# 全局逻辑删除的实体字段名(since 3.3.0,配置后可以忽略不配置步骤2)
|
||||
mybatis-plus.global-config.db-config.logic-delete-field=deleted
|
||||
# 逻辑已删除值(默认为 1)
|
||||
mybatis-plus.global-config.db-config.logic-delete-value=id
|
||||
# 逻辑未删除值(默认为 0)
|
||||
mybatis-plus.global-config.db-config.logic-not-delete-value=0
|
||||
mybatis-plus.configuration.default-enum-type-handler=org.apache.ibatis.type.EnumOrdinalTypeHandler
|
||||
|
||||
# Sa-Token 配置
|
||||
sa-token.is-print=false
|
||||
# token名称 (同时也是cookie名称)
|
||||
sa-token.token-name=${plusone.application.name}
|
||||
# 是否允许同一账号并发登录 (为true时允许一起登录, 为 false 时新登录挤掉旧登录)
|
||||
sa-token.is-concurrent=true
|
||||
# 在多人登录同一账号时,是否共用一个token (为 true 时所有登录共用一个 token, 为false时每次登录新建一个 token)
|
||||
sa-token.is-share=true
|
||||
sa-token.token-style=simple-uuid
|
||||
sa-token.activity-timeout=1800
|
||||
sa-token.is-read-cookie=false
|
||||
sa-token.is-log=${plusone.debug}
|
@@ -0,0 +1,41 @@
|
||||
package xyz.zhouxy.plusone.validator;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import xyz.zhouxy.plusone.constant.RegexConsts;
|
||||
|
||||
class BaseValidator2Test {
|
||||
|
||||
@Test
|
||||
void testValid() {
|
||||
LoginCommand loginCommand = new LoginCommand("13169053215@qq.com", "8GouTDE53a", false);
|
||||
LoginCommandValidator.INSTANCE.validate(loginCommand);
|
||||
System.err.println(loginCommand);
|
||||
}
|
||||
}
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
class LoginCommand {
|
||||
private String account;
|
||||
private String pwd;
|
||||
private boolean rememberMe;
|
||||
}
|
||||
|
||||
class LoginCommandValidator extends BaseValidator2<LoginCommand> {
|
||||
|
||||
public static final LoginCommandValidator INSTANCE = new LoginCommandValidator();
|
||||
|
||||
private LoginCommandValidator() {
|
||||
ruleFor(loginCommand -> loginCommand.getAccount())
|
||||
.nonNull("邮箱地址不能为空")
|
||||
.matchesOr(new String[] { RegexConsts.EMAIL, RegexConsts.MOBILE_PHONE }, "请输入邮箱地址或手机号");
|
||||
ruleFor(loginCommand -> loginCommand.getPwd())
|
||||
.nonNull("密码不能为空")
|
||||
.matches(RegexConsts.PASSWORD, "密码格式错误");
|
||||
}
|
||||
}
|
57
plusone-basic/pom.xml
Normal file
57
plusone-basic/pom.xml
Normal file
@@ -0,0 +1,57 @@
|
||||
<?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">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<artifactId>plusone</artifactId>
|
||||
<groupId>xyz.zhouxy</groupId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<groupId>xyz.zhouxy</groupId>
|
||||
<artifactId>plusone-basic</artifactId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<name>plusone-basic</name>
|
||||
|
||||
<modules>
|
||||
<module>plusone-basic-common</module>
|
||||
<module>plusone-basic-domain</module>
|
||||
<module>plusone-basic-infrastructure</module>
|
||||
<module>plusone-basic-application</module>
|
||||
</modules>
|
||||
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>xyz.zhouxy</groupId>
|
||||
<artifactId>plusone-basic-common</artifactId>
|
||||
<version>${plusone-basic.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>xyz.zhouxy</groupId>
|
||||
<artifactId>plusone-basic-domain</artifactId>
|
||||
<version>${plusone-basic.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>xyz.zhouxy</groupId>
|
||||
<artifactId>plusone-basic-infrastructure</artifactId>
|
||||
<version>${plusone-basic.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>xyz.zhouxy</groupId>
|
||||
<artifactId>plusone-basic-application</artifactId>
|
||||
<version>${plusone-basic.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-api</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
Reference in New Issue
Block a user