From e916d067f3fac7a14655d277f33859812f4bcdc9 Mon Sep 17 00:00:00 2001
From: ZhouXY108
Date: Wed, 7 Dec 2022 18:14:38 +0800
Subject: [PATCH] first commit.
---
.editorconfig | 36 +
.gitignore | 34 +
README.md | 21 +
err_code.json | 774 ++++++++++++++++++
.../plusone-basic-application/pom.xml | 34 +
.../config/PlusoneExceptionHandlerConfig.java | 17 +
.../handler/AllExceptionHandler.java | 29 +
.../handler/DefaultExceptionHandler.java | 63 ++
plusone-basic/plusone-basic-common/pom.xml | 46 ++
.../exception/DataNotExistException.java | 31 +
.../DataOperationNumberException.java | 31 +
.../exception/InvalidInputException.java | 56 ++
.../exception/UserOperationException.java | 58 ++
.../xyz/zhouxy/plusone/util/AssertResult.java | 67 ++
.../zhouxy/plusone/util/PlusoneStrUtil.java | 43 +
plusone-basic/plusone-basic-domain/pom.xml | 39 +
.../zhouxy/plusone/constant/EntityStatus.java | 32 +
.../zhouxy/plusone/constant/RegexConsts.java | 27 +
.../zhouxy/plusone/domain/AggregateRoot.java | 17 +
.../zhouxy/plusone/domain/DomainEvent.java | 26 +
.../xyz/zhouxy/plusone/domain/Entity.java | 32 +
.../xyz/zhouxy/plusone/domain/ICommand.java | 10 +
.../plusone/domain/ICommandHandler.java | 12 +
.../zhouxy/plusone/domain/IEventHandler.java | 12 +
.../zhouxy/plusone/domain/IRepository.java | 21 +
.../zhouxy/plusone/domain/IValueObject.java | 9 +
.../xyz/zhouxy/plusone/domain/IWithLabel.java | 10 +
.../plusone/domain/IWithOrderNumber.java | 33 +
.../zhouxy/plusone/domain/IWithVersion.java | 10 +
.../domain/ValidatableStringRecord.java | 34 +
.../xyz/zhouxy/plusone/util/RegexUtil.java | 36 +
.../plusone-basic-infrastructure/pom.xml | 95 +++
.../zhouxy/plusone/config/PlusoneConfig.java | 14 +
.../infrastructure/pojo/AbstractEntityPO.java | 41 +
.../plusone/jdbc/BeanPropertyParamSource.java | 33 +
.../xyz/zhouxy/plusone/jdbc/JdbcConfig.java | 51 ++
.../plusone/jdbc/JdbcEntityDaoSupport.java | 68 ++
.../xyz/zhouxy/plusone/jdbc/JdbcFactory.java | 30 +
.../plusone/jdbc/JdbcRepositorySupport.java | 58 ++
.../plusone/jdbc/common/ResultMapper.java | 45 +
.../jdbc/common/SimpleResultMapper.java | 30 +
.../converter/EnumToOrdinalConverter.java | 20 +
.../converter/OrdinalToEnumConverter.java | 44 +
.../plusone/mail/MailMessageFactory.java | 31 +
.../xyz/zhouxy/plusone/mail/MailService.java | 17 +
.../mail/PlusoneMailAutoConfiguration.java | 26 +
.../plusone/mail/PlusoneMailProperties.java | 16 +
.../plusone/mail/SimpleMailService.java | 57 ++
.../mybatis/MyBatisAutoConfiguration.java | 17 +
.../plusone/mybatis/MyBatisPlusConfig.java | 19 +
.../zhouxy/plusone/mybatis/MybatisUtil.java | 28 +
.../plusone/redis/RedisStrCacheDAO.java | 42 +
.../xyz/zhouxy/plusone/redis/StrCacheDAO.java | 17 +
.../sms/PlusoneSmsAutoConfiguration.java | 27 +
.../xyz/zhouxy/plusone/sms/SmsProperties.java | 51 ++
.../xyz/zhouxy/plusone/sms/SmsService.java | 13 +
.../plusone/sms/TencentSmsServiceImpl.java | 145 ++++
.../plusone/spring/SpringContextHolder.java | 28 +
.../main/java/xyz/zhouxy/plusone/sql/SQL.java | 34 +
.../plusone/validator/BaseValidator.java | 72 ++
.../plusone/validator/BaseValidator2.java | 129 +++
.../plusone/validator/DtoValidator.java | 12 +
.../plusone/validator/IValidateRequired.java | 13 +
.../zhouxy/plusone/validator/ValidateDto.java | 42 +
.../plusone/validator/ValidateUtil.java | 29 +
.../zhouxy/plusone/validator/Validator.java | 42 +
.../plusone/web/config/WebCorsConfig.java | 23 +
.../plusone/web/config/WebEnumMapConfig.java | 55 ++
.../main/resources/conf/plusone.properties | 31 +
.../plusone/validator/BaseValidator2Test.java | 41 +
plusone-basic/pom.xml | 57 ++
plusone-start/pom.xml | 85 ++
.../zhouxy/plusone/PlusoneApplication.java | 17 +
...itional-spring-configuration-metadata.json | 34 +
.../src/main/resources/application.yaml | 79 ++
plusone-start/src/main/resources/logback.xml | 32 +
.../java/xyz/zhouxy/plusone/SerialTests.java | 22 +
.../test/java/xyz/zhouxy/plusone/TestAop.java | 31 +
.../plusone-system-application/pom.xml | 27 +
.../application/common/util/MenuUtil.java | 61 ++
.../common/util/PrincipalType.java | 18 +
.../common/util/PrincipalUtil.java | 64 ++
.../controller/AccountContextController.java | 40 +
.../AccountManagementController.java | 83 ++
.../controller/AdminLoginController.java | 49 ++
.../controller/AdminLogoutController.java | 30 +
.../controller/DictManagementController.java | 83 ++
.../controller/MenuManagementController.java | 85 ++
.../controller/RegisterAccountController.java | 42 +
.../controller/RoleManagementController.java | 78 ++
.../exception/AccountLoginException.java | 38 +
.../exception/AccountRegisterException.java | 57 ++
.../UnsupportedMenuTypeException.java | 17 +
.../handler/SaTokenExceptionHandler.java | 56 ++
.../application/query/AccountQueries.java | 32 +
.../system/application/query/DictQueries.java | 39 +
.../application/query/PermissionQueries.java | 10 +
.../system/application/query/RoleQueries.java | 64 ++
.../query/params/AccountQueryParams.java | 55 ++
.../query/params/DictQueryParams.java | 21 +
.../query/params/RoleQueryParams.java | 34 +
.../query/result/AccountDetails.java | 29 +
.../query/result/AccountOverview.java | 38 +
.../query/result/DictOverview.java | 22 +
.../query/result/LoginInfoViewObject.java | 24 +
.../query/result/MenuViewObject.java | 131 +++
.../query/result/RoleOverview.java | 23 +
.../service/AccountContextService.java | 38 +
.../service/AccountManagementService.java | 105 +++
.../service/AdminLoginService.java | 104 +++
.../service/AdminLogoutService.java | 19 +
.../application/service/AuthService.java | 60 ++
.../service/DictManagementService.java | 74 ++
.../service/MailAndSmsVerifyService.java | 113 +++
.../service/MenuManagementService.java | 174 ++++
.../service/RegisterAccountService.java | 98 +++
.../service/RoleManagementService.java | 95 +++
.../service/command/CreateAccountCommand.java | 56 ++
.../service/command/CreateDictCommand.java | 27 +
.../service/command/CreateMenuCommand.java | 55 ++
.../service/command/CreateRoleCommand.java | 31 +
.../service/command/LoginByOtpCommand.java | 21 +
.../command/LoginByPasswordCommand.java | 21 +
.../command/RegisterAccountCommand.java | 45 +
.../service/command/UpdateAccountCommand.java | 31 +
.../service/command/UpdateDictCommand.java | 30 +
.../service/command/UpdateMenuCommand.java | 59 ++
.../service/command/UpdateRoleCommand.java | 40 +
.../validator/LoginByOtpCommandValidator.java | 29 +
.../LoginByPasswordCommandValidator.java | 31 +
.../application/query/AccountQueries.xml | 196 +++++
plusone-system/plusone-system-common/pom.xml | 23 +
.../plusone/system/constant/AuthLogic.java | 20 +
.../plusone/system/util/PasswordUtil.java | 51 ++
plusone-system/plusone-system-domain/pom.xml | 23 +
.../system/domain/event/AccountCreated.java | 33 +
.../system/domain/event/AccountLocked.java | 33 +
.../domain/event/AccountPasswordChanged.java | 34 +
.../system/domain/event/AccountRecovered.java | 27 +
.../domain/event/AccountRolesBound.java | 27 +
.../system/domain/event/EmailChanged.java | 30 +
.../domain/event/MobilePhoneChanged.java | 30 +
.../system/domain/event/UsernameChanged.java | 33 +
.../system/domain/model/account/Account.java | 235 ++++++
.../domain/model/account/AccountInfo.java | 43 +
.../model/account/AccountRepository.java | 29 +
.../domain/model/account/AccountStatus.java | 29 +
.../system/domain/model/account/Email.java | 47 ++
.../domain/model/account/MobilePhone.java | 44 +
.../system/domain/model/account/Nickname.java | 35 +
.../system/domain/model/account/Password.java | 90 ++
.../domain/model/account/Principal.java | 14 +
.../system/domain/model/account/Sex.java | 30 +
.../system/domain/model/account/Username.java | 28 +
.../system/domain/model/dict/Dict.java | 129 +++
.../domain/model/dict/DictRepository.java | 10 +
.../system/domain/model/dict/DictValue.java | 51 ++
.../system/domain/model/menu/Action.java | 54 ++
.../system/domain/model/menu/Menu.java | 140 ++++
.../domain/model/menu/MenuConstructor.java | 55 ++
.../domain/model/menu/MenuRepository.java | 15 +
.../system/domain/model/menu/Target.java | 26 +
.../domain/model/permission/Action.java | 47 ++
.../domain/model/permission/Permission.java | 69 ++
.../system/domain/model/permission/README.md | 3 +
.../system/domain/model/role/ActionRef.java | 12 +
.../system/domain/model/role/MenuRef.java | 12 +
.../system/domain/model/role/Role.java | 112 +++
.../domain/model/role/RoleRepository.java | 11 +
.../system/domain/service/MenuService.java | 44 +
.../domain/model/account/PasswordTests.java | 18 +
.../plusone-system-infrastructure/pom.xml | 23 +
.../model/account/AccountRepositoryImpl.java | 218 +++++
.../model/account/AccountRoleRefDAO.java | 53 ++
.../domain/model/dict/DictRepositoryImpl.java | 118 +++
.../domain/model/dict/DictValueDAO.java | 51 ++
.../system/domain/model/menu/ActionDAO.java | 123 +++
.../domain/model/menu/MenuRepositoryImpl.java | 200 +++++
.../domain/model/role/RoleMenuRefDAO.java | 45 +
.../model/role/RolePermissionRefDAO.java | 45 +
.../domain/model/role/RoleRepositoryImpl.java | 142 ++++
plusone-system/pom.xml | 79 ++
pom.xml | 211 +++++
183 files changed, 9649 insertions(+)
create mode 100644 .editorconfig
create mode 100644 .gitignore
create mode 100644 README.md
create mode 100644 err_code.json
create mode 100644 plusone-basic/plusone-basic-application/pom.xml
create mode 100644 plusone-basic/plusone-basic-application/src/main/java/xyz/zhouxy/plusone/exception/config/PlusoneExceptionHandlerConfig.java
create mode 100644 plusone-basic/plusone-basic-application/src/main/java/xyz/zhouxy/plusone/exception/handler/AllExceptionHandler.java
create mode 100644 plusone-basic/plusone-basic-application/src/main/java/xyz/zhouxy/plusone/exception/handler/DefaultExceptionHandler.java
create mode 100644 plusone-basic/plusone-basic-common/pom.xml
create mode 100644 plusone-basic/plusone-basic-common/src/main/java/xyz/zhouxy/plusone/exception/DataNotExistException.java
create mode 100644 plusone-basic/plusone-basic-common/src/main/java/xyz/zhouxy/plusone/exception/DataOperationNumberException.java
create mode 100644 plusone-basic/plusone-basic-common/src/main/java/xyz/zhouxy/plusone/exception/InvalidInputException.java
create mode 100644 plusone-basic/plusone-basic-common/src/main/java/xyz/zhouxy/plusone/exception/UserOperationException.java
create mode 100644 plusone-basic/plusone-basic-common/src/main/java/xyz/zhouxy/plusone/util/AssertResult.java
create mode 100644 plusone-basic/plusone-basic-common/src/main/java/xyz/zhouxy/plusone/util/PlusoneStrUtil.java
create mode 100644 plusone-basic/plusone-basic-domain/pom.xml
create mode 100644 plusone-basic/plusone-basic-domain/src/main/java/xyz/zhouxy/plusone/constant/EntityStatus.java
create mode 100644 plusone-basic/plusone-basic-domain/src/main/java/xyz/zhouxy/plusone/constant/RegexConsts.java
create mode 100644 plusone-basic/plusone-basic-domain/src/main/java/xyz/zhouxy/plusone/domain/AggregateRoot.java
create mode 100644 plusone-basic/plusone-basic-domain/src/main/java/xyz/zhouxy/plusone/domain/DomainEvent.java
create mode 100644 plusone-basic/plusone-basic-domain/src/main/java/xyz/zhouxy/plusone/domain/Entity.java
create mode 100644 plusone-basic/plusone-basic-domain/src/main/java/xyz/zhouxy/plusone/domain/ICommand.java
create mode 100644 plusone-basic/plusone-basic-domain/src/main/java/xyz/zhouxy/plusone/domain/ICommandHandler.java
create mode 100644 plusone-basic/plusone-basic-domain/src/main/java/xyz/zhouxy/plusone/domain/IEventHandler.java
create mode 100644 plusone-basic/plusone-basic-domain/src/main/java/xyz/zhouxy/plusone/domain/IRepository.java
create mode 100644 plusone-basic/plusone-basic-domain/src/main/java/xyz/zhouxy/plusone/domain/IValueObject.java
create mode 100644 plusone-basic/plusone-basic-domain/src/main/java/xyz/zhouxy/plusone/domain/IWithLabel.java
create mode 100644 plusone-basic/plusone-basic-domain/src/main/java/xyz/zhouxy/plusone/domain/IWithOrderNumber.java
create mode 100644 plusone-basic/plusone-basic-domain/src/main/java/xyz/zhouxy/plusone/domain/IWithVersion.java
create mode 100644 plusone-basic/plusone-basic-domain/src/main/java/xyz/zhouxy/plusone/domain/ValidatableStringRecord.java
create mode 100644 plusone-basic/plusone-basic-domain/src/main/java/xyz/zhouxy/plusone/util/RegexUtil.java
create mode 100644 plusone-basic/plusone-basic-infrastructure/pom.xml
create mode 100644 plusone-basic/plusone-basic-infrastructure/src/main/java/xyz/zhouxy/plusone/config/PlusoneConfig.java
create mode 100644 plusone-basic/plusone-basic-infrastructure/src/main/java/xyz/zhouxy/plusone/infrastructure/pojo/AbstractEntityPO.java
create mode 100644 plusone-basic/plusone-basic-infrastructure/src/main/java/xyz/zhouxy/plusone/jdbc/BeanPropertyParamSource.java
create mode 100644 plusone-basic/plusone-basic-infrastructure/src/main/java/xyz/zhouxy/plusone/jdbc/JdbcConfig.java
create mode 100644 plusone-basic/plusone-basic-infrastructure/src/main/java/xyz/zhouxy/plusone/jdbc/JdbcEntityDaoSupport.java
create mode 100644 plusone-basic/plusone-basic-infrastructure/src/main/java/xyz/zhouxy/plusone/jdbc/JdbcFactory.java
create mode 100644 plusone-basic/plusone-basic-infrastructure/src/main/java/xyz/zhouxy/plusone/jdbc/JdbcRepositorySupport.java
create mode 100644 plusone-basic/plusone-basic-infrastructure/src/main/java/xyz/zhouxy/plusone/jdbc/common/ResultMapper.java
create mode 100644 plusone-basic/plusone-basic-infrastructure/src/main/java/xyz/zhouxy/plusone/jdbc/common/SimpleResultMapper.java
create mode 100644 plusone-basic/plusone-basic-infrastructure/src/main/java/xyz/zhouxy/plusone/jdbc/converter/EnumToOrdinalConverter.java
create mode 100644 plusone-basic/plusone-basic-infrastructure/src/main/java/xyz/zhouxy/plusone/jdbc/converter/OrdinalToEnumConverter.java
create mode 100644 plusone-basic/plusone-basic-infrastructure/src/main/java/xyz/zhouxy/plusone/mail/MailMessageFactory.java
create mode 100644 plusone-basic/plusone-basic-infrastructure/src/main/java/xyz/zhouxy/plusone/mail/MailService.java
create mode 100644 plusone-basic/plusone-basic-infrastructure/src/main/java/xyz/zhouxy/plusone/mail/PlusoneMailAutoConfiguration.java
create mode 100644 plusone-basic/plusone-basic-infrastructure/src/main/java/xyz/zhouxy/plusone/mail/PlusoneMailProperties.java
create mode 100644 plusone-basic/plusone-basic-infrastructure/src/main/java/xyz/zhouxy/plusone/mail/SimpleMailService.java
create mode 100644 plusone-basic/plusone-basic-infrastructure/src/main/java/xyz/zhouxy/plusone/mybatis/MyBatisAutoConfiguration.java
create mode 100644 plusone-basic/plusone-basic-infrastructure/src/main/java/xyz/zhouxy/plusone/mybatis/MyBatisPlusConfig.java
create mode 100644 plusone-basic/plusone-basic-infrastructure/src/main/java/xyz/zhouxy/plusone/mybatis/MybatisUtil.java
create mode 100644 plusone-basic/plusone-basic-infrastructure/src/main/java/xyz/zhouxy/plusone/redis/RedisStrCacheDAO.java
create mode 100644 plusone-basic/plusone-basic-infrastructure/src/main/java/xyz/zhouxy/plusone/redis/StrCacheDAO.java
create mode 100644 plusone-basic/plusone-basic-infrastructure/src/main/java/xyz/zhouxy/plusone/sms/PlusoneSmsAutoConfiguration.java
create mode 100644 plusone-basic/plusone-basic-infrastructure/src/main/java/xyz/zhouxy/plusone/sms/SmsProperties.java
create mode 100644 plusone-basic/plusone-basic-infrastructure/src/main/java/xyz/zhouxy/plusone/sms/SmsService.java
create mode 100644 plusone-basic/plusone-basic-infrastructure/src/main/java/xyz/zhouxy/plusone/sms/TencentSmsServiceImpl.java
create mode 100644 plusone-basic/plusone-basic-infrastructure/src/main/java/xyz/zhouxy/plusone/spring/SpringContextHolder.java
create mode 100644 plusone-basic/plusone-basic-infrastructure/src/main/java/xyz/zhouxy/plusone/sql/SQL.java
create mode 100644 plusone-basic/plusone-basic-infrastructure/src/main/java/xyz/zhouxy/plusone/validator/BaseValidator.java
create mode 100644 plusone-basic/plusone-basic-infrastructure/src/main/java/xyz/zhouxy/plusone/validator/BaseValidator2.java
create mode 100644 plusone-basic/plusone-basic-infrastructure/src/main/java/xyz/zhouxy/plusone/validator/DtoValidator.java
create mode 100644 plusone-basic/plusone-basic-infrastructure/src/main/java/xyz/zhouxy/plusone/validator/IValidateRequired.java
create mode 100644 plusone-basic/plusone-basic-infrastructure/src/main/java/xyz/zhouxy/plusone/validator/ValidateDto.java
create mode 100644 plusone-basic/plusone-basic-infrastructure/src/main/java/xyz/zhouxy/plusone/validator/ValidateUtil.java
create mode 100644 plusone-basic/plusone-basic-infrastructure/src/main/java/xyz/zhouxy/plusone/validator/Validator.java
create mode 100644 plusone-basic/plusone-basic-infrastructure/src/main/java/xyz/zhouxy/plusone/web/config/WebCorsConfig.java
create mode 100644 plusone-basic/plusone-basic-infrastructure/src/main/java/xyz/zhouxy/plusone/web/config/WebEnumMapConfig.java
create mode 100644 plusone-basic/plusone-basic-infrastructure/src/main/resources/conf/plusone.properties
create mode 100644 plusone-basic/plusone-basic-infrastructure/src/test/java/xyz/zhouxy/plusone/validator/BaseValidator2Test.java
create mode 100644 plusone-basic/pom.xml
create mode 100644 plusone-start/pom.xml
create mode 100644 plusone-start/src/main/java/xyz/zhouxy/plusone/PlusoneApplication.java
create mode 100644 plusone-start/src/main/resources/META-INF/additional-spring-configuration-metadata.json
create mode 100644 plusone-start/src/main/resources/application.yaml
create mode 100644 plusone-start/src/main/resources/logback.xml
create mode 100644 plusone-start/src/test/java/xyz/zhouxy/plusone/SerialTests.java
create mode 100644 plusone-start/src/test/java/xyz/zhouxy/plusone/TestAop.java
create mode 100644 plusone-system/plusone-system-application/pom.xml
create mode 100644 plusone-system/plusone-system-application/src/main/java/xyz/zhouxy/plusone/system/application/common/util/MenuUtil.java
create mode 100644 plusone-system/plusone-system-application/src/main/java/xyz/zhouxy/plusone/system/application/common/util/PrincipalType.java
create mode 100644 plusone-system/plusone-system-application/src/main/java/xyz/zhouxy/plusone/system/application/common/util/PrincipalUtil.java
create mode 100644 plusone-system/plusone-system-application/src/main/java/xyz/zhouxy/plusone/system/application/controller/AccountContextController.java
create mode 100644 plusone-system/plusone-system-application/src/main/java/xyz/zhouxy/plusone/system/application/controller/AccountManagementController.java
create mode 100644 plusone-system/plusone-system-application/src/main/java/xyz/zhouxy/plusone/system/application/controller/AdminLoginController.java
create mode 100644 plusone-system/plusone-system-application/src/main/java/xyz/zhouxy/plusone/system/application/controller/AdminLogoutController.java
create mode 100644 plusone-system/plusone-system-application/src/main/java/xyz/zhouxy/plusone/system/application/controller/DictManagementController.java
create mode 100644 plusone-system/plusone-system-application/src/main/java/xyz/zhouxy/plusone/system/application/controller/MenuManagementController.java
create mode 100644 plusone-system/plusone-system-application/src/main/java/xyz/zhouxy/plusone/system/application/controller/RegisterAccountController.java
create mode 100644 plusone-system/plusone-system-application/src/main/java/xyz/zhouxy/plusone/system/application/controller/RoleManagementController.java
create mode 100644 plusone-system/plusone-system-application/src/main/java/xyz/zhouxy/plusone/system/application/exception/AccountLoginException.java
create mode 100644 plusone-system/plusone-system-application/src/main/java/xyz/zhouxy/plusone/system/application/exception/AccountRegisterException.java
create mode 100644 plusone-system/plusone-system-application/src/main/java/xyz/zhouxy/plusone/system/application/exception/UnsupportedMenuTypeException.java
create mode 100644 plusone-system/plusone-system-application/src/main/java/xyz/zhouxy/plusone/system/application/exception/handler/SaTokenExceptionHandler.java
create mode 100644 plusone-system/plusone-system-application/src/main/java/xyz/zhouxy/plusone/system/application/query/AccountQueries.java
create mode 100644 plusone-system/plusone-system-application/src/main/java/xyz/zhouxy/plusone/system/application/query/DictQueries.java
create mode 100644 plusone-system/plusone-system-application/src/main/java/xyz/zhouxy/plusone/system/application/query/PermissionQueries.java
create mode 100644 plusone-system/plusone-system-application/src/main/java/xyz/zhouxy/plusone/system/application/query/RoleQueries.java
create mode 100644 plusone-system/plusone-system-application/src/main/java/xyz/zhouxy/plusone/system/application/query/params/AccountQueryParams.java
create mode 100644 plusone-system/plusone-system-application/src/main/java/xyz/zhouxy/plusone/system/application/query/params/DictQueryParams.java
create mode 100644 plusone-system/plusone-system-application/src/main/java/xyz/zhouxy/plusone/system/application/query/params/RoleQueryParams.java
create mode 100644 plusone-system/plusone-system-application/src/main/java/xyz/zhouxy/plusone/system/application/query/result/AccountDetails.java
create mode 100644 plusone-system/plusone-system-application/src/main/java/xyz/zhouxy/plusone/system/application/query/result/AccountOverview.java
create mode 100644 plusone-system/plusone-system-application/src/main/java/xyz/zhouxy/plusone/system/application/query/result/DictOverview.java
create mode 100644 plusone-system/plusone-system-application/src/main/java/xyz/zhouxy/plusone/system/application/query/result/LoginInfoViewObject.java
create mode 100644 plusone-system/plusone-system-application/src/main/java/xyz/zhouxy/plusone/system/application/query/result/MenuViewObject.java
create mode 100644 plusone-system/plusone-system-application/src/main/java/xyz/zhouxy/plusone/system/application/query/result/RoleOverview.java
create mode 100644 plusone-system/plusone-system-application/src/main/java/xyz/zhouxy/plusone/system/application/service/AccountContextService.java
create mode 100644 plusone-system/plusone-system-application/src/main/java/xyz/zhouxy/plusone/system/application/service/AccountManagementService.java
create mode 100644 plusone-system/plusone-system-application/src/main/java/xyz/zhouxy/plusone/system/application/service/AdminLoginService.java
create mode 100644 plusone-system/plusone-system-application/src/main/java/xyz/zhouxy/plusone/system/application/service/AdminLogoutService.java
create mode 100644 plusone-system/plusone-system-application/src/main/java/xyz/zhouxy/plusone/system/application/service/AuthService.java
create mode 100644 plusone-system/plusone-system-application/src/main/java/xyz/zhouxy/plusone/system/application/service/DictManagementService.java
create mode 100644 plusone-system/plusone-system-application/src/main/java/xyz/zhouxy/plusone/system/application/service/MailAndSmsVerifyService.java
create mode 100644 plusone-system/plusone-system-application/src/main/java/xyz/zhouxy/plusone/system/application/service/MenuManagementService.java
create mode 100644 plusone-system/plusone-system-application/src/main/java/xyz/zhouxy/plusone/system/application/service/RegisterAccountService.java
create mode 100644 plusone-system/plusone-system-application/src/main/java/xyz/zhouxy/plusone/system/application/service/RoleManagementService.java
create mode 100644 plusone-system/plusone-system-application/src/main/java/xyz/zhouxy/plusone/system/application/service/command/CreateAccountCommand.java
create mode 100644 plusone-system/plusone-system-application/src/main/java/xyz/zhouxy/plusone/system/application/service/command/CreateDictCommand.java
create mode 100644 plusone-system/plusone-system-application/src/main/java/xyz/zhouxy/plusone/system/application/service/command/CreateMenuCommand.java
create mode 100644 plusone-system/plusone-system-application/src/main/java/xyz/zhouxy/plusone/system/application/service/command/CreateRoleCommand.java
create mode 100644 plusone-system/plusone-system-application/src/main/java/xyz/zhouxy/plusone/system/application/service/command/LoginByOtpCommand.java
create mode 100644 plusone-system/plusone-system-application/src/main/java/xyz/zhouxy/plusone/system/application/service/command/LoginByPasswordCommand.java
create mode 100644 plusone-system/plusone-system-application/src/main/java/xyz/zhouxy/plusone/system/application/service/command/RegisterAccountCommand.java
create mode 100644 plusone-system/plusone-system-application/src/main/java/xyz/zhouxy/plusone/system/application/service/command/UpdateAccountCommand.java
create mode 100644 plusone-system/plusone-system-application/src/main/java/xyz/zhouxy/plusone/system/application/service/command/UpdateDictCommand.java
create mode 100644 plusone-system/plusone-system-application/src/main/java/xyz/zhouxy/plusone/system/application/service/command/UpdateMenuCommand.java
create mode 100644 plusone-system/plusone-system-application/src/main/java/xyz/zhouxy/plusone/system/application/service/command/UpdateRoleCommand.java
create mode 100644 plusone-system/plusone-system-application/src/main/java/xyz/zhouxy/plusone/system/application/service/command/validator/LoginByOtpCommandValidator.java
create mode 100644 plusone-system/plusone-system-application/src/main/java/xyz/zhouxy/plusone/system/application/service/command/validator/LoginByPasswordCommandValidator.java
create mode 100644 plusone-system/plusone-system-application/src/main/resources/xyz/zhouxy/plusone/system/application/query/AccountQueries.xml
create mode 100644 plusone-system/plusone-system-common/pom.xml
create mode 100644 plusone-system/plusone-system-common/src/main/java/xyz/zhouxy/plusone/system/constant/AuthLogic.java
create mode 100644 plusone-system/plusone-system-common/src/main/java/xyz/zhouxy/plusone/system/util/PasswordUtil.java
create mode 100644 plusone-system/plusone-system-domain/pom.xml
create mode 100644 plusone-system/plusone-system-domain/src/main/java/xyz/zhouxy/plusone/system/domain/event/AccountCreated.java
create mode 100644 plusone-system/plusone-system-domain/src/main/java/xyz/zhouxy/plusone/system/domain/event/AccountLocked.java
create mode 100644 plusone-system/plusone-system-domain/src/main/java/xyz/zhouxy/plusone/system/domain/event/AccountPasswordChanged.java
create mode 100644 plusone-system/plusone-system-domain/src/main/java/xyz/zhouxy/plusone/system/domain/event/AccountRecovered.java
create mode 100644 plusone-system/plusone-system-domain/src/main/java/xyz/zhouxy/plusone/system/domain/event/AccountRolesBound.java
create mode 100644 plusone-system/plusone-system-domain/src/main/java/xyz/zhouxy/plusone/system/domain/event/EmailChanged.java
create mode 100644 plusone-system/plusone-system-domain/src/main/java/xyz/zhouxy/plusone/system/domain/event/MobilePhoneChanged.java
create mode 100644 plusone-system/plusone-system-domain/src/main/java/xyz/zhouxy/plusone/system/domain/event/UsernameChanged.java
create mode 100644 plusone-system/plusone-system-domain/src/main/java/xyz/zhouxy/plusone/system/domain/model/account/Account.java
create mode 100644 plusone-system/plusone-system-domain/src/main/java/xyz/zhouxy/plusone/system/domain/model/account/AccountInfo.java
create mode 100644 plusone-system/plusone-system-domain/src/main/java/xyz/zhouxy/plusone/system/domain/model/account/AccountRepository.java
create mode 100644 plusone-system/plusone-system-domain/src/main/java/xyz/zhouxy/plusone/system/domain/model/account/AccountStatus.java
create mode 100644 plusone-system/plusone-system-domain/src/main/java/xyz/zhouxy/plusone/system/domain/model/account/Email.java
create mode 100644 plusone-system/plusone-system-domain/src/main/java/xyz/zhouxy/plusone/system/domain/model/account/MobilePhone.java
create mode 100644 plusone-system/plusone-system-domain/src/main/java/xyz/zhouxy/plusone/system/domain/model/account/Nickname.java
create mode 100644 plusone-system/plusone-system-domain/src/main/java/xyz/zhouxy/plusone/system/domain/model/account/Password.java
create mode 100644 plusone-system/plusone-system-domain/src/main/java/xyz/zhouxy/plusone/system/domain/model/account/Principal.java
create mode 100644 plusone-system/plusone-system-domain/src/main/java/xyz/zhouxy/plusone/system/domain/model/account/Sex.java
create mode 100644 plusone-system/plusone-system-domain/src/main/java/xyz/zhouxy/plusone/system/domain/model/account/Username.java
create mode 100644 plusone-system/plusone-system-domain/src/main/java/xyz/zhouxy/plusone/system/domain/model/dict/Dict.java
create mode 100644 plusone-system/plusone-system-domain/src/main/java/xyz/zhouxy/plusone/system/domain/model/dict/DictRepository.java
create mode 100644 plusone-system/plusone-system-domain/src/main/java/xyz/zhouxy/plusone/system/domain/model/dict/DictValue.java
create mode 100644 plusone-system/plusone-system-domain/src/main/java/xyz/zhouxy/plusone/system/domain/model/menu/Action.java
create mode 100644 plusone-system/plusone-system-domain/src/main/java/xyz/zhouxy/plusone/system/domain/model/menu/Menu.java
create mode 100644 plusone-system/plusone-system-domain/src/main/java/xyz/zhouxy/plusone/system/domain/model/menu/MenuConstructor.java
create mode 100644 plusone-system/plusone-system-domain/src/main/java/xyz/zhouxy/plusone/system/domain/model/menu/MenuRepository.java
create mode 100644 plusone-system/plusone-system-domain/src/main/java/xyz/zhouxy/plusone/system/domain/model/menu/Target.java
create mode 100644 plusone-system/plusone-system-domain/src/main/java/xyz/zhouxy/plusone/system/domain/model/permission/Action.java
create mode 100644 plusone-system/plusone-system-domain/src/main/java/xyz/zhouxy/plusone/system/domain/model/permission/Permission.java
create mode 100644 plusone-system/plusone-system-domain/src/main/java/xyz/zhouxy/plusone/system/domain/model/permission/README.md
create mode 100644 plusone-system/plusone-system-domain/src/main/java/xyz/zhouxy/plusone/system/domain/model/role/ActionRef.java
create mode 100644 plusone-system/plusone-system-domain/src/main/java/xyz/zhouxy/plusone/system/domain/model/role/MenuRef.java
create mode 100644 plusone-system/plusone-system-domain/src/main/java/xyz/zhouxy/plusone/system/domain/model/role/Role.java
create mode 100644 plusone-system/plusone-system-domain/src/main/java/xyz/zhouxy/plusone/system/domain/model/role/RoleRepository.java
create mode 100644 plusone-system/plusone-system-domain/src/main/java/xyz/zhouxy/plusone/system/domain/service/MenuService.java
create mode 100644 plusone-system/plusone-system-domain/src/test/java/xyz/zhouxy/plusone/system/domain/model/account/PasswordTests.java
create mode 100644 plusone-system/plusone-system-infrastructure/pom.xml
create mode 100644 plusone-system/plusone-system-infrastructure/src/main/java/xyz/zhouxy/plusone/system/domain/model/account/AccountRepositoryImpl.java
create mode 100644 plusone-system/plusone-system-infrastructure/src/main/java/xyz/zhouxy/plusone/system/domain/model/account/AccountRoleRefDAO.java
create mode 100644 plusone-system/plusone-system-infrastructure/src/main/java/xyz/zhouxy/plusone/system/domain/model/dict/DictRepositoryImpl.java
create mode 100644 plusone-system/plusone-system-infrastructure/src/main/java/xyz/zhouxy/plusone/system/domain/model/dict/DictValueDAO.java
create mode 100644 plusone-system/plusone-system-infrastructure/src/main/java/xyz/zhouxy/plusone/system/domain/model/menu/ActionDAO.java
create mode 100644 plusone-system/plusone-system-infrastructure/src/main/java/xyz/zhouxy/plusone/system/domain/model/menu/MenuRepositoryImpl.java
create mode 100644 plusone-system/plusone-system-infrastructure/src/main/java/xyz/zhouxy/plusone/system/domain/model/role/RoleMenuRefDAO.java
create mode 100644 plusone-system/plusone-system-infrastructure/src/main/java/xyz/zhouxy/plusone/system/domain/model/role/RolePermissionRefDAO.java
create mode 100644 plusone-system/plusone-system-infrastructure/src/main/java/xyz/zhouxy/plusone/system/domain/model/role/RoleRepositoryImpl.java
create mode 100644 plusone-system/pom.xml
create mode 100644 pom.xml
diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..d37d8e9
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,36 @@
+root = true
+
+[*]
+charset = utf-8
+indent_style = space
+end_of_line = lf
+insert_final_newline = true
+trim_trailing_whitespace = true
+
+
+[{*.ng,*.sht,*.html,*.shtm,*.shtml,*.htm}]
+indent_size=4
+
+[{*.jhm,*.xslt,*.xul,*.rng,*.xsl,*.xsd,*.ant,*.tld,*.fxml,*.jrxml,*.xml,*.jnlp,*.wsdl}]
+indent_size=4
+
+[{.babelrc,.stylelintrc,jest.config,.eslintrc,.prettierrc,*.json,*.jsb3,*.jsb2,*.bowerrc}]
+indent_size=2
+
+[*.svg]
+indent_size=2
+
+[*.js.map]
+indent_size=2
+
+[*.less]
+indent_size=2
+
+[*.vue]
+indent_size=4
+
+[{.analysis_options,*.yml,*.yaml}]
+indent_size=2
+
+[*.java]
+indent_size = 4
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..96041e9
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,34 @@
+HELP.md
+target/
+!.mvn/wrapper/maven-wrapper.jar
+!**/src/main/**/target/
+!**/src/test/**/target/
+*/**/http/*.http
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+build/
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### VS Code ###
+.vscode/
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..1dfb6fd
--- /dev/null
+++ b/README.md
@@ -0,0 +1,21 @@
+# PlusoneAdmin
+
+后台管理系统 +1
+
+本仓库是后端部分,使用 Spring Boot 2.X 进行开发,对前端提供接口。
+
+简单落地 DDD。
+
+- JDK 17
+
+- 主要使用 Spring JDBC 的 NamedParameterJdbcTemplate 进行数据的查询和持久化,因为感觉有更多的自由度。可以根据 ResultSet,任意按照需要的方式实例化 Entity。MyBatis 在某些地方用于查询。
+
+- 数据库使用的是 PostgreSQL;
+
+- 权限管理使用的是 Sa-Token,使用 Redis 共享 Session。
+
+- 短信服务使用的是腾讯云的 SMS 服务。后续将其解耦,使实现等各方面可替换为阿里云或其它 SMS 服务。
+
+目前项目还没完成,开发中……
+
+相关的文档和介绍完善中……
\ No newline at end of file
diff --git a/err_code.json b/err_code.json
new file mode 100644
index 0000000..0752942
--- /dev/null
+++ b/err_code.json
@@ -0,0 +1,774 @@
+[
+ {
+ "code": 2000000,
+ "description": "正常"
+ },
+ {
+ "code": 4000000,
+ "description": "用户端错误",
+ "children": [
+ {
+ "code": 4010000,
+ "description": "用户注册错误",
+ "children": [
+ {
+ "code": 4010100,
+ "description": "用户未同意隐私协议"
+ },
+ {
+ "code": 4010200,
+ "description": "注册国家或地区受限"
+ },
+ {
+ "code": 4010300,
+ "description": "邮箱和手机号应至少绑定一个"
+ },
+ {
+ "code": 4010400,
+ "description": "用户名已存在"
+ },
+ {
+ "code": 4010500,
+ "description": "邮箱已存在"
+ },
+ {
+ "code": 4010600,
+ "description": "手机号已存在"
+ },
+ {
+ "code": 4010700,
+ "description": "校验码异常",
+ "children": [
+ {
+ "code": 4010701,
+ "description": "校验码输入错误"
+ },
+ {
+ "code": 4010702,
+ "description": "校验码不存在或过期"
+ }
+ ]
+ },
+ {
+ "code": 4010800,
+ "description": "用户证件异常",
+ "children": [
+ {
+ "code": 4010801,
+ "description": "用户证件类型未选择"
+ },
+ {
+ "code": 4010802,
+ "description": "大陆身份证编号校验非法"
+ },
+ {
+ "code": 4010803,
+ "description": "护照编号校验非法"
+ },
+ {
+ "code": 4010804,
+ "description": "军官证编号校验非法"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "code": 4020000,
+ "description": "用户登录异常",
+ "children": [
+ {
+ "code": 4020100,
+ "description": "用户账户异常",
+ "children": [
+ {
+ "code": 4020101,
+ "description": "用户账户不存在"
+ },
+ {
+ "code": 4020102,
+ "description": "用户账户被冻结"
+ },
+ {
+ "code": 4020103,
+ "description": "用户账户已作废"
+ }
+ ]
+ },
+ {
+ "code": 4020200,
+ "description": "用户密码错误",
+ "children": [
+ {
+ "code": 4020201,
+ "description": "用户输入密码错误次数超限"
+ }
+ ]
+ },
+ {
+ "code": 4020300,
+ "description": "用户身份校验失败",
+ "children": [
+ {
+ "code": 4020301,
+ "description": "用户指纹识别失败"
+ },
+ {
+ "code": 4020302,
+ "description": "用户面容识别失败"
+ },
+ {
+ "code": 4020303,
+ "description": "用户未获得第三方登录授权"
+ }
+ ]
+ },
+ {
+ "code": 4020400,
+ "description": "用户登录已过期"
+ },
+ {
+ "code": 4020500,
+ "description": "用户验证码错误",
+ "children": [
+ {
+ "code": 4020501,
+ "description": "用户验证码输入错误"
+ },
+ {
+ "code": 4020502,
+ "description": "用户验证码不存在或已过期"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "code": 4030000,
+ "description": "访问权限异常",
+ "children": [
+ {
+ "code": 4030100,
+ "description": "访问未授权",
+ "children": [
+ {
+ "code": 4030101,
+ "description": "因访问对象隐私设置被拦截"
+ },
+ {
+ "code": 4030102,
+ "description": "授权已过期"
+ },
+ {
+ "code": 4030103,
+ "description": "无权限使用 API"
+ }
+ ]
+ },
+ {
+ "code": 4030200,
+ "description": "用户访问被拦截",
+ "children": [
+ {
+ "code": 4030201,
+ "description": "黑名单用户"
+ },
+ {
+ "code": 4030202,
+ "description": "账号被冻结"
+ },
+ {
+ "code": 4030203,
+ "description": "非法 IP 地址"
+ },
+ {
+ "code": 4030204,
+ "description": "网关访问受限"
+ },
+ {
+ "code": 4030205,
+ "description": "地域黑名单"
+ }
+ ]
+ },
+ {
+ "code": 4030300,
+ "description": "服务已欠费"
+ },
+ {
+ "code": 4030400,
+ "description": "用户签名异常",
+ "children": [
+ {
+ "code": 4030401,
+ "description": "RSA 签名错误"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "code": 4040000,
+ "description": "用户请求参数错误",
+ "children": [
+ {
+ "code": 4040100,
+ "description": "包含非法恶意跳转链接"
+ },
+ {
+ "code": 4040200,
+ "description": "无效的用户输入",
+ "children": [
+ {
+ "code": 4040201,
+ "description": "不支持的 PrincipalType"
+ }
+ ]
+ },
+ {
+ "code": 4040300,
+ "description": "请求必填参数为空",
+ "children": [
+ {
+ "code": 4040301,
+ "description": "用户订单号为空"
+ },
+ {
+ "code": 4040302,
+ "description": "订购数量为空"
+ },
+ {
+ "code": 4040303,
+ "description": "缺少时间戳参数"
+ },
+ {
+ "code": 4040304,
+ "description": "非法的时间戳参数"
+ }
+ ]
+ },
+ {
+ "code": 4040400,
+ "description": "请求参数值超出允许的范围",
+ "children": [
+ {
+ "code": 4040401,
+ "description": "参数格式不匹配"
+ },
+ {
+ "code": 4040402,
+ "description": "地址不在服务范围"
+ },
+ {
+ "code": 4040403,
+ "description": "时间不在服务范围"
+ },
+ {
+ "code": 4040404,
+ "description": "金额超出限制"
+ },
+ {
+ "code": 4040405,
+ "description": "数量超出限制"
+ },
+ {
+ "code": 4040406,
+ "description": "请求批量处理总个数超出限制"
+ },
+ {
+ "code": 4040407,
+ "description": "请求 JSON 解析失败"
+ }
+ ]
+ },
+ {
+ "code": 4040500,
+ "description": "用户输入内容非法",
+ "children": [
+ {
+ "code": 4040501,
+ "description": "包含违禁敏感词"
+ },
+ {
+ "code": 4040502,
+ "description": "图片包含违禁信息"
+ },
+ {
+ "code": 4040503,
+ "description": "文件侵犯版权"
+ }
+ ]
+ },
+ {
+ "code": 4040600,
+ "description": "用户操作异常",
+ "children": [
+ {
+ "code": 4040601,
+ "description": "用户支付超时"
+ },
+ {
+ "code": 4040602,
+ "description": "确认订单超时"
+ },
+ {
+ "code": 4040603,
+ "description": "订单已关闭"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "code": 4050000,
+ "description": "用户请求服务异常",
+ "children": [
+ {
+ "code": 4050100,
+ "description": "请求次数超出限制"
+ },
+ {
+ "code": 4050200,
+ "description": "请求并发数超出限制"
+ },
+ {
+ "code": 4050300,
+ "description": "用户操作请等待"
+ },
+ {
+ "code": 4050400,
+ "description": "WebSocket 连接异常"
+ },
+ {
+ "code": 4050500,
+ "description": "WebSocket 连接断开"
+ },
+ {
+ "code": 4050600,
+ "description": "用户重复请求"
+ }
+ ]
+ },
+ {
+ "code": 4060000,
+ "description": "用户资源异常",
+ "children": [
+ {
+ "code": 4060100,
+ "description": "账户余额不足"
+ },
+ {
+ "code": 4060200,
+ "description": "用户磁盘空间不足"
+ },
+ {
+ "code": 4060300,
+ "description": "用户内存空间不足"
+ },
+ {
+ "code": 4060400,
+ "description": "用户 OSS 容量不足"
+ },
+ {
+ "code": 4060500,
+ "description": "用户配额已用光"
+ }
+ ]
+ },
+ {
+ "code": 4070000,
+ "description": "用户上传文件异常",
+ "children": [
+ {
+ "code": 4070100,
+ "description": "用户上传文件类型不匹配"
+ },
+ {
+ "code": 4070200,
+ "description": "用户上传文件太大"
+ },
+ {
+ "code": 4070300,
+ "description": "用户上传图片太大"
+ },
+ {
+ "code": 4070400,
+ "description": "用户上传视频太大"
+ },
+ {
+ "code": 4070500,
+ "description": "用户上传压缩文件太大"
+ }
+ ]
+ },
+ {
+ "code": 4080000,
+ "description": "用户当前版本异常",
+ "children": [
+ {
+ "code": 4080100,
+ "description": "用户安装版本与系统不匹配",
+ "children": [
+ {
+ "code": 4080101,
+ "description": "用户安装版本过低"
+ },
+ {
+ "code": 4080102,
+ "description": "用户安装版本过高"
+ },
+ {
+ "code": 4080103,
+ "description": "用户安装版本已过期"
+ }
+ ]
+ },
+ {
+ "code": 4080200,
+ "description": "用户 API 请求版本不匹配",
+ "children": [
+ {
+ "code": 4080201,
+ "description": "用户 API 请求版本过高"
+ },
+ {
+ "code": 4080202,
+ "description": "用户 API 请求版本过低"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "code": 4090000,
+ "description": "用户隐私未授权",
+ "children": [
+ {
+ "code": 4090100,
+ "description": "用户隐私未签署"
+ },
+ {
+ "code": 4090200,
+ "description": "用户摄像头未授权"
+ },
+ {
+ "code": 4090300,
+ "description": "用户相机未授权"
+ },
+ {
+ "code": 4090400,
+ "description": "用户图片库未授权"
+ },
+ {
+ "code": 4090500,
+ "description": "用户文件未授权"
+ },
+ {
+ "code": 4090600,
+ "description": "用户位置信息未授权"
+ },
+ {
+ "code": 4090700,
+ "description": "用户通讯录未授权"
+ }
+ ]
+ },
+ {
+ "code": 4100000,
+ "description": "用户设备异常",
+ "children": [
+ {
+ "code": 4100100,
+ "description": "用户相机异常"
+ },
+ {
+ "code": 4100200,
+ "description": "用户麦克风异常"
+ },
+ {
+ "code": 4100300,
+ "description": "用户听筒异常"
+ },
+ {
+ "code": 4100400,
+ "description": "用户扬声器异常"
+ },
+ {
+ "code": 4100500,
+ "description": "用户 GPS 定位异常"
+ }
+ ]
+ },
+ {
+ "code": 4110000,
+ "description": "数据资源异常",
+ "children": [
+ {
+ "code": 4110100,
+ "description": "数据资源不存在"
+ },
+ {
+ "code": 4110200,
+ "description": "数据操作的行数不符合预期"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "code": 5000000,
+ "description": "系统执行出错",
+ "children": [
+ {
+ "code": 5010000,
+ "description": "系统执行超时",
+ "children": [
+ {
+ "code": 5010100,
+ "description": "系统订单处理超时"
+ }
+ ]
+ },
+ {
+ "code": 5020000,
+ "description": "系统容灾功能被触发",
+ "children": [
+ {
+ "code": 5020100,
+ "description": "系统限流"
+ },
+ {
+ "code": 5020200,
+ "description": "系统功能降级"
+ }
+ ]
+ },
+ {
+ "code": 5030000,
+ "description": "系统资源异常",
+ "children": [
+ {
+ "code": 5030100,
+ "description": "系统资源耗尽",
+ "children": [
+ {
+ "code": 5030101,
+ "description": "系统磁盘空间耗尽"
+ },
+ {
+ "code": 5030102,
+ "description": "系统内存耗尽"
+ },
+ {
+ "code": 5030103,
+ "description": "文件句柄耗尽"
+ },
+ {
+ "code": 5030104,
+ "description": "系统连接池耗尽"
+ },
+ {
+ "code": 5030105,
+ "description": "系统线程池耗尽"
+ }
+ ]
+ },
+ {
+ "code": 5030200,
+ "description": "系统资源访问异常",
+ "children": [
+ {
+ "code": 5030201,
+ "description": "系统读取磁盘文件失败"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "code": 6000000,
+ "description": "调用第三方服务出错",
+ "children": [
+ {
+ "code": 6010000,
+ "description": "中间件服务出错",
+ "children": [
+ {
+ "code": 6010100,
+ "description": "RPC 服务出错",
+ "children": [
+ {
+ "code": 6010101,
+ "description": "RPC 服务未找到"
+ },
+ {
+ "code": 6010102,
+ "description": "RPC 服务未注册"
+ },
+ {
+ "code": 6010103,
+ "description": "接口不存在"
+ }
+ ]
+ },
+ {
+ "code": 6010200,
+ "description": "消息服务出错",
+ "children": [
+ {
+ "code": 6010201,
+ "description": "消息投递出错"
+ },
+ {
+ "code": 6010202,
+ "description": "消息消费出错"
+ },
+ {
+ "code": 6010203,
+ "description": "消息订阅出错"
+ },
+ {
+ "code": 6010204,
+ "description": "消息分组未查到"
+ }
+ ]
+ },
+ {
+ "code": 6010300,
+ "description": "缓存服务出错",
+ "children": [
+ {
+ "code": 6010301,
+ "description": "key 长度超过限制"
+ },
+ {
+ "code": 6010302,
+ "description": "value 长度超过限制"
+ },
+ {
+ "code": 6010303,
+ "description": "存储容量已满"
+ },
+ {
+ "code": 6010304,
+ "description": "不支持的数据格式"
+ }
+ ]
+ },
+ {
+ "code": 6010400,
+ "description": "配置服务出错"
+ },
+ {
+ "code": 6010500,
+ "description": "网络资源服务出错",
+ "children": [
+ {
+ "code": 6010501,
+ "description": "VPN 服务出错"
+ },
+ {
+ "code": 6010502,
+ "description": "CDN 服务出错"
+ },
+ {
+ "code": 6010503,
+ "description": "域名解析服务出错"
+ },
+ {
+ "code": 6010504,
+ "description": "网关服务出错"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "code": 6020000,
+ "description": "第三方系统执行超时",
+ "children": [
+ {
+ "code": 6020100,
+ "description": "RPC 执行超时"
+ },
+ {
+ "code": 6020200,
+ "description": "消息投递超时"
+ },
+ {
+ "code": 6020300,
+ "description": "缓存服务超时"
+ },
+ {
+ "code": 6020400,
+ "description": "配置服务超时"
+ },
+ {
+ "code": 6020500,
+ "description": "数据库服务超时"
+ }
+ ]
+ },
+ {
+ "code": 6030000,
+ "description": "数据库服务出错",
+ "children": [
+ {
+ "code": 6030100,
+ "description": "表不存在"
+ },
+ {
+ "code": 6030200,
+ "description": "列不存在"
+ },
+ {
+ "code": 6030300,
+ "description": "多表关联中存在多个相同名称的列"
+ },
+ {
+ "code": 6030400,
+ "description": "数据库死锁"
+ },
+ {
+ "code": 6030500,
+ "description": "主键冲突"
+ }
+ ]
+ },
+ {
+ "code": 6040000,
+ "description": "第三方容灾系统被触发",
+ "children": [
+ {
+ "code": 6040100,
+ "description": "第三方系统限流"
+ },
+ {
+ "code": 6040200,
+ "description": "第三方功能降级"
+ }
+ ]
+ },
+ {
+ "code": 6050000,
+ "description": "通知服务出错",
+ "children": [
+ {
+ "code": 6050100,
+ "description": "短信提醒服务失败"
+ },
+ {
+ "code": 6050200,
+ "description": "语音提醒服务失败"
+ },
+ {
+ "code": 6050300,
+ "description": "邮件提醒服务失败"
+ }
+ ]
+ }
+ ]
+ }
+]
diff --git a/plusone-basic/plusone-basic-application/pom.xml b/plusone-basic/plusone-basic-application/pom.xml
new file mode 100644
index 0000000..21baad0
--- /dev/null
+++ b/plusone-basic/plusone-basic-application/pom.xml
@@ -0,0 +1,34 @@
+
+
+ 4.0.0
+
+ plusone-basic
+ xyz.zhouxy
+ 1.0.0-SNAPSHOT
+ ../pom.xml
+
+
+ plusone-basic-application
+
+
+
+ xyz.zhouxy
+ plusone-basic-common
+
+
+ xyz.zhouxy
+ plusone-basic-domain
+
+
+ xyz.zhouxy
+ plusone-basic-infrastructure
+
+
+
+
+ net.dreamlu
+ mica-xss
+
+
+
+
diff --git a/plusone-basic/plusone-basic-application/src/main/java/xyz/zhouxy/plusone/exception/config/PlusoneExceptionHandlerConfig.java b/plusone-basic/plusone-basic-application/src/main/java/xyz/zhouxy/plusone/exception/config/PlusoneExceptionHandlerConfig.java
new file mode 100644
index 0000000..7a06b84
--- /dev/null
+++ b/plusone-basic/plusone-basic-application/src/main/java/xyz/zhouxy/plusone/exception/config/PlusoneExceptionHandlerConfig.java
@@ -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();
+ }
+}
diff --git a/plusone-basic/plusone-basic-application/src/main/java/xyz/zhouxy/plusone/exception/handler/AllExceptionHandler.java b/plusone-basic/plusone-basic-application/src/main/java/xyz/zhouxy/plusone/exception/handler/AllExceptionHandler.java
new file mode 100644
index 0000000..00fb1e4
--- /dev/null
+++ b/plusone-basic/plusone-basic-application/src/main/java/xyz/zhouxy/plusone/exception/handler/AllExceptionHandler.java
@@ -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 ZhouXY
+ */
+@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 handleException(Throwable e) {
+ log.error(e.getMessage(), e);
+ return this.buildExceptionResponse(e);
+ }
+}
diff --git a/plusone-basic/plusone-basic-application/src/main/java/xyz/zhouxy/plusone/exception/handler/DefaultExceptionHandler.java b/plusone-basic/plusone-basic-application/src/main/java/xyz/zhouxy/plusone/exception/handler/DefaultExceptionHandler.java
new file mode 100644
index 0000000..e991bb8
--- /dev/null
+++ b/plusone-basic/plusone-basic-application/src/main/java/xyz/zhouxy/plusone/exception/handler/DefaultExceptionHandler.java
@@ -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;
+
+/**
+ * 默认异常的处理器
+ *
+ *
+ * 对 {@link IllegalArgumentException} 异常做了写默认处理。
+ *
+ *
+ *
+ * 将异常信息返回给前端时,隐藏掉数据库异常信息中关于数据库结构、SQL 语句的描述。
+ *
+ *
+ *
+ * 为了避免因为 {@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,甚至是负数。
+ *
+ *
+ * @author ZhouXY
+ */
+@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 handleException(Exception e) {
+ log.error(e.getMessage(), e);
+ return buildExceptionResponse(e);
+ }
+}
diff --git a/plusone-basic/plusone-basic-common/pom.xml b/plusone-basic/plusone-basic-common/pom.xml
new file mode 100644
index 0000000..f48323f
--- /dev/null
+++ b/plusone-basic/plusone-basic-common/pom.xml
@@ -0,0 +1,46 @@
+
+
+ 4.0.0
+
+ plusone-basic
+ xyz.zhouxy
+ 1.0.0-SNAPSHOT
+ ../pom.xml
+
+
+ plusone-basic-common
+
+
+
+ cn.dev33
+ sa-token-core
+
+
+
+ org.projectlombok
+ lombok
+
+
+
+ org.springframework
+ spring-tx
+
+
+
+ org.springframework
+ spring-webmvc
+
+
+
+ xyz.zhouxy.plusone
+ plusone-commons
+ 0.0.1-SNAPSHOT
+
+
+ xyz.zhouxy.plusone
+ plusone-exception-handler
+ 0.0.1-SNAPSHOT
+
+
+
+
diff --git a/plusone-basic/plusone-basic-common/src/main/java/xyz/zhouxy/plusone/exception/DataNotExistException.java b/plusone-basic/plusone-basic-common/src/main/java/xyz/zhouxy/plusone/exception/DataNotExistException.java
new file mode 100644
index 0000000..c727bc7
--- /dev/null
+++ b/plusone-basic/plusone-basic-common/src/main/java/xyz/zhouxy/plusone/exception/DataNotExistException.java
@@ -0,0 +1,31 @@
+package xyz.zhouxy.plusone.exception;
+
+import org.springframework.http.HttpStatus;
+import org.springframework.web.bind.annotation.ResponseStatus;
+
+/**
+ * 需要时,当查询数据不存在时抛出的异常
+ *
+ *
+ * 暂时先这样,后续完善异常体系时可能会更改。
+ *
+ *
+ * @author ZhouXY
+ * @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);
+ }
+}
diff --git a/plusone-basic/plusone-basic-common/src/main/java/xyz/zhouxy/plusone/exception/DataOperationNumberException.java b/plusone-basic/plusone-basic-common/src/main/java/xyz/zhouxy/plusone/exception/DataOperationNumberException.java
new file mode 100644
index 0000000..b121bdc
--- /dev/null
+++ b/plusone-basic/plusone-basic-common/src/main/java/xyz/zhouxy/plusone/exception/DataOperationNumberException.java
@@ -0,0 +1,31 @@
+package xyz.zhouxy.plusone.exception;
+
+import org.springframework.http.HttpStatus;
+import org.springframework.web.bind.annotation.ResponseStatus;
+
+/**
+ * 需要时,当数据操作的行数不符合预期时抛出的异常
+ *
+ *
+ * 暂时先这样,后续完善异常体系时可能会更改。
+ *
+ *
+ * @author ZhouXY
+ * @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);
+ }
+}
diff --git a/plusone-basic/plusone-basic-common/src/main/java/xyz/zhouxy/plusone/exception/InvalidInputException.java b/plusone-basic/plusone-basic-common/src/main/java/xyz/zhouxy/plusone/exception/InvalidInputException.java
new file mode 100644
index 0000000..f2a3be9
--- /dev/null
+++ b/plusone-basic/plusone-basic-common/src/main/java/xyz/zhouxy/plusone/exception/InvalidInputException.java
@@ -0,0 +1,56 @@
+package xyz.zhouxy.plusone.exception;
+
+import org.springframework.http.HttpStatus;
+import org.springframework.web.bind.annotation.ResponseStatus;
+
+/**
+ * 4040200 - 无效的用户输入
+ *
+ * @author ZhouXY
+ */
+@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);
+ }
+}
diff --git a/plusone-basic/plusone-basic-common/src/main/java/xyz/zhouxy/plusone/exception/UserOperationException.java b/plusone-basic/plusone-basic-common/src/main/java/xyz/zhouxy/plusone/exception/UserOperationException.java
new file mode 100644
index 0000000..77474b8
--- /dev/null
+++ b/plusone-basic/plusone-basic-common/src/main/java/xyz/zhouxy/plusone/exception/UserOperationException.java
@@ -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);
+ }
+
+}
diff --git a/plusone-basic/plusone-basic-common/src/main/java/xyz/zhouxy/plusone/util/AssertResult.java b/plusone-basic/plusone-basic-common/src/main/java/xyz/zhouxy/plusone/util/AssertResult.java
new file mode 100644
index 0000000..0e66185
--- /dev/null
+++ b/plusone-basic/plusone-basic-common/src/main/java/xyz/zhouxy/plusone/util/AssertResult.java
@@ -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 ZhouXY
+ */
+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);
+ }
+ }
+}
diff --git a/plusone-basic/plusone-basic-common/src/main/java/xyz/zhouxy/plusone/util/PlusoneStrUtil.java b/plusone-basic/plusone-basic-common/src/main/java/xyz/zhouxy/plusone/util/PlusoneStrUtil.java
new file mode 100644
index 0000000..f6ce284
--- /dev/null
+++ b/plusone-basic/plusone-basic-common/src/main/java/xyz/zhouxy/plusone/util/PlusoneStrUtil.java
@@ -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 ZhouXY
+ */
+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) : "";
+ }
+}
diff --git a/plusone-basic/plusone-basic-domain/pom.xml b/plusone-basic/plusone-basic-domain/pom.xml
new file mode 100644
index 0000000..ca01461
--- /dev/null
+++ b/plusone-basic/plusone-basic-domain/pom.xml
@@ -0,0 +1,39 @@
+
+
+ 4.0.0
+
+ xyz.zhouxy
+ plusone-basic
+ 1.0.0-SNAPSHOT
+ ../pom.xml
+
+ plusone-basic-domain
+
+
+
+ plusone-basic-common
+ xyz.zhouxy
+
+
+
+ cn.hutool
+ hutool-core
+
+
+
+ org.projectlombok
+ lombok
+
+
+
+ com.fasterxml.jackson.core
+ jackson-core
+ 2.13.4
+
+
+ com.fasterxml.jackson.core
+ jackson-annotations
+ 2.13.4
+
+
+
diff --git a/plusone-basic/plusone-basic-domain/src/main/java/xyz/zhouxy/plusone/constant/EntityStatus.java b/plusone-basic/plusone-basic-domain/src/main/java/xyz/zhouxy/plusone/constant/EntityStatus.java
new file mode 100644
index 0000000..cc0f7c7
--- /dev/null
+++ b/plusone-basic/plusone-basic-domain/src/main/java/xyz/zhouxy/plusone/constant/EntityStatus.java
@@ -0,0 +1,32 @@
+package xyz.zhouxy.plusone.constant;
+
+import xyz.zhouxy.plusone.util.Enumeration;
+import xyz.zhouxy.plusone.util.EnumerationValuesHolder;
+
+/**
+ * 实体状态
+ *
+ * @author ZhouXY
+ */
+public class EntityStatus extends Enumeration {
+
+ 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 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();
+ }
+}
diff --git a/plusone-basic/plusone-basic-domain/src/main/java/xyz/zhouxy/plusone/constant/RegexConsts.java b/plusone-basic/plusone-basic-domain/src/main/java/xyz/zhouxy/plusone/constant/RegexConsts.java
new file mode 100644
index 0000000..a34c436
--- /dev/null
+++ b/plusone-basic/plusone-basic-domain/src/main/java/xyz/zhouxy/plusone/constant/RegexConsts.java
@@ -0,0 +1,27 @@
+package xyz.zhouxy.plusone.constant;
+
+/**
+ * 正则表达式常量
+ *
+ * @author ZhouXY
+ */
+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");
+ }
+}
diff --git a/plusone-basic/plusone-basic-domain/src/main/java/xyz/zhouxy/plusone/domain/AggregateRoot.java b/plusone-basic/plusone-basic-domain/src/main/java/xyz/zhouxy/plusone/domain/AggregateRoot.java
new file mode 100644
index 0000000..0fb3a66
--- /dev/null
+++ b/plusone-basic/plusone-basic-domain/src/main/java/xyz/zhouxy/plusone/domain/AggregateRoot.java
@@ -0,0 +1,17 @@
+package xyz.zhouxy.plusone.domain;
+
+import java.io.Serializable;
+
+/**
+ * 聚合根
+ *
+ *
+ * 由 Repository 负责整个聚合根的查询、保存、删除。
+ *
+ *
+ * @author ZhouXY
+ * @see Entity
+ * @see IRepository
+ */
+public abstract class AggregateRoot extends Entity {
+}
diff --git a/plusone-basic/plusone-basic-domain/src/main/java/xyz/zhouxy/plusone/domain/DomainEvent.java b/plusone-basic/plusone-basic-domain/src/main/java/xyz/zhouxy/plusone/domain/DomainEvent.java
new file mode 100644
index 0000000..34acdda
--- /dev/null
+++ b/plusone-basic/plusone-basic-domain/src/main/java/xyz/zhouxy/plusone/domain/DomainEvent.java
@@ -0,0 +1,26 @@
+package xyz.zhouxy.plusone.domain;
+
+import cn.hutool.core.lang.UUID;
+import lombok.Getter;
+
+/**
+ * 领域事件
+ *
+ *
+ * 根据所使用的消息机制的不同,可能需要继承其它类,或实现 {@link java.io.Serializable} 接口
+ *
+ *
+ * @author ZhouXY
+ * @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();
+ }
+}
diff --git a/plusone-basic/plusone-basic-domain/src/main/java/xyz/zhouxy/plusone/domain/Entity.java b/plusone-basic/plusone-basic-domain/src/main/java/xyz/zhouxy/plusone/domain/Entity.java
new file mode 100644
index 0000000..06daaf6
--- /dev/null
+++ b/plusone-basic/plusone-basic-domain/src/main/java/xyz/zhouxy/plusone/domain/Entity.java
@@ -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;
+
+/**
+ * 实体
+ *
+ *
+ * DDD 中的实体,带有 ID。
+ *
+ *
+ *
+ * 维护一个 {@link DomainEvent} 的列表,持久化时可将其中的领域事件进行发布。
+ *
+ *
+ * @author ZhouXY
+ * @see IValueObject
+ * @see AggregateRoot
+ */
+public abstract class Entity {
+
+ private final List domainEvents = new ArrayList<>();
+
+ public abstract Optional getId();
+
+ protected void addDomainEvent(DomainEvent domainEvent) {
+ domainEvents.add(domainEvent);
+ }
+}
diff --git a/plusone-basic/plusone-basic-domain/src/main/java/xyz/zhouxy/plusone/domain/ICommand.java b/plusone-basic/plusone-basic-domain/src/main/java/xyz/zhouxy/plusone/domain/ICommand.java
new file mode 100644
index 0000000..de275f5
--- /dev/null
+++ b/plusone-basic/plusone-basic-domain/src/main/java/xyz/zhouxy/plusone/domain/ICommand.java
@@ -0,0 +1,10 @@
+package xyz.zhouxy.plusone.domain;
+
+/**
+ * 命令
+ *
+ * @author ZhouXY
+ * @see ICommandHandler
+ */
+public interface ICommand {
+}
diff --git a/plusone-basic/plusone-basic-domain/src/main/java/xyz/zhouxy/plusone/domain/ICommandHandler.java b/plusone-basic/plusone-basic-domain/src/main/java/xyz/zhouxy/plusone/domain/ICommandHandler.java
new file mode 100644
index 0000000..a3feaf8
--- /dev/null
+++ b/plusone-basic/plusone-basic-domain/src/main/java/xyz/zhouxy/plusone/domain/ICommandHandler.java
@@ -0,0 +1,12 @@
+package xyz.zhouxy.plusone.domain;
+
+/**
+ * 命令处理器
+ *
+ * @author ZhouXY
+ * @see ICommand
+ */
+@FunctionalInterface
+public interface ICommandHandler {
+ void handle(T command);
+}
diff --git a/plusone-basic/plusone-basic-domain/src/main/java/xyz/zhouxy/plusone/domain/IEventHandler.java b/plusone-basic/plusone-basic-domain/src/main/java/xyz/zhouxy/plusone/domain/IEventHandler.java
new file mode 100644
index 0000000..d49299a
--- /dev/null
+++ b/plusone-basic/plusone-basic-domain/src/main/java/xyz/zhouxy/plusone/domain/IEventHandler.java
@@ -0,0 +1,12 @@
+package xyz.zhouxy.plusone.domain;
+
+/**
+ * 事件处理器
+ *
+ * @author ZhouXY
+ * @see DomainEvent
+ */
+@FunctionalInterface
+public interface IEventHandler {
+ void handle(E command);
+}
diff --git a/plusone-basic/plusone-basic-domain/src/main/java/xyz/zhouxy/plusone/domain/IRepository.java b/plusone-basic/plusone-basic-domain/src/main/java/xyz/zhouxy/plusone/domain/IRepository.java
new file mode 100644
index 0000000..f913a4b
--- /dev/null
+++ b/plusone-basic/plusone-basic-domain/src/main/java/xyz/zhouxy/plusone/domain/IRepository.java
@@ -0,0 +1,21 @@
+package xyz.zhouxy.plusone.domain;
+
+import java.io.Serializable;
+
+/**
+ * Repository 基础接口
+ *
+ * @author ZhouXY
+ * @see AggregateRoot
+ */
+public interface IRepository, ID extends Serializable> {
+
+ T find(ID id);
+
+ T save(T entity);
+
+ void delete(T entity);
+
+ boolean exists(ID id);
+
+}
diff --git a/plusone-basic/plusone-basic-domain/src/main/java/xyz/zhouxy/plusone/domain/IValueObject.java b/plusone-basic/plusone-basic-domain/src/main/java/xyz/zhouxy/plusone/domain/IValueObject.java
new file mode 100644
index 0000000..4c6b0a6
--- /dev/null
+++ b/plusone-basic/plusone-basic-domain/src/main/java/xyz/zhouxy/plusone/domain/IValueObject.java
@@ -0,0 +1,9 @@
+package xyz.zhouxy.plusone.domain;
+
+/**
+ * 值对象
+ *
+ * @author ZhouXY
+ */
+public interface IValueObject {
+}
diff --git a/plusone-basic/plusone-basic-domain/src/main/java/xyz/zhouxy/plusone/domain/IWithLabel.java b/plusone-basic/plusone-basic-domain/src/main/java/xyz/zhouxy/plusone/domain/IWithLabel.java
new file mode 100644
index 0000000..156c6b9
--- /dev/null
+++ b/plusone-basic/plusone-basic-domain/src/main/java/xyz/zhouxy/plusone/domain/IWithLabel.java
@@ -0,0 +1,10 @@
+package xyz.zhouxy.plusone.domain;
+
+/**
+ * 带可读的 label 属性
+ *
+ * @author ZhouXY
+ */
+public interface IWithLabel {
+ String getLabel();
+}
diff --git a/plusone-basic/plusone-basic-domain/src/main/java/xyz/zhouxy/plusone/domain/IWithOrderNumber.java b/plusone-basic/plusone-basic-domain/src/main/java/xyz/zhouxy/plusone/domain/IWithOrderNumber.java
new file mode 100644
index 0000000..7c7e151
--- /dev/null
+++ b/plusone-basic/plusone-basic-domain/src/main/java/xyz/zhouxy/plusone/domain/IWithOrderNumber.java
@@ -0,0 +1,33 @@
+package xyz.zhouxy.plusone.domain;
+
+import java.util.Comparator;
+import java.util.Objects;
+
+/**
+ * 带 orderNumber 字段,可用来排序
+ *
+ * @author ZhouXY
+ */
+public interface IWithOrderNumber extends Comparable {
+
+ int getOrderNumber();
+
+ @Override
+ default int compareTo(IWithOrderNumber that) {
+ return new OrderNumberComparator().compare(this, that);
+ }
+
+ class OrderNumberComparator>
+ implements Comparator {
+ @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());
+ }
+ }
+}
diff --git a/plusone-basic/plusone-basic-domain/src/main/java/xyz/zhouxy/plusone/domain/IWithVersion.java b/plusone-basic/plusone-basic-domain/src/main/java/xyz/zhouxy/plusone/domain/IWithVersion.java
new file mode 100644
index 0000000..068b2bf
--- /dev/null
+++ b/plusone-basic/plusone-basic-domain/src/main/java/xyz/zhouxy/plusone/domain/IWithVersion.java
@@ -0,0 +1,10 @@
+package xyz.zhouxy.plusone.domain;
+
+/**
+ * 带版本号
+ *
+ * @author ZhouXY
+ */
+public interface IWithVersion {
+ long getVersion();
+}
diff --git a/plusone-basic/plusone-basic-domain/src/main/java/xyz/zhouxy/plusone/domain/ValidatableStringRecord.java b/plusone-basic/plusone-basic-domain/src/main/java/xyz/zhouxy/plusone/domain/ValidatableStringRecord.java
new file mode 100644
index 0000000..7a0e8ec
--- /dev/null
+++ b/plusone-basic/plusone-basic-domain/src/main/java/xyz/zhouxy/plusone/domain/ValidatableStringRecord.java
@@ -0,0 +1,34 @@
+package xyz.zhouxy.plusone.domain;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonValue;
+
+/**
+ * 带校验的字符串值对象
+ *
+ * @author ZhouXY
+ */
+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;
+ }
+}
diff --git a/plusone-basic/plusone-basic-domain/src/main/java/xyz/zhouxy/plusone/util/RegexUtil.java b/plusone-basic/plusone-basic-domain/src/main/java/xyz/zhouxy/plusone/util/RegexUtil.java
new file mode 100644
index 0000000..d0a5538
--- /dev/null
+++ b/plusone-basic/plusone-basic-domain/src/main/java/xyz/zhouxy/plusone/util/RegexUtil.java
@@ -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;
+ }
+}
diff --git a/plusone-basic/plusone-basic-infrastructure/pom.xml b/plusone-basic/plusone-basic-infrastructure/pom.xml
new file mode 100644
index 0000000..0a32200
--- /dev/null
+++ b/plusone-basic/plusone-basic-infrastructure/pom.xml
@@ -0,0 +1,95 @@
+
+
+ 4.0.0
+
+ xyz.zhouxy
+ plusone-basic
+ 1.0.0-SNAPSHOT
+ ../pom.xml
+
+ plusone-basic-infrastructure
+
+
+
+
+ xyz.zhouxy
+ plusone-basic-common
+
+
+ xyz.zhouxy
+ plusone-basic-domain
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ org.springframework.boot
+ spring-boot-starter-aop
+
+
+ com.baomidou
+ mybatis-plus-boot-starter
+
+
+ org.springframework.boot
+ spring-boot-starter-data-jdbc
+
+
+ org.springframework.boot
+ spring-boot-starter-data-redis
+
+
+ org.springframework.boot
+ spring-boot-starter-mail
+
+
+ org.springframework.boot
+ spring-boot-starter-validation
+
+
+
+ org.springframework.boot
+ spring-boot-devtools
+ runtime
+
+
+
+ org.postgresql
+ postgresql
+ runtime
+
+
+ org.projectlombok
+ lombok
+
+
+
+ cn.dev33
+ sa-token-spring-boot-starter
+
+
+
+ cn.dev33
+ sa-token-dao-redis-jackson
+
+
+
+ org.springframework.boot
+ spring-boot-autoconfigure
+
+
+
+ org.springframework.boot
+ spring-boot-configuration-processor
+ true
+
+
+
+ com.tencentcloudapi
+ tencentcloud-sdk-java-sms
+
+
+
+
diff --git a/plusone-basic/plusone-basic-infrastructure/src/main/java/xyz/zhouxy/plusone/config/PlusoneConfig.java b/plusone-basic/plusone-basic-infrastructure/src/main/java/xyz/zhouxy/plusone/config/PlusoneConfig.java
new file mode 100644
index 0000000..7772cbb
--- /dev/null
+++ b/plusone-basic/plusone-basic-infrastructure/src/main/java/xyz/zhouxy/plusone/config/PlusoneConfig.java
@@ -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 ZhouXY
+ */
+@Configuration
+@PropertySource(value = {"classpath:conf/plusone.properties"}, encoding = "utf-8")
+public class PlusoneConfig {
+}
diff --git a/plusone-basic/plusone-basic-infrastructure/src/main/java/xyz/zhouxy/plusone/infrastructure/pojo/AbstractEntityPO.java b/plusone-basic/plusone-basic-infrastructure/src/main/java/xyz/zhouxy/plusone/infrastructure/pojo/AbstractEntityPO.java
new file mode 100644
index 0000000..de65a41
--- /dev/null
+++ b/plusone-basic/plusone-basic-infrastructure/src/main/java/xyz/zhouxy/plusone/infrastructure/pojo/AbstractEntityPO.java
@@ -0,0 +1,41 @@
+package xyz.zhouxy.plusone.infrastructure.pojo;
+
+import java.time.LocalDateTime;
+
+import lombok.Getter;
+import lombok.Setter;
+import lombok.ToString;
+
+/**
+ * 实体的持久化对象
+ *
+ *
+ * 由于本项目目前是手动实现 Repository,直接将实体持久化,
+ * 查询结果也是直接实例化为 Entity,故暂时不使用 PO。
+ *
+ *
+ * @author ZhouXY
+ */
+@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;
+ }
+}
diff --git a/plusone-basic/plusone-basic-infrastructure/src/main/java/xyz/zhouxy/plusone/jdbc/BeanPropertyParamSource.java b/plusone-basic/plusone-basic-infrastructure/src/main/java/xyz/zhouxy/plusone/jdbc/BeanPropertyParamSource.java
new file mode 100644
index 0000000..bcb72b0
--- /dev/null
+++ b/plusone-basic/plusone-basic-infrastructure/src/main/java/xyz/zhouxy/plusone/jdbc/BeanPropertyParamSource.java
@@ -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 ZhouXY
+ *
+ * @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;
+ }
+}
diff --git a/plusone-basic/plusone-basic-infrastructure/src/main/java/xyz/zhouxy/plusone/jdbc/JdbcConfig.java b/plusone-basic/plusone-basic-infrastructure/src/main/java/xyz/zhouxy/plusone/jdbc/JdbcConfig.java
new file mode 100644
index 0000000..01041e2
--- /dev/null
+++ b/plusone-basic/plusone-basic-infrastructure/src/main/java/xyz/zhouxy/plusone/jdbc/JdbcConfig.java
@@ -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 配置
+ *
+ *
+ * 配置了类型转换器
+ *
+ *
+ *
+ * 这里声明了 {@link DefaultQueryMappingConfiguration} 的实例为 Spring bean。
+ * 在其它配置类中注入该 bean,就可以向其中添加
+ * {@link org.springframework.jdbc.core.RowMapper}
+ * 供 Spring Data JDBC 查询时使用。如下所示:
+ *
+ *
+ * {@code @Configuration}
+ * class CustomConfig {
+ * public CustomConfig(DefaultQueryMappingConfiguration rowMappers) {
+ * rowMappers.registerRowMapper(Person.class, new PersonRowMapper());
+ * }
+ * }
+ *
+ *
+ *
+ * @author ZhouXY
+ */
+@Configuration
+public class JdbcConfig extends AbstractJdbcConfiguration {
+
+ @Override
+ public JdbcCustomConversions jdbcCustomConversions() {
+ return new JdbcCustomConversions(List.of(EnumToOrdinalConverter.INSTANCE));
+ }
+
+ @Bean
+ QueryMappingConfiguration rowMappers() {
+ return new DefaultQueryMappingConfiguration();
+ }
+}
diff --git a/plusone-basic/plusone-basic-infrastructure/src/main/java/xyz/zhouxy/plusone/jdbc/JdbcEntityDaoSupport.java b/plusone-basic/plusone-basic-infrastructure/src/main/java/xyz/zhouxy/plusone/jdbc/JdbcEntityDaoSupport.java
new file mode 100644
index 0000000..96ec4a4
--- /dev/null
+++ b/plusone-basic/plusone-basic-infrastructure/src/main/java/xyz/zhouxy/plusone/jdbc/JdbcEntityDaoSupport.java
@@ -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, ID extends Serializable> {
+ protected final NamedParameterJdbcTemplate jdbc;
+
+ protected RowMapper rowMapper;
+ protected ResultSetExtractor 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 queryForList(String sql) {
+ return this.jdbc.query(sql, this.rowMapper);
+ }
+
+ protected final List queryForList(String sql, SqlParameterSource parameterSource) {
+ return this.jdbc.query(sql, parameterSource, this.rowMapper);
+ }
+
+ protected final Stream queryForStream(String sql, SqlParameterSource parameterSource) {
+ return this.jdbc.queryForStream(sql, parameterSource, this.rowMapper);
+ }
+
+ protected final Stream queryForStream(String sql, SqlParameterSource parameterSource, Class 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 rowMapper) {
+ this.rowMapper = rowMapper;
+ }
+
+ protected void setResultSetExtractor(@Nonnull ResultSetExtractor resultSetExtractor) {
+ this.resultSetExtractor = resultSetExtractor;
+ }
+}
diff --git a/plusone-basic/plusone-basic-infrastructure/src/main/java/xyz/zhouxy/plusone/jdbc/JdbcFactory.java b/plusone-basic/plusone-basic-infrastructure/src/main/java/xyz/zhouxy/plusone/jdbc/JdbcFactory.java
new file mode 100644
index 0000000..d3acc45
--- /dev/null
+++ b/plusone-basic/plusone-basic-infrastructure/src/main/java/xyz/zhouxy/plusone/jdbc/JdbcFactory.java
@@ -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 ZhouXY
+ * @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);
+ }
+}
diff --git a/plusone-basic/plusone-basic-infrastructure/src/main/java/xyz/zhouxy/plusone/jdbc/JdbcRepositorySupport.java b/plusone-basic/plusone-basic-infrastructure/src/main/java/xyz/zhouxy/plusone/jdbc/JdbcRepositorySupport.java
new file mode 100644
index 0000000..3b44b60
--- /dev/null
+++ b/plusone-basic/plusone-basic-infrastructure/src/main/java/xyz/zhouxy/plusone/jdbc/JdbcRepositorySupport.java
@@ -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, ID extends Serializable>
+ extends JdbcEntityDaoSupport
+ implements IRepository {
+
+ 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);
+ }
+}
diff --git a/plusone-basic/plusone-basic-infrastructure/src/main/java/xyz/zhouxy/plusone/jdbc/common/ResultMapper.java b/plusone-basic/plusone-basic-infrastructure/src/main/java/xyz/zhouxy/plusone/jdbc/common/ResultMapper.java
new file mode 100644
index 0000000..55cf363
--- /dev/null
+++ b/plusone-basic/plusone-basic-infrastructure/src/main/java/xyz/zhouxy/plusone/jdbc/common/ResultMapper.java
@@ -0,0 +1,45 @@
+package xyz.zhouxy.plusone.jdbc.common;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+/**
+ * 查询结果处理
+ *
+ *
+ * 通过在 {@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 的配置中使用。
+ *
+ *
+ *
+ * 在
+ * {@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} 的对象即可。
+ *
+ *
+ * @author ZhouXY
+ * @see org.springframework.data.jdbc.repository.config.DefaultQueryMappingConfiguration
+ */
+@FunctionalInterface
+public interface ResultMapper {
+
+ 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;
+ }
+}
diff --git a/plusone-basic/plusone-basic-infrastructure/src/main/java/xyz/zhouxy/plusone/jdbc/common/SimpleResultMapper.java b/plusone-basic/plusone-basic-infrastructure/src/main/java/xyz/zhouxy/plusone/jdbc/common/SimpleResultMapper.java
new file mode 100644
index 0000000..0420538
--- /dev/null
+++ b/plusone-basic/plusone-basic-infrastructure/src/main/java/xyz/zhouxy/plusone/jdbc/common/SimpleResultMapper.java
@@ -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 implements ResultMapper {
+
+ private final RowMapper rowMapper;
+
+ public SimpleResultMapper(RowMapper 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 SimpleResultMapper of(Class clazz) {
+ return new SimpleResultMapper<>(new BeanPropertyRowMapper<>(clazz));
+ }
+}
diff --git a/plusone-basic/plusone-basic-infrastructure/src/main/java/xyz/zhouxy/plusone/jdbc/converter/EnumToOrdinalConverter.java b/plusone-basic/plusone-basic-infrastructure/src/main/java/xyz/zhouxy/plusone/jdbc/converter/EnumToOrdinalConverter.java
new file mode 100644
index 0000000..85af739
--- /dev/null
+++ b/plusone-basic/plusone-basic-infrastructure/src/main/java/xyz/zhouxy/plusone/jdbc/converter/EnumToOrdinalConverter.java
@@ -0,0 +1,20 @@
+package xyz.zhouxy.plusone.jdbc.converter;
+
+import org.springframework.core.convert.converter.Converter;
+import org.springframework.data.convert.WritingConverter;
+
+/**
+ * 枚举序号转换器
+ *
+ * @author ZhouXY
+ */
+@WritingConverter
+public enum EnumToOrdinalConverter implements Converter, Integer> {
+ INSTANCE
+ ;
+
+ @Override
+ public Integer convert(Enum> source) {
+ return source.ordinal();
+ }
+}
diff --git a/plusone-basic/plusone-basic-infrastructure/src/main/java/xyz/zhouxy/plusone/jdbc/converter/OrdinalToEnumConverter.java b/plusone-basic/plusone-basic-infrastructure/src/main/java/xyz/zhouxy/plusone/jdbc/converter/OrdinalToEnumConverter.java
new file mode 100644
index 0000000..d62f17a
--- /dev/null
+++ b/plusone-basic/plusone-basic-infrastructure/src/main/java/xyz/zhouxy/plusone/jdbc/converter/OrdinalToEnumConverter.java
@@ -0,0 +1,44 @@
+package xyz.zhouxy.plusone.jdbc.converter;
+
+import org.springframework.core.convert.converter.Converter;
+import org.springframework.data.convert.ReadingConverter;
+
+/**
+ * 序号枚举转换器
+ *
+ * 向 {@link org.springframework.data.jdbc.core.convert.JdbcCustomConversions}
+ * 中添加类型转换器,
+ * 如:
+ *
+ *
+ * return new JdbcCustomConversions(List.of(new OrdinalToEnumConverter()));
+ *
+ *
+ *
+ * @author ZhouXY
+ *
+ * @see Converter
+ * @see org.springframework.data.jdbc.core.convert.JdbcCustomConversions
+ */
+@ReadingConverter
+public class OrdinalToEnumConverter> implements Converter> {
+
+ private final Class type;
+ private final E[] constants;
+
+ public OrdinalToEnumConverter(Class type) {
+ this.type = type;
+ this.constants = type.getEnumConstants();
+ }
+
+ @Override
+ public Enum 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);
+ }
+ }
+}
diff --git a/plusone-basic/plusone-basic-infrastructure/src/main/java/xyz/zhouxy/plusone/mail/MailMessageFactory.java b/plusone-basic/plusone-basic-infrastructure/src/main/java/xyz/zhouxy/plusone/mail/MailMessageFactory.java
new file mode 100644
index 0000000..330d4cc
--- /dev/null
+++ b/plusone-basic/plusone-basic-infrastructure/src/main/java/xyz/zhouxy/plusone/mail/MailMessageFactory.java
@@ -0,0 +1,31 @@
+package xyz.zhouxy.plusone.mail;
+
+import org.springframework.mail.SimpleMailMessage;
+
+/**
+ * 构建 {@link SimpleMailMessage}
+ *
+ * @author ZhouXY
+ */
+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;
+ }
+}
diff --git a/plusone-basic/plusone-basic-infrastructure/src/main/java/xyz/zhouxy/plusone/mail/MailService.java b/plusone-basic/plusone-basic-infrastructure/src/main/java/xyz/zhouxy/plusone/mail/MailService.java
new file mode 100644
index 0000000..3590015
--- /dev/null
+++ b/plusone-basic/plusone-basic-infrastructure/src/main/java/xyz/zhouxy/plusone/mail/MailService.java
@@ -0,0 +1,17 @@
+package xyz.zhouxy.plusone.mail;
+
+import javax.mail.MessagingException;
+
+/**
+ * 邮件服务
+ *
+ * @author ZhouXY
+ */
+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);
+}
diff --git a/plusone-basic/plusone-basic-infrastructure/src/main/java/xyz/zhouxy/plusone/mail/PlusoneMailAutoConfiguration.java b/plusone-basic/plusone-basic-infrastructure/src/main/java/xyz/zhouxy/plusone/mail/PlusoneMailAutoConfiguration.java
new file mode 100644
index 0000000..69db2b2
--- /dev/null
+++ b/plusone-basic/plusone-basic-infrastructure/src/main/java/xyz/zhouxy/plusone/mail/PlusoneMailAutoConfiguration.java
@@ -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 ZhouXY
+ */
+@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);
+ }
+}
diff --git a/plusone-basic/plusone-basic-infrastructure/src/main/java/xyz/zhouxy/plusone/mail/PlusoneMailProperties.java b/plusone-basic/plusone-basic-infrastructure/src/main/java/xyz/zhouxy/plusone/mail/PlusoneMailProperties.java
new file mode 100644
index 0000000..74eb2c8
--- /dev/null
+++ b/plusone-basic/plusone-basic-infrastructure/src/main/java/xyz/zhouxy/plusone/mail/PlusoneMailProperties.java
@@ -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 subject;
+ private Map template;
+}
diff --git a/plusone-basic/plusone-basic-infrastructure/src/main/java/xyz/zhouxy/plusone/mail/SimpleMailService.java b/plusone-basic/plusone-basic-infrastructure/src/main/java/xyz/zhouxy/plusone/mail/SimpleMailService.java
new file mode 100644
index 0000000..e333994
--- /dev/null
+++ b/plusone-basic/plusone-basic-infrastructure/src/main/java/xyz/zhouxy/plusone/mail/SimpleMailService.java
@@ -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;
+
+/**
+ * 邮件服务。不能发送 HTML 邮件。
+ *
+ * @author ZhouXY
+ */
+@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;
+ }
+ }
+}
diff --git a/plusone-basic/plusone-basic-infrastructure/src/main/java/xyz/zhouxy/plusone/mybatis/MyBatisAutoConfiguration.java b/plusone-basic/plusone-basic-infrastructure/src/main/java/xyz/zhouxy/plusone/mybatis/MyBatisAutoConfiguration.java
new file mode 100644
index 0000000..2191a44
--- /dev/null
+++ b/plusone-basic/plusone-basic-infrastructure/src/main/java/xyz/zhouxy/plusone/mybatis/MyBatisAutoConfiguration.java
@@ -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);
+ }
+}
diff --git a/plusone-basic/plusone-basic-infrastructure/src/main/java/xyz/zhouxy/plusone/mybatis/MyBatisPlusConfig.java b/plusone-basic/plusone-basic-infrastructure/src/main/java/xyz/zhouxy/plusone/mybatis/MyBatisPlusConfig.java
new file mode 100644
index 0000000..0ba5c2c
--- /dev/null
+++ b/plusone-basic/plusone-basic-infrastructure/src/main/java/xyz/zhouxy/plusone/mybatis/MyBatisPlusConfig.java
@@ -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;
+ }
+}
diff --git a/plusone-basic/plusone-basic-infrastructure/src/main/java/xyz/zhouxy/plusone/mybatis/MybatisUtil.java b/plusone-basic/plusone-basic-infrastructure/src/main/java/xyz/zhouxy/plusone/mybatis/MybatisUtil.java
new file mode 100644
index 0000000..7a33ff5
--- /dev/null
+++ b/plusone-basic/plusone-basic-infrastructure/src/main/java/xyz/zhouxy/plusone/mybatis/MybatisUtil.java
@@ -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;
+ }
+}
diff --git a/plusone-basic/plusone-basic-infrastructure/src/main/java/xyz/zhouxy/plusone/redis/RedisStrCacheDAO.java b/plusone-basic/plusone-basic-infrastructure/src/main/java/xyz/zhouxy/plusone/redis/RedisStrCacheDAO.java
new file mode 100644
index 0000000..3c4e94a
--- /dev/null
+++ b/plusone-basic/plusone-basic-infrastructure/src/main/java/xyz/zhouxy/plusone/redis/RedisStrCacheDAO.java
@@ -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 ZhouXY
+ */
+@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 ops = template.opsForValue();
+ ops.set(key, value, timeout, unit);
+ }
+
+ @Override
+ public String getValue(String key) {
+ ValueOperations ops = this.template.opsForValue();
+ return ops.get(key);
+ }
+
+ @Override
+ public String getValueAndDelete(String key) {
+ ValueOperations ops = this.template.opsForValue();
+ String value = ops.get(key);
+ template.delete(key);
+ return value;
+ }
+}
diff --git a/plusone-basic/plusone-basic-infrastructure/src/main/java/xyz/zhouxy/plusone/redis/StrCacheDAO.java b/plusone-basic/plusone-basic-infrastructure/src/main/java/xyz/zhouxy/plusone/redis/StrCacheDAO.java
new file mode 100644
index 0000000..a6ec8e4
--- /dev/null
+++ b/plusone-basic/plusone-basic-infrastructure/src/main/java/xyz/zhouxy/plusone/redis/StrCacheDAO.java
@@ -0,0 +1,17 @@
+package xyz.zhouxy.plusone.redis;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * 字符串缓存接口
+ *
+ * @author ZhouXY
+ */
+public interface StrCacheDAO {
+
+ void setValue(String key, String value, long timeout, TimeUnit unit);
+
+ String getValue(String key);
+
+ String getValueAndDelete(String key);
+}
diff --git a/plusone-basic/plusone-basic-infrastructure/src/main/java/xyz/zhouxy/plusone/sms/PlusoneSmsAutoConfiguration.java b/plusone-basic/plusone-basic-infrastructure/src/main/java/xyz/zhouxy/plusone/sms/PlusoneSmsAutoConfiguration.java
new file mode 100644
index 0000000..f712f41
--- /dev/null
+++ b/plusone-basic/plusone-basic-infrastructure/src/main/java/xyz/zhouxy/plusone/sms/PlusoneSmsAutoConfiguration.java
@@ -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 ZhouXY
+ */
+@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);
+ }
+}
diff --git a/plusone-basic/plusone-basic-infrastructure/src/main/java/xyz/zhouxy/plusone/sms/SmsProperties.java b/plusone-basic/plusone-basic-infrastructure/src/main/java/xyz/zhouxy/plusone/sms/SmsProperties.java
new file mode 100644
index 0000000..7107cd8
--- /dev/null
+++ b/plusone-basic/plusone-basic-infrastructure/src/main/java/xyz/zhouxy/plusone/sms/SmsProperties.java
@@ -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 ZhouXY
+ */
+@Data
+@ConfigurationProperties("plusone.sms")
+public class SmsProperties {
+ private String region;
+ private SmsCredentialProperties credential;
+ private SmsClientProperties client;
+ private String appId;
+ private Map 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;
+}
diff --git a/plusone-basic/plusone-basic-infrastructure/src/main/java/xyz/zhouxy/plusone/sms/SmsService.java b/plusone-basic/plusone-basic-infrastructure/src/main/java/xyz/zhouxy/plusone/sms/SmsService.java
new file mode 100644
index 0000000..2e872a1
--- /dev/null
+++ b/plusone-basic/plusone-basic-infrastructure/src/main/java/xyz/zhouxy/plusone/sms/SmsService.java
@@ -0,0 +1,13 @@
+package xyz.zhouxy.plusone.sms;
+
+/**
+ * SMS 服务
+ *
+ * @author ZhouXY
+ */
+public interface SmsService {
+
+ void sendCodeMessage(String code, String phoneNumber);
+
+ void sendCodeMessage(String signName, String code, String phoneNumber);
+}
diff --git a/plusone-basic/plusone-basic-infrastructure/src/main/java/xyz/zhouxy/plusone/sms/TencentSmsServiceImpl.java b/plusone-basic/plusone-basic-infrastructure/src/main/java/xyz/zhouxy/plusone/sms/TencentSmsServiceImpl.java
new file mode 100644
index 0000000..2b79f97
--- /dev/null
+++ b/plusone-basic/plusone-basic-infrastructure/src/main/java/xyz/zhouxy/plusone/sms/TencentSmsServiceImpl.java
@@ -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 ZhouXY
+ */
+@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;
+ }
+}
diff --git a/plusone-basic/plusone-basic-infrastructure/src/main/java/xyz/zhouxy/plusone/spring/SpringContextHolder.java b/plusone-basic/plusone-basic-infrastructure/src/main/java/xyz/zhouxy/plusone/spring/SpringContextHolder.java
new file mode 100644
index 0000000..089cee5
--- /dev/null
+++ b/plusone-basic/plusone-basic-infrastructure/src/main/java/xyz/zhouxy/plusone/spring/SpringContextHolder.java
@@ -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;
+ }
+ }
+}
diff --git a/plusone-basic/plusone-basic-infrastructure/src/main/java/xyz/zhouxy/plusone/sql/SQL.java b/plusone-basic/plusone-basic-infrastructure/src/main/java/xyz/zhouxy/plusone/sql/SQL.java
new file mode 100644
index 0000000..df2867c
--- /dev/null
+++ b/plusone-basic/plusone-basic-infrastructure/src/main/java/xyz/zhouxy/plusone/sql/SQL.java
@@ -0,0 +1,34 @@
+package xyz.zhouxy.plusone.sql;
+
+import java.util.Objects;
+
+import org.apache.ibatis.jdbc.AbstractSQL;
+
+/**
+ * 扩展 MyBatis 中的 SQL 语句构造器
+ *
+ * @author ZhouXY
+ */
+public class SQL extends AbstractSQL {
+
+ 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;
+ }
+}
diff --git a/plusone-basic/plusone-basic-infrastructure/src/main/java/xyz/zhouxy/plusone/validator/BaseValidator.java b/plusone-basic/plusone-basic-infrastructure/src/main/java/xyz/zhouxy/plusone/validator/BaseValidator.java
new file mode 100644
index 0000000..36b13f6
--- /dev/null
+++ b/plusone-basic/plusone-basic-infrastructure/src/main/java/xyz/zhouxy/plusone/validator/BaseValidator.java
@@ -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;
+
+/**
+ * 校验器
+ *
+ * 可以使用以下方式初始化一个校验器:
+ *
+ *
+ * BaseValidator<Integer> validator = new BaseValidator<>() {
+ * {
+ * ruleFor(value -> Objects.nonNull(value), "value 不能为空");
+ * ruleFor(value -> (value >= 0 && value <= 500), "value 应在 [0, 500] 内");
+ * }
+ * };
+ *
+ *
+ * 也可以通过继承本类,定义一个校验器(可使用单例模式)。
+ *
+ *
+ * 然后通过校验器的 {@link #validate} 方法,或
+ * {@link ValidateUtil#validate(Object, Validator)} 对指定对象进行校验。
+ *
+ *
+ *
+ * ValidateUtil.validate(255, validator);
+ *
+ *
+ *
+ * validator.validate(666);
+ *
+ *
+ *
+ * @author ZhouXY
+ * @see IValidateRequired
+ * @see ValidateUtil
+ * @see Validator
+ */
+public abstract class BaseValidator {
+
+ private final List> rules = new ArrayList<>();
+
+ protected BaseValidator() {
+ }
+
+ protected final void ruleFor(Predicate rule, String errorMessage) {
+ this.rules.add(new RuleInfo<>(rule, errorMessage));
+ }
+
+ public void validate(T obj) {
+ this.rules.forEach((RuleInfo ruleInfo) -> {
+ if (!ruleInfo.rule.test(obj)) {
+ throw new InvalidInputException(ruleInfo.message);
+ }
+ });
+ }
+
+ protected static class RuleInfo {
+ Predicate rule;
+ String message;
+
+ public RuleInfo(Predicate rule, String message) {
+ this.rule = rule;
+ this.message = message;
+ }
+ }
+}
diff --git a/plusone-basic/plusone-basic-infrastructure/src/main/java/xyz/zhouxy/plusone/validator/BaseValidator2.java b/plusone-basic/plusone-basic-infrastructure/src/main/java/xyz/zhouxy/plusone/validator/BaseValidator2.java
new file mode 100644
index 0000000..b7ba736
--- /dev/null
+++ b/plusone-basic/plusone-basic-infrastructure/src/main/java/xyz/zhouxy/plusone/validator/BaseValidator2.java
@@ -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 {
+
+ private List> hs = new ArrayList<>();
+
+ protected final ValidValueHolder ruleFor(Function getter) {
+ ValidValueHolder 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 {
+ Function getter;
+
+ List> rules = new ArrayList<>();
+
+ public ValidValueHolder(Function getter) {
+ this.getter = getter;
+ }
+
+ private void addRule(Predicate