From 81b9ac5923bbe3d5ddd84a799f0bd209018622dd Mon Sep 17 00:00:00 2001 From: zhaojun <873019219@qq.com> Date: Thu, 3 Apr 2025 21:33:53 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BB=8E=E6=8D=90=E8=B5=A0=E7=89=88=E5=90=88?= =?UTF-8?q?=E5=B9=B6=E9=83=A8=E5=88=86=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 224 +- .../core/handlers/MybatisEnumTypeHandler.java | 173 - .../zfile/core/annotation/ApiLimit.java | 33 + .../zfile/core/annotation/DemoDisable.java | 17 + .../zfile/core/aspect/ApiLimitAspect.java | 74 + .../CommonResultControllerAdvice.java | 12 +- .../zfile/core/aspect/DemoDisableAspect.java | 45 + .../core/config/FlywayDbInitializer.java | 1 - .../core/config/Knife4jConfiguration.java | 1 - .../zfile/core/config/MyBatisPlusConfig.java | 66 - .../zfile/core/config/RestTemplateConfig.java | 27 - .../zfile/core/config/SaTokenConfigure.java | 1 - .../zfile/core/config/ZFileProperties.java | 18 + .../DataSourceBeanPostProcessor.java | 123 + .../config/docs/Knife4jConfiguration.java | 73 + .../jackson/JSONStringDeserializer.java | 25 + .../config/jackson/JSONStringSerializer.java | 22 + .../mybatis/CollectionIntegerTypeHandler.java | 7 + .../mybatis/CollectionStrTypeHandler.java | 7 + .../config/mybatis/CollectionTypeHandler.java | 121 + .../config/mybatis/MyBatisPlusConfig.java | 33 + .../{ => mybatis}/MyDatabaseIdProvider.java | 11 +- .../config/mybatis/MyMetaObjectHandler.java | 29 + .../SaSessionForJacksonCustomized.java | 32 + .../config/security/SaTokenConfigure.java | 34 + .../security/SaTokenDaoRedisJackson.java | 305 ++ .../config/security/StpInterfaceImpl.java | 44 + .../{ => spring}/JacksonEnumDeserializer.java | 7 +- .../{ => spring}/SpringCacheConfig.java | 9 +- .../StringToEnumConverterFactory.java | 9 +- .../config/{ => spring}/WebMvcConfig.java | 43 +- .../config/totp/TotpAutoConfiguration.java | 91 + .../core/config/totp/TotpProperties.java | 85 + .../zfile/core/constant/RuleTypeConstant.java | 18 + .../zfile/core/constant/ZFileConstant.java | 4 - .../constant/ZFileHttpHeaderConstant.java | 16 + .../core/controller/FrontIndexController.java | 85 +- .../zfile/core/controller/LogController.java | 10 +- .../zfile/core/exception/ErrorCode.java | 135 + .../exception/GlobalExceptionHandler.java | 403 +++ .../IllegalDownloadLinkException.java | 14 - .../exception/InstallSystemException.java | 14 - .../exception/InvalidShortLinkException.java | 14 - .../core/exception/LoginVerifyException.java | 14 - .../exception/PasswordVerifyException.java | 22 - .../core/exception/PreviewException.java | 14 - .../core/exception/ServiceException.java | 42 - .../StorageSourceAutoConfigCorsException.java | 21 - .../exception/StorageSourceException.java | 60 - ...eSourceNotSupportProxyUploadException.java | 14 - .../StorageSourceRefreshTokenException.java | 22 - .../core/exception/TextParseException.java | 17 - .../core/exception/ZFileRetryException.java | 26 - .../core/exception/ZFileRuntimeException.java | 15 - .../biz/APIHttpRequestBizException.java | 31 + .../core/exception/biz/CorsBizException.java | 21 + .../biz/FilePathSecurityBizException.java | 26 + .../GetPreviewTextContentBizException.java | 27 + .../InitializeStorageSourceBizException.java | 29 + .../biz/InvalidStorageSourceBizException.java | 31 + ...SourceFileForbiddenAccessBizException.java | 27 + ...ageSourceIllegalOperationBizException.java | 28 + .../core/exception/core/BizException.java | 101 + .../exception/core/ErrorPageBizException.java | 103 + .../core/exception/core/SystemException.java | 94 + .../file/InvalidStorageSourceException.java | 25 - .../InitializeStorageSourceException.java | 21 - .../DisableProxyDownloadException.java | 24 - .../StorageSourceFileOperatorException.java | 18 - .../handler/GlobalExceptionHandler.java | 1 - .../HttpResponseStatusErrorException.java | 12 - .../status/BadRequestAccessException.java | 18 + .../status/ForbiddenAccessException.java | 19 + .../MethodNotAllowedAccessException.java | 19 + .../status/NotFoundAccessException.java | 19 + .../status/UnauthorizedAccessException.java | 18 + .../system/UploadFileFailSystemException.java | 39 + .../ZFileAuthorizationSystemException.java | 28 + .../zhaojun/zfile/core/filter/CorsFilter.java | 28 +- .../zhaojun/zfile/core/filter/MDCFilter.java | 22 +- .../zfile/core/filter/SecurityFilter.java | 80 + .../ZFileOkHttp3ClientHttpRequestFactory.java | 22 - .../logging/HttpLoggingInterceptor.java | 340 -- ...nsureContentLengthInputStreamResource.java | 66 + .../zfile/core/io/ThrottledInputStream.java | 91 + .../zfile/core/io/ThrottledOutputStream.java | 57 + .../core/model/request/PageQueryRequest.java | 12 +- .../service/DynamicControllerManager.java | 43 - .../im/zhaojun/zfile/core/util/AjaxJson.java | 108 +- .../zhaojun/zfile/core/util/ArrayUtils.java | 40 + .../im/zhaojun/zfile/core/util/CharPool.java | 10 + .../zfile/core/util/CharSequenceUtil.java | 727 ++++ .../zhaojun/zfile/core/util/ClassUtils.java | 14 + .../im/zhaojun/zfile/core/util/CodeMsg.java | 76 - .../zfile/core/util/CollectionUtils.java | 131 + .../im/zhaojun/zfile/core/util/DnsUtil.java | 52 + .../zfile/core/util/FileComparator.java | 23 +- .../zfile/core/util/FileResponseUtil.java | 16 +- .../im/zhaojun/zfile/core/util/FileUtils.java | 46 + .../im/zhaojun/zfile/core/util/HttpUtil.java | 12 +- .../zhaojun/zfile/core/util/NumberUtils.java | 18 + .../core/util/OnlyOfficeKeyCacheUtils.java | 98 + .../zfile/core/util/PatternMatcherUtils.java | 15 +- .../zfile/core/util/PlaceholderUtils.java | 43 +- .../core/util/ProxyDownloadUrlUtils.java | 33 +- .../zfile/core/util/RequestHolder.java | 169 +- .../zhaojun/zfile/core/util/RequestUtils.java | 18 + .../utils => core/util}/SpringMvcUtils.java | 7 +- .../im/zhaojun/zfile/core/util/StrPool.java | 98 + .../zhaojun/zfile/core/util/StringUtils.java | 213 +- .../im/zhaojun/zfile/core/util/UrlUtils.java | 86 +- .../zfile/core/util/ZFileAuthUtil.java | 38 + .../util/matcher/AbstractRuleMatcher.java | 43 + .../zfile/core/util/matcher/IRuleMatcher.java | 75 + .../core/util/matcher/RuleMatcherFactory.java | 42 + .../util/matcher/impl/AntPathRuleMatcher.java | 32 + .../core/util/matcher/impl/IpRuleMatcher.java | 272 ++ .../util/matcher/impl/RegexRuleMatcher.java | 39 + .../matcher/impl/SpringSimpleRuleMatcher.java | 35 + .../core/validation/StringListValue.java | 4 +- .../StringListValueConstraintValidator.java | 8 +- .../controller/IpHelperController.java | 15 +- .../controller/RuleMatcherTestController.java | 50 + .../model/request/TestRuleMatcherRequest.java | 19 + .../config/annotation/JSONStringParse.java | 17 + .../config/constant/SystemConfigConstant.java | 3 +- .../AntPathMatcherHelperController.java | 40 - .../config/controller/InstallController.java | 61 - .../config/controller/SettingController.java | 94 +- .../config/controller/SiteController.java | 56 +- .../event/DirectLinkPrefixModifyHandler.java | 24 +- ...va => SystemConfigModifyHandlerChain.java} | 13 +- .../config/model/dto/LinkExpireDTO.java | 18 + .../config/model/dto/SystemConfigDTO.java | 207 +- .../config/model/entity/SystemConfig.java | 13 +- .../model/request/InstallSystemRequest.java | 28 - .../request/TestAntPathMatcherRequest.java | 12 - .../request/UpdateAccessSettingRequest.java | 21 + .../request/UpdateLinkSettingRequest.java | 36 +- .../request/UpdateSecuritySettingRequest.java | 24 +- .../request/UpdateSiteSettingRequest.java | 40 +- .../UpdateUserNameAndPasswordRequest.java | 26 +- .../request/UpdateViewSettingRequest.java | 59 +- .../model/request/UpdateWebDAVRequest.java | 26 - .../model/result/FrontSiteConfigResult.java | 144 + .../config/model/result/SiteConfigResult.java | 125 - .../config/service/SystemConfigService.java | 308 +- .../StorageSourceFilterController.java | 31 +- .../filter/model/entity/FilterConfig.java | 16 +- .../filter/service/FilterConfigService.java | 136 +- .../install/controller/InstallController.java | 42 + .../model/request/InstallSystemRequest.java | 24 + .../install/service/InstallService.java | 52 + .../link/aspect/LinkRateLimiterAspect.java | 33 +- .../link/aspect/RefererCheckAspect.java | 38 +- .../link/cache/LinkRateLimiterCache.java | 55 + .../link/controller/DirectLinkController.java | 41 +- .../link/controller/ShortLinkController.java | 51 +- .../ShortLinkManagerController.java | 195 +- .../module/link/convert/ShortLinkConvert.java | 3 +- .../dto/DynamicRegisterMappingHandlerDTO.java | 19 + .../link/event/DeleteExpireLinkEvent.java | 7 + .../module/link/mapper/ShortLinkMapper.java | 33 +- .../module/link/model/dto/CacheInfo.java | 18 + .../module/link/model/entity/ShortLink.java | 28 +- .../request/BatchGenerateLinkRequest.java | 10 +- .../request/QueryDownloadLogRequest.java | 48 +- .../model/request/QueryLoginLogRequest.java | 61 + .../request/QueryShortLinkLogRequest.java | 38 +- .../link/model/request/ShortLinkResult.java | 24 +- .../model/request/ShortLinkSearchRequest.java | 25 +- .../result/BatchGenerateLinkResponse.java | 8 +- .../DynamicDirectLinkPrefixService.java | 45 + .../link/service/LinkDownloadService.java | 121 +- .../module/link/service/ShortLinkService.java | 99 +- .../DownloadLogManagerController.java | 87 +- .../log/controller/LoginLogController.java | 64 + .../module/log/mapper/DownloadLogMapper.java | 10 +- .../module/log/mapper/LoginLogMapper.java | 9 + .../log/model/dto/DownloadTopFileDTO.java | 26 - .../log/model/dto/DownloadTopIpDTO.java | 19 - .../log/model/dto/DownloadTopRefererDTO.java | 19 - .../module/log/model/entity/DownloadLog.java | 30 +- .../module/log/model/entity/LoginLog.java | 39 + .../model/request/DownloadTopInfoRequest.java | 23 - .../log/model/result/DownloadLogResult.java | 22 +- .../log/service/DownloadLogService.java | 53 +- .../module/log/service/LoginLogService.java | 24 + .../login/controller/LoginController.java | 1 - ...ifyLoginTwoFactorAuthenticatorRequest.java | 28 - .../LoginTwoFactorAuthenticatorResult.java | 24 - .../model/result/LoginVerifyImgResult.java | 22 - .../login/request/UserLoginRequest.java | 1 - .../controller/OnlyOfficeController.java | 222 ++ .../onlyoffice/model/OnlyOfficeCallback.java | 92 + .../onlyoffice/model/OnlyOfficeFile.java | 14 + .../StorageSourcePasswordController.java | 31 +- .../password/model/dto/VerifyResultDTO.java | 21 +- .../password/model/entity/PasswordConfig.java | 16 +- .../service/PasswordConfigService.java | 120 +- .../controller/PermissionController.java | 37 + .../StorageSourcePermissionController.java | 56 + .../convert/PermissionConfigConvert.java | 22 + .../mapper/PermissionConfigMapper.java | 39 + .../model/entity/PermissionConfig.java | 62 + .../model/result/PermissionConfigResult.java | 37 + .../model/result/PermissionInfoResult.java | 18 + .../service/PermissionConfigService.java | 105 + .../StorageSourceReadmeController.java | 31 +- .../readme/model/entity/ReadmeConfig.java | 18 +- .../readme/service/ReadmeConfigService.java | 116 +- .../storage/annotation/CheckPassword.java | 6 +- .../storage/annotation/CheckPasswords.java | 14 + .../module/storage/annotation/ProCheck.java | 16 + .../storage/annotation/StorageParamItem.java | 25 +- .../annotation/StorageParamSelectOption.java | 2 +- .../annotation/StoragePermissionCheck.java | 24 + .../storage/aspect/CheckPasswordAspect.java | 82 +- .../aspect/FileOperatorCheckAspect.java | 140 +- .../FileOperatorExceptionWrapperAspect.java | 143 - .../zfile/module/storage/chain/FileChain.java | 14 +- .../module/storage/chain/FileContext.java | 6 + .../FileAccessPermissionVerifyCommand.java | 9 +- .../FileDownloadPermissionCommand.java | 47 + .../chain/command/FileHiddenCommand.java | 6 +- .../command/FileUrlAddVersionCommand.java | 45 - .../command/FolderPasswordVerifyCommand.java | 6 +- .../constant/S3SignerTypeConstant.java | 24 + .../constant/StorageConfigConstant.java | 4 + .../StorageSourceConnectionProperties.java | 15 + .../storage/context/StorageSourceContext.java | 181 +- .../context/StorageSourceInitializer.java | 61 + .../base/StorageMetaDataController.java | 10 +- .../base/StorageSourceController.java | 81 +- .../GoogleDriveCallbackController.java | 119 +- .../callback/OneDriveCallbackController.java | 114 +- .../controller/file/FileController.java | 128 +- .../file/FileOperatorController.java | 256 +- .../helper/GoogleDriveHelperController.java | 10 +- .../controller/helper/S3HelperController.java | 131 +- .../helper/SharePointHelperController.java | 124 +- .../proxy/ProxyDownloadController.java | 57 +- .../proxy/ProxyUploadController.java | 31 +- .../storage/convert/StorageSourceConvert.java | 4 +- .../enums/StorageParamItemAnnoEnum.java | 21 + .../storage/event/StorageSourceCopyEvent.java | 20 + .../event/StorageSourceDeleteEvent.java | 28 + ...nFileOperatorTypeEnumDefaultValueFunc.java | 27 + ...lFileOperatorTypeEnumDefaultValueFunc.java | 27 + ...cFileOperatorTypeEnumDefaultValueFunc.java | 50 + ...lFileOperatorTypeEnumDefaultValueFunc.java | 27 + ...kFileOperatorTypeEnumDefaultValueFunc.java | 40 + ...hFileOperatorTypeEnumDefaultValueFunc.java | 27 + ...kFileOperatorTypeEnumDefaultValueFunc.java | 39 + .../storage/mapper/StorageSourceMapper.java | 2 +- .../model/bo/StorageSourceMetadata.java | 61 + .../model/bo/StorageSourceParamDef.java | 22 +- .../storage/model/dto/OAuth2TokenDTO.java | 2 + .../model/dto/StorageSourceAllParamDTO.java | 123 +- .../storage/model/dto/StorageSourceDTO.java | 45 +- .../model/dto/StorageSourceInitDTO.java | 39 + .../storage/model/dto/ZFileCORSRule.java | 93 + .../storage/model/entity/StorageSource.java | 46 +- .../model/entity/StorageSourceConfig.java | 17 +- .../model/enums/FileOperatorTypeEnum.java | 150 +- .../model/enums/SearchFolderModeEnum.java | 36 + .../storage/model/enums/StorageTypeEnum.java | 6 +- .../storage/model/param/AliyunParam.java | 1 + .../storage/model/param/DogeCloudParam.java | 25 +- .../module/storage/model/param/FtpParam.java | 38 +- .../storage/model/param/GoogleDriveParam.java | 2 +- .../storage/model/param/LocalParam.java | 5 +- .../model/param/MicrosoftDriveParam.java | 25 +- .../storage/model/param/MinIOParam.java | 18 +- .../param/OptionalProxyTransferParam.java | 26 + .../model/param/ProxyDownloadParam.java | 13 - .../model/param/ProxyTransferParam.java | 13 +- .../storage/model/param/ProxyUploadParam.java | 10 - .../storage/model/param/S3BaseParam.java | 38 +- .../module/storage/model/param/S3Param.java | 7 +- .../module/storage/model/param/SftpParam.java | 30 +- .../storage/model/param/TencentParam.java | 10 +- .../storage/model/param/UpYunParam.java | 20 +- .../storage/model/param/WebdavParam.java | 22 +- .../request/GetGoogleDriveListRequest.java | 9 +- .../model/request/GetS3BucketListRequest.java | 15 +- .../model/request/GetS3CorsListRequest.java | 34 + .../model/request/SharePointInfoRequest.java | 17 +- .../request/SharePointSearchSitesRequest.java | 6 +- .../request/SharePointSiteListsRequest.java | 8 +- .../admin/CopyStorageSourceRequest.java | 15 +- .../request/admin/UpdateStorageIdRequest.java | 11 +- .../admin/UpdateStorageSortRequest.java | 11 +- .../model/request/base/FileItemRequest.java | 16 +- .../request/base/FileListConfigRequest.java | 14 +- .../model/request/base/FileListRequest.java | 19 +- .../base/SaveStorageSourceRequest.java | 39 +- .../request/base/SearchStorageRequest.java | 64 + .../request/operator/BatchDeleteRequest.java | 21 +- .../operator/BatchMoveOrCopyFileRequest.java | 46 + .../request/operator/CopyFileRequest.java | 40 - .../request/operator/CopyFolderRequest.java | 40 - .../request/operator/MoveFileRequest.java | 40 - .../request/operator/MoveFolderRequest.java | 40 - .../request/operator/NewFolderRequest.java | 16 +- .../request/operator/RenameFileRequest.java | 18 +- .../request/operator/RenameFolderRequest.java | 18 +- .../request/operator/UploadFileRequest.java | 18 +- .../storage/model/result/FileInfoResult.java | 9 +- .../storage/model/result/FileItemResult.java | 17 +- .../model/result/GoogleDriveInfoResult.java | 9 +- .../model/result/S3BucketNameResult.java | 9 +- .../result/SharepointSiteListResult.java | 13 +- .../model/result/SharepointSiteResult.java | 11 +- .../result/StorageSourceAdminResult.java | 39 +- .../result/StorageSourceConfigResult.java | 24 +- .../model/result/StorageSourceResult.java | 15 +- .../result/operator/BatchOperatorResult.java | 38 + .../AbstractMicrosoftOAuth2Service.java | 85 + .../service/GoogleDriveOAuth2ServiceImpl.java | 98 + .../oauth2/service/IOAuth2Service.java | 22 + .../OneDriveChinaOAuth2ServiceImpl.java | 38 + .../service/OneDriveOAuth2ServiceImpl.java | 38 + .../schedule/AccessTokenRefreshSchedule.java | 7 +- .../service/StorageSourceConfigService.java | 117 +- .../storage/service/StorageSourceService.java | 319 +- .../service/base/AbstractBaseFileService.java | 56 +- .../base/AbstractMicrosoftDriveService.java | 436 ++- .../base/AbstractOneDriveServiceBase.java | 5 - .../base/AbstractProxyDownloadService.java | 21 - .../base/AbstractProxyTransferService.java | 98 +- .../base/AbstractProxyUploadService.java | 22 - .../base/AbstractS3BaseFileService.java | 414 ++- .../base/AbstractSharePointServiceBase.java | 5 - .../storage/service/base/BaseFileService.java | 7 + .../service/impl/AliyunServiceImpl.java | 160 +- .../service/impl/DogeCloudServiceImpl.java | 74 +- .../storage/service/impl/FtpServiceImpl.java | 318 +- .../service/impl/GoogleDriveServiceImpl.java | 200 +- .../service/impl/HuaweiServiceImpl.java | 40 +- .../service/impl/LocalServiceImpl.java | 187 +- .../service/impl/MinIOServiceImpl.java | 51 +- .../impl/OneDriveChinaServiceImpl.java | 27 +- .../service/impl/OneDriveServiceImpl.java | 29 +- .../service/impl/QiniuServiceImpl.java | 55 +- .../storage/service/impl/S3ServiceImpl.java | 52 +- .../storage/service/impl/SftpServiceImpl.java | 274 +- .../impl/SharePointChinaServiceImpl.java | 27 +- .../service/impl/SharePointServiceImpl.java | 27 +- .../service/impl/TencentServiceImpl.java | 46 +- .../service/impl/UpYunServiceImpl.java | 187 +- .../service/impl/WebdavServiceImpl.java | 130 +- .../storage/support/StorageSourceSupport.java | 143 +- .../storage/support/ftp/FtpClientFactory.java | 71 + .../storage/support/ftp/FtpClientPool.java | 17 + .../support/sftp/SFtpClientFactory.java | 63 + .../storage/support/sftp/SFtpClientPool.java | 17 + .../module/user/aspect/LoginLogAspect.java | 82 + .../user/controller/AdminTwoFAController.java | 50 + .../user/controller/UserController.java | 162 + .../controller/UserManagerController.java | 113 + .../module/user/event/UserCopyEvent.java | 22 + .../module/user/event/UserDeleteEvent.java | 23 + .../module/user/manager/UserManager.java | 129 + .../zfile/module/user/mapper/UserMapper.java | 17 + .../user/mapper/UserStorageSourceMapper.java | 26 + .../user/model/constant/UserConstant.java | 8 + .../model/dto/UserStorageSourceDetailDTO.java | 57 + .../zfile/module/user/model/entity/User.java | 48 + .../user/model/entity/UserStorageSource.java | 36 + .../user/model/enums/LoginLogModeEnum.java | 41 + .../model/enums/LoginVerifyModeEnum.java | 2 +- .../request/CheckUserDuplicateRequest.java | 15 + .../user/model/request/CopyUserRequest.java | 32 + .../user/model/request/QueryUserRequest.java | 53 + .../ResetAdminUserNameAndPasswordRequest.java | 20 + .../user/model/request/SaveUserRequest.java | 37 + .../model/request/UpdateUserPwdRequest.java | 19 + .../user/model/request/UserLoginRequest.java | 31 + ...ifyLoginTwoFactorAuthenticatorRequest.java | 27 + .../model/response/UserDetailResponse.java | 29 + .../user/model/result/CheckLoginResult.java | 20 + .../module/user/model/result/LoginResult.java | 20 + .../LoginTwoFactorAuthenticatorResult.java | 27 + .../model/result/LoginVerifyImgResult.java | 25 + .../module/user/service/UserService.java | 374 ++ .../service/UserStorageSourceService.java | 205 ++ .../service/login}/ImgVerifyCodeService.java | 24 +- .../user/service/login/LoginService.java | 25 + .../TwoFactorAuthenticatorVerifyService.java | 37 +- .../login/verify/LoginVerifyService.java | 9 + .../impl/ImgCodeLoginVerifyService.java | 49 + .../verify/impl/PasswordVerifyService.java | 41 + .../impl/TwoFactorAuthLoginVerifyService.java | 50 + .../user/utils/PasswordVerifyUtils.java | 32 + .../web/Swagger2ControllerWebFlux.java | 106 - .../web/Swagger2ControllerWebMvc.java | 110 - .../resources/application-default.properties | 55 +- src/main/resources/application.properties | 21 +- .../V10__system_config_add_field_webdav.sql | 4 + ...fig_add_field_only_office_secret_field.sql | 1 + ...nfig_add_field_enable_hover_menu_field.sql | 1 + ...tem_config_add_field_site_access_field.sql | 2 + ...__system_config_add_field_login_verify.sql | 15 + .../V27__add_table_login_log.sql | 13 + .../migration-mysql/V28__add_multi_user.sql | 36 + ...__system_config_add_field_login_verify.sql | 2 + ...delete_storage_source_auto_cors_config.sql | 1 + .../V31__system_config_add_field_webdav.sql | 1 + ...V32__system_config_delete_domain_field.sql | 1 + ...33__storage_source_config_update_field.sql | 26 + ...34__storage_source_config_update_field.sql | 1 + ...system_config_add_field_login_log_mode.sql | 1 + .../V36__user_add_field_salt.sql | 1 + .../V37__set_login_log_model_default_off.sql | 1 + .../V38__update_login_log_ip_field_length.sql | 1 + ..._system_config_add_field_mobile_layout.sql | 1 + ...system_config_add_custom_office_suffix.sql | 1 + ...42__system_config_add_guest_index_html.sql | 1 + .../V43__set_2fa_default_value.sql | 3 + .../V5__add_permission_config_table.sql | 9 + .../V6__system_config_add_field_auth_code.sql | 1 + .../V10__system_config_add_field_webdav.sql | 4 + ...fig_add_field_only_office_secret_field.sql | 1 + ...nfig_add_field_enable_hover_menu_field.sql | 1 + ...tem_config_add_field_site_access_field.sql | 2 + ...__system_config_add_field_login_verify.sql | 2 + .../V27__add_table_login_log.sql | 12 + .../migration-sqlite/V28__add_multi_user.sql | 36 + ...__system_config_add_field_login_verify.sql | 2 + ...delete_storage_source_auto_cors_config.sql | 1 + .../V31__system_config_add_field_webdav.sql | 1 + ...V32__system_config_delete_domain_field.sql | 1 + ...33__storage_source_config_update_field.sql | 26 + ...34__storage_source_config_update_field.sql | 1 + ...system_config_add_field_login_log_mode.sql | 1 + .../V36__user_add_field_salt.sql | 1 + ...ix_user_create_time_field_to_timestamp.sql | 1 + .../V38__set_login_log_model_default_off.sql | 1 + ..._system_config_add_field_mobile_layout.sql | 1 + ...system_config_add_custom_office_suffix.sql | 1 + ...42__system_config_add_guest_index_html.sql | 1 + .../V43__set_2fa_default_value.sql | 3 + .../V5__add_permission_config_table.sql | 9 + .../V6__system_config_add_field_auth_code.sql | 1 + src/main/resources/logback-spring.xml | 39 +- .../resources/mapper/DownloadLogMapper.xml | 19 + src/main/resources/mapper/LoginLogMapper.xml | 20 + .../mapper/PermissionConfigMapper.xml | 30 + src/main/resources/mapper/ShortLinkMapper.xml | 34 +- .../resources/mapper/StorageSourceMapper.xml | 17 +- src/main/resources/mapper/UserMapper.xml | 49 + .../mapper/UserStorageSourceMapper.xml | 90 + .../static/assets/401-legacy.31f27c8f.js | 1 - .../static/assets/401-legacy.96207b9a.js | 1 - .../resources/static/assets/401.b992c54d.js | 1 - .../resources/static/assets/401.ef03378b.js | 1 - .../resources/static/assets/401.f56d4c2a.svg | 1 - .../static/assets/403-legacy.914109bb.js | 1 - .../resources/static/assets/403.07bdf36d.svg | 45 - .../resources/static/assets/403.8d1625ed.css | 1 - .../resources/static/assets/403.c3876e49.js | 1 - .../static/assets/404-legacy.269276b8.js | 1 - .../resources/static/assets/404.32b48550.css | 1 - .../resources/static/assets/404.46de94fc.svg | 1 - .../resources/static/assets/404.5907bca3.js | 1 - .../assets/CheckBadgeIcon-legacy.4daa3dd8.js | 1 - .../static/assets/CheckBadgeIcon.b1bb4ad5.js | 1 - .../assets/FileGallery-legacy.240d14d0.js | 1 - .../static/assets/FileGallery.1e77159b.js | 1 - .../static/assets/FileGallery.94dcaf1e.css | 1 - .../assets/MarkdownViewer-legacy.cca71c6b.js | 1 - .../static/assets/MarkdownViewer.0360ebdf.js | 48 - .../static/assets/MarkdownViewer.925cd2a3.css | 1 - .../assets/OfficeViewer-legacy.c8f08ae6.js | 1 - .../static/assets/OfficeViewer.3a9ee45a.css | 1 - .../static/assets/OfficeViewer.e85f2f3b.js | 1 - .../assets/PdfViewer-legacy.27718d19.js | 1 - .../static/assets/PdfViewer.d597acc9.css | 1 - .../static/assets/PdfViewer.f53e17e1.js | 54 - .../static/assets/SvgIcon-legacy.d49e6d22.js | 1 - .../static/assets/SvgIcon.314dabdd.js | 1 - .../assets/TextViewer-legacy.899a419d.js | 8 - .../static/assets/TextViewer.46bc3ab5.js | 664 ---- .../static/assets/TextViewer.56ee38b8.css | 1 - .../assets/Three3dPreview-legacy.cc5520ca.js | 13 - .../static/assets/Three3dPreview.aa758a1e.js | 3159 ----------------- .../static/assets/Three3dPreview.fecaa377.css | 1 - .../assets/VideoPlayer-legacy.45d2dec4.js | 16 - .../static/assets/VideoPlayer.5a35bf29.css | 1 - .../static/assets/VideoPlayer.de65c86a.js | 60 - .../assets/XCircleIcon-legacy.2726efd9.js | 1 - .../static/assets/XCircleIcon.af7bde30.js | 1 - .../assets/ZFormItem-legacy.19a998d2.js | 1 - .../static/assets/ZFormItem.e3c3668e.css | 1 - .../static/assets/ZFormItem.f027e70d.js | 1 - .../assets/_Uint8Array-legacy.5581156d.js | 1 - .../static/assets/_Uint8Array.4350f927.js | 1 - .../_initCloneObject-legacy.54c17a65.js | 1 - .../assets/_initCloneObject.7947104d.js | 1 - .../assets/_storageId_-legacy.90b0dde8.js | 1 - .../assets/_storageId_-legacy.abec9893.js | 1 - .../assets/_storageId_-legacy.df51ee30.js | 1 - .../assets/_storageId_-legacy.e37c7dbf.js | 1 - .../static/assets/_storageId_.3b67f43e.js | 1 - .../static/assets/_storageId_.83e8203a.css | 1 - .../static/assets/_storageId_.852036d5.css | 1 - .../static/assets/_storageId_.943efe40.js | 1 - .../static/assets/_storageId_.977af154.js | 1 - .../static/assets/_storageId_.9dce9f5f.js | 1 - .../static/assets/_storageId_.dcd27aaf.css | 1 - .../static/assets/add-file.9d01a01a.svg | 1 - .../assets/add-folder-back.28141d7f.svg | 1 - .../static/assets/add-folder.7ef044d1.svg | 1 - .../resources/static/assets/add.598ba734.svg | 1 - .../static/assets/admin-legacy.6f2f2bc6.js | 1 - .../static/assets/admin-login.db1d73c1.svg | 1 - .../assets/admin-setting-legacy.2d83c4ce.js | 1 - .../static/assets/admin-setting.46e4ebbe.js | 1 - .../assets/admin-storage-legacy.9d4d7743.js | 1 - .../static/assets/admin-storage.1997580a.js | 1 - .../resources/static/assets/admin.5b179419.js | 1 - .../static/assets/alert-legacy.b748a33f.js | 1 - .../static/assets/alert.220a30c1.css | 1 - .../resources/static/assets/alert.6929fc12.js | 1 - .../static/assets/aliyun.0f005258.svg | 1 - .../static/assets/badge-legacy.6a43abe6.js | 1 - .../resources/static/assets/badge.1ec88ea5.js | 1 - .../static/assets/badge.28cc8ef5.css | 1 - .../static/assets/baidu.4a11f5ee.svg | 1 - .../assets/base-editor-legacy.3e59db2b.js | 1 - .../assets/base-editor-legacy.410912eb.js | 1 - .../static/assets/base-editor.079785da.css | 1 - .../static/assets/base-editor.5b9d1ebf.js | 15 - .../static/assets/base-legacy.28cad0e5.js | 1 - .../resources/static/assets/base.b65bdcd1.js | 1 - .../resources/static/assets/base.ef3e45d0.css | 1 - .../assets/basic-setting-legacy.167f29dc.js | 1 - .../static/assets/basic-setting.0f4062c7.js | 1 - .../static/assets/basic-setting.39c6db10.css | 1 - .../static/assets/button-legacy.96a0013c.js | 1 - .../static/assets/button.cca160de.css | 1 - .../static/assets/cancel.ba311fb8.svg | 1 - .../static/assets/card-layout.0aa8ea66.svg | 1 - .../static/assets/card-legacy.bb1d668a.js | 1 - .../resources/static/assets/card.23e4220d.js | 1 - .../resources/static/assets/card.7b41c195.css | 1 - .../static/assets/check.8203fabe.svg | 1 - .../static/assets/checkbox-legacy.dcd6a125.js | 1 - .../static/assets/checkbox.adc831f9.css | 1 - .../static/assets/checkbox.c90315f5.js | 1 - .../static/assets/clean.cc9d5116.svg | 1 - .../static/assets/common-legacy.2b8660dc.js | 1 - .../static/assets/common-legacy.503d039e.js | 1 - .../static/assets/common-legacy.6bc9e0b9.js | 1 - .../static/assets/common.1b9e00e4.js | 1 - .../static/assets/common.412949d0.js | 1 - .../static/assets/common.df582fdd.js | 1 - .../resources/static/assets/copy.0d4a2f7b.svg | 1 - .../static/assets/copy2.6713dbfa.svg | 1 - .../static/assets/cpp-legacy.f27eb3b5.js | 8 - .../resources/static/assets/cpp.842d3355.js | 6 - .../static/assets/css-legacy.d1c6b9ed.js | 8 - .../resources/static/assets/css.2d6e4cde.js | 8 - .../assets/dayjs.min-legacy.bfc9e23f.js | 1 - .../static/assets/dayjs.min.65a08ee1.js | 1 - .../static/assets/debounce-legacy.45d124e9.js | 1 - .../static/assets/debounce.da5c4ce9.js | 1 - .../static/assets/default-legacy.76897c15.js | 1 - .../static/assets/default.841b220f.js | 1 - .../static/assets/delete.51a1dbaf.svg | 7 - .../static/assets/dialog-legacy.dfba30ef.js | 1 - .../static/assets/dialog.bc6c9616.js | 1 - .../static/assets/dialog.f31b232b.css | 1 - .../assets/directive-legacy.0554e5f2.js | 1 - .../static/assets/directive.d58852f6.js | 1 - .../assets/dockerfile-legacy.8b5f349f.js | 8 - .../static/assets/dockerfile.339e94af.js | 6 - .../static/assets/doge-cloud.06a44b40.svg | 207 -- .../static/assets/download-mult.4332fce8.svg | 1 - .../assets/download-package.62191a76.svg | 1 - .../static/assets/download.f863f7c2.svg | 1 - .../assets/dropdown-item-legacy.76ce8010.js | 1 - .../static/assets/dropdown-item.4d20c061.css | 1 - .../static/assets/dropdown-item.f9529266.js | 1 - .../static/assets/dropdown-legacy.b5ca9dce.js | 1 - .../static/assets/dropdown.d1deefd6.js | 1 - .../resources/static/assets/edit.a2e2c356.svg | 5 - .../static/assets/empty.d5d3dec8.svg | 145 - .../static/assets/error.69207ff6.svg | 1 - .../static/assets/event-legacy.39ad8904.js | 1 - .../resources/static/assets/event.776e7e11.js | 1 - .../static/assets/file-legacy.78f43f4a.js | 1 - .../static/assets/file-legacy.ed045f34.js | 1 - .../static/assets/file-type-apk.e1385fc3.svg | 1 - .../assets/file-type-archive.f4b181db.svg | 1 - .../assets/file-type-audio.94e75894.svg | 1 - .../static/assets/file-type-back.ca0b84c2.svg | 1 - .../static/assets/file-type-css.5dfb9ed8.svg | 1 - .../static/assets/file-type-deb.5804788b.svg | 1 - .../static/assets/file-type-dll.6ea302d9.svg | 1 - .../static/assets/file-type-doc.a9d08313.svg | 1 - .../assets/file-type-document.b6e28e75.svg | 1 - .../static/assets/file-type-exe.b561ed3f.svg | 1 - .../assets/file-type-expression.3aaf04f1.svg | 1 - .../static/assets/file-type-file.8027949d.svg | 1 - .../assets/file-type-folder.35bcb02c.svg | 1 - .../static/assets/file-type-html.e37c00f9.svg | 1 - .../assets/file-type-image.689e667c.svg | 1 - .../static/assets/file-type-java.e50a4779.svg | 1 - .../static/assets/file-type-js.5c3b67c3.svg | 1 - .../static/assets/file-type-less.429d4f89.svg | 1 - .../static/assets/file-type-md.4dd32bf9.svg | 1 - .../assets/file-type-office.9af40573.svg | 1 - .../static/assets/file-type-pdf.1e018e0c.svg | 1 - .../static/assets/file-type-php.4feb4413.svg | 1 - .../static/assets/file-type-ppt.3d3e6af9.svg | 1 - .../static/assets/file-type-py.09b51350.svg | 1 - .../static/assets/file-type-rb.32961396.svg | 1 - .../static/assets/file-type-root.80ddc97b.svg | 1 - .../static/assets/file-type-rpm.559b18a5.svg | 1 - .../static/assets/file-type-rust.69994176.svg | 1 - .../assets/file-type-script.52b80ced.svg | 1 - .../static/assets/file-type-text.8ff306a0.svg | 1 - .../assets/file-type-three3d.e17881e7.svg | 5 - .../static/assets/file-type-vbs.22c9b0b7.svg | 1 - .../assets/file-type-video.888eeb7b.svg | 1 - .../static/assets/file-type-xls.db6934ae.svg | 1 - .../static/assets/file-type-xml.234b67f6.svg | 1 - .../static/assets/file-type-yaml.c1af7aa9.svg | 1 - .../static/assets/file-upload.65dff662.svg | 1 - .../resources/static/assets/file.2ad0369e.css | 1 - .../resources/static/assets/file.a8d0d277.js | 1199 ------- .../resources/static/assets/file.c473a8ba.css | 1 - .../resources/static/assets/file.fce05f37.js | 1 - .../static/assets/files.a94428cd.svg | 1 - .../assets/focus-trap-legacy.be7f3f75.js | 1 - .../static/assets/focus-trap.2030fc87.js | 1 - .../assets/form-item-legacy.8eb59483.js | 1 - .../static/assets/form-item.2ed8d062.css | 1 - .../static/assets/form-item.5db46a7d.js | 1 - .../resources/static/assets/ftp.c6a679c1.svg | 13 - .../static/assets/github-legacy.a6e57560.js | 1 - .../static/assets/github-legacy.f9623fea.js | 1 - .../static/assets/github.14b189fa.css | 1 - .../static/assets/github.afc55da0.js | 19 - .../static/assets/go-legacy.0e8fe384.js | 8 - .../resources/static/assets/go.51190b50.js | 6 - .../static/assets/google-drive.d619e147.svg | 8 - .../static/assets/graphql-legacy.974a4aa9.js | 8 - .../static/assets/graphql.543bb499.js | 6 - .../static/assets/html-legacy.4b7ee50f.js | 8 - .../resources/static/assets/html.97b9b105.js | 6 - .../static/assets/huawei.57d8824c.svg | 1 - .../static/assets/icon-legacy.45dc7071.js | 1 - .../resources/static/assets/icon.cca102d8.css | 1 - .../assets/image-viewer-legacy.015bdb10.js | 1 - .../static/assets/image-viewer.4bd3c6ca.css | 1 - .../static/assets/image-viewer.50a93231.js | 1 - .../static/assets/image.69ca2c19.svg | 1 - .../static/assets/img-disable.16380239.svg | 22 - .../static/assets/img-enable.ca957fe0.svg | 20 - .../static/assets/index-legacy.021508e8.js | 1 - .../static/assets/index-legacy.2624a61d.js | 1 - .../static/assets/index-legacy.31b96dde.js | 1 - .../static/assets/index-legacy.492b6ebd.js | 29 - .../static/assets/index-legacy.4b4c1514.js | 1 - .../static/assets/index-legacy.4f8ed873.js | 1 - .../static/assets/index-legacy.600bb797.js | 1 - .../static/assets/index-legacy.677ea204.js | 1 - .../static/assets/index-legacy.6c7a445e.js | 1 - .../static/assets/index-legacy.762b537a.js | 1 - .../static/assets/index-legacy.7703180a.js | 7 - .../static/assets/index-legacy.7d58e80a.js | 1 - .../static/assets/index-legacy.89e00a81.js | 1 - .../static/assets/index-legacy.c304ec26.js | 1 - .../static/assets/index-legacy.d5045c51.js | 1 - .../static/assets/index-legacy.ebfe533e.js | 1 - .../static/assets/index-legacy.eea43e0b.js | 1 - .../resources/static/assets/index.04bc4c90.js | 1 - .../resources/static/assets/index.0f169a7e.js | 1 - .../static/assets/index.1f8079a9.css | 1 - .../resources/static/assets/index.22cf9654.js | 20 - .../resources/static/assets/index.2a66a81e.js | 1 - .../resources/static/assets/index.2f5dd8a9.js | 1 - .../static/assets/index.3104d2da.css | 1 - .../resources/static/assets/index.4527d98c.js | 1 - .../resources/static/assets/index.5a412c6a.js | 12 - .../resources/static/assets/index.690b9de8.js | 1 - .../static/assets/index.6db9fd01.css | 7 - .../resources/static/assets/index.871a70d2.js | 1 - .../resources/static/assets/index.9300971e.js | 1 - .../resources/static/assets/index.94e7820c.js | 1 - .../resources/static/assets/index.965ac11d.js | 51 - .../resources/static/assets/index.c4c36dd3.js | 1 - .../static/assets/index.c6583322.css | 1 - .../resources/static/assets/index.e52e3560.js | 1 - .../resources/static/assets/index.e6797f5b.js | 10 - .../static/assets/index.e7a8d578.css | 1 - .../resources/static/assets/index.efd5e5c6.js | 1 - .../resources/static/assets/index.f33f0cba.js | 1 - .../resources/static/assets/info.54497d89.svg | 3 - .../static/assets/ini-legacy.4a63db00.js | 8 - .../resources/static/assets/ini.d9bdfd1c.js | 6 - .../static/assets/input-legacy.d4357d07.js | 1 - .../assets/input-number-legacy.2d301e91.js | 1 - .../static/assets/input-number.1615f7f4.css | 1 - .../static/assets/input-number.df41d4c6.js | 1 - .../static/assets/input.50bb0754.css | 1 - .../static/assets/install-legacy.b1db3ab1.js | 1 - .../static/assets/install-step.28fbaa8c.svg | 40 - .../static/assets/install.f1fa4e8e.js | 1 - .../static/assets/java-legacy.33e41b69.js | 8 - .../resources/static/assets/java.676f2079.js | 6 - .../assets/javascript-legacy.9ed89cd9.js | 8 - .../static/assets/javascript.7432b13b.js | 6 - .../static/assets/kotlin-legacy.823db493.js | 8 - .../static/assets/kotlin.3c1f4364.js | 6 - .../static/assets/less-legacy.faf52af7.js | 8 - .../resources/static/assets/less.d79b8bf9.js | 7 - .../static/assets/link-legacy.0bc47829.js | 1 - .../resources/static/assets/link.0a44cb5c.svg | 1 - .../resources/static/assets/link.f7c436ab.css | 1 - .../resources/static/assets/link.fbecb10e.js | 1 - .../static/assets/loading-legacy.a92cda73.js | 1 - .../static/assets/loading.dd11abc3.css | 1 - .../static/assets/local.f6bc02e5.svg | 11 - .../static/assets/log-legacy.71b64f46.js | 1 - .../resources/static/assets/log.b60ad730.css | 1 - .../resources/static/assets/log.f30d0d0d.js | 1 - .../static/assets/login-legacy.98297f48.js | 1 - .../static/assets/login-legacy.bc2a8275.js | 1 - .../resources/static/assets/login.1387933f.js | 1 - .../static/assets/login.53131169.css | 1 - .../resources/static/assets/login.d278bc9b.js | 1 - .../static/assets/login.e2217ec4.svg | 1 - .../static/assets/markdown-legacy.3c9f539e.js | 8 - .../static/assets/markdown.387e712e.js | 6 - .../static/assets/minio.e5474b90.svg | 1 - .../resources/static/assets/move.897d1439.svg | 6 - .../static/assets/mysql-legacy.d54b102a.js | 8 - .../resources/static/assets/mysql.294467b0.js | 6 - .../static/assets/new-folder.549c271c.svg | 1 - .../resources/static/assets/next.ed93fad9.svg | 1 - .../static/assets/notFound-legacy.c7d2c860.js | 1 - .../static/assets/notFound.345f1754.svg | 1 - .../static/assets/notFound.3cf6b4d2.js | 1 - .../static/assets/onedrive-china.f477f7d4.svg | 18 - .../static/assets/onedrive.1120a54d.svg | 4 - .../static/assets/overlay-legacy.7c160b60.js | 1 - .../static/assets/overlay.73d3f939.js | 1 - .../static/assets/overlay.dd389659.css | 1 - .../static/assets/php-legacy.8098c3de.js | 8 - .../resources/static/assets/php.38bacf90.js | 6 - ...lugin-vue_export-helper-legacy.7bb61c33.js | 1 - .../plugin-vue_export-helper.21dcd24c.js | 1 - .../assets/polyfills-legacy.b01f53df.js | 1 - .../assets/polyfills-modern.2ccba09e.js | 1 - .../static/assets/popover-legacy.0eba315a.js | 1 - .../static/assets/popover.aabbd9ff.css | 1 - .../static/assets/popper-legacy.3e7fb573.js | 1 - .../static/assets/popper.aedd2598.js | 1 - .../static/assets/popper.e5c0055c.css | 1 - .../resources/static/assets/prev.f23ef4a8.svg | 1 - .../static/assets/preview-legacy.20c2e7f6.js | 1 - .../static/assets/preview-legacy.ad944044.js | 1 - .../static/assets/preview.330b1af8.svg | 10 - .../static/assets/preview.a4eb0df8.js | 5 - .../static/assets/preview.ebfa3115.css | 1 - .../static/assets/prism-legacy.f70e0815.js | 1 - .../resources/static/assets/prism.be667f0e.js | 9 - .../static/assets/python-legacy.29f825d3.js | 8 - .../static/assets/python.4937e608.js | 6 - .../static/assets/qiniu.6036c6bb.svg | 1 - .../static/assets/radio-legacy.ac4eca21.js | 1 - .../static/assets/radio.3ebff4f2.css | 1 - .../resources/static/assets/radio.5a7362d7.js | 1 - .../readme-editor-dialog-legacy.d3efc623.js | 1 - .../assets/readme-editor-dialog.b1308d32.js | 1 - .../static/assets/refresh.44905e7c.svg | 1 - .../static/assets/refs-legacy.309a8508.js | 1 - .../resources/static/assets/refs.45a892d9.js | 1 - .../static/assets/request-legacy.01567c6f.js | 1 - .../static/assets/request.2ccc188b.js | 1 - .../static/assets/reset-password.b19e1ea5.svg | 1 - .../assets/route-block-legacy.2d113b05.js | 1 - .../static/assets/route-block.9b0645f8.js | 1 - .../resources/static/assets/s3.280509b0.svg | 5 - .../static/assets/scroll-legacy.0bcf8f62.js | 1 - .../static/assets/scroll.06c8f1a5.js | 1 - .../assets/scrollbar-legacy.c0fd5057.js | 1 - .../static/assets/scrollbar.117c6324.js | 1 - .../static/assets/scrollbar.193d2636.css | 1 - .../static/assets/scss-legacy.9d707fe2.js | 8 - .../resources/static/assets/scss.3d57ef5d.js | 8 - .../static/assets/search.8b68470c.svg | 1 - .../static/assets/select-all.4aaccd3e.svg | 1 - .../static/assets/select-legacy.9fc0fff6.js | 1 - .../static/assets/select.3bf869d2.css | 1 - .../static/assets/select.5ac33161.js | 1 - .../static/assets/server.f8a853a2.svg | 1 - .../static/assets/setting-legacy.ede95197.js | 1 - .../static/assets/setting.02a8da25.js | 1 - .../static/assets/settings.b524ea2e.svg | 1 - .../resources/static/assets/sftp.c71078df.svg | 13 - .../assets/sharepoint-china.9907efac.svg | 23 - .../static/assets/sharepoint.1bd337fc.svg | 1 - .../assets/site-setting-legacy.e4779f73.js | 1 - .../static/assets/site-setting.15f4d0d4.js | 1 - .../static/assets/site-setting.2974b3f9.css | 1 - .../assets/sortable.esm-legacy.a574004a.js | 8 - .../static/assets/sortable.esm.a99254e8.js | 6 - .../static/assets/sql-legacy.79102732.js | 8 - .../resources/static/assets/sql.45ce0396.js | 6 - .../assets/storage-copy-legacy.05703ca0.js | 1 - .../static/assets/storage-copy.93e6ed15.js | 1 - .../assets/storage-list-legacy.9d9ab7ef.js | 1 - .../assets/storage-list-legacy.d69e21ea.js | 1 - .../static/assets/storage-list.1cb4b2bc.js | 1 - .../static/assets/storage-list.dd71c919.js | 1 - .../static/assets/storage-list.eb7fff44.css | 1 - .../static/assets/success.ddc7b948.svg | 1 - .../static/assets/switch-legacy.e46c6730.js | 1 - .../static/assets/switch.1ecf928e.js | 1 - .../static/assets/switch.6cf08487.css | 1 - .../assets/table-column-legacy.ab1663ac.js | 1 - .../static/assets/table-column.5d9b8d93.css | 1 - .../static/assets/table-layout.e8017f7c.svg | 1 - .../static/assets/table-legacy.8ce31529.js | 15 - .../resources/static/assets/table.29200b56.js | 20 - .../static/assets/table.75fd924f.css | 1 - .../static/assets/tag-legacy.1f08e7fb.js | 1 - .../resources/static/assets/tag.9021a61b.css | 1 - .../static/assets/target.dcdb3ade.svg | 1 - .../static/assets/tencent.7b52a406.svg | 9 - .../static/assets/tool-close.4c4ed1a4.svg | 7 - .../static/assets/tool-close2.a77027c1.svg | 4 - .../static/assets/tool-delete.306633e9.svg | 5 - .../assets/tool-download-mult.3461b3f2.svg | 1 - .../assets/tool-download-package.0ea9ae5f.svg | 1 - .../static/assets/tool-download.b65826a2.svg | 4 - .../static/assets/tool-edit.405fd9c3.svg | 5 - .../static/assets/tool-link.6a893d0f.svg | 1 - .../static/assets/tool-move.fa418958.svg | 6 - .../static/assets/tool-preview.13c8406c.svg | 10 - .../static/assets/tool-setting.f36fb1c2.svg | 8 - .../static/assets/tooltip-legacy.7c89f687.js | 1 - .../static/assets/tooltip.afcd3f9d.js | 1 - .../assets/typescript-legacy.f76524cc.js | 8 - .../static/assets/typescript.be963bb3.js | 6 - .../static/assets/ufile.e305639b.svg | 14 - .../static/assets/unit-legacy.203f0ef2.js | 1 - .../resources/static/assets/unit.5ed4b9cd.js | 1 - .../assets/update-password-legacy.47e4b370.js | 1 - .../static/assets/update-password.73d5bebe.js | 1 - .../static/assets/upload-1.f348e120.svg | 50 - .../static/assets/upload-folder.8e924b23.svg | 1 - .../static/assets/upload.a9eeb994.svg | 1 - .../static/assets/upyun.b24d52ea.svg | 1 - .../use-outside-click-legacy.3e48aa6b.js | 1 - .../assets/use-outside-click.18deedcf.js | 4 - .../assets/useCommon-legacy.ff7c2bc7.js | 1 - .../static/assets/useCommon.eb7f3fe8.js | 1 - .../assets/useFileUpload-legacy.a81d91ec.js | 1 - .../static/assets/useFileUpload.a56ef935.js | 138 - .../assets/useLinkSetting-legacy.c2769623.js | 1 - .../static/assets/useLinkSetting.2b12bf8c.js | 1 - .../assets/validator-legacy.0afd2ceb.js | 1 - .../static/assets/validator.58bcb820.js | 1 - .../static/assets/video-download.3a226681.png | Bin 5589 -> 0 bytes .../static/assets/video-iina.1db26170.png | Bin 56634 -> 0 bytes .../static/assets/video-motrix.95ef7e61.png | Bin 24021 -> 0 bytes .../assets/video-mxplayer-pro.10406916.png | Bin 33135 -> 0 bytes .../static/assets/video-mxplayer.5dfa69aa.png | Bin 4679 -> 0 bytes .../static/assets/video-thunder.5e7f4cec.png | Bin 79692 -> 0 bytes .../static/assets/video-vlc.54e63a7e.png | Bin 9581 -> 0 bytes .../assets/view-setting-legacy.25c262f4.js | 1 - .../static/assets/view-setting.71fba2df.css | 1 - .../static/assets/view-setting.756f34e1.js | 1 - ...vue.runtime.esm-bundler-legacy.6fce6a6e.js | 1 - .../vue.runtime.esm-bundler.e1e535bc.js | 1 - ...3-clipboard.esm-bundler-legacy.fd707006.js | 14 - .../vue3-clipboard.esm-bundler.34366eba.js | 10 - .../static/assets/vuepress-legacy.1caa391f.js | 1 - .../static/assets/vuepress-legacy.8fbe8168.js | 1 - .../static/assets/vuepress.549e0153.js | 25 - .../static/assets/vuepress.95aa9197.css | 1 - .../static/assets/warning.7093bb23.svg | 1 - .../static/assets/webdav.c885a54c.svg | 1 - .../static/assets/xml-legacy.72fec0f9.js | 8 - .../resources/static/assets/xml.e1803075.js | 6 - .../static/assets/yaml-legacy.eb4e6c0a.js | 8 - .../resources/static/assets/yaml.311a8425.js | 6 - .../assets/zfile-basic-legacy.3f61b849.js | 1 - .../static/assets/zfile-basic.58891cac.svg | 17 - .../static/assets/zfile-basic.8b129fdf.js | 1 - .../zfile-horizontal-legacy.4a9c30e4.js | 1 - .../assets/zfile-horizontal.7507aff8.js | 1 - .../assets/zfile-horizontal.abd5aec9.svg | 16 - .../static/assets/zfile.76406368.svg | 1 - .../static/assets/zh-cn-legacy.8e639616.js | 1 - .../static/assets/zh-cn.198b941f.css | 1 - .../resources/static/assets/zh-cn.1b322053.js | 1 - src/main/resources/static/error.svg | 1 - src/main/resources/static/favicon.svg | 14 - src/main/resources/static/index.html | 34 - src/main/resources/static/logo.png | Bin 11411 -> 0 bytes src/main/resources/static/zfile.config.json | 26 - 908 files changed, 16269 insertions(+), 12912 deletions(-) delete mode 100644 src/main/java/com/baomidou/mybatisplus/core/handlers/MybatisEnumTypeHandler.java create mode 100644 src/main/java/im/zhaojun/zfile/core/annotation/ApiLimit.java create mode 100644 src/main/java/im/zhaojun/zfile/core/annotation/DemoDisable.java create mode 100644 src/main/java/im/zhaojun/zfile/core/aspect/ApiLimitAspect.java rename src/main/java/im/zhaojun/zfile/core/{ => aspect}/CommonResultControllerAdvice.java (87%) create mode 100644 src/main/java/im/zhaojun/zfile/core/aspect/DemoDisableAspect.java delete mode 100644 src/main/java/im/zhaojun/zfile/core/config/FlywayDbInitializer.java delete mode 100644 src/main/java/im/zhaojun/zfile/core/config/Knife4jConfiguration.java delete mode 100644 src/main/java/im/zhaojun/zfile/core/config/MyBatisPlusConfig.java delete mode 100644 src/main/java/im/zhaojun/zfile/core/config/RestTemplateConfig.java delete mode 100644 src/main/java/im/zhaojun/zfile/core/config/SaTokenConfigure.java create mode 100644 src/main/java/im/zhaojun/zfile/core/config/datasource/DataSourceBeanPostProcessor.java create mode 100644 src/main/java/im/zhaojun/zfile/core/config/docs/Knife4jConfiguration.java create mode 100644 src/main/java/im/zhaojun/zfile/core/config/jackson/JSONStringDeserializer.java create mode 100644 src/main/java/im/zhaojun/zfile/core/config/jackson/JSONStringSerializer.java create mode 100644 src/main/java/im/zhaojun/zfile/core/config/mybatis/CollectionIntegerTypeHandler.java create mode 100644 src/main/java/im/zhaojun/zfile/core/config/mybatis/CollectionStrTypeHandler.java create mode 100644 src/main/java/im/zhaojun/zfile/core/config/mybatis/CollectionTypeHandler.java create mode 100644 src/main/java/im/zhaojun/zfile/core/config/mybatis/MyBatisPlusConfig.java rename src/main/java/im/zhaojun/zfile/core/config/{ => mybatis}/MyDatabaseIdProvider.java (69%) create mode 100644 src/main/java/im/zhaojun/zfile/core/config/mybatis/MyMetaObjectHandler.java create mode 100644 src/main/java/im/zhaojun/zfile/core/config/security/SaSessionForJacksonCustomized.java create mode 100644 src/main/java/im/zhaojun/zfile/core/config/security/SaTokenConfigure.java create mode 100644 src/main/java/im/zhaojun/zfile/core/config/security/SaTokenDaoRedisJackson.java create mode 100644 src/main/java/im/zhaojun/zfile/core/config/security/StpInterfaceImpl.java rename src/main/java/im/zhaojun/zfile/core/config/{ => spring}/JacksonEnumDeserializer.java (93%) rename src/main/java/im/zhaojun/zfile/core/config/{ => spring}/SpringCacheConfig.java (67%) rename src/main/java/im/zhaojun/zfile/core/config/{ => spring}/StringToEnumConverterFactory.java (91%) rename src/main/java/im/zhaojun/zfile/core/config/{ => spring}/WebMvcConfig.java (61%) create mode 100644 src/main/java/im/zhaojun/zfile/core/config/totp/TotpAutoConfiguration.java create mode 100644 src/main/java/im/zhaojun/zfile/core/config/totp/TotpProperties.java create mode 100644 src/main/java/im/zhaojun/zfile/core/constant/RuleTypeConstant.java create mode 100644 src/main/java/im/zhaojun/zfile/core/constant/ZFileHttpHeaderConstant.java create mode 100644 src/main/java/im/zhaojun/zfile/core/exception/ErrorCode.java create mode 100644 src/main/java/im/zhaojun/zfile/core/exception/GlobalExceptionHandler.java delete mode 100644 src/main/java/im/zhaojun/zfile/core/exception/IllegalDownloadLinkException.java delete mode 100644 src/main/java/im/zhaojun/zfile/core/exception/InstallSystemException.java delete mode 100644 src/main/java/im/zhaojun/zfile/core/exception/InvalidShortLinkException.java delete mode 100644 src/main/java/im/zhaojun/zfile/core/exception/LoginVerifyException.java delete mode 100644 src/main/java/im/zhaojun/zfile/core/exception/PasswordVerifyException.java delete mode 100644 src/main/java/im/zhaojun/zfile/core/exception/PreviewException.java delete mode 100644 src/main/java/im/zhaojun/zfile/core/exception/ServiceException.java delete mode 100644 src/main/java/im/zhaojun/zfile/core/exception/StorageSourceAutoConfigCorsException.java delete mode 100644 src/main/java/im/zhaojun/zfile/core/exception/StorageSourceException.java delete mode 100644 src/main/java/im/zhaojun/zfile/core/exception/StorageSourceNotSupportProxyUploadException.java delete mode 100644 src/main/java/im/zhaojun/zfile/core/exception/StorageSourceRefreshTokenException.java delete mode 100644 src/main/java/im/zhaojun/zfile/core/exception/TextParseException.java delete mode 100644 src/main/java/im/zhaojun/zfile/core/exception/ZFileRetryException.java delete mode 100644 src/main/java/im/zhaojun/zfile/core/exception/ZFileRuntimeException.java create mode 100644 src/main/java/im/zhaojun/zfile/core/exception/biz/APIHttpRequestBizException.java create mode 100644 src/main/java/im/zhaojun/zfile/core/exception/biz/CorsBizException.java create mode 100644 src/main/java/im/zhaojun/zfile/core/exception/biz/FilePathSecurityBizException.java create mode 100644 src/main/java/im/zhaojun/zfile/core/exception/biz/GetPreviewTextContentBizException.java create mode 100644 src/main/java/im/zhaojun/zfile/core/exception/biz/InitializeStorageSourceBizException.java create mode 100644 src/main/java/im/zhaojun/zfile/core/exception/biz/InvalidStorageSourceBizException.java create mode 100644 src/main/java/im/zhaojun/zfile/core/exception/biz/StorageSourceFileForbiddenAccessBizException.java create mode 100644 src/main/java/im/zhaojun/zfile/core/exception/biz/StorageSourceIllegalOperationBizException.java create mode 100644 src/main/java/im/zhaojun/zfile/core/exception/core/BizException.java create mode 100644 src/main/java/im/zhaojun/zfile/core/exception/core/ErrorPageBizException.java create mode 100644 src/main/java/im/zhaojun/zfile/core/exception/core/SystemException.java delete mode 100644 src/main/java/im/zhaojun/zfile/core/exception/file/InvalidStorageSourceException.java delete mode 100644 src/main/java/im/zhaojun/zfile/core/exception/file/init/InitializeStorageSourceException.java delete mode 100644 src/main/java/im/zhaojun/zfile/core/exception/file/operator/DisableProxyDownloadException.java delete mode 100644 src/main/java/im/zhaojun/zfile/core/exception/file/operator/StorageSourceFileOperatorException.java delete mode 100644 src/main/java/im/zhaojun/zfile/core/exception/handler/GlobalExceptionHandler.java delete mode 100644 src/main/java/im/zhaojun/zfile/core/exception/http/HttpResponseStatusErrorException.java create mode 100644 src/main/java/im/zhaojun/zfile/core/exception/status/BadRequestAccessException.java create mode 100644 src/main/java/im/zhaojun/zfile/core/exception/status/ForbiddenAccessException.java create mode 100644 src/main/java/im/zhaojun/zfile/core/exception/status/MethodNotAllowedAccessException.java create mode 100644 src/main/java/im/zhaojun/zfile/core/exception/status/NotFoundAccessException.java create mode 100644 src/main/java/im/zhaojun/zfile/core/exception/status/UnauthorizedAccessException.java create mode 100644 src/main/java/im/zhaojun/zfile/core/exception/system/UploadFileFailSystemException.java create mode 100644 src/main/java/im/zhaojun/zfile/core/exception/system/ZFileAuthorizationSystemException.java create mode 100644 src/main/java/im/zhaojun/zfile/core/filter/SecurityFilter.java delete mode 100644 src/main/java/im/zhaojun/zfile/core/httpclient/ZFileOkHttp3ClientHttpRequestFactory.java delete mode 100644 src/main/java/im/zhaojun/zfile/core/httpclient/logging/HttpLoggingInterceptor.java create mode 100644 src/main/java/im/zhaojun/zfile/core/io/EnsureContentLengthInputStreamResource.java create mode 100644 src/main/java/im/zhaojun/zfile/core/io/ThrottledInputStream.java create mode 100644 src/main/java/im/zhaojun/zfile/core/io/ThrottledOutputStream.java delete mode 100644 src/main/java/im/zhaojun/zfile/core/service/DynamicControllerManager.java create mode 100644 src/main/java/im/zhaojun/zfile/core/util/ArrayUtils.java create mode 100644 src/main/java/im/zhaojun/zfile/core/util/CharPool.java create mode 100644 src/main/java/im/zhaojun/zfile/core/util/CharSequenceUtil.java delete mode 100644 src/main/java/im/zhaojun/zfile/core/util/CodeMsg.java create mode 100644 src/main/java/im/zhaojun/zfile/core/util/CollectionUtils.java create mode 100644 src/main/java/im/zhaojun/zfile/core/util/DnsUtil.java create mode 100644 src/main/java/im/zhaojun/zfile/core/util/FileUtils.java create mode 100644 src/main/java/im/zhaojun/zfile/core/util/NumberUtils.java create mode 100644 src/main/java/im/zhaojun/zfile/core/util/OnlyOfficeKeyCacheUtils.java create mode 100644 src/main/java/im/zhaojun/zfile/core/util/RequestUtils.java rename src/main/java/im/zhaojun/zfile/{module/config/utils => core/util}/SpringMvcUtils.java (82%) create mode 100644 src/main/java/im/zhaojun/zfile/core/util/StrPool.java create mode 100644 src/main/java/im/zhaojun/zfile/core/util/ZFileAuthUtil.java create mode 100644 src/main/java/im/zhaojun/zfile/core/util/matcher/AbstractRuleMatcher.java create mode 100644 src/main/java/im/zhaojun/zfile/core/util/matcher/IRuleMatcher.java create mode 100644 src/main/java/im/zhaojun/zfile/core/util/matcher/RuleMatcherFactory.java create mode 100644 src/main/java/im/zhaojun/zfile/core/util/matcher/impl/AntPathRuleMatcher.java create mode 100644 src/main/java/im/zhaojun/zfile/core/util/matcher/impl/IpRuleMatcher.java create mode 100644 src/main/java/im/zhaojun/zfile/core/util/matcher/impl/RegexRuleMatcher.java create mode 100644 src/main/java/im/zhaojun/zfile/core/util/matcher/impl/SpringSimpleRuleMatcher.java rename src/main/java/im/zhaojun/zfile/module/{config => admin}/controller/IpHelperController.java (61%) create mode 100644 src/main/java/im/zhaojun/zfile/module/admin/controller/RuleMatcherTestController.java create mode 100644 src/main/java/im/zhaojun/zfile/module/admin/model/request/TestRuleMatcherRequest.java create mode 100644 src/main/java/im/zhaojun/zfile/module/config/annotation/JSONStringParse.java delete mode 100644 src/main/java/im/zhaojun/zfile/module/config/controller/AntPathMatcherHelperController.java delete mode 100644 src/main/java/im/zhaojun/zfile/module/config/controller/InstallController.java rename src/main/java/im/zhaojun/zfile/module/config/event/{SystemConfigModifyHandler.java => SystemConfigModifyHandlerChain.java} (63%) create mode 100644 src/main/java/im/zhaojun/zfile/module/config/model/dto/LinkExpireDTO.java delete mode 100644 src/main/java/im/zhaojun/zfile/module/config/model/request/InstallSystemRequest.java delete mode 100644 src/main/java/im/zhaojun/zfile/module/config/model/request/TestAntPathMatcherRequest.java create mode 100644 src/main/java/im/zhaojun/zfile/module/config/model/request/UpdateAccessSettingRequest.java delete mode 100644 src/main/java/im/zhaojun/zfile/module/config/model/request/UpdateWebDAVRequest.java create mode 100644 src/main/java/im/zhaojun/zfile/module/config/model/result/FrontSiteConfigResult.java delete mode 100644 src/main/java/im/zhaojun/zfile/module/config/model/result/SiteConfigResult.java create mode 100644 src/main/java/im/zhaojun/zfile/module/install/controller/InstallController.java create mode 100644 src/main/java/im/zhaojun/zfile/module/install/model/request/InstallSystemRequest.java create mode 100644 src/main/java/im/zhaojun/zfile/module/install/service/InstallService.java create mode 100644 src/main/java/im/zhaojun/zfile/module/link/cache/LinkRateLimiterCache.java create mode 100644 src/main/java/im/zhaojun/zfile/module/link/dto/DynamicRegisterMappingHandlerDTO.java create mode 100644 src/main/java/im/zhaojun/zfile/module/link/event/DeleteExpireLinkEvent.java create mode 100644 src/main/java/im/zhaojun/zfile/module/link/model/dto/CacheInfo.java create mode 100644 src/main/java/im/zhaojun/zfile/module/link/model/request/QueryLoginLogRequest.java create mode 100644 src/main/java/im/zhaojun/zfile/module/link/service/DynamicDirectLinkPrefixService.java create mode 100644 src/main/java/im/zhaojun/zfile/module/log/controller/LoginLogController.java create mode 100644 src/main/java/im/zhaojun/zfile/module/log/mapper/LoginLogMapper.java delete mode 100644 src/main/java/im/zhaojun/zfile/module/log/model/dto/DownloadTopFileDTO.java delete mode 100644 src/main/java/im/zhaojun/zfile/module/log/model/dto/DownloadTopIpDTO.java delete mode 100644 src/main/java/im/zhaojun/zfile/module/log/model/dto/DownloadTopRefererDTO.java create mode 100644 src/main/java/im/zhaojun/zfile/module/log/model/entity/LoginLog.java delete mode 100644 src/main/java/im/zhaojun/zfile/module/log/model/request/DownloadTopInfoRequest.java create mode 100644 src/main/java/im/zhaojun/zfile/module/log/service/LoginLogService.java delete mode 100644 src/main/java/im/zhaojun/zfile/module/login/controller/LoginController.java delete mode 100644 src/main/java/im/zhaojun/zfile/module/login/model/request/VerifyLoginTwoFactorAuthenticatorRequest.java delete mode 100644 src/main/java/im/zhaojun/zfile/module/login/model/result/LoginTwoFactorAuthenticatorResult.java delete mode 100644 src/main/java/im/zhaojun/zfile/module/login/model/result/LoginVerifyImgResult.java delete mode 100644 src/main/java/im/zhaojun/zfile/module/login/request/UserLoginRequest.java create mode 100644 src/main/java/im/zhaojun/zfile/module/onlyoffice/controller/OnlyOfficeController.java create mode 100644 src/main/java/im/zhaojun/zfile/module/onlyoffice/model/OnlyOfficeCallback.java create mode 100644 src/main/java/im/zhaojun/zfile/module/onlyoffice/model/OnlyOfficeFile.java create mode 100644 src/main/java/im/zhaojun/zfile/module/permission/controller/PermissionController.java create mode 100644 src/main/java/im/zhaojun/zfile/module/permission/controller/StorageSourcePermissionController.java create mode 100644 src/main/java/im/zhaojun/zfile/module/permission/convert/PermissionConfigConvert.java create mode 100644 src/main/java/im/zhaojun/zfile/module/permission/mapper/PermissionConfigMapper.java create mode 100644 src/main/java/im/zhaojun/zfile/module/permission/model/entity/PermissionConfig.java create mode 100644 src/main/java/im/zhaojun/zfile/module/permission/model/result/PermissionConfigResult.java create mode 100644 src/main/java/im/zhaojun/zfile/module/permission/model/result/PermissionInfoResult.java create mode 100644 src/main/java/im/zhaojun/zfile/module/permission/service/PermissionConfigService.java create mode 100644 src/main/java/im/zhaojun/zfile/module/storage/annotation/CheckPasswords.java create mode 100644 src/main/java/im/zhaojun/zfile/module/storage/annotation/ProCheck.java create mode 100644 src/main/java/im/zhaojun/zfile/module/storage/annotation/StoragePermissionCheck.java delete mode 100644 src/main/java/im/zhaojun/zfile/module/storage/aspect/FileOperatorExceptionWrapperAspect.java create mode 100644 src/main/java/im/zhaojun/zfile/module/storage/chain/command/FileDownloadPermissionCommand.java delete mode 100644 src/main/java/im/zhaojun/zfile/module/storage/chain/command/FileUrlAddVersionCommand.java create mode 100644 src/main/java/im/zhaojun/zfile/module/storage/constant/S3SignerTypeConstant.java create mode 100644 src/main/java/im/zhaojun/zfile/module/storage/constant/StorageSourceConnectionProperties.java create mode 100644 src/main/java/im/zhaojun/zfile/module/storage/context/StorageSourceInitializer.java create mode 100644 src/main/java/im/zhaojun/zfile/module/storage/enums/StorageParamItemAnnoEnum.java create mode 100644 src/main/java/im/zhaojun/zfile/module/storage/event/StorageSourceCopyEvent.java create mode 100644 src/main/java/im/zhaojun/zfile/module/storage/event/StorageSourceDeleteEvent.java create mode 100644 src/main/java/im/zhaojun/zfile/module/storage/function/AllowAdminFileOperatorTypeEnumDefaultValueFunc.java create mode 100644 src/main/java/im/zhaojun/zfile/module/storage/function/AllowAllFileOperatorTypeEnumDefaultValueFunc.java create mode 100644 src/main/java/im/zhaojun/zfile/module/storage/function/BasicFileOperatorTypeEnumDefaultValueFunc.java create mode 100644 src/main/java/im/zhaojun/zfile/module/storage/function/DisableAllFileOperatorTypeEnumDefaultValueFunc.java create mode 100644 src/main/java/im/zhaojun/zfile/module/storage/function/LinkFileOperatorTypeEnumDefaultValueFunc.java create mode 100644 src/main/java/im/zhaojun/zfile/module/storage/function/SearchFileOperatorTypeEnumDefaultValueFunc.java create mode 100644 src/main/java/im/zhaojun/zfile/module/storage/function/ShortLinkFileOperatorTypeEnumDefaultValueFunc.java create mode 100644 src/main/java/im/zhaojun/zfile/module/storage/model/bo/StorageSourceMetadata.java create mode 100644 src/main/java/im/zhaojun/zfile/module/storage/model/dto/StorageSourceInitDTO.java create mode 100644 src/main/java/im/zhaojun/zfile/module/storage/model/dto/ZFileCORSRule.java create mode 100644 src/main/java/im/zhaojun/zfile/module/storage/model/enums/SearchFolderModeEnum.java create mode 100644 src/main/java/im/zhaojun/zfile/module/storage/model/param/OptionalProxyTransferParam.java delete mode 100644 src/main/java/im/zhaojun/zfile/module/storage/model/param/ProxyDownloadParam.java delete mode 100644 src/main/java/im/zhaojun/zfile/module/storage/model/param/ProxyUploadParam.java create mode 100644 src/main/java/im/zhaojun/zfile/module/storage/model/request/GetS3CorsListRequest.java create mode 100644 src/main/java/im/zhaojun/zfile/module/storage/model/request/base/SearchStorageRequest.java create mode 100644 src/main/java/im/zhaojun/zfile/module/storage/model/request/operator/BatchMoveOrCopyFileRequest.java delete mode 100644 src/main/java/im/zhaojun/zfile/module/storage/model/request/operator/CopyFileRequest.java delete mode 100644 src/main/java/im/zhaojun/zfile/module/storage/model/request/operator/CopyFolderRequest.java delete mode 100644 src/main/java/im/zhaojun/zfile/module/storage/model/request/operator/MoveFileRequest.java delete mode 100644 src/main/java/im/zhaojun/zfile/module/storage/model/request/operator/MoveFolderRequest.java create mode 100644 src/main/java/im/zhaojun/zfile/module/storage/model/result/operator/BatchOperatorResult.java create mode 100644 src/main/java/im/zhaojun/zfile/module/storage/oauth2/service/AbstractMicrosoftOAuth2Service.java create mode 100644 src/main/java/im/zhaojun/zfile/module/storage/oauth2/service/GoogleDriveOAuth2ServiceImpl.java create mode 100644 src/main/java/im/zhaojun/zfile/module/storage/oauth2/service/IOAuth2Service.java create mode 100644 src/main/java/im/zhaojun/zfile/module/storage/oauth2/service/OneDriveChinaOAuth2ServiceImpl.java create mode 100644 src/main/java/im/zhaojun/zfile/module/storage/oauth2/service/OneDriveOAuth2ServiceImpl.java delete mode 100644 src/main/java/im/zhaojun/zfile/module/storage/service/base/AbstractProxyDownloadService.java delete mode 100644 src/main/java/im/zhaojun/zfile/module/storage/service/base/AbstractProxyUploadService.java create mode 100644 src/main/java/im/zhaojun/zfile/module/storage/support/ftp/FtpClientFactory.java create mode 100644 src/main/java/im/zhaojun/zfile/module/storage/support/ftp/FtpClientPool.java create mode 100644 src/main/java/im/zhaojun/zfile/module/storage/support/sftp/SFtpClientFactory.java create mode 100644 src/main/java/im/zhaojun/zfile/module/storage/support/sftp/SFtpClientPool.java create mode 100644 src/main/java/im/zhaojun/zfile/module/user/aspect/LoginLogAspect.java create mode 100644 src/main/java/im/zhaojun/zfile/module/user/controller/AdminTwoFAController.java create mode 100644 src/main/java/im/zhaojun/zfile/module/user/controller/UserController.java create mode 100644 src/main/java/im/zhaojun/zfile/module/user/controller/UserManagerController.java create mode 100644 src/main/java/im/zhaojun/zfile/module/user/event/UserCopyEvent.java create mode 100644 src/main/java/im/zhaojun/zfile/module/user/event/UserDeleteEvent.java create mode 100644 src/main/java/im/zhaojun/zfile/module/user/manager/UserManager.java create mode 100644 src/main/java/im/zhaojun/zfile/module/user/mapper/UserMapper.java create mode 100644 src/main/java/im/zhaojun/zfile/module/user/mapper/UserStorageSourceMapper.java create mode 100644 src/main/java/im/zhaojun/zfile/module/user/model/constant/UserConstant.java create mode 100644 src/main/java/im/zhaojun/zfile/module/user/model/dto/UserStorageSourceDetailDTO.java create mode 100644 src/main/java/im/zhaojun/zfile/module/user/model/entity/User.java create mode 100644 src/main/java/im/zhaojun/zfile/module/user/model/entity/UserStorageSource.java create mode 100644 src/main/java/im/zhaojun/zfile/module/user/model/enums/LoginLogModeEnum.java rename src/main/java/im/zhaojun/zfile/module/{login => user}/model/enums/LoginVerifyModeEnum.java (91%) create mode 100644 src/main/java/im/zhaojun/zfile/module/user/model/request/CheckUserDuplicateRequest.java create mode 100644 src/main/java/im/zhaojun/zfile/module/user/model/request/CopyUserRequest.java create mode 100644 src/main/java/im/zhaojun/zfile/module/user/model/request/QueryUserRequest.java create mode 100644 src/main/java/im/zhaojun/zfile/module/user/model/request/ResetAdminUserNameAndPasswordRequest.java create mode 100644 src/main/java/im/zhaojun/zfile/module/user/model/request/SaveUserRequest.java create mode 100644 src/main/java/im/zhaojun/zfile/module/user/model/request/UpdateUserPwdRequest.java create mode 100644 src/main/java/im/zhaojun/zfile/module/user/model/request/UserLoginRequest.java create mode 100644 src/main/java/im/zhaojun/zfile/module/user/model/request/VerifyLoginTwoFactorAuthenticatorRequest.java create mode 100644 src/main/java/im/zhaojun/zfile/module/user/model/response/UserDetailResponse.java create mode 100644 src/main/java/im/zhaojun/zfile/module/user/model/result/CheckLoginResult.java create mode 100644 src/main/java/im/zhaojun/zfile/module/user/model/result/LoginResult.java create mode 100644 src/main/java/im/zhaojun/zfile/module/user/model/result/LoginTwoFactorAuthenticatorResult.java create mode 100644 src/main/java/im/zhaojun/zfile/module/user/model/result/LoginVerifyImgResult.java create mode 100644 src/main/java/im/zhaojun/zfile/module/user/service/UserService.java create mode 100644 src/main/java/im/zhaojun/zfile/module/user/service/UserStorageSourceService.java rename src/main/java/im/zhaojun/zfile/module/{login/service => user/service/login}/ImgVerifyCodeService.java (68%) create mode 100644 src/main/java/im/zhaojun/zfile/module/user/service/login/LoginService.java rename src/main/java/im/zhaojun/zfile/module/{login/service => user/service/login}/TwoFactorAuthenticatorVerifyService.java (67%) create mode 100644 src/main/java/im/zhaojun/zfile/module/user/service/login/verify/LoginVerifyService.java create mode 100644 src/main/java/im/zhaojun/zfile/module/user/service/login/verify/impl/ImgCodeLoginVerifyService.java create mode 100644 src/main/java/im/zhaojun/zfile/module/user/service/login/verify/impl/PasswordVerifyService.java create mode 100644 src/main/java/im/zhaojun/zfile/module/user/service/login/verify/impl/TwoFactorAuthLoginVerifyService.java create mode 100644 src/main/java/im/zhaojun/zfile/module/user/utils/PasswordVerifyUtils.java delete mode 100644 src/main/java/springfox/documentation/swagger2/web/Swagger2ControllerWebFlux.java delete mode 100644 src/main/java/springfox/documentation/swagger2/web/Swagger2ControllerWebMvc.java create mode 100644 src/main/resources/db/migration-mysql/V10__system_config_add_field_webdav.sql create mode 100644 src/main/resources/db/migration-mysql/V23__system_config_add_field_only_office_secret_field.sql create mode 100644 src/main/resources/db/migration-mysql/V24__system_config_add_field_enable_hover_menu_field.sql create mode 100644 src/main/resources/db/migration-mysql/V25__system_config_add_field_site_access_field.sql create mode 100644 src/main/resources/db/migration-mysql/V26__system_config_add_field_login_verify.sql create mode 100644 src/main/resources/db/migration-mysql/V27__add_table_login_log.sql create mode 100644 src/main/resources/db/migration-mysql/V28__add_multi_user.sql create mode 100644 src/main/resources/db/migration-mysql/V29__system_config_add_field_login_verify.sql create mode 100644 src/main/resources/db/migration-mysql/V30__delete_storage_source_auto_cors_config.sql create mode 100644 src/main/resources/db/migration-mysql/V31__system_config_add_field_webdav.sql create mode 100644 src/main/resources/db/migration-mysql/V32__system_config_delete_domain_field.sql create mode 100644 src/main/resources/db/migration-mysql/V33__storage_source_config_update_field.sql create mode 100644 src/main/resources/db/migration-mysql/V34__storage_source_config_update_field.sql create mode 100644 src/main/resources/db/migration-mysql/V35__system_config_add_field_login_log_mode.sql create mode 100644 src/main/resources/db/migration-mysql/V36__user_add_field_salt.sql create mode 100644 src/main/resources/db/migration-mysql/V37__set_login_log_model_default_off.sql create mode 100644 src/main/resources/db/migration-mysql/V38__update_login_log_ip_field_length.sql create mode 100644 src/main/resources/db/migration-mysql/V40__system_config_add_field_mobile_layout.sql create mode 100644 src/main/resources/db/migration-mysql/V41__system_config_add_custom_office_suffix.sql create mode 100644 src/main/resources/db/migration-mysql/V42__system_config_add_guest_index_html.sql create mode 100644 src/main/resources/db/migration-mysql/V43__set_2fa_default_value.sql create mode 100644 src/main/resources/db/migration-mysql/V5__add_permission_config_table.sql create mode 100644 src/main/resources/db/migration-mysql/V6__system_config_add_field_auth_code.sql create mode 100644 src/main/resources/db/migration-sqlite/V10__system_config_add_field_webdav.sql create mode 100644 src/main/resources/db/migration-sqlite/V23__system_config_add_field_only_office_secret_field.sql create mode 100644 src/main/resources/db/migration-sqlite/V24__system_config_add_field_enable_hover_menu_field.sql create mode 100644 src/main/resources/db/migration-sqlite/V25__system_config_add_field_site_access_field.sql create mode 100644 src/main/resources/db/migration-sqlite/V26__system_config_add_field_login_verify.sql create mode 100644 src/main/resources/db/migration-sqlite/V27__add_table_login_log.sql create mode 100644 src/main/resources/db/migration-sqlite/V28__add_multi_user.sql create mode 100644 src/main/resources/db/migration-sqlite/V29__system_config_add_field_login_verify.sql create mode 100644 src/main/resources/db/migration-sqlite/V30__delete_storage_source_auto_cors_config.sql create mode 100644 src/main/resources/db/migration-sqlite/V31__system_config_add_field_webdav.sql create mode 100644 src/main/resources/db/migration-sqlite/V32__system_config_delete_domain_field.sql create mode 100644 src/main/resources/db/migration-sqlite/V33__storage_source_config_update_field.sql create mode 100644 src/main/resources/db/migration-sqlite/V34__storage_source_config_update_field.sql create mode 100644 src/main/resources/db/migration-sqlite/V35__system_config_add_field_login_log_mode.sql create mode 100644 src/main/resources/db/migration-sqlite/V36__user_add_field_salt.sql create mode 100644 src/main/resources/db/migration-sqlite/V37__fix_user_create_time_field_to_timestamp.sql create mode 100644 src/main/resources/db/migration-sqlite/V38__set_login_log_model_default_off.sql create mode 100644 src/main/resources/db/migration-sqlite/V40__system_config_add_field_mobile_layout.sql create mode 100644 src/main/resources/db/migration-sqlite/V41__system_config_add_custom_office_suffix.sql create mode 100644 src/main/resources/db/migration-sqlite/V42__system_config_add_guest_index_html.sql create mode 100644 src/main/resources/db/migration-sqlite/V43__set_2fa_default_value.sql create mode 100644 src/main/resources/db/migration-sqlite/V5__add_permission_config_table.sql create mode 100644 src/main/resources/db/migration-sqlite/V6__system_config_add_field_auth_code.sql create mode 100644 src/main/resources/mapper/LoginLogMapper.xml create mode 100644 src/main/resources/mapper/PermissionConfigMapper.xml create mode 100644 src/main/resources/mapper/UserMapper.xml create mode 100644 src/main/resources/mapper/UserStorageSourceMapper.xml delete mode 100644 src/main/resources/static/assets/401-legacy.31f27c8f.js delete mode 100644 src/main/resources/static/assets/401-legacy.96207b9a.js delete mode 100644 src/main/resources/static/assets/401.b992c54d.js delete mode 100644 src/main/resources/static/assets/401.ef03378b.js delete mode 100644 src/main/resources/static/assets/401.f56d4c2a.svg delete mode 100644 src/main/resources/static/assets/403-legacy.914109bb.js delete mode 100644 src/main/resources/static/assets/403.07bdf36d.svg delete mode 100644 src/main/resources/static/assets/403.8d1625ed.css delete mode 100644 src/main/resources/static/assets/403.c3876e49.js delete mode 100644 src/main/resources/static/assets/404-legacy.269276b8.js delete mode 100644 src/main/resources/static/assets/404.32b48550.css delete mode 100644 src/main/resources/static/assets/404.46de94fc.svg delete mode 100644 src/main/resources/static/assets/404.5907bca3.js delete mode 100644 src/main/resources/static/assets/CheckBadgeIcon-legacy.4daa3dd8.js delete mode 100644 src/main/resources/static/assets/CheckBadgeIcon.b1bb4ad5.js delete mode 100644 src/main/resources/static/assets/FileGallery-legacy.240d14d0.js delete mode 100644 src/main/resources/static/assets/FileGallery.1e77159b.js delete mode 100644 src/main/resources/static/assets/FileGallery.94dcaf1e.css delete mode 100644 src/main/resources/static/assets/MarkdownViewer-legacy.cca71c6b.js delete mode 100644 src/main/resources/static/assets/MarkdownViewer.0360ebdf.js delete mode 100644 src/main/resources/static/assets/MarkdownViewer.925cd2a3.css delete mode 100644 src/main/resources/static/assets/OfficeViewer-legacy.c8f08ae6.js delete mode 100644 src/main/resources/static/assets/OfficeViewer.3a9ee45a.css delete mode 100644 src/main/resources/static/assets/OfficeViewer.e85f2f3b.js delete mode 100644 src/main/resources/static/assets/PdfViewer-legacy.27718d19.js delete mode 100644 src/main/resources/static/assets/PdfViewer.d597acc9.css delete mode 100644 src/main/resources/static/assets/PdfViewer.f53e17e1.js delete mode 100644 src/main/resources/static/assets/SvgIcon-legacy.d49e6d22.js delete mode 100644 src/main/resources/static/assets/SvgIcon.314dabdd.js delete mode 100644 src/main/resources/static/assets/TextViewer-legacy.899a419d.js delete mode 100644 src/main/resources/static/assets/TextViewer.46bc3ab5.js delete mode 100644 src/main/resources/static/assets/TextViewer.56ee38b8.css delete mode 100644 src/main/resources/static/assets/Three3dPreview-legacy.cc5520ca.js delete mode 100644 src/main/resources/static/assets/Three3dPreview.aa758a1e.js delete mode 100644 src/main/resources/static/assets/Three3dPreview.fecaa377.css delete mode 100644 src/main/resources/static/assets/VideoPlayer-legacy.45d2dec4.js delete mode 100644 src/main/resources/static/assets/VideoPlayer.5a35bf29.css delete mode 100644 src/main/resources/static/assets/VideoPlayer.de65c86a.js delete mode 100644 src/main/resources/static/assets/XCircleIcon-legacy.2726efd9.js delete mode 100644 src/main/resources/static/assets/XCircleIcon.af7bde30.js delete mode 100644 src/main/resources/static/assets/ZFormItem-legacy.19a998d2.js delete mode 100644 src/main/resources/static/assets/ZFormItem.e3c3668e.css delete mode 100644 src/main/resources/static/assets/ZFormItem.f027e70d.js delete mode 100644 src/main/resources/static/assets/_Uint8Array-legacy.5581156d.js delete mode 100644 src/main/resources/static/assets/_Uint8Array.4350f927.js delete mode 100644 src/main/resources/static/assets/_initCloneObject-legacy.54c17a65.js delete mode 100644 src/main/resources/static/assets/_initCloneObject.7947104d.js delete mode 100644 src/main/resources/static/assets/_storageId_-legacy.90b0dde8.js delete mode 100644 src/main/resources/static/assets/_storageId_-legacy.abec9893.js delete mode 100644 src/main/resources/static/assets/_storageId_-legacy.df51ee30.js delete mode 100644 src/main/resources/static/assets/_storageId_-legacy.e37c7dbf.js delete mode 100644 src/main/resources/static/assets/_storageId_.3b67f43e.js delete mode 100644 src/main/resources/static/assets/_storageId_.83e8203a.css delete mode 100644 src/main/resources/static/assets/_storageId_.852036d5.css delete mode 100644 src/main/resources/static/assets/_storageId_.943efe40.js delete mode 100644 src/main/resources/static/assets/_storageId_.977af154.js delete mode 100644 src/main/resources/static/assets/_storageId_.9dce9f5f.js delete mode 100644 src/main/resources/static/assets/_storageId_.dcd27aaf.css delete mode 100644 src/main/resources/static/assets/add-file.9d01a01a.svg delete mode 100644 src/main/resources/static/assets/add-folder-back.28141d7f.svg delete mode 100644 src/main/resources/static/assets/add-folder.7ef044d1.svg delete mode 100644 src/main/resources/static/assets/add.598ba734.svg delete mode 100644 src/main/resources/static/assets/admin-legacy.6f2f2bc6.js delete mode 100644 src/main/resources/static/assets/admin-login.db1d73c1.svg delete mode 100644 src/main/resources/static/assets/admin-setting-legacy.2d83c4ce.js delete mode 100644 src/main/resources/static/assets/admin-setting.46e4ebbe.js delete mode 100644 src/main/resources/static/assets/admin-storage-legacy.9d4d7743.js delete mode 100644 src/main/resources/static/assets/admin-storage.1997580a.js delete mode 100644 src/main/resources/static/assets/admin.5b179419.js delete mode 100644 src/main/resources/static/assets/alert-legacy.b748a33f.js delete mode 100644 src/main/resources/static/assets/alert.220a30c1.css delete mode 100644 src/main/resources/static/assets/alert.6929fc12.js delete mode 100644 src/main/resources/static/assets/aliyun.0f005258.svg delete mode 100644 src/main/resources/static/assets/badge-legacy.6a43abe6.js delete mode 100644 src/main/resources/static/assets/badge.1ec88ea5.js delete mode 100644 src/main/resources/static/assets/badge.28cc8ef5.css delete mode 100644 src/main/resources/static/assets/baidu.4a11f5ee.svg delete mode 100644 src/main/resources/static/assets/base-editor-legacy.3e59db2b.js delete mode 100644 src/main/resources/static/assets/base-editor-legacy.410912eb.js delete mode 100644 src/main/resources/static/assets/base-editor.079785da.css delete mode 100644 src/main/resources/static/assets/base-editor.5b9d1ebf.js delete mode 100644 src/main/resources/static/assets/base-legacy.28cad0e5.js delete mode 100644 src/main/resources/static/assets/base.b65bdcd1.js delete mode 100644 src/main/resources/static/assets/base.ef3e45d0.css delete mode 100644 src/main/resources/static/assets/basic-setting-legacy.167f29dc.js delete mode 100644 src/main/resources/static/assets/basic-setting.0f4062c7.js delete mode 100644 src/main/resources/static/assets/basic-setting.39c6db10.css delete mode 100644 src/main/resources/static/assets/button-legacy.96a0013c.js delete mode 100644 src/main/resources/static/assets/button.cca160de.css delete mode 100644 src/main/resources/static/assets/cancel.ba311fb8.svg delete mode 100644 src/main/resources/static/assets/card-layout.0aa8ea66.svg delete mode 100644 src/main/resources/static/assets/card-legacy.bb1d668a.js delete mode 100644 src/main/resources/static/assets/card.23e4220d.js delete mode 100644 src/main/resources/static/assets/card.7b41c195.css delete mode 100644 src/main/resources/static/assets/check.8203fabe.svg delete mode 100644 src/main/resources/static/assets/checkbox-legacy.dcd6a125.js delete mode 100644 src/main/resources/static/assets/checkbox.adc831f9.css delete mode 100644 src/main/resources/static/assets/checkbox.c90315f5.js delete mode 100644 src/main/resources/static/assets/clean.cc9d5116.svg delete mode 100644 src/main/resources/static/assets/common-legacy.2b8660dc.js delete mode 100644 src/main/resources/static/assets/common-legacy.503d039e.js delete mode 100644 src/main/resources/static/assets/common-legacy.6bc9e0b9.js delete mode 100644 src/main/resources/static/assets/common.1b9e00e4.js delete mode 100644 src/main/resources/static/assets/common.412949d0.js delete mode 100644 src/main/resources/static/assets/common.df582fdd.js delete mode 100644 src/main/resources/static/assets/copy.0d4a2f7b.svg delete mode 100644 src/main/resources/static/assets/copy2.6713dbfa.svg delete mode 100644 src/main/resources/static/assets/cpp-legacy.f27eb3b5.js delete mode 100644 src/main/resources/static/assets/cpp.842d3355.js delete mode 100644 src/main/resources/static/assets/css-legacy.d1c6b9ed.js delete mode 100644 src/main/resources/static/assets/css.2d6e4cde.js delete mode 100644 src/main/resources/static/assets/dayjs.min-legacy.bfc9e23f.js delete mode 100644 src/main/resources/static/assets/dayjs.min.65a08ee1.js delete mode 100644 src/main/resources/static/assets/debounce-legacy.45d124e9.js delete mode 100644 src/main/resources/static/assets/debounce.da5c4ce9.js delete mode 100644 src/main/resources/static/assets/default-legacy.76897c15.js delete mode 100644 src/main/resources/static/assets/default.841b220f.js delete mode 100644 src/main/resources/static/assets/delete.51a1dbaf.svg delete mode 100644 src/main/resources/static/assets/dialog-legacy.dfba30ef.js delete mode 100644 src/main/resources/static/assets/dialog.bc6c9616.js delete mode 100644 src/main/resources/static/assets/dialog.f31b232b.css delete mode 100644 src/main/resources/static/assets/directive-legacy.0554e5f2.js delete mode 100644 src/main/resources/static/assets/directive.d58852f6.js delete mode 100644 src/main/resources/static/assets/dockerfile-legacy.8b5f349f.js delete mode 100644 src/main/resources/static/assets/dockerfile.339e94af.js delete mode 100644 src/main/resources/static/assets/doge-cloud.06a44b40.svg delete mode 100644 src/main/resources/static/assets/download-mult.4332fce8.svg delete mode 100644 src/main/resources/static/assets/download-package.62191a76.svg delete mode 100644 src/main/resources/static/assets/download.f863f7c2.svg delete mode 100644 src/main/resources/static/assets/dropdown-item-legacy.76ce8010.js delete mode 100644 src/main/resources/static/assets/dropdown-item.4d20c061.css delete mode 100644 src/main/resources/static/assets/dropdown-item.f9529266.js delete mode 100644 src/main/resources/static/assets/dropdown-legacy.b5ca9dce.js delete mode 100644 src/main/resources/static/assets/dropdown.d1deefd6.js delete mode 100644 src/main/resources/static/assets/edit.a2e2c356.svg delete mode 100644 src/main/resources/static/assets/empty.d5d3dec8.svg delete mode 100644 src/main/resources/static/assets/error.69207ff6.svg delete mode 100644 src/main/resources/static/assets/event-legacy.39ad8904.js delete mode 100644 src/main/resources/static/assets/event.776e7e11.js delete mode 100644 src/main/resources/static/assets/file-legacy.78f43f4a.js delete mode 100644 src/main/resources/static/assets/file-legacy.ed045f34.js delete mode 100644 src/main/resources/static/assets/file-type-apk.e1385fc3.svg delete mode 100644 src/main/resources/static/assets/file-type-archive.f4b181db.svg delete mode 100644 src/main/resources/static/assets/file-type-audio.94e75894.svg delete mode 100644 src/main/resources/static/assets/file-type-back.ca0b84c2.svg delete mode 100644 src/main/resources/static/assets/file-type-css.5dfb9ed8.svg delete mode 100644 src/main/resources/static/assets/file-type-deb.5804788b.svg delete mode 100644 src/main/resources/static/assets/file-type-dll.6ea302d9.svg delete mode 100644 src/main/resources/static/assets/file-type-doc.a9d08313.svg delete mode 100644 src/main/resources/static/assets/file-type-document.b6e28e75.svg delete mode 100644 src/main/resources/static/assets/file-type-exe.b561ed3f.svg delete mode 100644 src/main/resources/static/assets/file-type-expression.3aaf04f1.svg delete mode 100644 src/main/resources/static/assets/file-type-file.8027949d.svg delete mode 100644 src/main/resources/static/assets/file-type-folder.35bcb02c.svg delete mode 100644 src/main/resources/static/assets/file-type-html.e37c00f9.svg delete mode 100644 src/main/resources/static/assets/file-type-image.689e667c.svg delete mode 100644 src/main/resources/static/assets/file-type-java.e50a4779.svg delete mode 100644 src/main/resources/static/assets/file-type-js.5c3b67c3.svg delete mode 100644 src/main/resources/static/assets/file-type-less.429d4f89.svg delete mode 100644 src/main/resources/static/assets/file-type-md.4dd32bf9.svg delete mode 100644 src/main/resources/static/assets/file-type-office.9af40573.svg delete mode 100644 src/main/resources/static/assets/file-type-pdf.1e018e0c.svg delete mode 100644 src/main/resources/static/assets/file-type-php.4feb4413.svg delete mode 100644 src/main/resources/static/assets/file-type-ppt.3d3e6af9.svg delete mode 100644 src/main/resources/static/assets/file-type-py.09b51350.svg delete mode 100644 src/main/resources/static/assets/file-type-rb.32961396.svg delete mode 100644 src/main/resources/static/assets/file-type-root.80ddc97b.svg delete mode 100644 src/main/resources/static/assets/file-type-rpm.559b18a5.svg delete mode 100644 src/main/resources/static/assets/file-type-rust.69994176.svg delete mode 100644 src/main/resources/static/assets/file-type-script.52b80ced.svg delete mode 100644 src/main/resources/static/assets/file-type-text.8ff306a0.svg delete mode 100644 src/main/resources/static/assets/file-type-three3d.e17881e7.svg delete mode 100644 src/main/resources/static/assets/file-type-vbs.22c9b0b7.svg delete mode 100644 src/main/resources/static/assets/file-type-video.888eeb7b.svg delete mode 100644 src/main/resources/static/assets/file-type-xls.db6934ae.svg delete mode 100644 src/main/resources/static/assets/file-type-xml.234b67f6.svg delete mode 100644 src/main/resources/static/assets/file-type-yaml.c1af7aa9.svg delete mode 100644 src/main/resources/static/assets/file-upload.65dff662.svg delete mode 100644 src/main/resources/static/assets/file.2ad0369e.css delete mode 100644 src/main/resources/static/assets/file.a8d0d277.js delete mode 100644 src/main/resources/static/assets/file.c473a8ba.css delete mode 100644 src/main/resources/static/assets/file.fce05f37.js delete mode 100644 src/main/resources/static/assets/files.a94428cd.svg delete mode 100644 src/main/resources/static/assets/focus-trap-legacy.be7f3f75.js delete mode 100644 src/main/resources/static/assets/focus-trap.2030fc87.js delete mode 100644 src/main/resources/static/assets/form-item-legacy.8eb59483.js delete mode 100644 src/main/resources/static/assets/form-item.2ed8d062.css delete mode 100644 src/main/resources/static/assets/form-item.5db46a7d.js delete mode 100644 src/main/resources/static/assets/ftp.c6a679c1.svg delete mode 100644 src/main/resources/static/assets/github-legacy.a6e57560.js delete mode 100644 src/main/resources/static/assets/github-legacy.f9623fea.js delete mode 100644 src/main/resources/static/assets/github.14b189fa.css delete mode 100644 src/main/resources/static/assets/github.afc55da0.js delete mode 100644 src/main/resources/static/assets/go-legacy.0e8fe384.js delete mode 100644 src/main/resources/static/assets/go.51190b50.js delete mode 100644 src/main/resources/static/assets/google-drive.d619e147.svg delete mode 100644 src/main/resources/static/assets/graphql-legacy.974a4aa9.js delete mode 100644 src/main/resources/static/assets/graphql.543bb499.js delete mode 100644 src/main/resources/static/assets/html-legacy.4b7ee50f.js delete mode 100644 src/main/resources/static/assets/html.97b9b105.js delete mode 100644 src/main/resources/static/assets/huawei.57d8824c.svg delete mode 100644 src/main/resources/static/assets/icon-legacy.45dc7071.js delete mode 100644 src/main/resources/static/assets/icon.cca102d8.css delete mode 100644 src/main/resources/static/assets/image-viewer-legacy.015bdb10.js delete mode 100644 src/main/resources/static/assets/image-viewer.4bd3c6ca.css delete mode 100644 src/main/resources/static/assets/image-viewer.50a93231.js delete mode 100644 src/main/resources/static/assets/image.69ca2c19.svg delete mode 100644 src/main/resources/static/assets/img-disable.16380239.svg delete mode 100644 src/main/resources/static/assets/img-enable.ca957fe0.svg delete mode 100644 src/main/resources/static/assets/index-legacy.021508e8.js delete mode 100644 src/main/resources/static/assets/index-legacy.2624a61d.js delete mode 100644 src/main/resources/static/assets/index-legacy.31b96dde.js delete mode 100644 src/main/resources/static/assets/index-legacy.492b6ebd.js delete mode 100644 src/main/resources/static/assets/index-legacy.4b4c1514.js delete mode 100644 src/main/resources/static/assets/index-legacy.4f8ed873.js delete mode 100644 src/main/resources/static/assets/index-legacy.600bb797.js delete mode 100644 src/main/resources/static/assets/index-legacy.677ea204.js delete mode 100644 src/main/resources/static/assets/index-legacy.6c7a445e.js delete mode 100644 src/main/resources/static/assets/index-legacy.762b537a.js delete mode 100644 src/main/resources/static/assets/index-legacy.7703180a.js delete mode 100644 src/main/resources/static/assets/index-legacy.7d58e80a.js delete mode 100644 src/main/resources/static/assets/index-legacy.89e00a81.js delete mode 100644 src/main/resources/static/assets/index-legacy.c304ec26.js delete mode 100644 src/main/resources/static/assets/index-legacy.d5045c51.js delete mode 100644 src/main/resources/static/assets/index-legacy.ebfe533e.js delete mode 100644 src/main/resources/static/assets/index-legacy.eea43e0b.js delete mode 100644 src/main/resources/static/assets/index.04bc4c90.js delete mode 100644 src/main/resources/static/assets/index.0f169a7e.js delete mode 100644 src/main/resources/static/assets/index.1f8079a9.css delete mode 100644 src/main/resources/static/assets/index.22cf9654.js delete mode 100644 src/main/resources/static/assets/index.2a66a81e.js delete mode 100644 src/main/resources/static/assets/index.2f5dd8a9.js delete mode 100644 src/main/resources/static/assets/index.3104d2da.css delete mode 100644 src/main/resources/static/assets/index.4527d98c.js delete mode 100644 src/main/resources/static/assets/index.5a412c6a.js delete mode 100644 src/main/resources/static/assets/index.690b9de8.js delete mode 100644 src/main/resources/static/assets/index.6db9fd01.css delete mode 100644 src/main/resources/static/assets/index.871a70d2.js delete mode 100644 src/main/resources/static/assets/index.9300971e.js delete mode 100644 src/main/resources/static/assets/index.94e7820c.js delete mode 100644 src/main/resources/static/assets/index.965ac11d.js delete mode 100644 src/main/resources/static/assets/index.c4c36dd3.js delete mode 100644 src/main/resources/static/assets/index.c6583322.css delete mode 100644 src/main/resources/static/assets/index.e52e3560.js delete mode 100644 src/main/resources/static/assets/index.e6797f5b.js delete mode 100644 src/main/resources/static/assets/index.e7a8d578.css delete mode 100644 src/main/resources/static/assets/index.efd5e5c6.js delete mode 100644 src/main/resources/static/assets/index.f33f0cba.js delete mode 100644 src/main/resources/static/assets/info.54497d89.svg delete mode 100644 src/main/resources/static/assets/ini-legacy.4a63db00.js delete mode 100644 src/main/resources/static/assets/ini.d9bdfd1c.js delete mode 100644 src/main/resources/static/assets/input-legacy.d4357d07.js delete mode 100644 src/main/resources/static/assets/input-number-legacy.2d301e91.js delete mode 100644 src/main/resources/static/assets/input-number.1615f7f4.css delete mode 100644 src/main/resources/static/assets/input-number.df41d4c6.js delete mode 100644 src/main/resources/static/assets/input.50bb0754.css delete mode 100644 src/main/resources/static/assets/install-legacy.b1db3ab1.js delete mode 100644 src/main/resources/static/assets/install-step.28fbaa8c.svg delete mode 100644 src/main/resources/static/assets/install.f1fa4e8e.js delete mode 100644 src/main/resources/static/assets/java-legacy.33e41b69.js delete mode 100644 src/main/resources/static/assets/java.676f2079.js delete mode 100644 src/main/resources/static/assets/javascript-legacy.9ed89cd9.js delete mode 100644 src/main/resources/static/assets/javascript.7432b13b.js delete mode 100644 src/main/resources/static/assets/kotlin-legacy.823db493.js delete mode 100644 src/main/resources/static/assets/kotlin.3c1f4364.js delete mode 100644 src/main/resources/static/assets/less-legacy.faf52af7.js delete mode 100644 src/main/resources/static/assets/less.d79b8bf9.js delete mode 100644 src/main/resources/static/assets/link-legacy.0bc47829.js delete mode 100644 src/main/resources/static/assets/link.0a44cb5c.svg delete mode 100644 src/main/resources/static/assets/link.f7c436ab.css delete mode 100644 src/main/resources/static/assets/link.fbecb10e.js delete mode 100644 src/main/resources/static/assets/loading-legacy.a92cda73.js delete mode 100644 src/main/resources/static/assets/loading.dd11abc3.css delete mode 100644 src/main/resources/static/assets/local.f6bc02e5.svg delete mode 100644 src/main/resources/static/assets/log-legacy.71b64f46.js delete mode 100644 src/main/resources/static/assets/log.b60ad730.css delete mode 100644 src/main/resources/static/assets/log.f30d0d0d.js delete mode 100644 src/main/resources/static/assets/login-legacy.98297f48.js delete mode 100644 src/main/resources/static/assets/login-legacy.bc2a8275.js delete mode 100644 src/main/resources/static/assets/login.1387933f.js delete mode 100644 src/main/resources/static/assets/login.53131169.css delete mode 100644 src/main/resources/static/assets/login.d278bc9b.js delete mode 100644 src/main/resources/static/assets/login.e2217ec4.svg delete mode 100644 src/main/resources/static/assets/markdown-legacy.3c9f539e.js delete mode 100644 src/main/resources/static/assets/markdown.387e712e.js delete mode 100644 src/main/resources/static/assets/minio.e5474b90.svg delete mode 100644 src/main/resources/static/assets/move.897d1439.svg delete mode 100644 src/main/resources/static/assets/mysql-legacy.d54b102a.js delete mode 100644 src/main/resources/static/assets/mysql.294467b0.js delete mode 100644 src/main/resources/static/assets/new-folder.549c271c.svg delete mode 100644 src/main/resources/static/assets/next.ed93fad9.svg delete mode 100644 src/main/resources/static/assets/notFound-legacy.c7d2c860.js delete mode 100644 src/main/resources/static/assets/notFound.345f1754.svg delete mode 100644 src/main/resources/static/assets/notFound.3cf6b4d2.js delete mode 100644 src/main/resources/static/assets/onedrive-china.f477f7d4.svg delete mode 100644 src/main/resources/static/assets/onedrive.1120a54d.svg delete mode 100644 src/main/resources/static/assets/overlay-legacy.7c160b60.js delete mode 100644 src/main/resources/static/assets/overlay.73d3f939.js delete mode 100644 src/main/resources/static/assets/overlay.dd389659.css delete mode 100644 src/main/resources/static/assets/php-legacy.8098c3de.js delete mode 100644 src/main/resources/static/assets/php.38bacf90.js delete mode 100644 src/main/resources/static/assets/plugin-vue_export-helper-legacy.7bb61c33.js delete mode 100644 src/main/resources/static/assets/plugin-vue_export-helper.21dcd24c.js delete mode 100644 src/main/resources/static/assets/polyfills-legacy.b01f53df.js delete mode 100644 src/main/resources/static/assets/polyfills-modern.2ccba09e.js delete mode 100644 src/main/resources/static/assets/popover-legacy.0eba315a.js delete mode 100644 src/main/resources/static/assets/popover.aabbd9ff.css delete mode 100644 src/main/resources/static/assets/popper-legacy.3e7fb573.js delete mode 100644 src/main/resources/static/assets/popper.aedd2598.js delete mode 100644 src/main/resources/static/assets/popper.e5c0055c.css delete mode 100644 src/main/resources/static/assets/prev.f23ef4a8.svg delete mode 100644 src/main/resources/static/assets/preview-legacy.20c2e7f6.js delete mode 100644 src/main/resources/static/assets/preview-legacy.ad944044.js delete mode 100644 src/main/resources/static/assets/preview.330b1af8.svg delete mode 100644 src/main/resources/static/assets/preview.a4eb0df8.js delete mode 100644 src/main/resources/static/assets/preview.ebfa3115.css delete mode 100644 src/main/resources/static/assets/prism-legacy.f70e0815.js delete mode 100644 src/main/resources/static/assets/prism.be667f0e.js delete mode 100644 src/main/resources/static/assets/python-legacy.29f825d3.js delete mode 100644 src/main/resources/static/assets/python.4937e608.js delete mode 100644 src/main/resources/static/assets/qiniu.6036c6bb.svg delete mode 100644 src/main/resources/static/assets/radio-legacy.ac4eca21.js delete mode 100644 src/main/resources/static/assets/radio.3ebff4f2.css delete mode 100644 src/main/resources/static/assets/radio.5a7362d7.js delete mode 100644 src/main/resources/static/assets/readme-editor-dialog-legacy.d3efc623.js delete mode 100644 src/main/resources/static/assets/readme-editor-dialog.b1308d32.js delete mode 100644 src/main/resources/static/assets/refresh.44905e7c.svg delete mode 100644 src/main/resources/static/assets/refs-legacy.309a8508.js delete mode 100644 src/main/resources/static/assets/refs.45a892d9.js delete mode 100644 src/main/resources/static/assets/request-legacy.01567c6f.js delete mode 100644 src/main/resources/static/assets/request.2ccc188b.js delete mode 100644 src/main/resources/static/assets/reset-password.b19e1ea5.svg delete mode 100644 src/main/resources/static/assets/route-block-legacy.2d113b05.js delete mode 100644 src/main/resources/static/assets/route-block.9b0645f8.js delete mode 100644 src/main/resources/static/assets/s3.280509b0.svg delete mode 100644 src/main/resources/static/assets/scroll-legacy.0bcf8f62.js delete mode 100644 src/main/resources/static/assets/scroll.06c8f1a5.js delete mode 100644 src/main/resources/static/assets/scrollbar-legacy.c0fd5057.js delete mode 100644 src/main/resources/static/assets/scrollbar.117c6324.js delete mode 100644 src/main/resources/static/assets/scrollbar.193d2636.css delete mode 100644 src/main/resources/static/assets/scss-legacy.9d707fe2.js delete mode 100644 src/main/resources/static/assets/scss.3d57ef5d.js delete mode 100644 src/main/resources/static/assets/search.8b68470c.svg delete mode 100644 src/main/resources/static/assets/select-all.4aaccd3e.svg delete mode 100644 src/main/resources/static/assets/select-legacy.9fc0fff6.js delete mode 100644 src/main/resources/static/assets/select.3bf869d2.css delete mode 100644 src/main/resources/static/assets/select.5ac33161.js delete mode 100644 src/main/resources/static/assets/server.f8a853a2.svg delete mode 100644 src/main/resources/static/assets/setting-legacy.ede95197.js delete mode 100644 src/main/resources/static/assets/setting.02a8da25.js delete mode 100644 src/main/resources/static/assets/settings.b524ea2e.svg delete mode 100644 src/main/resources/static/assets/sftp.c71078df.svg delete mode 100644 src/main/resources/static/assets/sharepoint-china.9907efac.svg delete mode 100644 src/main/resources/static/assets/sharepoint.1bd337fc.svg delete mode 100644 src/main/resources/static/assets/site-setting-legacy.e4779f73.js delete mode 100644 src/main/resources/static/assets/site-setting.15f4d0d4.js delete mode 100644 src/main/resources/static/assets/site-setting.2974b3f9.css delete mode 100644 src/main/resources/static/assets/sortable.esm-legacy.a574004a.js delete mode 100644 src/main/resources/static/assets/sortable.esm.a99254e8.js delete mode 100644 src/main/resources/static/assets/sql-legacy.79102732.js delete mode 100644 src/main/resources/static/assets/sql.45ce0396.js delete mode 100644 src/main/resources/static/assets/storage-copy-legacy.05703ca0.js delete mode 100644 src/main/resources/static/assets/storage-copy.93e6ed15.js delete mode 100644 src/main/resources/static/assets/storage-list-legacy.9d9ab7ef.js delete mode 100644 src/main/resources/static/assets/storage-list-legacy.d69e21ea.js delete mode 100644 src/main/resources/static/assets/storage-list.1cb4b2bc.js delete mode 100644 src/main/resources/static/assets/storage-list.dd71c919.js delete mode 100644 src/main/resources/static/assets/storage-list.eb7fff44.css delete mode 100644 src/main/resources/static/assets/success.ddc7b948.svg delete mode 100644 src/main/resources/static/assets/switch-legacy.e46c6730.js delete mode 100644 src/main/resources/static/assets/switch.1ecf928e.js delete mode 100644 src/main/resources/static/assets/switch.6cf08487.css delete mode 100644 src/main/resources/static/assets/table-column-legacy.ab1663ac.js delete mode 100644 src/main/resources/static/assets/table-column.5d9b8d93.css delete mode 100644 src/main/resources/static/assets/table-layout.e8017f7c.svg delete mode 100644 src/main/resources/static/assets/table-legacy.8ce31529.js delete mode 100644 src/main/resources/static/assets/table.29200b56.js delete mode 100644 src/main/resources/static/assets/table.75fd924f.css delete mode 100644 src/main/resources/static/assets/tag-legacy.1f08e7fb.js delete mode 100644 src/main/resources/static/assets/tag.9021a61b.css delete mode 100644 src/main/resources/static/assets/target.dcdb3ade.svg delete mode 100644 src/main/resources/static/assets/tencent.7b52a406.svg delete mode 100644 src/main/resources/static/assets/tool-close.4c4ed1a4.svg delete mode 100644 src/main/resources/static/assets/tool-close2.a77027c1.svg delete mode 100644 src/main/resources/static/assets/tool-delete.306633e9.svg delete mode 100644 src/main/resources/static/assets/tool-download-mult.3461b3f2.svg delete mode 100644 src/main/resources/static/assets/tool-download-package.0ea9ae5f.svg delete mode 100644 src/main/resources/static/assets/tool-download.b65826a2.svg delete mode 100644 src/main/resources/static/assets/tool-edit.405fd9c3.svg delete mode 100644 src/main/resources/static/assets/tool-link.6a893d0f.svg delete mode 100644 src/main/resources/static/assets/tool-move.fa418958.svg delete mode 100644 src/main/resources/static/assets/tool-preview.13c8406c.svg delete mode 100644 src/main/resources/static/assets/tool-setting.f36fb1c2.svg delete mode 100644 src/main/resources/static/assets/tooltip-legacy.7c89f687.js delete mode 100644 src/main/resources/static/assets/tooltip.afcd3f9d.js delete mode 100644 src/main/resources/static/assets/typescript-legacy.f76524cc.js delete mode 100644 src/main/resources/static/assets/typescript.be963bb3.js delete mode 100644 src/main/resources/static/assets/ufile.e305639b.svg delete mode 100644 src/main/resources/static/assets/unit-legacy.203f0ef2.js delete mode 100644 src/main/resources/static/assets/unit.5ed4b9cd.js delete mode 100644 src/main/resources/static/assets/update-password-legacy.47e4b370.js delete mode 100644 src/main/resources/static/assets/update-password.73d5bebe.js delete mode 100644 src/main/resources/static/assets/upload-1.f348e120.svg delete mode 100644 src/main/resources/static/assets/upload-folder.8e924b23.svg delete mode 100644 src/main/resources/static/assets/upload.a9eeb994.svg delete mode 100644 src/main/resources/static/assets/upyun.b24d52ea.svg delete mode 100644 src/main/resources/static/assets/use-outside-click-legacy.3e48aa6b.js delete mode 100644 src/main/resources/static/assets/use-outside-click.18deedcf.js delete mode 100644 src/main/resources/static/assets/useCommon-legacy.ff7c2bc7.js delete mode 100644 src/main/resources/static/assets/useCommon.eb7f3fe8.js delete mode 100644 src/main/resources/static/assets/useFileUpload-legacy.a81d91ec.js delete mode 100644 src/main/resources/static/assets/useFileUpload.a56ef935.js delete mode 100644 src/main/resources/static/assets/useLinkSetting-legacy.c2769623.js delete mode 100644 src/main/resources/static/assets/useLinkSetting.2b12bf8c.js delete mode 100644 src/main/resources/static/assets/validator-legacy.0afd2ceb.js delete mode 100644 src/main/resources/static/assets/validator.58bcb820.js delete mode 100644 src/main/resources/static/assets/video-download.3a226681.png delete mode 100644 src/main/resources/static/assets/video-iina.1db26170.png delete mode 100644 src/main/resources/static/assets/video-motrix.95ef7e61.png delete mode 100644 src/main/resources/static/assets/video-mxplayer-pro.10406916.png delete mode 100644 src/main/resources/static/assets/video-mxplayer.5dfa69aa.png delete mode 100644 src/main/resources/static/assets/video-thunder.5e7f4cec.png delete mode 100644 src/main/resources/static/assets/video-vlc.54e63a7e.png delete mode 100644 src/main/resources/static/assets/view-setting-legacy.25c262f4.js delete mode 100644 src/main/resources/static/assets/view-setting.71fba2df.css delete mode 100644 src/main/resources/static/assets/view-setting.756f34e1.js delete mode 100644 src/main/resources/static/assets/vue.runtime.esm-bundler-legacy.6fce6a6e.js delete mode 100644 src/main/resources/static/assets/vue.runtime.esm-bundler.e1e535bc.js delete mode 100644 src/main/resources/static/assets/vue3-clipboard.esm-bundler-legacy.fd707006.js delete mode 100644 src/main/resources/static/assets/vue3-clipboard.esm-bundler.34366eba.js delete mode 100644 src/main/resources/static/assets/vuepress-legacy.1caa391f.js delete mode 100644 src/main/resources/static/assets/vuepress-legacy.8fbe8168.js delete mode 100644 src/main/resources/static/assets/vuepress.549e0153.js delete mode 100644 src/main/resources/static/assets/vuepress.95aa9197.css delete mode 100644 src/main/resources/static/assets/warning.7093bb23.svg delete mode 100644 src/main/resources/static/assets/webdav.c885a54c.svg delete mode 100644 src/main/resources/static/assets/xml-legacy.72fec0f9.js delete mode 100644 src/main/resources/static/assets/xml.e1803075.js delete mode 100644 src/main/resources/static/assets/yaml-legacy.eb4e6c0a.js delete mode 100644 src/main/resources/static/assets/yaml.311a8425.js delete mode 100644 src/main/resources/static/assets/zfile-basic-legacy.3f61b849.js delete mode 100644 src/main/resources/static/assets/zfile-basic.58891cac.svg delete mode 100644 src/main/resources/static/assets/zfile-basic.8b129fdf.js delete mode 100644 src/main/resources/static/assets/zfile-horizontal-legacy.4a9c30e4.js delete mode 100644 src/main/resources/static/assets/zfile-horizontal.7507aff8.js delete mode 100644 src/main/resources/static/assets/zfile-horizontal.abd5aec9.svg delete mode 100644 src/main/resources/static/assets/zfile.76406368.svg delete mode 100644 src/main/resources/static/assets/zh-cn-legacy.8e639616.js delete mode 100644 src/main/resources/static/assets/zh-cn.198b941f.css delete mode 100644 src/main/resources/static/assets/zh-cn.1b322053.js delete mode 100644 src/main/resources/static/error.svg delete mode 100644 src/main/resources/static/favicon.svg delete mode 100644 src/main/resources/static/index.html delete mode 100644 src/main/resources/static/logo.png delete mode 100644 src/main/resources/static/zfile.config.json diff --git a/pom.xml b/pom.xml index 54d20cf..eba9566 100644 --- a/pom.xml +++ b/pom.xml @@ -4,29 +4,58 @@ im.zhaojun zfile - 4.1.5 + 4.2.0 zfile - ${packaging} + jar 一个在线的文件浏览系统 org.springframework.boot spring-boot-starter-parent - 2.7.12 + 3.3.2 - 1.8 + true + + 21 + 21 + 21 + + UTF-8 + UTF-8 + UTF-8 + 1.5.3.Final 2.0 2.14.1 - 3.41.2.2 - jar + 3.46.0.1 + 10.12.0 + + 1.18.32 + + + + software.amazon.awssdk + bom + 2.24.3 + pom + import + + + + - + + + org.graalvm.sdk + graal-sdk + 24.1.0 + provided + org.springframework.boot spring-boot-starter-web @@ -35,11 +64,16 @@ org.springframework.boot spring-boot-starter-aop + org.springframework.boot spring-boot-configuration-processor true + + org.springframework.boot + spring-boot-starter-data-redis + org.springframework.boot spring-boot-starter-cache @@ -53,12 +87,10 @@ spring-boot-starter-validation - com.mysql mysql-connector-j - 8.0.33 runtime @@ -68,12 +100,18 @@ org.flywaydb flyway-core - 7.15.0 + ${flyway.version} + + org.flywaydb + flyway-mysql + ${flyway.version} + + com.baomidou - mybatis-plus-boot-starter - 3.5.3.1 + mybatis-plus-spring-boot3-starter + 3.5.6 @@ -83,10 +121,10 @@ java-sdk 4.2.3 + - com.amazonaws - aws-java-sdk-s3 - 1.12.470 + software.amazon.awssdk + s3 com.qiniu @@ -94,53 +132,86 @@ 7.12.1 - com.jcraft + com.github.mwiede jsch - 0.1.55 + 0.2.20 com.github.lookfirst sardine - 5.10 + 5.12 + + + org.slf4j + slf4j-simple + + + + + com.google.api-client + google-api-client + 1.35.2 - cn.dev33 - sa-token-spring-boot-starter - 1.34.0.temp1 + sa-token-spring-boot3-starter + 1.38.0 com.github.xiaoymin - knife4j-spring-boot-starter - 3.0.3 + knife4j-openapi3-jakarta-spring-boot-starter + 4.5.0 - + + + + + cn.hutool hutool-all - 5.8.18 + 5.8.28 + + org.apache.poi + poi-ooxml + 5.2.5 + + + org.apache.commons + commons-compress + + + + + + org.apache.commons + commons-compress + 1.26.2 + compile + + org.projectlombok lombok - 1.18.24 provided - - org.apache.httpcomponents - httpclient - + commons-net commons-net - 3.9.0 + 3.11.0 + + + org.apache.commons + commons-pool2 com.squareup.okhttp3 @@ -154,7 +225,7 @@ com.google.guava guava - 30.1.1-jre + 33.2.0-jre org.mapstruct @@ -168,7 +239,7 @@ dev.samstevens.totp - totp-spring-boot-starter + totp 1.7.1 @@ -179,13 +250,17 @@ org.json json - 20230227 + 20231013 org.apache.httpcomponents httpmime 4.5.13 + + org.apache.httpcomponents.client5 + httpclient5 + org.bouncycastle bcprov-jdk15on @@ -206,8 +281,15 @@ com.alibaba dns-cache-manipulator - 1.8.1 + 1.8.2 + + + com.github.oshi + oshi-core + 6.6.3 + + @@ -215,21 +297,13 @@ org.springframework.boot spring-boot-maven-plugin - - - - org.projectlombok - lombok - - - org.apache.maven.plugins maven-compiler-plugin - 1.8 - 1.8 + 21 + 21 UTF-8 @@ -240,7 +314,7 @@ org.projectlombok lombok - 1.18.16 + 1.18.32 org.projectlombok @@ -257,51 +331,49 @@ + - enable-war-packaging - - - packaging - war - - + native - com.uyoqu.framework - maven-plugin-starter - 1.0.0 + org.graalvm.buildtools + native-maven-plugin + true - package + build-native - bin + compile-no-fork - - - -Djava.security.egd=file:/dev/./urandom - -Dfile.encoding=utf-8 - -Djava.net.preferIPv4Stack=false - -Djava.net.preferIPv4Addresses=true - -Djava.awt.headless=true - - + package + + false + ${project.name} + + true + + + --add-opens=java.base/java.net=ALL-UNNAMED + --add-opens=java.base/sun.net=ALL-UNNAMED + + + + -march=compatibility + -H:+AddAllCharsets + --features=im.zhaojun.zfile.aot.LambdaRegistrationFeature + --features=im.zhaojun.zfile.aot.BouncyCastleFeature + --features=im.zhaojun.zfile.aot.SQLiteNativeConfiguration + + + - - enable-jar-packaging - - - packaging - jar - - - \ No newline at end of file diff --git a/src/main/java/com/baomidou/mybatisplus/core/handlers/MybatisEnumTypeHandler.java b/src/main/java/com/baomidou/mybatisplus/core/handlers/MybatisEnumTypeHandler.java deleted file mode 100644 index b4674eb..0000000 --- a/src/main/java/com/baomidou/mybatisplus/core/handlers/MybatisEnumTypeHandler.java +++ /dev/null @@ -1,173 +0,0 @@ -/* - * Copyright (c) 2011-2022, baomidou (jobob@qq.com). - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.baomidou.mybatisplus.core.handlers; - -import com.baomidou.mybatisplus.annotation.EnumValue; -import com.baomidou.mybatisplus.annotation.IEnum; -import com.baomidou.mybatisplus.core.toolkit.CollectionUtils; -import com.baomidou.mybatisplus.core.toolkit.ExceptionUtils; -import com.baomidou.mybatisplus.core.toolkit.ReflectionKit; -import com.baomidou.mybatisplus.core.toolkit.StringUtils; -import org.apache.ibatis.reflection.DefaultReflectorFactory; -import org.apache.ibatis.reflection.MetaClass; -import org.apache.ibatis.reflection.ReflectorFactory; -import org.apache.ibatis.reflection.invoker.Invoker; -import org.apache.ibatis.type.BaseTypeHandler; -import org.apache.ibatis.type.JdbcType; - -import java.lang.reflect.Field; -import java.math.BigDecimal; -import java.sql.CallableStatement; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.util.Arrays; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.concurrent.ConcurrentHashMap; - -/** - * 自定义枚举属性转换器 - * - * @author hubin - * @since 2017-10-11 - */ -public class MybatisEnumTypeHandler> extends BaseTypeHandler { - - private static final Map TABLE_METHOD_OF_ENUM_TYPES = new ConcurrentHashMap<>(); - private static final ReflectorFactory REFLECTOR_FACTORY = new DefaultReflectorFactory(); - private final Class enumClassType; - private final Class propertyType; - private final Invoker getInvoker; - - public MybatisEnumTypeHandler(Class enumClassType) { - if (enumClassType == null) { - throw new IllegalArgumentException("Type argument cannot be null"); - } - this.enumClassType = enumClassType; - MetaClass metaClass = MetaClass.forClass(enumClassType, REFLECTOR_FACTORY); - String name = "value"; - if (!IEnum.class.isAssignableFrom(enumClassType)) { - name = findEnumValueFieldName(this.enumClassType).orElseThrow(() -> new IllegalArgumentException(String.format("Could not find @EnumValue in Class: %s.", this.enumClassType.getName()))); - } - this.propertyType = ReflectionKit.resolvePrimitiveIfNecessary(metaClass.getGetterType(name)); - this.getInvoker = metaClass.getGetInvoker(name); - } - - /** - * 查找标记标记EnumValue字段 - * - * @param clazz class - * @return EnumValue字段 - * @since 3.3.1 - */ - public static Optional findEnumValueFieldName(Class clazz) { - if (clazz != null && clazz.isEnum()) { - String className = clazz.getName(); - return Optional.ofNullable(CollectionUtils.computeIfAbsent(TABLE_METHOD_OF_ENUM_TYPES, className, key -> { - Optional fieldOptional = findEnumValueAnnotationField(clazz); - return fieldOptional.map(Field::getName).orElse(null); - })); - } - return Optional.empty(); - } - - private static Optional findEnumValueAnnotationField(Class clazz) { - return Arrays.stream(clazz.getDeclaredFields()).filter(field -> field.isAnnotationPresent(EnumValue.class)).findFirst(); - } - - /** - * 判断是否为MP枚举处理 - * - * @param clazz class - * @return 是否为MP枚举处理 - * @since 3.3.1 - */ - public static boolean isMpEnums(Class clazz) { - return clazz != null && clazz.isEnum() && (IEnum.class.isAssignableFrom(clazz) || findEnumValueFieldName(clazz).isPresent()); - } - - @SuppressWarnings("Duplicates") - @Override - public void setNonNullParameter(PreparedStatement ps, int i, E parameter, JdbcType jdbcType) - throws SQLException { - if (jdbcType == null) { - ps.setObject(i, this.getValue(parameter)); - } else { - // see r3589 - ps.setObject(i, this.getValue(parameter), jdbcType.TYPE_CODE); - } - } - - @Override - public E getNullableResult(ResultSet rs, String columnName) throws SQLException { - Object value = rs.getObject(columnName); - if (null == value && rs.wasNull()) { - return null; - } - return this.valueOf(value); - } - - @Override - public E getNullableResult(ResultSet rs, int columnIndex) throws SQLException { - Object value = rs.getObject(columnIndex, this.propertyType); - if (null == value && rs.wasNull()) { - return null; - } - return this.valueOf(value); - } - - @Override - public E getNullableResult(CallableStatement cs, int columnIndex) throws SQLException { - Object value = cs.getObject(columnIndex, this.propertyType); - if (null == value && cs.wasNull()) { - return null; - } - return this.valueOf(value); - } - - private E valueOf(Object value) { - E[] es = this.enumClassType.getEnumConstants(); - return Arrays.stream(es).filter((e) -> equalsValue(value, getValue(e))).findAny().orElse(null); - } - - /** - * 值比较 - * - * @param sourceValue 数据库字段值 - * @param targetValue 当前枚举属性值 - * @return 是否匹配 - * @since 3.3.0 - */ - protected boolean equalsValue(Object sourceValue, Object targetValue) { - String sValue = StringUtils.toStringTrim(sourceValue); - String tValue = StringUtils.toStringTrim(targetValue); - if (sourceValue instanceof Number && targetValue instanceof Number - && new BigDecimal(sValue).compareTo(new BigDecimal(tValue)) == 0) { - return true; - } - return Objects.equals(sValue, tValue); - } - - private Object getValue(Object object) { - try { - return this.getInvoker.invoke(object, new Object[0]); - } catch (ReflectiveOperationException e) { - throw ExceptionUtils.mpe(e); - } - } -} \ No newline at end of file diff --git a/src/main/java/im/zhaojun/zfile/core/annotation/ApiLimit.java b/src/main/java/im/zhaojun/zfile/core/annotation/ApiLimit.java new file mode 100644 index 0000000..a0f7545 --- /dev/null +++ b/src/main/java/im/zhaojun/zfile/core/annotation/ApiLimit.java @@ -0,0 +1,33 @@ +package im.zhaojun.zfile.core.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.util.concurrent.TimeUnit; + +/** + * 接口限流注解 + * + * @author zhaojun + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface ApiLimit { + + /** + * 持续时间 + */ + int timeout(); + + /** + * 时间单位, 默认为秒 + */ + TimeUnit timeUnit() default TimeUnit.SECONDS; + + /** + * 单位时间内允许访问的最大次数 + */ + long maxCount(); + +} \ No newline at end of file diff --git a/src/main/java/im/zhaojun/zfile/core/annotation/DemoDisable.java b/src/main/java/im/zhaojun/zfile/core/annotation/DemoDisable.java new file mode 100644 index 0000000..50dcc4b --- /dev/null +++ b/src/main/java/im/zhaojun/zfile/core/annotation/DemoDisable.java @@ -0,0 +1,17 @@ +package im.zhaojun.zfile.core.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 演示系统禁用功能注解 + * + * @author zhaojun + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface DemoDisable { + +} \ No newline at end of file diff --git a/src/main/java/im/zhaojun/zfile/core/aspect/ApiLimitAspect.java b/src/main/java/im/zhaojun/zfile/core/aspect/ApiLimitAspect.java new file mode 100644 index 0000000..a7f4b7c --- /dev/null +++ b/src/main/java/im/zhaojun/zfile/core/aspect/ApiLimitAspect.java @@ -0,0 +1,74 @@ +package im.zhaojun.zfile.core.aspect; + +import cn.hutool.cache.CacheUtil; +import cn.hutool.cache.impl.TimedCache; +import cn.hutool.extra.servlet.JakartaServletUtil; +import im.zhaojun.zfile.core.annotation.ApiLimit; +import im.zhaojun.zfile.core.exception.ErrorCode; +import im.zhaojun.zfile.core.exception.core.BizException; +import im.zhaojun.zfile.core.util.RequestHolder; +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Before; +import org.aspectj.lang.annotation.Pointcut; +import org.aspectj.lang.reflect.MethodSignature; +import org.springframework.stereotype.Component; + +import java.lang.reflect.Method; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicLong; + +/** + * 接口限流切面, 通过注解 {@link ApiLimit} 进行限流. + * + * @author zhaojun + */ +@Aspect +@Component +public class ApiLimitAspect { + + private final TimedCache apiLimitTimedCache = CacheUtil.newTimedCache(1000); + + public static final String API_LIMIT_KEY_PREFIX = "api_limit_"; + + /** + * 定义一个切点(通过注解) + */ + @Pointcut("@annotation(im.zhaojun.zfile.core.annotation.ApiLimit)") + public void apiLimit() { + } + + /** + * 在标记了 {@link ApiLimit} 注解的方法执行前进行限流校验. + * + * @param joinPoint 切点 + */ + @Before("apiLimit()") + public void before(JoinPoint joinPoint) { + // 获取当前请求的方法上的注解中设置的值 + MethodSignature signature = (MethodSignature) joinPoint.getSignature(); + // 反射获取当前被调用的方法 + Method method = signature.getMethod(); + // 获取方法中的注解 + ApiLimit apiLimit = method.getDeclaredAnnotation(ApiLimit.class); + int timeout = apiLimit.timeout(); + TimeUnit timeUnit = apiLimit.timeUnit(); + long millis = timeUnit.toMillis(timeout); + long maxCount = apiLimit.maxCount(); + + // 获取请求相关信息 + String ip = JakartaServletUtil.getClientIP(RequestHolder.getRequest()); + + // 限制访问次数 + String key = API_LIMIT_KEY_PREFIX.concat(ip).concat(method.getName()); + AtomicLong atomicLong = apiLimitTimedCache.get(key, false); + if (atomicLong == null) { + apiLimitTimedCache.put(key, new AtomicLong(1), millis); + } else { + if (atomicLong.incrementAndGet() > maxCount) { + throw new BizException(ErrorCode.BIZ_ACCESS_TOO_FREQUENT); + } + } + } + +} \ No newline at end of file diff --git a/src/main/java/im/zhaojun/zfile/core/CommonResultControllerAdvice.java b/src/main/java/im/zhaojun/zfile/core/aspect/CommonResultControllerAdvice.java similarity index 87% rename from src/main/java/im/zhaojun/zfile/core/CommonResultControllerAdvice.java rename to src/main/java/im/zhaojun/zfile/core/aspect/CommonResultControllerAdvice.java index 8a89fa0..f4cc5df 100644 --- a/src/main/java/im/zhaojun/zfile/core/CommonResultControllerAdvice.java +++ b/src/main/java/im/zhaojun/zfile/core/aspect/CommonResultControllerAdvice.java @@ -1,5 +1,6 @@ -package im.zhaojun.zfile.core; +package im.zhaojun.zfile.core.aspect; +import im.zhaojun.zfile.core.constant.MdcConstant; import im.zhaojun.zfile.core.util.AjaxJson; import org.slf4j.MDC; import org.springframework.core.MethodParameter; @@ -15,6 +16,8 @@ import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice; /** + * Controller 切面, 用于处理返回值统一封装. + * * @author zhaojun */ @ControllerAdvice @@ -57,10 +60,9 @@ public class CommonResultControllerAdvice implements ResponseBodyAdvice // Get return body Object returnBody = bodyContainer.getValue(); - if (returnBody instanceof AjaxJson) { - // If the return body is instance of BaseResponse, then just do nothing - AjaxJson baseResponse = (AjaxJson) returnBody; - baseResponse.setTraceId(MDC.get("traceId")); + if (returnBody instanceof AjaxJson baseResponse) { + // 将 MDC 中的 TraceId 设置到返回值中 + baseResponse.setTraceId(MDC.get(MdcConstant.TRACE_ID)); } } diff --git a/src/main/java/im/zhaojun/zfile/core/aspect/DemoDisableAspect.java b/src/main/java/im/zhaojun/zfile/core/aspect/DemoDisableAspect.java new file mode 100644 index 0000000..f93769e --- /dev/null +++ b/src/main/java/im/zhaojun/zfile/core/aspect/DemoDisableAspect.java @@ -0,0 +1,45 @@ +package im.zhaojun.zfile.core.aspect; + +import im.zhaojun.zfile.core.annotation.DemoDisable; +import im.zhaojun.zfile.core.config.ZFileProperties; +import im.zhaojun.zfile.core.exception.ErrorCode; +import im.zhaojun.zfile.core.exception.core.BizException; +import jakarta.annotation.Resource; +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Before; +import org.aspectj.lang.annotation.Pointcut; +import org.springframework.stereotype.Component; + +/** + * 通过注解 {@link DemoDisable} 限制演示系统不可操作的功能. + * + * @author zhaojun + */ +@Aspect +@Component +public class DemoDisableAspect { + + @Resource + private ZFileProperties zFileProperties; + + /** + * 定义一个切点(通过注解) + */ + @Pointcut("@annotation(im.zhaojun.zfile.core.annotation.DemoDisable)") + public void demoDisable() { + } + + /** + * 在标记了 {@link DemoDisable} 注解的方法执行前进行限流校验. + * + * @param joinPoint 切点 + */ + @Before("demoDisable()") + public void before(JoinPoint joinPoint) { + if (zFileProperties.isDemoSite()) { + throw new BizException(ErrorCode.DEMO_SITE_DISABLE_OPERATOR); + } + } + +} \ No newline at end of file diff --git a/src/main/java/im/zhaojun/zfile/core/config/FlywayDbInitializer.java b/src/main/java/im/zhaojun/zfile/core/config/FlywayDbInitializer.java deleted file mode 100644 index c38073f..0000000 --- a/src/main/java/im/zhaojun/zfile/core/config/FlywayDbInitializer.java +++ /dev/null @@ -1 +0,0 @@ -package im.zhaojun.zfile.core.config; import cn.hutool.core.util.StrUtil; import org.flywaydb.core.Flyway; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.DependsOn; import javax.annotation.PostConstruct; import javax.annotation.Resource; import javax.sql.DataSource; import java.sql.SQLException; import java.util.Locale; /** * 数据库初始化 * * @author zhaojun */ @DependsOn("myBatisPlusConfig") @Configuration public class FlywayDbInitializer { public static final String[] SUPPORT_DB_TYPE = new String[]{"mysql", "sqlite"}; @Resource private DataSource dataSource; /** * 启动时根据当前数据库类型执行数据库初始化 */ @PostConstruct public void migrateFlyway() { try { String databaseProductName = dataSource.getConnection().getMetaData().getDatabaseProductName(); String dbType = databaseProductName.toLowerCase(Locale.ROOT); // 检查当前数据库类型是否支持 if (!StrUtil.equalsAnyIgnoreCase(dbType, SUPPORT_DB_TYPE)) { throw new RuntimeException("不支持的数据库类型 [" + dbType + "]"); } Flyway load = Flyway.configure().dataSource(dataSource).outOfOrder(true).locations("db/migration-" + dbType).load(); load.migrate(); } catch (SQLException e) { e.printStackTrace(); } } } \ No newline at end of file diff --git a/src/main/java/im/zhaojun/zfile/core/config/Knife4jConfiguration.java b/src/main/java/im/zhaojun/zfile/core/config/Knife4jConfiguration.java deleted file mode 100644 index c3eda55..0000000 --- a/src/main/java/im/zhaojun/zfile/core/config/Knife4jConfiguration.java +++ /dev/null @@ -1 +0,0 @@ -package im.zhaojun.zfile.core.config; import com.github.xiaoymin.knife4j.spring.extension.OpenApiExtensionResolver; import io.swagger.models.auth.In; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import springfox.documentation.builders.ApiInfoBuilder; import springfox.documentation.builders.PathSelectors; import springfox.documentation.builders.RequestHandlerSelectors; import springfox.documentation.builders.RequestParameterBuilder; import springfox.documentation.service.ApiInfo; import springfox.documentation.service.Contact; import springfox.documentation.service.RequestParameter; import springfox.documentation.spi.DocumentationType; import springfox.documentation.spring.web.plugins.Docket; import springfox.documentation.swagger2.annotations.EnableSwagger2; import java.util.ArrayList; import java.util.List; /** * @author zhaojun */ @Configuration @EnableSwagger2 public class Knife4jConfiguration { private final OpenApiExtensionResolver openApiExtensionResolver; @Autowired public Knife4jConfiguration(OpenApiExtensionResolver openApiExtensionResolver) { this.openApiExtensionResolver = openApiExtensionResolver; } /** * base api docket * * @return docket */ @Bean(value = "baseApi") public Docket baseApi() { String groupName = "前台功能"; return new Docket(DocumentationType.SWAGGER_2) .apiInfo(apiInfo()) .select() .apis(RequestHandlerSelectors.basePackage("im.zhaojun.zfile.module")) .paths(PathSelectors.ant("/admin/**").negate()) .build() .groupName(groupName) .extensions(openApiExtensionResolver.buildExtensions(groupName)); } /** * admin api docket * * @return docket */ @Bean(value = "adminApi") public Docket adminApi() { String groupName = "管理员功能"; return new Docket(DocumentationType.SWAGGER_2) .apiInfo(apiInfo()) // 全局请求参数 .globalRequestParameters(generateRequestParameters()) .select() .apis(RequestHandlerSelectors.basePackage("im.zhaojun.zfile.module")) .paths(PathSelectors.ant("/admin/**")) .build() .groupName(groupName) .extensions(openApiExtensionResolver.buildExtensions(groupName)); } /** * 获取通用的全局参数 * * @return 全局参数列表 */ private List generateRequestParameters(){ RequestParameterBuilder token = new RequestParameterBuilder(); List parameters = new ArrayList<>(); token.name("zfile-token").description("token").in(In.HEADER.toValue()).required(true).build(); parameters.add(token.build()); return parameters; } /** * api 基本信息描述 * * @return ApiInfo */ private ApiInfo apiInfo() { return new ApiInfoBuilder() .title("ZFILE 文档") .description("# 这是 ZFILE Restful 接口文档展示页面") .termsOfServiceUrl("https://www.zfile.vip") .contact(new Contact("zhaojun", "https://zfile.vip", "admin@zfile.vip")) .version("1.0") .build(); } } \ No newline at end of file diff --git a/src/main/java/im/zhaojun/zfile/core/config/MyBatisPlusConfig.java b/src/main/java/im/zhaojun/zfile/core/config/MyBatisPlusConfig.java deleted file mode 100644 index cafb042..0000000 --- a/src/main/java/im/zhaojun/zfile/core/config/MyBatisPlusConfig.java +++ /dev/null @@ -1,66 +0,0 @@ -package im.zhaojun.zfile.core.config; - -import cn.hutool.core.io.FileUtil; -import cn.hutool.core.util.StrUtil; -import com.baomidou.mybatisplus.annotation.DbType; -import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; -import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -import javax.annotation.PostConstruct; -import javax.annotation.Resource; -import javax.sql.DataSource; -import java.sql.SQLException; - -/** - * mybatis-plus 配置类 - * - * @author zhaojun - */ -@Slf4j -@Configuration -public class MyBatisPlusConfig { - - @Resource - private DataSource dataSource; - - @Value("${spring.datasource.driver-class-name}") - private String datasourceDriveClassName; - - @Value("${spring.datasource.url}") - private String datasourceUrl; - - /** - * 如果是 sqlite 数据库,自动创建数据库文件所在目录 - */ - @PostConstruct - public void init() { - if (StrUtil.equals(datasourceDriveClassName, "org.sqlite.JDBC")) { - String path = datasourceUrl.replace("jdbc:sqlite:", ""); - String folderPath = FileUtil.getParent(path, 1); - log.info("SQLite 数据库文件所在目录: [{}]", folderPath); - if (!FileUtil.exist(folderPath)) { - FileUtil.mkdir(folderPath); - log.info("检测到 SQLite 数据库文件所在目录不存在, 已自动创建."); - } else { - log.info("检测到 SQLite 数据库文件所在目录已存在, 无需自动创建."); - } - } - } - - /** - * mybatis plus 分页插件配置 - */ - @Bean - public MybatisPlusInterceptor mybatisPlusInterceptor() throws SQLException { - MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); - String databaseProductName = dataSource.getConnection().getMetaData().getDatabaseProductName(); - DbType dbType = DbType.getDbType(databaseProductName); - interceptor.addInnerInterceptor(new PaginationInnerInterceptor(dbType)); - return interceptor; - } - -} \ No newline at end of file diff --git a/src/main/java/im/zhaojun/zfile/core/config/RestTemplateConfig.java b/src/main/java/im/zhaojun/zfile/core/config/RestTemplateConfig.java deleted file mode 100644 index 86cef7b..0000000 --- a/src/main/java/im/zhaojun/zfile/core/config/RestTemplateConfig.java +++ /dev/null @@ -1,27 +0,0 @@ -package im.zhaojun.zfile.core.config; - -import im.zhaojun.zfile.core.httpclient.ZFileOkHttp3ClientHttpRequestFactory; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.web.client.RestTemplate; - -/** - * restTemplate 相关配置 - * - * @author zhaojun - */ -@Configuration -public class RestTemplateConfig { - - /** - * OneDrive 请求 RestTemplate. - * 获取 header 中的 storageId 来判断到底是哪个存储源 ID, 在请求头中添加 Bearer: Authorization {token} 信息, 用于 API 认证. - */ - @Bean - public RestTemplate oneDriveRestTemplate() { - RestTemplate restTemplate = new RestTemplate(); - restTemplate.setRequestFactory(new ZFileOkHttp3ClientHttpRequestFactory()); - return restTemplate; - } - -} \ No newline at end of file diff --git a/src/main/java/im/zhaojun/zfile/core/config/SaTokenConfigure.java b/src/main/java/im/zhaojun/zfile/core/config/SaTokenConfigure.java deleted file mode 100644 index e18f1db..0000000 --- a/src/main/java/im/zhaojun/zfile/core/config/SaTokenConfigure.java +++ /dev/null @@ -1 +0,0 @@ -package im.zhaojun.zfile.core.config; import cn.dev33.satoken.interceptor.SaInterceptor; import cn.dev33.satoken.router.SaRouter; import cn.dev33.satoken.stp.StpUtil; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; /** * SaToken 权限配置, 配置管理员才能访问管理员功能. * * @author zhaojun */ @Configuration public class SaTokenConfigure implements WebMvcConfigurer { /** * 注册权限校验拦截器, 拦截所有 /admin/** 请求, 但登陆相关的接口不需要认证. * @param registry * 拦截器注册器 */ @Override public void addInterceptors(InterceptorRegistry registry) { // 注册路由拦截器,自定义验证规则 registry.addInterceptor(new SaInterceptor(handle -> { // 根据路由划分模块,不同模块不同鉴权 SaRouter.match("/admin/**", StpUtil::checkLogin); // 忽略所有登陆相关接口 })).addPathPatterns("/**").excludePathPatterns("/admin/login", "/admin/login/**", "/admin"); } } \ No newline at end of file diff --git a/src/main/java/im/zhaojun/zfile/core/config/ZFileProperties.java b/src/main/java/im/zhaojun/zfile/core/config/ZFileProperties.java index 8a5f74f..b34d833 100644 --- a/src/main/java/im/zhaojun/zfile/core/config/ZFileProperties.java +++ b/src/main/java/im/zhaojun/zfile/core/config/ZFileProperties.java @@ -6,6 +6,8 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties import org.springframework.stereotype.Component; /** + * ZFile 配置类,将配置文件中的 zfile 配置项映射到该类中. + * * @author zhaojun */ @Data @@ -16,4 +18,20 @@ public class ZFileProperties { private boolean debug; + private String version; + + private boolean isDemoSite; + + private OAuth2Properties onedrive = new OAuth2Properties(); + private OAuth2Properties onedriveChina = new OAuth2Properties(); + private OAuth2Properties gd = new OAuth2Properties(); + + @Data + public static class OAuth2Properties { + private String clientId; + private String clientSecret; + private String redirectUri; + private String scope; + } + } \ No newline at end of file diff --git a/src/main/java/im/zhaojun/zfile/core/config/datasource/DataSourceBeanPostProcessor.java b/src/main/java/im/zhaojun/zfile/core/config/datasource/DataSourceBeanPostProcessor.java new file mode 100644 index 0000000..5af22e5 --- /dev/null +++ b/src/main/java/im/zhaojun/zfile/core/config/datasource/DataSourceBeanPostProcessor.java @@ -0,0 +1,123 @@ +package im.zhaojun.zfile.core.config.datasource; + + +import cn.hutool.core.io.FileUtil; +import cn.hutool.extra.spring.SpringUtil; +import com.zaxxer.hikari.HikariDataSource; +import im.zhaojun.zfile.core.util.StringUtils; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.boot.autoconfigure.flyway.FlywayProperties; +import org.springframework.core.PriorityOrdered; +import org.springframework.stereotype.Component; + +import java.io.File; +import java.util.List; + +/** + * 在 Spring 容器初始化时, 对数据源进行处理. + *
+ * 1. 针对 DataSource 进行处理,仅针对 sqlite: + *
    + *
  • 提前创建 sqlite 数据文件所在目录.
  • + *
  • 检测到版本更新时(pom.xml -> project.version)自动备份原数据库.
  • + *
+ *
+ * 2. 针对 Flyway 进行处理,根据数据库类型, 配置不同的 Flyway Migration Location: + *
    + *
  • SQLite 数据库使用 migration-sqlite 目录.
  • + *
  • MySQL 数据库使用 migration-mysql 目录.
  • + *
+ * + * @author zhaojun + */ +@Slf4j +@Component +public class DataSourceBeanPostProcessor implements BeanPostProcessor, PriorityOrdered { + + public static final String ZFILE_VERSION_PROPERTIES = "zfile.version"; + + public static final String DRIVE_CLASS_NAME_PROPERTIES = "spring.datasource.driver-class-name"; + + public static final String DATA_SOURCE_BEAN_NAME = "dataSource"; + + public static final String SQLITE_DRIVE_CLASS_NAME = "org.sqlite.JDBC"; + + public static final String MYSQL_DRIVE_CLASS_NAME = "com.mysql.cj.jdbc.Driver"; + + @Override + public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { + // 如果更改了数据源类型这里要修改 + if (bean instanceof HikariDataSource dataSource && DATA_SOURCE_BEAN_NAME.equals(beanName)) { + processSqliteDataSource(dataSource); + } else if (bean instanceof FlywayProperties flywayProperties) { + processFlywayLocations(flywayProperties); + } + return bean; + } + + /** + * 如果是 sqlite 数据库, 提前创建数据库文件所在目录.
+ * + * 如果检测到版本更新, 自动备份原数据库文件. + * + * @param dataSource + * 数据源 + */ + private void processSqliteDataSource(HikariDataSource dataSource) { + String driverClassName = dataSource.getDriverClassName(); + String jdbcUrl = dataSource.getJdbcUrl(); + if (StringUtils.equals(driverClassName, SQLITE_DRIVE_CLASS_NAME)) { + String path = jdbcUrl.replace("jdbc:sqlite:", ""); + String folderPath = FileUtil.getAbsolutePath(new File(path).getParentFile()); + log.info("SQLite 数据库文件所在目录: [{}]", folderPath); + File file = new File(folderPath); + if (!file.exists()) { + log.info("检测到 SQLite 数据库文件所在目录不存在, 已自动创建."); + if (!file.mkdirs()) { + log.error("SQLite 数据库文件创建失败."); + } + } else { + log.info("检测到 SQLite 数据库文件所在目录已存在, 无需自动创建."); + + // 更新版本时, 先自动备份数据库文件 + String version = SpringUtil.getProperty(ZFILE_VERSION_PROPERTIES); + if (StringUtils.isNotEmpty(version)) { + String backupPath = folderPath + "/zfile-update-" + version + "-backup.db"; + if (!FileUtil.exist(path)) { + log.error("检测到 SQLite 数据库文件不存在, 一般为初始化状态,无需备份."); + return; + } + if (FileUtil.exist(backupPath)) { + log.info("检测到 SQLite 数据库备份文件 [{}] 已存在, 无需再次备份.", backupPath); + } else { + FileUtil.copy(path, backupPath, false); + log.info("自动备份 SQLite 数据库文件到: [{}]", backupPath); + } + } + } + } + } + + /** + * 根据使用的不同数据库, 配置使用不同的 migration location + * + * @param flywayProperties + * flyway 配置项 + */ + private void processFlywayLocations(FlywayProperties flywayProperties) { + String driveClassName = SpringUtil.getProperty(DRIVE_CLASS_NAME_PROPERTIES); + if (SQLITE_DRIVE_CLASS_NAME.equals(driveClassName)) { + flywayProperties.setLocations(List.of("classpath:db/migration-sqlite")); + } else if (MYSQL_DRIVE_CLASS_NAME.equals(driveClassName)) { + flywayProperties.setLocations(List.of("classpath:db/migration-mysql")); + } + } + + @Override + public int getOrder() { + return Integer.MIN_VALUE; + } + +} \ No newline at end of file diff --git a/src/main/java/im/zhaojun/zfile/core/config/docs/Knife4jConfiguration.java b/src/main/java/im/zhaojun/zfile/core/config/docs/Knife4jConfiguration.java new file mode 100644 index 0000000..e7ff6af --- /dev/null +++ b/src/main/java/im/zhaojun/zfile/core/config/docs/Knife4jConfiguration.java @@ -0,0 +1,73 @@ +package im.zhaojun.zfile.core.config.docs; + +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.info.Contact; +import io.swagger.v3.oas.models.info.Info; +import io.swagger.v3.oas.models.info.License; +import io.swagger.v3.oas.models.media.StringSchema; +import io.swagger.v3.oas.models.parameters.HeaderParameter; +import org.springdoc.core.customizers.OperationCustomizer; +import org.springdoc.core.models.GroupedOpenApi; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * Knife4j 参数配置,区分前台功能和管理员功能,并为管理员接口增加统一 token header 配置. + * + * @author zhaojun + */ +@Configuration +public class Knife4jConfiguration { + + @Bean + public GroupedOpenApi groupedOpenApi() { + String groupName = "前台功能"; + return GroupedOpenApi.builder() + .group(groupName) + .packagesToScan("im.zhaojun.zfile.module") + .pathsToExclude("/admin/**") + .build(); + } + + @Bean + public GroupedOpenApi groupedOpenApi2() { + String groupName = "管理员功能"; + return GroupedOpenApi.builder() + .group(groupName) + .packagesToScan("im.zhaojun.zfile.module") + .pathsToMatch("/admin/**") + .addOperationCustomizer(globalOperationCustomizer()) + .build(); + } + + public OperationCustomizer globalOperationCustomizer() { + return (operation, handlerMethod) -> { + operation.addParametersItem(new HeaderParameter() + .name("zfile-token") + .description("token") + .required(true) + .schema(new StringSchema())); + return operation; + }; + } + + @Bean + public OpenAPI customOpenAPI() { + Contact contact = new Contact(); + contact.setName("zhaojun"); + contact.setUrl("https://zfile.vip"); + contact.setEmail("873019219@qq.com"); + + return new OpenAPI() + .info(new Info() + .title("ZFILE 文档") + .description("# 这是 ZFILE Restful 接口文档展示页面") + .termsOfService("https://www.zfile.vip") + .contact(contact) + .version("1.0") + .license(new License() + .name("Apache 2.0") + .url("http://doc.xiaominfo.com"))); + } + +} \ No newline at end of file diff --git a/src/main/java/im/zhaojun/zfile/core/config/jackson/JSONStringDeserializer.java b/src/main/java/im/zhaojun/zfile/core/config/jackson/JSONStringDeserializer.java new file mode 100644 index 0000000..01ab235 --- /dev/null +++ b/src/main/java/im/zhaojun/zfile/core/config/jackson/JSONStringDeserializer.java @@ -0,0 +1,25 @@ +package im.zhaojun.zfile.core.config.jackson; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; + +import java.io.IOException; + +/** + * JSON String 反序列化器, 用于将 JSON 字符串反序列化为 JSON 对象. + * + * @author zhaojun + */ +public class JSONStringDeserializer extends JsonDeserializer { + + @Override + public String deserialize(JsonParser p, DeserializationContext context) throws IOException { + JsonNode node = p.getCodec().readTree(p); + ObjectMapper mapper = new ObjectMapper(); + return mapper.writeValueAsString(node); + } + +} \ No newline at end of file diff --git a/src/main/java/im/zhaojun/zfile/core/config/jackson/JSONStringSerializer.java b/src/main/java/im/zhaojun/zfile/core/config/jackson/JSONStringSerializer.java new file mode 100644 index 0000000..aa71e0c --- /dev/null +++ b/src/main/java/im/zhaojun/zfile/core/config/jackson/JSONStringSerializer.java @@ -0,0 +1,22 @@ +package im.zhaojun.zfile.core.config.jackson; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; + +import java.io.IOException; + + +/** + * JSON String 序列化器, 用于将 JSON 字符串序列化为 JSON 对象. + * + * @author zhaojun + */ +public class JSONStringSerializer extends JsonSerializer { + + @Override + public void serialize(String value, JsonGenerator gen, SerializerProvider serializers) throws IOException { + gen.writeRawValue(value); + } + +} \ No newline at end of file diff --git a/src/main/java/im/zhaojun/zfile/core/config/mybatis/CollectionIntegerTypeHandler.java b/src/main/java/im/zhaojun/zfile/core/config/mybatis/CollectionIntegerTypeHandler.java new file mode 100644 index 0000000..443bf32 --- /dev/null +++ b/src/main/java/im/zhaojun/zfile/core/config/mybatis/CollectionIntegerTypeHandler.java @@ -0,0 +1,7 @@ +package im.zhaojun.zfile.core.config.mybatis; + +import java.util.Set; + +public class CollectionIntegerTypeHandler extends CollectionTypeHandler> { + +} \ No newline at end of file diff --git a/src/main/java/im/zhaojun/zfile/core/config/mybatis/CollectionStrTypeHandler.java b/src/main/java/im/zhaojun/zfile/core/config/mybatis/CollectionStrTypeHandler.java new file mode 100644 index 0000000..1ee2946 --- /dev/null +++ b/src/main/java/im/zhaojun/zfile/core/config/mybatis/CollectionStrTypeHandler.java @@ -0,0 +1,7 @@ +package im.zhaojun.zfile.core.config.mybatis; + +import java.util.Set; + +public class CollectionStrTypeHandler extends CollectionTypeHandler> { + +} \ No newline at end of file diff --git a/src/main/java/im/zhaojun/zfile/core/config/mybatis/CollectionTypeHandler.java b/src/main/java/im/zhaojun/zfile/core/config/mybatis/CollectionTypeHandler.java new file mode 100644 index 0000000..c5f1c61 --- /dev/null +++ b/src/main/java/im/zhaojun/zfile/core/config/mybatis/CollectionTypeHandler.java @@ -0,0 +1,121 @@ +package im.zhaojun.zfile.core.config.mybatis; + +import cn.hutool.core.convert.Convert; +import cn.hutool.core.util.NumberUtil; +import cn.hutool.core.util.StrUtil; +import org.apache.ibatis.type.BaseTypeHandler; +import org.apache.ibatis.type.JdbcType; +import org.apache.ibatis.type.MappedJdbcTypes; +import org.apache.ibatis.type.MappedTypes; +import org.springframework.core.ResolvableType; + +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.sql.CallableStatement; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.*; + +/** + * 自定义 Set 类型处理器, 用于处理数据库 VARCHAR 类型字段和 Java Set 类型属性之间的转换. + * 支持字符串格式为: "[a, b, c]". + * + * @author zhaojun + */ +@MappedJdbcTypes(JdbcType.VARCHAR) +public abstract class CollectionTypeHandler extends BaseTypeHandler { + + @Override + public void setNonNullParameter(PreparedStatement ps, int i, Object parameter, JdbcType jdbcType) + throws SQLException { + if (parameter instanceof Collection collection) { + StringJoiner joiner = new StringJoiner(","); + for (Object o : collection) { + joiner.add(Convert.toStr(o)); + } + ps.setString(i, joiner.toString()); + } else { + ps.setString(i, Convert.toStr(parameter)); + } + } + + @Override + public Object getNullableResult(ResultSet rs, String columnName) + throws SQLException { + String str = rs.getString(columnName); + return convertToEntityAttribute(str); + } + + @Override + public Object getNullableResult(ResultSet rs, int columnIndex) + throws SQLException { + String str = rs.getString(columnIndex); + return convertToEntityAttribute(str); + } + + @Override + public Object getNullableResult(CallableStatement cs, int columnIndex) + throws SQLException { + String str = cs.getString(columnIndex); + return convertToEntityAttribute(str); + } + + private Class collectionClazz; + + private Type innerType; + + /** + * 构造方法 + */ + public CollectionTypeHandler() { + ResolvableType resolvableType = ResolvableType.forClass(getClass()); + Type type = resolvableType.as(CollectionTypeHandler.class).getGeneric().getType(); + + if (type instanceof ParameterizedType parameterizedType) { + collectionClazz = (Class) parameterizedType.getRawType(); + // 获取实际类型参数(泛型参数,例如 List 中的 String) + Type[] actualTypeArguments = parameterizedType.getActualTypeArguments(); + + // 使用这些信息做进一步操作 + for (Type actualTypeArgument : actualTypeArguments) { + innerType = actualTypeArgument; + break; + } + } + } + + private Object convertToEntityAttribute(String dbData) { + if (StrUtil.isEmpty(dbData)) { + if (List.class.isAssignableFrom(collectionClazz)) { + return Collections.emptyList(); + } else if (Set.class.isAssignableFrom(collectionClazz)) { + return Collections.emptySet(); + } else { + return null; + } + } + + Collection collection; + + if (List.class.isAssignableFrom(collectionClazz)) { + collection = new ArrayList<>(); + } else if (Set.class.isAssignableFrom(collectionClazz)) { + collection = new HashSet<>(); + } else { + return null; + } + + String[] split = dbData.split(","); + for (String s : split) { + if (NumberUtil.isNumber(s)) { + collection.add(Convert.convert(Integer.class, s)); + } else { + collection.add(s); + } + } + + return collection; + } + +} \ No newline at end of file diff --git a/src/main/java/im/zhaojun/zfile/core/config/mybatis/MyBatisPlusConfig.java b/src/main/java/im/zhaojun/zfile/core/config/mybatis/MyBatisPlusConfig.java new file mode 100644 index 0000000..b105c36 --- /dev/null +++ b/src/main/java/im/zhaojun/zfile/core/config/mybatis/MyBatisPlusConfig.java @@ -0,0 +1,33 @@ +package im.zhaojun.zfile.core.config.mybatis; + +import com.baomidou.mybatisplus.annotation.DbType; +import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; +import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + + +import javax.sql.DataSource; +import java.sql.SQLException; + +/** + * mybatis-plus 配置类 + * + * @author zhaojun + */ +@Configuration +public class MyBatisPlusConfig { + + /** + * mybatis plus 分页插件配置 + */ + @Bean + public MybatisPlusInterceptor mybatisPlusInterceptor(DataSource dataSource) throws SQLException { + MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); + String databaseProductName = dataSource.getConnection().getMetaData().getDatabaseProductName(); + DbType dbType = DbType.getDbType(databaseProductName); + interceptor.addInnerInterceptor(new PaginationInnerInterceptor(dbType)); + return interceptor; + } + +} \ No newline at end of file diff --git a/src/main/java/im/zhaojun/zfile/core/config/MyDatabaseIdProvider.java b/src/main/java/im/zhaojun/zfile/core/config/mybatis/MyDatabaseIdProvider.java similarity index 69% rename from src/main/java/im/zhaojun/zfile/core/config/MyDatabaseIdProvider.java rename to src/main/java/im/zhaojun/zfile/core/config/mybatis/MyDatabaseIdProvider.java index 88d02ef..e85ca36 100644 --- a/src/main/java/im/zhaojun/zfile/core/config/MyDatabaseIdProvider.java +++ b/src/main/java/im/zhaojun/zfile/core/config/mybatis/MyDatabaseIdProvider.java @@ -1,4 +1,4 @@ -package im.zhaojun.zfile.core.config; +package im.zhaojun.zfile.core.config.mybatis; import org.apache.ibatis.mapping.DatabaseIdProvider; import org.springframework.stereotype.Component; @@ -7,6 +7,13 @@ import javax.sql.DataSource; import java.sql.Connection; import java.sql.SQLException; +/** + * MyBatis 数据库 ID Provider, 用于判断当前数据库类型来执行不同的 SQL 语句.
+ * 可在 xml 中使用 <if test="_databaseId = 'mysql'"> 来判断数据库类型.
+ * 也可以在外层使用,如 <delete id="xxx" databaseId="sqlite"> 来判断数据库类型. + * + * @author zhaojun + */ @Component public class MyDatabaseIdProvider implements DatabaseIdProvider { @@ -31,4 +38,4 @@ public class MyDatabaseIdProvider implements DatabaseIdProvider { return dbAlias; } -} +} \ No newline at end of file diff --git a/src/main/java/im/zhaojun/zfile/core/config/mybatis/MyMetaObjectHandler.java b/src/main/java/im/zhaojun/zfile/core/config/mybatis/MyMetaObjectHandler.java new file mode 100644 index 0000000..915313d --- /dev/null +++ b/src/main/java/im/zhaojun/zfile/core/config/mybatis/MyMetaObjectHandler.java @@ -0,0 +1,29 @@ +package im.zhaojun.zfile.core.config.mybatis; + +import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; +import lombok.extern.slf4j.Slf4j; +import org.apache.ibatis.reflection.MetaObject; +import org.springframework.stereotype.Component; + +import java.util.Date; + +/** + * MyBatis Plus 自动填充配置类 + * 用于自动填充 createTime 和 updateTime 字段 + * + * @author zhaojun + */ +@Slf4j +@Component +public class MyMetaObjectHandler implements MetaObjectHandler { + + @Override + public void insertFill(MetaObject metaObject) { + this.strictInsertFill(metaObject, "createTime", Date.class, new Date()); + } + + @Override + public void updateFill(MetaObject metaObject) { + this.strictUpdateFill(metaObject, "updateTime", Date.class, new Date()); + } +} \ No newline at end of file diff --git a/src/main/java/im/zhaojun/zfile/core/config/security/SaSessionForJacksonCustomized.java b/src/main/java/im/zhaojun/zfile/core/config/security/SaSessionForJacksonCustomized.java new file mode 100644 index 0000000..51c2eb8 --- /dev/null +++ b/src/main/java/im/zhaojun/zfile/core/config/security/SaSessionForJacksonCustomized.java @@ -0,0 +1,32 @@ +package im.zhaojun.zfile.core.config.security; + +import cn.dev33.satoken.session.SaSession; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +/** + * Jackson 定制版 SaSession,忽略 timeout 等属性的序列化 + * + * @author click33 + * @since 1.34.0 + */ +@JsonIgnoreProperties({"timeout"}) +public class SaSessionForJacksonCustomized extends SaSession { + + /** + * + */ + private static final long serialVersionUID = -7600983549653130681L; + + public SaSessionForJacksonCustomized() { + super(); + } + + /** + * 构建一个Session对象 + * @param id Session的id + */ + public SaSessionForJacksonCustomized(String id) { + super(id); + } + +} \ No newline at end of file diff --git a/src/main/java/im/zhaojun/zfile/core/config/security/SaTokenConfigure.java b/src/main/java/im/zhaojun/zfile/core/config/security/SaTokenConfigure.java new file mode 100644 index 0000000..536385f --- /dev/null +++ b/src/main/java/im/zhaojun/zfile/core/config/security/SaTokenConfigure.java @@ -0,0 +1,34 @@ +package im.zhaojun.zfile.core.config.security; + +import cn.dev33.satoken.interceptor.SaInterceptor; +import cn.dev33.satoken.router.SaRouter; +import cn.dev33.satoken.stp.StpUtil; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +/** + * SaToken 权限配置, 配置管理员才能访问管理员功能. + * + * @author zhaojun + */ +@Configuration +public class SaTokenConfigure implements WebMvcConfigurer { + + /** + * 注册权限校验拦截器, 拦截所有 /admin/** 请求,但不包含 /admin 因为这个是登录页面. + * + * @param registry + * 拦截器注册器 + */ + @Override + public void addInterceptors(InterceptorRegistry registry) { + registry.addInterceptor(new SaInterceptor(handle -> { + SaRouter.match("/admin/**", () -> { + StpUtil.checkLogin(); + StpUtil.checkRole("admin"); + }); + })).addPathPatterns("/**").excludePathPatterns("/admin"); + } + +} \ No newline at end of file diff --git a/src/main/java/im/zhaojun/zfile/core/config/security/SaTokenDaoRedisJackson.java b/src/main/java/im/zhaojun/zfile/core/config/security/SaTokenDaoRedisJackson.java new file mode 100644 index 0000000..7f4a384 --- /dev/null +++ b/src/main/java/im/zhaojun/zfile/core/config/security/SaTokenDaoRedisJackson.java @@ -0,0 +1,305 @@ +// +// Source code recreated from a .class file by IntelliJ IDEA +// (powered by FernFlower decompiler) +// + +package im.zhaojun.zfile.core.config.security; + +import cn.dev33.satoken.dao.SaTokenDao; +import cn.dev33.satoken.strategy.SaStrategy; +import cn.dev33.satoken.util.SaFoxUtil; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer; +import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer; +import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer; +import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer; +import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer; +import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; +import org.springframework.data.redis.serializer.StringRedisSerializer; +import org.springframework.stereotype.Component; + +import java.lang.reflect.Field; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.concurrent.TimeUnit; + +/** + * Sa-Token 持久层实现 [ Redis存储、Jackson序列化 ] + * + * @author click33 + * @since 1.34.0 + */ +@Component +@ConditionalOnProperty(name = "spring.data.redis.host") +public class SaTokenDaoRedisJackson implements SaTokenDao { + + public static final String DATE_TIME_PATTERN = "yyyy-MM-dd HH:mm:ss"; + public static final String DATE_PATTERN = "yyyy-MM-dd"; + public static final String TIME_PATTERN = "HH:mm:ss"; + public static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern(DATE_TIME_PATTERN); + public static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern(DATE_PATTERN); + public static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern(TIME_PATTERN); + + /** + * ObjectMapper 对象 (以 public 作用域暴露出此对象,方便开发者二次更改配置) + * + *

例如: + *

+     *      SaTokenDaoRedisJackson redisJackson = (SaTokenDaoRedisJackson) SaManager.getSaTokenDao();
+     *      redisJackson.objectMapper.xxx = xxx;
+     * 	
+ *

+ */ + public ObjectMapper objectMapper; + + /** + * String 读写专用 + */ + public StringRedisTemplate stringRedisTemplate; + + /** + * Object 读写专用 + */ + public RedisTemplate objectRedisTemplate; + + /** + * 标记:是否已初始化成功 + */ + public boolean isInit; + + @Autowired + public void init(RedisConnectionFactory connectionFactory) { + // 如果已经初始化成功了,就立刻退出,不重复初始化 + if(this.isInit) { + return; + } + + // 指定相应的序列化方案 + StringRedisSerializer keySerializer = new StringRedisSerializer(); + GenericJackson2JsonRedisSerializer valueSerializer = new GenericJackson2JsonRedisSerializer(); + + // 通过反射获取Mapper对象, 增加一些配置, 增强兼容性 + try { + Field field = GenericJackson2JsonRedisSerializer.class.getDeclaredField("mapper"); + field.setAccessible(true); + this.objectMapper = (ObjectMapper) field.get(valueSerializer); + + // 配置[忽略未知字段] + this.objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + + // 配置[时间类型转换] + JavaTimeModule timeModule = new JavaTimeModule(); + + // LocalDateTime序列化与反序列化 + timeModule.addSerializer(new LocalDateTimeSerializer(DATE_TIME_FORMATTER)); + timeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DATE_TIME_FORMATTER)); + + // LocalDate序列化与反序列化 + timeModule.addSerializer(new LocalDateSerializer(DATE_FORMATTER)); + timeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(DATE_FORMATTER)); + + // LocalTime序列化与反序列化 + timeModule.addSerializer(new LocalTimeSerializer(TIME_FORMATTER)); + timeModule.addDeserializer(LocalTime.class, new LocalTimeDeserializer(TIME_FORMATTER)); + + this.objectMapper.registerModule(timeModule); + + // 重写 SaSession 生成策略 + SaStrategy.instance.createSession = (sessionId) -> new SaSessionForJacksonCustomized(sessionId); + } catch (Exception e) { + System.err.println(e.getMessage()); + } + // 构建StringRedisTemplate + StringRedisTemplate stringTemplate = new StringRedisTemplate(); + stringTemplate.setConnectionFactory(connectionFactory); + stringTemplate.afterPropertiesSet(); + + // 构建RedisTemplate + RedisTemplate template = new RedisTemplate<>(); + template.setConnectionFactory(connectionFactory); + template.setKeySerializer(keySerializer); + template.setHashKeySerializer(keySerializer); + template.setValueSerializer(valueSerializer); + template.setHashValueSerializer(valueSerializer); + template.afterPropertiesSet(); + + // 开始初始化相关组件 + this.stringRedisTemplate = stringTemplate; + this.objectRedisTemplate = template; + + // 打上标记,表示已经初始化成功,后续无需再重新初始化 + this.isInit = true; + } + + + /** + * 获取Value,如无返空 + */ + @Override + public String get(String key) { + return stringRedisTemplate.opsForValue().get(key); + } + + /** + * 写入Value,并设定存活时间 (单位: 秒) + */ + @Override + public void set(String key, String value, long timeout) { + if(timeout == 0 || timeout <= SaTokenDao.NOT_VALUE_EXPIRE) { + return; + } + // 判断是否为永不过期 + if(timeout == SaTokenDao.NEVER_EXPIRE) { + stringRedisTemplate.opsForValue().set(key, value); + } else { + stringRedisTemplate.opsForValue().set(key, value, timeout, TimeUnit.SECONDS); + } + } + + /** + * 修修改指定key-value键值对 (过期时间不变) + */ + @Override + public void update(String key, String value) { + long expire = getTimeout(key); + // -2 = 无此键 + if(expire == SaTokenDao.NOT_VALUE_EXPIRE) { + return; + } + this.set(key, value, expire); + } + + /** + * 删除Value + */ + @Override + public void delete(String key) { + stringRedisTemplate.delete(key); + } + + /** + * 获取Value的剩余存活时间 (单位: 秒) + */ + @Override + public long getTimeout(String key) { + return stringRedisTemplate.getExpire(key); + } + + /** + * 修改Value的剩余存活时间 (单位: 秒) + */ + @Override + public void updateTimeout(String key, long timeout) { + // 判断是否想要设置为永久 + if(timeout == SaTokenDao.NEVER_EXPIRE) { + long expire = getTimeout(key); + if(expire == SaTokenDao.NEVER_EXPIRE) { + // 如果其已经被设置为永久,则不作任何处理 + } else { + // 如果尚未被设置为永久,那么再次set一次 + this.set(key, this.get(key), timeout); + } + return; + } + stringRedisTemplate.expire(key, timeout, TimeUnit.SECONDS); + } + + + + /** + * 获取Object,如无返空 + */ + @Override + public Object getObject(String key) { + return objectRedisTemplate.opsForValue().get(key); + } + + /** + * 写入Object,并设定存活时间 (单位: 秒) + */ + @Override + public void setObject(String key, Object object, long timeout) { + if(timeout == 0 || timeout <= SaTokenDao.NOT_VALUE_EXPIRE) { + return; + } + // 判断是否为永不过期 + if(timeout == SaTokenDao.NEVER_EXPIRE) { + objectRedisTemplate.opsForValue().set(key, object); + } else { + objectRedisTemplate.opsForValue().set(key, object, timeout, TimeUnit.SECONDS); + } + } + + /** + * 更新Object (过期时间不变) + */ + @Override + public void updateObject(String key, Object object) { + long expire = getObjectTimeout(key); + // -2 = 无此键 + if(expire == SaTokenDao.NOT_VALUE_EXPIRE) { + return; + } + this.setObject(key, object, expire); + } + + /** + * 删除Object + */ + @Override + public void deleteObject(String key) { + objectRedisTemplate.delete(key); + } + + /** + * 获取Object的剩余存活时间 (单位: 秒) + */ + @Override + public long getObjectTimeout(String key) { + return objectRedisTemplate.getExpire(key); + } + + /** + * 修改Object的剩余存活时间 (单位: 秒) + */ + @Override + public void updateObjectTimeout(String key, long timeout) { + // 判断是否想要设置为永久 + if(timeout == SaTokenDao.NEVER_EXPIRE) { + long expire = getObjectTimeout(key); + if(expire == SaTokenDao.NEVER_EXPIRE) { + // 如果其已经被设置为永久,则不作任何处理 + } else { + // 如果尚未被设置为永久,那么再次set一次 + this.setObject(key, this.getObject(key), timeout); + } + return; + } + objectRedisTemplate.expire(key, timeout, TimeUnit.SECONDS); + } + + + /** + * 搜索数据 + */ + @Override + public List searchData(String prefix, String keyword, int start, int size, boolean sortType) { + Set keys = stringRedisTemplate.keys(prefix + "*" + keyword + "*"); + List list = new ArrayList<>(keys); + return SaFoxUtil.searchList(list, start, size, sortType); + } + +} diff --git a/src/main/java/im/zhaojun/zfile/core/config/security/StpInterfaceImpl.java b/src/main/java/im/zhaojun/zfile/core/config/security/StpInterfaceImpl.java new file mode 100644 index 0000000..143da88 --- /dev/null +++ b/src/main/java/im/zhaojun/zfile/core/config/security/StpInterfaceImpl.java @@ -0,0 +1,44 @@ +package im.zhaojun.zfile.core.config.security; + +import cn.dev33.satoken.stp.StpInterface; +import cn.hutool.core.convert.Convert; +import im.zhaojun.zfile.module.user.service.UserService; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Component; + +import java.util.Collections; +import java.util.List; + +/** + * 自定义权限加载接口实现类 + * + * @author zhaojun + */ +@Component +public class StpInterfaceImpl implements StpInterface { + + private static final List ADMIN_ROLE_LIST = Collections.singletonList("admin"); + + public static final List EMPTY_ROLE_LIST = Collections.emptyList(); + + @Resource + private UserService userService; + + /** + * 返回一个账号所拥有的权限码集合,这里没用到这个功能,所以返回空集合 + */ + @Override + public List getPermissionList(Object loginId, String loginType) { + return Collections.emptyList(); + } + + /** + * 返回一个账号所拥有的角色标识集合 (权限与角色可分开校验) + */ + @Override + public List getRoleList(Object loginId, String loginType) { + boolean isAdmin = userService.isAdmin(Convert.toInt(loginId)); + return isAdmin ? ADMIN_ROLE_LIST : EMPTY_ROLE_LIST; + } + +} \ No newline at end of file diff --git a/src/main/java/im/zhaojun/zfile/core/config/JacksonEnumDeserializer.java b/src/main/java/im/zhaojun/zfile/core/config/spring/JacksonEnumDeserializer.java similarity index 93% rename from src/main/java/im/zhaojun/zfile/core/config/JacksonEnumDeserializer.java rename to src/main/java/im/zhaojun/zfile/core/config/spring/JacksonEnumDeserializer.java index b3c6732..071f6ca 100644 --- a/src/main/java/im/zhaojun/zfile/core/config/JacksonEnumDeserializer.java +++ b/src/main/java/im/zhaojun/zfile/core/config/spring/JacksonEnumDeserializer.java @@ -1,4 +1,4 @@ -package im.zhaojun.zfile.core.config; +package im.zhaojun.zfile.core.config.spring; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.BeanProperty; @@ -15,18 +15,17 @@ import java.lang.reflect.Method; import java.util.Objects; /** - * Jackson 枚举反序列化器 + * Jackson 枚举反序列化器, 用于将接收请求中的参数(一般为字符串)转换为枚举对象. * * @author zhaojun */ -@Slf4j @Setter +@Slf4j @JsonComponent public class JacksonEnumDeserializer extends JsonDeserializer> implements ContextualDeserializer { private Class clazz; - /** * 反序列化操作 * diff --git a/src/main/java/im/zhaojun/zfile/core/config/SpringCacheConfig.java b/src/main/java/im/zhaojun/zfile/core/config/spring/SpringCacheConfig.java similarity index 67% rename from src/main/java/im/zhaojun/zfile/core/config/SpringCacheConfig.java rename to src/main/java/im/zhaojun/zfile/core/config/spring/SpringCacheConfig.java index b1e8cdd..fca7aea 100644 --- a/src/main/java/im/zhaojun/zfile/core/config/SpringCacheConfig.java +++ b/src/main/java/im/zhaojun/zfile/core/config/spring/SpringCacheConfig.java @@ -1,7 +1,9 @@ -package im.zhaojun.zfile.core.config; +package im.zhaojun.zfile.core.config.spring; -import cn.hutool.core.util.BooleanUtil; +import im.zhaojun.zfile.core.config.security.SaTokenDaoRedisJackson; +import org.apache.commons.lang3.BooleanUtils; import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.cache.CacheManager; import org.springframework.cache.annotation.EnableCaching; import org.springframework.cache.concurrent.ConcurrentMapCacheManager; @@ -26,8 +28,9 @@ public class SpringCacheConfig { * 使用 TransactionAwareCacheManagerProxy 装饰 ConcurrentMapCacheManager,使其支持事务 (将 put、evict、clear 操作延迟到事务成功提交再执行.) */ @Bean + @ConditionalOnMissingBean(SaTokenDaoRedisJackson.class) public CacheManager cacheManager() { - return BooleanUtil.isFalse(dbCacheEnable) ? new NoOpCacheManager() : new TransactionAwareCacheManagerProxy(new ConcurrentMapCacheManager()); + return BooleanUtils.isNotTrue(dbCacheEnable) ? new NoOpCacheManager() : new TransactionAwareCacheManagerProxy(new ConcurrentMapCacheManager()); } } \ No newline at end of file diff --git a/src/main/java/im/zhaojun/zfile/core/config/StringToEnumConverterFactory.java b/src/main/java/im/zhaojun/zfile/core/config/spring/StringToEnumConverterFactory.java similarity index 91% rename from src/main/java/im/zhaojun/zfile/core/config/StringToEnumConverterFactory.java rename to src/main/java/im/zhaojun/zfile/core/config/spring/StringToEnumConverterFactory.java index 2ad353b..5e6dcee 100644 --- a/src/main/java/im/zhaojun/zfile/core/config/StringToEnumConverterFactory.java +++ b/src/main/java/im/zhaojun/zfile/core/config/spring/StringToEnumConverterFactory.java @@ -1,13 +1,14 @@ -package im.zhaojun.zfile.core.config; +package im.zhaojun.zfile.core.config.spring; import com.baomidou.mybatisplus.annotation.EnumValue; import com.baomidou.mybatisplus.annotation.IEnum; import com.baomidou.mybatisplus.core.toolkit.StringUtils; +import im.zhaojun.zfile.core.exception.core.SystemException; +import jakarta.validation.constraints.NotNull; import lombok.extern.slf4j.Slf4j; import org.springframework.core.convert.converter.Converter; import org.springframework.core.convert.converter.ConverterFactory; -import javax.validation.constraints.NotNull; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; @@ -70,7 +71,7 @@ public class StringToEnumConverterFactory implements ConverterFactory[\]^`{|} 这些特殊字符. */ @@ -49,20 +31,13 @@ public class WebMvcConfig implements WebMvcConfigurer { return webServerFactory; } - - @Bean - public WebServerFactoryCustomizer webServerFactoryCustomizer(){ - return factory -> { - ErrorPage error404Page = new ErrorPage(HttpStatus.NOT_FOUND, "/index.html"); - ErrorPage error200Page = new ErrorPage(HttpStatus.OK, "/index.html"); - Set errorPages = new HashSet<>(); - errorPages.add(error404Page); - errorPages.add(error200Page); - factory.setErrorPages(errorPages); - }; + /** + * 添加自定义枚举格式化器. + * @see StorageTypeEnum + */ + @Override + public void addFormatters(FormatterRegistry registry) { + registry.addConverterFactory(new StringToEnumConverterFactory()); } - - - - + } \ No newline at end of file diff --git a/src/main/java/im/zhaojun/zfile/core/config/totp/TotpAutoConfiguration.java b/src/main/java/im/zhaojun/zfile/core/config/totp/TotpAutoConfiguration.java new file mode 100644 index 0000000..1f51be1 --- /dev/null +++ b/src/main/java/im/zhaojun/zfile/core/config/totp/TotpAutoConfiguration.java @@ -0,0 +1,91 @@ +package im.zhaojun.zfile.core.config.totp; + +import dev.samstevens.totp.TotpInfo; +import dev.samstevens.totp.code.*; +import dev.samstevens.totp.qr.QrDataFactory; +import dev.samstevens.totp.qr.QrGenerator; +import dev.samstevens.totp.qr.ZxingPngQrGenerator; +import dev.samstevens.totp.recovery.RecoveryCodeGenerator; +import dev.samstevens.totp.secret.DefaultSecretGenerator; +import dev.samstevens.totp.secret.SecretGenerator; +import dev.samstevens.totp.time.SystemTimeProvider; +import dev.samstevens.totp.time.TimeProvider; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +@ConditionalOnClass({TotpInfo.class}) +@EnableConfigurationProperties({TotpProperties.class}) +public class TotpAutoConfiguration { + + private final TotpProperties props; + + @Autowired + public TotpAutoConfiguration(TotpProperties props) { + this.props = props; + } + + @Bean + @ConditionalOnMissingBean + public SecretGenerator secretGenerator() { + int length = this.props.getSecret().getLength(); + return new DefaultSecretGenerator(length); + } + + @Bean + @ConditionalOnMissingBean + public TimeProvider timeProvider() { + return new SystemTimeProvider(); + } + + @Bean + @ConditionalOnMissingBean + public HashingAlgorithm hashingAlgorithm() { + return HashingAlgorithm.SHA1; + } + + @Bean + @ConditionalOnMissingBean + public QrDataFactory qrDataFactory(HashingAlgorithm hashingAlgorithm) { + return new QrDataFactory(hashingAlgorithm, this.getCodeLength(), this.getTimePeriod()); + } + + @Bean + @ConditionalOnMissingBean + public QrGenerator qrGenerator() { + return new ZxingPngQrGenerator(); + } + + @Bean + @ConditionalOnMissingBean + public CodeGenerator codeGenerator(HashingAlgorithm algorithm) { + return new DefaultCodeGenerator(algorithm, this.getCodeLength()); + } + + @Bean + @ConditionalOnMissingBean + public CodeVerifier codeVerifier(CodeGenerator codeGenerator, TimeProvider timeProvider) { + DefaultCodeVerifier verifier = new DefaultCodeVerifier(codeGenerator, timeProvider); + verifier.setTimePeriod(this.getTimePeriod()); + verifier.setAllowedTimePeriodDiscrepancy(this.props.getTime().getDiscrepancy()); + return verifier; + } + + @Bean + @ConditionalOnMissingBean + public RecoveryCodeGenerator recoveryCodeGenerator() { + return new RecoveryCodeGenerator(); + } + + private int getCodeLength() { + return this.props.getCode().getLength(); + } + + private int getTimePeriod() { + return this.props.getTime().getPeriod(); + } +} \ No newline at end of file diff --git a/src/main/java/im/zhaojun/zfile/core/config/totp/TotpProperties.java b/src/main/java/im/zhaojun/zfile/core/config/totp/TotpProperties.java new file mode 100644 index 0000000..68b8aa5 --- /dev/null +++ b/src/main/java/im/zhaojun/zfile/core/config/totp/TotpProperties.java @@ -0,0 +1,85 @@ +package im.zhaojun.zfile.core.config.totp; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties( + prefix = "totp" +) +public class TotpProperties { + private static final int DEFAULT_SECRET_LENGTH = 32; + private static final int DEFAULT_CODE_LENGTH = 6; + private static final int DEFAULT_TIME_PERIOD = 30; + private static final int DEFAULT_TIME_DISCREPANCY = 1; + private final Secret secret = new Secret(); + private final Code code = new Code(); + private final Time time = new Time(); + + public TotpProperties() { + } + + public Secret getSecret() { + return this.secret; + } + + public Code getCode() { + return this.code; + } + + public Time getTime() { + return this.time; + } + + public static class Time { + private int period = 30; + private int discrepancy = 1; + + public Time() { + } + + public int getPeriod() { + return this.period; + } + + public void setPeriod(int period) { + this.period = period; + } + + public int getDiscrepancy() { + return this.discrepancy; + } + + public void setDiscrepancy(int discrepancy) { + this.discrepancy = discrepancy; + } + } + + public static class Code { + private int length = 6; + + public Code() { + } + + public int getLength() { + return this.length; + } + + public void setLength(int length) { + this.length = length; + } + } + + public static class Secret { + private int length = 32; + + public Secret() { + } + + public int getLength() { + return this.length; + } + + public void setLength(int length) { + this.length = length; + } + } +} diff --git a/src/main/java/im/zhaojun/zfile/core/constant/RuleTypeConstant.java b/src/main/java/im/zhaojun/zfile/core/constant/RuleTypeConstant.java new file mode 100644 index 0000000..7613cea --- /dev/null +++ b/src/main/java/im/zhaojun/zfile/core/constant/RuleTypeConstant.java @@ -0,0 +1,18 @@ +package im.zhaojun.zfile.core.constant; + +/** + * 规则表达式类型常量 + * + * @author zhaojun + */ +public class RuleTypeConstant { + + public static final String IP = "ip"; + + public static final String REGEX = "regex"; + + public static final String ANT_PATH = "antPath"; + + public static final String SPRING_SIMPLE = "springSimple"; + +} \ No newline at end of file diff --git a/src/main/java/im/zhaojun/zfile/core/constant/ZFileConstant.java b/src/main/java/im/zhaojun/zfile/core/constant/ZFileConstant.java index ee1a950..79ecd4b 100644 --- a/src/main/java/im/zhaojun/zfile/core/constant/ZFileConstant.java +++ b/src/main/java/im/zhaojun/zfile/core/constant/ZFileConstant.java @@ -12,10 +12,6 @@ import org.springframework.context.annotation.Configuration; @Configuration public class ZFileConstant { - public static final Character PATH_SEPARATOR_CHAR = '/'; - - public static final String PATH_SEPARATOR = "/"; - /** * 最大支持文本文件大小为 ? KB 的文件内容. */ diff --git a/src/main/java/im/zhaojun/zfile/core/constant/ZFileHttpHeaderConstant.java b/src/main/java/im/zhaojun/zfile/core/constant/ZFileHttpHeaderConstant.java new file mode 100644 index 0000000..85fac61 --- /dev/null +++ b/src/main/java/im/zhaojun/zfile/core/constant/ZFileHttpHeaderConstant.java @@ -0,0 +1,16 @@ +package im.zhaojun.zfile.core.constant; + +/** + * ZFile 自定义 HTTP 请求头常量 + * + * @author zhaojun + */ +public class ZFileHttpHeaderConstant { + + public static final String ZFILE_TOKEN = "Zfile-Token"; + + public static final String AXIOS_REQUEST = "Axios-Request"; + + public static final String AXIOS_FROM = "Axios-From"; + +} \ No newline at end of file diff --git a/src/main/java/im/zhaojun/zfile/core/controller/FrontIndexController.java b/src/main/java/im/zhaojun/zfile/core/controller/FrontIndexController.java index ae88c7f..9bb6e2a 100644 --- a/src/main/java/im/zhaojun/zfile/core/controller/FrontIndexController.java +++ b/src/main/java/im/zhaojun/zfile/core/controller/FrontIndexController.java @@ -1,17 +1,17 @@ package im.zhaojun.zfile.core.controller; -import cn.hutool.core.util.StrUtil; +import im.zhaojun.zfile.core.util.StringUtils; import im.zhaojun.zfile.module.config.model.dto.SystemConfigDTO; import im.zhaojun.zfile.module.config.service.SystemConfigService; -import org.apache.commons.io.IOUtils; -import org.springframework.core.io.ClassPathResource; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.autoconfigure.web.WebProperties; +import org.springframework.core.io.FileSystemResourceLoader; +import org.springframework.core.io.ResourceLoader; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; -import javax.annotation.Resource; -import java.io.IOException; -import java.io.InputStream; import java.nio.charset.StandardCharsets; /** @@ -19,12 +19,15 @@ import java.nio.charset.StandardCharsets; * * @author zhaojun */ +@Slf4j @Controller public class FrontIndexController { @Resource private SystemConfigService systemConfigService; + @Resource + private WebProperties webProperties; /** * 所有未找到的页面都跳转到首页, 用户解决 vue history 直接访问 404 的问题 @@ -32,26 +35,64 @@ public class FrontIndexController { * * @return 转发到 /index.html */ - @RequestMapping(value = {"/**/{[path:[^\\.]*}", "/"}) + @RequestMapping(value = { "/"}) @ResponseBody - public String redirect() throws IOException { + public String redirect() { // 读取 resources/static/index.html 文件修改 title 和 favicon 后返回 - ClassPathResource resource = new ClassPathResource("static/index.html"); - InputStream inputStream = resource.getInputStream(); - String content = IOUtils.toString(inputStream, StandardCharsets.UTF_8); + ResourceLoader resourceLoader = new FileSystemResourceLoader(); + String[] staticLocations = webProperties.getResources().getStaticLocations(); + // 如果 staticLocations 里没有包含 file:static/, 则手动添加 + boolean fileStaticExist = false; + for (String staticLocation : staticLocations) { + if (staticLocation.startsWith("file:")) { + fileStaticExist = true; + break; + } + } + if (!fileStaticExist) { + staticLocations = org.apache.commons.lang3.ArrayUtils.add(staticLocations, "file:static/"); + } + + for (String staticLocation : staticLocations) { + org.springframework.core.io.Resource resource = resourceLoader.getResource(staticLocation + "/index.html"); + boolean exists = resource.exists(); + if (exists) { + String content; + try { + content = resource.getContentAsString(StandardCharsets.UTF_8); + log.debug("读取 index.html 文件成功, 文件路径: {}", staticLocation); + } catch (Exception e) { + log.error("{} 资源存在但读取 index.html 文件失败.", staticLocation); + return "static index.html read error"; + } + + SystemConfigDTO systemConfig = systemConfigService.getSystemConfig(); + + // 替换为系统设置中的站点名称 + String siteName = systemConfig.getSiteName(); + if (StringUtils.isNotBlank(siteName)) { + content = content.replace("ZFile", "" + siteName + ""); + } + + // 替换为系统设置中的 favicon 地址 + String faviconUrl = systemConfig.getFaviconUrl(); + if (StringUtils.isNotBlank(faviconUrl)) { + content = content.replace("/favicon.svg", faviconUrl); + } + + return content; + } + } + + return "static index.html not found"; + } + + @RequestMapping(value = { "/guest"}) + @ResponseBody + public String guest() { SystemConfigDTO systemConfig = systemConfigService.getSystemConfig(); - String siteName = systemConfig.getSiteName(); - if (StrUtil.isNotBlank(siteName)) { - content = content.replace("ZFile", "" + siteName + ""); - } - - String faviconUrl = systemConfig.getFaviconUrl(); - if (StrUtil.isNotBlank(faviconUrl)) { - content = content.replace("/favicon.svg", faviconUrl); - } - - return content; + return systemConfig.getGuestIndexHtml(); } } \ No newline at end of file diff --git a/src/main/java/im/zhaojun/zfile/core/controller/LogController.java b/src/main/java/im/zhaojun/zfile/core/controller/LogController.java index b2529e8..916e1fc 100644 --- a/src/main/java/im/zhaojun/zfile/core/controller/LogController.java +++ b/src/main/java/im/zhaojun/zfile/core/controller/LogController.java @@ -3,9 +3,10 @@ package im.zhaojun.zfile.core.controller; import cn.hutool.core.date.DateUtil; import cn.hutool.core.util.ZipUtil; import com.github.xiaoymin.knife4j.annotations.ApiSort; +import im.zhaojun.zfile.core.annotation.DemoDisable; import im.zhaojun.zfile.core.util.FileResponseUtil; -import io.swagger.annotations.Api; -import io.swagger.annotations.ApiOperation; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.core.io.Resource; @@ -22,7 +23,7 @@ import java.util.Date; * * @author zhaojun */ -@Api(tags = "日志") +@Tag(name = "日志") @ApiSort(8) @Slf4j @RestController @@ -33,7 +34,8 @@ public class LogController { private String zfileLogPath; @GetMapping("/log/download") - @ApiOperation(value = "下载系统日志") + @Operation(summary = "下载系统日志") + @DemoDisable public ResponseEntity downloadLog() { if (log.isDebugEnabled()) { log.debug("下载诊断日志"); diff --git a/src/main/java/im/zhaojun/zfile/core/exception/ErrorCode.java b/src/main/java/im/zhaojun/zfile/core/exception/ErrorCode.java new file mode 100644 index 0000000..52decca --- /dev/null +++ b/src/main/java/im/zhaojun/zfile/core/exception/ErrorCode.java @@ -0,0 +1,135 @@ +package im.zhaojun.zfile.core.exception; + +import lombok.Getter; + +/** + * 异常信息枚举类 + * + * @author zhaojun + */ +@Getter +public enum ErrorCode { + + /** + * 系统异常 + */ + SYSTEM_ERROR("50000", "系统异常"), + INVALID_STORAGE_SOURCE("50001", "无效或初始化失败的存储源"), + DEMO_SITE_DISABLE_OPERATOR("50002", "演示站点不允许此操作"), + + /** + * 业务异常 4xxxx. + * 第二位为 0 时,是系统初始化相关错误 + * 第二位为 1 时,是前台(文件管理)错误 + * 第二位为 2 时,是登录错误 + * 第二位为 3 时,是管理员端错误 + */ + BIZ_ERROR("40000", "操作失败"), + BIZ_NOT_FOUND("40400", "NOT FOUND"), + + // 第二位为 0 时,是系统初始化相关错误 + BIZ_SYSTEM_ALREADY_INIT("40001", "系统已初始化,请勿重复初始化"), + BIZ_SYSTEM_INIT_ERROR("40002", "系统初始化错误"), + + // 第二位为 1 时,是前台(文件管理)错误 + BIZ_BAD_REQUEST("41000", "请求参数异常"), + BIZ_UNSUPPORTED_PROXY_DOWNLOAD("41001", "该存储源不支持代理下载"), + BIZ_INVALID_SIGNATURE("41002", "签名无效或下载地址已过期"), + BIZ_PREVIEW_FILE_SIZE_EXCEED("41003", "预览文本文件大小超出系统限制"), + BIZ_FILE_NOT_EXIST("41004", "文件不存在"), + BIZ_ACCESS_TOO_FREQUENT("41005", "请求太频繁了,请稍后再试"), + BIZ_UPLOAD_FILE_NOT_EMPTY("41006", "上传文件不能为空"), + BIZ_UPLOAD_FILE_ERROR("41010", "上传文件失败"), + BIZ_UPLOAD_FILE_TIMEOUT_ERROR("41026", "上传文件超时"), + BIZ_EXPIRE_TIME_ILLEGAL("41007", "过期时间不合法"), + BIZ_DELETE_FILE_NOT_EMPTY("41008", "非空文件夹不允许删除"), + BIZ_FILE_PATH_ILLEGAL("41009", "文件名/路径存在安全隐患"), + BIZ_DIRECT_LINK_NOT_ALLOWED("41011", "当前系统不允许使用直链"), + BIZ_SHORT_LINK_NOT_ALLOWED("41012", "当前系统不允许使用短链"), + BIZ_SHORT_LINK_EXPIRED("41013", "短链已失效"), + BIZ_SHORT_LINK_NOT_FOUNT("41014", "短链不存在"), + BIZ_DIRECT_LINK_EXPIRED("41015", "直链已失效"), + BIZ_STORAGE_NOT_SUPPORT_OPERATION("41016", "该存储类型不支持此操作"), + BIZ_STORAGE_NOT_FOUND("41017", "存储源不存在"), + BIZ_STORAGE_SOURCE_ILLEGAL_OPERATION("41018", "非法或未授权的操作"), + BIZ_STORAGE_SOURCE_FILE_FORBIDDEN("41019", "文件目录无访问权限"), + BIZ_STORAGE_SOURCE_FOLDER_PASSWORD_REQUIRED("41020", "此文件夹需要密码"), + BIZ_STORAGE_SOURCE_FOLDER_PASSWORD_ERROR("41021", "密码错误"), + BIZ_INVALID_FILE_NAME("41022", "文件名不合法"), + BIZ_UNSUPPORTED_OPERATION("41023", "不支持的操作"), + BIZ_FTP_CLIENT_POOL_FULL("41024", "FTP 客户端连接池已满"), + BIZ_SFTP_CLIENT_POOL_FULL("41025", "SFTP 客户端连接池已满"), + BIZ_FOLDER_NOT_EXIST("41026", "文件夹不存在"), + BIZ_UPLOAD_FILE_TYPE_NOT_ALLOWED("41027", "不允许上传的文件"), + BIZ_RENAME_FILE_TYPE_NOT_ALLOWED("41028", "不允许重命名到该名称"), + + // 第二位为 2 时,是登录错误 + BIZ_UNAUTHORIZED("42000", "未登录或未授权"), + BIZ_LOGIN_ERROR("42001", "登录失败, 账号或密码错误"), + BIZ_VERIFY_CODE_ERROR("42002", "验证码错误或已失效"), + + // 第二位为 3 时,是管理员端错误 + BIZ_ADMIN_ERROR("43000", "操作失败"), + BIZ_USER_NOT_EXIST("43001", "用户不存在"), + BIZ_USER_EXIST("43002", "用户已存在"), + BIZ_PASSWORD_NOT_SAME("43003", "两次密码不一致"), + BIZ_OLD_PASSWORD_ERROR("43004", "旧密码不匹配"), + BIZ_DELETE_BUILT_IN_USER("43005", "不能删除内置用户"), + BIZ_UNSUPPORTED_STORAGE_TYPE("43006", "不支持的存储类型"), + BIZ_STORAGE_KEY_EXIST("43007", "存储源别名已存在"), + BIZ_AUTO_GET_SHARE_POINT_SITES_ERROR("43008", "自动获取 SharePoint 网站列表失败"), + BIZ_ORIGINS_NOT_EMPTY("43009", "请先在 \"站点设置\" 中配置站点域名"), + BIZ_2FA_CODE_ERROR("43010", "两步验证失败"), + BIZ_STORAGE_INIT_ERROR("43011", "存储源初始化失败"), + BIZ_RULE_EXIST("43012", "规则已存在"), + + + + /** + * 通用的无权限异常 + */ + NO_FORBIDDEN("30000", "没有权限"), + + + /** + * 授权校验异常 + */ + PRO_AUTH_CODE_EMPTY("20000", "请先去后台 \"基本设置\" 填写 \"授权码\""), + PRO_CHECK_REFERER_EMPTY("20001", "Referer 无效,请检查服务端设置,20001"), // Referer 无效,请检查服务端设置 + PRO_CHECK_TIME_NO_SYNC("20002", "授权校验失败, 服务器时间异常,20002"), // 授权校验失败, 服务器时间异常. + PRO_AUTH_CODE_INVALID_ERROR("20003", "授权码无效, 请检查后台 \"站点设置\" 中的 \"授权码\" 20003"), + PRO_CHECK_UNKNOWN_ERROR("20004", "授权验证异常,未知异常,20098"), + PRO_MSG_ERROR("20005", null); + + private String code; + + private String message; + + ErrorCode(String code, String message) { + this.code = code; + this.message = message; + } + + /** + * 设置错误码 + * + * @param code 错误码 + * @return 返回当前枚举 + */ + public ErrorCode setCode(String code) { + this.code = code; + return this; + } + + /** + * 设置错误信息 + * + * @param message 错误信息 + * @return 返回当前枚举 + */ + public ErrorCode setMessage(String message) { + this.message = message; + return this; + } + +} \ No newline at end of file diff --git a/src/main/java/im/zhaojun/zfile/core/exception/GlobalExceptionHandler.java b/src/main/java/im/zhaojun/zfile/core/exception/GlobalExceptionHandler.java new file mode 100644 index 0000000..696bcbc --- /dev/null +++ b/src/main/java/im/zhaojun/zfile/core/exception/GlobalExceptionHandler.java @@ -0,0 +1,403 @@ +package im.zhaojun.zfile.core.exception; + +import cn.dev33.satoken.exception.NotLoginException; +import cn.dev33.satoken.exception.NotRoleException; +import im.zhaojun.zfile.core.controller.FrontIndexController; +import im.zhaojun.zfile.core.exception.biz.*; +import im.zhaojun.zfile.core.exception.core.BizException; +import im.zhaojun.zfile.core.exception.core.ErrorPageBizException; +import im.zhaojun.zfile.core.exception.core.SystemException; +import im.zhaojun.zfile.core.exception.status.*; +import im.zhaojun.zfile.core.exception.system.UploadFileFailSystemException; +import im.zhaojun.zfile.core.exception.system.ZFileAuthorizationSystemException; +import im.zhaojun.zfile.core.util.AjaxJson; +import im.zhaojun.zfile.core.util.RequestHolder; +import im.zhaojun.zfile.core.util.StringUtils; +import im.zhaojun.zfile.module.config.service.SystemConfigService; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.apache.catalina.connector.ClientAbortException; +import org.springframework.core.annotation.Order; +import org.springframework.http.HttpStatus; +import org.springframework.validation.BindException; +import org.springframework.validation.BindingResult; +import org.springframework.validation.FieldError; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.servlet.resource.NoResourceFoundException; +import org.sqlite.SQLiteException; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +/** + * 全局异常处理 + * + * @author zhaojun + */ +@ControllerAdvice +@Slf4j +@Order(1) +public class GlobalExceptionHandler { + + private static final ThreadLocal exceptionMessage = new ThreadLocal<>(); + + @Resource + private SystemConfigService systemConfigService; + + @Resource + private FrontIndexController frontIndexController; + + + private static final int MAX_FIND_CAUSE_EXCEPTION_DEPTH = 10; + + // ---------------------- status exception start ---------------------- + + @ExceptionHandler(value = UnauthorizedAccessException.class) + @ResponseBody + @ResponseStatus(HttpStatus.UNAUTHORIZED) + public AjaxJson unauthorizedAccessException() { + if (RequestHolder.isAxiosRequest()) { + return AjaxJson.getUnauthorizedResult(); + } + try { + String unauthorizedUrl = systemConfigService.getUnauthorizedUrl(); + RequestHolder.getResponse().sendRedirect(unauthorizedUrl); + } catch (IOException ex) { + return AjaxJson.getUnauthorizedResult(); + } + + return null; + } + + @ExceptionHandler(value = { + NotRoleException.class + }) + @ResponseBody + @ResponseStatus(HttpStatus.FORBIDDEN) + public AjaxJson forbiddenAccessException() { + if (RequestHolder.isAxiosRequest()) { + return AjaxJson.getForbiddenResult(); + } + try { + String forbiddenUrl = systemConfigService.getForbiddenUrl(); + RequestHolder.getResponse().sendRedirect(forbiddenUrl); + } catch (IOException ex) { + return AjaxJson.getForbiddenResult(); + } + + return null; + } + + @ExceptionHandler(value = ForbiddenAccessException.class) + @ResponseBody + @ResponseStatus(HttpStatus.FORBIDDEN) + public AjaxJson forbiddenAccessException(ForbiddenAccessException e) { + if (RequestHolder.isAxiosRequest()) { + return AjaxJson.getError(e.getCode(), e.getMessage()); + } + try { + String forbiddenUrl = systemConfigService.getForbiddenUrl(e.getCode(), e.getMessage()); + RequestHolder.getResponse().sendRedirect(forbiddenUrl); + } catch (IOException ex) { + return AjaxJson.getError(e.getCode(), e.getMessage()); + } + + return null; + } + + @ExceptionHandler(value = NotFoundAccessException.class) + @ResponseBody + @ResponseStatus(HttpStatus.NOT_FOUND) + public AjaxJson notFoundAccessException(NotFoundAccessException e) { + if (RequestHolder.isAxiosRequest()) { + return AjaxJson.getError(e.getCode(), e.getMessage()); + } + try { + String notFoundUrl = systemConfigService.getNotFoundUrl(e.getCode(), e.getMessage()); + RequestHolder.getResponse().sendRedirect(notFoundUrl); + } catch (IOException ex) { + return AjaxJson.getError(e.getCode(), e.getMessage()); + } + + return null; + } + + + /** + * 所有未找到的页面都跳转到首页, 用户解决 vue history 直接访问 404 的问题 + * 同时, 读取 index.html 文件, 修改 title 和 favicon 后返回. + * + * @return 转发到 /index.html + */ + @ExceptionHandler(value = NoResourceFoundException.class) + @ResponseBody + public String notFoundAccessException() { + return frontIndexController.redirect(); + } + + @ExceptionHandler(value = MethodNotAllowedAccessException.class) + @ResponseBody + @ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED) + public AjaxJson methodNotAllowedAccessException(MethodNotAllowedAccessException e) { + return new AjaxJson<>(e.getCode(), e.getMessage()); + } + + @ExceptionHandler(value = BadRequestAccessException.class) + @ResponseBody + @ResponseStatus(HttpStatus.BAD_REQUEST) + public AjaxJson badRequestAccessException(BadRequestAccessException e) { + return new AjaxJson<>(e.getCode(), e.getMessage()); + } + + // ---------------------- status exception end ---------------------- + + + + + // ---------------------- biz exception start ---------------------- + + @ExceptionHandler(value = APIHttpRequestBizException.class) + @ResponseBody + @ResponseStatus + public AjaxJson apiHttpRequestBizException(APIHttpRequestBizException e) { + log.warn("请求第三方 API 异常, 请求地址: {}, 响应码: {}, 响应体: {}", e.getUrl(), e.getResponseCode(), e.getResponseBody()); + return new AjaxJson<>(e.getCode(), e.getMessage()); + } + + @ExceptionHandler(value = FilePathSecurityBizException.class) + @ResponseBody + @ResponseStatus(HttpStatus.BAD_REQUEST) + public AjaxJson filePathSecurityBizException(FilePathSecurityBizException e) { + log.warn("获取文件路径存在安全风险, 文件路径: {}", e.getPath()); + return new AjaxJson<>(e.getCode(), e.getMessage()); + } + + @ExceptionHandler(value = GetPreviewTextContentBizException.class) + @ResponseBody + @ResponseStatus + public AjaxJson getPreviewTextContentBizException(GetPreviewTextContentBizException e) { + log.warn("获取预览文件内容失败, 文件 url: {}", e.getUrl(), e); + return new AjaxJson<>(e.getCode(), "预览文件内容失败, 请联系管理员."); + } + + @ExceptionHandler(value = InitializeStorageSourceBizException.class) + @ResponseBody + @ResponseStatus + public AjaxJson initializeStorageSourceBizException(InitializeStorageSourceBizException e) { + log.error("存储源初始化失败, 存储源 ID: {}.", e.getStorageId(), e); + return new AjaxJson<>(e.getCode(), "存储源初始化失败:" + e.getMessage()); + } + + @ExceptionHandler(value = StorageSourceFileForbiddenAccessBizException.class) + @ResponseBody + @ResponseStatus + public AjaxJson storageSourceFileForbiddenAccessBizException(StorageSourceFileForbiddenAccessBizException e) { + log.warn("尝试访问不被授权的文件/目录, 存储源 ID: {}: 目录: {}", e.getStorageId(), e.getPath()); + return new AjaxJson<>(e.getCode(), e.getMessage()); + } + + @ExceptionHandler(value = StorageSourceIllegalOperationBizException.class) + @ResponseBody + @ResponseStatus + public AjaxJson storageSourceIllegalOperationBizException(StorageSourceIllegalOperationBizException e) { + log.warn("存储源非法或未授权的操作, 存储源 ID: {}, 操作类型: {}", e.getStorageId(), e.getAction()); + return new AjaxJson<>(e.getCode(), e.getMessage()); + } + + @ExceptionHandler(value = CorsBizException.class) + @ResponseBody + @ResponseStatus + public AjaxJson corsBizException(CorsBizException e) { + log.warn("跨域异常:", e); + return new AjaxJson<>(e.getCode(), e.getMessage()); + } + + @ExceptionHandler(value = ErrorPageBizException.class) + @ResponseBody + @ResponseStatus + public AjaxJson errorPageBizException(ErrorPageBizException e) { + if (RequestHolder.isAxiosRequest()) { + return AjaxJson.getError(e.getCode(), e.getMessage()); + } + try { + String errorPageUrl = systemConfigService.getErrorPageUrl(e.getCode(), e.getMessage()); + RequestHolder.getResponse().sendRedirect(errorPageUrl); + } catch (IOException ex) { + return AjaxJson.getError(e.getCode(), e.getMessage()); + } + + return null; + } + + + @ExceptionHandler(value = BizException.class) + @ResponseBody + @ResponseStatus + public AjaxJson bizException(BizException e) { + return new AjaxJson<>(e.getCode(), e.getMessage()); + } + + // ---------------------- biz exception end ---------------------- + + + // ---------------------- system exception end ---------------------- + + @ExceptionHandler(value = UploadFileFailSystemException.class) + @ResponseBody + @ResponseStatus + public AjaxJson uploadFileFailSystemException(UploadFileFailSystemException e) { + log.warn("上传文件失败, 存储类型: {}, 上传路径: {}, 输入流可用字节数: {}, 响应码: {}, 响应体: {}", + e.getStorageTypeEnum(), e.getUploadPath(), e.getInputStreamAvailable(), e.getResponseCode(), e.getResponseBody()); + return new AjaxJson<>(e.getCode(), e.getMessage()); + } + + @ExceptionHandler(value = ZFileAuthorizationSystemException.class) + @ResponseBody + @ResponseStatus + public AjaxJson zfileAuthorizationSystemException(ZFileAuthorizationSystemException e) { + return new AjaxJson<>(e.getCode(), e.getMessage()); + } + + @ExceptionHandler(value = SystemException.class) + @ResponseBody + @ResponseStatus + public AjaxJson systemException(SystemException e) { + return new AjaxJson<>(e.getCode(), e.getMessage()); + } + + + + // ---------------------- system exception end ---------------------- + + + + // ---------------------- common exception end ---------------------- + + @ExceptionHandler(value = {MethodArgumentNotValidException.class, BindException.class}) + @ResponseBody + @ResponseStatus(code = HttpStatus.BAD_REQUEST) + public AjaxJson> handleValidException(Exception e) { + BindingResult bindingResult = null; + if (e instanceof MethodArgumentNotValidException) { + bindingResult = ((MethodArgumentNotValidException) e).getBindingResult(); + } else if (e instanceof BindException) { + bindingResult = ((BindException) e).getBindingResult(); + } + Map errorMap = new HashMap<>(16); + + Optional.ofNullable(bindingResult) + .map(BindingResult::getFieldErrors) + .ifPresent(fieldErrors -> { + for (FieldError fieldError : fieldErrors) { + errorMap.put(fieldError.getField(), fieldError.getDefaultMessage()); + } + }); + return new AjaxJson<>(ErrorCode.BIZ_BAD_REQUEST.getCode(), ErrorCode.BIZ_BAD_REQUEST.getMessage(), errorMap); + + } + + @ExceptionHandler({FileNotFoundException.class}) + @ResponseBody + @ResponseStatus(HttpStatus.NOT_FOUND) + public AjaxJson fileNotFound() { + return AjaxJson.getError("文件不存在"); + } + + + /** + * 登录异常拦截器 + */ + @ExceptionHandler(NotLoginException.class) + @ResponseStatus(HttpStatus.UNAUTHORIZED) + @ResponseBody + public AjaxJson handlerNotLoginException(NotLoginException e) { + if (RequestHolder.isAxiosRequest()) { + return AjaxJson.getUnauthorizedResult(); + } + try { + String domain = systemConfigService.getRealFrontDomain(); + if (StringUtils.isBlank(domain)) { + domain = ""; + } + String loginUrl = StringUtils.concat(domain, "/login"); + RequestHolder.getResponse().sendRedirect(loginUrl); + } catch (IOException ex) { + return AjaxJson.getUnauthorizedResult(); + } + + return null; + } + + @ExceptionHandler + @ResponseBody + @ResponseStatus + public AjaxJson extraExceptionHandler(Exception e) { + ExceptionType exceptionType = getExceptionType(e); + if (exceptionType == ExceptionType.IGNORE_PRINT_STACK_TRACE_EXCEPTION) { + log.warn(e.getMessage()); + } else if (exceptionType == ExceptionType.OTHER) { + log.error(e.getMessage(), e); + } else if (exceptionType == ExceptionType.SPECIFY_MESSAGE_EXCEPTION) { + if (exceptionMessage.get() != null) { + String message = exceptionMessage.get(); + log.error("发生异常: {}", message,e ); + exceptionMessage.remove(); + return AjaxJson.getError(message); + } + } + + if (e.getClass() == Exception.class) { + return AjaxJson.getError("系统异常, 请联系管理员"); + } else { + return AjaxJson.getError(e.getMessage()); + } + } + + + private static ExceptionType getExceptionType(Exception e) { + int findCauseCount = 0; + do { + if (e instanceof BizException) { + return ExceptionType.IGNORE_PRINT_STACK_TRACE_EXCEPTION; + } else if (e instanceof ClientAbortException) { + return ExceptionType.IGNORE_EXCEPTION; + } else if (e instanceof SQLiteException && e.getMessage().contains("database is locked")) { + exceptionMessage.set("数据库繁忙,请稍后再试"); + return ExceptionType.SPECIFY_MESSAGE_EXCEPTION; + } + e = (Exception) e.getCause(); + findCauseCount++; + } while (e != null && findCauseCount < MAX_FIND_CAUSE_EXCEPTION_DEPTH); + + return ExceptionType.OTHER; + } + + enum ExceptionType { + /** + * 忽略打印异常信息和堆栈信息 + */ + IGNORE_EXCEPTION, + + /** + * 仅打印异常信息, 不打印堆栈信息 + */ + IGNORE_PRINT_STACK_TRACE_EXCEPTION, + + /** + * 不打印堆栈信息,但指定异常信息 + */ + SPECIFY_MESSAGE_EXCEPTION, + + /** + * 其他异常, 打印异常信息和堆栈信息 + */ + OTHER; + } +} \ No newline at end of file diff --git a/src/main/java/im/zhaojun/zfile/core/exception/IllegalDownloadLinkException.java b/src/main/java/im/zhaojun/zfile/core/exception/IllegalDownloadLinkException.java deleted file mode 100644 index e0ba482..0000000 --- a/src/main/java/im/zhaojun/zfile/core/exception/IllegalDownloadLinkException.java +++ /dev/null @@ -1,14 +0,0 @@ -package im.zhaojun.zfile.core.exception; - -/** - * 非法使用下载链接异常. - * - * @author zhaojun - */ -public class IllegalDownloadLinkException extends ZFileRuntimeException { - - public IllegalDownloadLinkException(String message) { - super(message); - } - -} \ No newline at end of file diff --git a/src/main/java/im/zhaojun/zfile/core/exception/InstallSystemException.java b/src/main/java/im/zhaojun/zfile/core/exception/InstallSystemException.java deleted file mode 100644 index 750f774..0000000 --- a/src/main/java/im/zhaojun/zfile/core/exception/InstallSystemException.java +++ /dev/null @@ -1,14 +0,0 @@ -package im.zhaojun.zfile.core.exception; - -/** - * 系统初始化异常 - * - * @author zhaojun - */ -public class InstallSystemException extends ZFileRuntimeException { - - public InstallSystemException(String message) { - super(message); - } - -} \ No newline at end of file diff --git a/src/main/java/im/zhaojun/zfile/core/exception/InvalidShortLinkException.java b/src/main/java/im/zhaojun/zfile/core/exception/InvalidShortLinkException.java deleted file mode 100644 index 23e484c..0000000 --- a/src/main/java/im/zhaojun/zfile/core/exception/InvalidShortLinkException.java +++ /dev/null @@ -1,14 +0,0 @@ -package im.zhaojun.zfile.core.exception; - -/** - * 无效的直链异常 - * - * @author zhaojun - */ -public class InvalidShortLinkException extends ZFileRuntimeException { - - public InvalidShortLinkException(String message) { - super(message); - } - -} \ No newline at end of file diff --git a/src/main/java/im/zhaojun/zfile/core/exception/LoginVerifyException.java b/src/main/java/im/zhaojun/zfile/core/exception/LoginVerifyException.java deleted file mode 100644 index c6b8043..0000000 --- a/src/main/java/im/zhaojun/zfile/core/exception/LoginVerifyException.java +++ /dev/null @@ -1,14 +0,0 @@ -package im.zhaojun.zfile.core.exception; - -/** - * 登陆验证码验证异常 - * - * @author zhaojun - */ -public class LoginVerifyException extends ZFileRuntimeException { - - public LoginVerifyException(String message) { - super(message); - } - -} \ No newline at end of file diff --git a/src/main/java/im/zhaojun/zfile/core/exception/PasswordVerifyException.java b/src/main/java/im/zhaojun/zfile/core/exception/PasswordVerifyException.java deleted file mode 100644 index 8ee0ed3..0000000 --- a/src/main/java/im/zhaojun/zfile/core/exception/PasswordVerifyException.java +++ /dev/null @@ -1,22 +0,0 @@ -package im.zhaojun.zfile.core.exception; - - -/** - * 密码校验失败异常 - * - * @author zhaojun - */ -public class PasswordVerifyException extends RuntimeException { - - private final Integer code; - - public PasswordVerifyException(Integer code, String message) { - super(message); - this.code = code; - } - - public Integer getCode() { - return code; - } - -} \ No newline at end of file diff --git a/src/main/java/im/zhaojun/zfile/core/exception/PreviewException.java b/src/main/java/im/zhaojun/zfile/core/exception/PreviewException.java deleted file mode 100644 index 545f03b..0000000 --- a/src/main/java/im/zhaojun/zfile/core/exception/PreviewException.java +++ /dev/null @@ -1,14 +0,0 @@ -package im.zhaojun.zfile.core.exception; - -/** - * 文件预览异常类 - * - * @author zhaojun - */ -public class PreviewException extends ZFileRuntimeException { - - public PreviewException(String message) { - super(message); - } - -} \ No newline at end of file diff --git a/src/main/java/im/zhaojun/zfile/core/exception/ServiceException.java b/src/main/java/im/zhaojun/zfile/core/exception/ServiceException.java deleted file mode 100644 index 52d6e37..0000000 --- a/src/main/java/im/zhaojun/zfile/core/exception/ServiceException.java +++ /dev/null @@ -1,42 +0,0 @@ -package im.zhaojun.zfile.core.exception; - -import im.zhaojun.zfile.core.util.CodeMsg; -import lombok.Data; -import lombok.EqualsAndHashCode; - -/** - * Service 层异常 - * 所有 message 均为系统日志打印输出, CodeMsg 中的消息才是返回给客户端的消息. - * - * @author zhaojun - */ -@Data -@EqualsAndHashCode(callSuper = true) -public class ServiceException extends RuntimeException { - - private CodeMsg codeMsg; - - public ServiceException(CodeMsg codeMsg) { - this.codeMsg = codeMsg; - } - - public ServiceException(String message, CodeMsg codeMsg) { - super(message); - this.codeMsg = codeMsg; - } - - public ServiceException(String message, Throwable cause, CodeMsg codeMsg) { - super(message, cause); - this.codeMsg = codeMsg; - } - - public ServiceException(Throwable cause, CodeMsg codeMsg) { - super(cause); - this.codeMsg = codeMsg; - } - - public ServiceException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace, CodeMsg codeMsg) { - super(message, cause, enableSuppression, writableStackTrace); - this.codeMsg = codeMsg; - } -} \ No newline at end of file diff --git a/src/main/java/im/zhaojun/zfile/core/exception/StorageSourceAutoConfigCorsException.java b/src/main/java/im/zhaojun/zfile/core/exception/StorageSourceAutoConfigCorsException.java deleted file mode 100644 index 0b71077..0000000 --- a/src/main/java/im/zhaojun/zfile/core/exception/StorageSourceAutoConfigCorsException.java +++ /dev/null @@ -1,21 +0,0 @@ -package im.zhaojun.zfile.core.exception; - -import im.zhaojun.zfile.module.storage.model.param.IStorageParam; -import lombok.Getter; - -/** - * 存储源自动设置 cors 异常 - * - * @author zhaojun - */ -@Getter -public class StorageSourceAutoConfigCorsException extends RuntimeException { - - private final IStorageParam iStorageParam; - - public StorageSourceAutoConfigCorsException(String message, Throwable cause, IStorageParam iStorageParam) { - super(message, cause); - this.iStorageParam = iStorageParam; - } - -} \ No newline at end of file diff --git a/src/main/java/im/zhaojun/zfile/core/exception/StorageSourceException.java b/src/main/java/im/zhaojun/zfile/core/exception/StorageSourceException.java deleted file mode 100644 index 303124d..0000000 --- a/src/main/java/im/zhaojun/zfile/core/exception/StorageSourceException.java +++ /dev/null @@ -1,60 +0,0 @@ -package im.zhaojun.zfile.core.exception; - -import im.zhaojun.zfile.core.util.CodeMsg; -import lombok.EqualsAndHashCode; -import lombok.Getter; - -/** - * 存储源异常 - * - * @author zhaojun - */ -@EqualsAndHashCode(callSuper = true) -@Getter -public class StorageSourceException extends ServiceException { - - /** - * 是否使用异常消息进行接口返回,如果是则取异常的 message, 否则取 CodeMsg 中的 message - */ - private boolean responseExceptionMessage; - - /** - * 存储源 ID - */ - private final Integer storageId; - - public StorageSourceException(CodeMsg codeMsg, Integer storageId, String message) { - super(message, codeMsg); - this.storageId = storageId; - } - - public StorageSourceException(CodeMsg codeMsg, Integer storageId, String message, Throwable cause) { - super(message, cause, codeMsg); - this.storageId = storageId; - } - - - /** - * 根据 responseExceptionMessage 判断使用异常消息进行接口返回,如果是则取异常的 message, 否则取 CodeMsg 中的 message - * - * @return 异常消息 - */ - public String getResultMessage() { - return responseExceptionMessage ? super.getMessage() : super.getCodeMsg().getMsg(); - } - - - /** - * 设置值是否使用异常消息进行接口返回 - * - * @param responseExceptionMessage - * 是否使用异常消息进行接口返回 - * - * @return 当前对象 - */ - public StorageSourceException setResponseExceptionMessage(boolean responseExceptionMessage) { - this.responseExceptionMessage = responseExceptionMessage; - return this; - } - -} \ No newline at end of file diff --git a/src/main/java/im/zhaojun/zfile/core/exception/StorageSourceNotSupportProxyUploadException.java b/src/main/java/im/zhaojun/zfile/core/exception/StorageSourceNotSupportProxyUploadException.java deleted file mode 100644 index 5600bf6..0000000 --- a/src/main/java/im/zhaojun/zfile/core/exception/StorageSourceNotSupportProxyUploadException.java +++ /dev/null @@ -1,14 +0,0 @@ -package im.zhaojun.zfile.core.exception; - -/** - * 存储源不支持代理上传异常 - * - * @author zhaojun - */ -public class StorageSourceNotSupportProxyUploadException extends ZFileRuntimeException { - - public StorageSourceNotSupportProxyUploadException(String message) { - super(message); - } - -} \ No newline at end of file diff --git a/src/main/java/im/zhaojun/zfile/core/exception/StorageSourceRefreshTokenException.java b/src/main/java/im/zhaojun/zfile/core/exception/StorageSourceRefreshTokenException.java deleted file mode 100644 index f908dee..0000000 --- a/src/main/java/im/zhaojun/zfile/core/exception/StorageSourceRefreshTokenException.java +++ /dev/null @@ -1,22 +0,0 @@ -package im.zhaojun.zfile.core.exception; - -import lombok.Getter; - -/** - * @author zhaojun - */ -@Getter -public class StorageSourceRefreshTokenException extends RuntimeException { - - private final Integer storageId; - - public StorageSourceRefreshTokenException(String message, Integer storageId) { - super(message); - this.storageId = storageId; - } - - public StorageSourceRefreshTokenException(String message, Throwable cause, Integer storageId) { - super(message, cause); - this.storageId = storageId; - } -} \ No newline at end of file diff --git a/src/main/java/im/zhaojun/zfile/core/exception/TextParseException.java b/src/main/java/im/zhaojun/zfile/core/exception/TextParseException.java deleted file mode 100644 index b47df15..0000000 --- a/src/main/java/im/zhaojun/zfile/core/exception/TextParseException.java +++ /dev/null @@ -1,17 +0,0 @@ -package im.zhaojun.zfile.core.exception; - -/** - * 文件解析异常 - * - * @author zhaojun - */ -public class TextParseException extends ZFileRuntimeException { - - public TextParseException(String message) { - super(message); - } - - public TextParseException(String message, Throwable cause) { - super(message, cause); - } -} \ No newline at end of file diff --git a/src/main/java/im/zhaojun/zfile/core/exception/ZFileRetryException.java b/src/main/java/im/zhaojun/zfile/core/exception/ZFileRetryException.java deleted file mode 100644 index 28138e7..0000000 --- a/src/main/java/im/zhaojun/zfile/core/exception/ZFileRetryException.java +++ /dev/null @@ -1,26 +0,0 @@ -package im.zhaojun.zfile.core.exception; - -/** - * @author zhaojun - */ -public class ZFileRetryException extends RuntimeException { - - public ZFileRetryException() { - } - - public ZFileRetryException(String message) { - super(message); - } - - public ZFileRetryException(String message, Throwable cause) { - super(message, cause); - } - - public ZFileRetryException(Throwable cause) { - super(cause); - } - - public ZFileRetryException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { - super(message, cause, enableSuppression, writableStackTrace); - } -} \ No newline at end of file diff --git a/src/main/java/im/zhaojun/zfile/core/exception/ZFileRuntimeException.java b/src/main/java/im/zhaojun/zfile/core/exception/ZFileRuntimeException.java deleted file mode 100644 index 45d457c..0000000 --- a/src/main/java/im/zhaojun/zfile/core/exception/ZFileRuntimeException.java +++ /dev/null @@ -1,15 +0,0 @@ -package im.zhaojun.zfile.core.exception; - -/** - * @author zhaojun - */ -public class ZFileRuntimeException extends RuntimeException { - - public ZFileRuntimeException(String message) { - super(message); - } - - public ZFileRuntimeException(String message, Throwable cause) { - super(message, cause); - } -} \ No newline at end of file diff --git a/src/main/java/im/zhaojun/zfile/core/exception/biz/APIHttpRequestBizException.java b/src/main/java/im/zhaojun/zfile/core/exception/biz/APIHttpRequestBizException.java new file mode 100644 index 0000000..7610b65 --- /dev/null +++ b/src/main/java/im/zhaojun/zfile/core/exception/biz/APIHttpRequestBizException.java @@ -0,0 +1,31 @@ +package im.zhaojun.zfile.core.exception.biz; + +import im.zhaojun.zfile.core.exception.ErrorCode; +import im.zhaojun.zfile.core.exception.core.BizException; +import im.zhaojun.zfile.core.exception.GlobalExceptionHandler; +import lombok.Getter; + +/** + * 请求第三方 API 时如果返回非 2xx 状态码, 则抛出此异常. 需记录请求地址, 响应状态码, 响应内容. + *

+ * 需要全局异常处理器捕获此异常, 并记录日志. {@link GlobalExceptionHandler#apiHttpRequestBizException(APIHttpRequestBizException)} + * + * @author zhaojun + */ +@Getter +public class APIHttpRequestBizException extends BizException { + + private final String url; + + private final int responseCode; + + private final String responseBody; + + public APIHttpRequestBizException(ErrorCode errorCode, String url, int responseCode, String responseBody) { + super(errorCode); + this.url = url; + this.responseCode = responseCode; + this.responseBody = responseBody; + } + +} diff --git a/src/main/java/im/zhaojun/zfile/core/exception/biz/CorsBizException.java b/src/main/java/im/zhaojun/zfile/core/exception/biz/CorsBizException.java new file mode 100644 index 0000000..4efa41b --- /dev/null +++ b/src/main/java/im/zhaojun/zfile/core/exception/biz/CorsBizException.java @@ -0,0 +1,21 @@ +package im.zhaojun.zfile.core.exception.biz; + +import im.zhaojun.zfile.core.exception.core.BizException; +import lombok.Getter; + +/** + * @author zhaojun + */ +@Getter +public class CorsBizException extends BizException { + + public CorsBizException(String message, Throwable cause) { + super(message, cause); + } + + @Override + public boolean printExceptionStackTrace() { + return true; + } + +} \ No newline at end of file diff --git a/src/main/java/im/zhaojun/zfile/core/exception/biz/FilePathSecurityBizException.java b/src/main/java/im/zhaojun/zfile/core/exception/biz/FilePathSecurityBizException.java new file mode 100644 index 0000000..13bed21 --- /dev/null +++ b/src/main/java/im/zhaojun/zfile/core/exception/biz/FilePathSecurityBizException.java @@ -0,0 +1,26 @@ +package im.zhaojun.zfile.core.exception.biz; + + +import im.zhaojun.zfile.core.exception.ErrorCode; +import im.zhaojun.zfile.core.exception.core.BizException; +import im.zhaojun.zfile.core.exception.GlobalExceptionHandler; +import lombok.Getter; + +/** + * 文件路径安全异常, 表示文件路径不合法,如包含了 "./" 或 "../" 等字符来尝试访问非法目录. + *

+ * 需要全局异常处理器捕获此异常, 并记录日志. {@link GlobalExceptionHandler#filePathSecurityBizException(FilePathSecurityBizException)} + * + * @author zhaojun + */ +@Getter +public class FilePathSecurityBizException extends BizException { + + private final String path; + + public FilePathSecurityBizException(String path) { + super(ErrorCode.BIZ_FILE_PATH_ILLEGAL); + this.path = path; + } + +} \ No newline at end of file diff --git a/src/main/java/im/zhaojun/zfile/core/exception/biz/GetPreviewTextContentBizException.java b/src/main/java/im/zhaojun/zfile/core/exception/biz/GetPreviewTextContentBizException.java new file mode 100644 index 0000000..9be1841 --- /dev/null +++ b/src/main/java/im/zhaojun/zfile/core/exception/biz/GetPreviewTextContentBizException.java @@ -0,0 +1,27 @@ +package im.zhaojun.zfile.core.exception.biz; + +import im.zhaojun.zfile.core.exception.core.BizException; +import im.zhaojun.zfile.core.exception.GlobalExceptionHandler; +import lombok.Getter; + +/** + * 获取预览文件内容异常, 可能是目标连接无法访问/文件不存在等原因. + *

+ * 需要全局异常处理器捕获此异常, 并记录日志. {@link GlobalExceptionHandler#getPreviewTextContentBizException(GetPreviewTextContentBizException)} + * + * @author zhaojun + */ +@Getter +public class GetPreviewTextContentBizException extends BizException { + + /** + * 获取预览文件的 URL + */ + private final String url; + + public GetPreviewTextContentBizException(String url, Throwable cause) { + super(cause); + this.url = url; + } + +} \ No newline at end of file diff --git a/src/main/java/im/zhaojun/zfile/core/exception/biz/InitializeStorageSourceBizException.java b/src/main/java/im/zhaojun/zfile/core/exception/biz/InitializeStorageSourceBizException.java new file mode 100644 index 0000000..1c9a8e8 --- /dev/null +++ b/src/main/java/im/zhaojun/zfile/core/exception/biz/InitializeStorageSourceBizException.java @@ -0,0 +1,29 @@ +package im.zhaojun.zfile.core.exception.biz; + +import im.zhaojun.zfile.core.exception.GlobalExceptionHandler; +import im.zhaojun.zfile.core.exception.core.BizException; +import lombok.Getter; + +/** + * 初始化存储源时失败产生的异常 + *

+ * 需要全局异常处理器捕获此异常, 并记录日志. {@link GlobalExceptionHandler#initializeStorageSourceBizException(InitializeStorageSourceBizException)} + * + * @author zhaojun + */ +@Getter +public class InitializeStorageSourceBizException extends BizException { + + private final Integer storageId; + + public InitializeStorageSourceBizException(String message, Integer storageId) { + super(message); + this.storageId = storageId; + } + + public InitializeStorageSourceBizException(String code, String message, Integer storageId, Throwable cause) { + super(code, message, cause); + this.storageId = storageId; + } + +} diff --git a/src/main/java/im/zhaojun/zfile/core/exception/biz/InvalidStorageSourceBizException.java b/src/main/java/im/zhaojun/zfile/core/exception/biz/InvalidStorageSourceBizException.java new file mode 100644 index 0000000..45028ec --- /dev/null +++ b/src/main/java/im/zhaojun/zfile/core/exception/biz/InvalidStorageSourceBizException.java @@ -0,0 +1,31 @@ +package im.zhaojun.zfile.core.exception.biz; + +import im.zhaojun.zfile.core.exception.ErrorCode; +import im.zhaojun.zfile.core.exception.core.BizException; +import lombok.Getter; + +/** + * 不存在或初始化失败的存储源异常。 + * + * @author zhaojun + */ +@Getter +public class InvalidStorageSourceBizException extends BizException { + + private final Integer storageId; + + private final String storageKey; + + public InvalidStorageSourceBizException(String storageKey) { + super(ErrorCode.INVALID_STORAGE_SOURCE); + this.storageKey = storageKey; + this.storageId = null; + } + + public InvalidStorageSourceBizException(Integer storageId) { + super(ErrorCode.INVALID_STORAGE_SOURCE); + this.storageId = storageId; + this.storageKey = null; + } + +} \ No newline at end of file diff --git a/src/main/java/im/zhaojun/zfile/core/exception/biz/StorageSourceFileForbiddenAccessBizException.java b/src/main/java/im/zhaojun/zfile/core/exception/biz/StorageSourceFileForbiddenAccessBizException.java new file mode 100644 index 0000000..21455fc --- /dev/null +++ b/src/main/java/im/zhaojun/zfile/core/exception/biz/StorageSourceFileForbiddenAccessBizException.java @@ -0,0 +1,27 @@ +package im.zhaojun.zfile.core.exception.biz; + +import im.zhaojun.zfile.core.exception.ErrorCode; +import im.zhaojun.zfile.core.exception.core.BizException; +import im.zhaojun.zfile.core.exception.GlobalExceptionHandler; +import lombok.Getter; + +/** + * 访问了禁止访问的存储源文件/目录时抛出此异常. + *

+ * 需要全局异常处理器捕获此异常, 并记录日志. {@link GlobalExceptionHandler#storageSourceFileForbiddenAccessBizException(StorageSourceFileForbiddenAccessBizException)} + * + * @author zhaojun + */ +@Getter +public class StorageSourceFileForbiddenAccessBizException extends BizException { + + private final Integer storageId; + + private final String path; + + public StorageSourceFileForbiddenAccessBizException(Integer storageId, String path) { + super(ErrorCode.BIZ_STORAGE_SOURCE_FILE_FORBIDDEN); + this.storageId = storageId; + this.path = path; + } +} diff --git a/src/main/java/im/zhaojun/zfile/core/exception/biz/StorageSourceIllegalOperationBizException.java b/src/main/java/im/zhaojun/zfile/core/exception/biz/StorageSourceIllegalOperationBizException.java new file mode 100644 index 0000000..056ec02 --- /dev/null +++ b/src/main/java/im/zhaojun/zfile/core/exception/biz/StorageSourceIllegalOperationBizException.java @@ -0,0 +1,28 @@ +package im.zhaojun.zfile.core.exception.biz; + +import im.zhaojun.zfile.core.exception.ErrorCode; +import im.zhaojun.zfile.core.exception.core.BizException; +import im.zhaojun.zfile.core.exception.GlobalExceptionHandler; +import im.zhaojun.zfile.module.storage.model.enums.FileOperatorTypeEnum; +import lombok.Getter; + +/** + * 对存储源进行非法(未授权)的操作产生的异常 + *

+ * 需要全局异常处理器捕获此异常, 并记录日志. {@link GlobalExceptionHandler#storageSourceIllegalOperationBizException(StorageSourceIllegalOperationBizException)} + * + * @author zhaojun + */ +@Getter +public class StorageSourceIllegalOperationBizException extends BizException { + + private final Integer storageId; + + private final FileOperatorTypeEnum action; + + public StorageSourceIllegalOperationBizException(Integer storageId, FileOperatorTypeEnum action) { + super(ErrorCode.BIZ_STORAGE_SOURCE_ILLEGAL_OPERATION); + this.storageId = storageId; + this.action = action; + } +} diff --git a/src/main/java/im/zhaojun/zfile/core/exception/core/BizException.java b/src/main/java/im/zhaojun/zfile/core/exception/core/BizException.java new file mode 100644 index 0000000..84e79ef --- /dev/null +++ b/src/main/java/im/zhaojun/zfile/core/exception/core/BizException.java @@ -0,0 +1,101 @@ +package im.zhaojun.zfile.core.exception.core; + +import im.zhaojun.zfile.core.exception.ErrorCode; +import lombok.Getter; + +/** + * 业务异常,该类异常用户可自行处理,无需记录日志,属于正常业务流程中的异常. 如: 用户名密码错误, 未登录等. + * + * @author zhaojun + */ +@Getter +public class BizException extends RuntimeException { + + private static final long serialVersionUID = 8312907182931723379L; + + /** + * 错误码 + */ + private String code; + + /** + * 是否打印堆栈信息,业务异常默认不打印堆栈信息,如果需要打印堆栈信息,可以通过子类覆盖该方法修改返回值为 true. + */ + public boolean printExceptionStackTrace() { + return false; + } + + /** + * 构造一个没有错误信息的 SystemException + */ + public BizException() { + super(); + } + + /** + * 使用指定的 Throwable 和 Throwable.toString() 作为异常信息来构造 SystemException + * + * @param cause 错误原因, 通过 Throwable.getCause() 方法可以获取传入的 cause信息 + */ + public BizException(Throwable cause) { + super(cause); + } + + /** + * 使用错误信息 message 构造 SystemException + * + * @param message 错误信息 + */ + public BizException(String message) { + super(message); + } + + /** + * 使用错误码和错误信息构造 SystemException + * + * @param code 错误码 + * @param message 错误信息 + */ + public BizException(String code, String message) { + super(message); + this.code = code; + } + + /** + * 使用错误信息和 Throwable 构造 SystemException + * + * @param message 错误信息 + * @param cause 错误原因 + */ + public BizException(String message, Throwable cause) { + super(message, cause); + } + + /** + * @param code 错误码 + * @param message 错误信息 + * @param cause 错误原因 + */ + public BizException(String code, String message, Throwable cause) { + super(message, cause); + this.code = code; + } + + /** + * @param errorCode ErrorCode + */ + public BizException(ErrorCode errorCode) { + super(errorCode.getMessage()); + this.code = errorCode.getCode(); + } + + /** + * @param errorCode ErrorCode + * @param cause 错误原因 + */ + public BizException(ErrorCode errorCode, Throwable cause) { + super(errorCode.getMessage(), cause); + this.code = errorCode.getCode(); + } + +} \ No newline at end of file diff --git a/src/main/java/im/zhaojun/zfile/core/exception/core/ErrorPageBizException.java b/src/main/java/im/zhaojun/zfile/core/exception/core/ErrorPageBizException.java new file mode 100644 index 0000000..a708cf5 --- /dev/null +++ b/src/main/java/im/zhaojun/zfile/core/exception/core/ErrorPageBizException.java @@ -0,0 +1,103 @@ +package im.zhaojun.zfile.core.exception.core; + +import im.zhaojun.zfile.core.exception.ErrorCode; +import lombok.Getter; + +/** + * 业务异常,该类异常用户可自行处理,无需记录日志,属于正常业务流程中的异常. 如: 用户名密码错误, 未登录等.
+ * 使用该类的异常,当该异常被抛出时,会跳转到 500 错误页面(错误码和错误消息可被 {@link #code} 和 {@link #getMessage()} 覆盖),而不是返回 JSON 数据.
+ * 一般使用该异常得请求不会是 AJAX 请求,而是直接在浏览器中访问的页面请求. + * + * @author zhaojun + */ +@Getter +public class ErrorPageBizException extends RuntimeException { + + private static final long serialVersionUID = 8312907182931723379L; + + /** + * 错误码 + */ + private String code; + + /** + * 是否打印堆栈信息,业务异常默认不打印堆栈信息,如果需要打印堆栈信息,可以通过子类覆盖该方法修改返回值为 true. + */ + public boolean printExceptionStackTrace() { + return false; + } + + /** + * 构造一个没有错误信息的 SystemException + */ + public ErrorPageBizException() { + super(); + } + + /** + * 使用指定的 Throwable 和 Throwable.toString() 作为异常信息来构造 SystemException + * + * @param cause 错误原因, 通过 Throwable.getCause() 方法可以获取传入的 cause信息 + */ + public ErrorPageBizException(Throwable cause) { + super(cause); + } + + /** + * 使用错误信息 message 构造 SystemException + * + * @param message 错误信息 + */ + public ErrorPageBizException(String message) { + super(message); + } + + /** + * 使用错误码和错误信息构造 SystemException + * + * @param code 错误码 + * @param message 错误信息 + */ + public ErrorPageBizException(String code, String message) { + super(message); + this.code = code; + } + + /** + * 使用错误信息和 Throwable 构造 SystemException + * + * @param message 错误信息 + * @param cause 错误原因 + */ + public ErrorPageBizException(String message, Throwable cause) { + super(message, cause); + } + + /** + * @param code 错误码 + * @param message 错误信息 + * @param cause 错误原因 + */ + public ErrorPageBizException(String code, String message, Throwable cause) { + super(message, cause); + this.code = code; + } + + /** + * @param errorCode ErrorCode + */ + public ErrorPageBizException(ErrorCode errorCode) { + super(errorCode.getMessage()); + this.code = errorCode.getCode(); + } + + /** + * @param errorCode ErrorCode + * @param cause 错误原因 + */ + public ErrorPageBizException(ErrorCode errorCode, Throwable cause) { + super(errorCode.getMessage(), cause); + this.code = errorCode.getCode(); + } + +} \ No newline at end of file diff --git a/src/main/java/im/zhaojun/zfile/core/exception/core/SystemException.java b/src/main/java/im/zhaojun/zfile/core/exception/core/SystemException.java new file mode 100644 index 0000000..4947f8d --- /dev/null +++ b/src/main/java/im/zhaojun/zfile/core/exception/core/SystemException.java @@ -0,0 +1,94 @@ +package im.zhaojun.zfile.core.exception.core; + +import im.zhaojun.zfile.core.exception.ErrorCode; +import lombok.Getter; + +/** + * 系统异常, 该类异常用户无法处理,需要记录日志, 属于系统异常. 如: 网络异常, 服务器异常等. + * + * @author zhaojun + */ +@Getter +public class SystemException extends RuntimeException { + + private static final long serialVersionUID = 8312907182931723379L; + + /** + * 错误码 + */ + private String code; + + /** + * 构造一个没有错误信息的 SystemException + */ + public SystemException() { + super(); + } + + /** + * 使用指定的 Throwable 和 Throwable.toString() 作为异常信息来构造 SystemException + * + * @param cause 错误原因, 通过 Throwable.getCause() 方法可以获取传入的 cause信息 + */ + public SystemException(Throwable cause) { + super(cause); + } + + /** + * 使用错误信息 message 构造 SystemException + * + * @param message 错误信息 + */ + public SystemException(String message) { + super(message); + } + + /** + * 使用错误码和错误信息构造 SystemException + * + * @param code 错误码 + * @param message 错误信息 + */ + public SystemException(String code, String message) { + super(message); + this.code = code; + } + + /** + * 使用错误信息和 Throwable 构造 SystemException + * + * @param message 错误信息 + * @param cause 错误原因 + */ + public SystemException(String message, Throwable cause) { + super(message, cause); + } + + /** + * @param code 错误码 + * @param message 错误信息 + * @param cause 错误原因 + */ + public SystemException(String code, String message, Throwable cause) { + super(message, cause); + this.code = code; + } + + /** + * @param errorCode ErrorCode + */ + public SystemException(ErrorCode errorCode) { + super(errorCode.getMessage()); + this.code = errorCode.getCode(); + } + + /** + * @param errorCode ErrorCode + * @param cause 错误原因 + */ + public SystemException(ErrorCode errorCode, Throwable cause) { + super(errorCode.getMessage(), cause); + this.code = errorCode.getCode(); + } + +} \ No newline at end of file diff --git a/src/main/java/im/zhaojun/zfile/core/exception/file/InvalidStorageSourceException.java b/src/main/java/im/zhaojun/zfile/core/exception/file/InvalidStorageSourceException.java deleted file mode 100644 index 6ce388f..0000000 --- a/src/main/java/im/zhaojun/zfile/core/exception/file/InvalidStorageSourceException.java +++ /dev/null @@ -1,25 +0,0 @@ -package im.zhaojun.zfile.core.exception.file; - -import im.zhaojun.zfile.core.exception.StorageSourceException; -import im.zhaojun.zfile.core.util.CodeMsg; - -/** - * 无效的存储源异常 - * - * @author zhaojun - */ -public class InvalidStorageSourceException extends StorageSourceException { - - public InvalidStorageSourceException(String message) { - super(CodeMsg.STORAGE_SOURCE_NOT_FOUND, null, message); - } - - public InvalidStorageSourceException(Integer storageId) { - super(CodeMsg.STORAGE_SOURCE_NOT_FOUND, storageId, CodeMsg.STORAGE_SOURCE_NOT_FOUND.getMsg()); - } - - public InvalidStorageSourceException(Integer storageId, String message) { - super(CodeMsg.STORAGE_SOURCE_NOT_FOUND, storageId, message); - } - -} \ No newline at end of file diff --git a/src/main/java/im/zhaojun/zfile/core/exception/file/init/InitializeStorageSourceException.java b/src/main/java/im/zhaojun/zfile/core/exception/file/init/InitializeStorageSourceException.java deleted file mode 100644 index e33a5e0..0000000 --- a/src/main/java/im/zhaojun/zfile/core/exception/file/init/InitializeStorageSourceException.java +++ /dev/null @@ -1,21 +0,0 @@ -package im.zhaojun.zfile.core.exception.file.init; - -import im.zhaojun.zfile.core.exception.StorageSourceException; -import im.zhaojun.zfile.core.util.CodeMsg; - -/** - * 存储源初始化异常 - * - * @author zhaojun - */ -public class InitializeStorageSourceException extends StorageSourceException { - - public InitializeStorageSourceException(CodeMsg codeMsg, Integer storageId, String message) { - super(codeMsg, storageId, message); - } - - public InitializeStorageSourceException(CodeMsg codeMsg, Integer storageId, String message, Throwable cause) { - super(codeMsg, storageId, message, cause); - } - -} \ No newline at end of file diff --git a/src/main/java/im/zhaojun/zfile/core/exception/file/operator/DisableProxyDownloadException.java b/src/main/java/im/zhaojun/zfile/core/exception/file/operator/DisableProxyDownloadException.java deleted file mode 100644 index d398fb1..0000000 --- a/src/main/java/im/zhaojun/zfile/core/exception/file/operator/DisableProxyDownloadException.java +++ /dev/null @@ -1,24 +0,0 @@ -package im.zhaojun.zfile.core.exception.file.operator; - -import im.zhaojun.zfile.core.exception.StorageSourceException; -import im.zhaojun.zfile.core.util.CodeMsg; - -/** - * 禁止服务器代理下载异常 - * - * @author zhaojun - */ -public class DisableProxyDownloadException extends StorageSourceException { - - public DisableProxyDownloadException(CodeMsg codeMsg, Integer storageId) { - super(codeMsg, storageId, null); - } - - public DisableProxyDownloadException(CodeMsg codeMsg, Integer storageId, String message) { - super(codeMsg, storageId, message); - } - - public DisableProxyDownloadException(CodeMsg codeMsg, Integer storageId, String message, Throwable cause) { - super(codeMsg, storageId, message, cause); - } -} \ No newline at end of file diff --git a/src/main/java/im/zhaojun/zfile/core/exception/file/operator/StorageSourceFileOperatorException.java b/src/main/java/im/zhaojun/zfile/core/exception/file/operator/StorageSourceFileOperatorException.java deleted file mode 100644 index a78d434..0000000 --- a/src/main/java/im/zhaojun/zfile/core/exception/file/operator/StorageSourceFileOperatorException.java +++ /dev/null @@ -1,18 +0,0 @@ -package im.zhaojun.zfile.core.exception.file.operator; - -import im.zhaojun.zfile.core.exception.StorageSourceException; -import im.zhaojun.zfile.core.util.CodeMsg; -import lombok.Getter; - -/** - * 存储源文件操作异常 - * @author zhaojun - */ -@Getter -public class StorageSourceFileOperatorException extends StorageSourceException { - - public StorageSourceFileOperatorException(CodeMsg codeMsg, Integer storageId, String message, Throwable cause) { - super(codeMsg, storageId, message, cause); - } - -} \ No newline at end of file diff --git a/src/main/java/im/zhaojun/zfile/core/exception/handler/GlobalExceptionHandler.java b/src/main/java/im/zhaojun/zfile/core/exception/handler/GlobalExceptionHandler.java deleted file mode 100644 index 560d2b4..0000000 --- a/src/main/java/im/zhaojun/zfile/core/exception/handler/GlobalExceptionHandler.java +++ /dev/null @@ -1 +0,0 @@ -package im.zhaojun.zfile.core.exception.handler; import cn.dev33.satoken.exception.NotLoginException; import cn.hutool.core.io.IORuntimeException; import cn.hutool.core.util.StrUtil; import com.alibaba.fastjson2.JSON; import im.zhaojun.zfile.core.exception.*; import im.zhaojun.zfile.core.exception.file.init.InitializeStorageSourceException; import im.zhaojun.zfile.core.exception.file.operator.DisableProxyDownloadException; import im.zhaojun.zfile.core.exception.file.operator.StorageSourceFileOperatorException; import im.zhaojun.zfile.core.util.AjaxJson; import im.zhaojun.zfile.core.util.RequestHolder; import im.zhaojun.zfile.core.util.StringUtils; import im.zhaojun.zfile.module.config.service.SystemConfigService; import lombok.extern.slf4j.Slf4j; import org.apache.catalina.connector.ClientAbortException; import org.springframework.core.annotation.Order; import org.springframework.http.HttpStatus; import org.springframework.http.converter.HttpMessageNotReadableException; import org.springframework.validation.BindException; import org.springframework.validation.BindingResult; import org.springframework.validation.FieldError; import org.springframework.web.HttpMediaTypeNotAcceptableException; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseStatus; import javax.annotation.Resource; import javax.servlet.http.HttpServletRequest; import java.io.FileNotFoundException; import java.io.IOException; import java.util.HashMap; import java.util.Map; import java.util.Optional; /** * 全局异常处理 * * @author zhaojun */ @ControllerAdvice @Slf4j @Order(1) public class GlobalExceptionHandler { @Resource private SystemConfigService systemConfigService; /** * 存储源文件操作相关异常处理 */ @ExceptionHandler(value = StorageSourceException.class) @ResponseBody @ResponseStatus public AjaxJson storageSourceException(StorageSourceException e) { log.error("存储源 {} 出现异常", e.getStorageId(), e); return AjaxJson.getError(e.getResultMessage()); } /** * 存储源初始化相关异常处理 */ @ExceptionHandler(value = InitializeStorageSourceException.class) @ResponseBody @ResponseStatus public AjaxJson initializeException(InitializeStorageSourceException e) { log.error("初始化存储源 {} 失败", e.getStorageId(), e); return AjaxJson.getError(e.getResultMessage()); } /** * 存储源文件操作相关异常处理 */ @ExceptionHandler(value = StorageSourceFileOperatorException.class) @ResponseBody @ResponseStatus public AjaxJson storageSourceFileOperatorException(StorageSourceFileOperatorException e) { log.error("存储源 {} 文件操作异常", e.getStorageId(), e); return AjaxJson.getError(e.getResultMessage()); } @ExceptionHandler({ZFileRuntimeException.class}) @ResponseBody @ResponseStatus public AjaxJson getFileInfoException(ZFileRuntimeException e) { if (e.getCause() != null) { log.error("ZFileRuntimeException", e); } return AjaxJson.getError(e.getMessage()); } @ExceptionHandler({InvalidShortLinkException.class}) @ResponseBody @ResponseStatus(HttpStatus.NOT_FOUND) public AjaxJson getFileInfoException(InvalidShortLinkException e) { return AjaxJson.getError(e.getMessage()); } @ExceptionHandler({IllegalDownloadLinkException.class}) @ResponseBody @ResponseStatus(HttpStatus.FORBIDDEN) public AjaxJson getFileInfoException(IllegalDownloadLinkException e) { return AjaxJson.getError(e.getMessage()); } @ExceptionHandler({LoginVerifyException.class}) @ResponseBody @ResponseStatus(HttpStatus.FORBIDDEN) public AjaxJson loginVerifyException(LoginVerifyException e) { return AjaxJson.getError(e.getMessage()); } @ExceptionHandler(value = {MethodArgumentNotValidException.class, BindException.class}) @ResponseBody public AjaxJson> handleValidException(Exception e) { BindingResult bindingResult = null; if (e instanceof MethodArgumentNotValidException) { bindingResult = ((MethodArgumentNotValidException) e).getBindingResult(); } else if (e instanceof BindException) { bindingResult = ((BindException) e).getBindingResult(); } Map errorMap = new HashMap<>(16); Optional.ofNullable(bindingResult) .map(BindingResult::getFieldErrors) .ifPresent(fieldErrors -> { for (FieldError fieldError : fieldErrors) { errorMap.put(fieldError.getField(), fieldError.getDefaultMessage()); } }); return new AjaxJson<>(400, "非法参数 !", errorMap); } @ExceptionHandler({FileNotFoundException.class}) @ResponseBody @ResponseStatus(HttpStatus.NOT_FOUND) public AjaxJson fileNotFound() { return AjaxJson.getError("文件不存在"); } /** * 密码校验异常 */ @ExceptionHandler({PasswordVerifyException.class}) @ResponseBody @ResponseStatus public AjaxJson passwordVerifyException(PasswordVerifyException ex) { return AjaxJson.get(ex.getCode(), ex.getMessage()); } /** * 初始化时自动设置 cors 异常 */ @ExceptionHandler({StorageSourceAutoConfigCorsException.class}) @ResponseBody @ResponseStatus public AjaxJson autoConfigCorsException(StorageSourceAutoConfigCorsException ex) { log.error("跨域配置失败, 存储源初始化信息 {}, 异常信息: ", JSON.toJSON(ex.getIStorageParam()), ex); return AjaxJson.getError(ex.getMessage()); } @ExceptionHandler({StorageSourceNotSupportProxyUploadException.class}) @ResponseBody @ResponseStatus(HttpStatus.BAD_REQUEST) public AjaxJson storageSourceNotSupportProxyUploadException(StorageSourceNotSupportProxyUploadException e) { return AjaxJson.getError("非法操作, 当前存储源不支持此方式上传."); } @ExceptionHandler(value = DisableProxyDownloadException.class) @ResponseBody @ResponseStatus(HttpStatus.BAD_REQUEST) public AjaxJson disableProxyDownloadException(DisableProxyDownloadException e) { return AjaxJson.getError(e.getResultMessage()); } @ExceptionHandler({HttpMessageNotReadableException.class}) @ResponseBody @ResponseStatus(HttpStatus.BAD_REQUEST) public AjaxJson handlerHttpMessageNotReadableException(HttpMessageNotReadableException e) { return AjaxJson.getBadRequestError("请求参数不合法"); } /** * 捕获 ClientAbortException 异常, 不做任何处理, 防止出现大量堆栈日志输出, 此异常不影响功能. */ @ExceptionHandler({HttpMediaTypeNotAcceptableException.class, ClientAbortException.class}) @ResponseBody @ResponseStatus public void clientAbortException() { // if (log.isDebugEnabled()) { // log.debug("出现了断开异常:", ex); // } } /** * 捕获 IORuntimeException 异常, 如果 cause 是 ClientAbortException, 不做任何处理, 防止出现大量堆栈日志输出, 此异常不影响功能. */ @ExceptionHandler({ IORuntimeException.class }) @ResponseBody @ResponseStatus public void ioRuntimeException(IORuntimeException e) { if (e.getCause() instanceof ClientAbortException) { // log.info("捕获 ClientAbortException 异常, 不做任何处理, 防止出现大量堆栈日志输出, 此异常不影响功能."); return; } log.error(e.getMessage(), e); } @ExceptionHandler @ResponseBody @ResponseStatus public AjaxJson extraExceptionHandler(Exception e) { log.error(e.getMessage(), e); if (e.getClass() == Exception.class) { return AjaxJson.getError("系统异常, 请联系管理员"); } else { return AjaxJson.getError(e.getMessage()); } } /** * 登录异常拦截器 */ @ExceptionHandler(NotLoginException.class) @ResponseStatus(HttpStatus.UNAUTHORIZED) @ResponseBody public AjaxJson handlerNotLoginException(NotLoginException e) { HttpServletRequest request = RequestHolder.getRequest(); String axiosRequest = request.getHeader("axios-request"); if (StrUtil.isNotEmpty(axiosRequest)){ return AjaxJson.getNotLogin(); } try { String domain = systemConfigService.getRealFrontDomain(); if (StrUtil.isEmpty(domain)) { domain = "/"; } String loginUrl = StringUtils.removeDuplicateSlashes(domain + "/login"); RequestHolder.getResponse().sendRedirect(loginUrl); } catch (IOException ex) { throw new RuntimeException(ex); } return null; } } \ No newline at end of file diff --git a/src/main/java/im/zhaojun/zfile/core/exception/http/HttpResponseStatusErrorException.java b/src/main/java/im/zhaojun/zfile/core/exception/http/HttpResponseStatusErrorException.java deleted file mode 100644 index 6db55cc..0000000 --- a/src/main/java/im/zhaojun/zfile/core/exception/http/HttpResponseStatusErrorException.java +++ /dev/null @@ -1,12 +0,0 @@ -package im.zhaojun.zfile.core.exception.http; - -/** - * Http 请求状态码异常 (返回状态码为 5xx 抛出此异常) - * @author zhaojun - */ -public class HttpResponseStatusErrorException extends RuntimeException { - - public HttpResponseStatusErrorException(String message) { - super(message); - } -} \ No newline at end of file diff --git a/src/main/java/im/zhaojun/zfile/core/exception/status/BadRequestAccessException.java b/src/main/java/im/zhaojun/zfile/core/exception/status/BadRequestAccessException.java new file mode 100644 index 0000000..832187e --- /dev/null +++ b/src/main/java/im/zhaojun/zfile/core/exception/status/BadRequestAccessException.java @@ -0,0 +1,18 @@ +package im.zhaojun.zfile.core.exception.status; + +import im.zhaojun.zfile.core.exception.GlobalExceptionHandler; +import im.zhaojun.zfile.core.exception.core.BizException; + +/** + * 错误请求异常, 表示请求参数有误或者服务器无法理解, 一般返回 400 状态码 + *

+ * 需要全局异常处理器捕获此异常, 并记录日志. {@link GlobalExceptionHandler#badRequestAccessException(BadRequestAccessException)} + * + * @author zhaojun + */ +public class BadRequestAccessException extends BizException { + + public BadRequestAccessException(String message) { + super(message); + } +} \ No newline at end of file diff --git a/src/main/java/im/zhaojun/zfile/core/exception/status/ForbiddenAccessException.java b/src/main/java/im/zhaojun/zfile/core/exception/status/ForbiddenAccessException.java new file mode 100644 index 0000000..8891829 --- /dev/null +++ b/src/main/java/im/zhaojun/zfile/core/exception/status/ForbiddenAccessException.java @@ -0,0 +1,19 @@ +package im.zhaojun.zfile.core.exception.status; + +import im.zhaojun.zfile.core.exception.ErrorCode; +import im.zhaojun.zfile.core.exception.core.BizException; + +/** + * 禁止访问异常, 表示用户没有权限访问该资源, 一般返回 403 状态码. (已经有身份,如果没有身份,应该是 UnauthorizedAccessException) + *

+ * 需要全局异常处理器捕获此异常, 并记录日志. {@link im.zhaojun.zfile.core.exception.GlobalExceptionHandler#forbiddenAccessException} + * + * @author zhaojun + */ +public class ForbiddenAccessException extends BizException { + + public ForbiddenAccessException(ErrorCode errorCode) { + super(errorCode); + } + +} \ No newline at end of file diff --git a/src/main/java/im/zhaojun/zfile/core/exception/status/MethodNotAllowedAccessException.java b/src/main/java/im/zhaojun/zfile/core/exception/status/MethodNotAllowedAccessException.java new file mode 100644 index 0000000..9c3a21d --- /dev/null +++ b/src/main/java/im/zhaojun/zfile/core/exception/status/MethodNotAllowedAccessException.java @@ -0,0 +1,19 @@ +package im.zhaojun.zfile.core.exception.status; + +import im.zhaojun.zfile.core.exception.ErrorCode; +import im.zhaojun.zfile.core.exception.GlobalExceptionHandler; +import im.zhaojun.zfile.core.exception.core.BizException; + +/** + * 错误请求异常, 表示请求方法有误或者服务器无法理解, 一般返回 405 状态码 + *

+ * 需要全局异常处理器捕获此异常, 并记录日志. {@link GlobalExceptionHandler#methodNotAllowedAccessException(MethodNotAllowedAccessException)} + * + * @author zhaojun + */ +public class MethodNotAllowedAccessException extends BizException { + + public MethodNotAllowedAccessException(ErrorCode errorCode) { + super(errorCode); + } +} \ No newline at end of file diff --git a/src/main/java/im/zhaojun/zfile/core/exception/status/NotFoundAccessException.java b/src/main/java/im/zhaojun/zfile/core/exception/status/NotFoundAccessException.java new file mode 100644 index 0000000..289dace --- /dev/null +++ b/src/main/java/im/zhaojun/zfile/core/exception/status/NotFoundAccessException.java @@ -0,0 +1,19 @@ +package im.zhaojun.zfile.core.exception.status; + +import im.zhaojun.zfile.core.exception.ErrorCode; +import im.zhaojun.zfile.core.exception.core.BizException; + +/** + * 访问内容不存在异常, 表示用户请求的资源不存在时抛出, 一般返回 404 状态码. + *

+ * 需要全局异常处理器捕获此异常, 并记录日志. {@link im.zhaojun.zfile.core.exception.GlobalExceptionHandler#notFoundAccessException} + * + * @author zhaojun + */ +public class NotFoundAccessException extends BizException { + + public NotFoundAccessException(ErrorCode errorCode) { + super(errorCode); + } + +} \ No newline at end of file diff --git a/src/main/java/im/zhaojun/zfile/core/exception/status/UnauthorizedAccessException.java b/src/main/java/im/zhaojun/zfile/core/exception/status/UnauthorizedAccessException.java new file mode 100644 index 0000000..3241db4 --- /dev/null +++ b/src/main/java/im/zhaojun/zfile/core/exception/status/UnauthorizedAccessException.java @@ -0,0 +1,18 @@ +package im.zhaojun.zfile.core.exception.status; + +import im.zhaojun.zfile.core.exception.core.BizException; + +/** + * 禁止访问异常, 表示用户未进行身份认证, 一般返回 401 状态码. + *

+ * 需要全局异常处理器捕获此异常, 并记录日志. {@link im.zhaojun.zfile.core.exception.GlobalExceptionHandler#unauthorizedAccessException} + * + * @author zhaojun + */ +public class UnauthorizedAccessException extends BizException { + + public UnauthorizedAccessException(String message) { + super(message); + } + +} \ No newline at end of file diff --git a/src/main/java/im/zhaojun/zfile/core/exception/system/UploadFileFailSystemException.java b/src/main/java/im/zhaojun/zfile/core/exception/system/UploadFileFailSystemException.java new file mode 100644 index 0000000..b2ff6c1 --- /dev/null +++ b/src/main/java/im/zhaojun/zfile/core/exception/system/UploadFileFailSystemException.java @@ -0,0 +1,39 @@ +package im.zhaojun.zfile.core.exception.system; + +import im.zhaojun.zfile.core.exception.ErrorCode; +import im.zhaojun.zfile.core.exception.GlobalExceptionHandler; +import im.zhaojun.zfile.core.exception.core.SystemException; +import im.zhaojun.zfile.module.storage.model.enums.StorageTypeEnum; +import lombok.Getter; + + +/** + * 上传文件失败系统异常, 该异常用户无法处理,需要记录日志, 属于系统异常. 如: 网络异常, 目标存储源异常等 + *

+ * 需要全局异常处理器捕获此异常, 并记录日志. {@link GlobalExceptionHandler#uploadFileFailSystemException(UploadFileFailSystemException)} + * + * @author zhaojun + */ +@Getter +public class UploadFileFailSystemException extends SystemException { + + private final StorageTypeEnum storageTypeEnum; + + private final String uploadPath; + + private final Integer inputStreamAvailable; + + private final int responseCode; + + private final String responseBody; + + public UploadFileFailSystemException(StorageTypeEnum storageTypeEnum, String uploadPath, Integer inputStreamAvailable, int responseCode, String responseBody) { + super(ErrorCode.BIZ_UPLOAD_FILE_ERROR); + this.storageTypeEnum = storageTypeEnum; + this.uploadPath = uploadPath; + this.inputStreamAvailable = inputStreamAvailable; + this.responseCode = responseCode; + this.responseBody = responseBody; + } + +} \ No newline at end of file diff --git a/src/main/java/im/zhaojun/zfile/core/exception/system/ZFileAuthorizationSystemException.java b/src/main/java/im/zhaojun/zfile/core/exception/system/ZFileAuthorizationSystemException.java new file mode 100644 index 0000000..c68e724 --- /dev/null +++ b/src/main/java/im/zhaojun/zfile/core/exception/system/ZFileAuthorizationSystemException.java @@ -0,0 +1,28 @@ +package im.zhaojun.zfile.core.exception.system; + +import im.zhaojun.zfile.core.exception.ErrorCode; +import im.zhaojun.zfile.core.exception.GlobalExceptionHandler; +import im.zhaojun.zfile.core.exception.core.SystemException; + +/** + * ZFile 授权异常 + *

+ * 需要全局异常处理器捕获此异常, 并记录日志. {@link GlobalExceptionHandler#zfileAuthorizationSystemException(ZFileAuthorizationSystemException)} + * + * @author zhaojun + */ +public class ZFileAuthorizationSystemException extends SystemException { + + public ZFileAuthorizationSystemException(String code, String message) { + super(code, message); + } + + public ZFileAuthorizationSystemException(ErrorCode errorCode) { + super(errorCode); + } + + public ZFileAuthorizationSystemException(ErrorCode errorCode, Throwable cause) { + super(errorCode, cause); + } + +} diff --git a/src/main/java/im/zhaojun/zfile/core/filter/CorsFilter.java b/src/main/java/im/zhaojun/zfile/core/filter/CorsFilter.java index 469cd64..7f161be 100644 --- a/src/main/java/im/zhaojun/zfile/core/filter/CorsFilter.java +++ b/src/main/java/im/zhaojun/zfile/core/filter/CorsFilter.java @@ -1,18 +1,20 @@ package im.zhaojun.zfile.core.filter; import cn.hutool.core.util.ObjectUtil; +import im.zhaojun.zfile.core.constant.ZFileHttpHeaderConstant; +import im.zhaojun.zfile.core.util.StringUtils; +import jakarta.servlet.*; +import jakarta.servlet.annotation.WebFilter; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.core.annotation.Order; import org.springframework.http.HttpHeaders; +import org.springframework.stereotype.Component; import org.springframework.web.cors.CorsUtils; -import javax.servlet.Filter; -import javax.servlet.FilterChain; -import javax.servlet.ServletException; -import javax.servlet.ServletRequest; -import javax.servlet.ServletResponse; -import javax.servlet.annotation.WebFilter; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; import java.io.IOException; +import java.util.Arrays; +import java.util.List; /** * 开启跨域支持. 一般用于开发环境, 或前后端分离部署时开启. @@ -20,16 +22,24 @@ import java.io.IOException; * @author zhaojun */ @WebFilter(urlPatterns = "/*") +@Order(Integer.MIN_VALUE) +@Component public class CorsFilter implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest httpServletRequest = (HttpServletRequest) request; HttpServletResponse httpServletResponse = (HttpServletResponse) response; + + if (httpServletRequest.getRequestURI().equals("/favicon.ico")) { + return; + } String header = httpServletRequest.getHeader(HttpHeaders.ORIGIN); + + List allowHeaders = Arrays.asList("Origin", "X-Requested-With", "Content-Type", "Accept", ZFileHttpHeaderConstant.ZFILE_TOKEN, ZFileHttpHeaderConstant.AXIOS_REQUEST, ZFileHttpHeaderConstant.AXIOS_FROM); httpServletResponse.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, ObjectUtil.defaultIfNull(header, "*")); - httpServletResponse.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, "Origin, X-Requested-With, Content-Type, Accept, zfile-token, axios-request"); + httpServletResponse.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, StringUtils.join(",", allowHeaders)); httpServletResponse.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, "GET, POST, PUT, DELETE, OPTIONS"); httpServletResponse.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "false"); httpServletResponse.setHeader(HttpHeaders.ACCESS_CONTROL_MAX_AGE, "600"); diff --git a/src/main/java/im/zhaojun/zfile/core/filter/MDCFilter.java b/src/main/java/im/zhaojun/zfile/core/filter/MDCFilter.java index fdb045b..31edd30 100644 --- a/src/main/java/im/zhaojun/zfile/core/filter/MDCFilter.java +++ b/src/main/java/im/zhaojun/zfile/core/filter/MDCFilter.java @@ -1,22 +1,20 @@ package im.zhaojun.zfile.core.filter; -import cn.dev33.satoken.stp.StpUtil; import cn.hutool.core.util.IdUtil; -import cn.hutool.extra.servlet.ServletUtil; +import cn.hutool.extra.servlet.JakartaServletUtil; import im.zhaojun.zfile.core.constant.MdcConstant; +import im.zhaojun.zfile.core.util.ZFileAuthUtil; +import jakarta.servlet.*; +import jakarta.servlet.annotation.WebFilter; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import org.slf4j.MDC; -import javax.servlet.Filter; -import javax.servlet.FilterChain; -import javax.servlet.ServletException; -import javax.servlet.ServletRequest; -import javax.servlet.ServletResponse; -import javax.servlet.annotation.WebFilter; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; import java.io.IOException; /** + * MDC 过滤器, 用于写入 TraceId, 请求 IP, 用户名等信息到日志中. + * * @author zhaojun */ @WebFilter(urlPatterns = "/*") @@ -28,8 +26,8 @@ public class MDCFilter implements Filter { HttpServletResponse httpServletResponse = (HttpServletResponse) response; MDC.put(MdcConstant.TRACE_ID, IdUtil.fastUUID()); - MDC.put(MdcConstant.IP, ServletUtil.getClientIP(httpServletRequest)); - MDC.put(MdcConstant.USER, StpUtil.isLogin() ? StpUtil.getLoginIdAsString() : "anonymous"); + MDC.put(MdcConstant.IP, JakartaServletUtil.getClientIP(httpServletRequest)); + MDC.put(MdcConstant.USER, ZFileAuthUtil.getCurrentUserId().toString()); try { filterChain.doFilter(httpServletRequest, httpServletResponse); diff --git a/src/main/java/im/zhaojun/zfile/core/filter/SecurityFilter.java b/src/main/java/im/zhaojun/zfile/core/filter/SecurityFilter.java new file mode 100644 index 0000000..cdab086 --- /dev/null +++ b/src/main/java/im/zhaojun/zfile/core/filter/SecurityFilter.java @@ -0,0 +1,80 @@ +package im.zhaojun.zfile.core.filter; + +import cn.hutool.extra.servlet.JakartaServletUtil; +import cn.hutool.extra.spring.SpringUtil; +import im.zhaojun.zfile.core.constant.RuleTypeConstant; +import im.zhaojun.zfile.core.util.StringUtils; +import im.zhaojun.zfile.core.util.matcher.IRuleMatcher; +import im.zhaojun.zfile.core.util.matcher.RuleMatcherFactory; +import im.zhaojun.zfile.module.config.model.dto.SystemConfigDTO; +import im.zhaojun.zfile.module.config.service.SystemConfigService; +import jakarta.servlet.*; +import jakarta.servlet.annotation.WebFilter; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; + +import java.io.IOException; +import java.util.List; + +/** + * 检测访问的 IP 和 UA 是否符合系统安全设置中的规则 + * + * @author zhaojun + */ +@WebFilter(urlPatterns = "/*") +public class SecurityFilter implements Filter { + + private static volatile SystemConfigService systemConfigService; + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException { + HttpServletRequest httpServletRequest = (HttpServletRequest) request; + HttpServletResponse httpServletResponse = (HttpServletResponse) response; + + // 双重检测锁, 防止多次初始化 + if (systemConfigService == null) { + synchronized (this) { + if (systemConfigService == null) { + systemConfigService = SpringUtil.getBean(SystemConfigService.class); + } + } + } + + SystemConfigDTO systemConfig = systemConfigService.getSystemConfig(); + String accessIpBlocklist = systemConfig.getAccessIpBlocklist(); + String accessUaBlocklist = systemConfig.getAccessUaBlocklist(); + + // 判断当前访问 IP 是否在黑名单中 + String currentAccessIp = JakartaServletUtil.getClientIP(httpServletRequest); + if (StringUtils.isNotBlank(accessIpBlocklist) && checkIsDisableIP(accessIpBlocklist, currentAccessIp)) { + httpServletResponse.setStatus(HttpStatus.FORBIDDEN.value()); + httpServletResponse.getWriter().write("disable access.[" + currentAccessIp + "]"); + return; + } + + // 判断当前访问 User-Agent 是否在黑名单中 + String userAgent = httpServletRequest.getHeader(HttpHeaders.USER_AGENT); + if (StringUtils.isNotBlank(accessUaBlocklist) && checkIsDisableUA(accessUaBlocklist, userAgent)) { + httpServletResponse.setStatus(HttpStatus.FORBIDDEN.value()); + httpServletResponse.getWriter().write("disable access.[" + userAgent + "]"); + return; + } + + filterChain.doFilter(httpServletRequest, httpServletResponse); + } + + private boolean checkIsDisableIP(String accessIpBlocklist, String currentAccessIp) { + IRuleMatcher ruleMatcher = RuleMatcherFactory.getRuleMatcher(RuleTypeConstant.IP); + List ruleList = StringUtils.split(accessIpBlocklist, StringUtils.LF); + return ruleMatcher.matchAny(ruleList, currentAccessIp); + } + + private boolean checkIsDisableUA(String accessUaBlocklist, String currentAccessUA) { + IRuleMatcher ruleMatcher = RuleMatcherFactory.getRuleMatcher(RuleTypeConstant.SPRING_SIMPLE); + List ruleList = StringUtils.split(accessUaBlocklist, StringUtils.LF); + return ruleMatcher.matchAny(ruleList, currentAccessUA); + } + +} \ No newline at end of file diff --git a/src/main/java/im/zhaojun/zfile/core/httpclient/ZFileOkHttp3ClientHttpRequestFactory.java b/src/main/java/im/zhaojun/zfile/core/httpclient/ZFileOkHttp3ClientHttpRequestFactory.java deleted file mode 100644 index 97ee508..0000000 --- a/src/main/java/im/zhaojun/zfile/core/httpclient/ZFileOkHttp3ClientHttpRequestFactory.java +++ /dev/null @@ -1,22 +0,0 @@ -package im.zhaojun.zfile.core.httpclient; - -import im.zhaojun.zfile.core.httpclient.logging.HttpLoggingInterceptor; -import okhttp3.OkHttpClient; -import org.springframework.http.client.OkHttp3ClientHttpRequestFactory; - -/** - * 自建 OkHTTP3 客户端工厂类, 增加日志输出拦截器. - * @author zhaojun - */ -public class ZFileOkHttp3ClientHttpRequestFactory extends OkHttp3ClientHttpRequestFactory { - - public ZFileOkHttp3ClientHttpRequestFactory() { - // 使用 OkHttp3 作为底层请求库, 并设置重试机制和日志拦截器 - super(new OkHttpClient() - .newBuilder() - .addNetworkInterceptor(new HttpLoggingInterceptor(HttpLoggingInterceptor.Logger.DEBUG) - .setLevel(HttpLoggingInterceptor.Level.HEADERS)) - .build()); - } - -} \ No newline at end of file diff --git a/src/main/java/im/zhaojun/zfile/core/httpclient/logging/HttpLoggingInterceptor.java b/src/main/java/im/zhaojun/zfile/core/httpclient/logging/HttpLoggingInterceptor.java deleted file mode 100644 index 866259d..0000000 --- a/src/main/java/im/zhaojun/zfile/core/httpclient/logging/HttpLoggingInterceptor.java +++ /dev/null @@ -1,340 +0,0 @@ -/* - * Copyright (C) 2015 Square, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package im.zhaojun.zfile.core.httpclient.logging; - -import lombok.extern.slf4j.Slf4j; -import okhttp3.Connection; -import okhttp3.Headers; -import okhttp3.Interceptor; -import okhttp3.MediaType; -import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.RequestBody; -import okhttp3.Response; -import okhttp3.ResponseBody; -import okhttp3.internal.http.HttpHeaders; -import okhttp3.internal.platform.Platform; -import okio.Buffer; -import okio.BufferedSource; -import okio.GzipSource; - -import java.io.EOFException; -import java.io.IOException; -import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; -import java.util.Collections; -import java.util.Set; -import java.util.TreeSet; -import java.util.concurrent.TimeUnit; - -import static okhttp3.internal.platform.Platform.INFO; - -/** - * 此类代码来源于 logging-interceptor. - *
- * An OkHttp interceptor which logs request and response information. Can be applied as an - * {@linkplain OkHttpClient#interceptors() application interceptor} or as a {@linkplain - * OkHttpClient#networkInterceptors() network interceptor}.

The format of the logs created by - * this class should not be considered stable and may change slightly between releases. If you need - * a stable logging format, use your own interceptor. - *
- * @author zhaojun - */ -@Slf4j -public final class HttpLoggingInterceptor implements Interceptor { - - private static final Charset UTF8 = StandardCharsets.UTF_8; - - public enum Level { - /** No logs. */ - NONE, - /** - * Logs request and response lines. - * - *

Example: - *

{@code
-		 * --> POST /greeting http/1.1 (3-byte body)
-		 *
-		 * <-- 200 OK (22ms, 6-byte body)
-		 * }
- */ - BASIC, - /** - * Logs request and response lines and their respective headers. - * - *

Example: - *

{@code
-		 * --> POST /greeting http/1.1
-		 * Host: example.com
-		 * Content-Type: plain/text
-		 * Content-Length: 3
-		 * --> END POST
-		 *
-		 * <-- 200 OK (22ms)
-		 * Content-Type: plain/text
-		 * Content-Length: 6
-		 * <-- END HTTP
-		 * }
- */ - HEADERS, - /** - * Logs request and response lines and their respective headers and bodies (if present). - * - *

Example: - *

{@code
-		 * --> POST /greeting http/1.1
-		 * Host: example.com
-		 * Content-Type: plain/text
-		 * Content-Length: 3
-		 *
-		 * Hi?
-		 * --> END POST
-		 *
-		 * <-- 200 OK (22ms)
-		 * Content-Type: plain/text
-		 * Content-Length: 6
-		 *
-		 * Hello!
-		 * <-- END HTTP
-		 * }
- */ - BODY - } - - public interface Logger { - void log(String message); - - /** A {@link Logger} defaults output appropriate for the current platform. */ - Logger DEFAULT = message -> Platform.get().log(message, INFO, null); - - Logger DEBUG = log::debug; - Logger TRACE = log::trace; - - } - - public HttpLoggingInterceptor() { - this(Logger.DEFAULT); - } - - public HttpLoggingInterceptor(Logger logger) { - this.logger = logger; - } - - private final Logger logger; - - private volatile Set headersToRedact = Collections.emptySet(); - - public void redactHeader(String name) { - Set newHeadersToRedact = new TreeSet<>(String.CASE_INSENSITIVE_ORDER); - newHeadersToRedact.addAll(headersToRedact); - newHeadersToRedact.add(name); - headersToRedact = newHeadersToRedact; - } - - private volatile Level level = Level.NONE; - - /** Change the level at which this interceptor logs. */ - public HttpLoggingInterceptor setLevel(Level level) { - if (level == null) throw new NullPointerException("level == null. Use Level.NONE instead."); - this.level = level; - return this; - } - - public Level getLevel() { - return level; - } - - @Override public Response intercept(Chain chain) throws IOException { - Level level = this.level; - - Request request = chain.request(); - if (level == Level.NONE) { - return chain.proceed(request); - } - - boolean logBody = level == Level.BODY; - boolean logHeaders = logBody || level == Level.HEADERS; - - RequestBody requestBody = request.body(); - boolean hasRequestBody = requestBody != null; - - Connection connection = chain.connection(); - String requestStartMessage = "--> " - + request.method() - + ' ' + request.url() - + (connection != null ? " " + connection.protocol() : ""); - if (!logHeaders && hasRequestBody) { - requestStartMessage += " (" + requestBody.contentLength() + "-byte body)"; - } - logger.log(requestStartMessage); - - if (logHeaders) { - if (hasRequestBody) { - // Request body headers are only present when installed as a network interceptor. Force - // them to be included (when available) so there values are known. - if (requestBody.contentType() != null) { - logger.log("Content-Type: " + requestBody.contentType()); - } - if (requestBody.contentLength() != -1) { - logger.log("Content-Length: " + requestBody.contentLength()); - } - } - - Headers headers = request.headers(); - for (int i = 0, count = headers.size(); i < count; i++) { - String name = headers.name(i); - // Skip headers from the request body as they are explicitly logged above. - if (!"Content-Type".equalsIgnoreCase(name) && !"Content-Length".equalsIgnoreCase(name)) { - logHeader(headers, i); - } - } - - if (!logBody || !hasRequestBody) { - logger.log("--> END " + request.method()); - } else if (bodyHasUnknownEncoding(request.headers())) { - logger.log("--> END " + request.method() + " (encoded body omitted)"); - } else if (requestBody.isDuplex()) { - logger.log("--> END " + request.method() + " (duplex request body omitted)"); - } else { - Buffer buffer = new Buffer(); - requestBody.writeTo(buffer); - - Charset charset = UTF8; - MediaType contentType = requestBody.contentType(); - if (contentType != null) { - charset = contentType.charset(UTF8); - } - - logger.log(""); - if (isPlaintext(buffer)) { - logger.log(buffer.readString(charset)); - logger.log("--> END " + request.method() - + " (" + requestBody.contentLength() + "-byte body)"); - } else { - logger.log("--> END " + request.method() + " (binary " - + requestBody.contentLength() + "-byte body omitted)"); - } - } - } - - long startNs = System.nanoTime(); - Response response; - try { - response = chain.proceed(request); - } catch (Exception e) { - logger.log("<-- HTTP FAILED: " + e); - throw e; - } - long tookMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNs); - - ResponseBody responseBody = response.body(); - long contentLength = responseBody.contentLength(); - String bodySize = contentLength != -1 ? contentLength + "-byte" : "unknown-length"; - logger.log("<-- " - + response.code() - + (response.message().isEmpty() ? "" : ' ' + response.message()) - + ' ' + response.request().url() - + " (" + tookMs + "ms" + (!logHeaders ? ", " + bodySize + " body" : "") + ')'); - - if (logHeaders) { - Headers headers = response.headers(); - for (int i = 0, count = headers.size(); i < count; i++) { - logHeader(headers, i); - } - - if (!logBody || !HttpHeaders.hasBody(response)) { - logger.log("<-- END HTTP"); - } else if (bodyHasUnknownEncoding(response.headers())) { - logger.log("<-- END HTTP (encoded body omitted)"); - } else { - BufferedSource source = responseBody.source(); - source.request(Long.MAX_VALUE); // Buffer the entire body. - Buffer buffer = source.getBuffer(); - - Long gzippedLength = null; - if ("gzip".equalsIgnoreCase(headers.get("Content-Encoding"))) { - gzippedLength = buffer.size(); - try (GzipSource gzippedResponseBody = new GzipSource(buffer.clone())) { - buffer = new Buffer(); - buffer.writeAll(gzippedResponseBody); - } - } - - Charset charset = UTF8; - MediaType contentType = responseBody.contentType(); - if (contentType != null) { - charset = contentType.charset(UTF8); - } - - if (!isPlaintext(buffer)) { - logger.log(""); - logger.log("<-- END HTTP (binary " + buffer.size() + "-byte body omitted)"); - return response; - } - - if (contentLength != 0) { - logger.log(""); - logger.log(buffer.clone().readString(charset)); - } - - if (gzippedLength != null) { - logger.log("<-- END HTTP (" + buffer.size() + "-byte, " - + gzippedLength + "-gzipped-byte body)"); - } else { - logger.log("<-- END HTTP (" + buffer.size() + "-byte body)"); - } - } - } - - return response; - } - - private void logHeader(Headers headers, int i) { - String value = headersToRedact.contains(headers.name(i)) ? "██" : headers.value(i); - logger.log(headers.name(i) + ": " + value); - } - - /** - * Returns true if the body in question probably contains human readable text. Uses a small sample - * of code points to detect unicode control characters commonly used in binary file signatures. - */ - static boolean isPlaintext(Buffer buffer) { - try { - Buffer prefix = new Buffer(); - long byteCount = buffer.size() < 64 ? buffer.size() : 64; - buffer.copyTo(prefix, 0, byteCount); - for (int i = 0; i < 16; i++) { - if (prefix.exhausted()) { - break; - } - int codePoint = prefix.readUtf8CodePoint(); - if (Character.isISOControl(codePoint) && !Character.isWhitespace(codePoint)) { - return false; - } - } - return true; - } catch (EOFException e) { - return false; // Truncated UTF-8 sequence. - } - } - - private static boolean bodyHasUnknownEncoding(Headers headers) { - String contentEncoding = headers.get("Content-Encoding"); - return contentEncoding != null - && !contentEncoding.equalsIgnoreCase("identity") - && !contentEncoding.equalsIgnoreCase("gzip"); - } -} \ No newline at end of file diff --git a/src/main/java/im/zhaojun/zfile/core/io/EnsureContentLengthInputStreamResource.java b/src/main/java/im/zhaojun/zfile/core/io/EnsureContentLengthInputStreamResource.java new file mode 100644 index 0000000..3eb59ad --- /dev/null +++ b/src/main/java/im/zhaojun/zfile/core/io/EnsureContentLengthInputStreamResource.java @@ -0,0 +1,66 @@ +/* + * Copyright 2002-2018 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.zhaojun.zfile.core.io; + +import org.springframework.core.io.ByteArrayResource; +import org.springframework.core.io.InputStreamResource; + +import java.io.InputStream; + +/** + * + * 自定义 EnsureContentLengthInputStreamResource 可以保证必须实现 InputStream 的 contentLength 方法返回实际的长度. + * 此类相较于 {@link org.springframework.core.io.InputStreamResource} 仅实现了 contentLength 方法. + *

+ * {@link org.springframework.core.io.Resource} implementation for a given {@link InputStream}. + *

Should only be used if no other specific {@code Resource} implementation + * is applicable. In particular, prefer {@link ByteArrayResource} or any of the + * file-based {@code Resource} implementations where possible. + * + *

In contrast to other {@code Resource} implementations, this is a descriptor + * for an already opened resource - therefore returning {@code true} from + * {@link #isOpen()}. Do not use an {@code InputStreamResource} if you need to + * keep the resource descriptor somewhere, or if you need to read from a stream + * multiple times. + * + * @author Juergen Hoeller + * @author Sam Brannen + * @since 28.12.2003 + * @see ByteArrayResource + * @see org.springframework.core.io.ClassPathResource + * @see org.springframework.core.io.FileSystemResource + * @see org.springframework.core.io.UrlResource + */ +public class EnsureContentLengthInputStreamResource extends InputStreamResource { + + private final long contentLength; + + /** + * Create a new InputStreamResource. + * @param inputStream the InputStream to use + */ + public EnsureContentLengthInputStreamResource(InputStream inputStream, long contentLength) { + super(inputStream); + this.contentLength = contentLength; + } + + @Override + public long contentLength() { + return contentLength; + } + +} \ No newline at end of file diff --git a/src/main/java/im/zhaojun/zfile/core/io/ThrottledInputStream.java b/src/main/java/im/zhaojun/zfile/core/io/ThrottledInputStream.java new file mode 100644 index 0000000..2d8223f --- /dev/null +++ b/src/main/java/im/zhaojun/zfile/core/io/ThrottledInputStream.java @@ -0,0 +1,91 @@ +package im.zhaojun.zfile.core.io; + +import com.google.common.util.concurrent.RateLimiter; +import jakarta.validation.constraints.NotNull; + +import java.io.IOException; +import java.io.InputStream; + +/** + * 使用装饰器模式, 限速输入流, 单位为字节/秒. + * + * @author zhaojun + */ +public final class ThrottledInputStream extends InputStream { + + private final InputStream originalInputStream; + private final RateLimiter rateLimiter; + + public ThrottledInputStream(InputStream originalInputStream, double bytesPerSecond) { + this.originalInputStream = originalInputStream; + this.rateLimiter = RateLimiter.create(bytesPerSecond); + } + + @Override + public int read() throws IOException { + rateLimiter.acquire(); + return originalInputStream.read(); + } + + @Override + public int read(@NotNull byte[] b) throws IOException { + return originalInputStream.read(b); + } + + @Override + public int read(@NotNull byte[] b, int off, int len) throws IOException { + rateLimiter.acquire(len); + return originalInputStream.read(b, off, len); + } + + @Override + public byte[] readAllBytes() throws IOException { + return originalInputStream.readAllBytes(); + } + + @Override + public byte[] readNBytes(int len) throws IOException { + return originalInputStream.readNBytes(len); + } + + @Override + public int readNBytes(byte[] b, int off, int len) throws IOException { + return originalInputStream.readNBytes(b, off, len); + } + + @Override + public long skip(long n) throws IOException { + return originalInputStream.skip(n); + } + + @Override + public void skipNBytes(long n) throws IOException { + originalInputStream.skipNBytes(n); + } + + @Override + public int available() throws IOException { + return originalInputStream.available(); + } + + @Override + public void close() throws IOException { + originalInputStream.close(); + } + + @Override + public void mark(int readlimit) { + originalInputStream.mark(readlimit); + } + + @Override + public void reset() throws IOException { + originalInputStream.reset(); + } + + @Override + public boolean markSupported() { + return originalInputStream.markSupported(); + } + +} \ No newline at end of file diff --git a/src/main/java/im/zhaojun/zfile/core/io/ThrottledOutputStream.java b/src/main/java/im/zhaojun/zfile/core/io/ThrottledOutputStream.java new file mode 100644 index 0000000..5c04136 --- /dev/null +++ b/src/main/java/im/zhaojun/zfile/core/io/ThrottledOutputStream.java @@ -0,0 +1,57 @@ +package im.zhaojun.zfile.core.io; + +import com.google.common.util.concurrent.RateLimiter; +import lombok.extern.slf4j.Slf4j; + +import java.io.IOException; +import java.io.OutputStream; + +/** + * 使用装饰器模式, 限速输出流, 单位为字节/秒. + * + * @author zhaojun + */ +@Slf4j +public final class ThrottledOutputStream extends OutputStream { + + private final OutputStream originalOutputStream; + private final RateLimiter rateLimiter; + + public ThrottledOutputStream(OutputStream out, double bytesPerSecond) { + this.originalOutputStream = out; + this.rateLimiter = RateLimiter.create(bytesPerSecond); + } + + public void setRate(double bytesPerSecond) { + rateLimiter.setRate(bytesPerSecond); + } + + @Override + public void write(int b) throws IOException { + rateLimiter.acquire(); + originalOutputStream.write(b); + } + + @Override + public void write(byte[] b) throws IOException { + rateLimiter.acquire(b.length); + originalOutputStream.write(b); + } + + @Override + public void write(byte[] b, int off, int len) throws IOException { + rateLimiter.acquire(len); + originalOutputStream.write(b, off, len); + } + + @Override + public void flush() throws IOException { + originalOutputStream.flush(); + } + + @Override + public void close() throws IOException { + originalOutputStream.close(); + } + +} \ No newline at end of file diff --git a/src/main/java/im/zhaojun/zfile/core/model/request/PageQueryRequest.java b/src/main/java/im/zhaojun/zfile/core/model/request/PageQueryRequest.java index 877d99f..faf3cd3 100644 --- a/src/main/java/im/zhaojun/zfile/core/model/request/PageQueryRequest.java +++ b/src/main/java/im/zhaojun/zfile/core/model/request/PageQueryRequest.java @@ -1,24 +1,26 @@ package im.zhaojun.zfile.core.model.request; -import io.swagger.annotations.ApiModelProperty; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; /** + * 通用分页请求对象,可继承该类增加业务字段. + * * @author zhaojun */ @Data public class PageQueryRequest { - @ApiModelProperty(value="分页页数") + @Schema(name="分页页数") private Integer page = 1; - @ApiModelProperty(value="每页条数") + @Schema(name="每页条数") private Integer limit = 10; - @ApiModelProperty(value="排序字段") + @Schema(name="排序字段") private String orderBy = "create_date"; - @ApiModelProperty(value="排序顺序") + @Schema(name="排序顺序") private String orderDirection = "desc"; } \ No newline at end of file diff --git a/src/main/java/im/zhaojun/zfile/core/service/DynamicControllerManager.java b/src/main/java/im/zhaojun/zfile/core/service/DynamicControllerManager.java deleted file mode 100644 index d2a3a2a..0000000 --- a/src/main/java/im/zhaojun/zfile/core/service/DynamicControllerManager.java +++ /dev/null @@ -1,43 +0,0 @@ -package im.zhaojun.zfile.core.service; - -import org.springframework.stereotype.Service; -import org.springframework.web.servlet.mvc.method.RequestMappingInfo; -import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; - -import javax.annotation.Resource; -import java.lang.reflect.Method; - -/** - * @author zhaojun - */ -@Service -public class DynamicControllerManager { - - @Resource - private RequestMappingHandlerMapping requestMappingHandlerMapping; - - private RequestMappingInfo shortLinkMappingInfo; - - private Object shortLinkHandler; - - private Method shortLinkMethod; - - public void initDirectLinkPrefixPath(String path, Object handler, Method method) { - if (shortLinkMappingInfo != null) { - throw new RuntimeException("请勿重复初始化 DirectLinkPrefixPath."); - } - shortLinkMappingInfo = RequestMappingInfo.paths(path + "/{storageKey}/**").build(); - shortLinkHandler = handler; - shortLinkMethod = method; - requestMappingHandlerMapping.registerMapping(shortLinkMappingInfo, handler, method); - } - - public void changeDirectLinkPrefixPath(String path) { - if (shortLinkMappingInfo != null) { - requestMappingHandlerMapping.unregisterMapping(shortLinkMappingInfo); - } - shortLinkMappingInfo = RequestMappingInfo.paths(path + "/**").build(); - requestMappingHandlerMapping.registerMapping(shortLinkMappingInfo, shortLinkHandler, shortLinkMethod); - } - -} diff --git a/src/main/java/im/zhaojun/zfile/core/util/AjaxJson.java b/src/main/java/im/zhaojun/zfile/core/util/AjaxJson.java index c4f0a1f..39ae7f4 100644 --- a/src/main/java/im/zhaojun/zfile/core/util/AjaxJson.java +++ b/src/main/java/im/zhaojun/zfile/core/util/AjaxJson.java @@ -1 +1,107 @@ -package im.zhaojun.zfile.core.util; import io.swagger.annotations.ApiModelProperty; import java.io.Serializable; /** * ajax 请求返回 JSON 格式数据的封装 * * @author zhaojun */ public class AjaxJson implements Serializable { private static final long serialVersionUID = 1L; // 序列化版本号 public static final int CODE_SUCCESS = 0; // 成功状态码 public static final int CODE_ERROR = 500; // 错误状态码 public static final int CODE_WARNING = 501; // 警告状态码 public static final int CODE_NOT_JUR = 403; // 无权限状态码 public static final int CODE_NOT_LOGIN = 401; // 未登录状态码 public static final int CODE_INVALID_REQUEST = 400; // 无效请求状态码 public static final int REQUIRED_PASSWORD = 405; // 未输入密码 public static final int INVALID_PASSWORD = 406; // 无效的密码 @ApiModelProperty(value = "业务状态码,0 为正常,其他值均为异常,异常情况下见响应消息", example = "0") private final int code; @ApiModelProperty(value = "响应消息", example = "ok") private String msg; @ApiModelProperty(value = "响应数据") private T data; @ApiModelProperty(value = "数据总条数,分页情况有效") private final Long dataCount; @ApiModelProperty(value = "跟踪 ID") private String traceId; /** * 返回code */ public int getCode() { return this.code; } /** * 给 msg 赋值,连缀风格 */ public AjaxJson setMsg(String msg) { this.msg = msg; return this; } public String getMsg() { return this.msg; } /** * 给data赋值,连缀风格 */ public AjaxJson setData(T data) { this.data = data; return this; } // ============================ 构建 ================================== public AjaxJson(int code, String msg, T data) { this.code = code; this.msg = msg; this.data = data; this.dataCount = null; } public AjaxJson(int code, String msg, T data, Long dataCount) { this.code = code; this.msg = msg; this.data = data; this.dataCount = dataCount; } // 返回成功 public static AjaxJson getSuccess() { return new AjaxJson<>(CODE_SUCCESS, "ok", null, null); } public static AjaxJson getSuccess(String msg) { return new AjaxJson<>(CODE_SUCCESS, msg, null, null); } public static AjaxJson getSuccess(String msg, Object data) { return new AjaxJson<>(CODE_SUCCESS, msg, data, null); } public static AjaxJson getSuccessData(T data) { return new AjaxJson<>(CODE_SUCCESS, "ok", data, null); } public static AjaxJson getSuccessArray(Object... data) { return new AjaxJson<>(CODE_SUCCESS, "ok", data, null); } // 返回失败 public static AjaxJson getError() { return new AjaxJson<>(CODE_ERROR, "error", null, null); } public static AjaxJson getError(String msg) { return new AjaxJson<>(CODE_ERROR, msg, null, null); } public static AjaxJson getBadRequestError(String msg) { return new AjaxJson<>(CODE_INVALID_REQUEST, msg, null, null); } // 返回警告 public static AjaxJson getWarning() { return new AjaxJson<>(CODE_ERROR, "warning", null, null); } public static AjaxJson getWarning(String msg) { return new AjaxJson<>(CODE_WARNING, msg, null, null); } // 返回未登录 public static AjaxJson getNotLogin() { return new AjaxJson<>(CODE_NOT_LOGIN, "未登录,请登录后再次访问", null, null); } // 返回没有权限的 public static AjaxJson getNotJur(String msg) { return new AjaxJson<>(CODE_NOT_JUR, msg, null, null); } // 返回一个自定义状态码的 public static AjaxJson get(int code, String msg) { return new AjaxJson<>(code, msg, null, null); } // 返回分页和数据的 public static AjaxJson getPageData(Long dataCount, Object data) { return new AjaxJson<>(CODE_SUCCESS, "ok", data, dataCount); } // 返回,根据布尔值来确定最终结果的 (true=ok,false=error) public static AjaxJson getByBoolean(boolean b) { return b ? getSuccess("ok") : getError("error"); } @Override public String toString() { return "AjaxJson{" + "code=" + code + ", msg='" + msg + '\'' + ", data=" + data + ", dataCount=" + dataCount + ", traceId='" + traceId + '\'' + '}'; } public T getData() { return data; } public Long getDataCount() { return dataCount; } public void setTraceId(String traceId) { this.traceId = traceId; } public String getTraceId() { return traceId; } } \ No newline at end of file +package im.zhaojun.zfile.core.util; + +import im.zhaojun.zfile.core.exception.ErrorCode; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.ToString; + +import java.io.Serializable; + +/** + * ajax 请求返回 JSON 格式数据的封装 + * + * @author zhaojun + */ +@Data +@ToString +public class AjaxJson implements Serializable { + + private static final long serialVersionUID = 1L; // 序列化版本号 + + public static final String CODE_SUCCESS = "0"; // 成功状态码 + + @Schema(name = "业务状态码,0 为正常,其他值均为异常,异常情况下见响应消息", example = "0") + private final String code; + + @Schema(name = "响应消息", example = "ok") + private String msg; + + @Schema(name = "响应数据") + private T data; + + @Schema(name = "数据总条数,分页情况有效") + private final Long dataCount; + + @Schema(name = "跟踪 ID") + private String traceId; + + public AjaxJson(String code, String msg) { + if (code == null) { + code = ErrorCode.SYSTEM_ERROR.getCode(); + } + this.code = code; + this.msg = msg; + this.dataCount = null; + } + + public AjaxJson(String code, String msg, T data) { + this.code = code; + this.msg = msg; + this.data = data; + this.dataCount = null; + } + + public AjaxJson(String code, String msg, T data, Long dataCount) { + this.code = code; + this.msg = msg; + this.data = data; + this.dataCount = dataCount; + } + + // 返回成功 + public static AjaxJson getSuccess() { + return new AjaxJson<>(CODE_SUCCESS, "ok"); + } + + public static AjaxJson getSuccess(String msg) { + return new AjaxJson<>(CODE_SUCCESS, msg); + } + + public static AjaxJson getSuccess(String msg, T data) { + return new AjaxJson<>(CODE_SUCCESS, msg, data); + } + + public static AjaxJson getSuccessData(T data) { + return new AjaxJson<>(CODE_SUCCESS, "ok", data); + } + + // 返回分页和数据的 + public static AjaxJson getPageData(Long dataCount, T data) { + return new AjaxJson<>(CODE_SUCCESS, "ok", data, dataCount); + } + + // 返回错误 + public static AjaxJson getError(String msg) { + return new AjaxJson<>(ErrorCode.SYSTEM_ERROR.getCode(), msg); + } + + // 返回未登录 + public static AjaxJson getUnauthorizedResult() { + return new AjaxJson<>(ErrorCode.BIZ_UNAUTHORIZED.getCode(), "未登录,请登录后再次访问"); + } + + // 返回没权限的 + public static AjaxJson getForbiddenResult() { + return new AjaxJson<>(ErrorCode.NO_FORBIDDEN.getCode(), "未授权,请登录正确权限账号再试"); + } + + // 返回未找到的 + public static AjaxJson getNotFoundResult() { + return new AjaxJson<>(ErrorCode.BIZ_NOT_FOUND.getCode(), ErrorCode.BIZ_NOT_FOUND.getMessage()); + } + + public static AjaxJson getError(String code, String msg) { + return new AjaxJson<>(code, msg); + } + +} \ No newline at end of file diff --git a/src/main/java/im/zhaojun/zfile/core/util/ArrayUtils.java b/src/main/java/im/zhaojun/zfile/core/util/ArrayUtils.java new file mode 100644 index 0000000..cac7772 --- /dev/null +++ b/src/main/java/im/zhaojun/zfile/core/util/ArrayUtils.java @@ -0,0 +1,40 @@ +package im.zhaojun.zfile.core.util; + +/** + * 数组工具类 + * + * @author zhaojun + */ +public class ArrayUtils { + + /** + * 数组是否为空 + * + * @param + * 数组元素类型 + * + * @param array + * 数组 + * + * @return 是否为空 + */ + public static boolean isEmpty(T[] array) { + return array == null || array.length == 0; + } + + /** + * 数组是否不为空 + * + * @param + * 数组元素类型 + * + * @param array + * 数组 + * + * @return 是否不为空 + */ + public static boolean isNotEmpty(T[] array) { + return !isEmpty(array); + } + +} \ No newline at end of file diff --git a/src/main/java/im/zhaojun/zfile/core/util/CharPool.java b/src/main/java/im/zhaojun/zfile/core/util/CharPool.java new file mode 100644 index 0000000..480188e --- /dev/null +++ b/src/main/java/im/zhaojun/zfile/core/util/CharPool.java @@ -0,0 +1,10 @@ +package im.zhaojun.zfile.core.util; + +public interface CharPool { + + /** + * CHAR 常量:斜杠 {@code '/'} ASCII 47 + */ + char SLASH_CHAR = '/'; + +} \ No newline at end of file diff --git a/src/main/java/im/zhaojun/zfile/core/util/CharSequenceUtil.java b/src/main/java/im/zhaojun/zfile/core/util/CharSequenceUtil.java new file mode 100644 index 0000000..ae1728a --- /dev/null +++ b/src/main/java/im/zhaojun/zfile/core/util/CharSequenceUtil.java @@ -0,0 +1,727 @@ +package im.zhaojun.zfile.core.util; + +import cn.hutool.core.text.StrSplitter; +import jakarta.annotation.Nullable; +import org.apache.commons.lang3.StringUtils; + +import java.util.Collection; +import java.util.List; + +/** + * 字符串工具类 + * + * @author zhaojun + */ +public class CharSequenceUtil implements CharPool { + + /** + * 找不到索引时的返回值 + */ + public static final int INDEX_NOT_FOUND = -1; + + /** + * 字符串常量:{@code "null"}
+ * 注意:{@code "null" != null} + */ + public static final String NULL = "null"; + + /** + * 字符串常量:空字符串 {@code ""} + */ + public static final String EMPTY = ""; + + /** + * 字符串常量:空格符 {@code " "} + */ + public static final String SPACE = " "; + + + /** + * 获取 CharSequence 的长度, 如果为 null, 返回 0 + * + * @param ch + * 要获取长度的 CharSequence, 可能为 null + * + * @return CharSequence 的长度 + */ + public static int length(final @Nullable CharSequence ch) { + return ch == null ? 0 : ch.length(); + } + + + /** + * {@link CharSequence} 转为字符串 + * + * @param cs + * {@link CharSequence} + * + * @return 字符串 + */ + public static String str(final @Nullable CharSequence cs) { + return null == cs ? null : cs.toString(); + } + + + /** + * 判断 CharSequence 是否为空 + * + * @param cs + * {@link CharSequence} + * + * @return 是否为空 + */ + public static boolean isEmpty(final @Nullable CharSequence cs) { + return cs == null || cs.isEmpty(); + } + + + /** + * CharSequence 是否不为空 + * + * @param cs + * {@link CharSequence} + * + * @return 是否不为空 + */ + public static boolean isNotEmpty(final @Nullable CharSequence cs) { + return !isEmpty(cs); + } + + + /** + *

指定字符串数组中的元素,是否全部为空字符串。

+ *

如果指定的字符串数组的长度为 0,或者所有元素都是空字符串,则返回 true。

+ *
+ * + *

例:

+ *
    + *
  • {@code CharSequenceUtil.isAllEmpty() // true}
  • + *
  • {@code CharSequenceUtil.isAllEmpty("", null) // true}
  • + *
  • {@code CharSequenceUtil.isAllEmpty("123", "") // false}
  • + *
  • {@code CharSequenceUtil.isAllEmpty("123", "abc") // false}
  • + *
  • {@code CharSequenceUtil.isAllEmpty(" ", "\t", "\n") // false}
  • + *
+ * + * @param strs + * 字符串列表 + * + * @return 所有字符串是否都为空 + */ + public static boolean isAllEmpty(final @Nullable CharSequence... strs) { + if (strs == null) { + return true; + } + + for (CharSequence str : strs) { + if (isNotEmpty(str)) { + return false; + } + } + return true; + } + + /** + *

是否包含空字符串。

+ *

如果指定的字符串数组的长度为 0,或者其中的任意一个元素是空字符串,则返回 true。

+ *
+ * + *

例:

+ *
    + *
  • {@code CharSequenceUtil.hasEmpty() // true}
  • + *
  • {@code CharSequenceUtil.hasEmpty("", null) // true}
  • + *
  • {@code CharSequenceUtil.hasEmpty("123", "") // true}
  • + *
  • {@code CharSequenceUtil.hasEmpty("123", "abc") // false}
  • + *
  • {@code CharSequenceUtil.hasEmpty(" ", "\t", "\n") // false}
  • + *
+ * + * @param strs + * 字符串列表 + * + * @return 是否包含空字符串 + */ + public static boolean hasEmpty(final @Nullable CharSequence... strs) { + if (ArrayUtils.isEmpty(strs)) { + return true; + } + + for (CharSequence str : strs) { + if (isEmpty(str)) { + return true; + } + } + return false; + } + + + /** + *

指定字符串数组中的元素,是否都不为空字符串。

+ *

如果指定的字符串数组的长度不为 0,或者所有元素都不是空字符串,则返回 true。

+ *
+ * + *

例:

+ *
    + *
  • {@code CharSequenceUtil.isAllNotEmpty() // false}
  • + *
  • {@code CharSequenceUtil.isAllNotEmpty("", null) // false}
  • + *
  • {@code CharSequenceUtil.isAllNotEmpty("123", "") // false}
  • + *
  • {@code CharSequenceUtil.isAllNotEmpty("123", "abc") // true}
  • + *
  • {@code CharSequenceUtil.isAllNotEmpty(" ", "\t", "\n") // true}
  • + *
+ * + * @param args + * 字符串数组 + * + * @return 所有字符串是否都不为为空白 + */ + public static boolean isAllNotEmpty(final @Nullable CharSequence... args) { + return !hasEmpty(args); + } + + + /** + * 字符串是否为空白 + * + * @param ch + * 要判断的字符串, 可能为 null + * + * @return 是否为空白 + */ + public static boolean isBlank(final @Nullable CharSequence ch) { + final int strLen = ch == null ? 0 : ch.length(); + if (strLen == 0) { + return true; + } + for (int i = 0; i < strLen; i++) { + if (!Character.isWhitespace(ch.charAt(i))) { + return false; + } + } + return true; + } + + + /** + * 字符串是否不为空白 + * + * @param cs + * 字符串 + * + * @return 是否不为空白 + */ + public static boolean isNotBlank(final @Nullable CharSequence cs) { + return !isBlank(cs); + } + + + /** + * 比较两个 CharSequence 是否相等, 区分大小写, 如果两个都为 null, 返回 true + * + * @param cs1 + * CharSequence 1, 可能为 null + * + * @param cs2 + * CharSequence 2, 可能为 null + * + * @return 是否相等 + */ + public static boolean equals(final @Nullable CharSequence cs1, final @Nullable CharSequence cs2) { + if (cs1 == cs2) { + return true; + } + if (cs1 == null || cs2 == null) { + return false; + } + if (cs1.length() != cs2.length()) { + return false; + } + if (cs1 instanceof String && cs2 instanceof String) { + return cs1.equals(cs2); + } + // 逐个比较 + final int length = cs1.length(); + for (int i = 0; i < length; i++) { + if (cs1.charAt(i) != cs2.charAt(i)) { + return false; + } + } + return true; + } + + + /** + * 比较两个 CharSequence 是否相等, 可以选择是否忽略大小写, 如果两个都为 null, 返回 true + * + * @param cs1 + * 字符串 1 + * + * @param cs2 + * 字符串 2 + * + * @param ignoreCase + * 是否忽略大小写 + * + * @return 是否相等 + */ + public static boolean equals(final @Nullable CharSequence cs1,final @Nullable CharSequence cs2, boolean ignoreCase) { + return ignoreCase ? equalsIgnoreCase(cs1, cs2) : equals(cs1, cs2); + } + + + /** + * 字符串是否相等, 忽略大小写 + * + * @param cs1 + * 字符串 1 + * + * @param cs2 + * 字符串 2 + * + * @return 忽略大小写后是否相等 + */ + public static boolean equalsIgnoreCase(final @Nullable CharSequence cs1, final @Nullable CharSequence cs2) { + if (cs1 == cs2) { + return true; + } + if (cs1 == null || cs2 == null) { + return false; + } + if (cs1.length() != cs2.length()) { + return false; + } + + return cs1.toString().equalsIgnoreCase(cs2.toString()); + } + + + /** + * 切分字符串,如果分隔符不存在则返回原字符串 + * + * @param str + * 被切分的字符串 + * + * @param separator + * 分隔符 + * + * @return 字符串 + */ + public static List split(final CharSequence str, final CharSequence separator) { + return split(str, separator, false, false); + } + + + /** + * 切分字符串 + * + * @param str + * 被切分的字符串 + * + * @param separator + * 分隔符字符 + * + * @param isTrim + * 是否去除切分字符串后每个元素两边的空格 + * + * @param ignoreEmpty + * 是否忽略空串 + * + * @return 切分后的集合 + */ + public static List split(CharSequence str, CharSequence separator, boolean isTrim, boolean ignoreEmpty) { + return split(str, separator, 0, isTrim, ignoreEmpty); + } + + + /** + * 切分字符串 + * + * @param str + * 被切分的字符串 + * + * @param separator + * 分隔符字符 + * + * @param limit + * 限制分片数,-1 不限制 + * + * @param isTrim + * 是否去除切分字符串后每个元素两边的空格 + * + * @param ignoreEmpty + * 是否忽略空串 + * + * @return 切分后的集合 + */ + public static List split(CharSequence str, CharSequence separator, int limit, boolean isTrim, boolean ignoreEmpty) { + final String separatorStr = (null == separator) ? null : separator.toString(); + return StrSplitter.split(str, separatorStr, limit, isTrim, ignoreEmpty); + } + + + /** + * 指定字符串是否在字符串中出现过 + * + * @param str + * 字符串 + * + * @param searchStr + * 被查找的字符串 + * + * @return 是否包含 + */ + public static boolean contains(final @Nullable CharSequence str, final @Nullable CharSequence searchStr) { + if (null == str || null == searchStr) { + return false; + } + return str.toString().contains(searchStr); + } + + + /** + * 查找指定字符串是否包含指定字符串列表中的任意一个字符串 + * + * @param str + * 指定字符串 + * + * @param testStrs + * 需要检查的字符串数组 + * + * @return 是否包含任意一个字符串 + */ + public static boolean containsAny(final @Nullable CharSequence str, final @Nullable CharSequence... testStrs) { + if (isEmpty(str) || ArrayUtils.isEmpty(testStrs)) { + return false; + } + for (CharSequence checkStr : testStrs) { + if (null != checkStr && str.toString().contains(checkStr)) { + return true; + } + } + return false; + } + + + /** + * 查找指定字符串是否包含指定字符串列表中的任意一个字符串
+ * 忽略大小写 + * + * @param str + * 指定字符串 + * + * @param testStrs + * 需要检查的字符串数组 + * + * @return 是否包含任意一个字符串 + */ + public static boolean containsAnyIgnoreCase(final @Nullable CharSequence str, final @Nullable CharSequence... testStrs) { + return StringUtils.containsAnyIgnoreCase(str, testStrs); + } + + + /** + * 以 conjunction 为分隔符将多个对象转换为字符串 + * + * @param conjunction + * 分隔符 + * + * @param objs + * 数组 + * + * @return 连接后的字符串 + */ + public static String join(CharSequence conjunction, Object... objs) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < objs.length; i++) { + Object item = objs[i]; + sb.append(item); + if (i < objs.length - 1) { + sb.append(conjunction); + } + } + return sb.toString(); + } + + + /** + * 以 conjunction 为分隔符将 Collection 对象转换为字符串 + * + * @param conjunction + * 分隔符 + * + * @param collection + * 集合 + * + * @return 连接后的字符串 + */ + public static String join(CharSequence conjunction, Collection collection) { + StringBuilder sb = new StringBuilder(); + for (Object item : collection) { + sb.append(item).append(conjunction); + } + if (!sb.isEmpty()) { + sb.delete(sb.length() - conjunction.length(), sb.length()); + } + return sb.toString(); + } + + + /** + * 是否以指定字符串开头 + * + * @param str + * 被监测字符串 + * + * @param prefix + * 开头字符串 + * + * @return 是否以指定字符串开头 + */ + public static boolean startWith(CharSequence str, CharSequence prefix) { + return startWith(str, prefix, false); + } + + + /** + * 是否以指定字符串开头,忽略大小写 + * + * @param str + * 被监测字符串 + * + * @param prefix + * 开头字符串 + * + * @return 是否以指定字符串开头 + */ + public static boolean startWithIgnoreCase(CharSequence str, CharSequence prefix) { + return startWith(str, prefix, true); + } + + + /** + * 是否以指定字符串开头
+ * 如果给定的字符串和开头字符串都为null则返回true,否则任意一个值为null返回false + * + * @param str + * 被监测字符串 + * + * @param prefix + * 开头字符串 + * + * @param ignoreCase + * 是否忽略大小写 + * + * @return 是否以指定字符串开头 + */ + public static boolean startWith(CharSequence str, CharSequence prefix, boolean ignoreCase) { + return startWith(str, prefix, ignoreCase, false); + } + + + /** + * 是否以指定字符串开头
+ * 如果给定的字符串和开头字符串都为 null 则返回 true,否则任意一个值为 null 返回 false
+ *
+     *     CharSequenceUtil.startWith("123", "123", false, true);   -- false
+     *     CharSequenceUtil.startWith("ABCDEF", "abc", true, true); -- true
+     *     CharSequenceUtil.startWith("abc", "abc", true, true);    -- false
+     * 
+ * + * @param str + * 被监测字符串 + * + * @param prefix + * 开头字符串 + * + * @param ignoreCase + * 是否忽略大小写 + * + * @param ignoreEquals + * 是否忽略字符串相等的情况 + * + * @return 是否以指定字符串开头 + */ + public static boolean startWith(final @Nullable CharSequence str, final @Nullable CharSequence prefix, boolean ignoreCase, boolean ignoreEquals) { + if (null == str || null == prefix) { + if (ignoreEquals) { + return false; + } + return null == str && null == prefix; + } + + boolean isStartWith = str.toString() + .regionMatches(ignoreCase, 0, prefix.toString(), 0, prefix.length()); + + if (isStartWith) { + return (!ignoreEquals) || (!equals(str, prefix, ignoreCase)); + } + return false; + } + + + /** + * 是否以指定字符串结尾 + * + * @param str + * 被监测字符串 + * + * @param suffix + * 结尾字符串 + * + * @return 是否以指定字符串结尾 + */ + public static boolean endWith(final @Nullable CharSequence str, final @Nullable CharSequence suffix) { + return endWith(str, suffix, false); + } + + + /** + * 是否以指定字符串结尾
+ * 如果给定的字符串和开头字符串都为null则返回true,否则任意一个值为null返回false + * + * @param str + * 被监测字符串 + * + * @param suffix + * 结尾字符串 + * + * @param ignoreCase + * 是否忽略大小写 + * + * @return 是否以指定字符串结尾 + */ + public static boolean endWith(final @Nullable CharSequence str, final @Nullable CharSequence suffix, boolean ignoreCase) { + return endWith(str, suffix, ignoreCase, false); + } + + + /** + * 是否以指定字符串结尾
+ * 如果给定的字符串和开头字符串都为null则返回true,否则任意一个值为null返回false + * + * @param str + * 被监测字符串 + * + * @param suffix + * 结尾字符串 + * + * @param ignoreCase + * 是否忽略大小写 + * + * @param ignoreEquals + * 是否忽略字符串相等的情况 + * + * @return 是否以指定字符串结尾 + */ + public static boolean endWith(final @Nullable CharSequence str, final @Nullable CharSequence suffix, boolean ignoreCase, boolean ignoreEquals) { + if (null == str || null == suffix) { + if (ignoreEquals) { + return false; + } + return null == str && null == suffix; + } + + final int strOffset = str.length() - suffix.length(); + boolean isEndWith = str.toString() + .regionMatches(ignoreCase, strOffset, suffix.toString(), 0, suffix.length()); + + if (isEndWith) { + return (!ignoreEquals) || (!equals(str, suffix, ignoreCase)); + } + return false; + } + + + /** + * 去掉指定前缀 + * + * @param str + * 字符串 + * + * @param prefix + * 前缀 + * + * @return 切掉后的字符串,若前缀不是 preffix, 返回原字符串 + */ + public static String removePrefix(final @Nullable CharSequence str, final @Nullable CharSequence prefix) { + if (isEmpty(str) || isEmpty(prefix)) { + return str(str); + } + String str2 = str.toString(); + String prefix2 = prefix.toString(); + if (str2.startsWith(prefix2)) { + return str.subSequence(prefix.length(), str.length()).toString(); + } + return str2; // 若前缀不是 prefix,返回原字符串 + } + + + /** + * 返回第一个非 {@code null} 元素 + * + * @param strs + * 多个元素 + * + * @param + * 元素类型 + * + * @return 第一个非空元素,如果给定的数组为空或者都为空,返回{@code null} + */ + @SuppressWarnings("unchecked") + public static T firstNonNull(T... strs) { + if (ArrayUtils.isNotEmpty(strs)) { + for (T str : strs) { + if (isNotEmpty(str)) { + return str; + } + } + } + return null; + } + + + /** + * 截取分隔字符串之前的字符串,不包括分隔字符串
+ * 如果给定的字符串为空串(null或"")或者分隔字符串为null,返回原字符串
+ * 如果分隔字符串为空串"",则返回空串,如果分隔字符串未找到,返回原字符串,举例如下: + * + *
+     * CharSequenceUtil.subBefore(null, *, false)      = null
+     * CharSequenceUtil.subBefore("", *, false)        = ""
+     * CharSequenceUtil.subBefore("abc", "a", false)   = ""
+     * CharSequenceUtil.subBefore("abcba", "b", false) = "a"
+     * CharSequenceUtil.subBefore("abc", "c", false)   = "ab"
+     * CharSequenceUtil.subBefore("abc", "d", false)   = "abc"
+     * CharSequenceUtil.subBefore("abc", "", false)    = ""
+     * CharSequenceUtil.subBefore("abc", null, false)  = "abc"
+     * 
+ * + * @param string + * 被查找的字符串 + * + * @param separator + * 分隔字符串(不包括) + * + * @param isLastSeparator + * 是否查找最后一个分隔字符串(多次出现分隔字符串时选取最后一个),true为选取最后一个 + * + * @return 切割后的字符串 + */ + public static String subBefore(final @Nullable CharSequence string, final @Nullable CharSequence separator, boolean isLastSeparator) { + if (isEmpty(string) || separator == null) { + return null == string ? null : string.toString(); + } + + final String str = string.toString(); + final String sep = separator.toString(); + if (sep.isEmpty()) { + return EMPTY; + } + final int pos = isLastSeparator ? str.lastIndexOf(sep) : str.indexOf(sep); + if (INDEX_NOT_FOUND == pos) { + return str; + } + if (0 == pos) { + return EMPTY; + } + return str.substring(0, pos); + } + +} \ No newline at end of file diff --git a/src/main/java/im/zhaojun/zfile/core/util/ClassUtils.java b/src/main/java/im/zhaojun/zfile/core/util/ClassUtils.java index 14a2543..057d55c 100644 --- a/src/main/java/im/zhaojun/zfile/core/util/ClassUtils.java +++ b/src/main/java/im/zhaojun/zfile/core/util/ClassUtils.java @@ -1,5 +1,6 @@ package im.zhaojun.zfile.core.util; +import java.lang.reflect.Field; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; @@ -10,6 +11,14 @@ import java.lang.reflect.Type; */ public class ClassUtils { + public static Class forName(String className) { + try { + return Class.forName(className); + } catch (ClassNotFoundException e) { + throw new RuntimeException(e); + } + } + /** * 获取指定类的泛型类型, 只获取第一个泛型类型 * @@ -24,4 +33,9 @@ public class ClassUtils { return (Class) actualTypeArgument; } + public static Class getGenericType(Field field) { + ParameterizedType listType = (ParameterizedType) field.getGenericType(); + return (Class) listType.getActualTypeArguments()[0]; + } + } \ No newline at end of file diff --git a/src/main/java/im/zhaojun/zfile/core/util/CodeMsg.java b/src/main/java/im/zhaojun/zfile/core/util/CodeMsg.java deleted file mode 100644 index 59f23f0..0000000 --- a/src/main/java/im/zhaojun/zfile/core/util/CodeMsg.java +++ /dev/null @@ -1,76 +0,0 @@ -package im.zhaojun.zfile.core.util; - -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; -import lombok.experimental.Accessors; - -/** - * @author zhaojun - */ -@Data -@NoArgsConstructor -@AllArgsConstructor -@Accessors(chain = true) -public class CodeMsg { - - /** - * 错误码 - *

- * 均为 5 位数, 如 00000, 10100, 20105 等. - *
- * 第一位表示错误类型, 4 为用户请求输入错误, 5 为服务端处理错误, 6 为警告信息 - *
- * 第二位到第三位为二级类型 - *
- * 第四位到第五位为具体错误代码, 根据业务场景自行定义 - *

- * 以上三种类型均不允许重复, 且都需保持递增. - */ - private String code; - - /** - * 错误消息 - */ - private String msg; - - // 通用返回值 - public static CodeMsg SUCCESS = new CodeMsg("00000", "success"); - public static CodeMsg BAD_REQUEST = new CodeMsg("40000", "非法请求"); - public static CodeMsg ERROR = new CodeMsg("50000", "服务端异常"); - - - // -------------- 用户输入级错误 -------------- - public static CodeMsg REQUIRED_PASSWORD = new CodeMsg("40100", "请输入密码"); - public static CodeMsg PASSWORD_FAULT = new CodeMsg("40101", "密码输入错误"); - - public static CodeMsg STORAGE_SOURCE_NOT_FOUND = new CodeMsg("40102", "无效的或初始化失败的存储源"); - public static CodeMsg STORAGE_SOURCE_FORBIDDEN = new CodeMsg("40103", "无权访问存储源"); - public static CodeMsg STORAGE_SOURCE_FILE_FORBIDDEN = new CodeMsg("40104", "无权访问该目录"); - public static CodeMsg STORAGE_SOURCE_ILLEGAL_OPERATION = new CodeMsg("40105", "非法操作"); - - - - // -------------- 服务端处理错误 -------------- - - // 初始化相关错误 - public static CodeMsg STORAGE_SOURCE_INIT_FAIL = new CodeMsg("50100", "初始化存储源失败"); - public static CodeMsg STORAGE_SOURCE_INIT_STORAGE_CONFIG_FAIL = new CodeMsg("50101", "初始化存储源参数失败"); - public static CodeMsg STORAGE_SOURCE_INIT_STORAGE_PARAM_FIELD_FAIL = new CodeMsg("50102", "填充存储源字段失败"); - - - // 文件操作相关错误 - public static CodeMsg STORAGE_SOURCE_FILE_NEW_FOLDER_FAIL = new CodeMsg("50201", "新建文件夹失败"); - public static CodeMsg STORAGE_SOURCE_FILE_DELETE_FAIL = new CodeMsg("50202", "删除失败"); - public static CodeMsg STORAGE_SOURCE_FILE_RENAME_FAIL = new CodeMsg("50203", "重命名失败"); - public static CodeMsg STORAGE_SOURCE_FILE_GET_UPLOAD_FAIL = new CodeMsg("50204", "获取上传链接失败"); - public static CodeMsg STORAGE_SOURCE_FILE_PROXY_UPLOAD_FAIL = new CodeMsg("50205", "文件上传失败"); - public static CodeMsg STORAGE_SOURCE_FILE_PROXY_DOWNLOAD_FAIL = new CodeMsg("50206", "文件下载失败"); - public static CodeMsg STORAGE_SOURCE_FILE_GET_ITEM_FAIL = new CodeMsg("50207", "文件不存在或请求异常"); - public static CodeMsg STORAGE_SOURCE_FILE_DISABLE_PROXY_DOWNLOAD = new CodeMsg("50208", "非法操作, 当前文件不支持此类下载方式"); - - - - - -} \ No newline at end of file diff --git a/src/main/java/im/zhaojun/zfile/core/util/CollectionUtils.java b/src/main/java/im/zhaojun/zfile/core/util/CollectionUtils.java new file mode 100644 index 0000000..4749477 --- /dev/null +++ b/src/main/java/im/zhaojun/zfile/core/util/CollectionUtils.java @@ -0,0 +1,131 @@ +package im.zhaojun.zfile.core.util; + +import cn.hutool.core.lang.func.Func1; + +import javax.annotation.Nullable; +import java.util.*; + +public class CollectionUtils { + + + /** + * 判断集合是否为空 + * + * @param collection + * 集合 + * + * @return 是否为空 + */ + public static boolean isEmpty(@Nullable Collection collection) { + return (collection == null || collection.isEmpty()); + } + + + /** + * 判断集合是否不为空 + * + * @param collection + * 集合 + * + * @return 是否不为空 + */ + public static boolean isNotEmpty(@Nullable Collection collection) { + return !isEmpty(collection); + } + + + /** + * 从集合中获取第一个元素, 如果集合为空则返回 {@code null} + * + * @param list + * 集合,可能为 {@code null} + * + * @return 第一个元素,如果集合为空则返回 {@code null} + */ + @Nullable + public static T getFirst(@Nullable List list) { + if (isEmpty(list)) { + return null; + } + return list.get(0); + } + + + /** + * 从集合中获取最后一个元素, 如果集合为空则返回 {@code null} + * + * @param list + * 集合,可能为 {@code null} + * + * @return 最后一个元素,如果集合为空则返回 {@code null} + */ + @Nullable + public static T getLast(@Nullable List list) { + if (isEmpty(list)) { + return null; + } + return list.get(list.size() - 1); + } + + + /** + * 加入全部 + * + * @param + * 集合元素类型 + * + * @param collection + * 被加入的集合 {@link Collection} + * + * @param values + * 要加入的内容数组 + * + * @return 原集合 + */ + public static Collection addAll(Collection collection, T[] values) { + if (null != collection && null != values) { + Collections.addAll(collection, values); + } + return collection; + } + + + /** + * Iterable 转换为 Map, 根据指定的 keyFunc 函数生成 Key. Value 为 Iterable 中的元素.
+ * 可以指定将结果放入的 Map, 如不指定则会新建一个 HashMap 返回. + * + * @param + * Map Key 类型 + * + * @param + * Map Value 类型 + * + * @param values + * 被转换的 Iterable + * + * @param map + * 转换后的 Value 存放的 Map, 如果为 {@code null} 则新建一个 HashMap + * + * @param keyFunc + * 生成 Map 的 Key 的函数 + * + * @return 转换后的 Map + */ + public static Map toMap(final @Nullable Iterable values, final @Nullable Map map, final @Nullable Func1 keyFunc) { + if (values == null || keyFunc == null) { + return Collections.emptyMap(); + } + + final Map result = map == null ? new HashMap<>() : map; + + for (V value : values) { + try { + result.put(keyFunc.call(value), value); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + return result; + } + +} \ No newline at end of file diff --git a/src/main/java/im/zhaojun/zfile/core/util/DnsUtil.java b/src/main/java/im/zhaojun/zfile/core/util/DnsUtil.java new file mode 100644 index 0000000..4c636ff --- /dev/null +++ b/src/main/java/im/zhaojun/zfile/core/util/DnsUtil.java @@ -0,0 +1,52 @@ +package im.zhaojun.zfile.core.util; + +import com.alibaba.dcm.DnsCacheManipulator; +import com.alibaba.fastjson2.JSONArray; +import org.springframework.lang.Nullable; + +public class DnsUtil { + + /** + * 通过 HTTP DNS 获取域名对应的 IP 地址 + * + * @param domain + * 域名 + * + * @return IP 地址数组 + */ + public static @Nullable String[] getDomainIpByHttpDns(String domain) { + String jsonArrayStr = cn.hutool.http.HttpUtil.get("http://223.5.5.5/resolve?name=" + domain + "&short=1", 3000); + JSONArray jsonArray = JSONArray.parseArray(jsonArrayStr); + if (!jsonArray.isEmpty()) { + String[] result = new String[jsonArray.size()]; + for (int i = 0; i < jsonArray.size(); i++) { + result[i] = jsonArray.getString(i); + } + return result; + } else { + return null; + } + } + + + /** + * 通过 HTTP DNS 获取域名对应的 IP 地址, 并设置 DNS 缓存. + * + * @param domain + * 域名 + * + * @param cacheTime + * 缓存时间, 单位: 毫秒 + * + * @return IP 地址数组 + */ + public static String[] getDomainIpByHttpDnsAndCache(String domain, int cacheTime) { + String[] domainIpByHttpDns = getDomainIpByHttpDns(domain); + if (domainIpByHttpDns != null) { + // 设置 DNS 缓存 + DnsCacheManipulator.setDnsCache(cacheTime, domain, domainIpByHttpDns); + } + return domainIpByHttpDns; + } + +} \ No newline at end of file diff --git a/src/main/java/im/zhaojun/zfile/core/util/FileComparator.java b/src/main/java/im/zhaojun/zfile/core/util/FileComparator.java index 41cdc61..2acda55 100644 --- a/src/main/java/im/zhaojun/zfile/core/util/FileComparator.java +++ b/src/main/java/im/zhaojun/zfile/core/util/FileComparator.java @@ -8,12 +8,12 @@ import java.util.Comparator; /** * 文件比较器 - * - * - 文件夹始终比文件排序高 - * - 默认按照名称排序 - * - 默认排序为升序 - * - 按名称排序不区分大小写 - * + *

    + *
  • 文件夹始终比文件排序高
  • + *
  • 默认按照名称排序
  • + *
  • 默认排序为升序
  • + *
  • 按名称排序不区分大小写
  • + *
* @author zhaojun */ public class FileComparator implements Comparator { @@ -52,12 +52,11 @@ public class FileComparator implements Comparator { FileTypeEnum o2Type = o2.getType(); NaturalOrderComparator naturalOrderComparator = new NaturalOrderComparator(); if (o1Type.equals(o2Type)) { - int result; - switch (sortBy) { - case "time": result = CompareUtil.compare(o1.getTime(), o2.getTime()); break; - case "size": result = CompareUtil.compare(o1.getSize(), o2.getSize()); break; - default: result = naturalOrderComparator.compare(o1.getName(), o2.getName()); break; - } + int result = switch (sortBy) { + case "time" -> CompareUtil.compare(o1.getTime(), o2.getTime()); + case "size" -> CompareUtil.compare(o1.getSize(), o2.getSize()); + default -> naturalOrderComparator.compare(o1.getName(), o2.getName()); + }; return "asc".equals(order) ? result : -result; } diff --git a/src/main/java/im/zhaojun/zfile/core/util/FileResponseUtil.java b/src/main/java/im/zhaojun/zfile/core/util/FileResponseUtil.java index 0b83d9b..618412e 100644 --- a/src/main/java/im/zhaojun/zfile/core/util/FileResponseUtil.java +++ b/src/main/java/im/zhaojun/zfile/core/util/FileResponseUtil.java @@ -1,18 +1,17 @@ package im.zhaojun.zfile.core.util; import cn.hutool.core.io.FileUtil; -import cn.hutool.core.util.StrUtil; +import im.zhaojun.zfile.core.exception.ErrorCode; +import im.zhaojun.zfile.core.exception.status.NotFoundAccessException; import lombok.extern.slf4j.Slf4j; -import org.springframework.core.io.ByteArrayResource; import org.springframework.core.io.InputStreamResource; import org.springframework.core.io.Resource; import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import java.io.File; -import java.nio.charset.StandardCharsets; +import java.util.Collections; /** * 将文件输出对象 @@ -36,21 +35,18 @@ public class FileResponseUtil { */ public static ResponseEntity exportSingleThread(File file, String fileName) { if (!file.exists()) { - ByteArrayResource byteArrayResource = new ByteArrayResource("文件不存在或异常,请联系管理员.".getBytes(StandardCharsets.UTF_8)); - return ResponseEntity.status(HttpStatus.NOT_FOUND) - .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) - .body(byteArrayResource); + throw new NotFoundAccessException(ErrorCode.BIZ_FILE_NOT_EXIST); } MediaType mediaType = MediaType.APPLICATION_OCTET_STREAM; HttpHeaders headers = new HttpHeaders(); - if (StrUtil.isEmpty(fileName)) { + if (StringUtils.isEmpty(fileName)) { fileName = file.getName(); } - headers.setContentDispositionFormData("attachment", StringUtils.encodeAllIgnoreSlashes(fileName)); + headers.put(HttpHeaders.CONTENT_DISPOSITION, Collections.singletonList("inline; filename=\"" + StringUtils.encodeAllIgnoreSlashes(fileName) + "\"")); return ResponseEntity .ok() diff --git a/src/main/java/im/zhaojun/zfile/core/util/FileUtils.java b/src/main/java/im/zhaojun/zfile/core/util/FileUtils.java new file mode 100644 index 0000000..b3e617f --- /dev/null +++ b/src/main/java/im/zhaojun/zfile/core/util/FileUtils.java @@ -0,0 +1,46 @@ +package im.zhaojun.zfile.core.util; + +import org.apache.commons.io.FilenameUtils; + +/** + * 文件相关工具类 + * + * @author zhaojun + */ +public class FileUtils { + + public static String getName(final String fileName) { + if (fileName == null) { + return null; + } + + int i = fileName.lastIndexOf(CharSequenceUtil.SLASH_CHAR); + if (i >= 0 && i <= fileName.length() - 1) { + return fileName.substring(i + 1); + } + + return fileName; + } + + public static String getParentPath(final String fileName) { + String fullPathNoEndSeparator = FilenameUtils.getFullPathNoEndSeparator(fileName); + if (fullPathNoEndSeparator == null || fullPathNoEndSeparator.isEmpty()) { + return StringUtils.SLASH; + } + return fullPathNoEndSeparator; + } + + public static String getExtension(final String fileName) throws IllegalArgumentException { + if (fileName == null) { + return null; + } + + int i = fileName.lastIndexOf('.'); + if (i > 0 && i < fileName.length() - 1) { + return fileName.substring(i + 1); + } + + return ""; + } + +} \ No newline at end of file diff --git a/src/main/java/im/zhaojun/zfile/core/util/HttpUtil.java b/src/main/java/im/zhaojun/zfile/core/util/HttpUtil.java index 88f19bb..496d35a 100644 --- a/src/main/java/im/zhaojun/zfile/core/util/HttpUtil.java +++ b/src/main/java/im/zhaojun/zfile/core/util/HttpUtil.java @@ -1,10 +1,9 @@ package im.zhaojun.zfile.core.util; -import cn.hutool.core.io.FileUtil; -import cn.hutool.core.util.StrUtil; import im.zhaojun.zfile.core.constant.ZFileConstant; -import im.zhaojun.zfile.core.exception.PreviewException; -import im.zhaojun.zfile.core.exception.TextParseException; +import im.zhaojun.zfile.core.exception.ErrorCode; +import im.zhaojun.zfile.core.exception.biz.GetPreviewTextContentBizException; +import im.zhaojun.zfile.core.exception.core.BizException; import lombok.extern.slf4j.Slf4j; import java.io.IOException; @@ -19,7 +18,6 @@ import java.net.URLConnection; @Slf4j public class HttpUtil { - /** * 获取 URL 对应的文件内容 * @@ -32,14 +30,14 @@ public class HttpUtil { long maxFileSize = 1024 * ZFileConstant.TEXT_MAX_FILE_SIZE_KB; if (getRemoteFileSize(url) > maxFileSize) { - throw new PreviewException("预览文件超出大小, 最大支持 " + FileUtil.readableFileSize(maxFileSize)); + throw new BizException(ErrorCode.BIZ_PREVIEW_FILE_SIZE_EXCEED); } String result; try { result = cn.hutool.http.HttpUtil.get(url); } catch (Exception e) { - throw new TextParseException(StrUtil.format("获取文件内容失败, URL: {}", url), e); + throw new GetPreviewTextContentBizException(url, e); } return result == null ? "" : result; diff --git a/src/main/java/im/zhaojun/zfile/core/util/NumberUtils.java b/src/main/java/im/zhaojun/zfile/core/util/NumberUtils.java new file mode 100644 index 0000000..fd7be9a --- /dev/null +++ b/src/main/java/im/zhaojun/zfile/core/util/NumberUtils.java @@ -0,0 +1,18 @@ +package im.zhaojun.zfile.core.util; + +/** + * 数字工具类 + * + * @author zhaojun + */ +public class NumberUtils { + + public static boolean isNullOrZero(Integer number) { + return number == null || number == 0; + } + + public static boolean isNotNullOrZero(Integer number) { + return number != null && number != 0; + } + +} \ No newline at end of file diff --git a/src/main/java/im/zhaojun/zfile/core/util/OnlyOfficeKeyCacheUtils.java b/src/main/java/im/zhaojun/zfile/core/util/OnlyOfficeKeyCacheUtils.java new file mode 100644 index 0000000..5ee513f --- /dev/null +++ b/src/main/java/im/zhaojun/zfile/core/util/OnlyOfficeKeyCacheUtils.java @@ -0,0 +1,98 @@ +package im.zhaojun.zfile.core.util; + +import cn.hutool.cache.Cache; +import cn.hutool.cache.CacheUtil; +import im.zhaojun.zfile.module.onlyoffice.model.OnlyOfficeFile; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.BooleanUtils; +import org.apache.commons.lang3.RandomStringUtils; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.ReentrantLock; + +/** + * OnlyOffice 文件信息与 Key 缓存工具类 + * + * @author zhaojun + */ +@Slf4j +public class OnlyOfficeKeyCacheUtils { + + /** + * 存储 OnlyOffice 文件信息与 Key 的映射关系. 最多存储 10000 个 Key, 防止内存溢出. + */ + private static final Cache ONLY_OFFICE_FILE_KEY_MAP = CacheUtil.newLRUCache(10000); + + /** + * 存储 OnlyOffice Key 与文件信息的映射关系. 最多存储 10000 个 Key, 防止内存溢出. + */ + private static final Cache ONLY_OFFICE_KEY_FILE_MAP = CacheUtil.newLRUCache(10000); + + /** + * 存储文件锁, 防止并发操作文件缓存时出现问题. + */ + private static final Cache locks = CacheUtil.newLRUCache(300); + + /** + * 获取该文件缓存的 key, 如果不存在则生成一个新的 key 并缓存. + * + * @param onlyOfficeFile + * OnlyOffice 文件信息 + * + * @return 该文件唯一标识 + */ + public static String getKeyOrPutNew(OnlyOfficeFile onlyOfficeFile, long timeout) { + ReentrantLock lock = getLock(onlyOfficeFile); + try { + boolean getLock = lock.tryLock(timeout, TimeUnit.MILLISECONDS); + if (BooleanUtils.isFalse(getLock)) { + log.warn("{} 尝试获取锁超时, 强制忽略锁直接操作文件.", onlyOfficeFile); + } + try { + if (ONLY_OFFICE_FILE_KEY_MAP.containsKey(onlyOfficeFile)) { + return ONLY_OFFICE_FILE_KEY_MAP.get(onlyOfficeFile); + } else { + String key = RandomStringUtils.randomAlphabetic(10); + ONLY_OFFICE_FILE_KEY_MAP.put(onlyOfficeFile, key); + ONLY_OFFICE_KEY_FILE_MAP.put(key, onlyOfficeFile); + return key; + } + } finally { + lock.unlock(); + } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new IllegalStateException("Thread was interrupted", e); + } + + } + + /** + * 清理缓存中的 Key 与文件信息的映射关系.(文件发生了变化, 需要重新生成 OnlyOffice 预览链接时调用) + * + * @param key + * 文件唯一标识 + */ + public static OnlyOfficeFile removeByKey(String key) { + OnlyOfficeFile onlyOfficeFile = ONLY_OFFICE_KEY_FILE_MAP.get(key); + if (onlyOfficeFile == null) { + return null; + } + ONLY_OFFICE_FILE_KEY_MAP.remove(onlyOfficeFile); + ONLY_OFFICE_KEY_FILE_MAP.remove(key); + return onlyOfficeFile; + } + + /** + * 获取文件锁, 防止并发操作文件缓存时出现问题. + * + * @param key + * 文件唯一标识 + * + * @return 锁对象 + */ + public static ReentrantLock getLock(OnlyOfficeFile key) { + return locks.get(key, true, ReentrantLock::new); + } + +} \ No newline at end of file diff --git a/src/main/java/im/zhaojun/zfile/core/util/PatternMatcherUtils.java b/src/main/java/im/zhaojun/zfile/core/util/PatternMatcherUtils.java index c05606e..3d931e5 100644 --- a/src/main/java/im/zhaojun/zfile/core/util/PatternMatcherUtils.java +++ b/src/main/java/im/zhaojun/zfile/core/util/PatternMatcherUtils.java @@ -1,8 +1,5 @@ package im.zhaojun.zfile.core.util; -import cn.hutool.core.util.StrUtil; -import im.zhaojun.zfile.core.constant.ZFileConstant; - import java.nio.file.FileSystems; import java.nio.file.PathMatcher; import java.nio.file.Paths; @@ -26,7 +23,7 @@ public class PatternMatcherUtils { *
  • test1: /a
  • *
  • test2: /a/
  • *