From 41cf60b3d8f6e41a4404b504cfbfd58783d9efee Mon Sep 17 00:00:00 2001
From: valarchie <343928303@qq.com>
Date: Sat, 8 Oct 2022 13:50:29 +0800
Subject: [PATCH] =?UTF-8?q?AgileBoot=20V1.0=20=E5=8F=91=E5=B8=83=20=20?=
=?UTF-8?q?=E5=9F=BA=E4=BA=8ERuoyi=E6=94=B9=E9=80=A0=20=E5=9F=BA=E6=9C=AC?=
=?UTF-8?q?=E9=83=BD=E5=B7=B2=E9=87=8D=E6=9E=84?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.github/FUNDING.yml | 1 +
.gitignore | 48 ++
GoogleStyle.xml | 567 +++++++++++++++
README.md | 103 +++
agileboot-admin/pom.xml | 58 ++
.../controller/common/FileController.java | 123 ++++
.../controller/common/LoginController.java | 120 ++++
.../controller/monitor/MonitorController.java | 77 ++
.../system/SysConfigController.java | 98 +++
.../controller/system/SysDeptController.java | 126 ++++
.../system/SysLoginInfoController.java | 71 ++
.../controller/system/SysMenuController.java | 120 ++++
.../system/SysNoticeController.java | 97 +++
.../system/SysOperationLogController.java | 67 ++
.../controller/system/SysPostController.java | 104 +++
.../system/SysProfileController.java | 91 +++
.../controller/system/SysRoleController.java | 192 +++++
.../controller/system/SysUserController.java | 171 +++++
.../controller/tool/SwaggerController.java | 23 +
.../tool/SwaggerTemplateController.java | 126 ++++
.../com/agileboot/admin/request/LoginDTO.java | 33 +
.../agileboot/admin/request/RegisterDTO.java | 27 +
.../agileboot/admin/response/UploadDTO.java | 18 +
.../admin/response/UserPermissionDTO.java | 16 +
agileboot-api/pom.xml | 40 ++
.../api/controller/OrderController.java | 16 +
agileboot-common/pom.xml | 166 +++++
.../common/annotation/ExcelColumn.java | 19 +
.../common/annotation/ExcelSheet.java | 20 +
.../common/config/AgileBootConfig.java | 146 ++++
.../agileboot/common/constant/Constants.java | 66 ++
.../common/core/base/BaseController.java | 40 ++
.../common/core/base/BaseEntity.java | 62 ++
.../agileboot/common/core/base/BaseUser.java | 13 +
.../common/core/dto/ResponseDTO.java | 64 ++
.../agileboot/common/core/page/PageDTO.java | 37 +
.../common/enums/DataSourceType.java | 18 +
.../com/agileboot/common/enums/LimitType.java | 19 +
.../common/exception/ApiException.java | 76 ++
.../common/exception/error/ErrorCode.java | 322 +++++++++
.../exception/error/ErrorCodeInterface.java | 15 +
.../common/exception/error/Module.java | 46 ++
.../common/utils/ServletHolderUtil.java | 71 ++
.../common/utils/file/FileUploadUtils.java | 216 ++++++
.../common/utils/i18n/MessageUtils.java | 25 +
.../agileboot/common/utils/ip/IpRegion.java | 32 +
.../common/utils/ip/IpRegionUtil.java | 26 +
.../common/utils/ip/OfflineIpRegionUtil.java | 51 ++
.../common/utils/ip/OnlineIpRegionUtil.java | 49 ++
.../utils/jackson/JacksonException.java | 23 +
.../common/utils/jackson/JacksonUtil.java | 679 ++++++++++++++++++
.../common/utils/poi/CustomExcelUtil.java | 86 +++
.../common/utils/time/DatePicker.java | 42 ++
.../src/main/resources/ip2region.xdb | Bin 0 -> 11065998 bytes
.../core/exception/ApiExceptionTest.java | 24 +
.../common/utils/JacksonUtilTest.java | 62 ++
.../com/agileboot/common/utils/Person.java | 42 ++
.../utils/file/FileUploadUtilsTest.java | 16 +
.../utils/ip/OfflineIpRegionUtilTest.java | 16 +
agileboot-domain/pom.xml | 47 ++
.../domain/common/BulkOperationCommand.java | 22 +
.../domain/common/UploadFileDTO.java | 16 +
.../domain/system/TreeSelectedDTO.java | 16 +
.../domain/system/config/ConfigDTO.java | 43 ++
.../system/config/ConfigDomainService.java | 58 ++
.../domain/system/config/ConfigModel.java | 42 ++
.../domain/system/config/ConfigQuery.java | 34 +
.../system/config/ConfigUpdateCommand.java | 18 +
.../domain/system/dept/AddDeptCommand.java | 65 ++
.../agileboot/domain/system/dept/DeptDTO.java | 44 ++
.../domain/system/dept/DeptDomainService.java | 126 ++++
.../domain/system/dept/DeptModel.java | 67 ++
.../domain/system/dept/DeptQuery.java | 39 +
.../domain/system/dept/UpdateDeptCommand.java | 26 +
.../domain/system/loginInfo/LoginInfoDTO.java | 60 ++
.../loginInfo/LoginInfoDomainService.java | 35 +
.../system/loginInfo/LoginInfoQuery.java | 32 +
.../system/loginInfo/SearchUserQuery.java | 37 +
.../domain/system/menu/AddMenuCommand.java | 50 ++
.../agileboot/domain/system/menu/MenuDTO.java | 51 ++
.../domain/system/menu/MenuDomainService.java | 190 +++++
.../domain/system/menu/MenuModel.java | 54 ++
.../domain/system/menu/MenuQuery.java | 29 +
.../agileboot/domain/system/menu/MetaVo.java | 59 ++
.../domain/system/menu/RouterModel.java | 197 +++++
.../domain/system/menu/RouterVo.java | 61 ++
.../domain/system/menu/UpdateMenuCommand.java | 19 +
.../system/monitor/MonitorDomainService.java | 124 ++++
.../system/monitor/dto/RedisCacheInfoDTO.java | 24 +
.../system/notice/NoticeAddCommand.java | 33 +
.../domain/system/notice/NoticeDTO.java | 36 +
.../system/notice/NoticeDomainService.java | 72 ++
.../domain/system/notice/NoticeModel.java | 23 +
.../domain/system/notice/NoticeQuery.java | 33 +
.../system/notice/NoticeUpdateCommand.java | 23 +
.../system/operationLog/OperationLogDTO.java | 92 +++
.../OperationLogDomainService.java | 32 +
.../operationLog/OperationLogQuery.java | 34 +
.../domain/system/post/AddPostCommand.java | 47 ++
.../agileboot/domain/system/post/PostDTO.java | 42 ++
.../domain/system/post/PostDomainService.java | 93 +++
.../domain/system/post/PostModel.java | 26 +
.../domain/system/post/PostQuery.java | 31 +
.../domain/system/post/UpdatePostCommand.java | 21 +
.../domain/system/role/AddRoleCommand.java | 64 ++
.../system/role/AllocatedRoleQuery.java | 27 +
.../agileboot/domain/system/role/RoleDTO.java | 42 ++
.../domain/system/role/RoleDomainService.java | 184 +++++
.../domain/system/role/RoleModel.java | 99 +++
.../domain/system/role/RoleQuery.java | 34 +
.../system/role/UnallocatedRoleQuery.java | 29 +
.../system/role/UpdateDataScopeCommand.java | 23 +
.../domain/system/role/UpdateRoleCommand.java | 21 +
.../system/role/UpdateStatusCommand.java | 14 +
.../domain/system/user/RegisterUserModel.java | 12 +
.../agileboot/domain/system/user/UserDTO.java | 102 +++
.../domain/system/user/UserDetailDTO.java | 30 +
.../domain/system/user/UserDomainService.java | 209 ++++++
.../domain/system/user/UserInfoDTO.java | 12 +
.../domain/system/user/UserModel.java | 70 ++
.../domain/system/user/UserProfileDTO.java | 32 +
.../system/user/command/AddUserCommand.java | 70 ++
.../user/command/ChangeStatusCommand.java | 11 +
.../user/command/ResetPasswordCommand.java | 11 +
.../user/command/UpdateProfileCommand.java | 26 +
.../user/command/UpdateUserAvatarCommand.java | 18 +
.../user/command/UpdateUserCommand.java | 19 +
.../command/UpdateUserPasswordCommand.java | 12 +
agileboot-infrastructure/pom.xml | 134 ++++
.../infrastructure/AgileBootApplication.java | 32 +
.../WarDeploymentInitializer.java | 19 +
.../infrastructure/annotations/AccessLog.java | 45 ++
.../annotations/DataPermissionScope.java | 28 +
.../annotations/DataSource.java | 28 +
.../annotations/RateLimiter.java | 41 ++
.../annotations/RepeatSubmit.java | 30 +
.../aspectj/AccessLogAspect.java | 202 ++++++
.../aspectj/DBExceptionAspect.java | 42 ++
.../aspectj/DataScopeAspect.java | 115 +++
.../aspectj/DataSourceAspect.java | 61 ++
.../aspectj/MethodLogAspect.java | 85 +++
.../aspectj/RateLimiterAspect.java | 84 +++
.../infrastructure/cache/CacheCenter.java | 28 +
.../infrastructure/cache/RedisUtil.java | 212 ++++++
.../cache/aop/CacheNameConstants.java | 12 +
.../cache/aop/GuavaCacheBean.java | 89 +++
.../cache/aop/RedisCacheBean.java | 70 ++
.../cache/aop/package-info.java | 11 +
.../cache/guava/GuavaCacheService.java | 62 ++
.../cache/guava/GuavaCacheTemplate.java | 104 +++
.../infrastructure/cache/map/MapCache.java | 60 ++
.../cache/redis/CacheKeyEnum.java | 44 ++
.../cache/redis/RedisCacheService.java | 48 ++
.../cache/redis/RedisCacheTemplate.java | 126 ++++
.../config/ApplicationConfig.java | 29 +
.../infrastructure/config/FilterConfig.java | 50 ++
.../infrastructure/config/MyBatisConfig.java | 26 +
.../infrastructure/config/RedisConfig.java | 94 +++
.../config/ResourcesConfig.java | 67 ++
.../config/SecurityConfiguration.java | 122 ++++
.../config/SecuritySbConfig.java | 132 ++++
.../infrastructure/config/SwaggerConfig.java | 126 ++++
.../config/captcha/CaptchaConfig.java | 95 +++
.../captcha/CaptchaMathTextCreator.java | 73 ++
.../config/druid/DruidConfig.java | 115 +++
.../config/druid/DruidProperties.java | 77 ++
.../datasource/DynamicDataSource.java | 24 +
.../DynamicDataSourceContextHolder.java | 40 ++
.../filter/RepeatableFilter.java | 46 ++
.../filter/RepeatedlyRequestWrapper.java | 65 ++
.../i18n/MessageI18nCheckerRunner.java | 45 ++
.../exception/GlobalExceptionHandler.java | 126 ++++
.../AbstractRepeatSubmitInterceptor.java | 44 ++
.../repeatSubmit/RepeatRequest.java | 30 +
.../SameUrlDataInterceptorAbstract.java | 69 ++
.../mybatisplus/CodeGenerator.java | 254 +++++++
.../mybatisplus/CustomMetaObjectHandler.java | 24 +
.../security/RsaKeyPairGenerator.java | 19 +
.../filter/JwtAuthenticationTokenFilter.java | 46 ++
.../handle/AuthenticationEntryPointImpl.java | 29 +
.../handle/LogoutSuccessHandlerImpl.java | 46 ++
.../security/xss/JsonHtmlXssSerializer.java | 37 +
.../security/xss/JsonXssConfiguration.java | 21 +
.../thread/AsyncTaskFactory.java | 76 ++
.../infrastructure/thread/ShutdownHook.java | 32 +
.../infrastructure/thread/ThreadConfig.java | 18 +
.../thread/ThreadPoolManager.java | 76 ++
.../infrastructure/web/domain/OnlineUser.java | 53 ++
.../web/domain/login/CaptchaDTO.java | 15 +
.../web/domain/login/LoginInfo.java | 28 +
.../web/domain/login/LoginUser.java | 129 ++++
.../infrastructure/web/domain/login/Role.java | 37 +
.../web/domain/server/CpuInfo.java | 64 ++
.../web/domain/server/DiskInfo.java | 48 ++
.../web/domain/server/JvmInfo.java | 92 +++
.../web/domain/server/MemoryInfo.java | 45 ++
.../web/domain/server/ServerInfo.java | 175 +++++
.../web/domain/server/SystemInfo.java | 38 +
.../web/service/LoginService.java | 203 ++++++
.../web/service/PermissionService.java | 160 +++++
.../web/service/TokenService.java | 214 ++++++
.../web/service/UserDetailsServiceImpl.java | 90 +++
.../web/util/AuthenticationUtils.java | 109 +++
.../src/main/resources/application-dev.yml | 79 ++
.../src/main/resources/application-test.yml | 79 ++
.../src/main/resources/application.yml | 130 ++++
.../src/main/resources/banner.txt | 8 +
.../main/resources/i18n/messages.properties | 46 ++
.../src/main/resources/logback.xml | 94 +++
.../mybatis/mybatis-config-deprecated.xml | 20 +
.../config/CaptchaMathTextCreatorTest.java | 27 +
.../enums/interfaces/BasicEnumUtilTest.java | 20 +
agileboot-orm/pom.xml | 44 ++
.../agileboot/orm/entity/SysConfigEntity.java | 64 ++
.../agileboot/orm/entity/SysDeptEntity.java | 75 ++
.../orm/entity/SysLoginInfoEntity.java | 79 ++
.../agileboot/orm/entity/SysMenuEntity.java | 96 +++
.../agileboot/orm/entity/SysNoticeEntity.java | 60 ++
.../orm/entity/SysOperationLogEntity.java | 115 +++
.../agileboot/orm/entity/SysPostEntity.java | 60 ++
.../agileboot/orm/entity/SysRoleEntity.java | 68 ++
.../orm/entity/SysRoleMenuEntity.java | 44 ++
.../agileboot/orm/entity/SysUserEntity.java | 101 +++
.../com/agileboot/orm/enums/BusinessType.java | 74 ++
.../agileboot/orm/enums/DataScopeEnum.java | 42 ++
.../agileboot/orm/enums/LoginStatusEnum.java | 36 +
.../orm/enums/MenuComponentEnum.java | 34 +
.../com/agileboot/orm/enums/MenuTypeEnum.java | 37 +
.../agileboot/orm/enums/OperatorTypeEnum.java | 36 +
.../orm/enums/RequestMethodEnum.java | 38 +
.../agileboot/orm/enums/SystemConfigEnum.java | 40 ++
.../agileboot/orm/enums/UserStatusEnum.java | 32 +
.../enums/dictionary/BusinessTypeEnum.java | 51 ++
.../enums/dictionary/CommonAnswerEnum.java | 52 ++
.../enums/dictionary/CommonStatusEnum.java | 48 ++
.../orm/enums/dictionary/CssTag.java | 15 +
.../orm/enums/dictionary/GenderEnum.java | 52 ++
.../enums/dictionary/NoticeStatusEnum.java | 49 ++
.../orm/enums/dictionary/NoticeTypeEnum.java | 49 ++
.../enums/dictionary/OperationStatusEnum.java | 46 ++
.../enums/dictionary/VisibleStatusEnum.java | 50 ++
.../orm/enums/interfaces/BasicEnum.java | 23 +
.../orm/enums/interfaces/BasicEnumUtil.java | 55 ++
.../orm/enums/interfaces/DictionaryEnum.java | 16 +
.../agileboot/orm/mapper/SysConfigMapper.java | 16 +
.../agileboot/orm/mapper/SysDeptMapper.java | 33 +
.../orm/mapper/SysLoginInfoMapper.java | 16 +
.../agileboot/orm/mapper/SysMenuMapper.java | 50 ++
.../agileboot/orm/mapper/SysNoticeMapper.java | 16 +
.../orm/mapper/SysOperationLogMapper.java | 16 +
.../agileboot/orm/mapper/SysPostMapper.java | 18 +
.../agileboot/orm/mapper/SysRoleMapper.java | 16 +
.../orm/mapper/SysRoleMenuMapper.java | 16 +
.../agileboot/orm/mapper/SysUserMapper.java | 105 +++
.../orm/query/AbstractPageQuery.java | 25 +
.../agileboot/orm/query/AbstractQuery.java | 85 +++
.../agileboot/orm/result/DictionaryData.java | 27 +
.../agileboot/orm/result/SearchUserDO.java | 15 +
.../orm/service/ISysConfigService.java | 18 +
.../orm/service/ISysDeptService.java | 37 +
.../orm/service/ISysLoginInfoService.java | 16 +
.../orm/service/ISysMenuService.java | 72 ++
.../orm/service/ISysNoticeService.java | 16 +
.../orm/service/ISysOperationLogService.java | 16 +
.../orm/service/ISysPostService.java | 36 +
.../orm/service/ISysRoleMenuService.java | 16 +
.../orm/service/ISysRoleService.java | 30 +
.../orm/service/ISysUserService.java | 98 +++
.../service/impl/SysConfigServiceImpl.java | 38 +
.../orm/service/impl/SysDeptServiceImpl.java | 58 ++
.../service/impl/SysLoginInfoServiceImpl.java | 21 +
.../orm/service/impl/SysMenuServiceImpl.java | 111 +++
.../service/impl/SysNoticeServiceImpl.java | 20 +
.../impl/SysOperationLogServiceImpl.java | 21 +
.../orm/service/impl/SysPostServiceImpl.java | 58 ++
.../service/impl/SysRoleMenuServiceImpl.java | 21 +
.../orm/service/impl/SysRoleServiceImpl.java | 43 ++
.../orm/service/impl/SysUserServiceImpl.java | 127 ++++
.../resources/mapper/orm/SysUserMapper.xml | 7 +
bin/clean.bat | 12 +
bin/package.bat | 12 +
bin/run.bat | 14 +
bin/ry.bat | 67 ++
bin/ry.sh | 86 +++
pom.xml | 308 ++++++++
sql/agileboot_20221007.sql | 463 ++++++++++++
286 files changed, 18581 insertions(+)
create mode 100644 .github/FUNDING.yml
create mode 100644 .gitignore
create mode 100644 GoogleStyle.xml
create mode 100644 README.md
create mode 100644 agileboot-admin/pom.xml
create mode 100644 agileboot-admin/src/main/java/com/agileboot/admin/controller/common/FileController.java
create mode 100644 agileboot-admin/src/main/java/com/agileboot/admin/controller/common/LoginController.java
create mode 100644 agileboot-admin/src/main/java/com/agileboot/admin/controller/monitor/MonitorController.java
create mode 100644 agileboot-admin/src/main/java/com/agileboot/admin/controller/system/SysConfigController.java
create mode 100644 agileboot-admin/src/main/java/com/agileboot/admin/controller/system/SysDeptController.java
create mode 100644 agileboot-admin/src/main/java/com/agileboot/admin/controller/system/SysLoginInfoController.java
create mode 100644 agileboot-admin/src/main/java/com/agileboot/admin/controller/system/SysMenuController.java
create mode 100644 agileboot-admin/src/main/java/com/agileboot/admin/controller/system/SysNoticeController.java
create mode 100644 agileboot-admin/src/main/java/com/agileboot/admin/controller/system/SysOperationLogController.java
create mode 100644 agileboot-admin/src/main/java/com/agileboot/admin/controller/system/SysPostController.java
create mode 100644 agileboot-admin/src/main/java/com/agileboot/admin/controller/system/SysProfileController.java
create mode 100644 agileboot-admin/src/main/java/com/agileboot/admin/controller/system/SysRoleController.java
create mode 100644 agileboot-admin/src/main/java/com/agileboot/admin/controller/system/SysUserController.java
create mode 100644 agileboot-admin/src/main/java/com/agileboot/admin/controller/tool/SwaggerController.java
create mode 100644 agileboot-admin/src/main/java/com/agileboot/admin/controller/tool/SwaggerTemplateController.java
create mode 100644 agileboot-admin/src/main/java/com/agileboot/admin/request/LoginDTO.java
create mode 100644 agileboot-admin/src/main/java/com/agileboot/admin/request/RegisterDTO.java
create mode 100644 agileboot-admin/src/main/java/com/agileboot/admin/response/UploadDTO.java
create mode 100644 agileboot-admin/src/main/java/com/agileboot/admin/response/UserPermissionDTO.java
create mode 100644 agileboot-api/pom.xml
create mode 100644 agileboot-api/src/main/java/com/agileboot/api/controller/OrderController.java
create mode 100644 agileboot-common/pom.xml
create mode 100644 agileboot-common/src/main/java/com/agileboot/common/annotation/ExcelColumn.java
create mode 100644 agileboot-common/src/main/java/com/agileboot/common/annotation/ExcelSheet.java
create mode 100644 agileboot-common/src/main/java/com/agileboot/common/config/AgileBootConfig.java
create mode 100644 agileboot-common/src/main/java/com/agileboot/common/constant/Constants.java
create mode 100644 agileboot-common/src/main/java/com/agileboot/common/core/base/BaseController.java
create mode 100644 agileboot-common/src/main/java/com/agileboot/common/core/base/BaseEntity.java
create mode 100644 agileboot-common/src/main/java/com/agileboot/common/core/base/BaseUser.java
create mode 100644 agileboot-common/src/main/java/com/agileboot/common/core/dto/ResponseDTO.java
create mode 100644 agileboot-common/src/main/java/com/agileboot/common/core/page/PageDTO.java
create mode 100644 agileboot-common/src/main/java/com/agileboot/common/enums/DataSourceType.java
create mode 100644 agileboot-common/src/main/java/com/agileboot/common/enums/LimitType.java
create mode 100644 agileboot-common/src/main/java/com/agileboot/common/exception/ApiException.java
create mode 100644 agileboot-common/src/main/java/com/agileboot/common/exception/error/ErrorCode.java
create mode 100644 agileboot-common/src/main/java/com/agileboot/common/exception/error/ErrorCodeInterface.java
create mode 100644 agileboot-common/src/main/java/com/agileboot/common/exception/error/Module.java
create mode 100644 agileboot-common/src/main/java/com/agileboot/common/utils/ServletHolderUtil.java
create mode 100644 agileboot-common/src/main/java/com/agileboot/common/utils/file/FileUploadUtils.java
create mode 100644 agileboot-common/src/main/java/com/agileboot/common/utils/i18n/MessageUtils.java
create mode 100644 agileboot-common/src/main/java/com/agileboot/common/utils/ip/IpRegion.java
create mode 100644 agileboot-common/src/main/java/com/agileboot/common/utils/ip/IpRegionUtil.java
create mode 100644 agileboot-common/src/main/java/com/agileboot/common/utils/ip/OfflineIpRegionUtil.java
create mode 100644 agileboot-common/src/main/java/com/agileboot/common/utils/ip/OnlineIpRegionUtil.java
create mode 100644 agileboot-common/src/main/java/com/agileboot/common/utils/jackson/JacksonException.java
create mode 100644 agileboot-common/src/main/java/com/agileboot/common/utils/jackson/JacksonUtil.java
create mode 100644 agileboot-common/src/main/java/com/agileboot/common/utils/poi/CustomExcelUtil.java
create mode 100644 agileboot-common/src/main/java/com/agileboot/common/utils/time/DatePicker.java
create mode 100644 agileboot-common/src/main/resources/ip2region.xdb
create mode 100644 agileboot-common/src/test/java/com/agileboot/common/core/exception/ApiExceptionTest.java
create mode 100644 agileboot-common/src/test/java/com/agileboot/common/utils/JacksonUtilTest.java
create mode 100644 agileboot-common/src/test/java/com/agileboot/common/utils/Person.java
create mode 100644 agileboot-common/src/test/java/com/agileboot/common/utils/file/FileUploadUtilsTest.java
create mode 100644 agileboot-common/src/test/java/com/agileboot/common/utils/ip/OfflineIpRegionUtilTest.java
create mode 100644 agileboot-domain/pom.xml
create mode 100644 agileboot-domain/src/main/java/com/agileboot/domain/common/BulkOperationCommand.java
create mode 100644 agileboot-domain/src/main/java/com/agileboot/domain/common/UploadFileDTO.java
create mode 100644 agileboot-domain/src/main/java/com/agileboot/domain/system/TreeSelectedDTO.java
create mode 100644 agileboot-domain/src/main/java/com/agileboot/domain/system/config/ConfigDTO.java
create mode 100644 agileboot-domain/src/main/java/com/agileboot/domain/system/config/ConfigDomainService.java
create mode 100644 agileboot-domain/src/main/java/com/agileboot/domain/system/config/ConfigModel.java
create mode 100644 agileboot-domain/src/main/java/com/agileboot/domain/system/config/ConfigQuery.java
create mode 100644 agileboot-domain/src/main/java/com/agileboot/domain/system/config/ConfigUpdateCommand.java
create mode 100644 agileboot-domain/src/main/java/com/agileboot/domain/system/dept/AddDeptCommand.java
create mode 100644 agileboot-domain/src/main/java/com/agileboot/domain/system/dept/DeptDTO.java
create mode 100644 agileboot-domain/src/main/java/com/agileboot/domain/system/dept/DeptDomainService.java
create mode 100644 agileboot-domain/src/main/java/com/agileboot/domain/system/dept/DeptModel.java
create mode 100644 agileboot-domain/src/main/java/com/agileboot/domain/system/dept/DeptQuery.java
create mode 100644 agileboot-domain/src/main/java/com/agileboot/domain/system/dept/UpdateDeptCommand.java
create mode 100644 agileboot-domain/src/main/java/com/agileboot/domain/system/loginInfo/LoginInfoDTO.java
create mode 100644 agileboot-domain/src/main/java/com/agileboot/domain/system/loginInfo/LoginInfoDomainService.java
create mode 100644 agileboot-domain/src/main/java/com/agileboot/domain/system/loginInfo/LoginInfoQuery.java
create mode 100644 agileboot-domain/src/main/java/com/agileboot/domain/system/loginInfo/SearchUserQuery.java
create mode 100644 agileboot-domain/src/main/java/com/agileboot/domain/system/menu/AddMenuCommand.java
create mode 100644 agileboot-domain/src/main/java/com/agileboot/domain/system/menu/MenuDTO.java
create mode 100644 agileboot-domain/src/main/java/com/agileboot/domain/system/menu/MenuDomainService.java
create mode 100644 agileboot-domain/src/main/java/com/agileboot/domain/system/menu/MenuModel.java
create mode 100644 agileboot-domain/src/main/java/com/agileboot/domain/system/menu/MenuQuery.java
create mode 100644 agileboot-domain/src/main/java/com/agileboot/domain/system/menu/MetaVo.java
create mode 100644 agileboot-domain/src/main/java/com/agileboot/domain/system/menu/RouterModel.java
create mode 100644 agileboot-domain/src/main/java/com/agileboot/domain/system/menu/RouterVo.java
create mode 100644 agileboot-domain/src/main/java/com/agileboot/domain/system/menu/UpdateMenuCommand.java
create mode 100644 agileboot-domain/src/main/java/com/agileboot/domain/system/monitor/MonitorDomainService.java
create mode 100644 agileboot-domain/src/main/java/com/agileboot/domain/system/monitor/dto/RedisCacheInfoDTO.java
create mode 100644 agileboot-domain/src/main/java/com/agileboot/domain/system/notice/NoticeAddCommand.java
create mode 100644 agileboot-domain/src/main/java/com/agileboot/domain/system/notice/NoticeDTO.java
create mode 100644 agileboot-domain/src/main/java/com/agileboot/domain/system/notice/NoticeDomainService.java
create mode 100644 agileboot-domain/src/main/java/com/agileboot/domain/system/notice/NoticeModel.java
create mode 100644 agileboot-domain/src/main/java/com/agileboot/domain/system/notice/NoticeQuery.java
create mode 100644 agileboot-domain/src/main/java/com/agileboot/domain/system/notice/NoticeUpdateCommand.java
create mode 100644 agileboot-domain/src/main/java/com/agileboot/domain/system/operationLog/OperationLogDTO.java
create mode 100644 agileboot-domain/src/main/java/com/agileboot/domain/system/operationLog/OperationLogDomainService.java
create mode 100644 agileboot-domain/src/main/java/com/agileboot/domain/system/operationLog/OperationLogQuery.java
create mode 100644 agileboot-domain/src/main/java/com/agileboot/domain/system/post/AddPostCommand.java
create mode 100644 agileboot-domain/src/main/java/com/agileboot/domain/system/post/PostDTO.java
create mode 100644 agileboot-domain/src/main/java/com/agileboot/domain/system/post/PostDomainService.java
create mode 100644 agileboot-domain/src/main/java/com/agileboot/domain/system/post/PostModel.java
create mode 100644 agileboot-domain/src/main/java/com/agileboot/domain/system/post/PostQuery.java
create mode 100644 agileboot-domain/src/main/java/com/agileboot/domain/system/post/UpdatePostCommand.java
create mode 100644 agileboot-domain/src/main/java/com/agileboot/domain/system/role/AddRoleCommand.java
create mode 100644 agileboot-domain/src/main/java/com/agileboot/domain/system/role/AllocatedRoleQuery.java
create mode 100644 agileboot-domain/src/main/java/com/agileboot/domain/system/role/RoleDTO.java
create mode 100644 agileboot-domain/src/main/java/com/agileboot/domain/system/role/RoleDomainService.java
create mode 100644 agileboot-domain/src/main/java/com/agileboot/domain/system/role/RoleModel.java
create mode 100644 agileboot-domain/src/main/java/com/agileboot/domain/system/role/RoleQuery.java
create mode 100644 agileboot-domain/src/main/java/com/agileboot/domain/system/role/UnallocatedRoleQuery.java
create mode 100644 agileboot-domain/src/main/java/com/agileboot/domain/system/role/UpdateDataScopeCommand.java
create mode 100644 agileboot-domain/src/main/java/com/agileboot/domain/system/role/UpdateRoleCommand.java
create mode 100644 agileboot-domain/src/main/java/com/agileboot/domain/system/role/UpdateStatusCommand.java
create mode 100644 agileboot-domain/src/main/java/com/agileboot/domain/system/user/RegisterUserModel.java
create mode 100644 agileboot-domain/src/main/java/com/agileboot/domain/system/user/UserDTO.java
create mode 100644 agileboot-domain/src/main/java/com/agileboot/domain/system/user/UserDetailDTO.java
create mode 100644 agileboot-domain/src/main/java/com/agileboot/domain/system/user/UserDomainService.java
create mode 100644 agileboot-domain/src/main/java/com/agileboot/domain/system/user/UserInfoDTO.java
create mode 100644 agileboot-domain/src/main/java/com/agileboot/domain/system/user/UserModel.java
create mode 100644 agileboot-domain/src/main/java/com/agileboot/domain/system/user/UserProfileDTO.java
create mode 100644 agileboot-domain/src/main/java/com/agileboot/domain/system/user/command/AddUserCommand.java
create mode 100644 agileboot-domain/src/main/java/com/agileboot/domain/system/user/command/ChangeStatusCommand.java
create mode 100644 agileboot-domain/src/main/java/com/agileboot/domain/system/user/command/ResetPasswordCommand.java
create mode 100644 agileboot-domain/src/main/java/com/agileboot/domain/system/user/command/UpdateProfileCommand.java
create mode 100644 agileboot-domain/src/main/java/com/agileboot/domain/system/user/command/UpdateUserAvatarCommand.java
create mode 100644 agileboot-domain/src/main/java/com/agileboot/domain/system/user/command/UpdateUserCommand.java
create mode 100644 agileboot-domain/src/main/java/com/agileboot/domain/system/user/command/UpdateUserPasswordCommand.java
create mode 100644 agileboot-infrastructure/pom.xml
create mode 100644 agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/AgileBootApplication.java
create mode 100644 agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/WarDeploymentInitializer.java
create mode 100644 agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/annotations/AccessLog.java
create mode 100644 agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/annotations/DataPermissionScope.java
create mode 100644 agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/annotations/DataSource.java
create mode 100644 agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/annotations/RateLimiter.java
create mode 100644 agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/annotations/RepeatSubmit.java
create mode 100644 agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/aspectj/AccessLogAspect.java
create mode 100644 agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/aspectj/DBExceptionAspect.java
create mode 100644 agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/aspectj/DataScopeAspect.java
create mode 100644 agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/aspectj/DataSourceAspect.java
create mode 100644 agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/aspectj/MethodLogAspect.java
create mode 100644 agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/aspectj/RateLimiterAspect.java
create mode 100644 agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/cache/CacheCenter.java
create mode 100644 agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/cache/RedisUtil.java
create mode 100644 agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/cache/aop/CacheNameConstants.java
create mode 100644 agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/cache/aop/GuavaCacheBean.java
create mode 100644 agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/cache/aop/RedisCacheBean.java
create mode 100644 agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/cache/aop/package-info.java
create mode 100644 agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/cache/guava/GuavaCacheService.java
create mode 100644 agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/cache/guava/GuavaCacheTemplate.java
create mode 100644 agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/cache/map/MapCache.java
create mode 100644 agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/cache/redis/CacheKeyEnum.java
create mode 100644 agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/cache/redis/RedisCacheService.java
create mode 100644 agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/cache/redis/RedisCacheTemplate.java
create mode 100644 agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/config/ApplicationConfig.java
create mode 100644 agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/config/FilterConfig.java
create mode 100644 agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/config/MyBatisConfig.java
create mode 100644 agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/config/RedisConfig.java
create mode 100644 agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/config/ResourcesConfig.java
create mode 100644 agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/config/SecurityConfiguration.java
create mode 100644 agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/config/SecuritySbConfig.java
create mode 100644 agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/config/SwaggerConfig.java
create mode 100644 agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/config/captcha/CaptchaConfig.java
create mode 100644 agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/config/captcha/CaptchaMathTextCreator.java
create mode 100644 agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/config/druid/DruidConfig.java
create mode 100644 agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/config/druid/DruidProperties.java
create mode 100644 agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/datasource/DynamicDataSource.java
create mode 100644 agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/datasource/DynamicDataSourceContextHolder.java
create mode 100644 agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/filter/RepeatableFilter.java
create mode 100644 agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/filter/RepeatedlyRequestWrapper.java
create mode 100644 agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/i18n/MessageI18nCheckerRunner.java
create mode 100644 agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/interceptor/exception/GlobalExceptionHandler.java
create mode 100644 agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/interceptor/repeatSubmit/AbstractRepeatSubmitInterceptor.java
create mode 100644 agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/interceptor/repeatSubmit/RepeatRequest.java
create mode 100644 agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/interceptor/repeatSubmit/SameUrlDataInterceptorAbstract.java
create mode 100644 agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/mybatisplus/CodeGenerator.java
create mode 100644 agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/mybatisplus/CustomMetaObjectHandler.java
create mode 100644 agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/security/RsaKeyPairGenerator.java
create mode 100644 agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/security/filter/JwtAuthenticationTokenFilter.java
create mode 100644 agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/security/handle/AuthenticationEntryPointImpl.java
create mode 100644 agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/security/handle/LogoutSuccessHandlerImpl.java
create mode 100644 agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/security/xss/JsonHtmlXssSerializer.java
create mode 100644 agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/security/xss/JsonXssConfiguration.java
create mode 100644 agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/thread/AsyncTaskFactory.java
create mode 100644 agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/thread/ShutdownHook.java
create mode 100644 agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/thread/ThreadConfig.java
create mode 100644 agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/thread/ThreadPoolManager.java
create mode 100644 agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/web/domain/OnlineUser.java
create mode 100644 agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/web/domain/login/CaptchaDTO.java
create mode 100644 agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/web/domain/login/LoginInfo.java
create mode 100644 agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/web/domain/login/LoginUser.java
create mode 100644 agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/web/domain/login/Role.java
create mode 100644 agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/web/domain/server/CpuInfo.java
create mode 100644 agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/web/domain/server/DiskInfo.java
create mode 100644 agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/web/domain/server/JvmInfo.java
create mode 100644 agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/web/domain/server/MemoryInfo.java
create mode 100644 agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/web/domain/server/ServerInfo.java
create mode 100644 agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/web/domain/server/SystemInfo.java
create mode 100644 agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/web/service/LoginService.java
create mode 100644 agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/web/service/PermissionService.java
create mode 100644 agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/web/service/TokenService.java
create mode 100644 agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/web/service/UserDetailsServiceImpl.java
create mode 100644 agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/web/util/AuthenticationUtils.java
create mode 100644 agileboot-infrastructure/src/main/resources/application-dev.yml
create mode 100644 agileboot-infrastructure/src/main/resources/application-test.yml
create mode 100644 agileboot-infrastructure/src/main/resources/application.yml
create mode 100644 agileboot-infrastructure/src/main/resources/banner.txt
create mode 100644 agileboot-infrastructure/src/main/resources/i18n/messages.properties
create mode 100644 agileboot-infrastructure/src/main/resources/logback.xml
create mode 100644 agileboot-infrastructure/src/main/resources/mybatis/mybatis-config-deprecated.xml
create mode 100644 agileboot-infrastructure/src/test/java/com/agileboot/framework/config/CaptchaMathTextCreatorTest.java
create mode 100644 agileboot-infrastructure/src/test/java/com/agileboot/orm/enums/interfaces/BasicEnumUtilTest.java
create mode 100644 agileboot-orm/pom.xml
create mode 100644 agileboot-orm/src/main/java/com/agileboot/orm/entity/SysConfigEntity.java
create mode 100644 agileboot-orm/src/main/java/com/agileboot/orm/entity/SysDeptEntity.java
create mode 100644 agileboot-orm/src/main/java/com/agileboot/orm/entity/SysLoginInfoEntity.java
create mode 100644 agileboot-orm/src/main/java/com/agileboot/orm/entity/SysMenuEntity.java
create mode 100644 agileboot-orm/src/main/java/com/agileboot/orm/entity/SysNoticeEntity.java
create mode 100644 agileboot-orm/src/main/java/com/agileboot/orm/entity/SysOperationLogEntity.java
create mode 100644 agileboot-orm/src/main/java/com/agileboot/orm/entity/SysPostEntity.java
create mode 100644 agileboot-orm/src/main/java/com/agileboot/orm/entity/SysRoleEntity.java
create mode 100644 agileboot-orm/src/main/java/com/agileboot/orm/entity/SysRoleMenuEntity.java
create mode 100644 agileboot-orm/src/main/java/com/agileboot/orm/entity/SysUserEntity.java
create mode 100644 agileboot-orm/src/main/java/com/agileboot/orm/enums/BusinessType.java
create mode 100644 agileboot-orm/src/main/java/com/agileboot/orm/enums/DataScopeEnum.java
create mode 100644 agileboot-orm/src/main/java/com/agileboot/orm/enums/LoginStatusEnum.java
create mode 100644 agileboot-orm/src/main/java/com/agileboot/orm/enums/MenuComponentEnum.java
create mode 100644 agileboot-orm/src/main/java/com/agileboot/orm/enums/MenuTypeEnum.java
create mode 100644 agileboot-orm/src/main/java/com/agileboot/orm/enums/OperatorTypeEnum.java
create mode 100644 agileboot-orm/src/main/java/com/agileboot/orm/enums/RequestMethodEnum.java
create mode 100644 agileboot-orm/src/main/java/com/agileboot/orm/enums/SystemConfigEnum.java
create mode 100644 agileboot-orm/src/main/java/com/agileboot/orm/enums/UserStatusEnum.java
create mode 100644 agileboot-orm/src/main/java/com/agileboot/orm/enums/dictionary/BusinessTypeEnum.java
create mode 100644 agileboot-orm/src/main/java/com/agileboot/orm/enums/dictionary/CommonAnswerEnum.java
create mode 100644 agileboot-orm/src/main/java/com/agileboot/orm/enums/dictionary/CommonStatusEnum.java
create mode 100644 agileboot-orm/src/main/java/com/agileboot/orm/enums/dictionary/CssTag.java
create mode 100644 agileboot-orm/src/main/java/com/agileboot/orm/enums/dictionary/GenderEnum.java
create mode 100644 agileboot-orm/src/main/java/com/agileboot/orm/enums/dictionary/NoticeStatusEnum.java
create mode 100644 agileboot-orm/src/main/java/com/agileboot/orm/enums/dictionary/NoticeTypeEnum.java
create mode 100644 agileboot-orm/src/main/java/com/agileboot/orm/enums/dictionary/OperationStatusEnum.java
create mode 100644 agileboot-orm/src/main/java/com/agileboot/orm/enums/dictionary/VisibleStatusEnum.java
create mode 100644 agileboot-orm/src/main/java/com/agileboot/orm/enums/interfaces/BasicEnum.java
create mode 100644 agileboot-orm/src/main/java/com/agileboot/orm/enums/interfaces/BasicEnumUtil.java
create mode 100644 agileboot-orm/src/main/java/com/agileboot/orm/enums/interfaces/DictionaryEnum.java
create mode 100644 agileboot-orm/src/main/java/com/agileboot/orm/mapper/SysConfigMapper.java
create mode 100644 agileboot-orm/src/main/java/com/agileboot/orm/mapper/SysDeptMapper.java
create mode 100644 agileboot-orm/src/main/java/com/agileboot/orm/mapper/SysLoginInfoMapper.java
create mode 100644 agileboot-orm/src/main/java/com/agileboot/orm/mapper/SysMenuMapper.java
create mode 100644 agileboot-orm/src/main/java/com/agileboot/orm/mapper/SysNoticeMapper.java
create mode 100644 agileboot-orm/src/main/java/com/agileboot/orm/mapper/SysOperationLogMapper.java
create mode 100644 agileboot-orm/src/main/java/com/agileboot/orm/mapper/SysPostMapper.java
create mode 100644 agileboot-orm/src/main/java/com/agileboot/orm/mapper/SysRoleMapper.java
create mode 100644 agileboot-orm/src/main/java/com/agileboot/orm/mapper/SysRoleMenuMapper.java
create mode 100644 agileboot-orm/src/main/java/com/agileboot/orm/mapper/SysUserMapper.java
create mode 100644 agileboot-orm/src/main/java/com/agileboot/orm/query/AbstractPageQuery.java
create mode 100644 agileboot-orm/src/main/java/com/agileboot/orm/query/AbstractQuery.java
create mode 100644 agileboot-orm/src/main/java/com/agileboot/orm/result/DictionaryData.java
create mode 100644 agileboot-orm/src/main/java/com/agileboot/orm/result/SearchUserDO.java
create mode 100644 agileboot-orm/src/main/java/com/agileboot/orm/service/ISysConfigService.java
create mode 100644 agileboot-orm/src/main/java/com/agileboot/orm/service/ISysDeptService.java
create mode 100644 agileboot-orm/src/main/java/com/agileboot/orm/service/ISysLoginInfoService.java
create mode 100644 agileboot-orm/src/main/java/com/agileboot/orm/service/ISysMenuService.java
create mode 100644 agileboot-orm/src/main/java/com/agileboot/orm/service/ISysNoticeService.java
create mode 100644 agileboot-orm/src/main/java/com/agileboot/orm/service/ISysOperationLogService.java
create mode 100644 agileboot-orm/src/main/java/com/agileboot/orm/service/ISysPostService.java
create mode 100644 agileboot-orm/src/main/java/com/agileboot/orm/service/ISysRoleMenuService.java
create mode 100644 agileboot-orm/src/main/java/com/agileboot/orm/service/ISysRoleService.java
create mode 100644 agileboot-orm/src/main/java/com/agileboot/orm/service/ISysUserService.java
create mode 100644 agileboot-orm/src/main/java/com/agileboot/orm/service/impl/SysConfigServiceImpl.java
create mode 100644 agileboot-orm/src/main/java/com/agileboot/orm/service/impl/SysDeptServiceImpl.java
create mode 100644 agileboot-orm/src/main/java/com/agileboot/orm/service/impl/SysLoginInfoServiceImpl.java
create mode 100644 agileboot-orm/src/main/java/com/agileboot/orm/service/impl/SysMenuServiceImpl.java
create mode 100644 agileboot-orm/src/main/java/com/agileboot/orm/service/impl/SysNoticeServiceImpl.java
create mode 100644 agileboot-orm/src/main/java/com/agileboot/orm/service/impl/SysOperationLogServiceImpl.java
create mode 100644 agileboot-orm/src/main/java/com/agileboot/orm/service/impl/SysPostServiceImpl.java
create mode 100644 agileboot-orm/src/main/java/com/agileboot/orm/service/impl/SysRoleMenuServiceImpl.java
create mode 100644 agileboot-orm/src/main/java/com/agileboot/orm/service/impl/SysRoleServiceImpl.java
create mode 100644 agileboot-orm/src/main/java/com/agileboot/orm/service/impl/SysUserServiceImpl.java
create mode 100644 agileboot-orm/src/main/resources/mapper/orm/SysUserMapper.xml
create mode 100644 bin/clean.bat
create mode 100644 bin/package.bat
create mode 100644 bin/run.bat
create mode 100644 bin/ry.bat
create mode 100644 bin/ry.sh
create mode 100644 pom.xml
create mode 100644 sql/agileboot_20221007.sql
diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
new file mode 100644
index 0000000..fbcab77
--- /dev/null
+++ b/.github/FUNDING.yml
@@ -0,0 +1 @@
+custom: http://doc.ruoyi.vip/ruoyi-vue/other/donate.html
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..8e6bfe3
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,48 @@
+######################################################################
+# Build Tools
+
+.gradle
+/build/
+!gradle/wrapper/gradle-wrapper.jar
+
+target/
+!.mvn/wrapper/maven-wrapper.jar
+
+######################################################################
+# IDE
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+
+### JRebel ###
+rebel.xml
+
+### NetBeans ###
+nbproject/private/
+build/*
+nbbuild/
+dist/
+nbdist/
+.nb-gradle/
+
+######################################################################
+# Others
+*.log
+*.xml.versionsBackup
+*.swp
+
+!*/build/*.java
+!*/build/*.html
+!*/build/*.xml
+/agileboot-admin/src/main/resources/application-dev.yml
diff --git a/GoogleStyle.xml b/GoogleStyle.xml
new file mode 100644
index 0000000..c2e1d68
--- /dev/null
+++ b/GoogleStyle.xml
@@ -0,0 +1,567 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ xmlns:android
+
+ ^$
+
+
+
+
+
+
+
+
+ xmlns:.*
+
+ ^$
+
+
+ BY_NAME
+
+
+
+
+
+
+ .*:id
+
+ http://schemas.android.com/apk/res/android
+
+
+
+
+
+
+
+
+ style
+
+ ^$
+
+
+
+
+
+
+
+
+ .*
+
+ ^$
+
+
+ BY_NAME
+
+
+
+
+
+
+ .*:.*Style
+
+ http://schemas.android.com/apk/res/android
+
+
+ BY_NAME
+
+
+
+
+
+
+ .*:layout_width
+
+ http://schemas.android.com/apk/res/android
+
+
+
+
+
+
+
+
+ .*:layout_height
+
+ http://schemas.android.com/apk/res/android
+
+
+
+
+
+
+
+
+ .*:layout_weight
+
+ http://schemas.android.com/apk/res/android
+
+
+
+
+
+
+
+
+ .*:layout_margin
+
+ http://schemas.android.com/apk/res/android
+
+
+
+
+
+
+
+
+ .*:layout_marginTop
+
+ http://schemas.android.com/apk/res/android
+
+
+
+
+
+
+
+
+ .*:layout_marginBottom
+
+ http://schemas.android.com/apk/res/android
+
+
+
+
+
+
+
+
+ .*:layout_marginStart
+
+ http://schemas.android.com/apk/res/android
+
+
+
+
+
+
+
+
+ .*:layout_marginEnd
+
+ http://schemas.android.com/apk/res/android
+
+
+
+
+
+
+
+
+ .*:layout_marginLeft
+
+ http://schemas.android.com/apk/res/android
+
+
+
+
+
+
+
+
+ .*:layout_marginRight
+
+ http://schemas.android.com/apk/res/android
+
+
+
+
+
+
+
+
+ .*:layout_.*
+
+ http://schemas.android.com/apk/res/android
+
+
+ BY_NAME
+
+
+
+
+
+
+ .*:padding
+
+ http://schemas.android.com/apk/res/android
+
+
+
+
+
+
+
+
+ .*:paddingTop
+
+ http://schemas.android.com/apk/res/android
+
+
+
+
+
+
+
+
+ .*:paddingBottom
+
+ http://schemas.android.com/apk/res/android
+
+
+
+
+
+
+
+
+ .*:paddingStart
+
+ http://schemas.android.com/apk/res/android
+
+
+
+
+
+
+
+
+ .*:paddingEnd
+
+ http://schemas.android.com/apk/res/android
+
+
+
+
+
+
+
+
+ .*:paddingLeft
+
+ http://schemas.android.com/apk/res/android
+
+
+
+
+
+
+
+
+ .*:paddingRight
+
+ http://schemas.android.com/apk/res/android
+
+
+
+
+
+
+
+
+ .*
+ http://schemas.android.com/apk/res/android
+
+
+ BY_NAME
+
+
+
+
+
+
+ .*
+ http://schemas.android.com/apk/res-auto
+
+
+ BY_NAME
+
+
+
+
+
+
+ .*
+ http://schemas.android.com/tools
+
+
+ BY_NAME
+
+
+
+
+
+
+ .*
+ .*
+
+
+ BY_NAME
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..b3ab959
--- /dev/null
+++ b/README.md
@@ -0,0 +1,103 @@
+
+
+
+RuoYi v3.8.2
+基于SpringBoot+Vue前后端分离的Java快速开发框架
+
+
+
+
+
+
+## 平台简介
+
+AgileBoot是一套全部开源的快速开发平台,毫无保留给个人及企业免费使用。
+
+* 前端采用Vue、Element UI。
+* 后端采用Spring Boot、Spring Security、Redis & Jwt。
+* 权限认证使用Jwt,支持多终端认证系统。
+* 支持加载动态权限菜单,多方式轻松权限控制。
+* 高效率开发,使用代码生成器可以一键生成前后端代码。
+* 提供了技术栈([Vue3](https://v3.cn.vuejs.org) [Element Plus](https://element-plus.org/zh-CN) [Vite](https://cn.vitejs.dev))版本[RuoYi-Vue3](https://github.com/yangzongzhuan/RuoYi-Vue3),保持同步更新。
+* 提供了单应用版本[RuoYi-Vue-fast](https://github.com/yangzongzhuan/RuoYi-Vue-fast),Oracle版本[RuoYi-Vue-Oracle](https://github.com/yangzongzhuan/RuoYi-Vue-Oracle),保持同步更新。
+* 不分离版本,请移步[RuoYi](https://gitee.com/y_project/RuoYi),微服务版本,请移步[RuoYi-Cloud](https://gitee.com/y_project/RuoYi-Cloud)
+* 特别鸣谢:[element](https://github.com/ElemeFE/element),[vue-element-admin](https://github.com/PanJiaChen/vue-element-admin),[eladmin-web](https://github.com/elunez/eladmin-web)。
+* 阿里云折扣场:[点我进入](http://aly.ruoyi.vip),腾讯云秒杀场:[点我进入](http://txy.ruoyi.vip)
+* 阿里云优惠券:[点我领取](https://www.aliyun.com/minisite/goods?userCode=brki8iof&share_source=copy_link),腾讯云优惠券:[点我领取](https://cloud.tencent.com/redirect.php?redirect=1025&cps_key=198c8df2ed259157187173bc7f4f32fd&from=console)
+
+## 内置功能
+
+1. 用户管理:用户是系统操作者,该功能主要完成系统用户配置。
+2. 部门管理:配置系统组织机构(公司、部门、小组),树结构展现支持数据权限。
+3. 岗位管理:配置系统用户所属担任职务。
+4. 菜单管理:配置系统菜单,操作权限,按钮权限标识等。
+5. 角色管理:角色菜单权限分配、设置角色按机构进行数据范围权限划分。
+6. 字典管理:对系统中经常使用的一些较为固定的数据进行维护。
+7. 参数管理:对系统动态配置常用参数。
+8. 通知公告:系统通知公告信息发布维护。
+9. 操作日志:系统正常操作日志记录和查询;系统异常信息日志记录和查询。
+10. 登录日志:系统登录日志记录查询包含登录异常。
+11. 在线用户:当前系统中活跃用户状态监控。
+12. 定时任务:在线(添加、修改、删除)任务调度包含执行结果日志。
+13. 代码生成:前后端代码的生成(java、html、xml、sql)支持CRUD下载 。
+14. 系统接口:根据业务代码自动生成相关的api接口文档。
+15. 服务监控:监视当前系统CPU、内存、磁盘、堆栈等相关信息。
+16. 缓存监控:对系统的缓存信息查询,命令统计等。
+17. 在线构建器:拖动表单元素生成相应的HTML代码。
+18. 连接池监视:监视当前系统数据库连接池状态,可进行分析SQL找出系统性能瓶颈。
+
+## 在线体验
+
+- admin/admin123
+- 陆陆续续收到一些打赏,为了更好的体验已用于演示服务器升级。谢谢各位小伙伴。
+
+演示地址:http://vue.ruoyi.vip
+文档地址:http://doc.ruoyi.vip
+
+## 演示图
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+## AG前后端分离交流群
+
+QQ群: [](https://jq.qq.com/?_wv=1027&k=5bVB1og) [](https://jq.qq.com/?_wv=1027&k=5eiA4DH) [](https://jq.qq.com/?_wv=1027&k=5AxMKlC) [](https://jq.qq.com/?_wv=1027&k=51G72yr) [](https://jq.qq.com/?_wv=1027&k=VvjN2nvu) [](https://jq.qq.com/?_wv=1027&k=5vYAqA05) [](https://jq.qq.com/?_wv=1027&k=kOIINEb5) [](https://jq.qq.com/?_wv=1027&k=UKtX5jhs) [](https://jq.qq.com/?_wv=1027&k=EI9an8lJ) [](https://jq.qq.com/?_wv=1027&k=SWCtLnMz) 点击按钮入群。
+
+TODO
+plan to do encrypt request and response. reference:https://github.com/ishuibo/rsa-encrypt-body-spring-boot
+
+
+### 如果老是出现项目中能找到包 但是编译的时候却找不到 可以运行 mvn clean install
diff --git a/agileboot-admin/pom.xml b/agileboot-admin/pom.xml
new file mode 100644
index 0000000..ce2e89d
--- /dev/null
+++ b/agileboot-admin/pom.xml
@@ -0,0 +1,58 @@
+
+
+
+ agileboot
+ com.agileboot
+ 1.0.0
+
+ 4.0.0
+
+ agileboot-admin
+
+
+ web服务入口
+
+
+
+
+
+
+
+
+
+
+
+ mysql
+ mysql-connector-java
+
+
+
+
+ com.agileboot
+ agileboot-infrastructure
+
+
+
+
+ com.agileboot
+ agileboot-api
+
+
+
+
+ com.agileboot
+ agileboot-domain
+
+
+
+
+
+
+
diff --git a/agileboot-admin/src/main/java/com/agileboot/admin/controller/common/FileController.java b/agileboot-admin/src/main/java/com/agileboot/admin/controller/common/FileController.java
new file mode 100644
index 0000000..bbb7250
--- /dev/null
+++ b/agileboot-admin/src/main/java/com/agileboot/admin/controller/common/FileController.java
@@ -0,0 +1,123 @@
+package com.agileboot.admin.controller.common;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.io.FileUtil;
+import cn.hutool.core.io.file.FileNameUtil;
+import cn.hutool.core.util.StrUtil;
+import com.agileboot.admin.response.UploadDTO;
+import com.agileboot.common.config.AgileBootConfig;
+import com.agileboot.common.core.dto.ResponseDTO;
+import com.agileboot.common.exception.ApiException;
+import com.agileboot.common.exception.error.ErrorCode;
+import com.agileboot.common.utils.ServletHolderUtil;
+import com.agileboot.common.utils.file.FileUploadUtils;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import javax.servlet.http.HttpServletResponse;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.multipart.MultipartFile;
+
+/**
+ * 通用请求处理
+ *
+ * @author valarchie
+ */
+@RestController
+@RequestMapping("/file")
+@Slf4j
+public class FileController {
+
+
+ /**
+ * 通用下载请求
+ *
+ * @param fileName 文件名称
+ */
+ @GetMapping("/download")
+ public ResponseEntity fileDownload(String fileName, HttpServletResponse response) {
+ try {
+ if (!FileUploadUtils.isAllowDownload(fileName)) {
+ throw new Exception(StrUtil.format("文件名称({})非法,不允许下载。 ", fileName));
+ }
+
+ String realFileName = System.currentTimeMillis() + fileName.substring(fileName.indexOf("_") + 1);
+ String filePath = AgileBootConfig.getDownloadPath() + fileName;
+
+ response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
+
+ HttpHeaders headers = new HttpHeaders();
+ headers.set("Content-Disposition", String.format("attachment;filename=%s", realFileName));
+ return new ResponseEntity<>(FileUtil.readBytes(filePath), headers, HttpStatus.OK);
+ } catch (Exception e) {
+ log.error("下载文件失败", e);
+ return null;
+ }
+ }
+
+ /**
+ * 通用上传请求(单个)
+ */
+ @PostMapping("/upload")
+ public ResponseDTO uploadFile(MultipartFile file) throws IOException {
+ if (file == null) {
+ throw new ApiException(ErrorCode.Business.UPLOAD_FILE_IS_EMPTY);
+ }
+
+ // 上传文件路径
+ String filePath = AgileBootConfig.getUploadPath();
+ // 上传并返回新文件名称
+ String fileName = FileUploadUtils.upload(filePath, file);
+
+ String url = ServletHolderUtil.getContextUrl() + fileName;
+
+ UploadDTO uploadDTO = UploadDTO.builder()
+ .url(url)
+ .fileName(fileName)
+ .newFileName(FileNameUtil.getName(fileName))
+ .originalFilename(file.getOriginalFilename()).build();
+
+ return ResponseDTO.ok(uploadDTO);
+ }
+
+ /**
+ * 通用上传请求(多个)
+ */
+ @PostMapping("/uploads")
+ public ResponseDTO> uploadFiles(List files) throws Exception {
+ if (CollUtil.isEmpty(files)) {
+ throw new ApiException(ErrorCode.Business.UPLOAD_FILE_IS_EMPTY);
+ }
+
+ // 上传文件路径
+ String filePath = AgileBootConfig.getUploadPath();
+
+ List uploads = new ArrayList<>();
+
+ for (MultipartFile file : files) {
+ if (file != null) {
+ // 上传并返回新文件名称
+ String fileName = FileUploadUtils.upload(filePath, file);
+ String url = ServletHolderUtil.getContextUrl() + fileName;
+ UploadDTO uploadDTO = UploadDTO.builder()
+ .url(url)
+ .fileName(fileName)
+ .newFileName(FileNameUtil.getName(fileName))
+ .originalFilename(file.getOriginalFilename()).build();
+
+ uploads.add(uploadDTO);
+
+ }
+ }
+ return ResponseDTO.ok(uploads);
+ }
+
+}
diff --git a/agileboot-admin/src/main/java/com/agileboot/admin/controller/common/LoginController.java b/agileboot-admin/src/main/java/com/agileboot/admin/controller/common/LoginController.java
new file mode 100644
index 0000000..a50b2d2
--- /dev/null
+++ b/agileboot-admin/src/main/java/com/agileboot/admin/controller/common/LoginController.java
@@ -0,0 +1,120 @@
+package com.agileboot.admin.controller.common;
+
+import cn.hutool.core.map.MapUtil;
+import cn.hutool.core.util.StrUtil;
+import com.agileboot.admin.request.LoginDTO;
+import com.agileboot.admin.request.RegisterDTO;
+import com.agileboot.admin.response.UserPermissionDTO;
+import com.agileboot.common.config.AgileBootConfig;
+import com.agileboot.common.constant.Constants.Token;
+import com.agileboot.common.core.dto.ResponseDTO;
+import com.agileboot.common.exception.error.ErrorCode.Business;
+import com.agileboot.domain.system.menu.MenuDomainService;
+import com.agileboot.domain.system.menu.RouterVo;
+import com.agileboot.domain.system.user.UserDTO;
+import com.agileboot.infrastructure.cache.map.MapCache;
+import com.agileboot.infrastructure.web.domain.login.CaptchaDTO;
+import com.agileboot.infrastructure.web.domain.login.LoginUser;
+import com.agileboot.infrastructure.web.service.LoginService;
+import com.agileboot.infrastructure.web.util.AuthenticationUtils;
+import java.util.List;
+import java.util.Map;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * 首页
+ *
+ * @author valarchie
+ */
+@RestController
+public class LoginController {
+
+ private final LoginService loginService;
+
+ private final MenuDomainService menuDomainService;
+ /**
+ * 系统基础配置
+ */
+ private final AgileBootConfig agileBootConfig;
+
+ public LoginController(LoginService loginService,
+ MenuDomainService menuDomainService, AgileBootConfig agileBootConfig) {
+ this.loginService = loginService;
+ this.menuDomainService = menuDomainService;
+ this.agileBootConfig = agileBootConfig;
+ }
+
+ /**
+ * 访问首页,提示语
+ */
+ @RequestMapping("/")
+ public String index() {
+ return StrUtil.format("欢迎使用{}后台管理框架,当前版本:v{},请通过前端地址访问。",
+ agileBootConfig.getName(), agileBootConfig.getVersion());
+ }
+
+ /**
+ * 生成验证码
+ */
+ @GetMapping("/captchaImage")
+ public ResponseDTO getCaptchaImg() {
+ CaptchaDTO captchaImg = loginService.getCaptchaImg();
+ return ResponseDTO.ok(captchaImg);
+ }
+
+ /**
+ * 登录方法
+ *
+ * @param loginDTO 登录信息
+ * @return 结果
+ */
+ @PostMapping("/login")
+ public ResponseDTO login(@RequestBody LoginDTO loginDTO) {
+ // 生成令牌
+ String token = loginService.login(loginDTO.getUsername(), loginDTO.getPassword(), loginDTO.getCode(),
+ loginDTO.getUuid());
+
+ return ResponseDTO.ok(MapUtil.of(Token.TOKEN_FIELD, token));
+ }
+
+ /**
+ * 获取用户信息
+ *
+ * @return 用户信息
+ */
+ @GetMapping("/getLoginUserInfo")
+ public ResponseDTO getLoginUserInfo() {
+ LoginUser loginUser = AuthenticationUtils.getLoginUser();
+
+ UserPermissionDTO permissionDTO = new UserPermissionDTO();
+ permissionDTO.setUser(new UserDTO(loginUser.getEntity()));
+ permissionDTO.setRoleKey(loginUser.getRoleKey());
+ permissionDTO.setPermissions(loginUser.getMenuPermissions());
+ permissionDTO.setDictTypes(MapCache.dictionaryCache());
+
+ return ResponseDTO.ok(permissionDTO);
+ }
+
+ /**
+ * 获取路由信息
+ *
+ * @return 路由信息
+ */
+ @GetMapping("/getRouters")
+ public ResponseDTO> getRouters() {
+ Long userId = AuthenticationUtils.getUserId();
+ List routerTree = menuDomainService.getRouterTree(userId);
+ return ResponseDTO.ok(routerTree);
+ }
+
+
+ @PostMapping("/register")
+ public ResponseDTO register(@RequestBody RegisterDTO user) {
+ return ResponseDTO.fail(Business.UNSUPPORTED_OPERATION);
+ }
+
+}
diff --git a/agileboot-admin/src/main/java/com/agileboot/admin/controller/monitor/MonitorController.java b/agileboot-admin/src/main/java/com/agileboot/admin/controller/monitor/MonitorController.java
new file mode 100644
index 0000000..5703e23
--- /dev/null
+++ b/agileboot-admin/src/main/java/com/agileboot/admin/controller/monitor/MonitorController.java
@@ -0,0 +1,77 @@
+package com.agileboot.admin.controller.monitor;
+
+import com.agileboot.common.core.base.BaseController;
+import com.agileboot.common.core.dto.ResponseDTO;
+import com.agileboot.common.core.page.PageDTO;
+import com.agileboot.domain.system.monitor.MonitorDomainService;
+import com.agileboot.domain.system.monitor.dto.RedisCacheInfoDTO;
+import com.agileboot.infrastructure.annotations.AccessLog;
+import com.agileboot.infrastructure.cache.redis.RedisCacheService;
+import com.agileboot.infrastructure.web.domain.OnlineUser;
+import com.agileboot.infrastructure.web.domain.server.ServerInfo;
+import com.agileboot.orm.enums.BusinessType;
+import java.util.List;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * 缓存监控
+ *
+ * @author ruoyi
+ */
+@RestController
+@RequestMapping("/monitor")
+public class MonitorController extends BaseController {
+
+ @Autowired
+ private MonitorDomainService monitorDomainService;
+
+ @Autowired
+ private RedisCacheService redisCacheService;
+
+ @PreAuthorize("@ss.hasPerm('monitor:cache:list')")
+ @GetMapping("/cacheInfo")
+ public ResponseDTO getRedisCacheInfo() {
+ RedisCacheInfoDTO redisCacheInfo = monitorDomainService.getRedisCacheInfo();
+ return ResponseDTO.ok(redisCacheInfo);
+ }
+
+
+ @PreAuthorize("@ss.hasPerm('monitor:server:list')")
+ @GetMapping("/serverInfo")
+ public ResponseDTO getServerInfo() {
+ ServerInfo serverInfo = monitorDomainService.getServerInfo();
+ return ResponseDTO.ok(serverInfo);
+ }
+
+ /**
+ * 获取在线用户列表
+ * @param ipaddr
+ * @param userName
+ * @return
+ */
+ @PreAuthorize("@ss.hasPerm('monitor:online:list')")
+ @GetMapping("/onlineUser/list")
+ public ResponseDTO list(String ipaddr, String userName) {
+ List onlineUserList = monitorDomainService.getOnlineUserList(userName, ipaddr);
+ return ResponseDTO.ok(new PageDTO(onlineUserList));
+ }
+
+ /**
+ * 强退用户
+ */
+ @PreAuthorize("@ss.hasPerm('monitor:online:forceLogout')")
+ @AccessLog(title = "在线用户", businessType = BusinessType.FORCE)
+ @DeleteMapping("/onlineUser/{tokenId}")
+ public ResponseDTO forceLogout(@PathVariable String tokenId) {
+ redisCacheService.loginUserCache.delete(tokenId);
+ return ResponseDTO.ok();
+ }
+
+
+}
diff --git a/agileboot-admin/src/main/java/com/agileboot/admin/controller/system/SysConfigController.java b/agileboot-admin/src/main/java/com/agileboot/admin/controller/system/SysConfigController.java
new file mode 100644
index 0000000..91b81de
--- /dev/null
+++ b/agileboot-admin/src/main/java/com/agileboot/admin/controller/system/SysConfigController.java
@@ -0,0 +1,98 @@
+package com.agileboot.admin.controller.system;
+
+import com.agileboot.common.core.base.BaseController;
+import com.agileboot.common.core.dto.ResponseDTO;
+import com.agileboot.common.core.page.PageDTO;
+import com.agileboot.domain.system.config.ConfigDTO;
+import com.agileboot.domain.system.config.ConfigDomainService;
+import com.agileboot.domain.system.config.ConfigQuery;
+import com.agileboot.domain.system.config.ConfigUpdateCommand;
+import com.agileboot.infrastructure.annotations.AccessLog;
+import com.agileboot.infrastructure.cache.guava.GuavaCacheService;
+import com.agileboot.infrastructure.cache.map.MapCache;
+import com.agileboot.infrastructure.web.util.AuthenticationUtils;
+import com.agileboot.orm.enums.BusinessType;
+import com.agileboot.orm.result.DictionaryData;
+import java.util.List;
+import javax.validation.constraints.NotNull;
+import javax.validation.constraints.Positive;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * 参数配置 信息操作处理
+ * @author valarchie
+ */
+@RestController
+@RequestMapping("/system/config")
+@Validated
+public class SysConfigController extends BaseController {
+
+ @Autowired
+ private ConfigDomainService configDomainService;
+
+ @Autowired
+ private GuavaCacheService guavaCacheService;
+
+ /**
+ * 获取参数配置列表
+ */
+ @PreAuthorize("@ss.hasPerm('system:config:list')")
+ @GetMapping("/list")
+ public ResponseDTO list(ConfigQuery query) {
+ PageDTO page = configDomainService.getConfigList(query);
+ return ResponseDTO.ok(page);
+ }
+
+ /**
+ * 根据字典类型查询字典数据信息
+ * 换成用Enum
+ */
+ @GetMapping(value = "/dict/{dictType}")
+ public ResponseDTO> dictType(@PathVariable String dictType) {
+ List dictionaryData = MapCache.dictionaryCache().get(dictType);
+ return ResponseDTO.ok(dictionaryData);
+ }
+
+
+ /**
+ * 根据参数编号获取详细信息
+ */
+ @PreAuthorize("@ss.hasPerm('system:config:query')")
+ @GetMapping(value = "/{configId}")
+ public ResponseDTO getInfo(@NotNull @Positive @PathVariable Long configId) {
+ ConfigDTO config = configDomainService.getConfigInfo(configId);
+ return ResponseDTO.ok(config);
+ }
+
+
+ /**
+ * 修改参数配置
+ */
+ @PreAuthorize("@ss.hasPerm('system:config:edit')")
+ @AccessLog(title = "参数管理", businessType = BusinessType.UPDATE)
+ @PutMapping
+ public ResponseDTO edit(@RequestBody ConfigUpdateCommand config) {
+ configDomainService.updateConfig(config, AuthenticationUtils.getLoginUser());
+ return ResponseDTO.ok();
+ }
+
+ /**
+ * 刷新参数缓存
+ */
+ @PreAuthorize("@ss.hasPerm('system:config:remove')")
+ @AccessLog(title = "参数管理", businessType = BusinessType.CLEAN)
+ @DeleteMapping("/refreshCache")
+ public ResponseDTO> refreshCache() {
+ guavaCacheService.configCache.invalidateAll();
+ return ResponseDTO.ok();
+ }
+}
diff --git a/agileboot-admin/src/main/java/com/agileboot/admin/controller/system/SysDeptController.java b/agileboot-admin/src/main/java/com/agileboot/admin/controller/system/SysDeptController.java
new file mode 100644
index 0000000..8224e8b
--- /dev/null
+++ b/agileboot-admin/src/main/java/com/agileboot/admin/controller/system/SysDeptController.java
@@ -0,0 +1,126 @@
+package com.agileboot.admin.controller.system;
+
+import cn.hutool.core.lang.tree.Tree;
+import com.agileboot.common.core.base.BaseController;
+import com.agileboot.common.core.dto.ResponseDTO;
+import com.agileboot.domain.system.TreeSelectedDTO;
+import com.agileboot.domain.system.dept.AddDeptCommand;
+import com.agileboot.domain.system.dept.DeptDTO;
+import com.agileboot.domain.system.dept.DeptDomainService;
+import com.agileboot.domain.system.dept.DeptQuery;
+import com.agileboot.domain.system.dept.UpdateDeptCommand;
+import com.agileboot.infrastructure.annotations.AccessLog;
+import com.agileboot.infrastructure.web.util.AuthenticationUtils;
+import com.agileboot.orm.enums.BusinessType;
+import java.util.List;
+import javax.validation.constraints.NotNull;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * 部门信息
+ *
+ * @author valarchie
+ */
+@RestController
+@RequestMapping("/system/dept")
+@Validated
+public class SysDeptController extends BaseController {
+
+ @Autowired
+ private DeptDomainService deptDomainService;
+
+ /**
+ * 获取部门列表
+ */
+ @PreAuthorize("@ss.hasPerm('system:dept:list')")
+ @GetMapping("/list")
+ public ResponseDTO list(DeptQuery query) {
+ List deptList = deptDomainService.getDeptList(query);
+ return ResponseDTO.ok(deptList);
+ }
+
+ /**
+ * 查询部门列表(排除当前部门,比如在修改部门的上级部门的时候,需要排除自身当前的部门,因为上级部门不能选自己)
+ */
+ @PreAuthorize("@ss.hasPerm('system:dept:list')")
+ @GetMapping("/list/exclude/{deptId}")
+ public ResponseDTO excludeCurrentDeptItself(@PathVariable(value = "deptId", required = false) Long deptId) {
+ DeptQuery query = new DeptQuery();
+ query.setDeptId(deptId);
+ query.setExcludeCurrentDept(true);
+
+ List deptList = deptDomainService.getDeptList(query);
+ return ResponseDTO.ok(deptList);
+ }
+
+ /**
+ * 根据部门编号获取详细信息
+ */
+ @PreAuthorize("@ss.hasPerm('system:dept:query')")
+ @GetMapping(value = "/{deptId}")
+ public ResponseDTO getInfo(@PathVariable Long deptId) {
+ DeptDTO dept = deptDomainService.getDeptInfo(deptId);
+ return ResponseDTO.ok(dept);
+ }
+
+ /**
+ * 获取部门下拉树列表
+ */
+ @GetMapping("/dropdownList")
+ public ResponseDTO dropdownList() {
+ List> deptTree = deptDomainService.getDeptTree();
+ return ResponseDTO.ok(deptTree);
+ }
+
+ /**
+ * 加载对应角色部门列表树
+ */
+ @GetMapping(value = "/dropdownList/role/{roleId}")
+ public ResponseDTO dropdownListForRole(@PathVariable("roleId") Long roleId) {
+ TreeSelectedDTO deptTreeForRole = deptDomainService.getDeptTreeForRole(roleId);
+ return ResponseDTO.ok(deptTreeForRole);
+ }
+
+ /**
+ * 新增部门
+ */
+ @PreAuthorize("@ss.hasPerm('system:dept:add')")
+ @AccessLog(title = "部门管理", businessType = BusinessType.INSERT)
+ @PostMapping
+ public ResponseDTO add(@RequestBody AddDeptCommand addCommand) {
+ deptDomainService.addDept(addCommand, AuthenticationUtils.getLoginUser());
+ return ResponseDTO.ok();
+ }
+
+ /**
+ * 修改部门
+ */
+ @PreAuthorize("@ss.hasPerm('system:dept:edit') AND @ss.checkDataScopeWithDeptId(#updateCommand.deptId)")
+ @AccessLog(title = "部门管理", businessType = BusinessType.UPDATE)
+ @PutMapping
+ public ResponseDTO edit(@RequestBody UpdateDeptCommand updateCommand) {
+ deptDomainService.updateDept(updateCommand, AuthenticationUtils.getLoginUser());
+ return ResponseDTO.ok();
+ }
+
+ /**
+ * 删除部门
+ */
+ @PreAuthorize("@ss.hasPerm('system:dept:remove') AND @ss.checkDataScopeWithDeptId(#deptId)")
+ @AccessLog(title = "部门管理", businessType = BusinessType.DELETE)
+ @DeleteMapping("/{deptId}")
+ public ResponseDTO remove(@PathVariable @NotNull Long deptId) {
+ deptDomainService.removeDept(deptId);
+ return ResponseDTO.ok();
+ }
+}
diff --git a/agileboot-admin/src/main/java/com/agileboot/admin/controller/system/SysLoginInfoController.java b/agileboot-admin/src/main/java/com/agileboot/admin/controller/system/SysLoginInfoController.java
new file mode 100644
index 0000000..65961cd
--- /dev/null
+++ b/agileboot-admin/src/main/java/com/agileboot/admin/controller/system/SysLoginInfoController.java
@@ -0,0 +1,71 @@
+package com.agileboot.admin.controller.system;
+
+
+import com.agileboot.common.core.base.BaseController;
+import com.agileboot.common.core.dto.ResponseDTO;
+import com.agileboot.common.core.page.PageDTO;
+import com.agileboot.common.exception.error.ErrorCode;
+import com.agileboot.common.utils.poi.CustomExcelUtil;
+import com.agileboot.domain.common.BulkOperationCommand;
+import com.agileboot.domain.system.loginInfo.LoginInfoDTO;
+import com.agileboot.domain.system.loginInfo.LoginInfoDomainService;
+import com.agileboot.domain.system.loginInfo.LoginInfoQuery;
+import com.agileboot.infrastructure.annotations.AccessLog;
+import com.agileboot.orm.enums.BusinessType;
+import java.util.List;
+import javax.servlet.http.HttpServletResponse;
+import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.NotNull;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * 系统访问记录
+ *
+ * @author valarchie
+ */
+@RestController
+@RequestMapping("/loginInfo")
+@Validated
+public class SysLoginInfoController extends BaseController {
+
+ @Autowired
+ private LoginInfoDomainService loginInfoDomainService;
+
+ @PreAuthorize("@ss.hasPerm('monitor:logininfor:list')")
+ @GetMapping("/list")
+ public ResponseDTO list(LoginInfoQuery query) {
+ PageDTO pageDTO = loginInfoDomainService.getLoginInfoList(query);
+ return ResponseDTO.ok(pageDTO);
+ }
+
+ @AccessLog(title = "登录日志", businessType = BusinessType.EXPORT)
+ @PreAuthorize("@ss.hasPerm('monitor:logininfor:export')")
+ @PostMapping("/export")
+ public void export(HttpServletResponse response, LoginInfoQuery query) {
+ PageDTO pageDTO = loginInfoDomainService.getLoginInfoList(query);
+ CustomExcelUtil.writeToResponse(pageDTO.getRows(), LoginInfoDTO.class, response);
+ }
+
+ @PreAuthorize("@ss.hasPerm('monitor:logininfor:remove')")
+ @AccessLog(title = "登录日志", businessType = BusinessType.DELETE)
+ @DeleteMapping("/{infoIds}")
+ public ResponseDTO remove(@PathVariable @NotNull @NotEmpty List infoIds) {
+ loginInfoDomainService.deleteLoginInfo(new BulkOperationCommand<>(infoIds));
+ return ResponseDTO.ok();
+ }
+
+ @PreAuthorize("@ss.hasPerm('monitor:logininfor:remove')")
+ @AccessLog(title = "登录日志", businessType = BusinessType.CLEAN)
+ @DeleteMapping("/clean")
+ public ResponseDTO clean() {
+ return ResponseDTO.fail(ErrorCode.Business.UNSUPPORTED_OPERATION);
+ }
+}
diff --git a/agileboot-admin/src/main/java/com/agileboot/admin/controller/system/SysMenuController.java b/agileboot-admin/src/main/java/com/agileboot/admin/controller/system/SysMenuController.java
new file mode 100644
index 0000000..d101179
--- /dev/null
+++ b/agileboot-admin/src/main/java/com/agileboot/admin/controller/system/SysMenuController.java
@@ -0,0 +1,120 @@
+package com.agileboot.admin.controller.system;
+
+import cn.hutool.core.lang.tree.Tree;
+import com.agileboot.common.core.base.BaseController;
+import com.agileboot.common.core.dto.ResponseDTO;
+import com.agileboot.domain.system.TreeSelectedDTO;
+import com.agileboot.domain.system.menu.AddMenuCommand;
+import com.agileboot.domain.system.menu.MenuDTO;
+import com.agileboot.domain.system.menu.MenuDomainService;
+import com.agileboot.domain.system.menu.MenuQuery;
+import com.agileboot.domain.system.menu.UpdateMenuCommand;
+import com.agileboot.infrastructure.annotations.AccessLog;
+import com.agileboot.infrastructure.web.domain.login.LoginUser;
+import com.agileboot.infrastructure.web.util.AuthenticationUtils;
+import com.agileboot.orm.enums.BusinessType;
+import java.util.List;
+import javax.validation.constraints.NotNull;
+import javax.validation.constraints.PositiveOrZero;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * 菜单信息
+ *
+ * @author ruoyi
+ */
+@RestController
+@RequestMapping("/system/menu")
+@Validated
+public class SysMenuController extends BaseController {
+
+ @Autowired
+ MenuDomainService menuDomainService;
+
+ /**
+ * 获取菜单列表
+ */
+ @PreAuthorize("@ss.hasPerm('system:menu:list')")
+ @GetMapping("/list")
+ public ResponseDTO list(MenuQuery query) {
+ List menuList = menuDomainService.getMenuList(query);
+ return ResponseDTO.ok(menuList);
+ }
+
+ /**
+ * 根据菜单编号获取详细信息
+ */
+ @PreAuthorize("@ss.hasPerm('system:menu:query')")
+ @GetMapping(value = "/{menuId}")
+ public ResponseDTO getInfo(@PathVariable @NotNull @PositiveOrZero Long menuId) {
+ MenuDTO menu = menuDomainService.getMenuInfo(menuId);
+ return ResponseDTO.ok(menu);
+ }
+
+ /**
+ * 获取菜单下拉树列表
+ */
+ @GetMapping("/dropdownList")
+ public ResponseDTO dropdownList() {
+ LoginUser loginUser = AuthenticationUtils.getLoginUser();
+ List> dropdownList = menuDomainService.getDropdownList(loginUser);
+ return ResponseDTO.ok(dropdownList);
+ }
+
+ /**
+ * 加载对应角色菜单列表树
+ */
+ @GetMapping(value = "/roleMenuTreeSelect/{roleId}")
+ public ResponseDTO roleMenuTreeSelect(@PathVariable("roleId") Long roleId) {
+ LoginUser loginUser = AuthenticationUtils.getLoginUser();
+ TreeSelectedDTO roleDropdownList = menuDomainService.getRoleDropdownList(loginUser, roleId);
+ return ResponseDTO.ok(roleDropdownList);
+ }
+
+ /**
+ * 新增菜单
+ */
+ @PreAuthorize("@ss.hasPerm('system:menu:add')")
+ @AccessLog(title = "菜单管理", businessType = BusinessType.INSERT)
+ @PostMapping
+ public ResponseDTO add(@RequestBody AddMenuCommand addCommand) {
+ LoginUser loginUser = AuthenticationUtils.getLoginUser();
+ menuDomainService.addMenu(addCommand, loginUser);
+ return ResponseDTO.ok();
+ }
+
+ /**
+ * 修改菜单
+ */
+ @PreAuthorize("@ss.hasPerm('system:menu:edit')")
+ @AccessLog(title = "菜单管理", businessType = BusinessType.UPDATE)
+ @PutMapping
+ public ResponseDTO edit(@RequestBody UpdateMenuCommand updateCommand) {
+ LoginUser loginUser = AuthenticationUtils.getLoginUser();
+ menuDomainService.updateMenu(updateCommand, loginUser);
+ return ResponseDTO.ok();
+ }
+
+ /**
+ * 删除菜单
+ */
+ @PreAuthorize("@ss.hasPerm('system:menu:remove')")
+ @AccessLog(title = "菜单管理", businessType = BusinessType.DELETE)
+ @DeleteMapping("/{menuId}")
+ public ResponseDTO remove(@PathVariable("menuId") Long menuId) {
+ menuDomainService.remove(menuId);
+ return ResponseDTO.ok();
+ }
+
+
+}
diff --git a/agileboot-admin/src/main/java/com/agileboot/admin/controller/system/SysNoticeController.java b/agileboot-admin/src/main/java/com/agileboot/admin/controller/system/SysNoticeController.java
new file mode 100644
index 0000000..a123cfd
--- /dev/null
+++ b/agileboot-admin/src/main/java/com/agileboot/admin/controller/system/SysNoticeController.java
@@ -0,0 +1,97 @@
+package com.agileboot.admin.controller.system;
+
+import com.agileboot.common.core.base.BaseController;
+import com.agileboot.common.core.dto.ResponseDTO;
+import com.agileboot.common.core.page.PageDTO;
+import com.agileboot.domain.common.BulkOperationCommand;
+import com.agileboot.domain.system.notice.NoticeAddCommand;
+import com.agileboot.domain.system.notice.NoticeDTO;
+import com.agileboot.domain.system.notice.NoticeDomainService;
+import com.agileboot.domain.system.notice.NoticeQuery;
+import com.agileboot.domain.system.notice.NoticeUpdateCommand;
+import com.agileboot.infrastructure.annotations.AccessLog;
+import com.agileboot.infrastructure.web.util.AuthenticationUtils;
+import com.agileboot.orm.enums.BusinessType;
+import java.util.List;
+import javax.validation.constraints.NotNull;
+import javax.validation.constraints.Positive;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * 公告 信息操作处理
+ *
+ * @author valarchie
+ */
+@RestController
+@RequestMapping("/system/notice")
+@Validated
+public class SysNoticeController extends BaseController {
+
+ @Autowired
+ private NoticeDomainService noticeDomainService;
+
+ /**
+ * 获取通知公告列表
+ */
+ @PreAuthorize("@ss.hasPerm('system:notice:list')")
+ @GetMapping("/list")
+ public ResponseDTO list(NoticeQuery query) {
+ PageDTO pageDTO = noticeDomainService.getNoticeList(query);
+ return ResponseDTO.ok(pageDTO);
+ }
+
+ /**
+ * 根据通知公告编号获取详细信息
+ */
+ @PreAuthorize("@ss.hasPerm('system:notice:query')")
+ @GetMapping(value = "/{noticeId}")
+ public ResponseDTO getInfo(@PathVariable @NotNull @Positive Long noticeId) {
+ return ResponseDTO.ok(noticeDomainService.getNoticeInfo(noticeId));
+ }
+
+ /**
+ * 新增通知公告
+ */
+ @PreAuthorize("@ss.hasPerm('system:notice:add')")
+ @AccessLog(title = "通知公告", businessType = BusinessType.INSERT)
+ @PostMapping
+ public ResponseDTO add(@RequestBody NoticeAddCommand addCommand) {
+ noticeDomainService.addNotice(addCommand, AuthenticationUtils.getLoginUser());
+ return ResponseDTO.ok();
+ }
+
+ /**
+ * 修改通知公告
+ */
+ @PreAuthorize("@ss.hasPerm('system:notice:edit')")
+ @AccessLog(title = "通知公告", businessType = BusinessType.UPDATE)
+ @PutMapping
+ public ResponseDTO edit(@RequestBody NoticeUpdateCommand updateCommand) {
+ noticeDomainService.updateNotice(updateCommand, AuthenticationUtils.getLoginUser());
+ return ResponseDTO.ok();
+ }
+
+ /**
+ * 删除通知公告
+ */
+ @PreAuthorize("@ss.hasPerm('system:notice:remove')")
+ @AccessLog(title = "通知公告", businessType = BusinessType.DELETE)
+ @DeleteMapping("/{noticeIds}")
+ public ResponseDTO remove(@PathVariable List noticeIds) {
+ noticeDomainService.deleteNotice(new BulkOperationCommand<>(noticeIds));
+ return ResponseDTO.ok();
+ }
+
+
+
+}
diff --git a/agileboot-admin/src/main/java/com/agileboot/admin/controller/system/SysOperationLogController.java b/agileboot-admin/src/main/java/com/agileboot/admin/controller/system/SysOperationLogController.java
new file mode 100644
index 0000000..dd0b5d8
--- /dev/null
+++ b/agileboot-admin/src/main/java/com/agileboot/admin/controller/system/SysOperationLogController.java
@@ -0,0 +1,67 @@
+package com.agileboot.admin.controller.system;
+
+import com.agileboot.common.core.base.BaseController;
+import com.agileboot.common.core.dto.ResponseDTO;
+import com.agileboot.common.core.page.PageDTO;
+import com.agileboot.common.exception.error.ErrorCode;
+import com.agileboot.common.utils.poi.CustomExcelUtil;
+import com.agileboot.domain.common.BulkOperationCommand;
+import com.agileboot.domain.system.operationLog.OperationLogDTO;
+import com.agileboot.domain.system.operationLog.OperationLogDomainService;
+import com.agileboot.domain.system.operationLog.OperationLogQuery;
+import com.agileboot.infrastructure.annotations.AccessLog;
+import com.agileboot.orm.enums.BusinessType;
+import java.util.List;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * 操作日志记录
+ *
+ * @author valarchie
+ */
+@RestController
+@RequestMapping("/operationLog")
+public class SysOperationLogController extends BaseController {
+
+ @Autowired
+ private OperationLogDomainService operationLogDomainService;
+
+ @PreAuthorize("@ss.hasPerm('monitor:operlog:list')")
+ @GetMapping("/list")
+ public ResponseDTO list(OperationLogQuery query, HttpServletRequest request) {
+ PageDTO pageDTO = operationLogDomainService.getOperationLogList(query);
+ return ResponseDTO.ok(pageDTO);
+ }
+
+ @AccessLog(title = "操作日志", businessType = BusinessType.EXPORT)
+ @PreAuthorize("@ss.hasPerm('monitor:operlog:export')")
+ @PostMapping("/export")
+ public void export(HttpServletResponse response, OperationLogQuery query) {
+ PageDTO pageDTO = operationLogDomainService.getOperationLogList(query);
+ CustomExcelUtil.writeToResponse(pageDTO.getRows(), OperationLogDTO.class, response);
+ }
+
+ @AccessLog(title = "操作日志", businessType = BusinessType.DELETE)
+ @PreAuthorize("@ss.hasPerm('monitor:operlog:remove')")
+ @DeleteMapping("/{operationIds}")
+ public ResponseDTO remove(@PathVariable List operationIds) {
+ operationLogDomainService.deleteOperationLog(new BulkOperationCommand<>(operationIds));
+ return ResponseDTO.ok();
+ }
+
+ @AccessLog(title = "操作日志", businessType = BusinessType.CLEAN)
+ @PreAuthorize("@ss.hasPerm('monitor:operlog:remove')")
+ @DeleteMapping("/clean")
+ public ResponseDTO clean() {
+ return ResponseDTO.fail(ErrorCode.Business.UNSUPPORTED_OPERATION);
+ }
+}
diff --git a/agileboot-admin/src/main/java/com/agileboot/admin/controller/system/SysPostController.java b/agileboot-admin/src/main/java/com/agileboot/admin/controller/system/SysPostController.java
new file mode 100644
index 0000000..16af203
--- /dev/null
+++ b/agileboot-admin/src/main/java/com/agileboot/admin/controller/system/SysPostController.java
@@ -0,0 +1,104 @@
+package com.agileboot.admin.controller.system;
+
+import com.agileboot.common.core.base.BaseController;
+import com.agileboot.common.core.dto.ResponseDTO;
+import com.agileboot.common.core.page.PageDTO;
+import com.agileboot.common.utils.poi.CustomExcelUtil;
+import com.agileboot.domain.common.BulkOperationCommand;
+import com.agileboot.domain.system.post.AddPostCommand;
+import com.agileboot.domain.system.post.PostDTO;
+import com.agileboot.domain.system.post.PostDomainService;
+import com.agileboot.domain.system.post.PostQuery;
+import com.agileboot.domain.system.post.UpdatePostCommand;
+import com.agileboot.infrastructure.annotations.AccessLog;
+import com.agileboot.infrastructure.web.util.AuthenticationUtils;
+import com.agileboot.orm.enums.BusinessType;
+import java.util.List;
+import javax.servlet.http.HttpServletResponse;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * 岗位信息操作处理
+ *
+ * @author ruoyi
+ */
+@RestController
+@RequestMapping("/system/post")
+@Validated
+public class SysPostController extends BaseController {
+
+ @Autowired
+ private PostDomainService postDomainService;
+
+ /**
+ * 获取岗位列表
+ */
+ @PreAuthorize("@ss.hasPerm('system:post:list')")
+ @GetMapping("/list")
+ public ResponseDTO list(PostQuery query) {
+ PageDTO pageDTO = postDomainService.getPostList(query);
+ return ResponseDTO.ok(pageDTO);
+ }
+
+ @AccessLog(title = "岗位管理", businessType = BusinessType.EXPORT)
+ @PreAuthorize("@ss.hasPerm('system:post:export')")
+ @PostMapping("/export")
+ public void export(HttpServletResponse response, PostQuery query) {
+ PageDTO pageDTO = postDomainService.getPostList(query);
+ CustomExcelUtil.writeToResponse(pageDTO.getRows(), PostDTO.class, response);
+ }
+
+ /**
+ * 根据岗位编号获取详细信息
+ */
+ @PreAuthorize("@ss.hasPerm('system:post:query')")
+ @GetMapping(value = "/{postId}")
+ public ResponseDTO getInfo(@PathVariable Long postId) {
+ PostDTO post = postDomainService.getPostInfo(postId);
+ return ResponseDTO.ok(post);
+ }
+
+ /**
+ * 新增岗位
+ */
+ @PreAuthorize("@ss.hasPerm('system:post:add')")
+ @AccessLog(title = "岗位管理", businessType = BusinessType.INSERT)
+ @PostMapping
+ public ResponseDTO add(@RequestBody AddPostCommand addCommand) {
+ postDomainService.addPost(addCommand, AuthenticationUtils.getLoginUser());
+ return ResponseDTO.ok();
+ }
+
+ /**
+ * 修改岗位
+ */
+ @PreAuthorize("@ss.hasPerm('system:post:edit')")
+ @AccessLog(title = "岗位管理", businessType = BusinessType.UPDATE)
+ @PutMapping
+ public ResponseDTO edit(@RequestBody UpdatePostCommand updateCommand) {
+ postDomainService.updatePost(updateCommand, AuthenticationUtils.getLoginUser());
+ return ResponseDTO.ok();
+ }
+
+ /**
+ * 删除岗位
+ */
+ @PreAuthorize("@ss.hasPerm('system:post:remove')")
+ @AccessLog(title = "岗位管理", businessType = BusinessType.DELETE)
+ @DeleteMapping("/{postIds}")
+ public ResponseDTO remove(@PathVariable List postIds) {
+ postDomainService.deletePost(new BulkOperationCommand<>(postIds));
+ return ResponseDTO.ok();
+ }
+
+}
diff --git a/agileboot-admin/src/main/java/com/agileboot/admin/controller/system/SysProfileController.java b/agileboot-admin/src/main/java/com/agileboot/admin/controller/system/SysProfileController.java
new file mode 100644
index 0000000..9a871f9
--- /dev/null
+++ b/agileboot-admin/src/main/java/com/agileboot/admin/controller/system/SysProfileController.java
@@ -0,0 +1,91 @@
+package com.agileboot.admin.controller.system;
+
+import com.agileboot.common.config.AgileBootConfig;
+import com.agileboot.common.core.base.BaseController;
+import com.agileboot.common.core.dto.ResponseDTO;
+import com.agileboot.common.exception.ApiException;
+import com.agileboot.common.exception.error.ErrorCode;
+import com.agileboot.common.utils.file.FileUploadUtils;
+import com.agileboot.domain.common.UploadFileDTO;
+import com.agileboot.domain.system.user.UserDomainService;
+import com.agileboot.domain.system.user.UserProfileDTO;
+import com.agileboot.domain.system.user.command.UpdateProfileCommand;
+import com.agileboot.domain.system.user.command.UpdateUserAvatarCommand;
+import com.agileboot.domain.system.user.command.UpdateUserPasswordCommand;
+import com.agileboot.infrastructure.annotations.AccessLog;
+import com.agileboot.infrastructure.web.domain.login.LoginUser;
+import com.agileboot.infrastructure.web.util.AuthenticationUtils;
+import com.agileboot.orm.enums.BusinessType;
+import java.io.IOException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.multipart.MultipartFile;
+
+/**
+ * 个人信息 业务处理
+ *
+ * @author ruoyi
+ */
+@RestController
+@RequestMapping("/system/user/profile")
+public class SysProfileController extends BaseController {
+
+ @Autowired
+ private UserDomainService userDomainService;
+
+ /**
+ * 个人信息
+ */
+ @GetMapping
+ public ResponseDTO profile() {
+ LoginUser user = AuthenticationUtils.getLoginUser();
+ UserProfileDTO userProfile = userDomainService.getUserProfile(user.getUserId());
+ return ResponseDTO.ok(userProfile);
+ }
+
+ /**
+ * 修改用户
+ */
+ @AccessLog(title = "个人信息", businessType = BusinessType.UPDATE)
+ @PutMapping
+ public ResponseDTO updateProfile(@RequestBody UpdateProfileCommand command) {
+ LoginUser loginUser = AuthenticationUtils.getLoginUser();
+ command.setUserId(loginUser.getUserId());
+ userDomainService.updateUserProfile(command, loginUser);
+ return ResponseDTO.ok();
+ }
+
+ /**
+ * 重置密码
+ */
+ @AccessLog(title = "个人信息", businessType = BusinessType.UPDATE)
+ @PutMapping("/password")
+ public ResponseDTO updatePassword(@RequestBody UpdateUserPasswordCommand command) {
+ LoginUser loginUser = AuthenticationUtils.getLoginUser();
+ command.setUserId(loginUser.getUserId());
+ userDomainService.updateUserPassword(loginUser, command);
+ return ResponseDTO.ok();
+ }
+
+ /**
+ * 头像上传
+ */
+ @AccessLog(title = "用户头像", businessType = BusinessType.UPDATE)
+ @PostMapping("/avatar")
+ public ResponseDTO avatar(@RequestParam("avatarfile") MultipartFile file) throws IOException {
+ if (file.isEmpty()) {
+ throw new ApiException(ErrorCode.Business.USER_UPLOAD_FILE_FAILED);
+ }
+ LoginUser loginUser = AuthenticationUtils.getLoginUser();
+ String avatar = FileUploadUtils.upload(AgileBootConfig.getAvatarPath(), file);
+
+ userDomainService.updateUserAvatar(loginUser, new UpdateUserAvatarCommand(loginUser.getUserId(), avatar));
+ return ResponseDTO.ok(new UploadFileDTO(avatar));
+ }
+}
diff --git a/agileboot-admin/src/main/java/com/agileboot/admin/controller/system/SysRoleController.java b/agileboot-admin/src/main/java/com/agileboot/admin/controller/system/SysRoleController.java
new file mode 100644
index 0000000..ee82db2
--- /dev/null
+++ b/agileboot-admin/src/main/java/com/agileboot/admin/controller/system/SysRoleController.java
@@ -0,0 +1,192 @@
+package com.agileboot.admin.controller.system;
+
+import com.agileboot.common.core.base.BaseController;
+import com.agileboot.common.core.dto.ResponseDTO;
+import com.agileboot.common.core.page.PageDTO;
+import com.agileboot.common.utils.poi.CustomExcelUtil;
+import com.agileboot.domain.system.role.AddRoleCommand;
+import com.agileboot.domain.system.role.AllocatedRoleQuery;
+import com.agileboot.domain.system.role.RoleDTO;
+import com.agileboot.domain.system.role.RoleDomainService;
+import com.agileboot.domain.system.role.RoleQuery;
+import com.agileboot.domain.system.role.UnallocatedRoleQuery;
+import com.agileboot.domain.system.role.UpdateDataScopeCommand;
+import com.agileboot.domain.system.role.UpdateRoleCommand;
+import com.agileboot.domain.system.role.UpdateStatusCommand;
+import com.agileboot.infrastructure.annotations.AccessLog;
+import com.agileboot.infrastructure.web.domain.login.LoginUser;
+import com.agileboot.infrastructure.web.util.AuthenticationUtils;
+import com.agileboot.orm.enums.BusinessType;
+import java.util.List;
+import javax.servlet.http.HttpServletResponse;
+import javax.validation.constraints.NotNull;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * 角色信息
+ *
+ * @author ruoyi
+ */
+@RestController
+@RequestMapping("/system/role")
+@Validated
+public class SysRoleController extends BaseController {
+
+ @Autowired
+ private RoleDomainService roleDomainService;
+
+ @PreAuthorize("@ss.hasPerm('system:role:list')")
+ @GetMapping("/list")
+ public ResponseDTO list(RoleQuery query) {
+ PageDTO pageDTO = roleDomainService.getRoleList(query);
+ return ResponseDTO.ok(pageDTO);
+ }
+
+ @AccessLog(title = "角色管理", businessType = BusinessType.EXPORT)
+ @PreAuthorize("@ss.hasPerm('system:role:export')")
+ @PostMapping("/export")
+ public void export(HttpServletResponse response, RoleQuery query) {
+ PageDTO pageDTO = roleDomainService.getRoleList(query);
+ CustomExcelUtil.writeToResponse(pageDTO.getRows(), RoleDTO.class, response);
+ }
+
+ /**
+ * 根据角色编号获取详细信息
+ */
+ @PreAuthorize("@ss.hasPerm('system:role:query')")
+ @GetMapping(value = "/{roleId}")
+ public ResponseDTO getInfo(@PathVariable @NotNull Long roleId) {
+ RoleDTO roleInfo = roleDomainService.getRoleInfo(roleId);
+ return ResponseDTO.ok(roleInfo);
+ }
+
+ /**
+ * 新增角色
+ */
+ @PreAuthorize("@ss.hasPerm('system:role:add')")
+ @AccessLog(title = "角色管理", businessType = BusinessType.INSERT)
+ @PostMapping
+ public ResponseDTO add(@RequestBody AddRoleCommand addCommand) {
+ LoginUser loginUser = AuthenticationUtils.getLoginUser();
+ roleDomainService.addRole(addCommand, loginUser);
+ return ResponseDTO.ok();
+ }
+
+ /**
+ * 新增角色
+ */
+ @PreAuthorize("@ss.hasPerm('system:role:remove')")
+ @AccessLog(title = "角色管理", businessType = BusinessType.INSERT)
+ @DeleteMapping(value = "/{roleId}")
+ public ResponseDTO remove(@PathVariable("roleId")List roleIds) {
+ LoginUser loginUser = AuthenticationUtils.getLoginUser();
+ roleDomainService.deleteRoleByBulk(roleIds, loginUser);
+ return ResponseDTO.ok();
+ }
+
+ /**
+ * 修改保存角色
+ */
+ @PreAuthorize("@ss.hasPerm('system:role:edit')")
+ @AccessLog(title = "角色管理", businessType = BusinessType.UPDATE)
+ @PutMapping
+ public ResponseDTO edit(@Validated @RequestBody UpdateRoleCommand updateCommand) {
+ LoginUser loginUser = AuthenticationUtils.getLoginUser();
+ roleDomainService.updateRole(updateCommand, loginUser);
+ return ResponseDTO.ok();
+ }
+
+ /**
+ * 修改保存数据权限
+ */
+ @PreAuthorize("@ss.hasPerm('system:role:edit')")
+ @AccessLog(title = "角色管理", businessType = BusinessType.UPDATE)
+ @PutMapping("/{roleId}/dataScope")
+ public ResponseDTO dataScope(@PathVariable("roleId")Long roleId, @RequestBody UpdateDataScopeCommand command) {
+ command.setRoleId(roleId);
+
+ roleDomainService.updateDataScope(command);
+ return ResponseDTO.ok();
+ }
+
+ /**
+ * 状态修改
+ */
+ @PreAuthorize("@ss.hasPerm('system:role:edit')")
+ @AccessLog(title = "角色管理", businessType = BusinessType.UPDATE)
+ @PutMapping("/{roleId}/status")
+ public ResponseDTO changeStatus(@PathVariable("roleId")Long roleId, @RequestBody UpdateStatusCommand command) {
+ LoginUser loginUser = AuthenticationUtils.getLoginUser();
+ command.setRoleId(roleId);
+
+ roleDomainService.updateStatus(command, loginUser);
+ return ResponseDTO.ok();
+ }
+
+
+ /**
+ * 查询已分配用户角色列表
+ */
+ @PreAuthorize("@ss.hasPerm('system:role:list')")
+ @GetMapping("/{roleId}/allocated/list")
+ public ResponseDTO allocatedUserList(@PathVariable("roleId")Long roleId, AllocatedRoleQuery query) {
+ query.setRoleId(roleId);
+ PageDTO page = roleDomainService.getAllocatedUserList(query);
+ return ResponseDTO.ok(page);
+ }
+
+ /**
+ * 查询未分配用户角色列表
+ */
+ @PreAuthorize("@ss.hasPerm('system:role:list')")
+ @GetMapping("/{roleId}/unallocated/list")
+ public ResponseDTO unallocatedUserList(@PathVariable("roleId")Long roleId, UnallocatedRoleQuery query) {
+ query.setRoleId(roleId);
+ PageDTO page = roleDomainService.getUnallocatedUserList(query);
+ return ResponseDTO.ok(page);
+ }
+
+ /**
+ * 取消授权用户
+ */
+ @PreAuthorize("@ss.hasPerm('system:role:edit')")
+ @AccessLog(title = "角色管理", businessType = BusinessType.GRANT)
+ @DeleteMapping("/{roleId}/user/grant")
+ public ResponseDTO deleteRoleOfUser(@PathVariable("roleId")Long roleId, @RequestBody Long userId) {
+ roleDomainService.deleteRoleOfUser(userId);
+ return ResponseDTO.ok();
+ }
+
+ /**
+ * 批量取消授权用户
+ */
+ @PreAuthorize("@ss.hasPerm('system:role:edit')")
+ @AccessLog(title = "角色管理", businessType = BusinessType.GRANT)
+ @DeleteMapping("/users/{userIds}/grant/bulk")
+ public ResponseDTO deleteRoleOfUserByBulk(@PathVariable("userIds") List userIds) {
+ roleDomainService.deleteRoleOfUserByBulk(userIds);
+ return ResponseDTO.ok();
+ }
+
+ /**
+ * 批量选择用户授权
+ */
+ @PreAuthorize("@ss.hasPerm('system:role:edit')")
+ @AccessLog(title = "角色管理", businessType = BusinessType.GRANT)
+ @PostMapping("/{roleId}/users/{userIds}/grant/bulk")
+ public ResponseDTO addRoleForUserByBulk(@PathVariable("roleId") Long roleId,
+ @PathVariable("userIds") List userIds) {
+ roleDomainService.addRoleOfUserByBulk(roleId, userIds);
+ return ResponseDTO.ok();
+ }
+}
diff --git a/agileboot-admin/src/main/java/com/agileboot/admin/controller/system/SysUserController.java b/agileboot-admin/src/main/java/com/agileboot/admin/controller/system/SysUserController.java
new file mode 100644
index 0000000..7760ea9
--- /dev/null
+++ b/agileboot-admin/src/main/java/com/agileboot/admin/controller/system/SysUserController.java
@@ -0,0 +1,171 @@
+package com.agileboot.admin.controller.system;
+
+import cn.hutool.core.collection.ListUtil;
+import com.agileboot.common.core.base.BaseController;
+import com.agileboot.common.core.dto.ResponseDTO;
+import com.agileboot.common.core.page.PageDTO;
+import com.agileboot.common.utils.poi.CustomExcelUtil;
+import com.agileboot.domain.common.BulkOperationCommand;
+import com.agileboot.domain.system.loginInfo.SearchUserQuery;
+import com.agileboot.domain.system.user.UserDTO;
+import com.agileboot.domain.system.user.UserDetailDTO;
+import com.agileboot.domain.system.user.UserDomainService;
+import com.agileboot.domain.system.user.UserInfoDTO;
+import com.agileboot.domain.system.user.command.AddUserCommand;
+import com.agileboot.domain.system.user.command.ChangeStatusCommand;
+import com.agileboot.domain.system.user.command.ResetPasswordCommand;
+import com.agileboot.domain.system.user.command.UpdateUserCommand;
+import com.agileboot.infrastructure.annotations.AccessLog;
+import com.agileboot.infrastructure.web.domain.login.LoginUser;
+import com.agileboot.infrastructure.web.util.AuthenticationUtils;
+import com.agileboot.orm.enums.BusinessType;
+import java.util.List;
+import javax.servlet.http.HttpServletResponse;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.multipart.MultipartFile;
+
+/**
+ * 用户信息
+ *
+ * @author ruoyi
+ */
+@RestController
+@RequestMapping("/system/user")
+public class SysUserController extends BaseController {
+
+ @Autowired
+ private UserDomainService userDomainService;
+
+ /**
+ * 获取用户列表
+ */
+ @PreAuthorize("@ss.hasPerm('system:user:list') AND @ss.checkDataScopeWithDeptId(#query.deptId)")
+ @GetMapping("/list")
+ public ResponseDTO list(SearchUserQuery query) {
+ PageDTO page = userDomainService.getUserList(query);
+ return ResponseDTO.ok(page);
+ }
+
+ @AccessLog(title = "用户管理", businessType = BusinessType.EXPORT)
+ @PreAuthorize("@ss.hasPerm('system:user:export')")
+ @PostMapping("/export")
+ public void export(HttpServletResponse response, SearchUserQuery query) {
+ PageDTO userList = userDomainService.getUserList(query);
+ CustomExcelUtil.writeToResponse(userList.getRows(), UserDTO.class, response);
+ }
+
+ @AccessLog(title = "用户管理", businessType = BusinessType.IMPORT)
+ @PreAuthorize("@ss.hasPerm('system:user:import')")
+ @PostMapping("/importData")
+ public ResponseDTO importData(MultipartFile file) {
+ List> commands = CustomExcelUtil.readFromResponse(AddUserCommand.class, file);
+ LoginUser loginUser = AuthenticationUtils.getLoginUser();
+
+ for (Object command : commands) {
+ AddUserCommand addUserCommand = (AddUserCommand) command;
+ userDomainService.addUser(loginUser, addUserCommand);
+ }
+ return ResponseDTO.ok();
+ }
+
+ @PostMapping("/importTemplate")
+ public void importTemplate(HttpServletResponse response) {
+ CustomExcelUtil.writeToResponse(ListUtil.toList(new AddUserCommand()), AddUserCommand.class, response);
+ }
+
+ /**
+ * 根据用户编号获取详细信息
+ */
+ @PreAuthorize("@ss.hasPerm('system:user:query')")
+ @GetMapping(value = {"/", "/{userId}"})
+ public ResponseDTO getUserDetailInfo(@PathVariable(value = "userId", required = false) Long userId) {
+ UserDetailDTO userDetailInfo = userDomainService.getUserDetailInfo(userId);
+ return ResponseDTO.ok(userDetailInfo);
+ }
+
+ /**
+ * 新增用户
+ */
+ @PreAuthorize("@ss.hasPerm('system:user:add') AND @ss.checkDataScopeWithDeptId(#command.deptId)")
+ @AccessLog(title = "用户管理", businessType = BusinessType.INSERT)
+ @PostMapping
+ public ResponseDTO add(@Validated @RequestBody AddUserCommand command) {
+ LoginUser loginUser = AuthenticationUtils.getLoginUser();
+ userDomainService.addUser(loginUser, command);
+ return ResponseDTO.ok();
+ }
+
+ /**
+ * 修改用户
+ */
+ @PreAuthorize("@ss.hasPerm('system:user:edit') AND @ss.checkDataScopeWithUserId(#command.userId)")
+ @AccessLog(title = "用户管理", businessType = BusinessType.UPDATE)
+ @PutMapping
+ public ResponseDTO edit(@Validated @RequestBody UpdateUserCommand command) {
+ LoginUser loginUser = AuthenticationUtils.getLoginUser();
+ userDomainService.updateUser(loginUser, command);
+ return ResponseDTO.ok();
+ }
+
+ /**
+ * 删除用户
+ */
+ @PreAuthorize("@ss.hasPerm('system:user:remove') AND @ss.checkDataScopeWithUserIds(#userIds)")
+ @AccessLog(title = "用户管理", businessType = BusinessType.DELETE)
+ @DeleteMapping("/{userIds}")
+ public ResponseDTO remove(@PathVariable List userIds) {
+ BulkOperationCommand bulkDeleteCommand = new BulkOperationCommand(userIds);
+ LoginUser loginUser = AuthenticationUtils.getLoginUser();
+ userDomainService.deleteUsers(loginUser, bulkDeleteCommand);
+ return ResponseDTO.ok();
+ }
+
+ /**
+ * 重置密码
+ */
+ @PreAuthorize("@ss.hasPerm('system:user:resetPwd') AND @ss.checkDataScopeWithUserId(#userId)")
+ @AccessLog(title = "用户管理", businessType = BusinessType.UPDATE)
+ @PutMapping("/{userId}/password/reset")
+ public ResponseDTO resetPassword(@PathVariable Long userId, @RequestBody ResetPasswordCommand command) {
+ command.setUserId(userId);
+ LoginUser loginUser = AuthenticationUtils.getLoginUser();
+ userDomainService.resetUserPassword(loginUser, command);
+ return ResponseDTO.ok();
+ }
+
+ /**
+ * 状态修改
+ */
+ @PreAuthorize("@ss.hasPerm('system:user:edit') AND @ss.checkDataScopeWithUserId(#command.userId)")
+ @AccessLog(title = "用户管理", businessType = BusinessType.UPDATE)
+ @PutMapping("/{userId}/status")
+ public ResponseDTO changeStatus(@PathVariable Long userId, @RequestBody ChangeStatusCommand command) {
+ command.setUserId(userId);
+ LoginUser loginUser = AuthenticationUtils.getLoginUser();
+ userDomainService.changeUserStatus(loginUser, command);
+ return ResponseDTO.ok();
+ }
+
+ /**
+ * 根据用户编号获取授权角色
+ */
+ @PreAuthorize("@ss.hasPerm('system:user:query')")
+ @GetMapping("/{userId}/role")
+ public ResponseDTO getRoleOfUser(@PathVariable("userId") Long userId) {
+ UserInfoDTO userWithRole = userDomainService.getUserWithRole(userId);
+ return ResponseDTO.ok(userWithRole);
+ }
+
+
+
+}
diff --git a/agileboot-admin/src/main/java/com/agileboot/admin/controller/tool/SwaggerController.java b/agileboot-admin/src/main/java/com/agileboot/admin/controller/tool/SwaggerController.java
new file mode 100644
index 0000000..a085d99
--- /dev/null
+++ b/agileboot-admin/src/main/java/com/agileboot/admin/controller/tool/SwaggerController.java
@@ -0,0 +1,23 @@
+package com.agileboot.admin.controller.tool;
+
+import com.agileboot.common.core.base.BaseController;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+
+/**
+ * swagger 接口
+ *
+ * @author ruoyi
+ */
+@Controller
+@RequestMapping("/tool/swagger")
+public class SwaggerController extends BaseController {
+
+ @PreAuthorize("@ss.hasPerm('tool:swagger:view')")
+ @GetMapping()
+ public String index() {
+ return redirect("/swagger-ui.html");
+ }
+}
diff --git a/agileboot-admin/src/main/java/com/agileboot/admin/controller/tool/SwaggerTemplateController.java b/agileboot-admin/src/main/java/com/agileboot/admin/controller/tool/SwaggerTemplateController.java
new file mode 100644
index 0000000..703b1ce
--- /dev/null
+++ b/agileboot-admin/src/main/java/com/agileboot/admin/controller/tool/SwaggerTemplateController.java
@@ -0,0 +1,126 @@
+package com.agileboot.admin.controller.tool;
+
+import com.agileboot.common.core.base.BaseController;
+import com.agileboot.common.core.dto.ResponseDTO;
+import com.agileboot.common.exception.error.ErrorCode;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiImplicitParam;
+import io.swagger.annotations.ApiImplicitParams;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import io.swagger.annotations.ApiOperation;
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+ @RequestMapping("/test/user")
+/**
+ * swagger 用户测试方法
+ *
+ * @author valarchie
+ */
+@Api("用户信息管理")
+public class SwaggerTemplateController extends BaseController {
+
+ private final static Map USER_ENTITY_MAP = new LinkedHashMap<>();
+
+ static {
+ USER_ENTITY_MAP.put(1, new UserEntity(1, "admin", "admin123", "15888888888"));
+ USER_ENTITY_MAP.put(2, new UserEntity(2, "agileBoot", "admin123", "15666666666"));
+ }
+
+ @ApiOperation("获取用户列表")
+ @GetMapping("/list")
+ public ResponseDTO> userList() {
+ List userList = new ArrayList<>(USER_ENTITY_MAP.values());
+ return ResponseDTO.ok(userList);
+ }
+
+ @ApiOperation("获取用户详细")
+ @ApiImplicitParam(name = "userId", value = "用户ID", required = true, dataType = "int", paramType = "path",
+ dataTypeClass = Integer.class)
+ @GetMapping("/{userId}")
+ public ResponseDTO getUser(@PathVariable Integer userId) {
+ if (!USER_ENTITY_MAP.isEmpty() && USER_ENTITY_MAP.containsKey(userId)) {
+ return ResponseDTO.ok(USER_ENTITY_MAP.get(userId));
+ } else {
+ return ResponseDTO.fail(ErrorCode.Business.USER_NON_EXIST);
+ }
+ }
+
+ @ApiOperation("新增用户")
+ @ApiImplicitParams({
+ @ApiImplicitParam(name = "userId", value = "用户id", dataType = "Integer", dataTypeClass = Integer.class),
+ @ApiImplicitParam(name = "username", value = "用户名称", dataType = "String", dataTypeClass = String.class),
+ @ApiImplicitParam(name = "password", value = "用户密码", dataType = "String", dataTypeClass = String.class),
+ @ApiImplicitParam(name = "mobile", value = "用户手机", dataType = "String", dataTypeClass = String.class)
+ })
+ @PostMapping("/save")
+ public ResponseDTO save(UserEntity user) {
+ if (user == null || user.getUserId() == null) {
+ return ResponseDTO.fail("用户ID不能为空");
+ }
+ USER_ENTITY_MAP.put(user.getUserId(), user);
+ return ResponseDTO.ok();
+ }
+
+ @ApiOperation("更新用户")
+ @PutMapping("/update")
+ public ResponseDTO update(@RequestBody UserEntity user) {
+ if (user == null || user.getUserId() == null) {
+ return ResponseDTO.fail("用户ID不能为空");
+ }
+ if (USER_ENTITY_MAP.isEmpty() || !USER_ENTITY_MAP.containsKey(user.getUserId())) {
+ return ResponseDTO.fail("用户不存在");
+ }
+ USER_ENTITY_MAP.remove(user.getUserId());
+ USER_ENTITY_MAP.put(user.getUserId(), user);
+ return ResponseDTO.ok();
+ }
+
+ @ApiOperation("删除用户信息")
+ @ApiImplicitParam(name = "userId", value = "用户ID", required = true, dataType = "int", paramType = "path",
+ dataTypeClass = Integer.class)
+ @DeleteMapping("/{userId}")
+ public ResponseDTO delete(@PathVariable Integer userId) {
+ if (!USER_ENTITY_MAP.isEmpty() && USER_ENTITY_MAP.containsKey(userId)) {
+ USER_ENTITY_MAP.remove(userId);
+ return ResponseDTO.ok();
+ } else {
+ return ResponseDTO.fail("用户不存在");
+ }
+ }
+
+ @ApiModel(value = "UserEntity", description = "用户实体")
+ @Data
+ @AllArgsConstructor
+ @NoArgsConstructor
+ static class UserEntity {
+
+ @ApiModelProperty("用户ID")
+ private Integer userId;
+
+ @ApiModelProperty("用户名称")
+ private String username;
+
+ @ApiModelProperty("用户密码")
+ private String password;
+
+ @ApiModelProperty("用户手机")
+ private String mobile;
+ }
+}
+
diff --git a/agileboot-admin/src/main/java/com/agileboot/admin/request/LoginDTO.java b/agileboot-admin/src/main/java/com/agileboot/admin/request/LoginDTO.java
new file mode 100644
index 0000000..cb97f27
--- /dev/null
+++ b/agileboot-admin/src/main/java/com/agileboot/admin/request/LoginDTO.java
@@ -0,0 +1,33 @@
+package com.agileboot.admin.request;
+
+import lombok.Data;
+
+/**
+ * 用户登录对象
+ *
+ * @author valarchie
+ */
+@Data
+public class LoginDTO {
+
+ /**
+ * 用户名
+ */
+ private String username;
+
+ /**
+ * 用户密码
+ */
+ private String password;
+
+ /**
+ * 验证码
+ */
+ private String code;
+
+ /**
+ * 唯一标识
+ */
+ private String uuid;
+
+}
diff --git a/agileboot-admin/src/main/java/com/agileboot/admin/request/RegisterDTO.java b/agileboot-admin/src/main/java/com/agileboot/admin/request/RegisterDTO.java
new file mode 100644
index 0000000..4f5e359
--- /dev/null
+++ b/agileboot-admin/src/main/java/com/agileboot/admin/request/RegisterDTO.java
@@ -0,0 +1,27 @@
+package com.agileboot.admin.request;
+
+import com.agileboot.domain.system.user.RegisterUserModel;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * 用户注册对象
+ *
+ * @author valarchie
+ */
+@EqualsAndHashCode(callSuper = true)
+@Data
+public class RegisterDTO extends LoginDTO {
+
+ public RegisterUserModel toModel() {
+ RegisterUserModel model = new RegisterUserModel();
+
+ model.setCode(this.getCode());
+ model.setUuid(this.getUuid());
+ model.setUsername(this.getUsername());
+ model.setPassword(this.getPassword());
+
+ return model;
+ }
+
+}
diff --git a/agileboot-admin/src/main/java/com/agileboot/admin/response/UploadDTO.java b/agileboot-admin/src/main/java/com/agileboot/admin/response/UploadDTO.java
new file mode 100644
index 0000000..a4a6c92
--- /dev/null
+++ b/agileboot-admin/src/main/java/com/agileboot/admin/response/UploadDTO.java
@@ -0,0 +1,18 @@
+package com.agileboot.admin.response;
+
+import lombok.Builder;
+import lombok.Data;
+
+/**
+ * @author valarchie
+ */
+@Data
+@Builder
+public class UploadDTO {
+
+ private String url;
+ private String fileName;
+ private String newFileName;
+ private String originalFilename;
+
+}
diff --git a/agileboot-admin/src/main/java/com/agileboot/admin/response/UserPermissionDTO.java b/agileboot-admin/src/main/java/com/agileboot/admin/response/UserPermissionDTO.java
new file mode 100644
index 0000000..a3f3ee0
--- /dev/null
+++ b/agileboot-admin/src/main/java/com/agileboot/admin/response/UserPermissionDTO.java
@@ -0,0 +1,16 @@
+package com.agileboot.admin.response;
+
+import com.agileboot.domain.system.user.UserDTO;
+import java.util.Map;
+import java.util.Set;
+import lombok.Data;
+
+@Data
+public class UserPermissionDTO {
+
+ private UserDTO user;
+ private String roleKey;
+ private Set permissions;
+ private Map dictTypes;
+
+}
diff --git a/agileboot-api/pom.xml b/agileboot-api/pom.xml
new file mode 100644
index 0000000..4f17bfb
--- /dev/null
+++ b/agileboot-api/pom.xml
@@ -0,0 +1,40 @@
+
+
+
+ agileboot
+ com.agileboot
+ 1.0.0
+
+ 4.0.0
+
+ agileboot-api
+
+
+ quartz定时任务
+
+
+
+
+
+
+ org.quartz-scheduler
+ quartz
+
+
+ com.mchange
+ c3p0
+
+
+
+
+
+
+ com.agileboot
+ agileboot-common
+
+
+
+
+
diff --git a/agileboot-api/src/main/java/com/agileboot/api/controller/OrderController.java b/agileboot-api/src/main/java/com/agileboot/api/controller/OrderController.java
new file mode 100644
index 0000000..0864de2
--- /dev/null
+++ b/agileboot-api/src/main/java/com/agileboot/api/controller/OrderController.java
@@ -0,0 +1,16 @@
+package com.agileboot.api.controller;
+
+import com.agileboot.common.core.base.BaseController;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * 调度日志操作处理
+ *
+ * @author ruoyi
+ */
+@RestController
+@RequestMapping("/api/order")
+public class OrderController extends BaseController {
+
+}
diff --git a/agileboot-common/pom.xml b/agileboot-common/pom.xml
new file mode 100644
index 0000000..d4b0064
--- /dev/null
+++ b/agileboot-common/pom.xml
@@ -0,0 +1,166 @@
+
+
+
+ agileboot
+ com.agileboot
+ 1.0.0
+
+ 4.0.0
+
+ agileboot-common
+
+
+ common通用工具
+
+
+
+
+
+
+ org.springframework
+ spring-context-support
+
+
+
+
+ org.springframework
+ spring-web
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-security
+
+
+
+
+ com.github.pagehelper
+ pagehelper-spring-boot-starter
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-validation
+
+
+
+
+ org.apache.commons
+ commons-lang3
+
+
+
+
+ com.fasterxml.jackson.core
+ jackson-databind
+
+
+
+
+
+ commons-io
+ commons-io
+
+
+
+
+ commons-fileupload
+ commons-fileupload
+
+
+
+
+ org.apache.poi
+ poi-ooxml
+
+
+
+
+ org.yaml
+ snakeyaml
+
+
+
+
+ io.jsonwebtoken
+ jjwt
+
+
+
+
+ javax.xml.bind
+ jaxb-api
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-data-redis
+
+
+
+
+ org.apache.commons
+ commons-pool2
+
+
+
+
+ eu.bitwalker
+ UserAgentUtils
+
+
+
+
+ javax.servlet
+ javax.servlet-api
+
+
+
+
+ com.fasterxml.jackson.core
+ jackson-annotations
+
+
+ com.fasterxml.jackson.core
+ jackson-core
+
+
+ com.fasterxml.jackson.module
+ jackson-module-parameter-names
+
+
+ com.fasterxml.jackson.datatype
+ jackson-datatype-jdk8
+
+
+ com.fasterxml.jackson.datatype
+ jackson-datatype-jsr310
+
+
+
+
+ io.springfox
+ springfox-boot-starter
+
+
+
+ org.lionsoul
+ ip2region
+ 2.6.5
+
+
+
+
+ io.swagger
+ swagger-models
+ 1.6.2
+
+
+
+
+
diff --git a/agileboot-common/src/main/java/com/agileboot/common/annotation/ExcelColumn.java b/agileboot-common/src/main/java/com/agileboot/common/annotation/ExcelColumn.java
new file mode 100644
index 0000000..17f1a5c
--- /dev/null
+++ b/agileboot-common/src/main/java/com/agileboot/common/annotation/ExcelColumn.java
@@ -0,0 +1,19 @@
+package com.agileboot.common.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * 自定义导出Excel数据注解
+ *
+ * @author valarchie
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.FIELD)
+public @interface ExcelColumn {
+
+ String name() default "";
+
+}
diff --git a/agileboot-common/src/main/java/com/agileboot/common/annotation/ExcelSheet.java b/agileboot-common/src/main/java/com/agileboot/common/annotation/ExcelSheet.java
new file mode 100644
index 0000000..af3f00f
--- /dev/null
+++ b/agileboot-common/src/main/java/com/agileboot/common/annotation/ExcelSheet.java
@@ -0,0 +1,20 @@
+package com.agileboot.common.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * @author valarchie
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+public @interface ExcelSheet {
+
+ /**
+ * sheet名称
+ */
+ String name() default "";
+
+}
diff --git a/agileboot-common/src/main/java/com/agileboot/common/config/AgileBootConfig.java b/agileboot-common/src/main/java/com/agileboot/common/config/AgileBootConfig.java
new file mode 100644
index 0000000..757b4b5
--- /dev/null
+++ b/agileboot-common/src/main/java/com/agileboot/common/config/AgileBootConfig.java
@@ -0,0 +1,146 @@
+package com.agileboot.common.config;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+
+/**
+ * 读取项目相关配置
+ *
+ * @author ruoyi
+ */
+@Component
+@ConfigurationProperties(prefix = "agileboot")
+public class AgileBootConfig {
+
+ /**
+ * 项目名称
+ */
+ private String name;
+
+ /**
+ * 版本
+ */
+ private String version;
+
+ /**
+ * 版权年份
+ */
+ private String copyrightYear;
+
+ /**
+ * 实例演示开关
+ */
+ private boolean demoEnabled;
+
+ /**
+ * 上传路径
+ */
+ private static String profile;
+
+ /**
+ * 获取地址开关
+ */
+ private static boolean addressEnabled;
+
+ /**
+ * 验证码类型
+ */
+ private static String captchaType;
+
+ /**
+ * rsa private key 静态属性的注入!! set方法一定不能是static 方法
+ */
+ private static String rsaPrivateKey;
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getVersion() {
+ return version;
+ }
+
+ public void setVersion(String version) {
+ this.version = version;
+ }
+
+ public String getCopyrightYear() {
+ return copyrightYear;
+ }
+
+ public void setCopyrightYear(String copyrightYear) {
+ this.copyrightYear = copyrightYear;
+ }
+
+ public boolean isDemoEnabled() {
+ return demoEnabled;
+ }
+
+ public void setDemoEnabled(boolean demoEnabled) {
+ this.demoEnabled = demoEnabled;
+ }
+
+ public static String getProfile() {
+ return profile;
+ }
+
+ public void setProfile(String profile) {
+ AgileBootConfig.profile = profile;
+ }
+
+ public static boolean isAddressEnabled() {
+ return addressEnabled;
+ }
+
+ public void setAddressEnabled(boolean addressEnabled) {
+ AgileBootConfig.addressEnabled = addressEnabled;
+ }
+
+ public static String getCaptchaType() {
+ return captchaType;
+ }
+
+ public void setCaptchaType(String captchaType) {
+ AgileBootConfig.captchaType = captchaType;
+ }
+
+ public static String getRsaPrivateKey() {
+ return rsaPrivateKey;
+ }
+
+ public void setRsaPrivateKey(String rsaPrivateKey) {
+ AgileBootConfig.rsaPrivateKey = rsaPrivateKey;
+ }
+
+ /**
+ * 获取导入上传路径
+ */
+ public static String getImportPath() {
+ return getProfile() + "/import";
+ }
+
+ /**
+ * 获取头像上传路径
+ */
+ public static String getAvatarPath() {
+ return getProfile() + "/avatar";
+ }
+
+ /**
+ * 获取下载路径
+ */
+ public static String getDownloadPath() {
+ return getProfile() + "/download/";
+ }
+
+ /**
+ * 获取上传路径
+ */
+ public static String getUploadPath() {
+ return getProfile() + "/upload";
+ }
+}
diff --git a/agileboot-common/src/main/java/com/agileboot/common/constant/Constants.java b/agileboot-common/src/main/java/com/agileboot/common/constant/Constants.java
new file mode 100644
index 0000000..a88d95b
--- /dev/null
+++ b/agileboot-common/src/main/java/com/agileboot/common/constant/Constants.java
@@ -0,0 +1,66 @@
+package com.agileboot.common.constant;
+
+
+/**
+ * 通用常量信息
+ *
+ * @author valarchie
+ */
+public class Constants {
+
+ public static final int KB = 1024;
+
+ public static final int MB = KB * 1024;
+
+ public static final int GB = MB * 1024;
+
+ /**
+ * http请求
+ */
+ public static final String HTTP = "http://";
+
+ /**
+ * https请求
+ */
+ public static final String HTTPS = "https://";
+
+ /**
+ * 资源映射路径 前缀
+ */
+ public static final String RESOURCE_PREFIX = "/profile";
+
+
+ public static class Token {
+ /**
+ * 令牌
+ */
+ public static final String TOKEN_FIELD = "token";
+
+ /**
+ * 令牌前缀
+ */
+ public static final String TOKEN_PREFIX = "Bearer ";
+
+ /**
+ * 令牌前缀
+ */
+ public static final String LOGIN_USER_KEY = "login_user_key";
+
+ }
+
+ public static class Captcha {
+ /**
+ * 令牌
+ */
+ public static final String MATH_TYPE = "math";
+
+ /**
+ * 令牌前缀
+ */
+ public static final String CHAR_TYPE = "char";
+
+ }
+
+
+
+}
diff --git a/agileboot-common/src/main/java/com/agileboot/common/core/base/BaseController.java b/agileboot-common/src/main/java/com/agileboot/common/core/base/BaseController.java
new file mode 100644
index 0000000..9fa9953
--- /dev/null
+++ b/agileboot-common/src/main/java/com/agileboot/common/core/base/BaseController.java
@@ -0,0 +1,40 @@
+package com.agileboot.common.core.base;
+
+import cn.hutool.core.date.DateUtil;
+import cn.hutool.core.util.StrUtil;
+import java.beans.PropertyEditorSupport;
+import java.util.Date;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.WebDataBinder;
+import org.springframework.web.bind.annotation.InitBinder;
+
+/**
+ * @author valarchie
+ */
+@Slf4j
+public class BaseController {
+
+ /**
+ *
+ * 将前台传递过来的日期格式的字符串,自动转化为Date类型
+ */
+ @InitBinder
+ public void initBinder(WebDataBinder binder) {
+ // Date 类型转换
+ binder.registerCustomEditor(Date.class, new PropertyEditorSupport() {
+ @Override
+ public void setAsText(String text) {
+ setValue(DateUtil.parseDate(text));
+ }
+ });
+ }
+
+ /**
+ * 页面跳转
+ */
+ public String redirect(String url) {
+ return StrUtil.format("redirect:{}", url);
+ }
+
+
+}
diff --git a/agileboot-common/src/main/java/com/agileboot/common/core/base/BaseEntity.java b/agileboot-common/src/main/java/com/agileboot/common/core/base/BaseEntity.java
new file mode 100644
index 0000000..77f8557
--- /dev/null
+++ b/agileboot-common/src/main/java/com/agileboot/common/core/base/BaseEntity.java
@@ -0,0 +1,62 @@
+package com.agileboot.common.core.base;
+
+import com.baomidou.mybatisplus.annotation.FieldFill;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableLogic;
+import com.baomidou.mybatisplus.extension.activerecord.Model;
+import io.swagger.annotations.ApiModelProperty;
+import java.util.Date;
+import lombok.Data;
+
+/**
+ * Entity基类
+ *
+ * @author valarchie
+ */
+@Data
+public class BaseEntity> extends Model {
+
+ @ApiModelProperty("创建者ID")
+ @TableField("creator_id")
+ private Long creatorId;
+
+ @ApiModelProperty("创建者")
+ @TableField("creator_name")
+ private String creatorName;
+
+ @ApiModelProperty("创建时间")
+ @TableField(value = "create_time", fill = FieldFill.INSERT)
+ private Date createTime;
+
+ @ApiModelProperty("更新者ID")
+ @TableField("updater_id")
+ private Long updaterId;
+
+ @ApiModelProperty("更新者")
+ @TableField("updater_name")
+ private String updaterName;
+
+ @ApiModelProperty("更新时间")
+ @TableField(value = "update_time", fill = FieldFill.INSERT_UPDATE)
+ private Date updateTime;
+
+ @ApiModelProperty("删除标志(0代表存在 1代表删除)")
+ @TableField("deleted")
+ @TableLogic
+ private Boolean deleted;
+
+ public void logCreator(BaseUser user) {
+ if (user != null) {
+ this.creatorId = user.getUserId();
+ this.creatorName = user.getUsername();
+ }
+ }
+
+ public void logUpdater(BaseUser user) {
+ if (user != null) {
+ this.updaterId = user.getUserId();
+ this.updaterName = user.getUsername();
+ }
+ }
+
+}
diff --git a/agileboot-common/src/main/java/com/agileboot/common/core/base/BaseUser.java b/agileboot-common/src/main/java/com/agileboot/common/core/base/BaseUser.java
new file mode 100644
index 0000000..0aa5935
--- /dev/null
+++ b/agileboot-common/src/main/java/com/agileboot/common/core/base/BaseUser.java
@@ -0,0 +1,13 @@
+package com.agileboot.common.core.base;
+
+import lombok.Data;
+
+@Data
+public class BaseUser {
+
+ private Long userId;
+ private String username;
+ private Long deptId;
+ private Long roleId;
+
+}
diff --git a/agileboot-common/src/main/java/com/agileboot/common/core/dto/ResponseDTO.java b/agileboot-common/src/main/java/com/agileboot/common/core/dto/ResponseDTO.java
new file mode 100644
index 0000000..360cb32
--- /dev/null
+++ b/agileboot-common/src/main/java/com/agileboot/common/core/dto/ResponseDTO.java
@@ -0,0 +1,64 @@
+package com.agileboot.common.core.dto;
+
+import cn.hutool.core.util.StrUtil;
+import com.agileboot.common.exception.ApiException;
+import com.agileboot.common.exception.error.ErrorCode;
+import com.agileboot.common.exception.error.ErrorCodeInterface;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+
+/**
+ * 响应信息主体
+ *
+ * @author valarchie
+ */
+@Data
+@AllArgsConstructor
+public class ResponseDTO {
+
+ private Integer code;
+
+ private String msg;
+
+ private T data;
+
+ public static ResponseDTO ok() {
+ return build(null, ErrorCode.SUCCESS);
+ }
+
+ public static ResponseDTO ok(T data) {
+ return build(data, ErrorCode.SUCCESS);
+ }
+
+ public static ResponseDTO fail() {
+ return build(null, ErrorCode.FAIL);
+ }
+
+ public static ResponseDTO fail(ErrorCodeInterface code) {
+ return build(null, code);
+ }
+
+ public static ResponseDTO fail(ApiException exception) {
+ return new ResponseDTO<>(exception.getErrorCode().code(), exception.getMessage(), null);
+ }
+
+ public static ResponseDTO fail(ErrorCodeInterface code, Object... args) {
+ return build( code, args);
+ }
+
+ public static ResponseDTO fail(T data) { return build(ErrorCode.FAIL, data); }
+
+ public static ResponseDTO build(T data, ErrorCodeInterface code) {
+ return new ResponseDTO<>(code.code(), code.message(), data);
+ }
+
+ public static ResponseDTO build(ErrorCodeInterface code, Object... args) {
+ return new ResponseDTO<>(code.code(), StrUtil.format(code.message(), args), null);
+ }
+
+ public static ResponseDTO build(T data, ErrorCodeInterface code, Object... args) {
+ return new ResponseDTO<>(code.code(), StrUtil.format(code.message(), args), data);
+ }
+
+}
+
diff --git a/agileboot-common/src/main/java/com/agileboot/common/core/page/PageDTO.java b/agileboot-common/src/main/java/com/agileboot/common/core/page/PageDTO.java
new file mode 100644
index 0000000..72a083e
--- /dev/null
+++ b/agileboot-common/src/main/java/com/agileboot/common/core/page/PageDTO.java
@@ -0,0 +1,37 @@
+package com.agileboot.common.core.page;
+
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import java.util.List;
+import lombok.Data;
+
+/**
+ * @author valarchie
+ */
+@Data
+public class PageDTO {
+ /**
+ * 总记录数
+ */
+ private Long total;
+
+ /**
+ * 列表数据
+ */
+ private List> rows;
+
+ public PageDTO(List> list) {
+ this.rows = list;
+ this.total = (long) list.size();
+ }
+
+ public PageDTO(Page page) {
+ this.rows = page.getRecords();
+ this.total = page.getTotal();
+ }
+
+ public PageDTO(List> list, Long count) {
+ this.rows = list;
+ this.total = count;
+ }
+
+}
diff --git a/agileboot-common/src/main/java/com/agileboot/common/enums/DataSourceType.java b/agileboot-common/src/main/java/com/agileboot/common/enums/DataSourceType.java
new file mode 100644
index 0000000..8701608
--- /dev/null
+++ b/agileboot-common/src/main/java/com/agileboot/common/enums/DataSourceType.java
@@ -0,0 +1,18 @@
+package com.agileboot.common.enums;
+
+/**
+ * 数据源
+ *
+ * @author ruoyi
+ */
+public enum DataSourceType {
+ /**
+ * 主库
+ */
+ MASTER,
+
+ /**
+ * 从库
+ */
+ SLAVE
+}
diff --git a/agileboot-common/src/main/java/com/agileboot/common/enums/LimitType.java b/agileboot-common/src/main/java/com/agileboot/common/enums/LimitType.java
new file mode 100644
index 0000000..8f91948
--- /dev/null
+++ b/agileboot-common/src/main/java/com/agileboot/common/enums/LimitType.java
@@ -0,0 +1,19 @@
+package com.agileboot.common.enums;
+
+/**
+ * 限流类型
+ *
+ * @author ruoyi
+ */
+
+public enum LimitType {
+ /**
+ * 默认策略全局限流
+ */
+ DEFAULT,
+
+ /**
+ * 根据请求者IP进行限流
+ */
+ IP
+}
diff --git a/agileboot-common/src/main/java/com/agileboot/common/exception/ApiException.java b/agileboot-common/src/main/java/com/agileboot/common/exception/ApiException.java
new file mode 100644
index 0000000..cf7380c
--- /dev/null
+++ b/agileboot-common/src/main/java/com/agileboot/common/exception/ApiException.java
@@ -0,0 +1,76 @@
+package com.agileboot.common.exception;
+
+import cn.hutool.core.util.StrUtil;
+import com.agileboot.common.exception.error.ErrorCodeInterface;
+import com.agileboot.common.utils.i18n.MessageUtils;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * @author valarchie
+ */
+@Slf4j
+public class ApiException extends RuntimeException{
+
+
+ protected ErrorCodeInterface errorCode;
+
+ protected String message;
+
+ protected Object[] args;
+
+ protected String formattedMessage;
+ protected String i18nFormattedMessage;
+
+ public ApiException(Throwable e, ErrorCodeInterface errorCode, Object... args) {
+ super(e);
+ fillErrorCode(errorCode, args);
+ }
+
+ public ApiException(Throwable e, ErrorCodeInterface errorCode) {
+ super(e);
+ fillErrorCode(errorCode);
+ }
+
+ public ApiException(ErrorCodeInterface errorCode, Object... args) {
+ fillErrorCode(errorCode, args);
+ }
+
+ public ApiException(ErrorCodeInterface errorCode) {
+ fillErrorCode(errorCode);
+ }
+
+ private void fillErrorCode(ErrorCodeInterface errorCode, Object... args) {
+ this.errorCode = errorCode;
+ this.message = errorCode.message();
+ this.args = args;
+
+ this.formattedMessage = StrUtil.format(this.message, args);
+
+ try {
+ this.i18nFormattedMessage = MessageUtils.message(errorCode.i18n(), args);
+ } catch (Exception e) {
+ log.error("could not found i18n error i18nMessage entry : " + e.getMessage());
+ }
+
+ }
+
+
+ public ErrorCodeInterface getErrorCode() {
+ return errorCode;
+ }
+
+ public void setErrorCode(ErrorCodeInterface errorCode) {
+ this.errorCode = errorCode;
+ }
+
+ @Override
+ public String getMessage() {
+ return i18nFormattedMessage != null ? i18nFormattedMessage : formattedMessage;
+ }
+
+ @Override
+ public String getLocalizedMessage() {
+ return i18nFormattedMessage;
+ }
+
+}
diff --git a/agileboot-common/src/main/java/com/agileboot/common/exception/error/ErrorCode.java b/agileboot-common/src/main/java/com/agileboot/common/exception/error/ErrorCode.java
new file mode 100644
index 0000000..0e52497
--- /dev/null
+++ b/agileboot-common/src/main/java/com/agileboot/common/exception/error/ErrorCode.java
@@ -0,0 +1,322 @@
+package com.agileboot.common.exception.error;
+
+/**
+ * 常用错误码 以及 保留错误码
+ * @author valarchie
+ */
+public enum ErrorCode implements ErrorCodeInterface {
+
+ /**
+ * 错误码集合
+ * 1~9999 为保留错误码 或者 常用错误码
+ * 10000~19999 为内部错误码
+ * 20000~29999 客户端错误码 (客户端异常调用之类的错误)
+ * 30000~39999 为第三方错误码 (代码正常,但是第三方异常)
+ * 40000~49999 为业务逻辑 错误码 (无异常,代码正常流转,并返回提示给用户)
+ * 由于系统内的错误码都是独一无二的,所以错误码应该放在common包集中管理
+ */
+ // -------------- 普通错误码 及保留错误码 ---------------
+ SUCCESS(0,"操作成功"),
+ FAIL(9999, "操作失败"),
+
+
+ UNKNOWN_ERROR(99999,"未知错误");
+
+ private final int code;
+ private final String msg;
+
+
+ ErrorCode(int code, String msg) {
+ this.code = code;
+ this.msg = msg;
+ }
+
+ @Override
+ public int code() {
+ return this.code;
+ }
+
+ @Override
+ public String message() {
+ return this.msg;
+ }
+
+ /**
+ * 40000~49999 为业务逻辑 错误码 (无代码异常,代码正常流转,并返回提示给用户)
+ */
+ public enum Business implements ErrorCodeInterface{
+
+ // ----------------------------- Common -----------------------------------------
+
+ OBJECT_NOT_FOUND(Module.COMMON, 1, "找不到ID为%s 的%s"),
+
+ UNSUPPORTED_OPERATION(Module.COMMON, 2, "不支持的操作"),
+
+ BULK_DELETE_IDS_IS_INVALID(Module.COMMON, 3, "批量参数ID列表为空"),
+
+ // ----------------------------- Permission -----------------------------------------
+
+ FORBIDDEN_TO_MODIFY_ADMIN(Module.PERMISSION, 1, "不允许修改管理员的信息"),
+
+ NO_PERMISSION_TO_OPERATE(Module.PERMISSION, 2, "没有权限进行此操作,请联系管理员"),
+
+ // ----------------------------- Login -----------------------------------------
+
+ LOGIN_WRONG_USER_PASSWORD(Module.LOGIN, 1, "用户密码错误,请重输"),
+
+ LOGIN_ERROR(Module.LOGIN, 2, "登录失败:{}"),
+
+ CAPTCHA_CODE_WRONG(Module.LOGIN, 3, "验证码错误"),
+
+ CAPTCHA_CODE_EXPIRE(Module.LOGIN, 4, "验证码过期"),
+
+ CAPTCHA_CODE_NULL(Module.LOGIN, 5, "验证码为空"),
+
+
+// ----------------------------- Upload -----------------------------------------
+
+ UPLOAD_FILE_TYPE_NOT_ALLOWED(Module.UPLOAD, 1, "不允许上传的文件类型,仅允许:%s"),
+
+ UPLOAD_FILE_NAME_EXCEED_MAX_LENGTH(Module.UPLOAD, 2, "文件名长度超过:%s "),
+
+ UPLOAD_FILE_SIZE_EXCEED_MAX_SIZE(Module.UPLOAD, 3, "文件名大小超过:%s MB"),
+
+ UPLOAD_IMPORT_EXCEL_FAILED(Module.UPLOAD, 4, "导入excel失败:%s"),
+
+ UPLOAD_FILE_IS_EMPTY(Module.UPLOAD, 5, "上传文件为空"),
+
+ // ----------------------------- Config -----------------------------------------
+
+ CONFIG_VALUE_IS_NOT_ALLOW_TO_EMPTY(Module.CONFIG, 1, "参数键值不允许为空"),
+
+ CONFIG_VALUE_IS_NOT_IN_OPTIONS(Module.CONFIG, 2, "参数键值不存在列表中"),
+
+ // ----------------------------- Post -----------------------------------------
+
+ POST_NAME_IS_NOT_UNIQUE(Module.POST, 1, "岗位名称:%s, 已存在"),
+
+ POST_CODE_IS_NOT_UNIQUE(Module.POST, 2, "岗位编号:%s, 已存在"),
+
+ POST_ALREADY_ASSIGNED_TO_USER_CAN_NOT_BE_DELETED(Module.POST, 3, "职位已分配给用户,请先取消分配再删除"),
+
+ // ------------------------------- Dept ---------------------------------------------
+
+ DEPT_NAME_IS_NOT_UNIQUE(Module.DEPT, 1, "部门名称:%s, 已存在"),
+
+ DEPT_PARENT_ID_IS_NOT_ALLOWED_SELF(Module.DEPT, 2, "父级部门不能选择自己"),
+
+ DEPT_STATUS_ID_IS_NOT_ALLOWED_CHANGE(Module.DEPT, 3, "子部门还有正在启用的部门,暂时不能停用该部门"),
+
+ DEPT_EXIST_CHILD_DEPT_NOT_ALLOW_DELETE(Module.DEPT, 4, "该部门存在下级部门不允许删除"),
+
+ DEPT_EXIST_LINK_USER_NOT_ALLOW_DELETE(Module.DEPT, 5, "该部门存在关联的用户不允许删除"),
+
+ DEPT_PARENT_DEPT_NO_EXIST_OR_DISABLED(Module.DEPT, 6, "该父级部门不存在或已停用"),
+
+ // ------------------------------- Menu -------------------------------------------------------
+
+ MENU_NAME_IS_NOT_UNIQUE(Module.MENU, 1, "新增菜单:%s 失败,菜单名称已存在"),
+
+ MENU_EXTERNAL_LINK_MUST_BE_HTTP(Module.MENU, 2, "菜单外链必须以 http(s)://开头"),
+
+ MENU_PARENT_ID_NOT_ALLOW_SELF(Module.MENU, 3, "父级菜单不能选择自身"),
+
+ MENU_EXIST_CHILD_MENU_NOT_ALLOW_DELETE(Module.MENU, 4, "存在子菜单不允许删除"),
+
+ MENU_ALREADY_ASSIGN_TO_ROLE_NOT_ALLOW_DELETE(Module.MENU, 5, "菜单已分配给角色,不允许"),
+
+ // -------------------------------- Role ----------------------------------------------------
+
+ ROLE_NAME_IS_NOT_UNIQUE(Module.ROLE, 1, "角色名称:%s, 已存在"),
+
+ ROLE_KEY_IS_NOT_UNIQUE(Module.ROLE, 2, "角色标识:%s, 已存在"),
+
+ ROLE_DATA_SCOPE_DUPLICATED_DEPT(Module.ROLE, 3, "重复的部门id"),
+
+ ROLE_ALREADY_ASSIGN_TO_USER(Module.ROLE, 4, "角色已分配给用户,请先取消分配,再删除角色"),
+
+
+ // ------------------------ User ------------------------------
+
+
+ USER_NON_EXIST(Module.USER, 1, "登录用户:%s 不存在"),
+
+ USER_IS_DISABLE(Module.USER, 2, "对不起, 您的账号:{} 已停用"),
+
+ USER_CACHE_IS_EXPIRE(Module.USER, 3, "用户缓存信息已经过期"),
+
+ USER_FAIL_TO_GET_USER_ID(Module.USER, 3, "获取用户ID失败"),
+
+ USER_FAIL_TO_GET_DEPT_ID(Module.USER, 4, "获取用户部门ID失败"),
+
+ USER_FAIL_TO_GET_ACCOUNT(Module.USER, 5, "获取用户账户失败"),
+
+ USER_FAIL_TO_GET_USER_INFO(Module.USER, 6, "获取用户信息失败"),
+
+ USER_IMPORT_DATA_IS_NULL(Module.USER, 7, "导入的用户为空"),
+
+ USER_PHONE_NUMBER_IS_NOT_UNIQUE(Module.USER, 8, "该电话号码已被其他用户占用"),
+
+ USER_EMAIL_IS_NOT_UNIQUE(Module.USER, 9, "该邮件地址已被其他用户占用"),
+
+ USER_PASSWORD_IS_NOT_CORRECT(Module.USER, 10, "用户密码错误"),
+
+ USER_NEW_PASSWORD_IS_THE_SAME_AS_OLD(Module.USER, 11, "用户新密码与旧密码相同"),
+
+ USER_UPLOAD_FILE_FAILED(Module.USER, 12, "用户上传文件失败"),
+
+ USER_NAME_IS_NOT_UNIQUE(Module.USER, 13, "用户名已被其他用户占用"),
+
+ USER_CURRENT_USER_CAN_NOT_BE_DELETE(Module.USER, 14, "当前用户不允许被删除"),
+
+ ;
+
+
+ private final int code;
+ private final String msg;
+
+ private static final int BASE_CODE = 40000;
+
+ Business(Module module, int code, String msg) {
+ this.code = BASE_CODE + module.code() + code;
+ this.msg = msg;
+ }
+
+ @Override
+ public int code() {
+ return this.code;
+ }
+
+ @Override
+ public String message() {
+ return this.msg;
+ }
+
+ }
+
+
+ /**
+ * 30000~39999是外部错误码 比如调用支付失败
+ */
+ public enum External implements ErrorCodeInterface {
+
+ /**
+ * 支付宝调用失败
+ */
+ FAIL_TO_PAY_ON_ALIPAY(Module.COMMON, 1,"支付宝调用失败");
+
+
+ private final int code;
+ private final String msg;
+
+ private static final int BASE_CODE = 30000;
+
+ External(Module module, int code, String msg) {
+ this.code = BASE_CODE + module.code() + code;
+ this.msg = msg;
+ }
+
+ @Override
+ public int code() {
+ return this.code;
+ }
+
+ @Override
+ public String message() { return this.msg; }
+
+
+ }
+
+
+ /**
+ * 20000~29999是客户端错误码
+ */
+ public enum Client implements ErrorCodeInterface {
+
+
+ /**
+ * 客户端错误码
+ */
+ COMMON_FORBIDDEN_TO_CALL(Module.COMMON, 1,"禁止调用"),
+
+ COMMON_REQUEST_TO_OFTEN(Module.COMMON, 2, "调用太过频繁"),
+
+ COMMON_REQUEST_PARAMETERS_INVALID(Module.COMMON, 3, "请求参数异常,%s"),
+
+ COMMON_REQUEST_METHOD_INVALID(Module.COMMON, 4, "请求方式不支持"),
+
+ COMMON_NO_AUTHORIZATION(Module.PERMISSION, 1, "请求接口:%s 失败,用户未授权"),
+
+ ;
+
+
+ private final int code;
+ private final String msg;
+
+ private static final int BASE_CODE = 20000;
+
+ Client(Module module, int code, String msg) {
+ this.code = BASE_CODE + module.code() + code;
+ this.msg = msg;
+ }
+
+ @Override
+ public int code() {
+ return this.code;
+ }
+
+ @Override
+ public String message() {
+ return this.msg;
+ }
+
+ }
+
+
+ /**
+ * 10000~19999是内部错误码 例如 框架有问题之类的
+ */
+ public enum Internal implements ErrorCodeInterface {
+
+ /**
+ * 内部错误码
+ */
+ INVALID_PARAMETER(Module.COMMON, 1,"参数异常"),
+
+ UNKNOWN_ERROR(Module.COMMON, 2,"未知异常: %s"),
+
+ GET_ENUM_FAILED(Module.COMMON, 3,"获取枚举类型失败, 枚举类:%s"),
+
+ LOGIN_CAPTCHA_GENERATE_FAIL(Module.LOGIN, 1,"验证码生成失败"),
+
+ INVALID_TOKEN(Module.PERMISSION, 1,"token异常"),
+
+ DB_INTERNAL_ERROR(Module.DB, 1, "数据库异常:%s"),
+
+ ;
+
+
+ private final int code;
+ private final String msg;
+
+ private static final int BASE_CODE = 10000;
+
+ Internal(Module module, int code, String msg) {
+ this.code = BASE_CODE + module.code() + code;
+ this.msg = msg;
+ }
+
+ @Override
+ public int code() {
+ return this.code;
+ }
+
+ @Override
+ public String message() {
+ return this.msg;
+ }
+
+ }
+
+
+}
diff --git a/agileboot-common/src/main/java/com/agileboot/common/exception/error/ErrorCodeInterface.java b/agileboot-common/src/main/java/com/agileboot/common/exception/error/ErrorCodeInterface.java
new file mode 100644
index 0000000..4575f7d
--- /dev/null
+++ b/agileboot-common/src/main/java/com/agileboot/common/exception/error/ErrorCodeInterface.java
@@ -0,0 +1,15 @@
+package com.agileboot.common.exception.error;
+
+public interface ErrorCodeInterface {
+
+ String name();
+
+ int code();
+
+ String message();
+
+ default String i18n() {
+ return code() + "_" + name();
+ }
+
+}
diff --git a/agileboot-common/src/main/java/com/agileboot/common/exception/error/Module.java b/agileboot-common/src/main/java/com/agileboot/common/exception/error/Module.java
new file mode 100644
index 0000000..fcb1e30
--- /dev/null
+++ b/agileboot-common/src/main/java/com/agileboot/common/exception/error/Module.java
@@ -0,0 +1,46 @@
+package com.agileboot.common.exception.error;
+
+/**
+ * 系统内的模块
+ */
+public enum Module {
+
+ /**
+ * 普通模块
+ */
+ COMMON(0),
+
+ /**
+ * 权限模块
+ */
+ PERMISSION(1),
+
+ LOGIN(2),
+
+ DB(3),
+
+ UPLOAD(4),
+
+ USER(5),
+
+ CONFIG(6),
+
+ POST(7),
+
+ DEPT(8),
+
+ MENU(9),
+
+ ROLE(10),
+
+
+ ;
+
+
+ private final int code;
+
+ Module(int code) { this.code = code * 100; }
+
+ public int code() {return code; }
+
+}
diff --git a/agileboot-common/src/main/java/com/agileboot/common/utils/ServletHolderUtil.java b/agileboot-common/src/main/java/com/agileboot/common/utils/ServletHolderUtil.java
new file mode 100644
index 0000000..6081006
--- /dev/null
+++ b/agileboot-common/src/main/java/com/agileboot/common/utils/ServletHolderUtil.java
@@ -0,0 +1,71 @@
+package com.agileboot.common.utils;
+
+import cn.hutool.core.util.StrUtil;
+import java.io.IOException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import org.springframework.web.context.request.RequestAttributes;
+import org.springframework.web.context.request.RequestContextHolder;
+import org.springframework.web.context.request.ServletRequestAttributes;
+
+/**
+ * 客户端工具类
+ *
+ * @author ruoyi
+ */
+public class ServletHolderUtil {
+
+ /**
+ * 获取request
+ */
+ public static HttpServletRequest getRequest() {
+ return getRequestAttributes().getRequest();
+ }
+
+ /**
+ * 获取response
+ */
+ public static HttpServletResponse getResponse() {
+ return getRequestAttributes().getResponse();
+ }
+
+
+ public static ServletRequestAttributes getRequestAttributes() {
+ RequestAttributes attributes = RequestContextHolder.getRequestAttributes();
+ return (ServletRequestAttributes) attributes;
+ }
+
+ /**
+ * 将字符串渲染到客户端
+ *
+ * @param response 渲染对象
+ * @param string 待渲染的字符串
+ */
+ public static void renderString(HttpServletResponse response, String string) {
+ try {
+ response.setStatus(200);
+ response.setContentType("application/json");
+ response.setCharacterEncoding("utf-8");
+ response.getWriter().print(string);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * 获取仅含有项目根路径的url
+ * 比如 localhost:8080/agileboot/user/list
+ * 返回 localhost:8080/agileboot
+ * @return
+ */
+ public static String getContextUrl() {
+ HttpServletRequest request = getRequest();
+ StringBuffer url = request.getRequestURL();
+ String contextPath = request.getServletContext().getContextPath();
+ String strip = StrUtil.strip(url, null, request.getRequestURI());
+ return strip + contextPath;
+ }
+
+
+
+}
diff --git a/agileboot-common/src/main/java/com/agileboot/common/utils/file/FileUploadUtils.java b/agileboot-common/src/main/java/com/agileboot/common/utils/file/FileUploadUtils.java
new file mode 100644
index 0000000..b8940ad
--- /dev/null
+++ b/agileboot-common/src/main/java/com/agileboot/common/utils/file/FileUploadUtils.java
@@ -0,0 +1,216 @@
+package com.agileboot.common.utils.file;
+
+import cn.hutool.core.date.DatePattern;
+import cn.hutool.core.date.DateUtil;
+import cn.hutool.core.io.file.FileNameUtil;
+import cn.hutool.core.util.CharsetUtil;
+import cn.hutool.core.util.IdUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.core.util.URLUtil;
+import com.agileboot.common.config.AgileBootConfig;
+import com.agileboot.common.constant.Constants;
+import com.agileboot.common.exception.ApiException;
+import com.agileboot.common.exception.error.ErrorCode;
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Paths;
+import java.util.Objects;
+import javax.servlet.http.HttpServletResponse;
+import org.apache.commons.io.FilenameUtils;
+import org.springframework.http.HttpHeaders;
+import org.springframework.util.MimeType;
+import org.springframework.util.MimeTypeUtils;
+import org.springframework.web.multipart.MultipartFile;
+
+/**
+ * 文件上传工具类
+ *
+ * @author ruoyi 待改进
+ */
+public class FileUploadUtils {
+
+ /**
+ * 默认大小 50M
+ */
+ public static final long MAX_FILE_SIZE = 50 * Constants.MB;
+
+ /**
+ * 默认的文件名最大长度 100
+ */
+ public static final int MAX_FILE_NAME_LENGTH = 100;
+
+ public static final String[] ALLOWED_DOWNLOAD_EXTENSIONS = {
+ // 图片
+ "bmp", "gif", "jpg", "jpeg", "png",
+ // word excel powerpoint
+ "doc", "docx", "xls", "xlsx", "ppt", "pptx", "html", "htm", "txt",
+ // 压缩文件
+ "rar", "zip", "gz", "bz2",
+ // 视频格式
+ "mp4", "avi", "rmvb",
+ // pdf
+ "pdf"};
+
+ /**
+ * 默认上传的地址
+ */
+ private static String defaultBaseDir = AgileBootConfig.getProfile();
+
+
+ public static String getDefaultBaseDir() {
+ return defaultBaseDir;
+ }
+
+ /**
+ * 以默认配置进行文件上传
+ *
+ * @param file 上传的文件
+ * @return 文件名称
+ */
+ public static String upload(MultipartFile file) throws IOException {
+ try {
+ return upload(getDefaultBaseDir(), file, ALLOWED_DOWNLOAD_EXTENSIONS);
+ } catch (Exception e) {
+ throw new IOException(e.getMessage(), e);
+ }
+ }
+
+ /**
+ * 根据文件路径上传
+ *
+ * @param baseDir 相对应用的基目录
+ * @param file 上传的文件
+ * @return 文件名称
+ */
+ public static String upload(String baseDir, MultipartFile file) throws IOException {
+ try {
+ return upload(baseDir, file, ALLOWED_DOWNLOAD_EXTENSIONS);
+ } catch (Exception e) {
+ throw new IOException(e.getMessage(), e);
+ }
+ }
+
+ /**
+ * 文件上传
+ *
+ * @param baseDir 相对应用的基目录
+ * @param file 上传的文件
+ * @param allowedExtension 上传文件类型
+ * @return 返回上传成功的文件名
+ * @throws IOException 比如读写文件出错时
+ */
+ public static String upload(String baseDir, MultipartFile file, String[] allowedExtension)
+ throws IOException {
+
+ int fileNameLength = Objects.requireNonNull(file.getOriginalFilename()).length();
+ if (fileNameLength > FileUploadUtils.MAX_FILE_NAME_LENGTH) {
+ throw new ApiException(ErrorCode.Business.UPLOAD_FILE_NAME_EXCEED_MAX_LENGTH, MAX_FILE_NAME_LENGTH);
+ }
+
+ assertAllowed(file, allowedExtension);
+
+ String fileName = generateFilename(file);
+
+ String absPath = getAbsoluteFile(baseDir, fileName).getAbsolutePath();
+ file.transferTo(Paths.get(absPath));
+ return getPathFileName(baseDir, fileName);
+ }
+
+ /**
+ * 编码文件名
+ */
+ public static String generateFilename(MultipartFile file) {
+ return StrUtil.format("{}_{}_{}.{}", DateUtil.format(DateUtil.date(), DatePattern.PURE_DATETIME_PATTERN),
+ FilenameUtils.getBaseName(file.getOriginalFilename()), IdUtil.simpleUUID(), getExtension(file));
+ }
+
+ public static File getAbsoluteFile(String uploadDir, String fileName) {
+ File desc = new File(uploadDir + File.separator + fileName);
+ if (!desc.exists()) {
+ if (!desc.getParentFile().exists()) {
+ desc.getParentFile().mkdirs();
+ }
+ }
+ return desc;
+ }
+
+ public static String getPathFileName(String uploadDir, String fileName) {
+ String currentDir = StrUtil.strip(uploadDir, AgileBootConfig.getProfile() + "/");
+ return Constants.RESOURCE_PREFIX + "/" + currentDir + "/" + fileName;
+ }
+
+ /**
+ * 文件大小校验
+ *
+ * @param file 上传的文件
+ */
+ public static void assertAllowed(MultipartFile file, String[] allowedExtension) {
+ long size = file.getSize();
+ if (size > MAX_FILE_SIZE) {
+ throw new ApiException(ErrorCode.Business.UPLOAD_FILE_SIZE_EXCEED_MAX_SIZE, MAX_FILE_SIZE / Constants.MB);
+ }
+
+ String extension = getExtension(file);
+ if (allowedExtension != null && !isAllowedExtension(extension, allowedExtension)) {
+ throw new ApiException(ErrorCode.Business.UPLOAD_FILE_TYPE_NOT_ALLOWED,
+ StrUtil.join(",", (Object[]) allowedExtension));
+ }
+
+ }
+
+ /**
+ * 判断MIME类型是否是允许的MIME类型
+ */
+ public static boolean isAllowedExtension(String extension, String[] allowedExtension) {
+ return StrUtil.containsAnyIgnoreCase(extension, allowedExtension);
+ }
+
+ /**
+ * 获取文件名的后缀
+ *
+ * @param file 表单文件
+ * @return 后缀名
+ */
+ public static String getExtension(MultipartFile file) {
+ String extension = FilenameUtils.getExtension(file.getOriginalFilename());
+ if (StrUtil.isEmpty(extension)) {
+ MimeType mimeType = MimeTypeUtils.parseMimeType(Objects.requireNonNull(file.getContentType()));
+ extension = mimeType.getSubtype();
+ }
+ return extension;
+ }
+
+
+ /**
+ * 检查文件是否可下载
+ *
+ * @param resource 需要下载的文件
+ * @return true 正常 false 非法
+ */
+ public static boolean isAllowDownload(String resource) {
+ // 禁止目录上跳级别
+ return !StrUtil.contains(resource, "..") &&
+ // 检查允许下载的文件规则
+ StrUtil.containsAnyIgnoreCase(FileNameUtil.getSuffix(resource), ALLOWED_DOWNLOAD_EXTENSIONS);
+ }
+
+
+ /**
+ * 下载文件名重新编码
+ *
+ * @param response 响应对象
+ * @param realFileName 真实文件名
+ */
+ public static void setAttachmentResponseHeader(HttpServletResponse response, String realFileName) {
+ String fileNameUrlEncoded = URLUtil.encode(realFileName, CharsetUtil.CHARSET_UTF_8);
+
+ String contentDisposition = String.format("attachment; filename=%s;filename*=utf-8''%s", fileNameUrlEncoded,
+ fileNameUrlEncoded);
+
+ response.addHeader(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, "Content-Disposition,download-filename");
+ response.setHeader(HttpHeaders.CONTENT_DISPOSITION, contentDisposition);
+ response.setHeader("download-filename", fileNameUrlEncoded);
+ }
+
+
+}
diff --git a/agileboot-common/src/main/java/com/agileboot/common/utils/i18n/MessageUtils.java b/agileboot-common/src/main/java/com/agileboot/common/utils/i18n/MessageUtils.java
new file mode 100644
index 0000000..72bff6c
--- /dev/null
+++ b/agileboot-common/src/main/java/com/agileboot/common/utils/i18n/MessageUtils.java
@@ -0,0 +1,25 @@
+package com.agileboot.common.utils.i18n;
+
+import cn.hutool.extra.spring.SpringUtil;
+import org.springframework.context.MessageSource;
+import org.springframework.context.i18n.LocaleContextHolder;
+
+/**
+ * 获取i18n资源文件
+ *
+ * @author ruoyi
+ */
+public class MessageUtils {
+
+ /**
+ * 根据消息键和参数 获取消息 委托给spring messageSource
+ *
+ * @param code 消息键
+ * @param args 参数
+ * @return 获取国际化翻译值
+ */
+ public static String message(String code, Object... args) {
+ MessageSource messageSource = SpringUtil.getBean(MessageSource.class);
+ return messageSource.getMessage(code, args, LocaleContextHolder.getLocale());
+ }
+}
diff --git a/agileboot-common/src/main/java/com/agileboot/common/utils/ip/IpRegion.java b/agileboot-common/src/main/java/com/agileboot/common/utils/ip/IpRegion.java
new file mode 100644
index 0000000..8af5816
--- /dev/null
+++ b/agileboot-common/src/main/java/com/agileboot/common/utils/ip/IpRegion.java
@@ -0,0 +1,32 @@
+package com.agileboot.common.utils.ip;
+
+import cn.hutool.core.text.CharSequenceUtil;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.ToString;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+@ToString
+public class IpRegion {
+ private static final String UNKNOWN = "未知";
+ private String country;
+ private String region;
+ private String province;
+ private String city;
+ private String isp;
+
+ public IpRegion(String province, String city) {
+ this.province = province;
+ this.city = city;
+ }
+
+ public String briefLocation() {
+ return String.format("%s %s",
+ CharSequenceUtil.nullToDefault(province, UNKNOWN),
+ CharSequenceUtil.nullToDefault(city, UNKNOWN));
+ }
+
+}
diff --git a/agileboot-common/src/main/java/com/agileboot/common/utils/ip/IpRegionUtil.java b/agileboot-common/src/main/java/com/agileboot/common/utils/ip/IpRegionUtil.java
new file mode 100644
index 0000000..a42a2e3
--- /dev/null
+++ b/agileboot-common/src/main/java/com/agileboot/common/utils/ip/IpRegionUtil.java
@@ -0,0 +1,26 @@
+package com.agileboot.common.utils.ip;
+
+/**
+ * @author valarchie
+ */
+public class IpRegionUtil {
+
+ public static IpRegion getIpRegion(String ip) {
+ IpRegion ipRegionOffline = OfflineIpRegionUtil.getIpRegion(ip);
+ if (ipRegionOffline != null) {
+ return ipRegionOffline;
+ }
+
+ IpRegion ipRegionOnline = OnlineIpRegionUtil.getIpRegion(ip);
+ if (ipRegionOnline != null) {
+ return ipRegionOnline;
+ }
+
+ return new IpRegion();
+ }
+
+ public static String getBriefLocationByIp(String ip) {
+ return getIpRegion(ip).briefLocation();
+ }
+
+}
diff --git a/agileboot-common/src/main/java/com/agileboot/common/utils/ip/OfflineIpRegionUtil.java b/agileboot-common/src/main/java/com/agileboot/common/utils/ip/OfflineIpRegionUtil.java
new file mode 100644
index 0000000..6a515c2
--- /dev/null
+++ b/agileboot-common/src/main/java/com/agileboot/common/utils/ip/OfflineIpRegionUtil.java
@@ -0,0 +1,51 @@
+package com.agileboot.common.utils.ip;
+
+import cn.hutool.core.util.StrUtil;
+import java.io.IOException;
+import java.io.InputStream;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.io.IOUtils;
+import org.lionsoul.ip2region.xdb.Searcher;
+
+@Slf4j
+public class OfflineIpRegionUtil {
+
+ private static Searcher searcher;
+
+ static {
+ InputStream resourceAsStream = OfflineIpRegionUtil.class.getResourceAsStream("/ip2region.xdb");
+
+ byte[] bytes = null;
+ try {
+ bytes = new byte[resourceAsStream.available()];
+ IOUtils.read(resourceAsStream, bytes);
+ } catch (IOException e) {
+ log.error("读取本地Ip文件失败", e);
+ }
+
+ try {
+ searcher = Searcher.newWithBuffer(bytes);
+ } catch (Exception e) {
+ log.error("构建本地Ip缓存失败", e);
+ }
+
+ }
+
+ public static IpRegion getIpRegion(String ip) {
+ try {
+
+ String rawRegion = searcher.search(ip);
+ if (StrUtil.isNotEmpty(rawRegion)) {
+ String[] split = rawRegion.split("\\|");
+ return new IpRegion(split[0], split[1], split[2], split[3], split[4]);
+ }
+
+ return null;
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+
+}
diff --git a/agileboot-common/src/main/java/com/agileboot/common/utils/ip/OnlineIpRegionUtil.java b/agileboot-common/src/main/java/com/agileboot/common/utils/ip/OnlineIpRegionUtil.java
new file mode 100644
index 0000000..89b5739
--- /dev/null
+++ b/agileboot-common/src/main/java/com/agileboot/common/utils/ip/OnlineIpRegionUtil.java
@@ -0,0 +1,49 @@
+package com.agileboot.common.utils.ip;
+
+import cn.hutool.core.net.NetUtil;
+import cn.hutool.core.util.CharsetUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.http.HttpUtil;
+import com.agileboot.common.config.AgileBootConfig;
+import com.agileboot.common.utils.jackson.JacksonUtil;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * query geography address from ip
+ *
+ * @author valarchie
+ */
+@Slf4j
+public class OnlineIpRegionUtil {
+
+ /**
+ * website for query geography address from ip
+ */
+ public static final String ADDRESS_QUERY_SITE = "http://whois.pconline.com.cn/ipJson.jsp";
+
+
+ public static IpRegion getIpRegion(String ip) {
+ // no need to query address for inner ip
+ if (NetUtil.isInnerIP(ip)) {
+ return new IpRegion("internal", "IP");
+ }
+ if (AgileBootConfig.isAddressEnabled()) {
+ try {
+ String rspStr = HttpUtil.get(ADDRESS_QUERY_SITE + "ip=" + ip + "&json=true",
+ CharsetUtil.CHARSET_GBK);
+ if (StrUtil.isEmpty(rspStr)) {
+ log.error("获取地理位置异常 {}", ip);
+ return null;
+ }
+
+ String province = JacksonUtil.getAsString(rspStr, "pro");
+ String city = JacksonUtil.getAsString(rspStr, "city");
+ return new IpRegion(province, city);
+ } catch (Exception e) {
+ log.error("获取地理位置异常 {}", ip);
+ }
+ }
+ return null;
+ }
+
+}
diff --git a/agileboot-common/src/main/java/com/agileboot/common/utils/jackson/JacksonException.java b/agileboot-common/src/main/java/com/agileboot/common/utils/jackson/JacksonException.java
new file mode 100644
index 0000000..0f43c90
--- /dev/null
+++ b/agileboot-common/src/main/java/com/agileboot/common/utils/jackson/JacksonException.java
@@ -0,0 +1,23 @@
+package com.agileboot.common.utils.jackson;
+
+/**
+ * @author valarchie
+ */
+public class JacksonException extends RuntimeException {
+ public JacksonException() {
+ super();
+ }
+
+ public JacksonException(String message) {
+ super(message);
+ }
+
+ public JacksonException(String message, Exception e) {
+ super(message, e);
+ }
+
+ public JacksonException(Throwable cause) {
+ super(cause);
+ }
+
+}
diff --git a/agileboot-common/src/main/java/com/agileboot/common/utils/jackson/JacksonUtil.java b/agileboot-common/src/main/java/com/agileboot/common/utils/jackson/JacksonUtil.java
new file mode 100644
index 0000000..49e23f5
--- /dev/null
+++ b/agileboot-common/src/main/java/com/agileboot/common/utils/jackson/JacksonUtil.java
@@ -0,0 +1,679 @@
+package com.agileboot.common.utils.jackson;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.convert.Convert;
+import cn.hutool.core.util.StrUtil;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.core.json.JsonReadFeature;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.JavaType;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.SerializationFeature;
+import com.fasterxml.jackson.databind.json.JsonMapper;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.fasterxml.jackson.databind.type.CollectionType;
+import com.fasterxml.jackson.databind.type.MapType;
+import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
+import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
+import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
+import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
+import com.fasterxml.jackson.module.paramnames.ParameterNamesModule;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Writer;
+import java.lang.reflect.Type;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.net.URL;
+import java.text.SimpleDateFormat;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.BooleanUtils;
+import org.apache.commons.lang3.StringUtils;
+
+/**
+ * Jackson工具类 优势: 数据量高于百万的时候,速度和FastJson相差极小 API和注解支持最完善,可定制性最强
+ * 支持的数据源最广泛(字符串,对象,文件、流、URL)
+ * @author valarchie
+ */
+@Slf4j
+public class JacksonUtil {
+
+ private static ObjectMapper mapper;
+
+ private static final Set JSON_READ_FEATURES_ENABLED = CollUtil.newHashSet(
+ //允许在JSON中使用Java注释
+ JsonReadFeature.ALLOW_JAVA_COMMENTS,
+ //允许 json 存在没用双引号括起来的 field
+ JsonReadFeature.ALLOW_UNQUOTED_FIELD_NAMES,
+ //允许 json 存在使用单引号括起来的 field
+ JsonReadFeature.ALLOW_SINGLE_QUOTES,
+ //允许 json 存在没用引号括起来的 ascii 控制字符
+ JsonReadFeature.ALLOW_UNESCAPED_CONTROL_CHARS,
+ //允许 json number 类型的数存在前导 0 (例: 0001)
+ JsonReadFeature.ALLOW_LEADING_ZEROS_FOR_NUMBERS,
+ //允许 json 存在 NaN, INF, -INF 作为 number 类型
+ JsonReadFeature.ALLOW_NON_NUMERIC_NUMBERS,
+ //允许 只有Key没有Value的情况
+ JsonReadFeature.ALLOW_MISSING_VALUES,
+ //允许数组json的结尾多逗号
+ JsonReadFeature.ALLOW_TRAILING_COMMA
+ );
+
+ static {
+ try {
+ //初始化
+ mapper = initMapper();
+ } catch (Exception e) {
+ log.error("jackson config error", e);
+ }
+ }
+
+ public static ObjectMapper initMapper() {
+ JsonMapper.Builder builder = JsonMapper.builder()
+ .enable(JSON_READ_FEATURES_ENABLED.toArray(new JsonReadFeature[0]));
+ return initMapperConfig(builder.build());
+ }
+
+ public static ObjectMapper initMapperConfig(ObjectMapper objectMapper) {
+ String dateTimeFormat = "yyyy-MM-dd HH:mm:ss";
+ objectMapper.setDateFormat(new SimpleDateFormat(dateTimeFormat));
+ //配置序列化级别
+ objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
+ //配置JSON缩进支持
+ objectMapper.configure(SerializationFeature.INDENT_OUTPUT, false);
+ //允许单个数值当做数组处理
+ objectMapper.enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY);
+ //禁止重复键, 抛出异常
+ objectMapper.enable(DeserializationFeature.FAIL_ON_READING_DUP_TREE_KEY);
+ //禁止使用int代表Enum的order()來反序列化Enum, 抛出异常
+ objectMapper.enable(DeserializationFeature.FAIL_ON_NUMBERS_FOR_ENUMS);
+ //有属性不能映射的时候不报错
+ objectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
+ //对象为空时不抛异常
+ objectMapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
+ //时间格式
+ objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
+ //允许未知字段
+ objectMapper.enable(JsonGenerator.Feature.IGNORE_UNKNOWN);
+ //序列化BigDecimal时之间输出原始数字还是科学计数, 默认false, 即是否以toPlainString()科学计数方式来输出
+ objectMapper.enable(JsonGenerator.Feature.WRITE_BIGDECIMAL_AS_PLAIN);
+ //识别Java8时间
+ objectMapper.registerModule(new ParameterNamesModule());
+ objectMapper.registerModule(new Jdk8Module());
+ JavaTimeModule javaTimeModule = new JavaTimeModule();
+ javaTimeModule.addSerializer(LocalDateTime.class,
+ new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(dateTimeFormat)))
+ .addDeserializer(LocalDateTime.class,
+ new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(dateTimeFormat)));
+ objectMapper.registerModule(javaTimeModule);
+ // if we use guava, we can add this line of code: objectMapper.registerModule(new GuavaModule())
+ return objectMapper;
+ }
+
+ public static ObjectMapper getObjectMapper() {
+ return mapper;
+ }
+
+ /**
+ * JSON反序列化
+ */
+ public static V from(URL url, Class type) {
+ try {
+ return mapper.readValue(url, type);
+ } catch (IOException e) {
+ throw new JacksonException(StrUtil.format("jackson from error, url: {}, type: {}", url.getPath(), type), e);
+ }
+ }
+
+ /**
+ * JSON反序列化
+ */
+ public static V from(URL url, TypeReference type) {
+ try {
+ return mapper.readValue(url, type);
+ } catch (IOException e) {
+ throw new JacksonException(StrUtil.format("jackson from error, url: {}, type: {}", url.getPath(), type), e);
+ }
+ }
+
+ /**
+ * JSON反序列化(List)
+ */
+ public static List fromList(URL url, Class type) {
+ try {
+ CollectionType collectionType = mapper.getTypeFactory().constructCollectionType(ArrayList.class, type);
+ return mapper.readValue(url, collectionType);
+ } catch (IOException e) {
+ throw new JacksonException(StrUtil.format("jackson from error, url: {}, type: {}", url.getPath(), type), e);
+ }
+ }
+
+ /**
+ * JSON反序列化
+ */
+ public static V from(InputStream inputStream, Class type) {
+ try {
+ return mapper.readValue(inputStream, type);
+ } catch (IOException e) {
+ throw new JacksonException(StrUtil.format("jackson from error, type: {}", type), e);
+ }
+ }
+
+ /**
+ * JSON反序列化
+ */
+ public static V from(InputStream inputStream, TypeReference type) {
+ try {
+ return mapper.readValue(inputStream, type);
+ } catch (IOException e) {
+ throw new JacksonException(StrUtil.format("jackson from error, type: {}", type), e);
+ }
+ }
+
+ /**
+ * JSON反序列化(List)
+ */
+ public static List fromList(InputStream inputStream, Class type) {
+ try {
+ CollectionType collectionType = mapper.getTypeFactory().constructCollectionType(ArrayList.class, type);
+ return mapper.readValue(inputStream, collectionType);
+ } catch (IOException e) {
+ throw new JacksonException(StrUtil.format("jackson from error, type: {}", type), e);
+ }
+ }
+
+ /**
+ * JSON反序列化
+ */
+ public static V from(File file, Class type) {
+ try {
+ return mapper.readValue(file, type);
+ } catch (IOException e) {
+ throw new JacksonException(
+ StrUtil.format("jackson from error, file path: {}, type: {}", file.getPath(), type), e);
+ }
+ }
+
+ /**
+ * JSON反序列化
+ */
+ public static V from(File file, TypeReference type) {
+ try {
+ return mapper.readValue(file, type);
+ } catch (IOException e) {
+ throw new JacksonException(
+ StrUtil.format("jackson from error, file path: {}, type: {}", file.getPath(), type), e);
+ }
+ }
+
+ /**
+ * JSON反序列化(List)
+ */
+ public static List fromList(File file, Class type) {
+ try {
+ CollectionType collectionType = mapper.getTypeFactory().constructCollectionType(ArrayList.class, type);
+ return mapper.readValue(file, collectionType);
+ } catch (IOException e) {
+ throw new JacksonException(
+ StrUtil.format("jackson from error, file path: {}, type: {}", file.getPath(), type), e);
+ }
+ }
+
+ /**
+ * JSON反序列化
+ */
+ public static V from(String json, Class type) {
+ return from(json, (Type) type);
+ }
+
+ /**
+ * JSON反序列化
+ */
+ public static V from(String json, TypeReference type) {
+ return from(json, type.getType());
+ }
+
+ /**
+ * JSON反序列化
+ */
+ public static V from(String json, Type type) {
+ if (StringUtils.isEmpty(json)) {
+ return null;
+ }
+ try {
+ JavaType javaType = mapper.getTypeFactory().constructType(type);
+ return mapper.readValue(json, javaType);
+ } catch (IOException e) {
+ throw new JacksonException(StrUtil.format("jackson from error, json: {}, type: {}", json, type), e);
+ }
+ }
+
+ /**
+ * JSON反序列化(List)
+ */
+ public static List fromList(String json, Class type) {
+ if (StringUtils.isEmpty(json)) {
+ return null;
+ }
+ try {
+ CollectionType collectionType = mapper.getTypeFactory().constructCollectionType(ArrayList.class, type);
+ return mapper.readValue(json, collectionType);
+ } catch (IOException e) {
+ throw new JacksonException(StrUtil.format("jackson from error, json: {}, type: {}", json, type), e);
+ }
+ }
+
+ /**
+ * JSON反序列化(Map)
+ */
+ public static Map fromMap(String json) {
+ if (StringUtils.isEmpty(json)) {
+ return null;
+ }
+ try {
+ MapType mapType = mapper.getTypeFactory().constructMapType(HashMap.class, String.class, Object.class);
+ return mapper.readValue(json, mapType);
+ } catch (IOException e) {
+ throw new JacksonException(StrUtil.format("jackson from error, json: {}, type: {}", json), e);
+ }
+ }
+
+ /**
+ * 序列化为JSON
+ */
+ public static String to(List list) {
+ try {
+ return mapper.writeValueAsString(list);
+ } catch (JsonProcessingException e) {
+ throw new JacksonException(StrUtil.format("jackson to error, data: {}", list), e);
+ }
+ }
+
+ /**
+ * 序列化为JSON
+ */
+ public static String to(V v) {
+ try {
+ return mapper.writeValueAsString(v);
+ } catch (JsonProcessingException e) {
+ throw new JacksonException(StrUtil.format("jackson to error, data: {}", v), e);
+ }
+ }
+
+ /**
+ * 序列化为JSON
+ */
+ public static void toFile(String path, List list) {
+ try (Writer writer = new FileWriter(new File(path), true)) {
+ mapper.writer().writeValues(writer).writeAll(list);
+ } catch (Exception e) {
+ throw new JacksonException(StrUtil.format("jackson to file error, path: {}, list: {}", path, list), e);
+ }
+ }
+
+ /**
+ * 序列化为JSON
+ */
+ public static void toFile(String path, V v) {
+ try (Writer writer = new FileWriter(new File(path), true)) {
+ mapper.writer().writeValues(writer).write(v);
+ } catch (Exception e) {
+ throw new JacksonException(StrUtil.format("jackson to file error, path: {}, data: {}", path, v), e);
+ }
+ }
+
+ /**
+ * 从json串中获取某个字段
+ *
+ * @return String,默认为 null
+ */
+ public static String getAsString(String json, String key) {
+ if (StringUtils.isEmpty(json)) {
+ return null;
+ }
+ try {
+ JsonNode jsonNode = getAsJsonObject(json, key);
+ if (null == jsonNode) {
+ return null;
+ }
+ return getAsString(jsonNode);
+ } catch (Exception e) {
+ throw new JacksonException(StrUtil.format("jackson get string error, json: {}, key: {}", json, key), e);
+ }
+ }
+
+ private static String getAsString(JsonNode jsonNode) {
+ return jsonNode.isTextual() ? jsonNode.textValue() : jsonNode.toString();
+ }
+
+ /**
+ * 从json串中获取某个字段
+ *
+ * @return int,默认为 0
+ */
+ public static int getAsInt(String json, String key) {
+ if (StringUtils.isEmpty(json)) {
+ return 0;
+ }
+ try {
+ JsonNode jsonNode = getAsJsonObject(json, key);
+ if (null == jsonNode) {
+ return 0;
+ }
+ return jsonNode.isInt() ? jsonNode.intValue() : Integer.parseInt(getAsString(jsonNode));
+ } catch (Exception e) {
+ throw new JacksonException(StrUtil.format("jackson get int error, json: {}, key: {}", json, key), e);
+ }
+ }
+
+ /**
+ * 从json串中获取某个字段
+ *
+ * @return long,默认为 0
+ */
+ public static long getAsLong(String json, String key) {
+ if (StringUtils.isEmpty(json)) {
+ return 0L;
+ }
+ try {
+ JsonNode jsonNode = getAsJsonObject(json, key);
+ if (null == jsonNode) {
+ return 0L;
+ }
+ return jsonNode.isLong() ? jsonNode.longValue() : Long.parseLong(getAsString(jsonNode));
+ } catch (Exception e) {
+ throw new JacksonException(StrUtil.format("jackson get long error, json: {}, key: {}", json, key), e);
+ }
+ }
+
+ /**
+ * 从json串中获取某个字段
+ *
+ * @return double,默认为 0.0
+ */
+ public static double getAsDouble(String json, String key) {
+ if (StringUtils.isEmpty(json)) {
+ return 0.0;
+ }
+ try {
+ JsonNode jsonNode = getAsJsonObject(json, key);
+ if (null == jsonNode) {
+ return 0.0;
+ }
+ return jsonNode.isDouble() ? jsonNode.doubleValue() : Double.parseDouble(getAsString(jsonNode));
+ } catch (Exception e) {
+ throw new JacksonException(StrUtil.format("jackson get double error, json: {}, key: {}", json, key), e);
+ }
+ }
+
+ /**
+ * 从json串中获取某个字段
+ *
+ * @return BigInteger,默认为 0.0
+ */
+ public static BigInteger getAsBigInteger(String json, String key) {
+ if (StringUtils.isEmpty(json)) {
+ return new BigInteger(String.valueOf(0.00));
+ }
+ try {
+ JsonNode jsonNode = getAsJsonObject(json, key);
+ if (null == jsonNode) {
+ return new BigInteger(String.valueOf(0.00));
+ }
+ return jsonNode.isBigInteger() ? jsonNode.bigIntegerValue() : new BigInteger(getAsString(jsonNode));
+ } catch (Exception e) {
+ throw new JacksonException(StrUtil.format("jackson get big integer error, json: {}, key: {}", json, key),
+ e);
+ }
+ }
+
+ /**
+ * 从json串中获取某个字段
+ *
+ * @return BigDecimal,默认为 0.00
+ */
+ public static BigDecimal getAsBigDecimal(String json, String key) {
+ if (StringUtils.isEmpty(json)) {
+ return new BigDecimal("0.00");
+ }
+ try {
+ JsonNode jsonNode = getAsJsonObject(json, key);
+ if (null == jsonNode) {
+ return new BigDecimal("0.00");
+ }
+ return jsonNode.isBigDecimal() ? jsonNode.decimalValue() : new BigDecimal(getAsString(jsonNode));
+ } catch (Exception e) {
+ throw new JacksonException(StrUtil.format("jackson get big decimal error, json: {}, key: {}", json, key),
+ e);
+ }
+ }
+
+ /**
+ * 从json串中获取某个字段
+ *
+ * @return boolean, 默认为false
+ */
+ public static boolean getAsBoolean(String json, String key) {
+ if (StringUtils.isEmpty(json)) {
+ return false;
+ }
+ try {
+ JsonNode jsonNode = getAsJsonObject(json, key);
+ if (null == jsonNode) {
+ return false;
+ }
+ if (jsonNode.isBoolean()) {
+ return jsonNode.booleanValue();
+ } else {
+ if (jsonNode.isTextual()) {
+ String textValue = jsonNode.textValue();
+ return Convert.toBool(textValue);
+ } else {//number
+ return BooleanUtils.toBoolean(jsonNode.intValue());
+ }
+ }
+ } catch (Exception e) {
+ throw new JacksonException(StrUtil.format("jackson get boolean error, json: {}, key: {}", json, key), e);
+ }
+ }
+
+ /**
+ * 从json串中获取某个字段
+ *
+ * @return byte[], 默认为 null
+ */
+ public static byte[] getAsBytes(String json, String key) {
+ if (StringUtils.isEmpty(json)) {
+ return null;
+ }
+ try {
+ JsonNode jsonNode = getAsJsonObject(json, key);
+ if (null == jsonNode) {
+ return null;
+ }
+ return jsonNode.isBinary() ? jsonNode.binaryValue() : getAsString(jsonNode).getBytes();
+ } catch (Exception e) {
+ throw new JacksonException(StrUtil.format("jackson get byte error, json: {}, key: {}", json, key), e);
+ }
+ }
+
+ /**
+ * 从json串中获取某个字段
+ *
+ * @return object, 默认为 null
+ */
+ public static V getAsObject(String json, String key, Class type) {
+ if (StringUtils.isEmpty(json)) {
+ return null;
+ }
+ try {
+ JsonNode jsonNode = getAsJsonObject(json, key);
+ if (null == jsonNode) {
+ return null;
+ }
+ JavaType javaType = mapper.getTypeFactory().constructType(type);
+ return from(getAsString(jsonNode), javaType);
+ } catch (Exception e) {
+ throw new JacksonException(
+ StrUtil.format("jackson get list error, json: {}, key: {}, type: {}", json, key, type), e);
+ }
+ }
+
+
+ /**
+ * 从json串中获取某个字段
+ *
+ * @return list, 默认为 null
+ */
+ public static List getAsList(String json, String key, Class type) {
+ if (StringUtils.isEmpty(json)) {
+ return null;
+ }
+ try {
+ JsonNode jsonNode = getAsJsonObject(json, key);
+ if (null == jsonNode) {
+ return null;
+ }
+ CollectionType collectionType = mapper.getTypeFactory().constructCollectionType(ArrayList.class, type);
+ return from(getAsString(jsonNode), collectionType);
+ } catch (Exception e) {
+ throw new JacksonException(
+ StrUtil.format("jackson get list error, json: {}, key: {}, type: {}", json, key, type), e);
+ }
+ }
+
+ /**
+ * 从json串中获取某个字段
+ *
+ * @return JsonNode, 默认为 null
+ */
+ public static JsonNode getAsJsonObject(String json, String key) {
+ try {
+ JsonNode node = mapper.readTree(json);
+ if (null == node) {
+ return null;
+ }
+ return node.get(key);
+ } catch (IOException e) {
+ throw new JacksonException(
+ StrUtil.format("jackson get object from json error, json: {}, key: {}", json, key), e);
+ }
+ }
+
+ /**
+ * 向json中添加属性
+ *
+ * @return json
+ */
+ public static String add(String json, String key, V value) {
+ try {
+ JsonNode node = mapper.readTree(json);
+ add(node, key, value);
+ return node.toString();
+ } catch (IOException e) {
+ throw new JacksonException(
+ StrUtil.format("jackson add error, json: {}, key: {}, value: {}", json, key, value), e);
+ }
+ }
+
+ /**
+ * 向json中添加属性
+ */
+ private static void add(JsonNode jsonNode, String key, V value) {
+ if (value instanceof String) {
+ ((ObjectNode) jsonNode).put(key, (String) value);
+ } else if (value instanceof Short) {
+ ((ObjectNode) jsonNode).put(key, (Short) value);
+ } else if (value instanceof Integer) {
+ ((ObjectNode) jsonNode).put(key, (Integer) value);
+ } else if (value instanceof Long) {
+ ((ObjectNode) jsonNode).put(key, (Long) value);
+ } else if (value instanceof Float) {
+ ((ObjectNode) jsonNode).put(key, (Float) value);
+ } else if (value instanceof Double) {
+ ((ObjectNode) jsonNode).put(key, (Double) value);
+ } else if (value instanceof BigDecimal) {
+ ((ObjectNode) jsonNode).put(key, (BigDecimal) value);
+ } else if (value instanceof BigInteger) {
+ ((ObjectNode) jsonNode).put(key, (BigInteger) value);
+ } else if (value instanceof Boolean) {
+ ((ObjectNode) jsonNode).put(key, (Boolean) value);
+ } else if (value instanceof byte[]) {
+ ((ObjectNode) jsonNode).put(key, (byte[]) value);
+ } else {
+ ((ObjectNode) jsonNode).put(key, to(value));
+ }
+ }
+
+ /**
+ * 除去json中的某个属性
+ *
+ * @return json
+ */
+ public static String remove(String json, String key) {
+ try {
+ JsonNode node = mapper.readTree(json);
+ ((ObjectNode) node).remove(key);
+ return node.toString();
+ } catch (IOException e) {
+ throw new JacksonException(StrUtil.format("jackson remove error, json: {}, key: {}", json, key), e);
+ }
+ }
+
+ /**
+ * 修改json中的属性
+ */
+ public static String update(String json, String key, V value) {
+ try {
+ JsonNode node = mapper.readTree(json);
+ ((ObjectNode) node).remove(key);
+ add(node, key, value);
+ return node.toString();
+ } catch (IOException e) {
+ throw new JacksonException(
+ StrUtil.format("jackson update error, json: {}, key: {}, value: {}", json, key, value), e);
+ }
+ }
+
+ /**
+ * 格式化Json(美化)
+ *
+ * @return json
+ */
+ public static String format(String json) {
+ try {
+ JsonNode node = mapper.readTree(json);
+ return mapper.writerWithDefaultPrettyPrinter().writeValueAsString(node);
+ } catch (IOException e) {
+ throw new JacksonException(StrUtil.format("jackson format json error, json: {}", json), e);
+ }
+ }
+
+ /**
+ * 判断字符串是否是json
+ *
+ * @return json
+ */
+ public static boolean isJson(String json) {
+ try {
+ mapper.readTree(json);
+ return true;
+ } catch (Exception e) {
+ return false;
+ }
+ }
+
+}
diff --git a/agileboot-common/src/main/java/com/agileboot/common/utils/poi/CustomExcelUtil.java b/agileboot-common/src/main/java/com/agileboot/common/utils/poi/CustomExcelUtil.java
new file mode 100644
index 0000000..da12ecc
--- /dev/null
+++ b/agileboot-common/src/main/java/com/agileboot/common/utils/poi/CustomExcelUtil.java
@@ -0,0 +1,86 @@
+package com.agileboot.common.utils.poi;
+
+import cn.hutool.poi.excel.ExcelReader;
+import cn.hutool.poi.excel.ExcelUtil;
+import cn.hutool.poi.excel.ExcelWriter;
+import com.agileboot.common.annotation.ExcelColumn;
+import com.agileboot.common.annotation.ExcelSheet;
+import java.io.IOException;
+import java.lang.reflect.Field;
+import java.util.List;
+import javax.servlet.http.HttpServletResponse;
+import org.springframework.web.multipart.MultipartFile;
+
+/**
+ * @author valarchie
+ */
+public class CustomExcelUtil {
+
+
+ public static void writeToResponse(List> list, Class clazz, HttpServletResponse response) {
+
+ // 通过工具类创建writer
+ ExcelWriter writer = ExcelUtil.getWriter();
+
+ ExcelSheet sheetAnno = (ExcelSheet)clazz.getAnnotation(ExcelSheet.class);
+
+ if (sheetAnno != null) {
+ // 默认的sheetName是 sheet1
+ writer.renameSheet(sheetAnno.name());
+ }
+
+ Field[] fields = clazz.getDeclaredFields();
+
+ //自定义标题别名
+ for (Field field : fields) {
+ ExcelColumn annotation = field.getAnnotation(ExcelColumn.class);
+ if (annotation != null) {
+ writer.addHeaderAlias(field.getName(), annotation.name());
+ }
+ }
+
+ // 默认的,未添加alias的属性也会写出,如果想只写出加了别名的字段,可以调用此方法排除之
+ writer.setOnlyAlias(true);
+
+ // 合并单元格后的标题行,使用默认标题样式
+ // writer.merge(4, "一班成绩单");
+ // 一次性写出内容,使用默认样式,强制输出标题
+ writer.write(list, true);
+
+ try {
+ writer.flush(response.getOutputStream(), true);
+ } catch (IOException e) {
+ writer.close();
+ e.printStackTrace();
+ }
+
+ }
+
+ public static List> readFromResponse(Class clazz, MultipartFile file) {
+
+ ExcelReader reader = null;
+ try {
+ reader = ExcelUtil.getReader(file.getInputStream());
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+
+
+ Field[] fields = clazz.getDeclaredFields();
+
+ //自定义标题别名
+ if (fields != null) {
+ for (Field field : fields) {
+ ExcelColumn annotation = field.getAnnotation(ExcelColumn.class);
+ if (annotation != null) {
+ reader.addHeaderAlias(annotation.name(), field.getName());
+ }
+ }
+ }
+
+ return reader.read(0, 1, clazz);
+ }
+
+
+
+}
diff --git a/agileboot-common/src/main/java/com/agileboot/common/utils/time/DatePicker.java b/agileboot-common/src/main/java/com/agileboot/common/utils/time/DatePicker.java
new file mode 100644
index 0000000..6c25e2d
--- /dev/null
+++ b/agileboot-common/src/main/java/com/agileboot/common/utils/time/DatePicker.java
@@ -0,0 +1,42 @@
+package com.agileboot.common.utils.time;
+
+import cn.hutool.core.date.DateTime;
+import cn.hutool.core.date.DateUtil;
+import java.util.Date;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * @author valarchie
+ */
+@Slf4j
+public class DatePicker {
+
+ public static Date getBeginOfTheDay(Object date) {
+ if (date == null) {
+ return null;
+ }
+ try {
+ DateTime parse = DateUtil.parse(date.toString());
+ return DateUtil.beginOfDay(parse);
+ } catch (Exception e) {
+ log.error("pick begin of day failed, due to: ", e);
+ }
+ return null;
+ }
+
+ public static Date getEndOfTheDay(Object date) {
+ if (date == null) {
+ return null;
+ }
+ try {
+ DateTime parse = DateUtil.parse(date.toString());
+ return DateUtil.endOfDay(parse);
+ } catch (Exception e) {
+ log.error("pick end of day failed, due to: ", e);
+ }
+ return null;
+ }
+
+
+
+}
diff --git a/agileboot-common/src/main/resources/ip2region.xdb b/agileboot-common/src/main/resources/ip2region.xdb
new file mode 100644
index 0000000000000000000000000000000000000000..31f96a1fb1695b14c86a73a0cc14fa6c600263c1
GIT binary patch
literal 11065998
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%0DYY6Z4R)1E
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