From e45809a0dbe5fc54103b4d54c64921d0f40d124f Mon Sep 17 00:00:00 2001 From: Timi Date: Tue, 28 Apr 2026 10:22:19 +0800 Subject: [PATCH] refactor all --- .gitignore | 3 - .idea/.gitignore | 3 + .idea/codeStyles/codeStyleConfig.xml | 5 + .idea/dataSources.xml | 17 + .idea/data_source_mapping.xml | 7 + .idea/inspectionProfiles/Project_Default.xml | 12 + .idea/misc.xml | 64 +- .idea/sqldialects.xml | 8 + .serena/.gitignore | 2 + .serena/memories/project_overview.md | 1 + .serena/memories/style_conventions.md | 1 + .serena/memories/suggested_commands.md | 1 + .serena/memories/task_completion.md | 1 + .serena/project.yml | 154 ++++ http/README.md | 163 +++++ http/common/attachment.http | 200 ++++++ http/common/multilingual.http | 66 ++ http/gao/action.http | 60 ++ http/gao/customer.http | 119 +++ http/gao/record.http | 86 +++ http/http-client.env.json | 8 + http/object/common.md | 77 ++ http/object/permission.md | 31 + http/object/role.md | 34 + http/object/user.md | 73 ++ http/user/permission.http | 84 +++ http/user/role.http | 113 +++ http/user/user.http | 186 +++++ pom.xml | 72 +- .../imyeyu/api/annotation/CaptchaValid.java | 22 - .../annotation/CaptchaValidInterceptor.java | 57 +- .../imyeyu/api/annotation/EnableSetting.java | 18 +- .../annotation/EnableSettingInterceptor.java | 29 +- .../api/annotation/MultilingualField.java | 31 + .../RequestRateLimitInterceptor.java | 18 +- .../api/annotation/RequireCorePermission.java | 29 + .../RequireCorePermissionInterceptor.java | 46 ++ .../api/annotation/RequireCoreRole.java | 29 + .../RequireCoreRoleInterceptor.java | 46 ++ .../annotation/RequiredTokenInterceptor.java | 39 - .../java/com/imyeyu/api/bean/CaptchaFrom.java | 34 - .../imyeyu/api/bean/CorePermissionCode.java | 80 +++ .../com/imyeyu/api/bean/CoreRoleCode.java | 41 ++ .../java/com/imyeyu/api/bean/ModuleCode.java | 13 + .../imyeyu/api/bean/MultilingualHandler.java | 26 - .../imyeyu/api/bean/PermissionMatchMode.java | 16 + .../com/imyeyu/api/bean/RoleMatchMode.java | 16 + .../com/imyeyu/api/config/BeanConfig.java | 15 +- .../com/imyeyu/api/config/RedisConfig.java | 107 +-- .../java/com/imyeyu/api/config/WebConfig.java | 18 +- .../config/dbsource/ForeverMCDBConfig.java | 3 - .../api/config/dbsource/GiteaDBConfig.java | 5 +- .../config/dbsource/TimiServerDBConfig.java | 13 +- .../blog/controller/ArticleController.java | 93 --- .../blog/controller/BlogController.java | 32 - .../api/modules/blog/entity/Article.java | 92 --- .../modules/blog/entity/ArticleRanking.java | 35 - .../blog/entity/CommentRemindQueue.java | 36 - .../api/modules/blog/entity/Friend.java | 21 - .../modules/blog/mapper/ArticleMapper.java | 33 - .../api/modules/blog/mapper/FriendMapper.java | 20 - .../modules/blog/service/ArticleService.java | 48 -- .../service/CommentRemindQueueService.java | 41 -- .../modules/blog/service/FriendService.java | 16 - .../implement/ArticleServiceImplement.java | 122 ---- .../CommentRemindQueueServiceImplement.java | 52 -- .../implement/FriendServiceImplement.java | 27 - .../api/modules/blog/util/UserToken.java | 147 ---- .../modules/blog/vo/article/ArticleView.java | 24 - .../modules/blog/vo/article/ClassPage.java | 16 - .../modules/blog/vo/article/KeywordPage.java | 18 - .../modules/blog/vo/article/LabelPage.java | 16 - .../modules/common/bean/CommentSupport.java | 16 - .../api/modules/common/bean/SettingKey.java | 191 ----- .../modules/common/bean/SettingModule.java | 50 ++ .../common/bean/attachment/AccessKey.java | 28 + .../bean/{ => attachment}/MediaAttach.java | 2 +- .../bean/{ => attachment}/Metadata.java | 10 +- .../bean/attachment/SettingSupport.java | 18 + .../bean/attachment/TempFileMetaData.java | 25 + .../controller/AttachmentController.java | 281 ++++++++ .../common/controller/CommentController.java | 255 ++++++- .../common/controller/CommonController.java | 479 +------------ .../common/controller/IconController.java | 91 +-- .../common/controller/LoggerController.java | 35 + .../controller/MultilingualController.java | 94 +++ .../common/controller/SettingController.java | 100 +++ .../common/controller/UserController.java | 316 -------- .../api/modules/common/entity/Article.java | 82 +++ .../api/modules/common/entity/Attachment.java | 75 +- .../api/modules/common/entity/Comment.java | 112 +-- .../modules/common/entity/CommentReply.java | 59 -- .../api/modules/common/entity/DataDict.java | 47 ++ .../modules/common/entity/DataDictOption.java | 50 ++ .../modules/common/entity/EmailFilter.java | 34 + .../api/modules/common/entity/EmailQueue.java | 43 -- .../modules/common/entity/EmailQueueLog.java | 23 - .../api/modules/common/entity/Feedback.java | 21 - .../api/modules/common/entity/Icon.java | 4 +- .../modules/common/entity/Multilingual.java | 37 +- .../api/modules/common/entity/Notify.java | 44 ++ .../modules/common/entity/NotifyDetail.java | 97 +++ .../api/modules/common/entity/Setting.java | 503 ++++++++++++- .../imyeyu/api/modules/common/entity/Tag.java | 32 +- .../api/modules/common/entity/TagApply.java | 25 + .../api/modules/common/entity/Task.java | 40 -- .../api/modules/common/entity/TaskDetail.java | 48 -- .../api/modules/common/entity/Template.java | 33 - .../api/modules/common/entity/User.java | 68 -- .../api/modules/common/entity/UserConfig.java | 33 - .../modules/common/entity/UserPrivacy.java | 51 -- .../modules/common/entity/UserProfile.java | 66 -- .../api/modules/common/entity/Version.java | 21 - .../modules/common/mapper/ArticleMapper.java | 20 + .../common/mapper/AttachmentMapper.java | 33 +- .../modules/common/mapper/CommentMapper.java | 148 +++- .../mapper/CommentRemindQueueMapper.java | 19 - .../common/mapper/CommentReplyMapper.java | 14 - .../modules/common/mapper/DataDictMapper.java | 13 + .../common/mapper/DataDictOptionMapper.java | 13 + .../common/mapper/EmailFilterMapper.java | 13 + .../common/mapper/EmailQueueLogMapper.java | 11 - .../common/mapper/EmailQueueMapper.java | 22 - .../modules/common/mapper/FeedbackMapper.java | 13 - .../api/modules/common/mapper/IconMapper.java | 24 +- .../common/mapper/MultilingualMapper.java | 21 +- .../common/mapper/NotifyDetailMapper.java | 13 + .../modules/common/mapper/NotifyMapper.java | 13 + .../modules/common/mapper/SettingMapper.java | 10 - .../api/modules/common/mapper/TagMapper.java | 2 +- .../api/modules/common/mapper/TaskMapper.java | 16 - .../modules/common/mapper/TemplateMapper.java | 15 - .../common/mapper/UserConfigMapper.java | 13 - .../api/modules/common/mapper/UserMapper.java | 20 - .../common/mapper/UserPrivacyMapper.java | 13 - .../common/mapper/UserProfileMapper.java | 13 - .../modules/common/mapper/VersionMapper.java | 17 - .../common/service/ArticleService.java | 13 + .../common/service/AttachmentService.java | 62 +- .../common/service/CommentReplyService.java | 13 - .../common/service/CommentService.java | 115 ++- .../common/service/DataDictOptionService.java | 13 + .../common/service/DataDictService.java | 13 + .../common/service/EmailFilterService.java | 13 + .../common/service/EmailQueueService.java | 52 -- .../common/service/FeedbackService.java | 14 - .../modules/common/service/IconService.java | 25 +- .../common/service/MultilingualService.java | 20 +- .../common/service/NotifyDetailService.java | 13 + .../modules/common/service/NotifyService.java | 13 + .../common/service/SettingService.java | 83 +-- .../modules/common/service/TagService.java | 10 +- .../modules/common/service/TaskService.java | 23 - .../common/service/TempFileService.java | 14 +- .../common/service/TemplateService.java | 12 - .../common/service/UserConfigService.java | 15 - .../common/service/UserPrivacyService.java | 15 - .../common/service/UserProfileService.java | 18 - .../modules/common/service/UserService.java | 149 ---- .../common/service/VersionService.java | 14 - .../implement/ArticleServiceImplement.java | 29 + .../implement/AttachmentServiceImplement.java | 386 +++++++--- .../implement/ClipboardServiceImplement.java | 2 +- .../CommentReplyServiceImplement.java | 172 ----- .../implement/CommentServiceImplement.java | 671 ++++++++++++++--- .../DataDictOptionServiceImplement.java | 27 + .../implement/DataDictServiceImplement.java | 27 + .../EmailFilterServiceImplement.java | 27 + .../implement/EmailQueueServiceImplement.java | 50 -- .../implement/FeedbackServiceImplement.java | 40 -- .../implement/IconServiceImplement.java | 83 ++- .../MultilingualServiceImplement.java | 91 +-- .../NotifyDetailServiceImplement.java | 27 + .../implement/NotifyServiceImplement.java | 27 + .../implement/SettingServiceImplement.java | 192 ++--- .../implement/TagServiceImplement.java | 26 +- .../implement/TaskServiceImplement.java | 28 - .../implement/TempFileServiceImplement.java | 173 +++-- .../implement/TemplateServiceImplement.java | 31 - .../implement/UserConfigServiceImplement.java | 45 -- .../UserPrivacyServiceImplement.java | 50 -- .../UserProfileServiceImplement.java | 104 --- .../implement/UserServiceImplement.java | 403 ----------- .../implement/VersionServiceImplement.java | 25 - .../api/modules/common/task/EmailTask.java | 219 ------ .../task/MultilingualTranslateTask.java | 165 ----- .../api/modules/common/vo/CaptchaRequest.java | 20 - .../modules/common/vo/FeedbackRequest.java | 22 - .../common/vo/attach/BizDeleteReq.java | 20 + .../common/vo/attach/BizUpdateReq.java | 26 + .../common/vo/attach/TempFileResp.java | 15 + .../common/vo/attach/TempFileSaveReq.java | 18 + .../common/vo/comment/CommentCreateReq.java | 28 + .../vo/comment/CommentLayerQueryReq.java | 28 + .../common/vo/comment/CommentLayerResp.java | 26 + .../common/vo/comment/CommentNodeResp.java | 30 + .../common/vo/comment/CommentReplyReq.java | 25 + .../common/vo/comment/CommentThreadResp.java | 30 + .../vo/comment/FlatCommentListItemResp.java | 30 + .../vo/comment/FlatCommentListResp.java | 25 + .../vo/comment/NestedCommentListResp.java | 25 + .../modules/common/vo/user/LoginResponse.java | 26 - .../common/vo/user/RegisterRequest.java | 32 - .../common/vo/user/UserProfileView.java | 21 - .../modules/common/vo/user/UserRequest.java | 33 - .../api/modules/common/vo/user/UserView.java | 56 -- .../modules/forevermc/bean/ServerStatus.java | 154 ---- .../controller/ClientPackController.java | 42 -- .../controller/ServerController.java | 133 ---- .../modules/forevermc/entity/ClientPack.java | 45 -- .../forevermc/entity/ClientPackSrc.java | 49 -- .../api/modules/forevermc/entity/Server.java | 50 -- .../forevermc/entity/ServerClient.java | 15 - .../forevermc/mapper/ClientPackMapper.java | 12 - .../forevermc/mapper/ClientPackSrcMapper.java | 17 - .../forevermc/mapper/ServerClientMapper.java | 14 - .../forevermc/mapper/ServerMapper.java | 11 - .../forevermc/service/ClientPackService.java | 19 - .../forevermc/service/ServerService.java | 38 - .../implement/ClientPackServiceImplement.java | 35 - .../implement/ServerServiceImplement.java | 86 --- .../gao/annotation/RequireGaoPermission.java | 29 + .../RequireGaoPermissionInterceptor.java | 46 ++ .../gao/annotation/RequireGaoRole.java | 29 + .../annotation/RequireGaoRoleInterceptor.java | 46 ++ .../modules/gao/bean/GaoPermissionCode.java | 94 +++ .../api/modules/gao/bean/GaoRoleCode.java | 41 ++ .../gao/controller/GaoCustomerController.java | 190 +++++ .../GaoRegistrationEventController.java | 82 +++ .../GaoRegistrationEventRecordController.java | 192 +++++ .../gao/controller/GaoRoleController.java | 36 + .../gao/controller/GaoUserController.java | 96 +++ .../api/modules/gao/entity/GaoCustomer.java | 77 ++ .../gao/entity/GaoRegistrationEvent.java | 37 + .../entity/GaoRegistrationEventRecord.java | 61 ++ .../GaoRegistrationEventRoleRelation.java | 21 + .../api/modules/gao/entity/GaoUser.java | 38 + .../modules/gao/mapper/GaoCustomerMapper.java | 39 + .../mapper/GaoRegistrationEventMapper.java | 18 + .../GaoRegistrationEventRecordMapper.java | 106 +++ ...aoRegistrationEventRoleRelationMapper.java | 23 + .../api/modules/gao/mapper/GaoUserMapper.java | 17 + .../gao/service/GaoCustomerService.java | 64 ++ .../GaoRegistrationEventRecordService.java | 97 +++ .../service/GaoRegistrationEventService.java | 33 + .../modules/gao/service/GaoUserService.java | 26 + .../GaoCustomerServiceImplement.java | 243 +++++++ ...gistrationEventRecordServiceImplement.java | 325 +++++++++ .../GaoRegistrationEventServiceImplement.java | 127 ++++ .../implement/GaoUserServiceImplement.java | 172 +++++ .../gao/vo/GaoCustomerDailyStatView.java | 21 + .../gao/vo/GaoCustomerInviteUpdateReq.java | 18 + .../gao/vo/GaoCustomerRegisterStatView.java | 30 + .../modules/gao/vo/GaoCustomerSearchReq.java | 18 + .../gao/vo/GaoCustomerTrendStatReq.java | 18 + .../GaoQuickRegistrationEventRecordReq.java | 27 + .../GaoRegistrationEventPeriodStatView.java | 21 + .../GaoRegistrationEventRecordCreateReq.java | 33 + ...oRegistrationEventRecordDailyStatView.java | 21 + .../GaoRegistrationEventRecordPeriodReq.java | 35 + .../GaoRegistrationEventRecordRangeReq.java | 24 + .../vo/GaoRegistrationEventRecordStatReq.java | 21 + ...aoRegistrationEventRecordTrendStatReq.java | 24 + .../gao/vo/GaoRegistrationEventSaveReq.java | 22 + .../api/modules/git/bean/AttachType.java | 14 - .../api/modules/git/bean/GitCommit.java | 25 - .../api/modules/git/bean/gitea/API.java | 59 -- .../api/modules/git/bean/gitea/Branch.java | 17 - .../api/modules/git/bean/gitea/File.java | 43 -- .../modules/git/bean/gitea/GiteaResponse.java | 14 - .../modules/git/bean/gitea/Repository.java | 52 -- .../modules/git/bean/hook/PostReceive.java | 21 - .../git/controller/DeveloperController.java | 43 -- .../git/controller/IssueController.java | 108 --- .../git/controller/MergeController.java | 110 --- .../git/controller/ReleaseController.java | 41 -- .../git/controller/RepositoryController.java | 174 ----- .../api/modules/git/entity/CommitLog.java | 33 - .../api/modules/git/entity/Developer.java | 37 - .../imyeyu/api/modules/git/entity/Issue.java | 120 ---- .../imyeyu/api/modules/git/entity/Merge.java | 133 ---- .../api/modules/git/entity/PushLog.java | 39 - .../api/modules/git/entity/Release.java | 33 - .../modules/git/mapper/DeveloperMapper.java | 15 - .../api/modules/git/mapper/IssueMapper.java | 35 - .../api/modules/git/mapper/MergeMapper.java | 23 - .../api/modules/git/mapper/ReleaseMapper.java | 23 - .../modules/git/service/DeveloperService.java | 17 - .../api/modules/git/service/IssueService.java | 20 - .../api/modules/git/service/MergeService.java | 20 - .../modules/git/service/ReleaseService.java | 14 - .../git/service/RepositoryService.java | 44 -- .../implement/DeveloperServiceImplement.java | 66 -- .../implement/IssueServiceImplement.java | 96 --- .../implement/MergeServiceImplement.java | 112 --- .../implement/ReleaseServiceImplement.java | 32 - .../implement/RepositoryServiceImplement.java | 224 ------ .../git/util/GiteaTimestampAdapter.java | 54 -- .../git/vo/developer/DeveloperRequest.java | 15 - .../api/modules/git/vo/issue/IssuePage.java | 21 - .../modules/git/vo/issue/IssueRequest.java | 37 - .../api/modules/git/vo/issue/IssueView.java | 22 - .../api/modules/git/vo/merge/MergePage.java | 21 - .../modules/git/vo/merge/MergeRequest.java | 44 -- .../api/modules/git/vo/merge/MergeView.java | 25 - .../modules/git/vo/release/ReleasePage.java | 16 - .../modules/git/vo/release/ReleaseView.java | 19 - .../git/vo/repository/RepositoryView.java | 19 - .../api/modules/gitea/bean/ActionLogDTO.java | 65 -- .../api/modules/gitea/entity/Action.java | 23 - .../api/modules/gitea/entity/Repository.java | 59 -- .../imyeyu/api/modules/gitea/entity/User.java | 152 ---- .../modules/gitea/mapper/ActionMapper.java | 16 - .../modules/gitea/mapper/GiteaUserMapper.java | 11 - .../gitea/mapper/RepositoryMapper.java | 12 - .../modules/gitea/service/GiteaService.java | 25 - .../implement/GiteaServiceImplement.java | 65 -- .../gitea/util/GiteaUTCTimestampAdapter.java | 39 - .../api/modules/gitea/vo/ActionLogView.java | 81 --- .../journal/controller/JournalController.java | 498 ++++++------- .../journal/controller/ToolController.java | 5 +- .../journal/controller/TravelController.java | 4 +- .../controller/TravelLocationController.java | 122 ---- .../api/modules/journal/entity/Journal.java | 4 +- .../api/modules/journal/entity/Travel.java | 4 +- .../journal/entity/TravelLocation.java | 17 +- .../modules/journal/mapper/JournalMapper.java | 2 +- .../journal/mapper/TravelLocationMapper.java | 2 +- .../modules/journal/mapper/TravelMapper.java | 2 +- .../journal/service/JournalService.java | 34 +- .../service/TravelLocationService.java | 15 +- .../journal/service/TravelService.java | 2 +- .../implement/JournalServiceImplement.java | 183 +---- .../TravelLocationServiceImplement.java | 92 +-- .../implement/TravelServiceImplement.java | 22 +- .../journal/util/JournalAPIInterceptor.java | 7 +- .../lyric/controller/LyricController.java | 93 +-- .../api/modules/lyric/entity/Lyric.java | 11 +- .../modules/lyric/entity/LyricCorrect.java | 16 +- .../lyric/mapper/LyricCorrectMapper.java | 2 +- .../api/modules/lyric/mapper/LyricMapper.java | 2 +- .../lyric/service/LyricCorrectService.java | 9 - .../modules/lyric/service/LyricService.java | 18 +- .../LyricCorrectServiceImplement.java | 82 +-- .../implement/LyricServiceImplement.java | 92 +-- .../modules/lyric/util/InitLyricSearch.java | 10 +- .../annotation/RequiredFMCServerToken.java | 20 - .../RequiredFMCServerTokenInterceptor.java | 51 -- .../modules/minecraft/bean/AttachType.java | 12 - .../controller/MinecraftController.java | 78 -- .../minecraft/controller/PackController.java | 47 -- .../controller/PlayerController.java | 126 ---- .../minecraft/entity/MinecraftPack.java | 45 -- .../minecraft/entity/MinecraftPackSource.java | 47 -- .../minecraft/entity/MinecraftPlayer.java | 28 - .../modules/minecraft/mapper/PackMapper.java | 14 - .../minecraft/mapper/PlayerMapper.java | 20 - .../minecraft/service/FMCImageMapService.java | 38 - .../minecraft/service/PackService.java | 14 - .../minecraft/service/PlayerService.java | 38 - .../FMCImageMapServiceImplement.java | 62 -- .../implement/PackServiceImplement.java | 25 - .../implement/PlayerServiceImplement.java | 151 ---- .../modules/minecraft/vo/LoginRequest.java | 15 - .../modules/minecraft/vo/TokenRequest.java | 18 - .../modules/minecraft/vo/TokenResponse.java | 26 - .../minecraft/vo/server/ReportRequest.java | 14 - .../api/modules/mirror/AttachmentMirror.java | 7 +- .../api/modules/mirror/FabricAPIMirror.java | 8 +- .../api/modules/mirror/MirrorSyncTask.java | 64 +- .../api/modules/mirror/OpenJDKMirror.java | 8 +- .../mirror/controller/MirrorController.java | 18 +- .../api/modules/mirror/entity/Mirror.java | 4 +- .../modules/mirror/mapper/MirrorMapper.java | 14 +- .../modules/mirror/service/MirrorService.java | 22 +- .../implement/MirrorServiceImplement.java | 16 +- .../runner/ControllerBootstrapRunner.java | 29 +- .../music/runner/PlayerBootstrapRunner.java | 18 +- .../api/modules/system/bean/AccessKey.java | 19 + .../api/modules/system/bean/ServerFile.java | 184 +---- .../controller/AsyncTaskController.java | 6 +- .../system/controller/FileController.java | 102 +-- .../system/controller/SystemController.java | 106 +-- .../system/controller/TerminalController.java | 99 --- .../api/modules/system/entity/AsyncTask.java | 6 +- .../api/modules/system/entity/Logger.java | 75 ++ .../modules/system/mapper/LoggerMapper.java | 13 + .../modules/system/service/LoggerService.java | 56 ++ .../modules/system/service/SystemService.java | 18 - .../service/implement/AbstractAsyncTask.java | 8 +- .../implement/AsyncTaskServiceImplement.java | 6 +- .../implement/FileServiceImplement.java | 72 +- .../implement/LoggerServiceImplement.java | 48 ++ .../implement/StatusServiceImplement.java | 10 +- .../implement/SystemServiceImplement.java | 73 -- .../implement/TerminalServiceImplement.java | 132 ---- .../modules/system/task/DockerStatusTask.java | 2 +- .../modules/system/task/ServerStatusTask.java | 38 +- .../api/modules/system/task/TerminalTask.java | 45 -- .../modules/system/task/UpsStatusTask.java | 6 +- .../status/AbstractDequeStatusCollector.java | 4 +- .../collector/NetworkStatusCollector.java | 9 +- .../api/modules/system/util/LoggerFilter.java | 8 +- .../system/util/SystemAPIInterceptor.java | 55 +- .../modules/system/vo/TempAttachRequest.java | 19 - .../api/modules/user/bean/BootstrapData.java | 108 +++ .../user/bean/BuiltinAuthRegistry.java | 61 ++ .../user/bean/BuiltinPermissionCode.java | 36 + .../modules/user/bean/BuiltinRoleCode.java | 31 + .../imyeyu/api/modules/user/bean/Gender.java | 12 + .../user/controller/PermissionController.java | 73 ++ .../user/controller/RoleController.java | 115 +++ .../user/controller/UserController.java | 270 +++++++ .../api/modules/user/entity/Permission.java | 35 + .../imyeyu/api/modules/user/entity/Role.java | 58 ++ .../modules/user/entity/RolePermission.java | 49 ++ .../api/modules/user/entity/RoleRelation.java | 37 + .../imyeyu/api/modules/user/entity/User.java | 156 ++++ .../user/entity/UserBehaviorRecord.java | 56 ++ .../api/modules/user/entity/UserRole.java | 37 + .../modules/user/mapper/PermissionMapper.java | 17 + .../api/modules/user/mapper/RoleMapper.java | 23 + .../user/mapper/RolePermissionMapper.java | 28 + .../user/mapper/RoleRelationMapper.java | 25 + .../user/mapper/UserBehaviorRecordMapper.java | 39 + .../api/modules/user/mapper/UserMapper.java | 36 + .../modules/user/mapper/UserRoleMapper.java | 19 + .../user/service/PermissionChecker.java | 56 ++ .../user/service/PermissionService.java | 33 + .../api/modules/user/service/RoleChecker.java | 44 ++ .../user/service/RoleRelationService.java | 31 + .../api/modules/user/service/RoleService.java | 83 +++ .../service/UserBehaviorRecordService.java | 48 ++ .../user/service/UserLoginService.java | 76 ++ .../modules/user/service/UserRoleService.java | 39 + .../api/modules/user/service/UserService.java | 23 + .../implement/PermissionCheckerImplement.java | 75 ++ .../implement/PermissionServiceImplement.java | 87 +++ .../implement/RoleCheckerImplement.java | 68 ++ .../RoleRelationServiceImplement.java | 68 ++ .../implement/RoleServiceImplement.java | 294 ++++++++ .../UserBehaviorRecordServiceImplement.java | 81 +++ .../implement/UserLoginServiceImplement.java | 195 +++++ .../implement/UserRoleServiceImplement.java | 44 ++ .../implement/UserServiceImplement.java | 143 ++++ .../{common => user}/validation/UserName.java | 4 +- .../validation/UserPassword.java | 4 +- .../validtor/UserNameValidator.java | 4 +- .../validtor/UserPasswordValidator.java | 8 +- .../api/modules/user/vo/CancelRequest.java | 18 + .../vo/user => user/vo}/LoginRequest.java | 6 +- .../api/modules/user/vo/LoginResponse.java | 30 + .../user/vo/PhoneVerifyCodeSendRequest.java | 18 + .../modules/user/vo/PhoneVerifyRequest.java | 23 + .../vo}/UpdatePasswordByKeyRequest.java | 4 +- .../vo}/UpdatePasswordRequest.java | 4 +- .../user/vo/permission/BatchCreateReq.java | 39 + .../user/vo/role/UserRoleAuthorizeReq.java | 22 + src/main/java/com/imyeyu/api/util/AES.java | 84 --- .../com/imyeyu/api/util/CaptchaManager.java | 91 ++- .../com/imyeyu/api/util/InitApplication.java | 63 +- .../com/imyeyu/api/util/InitBuiltinUser.java | 222 ++++++ .../api/util/MultilingualFieldBinder.java | 377 ++++++++++ .../util/MultilingualResponseBodyAdvice.java | 42 ++ .../com/imyeyu/api/util/RedisLanguage.java | 30 +- .../imyeyu/api/util/RedisMultilingual.java | 7 +- src/main/resources/application_export.yml | 23 +- src/main/resources/db.sql | 677 ------------------ .../timiserver/V10__create_tag_tables.sql | 23 + .../timiserver/V11__create_icon_table.sql | 12 + .../timiserver/V12__create_lyric_tables.sql | 32 + .../timiserver/V13__create_mirror_table.sql | 15 + .../V14__create_journal_travel_tables.sql | 54 ++ ...__create_gao_registration_event_tables.sql | 72 ++ .../migration/timiserver/V1__init_schema.sql | 219 ++++++ .../timiserver/V2__init_system_data.sql | 19 + .../timiserver/V3__create_article_table.sql | 22 + .../V4__create_async_task_table.sql | 19 + .../V5__create_attachment_table.sql | 24 + .../timiserver/V6__create_comment_table.sql | 28 + .../V7__create_data_dict_tables.sql | 29 + .../V8__create_email_filter_table.sql | 9 + .../timiserver/V9__create_notify_tables.sql | 39 + .../resources/mapper/blog/ArticleMapper.xml | 56 -- .../mapper/common/AttachmentMapper.xml | 79 -- .../resources/mapper/common/CommentMapper.xml | 25 - .../resources/mapper/common/IconMapper.xml | 35 - .../mapper/common/MultilingualMapper.xml | 11 - .../resources/mapper/common/SettingMapper.xml | 16 - .../resources/mapper/common/TaskMapper.xml | 55 -- .../mapper/common/UserConfigMapper.xml | 5 - .../mapper/common/UserPrivacyMapper.xml | 4 - .../mapper/common/UserProfileMapper.xml | 5 - src/main/resources/mapper/git/IssueMapper.xml | 40 -- src/main/resources/mapper/git/MergeMapper.xml | 40 -- .../resources/mapper/git/ReleaseMapper.xml | 43 -- .../resources/mapper/gitea/ActionMapper.xml | 52 -- .../mapper/journal/JournalMapper.xml | 20 - .../mapper/journal/TravelLocationMapper.xml | 20 - .../resources/mapper/minecraft/PackMapper.xml | 33 - .../mapper/minecraft/PlayerMapper.xml | 4 - .../mapper/system/AsyncTaskMapper.xml | 56 -- .../mapper/timi-server/GaoCustomerMapper.xml | 67 ++ .../GaoRegistrationEventRecordMapper.xml | 233 ++++++ .../mapper/timi-server/MultilingualMapper.xml | 21 + .../mapper/timi-server/RoleMapper.xml | 49 ++ .../timi-server/RolePermissionMapper.xml | 51 ++ .../mapper/timi-server/RoleRelationMapper.xml | 52 ++ .../mapper/timi-server/UserMapper.xml | 21 + .../mapper/timi-server/UserRoleMapper.xml | 25 + src/test/java/test/SpringTest.java | 110 --- src/test/java/test/Test.java | 37 +- 512 files changed, 14620 insertions(+), 13519 deletions(-) create mode 100644 .idea/codeStyles/codeStyleConfig.xml create mode 100644 .idea/dataSources.xml create mode 100644 .idea/data_source_mapping.xml create mode 100644 .idea/inspectionProfiles/Project_Default.xml create mode 100644 .idea/sqldialects.xml create mode 100644 .serena/.gitignore create mode 100644 .serena/memories/project_overview.md create mode 100644 .serena/memories/style_conventions.md create mode 100644 .serena/memories/suggested_commands.md create mode 100644 .serena/memories/task_completion.md create mode 100644 .serena/project.yml create mode 100644 http/README.md create mode 100644 http/common/attachment.http create mode 100644 http/common/multilingual.http create mode 100644 http/gao/action.http create mode 100644 http/gao/customer.http create mode 100644 http/gao/record.http create mode 100644 http/http-client.env.json create mode 100644 http/object/common.md create mode 100644 http/object/permission.md create mode 100644 http/object/role.md create mode 100644 http/object/user.md create mode 100644 http/user/permission.http create mode 100644 http/user/role.http create mode 100644 http/user/user.http delete mode 100644 src/main/java/com/imyeyu/api/annotation/CaptchaValid.java create mode 100644 src/main/java/com/imyeyu/api/annotation/MultilingualField.java create mode 100644 src/main/java/com/imyeyu/api/annotation/RequireCorePermission.java create mode 100644 src/main/java/com/imyeyu/api/annotation/RequireCorePermissionInterceptor.java create mode 100644 src/main/java/com/imyeyu/api/annotation/RequireCoreRole.java create mode 100644 src/main/java/com/imyeyu/api/annotation/RequireCoreRoleInterceptor.java delete mode 100644 src/main/java/com/imyeyu/api/annotation/RequiredTokenInterceptor.java delete mode 100644 src/main/java/com/imyeyu/api/bean/CaptchaFrom.java create mode 100644 src/main/java/com/imyeyu/api/bean/CorePermissionCode.java create mode 100644 src/main/java/com/imyeyu/api/bean/CoreRoleCode.java create mode 100644 src/main/java/com/imyeyu/api/bean/ModuleCode.java delete mode 100644 src/main/java/com/imyeyu/api/bean/MultilingualHandler.java create mode 100644 src/main/java/com/imyeyu/api/bean/PermissionMatchMode.java create mode 100644 src/main/java/com/imyeyu/api/bean/RoleMatchMode.java delete mode 100644 src/main/java/com/imyeyu/api/modules/blog/controller/ArticleController.java delete mode 100644 src/main/java/com/imyeyu/api/modules/blog/controller/BlogController.java delete mode 100644 src/main/java/com/imyeyu/api/modules/blog/entity/Article.java delete mode 100644 src/main/java/com/imyeyu/api/modules/blog/entity/ArticleRanking.java delete mode 100644 src/main/java/com/imyeyu/api/modules/blog/entity/CommentRemindQueue.java delete mode 100644 src/main/java/com/imyeyu/api/modules/blog/entity/Friend.java delete mode 100644 src/main/java/com/imyeyu/api/modules/blog/mapper/ArticleMapper.java delete mode 100644 src/main/java/com/imyeyu/api/modules/blog/mapper/FriendMapper.java delete mode 100644 src/main/java/com/imyeyu/api/modules/blog/service/ArticleService.java delete mode 100644 src/main/java/com/imyeyu/api/modules/blog/service/CommentRemindQueueService.java delete mode 100644 src/main/java/com/imyeyu/api/modules/blog/service/FriendService.java delete mode 100644 src/main/java/com/imyeyu/api/modules/blog/service/implement/ArticleServiceImplement.java delete mode 100644 src/main/java/com/imyeyu/api/modules/blog/service/implement/CommentRemindQueueServiceImplement.java delete mode 100644 src/main/java/com/imyeyu/api/modules/blog/service/implement/FriendServiceImplement.java delete mode 100644 src/main/java/com/imyeyu/api/modules/blog/util/UserToken.java delete mode 100644 src/main/java/com/imyeyu/api/modules/blog/vo/article/ArticleView.java delete mode 100644 src/main/java/com/imyeyu/api/modules/blog/vo/article/ClassPage.java delete mode 100644 src/main/java/com/imyeyu/api/modules/blog/vo/article/KeywordPage.java delete mode 100644 src/main/java/com/imyeyu/api/modules/blog/vo/article/LabelPage.java delete mode 100644 src/main/java/com/imyeyu/api/modules/common/bean/CommentSupport.java delete mode 100644 src/main/java/com/imyeyu/api/modules/common/bean/SettingKey.java create mode 100644 src/main/java/com/imyeyu/api/modules/common/bean/SettingModule.java create mode 100644 src/main/java/com/imyeyu/api/modules/common/bean/attachment/AccessKey.java rename src/main/java/com/imyeyu/api/modules/common/bean/{ => attachment}/MediaAttach.java (76%) rename src/main/java/com/imyeyu/api/modules/common/bean/{ => attachment}/Metadata.java (65%) create mode 100644 src/main/java/com/imyeyu/api/modules/common/bean/attachment/SettingSupport.java create mode 100644 src/main/java/com/imyeyu/api/modules/common/bean/attachment/TempFileMetaData.java create mode 100644 src/main/java/com/imyeyu/api/modules/common/controller/AttachmentController.java create mode 100644 src/main/java/com/imyeyu/api/modules/common/controller/LoggerController.java create mode 100644 src/main/java/com/imyeyu/api/modules/common/controller/MultilingualController.java create mode 100644 src/main/java/com/imyeyu/api/modules/common/controller/SettingController.java delete mode 100644 src/main/java/com/imyeyu/api/modules/common/controller/UserController.java create mode 100644 src/main/java/com/imyeyu/api/modules/common/entity/Article.java delete mode 100644 src/main/java/com/imyeyu/api/modules/common/entity/CommentReply.java create mode 100644 src/main/java/com/imyeyu/api/modules/common/entity/DataDict.java create mode 100644 src/main/java/com/imyeyu/api/modules/common/entity/DataDictOption.java create mode 100644 src/main/java/com/imyeyu/api/modules/common/entity/EmailFilter.java delete mode 100644 src/main/java/com/imyeyu/api/modules/common/entity/EmailQueue.java delete mode 100644 src/main/java/com/imyeyu/api/modules/common/entity/EmailQueueLog.java delete mode 100644 src/main/java/com/imyeyu/api/modules/common/entity/Feedback.java create mode 100644 src/main/java/com/imyeyu/api/modules/common/entity/Notify.java create mode 100644 src/main/java/com/imyeyu/api/modules/common/entity/NotifyDetail.java create mode 100644 src/main/java/com/imyeyu/api/modules/common/entity/TagApply.java delete mode 100644 src/main/java/com/imyeyu/api/modules/common/entity/Task.java delete mode 100644 src/main/java/com/imyeyu/api/modules/common/entity/TaskDetail.java delete mode 100644 src/main/java/com/imyeyu/api/modules/common/entity/Template.java delete mode 100644 src/main/java/com/imyeyu/api/modules/common/entity/User.java delete mode 100644 src/main/java/com/imyeyu/api/modules/common/entity/UserConfig.java delete mode 100644 src/main/java/com/imyeyu/api/modules/common/entity/UserPrivacy.java delete mode 100644 src/main/java/com/imyeyu/api/modules/common/entity/UserProfile.java delete mode 100644 src/main/java/com/imyeyu/api/modules/common/entity/Version.java create mode 100644 src/main/java/com/imyeyu/api/modules/common/mapper/ArticleMapper.java delete mode 100644 src/main/java/com/imyeyu/api/modules/common/mapper/CommentRemindQueueMapper.java delete mode 100644 src/main/java/com/imyeyu/api/modules/common/mapper/CommentReplyMapper.java create mode 100644 src/main/java/com/imyeyu/api/modules/common/mapper/DataDictMapper.java create mode 100644 src/main/java/com/imyeyu/api/modules/common/mapper/DataDictOptionMapper.java create mode 100644 src/main/java/com/imyeyu/api/modules/common/mapper/EmailFilterMapper.java delete mode 100644 src/main/java/com/imyeyu/api/modules/common/mapper/EmailQueueLogMapper.java delete mode 100644 src/main/java/com/imyeyu/api/modules/common/mapper/EmailQueueMapper.java delete mode 100644 src/main/java/com/imyeyu/api/modules/common/mapper/FeedbackMapper.java create mode 100644 src/main/java/com/imyeyu/api/modules/common/mapper/NotifyDetailMapper.java create mode 100644 src/main/java/com/imyeyu/api/modules/common/mapper/NotifyMapper.java delete mode 100644 src/main/java/com/imyeyu/api/modules/common/mapper/TaskMapper.java delete mode 100644 src/main/java/com/imyeyu/api/modules/common/mapper/TemplateMapper.java delete mode 100644 src/main/java/com/imyeyu/api/modules/common/mapper/UserConfigMapper.java delete mode 100644 src/main/java/com/imyeyu/api/modules/common/mapper/UserMapper.java delete mode 100644 src/main/java/com/imyeyu/api/modules/common/mapper/UserPrivacyMapper.java delete mode 100644 src/main/java/com/imyeyu/api/modules/common/mapper/UserProfileMapper.java delete mode 100644 src/main/java/com/imyeyu/api/modules/common/mapper/VersionMapper.java create mode 100644 src/main/java/com/imyeyu/api/modules/common/service/ArticleService.java delete mode 100644 src/main/java/com/imyeyu/api/modules/common/service/CommentReplyService.java create mode 100644 src/main/java/com/imyeyu/api/modules/common/service/DataDictOptionService.java create mode 100644 src/main/java/com/imyeyu/api/modules/common/service/DataDictService.java create mode 100644 src/main/java/com/imyeyu/api/modules/common/service/EmailFilterService.java delete mode 100644 src/main/java/com/imyeyu/api/modules/common/service/EmailQueueService.java delete mode 100644 src/main/java/com/imyeyu/api/modules/common/service/FeedbackService.java create mode 100644 src/main/java/com/imyeyu/api/modules/common/service/NotifyDetailService.java create mode 100644 src/main/java/com/imyeyu/api/modules/common/service/NotifyService.java delete mode 100644 src/main/java/com/imyeyu/api/modules/common/service/TaskService.java delete mode 100644 src/main/java/com/imyeyu/api/modules/common/service/TemplateService.java delete mode 100644 src/main/java/com/imyeyu/api/modules/common/service/UserConfigService.java delete mode 100644 src/main/java/com/imyeyu/api/modules/common/service/UserPrivacyService.java delete mode 100644 src/main/java/com/imyeyu/api/modules/common/service/UserProfileService.java delete mode 100644 src/main/java/com/imyeyu/api/modules/common/service/UserService.java delete mode 100644 src/main/java/com/imyeyu/api/modules/common/service/VersionService.java create mode 100644 src/main/java/com/imyeyu/api/modules/common/service/implement/ArticleServiceImplement.java delete mode 100644 src/main/java/com/imyeyu/api/modules/common/service/implement/CommentReplyServiceImplement.java create mode 100644 src/main/java/com/imyeyu/api/modules/common/service/implement/DataDictOptionServiceImplement.java create mode 100644 src/main/java/com/imyeyu/api/modules/common/service/implement/DataDictServiceImplement.java create mode 100644 src/main/java/com/imyeyu/api/modules/common/service/implement/EmailFilterServiceImplement.java delete mode 100644 src/main/java/com/imyeyu/api/modules/common/service/implement/EmailQueueServiceImplement.java delete mode 100644 src/main/java/com/imyeyu/api/modules/common/service/implement/FeedbackServiceImplement.java create mode 100644 src/main/java/com/imyeyu/api/modules/common/service/implement/NotifyDetailServiceImplement.java create mode 100644 src/main/java/com/imyeyu/api/modules/common/service/implement/NotifyServiceImplement.java delete mode 100644 src/main/java/com/imyeyu/api/modules/common/service/implement/TaskServiceImplement.java delete mode 100644 src/main/java/com/imyeyu/api/modules/common/service/implement/TemplateServiceImplement.java delete mode 100644 src/main/java/com/imyeyu/api/modules/common/service/implement/UserConfigServiceImplement.java delete mode 100644 src/main/java/com/imyeyu/api/modules/common/service/implement/UserPrivacyServiceImplement.java delete mode 100644 src/main/java/com/imyeyu/api/modules/common/service/implement/UserProfileServiceImplement.java delete mode 100644 src/main/java/com/imyeyu/api/modules/common/service/implement/UserServiceImplement.java delete mode 100644 src/main/java/com/imyeyu/api/modules/common/service/implement/VersionServiceImplement.java delete mode 100644 src/main/java/com/imyeyu/api/modules/common/task/EmailTask.java delete mode 100644 src/main/java/com/imyeyu/api/modules/common/task/MultilingualTranslateTask.java delete mode 100644 src/main/java/com/imyeyu/api/modules/common/vo/CaptchaRequest.java delete mode 100644 src/main/java/com/imyeyu/api/modules/common/vo/FeedbackRequest.java create mode 100644 src/main/java/com/imyeyu/api/modules/common/vo/attach/BizDeleteReq.java create mode 100644 src/main/java/com/imyeyu/api/modules/common/vo/attach/BizUpdateReq.java create mode 100644 src/main/java/com/imyeyu/api/modules/common/vo/attach/TempFileResp.java create mode 100644 src/main/java/com/imyeyu/api/modules/common/vo/attach/TempFileSaveReq.java create mode 100644 src/main/java/com/imyeyu/api/modules/common/vo/comment/CommentCreateReq.java create mode 100644 src/main/java/com/imyeyu/api/modules/common/vo/comment/CommentLayerQueryReq.java create mode 100644 src/main/java/com/imyeyu/api/modules/common/vo/comment/CommentLayerResp.java create mode 100644 src/main/java/com/imyeyu/api/modules/common/vo/comment/CommentNodeResp.java create mode 100644 src/main/java/com/imyeyu/api/modules/common/vo/comment/CommentReplyReq.java create mode 100644 src/main/java/com/imyeyu/api/modules/common/vo/comment/CommentThreadResp.java create mode 100644 src/main/java/com/imyeyu/api/modules/common/vo/comment/FlatCommentListItemResp.java create mode 100644 src/main/java/com/imyeyu/api/modules/common/vo/comment/FlatCommentListResp.java create mode 100644 src/main/java/com/imyeyu/api/modules/common/vo/comment/NestedCommentListResp.java delete mode 100644 src/main/java/com/imyeyu/api/modules/common/vo/user/LoginResponse.java delete mode 100644 src/main/java/com/imyeyu/api/modules/common/vo/user/RegisterRequest.java delete mode 100644 src/main/java/com/imyeyu/api/modules/common/vo/user/UserProfileView.java delete mode 100644 src/main/java/com/imyeyu/api/modules/common/vo/user/UserRequest.java delete mode 100644 src/main/java/com/imyeyu/api/modules/common/vo/user/UserView.java delete mode 100644 src/main/java/com/imyeyu/api/modules/forevermc/bean/ServerStatus.java delete mode 100644 src/main/java/com/imyeyu/api/modules/forevermc/controller/ClientPackController.java delete mode 100644 src/main/java/com/imyeyu/api/modules/forevermc/controller/ServerController.java delete mode 100644 src/main/java/com/imyeyu/api/modules/forevermc/entity/ClientPack.java delete mode 100644 src/main/java/com/imyeyu/api/modules/forevermc/entity/ClientPackSrc.java delete mode 100644 src/main/java/com/imyeyu/api/modules/forevermc/entity/Server.java delete mode 100644 src/main/java/com/imyeyu/api/modules/forevermc/entity/ServerClient.java delete mode 100644 src/main/java/com/imyeyu/api/modules/forevermc/mapper/ClientPackMapper.java delete mode 100644 src/main/java/com/imyeyu/api/modules/forevermc/mapper/ClientPackSrcMapper.java delete mode 100644 src/main/java/com/imyeyu/api/modules/forevermc/mapper/ServerClientMapper.java delete mode 100644 src/main/java/com/imyeyu/api/modules/forevermc/mapper/ServerMapper.java delete mode 100644 src/main/java/com/imyeyu/api/modules/forevermc/service/ClientPackService.java delete mode 100644 src/main/java/com/imyeyu/api/modules/forevermc/service/ServerService.java delete mode 100644 src/main/java/com/imyeyu/api/modules/forevermc/service/implement/ClientPackServiceImplement.java delete mode 100644 src/main/java/com/imyeyu/api/modules/forevermc/service/implement/ServerServiceImplement.java create mode 100644 src/main/java/com/imyeyu/api/modules/gao/annotation/RequireGaoPermission.java create mode 100644 src/main/java/com/imyeyu/api/modules/gao/annotation/RequireGaoPermissionInterceptor.java create mode 100644 src/main/java/com/imyeyu/api/modules/gao/annotation/RequireGaoRole.java create mode 100644 src/main/java/com/imyeyu/api/modules/gao/annotation/RequireGaoRoleInterceptor.java create mode 100644 src/main/java/com/imyeyu/api/modules/gao/bean/GaoPermissionCode.java create mode 100644 src/main/java/com/imyeyu/api/modules/gao/bean/GaoRoleCode.java create mode 100644 src/main/java/com/imyeyu/api/modules/gao/controller/GaoCustomerController.java create mode 100644 src/main/java/com/imyeyu/api/modules/gao/controller/GaoRegistrationEventController.java create mode 100644 src/main/java/com/imyeyu/api/modules/gao/controller/GaoRegistrationEventRecordController.java create mode 100644 src/main/java/com/imyeyu/api/modules/gao/controller/GaoRoleController.java create mode 100644 src/main/java/com/imyeyu/api/modules/gao/controller/GaoUserController.java create mode 100644 src/main/java/com/imyeyu/api/modules/gao/entity/GaoCustomer.java create mode 100644 src/main/java/com/imyeyu/api/modules/gao/entity/GaoRegistrationEvent.java create mode 100644 src/main/java/com/imyeyu/api/modules/gao/entity/GaoRegistrationEventRecord.java create mode 100644 src/main/java/com/imyeyu/api/modules/gao/entity/GaoRegistrationEventRoleRelation.java create mode 100644 src/main/java/com/imyeyu/api/modules/gao/entity/GaoUser.java create mode 100644 src/main/java/com/imyeyu/api/modules/gao/mapper/GaoCustomerMapper.java create mode 100644 src/main/java/com/imyeyu/api/modules/gao/mapper/GaoRegistrationEventMapper.java create mode 100644 src/main/java/com/imyeyu/api/modules/gao/mapper/GaoRegistrationEventRecordMapper.java create mode 100644 src/main/java/com/imyeyu/api/modules/gao/mapper/GaoRegistrationEventRoleRelationMapper.java create mode 100644 src/main/java/com/imyeyu/api/modules/gao/mapper/GaoUserMapper.java create mode 100644 src/main/java/com/imyeyu/api/modules/gao/service/GaoCustomerService.java create mode 100644 src/main/java/com/imyeyu/api/modules/gao/service/GaoRegistrationEventRecordService.java create mode 100644 src/main/java/com/imyeyu/api/modules/gao/service/GaoRegistrationEventService.java create mode 100644 src/main/java/com/imyeyu/api/modules/gao/service/GaoUserService.java create mode 100644 src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoCustomerServiceImplement.java create mode 100644 src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoRegistrationEventRecordServiceImplement.java create mode 100644 src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoRegistrationEventServiceImplement.java create mode 100644 src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoUserServiceImplement.java create mode 100644 src/main/java/com/imyeyu/api/modules/gao/vo/GaoCustomerDailyStatView.java create mode 100644 src/main/java/com/imyeyu/api/modules/gao/vo/GaoCustomerInviteUpdateReq.java create mode 100644 src/main/java/com/imyeyu/api/modules/gao/vo/GaoCustomerRegisterStatView.java create mode 100644 src/main/java/com/imyeyu/api/modules/gao/vo/GaoCustomerSearchReq.java create mode 100644 src/main/java/com/imyeyu/api/modules/gao/vo/GaoCustomerTrendStatReq.java create mode 100644 src/main/java/com/imyeyu/api/modules/gao/vo/GaoQuickRegistrationEventRecordReq.java create mode 100644 src/main/java/com/imyeyu/api/modules/gao/vo/GaoRegistrationEventPeriodStatView.java create mode 100644 src/main/java/com/imyeyu/api/modules/gao/vo/GaoRegistrationEventRecordCreateReq.java create mode 100644 src/main/java/com/imyeyu/api/modules/gao/vo/GaoRegistrationEventRecordDailyStatView.java create mode 100644 src/main/java/com/imyeyu/api/modules/gao/vo/GaoRegistrationEventRecordPeriodReq.java create mode 100644 src/main/java/com/imyeyu/api/modules/gao/vo/GaoRegistrationEventRecordRangeReq.java create mode 100644 src/main/java/com/imyeyu/api/modules/gao/vo/GaoRegistrationEventRecordStatReq.java create mode 100644 src/main/java/com/imyeyu/api/modules/gao/vo/GaoRegistrationEventRecordTrendStatReq.java create mode 100644 src/main/java/com/imyeyu/api/modules/gao/vo/GaoRegistrationEventSaveReq.java delete mode 100644 src/main/java/com/imyeyu/api/modules/git/bean/AttachType.java delete mode 100644 src/main/java/com/imyeyu/api/modules/git/bean/GitCommit.java delete mode 100644 src/main/java/com/imyeyu/api/modules/git/bean/gitea/API.java delete mode 100644 src/main/java/com/imyeyu/api/modules/git/bean/gitea/Branch.java delete mode 100644 src/main/java/com/imyeyu/api/modules/git/bean/gitea/File.java delete mode 100644 src/main/java/com/imyeyu/api/modules/git/bean/gitea/GiteaResponse.java delete mode 100644 src/main/java/com/imyeyu/api/modules/git/bean/gitea/Repository.java delete mode 100644 src/main/java/com/imyeyu/api/modules/git/bean/hook/PostReceive.java delete mode 100644 src/main/java/com/imyeyu/api/modules/git/controller/DeveloperController.java delete mode 100644 src/main/java/com/imyeyu/api/modules/git/controller/IssueController.java delete mode 100644 src/main/java/com/imyeyu/api/modules/git/controller/MergeController.java delete mode 100644 src/main/java/com/imyeyu/api/modules/git/controller/ReleaseController.java delete mode 100644 src/main/java/com/imyeyu/api/modules/git/controller/RepositoryController.java delete mode 100644 src/main/java/com/imyeyu/api/modules/git/entity/CommitLog.java delete mode 100644 src/main/java/com/imyeyu/api/modules/git/entity/Developer.java delete mode 100644 src/main/java/com/imyeyu/api/modules/git/entity/Issue.java delete mode 100644 src/main/java/com/imyeyu/api/modules/git/entity/Merge.java delete mode 100644 src/main/java/com/imyeyu/api/modules/git/entity/PushLog.java delete mode 100644 src/main/java/com/imyeyu/api/modules/git/entity/Release.java delete mode 100644 src/main/java/com/imyeyu/api/modules/git/mapper/DeveloperMapper.java delete mode 100644 src/main/java/com/imyeyu/api/modules/git/mapper/IssueMapper.java delete mode 100644 src/main/java/com/imyeyu/api/modules/git/mapper/MergeMapper.java delete mode 100644 src/main/java/com/imyeyu/api/modules/git/mapper/ReleaseMapper.java delete mode 100644 src/main/java/com/imyeyu/api/modules/git/service/DeveloperService.java delete mode 100644 src/main/java/com/imyeyu/api/modules/git/service/IssueService.java delete mode 100644 src/main/java/com/imyeyu/api/modules/git/service/MergeService.java delete mode 100644 src/main/java/com/imyeyu/api/modules/git/service/ReleaseService.java delete mode 100644 src/main/java/com/imyeyu/api/modules/git/service/RepositoryService.java delete mode 100644 src/main/java/com/imyeyu/api/modules/git/service/implement/DeveloperServiceImplement.java delete mode 100644 src/main/java/com/imyeyu/api/modules/git/service/implement/IssueServiceImplement.java delete mode 100644 src/main/java/com/imyeyu/api/modules/git/service/implement/MergeServiceImplement.java delete mode 100644 src/main/java/com/imyeyu/api/modules/git/service/implement/ReleaseServiceImplement.java delete mode 100644 src/main/java/com/imyeyu/api/modules/git/service/implement/RepositoryServiceImplement.java delete mode 100644 src/main/java/com/imyeyu/api/modules/git/util/GiteaTimestampAdapter.java delete mode 100644 src/main/java/com/imyeyu/api/modules/git/vo/developer/DeveloperRequest.java delete mode 100644 src/main/java/com/imyeyu/api/modules/git/vo/issue/IssuePage.java delete mode 100644 src/main/java/com/imyeyu/api/modules/git/vo/issue/IssueRequest.java delete mode 100644 src/main/java/com/imyeyu/api/modules/git/vo/issue/IssueView.java delete mode 100644 src/main/java/com/imyeyu/api/modules/git/vo/merge/MergePage.java delete mode 100644 src/main/java/com/imyeyu/api/modules/git/vo/merge/MergeRequest.java delete mode 100644 src/main/java/com/imyeyu/api/modules/git/vo/merge/MergeView.java delete mode 100644 src/main/java/com/imyeyu/api/modules/git/vo/release/ReleasePage.java delete mode 100644 src/main/java/com/imyeyu/api/modules/git/vo/release/ReleaseView.java delete mode 100644 src/main/java/com/imyeyu/api/modules/git/vo/repository/RepositoryView.java delete mode 100644 src/main/java/com/imyeyu/api/modules/gitea/bean/ActionLogDTO.java delete mode 100644 src/main/java/com/imyeyu/api/modules/gitea/entity/Action.java delete mode 100644 src/main/java/com/imyeyu/api/modules/gitea/entity/Repository.java delete mode 100644 src/main/java/com/imyeyu/api/modules/gitea/entity/User.java delete mode 100644 src/main/java/com/imyeyu/api/modules/gitea/mapper/ActionMapper.java delete mode 100644 src/main/java/com/imyeyu/api/modules/gitea/mapper/GiteaUserMapper.java delete mode 100644 src/main/java/com/imyeyu/api/modules/gitea/mapper/RepositoryMapper.java delete mode 100644 src/main/java/com/imyeyu/api/modules/gitea/service/GiteaService.java delete mode 100644 src/main/java/com/imyeyu/api/modules/gitea/service/implement/GiteaServiceImplement.java delete mode 100644 src/main/java/com/imyeyu/api/modules/gitea/util/GiteaUTCTimestampAdapter.java delete mode 100644 src/main/java/com/imyeyu/api/modules/gitea/vo/ActionLogView.java delete mode 100644 src/main/java/com/imyeyu/api/modules/minecraft/annotation/RequiredFMCServerToken.java delete mode 100644 src/main/java/com/imyeyu/api/modules/minecraft/annotation/RequiredFMCServerTokenInterceptor.java delete mode 100644 src/main/java/com/imyeyu/api/modules/minecraft/bean/AttachType.java delete mode 100644 src/main/java/com/imyeyu/api/modules/minecraft/controller/MinecraftController.java delete mode 100644 src/main/java/com/imyeyu/api/modules/minecraft/controller/PackController.java delete mode 100644 src/main/java/com/imyeyu/api/modules/minecraft/controller/PlayerController.java delete mode 100644 src/main/java/com/imyeyu/api/modules/minecraft/entity/MinecraftPack.java delete mode 100644 src/main/java/com/imyeyu/api/modules/minecraft/entity/MinecraftPackSource.java delete mode 100644 src/main/java/com/imyeyu/api/modules/minecraft/entity/MinecraftPlayer.java delete mode 100644 src/main/java/com/imyeyu/api/modules/minecraft/mapper/PackMapper.java delete mode 100644 src/main/java/com/imyeyu/api/modules/minecraft/mapper/PlayerMapper.java delete mode 100644 src/main/java/com/imyeyu/api/modules/minecraft/service/FMCImageMapService.java delete mode 100644 src/main/java/com/imyeyu/api/modules/minecraft/service/PackService.java delete mode 100644 src/main/java/com/imyeyu/api/modules/minecraft/service/PlayerService.java delete mode 100644 src/main/java/com/imyeyu/api/modules/minecraft/service/implement/FMCImageMapServiceImplement.java delete mode 100644 src/main/java/com/imyeyu/api/modules/minecraft/service/implement/PackServiceImplement.java delete mode 100644 src/main/java/com/imyeyu/api/modules/minecraft/service/implement/PlayerServiceImplement.java delete mode 100644 src/main/java/com/imyeyu/api/modules/minecraft/vo/LoginRequest.java delete mode 100644 src/main/java/com/imyeyu/api/modules/minecraft/vo/TokenRequest.java delete mode 100644 src/main/java/com/imyeyu/api/modules/minecraft/vo/TokenResponse.java delete mode 100644 src/main/java/com/imyeyu/api/modules/minecraft/vo/server/ReportRequest.java create mode 100644 src/main/java/com/imyeyu/api/modules/system/bean/AccessKey.java delete mode 100644 src/main/java/com/imyeyu/api/modules/system/controller/TerminalController.java create mode 100644 src/main/java/com/imyeyu/api/modules/system/entity/Logger.java create mode 100644 src/main/java/com/imyeyu/api/modules/system/mapper/LoggerMapper.java create mode 100644 src/main/java/com/imyeyu/api/modules/system/service/LoggerService.java delete mode 100644 src/main/java/com/imyeyu/api/modules/system/service/SystemService.java create mode 100644 src/main/java/com/imyeyu/api/modules/system/service/implement/LoggerServiceImplement.java delete mode 100644 src/main/java/com/imyeyu/api/modules/system/service/implement/SystemServiceImplement.java delete mode 100644 src/main/java/com/imyeyu/api/modules/system/service/implement/TerminalServiceImplement.java delete mode 100644 src/main/java/com/imyeyu/api/modules/system/task/TerminalTask.java delete mode 100644 src/main/java/com/imyeyu/api/modules/system/vo/TempAttachRequest.java create mode 100644 src/main/java/com/imyeyu/api/modules/user/bean/BootstrapData.java create mode 100644 src/main/java/com/imyeyu/api/modules/user/bean/BuiltinAuthRegistry.java create mode 100644 src/main/java/com/imyeyu/api/modules/user/bean/BuiltinPermissionCode.java create mode 100644 src/main/java/com/imyeyu/api/modules/user/bean/BuiltinRoleCode.java create mode 100644 src/main/java/com/imyeyu/api/modules/user/bean/Gender.java create mode 100644 src/main/java/com/imyeyu/api/modules/user/controller/PermissionController.java create mode 100644 src/main/java/com/imyeyu/api/modules/user/controller/RoleController.java create mode 100644 src/main/java/com/imyeyu/api/modules/user/controller/UserController.java create mode 100644 src/main/java/com/imyeyu/api/modules/user/entity/Permission.java create mode 100644 src/main/java/com/imyeyu/api/modules/user/entity/Role.java create mode 100644 src/main/java/com/imyeyu/api/modules/user/entity/RolePermission.java create mode 100644 src/main/java/com/imyeyu/api/modules/user/entity/RoleRelation.java create mode 100644 src/main/java/com/imyeyu/api/modules/user/entity/User.java create mode 100644 src/main/java/com/imyeyu/api/modules/user/entity/UserBehaviorRecord.java create mode 100644 src/main/java/com/imyeyu/api/modules/user/entity/UserRole.java create mode 100644 src/main/java/com/imyeyu/api/modules/user/mapper/PermissionMapper.java create mode 100644 src/main/java/com/imyeyu/api/modules/user/mapper/RoleMapper.java create mode 100644 src/main/java/com/imyeyu/api/modules/user/mapper/RolePermissionMapper.java create mode 100644 src/main/java/com/imyeyu/api/modules/user/mapper/RoleRelationMapper.java create mode 100644 src/main/java/com/imyeyu/api/modules/user/mapper/UserBehaviorRecordMapper.java create mode 100644 src/main/java/com/imyeyu/api/modules/user/mapper/UserMapper.java create mode 100644 src/main/java/com/imyeyu/api/modules/user/mapper/UserRoleMapper.java create mode 100644 src/main/java/com/imyeyu/api/modules/user/service/PermissionChecker.java create mode 100644 src/main/java/com/imyeyu/api/modules/user/service/PermissionService.java create mode 100644 src/main/java/com/imyeyu/api/modules/user/service/RoleChecker.java create mode 100644 src/main/java/com/imyeyu/api/modules/user/service/RoleRelationService.java create mode 100644 src/main/java/com/imyeyu/api/modules/user/service/RoleService.java create mode 100644 src/main/java/com/imyeyu/api/modules/user/service/UserBehaviorRecordService.java create mode 100644 src/main/java/com/imyeyu/api/modules/user/service/UserLoginService.java create mode 100644 src/main/java/com/imyeyu/api/modules/user/service/UserRoleService.java create mode 100644 src/main/java/com/imyeyu/api/modules/user/service/UserService.java create mode 100644 src/main/java/com/imyeyu/api/modules/user/service/implement/PermissionCheckerImplement.java create mode 100644 src/main/java/com/imyeyu/api/modules/user/service/implement/PermissionServiceImplement.java create mode 100644 src/main/java/com/imyeyu/api/modules/user/service/implement/RoleCheckerImplement.java create mode 100644 src/main/java/com/imyeyu/api/modules/user/service/implement/RoleRelationServiceImplement.java create mode 100644 src/main/java/com/imyeyu/api/modules/user/service/implement/RoleServiceImplement.java create mode 100644 src/main/java/com/imyeyu/api/modules/user/service/implement/UserBehaviorRecordServiceImplement.java create mode 100644 src/main/java/com/imyeyu/api/modules/user/service/implement/UserLoginServiceImplement.java create mode 100644 src/main/java/com/imyeyu/api/modules/user/service/implement/UserRoleServiceImplement.java create mode 100644 src/main/java/com/imyeyu/api/modules/user/service/implement/UserServiceImplement.java rename src/main/java/com/imyeyu/api/modules/{common => user}/validation/UserName.java (88%) rename src/main/java/com/imyeyu/api/modules/{common => user}/validation/UserPassword.java (88%) rename src/main/java/com/imyeyu/api/modules/{common => user}/validation/validtor/UserNameValidator.java (86%) rename src/main/java/com/imyeyu/api/modules/{common => user}/validation/validtor/UserPasswordValidator.java (69%) create mode 100644 src/main/java/com/imyeyu/api/modules/user/vo/CancelRequest.java rename src/main/java/com/imyeyu/api/modules/{common/vo/user => user/vo}/LoginRequest.java (67%) create mode 100644 src/main/java/com/imyeyu/api/modules/user/vo/LoginResponse.java create mode 100644 src/main/java/com/imyeyu/api/modules/user/vo/PhoneVerifyCodeSendRequest.java create mode 100644 src/main/java/com/imyeyu/api/modules/user/vo/PhoneVerifyRequest.java rename src/main/java/com/imyeyu/api/modules/{common/vo/user => user/vo}/UpdatePasswordByKeyRequest.java (69%) rename src/main/java/com/imyeyu/api/modules/{common/vo/user => user/vo}/UpdatePasswordRequest.java (65%) create mode 100644 src/main/java/com/imyeyu/api/modules/user/vo/permission/BatchCreateReq.java create mode 100644 src/main/java/com/imyeyu/api/modules/user/vo/role/UserRoleAuthorizeReq.java delete mode 100644 src/main/java/com/imyeyu/api/util/AES.java create mode 100644 src/main/java/com/imyeyu/api/util/InitBuiltinUser.java create mode 100644 src/main/java/com/imyeyu/api/util/MultilingualFieldBinder.java create mode 100644 src/main/java/com/imyeyu/api/util/MultilingualResponseBodyAdvice.java delete mode 100644 src/main/resources/db.sql create mode 100644 src/main/resources/db/migration/timiserver/V10__create_tag_tables.sql create mode 100644 src/main/resources/db/migration/timiserver/V11__create_icon_table.sql create mode 100644 src/main/resources/db/migration/timiserver/V12__create_lyric_tables.sql create mode 100644 src/main/resources/db/migration/timiserver/V13__create_mirror_table.sql create mode 100644 src/main/resources/db/migration/timiserver/V14__create_journal_travel_tables.sql create mode 100644 src/main/resources/db/migration/timiserver/V15__create_gao_registration_event_tables.sql create mode 100644 src/main/resources/db/migration/timiserver/V1__init_schema.sql create mode 100644 src/main/resources/db/migration/timiserver/V2__init_system_data.sql create mode 100644 src/main/resources/db/migration/timiserver/V3__create_article_table.sql create mode 100644 src/main/resources/db/migration/timiserver/V4__create_async_task_table.sql create mode 100644 src/main/resources/db/migration/timiserver/V5__create_attachment_table.sql create mode 100644 src/main/resources/db/migration/timiserver/V6__create_comment_table.sql create mode 100644 src/main/resources/db/migration/timiserver/V7__create_data_dict_tables.sql create mode 100644 src/main/resources/db/migration/timiserver/V8__create_email_filter_table.sql create mode 100644 src/main/resources/db/migration/timiserver/V9__create_notify_tables.sql delete mode 100644 src/main/resources/mapper/blog/ArticleMapper.xml delete mode 100644 src/main/resources/mapper/common/AttachmentMapper.xml delete mode 100644 src/main/resources/mapper/common/CommentMapper.xml delete mode 100644 src/main/resources/mapper/common/IconMapper.xml delete mode 100644 src/main/resources/mapper/common/MultilingualMapper.xml delete mode 100644 src/main/resources/mapper/common/SettingMapper.xml delete mode 100644 src/main/resources/mapper/common/TaskMapper.xml delete mode 100644 src/main/resources/mapper/common/UserConfigMapper.xml delete mode 100644 src/main/resources/mapper/common/UserPrivacyMapper.xml delete mode 100644 src/main/resources/mapper/common/UserProfileMapper.xml delete mode 100644 src/main/resources/mapper/git/IssueMapper.xml delete mode 100644 src/main/resources/mapper/git/MergeMapper.xml delete mode 100644 src/main/resources/mapper/git/ReleaseMapper.xml delete mode 100644 src/main/resources/mapper/gitea/ActionMapper.xml delete mode 100644 src/main/resources/mapper/journal/JournalMapper.xml delete mode 100644 src/main/resources/mapper/journal/TravelLocationMapper.xml delete mode 100644 src/main/resources/mapper/minecraft/PackMapper.xml delete mode 100644 src/main/resources/mapper/minecraft/PlayerMapper.xml delete mode 100644 src/main/resources/mapper/system/AsyncTaskMapper.xml create mode 100644 src/main/resources/mapper/timi-server/GaoCustomerMapper.xml create mode 100644 src/main/resources/mapper/timi-server/GaoRegistrationEventRecordMapper.xml create mode 100644 src/main/resources/mapper/timi-server/MultilingualMapper.xml create mode 100644 src/main/resources/mapper/timi-server/RoleMapper.xml create mode 100644 src/main/resources/mapper/timi-server/RolePermissionMapper.xml create mode 100644 src/main/resources/mapper/timi-server/RoleRelationMapper.xml create mode 100644 src/main/resources/mapper/timi-server/UserMapper.xml create mode 100644 src/main/resources/mapper/timi-server/UserRoleMapper.xml diff --git a/.gitignore b/.gitignore index b264bbe..720821d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,11 +1,8 @@ /config /data -/docs /logs /target /temp -/.claude -/CLAUDE.md /AGENTS.md multilingualField/ diff --git a/.idea/.gitignore b/.idea/.gitignore index 7ba768d..5a93628 100644 --- a/.idea/.gitignore +++ b/.idea/.gitignore @@ -1,5 +1,8 @@ # Default ignored files /shelf/ /workspace.xml +/httpRequests +/dataSources +/dataSources.local.xml CopilotChatHistory.xml developer-tools.xml \ No newline at end of file diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 0000000..a55e7a1 --- /dev/null +++ b/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/.idea/dataSources.xml b/.idea/dataSources.xml new file mode 100644 index 0000000..4c9d7f3 --- /dev/null +++ b/.idea/dataSources.xml @@ -0,0 +1,17 @@ + + + + + mariadb + true + org.mariadb.jdbc.Driver + jdbc:mariadb://vm.imyeyu.develop:3306/timi_server + + + + + + $ProjectFileDir$ + + + \ No newline at end of file diff --git a/.idea/data_source_mapping.xml b/.idea/data_source_mapping.xml new file mode 100644 index 0000000..22e3b6c --- /dev/null +++ b/.idea/data_source_mapping.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..46893ed --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,12 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index 454bc98..9077904 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,3 +1,4 @@ + @@ -10,49 +11,54 @@ - + \ No newline at end of file diff --git a/.idea/sqldialects.xml b/.idea/sqldialects.xml new file mode 100644 index 0000000..eb4a267 --- /dev/null +++ b/.idea/sqldialects.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.serena/.gitignore b/.serena/.gitignore new file mode 100644 index 0000000..2e510af --- /dev/null +++ b/.serena/.gitignore @@ -0,0 +1,2 @@ +/cache +/project.local.yml diff --git a/.serena/memories/project_overview.md b/.serena/memories/project_overview.md new file mode 100644 index 0000000..63e6088 --- /dev/null +++ b/.serena/memories/project_overview.md @@ -0,0 +1 @@ +TimiServerAPI 是一个基于 Spring Boot 的高耦合业务后端服务,代码位于 src/main/java/com/imyeyu/api,下分多个业务模块,如 blog、common、gitea、forevermc、journal、minecraft、music、system 等。项目使用多数据源 MySQL(timiserver、gitea、forevermc)、MongoDB、Redis、MyBatis,并引入 Flyway 做数据库迁移。运行环境为 Windows,JDK 21。 \ No newline at end of file diff --git a/.serena/memories/style_conventions.md b/.serena/memories/style_conventions.md new file mode 100644 index 0000000..95cdb34 --- /dev/null +++ b/.serena/memories/style_conventions.md @@ -0,0 +1 @@ +项目约定:全部沟通、注释、文档使用中文;Java 使用 tab 缩进;注释要求符合 javadoc;优先使用官方 API,不随意引入外部依赖;字符串优先用 formatted(),长动态字符串用 StringBuilder;优先用代码级工具做精确修改;避免盲目通读大文件,先缩小目录和符号范围。 \ No newline at end of file diff --git a/.serena/memories/suggested_commands.md b/.serena/memories/suggested_commands.md new file mode 100644 index 0000000..e062710 --- /dev/null +++ b/.serena/memories/suggested_commands.md @@ -0,0 +1 @@ +Windows / PowerShell 常用命令:`Get-ChildItem -Force` 查看目录,`rg --files` 列文件,`rg -n "PATTERN" PATH` 搜索文本,`mvn -q -DskipTests compile` 做基础编译验证,`git diff -- PATH` 查看改动。配置文件主要在 `src/main/resources`,Java 源码在 `src/main/java`。 \ No newline at end of file diff --git a/.serena/memories/task_completion.md b/.serena/memories/task_completion.md new file mode 100644 index 0000000..eb628b9 --- /dev/null +++ b/.serena/memories/task_completion.md @@ -0,0 +1 @@ +完成任务后,优先做最小范围验证。Java 侧先尝试 `mvn -q -DskipTests compile`;如果失败,区分是本次改动导致还是项目已有编译问题。不要回滚用户已有改动,不要做破坏性 git 操作。多数据源场景要特别检查 `@Primary`、`@Qualifier` 和自动装配框架(如 Flyway)的绑定关系。 \ No newline at end of file diff --git a/.serena/project.yml b/.serena/project.yml new file mode 100644 index 0000000..620976b --- /dev/null +++ b/.serena/project.yml @@ -0,0 +1,154 @@ +# the name by which the project can be referenced within Serena +project_name: "TimiServerAPI" + + +# list of languages for which language servers are started; choose from: +# al bash clojure cpp csharp +# csharp_omnisharp dart elixir elm erlang +# fortran fsharp go groovy haskell +# haxe java julia kotlin lua +# markdown +# matlab nix pascal perl php +# php_phpactor powershell python python_jedi r +# rego ruby ruby_solargraph rust scala +# swift terraform toml typescript typescript_vts +# vue yaml zig +# (This list may be outdated. For the current list, see values of Language enum here: +# https://github.com/oraios/serena/blob/main/src/solidlsp/ls_config.py +# For some languages, there are alternative language servers, e.g. csharp_omnisharp, ruby_solargraph.) +# Note: +# - For C, use cpp +# - For JavaScript, use typescript +# - For Free Pascal/Lazarus, use pascal +# Special requirements: +# Some languages require additional setup/installations. +# See here for details: https://oraios.github.io/serena/01-about/020_programming-languages.html#language-servers +# When using multiple languages, the first language server that supports a given file will be used for that file. +# The first language is the default language and the respective language server will be used as a fallback. +# Note that when using the JetBrains backend, language servers are not used and this list is correspondingly ignored. +languages: +- java + +# the encoding used by text files in the project +# For a list of possible encodings, see https://docs.python.org/3.11/library/codecs.html#standard-encodings +encoding: "utf-8" + +# line ending convention to use when writing source files. +# Possible values: unset (use global setting), "lf", "crlf", or "native" (platform default) +# This does not affect Serena's own files (e.g. memories and configuration files), which always use native line endings. +line_ending: + +# The language backend to use for this project. +# If not set, the global setting from serena_config.yml is used. +# Valid values: LSP, JetBrains +# Note: the backend is fixed at startup. If a project with a different backend +# is activated post-init, an error will be returned. +language_backend: + +# whether to use project's .gitignore files to ignore files +ignore_all_files_in_gitignore: true + +# advanced configuration option allowing to configure language server-specific options. +# Maps the language key to the options. +# Have a look at the docstring of the constructors of the LS implementations within solidlsp (e.g., for C# or PHP) to see which options are available. +# No documentation on options means no options are available. +ls_specific_settings: {} + +# list of additional paths to ignore in this project. +# Same syntax as gitignore, so you can use * and **. +# Note: global ignored_paths from serena_config.yml are also applied additively. +ignored_paths: [] + +# whether the project is in read-only mode +# If set to true, all editing tools will be disabled and attempts to use them will result in an error +# Added on 2025-04-18 +read_only: false + +# list of tool names to exclude. +# This extends the existing exclusions (e.g. from the global configuration) +# +# Below is the complete list of tools for convenience. +# To make sure you have the latest list of tools, and to view their descriptions, +# execute `uv run scripts/print_tool_overview.py`. +# +# * `activate_project`: Activates a project based on the project name or path. +# * `check_onboarding_performed`: Checks whether project onboarding was already performed. +# * `create_text_file`: Creates/overwrites a file in the project directory. +# * `delete_memory`: Delete a memory file. Should only happen if a user asks for it explicitly, +# for example by saying that the information retrieved from a memory file is no longer correct +# or no longer relevant for the project. +# * `edit_memory`: Replaces content matching a regular expression in a memory. +# * `execute_shell_command`: Executes a shell command. +# * `find_file`: Finds files in the given relative paths +# * `find_referencing_symbols`: Finds symbols that reference the given symbol using the language server backend +# * `find_symbol`: Performs a global (or local) search using the language server backend. +# * `get_current_config`: Prints the current configuration of the agent, including the active and available projects, tools, contexts, and modes. +# * `get_symbols_overview`: Gets an overview of the top-level symbols defined in a given file. +# * `initial_instructions`: Provides instructions Serena usage (i.e. the 'Serena Instructions Manual') +# for clients that do not read the initial instructions when the MCP server is connected. +# * `insert_after_symbol`: Inserts content after the end of the definition of a given symbol. +# * `insert_before_symbol`: Inserts content before the beginning of the definition of a given symbol. +# * `list_dir`: Lists files and directories in the given directory (optionally with recursion). +# * `list_memories`: List available memories. Any memory can be read using the `read_memory` tool. +# * `onboarding`: Performs onboarding (identifying the project structure and essential tasks, e.g. for testing or building). +# * `read_file`: Reads a file within the project directory. +# * `read_memory`: Read the content of a memory file. This tool should only be used if the information +# is relevant to the current task. You can infer whether the information +# is relevant from the memory file name. +# You should not read the same memory file multiple times in the same conversation. +# * `rename_memory`: Renames or moves a memory. Moving between project and global scope is supported +# (e.g., renaming "global/foo" to "bar" moves it from global to project scope). +# * `rename_symbol`: Renames a symbol throughout the codebase using language server refactoring capabilities. +# For JB, we use a separate tool. +# * `replace_content`: Replaces content in a file (optionally using regular expressions). +# * `replace_symbol_body`: Replaces the full definition of a symbol using the language server backend. +# * `safe_delete_symbol`: +# * `search_for_pattern`: Performs a search for a pattern in the project. +# * `write_memory`: Write some information (utf-8-encoded) about this project that can be useful for future tasks to a memory in md format. +# The memory name should be meaningful. +excluded_tools: [] + +# list of tools to include that would otherwise be disabled (particularly optional tools that are disabled by default). +# This extends the existing inclusions (e.g. from the global configuration). +included_optional_tools: [] + +# fixed set of tools to use as the base tool set (if non-empty), replacing Serena's default set of tools. +# This cannot be combined with non-empty excluded_tools or included_optional_tools. +fixed_tools: [] + +# list of mode names to that are always to be included in the set of active modes +# The full set of modes to be activated is base_modes + default_modes. +# If the setting is undefined, the base_modes from the global configuration (serena_config.yml) apply. +# Otherwise, this setting overrides the global configuration. +# Set this to [] to disable base modes for this project. +# Set this to a list of mode names to always include the respective modes for this project. +base_modes: + +# list of mode names that are to be activated by default. +# The full set of modes to be activated is base_modes + default_modes. +# If the setting is undefined, the default_modes from the global configuration (serena_config.yml) apply. +# Otherwise, this overrides the setting from the global configuration (serena_config.yml). +# This setting can, in turn, be overridden by CLI parameters (--mode). +default_modes: + +# initial prompt for the project. It will always be given to the LLM upon activating the project +# (contrary to the memories, which are loaded on demand). +initial_prompt: "" + +# time budget (seconds) per tool call for the retrieval of additional symbol information +# such as docstrings or parameter information. +# This overrides the corresponding setting in the global configuration; see the documentation there. +# If null or missing, use the setting from the global configuration. +symbol_info_budget: + +# list of regex patterns which, when matched, mark a memory entry as read‑only. +# Extends the list from the global configuration, merging the two lists. +read_only_memory_patterns: [] + +# list of regex patterns for memories to completely ignore. +# Matching memories will not appear in list_memories or activate_project output +# and cannot be accessed via read_memory or write_memory. +# To access ignored memory files, use the read_file tool on the raw file path. +# Extends the list from the global configuration, merging the two lists. +# Example: ["_archive/.*", "_episodes/.*"] +ignored_memory_patterns: [] diff --git a/http/README.md b/http/README.md new file mode 100644 index 0000000..01372e0 --- /dev/null +++ b/http/README.md @@ -0,0 +1,163 @@ +# HTTP 调试与文档规范 + +本目录下的 `.http` 文件同时承担接口调试和接口文档职责,也要保证 AIAgent 能稳定读取 + +## 目录分工 + +### `.http` + +`.http` 只描述接口级信息: + +1. 接口中文名 +2. 权限要求 +3. 前置条件 +4. 请求参数对应的对象说明 +5. 返回参数对应的对象说明 + +`.http` 不重复展开对象字段明细,复杂字段、嵌套结构、枚举值统一写到 `./object` 下 + +### `./object` + +`./object` 只描述对象级信息: + +1. 字段名 +2. 字段类型 +3. 字段说明 +4. 枚举值及含义 + +所有模块都可能复用的通用对象必须写在 `./object/common.md` + +按业务领域拆分对象说明文件,例如: + +1. `./object/common.md` +2. `./object/user.md` +3. `./object/role.md` +4. `./object/permission.md` + +不要把同一个对象的字段说明散落到多个文件里 + +## 全局约定 + +### 返回包装 + +除使用 `IgnoreGlobalReturn` 跳过全局包装的方法外,接口默认都会经过 +[GlobalReturnHandler](E:/IDEAProject/timi-spring/src/main/java/com/imyeyu/spring/util/GlobalReturnHandler.java) +统一包装返回 + +因此 `.http` 里的 `返回参数` 默认只描述业务 `data` 对象,不再重复赘述全局包装结构 + +全局返回包装、通用成功码、错误信息、常见分页壳子等内容统一维护在 `./object/common.md` + +如果接口使用 +[IgnoreGlobalReturn](E:/IDEAProject/timi-spring/src/main/java/com/imyeyu/spring/annotation/IgnoreGlobalReturn.java) +跳过全局包装,必须在对应接口的 `前置条件` 或补充说明里明确写出“该接口不走全局返回包装” + +### 文档真实性 + +1. 文档只记录服务端真实行为 +2. 不写猜测 +3. 不写“看代码” +4. 接口改了就同步更新 `.http` 和 `./object` + +### 变量复用 + +能复用的变量统一放在文件顶部,例如 `@user_token`、`@userId` + +## `.http` 模板 + +每个接口块固定使用下面格式,不要擅自增删字段名: + +```http +### <接口中文名> +### 权限: +### 前置条件: +### 请求参数: +### 返回参数: + +POST {{baseUrl}}/path +Content-Type: application/json +Token: {{user_token}} + +{} +``` + +## `.http` 填写要求 + +### 接口中文名 + +直接写接口用途,别写成控制器方法名 + +### 权限 + +1. 优先直接翻译控制器上的真实注解 +2. 同时存在角色和权限时一起写,例如 `需要 ADMIN 角色 + USER_UPDATE 权限` +3. 只有 Token 要求时写 `需要有效 Token` +4. 无需登录时直接写 `无需登录` + +### 前置条件 + +这里只写接口调用前必须知道的约束,例如: + +1. 需要先登录并回填 `user_token` +2. 需要先创建用户再传入 `userId` +3. 重复调用可能因为唯一约束失败 +4. 接口当前实现为空 +5. 该接口不走全局返回包装 + +没有额外限制时写 `无` + +### 请求参数 + +只写请求对象名和对象说明文件,不在 `.http` 内展开字段 + +推荐格式: + +1. `无` +2. `UserCreateRequest,详见 ./object/user.md` +3. `Page,详见 ./object/common.md 和 ./object/user.md` + +### 返回参数 + +只写业务返回对象名和对象说明文件,不在 `.http` 内展开字段 + +推荐格式: + +1. `无` +2. `User,详见 ./object/user.md` +3. `PageResult,详见 ./object/common.md 和 ./object/user.md` +4. `二进制数据流,该接口不走全局返回包装` + +## `./object` 编写要求 + +对象说明统一使用 Markdown 表格,推荐结构如下 + +```md +## User + +| 字段 | 类型 | 说明 | +|---|---|---| +| name | String | 登录名,系统内唯一 | + +## RoleCode + +| 枚举 | 说明 | +|---|---| +| SYSTEM | 系统 | +| ADMIN | 管理员 | +``` + +### 对象说明规则 + +1. 一个对象一个标题,标题名必须和 `.http` 里引用的对象名一致 +2. 字段说明只写真实存在且调用方需要关心的内容 +3. 枚举单独列出,不要把枚举值混进普通字段表 +4. 嵌套对象、列表元素对象也要有独立标题 +5. 通用壳子对象统一写在 `./object/common.md` + +## 维护要求 + +1. 一个控制器通常对应一个 `.http` 文件 +2. 新增接口时,同步补齐对应 `.http` +3. 新增或修改对象字段时,同步更新对应 `./object/*.md` +4. 注释语言统一用中文 +5. 示例请求优先保证可调试,其次才是看起来完整 diff --git a/http/common/attachment.http b/http/common/attachment.http new file mode 100644 index 0000000..afde9b4 --- /dev/null +++ b/http/common/attachment.http @@ -0,0 +1,200 @@ +### AttachmentController 接口测试 +### 在 IDEA 中选择 dev 或 prod 环境后执行 + +@attachmentId = +@attachmentBizType = TEMP_FILE +@attachmentBizId = demo-biz-id +@attachmentAttachType = SOURCE +@attachmentName = demo.png +@attachmentFilePath = E:/Temp/demo.png +@attachmentTempFileId = + +### 查询附件详情 +### 前置条件:`id` 必须传有效附件 ID +### 请求参数:URL 参数 `id` 为附件 ID +### 返回参数:Attachment,详见 ./object/common.md +POST {{baseUrl}}/attach/detail?id={{attachmentId}} +Token: {{user_token}} + +### 创建附件 +### 前置条件:回填 `attachmentFilePath` 为本地文件绝对路径 +### 请求参数:multipart/form-data +### 返回参数:Attachment,详见 ./object/common.md +POST {{baseUrl}}/attach/create +Content-Type: multipart/form-data; boundary=AttachmentCreateBoundary +Token: {{user_token}} + +--AttachmentCreateBoundary +Content-Disposition: form-data; name="bizType" + +{{attachmentBizType}} +--AttachmentCreateBoundary +Content-Disposition: form-data; name="bizId" + +{{attachmentBizId}} +--AttachmentCreateBoundary +Content-Disposition: form-data; name="attachType" + +{{attachmentAttachType}} +--AttachmentCreateBoundary +Content-Disposition: form-data; name="name" + +{{attachmentName}} +--AttachmentCreateBoundary +Content-Disposition: form-data; name="file"; filename="demo.png" +Content-Type: application/octet-stream + +< {{attachmentFilePath}} +--AttachmentCreateBoundary-- + +### 更新附件元数据 +### 前置条件:`id` 必须传有效附件 ID +### 请求参数:multipart/form-data +### 返回参数:无 +POST {{baseUrl}}/attach/update +Content-Type: multipart/form-data; boundary=AttachmentUpdateMetaBoundary +Token: {{user_token}} + +--AttachmentUpdateMetaBoundary +Content-Disposition: form-data; name="id" + +{{attachmentId}} +--AttachmentUpdateMetaBoundary +Content-Disposition: form-data; name="name" + +{{attachmentName}}-updated +--AttachmentUpdateMetaBoundary +Content-Disposition: form-data; name="attachType" + +{{attachmentAttachType}} +--AttachmentUpdateMetaBoundary-- + +### 更新附件文件 +### 前置条件:`id` 必须传有效附件 ID,`attachmentFilePath` 必须传本地文件绝对路径 +### 请求参数:multipart/form-data +### 返回参数:无 +POST {{baseUrl}}/attach/update +Content-Type: multipart/form-data; boundary=AttachmentUpdateFileBoundary +Token: {{user_token}} + +--AttachmentUpdateFileBoundary +Content-Disposition: form-data; name="id" + +{{attachmentId}} +--AttachmentUpdateFileBoundary +Content-Disposition: form-data; name="name" + +{{attachmentName}}-file-updated +--AttachmentUpdateFileBoundary +Content-Disposition: form-data; name="file"; filename="demo.png" +Content-Type: application/octet-stream + +< {{attachmentFilePath}} +--AttachmentUpdateFileBoundary-- + +### 删除附件 +### 前置条件:`id` 必须传有效附件 ID +### 请求参数:URL 参数 `id` 为附件 ID +### 返回参数:无 +POST {{baseUrl}}/attach/delete?id={{attachmentId}} +Token: {{user_token}} + +### 按业务查询单个附件 +### 前置条件:`bizType` 和 `bizId` 必须传有效值 +### 请求参数:URL 参数 `bizType`、`bizId` +### 返回参数:Attachment,详见 ./object/common.md +GET {{baseUrl}}/attach/detail/byBiz?bizType={{attachmentBizType}}&bizId={{attachmentBizId}} +Token: {{user_token}} + +### 按业务和附件类型查询单个附件 +### 前置条件:`bizType`、`bizId`、`attachType` 必须传有效值 +### 请求参数:URL 参数 `bizType`、`bizId`、`attachType` +### 返回参数:Attachment,详见 ./object/common.md +GET {{baseUrl}}/attach/detail/byAttachType?bizType={{attachmentBizType}}&bizId={{attachmentBizId}}&attachType={{attachmentAttachType}} +Token: {{user_token}} + +### 按业务查询附件列表 +### 前置条件:`bizType` 和 `bizId` 必须传有效值 +### 请求参数:URL 参数 `bizType`、`bizId`,可重复传 `attachTypeList` +### 返回参数:Attachment[] +GET {{baseUrl}}/attach/list/byBiz?bizType={{attachmentBizType}}&bizId={{attachmentBizId}}&attachTypeList={{attachmentAttachType}} +Token: {{user_token}} + +### 按业务统计附件数量 +### 前置条件:`bizType` 和 `bizId` 必须传有效值 +### 请求参数:URL 参数 `bizType`、`bizId`,可重复传 `attachTypeList` +### 返回参数:number +GET {{baseUrl}}/attach/count/byBiz?bizType={{attachmentBizType}}&bizId={{attachmentBizId}}&attachTypeList={{attachmentAttachType}} +Token: {{user_token}} + +### 按业务分页查询附件 +### 前置条件:`bizType` 和 `bizId` 必须传有效值 +### 请求参数:URL 参数 `bizType`、`bizId`,可重复传 `attachTypeList`,请求体为 Page +### 返回参数:PageResult +POST {{baseUrl}}/attach/page/byBiz?bizType={{attachmentBizType}}&bizId={{attachmentBizId}}&attachTypeList={{attachmentAttachType}} +Content-Type: application/json +Token: {{user_token}} + +{ + "index": 0, + "size": 10 +} + +### 按业务差分更新附件 +### 前置条件:新增项需要传有效 `tempFileId` +### 请求参数:BizUpdateReq +### 返回参数:无 +POST {{baseUrl}}/attach/update/byBiz +Content-Type: application/json +Token: {{user_token}} + +{ + "bizType": "{{attachmentBizType}}", + "bizId": "{{attachmentBizId}}", + "items": [ + { + "id": "{{attachmentId}}", + "attachType": "{{attachmentAttachType}}", + "name": "{{attachmentName}}-biz-updated" + }, + { + "tempFileId": "{{attachmentTempFileId}}", + "attachType": "{{attachmentAttachType}}", + "name": "{{attachmentName}}-from-temp" + } + ] +} + +### 按业务删除全部附件 +### 前置条件:`bizType` 和 `bizId` 必须传有效值 +### 请求参数:BizDeleteReq +### 返回参数:无 +POST {{baseUrl}}/attach/delete/byBiz +Content-Type: application/json +Token: {{user_token}} + +{ + "bizType": "{{attachmentBizType}}", + "bizId": "{{attachmentBizId}}" +} + +### 读取附件原图 +### 前置条件:`attachmentId` 必须传有效附件 ID +### 请求参数:路径参数 `id` +### 返回参数:附件二进制流 +GET {{baseUrl}}/attach/read/{{attachmentId}} +Token: {{user_token}} + +### 读取附件缩略图 +### 前置条件:`attachmentId` 必须传有效图片或视频附件 ID +### 请求参数:路径参数 `id`,URL 参数 `thumbWidth`、`thumbHeight` +### 返回参数:附件二进制流 +GET {{baseUrl}}/attach/read/{{attachmentId}}?thumbWidth=200&thumbHeight=200 +Token: {{user_token}} + +### 下载附件 +### 前置条件:`attachmentId` 必须传有效附件 ID +### 请求参数:路径参数 `id` +### 返回参数:附件二进制流 +GET {{baseUrl}}/attach/download/{{attachmentId}} +Token: {{user_token}} diff --git a/http/common/multilingual.http b/http/common/multilingual.http new file mode 100644 index 0000000..882de25 --- /dev/null +++ b/http/common/multilingual.http @@ -0,0 +1,66 @@ +### MultilingualController 接口测试 +### 在 IDEA 中选择 dev 或 prod 环境后执行 + +@multilingualId = +@multilingualZhCN = 测试简体中文 +@multilingualZhTW = 測試繁體中文 +@multilingualEnUS = Test English +@multilingualRuRU = Тест +@multilingualJaJP = テスト +@multilingualDeDE = Test Deutsch +@multilingualKoKR = 테스트 + +### 查询多语言详情 +### 权限:需要 ADMIN 角色 + MULTILINGUAL_READ 权限 +### 前置条件:需要先登录并回填 `user_token`,URL 参数 `id` 必须传有效多语言 ID +### 请求参数:无请求体,URL 参数:`id` 为多语言 ID +### 返回参数:Multilingual,详见 ./object/common.md +POST {{baseUrl}}/system/multilingual/detail?id={{multilingualId}} +Token: {{user_token}} + +### 创建多语言 +### 权限:需要 ADMIN 角色 + MULTILINGUAL_CREATE 权限 +### 前置条件:需要先登录并回填 `user_token` +### 请求参数:Multilingual,详见 ./object/common.md +### 返回参数:Multilingual,详见 ./object/common.md +POST {{baseUrl}}/system/multilingual/create +Content-Type: application/json +Token: {{user_token}} + +{ + "zhCN": "{{multilingualZhCN}}", + "zhTW": "{{multilingualZhTW}}", + "enUS": "{{multilingualEnUS}}", + "ruRU": "{{multilingualRuRU}}", + "jaJP": "{{multilingualJaJP}}", + "deDE": "{{multilingualDeDE}}", + "koKR": "{{multilingualKoKR}}" +} + +### 更新多语言 +### 权限:需要 ADMIN 角色 + MULTILINGUAL_UPDATE 权限 +### 前置条件:需要先登录并回填 `user_token`,`id` 必须存在 +### 请求参数:Multilingual,详见 ./object/common.md +### 返回参数:无 +POST {{baseUrl}}/system/multilingual/update +Content-Type: application/json +Token: {{user_token}} + +{ + "id": "{{multilingualId}}", + "zhCN": "{{multilingualZhCN}}-已更新", + "zhTW": "{{multilingualZhTW}}-已更新", + "enUS": "{{multilingualEnUS}}-updated", + "ruRU": "{{multilingualRuRU}}-updated", + "jaJP": "{{multilingualJaJP}}-updated", + "deDE": "{{multilingualDeDE}}-updated", + "koKR": "{{multilingualKoKR}}-updated" +} + +### 删除多语言 +### 权限:需要 ADMIN 角色 + MULTILINGUAL_DELETE 权限 +### 前置条件:需要先登录并回填 `user_token`,URL 参数 `id` 必须传有效多语言 ID +### 请求参数:无请求体,URL 参数:`id` 为多语言 ID +### 返回参数:无 +POST {{baseUrl}}/system/multilingual/delete?id={{multilingualId}} +Token: {{user_token}} diff --git a/http/gao/action.http b/http/gao/action.http new file mode 100644 index 0000000..3f5dd3e --- /dev/null +++ b/http/gao/action.http @@ -0,0 +1,60 @@ +### GaoRegisterAction 接口测试 +### 在 IDEA 中选择 dev 或 prod 环境后执行 + +@actionId = + +### 分页查询登记行为 +POST {{baseUrl}}/gao/action/list +Content-Type: application/json +Token: {{user_token}} + +{ + "index": 0, + "size": 16 +} + +### 查询启用行为 +POST {{baseUrl}}/gao/action/enabled/list +Token: {{user_token}} + +### 查询行为详情 +POST {{baseUrl}}/gao/action/detail?id={{actionId}} +Token: {{user_token}} + +### 创建登记行为 +POST {{baseUrl}}/gao/action/create +Content-Type: application/json +Token: {{user_token}} + +{ + "name": "签到", + "description": "客户到店签到", + "enabled": true, + "requirePhoto": false, + "sort": 10, + "permissionCodeList": [ + "RECORD:CREATE" + ] +} + +### 更新登记行为 +POST {{baseUrl}}/gao/action/update +Content-Type: application/json +Token: {{user_token}} + +{ + "id": "{{actionId}}", + "name": "物品领取", + "description": "登记客户领取物品", + "enabled": true, + "requirePhoto": true, + "sort": 20, + "permissionCodeList": [ + "RECORD:CREATE", + "STAT:READ" + ] +} + +### 删除登记行为 +POST {{baseUrl}}/gao/action/delete?id={{actionId}} +Token: {{user_token}} diff --git a/http/gao/customer.http b/http/gao/customer.http new file mode 100644 index 0000000..c5166cb --- /dev/null +++ b/http/gao/customer.http @@ -0,0 +1,119 @@ +### GaoCustomer 接口测试 +### 在 IDEA 中选择 dev 或 prod 环境后执行 + +@customerId = +@introducerCustomerId = +@customerCode = GAO-0001 + +### 分页查询客户 +POST {{baseUrl}}/gao/customer/list +Content-Type: application/json +Token: {{user_token}} + +{ + "index": 0, + "size": 16 +} + +### 搜索客户 +POST {{baseUrl}}/gao/customer/search +Content-Type: application/json +Token: {{user_token}} + +{ + "keyword": "张", + "introducerCustomerId": "{{introducerCustomerId}}" +} + +### 查询客户详情 +POST {{baseUrl}}/gao/customer/detail?id={{customerId}} +Token: {{user_token}} + +### 按客户编码查询 +POST {{baseUrl}}/gao/customer/detail/code?code={{customerCode}} +Token: {{user_token}} + +### 查询某客户邀请了哪些人 +POST {{baseUrl}}/gao/customer/invited/list?introducerCustomerId={{customerId}} +Token: {{user_token}} + +### 创建客户 +POST {{baseUrl}}/gao/customer/create +Content-Type: application/json +Token: {{user_token}} + +{ + "code": "{{customerCode}}", + "name": "张三", + "age": 56, + "telephone": "020-88886666", + "address": "广州市天河区某街道", + "remark": "首诊客户", + "disease": "肩颈不适", + "conditioningPart": [ + { + "code": "neck", + "name": "颈部" + }, + { + "code": "shoulder", + "name": "肩部" + } + ], + "introducerCustomerId": "{{introducerCustomerId}}" +} + +### 更新客户 +POST {{baseUrl}}/gao/customer/update +Content-Type: application/json +Token: {{user_token}} + +{ + "id": "{{customerId}}", + "code": "{{customerCode}}", + "name": "张三", + "age": 57, + "telephone": "020-88886667", + "address": "广州市天河区某街道 88 号", + "remark": "复诊客户", + "disease": "腰背调理", + "conditioningPart": { + "parts": [ + "waist", + "back" + ] + }, + "introducerCustomerId": "{{introducerCustomerId}}" +} + +### 删除客户 +POST {{baseUrl}}/gao/customer/delete?id={{customerId}} +Token: {{user_token}} + +### 创建客户邀请关系 +POST {{baseUrl}}/gao/customer/invite/create +Content-Type: application/json +Token: {{user_token}} + +{ + "customerId": "{{customerId}}", + "introducerCustomerId": "{{introducerCustomerId}}" +} + +### 查询客户邀请关系详情 +POST {{baseUrl}}/gao/customer/invite/detail?customerId={{customerId}} +Token: {{user_token}} + +### 更新客户邀请关系 +POST {{baseUrl}}/gao/customer/invite/update +Content-Type: application/json +Token: {{user_token}} + +{ + "customerId": "{{customerId}}", + "introducerCustomerId": "{{introducerCustomerId}}" +} + +### 删除客户邀请关系 +POST {{baseUrl}}/gao/customer/invite/delete?customerId={{customerId}} +Token: {{user_token}} diff --git a/http/gao/record.http b/http/gao/record.http new file mode 100644 index 0000000..005981b --- /dev/null +++ b/http/gao/record.http @@ -0,0 +1,86 @@ +### GaoRegisterRecord 接口测试 +### 在 IDEA 中选择 dev 或 prod 环境后执行 + +@recordId = +@customerId = +@customerCode = GAO-0001 +@actionId = + +### 分页查询登记记录 +POST {{baseUrl}}/gao/record/list +Content-Type: application/json +Token: {{user_token}} + +{ + "index": 0, + "size": 16 +} + +### 查询登记记录详情 +POST {{baseUrl}}/gao/record/detail?id={{recordId}} +Token: {{user_token}} + +### 创建登记记录,直接传客户 ID +POST {{baseUrl}}/gao/record/create +Content-Type: application/json +Token: {{user_token}} + +{ + "customerId": "{{customerId}}", + "actionId": "{{actionId}}", + "remark": "客户到店签到" +} + +### 创建登记记录,扫码后传客户编码 +POST {{baseUrl}}/gao/record/create +Content-Type: application/json +Token: {{user_token}} + +{ + "customerCode": "{{customerCode}}", + "actionId": "{{actionId}}", + "remark": "扫码快速登记" +} + +### 更新登记记录 +POST {{baseUrl}}/gao/record/update +Content-Type: application/json +Token: {{user_token}} + +{ + "id": "{{recordId}}", + "customerId": "{{customerId}}", + "actionId": "{{actionId}}", + "remark": "补充备注", + "registeredAt": 1785110400 +} + +### 删除登记记录 +POST {{baseUrl}}/gao/record/delete?id={{recordId}} +Token: {{user_token}} + +### 按日查询某个行为记录 +POST {{baseUrl}}/gao/record/period/list +Content-Type: application/json +Token: {{user_token}} + +{ + "actionId": "{{actionId}}", + "periodType": "DAY", + "periodValue": 20260727 +} + +### 按月统计某个行为记录 +POST {{baseUrl}}/gao/record/period/stat +Content-Type: application/json +Token: {{user_token}} + +{ + "actionId": "{{actionId}}", + "periodType": "MONTH", + "periodValue": 202607 +} + +### 查询客户所有登记统计 +POST {{baseUrl}}/gao/record/customer/stat?customerId={{customerId}} +Token: {{user_token}} diff --git a/http/http-client.env.json b/http/http-client.env.json new file mode 100644 index 0000000..22a7d47 --- /dev/null +++ b/http/http-client.env.json @@ -0,0 +1,8 @@ +{ + "dev": { + "baseUrl": "http://localhost:8091" + }, + "prod": { + "baseUrl": "https://api.example.com" + } +} diff --git a/http/object/common.md b/http/object/common.md new file mode 100644 index 0000000..76ce040 --- /dev/null +++ b/http/object/common.md @@ -0,0 +1,77 @@ +# 通用对象说明 + +## TimiResponse + +| 字段 | 类型 | 说明 | +|---|---|---| +| code | Integer | 响应码,`20000` 为成功 | +| msg | String | 响应消息 | +| data | T | 业务数据体,具体结构由接口返回对象决定 | + +## Page + +| 字段 | 类型 | 说明 | +|---|---|---| +| index | int | 页码下标,从 `0` 开始 | +| size | long | 每页数量 | +| equalsExample | T | 精确匹配示例对象,非空字段参与精确查询 | +| likesExample | T | 模糊匹配示例对象,非空字段参与模糊查询 | +| equalsLogic | Logic | 精确匹配字段之间的连接逻辑,默认 `AND` | +| likesLogic | Logic | 模糊匹配字段之间的连接逻辑,默认 `OR` | +| orderMap | LinkedHashMap | 排序字段映射,键通常为字段名,值为排序方向 | + +## PageResult + +| 字段 | 类型 | 说明 | +|---|---|---| +| total | long | 总数据量 | +| pages | int | 总页数 | +| list | List | 当前页数据列表 | + +## UUIDEntity + +| 字段 | 类型 | 说明 | +|---|---|---| +| id | String | 主键 ID,UUID 字符串 | +| createdAt | Long | 创建时间,Unix 时间戳毫秒 | +| updatedAt | Long | 更新时间,Unix 时间戳毫秒 | +| deletedAt | Long | 删除时间,Unix 时间戳毫秒,未删除时通常为空 | + +## Multilingual + +| 字段 | 类型 | 说明 | +|---|---|---| +| id | String | 多语言 ID | +| zhCN | String | 简体中文文本 | +| zhTW | String | 繁体中文文本 | +| enUS | String | 英文文本 | +| ruRU | String | 俄文文本 | +| jaJP | String | 日文文本 | +| deDE | String | 德文文本 | +| koKR | String | 韩文文本 | +| lastAccessAt | Long | 最后访问时间,Unix 时间戳毫秒 | +| createdAt | Long | 创建时间,Unix 时间戳毫秒 | +| updatedAt | Long | 更新时间,Unix 时间戳毫秒 | +| deletedAt | Long | 删除时间,Unix 时间戳毫秒,未删除时通常为空 | + +## Logic + +| 枚举 | 说明 | +|---|---| +| AND | 多个条件同时满足 | +| OR | 多个条件满足其一 | + +## OrderType + +| 枚举 | 说明 | +|---|---| +| ASC | 升序 | +| DESC | 降序 | + +## ImageType + +| 枚举 | 说明 | +|---|---| +| AUTO | 双线性 | +| SMOOTH | 模糊 | +| PIXELATED | 像素 | diff --git a/http/object/permission.md b/http/object/permission.md new file mode 100644 index 0000000..577f384 --- /dev/null +++ b/http/object/permission.md @@ -0,0 +1,31 @@ +# 权限对象说明 + +## Permission + +| 字段 | 类型 | 说明 | +|---|---|---| +| id | String | 权限 ID | +| createdAt | Long | 创建时间,Unix 时间戳毫秒 | +| updatedAt | Long | 更新时间,Unix 时间戳毫秒 | +| deletedAt | Long | 删除时间,Unix 时间戳毫秒 | +| code | String | 权限代码,需与数据库 `permission.code` 保持一致 | +| nameLangId | String | 权限名称的多语言键 | +| description | String | 权限说明 | +| name | String | 当前语言下的权限名称 | + +## BatchCreateReq + +| 字段 | 类型 | 说明 | +|---|---|---| +| prefixCode | String | 权限代码前缀,例如 `ROLE` | +| prefixName | String | 权限名称前缀,例如 `角色` | +| types | Type[] | 需要批量生成的权限类型列表,详见 `BatchCreateType` | + +## BatchCreateType + +| 枚举 | 说明 | +|---|---| +| CREATE | 创建权限 | +| READ | 读取权限 | +| UPDATE | 更新权限 | +| DELETE | 删除权限 | diff --git a/http/object/role.md b/http/object/role.md new file mode 100644 index 0000000..5304d45 --- /dev/null +++ b/http/object/role.md @@ -0,0 +1,34 @@ +# 角色对象说明 + +## Role + +| 字段 | 类型 | 说明 | +|---|---|---| +| id | String | 角色 ID | +| createdAt | Long | 创建时间,Unix 时间戳毫秒 | +| updatedAt | Long | 更新时间,Unix 时间戳毫秒 | +| deletedAt | Long | 删除时间,Unix 时间戳毫秒 | +| code | String | 角色代码,常见值见 `RoleCode` | +| nameLangId | String | 角色名称的多语言键 | +| description | String | 角色说明 | +| name | String | 当前语言下的角色名称 | +| permissionList | List | 角色自身直接绑定的权限列表 | +| allPermissionList | List | 角色全部权限列表,包含继承自子角色的权限 | +| childRoleList | List | 子角色列表 | +| permissionIdList | Set | 权限 ID 列表,创建或更新角色时使用 | +| childRoleIdList | Set | 子角色 ID 列表,创建或更新角色时使用 | + +## UserRoleAuthorizeReq + +| 字段 | 类型 | 说明 | +|---|---|---| +| userId | String | 目标用户 ID | +| roleIdList | List | 需要授权的角色 ID 列表 | +| roleCodeList | List | 需要授权的角色代码列表 | + +## RoleCode + +| 枚举 | 说明 | +|---|---| +| SYSTEM | 系统 | +| ADMIN | 管理员 | diff --git a/http/object/user.md b/http/object/user.md new file mode 100644 index 0000000..7b9c094 --- /dev/null +++ b/http/object/user.md @@ -0,0 +1,73 @@ +# 用户对象说明 + +## User + +| 字段 | 类型 | 说明 | +|---|---|---| +| id | String | 用户 ID | +| createdAt | Long | 创建时间,Unix 时间戳毫秒 | +| updatedAt | Long | 更新时间,Unix 时间戳毫秒 | +| deletedAt | Long | 删除时间,Unix 时间戳毫秒 | +| name | String | 登录名,系统内唯一 | +| nick | String | 用户昵称 | +| password | String | 明文密码,请求时填写,响应通常不返回 | +| email | String | 邮箱地址 | +| emailVerifyAt | Long | 邮箱验证时间,Unix 时间戳毫秒 | +| phoneNo | String | 手机号,11 位大陆手机号 | +| phoneNoVerifyAt | Long | 手机号验证时间,Unix 时间戳毫秒 | +| wrapperType | ImageType | 背景图渲染算法,详见 `ImageType` | +| avatarType | ImageType | 头像渲染算法,详见 `ImageType` | +| exp | Integer | 经验值 | +| sex | Integer | 性别,当前代码未定义枚举说明 | +| birthdate | Long | 出生日期,Unix 时间戳毫秒 | +| qq | String | QQ 号 | +| description | String | 个人说明 | +| lastLoginIP | String | 上次登录 IP | +| lastLoginAt | Long | 上次登录时间,Unix 时间戳毫秒 | +| unmuteAt | Long | 解除禁言时间,Unix 时间戳毫秒 | +| unbanAt | Long | 解除封禁时间,Unix 时间戳毫秒 | +| roleList | List | 角色代码列表 | +| permissionList | List | 权限代码列表 | +| attachmentList | List | 附件列表 | +| settingList | List | 设置列表 | + +## LoginRequest + +| 字段 | 类型 | 说明 | +|---|---|---| +| user | String | 用户标识,可能是 UID、邮箱或用户名 | +| password | String | 明文密码 | + +## LoginResponse + +| 字段 | 类型 | 说明 | +|---|---|---| +| token | String | 登录令牌 | +| expireAt | Long | 令牌过期时间,Unix 时间戳毫秒 | +| user | User | 当前登录用户 | + +## UpdatePasswordRequest + +| 字段 | 类型 | 说明 | +|---|---|---| +| oldValue | String | 原密码 | +| newValue | String | 新密码 | + +## PhoneVerifyCodeSendRequest + +| 字段 | 类型 | 说明 | +|---|---|---| +| phoneNo | String | 手机号,必须为 11 位大陆手机号 | + +## PhoneVerifyRequest + +| 字段 | 类型 | 说明 | +|---|---|---| +| phoneNo | String | 手机号,必须为 11 位大陆手机号 | +| verifyCode | String | 6 位数字验证码 | + +## CancelRequest + +| 字段 | 类型 | 说明 | +|---|---|---| +| password | String | 当前密码,用于确认注销 | diff --git a/http/user/permission.http b/http/user/permission.http new file mode 100644 index 0000000..59d7b24 --- /dev/null +++ b/http/user/permission.http @@ -0,0 +1,84 @@ +### PermissionController 接口测试 +### 在 IDEA 中选择 dev 或 prod 环境后执行 + +@user_token = +@permissionId = +@permissionCode = TEST_PERMISSION +@permissionNameLangId = permission.test.name +@permissionDescription = 测试权限 +@moduleCode = CORE + +### 分页查询权限 +### 权限:需要 PERMISSION_READ 权限 +### 前置条件:需要先登录并回填 `user_token` +### 请求参数:Page,详见 ./http/object/permission.md +### 返回参数:PageResult,详见 ./http/object/permission.md +POST {{baseUrl}}/user/permission/list +Content-Type: application/json +Token: {{user_token}} + +{ + "index": 0, + "size": 10, + "likesExample": { + "code": "{{permissionCode}}", + "moduleCode": "{{moduleCode}}" + } +} + +### 创建权限 +### 权限:当前代码要求 PERMISSION_READ 权限 +### 前置条件:需要先登录并回填 `user_token` +### 请求参数:Permission,详见 ./http/object/permission.md +### 返回参数:无 +POST {{baseUrl}}/user/permission/create +Content-Type: application/json +Token: {{user_token}} + +{ + "moduleCode": "{{moduleCode}}", + "code": "{{permissionCode}}", + "nameLangId": "{{permissionNameLangId}}", + "description": "{{permissionDescription}}" +} + +### 批量创建权限 +### 权限:需要 PERMISSION_CREATE 权限 +### 前置条件:需要先登录并回填 `user_token` +### 请求参数:BatchCreateReq,详见 ./http/object/permission.md +### 返回参数:无 +POST {{baseUrl}}/user/permission/create/batch +Content-Type: application/json +Token: {{user_token}} + +{ + "moduleCode": "{{moduleCode}}", + "prefixCode": "ROLE", + "prefixName": "角色", + "types": ["CREATE", "READ", "UPDATE", "DELETE"] +} + +### 更新权限 +### 权限:需要 PERMISSION_UPDATE 权限 +### 前置条件:需要先登录并回填 `user_token`,`id` 必须存在 +### 请求参数:Permission,详见 ./http/object/permission.md +### 返回参数:无 +POST {{baseUrl}}/user/permission/update +Content-Type: application/json +Token: {{user_token}} + +{ + "id": "{{permissionId}}", + "moduleCode": "{{moduleCode}}", + "code": "{{permissionCode}}", + "nameLangId": "{{permissionNameLangId}}", + "description": "测试权限-已更新" +} + +### 删除权限 +### 权限:需要 PERMISSION_DELETE 权限 +### 前置条件:需要先登录并回填 `user_token`,URL 参数 `id` 必须传有效权限 ID +### 请求参数:无请求体,URL 参数:`id` 为权限 ID +### 返回参数:无 +POST {{baseUrl}}/user/permission/delete?id={{permissionId}} +Token: {{user_token}} diff --git a/http/user/role.http b/http/user/role.http new file mode 100644 index 0000000..e48bb71 --- /dev/null +++ b/http/user/role.http @@ -0,0 +1,113 @@ +### RoleController 接口测试 +### 在 IDEA 中选择 dev 或 prod 环境后执行 + +@permissionId = +@roleId = +@roleCode = TEST_ROLE +@roleNameLangId = role.test.name +@roleDescription = 测试角色 +@childRoleId = +@authorizedUserId = +@moduleCode = CORE + +### 分页查询角色 +### 权限:需要 ADMIN 角色 + ROLE_READ 权限 +### 前置条件:需要先登录并回填 `user_token` +### 请求参数:Page,详见 ./http/object/role.md +### 返回参数:PageResult,详见 ./http/object/role.md +POST {{baseUrl}}/user/role/list +Content-Type: application/json +Token: {{user_token}} + +{ + "index": 0, + "size": 16, + "likesExample": { + "moduleCode": "{{moduleCode}}" + } +} + +### 查询角色详情 +### 权限:需要 ADMIN 角色 + ROLE_READ 权限 +### 前置条件:需要先登录并回填 `user_token`,URL 参数 `id` 必须传有效角色 ID +### 请求参数:无请求体,URL 参数:`id` 为角色 ID +### 返回参数:Role,详见 ./http/object/role.md +POST {{baseUrl}}/user/role/detail?id={{roleId}} +Token: {{user_token}} + +### 创建角色 +### 权限:需要 ADMIN 角色 + ROLE_CREATE 权限 +### 前置条件:需要先登录并回填 `user_token` +### 请求参数:Role,详见 ./http/object/role.md +### 返回参数:无 +POST {{baseUrl}}/user/role/create +Content-Type: application/json +Token: {{user_token}} + +{ + "moduleCode": "{{moduleCode}}", + "code": "{{roleCode}}", + "nameLangId": "{{roleNameLangId}}", + "description": "{{roleDescription}}", + "permissionIdList": ["{{permissionId}}"], + "childRoleIdList": [] +} + +### 更新角色 +### 权限:需要 ADMIN 角色 + ROLE_UPDATE 权限 +### 前置条件:需要先登录并回填 `user_token`,`id` 必须存在 +### 请求参数:Role,详见 ./http/object/role.md +### 返回参数:无 +POST {{baseUrl}}/user/role/update +Content-Type: application/json +Token: {{user_token}} + +{ + "id": "{{roleId}}", + "moduleCode": "{{moduleCode}}", + "code": "{{roleCode}}", + "nameLangId": "{{roleNameLangId}}", + "description": "测试角色-已更新", + "permissionIdList": ["{{permissionId}}"], + "childRoleIdList": ["{{childRoleId}}"] +} + +### 删除角色 +### 权限:需要 ADMIN 角色 + ROLE_DELETE 权限 +### 前置条件:需要先登录并回填 `user_token`,URL 参数 `id` 必须传有效角色 ID +### 请求参数:无请求体,URL 参数:`id` 为角色 ID +### 返回参数:无 +POST {{baseUrl}}/user/role/delete?id={{roleId}} +Token: {{user_token}} + +### 查询用户角色 +### 权限:需要 ADMIN 角色 + USER_ROLE_READ 权限 +### 前置条件:需要先登录并回填 `user_token`,URL 参数 `userId` 必须传有效用户 ID +### 请求参数:无请求体,URL 参数:`userId` 为用户 ID +### 返回参数:List,详见 ./http/object/role.md +POST {{baseUrl}}/user/role/authorized/list?userId={{authorizedUserId}} +Token: {{user_token}} + +### 设置用户角色 +### 权限:需要 ADMIN 角色 + 同时具备 USER_ROLE_CREATE 和 USER_ROLE_DELETE 权限 +### 前置条件:需要先登录并回填 `user_token` +### 请求参数:UserRoleAuthorizeReq,详见 ./http/object/role.md +### 返回参数:无 +POST {{baseUrl}}/user/role/authorized/create +Content-Type: application/json +Token: {{user_token}} + +{ + "userId": "{{authorizedUserId}}", + "moduleCode": "{{moduleCode}}", + "roleIdList": ["{{roleId}}"], + "roleCodeList": ["{{roleCode}}"] +} + +### 查询用户已授权权限 +### 权限:需要 ADMIN 角色 + USER_ROLE_READ 权限 +### 前置条件:需要先登录并回填 `user_token`,URL 参数 `userId` 必须传有效用户 ID +### 请求参数:无请求体,URL 参数:`userId` 为用户 ID +### 返回参数:List,详见 ./http/object/permission.md +POST {{baseUrl}}/user/role/authorized/permission?userId={{authorizedUserId}} +Token: {{user_token}} diff --git a/http/user/user.http b/http/user/user.http new file mode 100644 index 0000000..baeb74a --- /dev/null +++ b/http/user/user.http @@ -0,0 +1,186 @@ +### UserController 接口测试 +### 在 IDEA 中选择 dev 或 prod 环境后执行 + +@userId = +@name = System +@password = u#HA027xq$i9]P}uB]LweJspTb!3oS^. +@newPassword = qwe123 +@nick = 测试用户 +@email = test@example.com + +### 分页查询用户 +### 权限:需要 ADMIN 角色 + USER_READ 权限 +### 前置条件:需要先登录并回填 `user_token` +### 请求参数:Page,详见 ./http/object/user.md +### 返回参数:PageResult,详见 ./http/object/user.md +POST {{baseUrl}}/user/list +Content-Type: application/json +Token: {{user_token}} + +{ + "index": 0, + "size": 16 +} + +### 查询用户详情 +### 权限:需要 ADMIN 角色 + USER_READ 权限 +### 前置条件:需要先登录并回填 `user_token`,URL 参数 `id` 必须传有效用户 ID +### 请求参数:无请求体,URL 参数:`id` 为用户 ID +### 返回参数:User,详见 ./http/object/user.md +POST {{baseUrl}}/user/detail?id={{userId}} +Token: {{user_token}} + +### 创建用户 +### 权限:需要 ADMIN 角色 + USER_CREATE 权限 +### 前置条件:需要先登录并回填 `user_token`,重复用户名通常会失败 +### 请求参数:User,详见 ./http/object/user.md +### 返回参数:无 +POST {{baseUrl}}/user/create +Content-Type: application/json +Token: {{user_token}} + +{ + "name": "{{name}}", + "nick": "{{nick}}", + "email": "{{email}}", + "phoneNo": "13800138000", + "password": "{{password}}" +} + +### 更新用户 +### 权限:需要 ADMIN 角色 + USER_UPDATE 权限 +### 前置条件:需要先登录并回填 `user_token`,`id` 必须存在 +### 请求参数:User,详见 ./http/object/user.md +### 返回参数:无 +POST {{baseUrl}}/user/update +Content-Type: application/json +Token: {{user_token}} + +{ + "id": "{{userId}}", + "name": "{{name}}", + "nick": "{{nick}}", + "email": "{{email}}", + "phoneNo": "13800138000" +} + +### 删除用户 +### 权限:需要 ADMIN 角色 +### 前置条件:需要先登录并回填 `user_token`,URL 参数 `id` 必须传有效用户 ID +### 请求参数:无请求体,URL 参数:`id` 为用户 ID +### 返回参数:无 +POST {{baseUrl}}/user/delete?id={{userId}} +Token: {{user_token}} + +### 注册 +### 权限:无需登录 +### 前置条件:重复用户名通常会失败 +### 请求参数:User,详见 ./http/object/user.md +### 返回参数:LoginResponse,详见 ./http/object/user.md +POST {{baseUrl}}/user/register +Content-Type: application/json + +{ + "name": "Test", + "nick": "测试用户", + "phoneNo": "13800138000", + "password": "qwe123" +} + +### 登录 +### 权限:无需登录 +### 前置条件:成功后需要把响应中的 token 回填到 `user_token` +### 请求参数:LoginRequest,详见 ./http/object/user.md +### 返回参数:LoginResponse,详见 ./http/object/user.md +POST {{baseUrl}}/user/login +Content-Type: application/json + +{ + "user": "{{name}}", + "password": "{{password}}" +} + +> {% +client.global.set("user_token", response.body?.data?.token) +%} + +### 令牌登录 +### 权限:需要有效 Token +### 前置条件:需要先登录并回填 `user_token` +### 请求参数:无 +### 返回参数:LoginResponse,详见 ./http/object/user.md +GET {{baseUrl}}/user/login/token +Token: {{user_token}} + +### 登出 +### 权限:当前控制器未声明 RequiredToken/权限注解,按服务端实际会话处理 +### 前置条件:当前接口通常用于退出当前登录态 +### 请求参数:无 +### 返回参数:无 +POST {{baseUrl}}/user/logout +Token: {{user_token}} + +### 修改当前用户密码 +### 权限:需要 USER_UPDATE 权限 +### 前置条件:需要先登录并回填 `user_token` +### 请求参数:UpdatePasswordRequest,详见 ./http/object/user.md +### 返回参数:无 +POST {{baseUrl}}/user/update/password +Content-Type: application/json +Token: {{user_token}} + +{ + "oldValue": "{{password}}", + "newValue": "{{newPassword}}" +} + +### 修改当前用户邮箱 +### 权限:需要 USER_UPDATE 权限 +### 前置条件:当前方法体为空,接口尚未实现 +### 请求参数:无 +### 返回参数:无 +POST {{baseUrl}}/user/update/email +Content-Type: application/json +Token: {{user_token}} + +{} + +### 发送当前用户手机号验证码 +### 权限:需要 USER_UPDATE 权限 +### 前置条件:需要先登录并回填 `user_token` +### 请求参数:PhoneVerifyCodeSendRequest,详见 ./http/object/user.md +### 返回参数:无 +POST {{baseUrl}}/user/phone/verify/send +Content-Type: application/json +Token: {{user_token}} + +{ + "phoneNo": "13800138000" +} + +### 验证当前用户手机号 +### 权限:需要 USER_UPDATE 权限 +### 前置条件:需要先调用手机号验证码发送接口获取验证码 +### 请求参数:PhoneVerifyRequest,详见 ./http/object/user.md +### 返回参数:无 +POST {{baseUrl}}/user/phone/verify +Content-Type: application/json +Token: {{user_token}} + +{ + "phoneNo": "13800138000", + "verifyCode": "123456" +} + +### 注销当前用户 +### 权限:需要 USER_DELETE 权限 +### 前置条件:需要先登录并回填 `user_token` +### 请求参数:CancelRequest,详见 ./http/object/user.md +### 返回参数:无 +POST {{baseUrl}}/user/cancel +Content-Type: application/json +Token: {{user_token}} + +{ + "password": "{{password}}" +} diff --git a/pom.xml b/pom.xml index f743389..d751ab9 100644 --- a/pom.xml +++ b/pom.xml @@ -17,9 +17,10 @@ imyeyu.com API + 25 3.5.11 - 21 - 21 + 25 + 25 UTF-8 @@ -91,6 +92,47 @@ compile + + org.apache.maven.plugins + maven-compiler-plugin + 3.14.1 + + full + + + org.projectlombok + lombok + 1.18.46 + + + + + + org.projectlombok + lombok-maven-plugin + 1.18.20.0 + + ${project.basedir}/src/main/java + ${project.build.directory}/delombok + false + UTF-8 + + + + generate-sources + + delombok + + + + + + org.projectlombok + lombok + 1.18.46 + + + org.springframework.boot spring-boot-maven-plugin @@ -131,17 +173,27 @@ com.imyeyu.spring timi-spring - 0.0.10 + 0.0.16 com.imyeyu.network timi-network - 0.0.8 + 0.0.12 com.imyeyu.lang timi-lang - 0.0.2 + 0.0.4 + + + com.imyeyu.utils + timi-utils + 0.0.12 + + + com.imyeyu.font.icon + timi-icon-font + 0.0.1 org.springframework.boot @@ -160,6 +212,14 @@ mariadb-java-client 3.5.3 + + org.flywaydb + flyway-core + + + org.flywaydb + flyway-mysql + org.springframework.boot spring-boot-starter-freemarker @@ -187,7 +247,7 @@ org.projectlombok lombok - 1.18.34 + 1.18.46 provided diff --git a/src/main/java/com/imyeyu/api/annotation/CaptchaValid.java b/src/main/java/com/imyeyu/api/annotation/CaptchaValid.java deleted file mode 100644 index 2461324..0000000 --- a/src/main/java/com/imyeyu/api/annotation/CaptchaValid.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.imyeyu.api.annotation; - -import com.imyeyu.api.bean.CaptchaFrom; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * 图形验证码校验注解 - * - * @author 夜雨 - * @since 2023-07-15 10:09 - */ -@Target(ElementType.METHOD) -@Retention(RetentionPolicy.RUNTIME) -public @interface CaptchaValid { - - /** @return 验证码来源 */ - CaptchaFrom value(); -} diff --git a/src/main/java/com/imyeyu/api/annotation/CaptchaValidInterceptor.java b/src/main/java/com/imyeyu/api/annotation/CaptchaValidInterceptor.java index 9a52e11..e1279a0 100644 --- a/src/main/java/com/imyeyu/api/annotation/CaptchaValidInterceptor.java +++ b/src/main/java/com/imyeyu/api/annotation/CaptchaValidInterceptor.java @@ -1,20 +1,13 @@ package com.imyeyu.api.annotation; -import lombok.extern.slf4j.Slf4j; -import com.imyeyu.api.bean.CaptchaFrom; import com.imyeyu.api.util.CaptchaManager; -import com.imyeyu.spring.bean.CaptchaData; -import org.aspectj.lang.JoinPoint; +import com.imyeyu.spring.annotation.CaptchaValidAbstractInterceptor; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; 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.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; -import java.lang.reflect.Method; - /** * 图形验证码校验注解处理器 * @@ -24,47 +17,19 @@ import java.lang.reflect.Method; @Slf4j @Aspect @Component -public class CaptchaValidInterceptor { +@RequiredArgsConstructor +public class CaptchaValidInterceptor extends CaptchaValidAbstractInterceptor { @Value("${spring.profiles.active}") private String env; - @Autowired - private CaptchaManager captchaManager; + private final CaptchaManager captchaManager; - /** 注入注解 */ - @Pointcut("@annotation(com.imyeyu.api.annotation.CaptchaValid)") - public void captchaPointCut() { - } - - /** - * 执行前 - * - * @param joinPoint 切入点 - */ - @Before("captchaPointCut()") - public void doBefore(JoinPoint joinPoint) { - try { - if (env.startsWith("dev")) { - // 开发环境不校验 - return; - } - if (joinPoint.getSignature() instanceof MethodSignature ms) { - Method method = joinPoint.getTarget().getClass().getMethod(ms.getName(), ms.getParameterTypes()); - CaptchaValid annotation = method.getAnnotation(CaptchaValid.class); - CaptchaFrom from = annotation.value(); - - Object[] args = joinPoint.getArgs(); - for (int i = 0; i < args.length; i++) { - if (args[i] instanceof CaptchaData captchaData) { - // 校验请求参数的验证码 - captchaManager.test(captchaData.getCaptcha(), from.toString()); - break; - } - } - } - } catch (NoSuchMethodException e) { - throw new RuntimeException("TODO CaptchaValidInterceptor error"); + @Override + protected void verify(String captchaId, String captcha) { + if (!env.equals("prod")) { + return; } + captchaManager.verify(captchaId, captcha); } } diff --git a/src/main/java/com/imyeyu/api/annotation/EnableSetting.java b/src/main/java/com/imyeyu/api/annotation/EnableSetting.java index 9beebdb..f1d9da3 100644 --- a/src/main/java/com/imyeyu/api/annotation/EnableSetting.java +++ b/src/main/java/com/imyeyu/api/annotation/EnableSetting.java @@ -1,7 +1,5 @@ package com.imyeyu.api.annotation; -import com.imyeyu.api.modules.common.bean.SettingKey; - import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -17,8 +15,20 @@ import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) public @interface EnableSetting { - /** @return 配置键 */ - SettingKey value(); + /** + * + * + * @author 夜雨 + * @since 2026-04-27 14:45 + */ + enum Logic { + + AND, + + OR + } + + Logic logic() default Logic.AND; /** @return 未启用配置时响应消息语言映射键 */ String message() default "service.offline"; diff --git a/src/main/java/com/imyeyu/api/annotation/EnableSettingInterceptor.java b/src/main/java/com/imyeyu/api/annotation/EnableSettingInterceptor.java index 2cd390c..f2f2987 100644 --- a/src/main/java/com/imyeyu/api/annotation/EnableSettingInterceptor.java +++ b/src/main/java/com/imyeyu/api/annotation/EnableSettingInterceptor.java @@ -1,17 +1,20 @@ package com.imyeyu.api.annotation; +import com.imyeyu.api.modules.common.service.SettingService; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; -import com.imyeyu.java.bean.timi.TimiCode; -import com.imyeyu.java.bean.timi.TimiException; -import com.imyeyu.api.modules.common.service.SettingService; import org.springframework.context.annotation.Lazy; import org.springframework.lang.NonNull; import org.springframework.stereotype.Component; import org.springframework.web.method.HandlerMethod; import org.springframework.web.servlet.HandlerInterceptor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; + /** * 启用配置注解处理器 * @@ -24,16 +27,28 @@ public class EnableSettingInterceptor implements HandlerInterceptor { private final SettingService service; - public boolean preHandle(@NonNull HttpServletRequest req, @NonNull HttpServletResponse resp, @NonNull Object handler) { + public boolean preHandle(@NonNull HttpServletRequest req, @NonNull HttpServletResponse resp, @NonNull Object handler) throws InvocationTargetException, IllegalAccessException { if (handler instanceof HandlerMethod handlerMethod) { EnableSetting annotation = handlerMethod.getMethodAnnotation(EnableSetting.class); if (annotation == null) { return true; } - if (service.is(annotation.value())) { - return true; + EnableSetting.Logic logic = annotation.logic(); + List valueList = new ArrayList<>(); + for (Method method : annotation.getClass().getMethods()) { + if (method.getName().equals("message")) { + continue; + } + Enum[] enums = (Enum[]) method.invoke(annotation); + for (Enum anEnum : enums) { + valueList.add(service.getSystem(anEnum).isTrue()); + } + } + if (logic == EnableSetting.Logic.AND) { + return valueList.stream().allMatch(Boolean::booleanValue); + } else { + return valueList.stream().anyMatch(Boolean::booleanValue); } - throw new TimiException(TimiCode.ERROR_SERVICE_OFF, annotation.message()); } return true; } diff --git a/src/main/java/com/imyeyu/api/annotation/MultilingualField.java b/src/main/java/com/imyeyu/api/annotation/MultilingualField.java new file mode 100644 index 0000000..de98ff1 --- /dev/null +++ b/src/main/java/com/imyeyu/api/annotation/MultilingualField.java @@ -0,0 +1,31 @@ +package com.imyeyu.api.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 标记需要在响应阶段自动回填的多语言字段 + * + * @author 夜雨 + * @since 2026-07-17 00:00 + */ +@Target({ElementType.FIELD}) +@Retention(RetentionPolicy.RUNTIME) +public @interface MultilingualField { + + /** + * 多语言 ID 来源字段名 + * + * @return 来源字段名 + */ + String value(); + + /** + * 模板参数来源字段名 + * + * @return 同类中的 Map 字段名 + */ + String argMap() default ""; +} diff --git a/src/main/java/com/imyeyu/api/annotation/RequestRateLimitInterceptor.java b/src/main/java/com/imyeyu/api/annotation/RequestRateLimitInterceptor.java index 09f79fe..ebe951d 100644 --- a/src/main/java/com/imyeyu/api/annotation/RequestRateLimitInterceptor.java +++ b/src/main/java/com/imyeyu/api/annotation/RequestRateLimitInterceptor.java @@ -23,19 +23,21 @@ import org.springframework.stereotype.Component; @Component public class RequestRateLimitInterceptor extends RequestRateLimitAbstractInterceptor { + private static final String KEY_PREFIX = "REQUEST:RATE_LIMIT:"; + @Autowired @Qualifier("redisRateLimit") - private Redis redisRequestRateLimit; + private Redis redis; @Override public boolean beforeRun(HttpServletRequest req, HttpServletResponse resp, String id, int cycle, int limit) { // 键 - String key = "TimiServerAPI." + TimiSpring.getRequestIP() + "." + id; - if (redisRequestRateLimit.has(key)) { - Integer count = redisRequestRateLimit.get(key); + String key = getKey(id); + if (redis.has(key)) { + Integer count = redis.get(key); if (count != null) { if (count < limit) { - redisRequestRateLimit.setAndKeepTTL(key, ++count); + redis.setAndKeepTTL(key, ++count); } else { log.warn("请求频率过高:[" + key + "].C" + count + "L" + limit); throw new TimiException(TimiCode.REQUEST_BAD).msgKey("request_rate_limit"); @@ -43,7 +45,11 @@ public class RequestRateLimitInterceptor extends RequestRateLimitAbstractInterce } return true; } - redisRequestRateLimit.set(key, 0, Time.S * cycle); + redis.set(key, 0, Time.S * cycle); return true; } + + private String getKey(String id) { + return "%sTIMISERVERAPI:%s:%s".formatted(KEY_PREFIX, TimiSpring.getRequestIP(), id); + } } diff --git a/src/main/java/com/imyeyu/api/annotation/RequireCorePermission.java b/src/main/java/com/imyeyu/api/annotation/RequireCorePermission.java new file mode 100644 index 0000000..b734642 --- /dev/null +++ b/src/main/java/com/imyeyu/api/annotation/RequireCorePermission.java @@ -0,0 +1,29 @@ +package com.imyeyu.api.annotation; + +import com.imyeyu.api.bean.PermissionMatchMode; +import com.imyeyu.api.bean.CorePermissionCode; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/// +/// 要求 CORE 模块权限 +/// +/// @author Codex +/// @since 2026-07-26 +@Target({ElementType.METHOD, ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +public @interface RequireCorePermission { + + /// 所需权限 + /// + /// @return 权限列表 + CorePermissionCode[] value(); + + /// 匹配模式 + /// + /// @return 匹配模式 + PermissionMatchMode mode() default PermissionMatchMode.ALL; +} diff --git a/src/main/java/com/imyeyu/api/annotation/RequireCorePermissionInterceptor.java b/src/main/java/com/imyeyu/api/annotation/RequireCorePermissionInterceptor.java new file mode 100644 index 0000000..3f6ecbd --- /dev/null +++ b/src/main/java/com/imyeyu/api/annotation/RequireCorePermissionInterceptor.java @@ -0,0 +1,46 @@ +package com.imyeyu.api.annotation; + +import com.imyeyu.api.bean.PermissionMatchMode; +import com.imyeyu.api.bean.CorePermissionCode; +import com.imyeyu.api.bean.ModuleCode; +import com.imyeyu.api.modules.user.service.PermissionChecker; +import com.imyeyu.java.TimiJava; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.lang.NonNull; +import org.springframework.stereotype.Component; +import org.springframework.web.method.HandlerMethod; +import org.springframework.web.servlet.HandlerInterceptor; + +/// +/// CORE 权限注解处理器 +/// +/// @author Codex +/// @since 2026-07-27 +@Component +public class RequireCorePermissionInterceptor implements HandlerInterceptor { + + private final PermissionChecker permissionChecker; + + public RequireCorePermissionInterceptor(PermissionChecker permissionChecker) { + this.permissionChecker = permissionChecker; + } + + @Override + public boolean preHandle(@NonNull HttpServletRequest req, @NonNull HttpServletResponse resp, @NonNull Object handler) { + if (!(handler instanceof HandlerMethod handlerMethod)) { + return true; + } + RequireCorePermission annotation = handlerMethod.getMethodAnnotation(RequireCorePermission.class); + if (annotation == null || TimiJava.isEmpty(annotation.value())) { + return true; + } + String[] permissionCodeList = java.util.Arrays.stream(annotation.value()).map(CorePermissionCode::getValue).toArray(String[]::new); + if (annotation.mode() == PermissionMatchMode.ANY) { + permissionChecker.checkAny(ModuleCode.CORE, permissionCodeList); + } else { + permissionChecker.checkAll(ModuleCode.CORE, permissionCodeList); + } + return true; + } +} diff --git a/src/main/java/com/imyeyu/api/annotation/RequireCoreRole.java b/src/main/java/com/imyeyu/api/annotation/RequireCoreRole.java new file mode 100644 index 0000000..6e3da68 --- /dev/null +++ b/src/main/java/com/imyeyu/api/annotation/RequireCoreRole.java @@ -0,0 +1,29 @@ +package com.imyeyu.api.annotation; + +import com.imyeyu.api.bean.RoleMatchMode; +import com.imyeyu.api.bean.CoreRoleCode; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/// +/// 要求 CORE 模块角色 +/// +/// @author Codex +/// @since 2026-07-26 +@Target({ElementType.METHOD, ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +public @interface RequireCoreRole { + + /// 所需角色 + /// + /// @return 角色列表 + CoreRoleCode[] value(); + + /// 匹配模式 + /// + /// @return 匹配模式 + RoleMatchMode mode() default RoleMatchMode.ALL; +} diff --git a/src/main/java/com/imyeyu/api/annotation/RequireCoreRoleInterceptor.java b/src/main/java/com/imyeyu/api/annotation/RequireCoreRoleInterceptor.java new file mode 100644 index 0000000..3342560 --- /dev/null +++ b/src/main/java/com/imyeyu/api/annotation/RequireCoreRoleInterceptor.java @@ -0,0 +1,46 @@ +package com.imyeyu.api.annotation; + +import com.imyeyu.api.bean.ModuleCode; +import com.imyeyu.api.bean.RoleMatchMode; +import com.imyeyu.api.bean.CoreRoleCode; +import com.imyeyu.api.modules.user.service.RoleChecker; +import com.imyeyu.java.TimiJava; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.lang.NonNull; +import org.springframework.stereotype.Component; +import org.springframework.web.method.HandlerMethod; +import org.springframework.web.servlet.HandlerInterceptor; + +/// +/// CORE 角色注解处理器 +/// +/// @author Codex +/// @since 2026-07-27 +@Component +public class RequireCoreRoleInterceptor implements HandlerInterceptor { + + private final RoleChecker roleChecker; + + public RequireCoreRoleInterceptor(RoleChecker roleChecker) { + this.roleChecker = roleChecker; + } + + @Override + public boolean preHandle(@NonNull HttpServletRequest req, @NonNull HttpServletResponse resp, @NonNull Object handler) { + if (!(handler instanceof HandlerMethod handlerMethod)) { + return true; + } + RequireCoreRole annotation = handlerMethod.getMethodAnnotation(RequireCoreRole.class); + if (annotation == null || TimiJava.isEmpty(annotation.value())) { + return true; + } + String[] roleCodeList = java.util.Arrays.stream(annotation.value()).map(CoreRoleCode::name).toArray(String[]::new); + if (annotation.mode() == RoleMatchMode.ANY) { + roleChecker.checkAny(ModuleCode.CORE, roleCodeList); + } else { + roleChecker.checkAll(ModuleCode.CORE, roleCodeList); + } + return true; + } +} diff --git a/src/main/java/com/imyeyu/api/annotation/RequiredTokenInterceptor.java b/src/main/java/com/imyeyu/api/annotation/RequiredTokenInterceptor.java deleted file mode 100644 index 17fd3d4..0000000 --- a/src/main/java/com/imyeyu/api/annotation/RequiredTokenInterceptor.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.imyeyu.api.annotation; - -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import lombok.extern.slf4j.Slf4j; -import com.imyeyu.java.bean.timi.TimiCode; -import com.imyeyu.java.bean.timi.TimiException; -import com.imyeyu.api.modules.blog.util.UserToken; -import com.imyeyu.spring.TimiSpring; -import com.imyeyu.spring.annotation.RequiredToken; -import com.imyeyu.spring.annotation.RequiredTokenAbstractInterceptor; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; - -/** - * 令牌验证注解处理器 - * - * @author 夜雨 - * @since 2021-08-16 18:07 - */ -@Slf4j -@Component -public class RequiredTokenInterceptor extends RequiredTokenAbstractInterceptor { - - @Autowired - private UserToken userToken; - - public RequiredTokenInterceptor() { - super(RequiredToken.class); - } - - @Override - protected boolean beforeRun(HttpServletRequest req, HttpServletResponse resp) { - if (userToken.isInvalid(TimiSpring.getToken())) { - throw new TimiException(TimiCode.ARG_MISS).msgKey("token.illegal"); - } - return true; - } -} diff --git a/src/main/java/com/imyeyu/api/bean/CaptchaFrom.java b/src/main/java/com/imyeyu/api/bean/CaptchaFrom.java deleted file mode 100644 index 907a3e1..0000000 --- a/src/main/java/com/imyeyu/api/bean/CaptchaFrom.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.imyeyu.api.bean; - -/** - * 验证码来源 - * - * @author 夜雨 - * @since 2023-07-16 10:12 - */ -public enum CaptchaFrom { - - /** 注册 */ - REGISTER, - - /** 登录 */ - LOGIN, - - /** 忘记密码 */ - RESET_PASSWORD, - - /** 评论 */ - COMMENT, - - /** 评论回复 */ - COMMENT_REPLY, - - /** Git 反馈 */ - GIT_ISSUE, - - /** Git 合并请求 */ - GIT_MERGE, - - /** 歌词修正申请 */ - LYRIC_CORRECT -} diff --git a/src/main/java/com/imyeyu/api/bean/CorePermissionCode.java b/src/main/java/com/imyeyu/api/bean/CorePermissionCode.java new file mode 100644 index 0000000..2de1037 --- /dev/null +++ b/src/main/java/com/imyeyu/api/bean/CorePermissionCode.java @@ -0,0 +1,80 @@ +package com.imyeyu.api.bean; + +import com.imyeyu.api.modules.user.bean.BuiltinPermissionCode; +import lombok.AllArgsConstructor; + +/// +/// CORE 模块权限枚举 +/// +/// @author Codex +/// @since 2026-07-26 +@AllArgsConstructor +public enum CorePermissionCode implements BuiltinPermissionCode { + + PERMISSION_CREATE("PERMISSION:CREATE", "权限创建", false, true), + PERMISSION_READ("PERMISSION:READ", "权限读取", false, true), + PERMISSION_UPDATE("PERMISSION:UPDATE", "权限修改", false, true), + PERMISSION_DELETE("PERMISSION:DELETE", "权限删除", false, true), + + ROLE_CREATE("ROLE:CREATE", "角色创建", false, true), + ROLE_READ("ROLE:READ", "角色读取", false, true), + ROLE_UPDATE("ROLE:UPDATE", "角色修改", false, true), + ROLE_DELETE("ROLE:DELETE", "角色删除", false, true), + + ROLE_PERMISSION_CREATE("ROLE_PERMISSION:CREATE", "角色权限创建", false, true), + ROLE_PERMISSION_READ("ROLE_PERMISSION:READ", "角色权限读取", false, true), + ROLE_PERMISSION_DELETE("ROLE_PERMISSION:DELETE", "角色权限删除", false, true), + + ROLE_RELATION_CREATE("ROLE_RELATION:CREATE", "角色关联创建", false, true), + ROLE_RELATION_READ("ROLE_RELATION:READ", "角色关联读取", false, true), + ROLE_RELATION_DELETE("ROLE_RELATION:DELETE", "角色关联删除", false, true), + + SETTING_CREATE("SETTING:CREATE", "设置创建", false, true), + SETTING_READ("SETTING:READ", "设置读取", false, true), + SETTING_UPDATE("SETTING:UPDATE", "设置修改", false, true), + SETTING_DELETE("SETTING:DELETE", "设置删除", false, true), + + MULTILINGUAL_CREATE("MULTILINGUAL:CREATE", "多语言创建", false, true), + MULTILINGUAL_READ("MULTILINGUAL:READ", "多语言读取", false, true), + MULTILINGUAL_UPDATE("MULTILINGUAL:UPDATE", "多语言修改", false, true), + MULTILINGUAL_DELETE("MULTILINGUAL:DELETE", "多语言删除", false, true), + + USER_CREATE("USER:CREATE", "用户创建", false, true), + USER_READ("USER:READ", "用户读取", false, true), + USER_UPDATE("USER:UPDATE", "用户修改", false, true), + USER_DELETE("USER:DELETE", "用户删除", false, true), + + USER_ROLE_CREATE("USER_ROLE:CREATE", "用户角色创建", true, true), + USER_ROLE_READ("USER_ROLE:READ", "用户角色读取", true, true), + USER_ROLE_DELETE("USER_ROLE:DELETE", "用户角色删除", true, true); + + final String value; + final String defaultName; + final boolean grantToSystem; + final boolean grantToAdmin; + + @Override + public ModuleCode getModuleCode() { + return ModuleCode.CORE; + } + + @Override + public String getValue() { + return value; + } + + @Override + public String getDefaultName() { + return defaultName; + } + + @Override + public boolean isGrantToSystem() { + return grantToSystem; + } + + @Override + public boolean isGrantToAdmin() { + return grantToAdmin; + } +} diff --git a/src/main/java/com/imyeyu/api/bean/CoreRoleCode.java b/src/main/java/com/imyeyu/api/bean/CoreRoleCode.java new file mode 100644 index 0000000..3416d16 --- /dev/null +++ b/src/main/java/com/imyeyu/api/bean/CoreRoleCode.java @@ -0,0 +1,41 @@ +package com.imyeyu.api.bean; + +import com.imyeyu.api.modules.user.bean.BuiltinRoleCode; +import lombok.AllArgsConstructor; + +/// +/// CORE 模块角色枚举 +/// +/// @author Codex +/// @since 2026-07-26 +@AllArgsConstructor +public enum CoreRoleCode implements BuiltinRoleCode { + + SYSTEM("系统", true, null), + + ADMIN("管理员", false, SYSTEM.name()); + + final String defaultName; + final boolean protectedRole; + final String parentCode; + + @Override + public ModuleCode getModuleCode() { + return ModuleCode.CORE; + } + + @Override + public String getDefaultName() { + return defaultName; + } + + @Override + public boolean isProtected() { + return protectedRole; + } + + @Override + public String getParentCode() { + return parentCode; + } +} diff --git a/src/main/java/com/imyeyu/api/bean/ModuleCode.java b/src/main/java/com/imyeyu/api/bean/ModuleCode.java new file mode 100644 index 0000000..1bd278a --- /dev/null +++ b/src/main/java/com/imyeyu/api/bean/ModuleCode.java @@ -0,0 +1,13 @@ +package com.imyeyu.api.bean; + +/// +/// 模块代码 +/// +/// @author Codex +/// @since 2026-07-26 +public enum ModuleCode { + + CORE, + + GAO, +} diff --git a/src/main/java/com/imyeyu/api/bean/MultilingualHandler.java b/src/main/java/com/imyeyu/api/bean/MultilingualHandler.java deleted file mode 100644 index e5a067e..0000000 --- a/src/main/java/com/imyeyu/api/bean/MultilingualHandler.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.imyeyu.api.bean; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * @author 夜雨 - * @since 2023-10-25 10:10 - */ -public interface MultilingualHandler { - - /** - * - * - * @author 夜雨 - * @since 2023-10-25 10:25 - */ - @Target({ElementType.FIELD}) - @Retention(RetentionPolicy.RUNTIME) - @interface MultilingualField { - - String[] args() default {}; - } -} diff --git a/src/main/java/com/imyeyu/api/bean/PermissionMatchMode.java b/src/main/java/com/imyeyu/api/bean/PermissionMatchMode.java new file mode 100644 index 0000000..d1f5854 --- /dev/null +++ b/src/main/java/com/imyeyu/api/bean/PermissionMatchMode.java @@ -0,0 +1,16 @@ +package com.imyeyu.api.bean; + +/** + * 权限匹配模式 + * + * @author 夜雨 + * @since 2026-06-05 17:45 + */ +public enum PermissionMatchMode { + + /** 全部满足 */ + ALL, + + /** 满足任意一个 */ + ANY, +} diff --git a/src/main/java/com/imyeyu/api/bean/RoleMatchMode.java b/src/main/java/com/imyeyu/api/bean/RoleMatchMode.java new file mode 100644 index 0000000..97a8d02 --- /dev/null +++ b/src/main/java/com/imyeyu/api/bean/RoleMatchMode.java @@ -0,0 +1,16 @@ +package com.imyeyu.api.bean; + +/** + * 角色匹配模式 + * + * @author 夜雨 + * @since 2026-06-05 17:45 + */ +public enum RoleMatchMode { + + /** 全部满足 */ + ALL, + + /** 满足任意一个 */ + ANY, +} diff --git a/src/main/java/com/imyeyu/api/config/BeanConfig.java b/src/main/java/com/imyeyu/api/config/BeanConfig.java index b7f6a96..ad7f099 100644 --- a/src/main/java/com/imyeyu/api/config/BeanConfig.java +++ b/src/main/java/com/imyeyu/api/config/BeanConfig.java @@ -2,7 +2,11 @@ package com.imyeyu.api.config; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.MapperFeature; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import com.fasterxml.jackson.databind.PropertyNamingStrategy; +import com.fasterxml.jackson.databind.json.JsonMapper; import com.imyeyu.utils.Time; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -17,11 +21,12 @@ public class BeanConfig { @Bean public ObjectMapper jackson() { - ObjectMapper mapper = new ObjectMapper(); - mapper.setDateFormat(Time.dateTime); - mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); - mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); - return mapper; + return JsonMapper.builder() + .serializationInclusion(JsonInclude.Include.NON_NULL) + .defaultDateFormat(Time.dateTime) + .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) + .enable(MapperFeature.USE_STD_BEAN_NAMING) + .build(); } @Bean diff --git a/src/main/java/com/imyeyu/api/config/RedisConfig.java b/src/main/java/com/imyeyu/api/config/RedisConfig.java index a6c67dc..2ae799f 100644 --- a/src/main/java/com/imyeyu/api/config/RedisConfig.java +++ b/src/main/java/com/imyeyu/api/config/RedisConfig.java @@ -1,7 +1,6 @@ package com.imyeyu.api.config; import com.fasterxml.jackson.databind.ObjectMapper; -import com.imyeyu.api.modules.blog.entity.ArticleRanking; import com.imyeyu.api.modules.common.entity.Multilingual; import com.imyeyu.spring.bean.RedisConfigParams; import com.imyeyu.spring.config.AbstractRedisConfig; @@ -11,8 +10,8 @@ import com.imyeyu.utils.Time; import io.lettuce.core.api.StatefulConnection; import lombok.Data; import lombok.EqualsAndHashCode; -import lombok.RequiredArgsConstructor; import org.apache.commons.pool2.impl.GenericObjectPoolConfig; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.cache.interceptor.KeyGenerator; @@ -36,11 +35,11 @@ import java.time.Duration; @Configuration @EqualsAndHashCode(callSuper = true) @EnableAutoConfiguration -@RequiredArgsConstructor @ConfigurationProperties(prefix = "spring.redis") public class RedisConfig extends AbstractRedisConfig { - private final ObjectMapper jackson; + @Autowired + private ObjectMapper jackson; // ---------- 连接配置 ---------- @@ -53,57 +52,8 @@ public class RedisConfig extends AbstractRedisConfig { /** 密码 */ private String password; - /** 数据库 */ - private Database database; - - /** - * 数据库 - * - * @author 夜雨 - * @since 2023-08-21 16:25 - */ - @Data - public static class Database { - - /** 分布式锁 */ - private int locker; - - /** 多语言环境 */ - private int language; - - /** 多语言键环境 */ - private int languageMap; - - /** 文章排位 */ - private int articleRanking; - - /** 文章阅读记录 */ - private int articleRead; - - /** 用户登录令牌 */ - private int userToken; - - /** 用户经验值标记 */ - private int userExpFlag; - - /** 用户邮箱验证 */ - private int userEmailVerify; - - /** 用户重置密码验证 */ - private int userResetPWVerify; - - /** 访问频率控制 */ - private int rateLimit; - - /** 系统配置 */ - private int setting; - - /** Minecraft 登录 */ - private int fmcPlayerToken; - - /** 共享剪切板 */ - private int clipboard; - } + /** Redis 逻辑库 */ + private int database; @Override protected RedisConfigParams configParams() { @@ -149,13 +99,13 @@ public class RedisConfig extends AbstractRedisConfig { /** @return 分布式锁, ID: 尝试加锁次数 */ @Bean("redisLocker") public Redis getLockerRedisTemplate() { - return getRedis(database.locker, RedisSerializers.STRING, RedisSerializers.INTEGER); + return getRedis(database, RedisSerializers.STRING, RedisSerializers.INTEGER); } /** @return 多语言环境,ID: {@link Multilingual} */ @Bean("redisLanguage") - public Redis getLanguageRedisTemplate() { - return getRedis(database.language, RedisSerializers.LONG, new RedisSerializer<>() { + public Redis getLanguageRedisTemplate() { + return getRedis(database, RedisSerializers.STRING, new RedisSerializer<>() { public Multilingual deserialize(@Nullable byte[] bytes) { if (bytes == null || bytes.length == 0) { @@ -182,63 +132,74 @@ public class RedisConfig extends AbstractRedisConfig { }); } + @Bean("redisCaptcha") + public Redis getCaptchaRedisTemplate() { + return getRedis(database, RedisSerializers.STRING, RedisSerializers.STRING); + } + /** @return 文章访问记录,IP: [文章 ID] */ @Bean("redisLanguageMap") public Redis getLanguageMapRedisTemplate() { - return getRedis(database.languageMap, RedisSerializers.STRING, RedisSerializers.LONG); - } - - /** @return 文章访问统计,文章 ID: {@link ArticleRanking}(JSON) */ - @Bean("redisArticleRanking") - public Redis getArticleRankingRedisTemplate() { - return getRedis(database.articleRanking, RedisSerializers.LONG, RedisSerializers.jacksonSerializer(jackson, ArticleRanking.class)); + return getRedis(database, RedisSerializers.STRING, RedisSerializers.LONG); } /** @return 文章访问记录,IP: [文章 ID] */ @Bean("redisArticleRead") public Redis getArticleReadRedisTemplate() { - return getRedis(database.articleRead, RedisSerializers.STRING, RedisSerializers.LONG); + return getRedis(database, RedisSerializers.STRING, RedisSerializers.LONG); } /** @return 用户登录经验标记,UID: NULL,暂时没有值,数据死亡时间为次日零时 */ @Bean("redisUserExpFlag") public Redis getUserExpFlagRedisTemplate() { - return getRedis(database.userExpFlag, RedisSerializers.LONG, RedisSerializers.STRING); + return getRedis(database, RedisSerializers.LONG, RedisSerializers.STRING); + } + + /** @return 用户登录令牌,令牌: 用户 ID */ + @Bean("redisUserToken") + public Redis getUserTokenRedisTemplate() { + return getRedis(database, RedisSerializers.STRING, RedisSerializers.STRING); } /** @return 用户邮箱验证密钥,密钥: UID */ @Bean("redisUserEmailVerify") public Redis getUserEmailVerifyRedisTemplate() { - return getRedis(database.userEmailVerify, RedisSerializers.STRING, RedisSerializers.LONG); + return getRedis(database, RedisSerializers.STRING, RedisSerializers.LONG); } /** @return 用户重置密码密钥,密钥: UID */ @Bean("redisUserResetPWVerify") public Redis getUserResetPWVerifyRedisTemplate() { - return getRedis(database.userResetPWVerify, RedisSerializers.STRING, RedisSerializers.LONG); + return getRedis(database, RedisSerializers.STRING, RedisSerializers.LONG); + } + + /** @return 用户手机号验证码,手机号: 验证码 */ + @Bean("redisUserPhoneVerify") + public Redis getUserPhoneVerifyRedisTemplate() { + return getRedis(database, RedisSerializers.STRING, RedisSerializers.STRING); } /** @return 接口访问控制,IP#方法: 生命周期内访问次数 */ @Bean("redisRateLimit") public Redis getRateLimitRedisTemplate() { - return getRedis(database.rateLimit, RedisSerializers.STRING, RedisSerializers.INTEGER); + return getRedis(database, RedisSerializers.STRING, RedisSerializers.INTEGER); } /** @return 系统配置,Key 枚举: String 配置值 */ @Bean("redisSetting") public Redis getSettingRedisTemplate() { - return getRedis(database.setting, RedisSerializers.STRING, RedisSerializers.STRING); + return getRedis(database, RedisSerializers.STRING, RedisSerializers.STRING); } /** @return Minecraft 登录,令牌: 玩家 ID */ @Bean("redisMCPlayerToken") public Redis getMCPlayerLoginRedisTemplate() { - return getRedis(database.fmcPlayerToken, RedisSerializers.STRING, RedisSerializers.LONG); + return getRedis(database, RedisSerializers.STRING, RedisSerializers.LONG); } /** @return 共享剪切板,会话 ID: 内容 */ @Bean("redisClipboard") public Redis getClipboardRedisTemplate() { - return getRedis(database.clipboard, RedisSerializers.STRING, RedisSerializers.STRING); + return getRedis(database, RedisSerializers.STRING, RedisSerializers.STRING); } } diff --git a/src/main/java/com/imyeyu/api/config/WebConfig.java b/src/main/java/com/imyeyu/api/config/WebConfig.java index 9b35628..6357124 100644 --- a/src/main/java/com/imyeyu/api/config/WebConfig.java +++ b/src/main/java/com/imyeyu/api/config/WebConfig.java @@ -7,9 +7,11 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.json.JsonMapper; import com.imyeyu.api.annotation.EnableSettingInterceptor; import com.imyeyu.api.annotation.RequestRateLimitInterceptor; -import com.imyeyu.api.annotation.RequiredTokenInterceptor; +import com.imyeyu.api.annotation.RequireCorePermissionInterceptor; +import com.imyeyu.api.annotation.RequireCoreRoleInterceptor; +import com.imyeyu.api.modules.gao.annotation.RequireGaoPermissionInterceptor; +import com.imyeyu.api.modules.gao.annotation.RequireGaoRoleInterceptor; import com.imyeyu.api.modules.journal.util.JournalAPIInterceptor; -import com.imyeyu.api.modules.minecraft.annotation.RequiredFMCServerTokenInterceptor; import com.imyeyu.api.modules.system.util.SystemAPIInterceptor; import com.imyeyu.spring.annotation.RequestBodyValueArgumentResolver; import com.imyeyu.utils.Time; @@ -39,10 +41,12 @@ public class WebConfig implements WebMvcConfigurer { private final ObjectMapper jackson; private final SystemAPIInterceptor systemAPIInterceptor; private final JournalAPIInterceptor journalAPIInterceptor; - private final RequiredTokenInterceptor requiredTokenInterceptor; private final EnableSettingInterceptor enableSettingInterceptor; + private final RequireGaoRoleInterceptor requireGaoRoleInterceptor; + private final RequireCoreRoleInterceptor requireCoreRoleInterceptor; private final RequestRateLimitInterceptor requestRateLimitInterceptor; - private final RequiredFMCServerTokenInterceptor requiredFMCServerTokenInterceptor; + private final RequireGaoPermissionInterceptor requireGaoPermissionInterceptor; + private final RequireCorePermissionInterceptor requireCorePermissionInterceptor; /** * 过滤器 @@ -53,9 +57,11 @@ public class WebConfig implements WebMvcConfigurer { public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(systemAPIInterceptor).addPathPatterns(SystemAPIInterceptor.PATH); registry.addInterceptor(journalAPIInterceptor).addPathPatterns(JournalAPIInterceptor.PATH); - registry.addInterceptor(requiredFMCServerTokenInterceptor).addPathPatterns("/fmc/server/**"); - registry.addInterceptor(requiredTokenInterceptor).addPathPatterns("/**"); registry.addInterceptor(enableSettingInterceptor).addPathPatterns("/**"); + registry.addInterceptor(requireCoreRoleInterceptor).addPathPatterns("/**"); + registry.addInterceptor(requireCorePermissionInterceptor).addPathPatterns("/**"); + registry.addInterceptor(requireGaoRoleInterceptor).addPathPatterns("/**"); + registry.addInterceptor(requireGaoPermissionInterceptor).addPathPatterns("/**"); registry.addInterceptor(requestRateLimitInterceptor).addPathPatterns("/**"); } diff --git a/src/main/java/com/imyeyu/api/config/dbsource/ForeverMCDBConfig.java b/src/main/java/com/imyeyu/api/config/dbsource/ForeverMCDBConfig.java index 99d3135..0299f91 100644 --- a/src/main/java/com/imyeyu/api/config/dbsource/ForeverMCDBConfig.java +++ b/src/main/java/com/imyeyu/api/config/dbsource/ForeverMCDBConfig.java @@ -30,7 +30,6 @@ public class ForeverMCDBConfig { public static final String ROLLBACKER = "foreverMCTransactionManager"; @Bean(name = "foreverMCDataSource") - @Primary @ConfigurationProperties(prefix = "spring.datasource.forevermc") public DataSource getPrimaryDateSource() throws SQLException { HikariDataSource dataSource = new HikariDataSource(); @@ -50,7 +49,6 @@ public class ForeverMCDBConfig { @Bean(name = "foreverMCSqlSessionFactory") - @Primary public SqlSessionFactory primarySqlSessionFactory(@Qualifier("foreverMCDataSource") DataSource datasource) throws Exception { org.apache.ibatis.session.Configuration config = new org.apache.ibatis.session.Configuration(); config.setUseGeneratedKeys(true); @@ -64,7 +62,6 @@ public class ForeverMCDBConfig { } @Bean("foreverMCSqlSessionTemplate") - @Primary public SqlSessionTemplate primarySqlSessionTemplate(@Qualifier("foreverMCSqlSessionFactory") SqlSessionFactory sessionfactory) { return new SqlSessionTemplate(sessionfactory); } diff --git a/src/main/java/com/imyeyu/api/config/dbsource/GiteaDBConfig.java b/src/main/java/com/imyeyu/api/config/dbsource/GiteaDBConfig.java index a514b80..442dee1 100644 --- a/src/main/java/com/imyeyu/api/config/dbsource/GiteaDBConfig.java +++ b/src/main/java/com/imyeyu/api/config/dbsource/GiteaDBConfig.java @@ -38,7 +38,6 @@ public class GiteaDBConfig { public static final String ROLLBACKER = "giteaTransactionManager"; @Bean(name = "giteaDataSource") - @Primary @ConfigurationProperties(prefix = "spring.datasource.gitea") public DataSource dateSource() throws SQLException { HikariDataSource dataSource = new HikariDataSource(); @@ -58,7 +57,6 @@ public class GiteaDBConfig { @Bean(name = "giteaSqlSessionFactory") - @Primary public SqlSessionFactory sessionFactory(@Qualifier("giteaDataSource") DataSource datasource) throws Exception { org.apache.ibatis.session.Configuration config = new org.apache.ibatis.session.Configuration(); config.setUseGeneratedKeys(true); @@ -69,7 +67,7 @@ public class GiteaDBConfig { { ResourcePatternResolver resourceResolver = new PathMatchingResourcePatternResolver(); List mapperLocations = new ArrayList<>(); - mapperLocations.add("classpath:mapper/gitea/**/*.xml"); +// mapperLocations.add("classpath:mapper/gitea/**/*.xml"); for (String mapperLocation : mapperLocations) { resources.addAll(List.of(resourceResolver.getResources(mapperLocation))); } @@ -92,7 +90,6 @@ public class GiteaDBConfig { @Bean("giteaSqlSessionTemplate") - @Primary public SqlSessionTemplate sessionTemplate(@Qualifier("giteaSqlSessionFactory") SqlSessionFactory sessionfactory) { return new SqlSessionTemplate(sessionfactory); } diff --git a/src/main/java/com/imyeyu/api/config/dbsource/TimiServerDBConfig.java b/src/main/java/com/imyeyu/api/config/dbsource/TimiServerDBConfig.java index cc4ea35..f18c898 100644 --- a/src/main/java/com/imyeyu/api/config/dbsource/TimiServerDBConfig.java +++ b/src/main/java/com/imyeyu/api/config/dbsource/TimiServerDBConfig.java @@ -8,6 +8,7 @@ import org.mybatis.spring.SqlSessionFactoryBean; import org.mybatis.spring.SqlSessionTemplate; import org.mybatis.spring.annotation.MapperScan; import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.autoconfigure.flyway.FlywayDataSource; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -32,7 +33,9 @@ import java.util.List; @Configuration @MapperScan(basePackages = { "com.imyeyu.api.modules.git.mapper", + "com.imyeyu.api.modules.gao.mapper", "com.imyeyu.api.modules.bill.mapper", + "com.imyeyu.api.modules.user.mapper", "com.imyeyu.api.modules.blog.mapper", "com.imyeyu.api.modules.lyric.mapper", "com.imyeyu.api.modules.mirror.mapper", @@ -47,6 +50,7 @@ public class TimiServerDBConfig { @Bean(name = "timiServerDataSource") @Primary + @FlywayDataSource @ConfigurationProperties(prefix = "spring.datasource.timiserver") public DataSource getPrimaryDateSource() throws SQLException { HikariDataSource dataSource = new HikariDataSource(); @@ -77,19 +81,16 @@ public class TimiServerDBConfig { { ResourcePatternResolver resourceResolver = new PathMatchingResourcePatternResolver(); List mapperLocations = new ArrayList<>(); - mapperLocations.add("classpath:mapper/git/**/*.xml"); - mapperLocations.add("classpath:mapper/blog/**/*.xml"); - mapperLocations.add("classpath:mapper/common/**/*.xml"); - mapperLocations.add("classpath:mapper/system/**/*.xml"); - mapperLocations.add("classpath:mapper/journal/**/*.xml"); - mapperLocations.add("classpath:mapper/minecraft/**/*.xml"); + mapperLocations.add("classpath:mapper/timi-server/**/*.xml"); for (String mapperLocation : mapperLocations) { resources.addAll(List.of(resourceResolver.getResources(mapperLocation))); } } String[] typeAliases = { "com.imyeyu.api.modules.git.entity", + "com.imyeyu.api.modules.gao.entity", "com.imyeyu.api.modules.bill.entity", + "com.imyeyu.api.modules.user.entity", "com.imyeyu.api.modules.blog.entity", "com.imyeyu.api.modules.lyric.entity", "com.imyeyu.api.modules.mirror.entity", diff --git a/src/main/java/com/imyeyu/api/modules/blog/controller/ArticleController.java b/src/main/java/com/imyeyu/api/modules/blog/controller/ArticleController.java deleted file mode 100644 index 4e788f9..0000000 --- a/src/main/java/com/imyeyu/api/modules/blog/controller/ArticleController.java +++ /dev/null @@ -1,93 +0,0 @@ -package com.imyeyu.api.modules.blog.controller; - -import com.imyeyu.api.modules.blog.entity.Article; -import com.imyeyu.api.modules.blog.entity.ArticleRanking; -import com.imyeyu.api.modules.blog.service.ArticleService; -import com.imyeyu.api.modules.blog.vo.article.ArticleView; -import com.imyeyu.api.modules.blog.vo.article.KeywordPage; -import com.imyeyu.spring.annotation.AOPLog; -import com.imyeyu.spring.annotation.RequestRateLimit; -import com.imyeyu.spring.bean.Page; -import com.imyeyu.spring.bean.PageResult; -import jakarta.validation.Valid; -import jakarta.validation.constraints.Min; -import jakarta.validation.constraints.NotNull; -import lombok.RequiredArgsConstructor; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -import java.util.List; - -/** - * 文章接口 - * - * @author 夜雨 - * @since 2021-02-17 17:47 - */ -@Validated -@RestController -@RequiredArgsConstructor -@RequestMapping("/article") -public class ArticleController { - - private final ArticleService service; - - /** - * 查看 - * - * @param id 文章 ID - * @return 文章 - */ - @AOPLog - @RequestRateLimit - @RequestMapping("/{id}") - public ArticleView view(@Min(1) @NotNull @PathVariable Long id) { - return service.view(id); - } - - /** - * 喜欢文章 - * - * @param id 文章 ID - * @return 最新喜欢数量 - */ - @AOPLog - @RequestRateLimit - @RequestMapping("/like/{id}") - public int like(@Min(1) @NotNull @PathVariable Long id) { - return service.like(id); - } - - /** - * 主列表 - * - * @param page 页面参数 - * @return 文章列表 - */ - @RequestRateLimit - @RequestMapping("/list") - public PageResult
list(@Valid @RequestBody Page page) { - return service.page(page); - } - - /** - * 根据关键字获取列表 - * - * @param page 关键字页面参数 - * @return 文章列表 - */ - @RequestRateLimit - @RequestMapping("/list/search") - public PageResult
listByKeyword(@Valid @RequestBody KeywordPage page) { - return service.pageByKeyword(page); - } - - /** @return 每周访问排位 */ - @RequestMapping("/list/ranking") - public List ranking() { - return service.listRanking(); - } -} diff --git a/src/main/java/com/imyeyu/api/modules/blog/controller/BlogController.java b/src/main/java/com/imyeyu/api/modules/blog/controller/BlogController.java deleted file mode 100644 index 701d29d..0000000 --- a/src/main/java/com/imyeyu/api/modules/blog/controller/BlogController.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.imyeyu.api.modules.blog.controller; - -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import com.imyeyu.api.modules.blog.entity.Friend; -import com.imyeyu.api.modules.blog.service.FriendService; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -import java.util.List; - -/** - * 主控 - * - * @author 夜雨 - * @since 2023-02-04 10:28 - */ -@Slf4j -@RestController -@RequiredArgsConstructor -@RequestMapping("/") -public class BlogController { - - private final FriendService friendService; - - /** @return 所有友链列表 */ - @GetMapping("/friend") - public List friend() { - return friendService.listAll(); - } -} diff --git a/src/main/java/com/imyeyu/api/modules/blog/entity/Article.java b/src/main/java/com/imyeyu/api/modules/blog/entity/Article.java deleted file mode 100644 index d47ba6c..0000000 --- a/src/main/java/com/imyeyu/api/modules/blog/entity/Article.java +++ /dev/null @@ -1,92 +0,0 @@ -package com.imyeyu.api.modules.blog.entity; - -import com.fasterxml.jackson.databind.JsonNode; -import com.imyeyu.api.modules.common.bean.CommentSupport; -import com.imyeyu.spring.entity.Destroyable; -import com.imyeyu.spring.entity.Entity; -import lombok.Data; -import lombok.EqualsAndHashCode; - -/** - * 文章 - * - * @author 夜雨 - * @since 2021-03-01 17:10 - */ -@Data -@EqualsAndHashCode(callSuper = true) -public class Article extends Entity implements CommentSupport, Destroyable { - - /** - * 文章渲染类型,对应前端模板 - * - * @author 夜雨 - * @since 2021-07-04 09:23 - */ - public enum Type { - - /** 公版 */ - PUBLIC, - - /** 音乐 */ - MUSIC, - - /** 软件 */ - SOFTWARE - } - - /** 标题 */ - protected String title; - - /** 类型 */ - protected Type type; - - /** 摘要 */ - protected String digest; - - /** 数据 */ - protected String data; - - /** 扩展数据 */ - protected JsonNode extendData; - - /** 阅读数量 */ - protected int reads; - - /** 喜欢数量 */ - protected int likes; - - /** true 为显示评论 */ - protected boolean showComment; - - /** true 为可评论 */ - protected boolean canComment; - - /** true 为可排位 */ - protected boolean canRanking; - - /** true 为可通过列表查询 */ - protected boolean canList; - - /** @return true 为可评论 */ - @Override - public boolean canComment() { - return canComment; - } - - /** @return true 为不可评论 */ - @Override - public boolean canNotComment() { - return !canComment; - } - - /** @return true 为可排位 */ - public boolean canRanking() { - return canRanking; - } - - /** @return true 为不可排位 */ - public boolean canNotRanking() { - return !canRanking; - } -} diff --git a/src/main/java/com/imyeyu/api/modules/blog/entity/ArticleRanking.java b/src/main/java/com/imyeyu/api/modules/blog/entity/ArticleRanking.java deleted file mode 100644 index 8afb5ab..0000000 --- a/src/main/java/com/imyeyu/api/modules/blog/entity/ArticleRanking.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.imyeyu.api.modules.blog.entity; - -import com.imyeyu.spring.entity.Entity; -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.NoArgsConstructor; - -/** - * 访问排行(每周) - * 只记录访问次数、标题和最近访问,具体文章由 Redis key 记录 - * - * @author 夜雨 - * @since 2021-03-01 17:10 - */ -@Data -@NoArgsConstructor -@EqualsAndHashCode(callSuper = true) -public class ArticleRanking extends Entity { - - private String title; - private Article.Type type; - private int count = 1; - private Long recentAt; // 最近访问 - - public ArticleRanking(Long id, String title, Article.Type type) { - setId(id); - this.title = title; - this.type = type; - } - - /** 访问计数 + 1 */ - public void read() { - count++; - } -} diff --git a/src/main/java/com/imyeyu/api/modules/blog/entity/CommentRemindQueue.java b/src/main/java/com/imyeyu/api/modules/blog/entity/CommentRemindQueue.java deleted file mode 100644 index 2e9dfa4..0000000 --- a/src/main/java/com/imyeyu/api/modules/blog/entity/CommentRemindQueue.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.imyeyu.api.modules.blog.entity; - -import com.imyeyu.api.modules.common.entity.CommentReply; -import com.imyeyu.spring.annotation.table.AutoUUID; -import com.imyeyu.spring.annotation.table.Id; -import lombok.Data; -import lombok.NoArgsConstructor; - -/** - * 评论回复提醒队列,和 CommentReplyRecord 不一样,本队列在推送消息后就删除,而后者会持久保存 - * - *

基本逻辑: - *

- *     触发:用户回复一条评论
- *     条件:被回复者是注册用户 && 不是回复自己 && 邮箱已验证 && 接收回复提醒邮件
- *     事件:添加本对象到队列列表,等待邮件推送服务调度,邮件推送服务
- * 
- * 会针对用户收集本队列消息组合成邮件再一并推送 - * - * @author 夜雨 - * @since 2021-08-25 00:00 - */ -@Data -@NoArgsConstructor -public class CommentRemindQueue { - - @Id - @AutoUUID - private String UUID; - - private Long userId; - - private Long replyId; - - private CommentReply reply; -} diff --git a/src/main/java/com/imyeyu/api/modules/blog/entity/Friend.java b/src/main/java/com/imyeyu/api/modules/blog/entity/Friend.java deleted file mode 100644 index 3655d84..0000000 --- a/src/main/java/com/imyeyu/api/modules/blog/entity/Friend.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.imyeyu.api.modules.blog.entity; - -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.NoArgsConstructor; -import com.imyeyu.spring.entity.Entity; - -/** - * 夜雨 创建于 2021-07-15 15:59 - */ -@Data -@NoArgsConstructor -@AllArgsConstructor -@EqualsAndHashCode(callSuper = true) -public class Friend extends Entity { - - private String icon; - private String name; - private String link; -} diff --git a/src/main/java/com/imyeyu/api/modules/blog/mapper/ArticleMapper.java b/src/main/java/com/imyeyu/api/modules/blog/mapper/ArticleMapper.java deleted file mode 100644 index f6aaf55..0000000 --- a/src/main/java/com/imyeyu/api/modules/blog/mapper/ArticleMapper.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.imyeyu.api.modules.blog.mapper; - -import com.imyeyu.api.modules.blog.entity.Article; -import com.imyeyu.spring.bean.Page; -import com.imyeyu.spring.mapper.BaseMapper; -import org.apache.ibatis.annotations.Select; - -import java.util.List; - -/** - * 文章 - * - * @author 夜雨 - * @since 2021-02-23 21:34 - */ -public interface ArticleMapper extends BaseMapper { - - @Override - long countByPage(Page page); - - @Override - List
selectByPage(Page page); - - long countByKeyword(String keyword); - - List
selectByKeyword(String keyword, Long offset, long limit); - - @Select("UPDATE `article` SET `likes` = `likes` + 1 WHERE `id` = #{articleId}") - void like(Long articleId); - - @Select("UPDATE `article` SET `reads` = `reads` + 1 WHERE `id` = #{articleId}") - void read(Long articleId); -} diff --git a/src/main/java/com/imyeyu/api/modules/blog/mapper/FriendMapper.java b/src/main/java/com/imyeyu/api/modules/blog/mapper/FriendMapper.java deleted file mode 100644 index 12dbe75..0000000 --- a/src/main/java/com/imyeyu/api/modules/blog/mapper/FriendMapper.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.imyeyu.api.modules.blog.mapper; - - -import com.imyeyu.api.modules.blog.entity.Friend; -import com.imyeyu.spring.mapper.BaseMapper; -import org.apache.ibatis.annotations.Select; - -import java.util.List; - -/** - * 友链 - * - * @author 夜雨 - * @since 2021-07-15 16:11 - */ -public interface FriendMapper extends BaseMapper { - - @Select("SELECT * FROM friend WHERE 1 = 1" + NOT_DELETE) - List listAll(); -} diff --git a/src/main/java/com/imyeyu/api/modules/blog/service/ArticleService.java b/src/main/java/com/imyeyu/api/modules/blog/service/ArticleService.java deleted file mode 100644 index 641fa45..0000000 --- a/src/main/java/com/imyeyu/api/modules/blog/service/ArticleService.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.imyeyu.api.modules.blog.service; - -import com.imyeyu.java.bean.timi.TimiException; -import com.imyeyu.api.modules.blog.entity.Article; -import com.imyeyu.api.modules.blog.entity.ArticleRanking; -import com.imyeyu.api.modules.blog.vo.article.ArticleView; -import com.imyeyu.api.modules.blog.vo.article.KeywordPage; -import com.imyeyu.spring.bean.PageResult; -import com.imyeyu.spring.service.GettableService; -import com.imyeyu.spring.service.PageableService; - -import java.util.List; - -/** - * 文章服务 - * - * @author 夜雨 - * @since 2021-02-23 21:33 - */ -public interface ArticleService extends GettableService, PageableService
{ - - /** - * 获取文章,此方法触发阅读计数,包括触发每周热门排行统计,同一 IP 3 小时内访问多次的文章只计一次 - * - * @param id 文章 ID - * @throws TimiException 服务异常 - */ - ArticleView view(long id); - - PageResult
pageByKeyword(KeywordPage page); - - /** - * 获取每周阅读排行 - * - * @return 热门文章列表 - * @throws TimiException 服务异常 - */ - List listRanking(); - - /** - * 喜欢文章 - * - * @param articleId 文章 ID - * @return 最新喜欢数量 - * @throws TimiException 服务异常 - */ - int like(Long articleId); -} diff --git a/src/main/java/com/imyeyu/api/modules/blog/service/CommentRemindQueueService.java b/src/main/java/com/imyeyu/api/modules/blog/service/CommentRemindQueueService.java deleted file mode 100644 index 2ce171b..0000000 --- a/src/main/java/com/imyeyu/api/modules/blog/service/CommentRemindQueueService.java +++ /dev/null @@ -1,41 +0,0 @@ -package com.imyeyu.api.modules.blog.service; - -import com.imyeyu.java.bean.timi.TimiException; -import com.imyeyu.api.modules.blog.entity.CommentRemindQueue; -import com.imyeyu.spring.service.CreatableService; - -import java.util.List; - -/** - * 评论回复队列服务 - * - * @author 夜雨 - * @since 2021-08-25 00:11 - */ -public interface CommentRemindQueueService extends CreatableService { - - /** - * 根据用户 ID 获取 - * - * @param userId 用户 ID - * @return 回复提醒列表 - * @throws TimiException 服务异常 - */ - List listByUserId(Long userId); - - /** - * 根据用户 ID 移出队列 - * - * @param uid 用户 ID - * @throws TimiException 服务异常 - */ - void destroyByUserId(Long uid); - - /** - * 根据回复 ID 移出队列 - * - * @param rid 回复 ID - * @throws TimiException 服务异常 - */ - void destroyByReplyId(Long rid); -} diff --git a/src/main/java/com/imyeyu/api/modules/blog/service/FriendService.java b/src/main/java/com/imyeyu/api/modules/blog/service/FriendService.java deleted file mode 100644 index 99e242e..0000000 --- a/src/main/java/com/imyeyu/api/modules/blog/service/FriendService.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.imyeyu.api.modules.blog.service; - -import com.imyeyu.api.modules.blog.entity.Friend; - -import java.util.List; - -/** - * 友链服务 - * - * @author 夜雨 - * @since 2021-07-15 16:04 - */ -public interface FriendService { - - List listAll(); -} diff --git a/src/main/java/com/imyeyu/api/modules/blog/service/implement/ArticleServiceImplement.java b/src/main/java/com/imyeyu/api/modules/blog/service/implement/ArticleServiceImplement.java deleted file mode 100644 index d752459..0000000 --- a/src/main/java/com/imyeyu/api/modules/blog/service/implement/ArticleServiceImplement.java +++ /dev/null @@ -1,122 +0,0 @@ -package com.imyeyu.api.modules.blog.service.implement; - -import com.imyeyu.api.config.dbsource.TimiServerDBConfig; -import com.imyeyu.api.modules.blog.entity.Article; -import com.imyeyu.api.modules.blog.entity.ArticleRanking; -import com.imyeyu.api.modules.blog.mapper.ArticleMapper; -import com.imyeyu.api.modules.blog.service.ArticleService; -import com.imyeyu.api.modules.blog.vo.article.ArticleView; -import com.imyeyu.api.modules.blog.vo.article.KeywordPage; -import com.imyeyu.api.modules.common.entity.Attachment; -import com.imyeyu.api.modules.common.entity.Comment; -import com.imyeyu.api.modules.common.entity.Tag; -import com.imyeyu.api.modules.common.mapper.CommentMapper; -import com.imyeyu.api.modules.common.service.AttachmentService; -import com.imyeyu.api.modules.common.service.TagService; -import com.imyeyu.java.bean.timi.TimiException; -import com.imyeyu.spring.TimiSpring; -import com.imyeyu.spring.bean.Page; -import com.imyeyu.spring.bean.PageResult; -import com.imyeyu.spring.mapper.BaseMapper; -import com.imyeyu.spring.service.AbstractEntityService; -import com.imyeyu.spring.util.Redis; -import com.imyeyu.utils.Time; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.BeanUtils; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import java.util.Comparator; -import java.util.List; - -/** - * 文章服务实现 - * - * @author 夜雨 - * @since 2021-02-17 17:48 - */ -@Slf4j -@Service -@RequiredArgsConstructor -public class ArticleServiceImplement extends AbstractEntityService implements ArticleService { - - private final TagService tagService; - private final AttachmentService attachmentService; - - private final ArticleMapper mapper; - private final CommentMapper commentMapper; - - private final Redis redisArticleRead; - private final Redis redisArticleRanking; - - @Override - protected BaseMapper mapper() { - return mapper; - } - - @Override - public PageResult
page(Page page) { - PageResult
result = new PageResult<>(); - result.setList(mapper.selectByPage(page)); - result.setTotal(mapper.countByPage(page)); - return result; - } - - @Transactional(TimiServerDBConfig.ROLLBACKER) - @Override - public ArticleView view(long id) { - String ip = TimiSpring.getRequestIP(); - Article article = get(id); - TimiException.required(article, "article.not_found"); - // 计数 - if (!redisArticleRead.contains(ip, article.getId())) { - // 3 小时内访问记录 - redisArticleRead.add(ip, article.getId()); - redisArticleRead.setExpire(ip, Time.H * 3); - mapper.read(article.getId()); - // 每周访问计数 - if (article.canRanking()) { - ArticleRanking ranking = redisArticleRanking.get(article.getId()); - if (ranking == null) { - ranking = new ArticleRanking(article.getId(), article.getTitle(), article.getType()); - ranking.setRecentAt(Time.now()); - redisArticleRanking.set(article.getId(), ranking, Time.D * 7); - } else { - ranking.read(); - ranking.setRecentAt(Time.now()); - redisArticleRanking.setAndKeepTTL(article.getId(), ranking); - } - } - } - - ArticleView view = new ArticleView(); - BeanUtils.copyProperties(article, view); - view.setComments(commentMapper.countAll(Comment.BizType.ARTICLE, article.getId())); - view.setTagList(tagService.listByBizID(Tag.BizType.ARTICLE, String.valueOf(article.getId()))); - view.setAttachmentList(attachmentService.listByBizId(Attachment.BizType.ARTICLE, article.getId())); - return view; - } - - @Override - public PageResult
pageByKeyword(KeywordPage page) { - PageResult
result = new PageResult<>(); - result.setList(mapper.selectByKeyword(page.getKeyword(), page.getOffset(), page.getLimit())); - result.setTotal(mapper.countByKeyword(page.getKeyword())); - return result; - } - - @Override - public List listRanking() { - List list = redisArticleRanking.values(); - list.sort(Comparator.comparing(ArticleRanking::getCount).reversed()); - return list.subList(0, Math.min(10, list.size())); - } - - @Transactional(TimiServerDBConfig.ROLLBACKER) - @Override - public int like(Long articleId) { - mapper.like(articleId); - return get(articleId).getLikes(); - } -} diff --git a/src/main/java/com/imyeyu/api/modules/blog/service/implement/CommentRemindQueueServiceImplement.java b/src/main/java/com/imyeyu/api/modules/blog/service/implement/CommentRemindQueueServiceImplement.java deleted file mode 100644 index f13bd11..0000000 --- a/src/main/java/com/imyeyu/api/modules/blog/service/implement/CommentRemindQueueServiceImplement.java +++ /dev/null @@ -1,52 +0,0 @@ -package com.imyeyu.api.modules.blog.service.implement; - -import com.imyeyu.api.config.dbsource.TimiServerDBConfig; -import com.imyeyu.api.modules.blog.entity.CommentRemindQueue; -import com.imyeyu.api.modules.blog.service.CommentRemindQueueService; -import com.imyeyu.api.modules.common.mapper.CommentRemindQueueMapper; -import com.imyeyu.spring.mapper.BaseMapper; -import com.imyeyu.spring.service.AbstractEntityService; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import java.util.List; - -/** - * 评论回复队列服务实现 - * - * @author 夜雨 - * @since 2021-08-25 00:11 - */ -@Service -@RequiredArgsConstructor -public class CommentRemindQueueServiceImplement extends AbstractEntityService implements CommentRemindQueueService { - - private final CommentRemindQueueMapper mapper; - - @Override - protected BaseMapper mapper() { - return mapper; - } - - @Override - public List listByUserId(Long userId) { - return mapper.listByUserId(userId); - } - - @Transactional(TimiServerDBConfig.ROLLBACKER) - @Override - public void destroyByUserId(Long userId) { - CommentRemindQueue example = new CommentRemindQueue(); - example.setUserId(userId); - mapper.deleteAllByExample(example); - } - - @Transactional(TimiServerDBConfig.ROLLBACKER) - @Override - public void destroyByReplyId(Long replyId) { - CommentRemindQueue example = new CommentRemindQueue(); - example.setReplyId(replyId); - mapper.destroyAllByExample(example); - } -} diff --git a/src/main/java/com/imyeyu/api/modules/blog/service/implement/FriendServiceImplement.java b/src/main/java/com/imyeyu/api/modules/blog/service/implement/FriendServiceImplement.java deleted file mode 100644 index 8afc0c2..0000000 --- a/src/main/java/com/imyeyu/api/modules/blog/service/implement/FriendServiceImplement.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.imyeyu.api.modules.blog.service.implement; - -import lombok.RequiredArgsConstructor; -import com.imyeyu.api.modules.blog.entity.Friend; -import com.imyeyu.api.modules.blog.mapper.FriendMapper; -import com.imyeyu.api.modules.blog.service.FriendService; -import org.springframework.stereotype.Service; - -import java.util.List; - -/** - * 友链服务实现 - * - * @author 夜雨 - * @since 2021-07-15 16:05 - */ -@Service -@RequiredArgsConstructor -public class FriendServiceImplement implements FriendService { - - private final FriendMapper mapper; - - @Override - public List listAll() { - return mapper.listAll(); - } -} diff --git a/src/main/java/com/imyeyu/api/modules/blog/util/UserToken.java b/src/main/java/com/imyeyu/api/modules/blog/util/UserToken.java deleted file mode 100644 index c3c08d4..0000000 --- a/src/main/java/com/imyeyu/api/modules/blog/util/UserToken.java +++ /dev/null @@ -1,147 +0,0 @@ -package com.imyeyu.api.modules.blog.util; - -import com.imyeyu.java.TimiJava; -import com.imyeyu.java.bean.timi.TimiException; -import com.imyeyu.api.config.RedisConfig; -import com.imyeyu.api.modules.common.bean.SettingKey; -import com.imyeyu.api.modules.common.entity.User; -import com.imyeyu.api.modules.common.service.SettingService; -import com.imyeyu.api.modules.common.service.UserService; -import com.imyeyu.spring.TimiSpring; -import com.imyeyu.spring.util.Redis; -import com.imyeyu.spring.util.RedisSerializers; -import com.imyeyu.utils.Time; -import jakarta.annotation.Nullable; -import jakarta.annotation.PostConstruct; -import jakarta.servlet.http.Cookie; -import jakarta.validation.constraints.NotNull; -import lombok.RequiredArgsConstructor; -import org.springframework.context.annotation.Lazy; -import org.springframework.stereotype.Component; - -import java.util.Objects; - -/** - * Redis 令牌缓存 - * - *

一级缓存 Session,二级缓存 Redis,有效期为 {@link SettingKey#TTL_USER_TOKEN} 天,每次触发 - * 二级缓存获取时会刷新这个时间,即指定天数内不再访问则视为登出 - * - * @author 夜雨 - * @since 2023-07-17 16:58 - */ -@Component -@RequiredArgsConstructor(onConstructor_ = {@Lazy}) -public class UserToken { - - private final RedisConfig redisConfig; - - private final UserService userService; - private final SettingService settingService; - - private Redis redis; - - @PostConstruct - private void postConstruct() { - redis = redisConfig.getRedis(redisConfig.getDatabase().getUserToken(), RedisSerializers.STRING, RedisSerializers.LONG); - } - - public Long set(String token, Long userId) { - long ttl = Time.D * settingService.getAsInt(SettingKey.TTL_USER_TOKEN); - // 会话 - TimiSpring.setSessionAttr(token, userId); - // 跨站 Cookie - Cookie cookie = Objects.requireNonNullElse(TimiSpring.getCookie("Token"), new Cookie("Token", token)); - cookie.setDomain(settingService.getAsString(SettingKey.DOMAIN_ROOT)); - cookie.setPath("/"); - cookie.setSecure(true); - cookie.setMaxAge((int) (ttl / 1000)); - TimiSpring.addCookie(cookie); - // Redis - redis.set(token, userId, ttl); - return Time.now() + ttl; - } - - public Long getExpireAt(String token) { - return Time.now() + redis.getExpire(token); - } - - /** - * 获取令牌是否有效 - * - * @param token 令牌 - * @return true 为有效 - */ - public boolean isValid(String token) { - return getUserId(token) != null; - } - - /** - * 获取令牌是否无效({@link #isValid(String)} 取反) - * - * @param token 令牌 - * @return true 为无效 - */ - public boolean isInvalid(String token) { - return !isValid(token); - } - - /** - * 获取用户 ID - *

一级缓存 Session,二级缓存 Redis,每次触发二级缓存获取时会刷新这个时间 - *

Session 存键为 token,值为 UserId,Redis 也相同 - * - * @param token 令牌 - * @return 用户 ID - * @throws TimiException 无效 token - */ - public @Nullable Long getRequiredUserId(String token) throws TimiException { - return TimiException.required(getUserId(token), "invalid token"); - } - - /** - * 获取用户 ID - *

一级缓存 Session,二级缓存 Redis,每次触发二级缓存获取时会刷新这个时间 - *

Session 存键为 token,值为 UserId,Redis 也相同 - * - * @param token 令牌 - * @return 用户 ID,token 无效时为 null - */ - public @Nullable Long getUserId(String token) { - if (TimiJava.isEmpty(token)) { - return null; - } - Long userId; - // Session - if (TimiSpring.getSessionAttr(token) instanceof Long sessionUserId) { - userId = sessionUserId; - } else { - // Redis - userId = redis.get(token); - } - if (TimiJava.isEmpty(userId)) { - return null; - } - // 刷新 - set(token, userId); - return userId; - } - - public @NotNull User getUser(String token) { - return userService.get(getRequiredUserId(token)); - } - - public void clear(String token) { - // 会话 - TimiSpring.removeSessionAttr(token); - // 清除跨站 Cookie - Cookie cookie = new Cookie("Token", "DIED"); - cookie.setDomain(settingService.getAsString(SettingKey.DOMAIN_ROOT)); - cookie.setPath("/"); - cookie.setSecure(true); - cookie.setMaxAge(0); - TimiSpring.addCookie(cookie); - // Redis - redis.destroy(token); - } -} diff --git a/src/main/java/com/imyeyu/api/modules/blog/vo/article/ArticleView.java b/src/main/java/com/imyeyu/api/modules/blog/vo/article/ArticleView.java deleted file mode 100644 index 5400e74..0000000 --- a/src/main/java/com/imyeyu/api/modules/blog/vo/article/ArticleView.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.imyeyu.api.modules.blog.vo.article; - -import com.imyeyu.api.modules.blog.entity.Article; -import com.imyeyu.api.modules.common.entity.Attachment; -import com.imyeyu.api.modules.common.entity.Tag; -import lombok.Data; -import lombok.EqualsAndHashCode; - -import java.util.List; - -/** - * @author 夜雨 - * @since 2023-08-07 17:19 - */ -@Data -@EqualsAndHashCode(callSuper = true) -public class ArticleView extends Article { - - private long comments; - - private List tagList; - - private List attachmentList; -} diff --git a/src/main/java/com/imyeyu/api/modules/blog/vo/article/ClassPage.java b/src/main/java/com/imyeyu/api/modules/blog/vo/article/ClassPage.java deleted file mode 100644 index 33b5cbc..0000000 --- a/src/main/java/com/imyeyu/api/modules/blog/vo/article/ClassPage.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.imyeyu.api.modules.blog.vo.article; - -import lombok.Data; -import lombok.EqualsAndHashCode; -import com.imyeyu.spring.bean.Page; - -/** - * @author 夜雨 - * @since 2023-07-14 17:52 - */ -@Data -@EqualsAndHashCode(callSuper = true) -public class ClassPage extends Page { - - private long classId; -} diff --git a/src/main/java/com/imyeyu/api/modules/blog/vo/article/KeywordPage.java b/src/main/java/com/imyeyu/api/modules/blog/vo/article/KeywordPage.java deleted file mode 100644 index 902e7c7..0000000 --- a/src/main/java/com/imyeyu/api/modules/blog/vo/article/KeywordPage.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.imyeyu.api.modules.blog.vo.article; - -import jakarta.validation.constraints.NotBlank; -import lombok.Data; -import lombok.EqualsAndHashCode; -import com.imyeyu.spring.bean.Page; - -/** - * @author 夜雨 - * @since 2023-07-14 17:53 - */ -@Data -@EqualsAndHashCode(callSuper = true) -public class KeywordPage extends Page { - - @NotBlank(message = "article.keyword.empty") - private String keyword; -} diff --git a/src/main/java/com/imyeyu/api/modules/blog/vo/article/LabelPage.java b/src/main/java/com/imyeyu/api/modules/blog/vo/article/LabelPage.java deleted file mode 100644 index 712a5fa..0000000 --- a/src/main/java/com/imyeyu/api/modules/blog/vo/article/LabelPage.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.imyeyu.api.modules.blog.vo.article; - -import lombok.Data; -import lombok.EqualsAndHashCode; -import com.imyeyu.spring.bean.Page; - -/** - * @author 夜雨 - * @since 2023-07-14 17:53 - */ -@Data -@EqualsAndHashCode(callSuper = true) -public class LabelPage extends Page { - - private long labelId; -} diff --git a/src/main/java/com/imyeyu/api/modules/common/bean/CommentSupport.java b/src/main/java/com/imyeyu/api/modules/common/bean/CommentSupport.java deleted file mode 100644 index 4bbb82b..0000000 --- a/src/main/java/com/imyeyu/api/modules/common/bean/CommentSupport.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.imyeyu.api.modules.common.bean; - -/** - * 支持评论的实体 - * - * @author 夜雨 - * @since 2023-10-10 11:39 - */ -public interface CommentSupport { - - /** @return true 为可评论 */ - boolean canComment(); - - /** @return true 为不可评论 */ - boolean canNotComment(); -} diff --git a/src/main/java/com/imyeyu/api/modules/common/bean/SettingKey.java b/src/main/java/com/imyeyu/api/modules/common/bean/SettingKey.java deleted file mode 100644 index e02fb86..0000000 --- a/src/main/java/com/imyeyu/api/modules/common/bean/SettingKey.java +++ /dev/null @@ -1,191 +0,0 @@ -package com.imyeyu.api.modules.common.bean; - -/** - * 系统设置 - * - * @author 夜雨 - * @since 2021-07-20 21:46 - */ -public enum SettingKey { - - // ---------- 通用 ---------- - - RUN_ENV, - - PUBLIC_RESOURCES, - - /** 启用注册 */ - ENABLE_REGISTER, - - /** 启用登录 */ - ENABLE_LOGIN, - - /** 启用评论 */ - ENABLE_COMMENT, - - /** 启用测试 */ - ENABLE_DEBUG, - - /** 启用账号数据更新(User 和 UserProfile) */ - ENABLE_USER_UPDATE, - - /** 启用灰色滤镜 */ - ENABLE_GRAY_FILTER, - - TEMP_FILE_PATH, - - // ---------- ICP 备案号 ---------- - - ICP_IMYEYU_COM, - - // ---------- 域名 ---------- - - DOMAIN_ROOT, - - DOMAIN_API, - - DOMAIN_GIT, - - DOMAIN_BLOG, - - DOMAIN_SPACE, - - DOMAIN_RESOURCE, - - DOMAIN_DOWNLOAD, - - DOMAIN_FOREVER_MC, - - // ---------- ForeverMC ---------- - - /** 启用登录服务 */ - FMC_PLAYER_LOGIN_ENABLE, - - /** 最多绑定玩家数量 */ - FMC_MAX_BIND, - - /** 闪烁标语 */ - FMC_SPLASHES, - - /** 启动器背景 */ - FMC_BG, - - FMC_BGM, - - FMC_BG_SWIPER, - - /** JRE 列表 */ - FMC_JRE, - - /** 辅助登录模组 */ - FMC_LOGIN_FABRIC, - - /** 启用图片地图上传 */ - FMC_ENABLE_IMAGE_MAP_UPLOAD, - - /** 玩家登录令牌有效期(天) */ - FMC_PLAYER_LOGIN_TOKEN_TTL, - - /** 服务器与数据中心的通信令牌 */ - FMC_SERVER_TOKEN, - - // ---------- 生存时间 ---------- - - TTL_USER_TOKEN, - - TTL_SETTING, - - TTL_MULTILINGUAL, - - // ---------- 多语言翻译 ---------- - - MULTILINGUAL_TRANSLATE_API, - - MULTILINGUAL_TRANSLATE_APP_ID, - - MULTILINGUAL_TRANSLATE_KEY, - - // ---------- 账单 ---------- - - BILL_API_TOKEN, - - // ---------- Git ---------- - - GIT_API, - - GIT_ABOUT_ARTICLE, - - GIT_REPO_PATH, - - // ---------- 远程音乐 ---------- - - MUSIC_MAX_FRAME_LENGTH, - - MUSIC_PLAYER_PORT, - - MUSIC_PLAYER_IP, - - MUSIC_CONTROLLER_PORT, - - MUSIC_CONTROLLER_IP, - - MUSIC_CONTROLLER_URI, - - // ---------- 日记 ---------- - - JOURNAL_KEY, - - JOURNAL_APP_ID, - - JOURNAL_APP_SECRET, - - JOURNAL_MEMO, - - JOURNAL_OPEN_ID_WHITE_LIST, - - // ---------- 临时文件 ---------- - - /** 临时文件最小缓存时间 */ - TEMP_FILE_TTL_MIN, - - /** 临时文件最长缓存时间 */ - TEMP_FILE_TTL_MAX, - - /** 临时文件默认缓存时间 */ - TEMP_FILE_TTL_DEFAULT, - - /** 已过期的临时文件保留时间 */ - TEMP_FILE_RESIDUE_TIME, - - /** 每个 IP 限制有效临时文件容量 */ - TEMP_FILE_LIMIT, - - // ---------- 系统 ---------- - - SYSTEM_FILE_BASE, - - SYSTEM_FILE_TYPE, - - SYSTEM_FILE_SYNC, - - /** 文件过滤(通过密钥类型) */ - SYSTEM_FILE_FILTER, - - SYSTEM_STATUS_RATE, - - SYSTEM_STATUS_LIMIT, - - SYSTEM_STATUS_NETWORK_MAC, - - SYSTEM_TERMINAL_TTL, - - SYSTEM_TERMINAL_FILTERS, - - /** 一般密钥 */ - SYSTEM_API_KEY, - - /** 超级密钥 */ - SYSTEM_API_SUPER_KEY, - - SYSTEM_REBOOT_COMMAND, -} diff --git a/src/main/java/com/imyeyu/api/modules/common/bean/SettingModule.java b/src/main/java/com/imyeyu/api/modules/common/bean/SettingModule.java new file mode 100644 index 0000000..2086eeb --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/common/bean/SettingModule.java @@ -0,0 +1,50 @@ +package com.imyeyu.api.modules.common.bean; + +import com.imyeyu.api.modules.common.entity.Setting; +import com.imyeyu.java.bean.timi.TimiCode; +import com.imyeyu.java.bean.timi.TimiException; +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 设置模块 + * + * @author 夜雨 + * @since 2026-04-24 00:59 + */ +@Getter +@AllArgsConstructor +public enum SettingModule { + + COMMON(Setting.Module.Common.class), + + DOMAIN(Setting.Module.Domain.class), + + FOREVER_MC(Setting.Module.ForeverMC.class), + + MULTILINGUAL(Setting.Module.Multilingual.class), + + GITEA(Setting.Module.Gitea.class), + + MUSIC(Setting.Module.Music.class), + + JOURNAL(Setting.Module.Journal.class), + + TEMP_FILE(Setting.Module.TempFile.class), + + SYSTEM(Setting.Module.System.class), + + SYSTEM_STATUS(Setting.Module.SystemStatus.class); + + /** 模块枚举类 */ + final Class> clazz; + + public static > SettingModule fromClass(Class clazz) { + for (SettingModule value : values()) { + if (value.clazz.equals(clazz)) { + return value; + } + } + throw new TimiException(TimiCode.ERROR, "not found System Module: %s".formatted(clazz.getName())); + } +} diff --git a/src/main/java/com/imyeyu/api/modules/common/bean/attachment/AccessKey.java b/src/main/java/com/imyeyu/api/modules/common/bean/attachment/AccessKey.java new file mode 100644 index 0000000..4831872 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/common/bean/attachment/AccessKey.java @@ -0,0 +1,28 @@ +package com.imyeyu.api.modules.common.bean.attachment; + +import lombok.Data; + +/** + * 附件访问密钥 VO + * + * @author 夜雨 + * @since 2026-01-13 10:05 + */ +@Data +public class AccessKey { + + /** 访问密钥 */ + private String value; + + /** 附件 ID */ + private String attachmentId; + + /** true 为一次性访问 */ + private boolean isDisposable; + + /** 过期时间戳 */ + private Long expiredAt; + + /** 创建时间戳 */ + private Long createdAt; +} diff --git a/src/main/java/com/imyeyu/api/modules/common/bean/MediaAttach.java b/src/main/java/com/imyeyu/api/modules/common/bean/attachment/MediaAttach.java similarity index 76% rename from src/main/java/com/imyeyu/api/modules/common/bean/MediaAttach.java rename to src/main/java/com/imyeyu/api/modules/common/bean/attachment/MediaAttach.java index 25365d0..8cd48dc 100644 --- a/src/main/java/com/imyeyu/api/modules/common/bean/MediaAttach.java +++ b/src/main/java/com/imyeyu/api/modules/common/bean/attachment/MediaAttach.java @@ -1,4 +1,4 @@ -package com.imyeyu.api.modules.common.bean; +package com.imyeyu.api.modules.common.bean.attachment; /** * @author 夜雨 diff --git a/src/main/java/com/imyeyu/api/modules/common/bean/Metadata.java b/src/main/java/com/imyeyu/api/modules/common/bean/attachment/Metadata.java similarity index 65% rename from src/main/java/com/imyeyu/api/modules/common/bean/Metadata.java rename to src/main/java/com/imyeyu/api/modules/common/bean/attachment/Metadata.java index 84ef59d..bbd0325 100644 --- a/src/main/java/com/imyeyu/api/modules/common/bean/Metadata.java +++ b/src/main/java/com/imyeyu/api/modules/common/bean/attachment/Metadata.java @@ -1,4 +1,4 @@ -package com.imyeyu.api.modules.common.bean; +package com.imyeyu.api.modules.common.bean.attachment; import lombok.Data; import lombok.EqualsAndHashCode; @@ -33,10 +33,16 @@ public class Metadata { @EqualsAndHashCode(callSuper = true) public static class ThumbImage extends Image { - private long sourceId; + private String sourceId; private String sourceMongoId; private String sourceMimeType; + + /** 请求宽度(缓存键之一,null 表示未指定) */ + private Integer requestWidth; + + /** 请求高度(缓存键之一,null 表示未指定) */ + private Integer requestHeight; } } diff --git a/src/main/java/com/imyeyu/api/modules/common/bean/attachment/SettingSupport.java b/src/main/java/com/imyeyu/api/modules/common/bean/attachment/SettingSupport.java new file mode 100644 index 0000000..f00cc42 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/common/bean/attachment/SettingSupport.java @@ -0,0 +1,18 @@ +package com.imyeyu.api.modules.common.bean.attachment; + +import com.imyeyu.api.modules.common.entity.Setting; + +import java.util.List; + +/** + * + * + * @author 夜雨 + * @since 2026-05-13 17:27 + */ +public interface SettingSupport { + + String getId(); + + List getSettingList(); +} diff --git a/src/main/java/com/imyeyu/api/modules/common/bean/attachment/TempFileMetaData.java b/src/main/java/com/imyeyu/api/modules/common/bean/attachment/TempFileMetaData.java new file mode 100644 index 0000000..a5fe22a --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/common/bean/attachment/TempFileMetaData.java @@ -0,0 +1,25 @@ +package com.imyeyu.api.modules.common.bean.attachment; + +import lombok.Data; + +/** + * 临时文件元数据 + * + * @author 夜雨 + * @since 2025-09-27 01:47 + */ +@Data +public class TempFileMetaData { + + /** 附件 ID */ + private String id; + + /** 文件名 */ + private String name; + + /** 原始文件名 */ + private String originalName; + + /** 储存 ID */ + private String mongoId; +} diff --git a/src/main/java/com/imyeyu/api/modules/common/controller/AttachmentController.java b/src/main/java/com/imyeyu/api/modules/common/controller/AttachmentController.java new file mode 100644 index 0000000..6eb7dec --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/common/controller/AttachmentController.java @@ -0,0 +1,281 @@ +package com.imyeyu.api.modules.common.controller; + +import com.imyeyu.api.modules.common.entity.Attachment; +import com.imyeyu.api.modules.common.service.AttachmentService; +import com.imyeyu.api.modules.common.service.TempFileService; +import com.imyeyu.api.modules.common.vo.attach.BizDeleteReq; +import com.imyeyu.api.modules.common.vo.attach.BizUpdateReq; +import com.imyeyu.api.modules.common.vo.attach.TempFileResp; +import com.imyeyu.java.TimiJava; +import com.imyeyu.network.Network; +import com.imyeyu.spring.TimiSpring; +import com.imyeyu.spring.annotation.AOPLog; +import com.imyeyu.spring.annotation.IgnoreGlobalReturn; +import com.imyeyu.spring.annotation.RequestRateLimit; +import com.imyeyu.spring.bean.Page; +import com.imyeyu.spring.bean.PageResult; +import com.imyeyu.utils.Time; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; + +import java.io.UnsupportedEncodingException; +import java.util.List; + +/// +/// +/// @author 夜雨 +/// @since 2026-07-27 19:26 +@Slf4j +@RestController +@RequiredArgsConstructor +@RequestMapping("/attach") +public class AttachmentController { + + private final TempFileService tempFileService; + private final AttachmentService service; + + /** + * 查询附件详情 + * + * @param id 附件 ID + * @return 附件详情 + */ + @RequestRateLimit + @PostMapping("/detail") + public Attachment detail(@RequestParam String id) { + return service.get(id); + } + + /** + * 创建附件 + * + * @param attachment 附件 + * @return 创建结果 + */ + @AOPLog + @RequestRateLimit + @PostMapping(value = "/create", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + public Attachment create(Attachment attachment) { + service.create(attachment); + return attachment; + } + + /** + * 更新附件 + * + * @param attachment 附件 + */ + @AOPLog + @RequestRateLimit + @PostMapping(value = "/update", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + public void update(Attachment attachment) { + service.update(attachment); + } + + /** + * 删除附件 + * + * @param id 附件 ID + */ + @AOPLog + @RequestRateLimit + @PostMapping("/delete") + public void delete(@RequestParam String id) { + service.delete(id); + } + + /** + * 按业务查询单个附件 + * + * @param bizType 业务类型 + * @param bizId 业务 ID + * @return 附件详情 + */ + @RequestRateLimit + @GetMapping("/detail/byBiz") + public Attachment detailByBiz(@RequestParam Attachment.BizType bizType, @RequestParam String bizId) { + return service.getByBizId(bizType, bizId); + } + + /** + * 按业务和附件类型查询单个附件 + * + * @param bizType 业务类型 + * @param bizId 业务 ID + * @param attachType 附件类型 + * @return 附件详情 + */ + @RequestRateLimit + @GetMapping("/detail/byAttachType") + public Attachment detailByAttachType( + @RequestParam Attachment.BizType bizType, + @RequestParam String bizId, + @RequestParam String attachType + ) { + return service.getByAttachType(bizType, bizId, attachType); + } + + /** + * 按业务查询附件列表 + * + * @param bizType 业务类型 + * @param bizId 业务 ID + * @param attachTypeList 附件类型列表 + * @return 附件列表 + */ + @RequestRateLimit + @GetMapping("/list/biz") + public List listByBiz( + @RequestParam Attachment.BizType bizType, + @RequestParam String bizId, + @RequestParam(required = false) List attachTypeList + ) { + List types = TimiJava.defaultIfNull(attachTypeList, List.of()); + return service.listByBizId(bizType, bizId, types.toArray(String[]::new)); + } + + /** + * 按业务统计附件数量 + * + * @param bizType 业务类型 + * @param bizId 业务 ID + * @param attachTypeList 附件类型列表 + * @return 附件数量 + */ + @RequestRateLimit + @GetMapping("/count/biz") + public long countByBiz( + @RequestParam Attachment.BizType bizType, + @RequestParam String bizId, + @RequestParam(required = false) List attachTypeList + ) { + List types = TimiJava.defaultIfNull(attachTypeList, List.of()); + return service.countByBizId(bizType, bizId, types.toArray(String[]::new)); + } + + /** + * 按业务分页查询附件 + * + * @param bizType 业务类型 + * @param bizId 业务 ID + * @param attachTypeList 附件类型列表 + * @param page 分页参数 + * @return 分页结果 + */ + @RequestRateLimit + @PostMapping("/page/biz") + public PageResult pageByBiz( + @RequestParam Attachment.BizType bizType, + @RequestParam String bizId, + @RequestParam(required = false) List attachTypeList, + @RequestBody Page page + ) { + return service.pageByBizId(bizType, bizId, attachTypeList, page); + } + + /** + * 按业务差分更新附件 + * + * @param req 更新请求 + */ + @AOPLog + @RequestRateLimit + @PostMapping("/update/biz") + public void updateByBiz(@RequestBody BizUpdateReq req) { + service.updateByBiz(req); + } + + /** + * 按业务删除全部附件 + * + * @param req 删除请求 + */ + @AOPLog + @RequestRateLimit + @PostMapping("/delete/biz") + public void deleteByBiz(@RequestBody BizDeleteReq req) { + service.deleteByBiz(req); + } + + @AOPLog + @RequestRateLimit + @IgnoreGlobalReturn + @GetMapping({"/read/{id}", "/download/{id}"}) + public void readById(@PathVariable String id, @RequestParam(required = false) Integer thumbWidth, @RequestParam(required = false) Integer thumbHeight) throws UnsupportedEncodingException { + HttpServletResponse resp = TimiSpring.getResponse(); + Attachment attach = service.get(id); + if (attach == null) { + resp.setStatus(HttpServletResponse.SC_NOT_FOUND); + return; + } + responseAttachment(attach, thumbWidth, thumbHeight); + } + + @AOPLog + @PostMapping("/temp/upload") + public List upload(@RequestParam("file") List files, @RequestParam(value = "ttl", required = false) String ttl) { + return tempFileService.create(files, Time.D); + } + + @AOPLog + @RequestRateLimit + @IgnoreGlobalReturn + @GetMapping({"/temp/read", "/temp/download"}) + public void read(@RequestParam String id) throws UnsupportedEncodingException { + HttpServletResponse resp = TimiSpring.getResponse(); + Attachment attach = service.get(id); + if (TimiSpring.getRequest().getRequestURI().endsWith("/download")) { + resp.setHeader("Content-Disposition", Network.getFileDownloadHeader(attach.getName())); + } + response(attach); + } + + private void responseAttachment(Attachment attach, Integer thumbWidth, Integer thumbHeight) throws UnsupportedEncodingException { + HttpServletResponse resp = TimiSpring.getResponse(); + if (attach != null && TimiSpring.getRequest().getRequestURI().endsWith("/download") && attach.getName() != null) { + resp.setHeader("Content-Disposition", Network.getFileDownloadHeader(attach.getName())); + } + responseThumb(attach, thumbWidth, thumbHeight); + } + + protected void responseThumb(Attachment attachment, Integer thumbWidth, Integer thumbHeight) { + HttpServletResponse resp = TimiSpring.getResponse(); + if (TimiJava.isEmpty(attachment)) { + resp.setStatus(HttpServletResponse.SC_NOT_FOUND); + return; + } + if (thumbWidth == null && thumbHeight == null) { + // 原图 + response(attachment); + return; + } + // 缩略图 + Attachment thumb = service.fetchThumb(attachment, thumbWidth, thumbHeight); + response(TimiJava.defaultIfNull(thumb, attachment)); + } + + protected void response(Attachment attachment) { + HttpServletResponse resp = TimiSpring.getResponse(); + try { + if (TimiJava.isEmpty(attachment)) { + resp.setStatus(HttpServletResponse.SC_NOT_FOUND); + return; + } + resp.setContentType(attachment.getMimeType()); + attachment.doResponse(); + } catch (Exception e) { + log.error("response attachment error", e); + resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); + } + } +} diff --git a/src/main/java/com/imyeyu/api/modules/common/controller/CommentController.java b/src/main/java/com/imyeyu/api/modules/common/controller/CommentController.java index 3bca552..975922a 100644 --- a/src/main/java/com/imyeyu/api/modules/common/controller/CommentController.java +++ b/src/main/java/com/imyeyu/api/modules/common/controller/CommentController.java @@ -1,87 +1,264 @@ package com.imyeyu.api.modules.common.controller; -import com.imyeyu.api.annotation.CaptchaValid; -import com.imyeyu.api.annotation.EnableSetting; -import com.imyeyu.api.bean.CaptchaFrom; -import com.imyeyu.api.modules.common.bean.SettingKey; +import com.fasterxml.jackson.annotation.JsonView; import com.imyeyu.api.modules.common.entity.Comment; -import com.imyeyu.api.modules.common.entity.CommentReply; -import com.imyeyu.api.modules.common.service.CommentReplyService; +import com.imyeyu.api.modules.user.entity.UserBehaviorRecord; import com.imyeyu.api.modules.common.service.CommentService; +import com.imyeyu.api.modules.common.vo.comment.CommentCreateReq; +import com.imyeyu.api.modules.common.vo.comment.CommentLayerQueryReq; +import com.imyeyu.api.modules.common.vo.comment.CommentLayerResp; +import com.imyeyu.api.modules.common.vo.comment.CommentNodeResp; +import com.imyeyu.api.modules.common.vo.comment.CommentReplyReq; +import com.imyeyu.api.modules.common.vo.comment.CommentThreadResp; +import com.imyeyu.api.modules.common.vo.comment.FlatCommentListResp; +import com.imyeyu.api.modules.common.vo.comment.NestedCommentListResp; +import com.imyeyu.spring.TimiSpring; import com.imyeyu.spring.annotation.AOPLog; import com.imyeyu.spring.annotation.RequestRateLimit; -import com.imyeyu.spring.bean.CaptchaData; import com.imyeyu.spring.bean.Page; import com.imyeyu.spring.bean.PageResult; +import com.imyeyu.spring.util.ResponseView; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; /** * 评论操作接口 - *

*评论回复只依赖评论而不含业务关联,评论有关联业务,所以此接口是通用接口 * * @author 夜雨 * @since 2021-02-23 21:36 */ @Slf4j +@Validated @RestController @RequiredArgsConstructor @RequestMapping("/comment") public class CommentController { private final CommentService service; - private final CommentReplyService replyService; + /** + * 创建主评论 + * + * @param req 创建请求 + * @return 评论树详情 + */ @AOPLog - @CaptchaValid(CaptchaFrom.COMMENT) - @EnableSetting(value = SettingKey.ENABLE_COMMENT, message = "comment.off_service") + @JsonView(ResponseView.Public.class) @RequestRateLimit @PostMapping("/create") - public void create(@Valid @RequestBody CaptchaData captchaData) { - service.create(captchaData.getData()); - } - - @RequestRateLimit - @PostMapping("/list") - public PageResult list(@Valid @RequestBody Page page) { - return service.page(page); + public Comment create(@RequestBody @Valid CommentCreateReq req) { + Comment entity = new Comment(); + entity.setBizType(req.getBizType()); + entity.setBizId(req.getBizId()); + entity.setUserId(req.getUserId()); + entity.setUserNick(req.getUserNick()); + entity.setContent(req.getContent()); + entity.setIp(TimiSpring.getRequestIP()); + service.create(entity); + return service.detailTree(entity.getId(), req.getUserId()); } /** - * 创建评论回复 + * 回复评论 * - * @param request 回复数据 + * @param req 回复请求 + * @return 评论树详情 */ @AOPLog - @CaptchaValid(CaptchaFrom.COMMENT_REPLY) - @EnableSetting(value = SettingKey.ENABLE_COMMENT, message = "comment.off_service") + @JsonView(ResponseView.Public.class) @RequestRateLimit - @PostMapping("/reply/create") - public void createReply(@Valid @RequestBody CaptchaData request) { - replyService.create(request.getData()); + @PostMapping("/reply") + public Comment reply(@RequestBody @Valid CommentReplyReq req) { + Comment entity = new Comment(); + entity.setParentId(req.getParentId()); + entity.setUserId(req.getUserId()); + entity.setUserNick(req.getUserNick()); + entity.setContent(req.getContent()); + entity.setIp(TimiSpring.getRequestIP()); + service.create(entity); + return service.detailTree(entity.getId(), req.getUserId()); } /** - * 获取回复列表 + * 删除当前用户自己的评论 * - * @param page 页面参数 - * @return 回复列表 + * @param id 评论 ID + * @param userId 用户 ID */ + @AOPLog @RequestRateLimit - @RequestMapping("/reply/list") - public PageResult pageReplies(@Valid @RequestBody Page page) { - // 通用接口,只允许查询评论的回复 - CommentReply example = new CommentReply(); - if (page.getEqualsExample() != null) { - example.setCommentId(page.getEqualsExample().getCommentId()); - } - page.setEqualsExample(example); - page.setLikesExample(null); - return replyService.page(page); + @PostMapping("/delete") + public void delete(@RequestParam String id, @RequestParam String userId) { + service.deleteOwned(id, userId); + } + + /** + * 点赞评论 + * + * @param commentId 评论 ID + * @param userId 用户 ID + */ + @AOPLog + @RequestRateLimit + @PostMapping("/like") + public void like(@RequestParam String commentId, @RequestParam String userId) { + service.like(commentId, userId); + } + + /** + * 不喜欢评论 + * + * @param commentId 评论 ID + * @param userId 用户 ID + */ + @AOPLog + @RequestRateLimit + @PostMapping("/unlike") + public void unlike(@RequestParam String commentId, @RequestParam String userId) { + service.unlike(commentId, userId); + } + + /** + * 查询用户点赞评论记录 + * + * @param page 分页参数 + * @param userId 用户 ID + * @return 点赞评论记录分页 + */ + @JsonView(ResponseView.Public.class) + @RequestRateLimit + @PostMapping("/like/list") + public PageResult listLikedRecord(@RequestBody Page page, @RequestParam String userId) { + return service.listLikedRecord(page, userId); + } + + /** + * 查询扁平主评论列表 + * + * @param bizType 业务类型 + * @param bizId 业务 ID + * @param offset 起始偏移量 + * @param currentUserId 当前用户 ID,可为 null + * @return 扁平主评论列表 + */ + @JsonView(ResponseView.Public.class) + @RequestRateLimit + @GetMapping("/flat/list") + public FlatCommentListResp listFlatRoot( + @RequestParam String bizType, + @RequestParam String bizId, + @RequestParam(defaultValue = "0") int offset, + @RequestParam(required = false) String currentUserId + ) { + return service.listFlatRoot(bizType, bizId, offset, currentUserId); + } + + /** + * 查询扁平评论线程 + * + * @param commentId 评论 ID + * @param offset 起始偏移量 + * @param currentUserId 当前用户 ID,可为 null + * @return 评论线程 + */ + @JsonView(ResponseView.Public.class) + @RequestRateLimit + @GetMapping("/flat/thread") + public CommentThreadResp getFlatThread( + @RequestParam String commentId, + @RequestParam(defaultValue = "0") int offset, + @RequestParam(required = false) String currentUserId + ) { + return service.getUserThread(commentId, offset, currentUserId); + } + + /** + * 查询层级主评论列表 + * + * @param bizType 业务类型 + * @param bizId 业务 ID + * @param offset 起始偏移量 + * @param currentUserId 当前用户 ID,可为 null + * @return 层级主评论列表 + */ + @JsonView(ResponseView.Public.class) + @RequestRateLimit + @GetMapping("/nested/list") + public NestedCommentListResp listNestedRoot( + @RequestParam String bizType, + @RequestParam String bizId, + @RequestParam(defaultValue = "0") int offset, + @RequestParam(required = false) String currentUserId + ) { + return service.listNestedRoot(bizType, bizId, offset, currentUserId); + } + + /** + * 查询评论节点 + * + * @param commentId 评论 ID + * @param currentUserId 当前用户 ID,可为 null + * @return 评论节点 + */ + @JsonView(ResponseView.Public.class) + @RequestRateLimit + @GetMapping("/nested/detail") + public CommentNodeResp getNestedDetail(@RequestParam String commentId, @RequestParam(required = false) String currentUserId) { + return service.getUserNode(commentId, currentUserId); + } + + /** + * 查询直属子评论分页 + * + * @param commentId 评论 ID + * @param offset 起始偏移量 + * @param currentUserId 当前用户 ID,可为 null + * @return 子评论分页 + */ + @JsonView(ResponseView.Public.class) + @RequestRateLimit + @GetMapping("/nested/children") + public CommentLayerResp getNestedChildren( + @RequestParam String commentId, + @RequestParam(defaultValue = "0") int offset, + @RequestParam(required = false) String currentUserId + ) { + return service.getUserChildren(commentId, offset, currentUserId); + } + + /** + * 查询单层评论列表 + * + * @param req 查询请求 + * @param currentUserId 当前用户 ID,可为 null + * @return 评论层 + */ + @JsonView(ResponseView.Public.class) + @RequestRateLimit + @PostMapping("/layer/list") + public CommentLayerResp listLayer(@RequestBody CommentLayerQueryReq req, @RequestParam(required = false) String currentUserId) { + return service.listSdkLayer(req, currentUserId); + } + + /** + * 查询评论树详情 + * + * @param id 评论 ID + * @param currentUserId 当前用户 ID,可为 null + * @return 评论树详情 + */ + @JsonView(ResponseView.Public.class) + @RequestRateLimit + @GetMapping("/detail/tree") + public Comment detailTree(@RequestParam String id, @RequestParam(required = false) String currentUserId) { + return service.detailTree(id, currentUserId); } } diff --git a/src/main/java/com/imyeyu/api/modules/common/controller/CommonController.java b/src/main/java/com/imyeyu/api/modules/common/controller/CommonController.java index 4f5ce6f..a7ce26d 100644 --- a/src/main/java/com/imyeyu/api/modules/common/controller/CommonController.java +++ b/src/main/java/com/imyeyu/api/modules/common/controller/CommonController.java @@ -1,78 +1,23 @@ package com.imyeyu.api.modules.common.controller; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.imyeyu.api.bean.CaptchaFrom; -import com.imyeyu.api.modules.common.bean.ImageType; -import com.imyeyu.api.modules.common.bean.SettingKey; -import com.imyeyu.api.modules.common.entity.Attachment; -import com.imyeyu.api.modules.common.entity.Setting; -import com.imyeyu.api.modules.common.entity.Task; -import com.imyeyu.api.modules.common.entity.Template; -import com.imyeyu.api.modules.common.entity.Version; -import com.imyeyu.api.modules.common.service.AttachmentService; -import com.imyeyu.api.modules.common.service.ClipboardService; -import com.imyeyu.api.modules.common.service.FeedbackService; -import com.imyeyu.api.modules.common.service.SettingService; -import com.imyeyu.api.modules.common.service.TaskService; -import com.imyeyu.api.modules.common.service.TempFileService; -import com.imyeyu.api.modules.common.service.TemplateService; -import com.imyeyu.api.modules.common.service.VersionService; -import com.imyeyu.api.modules.common.vo.ClipboardRequest; -import com.imyeyu.api.modules.common.vo.FeedbackRequest; -import com.imyeyu.api.modules.common.vo.TempFileResponse; -import com.imyeyu.api.modules.system.util.ResourceHandler; import com.imyeyu.api.util.CaptchaManager; import com.imyeyu.io.IO; -import com.imyeyu.java.TimiJava; import com.imyeyu.java.bean.timi.TimiCode; import com.imyeyu.java.bean.timi.TimiException; -import com.imyeyu.java.ref.Ref; -import com.imyeyu.network.Network; import com.imyeyu.spring.TimiSpring; import com.imyeyu.spring.annotation.AOPLog; -import com.imyeyu.spring.annotation.IgnoreGlobalReturn; -import com.imyeyu.spring.annotation.RequestRateLimit; -import com.imyeyu.spring.bean.CaptchaData; -import com.imyeyu.spring.bean.RequestRange; -import com.mongodb.client.gridfs.GridFSBucket; -import com.mongodb.client.gridfs.GridFSDownloadStream; -import com.mongodb.client.gridfs.model.GridFSFile; -import jakarta.servlet.http.HttpServletRequest; +import com.imyeyu.utils.Decoder; +import jakarta.servlet.ServletOutputStream; import jakarta.servlet.http.HttpServletResponse; -import jakarta.validation.Valid; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; -import lombok.Cleanup; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.apache.tika.Tika; -import org.springframework.data.mongodb.gridfs.GridFsResource; -import org.springframework.http.MediaType; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.multipart.MultipartFile; -import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; -import org.yaml.snakeyaml.Yaml; import javax.imageio.ImageIO; -import java.awt.Graphics2D; -import java.awt.RenderingHints; -import java.awt.image.BufferedImage; -import java.io.ByteArrayInputStream; import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; /** * 系统接口 @@ -87,20 +32,7 @@ import java.util.stream.Collectors; @RequestMapping("/") public class CommonController { - private final TaskService taskService; - private final VersionService versionService; - private final SettingService settingService; - private final FeedbackService feedbackService; - private final TemplateService templateService; - private final TempFileService tempFileService; - private final AttachmentService attachmentService; - private final ClipboardService clipboardService; - - private final ObjectMapper jackson; - private final Yaml yaml; - private final GridFSBucket gridFSBucket; private final CaptchaManager captchaManager; - private final ResourceHandler resourceHandler; @AOPLog @RequestMapping("") @@ -108,399 +40,56 @@ public class CommonController { return "IT WORKING! " + TimiSpring.getRequestIP(); } - /** - * 获取共享剪切板内容 - * - * @param id 会话 ID - * @return 剪切板内容 - */ - @AOPLog - @RequestRateLimit - @GetMapping("/clipboard/{id}") - public String clipboardGet(@Valid @NotBlank @PathVariable("id") String id) { - return clipboardService.getContent(id); - } - - /** - * 设置共享剪切板内容 - * - * @param id 会话 ID - * @param request 请求内容 - */ - @AOPLog - @RequestRateLimit - @PostMapping("/clipboard/{id}") - public void clipboardSet(@Valid @NotBlank @PathVariable("id") String id, @Valid @RequestBody ClipboardRequest request) { - clipboardService.setContent(id, request.getContent()); - } - - /** - * 订阅共享剪切板实时更新 - * - * @param id 会话 ID - * @return SSE 发射器 - */ - @AOPLog - @IgnoreGlobalReturn - @GetMapping(value = "/clipboard/stream/{id}", produces = MediaType.TEXT_EVENT_STREAM_VALUE) - public SseEmitter clipboardStream(@Valid @NotBlank @PathVariable("id") String id) { - return clipboardService.subscribe(id); - } - /** * 获取验证码 * - * @param width 宽度 - * @param height 高度 - * @param from 来源 - * @param response 返回对象 + * @param width 宽度 + * @param height 高度 + * @return Base64 编码的图形验证码和唯一识别 ID */ - @IgnoreGlobalReturn @GetMapping("/captcha") - public void captcha(int width, int height, CaptchaFrom from, HttpServletResponse response) { - // 返回图像流 - response.setHeader("Pragma", "no-cache"); - response.setHeader("Cache-Control", "no-cache"); // 禁止缓存 - response.setDateHeader("Expires", -1); - response.setContentType("image/jpg"); + public CaptchaManager.Result captcha(Integer width, Integer height) { + TimiException.requiredTrue(width != null && 64 < width, "not found width or less than 64"); + TimiException.requiredTrue(height != null && 19 < height, "not found height or less than 19"); + return captchaManager.generate(width, height); + } + + /** + * 获取验证码(返回图形数据流,唯一识别 ID 在返回头 X-Captcha-ID) + * + * @param width 宽度 + * @param height 高度 + */ + @GetMapping("/captcha/img") + public void captchaImg(Integer width, Integer height, HttpServletResponse resp) { + CaptchaManager.Result result = captchaManager.generate(width, height); + resp.setHeader("Pragma", "no-cache"); + resp.setHeader("Cache-Control", "no-cache"); + resp.setHeader("X-Captcha-ID", result.id()); + resp.setHeader("Access-Control-Expose-Headers", "X-Captcha-ID"); + resp.setDateHeader("Expires", -1); + resp.setContentType("image/png"); try { - // 宽度 + ServletOutputStream os = resp.getOutputStream(); if (width < 64) { - ImageIO.write(captchaManager.error(TimiCode.ARG_BAD), "jpg", response.getOutputStream()); + ImageIO.write(captchaManager.error(TimiCode.ARG_BAD), "png", os); return; } // 高度 if (height < 19) { - ImageIO.write(captchaManager.error(TimiCode.ARG_BAD), "jpg", response.getOutputStream()); - return; - } - // 来自 - if (TimiJava.isEmpty(from)) { - ImageIO.write(captchaManager.error(TimiCode.ARG_MISS), "jpg", response.getOutputStream()); + ImageIO.write(captchaManager.error(TimiCode.ARG_BAD), "png", os); return; } // 输出图像流 - ImageIO.write(captchaManager.generate(from, width, height), "jpg", response.getOutputStream()); - } catch (Exception e) { - log.error("CommonController.getCaptcha", e); + IO.toOutputStream(os, Decoder.base64(result.data().split(",")[1])); + } catch (IOException e) { + log.error("captcha img error", e); + resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); try { - ImageIO.write(captchaManager.error(TimiCode.ERROR), "jpg", response.getOutputStream()); + ImageIO.write(captchaManager.error(TimiCode.ERROR), "png", resp.getOutputStream()); } catch (IOException subE) { log.error("write error image fail", subE); } } } - - /** - * 获取软件最新版本状态 - * - * @param name 软件名称 - * @return 最新版本状态 - * @deprecated 兼容旧程序 - */ - @AOPLog - @GetMapping("/versions/{name}") - @Deprecated - public Version versions(@Valid @NotBlank @PathVariable("name") String name) { - return versionService.getByName(name); - } - - /** - * 获取软件最新版本状态 - * - * @param name 软件名称 - * @return 最新版本状态 - */ - @AOPLog - @GetMapping("/version/{name}") - public Version version(@Valid @NotBlank @PathVariable("name") String name) { - return versionService.getByName(name); - } - - @AOPLog - @RequestRateLimit - @PostMapping("/feedback") - public void createFeedback(@Valid @NotNull @RequestBody CaptchaData request) { - captchaManager.test(request.getCaptcha(), request.getCaptchaId()); - feedbackService.create(request.getData()); - } - - /** @return 公开任务信息 */ - @AOPLog - @RequestRateLimit - @GetMapping("/tasklist") - public List getTasks() { - return taskService.listAll4Public(); - } - - @RequestRateLimit - @GetMapping("/template") - public String viewTemplate(@RequestParam Template.BizType bizType, @RequestParam String bizCode) { - return templateService.get(bizType, bizCode).getData(); - } - - @RequestRateLimit - @GetMapping("/setting/{key}") - public String settingByKey(@PathVariable("key") String key, @RequestParam(value = "as", required = false) Setting.Type asType) throws JsonProcessingException { - Setting setting = settingService.getByKey(SettingKey.valueOf(key.toUpperCase())); - if (setting.isPrivate()) { - throw new TimiException(TimiCode.PERMISSION_ERROR); - } - String result = setting.getValue(); - if (asType == null) { - return result; - } - switch (asType) { - case JSON -> { - if (setting.getType() == Setting.Type.YAML) { - Map obj = yaml.load(setting.getValue()); - result = jackson.writeValueAsString(obj); - } - } - case YAML -> { - if (setting.getType() == Setting.Type.JSON) { - Map obj = jackson.readValue(setting.getValue(), new TypeReference<>() {}); - result = yaml.dump(obj); - } - } - } - return result; - } - - @RequestRateLimit - @PostMapping("/setting/map") - public Map mapSettingByKeys(@RequestBody Map> settingMap) throws JsonProcessingException { - List result = settingService.listByKeys(new ArrayList<>(settingMap.keySet())); - for (int i = 0; i < result.size(); i++) { - Setting setting = result.get(i); - if (setting.isPrivate()) { - throw new TimiException(TimiCode.PERMISSION_ERROR); - } - Map args = settingMap.get(setting.getKey()); - if (args == null) { - continue; - } - if (args.containsKey("as")) { - switch (Ref.toType(Setting.Type.class, args.get("as").toString())) { - case JSON -> { - if (setting.getType() == Setting.Type.YAML) { - Map obj = new Yaml().load(setting.getValue()); - setting.setValue(jackson.writeValueAsString(obj)); - } - } - case YAML -> { - if (setting.getType() == Setting.Type.JSON) { - Map obj = jackson.readValue(setting.getValue(), new TypeReference<>() {}); - setting.setValue(new Yaml().dump(obj)); - } - } - } - } - } - return result.stream().collect(Collectors.toMap(Setting::getKey, Setting::getValue)); - } - - @AOPLog - @RequestRateLimit - @GetMapping("/attachment/{mongoId}") - public Attachment getAttachment(@PathVariable String mongoId) { - return attachmentService.getByMongoId(mongoId); - } - - @AOPLog - @IgnoreGlobalReturn - @GetMapping("/attachment/read/{mongoId}") - public void readAttachment( - @PathVariable String mongoId, - @RequestParam(name = "size", required = false) Integer size, - @RequestParam(name = "type", required = false) String type, - HttpServletRequest req, - HttpServletResponse resp - ) { - try { - GridFSFile file = attachmentService.readByMongoId(mongoId); - if (file == null) { - resp.setStatus(HttpServletResponse.SC_NOT_FOUND); - resp.setCharacterEncoding(StandardCharsets.UTF_8.toString()); - return; - } - GridFSDownloadStream mimeReadStream = gridFSBucket.openDownloadStream(file.getObjectId()); - String mimeType = new Tika().detect(mimeReadStream); - if (TimiJava.isNotEmpty(mimeType)) { - resp.setContentType(mimeType); - } - if (size != null) { - String fileType = switch (mimeType) { - case "image/png" -> "png"; - case "image/jpeg" -> "jpg"; - default -> throw new TimiException(TimiCode.ARG_BAD).msgKey("TODO not support Re render mineType"); - }; - - switch (mimeType) { - case "image/png", "image/jpeg" -> { - // 图片缩放 - GridFSDownloadStream stream = gridFSBucket.openDownloadStream(file.getObjectId()); - byte[] bytes = IO.toBytes(stream); - BufferedImage imgSrc = ImageIO.read(new ByteArrayInputStream(bytes)); - - double scale; - if (imgSrc.getHeight() < imgSrc.getWidth()) { - // 横向 - scale = 1D * size / imgSrc.getWidth(); - } else { - scale = 1D * size / imgSrc.getHeight(); - } - int width = (int) (imgSrc.getWidth() * scale); - int height = (int) (imgSrc.getHeight() * scale); - BufferedImage imgResult = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); - Graphics2D g = imgResult.createGraphics(); - if (ImageType.PIXELATED == Ref.toType(ImageType.class, type)) { - g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR); - } else { - g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC); - g.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY); - g.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY); - g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); - g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); - } - g.drawImage(imgSrc, 0, 0, width, height, null); - ImageIO.write(imgResult, fileType, resp.getOutputStream()); - } - default -> throw new TimiException(TimiCode.ARG_BAD).msgKey("TODO not support Re render mineType"); - } - } else { - GridFSDownloadStream downloadStream = gridFSBucket.openDownloadStream(file.getObjectId()); - GridFsResource gridFsResource = new GridFsResource(file, downloadStream); - req.setAttribute(ResourceHandler.ATTR_TYPE, ResourceHandler.Type.MONGO); - req.setAttribute(ResourceHandler.ATTR_VALUE, gridFsResource); - resourceHandler.handleRequest(req, resp); - } - } catch (Exception e) { - log.error("read attachment error", e); - resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); - resp.setCharacterEncoding(StandardCharsets.UTF_8.toString()); - } - } - - @AOPLog - @RequestRateLimit - @IgnoreGlobalReturn - @GetMapping("/attachment/download/{mongoId}") - public void downloadAttachment(@PathVariable String mongoId, HttpServletResponse resp) { - try { - Attachment attachment = attachmentService.getByMongoId(mongoId); - GridFSFile file = attachmentService.readByMongoId(mongoId); - if (file == null) { - resp.setStatus(HttpServletResponse.SC_NOT_FOUND); - resp.setCharacterEncoding(StandardCharsets.UTF_8.toString()); - return; - } - { - GridFSDownloadStream downloadStream = gridFSBucket.openDownloadStream(file.getObjectId()); - String mimeType = new Tika().detect(downloadStream); - if (TimiJava.isNotEmpty(mimeType)) { - resp.setContentType(mimeType); - } - } - GridFSDownloadStream downloadStream = gridFSBucket.openDownloadStream(file.getObjectId()); - resp.setHeader("Content-Disposition", Network.getFileDownloadHeader(file.getFilename())); - resp.setHeader("Content-Range", String.valueOf(attachment.getSize())); - resp.setHeader("Accept-Ranges", "bytes"); - resp.setContentLengthLong(attachment.getSize()); - IO.toOutputStream(downloadStream, resp.getOutputStream()); - resp.flushBuffer(); - } catch (Exception e) { - log.error("read attachment error", e); - resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); - resp.setCharacterEncoding(StandardCharsets.UTF_8.toString()); - } - } - - /** - * 上传临时文件 - * - * @param files 文件列表 - * @param ttl 缓存时长(毫秒),最长 3 天(259200000ms) - * @return 临时文件响应列表 - */ - @AOPLog - @PostMapping("/temp/file/upload") - public List tempFileUpload(@RequestParam("file") List files, @RequestParam(value = "ttl", required = false) Long ttl) { - return tempFileService.store(files, ttl); - } - - /** - * 读取临时文件 - * - * @param mongoId mongoId - * @param req 请求 - * @param resp 返回 - */ - @AOPLog - @RequestRateLimit - @IgnoreGlobalReturn - @GetMapping("/temp/file/read/{mongoId}") - public void tempFileRead(@PathVariable String mongoId, HttpServletRequest req, HttpServletResponse resp) { - try { - Attachment attach = attachmentService.getByMongoId(mongoId); - if (TimiJava.isEmpty(attach)) { - resp.setStatus(HttpServletResponse.SC_NOT_FOUND); - return; - } - GridFSFile file = attachmentService.readByMongoId(attach.getMongoId()); - GridFSDownloadStream downloadStream = gridFSBucket.openDownloadStream(file.getObjectId()); - GridFsResource gridFsResource = new GridFsResource(file, downloadStream); - req.setAttribute(ResourceHandler.ATTR_TYPE, ResourceHandler.Type.MONGO); - req.setAttribute(ResourceHandler.ATTR_VALUE, gridFsResource); - - resp.setContentType(attach.getMimeType()); - resourceHandler.handleRequest(req, resp); - } catch (Exception e) { - resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); - } - } - - /** - * 下载临时文件 - * - * @param mongoId - * @param resp - */ - @AOPLog - @RequestRateLimit - @IgnoreGlobalReturn - @RequestMapping("/temp/file/download/{mongoId}") - public void tempFileDownload(@PathVariable String mongoId, HttpServletResponse resp) { - try { - Attachment attach = attachmentService.getByMongoId(mongoId); - if (TimiJava.isEmpty(attach)) { - resp.setStatus(HttpServletResponse.SC_NOT_FOUND); - return; - } - resp.setContentType(attach.getMimeType()); - resp.setHeader("Content-Disposition", Network.getFileDownloadHeader(attach.getName())); - resp.setHeader("Accept-Ranges", "bytes"); - - GridFSFile file = attachmentService.readByMongoId(mongoId); - @Cleanup - GridFSDownloadStream downloadStream = gridFSBucket.openDownloadStream(file.getObjectId()); - RequestRange range = TimiSpring.getRequestRange(attach.getSize()); - if (range == null) { - // 完整文件 - resp.setContentLengthLong(attach.getSize()); - resp.setStatus(HttpServletResponse.SC_OK); - IO.toOutputStream(downloadStream, resp.getOutputStream()); - } else { - // 分片文件 - resp.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT); - resp.setHeader("Content-Range", "bytes %s-%s/%s".formatted(range.getStart(), range.getEnd(), attach.getSize())); - resp.setContentLengthLong(range.getLength()); - IO.toOutputStream(downloadStream, resp.getOutputStream(), range.getStart(), range.getEnd()); - resp.flushBuffer(); - } - } catch (Exception e) { - log.error("download error", e); - resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); - } - } } diff --git a/src/main/java/com/imyeyu/api/modules/common/controller/IconController.java b/src/main/java/com/imyeyu/api/modules/common/controller/IconController.java index 2c23c04..0fcb6db 100644 --- a/src/main/java/com/imyeyu/api/modules/common/controller/IconController.java +++ b/src/main/java/com/imyeyu/api/modules/common/controller/IconController.java @@ -1,26 +1,19 @@ package com.imyeyu.api.modules.common.controller; -import com.imyeyu.api.modules.common.entity.Icon; import com.imyeyu.api.modules.common.service.IconService; -import com.imyeyu.api.modules.common.vo.icon.AllResponse; -import com.imyeyu.api.modules.common.vo.icon.NamePage; -import com.imyeyu.api.modules.common.vo.icon.UnicodePage; -import com.imyeyu.spring.annotation.AOPLog; -import com.imyeyu.spring.annotation.RequestRateLimit; -import com.imyeyu.spring.bean.Page; -import com.imyeyu.spring.bean.PageResult; -import jakarta.validation.Valid; +import com.imyeyu.network.Network; +import com.imyeyu.spring.annotation.IgnoreGlobalReturn; +import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; +import java.io.IOException; import java.util.List; +import java.util.Map; /** * timi-icon 前端接口 @@ -38,60 +31,38 @@ public class IconController { private final IconService service; /** - * 获取图标列表 + * 查询全部图标名称 * - * @param page 查询列表参数 - * @return 图标列表 + * @return 图标名称列表 */ - @RequestRateLimit - @PostMapping("/list") - public PageResult list(@RequestBody Page page) { - return service.page(page); + @GetMapping("/name/list") + public List listName() { + return service.listName(); + } + + @GetMapping("/svg/map") + public Map mapSvg() { + return service.mapSvg(); + } + + @GetMapping("/unicode/map") + public Map mapUnicode() { + return service.mapUnicode(); } /** - * 获取所有图标,为了减小传输数据,此接口只返回 name 名称、Unicode 代码和 SVG 路径 + * 导出 TTF 字体文件 * - * @param latest 请求缓存的最新数据时间 - * @return 所有图标,如果请求缓存的最新时间等于数据库的最新数据时间,不返回任何数据 + * @param resp HTTP 响应 + * @throws IOException 写出字体文件失败 */ - @RequestRateLimit - @GetMapping("/list/all") - public AllResponse listAll(@Valid @RequestParam Long latest) { - AllResponse resp = service.listAll(latest); - List icons = resp.getIcons(); - for (int i = 0; i < icons.size(); i++) { - Icon icon = icons.get(i); - icon.setId(null); - icon.setCreatedAt(null); - icon.setUpdatedAt(null); - } - return resp; - } - - /** - * 根据名称获取查询列表参数列表 - * - * @param page 查询列表参数 - * @return 图标列表 - */ - @AOPLog - @RequestRateLimit - @PostMapping("/list/name") - public PageResult listByName(@Valid @RequestBody NamePage page) { - return service.pageByName(page); - } - - /** - * 根据 Unicode 获取图标列表 - * - * @param page 查询列表参数 - * @return 图标列表 - */ - @AOPLog - @RequestRateLimit - @PostMapping("/list/unicode") - public PageResult listByUnicode(@Valid @RequestBody UnicodePage page) { - return service.pageByUnicode(page); + @IgnoreGlobalReturn + @GetMapping(value = "/export/ttf", produces = "font/ttf") + public void exportTtf(HttpServletResponse resp) throws IOException { + byte[] body = service.exportTtf(); + resp.setContentType("font/ttf"); + resp.setContentLength(body.length); + resp.setHeader("Content-Disposition", Network.getFileDownloadHeader("timi-icon.ttf")); + resp.getOutputStream().write(body); } } diff --git a/src/main/java/com/imyeyu/api/modules/common/controller/LoggerController.java b/src/main/java/com/imyeyu/api/modules/common/controller/LoggerController.java new file mode 100644 index 0000000..4fc539f --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/common/controller/LoggerController.java @@ -0,0 +1,35 @@ +package com.imyeyu.api.modules.common.controller; + +import com.imyeyu.api.modules.system.entity.Logger; +import com.imyeyu.api.modules.system.service.LoggerService; +import com.imyeyu.spring.annotation.AOPLog; +import com.imyeyu.spring.annotation.RequiredToken; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * + * + * @author 夜雨 + * @since 2026-06-13 09:31 + */ +@Slf4j +@RestController +@RequiredArgsConstructor +@RequestMapping("/log") +public class LoggerController { + + private final LoggerService service; + + @AOPLog + @RequiredToken + @PostMapping("/create") + public void create(@Valid @RequestBody Logger logger) { + service.create(logger); + } +} \ No newline at end of file diff --git a/src/main/java/com/imyeyu/api/modules/common/controller/MultilingualController.java b/src/main/java/com/imyeyu/api/modules/common/controller/MultilingualController.java new file mode 100644 index 0000000..db002a3 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/common/controller/MultilingualController.java @@ -0,0 +1,94 @@ +package com.imyeyu.api.modules.common.controller; + +import com.fasterxml.jackson.annotation.JsonView; +import com.imyeyu.api.annotation.RequireCorePermission; +import com.imyeyu.api.annotation.RequireCoreRole; +import com.imyeyu.api.bean.CorePermissionCode; +import com.imyeyu.api.bean.CoreRoleCode; +import com.imyeyu.api.modules.common.entity.Multilingual; +import com.imyeyu.api.modules.common.service.MultilingualService; +import com.imyeyu.spring.annotation.AOPLog; +import com.imyeyu.spring.annotation.RequestRateLimit; +import com.imyeyu.spring.util.ResponseView; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +/** + * 多语言管理接口 + * + * @author Codex + * @since 2026-07-27 + */ +@Slf4j +@RestController +@RequiredArgsConstructor +@RequestMapping("/system/multilingual") +public class MultilingualController { + + private final MultilingualService service; + + /** + * 查询多语言详情 + * + * @param id 多语言 ID + * @return 多语言详情 + */ + @JsonView(ResponseView.Admin.class) + @RequestRateLimit + @RequireCoreRole(CoreRoleCode.ADMIN) + @RequireCorePermission(CorePermissionCode.MULTILINGUAL_READ) + @PostMapping("/detail") + public Multilingual detail(@RequestParam String id) { + return service.get(id); + } + + /** + * 创建多语言 + * + * @param multilingual 多语言 + * @return 创建结果 + */ + @AOPLog + @JsonView(ResponseView.Admin.class) + @RequestRateLimit + @RequireCoreRole(CoreRoleCode.ADMIN) + @RequireCorePermission(CorePermissionCode.MULTILINGUAL_CREATE) + @PostMapping("/create") + public Multilingual create(@RequestBody Multilingual multilingual) { + service.create(multilingual); + return multilingual; + } + + /** + * 更新多语言 + * + * @param multilingual 多语言 + */ + @AOPLog + @RequestRateLimit + @RequireCoreRole(CoreRoleCode.ADMIN) + @RequireCorePermission(CorePermissionCode.MULTILINGUAL_UPDATE) + @PostMapping("/update") + public void update(@RequestBody Multilingual multilingual) { + service.update(multilingual); + } + + /** + * 删除多语言 + * + * @param id 多语言 ID + */ + @AOPLog + @RequestRateLimit + @RequireCoreRole(CoreRoleCode.ADMIN) + @RequireCorePermission(CorePermissionCode.MULTILINGUAL_DELETE) + @PostMapping("/delete") + public void delete(@RequestParam String id) { + service.delete(id); + } +} diff --git a/src/main/java/com/imyeyu/api/modules/common/controller/SettingController.java b/src/main/java/com/imyeyu/api/modules/common/controller/SettingController.java new file mode 100644 index 0000000..eeb1b6c --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/common/controller/SettingController.java @@ -0,0 +1,100 @@ +package com.imyeyu.api.modules.common.controller; + +import com.fasterxml.jackson.annotation.JsonView; +import com.imyeyu.api.annotation.RequireCorePermission; +import com.imyeyu.api.bean.CorePermissionCode; +import com.imyeyu.api.modules.common.entity.Setting; +import com.imyeyu.api.modules.common.service.SettingService; +import com.imyeyu.spring.annotation.AOPLog; +import com.imyeyu.spring.annotation.RequestRateLimit; +import com.imyeyu.spring.bean.Page; +import com.imyeyu.spring.bean.PageResult; +import com.imyeyu.spring.util.ResponseView; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * + * + * @author 夜雨 + * @since 2026-04-23 16:31 + */ +@Slf4j +@RestController +@RequiredArgsConstructor +@RequestMapping("/system/setting") +public class SettingController { + + private final SettingService service; + + /** + * 查询配置列表 + * + * @param page 分页查询 + * @return 配置列表 + */ + @JsonView(ResponseView.Admin.class) + @RequestRateLimit + @RequireCorePermission(CorePermissionCode.SETTING_READ) + @PostMapping("/list") + public PageResult list(@RequestBody Page page) { + return service.page(page); + } + + /** + * 查询配置详情 + * + * @param id 配置 ID + * @return 配置详情 + */ + @JsonView(ResponseView.Admin.class) + @RequestRateLimit + @PostMapping("/detail") + public Setting detail(@RequestBody String id) { + return service.get(id); + } + + /** + * 创建配置 + * + * @param setting 配置 + * @return 配置 + */ + @AOPLog + @JsonView(ResponseView.Admin.class) + @RequestRateLimit + @PostMapping("/create") + public Setting create(@RequestBody Setting setting) { + service.create(setting); + return setting; + } + + /** + * 更新配置 + * + * @param setting 配置 + */ + @AOPLog + @RequestRateLimit + @PostMapping("/update") + public void update(@RequestBody Setting setting) { + service.update(setting); + } + + /** + * 删除配置 + * + * @param id 配置 ID + */ + @AOPLog + @RequestRateLimit + @RequireCorePermission(CorePermissionCode.SETTING_DELETE) + @PostMapping("/delete") + public void delete(@RequestBody String id) { + service.delete(id); + } +} diff --git a/src/main/java/com/imyeyu/api/modules/common/controller/UserController.java b/src/main/java/com/imyeyu/api/modules/common/controller/UserController.java deleted file mode 100644 index d7bb4e8..0000000 --- a/src/main/java/com/imyeyu/api/modules/common/controller/UserController.java +++ /dev/null @@ -1,316 +0,0 @@ -package com.imyeyu.api.modules.common.controller; - -import com.imyeyu.api.annotation.CaptchaValid; -import com.imyeyu.api.annotation.EnableSetting; -import com.imyeyu.api.bean.CaptchaFrom; -import com.imyeyu.api.modules.common.bean.SettingKey; -import com.imyeyu.api.modules.common.entity.Comment; -import com.imyeyu.api.modules.common.entity.CommentReply; -import com.imyeyu.api.modules.common.entity.UserConfig; -import com.imyeyu.api.modules.common.entity.UserPrivacy; -import com.imyeyu.api.modules.common.service.CommentReplyService; -import com.imyeyu.api.modules.common.service.CommentService; -import com.imyeyu.api.modules.common.service.UserConfigService; -import com.imyeyu.api.modules.common.service.UserPrivacyService; -import com.imyeyu.api.modules.common.service.UserProfileService; -import com.imyeyu.api.modules.common.service.UserService; -import com.imyeyu.api.modules.common.vo.user.EmailVerifyCallbackRequest; -import com.imyeyu.api.modules.common.vo.user.LoginRequest; -import com.imyeyu.api.modules.common.vo.user.LoginResponse; -import com.imyeyu.api.modules.common.vo.user.RegisterRequest; -import com.imyeyu.api.modules.common.vo.user.UpdatePasswordByKeyRequest; -import com.imyeyu.api.modules.common.vo.user.UpdatePasswordRequest; -import com.imyeyu.api.modules.common.vo.user.UserRequest; -import com.imyeyu.api.modules.common.vo.user.UserView; -import com.imyeyu.java.TimiJava; -import com.imyeyu.java.bean.timi.TimiException; -import com.imyeyu.spring.annotation.AOPLog; -import com.imyeyu.spring.annotation.RequestRateLimit; -import com.imyeyu.spring.annotation.RequiredToken; -import com.imyeyu.spring.bean.CaptchaData; -import com.imyeyu.spring.bean.Page; -import com.imyeyu.spring.bean.PageResult; -import com.imyeyu.utils.Time; -import jakarta.validation.Valid; -import jakarta.validation.constraints.Min; -import jakarta.validation.constraints.NotNull; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -/** - * 用户接口 - * - * @author 夜雨 - * @since 2021-02-23 21:38 - */ -@Slf4j -@Validated -@RestController -@RequiredArgsConstructor -@RequestMapping("/user") -public class UserController implements TimiJava { - - private final UserService service; - private final CommentService commentService; - private final UserConfigService configService; - private final UserProfileService profileService; - private final UserPrivacyService privacyService; - private final CommentReplyService commentReplyService; - - /** - * 注册。执行成功会自动登录 - * - * @param request 注册请求 - * @return 登录数据 - */ - @AOPLog - @CaptchaValid(CaptchaFrom.REGISTER) - @EnableSetting(value = SettingKey.ENABLE_REGISTER, message = "user.register.off_service") - @RequestRateLimit(value = 1, lifeCycle = 60) - @PostMapping("/register") - public LoginResponse register(@Valid @RequestBody CaptchaData request) { - return service.register(request.getData()); - } - - /** - * 登录 - * - * @param request 登录请求 - * @return 登录数据 - */ - @AOPLog - @CaptchaValid(CaptchaFrom.LOGIN) - @EnableSetting(value = SettingKey.ENABLE_LOGIN, message = "user.login.off_service") - @RequestRateLimit - @PostMapping("/login") - public LoginResponse login(@Valid @RequestBody CaptchaData request) { - return service.login(request.getData()); - } - - /** - * 根据令牌登录,请求头携带 Token 参数,通常用于延续登录令牌 - * - * @return 登录数据 - */ - @AOPLog - @EnableSetting(value = SettingKey.ENABLE_LOGIN, message = "user.login.off_service") - @RequestRateLimit - @PostMapping("/login/token") - public LoginResponse login4Token() { - return service.login4Token(); - } - - /** 登出 */ - @AOPLog - @RequestRateLimit - @PostMapping("/logout") - public void logout() { - service.logout(); - } - - /** 发送邮箱验证邮件 */ - @AOPLog - @RequiredToken - @RequestRateLimit(value = 1, lifeCycle = 50) - @PostMapping("/email/verify") - public void sendEmailVerify() { - service.sendEmailVerify(); - } - - /** - * 邮箱验证邮件回调,验证请求的密钥来源于 {@link #sendEmailVerify()} 接口发送的邮件 - * - * @param request 邮箱验证请求 - */ - @AOPLog - @RequiredToken - @RequestRateLimit(value = 1, lifeCycle = 50) - @PostMapping("/email/verify/callback") - public void emailVerifyCallback(@Valid @RequestBody EmailVerifyCallbackRequest request) { - service.emailVerifyCallback(request.getKey()); - } - - /** - * 修改密码,需要已登录状态,使用旧密码修改 - * - * @param request 修改密码请求 - */ - @AOPLog - @RequiredToken - @RequestRateLimit - @PostMapping("/password/update") - public void updatePassword(@Valid @RequestBody UpdatePasswordRequest request) { - service.updatePassword(request.getOldValue(), request.getNewValue()); - } - - /** - * 发送用于重置密码的忘记密码邮件,入参数据可能是 UID、邮箱或用户名,该数据目标用户的邮箱需要通过验证 - * - * @param request 忘记密码邮件请求 - */ - @AOPLog - @CaptchaValid(CaptchaFrom.RESET_PASSWORD) - @RequestRateLimit(value = 1, lifeCycle = 50) - @PostMapping("/password/forget") - public void sendPasswordForgetVerify(@Valid @RequestBody CaptchaData request) { - service.sendPasswordForgetVerify(request.getData()); - } - - /** - * 修改密码,不需要登录状态,入参数据的密钥来源于 {@link #sendPasswordForgetVerify(CaptchaData)} 接口发送的邮件 - * - * @param request 重置密码请求 - */ - @AOPLog - @RequestRateLimit(value = 1, lifeCycle = 50) - @PostMapping("/password/reset") - public void resetPasswordByKey(@Valid @RequestBody UpdatePasswordByKeyRequest request) { - service.resetPasswordByKey(request.getKey(), request.getNewPassword()); - } - - /** - * 注销账号,此操作将会标记此用户的所有数据为删除状态 - * - * @param password 密码 - */ - @AOPLog - @RequiredToken - @RequestRateLimit - @PostMapping("/cancel") - public void cancel(@RequestBody String password) { - service.cancel(password); - } - - /** - * 获取用户资料 - * - * @param userId 目标用户 ID - * @return 用户资料 - */ - @AOPLog - @RequestRateLimit - @PostMapping("/view/{userId}") - public UserView view(@Min(1) @NotNull @PathVariable Long userId) throws Exception { - return service.view(userId).doFilter(); - } - - /** - * 更新用户数据 - * - * @param data 用户数据(包括账号数据) - */ - @AOPLog - @RequiredToken - @EnableSetting(value = SettingKey.ENABLE_USER_UPDATE, message = "user.data.off_service") - @RequestRateLimit - @PostMapping("/profile/update") - public void updateProfile(@Valid UserRequest data) { - profileService.update(data); - } - - /** - * 获取用户隐私控制 - * - * @return 用户资料 - */ - @AOPLog - @RequiredToken - @RequestRateLimit - @PostMapping("/privacy") - public UserPrivacy privacy() { - return privacyService.get(service.getLoginUser().getId()); - } - - @AOPLog - @RequiredToken - @RequestRateLimit - @PostMapping("/privacy/update") - public void updatePrivacy(@Valid @RequestBody UserPrivacy privacy) { - privacyService.update(privacy); - } - - /** - * 获取用户设置 - * - * @return 用户设置 - */ - @AOPLog - @RequiredToken - @RequestRateLimit - @PostMapping("/config") - public UserConfig config() { - return configService.get(service.getLoginUser().getId()); - } - - @AOPLog - @RequiredToken - @RequestRateLimit - @PostMapping("/config/update") - public void updateConfig(@Valid @RequestBody UserConfig config) { - configService.update(config); - } - - /** - * 获取用户评论 - */ - @AOPLog - @RequiredToken - @RequestRateLimit - @PostMapping("/comment/list") - public PageResult listComment(@Valid @RequestBody Page page) { - Comment example = new Comment(); - example.setUserId(service.getLoginUser().getId()); - page.setEqualsExample(example); - return commentService.page(page); - } - - @AOPLog - @RequiredToken - @RequestRateLimit - @PostMapping("/comment/delete") - public void deleteComment(@RequestBody Long commentId) { - commentService.get(commentId); - commentService.delete(commentId); - } - - /** - * 获取用户被回复的评论 - */ - @AOPLog - @RequiredToken - @RequestRateLimit - @PostMapping("/comment/reply/list") - public PageResult listCommentReply(@Valid @RequestBody Page page) { - CommentReply example = new CommentReply(); - example.setReceiverId(service.getLoginUser().getId()); - page.setEqualsExample(example); - return commentReplyService.page(page); - } - - @AOPLog - @RequiredToken - @RequestRateLimit - @PostMapping("/comment/reply/delete") - public void deleteCommentReply(@RequestBody Long replyId) { - CommentReply reply = commentReplyService.get(replyId); - TimiException.requiredTrue(reply.getSenderId().equals(service.getLoginUser().getId()), "user.comment.reply.delete.not_owner"); - commentReplyService.delete(replyId); - } - - @AOPLog - @RequiredToken - @RequestRateLimit - @PostMapping("/comment/reply/ignore") - public void ignoreCommentReply(@RequestBody Long replyId) { - CommentReply reply = commentReplyService.get(replyId); - TimiException.requiredTrue(reply.getReceiverId().equals(service.getLoginUser().getId()), "user.comment.reply.ignore.not_owner"); - reply.setIgnoredAt(Time.now()); - commentReplyService.update(reply); - } -} diff --git a/src/main/java/com/imyeyu/api/modules/common/entity/Article.java b/src/main/java/com/imyeyu/api/modules/common/entity/Article.java new file mode 100644 index 0000000..ce3353a --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/common/entity/Article.java @@ -0,0 +1,82 @@ +package com.imyeyu.api.modules.common.entity; + +import com.imyeyu.api.modules.common.bean.attachment.SettingSupport; +import com.imyeyu.spring.annotation.table.PageIgnore; +import com.imyeyu.spring.annotation.table.Transient; +import com.imyeyu.spring.entity.UUIDEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.List; + +/** + * 文章 + * + * @author 夜雨 + * @since 2021-03-01 17:10 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class Article extends UUIDEntity implements SettingSupport { + + /** + * 类型 + * + * @author 夜雨 + * @since 2026-06-05 15:55 + */ + public enum BizType { + + /** 博客 */ + BLOG + } + + /** + * 正文类型 + * + * @author 夜雨 + * @since 2026-06-05 15:56 + */ + public enum ContentType { + + /** 纯文本 */ + TEXT, + + /** Markdown */ + MARKDOWN, + + /** TipTap 富文本 */ + TIP_TAP + } + + /** 业务类型 */ + protected BizType bizType; + + /** 业务 ID */ + protected String bizId; + + /** 标题 */ + protected String title; + + /** 摘要 */ + protected String digest; + + /** 正文类型 */ + protected ContentType contentType; + + /** 内容 */ + @PageIgnore + protected String content; + + /** 阅读数量 */ + protected Integer reads; + + /** 喜欢数量 */ + protected Integer likes; + + @Transient + protected List settingList; + + @Transient + protected List commentList; +} diff --git a/src/main/java/com/imyeyu/api/modules/common/entity/Attachment.java b/src/main/java/com/imyeyu/api/modules/common/entity/Attachment.java index 470c6e5..6b1c10d 100644 --- a/src/main/java/com/imyeyu/api/modules/common/entity/Attachment.java +++ b/src/main/java/com/imyeyu/api/modules/common/entity/Attachment.java @@ -2,16 +2,17 @@ package com.imyeyu.api.modules.common.entity; import com.fasterxml.jackson.databind.JsonNode; import com.imyeyu.api.TimiServerAPI; -import com.imyeyu.api.bean.MultilingualHandler; +import com.imyeyu.api.modules.common.bean.attachment.AccessKey; import com.imyeyu.api.modules.common.service.AttachmentService; import com.imyeyu.java.ref.Ref; +import com.imyeyu.spring.TimiSpring; import com.imyeyu.spring.annotation.table.Transient; -import com.imyeyu.spring.entity.Entity; -import lombok.AllArgsConstructor; +import com.imyeyu.spring.entity.UUIDEntity; import lombok.Data; import lombok.EqualsAndHashCode; -import lombok.Getter; +import org.springframework.web.multipart.MultipartFile; +import java.io.IOException; import java.io.InputStream; /** @@ -20,61 +21,33 @@ import java.io.InputStream; */ @Data @EqualsAndHashCode(callSuper = true) -public class Attachment extends Entity implements MultilingualHandler { +public class Attachment extends UUIDEntity { - /** - * 附件类型 - * - * @author 夜雨 - * @since 2023-08-21 16:32 - */ - @Getter - @AllArgsConstructor + /// + /// + /// @author 夜雨 + /// @since 2026-07-17 20:52 public enum BizType { - /** 用户 */ USER, - /** 文章 */ - ARTICLE, + GAO_CUSTOMER, - /** Git */ - GIT, + GAO_REGISTRATION_EVENT_RECORD, - /** 歌词 */ - LYRIC, + TEMP_FILE, - /** ForeverMC */ - FMC, - - /** 镜像 */ - MIRROR, - - /** 日记 */ - JOURNAL, - - /** 日记出行 */ - JOURNAL_TRAVEL, - - /** 日记瞬间 */ - JOURNAL_MOMENT, - - /** 系统 */ - SYSTEM, - - /** 临时文件 */ - TEMP_FILE + MIRROR } protected BizType bizType; - protected Long bizId; + protected String bizId; protected String attachType; protected String mongoId; - @MultilingualField protected String title; protected String name; @@ -93,9 +66,22 @@ public class Attachment extends Entity implements MultilingualHandler { protected Long destroyAt; + /** 数据流 */ @Transient protected InputStream inputStream; + /** 保存自临时文件 ID */ + @Transient + protected String tempFileId; + + /** 访问密钥 */ + @Transient + protected AccessKey accessKey; + + /** 请求文件 */ + @Transient + protected MultipartFile file; + public InputStream openInputStream() { AttachmentService service = TimiServerAPI.applicationContext.getBean(AttachmentService.class); return service.getInputStreamByMongoId(mongoId); @@ -108,4 +94,9 @@ public class Attachment extends Entity implements MultilingualHandler { public > T getAttachTypeValue(Class attachTypeClass) { return Ref.toType(attachTypeClass, attachType); } + + /** 执行接口流式返回 */ + public void doResponse() throws IOException { + TimiSpring.responseRangeStream(openInputStream(), size); + } } diff --git a/src/main/java/com/imyeyu/api/modules/common/entity/Comment.java b/src/main/java/com/imyeyu/api/modules/common/entity/Comment.java index df4315c..c5c1834 100644 --- a/src/main/java/com/imyeyu/api/modules/common/entity/Comment.java +++ b/src/main/java/com/imyeyu/api/modules/common/entity/Comment.java @@ -1,20 +1,11 @@ package com.imyeyu.api.modules.common.entity; -import com.imyeyu.api.modules.blog.entity.Article; -import com.imyeyu.api.modules.blog.service.implement.ArticleServiceImplement; -import com.imyeyu.api.modules.common.bean.CommentSupport; -import com.imyeyu.api.modules.common.vo.user.UserView; -import com.imyeyu.api.modules.git.bean.gitea.Repository; -import com.imyeyu.api.modules.git.service.implement.IssueServiceImplement; -import com.imyeyu.api.modules.git.service.implement.MergeServiceImplement; +import com.imyeyu.api.modules.user.entity.User; import com.imyeyu.spring.annotation.table.Transient; -import com.imyeyu.spring.entity.Entity; -import com.imyeyu.spring.service.GettableService; +import com.imyeyu.spring.entity.UUIDEntity; import jakarta.validation.constraints.NotBlank; -import lombok.AllArgsConstructor; import lombok.Data; import lombok.EqualsAndHashCode; -import lombok.Getter; import lombok.NoArgsConstructor; import java.util.List; @@ -23,71 +14,94 @@ import java.util.List; * 评论 * * @author 夜雨 - * @since 2021-02-25 14:46 + * @since 2026-05-27 17:20 */ @Data @NoArgsConstructor @EqualsAndHashCode(callSuper = true) -public class Comment extends Entity { - +public class Comment extends UUIDEntity { /** - * 关联业务类型 - *

- * TODO 添加模块名称以便区别邮件通知推送来源,使用多语言键 + * 评论状态 * * @author 夜雨 - * @since 2023-08-06 23:42 + * @since 2026-05-27 17:20 */ - @Getter - @AllArgsConstructor - public enum BizType { + public enum Status { - ARTICLE(ArticleServiceImplement.class), + /** 正常 */ + NORMAL, - GIT_ISSUE(IssueServiceImplement.class), - - GIT_MERGE(MergeServiceImplement.class); - - final Class> serviceClass; + /** 隐藏 */ + HIDDEN } - /** 关联业务类型 */ - private BizType bizType; + /** 业务类型 */ + protected String bizType; - /** 关联业务 ID */ - private Long bizId; + /** 业务 ID */ + protected String bizId; - /** 发送用户 ID,登录用户评论有值,游客无 */ - private Long userId; + /** 父评论 ID,主评论为 null */ + protected String parentId; - /** 发送用户昵称,游客评论有值,登录用户无 */ - private String nick; + /** 根评论 ID,主评论为自身 ID */ + protected String rootId; - /** 评论数据 */ + /** 评论用户 ID */ + protected String userId; + + /** 评论用户昵称,游客评论时使用 */ + protected String userNick; + + /** 被回复用户 ID */ + protected String replyUserId; + + /** 被回复用户昵称,被回复人为游客时使用 */ + protected String replyUserNick; + + /** 评论层级,1 为主评论 */ + protected Integer level; + + /** 评论内容 */ @NotBlank - private String content; + protected String content; - /** 发送用户 IP */ - private String ip; + /** IP 地址 */ + protected String ip; - /** 回复数量 */ + /** 回复数 */ + protected Integer replyCount; + + /** 点赞数 */ + protected Integer likeCount; + + /** 评论状态 */ + protected Status status; + + /** true 为置顶 */ + protected Boolean isTop; + + /** 最后回复时间 */ + protected Long lastReplyAt; + + /** 评论用户信息 */ @Transient - private long repliesLength; + protected User user; - /** 发送用户 */ + /** 被回复用户信息 */ @Transient - private UserView user; + protected User replyUser; - /** 关联文章 */ + /** 子评论列表 */ @Transient - private Article article; + protected List replyList; - /** 关联仓库 */ + /** true 为当前用户已点赞 */ @Transient - private Repository repository; + protected Boolean liked; - /** 回复列表 */ + /** true 为当前用户已不喜欢 */ @Transient - private List replies; + protected Boolean unliked; } diff --git a/src/main/java/com/imyeyu/api/modules/common/entity/CommentReply.java b/src/main/java/com/imyeyu/api/modules/common/entity/CommentReply.java deleted file mode 100644 index ddada70..0000000 --- a/src/main/java/com/imyeyu/api/modules/common/entity/CommentReply.java +++ /dev/null @@ -1,59 +0,0 @@ -package com.imyeyu.api.modules.common.entity; - -import com.imyeyu.api.modules.common.vo.user.UserView; -import com.imyeyu.spring.annotation.table.Transient; -import com.imyeyu.spring.entity.Entity; -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.NoArgsConstructor; - -/** - * 评论回复 - * - * @author 夜雨 - * @since 2021-03-01 17:11 - */ -@Data -@NoArgsConstructor -@EqualsAndHashCode(callSuper = true) -public class CommentReply extends Entity { - - /** 所属评论 ID */ - private Long commentId; - - /** 被回复的回复,回复主评论时为 NULL */ - private Long replyId; - - /** 发送用户 ID,登录用户回复有值,游客无 */ - private Long senderId; - - /** 回复用户 ID,系统用户回复有值,游客无 */ - private Long receiverId; - - /** 发送用户昵称,游客回复有值,登录用户无 */ - private String senderNick; - - /** 回复用户昵称,游客回复有值,系统用户无 */ - private String receiverNick; - - /** 回复数据 */ - private String content; - - /** 发送用户 IP */ - private String ip; - - /** 被回复用户忽略该回复的时间 */ - private Long ignoredAt; - - /** 所属评论 */ - @Transient - private Comment comment; - - /** 发送用户 */ - @Transient - private UserView sender; - - /** 回复用户 */ - @Transient - private UserView receiver; -} diff --git a/src/main/java/com/imyeyu/api/modules/common/entity/DataDict.java b/src/main/java/com/imyeyu/api/modules/common/entity/DataDict.java new file mode 100644 index 0000000..c8bdc78 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/common/entity/DataDict.java @@ -0,0 +1,47 @@ +package com.imyeyu.api.modules.common.entity; + +import com.imyeyu.spring.annotation.table.Transient; +import com.imyeyu.spring.entity.UUIDEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.List; + +/** + * 数据字典 + * + * @author 夜雨 + * @since 2026-05-13 14:40 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class DataDict extends UUIDEntity { + + /** 业务类型 */ + private String bizType; + + /** 代码 */ + private String code; + + /** 父级代码 */ + private String parentCode; + + /** 字典名称语言键 */ + private String nameLangId; + + /** 字典名称(读取) */ + @Transient + protected String name; + + /** 搜索名称 */ + @Transient + protected String searchName; + + /** 字典中文名称(写入) */ + @Transient + protected String zhCN; + + /** 选项列表 */ + @Transient + protected List dataDictOptionList; +} diff --git a/src/main/java/com/imyeyu/api/modules/common/entity/DataDictOption.java b/src/main/java/com/imyeyu/api/modules/common/entity/DataDictOption.java new file mode 100644 index 0000000..e1c2df2 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/common/entity/DataDictOption.java @@ -0,0 +1,50 @@ +package com.imyeyu.api.modules.common.entity; + +import com.imyeyu.spring.annotation.table.Transient; +import com.imyeyu.spring.entity.UUIDEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.List; + +/** + * 数据字典选项 + * + * @author 夜雨 + * @since 2026-05-13 14:40 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class DataDictOption extends UUIDEntity { + + /** 所属字典 ID */ + private String dictId; + + /** 值代码 */ + private String valueCode; + + /** 父级值代码 */ + private String valueParentCode; + + /** 值语言键 */ + private String valueLangId; + + /** 值(读取) */ + @Transient + protected String value; + + /** 中文值(写入) */ + @Transient + protected String zhCN; + + @Transient + protected String searchValue; + + /** 所属字典 */ + @Transient + protected DataDict dataDict; + + /** 附加参数设置 */ + @Transient + protected List settingList; +} diff --git a/src/main/java/com/imyeyu/api/modules/common/entity/EmailFilter.java b/src/main/java/com/imyeyu/api/modules/common/entity/EmailFilter.java new file mode 100644 index 0000000..fb7080b --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/common/entity/EmailFilter.java @@ -0,0 +1,34 @@ +package com.imyeyu.api.modules.common.entity; + +import com.imyeyu.spring.annotation.table.AutoUUID; +import com.imyeyu.spring.annotation.table.DeleteColumn; +import com.imyeyu.spring.annotation.table.Id; +import com.imyeyu.spring.entity.Creatable; +import com.imyeyu.spring.entity.Deletable; +import com.imyeyu.spring.entity.IDEntity; +import lombok.Data; + +/** + * 邮件过滤 + * + * @author 夜雨 + * @since 2026-05-13 14:40 + */ +@Data +public class EmailFilter implements IDEntity, Creatable, Deletable { + + /** 主键 */ + @Id + @AutoUUID + private String id; + + /** 邮箱 */ + private String email; + + /** 创建时间 */ + private Long createdAt; + + /** 删除时间 */ + @DeleteColumn + private Long deletedAt; +} diff --git a/src/main/java/com/imyeyu/api/modules/common/entity/EmailQueue.java b/src/main/java/com/imyeyu/api/modules/common/entity/EmailQueue.java deleted file mode 100644 index f422502..0000000 --- a/src/main/java/com/imyeyu/api/modules/common/entity/EmailQueue.java +++ /dev/null @@ -1,43 +0,0 @@ -package com.imyeyu.api.modules.common.entity; - -import com.imyeyu.spring.annotation.table.AutoUUID; -import com.imyeyu.spring.annotation.table.Id; -import lombok.Data; - -/** - * 邮件队列 - * - * @author 夜雨 - * @since 2021-08-24 14:59 - */ -@Data -public class EmailQueue { - - /** - * 业务类型 - * - * @author 夜雨 - * @since 2021-08-24 15:54 - */ - public enum BizType { - - /** 回复提醒 */ - REPLY_REMINAD, - - /** 邮箱验证 */ - EMAIL_VERIFY, - - /** 重置密码 */ - RESET_PASSWORD - } - - @Id - @AutoUUID - private String UUID; - - private BizType bizType; - - private Long bizId; - - private Long sendAt; -} diff --git a/src/main/java/com/imyeyu/api/modules/common/entity/EmailQueueLog.java b/src/main/java/com/imyeyu/api/modules/common/entity/EmailQueueLog.java deleted file mode 100644 index 25ef10c..0000000 --- a/src/main/java/com/imyeyu/api/modules/common/entity/EmailQueueLog.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.imyeyu.api.modules.common.entity; - -import lombok.Data; -import lombok.EqualsAndHashCode; -import com.imyeyu.spring.entity.Entity; - -/** - * @author 夜雨 - * @since 2021-08-24 18:00 - */ -@Data -@EqualsAndHashCode(callSuper = true) -public class EmailQueueLog extends Entity { - - private String UUID; - private EmailQueue.BizType bizType; - private Long bizId; - private String sendTo; - private Long sendAt; - - private Boolean isSent; - private String exceptionMsg; -} diff --git a/src/main/java/com/imyeyu/api/modules/common/entity/Feedback.java b/src/main/java/com/imyeyu/api/modules/common/entity/Feedback.java deleted file mode 100644 index 90b5ea1..0000000 --- a/src/main/java/com/imyeyu/api/modules/common/entity/Feedback.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.imyeyu.api.modules.common.entity; - -import lombok.Data; -import lombok.EqualsAndHashCode; -import com.imyeyu.spring.entity.Entity; - -/** - * 反馈 - * - * @author 夜雨 - * @since 2021-11-16 22:22 - */ -@Data -@EqualsAndHashCode(callSuper = true) -public class Feedback extends Entity { - - private String from; - private String email; - private String data; - private String ip; -} diff --git a/src/main/java/com/imyeyu/api/modules/common/entity/Icon.java b/src/main/java/com/imyeyu/api/modules/common/entity/Icon.java index bf1bb16..794c2e6 100644 --- a/src/main/java/com/imyeyu/api/modules/common/entity/Icon.java +++ b/src/main/java/com/imyeyu/api/modules/common/entity/Icon.java @@ -2,7 +2,7 @@ package com.imyeyu.api.modules.common.entity; import lombok.Data; import lombok.EqualsAndHashCode; -import com.imyeyu.spring.entity.Entity; +import com.imyeyu.spring.entity.UUIDEntity; /** * 字体图标 @@ -12,7 +12,7 @@ import com.imyeyu.spring.entity.Entity; */ @Data @EqualsAndHashCode(callSuper = true) -public class Icon extends Entity { +public class Icon extends UUIDEntity { /** 名称 */ private String name; diff --git a/src/main/java/com/imyeyu/api/modules/common/entity/Multilingual.java b/src/main/java/com/imyeyu/api/modules/common/entity/Multilingual.java index 82e81fc..19d59fd 100644 --- a/src/main/java/com/imyeyu/api/modules/common/entity/Multilingual.java +++ b/src/main/java/com/imyeyu/api/modules/common/entity/Multilingual.java @@ -1,28 +1,43 @@ package com.imyeyu.api.modules.common.entity; import com.imyeyu.java.bean.Language; -import com.imyeyu.spring.entity.Creatable; -import com.imyeyu.spring.entity.Deletable; -import com.imyeyu.spring.entity.IDEntity; -import com.imyeyu.spring.entity.Updatable; +import com.imyeyu.spring.entity.UUIDEntity; import lombok.Data; import lombok.EqualsAndHashCode; -import java.io.Serializable; - /** * @author 夜雨 * @since 2023-10-24 16:41 */ @Data @EqualsAndHashCode(callSuper = true) -public class Multilingual extends Language implements Serializable, IDEntity, Creatable, Updatable, Deletable { +public class Multilingual extends UUIDEntity { - protected Long id; + protected String zhCN; - protected Long createdAt; + protected String zhTW; - protected Long updatedAt; + protected String enUS; - protected Long deletedAt; + protected String ruRU; + + protected String jaJP; + + protected String deDE; + + protected String koKR; + + protected Long lastAccessAt; + + public String getValue(Language.Enum language) { + return switch (language) { + case zh_CN -> zhCN; + case zh_TW -> zhTW; + case en_US -> enUS; + case ru_RU -> ruRU; + case ja_JP -> jaJP; + case de_DE -> deDE; + case ko_KR -> koKR; + }; + } } diff --git a/src/main/java/com/imyeyu/api/modules/common/entity/Notify.java b/src/main/java/com/imyeyu/api/modules/common/entity/Notify.java new file mode 100644 index 0000000..286f6a9 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/common/entity/Notify.java @@ -0,0 +1,44 @@ +package com.imyeyu.api.modules.common.entity; + +import com.imyeyu.spring.annotation.table.AutoUUID; +import com.imyeyu.spring.annotation.table.Id; +import com.imyeyu.spring.annotation.table.Transient; +import com.imyeyu.spring.entity.UUIDEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.List; + +/** + * 通知 + * + * @author 夜雨 + * @since 2026-05-13 14:40 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class Notify extends UUIDEntity { + + /** 主键 */ + @Id + @AutoUUID + private String id; + + /** 业务类型 */ + private String bizType; + + /** 提醒业务类型 */ + private String bizRemindedType; + + /** 提醒业务 ID */ + private String bizRemindedId; + + /** 参数 */ + private String args; + + /** 创建者 */ + private String createdBy; + + @Transient + protected List detailList; +} diff --git a/src/main/java/com/imyeyu/api/modules/common/entity/NotifyDetail.java b/src/main/java/com/imyeyu/api/modules/common/entity/NotifyDetail.java new file mode 100644 index 0000000..48615ed --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/common/entity/NotifyDetail.java @@ -0,0 +1,97 @@ +package com.imyeyu.api.modules.common.entity; + +import com.imyeyu.spring.annotation.table.Transient; +import com.imyeyu.spring.entity.UUIDEntity; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 通知详情 + * + * @author 夜雨 + * @since 2026-05-13 14:40 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class NotifyDetail extends UUIDEntity { + + /** + * + * + * @author 夜雨 + * @since 2025-10-28 15:53 + */ + @AllArgsConstructor + public enum MsgType { + + SMS, + + MAIL, + + HTTP, + + WECHAT; + } + + /** + * + * + * @author 夜雨 + * @since 2025-10-28 15:55 + */ + public enum Status { + + WAITING, + + REMINDED, + + TIMEOUT, + + CANCEL, + + FAIL + } + + /** 所属通知 ID */ + private String notifyId; + + /** 消息类型 */ + private MsgType msgType; + + /** 模板 ID */ + private String templateId; + + /** 标题 */ + private String subject; + + /** 内容 */ + private String data; + + /** 参数 */ + private String args; + + /** 发送来源 */ + private String sendFrom; + + /** 发送去向 */ + private String sendTo; + + /** 发送时间 */ + private Long sendAt; + + /** 重试计数 */ + private Integer retry; + + /** 超时时间 */ + private Long timeoutAt; + + /** 结果描述 */ + private String resultDesc; + + /** 状态 */ + private Status status; + + @Transient + protected Notify notify; +} diff --git a/src/main/java/com/imyeyu/api/modules/common/entity/Setting.java b/src/main/java/com/imyeyu/api/modules/common/entity/Setting.java index 7cb7af2..2800b0b 100644 --- a/src/main/java/com/imyeyu/api/modules/common/entity/Setting.java +++ b/src/main/java/com/imyeyu/api/modules/common/entity/Setting.java @@ -1,10 +1,20 @@ package com.imyeyu.api.modules.common.entity; -import com.imyeyu.api.modules.common.bean.SettingKey; -import com.imyeyu.spring.annotation.table.Id; -import com.imyeyu.spring.entity.Creatable; -import com.imyeyu.spring.entity.Updatable; +import com.fasterxml.jackson.annotation.JsonView; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.imyeyu.api.TimiServerAPI; +import com.imyeyu.api.modules.common.bean.SettingModule; +import com.imyeyu.spring.annotation.table.Transient; +import com.imyeyu.spring.entity.UUIDEntity; +import com.imyeyu.spring.util.ResponseView; +import com.imyeyu.utils.Time; import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; /** * 系统配置 @@ -13,7 +23,9 @@ import lombok.Data; * @since 2021-07-20 21:46 */ @Data -public class Setting implements Creatable, Updatable { +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class Setting extends UUIDEntity { /** * @@ -21,31 +33,486 @@ public class Setting implements Creatable, Updatable { * @author 夜雨 * @since 2025-01-10 17:08 */ - public enum Type { - - INTEGER, + public enum ValueType { + /** 字符串 */ STRING, - JSON, + /** 单选 */ + SELECT_RADIO, - YAML, + /** 多选 */ + SELECT_CHECKBOX, + + /** 整数 */ + INTEGER, + + /** 十六进制颜色 */ + COLOR, + + /** 浮点数 */ + FLOAT, + + /** 日期 */ + DATE, + + /** 日期时间 */ + DATETIME, + + /** 时间 */ + TIME, + + /** 时长 */ + DURATION, + + /** 布尔值 */ + BOOLEAN, + + /** JSON 列表 */ + JSON_LIST, + + /** JSON 对象 */ + JSON_OBJECT } - @Id - private SettingKey key; + @JsonView(ResponseView.Public.class) + private String bizType; + @JsonView(ResponseView.Public.class) + private String bizId; + + @JsonView(ResponseView.Public.class) + private String code; + + @JsonView(ResponseView.Public.class) + private String description; + + @JsonView(ResponseView.Public.class) private String value; - private Type type; + @JsonView(ResponseView.Public.class) + private ValueType valueType; - private boolean isPrivate; + @JsonView(ResponseView.Public.class) + private JsonNode valueArgs; - private Long createdAt; + @JsonView(ResponseView.Admin.class) + private Boolean isPrivate; - private Long updatedAt; + @Transient + private Enum keyRaw; - public boolean isPublic() { - return !isPrivate; + public Setting(Enum key) { + setKeyRaw(key); + } + + public Setting(Enum key, Object value, ValueType valueType) { + setKeyRaw(key); + this.value = value.toString(); + this.valueType = valueType; + } + + /** + * 以字符串形式返回配置值 + * + * @return 配置字符串值 + */ + public String asString() { + return value; + } + + public Long asTime() { + return Time.parseToMS(asString()); + } + + /** + * 以整数形式返回配置值 + * + * @return 配置整数值 + * @throws NumberFormatException 配置值不是合法整数 + */ + public Integer asInteger() { + return Integer.parseInt(value); + } + + /** + * 以长整数形式返回配置值 + * + * @return 配置长整数值 + * @throws NumberFormatException 配置值不是合法长整数 + */ + public Long asLong() { + return Long.parseLong(value); + } + + /** + * 以双精度浮点数形式返回配置值 + * + * @return 配置双精度浮点数值 + * @throws NumberFormatException 配置值不是合法双精度浮点数 + */ + public Double asDouble() { + return Double.parseDouble(value); + } + + /** + * 以布尔值形式返回配置值 + * + * @return 配置布尔值 + */ + public Boolean asBoolean() { + return Boolean.parseBoolean(value); + } + + public boolean isTrue() { + return asBoolean(); + } + + public boolean isFalse() { + return !isTrue(); + } + + /** + * 以 JSON 节点形式返回配置值 + * + * @return 配置 JSON 节点 + * @throws IllegalArgumentException 配置值不是合法 JSON + */ + public JsonNode asJsonNode() { + return asJsonNode(JsonNode.class); + } + + /** + * 以 JSON 数组形式返回配置值 + * + * @return 配置 JSON 数组 + * @throws IllegalArgumentException 配置值不是合法 JSON 数组 + */ + public ArrayNode asArrayNode() { + return asJsonNode(ArrayNode.class); + } + + /** + * 以 JSON 对象形式返回配置值 + * + * @return 配置 JSON 对象 + * @throws IllegalArgumentException 配置值不是合法 JSON 对象 + */ + public ObjectNode asObjectNode() { + return asJsonNode(ObjectNode.class); + } + + /** + * 解析配置 JSON 值 + * + * @param type 目标类型 + * @param 目标类型 + * @return 解析后的 JSON 节点 + * @throws IllegalArgumentException 配置值不是合法 JSON + */ + public T asJsonNode(Class type) { + if (value == null) { + return null; + } + try { + ObjectMapper mapper = TimiServerAPI.applicationContext.getBean(ObjectMapper.class); + return mapper.readValue(value, type); + } catch (JsonProcessingException e) { + throw new IllegalArgumentException("setting value is not valid json: %s".formatted(value), e); + } + } + + public void setKeyRaw(Enum key) { + this.keyRaw = key; + this.bizType = SettingModule.fromClass(key.getClass()).toString(); + this.code = key.toString(); + } + + public boolean is(Enum key) { + return SettingModule.fromClass(key.getClass()).toString().equals(bizType) && key.toString().equals(code); + } + + /** + * + * + * @author 夜雨 + * @since 2026-04-24 14:23 + */ + public static final class Module { + + /** + * + * + * @author 夜雨 + * @since 2026-04-24 15:39 + */ + public enum Common { + + /** 微信收款码 */ + WECHAT_RECEIVE_QR_CODE + } + + /** + * 域名 + * + * @author 夜雨 + * @since 2026-04-26 17:18 + */ + public enum Domain { + + ROOT, + + API, + + GIT, + + BLOG, + + SPACE, + + FOREVER_MC + } + + /** + * + * + * @author 夜雨 + * @since 2026-04-24 15:14 + */ + public enum Blog { + + } + + /** + * + * + * @author 夜雨 + * @since 2026-04-24 15:03 + */ + public enum User { + + /** 启用登录 */ + ENABLE_LOGIN, + + /** 启用注册 */ + ENABLE_REGISTER, + + /** 允许数据更新 */ + ENABLE_PROFILE_UPDATE, + + /** 令牌有效期 */ + TOKEN_TTL + } + + /** + * + * + * @author 夜雨 + * @since 2026-04-24 15:21 + */ + public enum Comment { + + /** 启用服务 */ + ENABLE + } + + /** + * + * + * @author 夜雨 + * @since 2026-04-24 15:03 + */ + public enum ICP { + + IMYEYU_COM + } + + /** + * + * + * @author 夜雨 + * @since 2026-04-24 15:01 + */ + public enum ForeverMC { + + PLAYER_BIND_MAX, + + LOGIN_TOKEN_TTL + } + + /** + * + * + * @author 夜雨 + * @since 2026-04-24 14:57 + */ + public enum Multilingual { + + /** 翻译 API */ + TRANSLATE_API, + + /** 翻译 AppId */ + TRANSLATE_APP_ID, + + /** 翻译密钥 */ + TRANSLATE_KEY, + + /** 翻译计划任务表达式 */ + TRANSLATE_CORN, + + /** 缓存时长 */ + CACHE_TTL + } + + /** + * + * + * @author 夜雨 + * @since 2026-04-24 14:55 + */ + public enum Gitea { + + /** 接口地址 */ + API, + + /** 关于页面所属文章 ID */ + ABOUT_ARTICLE_ID, + + /** 仓库文件地址 */ + REPOSITORY_PATH + } + + /** + * + * + * @author 夜雨 + * @since 2026-04-24 14:52 + */ + public enum Music { + + /** 启用服务 */ + ENABLE, + + /** 通信消息最大帧长度 */ + MAX_FRAME_LENGTH, + + /** 受控端 IP */ + PLAYER_IP, + + /** 受控端端口 */ + PLAYER_PORT, + + /** 控制端 IP */ + CONTROLLER_IP, + + /** 控制端端口 */ + CONTROLLER_PORT, + + /** 控制端入口地址 */ + CONTROLLER_URI, + } + + /** + * + * + * @author 夜雨 + * @since 2026-04-24 14:49 + */ + public enum Journal { + + /** 启用服务 */ + ENABLE, + + /** 审核中 */ + REVIEWING, + + /** 访问密钥 */ + KEY, + + /** 微信小程序 AppId */ + APP_ID, + + /** 微信小程序 AppSecret */ + APP_SECRET, + + /** 备忘录内容 */ + MEMO, + } + + /** + * + * + * @author 夜雨 + * @since 2026-04-24 14:47 + */ + public enum TempFile { + + /** 启用服务 */ + ENABLE, + + /** 最小缓存时长 */ + TTL_MIN, + + /** 默认缓存时长 */ + TTL_DEFAULT, + + /** 最大缓存时长 */ + TTL_MAX, + + /** 已过期文件残留时长 */ + RESIDUE_TTL, + + /** 每个 IP 限制有效临时文件容量 */ + LIMIT_SIZE_OF_IP + } + + /** + * + * + * @author 夜雨 + * @since 2026-04-27 14:35 + */ + public enum FileSync { + + ENABLE, + + CRON, + } + + /** + * + * + * @author 夜雨 + * @since 2026-04-24 14:23 + */ + public enum System { + + /** 文件系统基准路径 */ + FILE_BASE_PATH, + + /** 设置缓存时长 */ + SETTING_TTL, + + /** 哀悼滤镜 */ + ENABLE_MOURN_FILTER, + } + + /** + * + * + * @author 夜雨 + * @since 2026-04-24 14:23 + */ + public enum SystemStatus { + + /** 状态采集数据量 */ + LIMIT, + + /** 状态采集网卡 */ + NETWORK_MAC, + + /** 状态采集频率 */ + RATE, + } } } diff --git a/src/main/java/com/imyeyu/api/modules/common/entity/Tag.java b/src/main/java/com/imyeyu/api/modules/common/entity/Tag.java index 547b88f..eb2bb7e 100644 --- a/src/main/java/com/imyeyu/api/modules/common/entity/Tag.java +++ b/src/main/java/com/imyeyu/api/modules/common/entity/Tag.java @@ -1,7 +1,7 @@ package com.imyeyu.api.modules.common.entity; -import com.imyeyu.api.bean.MultilingualHandler; -import com.imyeyu.spring.entity.Entity; +import com.imyeyu.spring.annotation.table.Transient; +import com.imyeyu.spring.entity.UUIDEntity; import lombok.Data; import lombok.EqualsAndHashCode; @@ -11,29 +11,13 @@ import lombok.EqualsAndHashCode; */ @Data @EqualsAndHashCode(callSuper = true) -public class Tag extends Entity implements MultilingualHandler { +public class Tag extends UUIDEntity { - /** - * - * - * @author 夜雨 - * @since 2024-08-28 14:26 - */ - public enum BizType { + protected String langKey; - ARTICLE, - - MUSIC, - - SERVER_FILE, - - WALLPAPER - } - - protected BizType bizType; - - protected String bizID; - - @MultilingualHandler.MultilingualField + @Transient protected String value; + + @Transient + protected String zhCN; } diff --git a/src/main/java/com/imyeyu/api/modules/common/entity/TagApply.java b/src/main/java/com/imyeyu/api/modules/common/entity/TagApply.java new file mode 100644 index 0000000..b2d9059 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/common/entity/TagApply.java @@ -0,0 +1,25 @@ +package com.imyeyu.api.modules.common.entity; + +import com.imyeyu.spring.entity.UUIDEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 标签应用 + * + * @author 夜雨 + * @since 2025-12-02 17:01 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class TagApply extends UUIDEntity { + + /** 业务类型 */ + protected String bizType; + + /** 业务 ID */ + protected String bizId; + + /** 标签 ID */ + protected String tagId; +} diff --git a/src/main/java/com/imyeyu/api/modules/common/entity/Task.java b/src/main/java/com/imyeyu/api/modules/common/entity/Task.java deleted file mode 100644 index 2039648..0000000 --- a/src/main/java/com/imyeyu/api/modules/common/entity/Task.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.imyeyu.api.modules.common.entity; - -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.Getter; -import com.imyeyu.spring.entity.Entity; - -import java.util.List; - -/** - * 开发任务 - * - * @author 夜雨 - * @since 2022-02-26 11:12 - */ -@Data -@EqualsAndHashCode(callSuper = true) -public class Task extends Entity { - - /** - * 任务状态 - * - * @author 夜雨 - * @since 2022-02-26 11:53 - */ - @Getter - public enum Status { - - UPDATE, WITH, WAIT, KEEP, DIE; - - final int sort = ordinal(); - } - - private String name; - private Status status; - private String digest; - - // 关联数据 - private List details; -} diff --git a/src/main/java/com/imyeyu/api/modules/common/entity/TaskDetail.java b/src/main/java/com/imyeyu/api/modules/common/entity/TaskDetail.java deleted file mode 100644 index 508e59b..0000000 --- a/src/main/java/com/imyeyu/api/modules/common/entity/TaskDetail.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.imyeyu.api.modules.common.entity; - -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.Getter; -import lombok.NoArgsConstructor; -import com.imyeyu.spring.entity.Entity; - -/** - * 开发任务详细信息 - * - * @author 夜雨 - * @since 2022-02-27 17:58 - */ -@Data -@NoArgsConstructor -@EqualsAndHashCode(callSuper = true) -public class TaskDetail extends Entity { - - /** - * 类型 - * - * @author 夜雨 - * @since 2022-02-27 18:03 - */ - @Getter - public enum Type { - - BUG, FEATURE - } - - /** - * 状态 - * - * @author 夜雨 - * @since 2022-02-27 18:06 - */ - @Getter - public enum Status { - - UPDATE, WAIT, FINISH, CLOSE - } - - private Long taskId; - private Type type; - private Status status; - private String digest; -} diff --git a/src/main/java/com/imyeyu/api/modules/common/entity/Template.java b/src/main/java/com/imyeyu/api/modules/common/entity/Template.java deleted file mode 100644 index e4ffdee..0000000 --- a/src/main/java/com/imyeyu/api/modules/common/entity/Template.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.imyeyu.api.modules.common.entity; - -import lombok.Data; -import lombok.EqualsAndHashCode; -import com.imyeyu.spring.entity.Entity; - -/** - * @author 夜雨 - * @since 2023-09-21 00:53 - */ -@Data -@EqualsAndHashCode(callSuper = true) -public class Template extends Entity { - - /** - * - * - * @author 夜雨 - * @since 2023-09-22 16:38 - */ - public enum BizType { - - GIT, - - FOREVER_MC - } - - private BizType bizType; - - private String bizCode; - - private String data; -} diff --git a/src/main/java/com/imyeyu/api/modules/common/entity/User.java b/src/main/java/com/imyeyu/api/modules/common/entity/User.java deleted file mode 100644 index 58a3968..0000000 --- a/src/main/java/com/imyeyu/api/modules/common/entity/User.java +++ /dev/null @@ -1,68 +0,0 @@ -package com.imyeyu.api.modules.common.entity; - -import lombok.Data; -import lombok.EqualsAndHashCode; -import com.imyeyu.spring.entity.Entity; -import com.imyeyu.utils.Time; - -/** - * 用户 - * - * @author 夜雨 - * @since 2021-03-01 17:11 - */ -@Data -@EqualsAndHashCode(callSuper = true) -public class User extends Entity { - - /** - * - * - * @author 夜雨 - * @since 2024-02-21 14:48 - */ - public enum AttachType { - - AVATAR, - - WRAPPER, - - LICENSE, - - DEFAULT_AVATAR, - - DEFAULT_WRAPPER - } - - /** 用户名 */ - protected String name; - - /** 密码 */ - protected String password; - - /** 邮箱 */ - protected String email; - - /** 邮箱验证时间 */ - protected Long emailVerifyAt; - - /** 解除禁言时间 */ - protected Long unmuteAt; - - /** 解除封禁时间 */ - protected Long unbanAt; - - /** @return true 为禁言中 */ - public boolean isMuting() { - return unmuteAt != null && Time.now() < unmuteAt; - } - - /** @return true 为封禁中 */ - public boolean isBanning() { - return unbanAt != null && Time.now() < unbanAt; - } - - public boolean emailVerified() { - return emailVerifyAt != null; - } -} diff --git a/src/main/java/com/imyeyu/api/modules/common/entity/UserConfig.java b/src/main/java/com/imyeyu/api/modules/common/entity/UserConfig.java deleted file mode 100644 index 0eafd0b..0000000 --- a/src/main/java/com/imyeyu/api/modules/common/entity/UserConfig.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.imyeyu.api.modules.common.entity; - -import com.imyeyu.spring.annotation.table.Id; -import com.imyeyu.spring.entity.Updatable; -import jakarta.validation.constraints.Min; -import lombok.Data; - -/** - * 用户设置 - * - * @author 夜雨 - * @since 2021-08-12 15:06 - */ -@Data -public class UserConfig implements Updatable { - - @Min(1) - @Id - private Long userId; - - private Boolean emailReplyRemind; - - private Long updatedAt; - - public UserConfig(Long uid) { - this.userId = uid; - emailReplyRemind = true; - } - - public Boolean isEmailReplyRemind() { - return emailReplyRemind; - } -} diff --git a/src/main/java/com/imyeyu/api/modules/common/entity/UserPrivacy.java b/src/main/java/com/imyeyu/api/modules/common/entity/UserPrivacy.java deleted file mode 100644 index 0e4e882..0000000 --- a/src/main/java/com/imyeyu/api/modules/common/entity/UserPrivacy.java +++ /dev/null @@ -1,51 +0,0 @@ -package com.imyeyu.api.modules.common.entity; - -import com.imyeyu.java.ref.Ref; -import com.imyeyu.spring.annotation.table.Id; -import com.imyeyu.spring.entity.Updatable; -import jakarta.validation.constraints.Min; -import lombok.Data; -import lombok.NoArgsConstructor; - -import java.lang.reflect.Field; -import java.util.List; - -/** - * 用户隐私控制 - * - * @author 夜雨 - * @since 2021-07-27 16:51 - */ -@Data -@NoArgsConstructor -public class UserPrivacy implements Updatable { - - @Min(1) - @Id - private Long userId; - - private boolean email; - private boolean sex; - private boolean birthdate; - private boolean qq; - private boolean lastLoginAt; - private boolean createdAt; - - private Long updatedAt; - - public UserPrivacy(Long uid) { - this.userId = uid; - } - - /** @return 过滤字段列表 */ - public List listFilterFields() { - return Ref.listFields(getClass()).stream().filter(f -> { - try { - f.setAccessible(true); - return boolean.class.isAssignableFrom(f.getType()) && !(boolean) f.get(this); - } catch (IllegalAccessException e) { - throw new RuntimeException(e); - } - }).map(Field::getName).toList(); - } -} diff --git a/src/main/java/com/imyeyu/api/modules/common/entity/UserProfile.java b/src/main/java/com/imyeyu/api/modules/common/entity/UserProfile.java deleted file mode 100644 index f78303c..0000000 --- a/src/main/java/com/imyeyu/api/modules/common/entity/UserProfile.java +++ /dev/null @@ -1,66 +0,0 @@ -package com.imyeyu.api.modules.common.entity; - -import com.imyeyu.api.modules.common.bean.ImageType; -import com.imyeyu.spring.annotation.table.Id; -import com.imyeyu.spring.entity.Updatable; -import jakarta.validation.constraints.Max; -import jakarta.validation.constraints.Min; -import jakarta.validation.constraints.Pattern; -import jakarta.validation.constraints.Size; -import lombok.Data; -import lombok.NoArgsConstructor; - -/** - * 用户数据 - * - * @author 夜雨 - * @since 2021-05-29 15:58 - */ -@Data -@NoArgsConstructor -public class UserProfile implements Updatable { - - /** 用户 ID */ - @Min(1) - @Id - protected Long userId; - - /** 封面类型 */ - protected ImageType wrapperType; - - /** 头像类型 */ - protected ImageType avatarType; - - /** 经验值 */ - protected Integer exp; - - /** 性别 */ - @Max(1) - @Min(0) - protected Byte sex; - - /** 出生日期 */ - @Min(0) - protected Long birthdate; - - /** QQ */ - @Pattern(regexp = "[1-9]\\d{4,14}") - protected String qq; - - /** 说明 */ - @Size(max = 240) - protected String description; - - /** 最近登录 IP */ - protected String lastLoginIP; - - /** 最近登录时间 */ - protected Long lastLoginAt; - - /** 修改时间 */ - protected Long updatedAt; - - public UserProfile(Long userId) { - this.userId = userId; - } -} diff --git a/src/main/java/com/imyeyu/api/modules/common/entity/Version.java b/src/main/java/com/imyeyu/api/modules/common/entity/Version.java deleted file mode 100644 index 455b034..0000000 --- a/src/main/java/com/imyeyu/api/modules/common/entity/Version.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.imyeyu.api.modules.common.entity; - -import lombok.Data; -import lombok.EqualsAndHashCode; -import com.imyeyu.spring.entity.Entity; - -/** - * 版本管理 - * - * @author 夜雨 - * @since 2021-06-10 16:01 - */ -@Data -@EqualsAndHashCode(callSuper = true) -public class Version extends Entity { - - private String name; - private String version; - private String content; - private String url; -} diff --git a/src/main/java/com/imyeyu/api/modules/common/mapper/ArticleMapper.java b/src/main/java/com/imyeyu/api/modules/common/mapper/ArticleMapper.java new file mode 100644 index 0000000..a6ea683 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/common/mapper/ArticleMapper.java @@ -0,0 +1,20 @@ +package com.imyeyu.api.modules.common.mapper; + +import com.imyeyu.api.modules.common.entity.Article; +import com.imyeyu.spring.mapper.BaseMapper; +import org.apache.ibatis.annotations.Select; + +/** + * 文章 + * + * @author 夜雨 + * @since 2021-02-23 21:34 + */ +public interface ArticleMapper extends BaseMapper { + + @Select("UPDATE `article` SET `likes` = `likes` + 1 WHERE `id` = #{articleId}") + void like(Long articleId); + + @Select("UPDATE `article` SET `reads` = `reads` + 1 WHERE `id` = #{articleId}") + void read(Long articleId); +} diff --git a/src/main/java/com/imyeyu/api/modules/common/mapper/AttachmentMapper.java b/src/main/java/com/imyeyu/api/modules/common/mapper/AttachmentMapper.java index 6ffce84..8c4a12a 100644 --- a/src/main/java/com/imyeyu/api/modules/common/mapper/AttachmentMapper.java +++ b/src/main/java/com/imyeyu/api/modules/common/mapper/AttachmentMapper.java @@ -12,25 +12,46 @@ import java.util.List; * @author 夜雨 * @since 2023-08-15 10:22 */ -public interface AttachmentMapper extends BaseMapper, RawMapper { +public interface AttachmentMapper extends BaseMapper, RawMapper { /** 有效条件,非删除和销毁 */ String VALID = NOT_DELETE + " AND (`destroy_at` IS NULL OR " + UNIX_TIME + " < `destroy_at`)"; @Select("SELECT * FROM attachment WHERE biz_type = #{bizType} AND biz_id = #{bizId} " + VALID + LIMIT_1) - Attachment selectByBizId(Attachment.BizType bizType, long bizId); + Attachment selectByBizId(Attachment.BizType bizType, String bizId); + + @Select("SELECT * FROM attachment WHERE biz_type = #{bizType} AND biz_id = #{bizId} " + VALID) + List selectAllByBizId(Attachment.BizType bizType, String bizId); @Select("SELECT * FROM attachment WHERE mongo_id = #{mongoId} " + VALID + LIMIT_1) Attachment selectByMongoId(String mongoId); @Select("SELECT * FROM attachment WHERE biz_type = #{bizType} AND biz_id = #{bizId} AND attach_type = #{attachType} " + VALID + LIMIT_1) - Attachment selectByAttachType(Attachment.BizType bizType, long bizId, Enum attachType); + Attachment selectByAttachType(Attachment.BizType bizType, String bizId, String attachType); - List listByBizId(Attachment.BizType bizType, Long bizId, List> attachTypes, Page page); + List selectByBizId(Attachment.BizType bizType, String bizId, List attachTypes, Page page); - long countByBizId(Attachment.BizType bizType, Long bizId, List> attachTypes); + long countByBizId(Attachment.BizType bizType, String bizId, List attachTypes); - List listByMd5s(Attachment.BizType bizType, Long bizId, List> attachTypes, List md5s); + /** + * 按源附件 ID 与请求尺寸查询已缓存缩略图 + * + * @param bizType 源附件业务类型 + * @param sourceId 源附件 ID + * @param requestWidth 请求宽度,{@code null} 表示未指定 + * @param requestHeight 请求高度,{@code null} 表示未指定 + * @return 缩略图附件,不存在返回 {@code null} + */ + Attachment selectThumb(Attachment.BizType bizType, String sourceId, Integer requestWidth, Integer requestHeight); + + /** + * 查询源附件的所有未销毁缩略图(含已软删除) + * + * @param sourceId 源附件 ID + * @return 缩略图列表 + */ + @Select("SELECT * FROM attachment WHERE biz_id = #{sourceId} AND attach_type = 'THUMB' AND is_destroyed = FALSE") + List selectThumbsBySourceId(String sourceId); @Select("SELECT * FROM attachment WHERE `is_destroyed` = FALSE AND `destroy_at` < " + UNIX_TIME) List selectNeedDestroy(); diff --git a/src/main/java/com/imyeyu/api/modules/common/mapper/CommentMapper.java b/src/main/java/com/imyeyu/api/modules/common/mapper/CommentMapper.java index de30895..2548d15 100644 --- a/src/main/java/com/imyeyu/api/modules/common/mapper/CommentMapper.java +++ b/src/main/java/com/imyeyu/api/modules/common/mapper/CommentMapper.java @@ -2,18 +2,154 @@ package com.imyeyu.api.modules.common.mapper; import com.imyeyu.api.modules.common.entity.Comment; import com.imyeyu.spring.mapper.BaseMapper; +import org.apache.ibatis.annotations.Select; import org.apache.ibatis.annotations.Update; +import java.util.List; + /** - * 评论 + * 评论 Mapper * * @author 夜雨 - * @since 2021-2-23 21:33 + * @since 2026-05-27 17:20 */ -public interface CommentMapper extends BaseMapper { +public interface CommentMapper extends BaseMapper { - long countAll(Comment.BizType bizType, Long bizId); + /** + * 根据 ID 列表查询评论 + * + * @param idList 评论 ID 列表 + * @return 评论列表 + */ + List listByIdList(List idList); - @Update("UPDATE comment SET deleted_at = FLOOR(UNIX_TIMESTAMP(NOW(3)) * 1000) WHERE user_id = #{userId} ") - void deleteByUserId(Long userId); + /** + * 查询业务下的主评论列表 + * + * @param bizType 业务类型 + * @param bizId 业务 ID + * @param offset 起始偏移量 + * @param limit 查询条数 + * @return 主评论列表 + */ + List selectRootList(String bizType, String bizId, int offset, int limit); + + /** + * 统计业务下的主评论数量 + * + * @param bizType 业务类型 + * @param bizId 业务 ID + * @return 主评论数量 + */ + long countRoot(String bizType, String bizId); + + /** + * 查询多个根评论下的回复预览列表 + * + * @param rootIdList 根评论 ID 列表 + * @param limit 每个根评论最多返回条数 + * @return 回复列表 + */ + List selectReplyPreviewByRootIds(List rootIdList, int limit); + + /** + * 查询父评论下的回复列表 + * + * @param parentId 父评论 ID + * @param offset 起始偏移量 + * @param limit 查询条数 + * @return 回复列表 + */ + List selectReplyListByParentId(String parentId, int offset, int limit); + + /** + * 查询根评论下的回复列表 + * + * @param rootId 根评论 ID + * @param offset 起始偏移量 + * @param limit 查询条数 + * @return 回复列表 + */ + List selectReplyListByRootId(String rootId, int offset, int limit); + + /** + * 统计父评论下的回复数量 + * + * @param parentId 父评论 ID + * @return 回复数量 + */ + long countReplyByParentId(String parentId); + + /** + * 统计根评论下的回复数量 + * + * @param rootId 根评论 ID + * @return 回复数量 + */ + long countReplyByRootId(String rootId); + + /** + * 查询多个根评论下的全部回复列表 + * + * @param rootIdList 根评论 ID 列表 + * @return 回复列表 + */ + List selectAllReplyListByRootIds(List rootIdList); + + /** + * 查询根评论下的全部回复列表 + * + * @param rootId 根评论 ID + * @return 回复列表 + */ + List selectAllReplyListByRootId(String rootId); + + /** + * 查询单层评论列表 + * + * @param bizType 业务类型 + * @param bizId 业务 ID + * @param parentId 父评论 ID + * @param offset 起始偏移量 + * @param limit 查询条数 + * @return 评论列表 + */ + List selectLayerList(String bizType, String bizId, String parentId, int offset, int limit); + + /** + * 统计单层评论数量 + * + * @param bizType 业务类型 + * @param bizId 业务 ID + * @param parentId 父评论 ID + * @return 评论数量 + */ + long countLayer(String bizType, String bizId, String parentId); + + /** + * 查询根评论最新回复时间 + * + * @param rootId 根评论 ID + * @return 最新回复时间 + */ + @Select("SELECT created_at FROM comment WHERE root_id = #{rootId} AND parent_id IS NOT NULL" + NOT_DELETE + "ORDER BY created_at DESC LIMIT 1") + Long selectLatestReplyAtByRootId(String rootId); + + /** + * 调整回复数 + * + * @param id 评论 ID + * @param delta 变更值 + */ + @Update("UPDATE comment SET reply_count = GREATEST(reply_count + #{delta}, 0), updated_at = " + UNIX_TIME + " WHERE id = #{id}" + NOT_DELETE) + void increaseReplyCount(String id, int delta); + + /** + * 调整点赞数 + * + * @param id 评论 ID + * @param delta 变更值 + */ + @Update("UPDATE comment SET like_count = GREATEST(like_count + #{delta}, 0), updated_at = " + UNIX_TIME + " WHERE id = #{id}" + NOT_DELETE) + void increaseLikeCount(String id, int delta); } diff --git a/src/main/java/com/imyeyu/api/modules/common/mapper/CommentRemindQueueMapper.java b/src/main/java/com/imyeyu/api/modules/common/mapper/CommentRemindQueueMapper.java deleted file mode 100644 index 89db088..0000000 --- a/src/main/java/com/imyeyu/api/modules/common/mapper/CommentRemindQueueMapper.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.imyeyu.api.modules.common.mapper; - -import com.imyeyu.api.modules.blog.entity.CommentRemindQueue; -import com.imyeyu.spring.mapper.BaseMapper; -import org.apache.ibatis.annotations.Select; - -import java.util.List; - -/** - * 评论回复提醒队列 - * - * @author 夜雨 - * @since 2021-08-25 00:15 - */ -public interface CommentRemindQueueMapper extends BaseMapper { - - @Select("SELECT * FROM comment_remind_queue WHERE user_id = #{userId}") - List listByUserId(Long userId); -} diff --git a/src/main/java/com/imyeyu/api/modules/common/mapper/CommentReplyMapper.java b/src/main/java/com/imyeyu/api/modules/common/mapper/CommentReplyMapper.java deleted file mode 100644 index 315d0bd..0000000 --- a/src/main/java/com/imyeyu/api/modules/common/mapper/CommentReplyMapper.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.imyeyu.api.modules.common.mapper; - - -import com.imyeyu.api.modules.common.entity.CommentReply; -import com.imyeyu.spring.mapper.BaseMapper; - -/** - * 评论回复 - * - * @author 夜雨 - * @since 2021-08-24 10:36 - */ -public interface CommentReplyMapper extends BaseMapper { -} diff --git a/src/main/java/com/imyeyu/api/modules/common/mapper/DataDictMapper.java b/src/main/java/com/imyeyu/api/modules/common/mapper/DataDictMapper.java new file mode 100644 index 0000000..06b397e --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/common/mapper/DataDictMapper.java @@ -0,0 +1,13 @@ +package com.imyeyu.api.modules.common.mapper; + +import com.imyeyu.api.modules.common.entity.DataDict; +import com.imyeyu.spring.mapper.BaseMapper; + +/** + * 数据字典 Mapper + * + * @author 夜雨 + * @since 2026-05-13 14:46 + */ +public interface DataDictMapper extends BaseMapper { +} diff --git a/src/main/java/com/imyeyu/api/modules/common/mapper/DataDictOptionMapper.java b/src/main/java/com/imyeyu/api/modules/common/mapper/DataDictOptionMapper.java new file mode 100644 index 0000000..156ecd2 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/common/mapper/DataDictOptionMapper.java @@ -0,0 +1,13 @@ +package com.imyeyu.api.modules.common.mapper; + +import com.imyeyu.api.modules.common.entity.DataDictOption; +import com.imyeyu.spring.mapper.BaseMapper; + +/** + * 数据字典选项 Mapper + * + * @author 夜雨 + * @since 2026-05-13 14:46 + */ +public interface DataDictOptionMapper extends BaseMapper { +} diff --git a/src/main/java/com/imyeyu/api/modules/common/mapper/EmailFilterMapper.java b/src/main/java/com/imyeyu/api/modules/common/mapper/EmailFilterMapper.java new file mode 100644 index 0000000..9913684 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/common/mapper/EmailFilterMapper.java @@ -0,0 +1,13 @@ +package com.imyeyu.api.modules.common.mapper; + +import com.imyeyu.api.modules.common.entity.EmailFilter; +import com.imyeyu.spring.mapper.BaseMapper; + +/** + * 邮件过滤 Mapper + * + * @author 夜雨 + * @since 2026-05-13 14:46 + */ +public interface EmailFilterMapper extends BaseMapper { +} diff --git a/src/main/java/com/imyeyu/api/modules/common/mapper/EmailQueueLogMapper.java b/src/main/java/com/imyeyu/api/modules/common/mapper/EmailQueueLogMapper.java deleted file mode 100644 index c845017..0000000 --- a/src/main/java/com/imyeyu/api/modules/common/mapper/EmailQueueLogMapper.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.imyeyu.api.modules.common.mapper; - -import com.imyeyu.api.modules.common.entity.EmailQueueLog; -import com.imyeyu.spring.mapper.BaseMapper; - -/** - * @author 夜雨 - * @since 2023-08-10 10:38 - */ -public interface EmailQueueLogMapper extends BaseMapper { -} diff --git a/src/main/java/com/imyeyu/api/modules/common/mapper/EmailQueueMapper.java b/src/main/java/com/imyeyu/api/modules/common/mapper/EmailQueueMapper.java deleted file mode 100644 index cb7fc92..0000000 --- a/src/main/java/com/imyeyu/api/modules/common/mapper/EmailQueueMapper.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.imyeyu.api.modules.common.mapper; - -import com.imyeyu.api.modules.common.entity.EmailQueue; -import com.imyeyu.spring.mapper.BaseMapper; -import org.apache.ibatis.annotations.Select; - -import java.util.List; - -/** - * 邮件推送队列 - * - * @author 夜雨 - * @since 2021-08-24 16:22 - */ -public interface EmailQueueMapper extends BaseMapper { - - @Select("SELECT * FROM email_queue WHERE biz_type = #{bizType} AND biz_id = #{bizId}") - EmailQueue query(EmailQueue.BizType bizType, Long bizId); - - @Select("SELECT * FROM email_queue") - List listAll(); -} diff --git a/src/main/java/com/imyeyu/api/modules/common/mapper/FeedbackMapper.java b/src/main/java/com/imyeyu/api/modules/common/mapper/FeedbackMapper.java deleted file mode 100644 index 9714af1..0000000 --- a/src/main/java/com/imyeyu/api/modules/common/mapper/FeedbackMapper.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.imyeyu.api.modules.common.mapper; - -import com.imyeyu.api.modules.common.entity.Feedback; -import com.imyeyu.spring.mapper.BaseMapper; - -/** - * 反馈 - * - * @author 夜雨 - * @since 2021-11-16 22:28 - */ -public interface FeedbackMapper extends BaseMapper { -} diff --git a/src/main/java/com/imyeyu/api/modules/common/mapper/IconMapper.java b/src/main/java/com/imyeyu/api/modules/common/mapper/IconMapper.java index 33e6622..c4311f0 100644 --- a/src/main/java/com/imyeyu/api/modules/common/mapper/IconMapper.java +++ b/src/main/java/com/imyeyu/api/modules/common/mapper/IconMapper.java @@ -2,9 +2,6 @@ package com.imyeyu.api.modules.common.mapper; import com.imyeyu.api.modules.common.entity.Icon; import com.imyeyu.spring.mapper.BaseMapper; -import org.apache.ibatis.annotations.Select; - -import java.util.List; /** * 图标 @@ -12,24 +9,5 @@ import java.util.List; * @author 夜雨 * @since 2022-09-15 00:02 */ -public interface IconMapper extends BaseMapper { - - @Select("SELECT * FROM icon WHERE 1 = 1" + NOT_DELETE) - List listAll(); - - @Select("SELECT COUNT(1) FROM icon WHERE name LIKE CONCAT('%', #{name}, '%')" + NOT_DELETE) - long countByName(String name); - - @Select("SELECT * FROM icon WHERE name LIKE CONCAT('%', #{name}, '%')" + PAGE) - List listByName(String name, long offset, long limit); - - long countByLabel(String lang, String label); - - List listByLabel(String lang, String label, long offset, long limit); - - @Select("SELECT COUNT(1) FROM icon WHERE unicode = #{unicode}" + NOT_DELETE) - long countByUnicode(String unicode); - - @Select("SELECT * FROM icon WHERE unicode = #{unicode}" + PAGE) - List listByUnicode(String unicode, long offset, long limit); +public interface IconMapper extends BaseMapper { } diff --git a/src/main/java/com/imyeyu/api/modules/common/mapper/MultilingualMapper.java b/src/main/java/com/imyeyu/api/modules/common/mapper/MultilingualMapper.java index ff68c7b..75394e7 100644 --- a/src/main/java/com/imyeyu/api/modules/common/mapper/MultilingualMapper.java +++ b/src/main/java/com/imyeyu/api/modules/common/mapper/MultilingualMapper.java @@ -2,7 +2,6 @@ package com.imyeyu.api.modules.common.mapper; import com.imyeyu.api.modules.common.entity.Multilingual; import com.imyeyu.spring.mapper.BaseMapper; -import org.apache.ibatis.annotations.Select; import java.util.List; @@ -10,23 +9,7 @@ import java.util.List; * @author 夜雨 * @since 2023-10-25 10:47 */ -public interface MultilingualMapper extends BaseMapper { +public interface MultilingualMapper extends BaseMapper { - // 以下临时 - - @Select("SELECT * FROM multilingual WHERE key LIKE CONCAT('%', #{key}, '%')" + NOT_DELETE) - List selectByKeyLike(String key); - - List selectByKeyList(List keys); - - // 以上临时 - - @Select("SELECT * FROM multilingual WHERE zh_cn = #{zhCN}" + NOT_DELETE + LIMIT_1) - Multilingual selectByZhCN(String zhCN); - - @Select("SELECT * FROM multilingual WHERE en_US IS NULL OR en_US = ''" + NOT_DELETE) - List selectByNotTranslate(); - - @Select("SELECT * FROM multilingual WHERE `key` = #{key}" + NOT_DELETE + LIMIT_1) - Multilingual selectByKey(String key); + List selectByIdList(List idList); } diff --git a/src/main/java/com/imyeyu/api/modules/common/mapper/NotifyDetailMapper.java b/src/main/java/com/imyeyu/api/modules/common/mapper/NotifyDetailMapper.java new file mode 100644 index 0000000..b4ada09 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/common/mapper/NotifyDetailMapper.java @@ -0,0 +1,13 @@ +package com.imyeyu.api.modules.common.mapper; + +import com.imyeyu.api.modules.common.entity.NotifyDetail; +import com.imyeyu.spring.mapper.BaseMapper; + +/** + * 通知详情 Mapper + * + * @author 夜雨 + * @since 2026-05-13 14:46 + */ +public interface NotifyDetailMapper extends BaseMapper { +} diff --git a/src/main/java/com/imyeyu/api/modules/common/mapper/NotifyMapper.java b/src/main/java/com/imyeyu/api/modules/common/mapper/NotifyMapper.java new file mode 100644 index 0000000..6126211 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/common/mapper/NotifyMapper.java @@ -0,0 +1,13 @@ +package com.imyeyu.api.modules.common.mapper; + +import com.imyeyu.api.modules.common.entity.Notify; +import com.imyeyu.spring.mapper.BaseMapper; + +/** + * 通知 Mapper + * + * @author 夜雨 + * @since 2026-05-13 14:46 + */ +public interface NotifyMapper extends BaseMapper { +} diff --git a/src/main/java/com/imyeyu/api/modules/common/mapper/SettingMapper.java b/src/main/java/com/imyeyu/api/modules/common/mapper/SettingMapper.java index a93d8fa..df3e36c 100644 --- a/src/main/java/com/imyeyu/api/modules/common/mapper/SettingMapper.java +++ b/src/main/java/com/imyeyu/api/modules/common/mapper/SettingMapper.java @@ -1,11 +1,7 @@ package com.imyeyu.api.modules.common.mapper; -import com.imyeyu.api.modules.common.bean.SettingKey; import com.imyeyu.api.modules.common.entity.Setting; import com.imyeyu.spring.mapper.BaseMapper; -import org.apache.ibatis.annotations.Select; - -import java.util.List; /** * 系统配置 @@ -14,10 +10,4 @@ import java.util.List; * @since 2021-07-20 22:26 */ public interface SettingMapper extends BaseMapper { - - @Select("SELECT * FROM `setting` WHERE `key` = #{key}") - Setting selectByKey(SettingKey key); - - @Select("SELECT * FROM `setting`") - List listAll(); } diff --git a/src/main/java/com/imyeyu/api/modules/common/mapper/TagMapper.java b/src/main/java/com/imyeyu/api/modules/common/mapper/TagMapper.java index 538d2e5..fcca350 100644 --- a/src/main/java/com/imyeyu/api/modules/common/mapper/TagMapper.java +++ b/src/main/java/com/imyeyu/api/modules/common/mapper/TagMapper.java @@ -7,5 +7,5 @@ import com.imyeyu.spring.mapper.BaseMapper; * @author 夜雨 * @since 2025-05-30 22:48 */ -public interface TagMapper extends BaseMapper { +public interface TagMapper extends BaseMapper { } diff --git a/src/main/java/com/imyeyu/api/modules/common/mapper/TaskMapper.java b/src/main/java/com/imyeyu/api/modules/common/mapper/TaskMapper.java deleted file mode 100644 index be47b61..0000000 --- a/src/main/java/com/imyeyu/api/modules/common/mapper/TaskMapper.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.imyeyu.api.modules.common.mapper; - -import com.imyeyu.api.modules.common.entity.Task; - -import java.util.List; - -/** - * 任务服务 - * - * @author 夜雨 - * @since 2022-04-03 15:37 - */ -public interface TaskMapper { - - List listAll4Public(); -} diff --git a/src/main/java/com/imyeyu/api/modules/common/mapper/TemplateMapper.java b/src/main/java/com/imyeyu/api/modules/common/mapper/TemplateMapper.java deleted file mode 100644 index 010f258..0000000 --- a/src/main/java/com/imyeyu/api/modules/common/mapper/TemplateMapper.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.imyeyu.api.modules.common.mapper; - -import com.imyeyu.api.modules.common.entity.Template; -import com.imyeyu.spring.mapper.BaseMapper; -import org.apache.ibatis.annotations.Select; - -/** - * @author 夜雨 - * @since 2023-09-22 16:41 - */ -public interface TemplateMapper extends BaseMapper { - - @Select("SELECT * FROM template WHERE biz_type = #{bizType} AND biz_code = #{bizCode}" + NOT_DELETE) - Template query(Template.BizType bizType, String bizCode); -} diff --git a/src/main/java/com/imyeyu/api/modules/common/mapper/UserConfigMapper.java b/src/main/java/com/imyeyu/api/modules/common/mapper/UserConfigMapper.java deleted file mode 100644 index e87ec35..0000000 --- a/src/main/java/com/imyeyu/api/modules/common/mapper/UserConfigMapper.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.imyeyu.api.modules.common.mapper; - -import com.imyeyu.api.modules.common.entity.UserConfig; -import com.imyeyu.spring.mapper.BaseMapper; - -/** - * 用户设置 - * - * @author 夜雨 - * @since 2021-08-12 16:36 - */ -public interface UserConfigMapper extends BaseMapper { -} diff --git a/src/main/java/com/imyeyu/api/modules/common/mapper/UserMapper.java b/src/main/java/com/imyeyu/api/modules/common/mapper/UserMapper.java deleted file mode 100644 index ecb65e8..0000000 --- a/src/main/java/com/imyeyu/api/modules/common/mapper/UserMapper.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.imyeyu.api.modules.common.mapper; - -import com.imyeyu.api.modules.common.entity.User; -import com.imyeyu.spring.mapper.BaseMapper; -import org.apache.ibatis.annotations.Select; - -/** - * 用户 - * - * @author 夜雨 - * @since 2021-02-23 21:33 - */ -public interface UserMapper extends BaseMapper { - - @Select("SELECT * FROM user WHERE BINARY name = #{name}" + NOT_DELETE + LIMIT_1) - User selectByName(String name); - - @Select("SELECT * FROM user WHERE BINARY email = #{email} AND email_verify_at IS NOT NULL" + NOT_DELETE + LIMIT_1) - User selectByEmail(String email); -} diff --git a/src/main/java/com/imyeyu/api/modules/common/mapper/UserPrivacyMapper.java b/src/main/java/com/imyeyu/api/modules/common/mapper/UserPrivacyMapper.java deleted file mode 100644 index d6cddf3..0000000 --- a/src/main/java/com/imyeyu/api/modules/common/mapper/UserPrivacyMapper.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.imyeyu.api.modules.common.mapper; - -import com.imyeyu.api.modules.common.entity.UserPrivacy; -import com.imyeyu.spring.mapper.BaseMapper; - -/** - * 用户隐私控制 - * - * @author 夜雨 - * @since 2021-07-27 17:18 - */ -public interface UserPrivacyMapper extends BaseMapper { -} diff --git a/src/main/java/com/imyeyu/api/modules/common/mapper/UserProfileMapper.java b/src/main/java/com/imyeyu/api/modules/common/mapper/UserProfileMapper.java deleted file mode 100644 index 4a98276..0000000 --- a/src/main/java/com/imyeyu/api/modules/common/mapper/UserProfileMapper.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.imyeyu.api.modules.common.mapper; - -import com.imyeyu.api.modules.common.entity.UserProfile; -import com.imyeyu.spring.mapper.BaseMapper; - -/** - * 用户数据 - * - * @author 夜雨 - * @since 2021-07-27 17:04 - */ -public interface UserProfileMapper extends BaseMapper { -} diff --git a/src/main/java/com/imyeyu/api/modules/common/mapper/VersionMapper.java b/src/main/java/com/imyeyu/api/modules/common/mapper/VersionMapper.java deleted file mode 100644 index cea1e28..0000000 --- a/src/main/java/com/imyeyu/api/modules/common/mapper/VersionMapper.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.imyeyu.api.modules.common.mapper; - -import com.imyeyu.api.modules.common.entity.Version; -import com.imyeyu.spring.mapper.BaseMapper; -import org.apache.ibatis.annotations.Select; - -/** - * 版本管理 - * - * @author 夜雨 - * @since 2021-06-10 16:08 - */ -public interface VersionMapper extends BaseMapper { - - @Select("SELECT * FROM version WHERE name = #{name}" + NOT_DELETE) - Version queryByName(String name); -} diff --git a/src/main/java/com/imyeyu/api/modules/common/service/ArticleService.java b/src/main/java/com/imyeyu/api/modules/common/service/ArticleService.java new file mode 100644 index 0000000..8590a20 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/common/service/ArticleService.java @@ -0,0 +1,13 @@ +package com.imyeyu.api.modules.common.service; + +import com.imyeyu.api.modules.common.entity.Article; +import com.imyeyu.spring.service.BaseService; + +/** + * 文章服务 + * + * @author 夜雨 + * @since 2021-02-23 21:33 + */ +public interface ArticleService extends BaseService { +} diff --git a/src/main/java/com/imyeyu/api/modules/common/service/AttachmentService.java b/src/main/java/com/imyeyu/api/modules/common/service/AttachmentService.java index 0d72edc..aa0e137 100644 --- a/src/main/java/com/imyeyu/api/modules/common/service/AttachmentService.java +++ b/src/main/java/com/imyeyu/api/modules/common/service/AttachmentService.java @@ -1,9 +1,11 @@ package com.imyeyu.api.modules.common.service; import com.imyeyu.api.modules.common.entity.Attachment; -import com.imyeyu.java.bean.timi.TimiException; +import com.imyeyu.api.modules.common.vo.attach.BizDeleteReq; +import com.imyeyu.api.modules.common.vo.attach.BizUpdateReq; +import com.imyeyu.spring.bean.Page; +import com.imyeyu.spring.bean.PageResult; import com.imyeyu.spring.service.BaseService; -import com.mongodb.client.gridfs.model.GridFSFile; import java.io.InputStream; import java.util.List; @@ -11,28 +13,32 @@ import java.util.List; /** * 附件服务 * - *

删除和销毁都为数据库软删除,但删除不删除 MongoDB 文件,而销毁则删除 MongoDB 文件 - * * @author 夜雨 * @since 2023-08-15 10:21 */ -public interface AttachmentService extends BaseService { +public interface AttachmentService extends BaseService { - Attachment createMedia(Attachment attachment); + /** + * 按需获取或创建缩略图附件。 + *

当附件为图片或视频时,若指定尺寸的缩略图已存在则直接返回缓存, + * 否则生成并持久化后返回。不支持缩略图的附件类型返回 {@code null}。

+ * + * @param source 源附件(必须已持久化,mimeType 已填充) + * @param requestWidth 请求宽度,{@code null} 表示未指定 + * @param requestHeight 请求高度,{@code null} 表示未指定 + * @return 缩略图附件,不支持时返回 {@code null} + */ + Attachment fetchThumb(Attachment source, Integer requestWidth, Integer requestHeight); - void deleteMedia(Long thumbId) throws TimiException; + Attachment getByBizId(Attachment.BizType bizType, String bizId); - Attachment getByBizId(Attachment.BizType bizType, long bizId); + Attachment getByAttachType(Attachment.BizType bizType, String bizId, String attachType); - Attachment getByAttachType(Attachment.BizType bizType, long bizId, Enum attachType); + Attachment getByMongoId(String MongoId); - Attachment getByMongoId(String mongoId); + InputStream getInputStreamByMongoId(String MongoId); - GridFSFile readByMongoId(String mongoId); - - InputStream getInputStreamByMongoId(String mongoId); - - byte[] readAllByMongoId(String mongoId); + byte[] readAllByMongoId(String MongoId); /** * 根据业务获取所有附件 @@ -41,9 +47,8 @@ public interface AttachmentService extends BaseService { * @param bizId 业务 ID,可为 null * @param attachTypes 附件类型,可为 null * @return 所有附件 - * @throws TimiException 服务异常 */ - List listByBizId(Attachment.BizType bizType, Long bizId, Enum ...attachTypes); + List listByBizId(Attachment.BizType bizType, String bizId, String... attachTypes); /** * 根据业务获取所有附件 @@ -52,13 +57,26 @@ public interface AttachmentService extends BaseService { * @param bizId 业务 ID,可为 null * @param attachTypes 附件类型,可为 null * @return 附件数量 - * @throws TimiException 服务异常 */ - long countByBizId(Attachment.BizType bizType, Long bizId, Enum ...attachTypes); + long countByBizId(Attachment.BizType bizType, String bizId, String... attachTypes); - List listByMd5s(Attachment.BizType bizType, Long bizId, List> attachTypeList, List md5s); - - void deleteByBizId(Attachment.BizType bizType, long bizId, Enum ...attachTypes); + PageResult pageByBizId(Attachment.BizType bizType, String bizId, List attachTypeList, Page page); List listNeedDestroy(); + + /** + * 根据业务差分保存附件列表。 + * + *
    + *
  • 请求有、数据库无 → 新增({@link Attachment#getTempFileId()} 必填)
  • + *
  • 数据库有、请求无 → 删除
  • + *
  • 请求有、数据库有、无 tempFileId → 仅更新元数据
  • + *
  • 请求有、数据库有、有 tempFileId → 更新文件与元数据
  • + *
+ * + * @param req 差分更新请求 + */ + void updateByBiz(BizUpdateReq req); + + void deleteByBiz(BizDeleteReq req); } diff --git a/src/main/java/com/imyeyu/api/modules/common/service/CommentReplyService.java b/src/main/java/com/imyeyu/api/modules/common/service/CommentReplyService.java deleted file mode 100644 index 49185b7..0000000 --- a/src/main/java/com/imyeyu/api/modules/common/service/CommentReplyService.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.imyeyu.api.modules.common.service; - -import com.imyeyu.api.modules.common.entity.CommentReply; -import com.imyeyu.spring.service.BaseService; - -/** - * 评论回复服务 - * - * @author 夜雨 - * @since 2021-08-24 10:33 - */ -public interface CommentReplyService extends BaseService { -} diff --git a/src/main/java/com/imyeyu/api/modules/common/service/CommentService.java b/src/main/java/com/imyeyu/api/modules/common/service/CommentService.java index ba56441..9a63a6d 100644 --- a/src/main/java/com/imyeyu/api/modules/common/service/CommentService.java +++ b/src/main/java/com/imyeyu/api/modules/common/service/CommentService.java @@ -1,13 +1,124 @@ package com.imyeyu.api.modules.common.service; import com.imyeyu.api.modules.common.entity.Comment; +import com.imyeyu.api.modules.user.entity.UserBehaviorRecord; +import com.imyeyu.api.modules.common.vo.comment.CommentLayerQueryReq; +import com.imyeyu.api.modules.common.vo.comment.CommentLayerResp; +import com.imyeyu.api.modules.common.vo.comment.CommentNodeResp; +import com.imyeyu.api.modules.common.vo.comment.CommentThreadResp; +import com.imyeyu.api.modules.common.vo.comment.FlatCommentListResp; +import com.imyeyu.api.modules.common.vo.comment.NestedCommentListResp; +import com.imyeyu.spring.bean.Page; +import com.imyeyu.spring.bean.PageResult; import com.imyeyu.spring.service.BaseService; /** * 评论服务 * * @author 夜雨 - * @since 2021-02-23 21:32 + * @since 2026-05-27 17:20 */ -public interface CommentService extends BaseService { +public interface CommentService extends BaseService { + + /** + * 点赞评论 + * + * @param commentId 评论 ID + * @param userId 用户 ID + */ + void like(String commentId, String userId); + + /** + * 不喜欢评论 + * + * @param commentId 评论 ID + * @param userId 用户 ID + */ + void unlike(String commentId, String userId); + + /** + * 查询当前用户点赞评论记录 + * + * @param page 分页参数 + * @param userId 用户 ID + * @return 点赞评论记录分页 + */ + PageResult listLikedRecord(Page page, String userId); + + /** + * 查询扁平模式主评论列表 + * + * @param bizType 业务类型 + * @param bizId 业务 ID + * @param offset 起始偏移量 + * @param currentUserId 当前用户 ID + * @return 主评论列表 + */ + FlatCommentListResp listFlatRoot(String bizType, String bizId, int offset, String currentUserId); + + /** + * 查询层级模式主评论列表 + * + * @param bizType 业务类型 + * @param bizId 业务 ID + * @param offset 起始偏移量 + * @param currentUserId 当前用户 ID + * @return 主评论列表 + */ + NestedCommentListResp listNestedRoot(String bizType, String bizId, int offset, String currentUserId); + + /** + * 查询用户侧评论线程 + * + * @param commentId 评论 ID + * @param offset 起始偏移量 + * @param currentUserId 当前用户 ID + * @return 评论线程 + */ + CommentThreadResp getUserThread(String commentId, int offset, String currentUserId); + + /** + * 查询 SDK 评论层 + * + * @param req 查询请求 + * @param currentUserId 当前用户 ID + * @return 评论层 + */ + CommentLayerResp listSdkLayer(CommentLayerQueryReq req, String currentUserId); + + /** + * 查询用户侧评论节点 + * + * @param commentId 评论 ID + * @param currentUserId 当前用户 ID + * @return 评论节点 + */ + CommentNodeResp getUserNode(String commentId, String currentUserId); + + /** + * 查询用户侧直属子评论分页 + * + * @param commentId 评论 ID + * @param offset 起始偏移量 + * @param currentUserId 当前用户 ID + * @return 子评论分页 + */ + CommentLayerResp getUserChildren(String commentId, int offset, String currentUserId); + + /** + * 查询评论树详情 + * + * @param id 评论 ID + * @param currentUserId 当前用户 ID + * @return 评论树详情 + */ + Comment detailTree(String id, String currentUserId); + + /** + * 删除当前用户自己的评论 + * + * @param id 评论 ID + * @param userId 用户 ID + */ + void deleteOwned(String id, String userId); } diff --git a/src/main/java/com/imyeyu/api/modules/common/service/DataDictOptionService.java b/src/main/java/com/imyeyu/api/modules/common/service/DataDictOptionService.java new file mode 100644 index 0000000..5e273da --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/common/service/DataDictOptionService.java @@ -0,0 +1,13 @@ +package com.imyeyu.api.modules.common.service; + +import com.imyeyu.api.modules.common.entity.DataDictOption; +import com.imyeyu.spring.service.BaseService; + +/** + * 数据字典选项服务 + * + * @author 夜雨 + * @since 2026-05-13 14:46 + */ +public interface DataDictOptionService extends BaseService { +} diff --git a/src/main/java/com/imyeyu/api/modules/common/service/DataDictService.java b/src/main/java/com/imyeyu/api/modules/common/service/DataDictService.java new file mode 100644 index 0000000..85318d7 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/common/service/DataDictService.java @@ -0,0 +1,13 @@ +package com.imyeyu.api.modules.common.service; + +import com.imyeyu.api.modules.common.entity.DataDict; +import com.imyeyu.spring.service.BaseService; + +/** + * 数据字典服务 + * + * @author 夜雨 + * @since 2026-05-13 14:46 + */ +public interface DataDictService extends BaseService { +} diff --git a/src/main/java/com/imyeyu/api/modules/common/service/EmailFilterService.java b/src/main/java/com/imyeyu/api/modules/common/service/EmailFilterService.java new file mode 100644 index 0000000..8288cd8 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/common/service/EmailFilterService.java @@ -0,0 +1,13 @@ +package com.imyeyu.api.modules.common.service; + +import com.imyeyu.api.modules.common.entity.EmailFilter; +import com.imyeyu.spring.service.BaseService; + +/** + * 邮件过滤服务 + * + * @author 夜雨 + * @since 2026-05-13 14:46 + */ +public interface EmailFilterService extends BaseService { +} diff --git a/src/main/java/com/imyeyu/api/modules/common/service/EmailQueueService.java b/src/main/java/com/imyeyu/api/modules/common/service/EmailQueueService.java deleted file mode 100644 index d5ba74f..0000000 --- a/src/main/java/com/imyeyu/api/modules/common/service/EmailQueueService.java +++ /dev/null @@ -1,52 +0,0 @@ -package com.imyeyu.api.modules.common.service; - -import com.imyeyu.java.bean.timi.TimiException; -import com.imyeyu.api.modules.common.entity.EmailQueue; -import com.imyeyu.api.modules.common.entity.EmailQueueLog; -import com.imyeyu.spring.service.CreatableService; -import com.imyeyu.spring.service.DestroyableService; - -import java.util.List; - -/** - * 邮件推送队列服务 - * - * @author 夜雨 - * @since 2021-08-24 16:20 - */ -public interface EmailQueueService extends CreatableService, DestroyableService { - - /** - * 根据推送类型和数据 ID 获取推送对象 - * - * @param type 推送类型 - * @param dataId 数据 ID - * @return 推送对象 - * @throws TimiException 服务异常 - */ - EmailQueue get(EmailQueue.BizType type, Long dataId); - - /** - * 移出队列 - * - * @param UUID UUID - * @throws TimiException 服务异常 - */ - void destroy(String UUID); - - /** - * 添加推送日志 - * - * @param log 推送日志 - * @throws TimiException 服务异常 - */ - void addLog(EmailQueueLog log); - - /** - * 遍历推送队列 - * - * @return 推送队列 - * @throws TimiException 服务异常 - */ - List listAll(); -} diff --git a/src/main/java/com/imyeyu/api/modules/common/service/FeedbackService.java b/src/main/java/com/imyeyu/api/modules/common/service/FeedbackService.java deleted file mode 100644 index 5055cae..0000000 --- a/src/main/java/com/imyeyu/api/modules/common/service/FeedbackService.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.imyeyu.api.modules.common.service; - -import com.imyeyu.api.modules.common.vo.FeedbackRequest; - -/** - * 反馈服务 - * - * @author 夜雨 - * @since 2021-11-16 22:27 - */ -public interface FeedbackService { - - void create(FeedbackRequest request); -} diff --git a/src/main/java/com/imyeyu/api/modules/common/service/IconService.java b/src/main/java/com/imyeyu/api/modules/common/service/IconService.java index 9a801df..7ac78b6 100644 --- a/src/main/java/com/imyeyu/api/modules/common/service/IconService.java +++ b/src/main/java/com/imyeyu/api/modules/common/service/IconService.java @@ -1,12 +1,11 @@ package com.imyeyu.api.modules.common.service; import com.imyeyu.api.modules.common.entity.Icon; -import com.imyeyu.api.modules.common.vo.icon.AllResponse; -import com.imyeyu.api.modules.common.vo.icon.NamePage; -import com.imyeyu.api.modules.common.vo.icon.UnicodePage; -import com.imyeyu.spring.bean.PageResult; import com.imyeyu.spring.service.PageableService; +import java.util.List; +import java.util.Map; + /** * 图标服务 * @@ -15,9 +14,21 @@ import com.imyeyu.spring.service.PageableService; */ public interface IconService extends PageableService { - AllResponse listAll(Long latest); + /** + * 查询全部图标名称 + * + * @return 图标名称列表 + */ + List listName(); - PageResult pageByName(NamePage page); + Map mapSvg(); - PageResult pageByUnicode(UnicodePage page); + Map mapUnicode(); + + /** + * 导出 TTF 字体文件 + * + * @return TTF 字体文件字节 + */ + byte[] exportTtf(); } diff --git a/src/main/java/com/imyeyu/api/modules/common/service/MultilingualService.java b/src/main/java/com/imyeyu/api/modules/common/service/MultilingualService.java index 576ec60..47231cd 100644 --- a/src/main/java/com/imyeyu/api/modules/common/service/MultilingualService.java +++ b/src/main/java/com/imyeyu/api/modules/common/service/MultilingualService.java @@ -1,8 +1,7 @@ package com.imyeyu.api.modules.common.service; -import com.imyeyu.java.bean.Language; import com.imyeyu.api.modules.common.entity.Multilingual; -import com.imyeyu.spring.service.UpdatableService; +import com.imyeyu.spring.service.BaseService; import java.util.List; @@ -10,15 +9,16 @@ import java.util.List; * @author 夜雨 * @since 2023-10-25 10:47 */ -public interface MultilingualService extends UpdatableService { +public interface MultilingualService extends BaseService { - Long create(String key, String zhCN); + default String create(String zhCN) { + Multilingual multilingual = new Multilingual(); + multilingual.setZhCN(zhCN); + create(multilingual); + return multilingual.getId(); + } - Long createIfNotExist(String key, String zhCN); + String createIfDifferent(String id, String zhCN); - String get(Language.Enum language, Long id); - - String getByKey(Language.Enum language, String key); - - List listByNotTranslate(); + List listByIdList(List idList); } diff --git a/src/main/java/com/imyeyu/api/modules/common/service/NotifyDetailService.java b/src/main/java/com/imyeyu/api/modules/common/service/NotifyDetailService.java new file mode 100644 index 0000000..3fdf427 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/common/service/NotifyDetailService.java @@ -0,0 +1,13 @@ +package com.imyeyu.api.modules.common.service; + +import com.imyeyu.api.modules.common.entity.NotifyDetail; +import com.imyeyu.spring.service.BaseService; + +/** + * 通知详情服务 + * + * @author 夜雨 + * @since 2026-05-13 14:46 + */ +public interface NotifyDetailService extends BaseService { +} diff --git a/src/main/java/com/imyeyu/api/modules/common/service/NotifyService.java b/src/main/java/com/imyeyu/api/modules/common/service/NotifyService.java new file mode 100644 index 0000000..80ebf9f --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/common/service/NotifyService.java @@ -0,0 +1,13 @@ +package com.imyeyu.api.modules.common.service; + +import com.imyeyu.api.modules.common.entity.Notify; +import com.imyeyu.spring.service.BaseService; + +/** + * 通知服务 + * + * @author 夜雨 + * @since 2026-05-13 14:46 + */ +public interface NotifyService extends BaseService { +} diff --git a/src/main/java/com/imyeyu/api/modules/common/service/SettingService.java b/src/main/java/com/imyeyu/api/modules/common/service/SettingService.java index 8efc9dc..1ce808b 100644 --- a/src/main/java/com/imyeyu/api/modules/common/service/SettingService.java +++ b/src/main/java/com/imyeyu/api/modules/common/service/SettingService.java @@ -1,80 +1,47 @@ package com.imyeyu.api.modules.common.service; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.node.ArrayNode; -import com.fasterxml.jackson.databind.node.ObjectNode; -import com.imyeyu.api.modules.common.bean.SettingKey; +import com.imyeyu.api.modules.common.bean.attachment.SettingSupport; import com.imyeyu.api.modules.common.entity.Setting; -import com.imyeyu.java.bean.timi.TimiException; -import com.imyeyu.spring.service.UpdatableService; +import com.imyeyu.java.TimiJava; +import com.imyeyu.spring.service.BaseService; +import com.imyeyu.utils.Text; -import java.util.Arrays; import java.util.List; /** - * 系统配置服务 + * 配置服务 * * @author 夜雨 * @since 2021-07-20 22:06 */ -public interface SettingService extends UpdatableService { +public interface SettingService extends BaseService { - default List listByKeys(SettingKey... keys) { - return listByKeys(Arrays.asList(keys)); + Setting getByCode(String bizType, String bizId, String code); + + default Setting getSystem(Enum bizType) { + return getByCode(Text.bizClassName(bizType.getClass()), TimiJava.ZERO_UUID, bizType.toString()); } - List listByKeys(List keyList); + List listByBizId(String bizType, String bizId); - Setting getByKey(SettingKey key); + default List listBySettingSupport(SettingSupport settingSupport) { + String bizType = settingSupport.getClass().getSimpleName().toUpperCase(); + return listByBizId(bizType, settingSupport.getId()); + } /** - * 获取指定类型配置值字符串 + * 差分更新设置 * - * @param key 键 - * @return 配置值 + * @param bizType 业务类型 + * @param bizId 业务 ID,可为空 + * @param list 设置列表 */ - String getAsString(SettingKey key); + void updateByBizId(String bizType, String bizId, List list); - int getAsInt(SettingKey key); + default void updateBySettingSupport(SettingSupport settingSupport) { + String bizType = settingSupport.getClass().getSimpleName().toUpperCase(); + updateByBizId(bizType, settingSupport.getId(), settingSupport.getSettingList()); + } - long getAsLong(SettingKey key); - - double getAsDouble(SettingKey key); - - /** - * 获取为布尔值 - * - * @param key 键 - * @return 配置值 - * @throws TimiException 服务异常 - */ - boolean is(SettingKey key); - - /** - * 获取为布尔值,并取反 - * - * @param key 键 - * @return 配置值 - * @throws TimiException 服务异常 - */ - boolean not(SettingKey key); - - JsonNode getAsJsonNode(SettingKey key); - - ObjectNode getAsJsonObject(SettingKey key); - - ArrayNode getAsArrayNode(SettingKey key); - - T fromJson(SettingKey key, Class clazz); - - T fromJson(SettingKey key, TypeReference typeReference); - - T fromYaml(SettingKey key, Class clazz); - - List listAll(); - - void clearCache(SettingKey key); - - void flushCache(); + void deleteByBizId(String bizType, String bizId); } diff --git a/src/main/java/com/imyeyu/api/modules/common/service/TagService.java b/src/main/java/com/imyeyu/api/modules/common/service/TagService.java index 41d41a8..3514d3c 100644 --- a/src/main/java/com/imyeyu/api/modules/common/service/TagService.java +++ b/src/main/java/com/imyeyu/api/modules/common/service/TagService.java @@ -1,17 +1,11 @@ package com.imyeyu.api.modules.common.service; -import com.imyeyu.java.bean.timi.TimiException; import com.imyeyu.api.modules.common.entity.Tag; -import com.imyeyu.spring.service.CreatableService; -import com.imyeyu.spring.service.DeletableService; - -import java.util.List; +import com.imyeyu.spring.service.BaseService; /** * @author 夜雨 * @since 2025-05-30 22:46 */ -public interface TagService extends CreatableService, DeletableService { - - List listByBizID(Tag.BizType bizType, String bizID) throws TimiException; +public interface TagService extends BaseService { } diff --git a/src/main/java/com/imyeyu/api/modules/common/service/TaskService.java b/src/main/java/com/imyeyu/api/modules/common/service/TaskService.java deleted file mode 100644 index 7b792ff..0000000 --- a/src/main/java/com/imyeyu/api/modules/common/service/TaskService.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.imyeyu.api.modules.common.service; - -import com.imyeyu.java.bean.timi.TimiException; -import com.imyeyu.api.modules.common.entity.Task; - -import java.util.List; - -/** - * 任务服务 - * - * @author 夜雨 - * @since 2022-04-03 15:36 - */ -public interface TaskService { - - /** - * 查询所有公开任务 - * - * @return 公开任务列表 - * @throws TimiException 服务异常 - */ - List listAll4Public(); -} diff --git a/src/main/java/com/imyeyu/api/modules/common/service/TempFileService.java b/src/main/java/com/imyeyu/api/modules/common/service/TempFileService.java index 80d911c..dcb5902 100644 --- a/src/main/java/com/imyeyu/api/modules/common/service/TempFileService.java +++ b/src/main/java/com/imyeyu/api/modules/common/service/TempFileService.java @@ -1,6 +1,7 @@ package com.imyeyu.api.modules.common.service; -import com.imyeyu.api.modules.common.vo.TempFileResponse; +import com.imyeyu.api.modules.common.vo.attach.TempFileResp; +import com.imyeyu.api.modules.common.vo.attach.TempFileSaveReq; import org.springframework.web.multipart.MultipartFile; import java.util.List; @@ -11,5 +12,14 @@ import java.util.List; */ public interface TempFileService { - List store(List files, Long ttl); + /** + * 存储临时文件 + * + * @param files 文件列表 + * @param ttl 存活时间(毫秒) + * @return 临时文件响应列表 + */ + List create(List files, Long ttl); + + void save(TempFileSaveReq request); } diff --git a/src/main/java/com/imyeyu/api/modules/common/service/TemplateService.java b/src/main/java/com/imyeyu/api/modules/common/service/TemplateService.java deleted file mode 100644 index f5a21cc..0000000 --- a/src/main/java/com/imyeyu/api/modules/common/service/TemplateService.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.imyeyu.api.modules.common.service; - -import com.imyeyu.api.modules.common.entity.Template; - -/** - * @author 夜雨 - * @since 2023-09-22 16:38 - */ -public interface TemplateService { - - Template get(Template.BizType bizType, String bizCode); -} diff --git a/src/main/java/com/imyeyu/api/modules/common/service/UserConfigService.java b/src/main/java/com/imyeyu/api/modules/common/service/UserConfigService.java deleted file mode 100644 index 96421d7..0000000 --- a/src/main/java/com/imyeyu/api/modules/common/service/UserConfigService.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.imyeyu.api.modules.common.service; - -import com.imyeyu.api.modules.common.entity.UserConfig; -import com.imyeyu.spring.service.CreatableService; -import com.imyeyu.spring.service.GettableService; -import com.imyeyu.spring.service.UpdatableService; - -/** - * 用户设置服务 - * - * @author 夜雨 - * @since 2021-08-12 16:23 - */ -public interface UserConfigService extends GettableService, CreatableService, UpdatableService { -} diff --git a/src/main/java/com/imyeyu/api/modules/common/service/UserPrivacyService.java b/src/main/java/com/imyeyu/api/modules/common/service/UserPrivacyService.java deleted file mode 100644 index b8535d5..0000000 --- a/src/main/java/com/imyeyu/api/modules/common/service/UserPrivacyService.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.imyeyu.api.modules.common.service; - -import com.imyeyu.api.modules.common.entity.UserPrivacy; -import com.imyeyu.spring.service.CreatableService; -import com.imyeyu.spring.service.GettableService; -import com.imyeyu.spring.service.UpdatableService; - -/** - * 用户隐私控制服务 - * - * @author 夜雨 - * @since 2021-07-27 17:18 - */ -public interface UserPrivacyService extends GettableService, CreatableService, UpdatableService { -} diff --git a/src/main/java/com/imyeyu/api/modules/common/service/UserProfileService.java b/src/main/java/com/imyeyu/api/modules/common/service/UserProfileService.java deleted file mode 100644 index f666301..0000000 --- a/src/main/java/com/imyeyu/api/modules/common/service/UserProfileService.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.imyeyu.api.modules.common.service; - -import com.imyeyu.api.modules.common.entity.UserProfile; -import com.imyeyu.api.modules.common.vo.user.UserRequest; -import com.imyeyu.spring.service.CreatableService; -import com.imyeyu.spring.service.GettableService; -import com.imyeyu.spring.service.UpdatableService; - -/** - * 用户数据服务 - * - * @author 夜雨 - * @since 2021-07-27 17:05 - */ -public interface UserProfileService extends GettableService, CreatableService, UpdatableService { - - void update(UserRequest request); -} diff --git a/src/main/java/com/imyeyu/api/modules/common/service/UserService.java b/src/main/java/com/imyeyu/api/modules/common/service/UserService.java deleted file mode 100644 index bb8350b..0000000 --- a/src/main/java/com/imyeyu/api/modules/common/service/UserService.java +++ /dev/null @@ -1,149 +0,0 @@ -package com.imyeyu.api.modules.common.service; - -import com.imyeyu.java.bean.timi.TimiException; -import com.imyeyu.api.modules.common.entity.User; -import com.imyeyu.api.modules.common.vo.user.LoginRequest; -import com.imyeyu.api.modules.common.vo.user.LoginResponse; -import com.imyeyu.api.modules.common.vo.user.RegisterRequest; -import com.imyeyu.api.modules.common.vo.user.UserView; -import com.imyeyu.spring.service.GettableService; - -/** - * 用户管理服务 - *

操作任何用户数据前应保证调用 find,以确保用户存在且没有注销(不需要判断,不存在时 find 会抛异常),如果不需要返回数据,可以调用 - * 一次 exist(),也不需要判断 - *

经过令牌验证的操作也不需要检验用户是否存在,不存在的用户无法登录也无法生成正确的令牌 - * - * @author 夜雨 - * @since 2021-02-23 21:32 - */ -public interface UserService extends GettableService { - - /** - * 注册用户,传入参 User.password 是明文 - * - * @param request 注册数据 - * @return 登录返回数据 - * @throws TimiException 服务异常 - */ - LoginResponse register(RegisterRequest request); - - /** - * 执行登录 - * - * @param request 登录数据 - * @return 登录返回数据 - * @throws TimiException 服务异常 - */ - LoginResponse login(LoginRequest request); - - /** - * 用户视图,未经过权限过滤和隐私过滤数据 - * - * @param userId - * @return - * @throws TimiException - */ - UserView view(Long userId); - - /** - * 密码验证 - * - * @param digestPassword 摘要密码 - * @param password 明文密码 - * @return true 为无效密码 - * @throws TimiException 服务异常 - */ - boolean isInvalidPassword(String digestPassword, String password); - - /** - * 令牌登录 - * - * @return 登录结果 - */ - LoginResponse login4Token(); - - /** 退出登录 */ - void logout(); - - /** - * 查找用户 - * - * @param user UID、邮箱和用户名 - * @return 用户 - * @throws TimiException 服务异常 - */ - User get(String user); - - boolean isLogged(); - - User getLoginUser(); - - /** - * 根据用户名查找 - * - * @param name 用户名 - * @return 账号数据 - * @throws TimiException 服务异常 - */ - User getByName(String name); - - /** - * 根据邮箱查找 - * - * @param email 邮箱 - * @return 账号数据 - * @throws TimiException 服务异常 - */ - User getByEmail(String email); - - /** - * 发送邮箱验证邮件 - * - * @throws TimiException 服务异常 - */ - void sendEmailVerify(); - - /** - * 邮箱验证回调 - * - * @param key 邮件密钥(非登录令牌) - * @throws TimiException 服务异常 - */ - void emailVerifyCallback(String key); - - /** - * 修改密码 - * - * @param oldValue 旧密码 - * @param newValue 新密码 - * @throws TimiException 服务异常 - */ - void updatePassword(String oldValue, String newValue); - - - /** - * 发送找回密码验证邮件 - * - * @param user UID、用户名或邮箱 - * @throws TimiException 服务异常 - */ - void sendPasswordForgetVerify(String user); - - /** - * 重置密码(需要密钥,由找回密码 sendPasswordForgetVerify 接口通过发送邮件分发) - * - * @param key 密钥 - * @param password 明文密码 - * @throws TimiException 服务异常 - */ - void resetPasswordByKey(String key, String password); - - /** - * 注销 - * - * @param password 密码校验 - * @throws TimiException 服务异常 - */ - void cancel(String password); -} diff --git a/src/main/java/com/imyeyu/api/modules/common/service/VersionService.java b/src/main/java/com/imyeyu/api/modules/common/service/VersionService.java deleted file mode 100644 index 35e2773..0000000 --- a/src/main/java/com/imyeyu/api/modules/common/service/VersionService.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.imyeyu.api.modules.common.service; - -import com.imyeyu.api.modules.common.entity.Version; - -/** - * 版本管理服务 - * - * @author 夜雨 - * @since 2021-06-10 16:06 - */ -public interface VersionService { - - Version getByName(String name); -} diff --git a/src/main/java/com/imyeyu/api/modules/common/service/implement/ArticleServiceImplement.java b/src/main/java/com/imyeyu/api/modules/common/service/implement/ArticleServiceImplement.java new file mode 100644 index 0000000..616b3b4 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/common/service/implement/ArticleServiceImplement.java @@ -0,0 +1,29 @@ +package com.imyeyu.api.modules.common.service.implement; + +import com.imyeyu.api.modules.common.entity.Article; +import com.imyeyu.api.modules.common.mapper.ArticleMapper; +import com.imyeyu.api.modules.common.service.ArticleService; +import com.imyeyu.spring.mapper.BaseMapper; +import com.imyeyu.spring.service.AbstractEntityService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +/** + * 文章服务实现 + * + * @author 夜雨 + * @since 2021-02-17 17:48 + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class ArticleServiceImplement extends AbstractEntityService implements ArticleService { + + private final ArticleMapper mapper; + + @Override + protected BaseMapper mapper() { + return mapper; + } +} diff --git a/src/main/java/com/imyeyu/api/modules/common/service/implement/AttachmentServiceImplement.java b/src/main/java/com/imyeyu/api/modules/common/service/implement/AttachmentServiceImplement.java index 87294e1..1238392 100644 --- a/src/main/java/com/imyeyu/api/modules/common/service/implement/AttachmentServiceImplement.java +++ b/src/main/java/com/imyeyu/api/modules/common/service/implement/AttachmentServiceImplement.java @@ -3,19 +3,24 @@ package com.imyeyu.api.modules.common.service.implement; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; import com.imyeyu.api.config.dbsource.TimiServerDBConfig; -import com.imyeyu.api.modules.common.bean.MediaAttach; -import com.imyeyu.api.modules.common.bean.Metadata; +import com.imyeyu.api.modules.common.bean.attachment.MediaAttach; +import com.imyeyu.api.modules.common.bean.attachment.Metadata; import com.imyeyu.api.modules.common.entity.Attachment; import com.imyeyu.api.modules.common.mapper.AttachmentMapper; import com.imyeyu.api.modules.common.service.AttachmentService; -import com.imyeyu.api.modules.common.service.SettingService; +import com.imyeyu.api.modules.common.vo.attach.BizDeleteReq; +import com.imyeyu.api.modules.common.vo.attach.BizUpdateReq; import com.imyeyu.api.util.JavaCV; import com.imyeyu.io.IO; import com.imyeyu.java.TimiJava; +import com.imyeyu.java.bean.CallbackArg; import com.imyeyu.java.bean.timi.TimiCode; import com.imyeyu.java.bean.timi.TimiException; +import com.imyeyu.java.ref.Ref; import com.imyeyu.network.Network; import com.imyeyu.spring.TimiSpring; +import com.imyeyu.spring.bean.Page; +import com.imyeyu.spring.bean.PageResult; import com.imyeyu.spring.mapper.BaseMapper; import com.imyeyu.spring.service.AbstractEntityService; import com.imyeyu.utils.Time; @@ -38,9 +43,11 @@ import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; -import java.util.Arrays; import java.util.List; +import java.util.Objects; +import java.util.Set; import java.util.UUID; +import java.util.stream.Collectors; /** * @author 夜雨 @@ -49,9 +56,7 @@ import java.util.UUID; @Slf4j @Service @RequiredArgsConstructor -public class AttachmentServiceImplement extends AbstractEntityService implements AttachmentService { - - private final SettingService settingService; +public class AttachmentServiceImplement extends AbstractEntityService implements AttachmentService { private final AttachmentMapper mapper; @@ -60,21 +65,59 @@ public class AttachmentServiceImplement extends AbstractEntityService mapper() { + protected BaseMapper mapper() { return mapper; } + @Transactional(TimiServerDBConfig.ROLLBACKER) + @Override + public void delete(String id) { + mapper.selectThumbsBySourceId(id).forEach(thumb -> super.delete(thumb.getId())); + super.delete(id); + } + + @Transactional(TimiServerDBConfig.ROLLBACKER) + @Override + public void destroy(String id) { + CallbackArg doDestroy = attach -> { + try { + if (attach.getMongoId() != null) { + gridFsTemplate.delete(Query.query(Criteria.where("_id").is(attach.getMongoId()))); + } + if (!attach.isDeleted()) { + attach.setDeletedAt(Time.now()); + } + attach.setIsDestroyed(true); + attach.setDestroyAt(Time.now()); + mapper.update(attach); + + if (Ref.toType(MediaAttach.Type.class, attach.getAttachType()) == MediaAttach.Type.THUMB) { + // 缩略图记录物理删除 + mapper.destroy(attach.getId()); + } + } catch (Exception e) { + log.error("delete mongo file error, id={}", attach.getId(), e); + throw new TimiException(TimiCode.ERROR).msgKey("TODO delete mongo file error"); + } + }; + mapper.selectThumbsBySourceId(id).forEach(doDestroy::handler); + doDestroy.handler(mapper.selectRaw(id)); + } + @Transactional(TimiServerDBConfig.ROLLBACKER) @Override public void create(Attachment attachment) { - TimiException.required(attachment.getBizType(), "not found attachment.bizType"); + TimiException.required(attachment.getBizType(), "not found String"); TimiException.required(attachment.getName(), "not found attachment.name"); String mongoId = null; try { + if (attachment.getFile() != null) { + attachment.setInputStream(attachment.getFile().getInputStream()); + } InputStream is = attachment.getInputStream(); TimiException.required(is, "not found attachment.inputStream"); - StringBuilder mongoName = new StringBuilder(attachment.getBizType().toString()); + StringBuilder mongoName = new StringBuilder(attachment.getBizType().name()); if (TimiJava.isNotEmpty(attachment.getAttachType())) { mongoName.append("_").append(attachment.getAttachType().toUpperCase()).append("_"); } @@ -91,108 +134,154 @@ public class AttachmentServiceImplement extends AbstractEntityService Thumbnails.of(sourceStream).width(256).keepAspectRatio(true).toOutputStream(thumbStream); - case "video/mp4", "video/quicktime" -> { - log.info("capturing thumbnail: {}", attachment.getName()); - long start = Time.now(); - File tempFile = IO.file("temp/%s_%s".formatted(UUID.randomUUID().toString(), attachment.getName())); - try { - IO.toFile(tempFile, sourceStream); - ByteArrayOutputStream baos = JavaCV.captureThumbnail(IO.getInputStream(tempFile), 2); - Thumbnails.of(IO.toInputStream(baos)).width(256).keepAspectRatio(true).toOutputStream(thumbStream); - log.info("captured thumbnail: {} at {} ms", attachment.getName(), Time.now() - start); - } finally { - IO.destroy(tempFile); - } + // 存储新文件 + newMongoId = gridFsTemplate.store(stream, mongoName.toString()).toString(); + GridFSFile gridFSFile = gridFsTemplate.findOne(new Query(Criteria.where("_id").is(newMongoId))); + attachment.setMongoId(newMongoId); + attachment.setSize(gridFSFile.getLength()); + attachment.setMd5(IO.md5(gridFSBucket.openDownloadStream(gridFSFile.getObjectId()))); + attachment.setMimeType(new Tika().detect(gridFSBucket.openDownloadStream(gridFSFile.getObjectId()))); + if (attachment.getMimeType().startsWith("image")) { + BufferedImage image = ImageIO.read(gridFSBucket.openDownloadStream(gridFSFile.getObjectId())); + attachment.setMetadata(TimiJava.defaultIfNull(attachment.getMetadata(), jackson.createObjectNode())); + if (attachment.getMetadata() instanceof ObjectNode obj) { + obj.put("width", image.getWidth()); + obj.put("height", image.getHeight()); } } - Metadata.ThumbImage thumbMetadata = new Metadata.ThumbImage(); - thumbMetadata.setSourceId(attachment.getId()); - thumbMetadata.setSourceMongoId(attachment.getMongoId()); - thumbMetadata.setSourceMimeType(mimeType); - Attachment thumbAttach = new Attachment(); - thumbAttach.setName(Network.simpleURIFileName(attachment.getName()) + ".png"); - thumbAttach.setBizType(attachment.getBizType()); - thumbAttach.setBizId(attachment.getBizId()); - thumbAttach.setAttachTypeValue(MediaAttach.Type.THUMB); - thumbAttach.setMetadata(jackson.valueToTree(thumbMetadata)); - thumbAttach.setInputStream(new ByteArrayInputStream(thumbStream.toByteArray())); - create(thumbAttach); - - return get(thumbAttach.getId()); + // 更新数据库后删除旧 GridFS 文件 + super.update(attachment); + gridFsTemplate.delete(Query.query(Criteria.where("_id").is(old.getMongoId()))); } catch (Exception e) { - log.error("create media attachment error", e); - throw new TimiException(TimiCode.ERROR).msgKey("TODO create media attachment error"); + // 新文件已上传但后续操作失败,清理新文件防止孤儿数据 + if (newMongoId != null) { + gridFsTemplate.delete(Query.query(Criteria.where("_id").is(newMongoId))); + } + log.error("update error", e); + throw new TimiException(TimiCode.ERROR).msgKey("TODO update file error"); } } @Transactional(TimiServerDBConfig.ROLLBACKER) @Override - public void deleteMedia(Long thumbId) throws TimiException { - Attachment attachment = get(thumbId); - delete(attachment.getId()); - Metadata.ThumbImage thumbMetadata = jackson.convertValue(attachment.getMetadata(), Metadata.ThumbImage.class); - delete(thumbMetadata.getSourceId()); + public Attachment fetchThumb(Attachment source, Integer requestWidth, Integer requestHeight) { + TimiException.requiredTrue(requestWidth != null || requestHeight != null, "required width or height"); + String mimeType = source.getMimeType(); + boolean isImage = mimeType != null && Set.of("image/png", "image/bmp", "image/jpeg").contains(mimeType); + boolean isVideo = mimeType != null && Set.of("video/mp4", "video/quicktime").contains(mimeType); + if (!isImage && !isVideo) { + return null; + } + // 命中缓存直接返回 + Attachment cached = mapper.selectThumb(source.getBizType(), source.getId(), requestWidth, requestHeight); + if (cached != null) { + return cached; + } + try { + ByteArrayOutputStream thumbStream = new ByteArrayOutputStream(); + CallbackArg applyThumbSize = inputStream -> { + try { + Thumbnails.Builder builder = Thumbnails.of(inputStream); + if (requestWidth != null && requestHeight != null) { + builder.size(requestWidth, requestHeight).keepAspectRatio(false).toOutputStream(thumbStream); + } else if (requestWidth != null) { + builder.width(requestWidth).keepAspectRatio(true).toOutputStream(thumbStream); + } else { + builder.height(requestHeight).keepAspectRatio(true).toOutputStream(thumbStream); + } + } catch (IOException e) { + log.error("applyThumbSize error for fetchThumb", e); + throw new RuntimeException(e); + } + }; + if (isImage) { + applyThumbSize.handler(source.openInputStream()); + } else { + String name = source.getName(); + File tempFile = IO.file("temp/%s_%s".formatted(UUID.randomUUID(), name)); + try { + IO.toFile(tempFile, source.openInputStream()); + ByteArrayOutputStream baos = JavaCV.captureThumbnail(IO.getInputStream(tempFile), 2); + applyThumbSize.handler(IO.toInputStream(baos)); + } finally { + IO.destroy(tempFile); + } + } + Metadata.ThumbImage thumbMeta = new Metadata.ThumbImage(); + thumbMeta.setSourceId(source.getId()); + thumbMeta.setSourceMongoId(source.getMongoId()); + thumbMeta.setSourceMimeType(mimeType); + thumbMeta.setRequestWidth(requestWidth); + thumbMeta.setRequestHeight(requestHeight); + + Attachment thumb = new Attachment(); + thumb.setName(Network.simpleURIFileName(source.getName()) + ".png"); + thumb.setBizType(source.getBizType()); + thumb.setBizId(source.getId()); + thumb.setAttachType(MediaAttach.Type.THUMB.toString()); + thumb.setMetadata(jackson.valueToTree(thumbMeta)); + thumb.setInputStream(new ByteArrayInputStream(thumbStream.toByteArray())); + // 缩略图 7 天后过期,额外 1 天宽限供后台任务销毁 + long expireAt = Time.now() + 7 * Time.D; + thumb.setDeletedAt(expireAt); + thumb.setDestroyAt(expireAt + Time.D); + create(thumb); + return thumb; + } catch (Exception e) { + log.error("create thumbnail error", e); + throw new TimiException(TimiCode.ERROR).msgKey("exception.attachment.create.error"); + } } @Override - public Attachment getByBizId(Attachment.BizType bizType, long bizId) { + public Attachment getByBizId(Attachment.BizType bizType, String bizId) { return mapper.selectByBizId(bizType, bizId); } @Override - public Attachment getByAttachType(Attachment.BizType bizType, long bizId, Enum attachType) { + public Attachment getByAttachType(Attachment.BizType bizType, String bizId, String attachType) { return mapper.selectByAttachType(bizType, bizId, attachType); } @@ -201,59 +290,134 @@ public class AttachmentServiceImplement extends AbstractEntityService listByBizId(Attachment.BizType bizType, Long bizId, Enum... attachTypes) { - return mapper.listByBizId(bizType, bizId, Arrays.asList(attachTypes), null); + public List listByBizId(Attachment.BizType bizType, String bizId, String... attachTypes) { + return mapper.selectByBizId(bizType, bizId, List.of(attachTypes), null); } @Override - public long countByBizId(Attachment.BizType bizType, Long bizId, Enum... attachTypes) { - return mapper.countByBizId(bizType, bizId, Arrays.asList(attachTypes)); + public long countByBizId(Attachment.BizType bizType, String bizId, String... attachTypes) { + return mapper.countByBizId(bizType, bizId, List.of(attachTypes)); } @Override - public List listByMd5s(Attachment.BizType bizType, Long bizId, List> attachTypeList, List md5s) throws TimiException { - return mapper.listByMd5s(bizType, bizId, attachTypeList, md5s); - } - - @Override - public void deleteByBizId(Attachment.BizType bizType, long bizId, Enum... attachTypes) { - Attachment example = new Attachment(); - example.setBizType(bizType); - example.setBizId(bizId); - if (TimiJava.isEmpty(attachTypes)) { - mapper.deleteAllByExample(example); - } else { - for (int i = 0; i < attachTypes.length; i++) { - example.setAttachTypeValue(attachTypes[i]); - mapper.deleteAllByExample(example); - } - } + public PageResult pageByBizId(Attachment.BizType bizType, String bizId, List attachTypeList, Page page) throws TimiException { + PageResult result = new PageResult<>(); + result.setList(mapper.selectByBizId(bizType, bizId, attachTypeList, page)); + result.setTotal(mapper.countByBizId(bizType, bizId, attachTypeList)); + return result; } @Override public List listNeedDestroy() { return mapper.selectNeedDestroy(); } + + @Transactional(TimiServerDBConfig.ROLLBACKER) + @Override + public void updateByBiz(BizUpdateReq req) { + Attachment.BizType bizType = req.getBizType(); + String bizId = req.getBizId(); + List items = req.getItems(); + + // 数据库现有附件 + List dbList = mapper.selectAllByBizId(bizType, bizId); + Set dbIds = dbList.stream().map(Attachment::getId).collect(Collectors.toSet()); + // 请求附件 ID + Set reqIds = items.stream().map(Attachment::getId).filter(Objects::nonNull).collect(Collectors.toSet()); + // 数据库有、请求无:删除 + for (Attachment attach : dbList) { + if (!reqIds.contains(attach.getId())) { + delete(attach.getId()); + } + } + + for (Attachment item : items) { + boolean isNew = item.getId() == null || !dbIds.contains(item.getId()); + String tempFileId = item.getTempFileId(); + + if (isNew) { + // 请求有、数据库无:将临时文件记录直接转存为永久附件 + if (TimiJava.isEmpty(tempFileId)) { + throw new TimiException(TimiCode.ERROR).msgKey("exception.attachment.file.required"); + } + Attachment tempFile = mapper.selectRaw(tempFileId); + tempFile.setBizType(bizType); + tempFile.setBizId(bizId); + tempFile.setAttachType(item.getAttachType()); + if (TimiJava.isNotEmpty(item.getName())) { + tempFile.setName(item.getName()); + } + if (item.getMetadata() != null) { + tempFile.setMetadata(item.getMetadata()); + } + // 清除过期标记,使其成为永久附件 + tempFile.setDeletedAt(null); + tempFile.setDestroyAt(null); + mapper.update(tempFile); + } else if (TimiJava.isNotEmpty(tempFileId)) { + // 请求有、数据库有、有 tempFileId → 更新文件与元数据 + // 将临时文件的 GridFS 存储转移至当前永久记录,废弃临时记录 + Attachment current = mapper.selectRaw(item.getId()); + Attachment tempFile = mapper.selectRaw(tempFileId); + // 删除旧 GridFS 文件 + gridFsTemplate.delete(Query.query(Criteria.where("_id").is(current.getMongoId()))); + // 将存储信息从临时记录迁移到永久记录 + current.setMongoId(tempFile.getMongoId()); + current.setSize(tempFile.getSize()); + current.setMd5(tempFile.getMd5()); + current.setMimeType(tempFile.getMimeType()); + current.setName(TimiJava.defaultIfEmpty(item.getName(), tempFile.getName())); + if (item.getAttachType() != null) { + current.setAttachType(item.getAttachType()); + } + if (item.getMetadata() != null) { + current.setMetadata(item.getMetadata()); + } + mapper.update(current); + // 废弃临时文件记录:清空 mongoId 防止后台任务误删已转移的 GridFS 文件 + tempFile.setMongoId(null); + tempFile.setIsDestroyed(true); + tempFile.setDestroyAt(Time.now()); + mapper.update(tempFile); + } else { + // 请求有、数据库有、无 tempFileId → 仅更新元数据 + Attachment current = mapper.selectRaw(item.getId()); + if (item.getAttachType() != null) { + current.setAttachType(item.getAttachType()); + } + if (TimiJava.isNotEmpty(item.getName())) { + current.setName(item.getName()); + } + if (item.getMetadata() != null) { + current.setMetadata(item.getMetadata()); + } + mapper.update(current); + } + } + } + + @Transactional(TimiServerDBConfig.ROLLBACKER) + @Override + public void deleteByBiz(BizDeleteReq req) { + List attachmentList = mapper.selectAllByBizId(req.getBizType(), req.getBizId()); + for (Attachment attachment : attachmentList) { + delete(attachment.getId()); + } + } } diff --git a/src/main/java/com/imyeyu/api/modules/common/service/implement/ClipboardServiceImplement.java b/src/main/java/com/imyeyu/api/modules/common/service/implement/ClipboardServiceImplement.java index 9c11d74..49f404a 100644 --- a/src/main/java/com/imyeyu/api/modules/common/service/implement/ClipboardServiceImplement.java +++ b/src/main/java/com/imyeyu/api/modules/common/service/implement/ClipboardServiceImplement.java @@ -30,7 +30,7 @@ import java.util.concurrent.CopyOnWriteArrayList; public class ClipboardServiceImplement implements ClipboardService { /** Redis key 前缀 */ - private static final String KEY_PREFIX = "clipboard:"; + private static final String KEY_PREFIX = "CLIPBOARD:"; /** 剪切板内容最大长度 */ private static final int MAX_CONTENT_SIZE = 10 * 1024 * 1024; diff --git a/src/main/java/com/imyeyu/api/modules/common/service/implement/CommentReplyServiceImplement.java b/src/main/java/com/imyeyu/api/modules/common/service/implement/CommentReplyServiceImplement.java deleted file mode 100644 index 20cd703..0000000 --- a/src/main/java/com/imyeyu/api/modules/common/service/implement/CommentReplyServiceImplement.java +++ /dev/null @@ -1,172 +0,0 @@ -package com.imyeyu.api.modules.common.service.implement; - -import com.imyeyu.api.TimiServerAPI; -import com.imyeyu.api.config.dbsource.TimiServerDBConfig; -import com.imyeyu.api.modules.blog.entity.CommentRemindQueue; -import com.imyeyu.api.modules.blog.service.CommentRemindQueueService; -import com.imyeyu.api.modules.common.bean.CommentSupport; -import com.imyeyu.api.modules.common.entity.Comment; -import com.imyeyu.api.modules.common.entity.CommentReply; -import com.imyeyu.api.modules.common.entity.EmailQueue; -import com.imyeyu.api.modules.common.entity.User; -import com.imyeyu.api.modules.common.entity.UserConfig; -import com.imyeyu.api.modules.common.mapper.CommentReplyMapper; -import com.imyeyu.api.modules.common.service.CommentReplyService; -import com.imyeyu.api.modules.common.service.CommentService; -import com.imyeyu.api.modules.common.service.EmailQueueService; -import com.imyeyu.api.modules.common.service.UserConfigService; -import com.imyeyu.api.modules.common.service.UserService; -import com.imyeyu.java.TimiJava; -import com.imyeyu.java.bean.timi.TimiCode; -import com.imyeyu.java.bean.timi.TimiException; -import com.imyeyu.spring.TimiSpring; -import com.imyeyu.spring.bean.Page; -import com.imyeyu.spring.bean.PageResult; -import com.imyeyu.spring.mapper.BaseMapper; -import com.imyeyu.spring.service.AbstractEntityService; -import com.imyeyu.spring.service.GettableService; -import com.imyeyu.utils.Time; -import lombok.RequiredArgsConstructor; -import org.springframework.context.annotation.Lazy; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import java.util.UUID; - -/** - * 评论回复服务实现 - * - * @author 夜雨 - * @since 2021-08-24 10:34 - */ -@Service -@RequiredArgsConstructor(onConstructor_ = {@Lazy}) -public class CommentReplyServiceImplement extends AbstractEntityService implements CommentReplyService { - - private final UserService userService; - private final CommentService commentService; - private final UserConfigService userConfigService; - private final EmailQueueService emailQueueService; - private final CommentRemindQueueService commentRemindQueueService; - - private final CommentReplyMapper mapper; - - @Override - protected BaseMapper mapper() { - return mapper; - } - - @Override - public PageResult page(Page page) { - PageResult result = super.page(page); - for (CommentReply reply : result.getList()) { - Comment comment = commentService.get(reply.getCommentId()); - if (TimiJava.isNotEmpty(comment.getUserId())) { - comment.setUser(userService.view(comment.getUserId())); - } - reply.setComment(comment); - if (TimiJava.isNotEmpty(reply.getSenderId())) { - reply.setSender(userService.view(reply.getSenderId())); - } - if (TimiJava.isNotEmpty(reply.getReceiverId())) { - reply.setReceiver(userService.view(reply.getReceiverId())); - } - } - return result; - } - - @Transactional(TimiServerDBConfig.ROLLBACKER) - @Override - public void create(CommentReply commentReply) { - Comment comment = commentService.get(commentReply.getCommentId()); - - Class> serviceClass = comment.getBizType().getServiceClass(); - GettableService service = TimiServerAPI.applicationContext.getBean(serviceClass); - CommentSupport commentSupport = service.get(comment.getBizId()); - if (commentSupport.canNotComment()) { - throw new TimiException(TimiCode.PERMISSION_ERROR).msgKey("评论已关闭"); - } - - CommentReply dbReply = new CommentReply(); - dbReply.setCommentId(comment.getId()); - - // 发送者 - String token = TimiSpring.getToken(); - if (TimiJava.isNotEmpty(token)) { - User senderUser = userService.getLoginUser(); - if (senderUser.isBanning() || senderUser.isMuting()) { - throw new TimiException(TimiCode.RESULT_BAN).msgKey("comment.banded"); - } - dbReply.setSenderId(senderUser.getId()); - dbReply.setSenderNick(null); - } else { - if (TimiJava.isEmpty(commentReply.getSenderNick())) { - throw new TimiException(TimiCode.ARG_MISS).msgKey("comment.nick.empty"); - } - dbReply.setSenderNick(commentReply.getSenderNick()); - } - // 被回复 - dbReply.setReplyId(commentReply.getReplyId()); - if (commentReply.getReplyId() == null) { - // 回复主评论 - dbReply.setReceiverId(comment.getUserId()); - if (TimiJava.isEmpty(comment.getUserId())) { - dbReply.setReceiverNick(comment.getNick()); - } - } else { - // 回复其他回复 - CommentReply targetReply = get(commentReply.getReplyId()); - dbReply.setReceiverId(targetReply.getSenderId()); - if (TimiJava.isEmpty(targetReply.getSenderId())) { - dbReply.setReceiverNick(targetReply.getSenderNick()); - } - } - // 创建回复 - dbReply.setContent(commentReply.getContent()); - dbReply.setIp(TimiSpring.getRequestIP()); - super.create(dbReply); - // 被回复为注册用户时处理通知和邮件 - { - if (TimiJava.isEmpty(dbReply.getReceiverId())) { - return; - } - if (TimiJava.isNotEmpty(dbReply.getSenderId()) && dbReply.getSenderId().equals(dbReply.getReceiverId())) { - return; - } - // 被回复账号 - User receiverUser = userService.get(dbReply.getReceiverId()); - UserConfig userConfig = userConfigService.get(dbReply.getReceiverId()); - if (!receiverUser.emailVerified() || !userConfig.isEmailReplyRemind()) { - return; - } - // 添加提醒队列 - CommentRemindQueue remindQueue = new CommentRemindQueue(); - remindQueue.setUUID(UUID.randomUUID().toString()); - remindQueue.setUserId(receiverUser.getId()); - remindQueue.setReplyId(dbReply.getId()); - commentRemindQueueService.create(remindQueue); - - // 邮件队列 - EmailQueue emailQueue = emailQueueService.get(EmailQueue.BizType.REPLY_REMINAD, receiverUser.getId()); - if (emailQueue == null) { - emailQueue = new EmailQueue(); - emailQueue.setBizType(EmailQueue.BizType.REPLY_REMINAD); - emailQueue.setBizId(receiverUser.getId()); - long H10 = Time.H * 10; - if (Time.now() < Time.today() + H10) { - emailQueue.setSendAt(Time.today() + H10); - } else { - emailQueue.setSendAt(Time.tomorrow() + H10); - } - emailQueueService.create(emailQueue); - } - } - } - - @Transactional(TimiServerDBConfig.ROLLBACKER) - @Override - public void delete(Long crId) { - super.delete(crId); - commentRemindQueueService.destroyByReplyId(crId); - } -} diff --git a/src/main/java/com/imyeyu/api/modules/common/service/implement/CommentServiceImplement.java b/src/main/java/com/imyeyu/api/modules/common/service/implement/CommentServiceImplement.java index 3c9cd35..766faa1 100644 --- a/src/main/java/com/imyeyu/api/modules/common/service/implement/CommentServiceImplement.java +++ b/src/main/java/com/imyeyu/api/modules/common/service/implement/CommentServiceImplement.java @@ -1,135 +1,628 @@ package com.imyeyu.api.modules.common.service.implement; -import com.imyeyu.api.TimiServerAPI; import com.imyeyu.api.config.dbsource.TimiServerDBConfig; -import com.imyeyu.api.modules.blog.entity.CommentRemindQueue; -import com.imyeyu.api.modules.common.bean.CommentSupport; import com.imyeyu.api.modules.common.entity.Comment; -import com.imyeyu.api.modules.common.entity.CommentReply; -import com.imyeyu.api.modules.common.entity.User; +import com.imyeyu.api.modules.user.entity.User; +import com.imyeyu.api.modules.user.entity.UserBehaviorRecord; import com.imyeyu.api.modules.common.mapper.CommentMapper; -import com.imyeyu.api.modules.common.mapper.CommentRemindQueueMapper; -import com.imyeyu.api.modules.common.mapper.CommentReplyMapper; +import com.imyeyu.api.modules.user.mapper.UserMapper; import com.imyeyu.api.modules.common.service.CommentService; -import com.imyeyu.api.modules.common.service.UserService; +import com.imyeyu.api.modules.user.service.UserBehaviorRecordService; +import com.imyeyu.api.modules.common.vo.comment.CommentLayerQueryReq; +import com.imyeyu.api.modules.common.vo.comment.CommentLayerResp; +import com.imyeyu.api.modules.common.vo.comment.CommentNodeResp; +import com.imyeyu.api.modules.common.vo.comment.CommentThreadResp; +import com.imyeyu.api.modules.common.vo.comment.FlatCommentListItemResp; +import com.imyeyu.api.modules.common.vo.comment.FlatCommentListResp; +import com.imyeyu.api.modules.common.vo.comment.NestedCommentListResp; import com.imyeyu.java.TimiJava; -import com.imyeyu.java.bean.timi.TimiCode; import com.imyeyu.java.bean.timi.TimiException; -import com.imyeyu.spring.TimiSpring; import com.imyeyu.spring.bean.Page; import com.imyeyu.spring.bean.PageResult; import com.imyeyu.spring.mapper.BaseMapper; import com.imyeyu.spring.service.AbstractEntityService; -import com.imyeyu.spring.service.GettableService; +import com.imyeyu.utils.Time; import lombok.RequiredArgsConstructor; -import org.springframework.context.annotation.Lazy; +import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; import java.util.List; +import java.util.Map; +import java.util.Set; /** - * 评论操作服务实现 - * + * 评论服务实现 + * * @author 夜雨 - * @since 2021-02-23 21:41 + * @since 2026-05-27 17:20 */ +@Slf4j @Service -@RequiredArgsConstructor(onConstructor_ = {@Lazy}) -public class CommentServiceImplement extends AbstractEntityService implements CommentService { +@RequiredArgsConstructor +public class CommentServiceImplement extends AbstractEntityService implements CommentService { - private final UserService userService; + private static final int USER_ROOT_LIMIT = 20; + private static final int USER_REPLY_LIMIT = 6; + private static final int SDK_LAYER_LIMIT = 100; + private static final String BEHAVIOR_BIZ_TYPE = "USER"; + private static final String BEHAVIOR_TARGET_TYPE_COMMENT = "COMMENT"; + private static final String BEHAVIOR_ACTION_TYPE_LIKE = "LIKE"; + private static final String BEHAVIOR_ACTION_TYPE_UNLIKE = "UNLIKE"; + private final UserMapper userMapper; + private final UserBehaviorRecordService userBehaviorRecordService; private final CommentMapper mapper; - private final CommentReplyMapper replyMapper; - private final CommentRemindQueueMapper remindQueueMapper; @Override - protected BaseMapper mapper() { + protected BaseMapper mapper() { return mapper; } + @Transactional(TimiServerDBConfig.ROLLBACKER) @Override - public PageResult page(Page page) { - PageResult result = super.page(page); - for (Comment comment : result.getList()) { + public void create(Comment entity) { + TimiException.required(entity, "not found entity"); + if (TimiJava.isEmpty(entity.getParentId())) { + createRoot(entity); + return; + } + createReply(entity, entity.getParentId()); + } + + @Transactional(TimiServerDBConfig.ROLLBACKER) + @Override + public void update(Comment comment) { + TimiException.required(comment, "not found comment"); + TimiException.required(comment.getId(), "not found comment.id"); + Comment dbComment = get(comment.getId()); + TimiException.required(dbComment, "not found comment"); + + dbComment.setContent(TimiJava.defaultIfEmpty(comment.getContent(), dbComment.getContent())); + dbComment.setIp(TimiJava.defaultIfEmpty(comment.getIp(), dbComment.getIp())); + dbComment.setStatus(TimiJava.defaultIfNull(comment.getStatus(), dbComment.getStatus())); + dbComment.setIsTop(TimiJava.defaultIfNull(comment.getIsTop(), dbComment.getIsTop())); + super.update(dbComment); + } + + @Transactional(TimiServerDBConfig.ROLLBACKER) + @Override + public void delete(String id) { + deleteInternal(id, null); + } + + @Transactional(TimiServerDBConfig.ROLLBACKER) + @Override + public void like(String commentId, String userId) { + Comment comment = get(commentId); + TimiException.required(comment, "not found comment"); + TimiException.required(userId, "not found userId"); + userBehaviorRecordService.cancel(BEHAVIOR_BIZ_TYPE, userId, BEHAVIOR_TARGET_TYPE_COMMENT, commentId, BEHAVIOR_ACTION_TYPE_UNLIKE); + boolean created = createBehaviorRecord(comment, userId, BEHAVIOR_ACTION_TYPE_LIKE); + if (created) { + mapper.increaseLikeCount(commentId, 1); + } + } + + @Transactional(TimiServerDBConfig.ROLLBACKER) + @Override + public void unlike(String commentId, String userId) { + Comment comment = get(commentId); + TimiException.required(comment, "not found comment"); + TimiException.required(userId, "not found userId"); + boolean canceled = userBehaviorRecordService.cancel(BEHAVIOR_BIZ_TYPE, userId, BEHAVIOR_TARGET_TYPE_COMMENT, commentId, BEHAVIOR_ACTION_TYPE_LIKE); + if (canceled) { + mapper.increaseLikeCount(commentId, -1); + } + createBehaviorRecord(comment, userId, BEHAVIOR_ACTION_TYPE_UNLIKE); + } + + @Override + public PageResult listLikedRecord(Page page, String userId) { + TimiException.required(userId, "not found userId"); + if (page == null) { + page = new Page<>(); + } + UserBehaviorRecord equalsExample = page.getEqualsExample(); + if (equalsExample == null) { + equalsExample = new UserBehaviorRecord(); + page.setEqualsExample(equalsExample); + } + equalsExample.setBizType(BEHAVIOR_BIZ_TYPE); + equalsExample.setUserId(userId); + equalsExample.setTargetType(BEHAVIOR_TARGET_TYPE_COMMENT); + equalsExample.setActionType(BEHAVIOR_ACTION_TYPE_LIKE); + + PageResult result = userBehaviorRecordService.page(page); + fillBehaviorRecordComment(result.getList()); + return result; + } + + @Override + public FlatCommentListResp listFlatRoot(String bizType, String bizId, int offset, String currentUserId) { + TimiException.required(bizType, "not found bizType"); + TimiException.required(bizId, "not found bizId"); + int validOffset = Math.max(offset, 0); + List rootList = mapper.selectRootList(bizType, bizId, validOffset, USER_ROOT_LIMIT); + List itemList = buildFlatRootPreview(rootList, currentUserId); + FlatCommentListResp resp = new FlatCommentListResp(); + resp.setTotal(mapper.countRoot(bizType, bizId)); + resp.setList(itemList); + return resp; + } + + @Override + public NestedCommentListResp listNestedRoot(String bizType, String bizId, int offset, String currentUserId) { + TimiException.required(bizType, "not found bizType"); + TimiException.required(bizId, "not found bizId"); + int validOffset = Math.max(offset, 0); + List rootList = mapper.selectRootList(bizType, bizId, validOffset, USER_ROOT_LIMIT); + List itemList = buildNestedRootPreview(rootList, currentUserId); + NestedCommentListResp resp = new NestedCommentListResp(); + resp.setTotal(mapper.countRoot(bizType, bizId)); + resp.setList(itemList); + return resp; + } + + @Override + public CommentThreadResp getUserThread(String commentId, int offset, String currentUserId) { + TimiException.required(commentId, "not found commentId"); + int validOffset = Math.max(offset, 0); + Comment current = get(commentId); + TimiException.required(current, "not found comment"); + String rootId = TimiJava.defaultIfEmpty(current.getRootId(), current.getId()); + Comment root = get(rootId); + TimiException.required(root, "not found root comment"); + List replyList = mapper.selectReplyListByRootId(rootId, validOffset, USER_ROOT_LIMIT); + fillUser(replyList); + fillLiked(replyList, currentUserId); + fillUser(List.of(root)); + fillLiked(List.of(root), currentUserId); + CommentThreadResp resp = new CommentThreadResp(); + resp.setRoot(root); + resp.setReplyTotal(mapper.countReplyByRootId(rootId)); + resp.setReplyList(replyList); + return resp; + } + + @Override + public CommentLayerResp listSdkLayer(CommentLayerQueryReq req, String currentUserId) { + TimiException.required(req, "not found req"); + TimiException.required(req.getBizType(), "not found req.bizType"); + TimiException.required(req.getBizId(), "not found req.bizId"); + + int validOffset = Math.max(TimiJava.defaultIfNull(req.getOffset(), 0), 0); + int validLimit = TimiJava.defaultIfNull(req.getLimit(), SDK_LAYER_LIMIT); + validLimit = Math.min(Math.max(validLimit, 1), SDK_LAYER_LIMIT); + + List commentList = mapper.selectLayerList(req.getBizType(), req.getBizId(), req.getParentId(), validOffset, validLimit); + fillUser(commentList); + fillLiked(commentList, currentUserId); + CommentLayerResp resp = new CommentLayerResp(); + resp.setTotal(mapper.countLayer(req.getBizType(), req.getBizId(), req.getParentId())); + resp.setList(commentList); + return resp; + } + + @Override + public CommentNodeResp getUserNode(String commentId, String currentUserId) { + TimiException.required(commentId, "not found commentId"); + Comment comment = get(commentId); + TimiException.required(comment, "not found comment"); + + fillUser(List.of(comment)); + fillLiked(List.of(comment), currentUserId); + List replyList = mapper.selectReplyListByParentId(commentId, 0, USER_REPLY_LIMIT); + fillUser(replyList); + fillLiked(replyList, currentUserId); + + CommentNodeResp resp = new CommentNodeResp(); + resp.setComment(comment); + resp.setReplyTotal(mapper.countReplyByParentId(commentId)); + resp.setReplyList(replyList); + return resp; + } + + @Override + public CommentLayerResp getUserChildren(String commentId, int offset, String currentUserId) { + TimiException.required(commentId, "not found commentId"); + int validOffset = Math.max(offset, 0); + Comment comment = get(commentId); + TimiException.required(comment, "not found comment"); + List replyList = mapper.selectReplyListByParentId(commentId, validOffset, USER_ROOT_LIMIT); + fillUser(replyList); + fillLiked(replyList, currentUserId); + CommentLayerResp resp = new CommentLayerResp(); + resp.setTotal(mapper.countReplyByParentId(commentId)); + resp.setList(replyList); + return resp; + } + + @Override + public Comment detailTree(String id, String currentUserId) { + Comment current = get(id); + TimiException.required(current, "not found comment"); + + String rootId = current.getRootId(); + if (TimiJava.isEmpty(rootId)) { + rootId = current.getId(); + } + Comment root = get(rootId); + TimiException.required(root, "not found root comment"); + List replyList = mapper.selectAllReplyListByRootId(rootId); + return attachTree(List.of(root), replyList, currentUserId).getFirst(); + } + + @Transactional(TimiServerDBConfig.ROLLBACKER) + @Override + public void deleteOwned(String id, String userId) { + TimiException.required(userId, "not found userId"); + deleteInternal(id, userId); + } + + /** + * 创建主评论 + * + * @param comment 评论 + */ + private void createRoot(Comment comment) { + TimiException.required(comment.getBizType(), "not found comment.bizType"); + TimiException.required(comment.getBizId(), "not found comment.bizId"); + requireCommentUser(comment); + TimiException.required(comment.getContent(), "not found comment.content"); + + comment.setParentId(null); + comment.setRootId(null); + comment.setReplyUserId(null); + comment.setReplyUserNick(null); + comment.setLevel(1); + comment.setReplyCount(TimiJava.defaultIfNull(comment.getReplyCount(), 0)); + comment.setLikeCount(TimiJava.defaultIfNull(comment.getLikeCount(), 0)); + comment.setStatus(TimiJava.defaultIfNull(comment.getStatus(), Comment.Status.NORMAL)); + comment.setIsTop(TimiJava.defaultIfNull(comment.getIsTop(), false)); + comment.setLastReplyAt(Time.now()); + + super.create(comment); + comment.setRootId(comment.getId()); + super.update(comment); + } + + /** + * 创建回复评论 + * + * @param comment 评论 + * @param parentId 父评论 ID + */ + private void createReply(Comment comment, String parentId) { + requireCommentUser(comment); + TimiException.required(comment.getContent(), "not found comment.content"); + + Comment parent = get(parentId); + TimiException.required(parent, "not found parent comment"); + + String rootId = TimiJava.defaultIfEmpty(parent.getRootId(), parent.getId()); + comment.setBizType(parent.getBizType()); + comment.setBizId(parent.getBizId()); + comment.setParentId(parent.getId()); + comment.setRootId(rootId); + comment.setReplyUserId(TimiJava.defaultIfEmpty(comment.getReplyUserId(), parent.getUserId())); + comment.setReplyUserNick(TimiJava.defaultIfEmpty(comment.getReplyUserNick(), parent.getUserNick())); + comment.setLevel(TimiJava.defaultIfNull(parent.getLevel(), 1) + 1); + comment.setReplyCount(TimiJava.defaultIfNull(comment.getReplyCount(), 0)); + comment.setLikeCount(TimiJava.defaultIfNull(comment.getLikeCount(), 0)); + comment.setStatus(TimiJava.defaultIfNull(comment.getStatus(), Comment.Status.NORMAL)); + comment.setIsTop(false); + comment.setLastReplyAt(null); + + super.create(comment); + mapper.increaseReplyCount(rootId, 1); + if (!rootId.equals(parent.getId())) { + mapper.increaseReplyCount(parent.getId(), 1); + } + Comment root = new Comment(); + root.setId(rootId); + root.setLastReplyAt(Time.now()); + super.update(root); + } + + /** + * 校验评论用户 + * + * @param comment 评论 + */ + private void requireCommentUser(Comment comment) { + TimiException.requiredTrue(TimiJava.isNotEmpty(comment.getUserId()) || TimiJava.isNotEmpty(comment.getUserNick()), "not found comment.userId or comment.userNick"); + } + + /** + * 删除评论及其子树 + * + * @param id 评论 ID + * @param requiredUserId 要求归属的用户 ID,为 null 时不校验 + */ + private void deleteInternal(String id, String requiredUserId) { + Comment current = get(id); + TimiException.required(current, "not found comment"); + if (requiredUserId != null) { + TimiException.requiredTrue(requiredUserId.equals(current.getUserId()), "comment not belong to current user"); + } + String rootId = TimiJava.defaultIfEmpty(current.getRootId(), current.getId()); + List replyList = mapper.selectAllReplyListByRootId(rootId); + Map> childMap = groupByParentId(replyList); + List deleteList = new ArrayList<>(); + collectDeleteComment(current, childMap, deleteList); + for (Comment comment : deleteList) { + super.delete(comment.getId()); + } + if (TimiJava.isEmpty(current.getParentId())) { + return; + } + int deleteReplyCount = deleteList.size(); + mapper.increaseReplyCount(rootId, -deleteReplyCount); + if (!rootId.equals(current.getParentId())) { + mapper.increaseReplyCount(current.getParentId(), -1); + } + Comment root = new Comment(); + root.setId(rootId); + root.setLastReplyAt(mapper.selectLatestReplyAtByRootId(rootId)); + super.update(root); + } + + /** + * 组装评论树 + * + * @param rootList 根评论列表 + * @param replyList 回复列表 + * @param currentUserId 当前用户 ID + * @return 评论树列表 + */ + private List attachTree(List rootList, List replyList, String currentUserId) { + List allCommentList = new ArrayList<>(rootList); + allCommentList.addAll(replyList); + fillUser(allCommentList); + Set likedCommentIdSet = getBehaviorCommentIdSet(currentUserId, allCommentList, BEHAVIOR_ACTION_TYPE_LIKE); + Set unlikedCommentIdSet = getBehaviorCommentIdSet(currentUserId, allCommentList, BEHAVIOR_ACTION_TYPE_UNLIKE); + for (Comment root : rootList) { + root.setReplyList(new ArrayList<>()); + root.setLiked(likedCommentIdSet.contains(root.getId())); + root.setUnliked(unlikedCommentIdSet.contains(root.getId())); + } + Map commentMap = new LinkedHashMap<>(); + for (Comment root : rootList) { + commentMap.put(root.getId(), root); + } + replyList.sort(Comparator.comparing(Comment::getCreatedAt, Comparator.nullsLast(Long::compareTo))); + for (Comment reply : replyList) { + reply.setReplyList(new ArrayList<>()); + reply.setLiked(likedCommentIdSet.contains(reply.getId())); + reply.setUnliked(unlikedCommentIdSet.contains(reply.getId())); + commentMap.put(reply.getId(), reply); + Comment parent = commentMap.get(reply.getParentId()); + if (parent != null) { + parent.getReplyList().add(reply); + } + } + return rootList; + } + + /** + * 填充点赞状态 + * + * @param commentList 评论列表 + * @param currentUserId 当前用户 ID + */ + private void fillLiked(List commentList, String currentUserId) { + if (TimiJava.isEmpty(commentList)) { + return; + } + Set likedCommentIdSet = getBehaviorCommentIdSet(currentUserId, commentList, BEHAVIOR_ACTION_TYPE_LIKE); + Set unlikedCommentIdSet = getBehaviorCommentIdSet(currentUserId, commentList, BEHAVIOR_ACTION_TYPE_UNLIKE); + for (Comment comment : commentList) { + comment.setLiked(likedCommentIdSet.contains(comment.getId())); + comment.setUnliked(unlikedCommentIdSet.contains(comment.getId())); + comment.setReplyList(new ArrayList<>()); + } + } + + /** + * 填充评论用户信息 + * + * @param commentList 评论列表 + */ + private void fillUser(List commentList) { + if (TimiJava.isEmpty(commentList)) { + return; + } + Set userIdSet = new LinkedHashSet<>(); + for (Comment comment : commentList) { if (TimiJava.isNotEmpty(comment.getUserId())) { - comment.setUser(userService.view(comment.getUserId())); + userIdSet.add(comment.getUserId()); } - Page replyPage = new Page<>(); - replyPage.setIndex(0); - replyPage.setSize(6); - { - CommentReply exampleReply = new CommentReply(); - exampleReply.setCommentId(comment.getId()); - replyPage.setEqualsExample(exampleReply); - } - PageResult replyResult = replyMapper.selectPageResult(replyPage); - comment.setReplies(replyResult.getList()); - comment.setRepliesLength(replyResult.getTotal()); - for (CommentReply reply : replyResult.getList()) { - if (TimiJava.isNotEmpty(reply.getSenderId())) { - reply.setSender(userService.view(reply.getSenderId())); - } - if (TimiJava.isNotEmpty(reply.getReceiverId())) { - reply.setReceiver(userService.view(reply.getReceiverId())); - } + if (TimiJava.isNotEmpty(comment.getReplyUserId())) { + userIdSet.add(comment.getReplyUserId()); } } + if (userIdSet.isEmpty()) { + return; + } + Map userMap = new LinkedHashMap<>(); + for (User user : userMapper.listByIdList(new ArrayList<>(userIdSet))) { + userMap.put(user.getId(), sanitizeUser(user)); + } + for (Comment comment : commentList) { + comment.setUser(userMap.get(comment.getUserId())); + comment.setReplyUser(userMap.get(comment.getReplyUserId())); + } + } + + /** + * 清理用户敏感字段 + * + * @param user 用户 + * @return 用户 + */ + private User sanitizeUser(User user) { + if (user == null) { + return null; + } + user.setPassword(null); + user.setLastLoginIP(null); + return user; + } + + /** + * 查询当前用户行为评论 ID 集合 + * + * @param currentUserId 当前用户 ID + * @param commentList 评论列表 + * @param actionType 行为类型 + * @return 评论 ID 集合 + */ + private Set getBehaviorCommentIdSet(String currentUserId, List commentList, String actionType) { + if (TimiJava.isEmpty(commentList)) { + return Set.of(); + } + return userBehaviorRecordService.getActiveTargetIdSet(BEHAVIOR_BIZ_TYPE, currentUserId, BEHAVIOR_TARGET_TYPE_COMMENT, actionType, commentList.stream().map(Comment::getId).toList()); + } + + /** + * 创建评论行为记录 + * + * @param comment 评论 + * @param userId 用户 ID + * @param actionType 行为类型 + * @return true 为新增记录 + */ + private boolean createBehaviorRecord(Comment comment, String userId, String actionType) { + UserBehaviorRecord record = new UserBehaviorRecord(); + record.setBizType(BEHAVIOR_BIZ_TYPE); + record.setUserId(userId); + record.setTargetType(BEHAVIOR_TARGET_TYPE_COMMENT); + record.setTargetId(comment.getId()); + record.setActionType(actionType); + return userBehaviorRecordService.record(record); + } + + /** + * 填充行为记录评论信息 + * + * @param recordList 行为记录列表 + */ + private void fillBehaviorRecordComment(List recordList) { + if (TimiJava.isEmpty(recordList)) { + return; + } + List commentIdList = recordList.stream().map(UserBehaviorRecord::getTargetId).filter(TimiJava::isNotEmpty).distinct().toList(); + if (commentIdList.isEmpty()) { + return; + } + List commentList = mapper.listByIdList(commentIdList); + fillUser(commentList); + Map commentMap = new LinkedHashMap<>(); + for (Comment comment : commentList) { + commentMap.put(comment.getId(), comment); + } + for (UserBehaviorRecord record : recordList) { + record.setComment(commentMap.get(record.getTargetId())); + } + } + + /** + * 构建扁平主评论预览 + * + * @param rootList 主评论列表 + * @param currentUserId 当前用户 ID + * @return 预览列表 + */ + private List buildFlatRootPreview(List rootList, String currentUserId) { + if (rootList == null || rootList.isEmpty()) { + return List.of(); + } + fillUser(rootList); + fillLiked(rootList, currentUserId); + List rootIdList = rootList.stream().map(Comment::getId).toList(); + List previewList = mapper.selectReplyPreviewByRootIds(rootIdList, USER_REPLY_LIMIT); + fillUser(previewList); + fillLiked(previewList, currentUserId); + Map> replyMap = new LinkedHashMap<>(); + for (Comment reply : previewList) { + replyMap.computeIfAbsent(reply.getRootId(), key -> new ArrayList<>()).add(reply); + } + + List result = new ArrayList<>(); + for (Comment root : rootList) { + FlatCommentListItemResp item = new FlatCommentListItemResp(); + item.setRoot(root); + item.setReplyPreviewTotal(mapper.countReplyByRootId(root.getId())); + item.setReplyPreviewList(replyMap.getOrDefault(root.getId(), List.of())); + result.add(item); + } return result; } - @Transactional(TimiServerDBConfig.ROLLBACKER) - @Override - public void create(Comment comment) { - Class> serviceClass = comment.getBizType().getServiceClass(); - GettableService service = TimiServerAPI.applicationContext.getBean(serviceClass); - CommentSupport commentSupport = service.get(comment.getBizId()); - TimiException.requiredTrue(commentSupport.canComment(), "comment.not.can.comment"); - - // 令牌和账号验证 - if (TimiJava.isNotEmpty(TimiSpring.getToken())) { - User commentUser = userService.getLoginUser(); - TimiException.requiredTrue(!commentUser.isBanning(), "comment.user.banned"); - TimiException.requiredTrue(!commentUser.isMuting(), "comment.user.muting"); - comment.setUserId(commentUser.getId()); - comment.setNick(null); - } else { - TimiException.required(comment.getNick(), "comment.nick.empty"); + /** + * 构建层级主评论预览 + * + * @param rootList 主评论列表 + * @param currentUserId 当前用户 ID + * @return 预览列表 + */ + private List buildNestedRootPreview(List rootList, String currentUserId) { + if (rootList == null || rootList.isEmpty()) { + return List.of(); } - // 内容 - TimiException.required(comment.getContent(), "comment.data.empty"); - comment.setIp(TimiSpring.getRequestIP()); - super.create(comment); + fillUser(rootList); + fillLiked(rootList, currentUserId); + List result = new ArrayList<>(); + for (Comment root : rootList) { + List replyList = mapper.selectReplyListByParentId(root.getId(), 0, USER_REPLY_LIMIT); + fillUser(replyList); + fillLiked(replyList, currentUserId); + CommentNodeResp item = new CommentNodeResp(); + item.setComment(root); + item.setReplyTotal(mapper.countReplyByParentId(root.getId())); + item.setReplyList(replyList); + result.add(item); + } + return result; } - @Transactional(TimiServerDBConfig.ROLLBACKER) - @Override - public void delete(Long commentId) { - User user = userService.getLoginUser(); - - Comment comment = get(commentId); - if (!comment.getUserId().equals(user.getId())) { - throw new TimiException(TimiCode.PERMISSION_ERROR).msgKey("token.illegal"); - } - { - // 移除被回复者的回复提醒队列 - CommentReply example = new CommentReply(); - example.setSenderId(user.getId()); - List replies = replyMapper.selectAllByExample(example); - for (CommentReply reply : replies) { - CommentRemindQueue destroyRemindExample = new CommentRemindQueue(); - destroyRemindExample.setReplyId(reply.getReplyId()); - remindQueueMapper.destroyAllByExample(destroyRemindExample); + /** + * 按父评论 ID 分组 + * + * @param commentList 评论列表 + * @return 分组结果 + */ + private Map> groupByParentId(List commentList) { + Map> result = new LinkedHashMap<>(); + for (Comment comment : commentList) { + if (TimiJava.isEmpty(comment.getParentId())) { + continue; } + result.computeIfAbsent(comment.getParentId(), key -> new ArrayList<>()).add(comment); } - { - // 删除回复 - CommentReply example = new CommentReply(); - example.setCommentId(commentId); - replyMapper.deleteAllByExample(example); + return result; + } + + /** + * 收集待删除评论 + * + * @param current 当前评论 + * @param childMap 子评论分组 + * @param result 结果列表 + */ + private void collectDeleteComment(Comment current, Map> childMap, List result) { + result.add(current); + List childList = childMap.get(current.getId()); + if (childList == null || childList.isEmpty()) { + return; + } + for (Comment child : childList) { + collectDeleteComment(child, childMap, result); } - // 删除评论 - super.delete(commentId); } } diff --git a/src/main/java/com/imyeyu/api/modules/common/service/implement/DataDictOptionServiceImplement.java b/src/main/java/com/imyeyu/api/modules/common/service/implement/DataDictOptionServiceImplement.java new file mode 100644 index 0000000..45442bd --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/common/service/implement/DataDictOptionServiceImplement.java @@ -0,0 +1,27 @@ +package com.imyeyu.api.modules.common.service.implement; + +import com.imyeyu.api.modules.common.entity.DataDictOption; +import com.imyeyu.api.modules.common.mapper.DataDictOptionMapper; +import com.imyeyu.api.modules.common.service.DataDictOptionService; +import com.imyeyu.spring.mapper.BaseMapper; +import com.imyeyu.spring.service.AbstractEntityService; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +/** + * 数据字典选项服务实现 + * + * @author 夜雨 + * @since 2026-05-13 14:46 + */ +@Service +@RequiredArgsConstructor +public class DataDictOptionServiceImplement extends AbstractEntityService implements DataDictOptionService { + + private final DataDictOptionMapper mapper; + + @Override + protected BaseMapper mapper() { + return mapper; + } +} diff --git a/src/main/java/com/imyeyu/api/modules/common/service/implement/DataDictServiceImplement.java b/src/main/java/com/imyeyu/api/modules/common/service/implement/DataDictServiceImplement.java new file mode 100644 index 0000000..f1e6ec4 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/common/service/implement/DataDictServiceImplement.java @@ -0,0 +1,27 @@ +package com.imyeyu.api.modules.common.service.implement; + +import com.imyeyu.api.modules.common.entity.DataDict; +import com.imyeyu.api.modules.common.mapper.DataDictMapper; +import com.imyeyu.api.modules.common.service.DataDictService; +import com.imyeyu.spring.mapper.BaseMapper; +import com.imyeyu.spring.service.AbstractEntityService; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +/** + * 数据字典服务实现 + * + * @author 夜雨 + * @since 2026-05-13 14:46 + */ +@Service +@RequiredArgsConstructor +public class DataDictServiceImplement extends AbstractEntityService implements DataDictService { + + private final DataDictMapper mapper; + + @Override + protected BaseMapper mapper() { + return mapper; + } +} diff --git a/src/main/java/com/imyeyu/api/modules/common/service/implement/EmailFilterServiceImplement.java b/src/main/java/com/imyeyu/api/modules/common/service/implement/EmailFilterServiceImplement.java new file mode 100644 index 0000000..6f25250 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/common/service/implement/EmailFilterServiceImplement.java @@ -0,0 +1,27 @@ +package com.imyeyu.api.modules.common.service.implement; + +import com.imyeyu.api.modules.common.entity.EmailFilter; +import com.imyeyu.api.modules.common.mapper.EmailFilterMapper; +import com.imyeyu.api.modules.common.service.EmailFilterService; +import com.imyeyu.spring.mapper.BaseMapper; +import com.imyeyu.spring.service.AbstractEntityService; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +/** + * 邮件过滤服务实现 + * + * @author 夜雨 + * @since 2026-05-13 14:46 + */ +@Service +@RequiredArgsConstructor +public class EmailFilterServiceImplement extends AbstractEntityService implements EmailFilterService { + + private final EmailFilterMapper mapper; + + @Override + protected BaseMapper mapper() { + return mapper; + } +} diff --git a/src/main/java/com/imyeyu/api/modules/common/service/implement/EmailQueueServiceImplement.java b/src/main/java/com/imyeyu/api/modules/common/service/implement/EmailQueueServiceImplement.java deleted file mode 100644 index 41596f4..0000000 --- a/src/main/java/com/imyeyu/api/modules/common/service/implement/EmailQueueServiceImplement.java +++ /dev/null @@ -1,50 +0,0 @@ -package com.imyeyu.api.modules.common.service.implement; - -import com.imyeyu.api.config.dbsource.TimiServerDBConfig; -import com.imyeyu.api.modules.common.entity.EmailQueue; -import com.imyeyu.api.modules.common.entity.EmailQueueLog; -import com.imyeyu.api.modules.common.mapper.EmailQueueLogMapper; -import com.imyeyu.api.modules.common.mapper.EmailQueueMapper; -import com.imyeyu.api.modules.common.service.EmailQueueService; -import com.imyeyu.spring.mapper.BaseMapper; -import com.imyeyu.spring.service.AbstractEntityService; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import java.util.List; - -/** - * 邮件推送队列服务实现 - * - * @author 夜雨 - * @since 2021-08-24 16:21 - */ -@Service -@RequiredArgsConstructor -public class EmailQueueServiceImplement extends AbstractEntityService implements EmailQueueService { - - private final EmailQueueMapper mapper; - private final EmailQueueLogMapper logMapper; - - @Override - protected BaseMapper mapper() { - return mapper; - } - - @Override - public EmailQueue get(EmailQueue.BizType type, Long dataId) { - return mapper.query(type, dataId); - } - - @Transactional(TimiServerDBConfig.ROLLBACKER) - @Override - public void addLog(EmailQueueLog log) { - logMapper.insert(log); - } - - @Override - public List listAll() { - return mapper.listAll(); - } -} diff --git a/src/main/java/com/imyeyu/api/modules/common/service/implement/FeedbackServiceImplement.java b/src/main/java/com/imyeyu/api/modules/common/service/implement/FeedbackServiceImplement.java deleted file mode 100644 index 0601815..0000000 --- a/src/main/java/com/imyeyu/api/modules/common/service/implement/FeedbackServiceImplement.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.imyeyu.api.modules.common.service.implement; - -import com.imyeyu.api.config.dbsource.TimiServerDBConfig; -import com.imyeyu.api.modules.common.entity.Feedback; -import com.imyeyu.api.modules.common.mapper.FeedbackMapper; -import com.imyeyu.api.modules.common.service.FeedbackService; -import com.imyeyu.api.modules.common.vo.FeedbackRequest; -import com.imyeyu.spring.mapper.BaseMapper; -import com.imyeyu.spring.service.AbstractEntityService; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -/** - * 反馈服务实现 - * - * @author 夜雨 - * @since 2021-11-16 22:27 - */ -@Service -@RequiredArgsConstructor -public class FeedbackServiceImplement extends AbstractEntityService implements FeedbackService { - - private final FeedbackMapper mapper; - - @Override - protected BaseMapper mapper() { - return mapper; - } - - @Transactional(TimiServerDBConfig.ROLLBACKER) - @Override - public void create(FeedbackRequest request) { - Feedback feedback = new Feedback(); - feedback.setFrom(request.getFrom()); - feedback.setEmail(request.getEmail()); - feedback.setData(request.getData()); - super.create(feedback); - } -} diff --git a/src/main/java/com/imyeyu/api/modules/common/service/implement/IconServiceImplement.java b/src/main/java/com/imyeyu/api/modules/common/service/implement/IconServiceImplement.java index 8dd13ef..bf070a1 100644 --- a/src/main/java/com/imyeyu/api/modules/common/service/implement/IconServiceImplement.java +++ b/src/main/java/com/imyeyu/api/modules/common/service/implement/IconServiceImplement.java @@ -3,16 +3,21 @@ package com.imyeyu.api.modules.common.service.implement; import com.imyeyu.api.modules.common.entity.Icon; import com.imyeyu.api.modules.common.mapper.IconMapper; import com.imyeyu.api.modules.common.service.IconService; -import com.imyeyu.api.modules.common.vo.icon.AllResponse; -import com.imyeyu.api.modules.common.vo.icon.NamePage; -import com.imyeyu.api.modules.common.vo.icon.UnicodePage; -import com.imyeyu.spring.bean.PageResult; +import com.imyeyu.font.icon.IconFontBuilder; +import com.imyeyu.font.icon.model.GlyphAlignment; +import com.imyeyu.font.icon.model.IconGlyph; +import com.imyeyu.font.icon.model.IconFontOptions; +import com.imyeyu.java.TimiJava; import com.imyeyu.spring.mapper.BaseMapper; import com.imyeyu.spring.service.AbstractEntityService; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; +import java.util.Comparator; import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.Stream; /** * 图标服务实现 @@ -22,53 +27,55 @@ import java.util.List; */ @Service @RequiredArgsConstructor -public class IconServiceImplement extends AbstractEntityService implements IconService { +public class IconServiceImplement extends AbstractEntityService implements IconService { + + private static final IconFontOptions TTF_OPTIONS = createTtfOptions(); private final IconMapper mapper; @Override - protected BaseMapper mapper() { + protected BaseMapper mapper() { return mapper; } @Override - public AllResponse listAll(Long latest) { - List list = mapper.listAll(); - long dbLatest = -1; - for (int i = 0; i < list.size(); i++) { - if (dbLatest < list.get(i).getCreatedAt()) { - dbLatest = list.get(i).getCreatedAt(); - } - if (list.get(i).getUpdatedAt() != null && dbLatest < list.get(i).getUpdatedAt()) { - dbLatest = list.get(i).getUpdatedAt(); - } - } - AllResponse response = new AllResponse(); - response.setLatest(dbLatest); - if (latest < dbLatest) { - // 存在更新 - response.setIcons(list); - } - return response; + public List listName() { + return allValid().map(Icon::getName).toList(); } @Override - public PageResult pageByName(NamePage page) { - PageResult result = new PageResult<>(); - result.setList(mapper.listByName(page.getName(), page.getOffset(), page.getLimit())); - result.setTotal(mapper.countByName(page.getName())); - return result; + public Map mapSvg() { + return allValid().collect(Collectors.toMap(Icon::getName, Icon::getSvg)); } @Override - public PageResult pageByUnicode(UnicodePage page) { - String unicode = page.getUnicode().toLowerCase(); - if (unicode.startsWith("0x") || unicode.startsWith("&#") || unicode.startsWith("\\u")) { - unicode = unicode.substring(2); - } - PageResult result = new PageResult<>(); - result.setList(mapper.listByUnicode(unicode, page.getOffset(), page.getLimit())); - result.setTotal(mapper.countByUnicode(unicode)); - return result; + public Map mapUnicode() { + return allValid().collect(Collectors.toMap(Icon::getName, Icon::getUnicode)); + } + + @Override + public byte[] exportTtf() { + List icons = allValid().map(icon -> IconGlyph.of(icon.getName(), icon.getUnicode(), icon.getSvg())).toList(); + return IconFontBuilder.build(TTF_OPTIONS, icons); + } + + private static IconFontOptions createTtfOptions() { + IconFontOptions options = IconFontOptions.defaults("timi-icon") + .withSourceBounds(0, 0, 16, 16) + .withAlignment(GlyphAlignment.GLYPH_CENTER, GlyphAlignment.GLYPH_CENTER) + .withSnapOffsetToGrid(true); + options.setGlyphPadding(0); + options.setAscent(960); + options.setDescent(-64); + return options; + } + + private Stream allValid() { + return mapper.selectAll() + .stream() + .filter(icon -> TimiJava.isNotEmpty(icon.getName())) + .filter(icon -> TimiJava.isNotEmpty(icon.getUnicode())) + .filter(icon -> TimiJava.isNotEmpty(icon.getSvg())) + .sorted(Comparator.comparing(Icon::getName)); } } diff --git a/src/main/java/com/imyeyu/api/modules/common/service/implement/MultilingualServiceImplement.java b/src/main/java/com/imyeyu/api/modules/common/service/implement/MultilingualServiceImplement.java index 06723ba..f71e32e 100644 --- a/src/main/java/com/imyeyu/api/modules/common/service/implement/MultilingualServiceImplement.java +++ b/src/main/java/com/imyeyu/api/modules/common/service/implement/MultilingualServiceImplement.java @@ -1,14 +1,8 @@ package com.imyeyu.api.modules.common.service.implement; -import com.imyeyu.java.TimiJava; -import com.imyeyu.java.bean.Language; -import com.imyeyu.java.bean.timi.TimiCode; -import com.imyeyu.java.bean.timi.TimiException; -import com.imyeyu.api.modules.common.bean.SettingKey; import com.imyeyu.api.modules.common.entity.Multilingual; import com.imyeyu.api.modules.common.mapper.MultilingualMapper; import com.imyeyu.api.modules.common.service.MultilingualService; -import com.imyeyu.api.modules.common.service.SettingService; import com.imyeyu.spring.mapper.BaseMapper; import com.imyeyu.spring.service.AbstractEntityService; import com.imyeyu.spring.util.Redis; @@ -17,6 +11,7 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; +import java.util.ArrayList; import java.util.List; /** @@ -26,70 +21,62 @@ import java.util.List; @Slf4j @Service @RequiredArgsConstructor -public class MultilingualServiceImplement extends AbstractEntityService implements MultilingualService { +public class MultilingualServiceImplement extends AbstractEntityService implements MultilingualService { - private final SettingService settingService; + private static final String KEY_PREFIX = "MULTILINGUAL:"; private final MultilingualMapper mapper; - private final Redis redisLanguageMap; - private final Redis redisLanguage; + private final Redis redisLanguage; @Override - protected BaseMapper mapper() { + protected BaseMapper mapper() { return mapper; } @Override - public Long create(String key, String zhCN) { - Multilingual multilingual = new Multilingual(); - multilingual.setKey(key); - multilingual.setZhCN(zhCN); - mapper.insert(multilingual); + public Multilingual get(String id) { + Multilingual result = redisLanguage.get(getKey(id)); + if (result == null) { + result = super.get(id); + redisLanguage.set(getKey(id), result, Time.D * 7); + } + return result; + } + + @Override + public String createIfDifferent(String id, String zhCN) { + Multilingual multilingual = mapper.select(id); + if (!multilingual.getZhCN().equals(zhCN)) { + return create(zhCN); + } return multilingual.getId(); } @Override - public Long createIfNotExist(String key, String zhCN) { - Multilingual existItem = mapper.selectByZhCN(zhCN); - if (TimiJava.isNotEmpty(existItem)) { - return existItem.getId(); - } - return create(key, zhCN); - } + public List listByIdList(List idList) { + List result = new ArrayList<>(); - @Override - public String get(Language.Enum language, Long id) { - Multilingual result = redisLanguage.get(id); - if (result == null) { - result = mapper.select(id); - if (result == null) { - throw new TimiException(TimiCode.RESULT_NULL).msgKey("TODO not found language"); + List notInCacheIdList = new ArrayList<>(); + for (String id : idList) { + Multilingual cache = redisLanguage.get(getKey(id)); + if (cache == null) { + notInCacheIdList.add(id); + } else { + result.add(cache); } - redisLanguage.set(id, result, Time.D * settingService.getAsInt(SettingKey.TTL_MULTILINGUAL)); } - return result.getValue(language); + + List notInCache = mapper.selectByIdList(notInCacheIdList); + for (Multilingual multilingual : notInCache) { + redisLanguage.set(getKey(multilingual.getId()), multilingual, Time.D * 7); + } + result.addAll(notInCache); + + return result; } - @Override - public String getByKey(Language.Enum language, String key) { - Long languageId = redisLanguageMap.get(key); - if (languageId == null) { - Multilingual result = mapper.selectByKey(key); - if (result == null) { - log.warn("not found language for key: {}", key); - return key; - } - long ttl = Time.D * settingService.getAsInt(SettingKey.TTL_MULTILINGUAL); - redisLanguage.set(result.getId(), result, ttl); - redisLanguageMap.set(result.getKey(), result.getId(), ttl); - return result.getValue(language); - } - return get(language, languageId); - } - - @Override - public List listByNotTranslate() { - return mapper.selectByNotTranslate(); + private String getKey(String id) { + return "%s%s".formatted(KEY_PREFIX, id); } } diff --git a/src/main/java/com/imyeyu/api/modules/common/service/implement/NotifyDetailServiceImplement.java b/src/main/java/com/imyeyu/api/modules/common/service/implement/NotifyDetailServiceImplement.java new file mode 100644 index 0000000..f9687ab --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/common/service/implement/NotifyDetailServiceImplement.java @@ -0,0 +1,27 @@ +package com.imyeyu.api.modules.common.service.implement; + +import com.imyeyu.api.modules.common.entity.NotifyDetail; +import com.imyeyu.api.modules.common.mapper.NotifyDetailMapper; +import com.imyeyu.api.modules.common.service.NotifyDetailService; +import com.imyeyu.spring.mapper.BaseMapper; +import com.imyeyu.spring.service.AbstractEntityService; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +/** + * 通知详情服务实现 + * + * @author 夜雨 + * @since 2026-05-13 14:46 + */ +@Service +@RequiredArgsConstructor +public class NotifyDetailServiceImplement extends AbstractEntityService implements NotifyDetailService { + + private final NotifyDetailMapper mapper; + + @Override + protected BaseMapper mapper() { + return mapper; + } +} diff --git a/src/main/java/com/imyeyu/api/modules/common/service/implement/NotifyServiceImplement.java b/src/main/java/com/imyeyu/api/modules/common/service/implement/NotifyServiceImplement.java new file mode 100644 index 0000000..5fac11f --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/common/service/implement/NotifyServiceImplement.java @@ -0,0 +1,27 @@ +package com.imyeyu.api.modules.common.service.implement; + +import com.imyeyu.api.modules.common.entity.Notify; +import com.imyeyu.api.modules.common.mapper.NotifyMapper; +import com.imyeyu.api.modules.common.service.NotifyService; +import com.imyeyu.spring.mapper.BaseMapper; +import com.imyeyu.spring.service.AbstractEntityService; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +/** + * 通知服务实现 + * + * @author 夜雨 + * @since 2026-05-13 14:46 + */ +@Service +@RequiredArgsConstructor +public class NotifyServiceImplement extends AbstractEntityService implements NotifyService { + + private final NotifyMapper mapper; + + @Override + protected BaseMapper mapper() { + return mapper; + } +} diff --git a/src/main/java/com/imyeyu/api/modules/common/service/implement/SettingServiceImplement.java b/src/main/java/com/imyeyu/api/modules/common/service/implement/SettingServiceImplement.java index 3eac7ac..aff0365 100644 --- a/src/main/java/com/imyeyu/api/modules/common/service/implement/SettingServiceImplement.java +++ b/src/main/java/com/imyeyu/api/modules/common/service/implement/SettingServiceImplement.java @@ -1,29 +1,22 @@ package com.imyeyu.api.modules.common.service.implement; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.node.ArrayNode; -import com.fasterxml.jackson.databind.node.ObjectNode; -import com.imyeyu.api.modules.common.bean.SettingKey; +import com.imyeyu.api.config.dbsource.TimiServerDBConfig; import com.imyeyu.api.modules.common.entity.Setting; import com.imyeyu.api.modules.common.mapper.SettingMapper; import com.imyeyu.api.modules.common.service.SettingService; import com.imyeyu.java.TimiJava; -import com.imyeyu.java.bean.timi.TimiCode; import com.imyeyu.java.bean.timi.TimiException; import com.imyeyu.spring.mapper.BaseMapper; import com.imyeyu.spring.service.AbstractEntityService; -import com.imyeyu.spring.util.Redis; -import com.imyeyu.utils.Time; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; -import org.yaml.snakeyaml.Yaml; +import org.springframework.transaction.annotation.Transactional; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; /** * 系统配置服务实现 @@ -37,9 +30,6 @@ import java.util.List; public class SettingServiceImplement extends AbstractEntityService implements SettingService { private final SettingMapper mapper; - private final Redis redisSetting; - - private final ObjectMapper jackson; @Override protected BaseMapper mapper() { @@ -47,132 +37,74 @@ public class SettingServiceImplement extends AbstractEntityService listByBizId(String bizType, String bizId) { + Setting example = new Setting(); + example.setBizType(bizType); + example.setBizId(bizId); + return mapper.selectAllByExample(example); + } + + @Transactional(TimiServerDBConfig.ROLLBACKER) + @Override + public void updateByBizId(String bizType, String bizId, List list) { + TimiException.required(bizType, "not found bizType"); + list = TimiJava.defaultIfNull(list, new ArrayList<>()); + + List dbList = listByBizId(bizType, bizId); + Map dbMap = new HashMap<>(dbList.size()); + for (Setting item : dbList) { + dbMap.put(item.getCode(), item); } - String cacheValue = redisSetting.get(key.toString()); - if (TimiJava.isNotEmpty(cacheValue)) { - try { - return jackson.readValue(cacheValue, Setting.class); - } catch (JsonProcessingException e) { - throw new TimiException(TimiCode.ERROR, "read setting cache error", e); + Map map = new HashMap<>(list.size()); + for (Setting item : list) { + TimiException.required(item.getCode(), "not found code"); + item.setBizType(bizType); + item.setBizId(bizId); + map.put(item.getCode(), item); + } + for (Setting dbSetting : dbList) { + if (!map.containsKey(dbSetting.getCode())) { + // 删除 + delete(dbSetting.getId()); } } - Setting setting = mapper.selectByKey(key); - if (TimiJava.isEmpty(setting.getValue())) { - return setting; - } - int settingTTL; - if (key == SettingKey.TTL_SETTING) { - settingTTL = Integer.parseInt(setting.getValue()); - } else { - settingTTL = Integer.parseInt(getByKey(SettingKey.TTL_SETTING).getValue()); - } - if (0 < settingTTL) { - try { - redisSetting.set(key.toString(), jackson.writeValueAsString(setting), Time.M * settingTTL); - } catch (JsonProcessingException e) { - throw new TimiException(TimiCode.ERROR, "write setting cache error", e); + for (Setting setting : map.values()) { + Setting dbSetting = dbMap.get(setting.getCode()); + if (dbSetting == null) { + // 新增 + create(setting); + continue; } - } - return setting; - } - - @Override - public List listByKeys(List keyList) { - List result = new ArrayList<>(); - for (int i = 0; i < keyList.size(); i++) { - result.add(getByKey(keyList.get(i))); - } - return result; - } - - @Override - public String getAsString(SettingKey key) { - return getByKey(key).getValue(); - } - - @Override - public int getAsInt(SettingKey key) { - return Integer.parseInt(getAsString(key)); - } - - @Override - public long getAsLong(SettingKey key) { - return Long.parseLong(getAsString(key)); - } - - @Override - public double getAsDouble(SettingKey key) { - return Double.parseDouble(getAsString(key)); - } - - @Override - public boolean is(SettingKey key) { - return Boolean.parseBoolean(getAsString(key)); - } - - @Override - public boolean not(SettingKey key) { - return !is(key); - } - - @Override - public JsonNode getAsJsonNode(SettingKey key) { - try { - return jackson.readTree(getAsString(key)); - } catch (JsonProcessingException e) { - throw new TimiException(TimiCode.ERROR, "read setting json error", e); + // 更新 + Setting update = new Setting(); + update.setId(dbSetting.getId()); + update.setBizType(bizType); + update.setBizId(bizId); + update.setCode(setting.getCode()); + update.setValue(setting.getValue()); + update.setValueType(setting.getValueType()); + update.setValueArgs(setting.getValueArgs()); + update.setDescription(setting.getDescription()); + update.setIsPrivate(setting.getIsPrivate()); + mapper.update(update); } } @Override - public ObjectNode getAsJsonObject(SettingKey key) { - return (ObjectNode) getAsJsonNode(key); - } - - @Override - public ArrayNode getAsArrayNode(SettingKey key) { - return (ArrayNode) getAsJsonNode(key); - } - - @Override - public T fromJson(SettingKey key, Class clazz) { - try { - return jackson.treeToValue(getAsJsonNode(key), clazz); - } catch (JsonProcessingException e) { - throw new TimiException(TimiCode.ERROR, "read setting json error", e); - } - } - - @Override - public T fromJson(SettingKey key, TypeReference typeReference) { - return jackson.convertValue(getAsJsonNode(key), typeReference); - } - - public T fromYaml(SettingKey key, Class clazz) { - return new Yaml().loadAs(getAsString(key), clazz); - } - - @Override - public List listAll() { - return mapper.listAll(); - } - - @Override - public void clearCache(SettingKey key) { - redisSetting.destroy(key.toString()); - } - - @Override - public void flushCache() { - redisSetting.flushAll(); + public void deleteByBizId(String bizType, String bizId) { + Setting example = new Setting(); + example.setBizType(bizType); + example.setBizId(bizId); + mapper.deleteAllByExample(example); } } diff --git a/src/main/java/com/imyeyu/api/modules/common/service/implement/TagServiceImplement.java b/src/main/java/com/imyeyu/api/modules/common/service/implement/TagServiceImplement.java index d1ae4f7..afcc742 100644 --- a/src/main/java/com/imyeyu/api/modules/common/service/implement/TagServiceImplement.java +++ b/src/main/java/com/imyeyu/api/modules/common/service/implement/TagServiceImplement.java @@ -1,9 +1,7 @@ package com.imyeyu.api.modules.common.service.implement; -import com.imyeyu.java.bean.timi.TimiException; import com.imyeyu.api.modules.common.entity.Tag; import com.imyeyu.api.modules.common.mapper.TagMapper; -import com.imyeyu.api.modules.common.service.MultilingualService; import com.imyeyu.api.modules.common.service.TagService; import com.imyeyu.spring.mapper.BaseMapper; import com.imyeyu.spring.service.AbstractEntityService; @@ -11,9 +9,6 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; -import java.util.List; -import java.util.UUID; - /** * @author 夜雨 * @since 2025-05-30 22:47 @@ -21,29 +16,12 @@ import java.util.UUID; @Slf4j @Service @RequiredArgsConstructor -public class TagServiceImplement extends AbstractEntityService implements TagService { - - private final MultilingualService multilingualService; +public class TagServiceImplement extends AbstractEntityService implements TagService { private final TagMapper mapper; @Override - protected BaseMapper mapper() { + protected BaseMapper mapper() { return mapper; } - - @Override - public void create(Tag tag) { - Long langId = multilingualService.createIfNotExist(UUID.randomUUID().toString(), tag.getValue()); - tag.setValue(String.valueOf(langId)); - super.create(tag); - } - - @Override - public List listByBizID(Tag.BizType bizType, String bizID) throws TimiException { - Tag example = new Tag(); - example.setBizType(bizType); - example.setBizID(bizID); - return mapper.selectAllByExample(example); - } } diff --git a/src/main/java/com/imyeyu/api/modules/common/service/implement/TaskServiceImplement.java b/src/main/java/com/imyeyu/api/modules/common/service/implement/TaskServiceImplement.java deleted file mode 100644 index b7bdb66..0000000 --- a/src/main/java/com/imyeyu/api/modules/common/service/implement/TaskServiceImplement.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.imyeyu.api.modules.common.service.implement; - -import com.imyeyu.api.modules.common.entity.Task; -import com.imyeyu.api.modules.common.mapper.TaskMapper; -import com.imyeyu.api.modules.common.service.TaskService; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; - -import java.util.Comparator; -import java.util.List; - -/** - * 任务服务 - * - * @author 夜雨 - * @since 2022-04-03 15:37 - */ -@Service -@RequiredArgsConstructor -public class TaskServiceImplement implements TaskService { - - private final TaskMapper mapper; - - @Override - public List listAll4Public() { - return mapper.listAll4Public().stream().sorted(Comparator.comparingInt(c -> c.getStatus().getSort())).toList(); - } -} diff --git a/src/main/java/com/imyeyu/api/modules/common/service/implement/TempFileServiceImplement.java b/src/main/java/com/imyeyu/api/modules/common/service/implement/TempFileServiceImplement.java index bd0db0d..65ba27e 100644 --- a/src/main/java/com/imyeyu/api/modules/common/service/implement/TempFileServiceImplement.java +++ b/src/main/java/com/imyeyu/api/modules/common/service/implement/TempFileServiceImplement.java @@ -1,10 +1,17 @@ package com.imyeyu.api.modules.common.service.implement; -import com.imyeyu.api.modules.common.bean.SettingKey; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; import com.imyeyu.api.modules.common.entity.Attachment; +import com.imyeyu.api.modules.common.entity.Setting; import com.imyeyu.api.modules.common.service.AttachmentService; import com.imyeyu.api.modules.common.service.SettingService; import com.imyeyu.api.modules.common.service.TempFileService; -import com.imyeyu.api.modules.common.vo.TempFileResponse; +import com.imyeyu.api.modules.common.vo.attach.TempFileResp; +import com.imyeyu.api.modules.common.vo.attach.TempFileSaveReq; +import com.imyeyu.api.modules.system.entity.Logger; +import com.imyeyu.api.modules.system.service.LoggerService; import com.imyeyu.io.IOSize; import com.imyeyu.java.TimiJava; import com.imyeyu.java.bean.timi.TimiCode; @@ -29,62 +36,138 @@ import java.util.List; @RequiredArgsConstructor public class TempFileServiceImplement implements TempFileService { + private static final Attachment.BizType BIZ_TYPE = Attachment.BizType.TEMP_FILE; + + private final ObjectMapper jackson; + + private final LoggerService loggerService; private final SettingService settingService; - private final AttachmentService attachmentService; + private final AttachmentService service; - public List store(List files, Long ttl) throws TimiException { - String ttlMinStr = settingService.getAsString(SettingKey.TEMP_FILE_TTL_MIN); - String ttlMaxStr = settingService.getAsString(SettingKey.TEMP_FILE_TTL_MAX); - String limitStr = settingService.getAsString(SettingKey.TEMP_FILE_LIMIT); - - long minTTL = Time.parseToMS(ttlMinStr); - long maxTTL = Time.parseToMS(ttlMaxStr); - long defaultTTL = Time.parseToMS(settingService.getAsString(SettingKey.TEMP_FILE_TTL_DEFAULT)); - long limit = IOSize.parse(limitStr); - long residueTime = Time.parseToMS(settingService.getAsString(SettingKey.TEMP_FILE_RESIDUE_TIME)); - - ttl = TimiJava.defaultIfNull(ttl, defaultTTL); - TimiException.requiredTrue(minTTL < ttl && ttl <= maxTTL, String.format("ttl must be between %s and %s", ttlMinStr, ttlMaxStr)); - - long newFileSize = files.stream().mapToLong(MultipartFile::getSize).sum(); - { - // 缓存池大小限制 - Page page = new Page<>(); - page.setIndex(0); - page.setSize(Long.MAX_VALUE); - { - Attachment example = new Attachment(); - example.setUploaderIp(TimiSpring.getRequestIP()); - page.setEqualsExample(example); - } - long currentUsage = attachmentService.page(page).getList().stream().mapToLong(Attachment::getSize).sum(); - TimiException.requiredTrue(currentUsage + newFileSize < limit, "out of storage limit(%s)".formatted(limitStr)); - } + public List create(List files, Long ttl) throws TimiException { + Logger logger = new Logger(); + logger.setModule("TEMP_FILE"); + logger.setOperation("STORE"); + logger.setIp(TimiSpring.getRequestIP()); try { - List result = new ArrayList<>(); + logger.setContent(jackson.writeValueAsString(logRequestArgs(files, ttl))); + + Setting ttlMin = settingService.getSystem(Setting.Module.TempFile.TTL_MIN); + Setting ttlMax = settingService.getSystem(Setting.Module.TempFile.TTL_MAX); + Setting ttlDefault = settingService.getSystem(Setting.Module.TempFile.TTL_DEFAULT); + Setting residueTime = settingService.getSystem(Setting.Module.TempFile.RESIDUE_TTL); + String limitStr = settingService.getSystem(Setting.Module.TempFile.LIMIT_SIZE_OF_IP).asString(); + + long limit = IOSize.parse(limitStr); + + ttl = TimiJava.defaultIfNull(ttl, ttlDefault.asTime()); + TimiException.requiredTrue(ttlMin.asTime() < ttl && ttl <= ttlMax.asTime(), String.format("ttl must be between %s and %s", ttlMin.asString(), ttlMax.asString())); + + long newFileSize = files.stream().mapToLong(MultipartFile::getSize).sum(); + { + // 缓存池大小限制 + Page page = new Page<>(); + page.setIndex(0); + page.setSize(Long.MAX_VALUE); + { + Attachment example = new Attachment(); + example.setUploaderIp(TimiSpring.getRequestIP()); + page.setEqualsExample(example); + } + long currentUsage = service.page(page).getList().stream().mapToLong(Attachment::getSize).sum(); + TimiException.requiredTrue(currentUsage + newFileSize < limit, "out of storage limit(%s)".formatted(limitStr)); + } + + List result = new ArrayList<>(); long deletedAt = Time.now() + ttl; - long destroyAt = deletedAt + residueTime; - for (int i = 0; i < files.size(); i++) { - MultipartFile file = files.get(i); + long destroyAt = deletedAt + residueTime.asTime(); + for (MultipartFile file : files) { + Attachment attachment = new Attachment(); + attachment.setBizType(BIZ_TYPE); + attachment.setName(file.getOriginalFilename()); + attachment.setDeletedAt(deletedAt); + attachment.setDestroyAt(destroyAt); + attachment.setInputStream(file.getInputStream()); + service.create(attachment); - Attachment attach = new Attachment(); - attach.setBizType(Attachment.BizType.TEMP_FILE); - attach.setName(file.getOriginalFilename()); - attach.setDeletedAt(deletedAt); - attach.setDestroyAt(destroyAt); - attach.setInputStream(file.getInputStream()); - attachmentService.create(attach); - - TempFileResponse resp = new TempFileResponse(); - resp.setId(attach.getMongoId()); + TempFileResp resp = new TempFileResp(); + resp.setId(attachment.getId()); resp.setExpireAt(deletedAt); result.add(resp); } + logger.setLevel(Logger.Level.INFO); + logger.setResult(jackson.writeValueAsString(result)); return result; + } catch (TimiException e) { + logger.setLevel(Logger.Level.WARN); + logger.setException(e.getMessage()); + throw e; } catch (Exception e) { + logger.setLevel(Logger.Level.ERROR); + logger.setException(TimiJava.serializeThrowable(e)); log.error("store temp file error", e); throw new TimiException(TimiCode.ERROR, "store temp file error", e); + } finally { + loggerService.create(logger); } } + + @Override + public void save(TempFileSaveReq request) { + Logger logger = new Logger(); + logger.setModule("TEMP_FILE"); + logger.setOperation("SAVE"); + logger.setIp(TimiSpring.getRequestIP()); + try { + logger.setContent(jackson.writeValueAsString(request)); + + String[] tempFileIds = request.getTempFileIds(); + if (TimiJava.isEmpty(tempFileIds)) { + return; + } + for (String tempFileId : tempFileIds) { + Attachment tempFileAttach = service.get(tempFileId); + if (tempFileAttach == null) { + continue; + } + Attachment attachment = new Attachment(); + attachment.setName(tempFileAttach.getName()); + attachment.setBizType(request.getBizType()); + attachment.setBizId(request.getBizId()); + attachment.setInputStream(tempFileAttach.openInputStream()); + service.create(attachment); + } + logger.setLevel(Logger.Level.INFO); + } catch (TimiException e) { + logger.setLevel(Logger.Level.WARN); + logger.setException(e.getMessage()); + throw e; + } catch (Exception e) { + logger.setLevel(Logger.Level.ERROR); + logger.setException(TimiJava.serializeThrowable(e)); + log.error("save temp file error", e); + throw new TimiException(TimiCode.ERROR, "save temp file error", e); + } finally { + loggerService.create(logger); + } + } + + private ObjectNode logRequestArgs(List files, Long ttl) { + ObjectNode result = jackson.createObjectNode(); + result.put("ttl", ttl); + { + ArrayNode filesArr = jackson.createArrayNode(); + for (MultipartFile file : files) { + ObjectNode fileObj = jackson.createObjectNode(); + fileObj.put("name", file.getName()); + fileObj.put("originalFilename", file.getOriginalFilename()); + fileObj.put("contentType", file.getContentType()); + fileObj.put("size", file.getSize()); + filesArr.add(fileObj); + } + result.set("files", filesArr); + } + return result; + } } diff --git a/src/main/java/com/imyeyu/api/modules/common/service/implement/TemplateServiceImplement.java b/src/main/java/com/imyeyu/api/modules/common/service/implement/TemplateServiceImplement.java deleted file mode 100644 index 87039ff..0000000 --- a/src/main/java/com/imyeyu/api/modules/common/service/implement/TemplateServiceImplement.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.imyeyu.api.modules.common.service.implement; - -import com.imyeyu.java.bean.timi.TimiCode; -import com.imyeyu.java.bean.timi.TimiException; -import com.imyeyu.api.modules.common.entity.Template; -import com.imyeyu.api.modules.common.mapper.TemplateMapper; -import com.imyeyu.api.modules.common.service.TemplateService; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Service; - -/** - * @author 夜雨 - * @since 2023-09-22 16:40 - */ -@Slf4j -@Service -@RequiredArgsConstructor -public class TemplateServiceImplement implements TemplateService { - - private final TemplateMapper mapper; - - @Override - public Template get(Template.BizType bizType, String bizCode) { - Template template = mapper.query(bizType, bizCode); - if (template == null) { - throw new TimiException(TimiCode.RESULT_NULL).msgKey("not found template"); - } - return template; - } -} diff --git a/src/main/java/com/imyeyu/api/modules/common/service/implement/UserConfigServiceImplement.java b/src/main/java/com/imyeyu/api/modules/common/service/implement/UserConfigServiceImplement.java deleted file mode 100644 index bea4485..0000000 --- a/src/main/java/com/imyeyu/api/modules/common/service/implement/UserConfigServiceImplement.java +++ /dev/null @@ -1,45 +0,0 @@ -package com.imyeyu.api.modules.common.service.implement; - -import com.imyeyu.api.config.dbsource.TimiServerDBConfig; -import com.imyeyu.api.modules.common.entity.User; -import com.imyeyu.api.modules.common.entity.UserConfig; -import com.imyeyu.api.modules.common.mapper.UserConfigMapper; -import com.imyeyu.api.modules.common.service.UserConfigService; -import com.imyeyu.api.modules.common.service.UserService; -import com.imyeyu.spring.service.AbstractEntityService; -import com.imyeyu.utils.Time; -import lombok.RequiredArgsConstructor; -import org.springframework.context.annotation.Lazy; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -/** - * 用户设置服务实现 - * - * @author 夜雨 - * @since 2021-08-12 16:24 - */ -@Service -@RequiredArgsConstructor(onConstructor_ = {@Lazy}) -public class UserConfigServiceImplement extends AbstractEntityService implements UserConfigService { - - private final UserService userService; - private final UserConfigMapper mapper; - - @Override - protected UserConfigMapper mapper() { - return mapper; - } - - @Transactional(TimiServerDBConfig.ROLLBACKER) - @Override - public void update(UserConfig config) { - User user = userService.getLoginUser(); - - UserConfig dbConfig = get(user.getId()); - dbConfig.setUserId(user.getId()); - dbConfig.setEmailReplyRemind(config.isEmailReplyRemind()); - dbConfig.setUpdatedAt(Time.now()); - super.update(dbConfig); - } -} diff --git a/src/main/java/com/imyeyu/api/modules/common/service/implement/UserPrivacyServiceImplement.java b/src/main/java/com/imyeyu/api/modules/common/service/implement/UserPrivacyServiceImplement.java deleted file mode 100644 index d4c090e..0000000 --- a/src/main/java/com/imyeyu/api/modules/common/service/implement/UserPrivacyServiceImplement.java +++ /dev/null @@ -1,50 +0,0 @@ -package com.imyeyu.api.modules.common.service.implement; - -import com.imyeyu.api.config.dbsource.TimiServerDBConfig; -import com.imyeyu.api.modules.common.entity.User; -import com.imyeyu.api.modules.common.entity.UserPrivacy; -import com.imyeyu.api.modules.common.mapper.UserPrivacyMapper; -import com.imyeyu.api.modules.common.service.UserPrivacyService; -import com.imyeyu.api.modules.common.service.UserService; -import com.imyeyu.spring.mapper.BaseMapper; -import com.imyeyu.spring.service.AbstractEntityService; -import com.imyeyu.utils.Time; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -/** - * 用户隐私控制服务实现 - * - * @author 夜雨 - * @since 2021-07-27 17:19 - */ -@Service -@RequiredArgsConstructor -public class UserPrivacyServiceImplement extends AbstractEntityService implements UserPrivacyService { - - private final UserService userService; - private final UserPrivacyMapper mapper; - - @Override - protected BaseMapper mapper() { - return mapper; - } - - @Transactional(TimiServerDBConfig.ROLLBACKER) - @Override - public void update(UserPrivacy privacy) { - User user = userService.getLoginUser(); - - UserPrivacy dbPrivacy = get(user.getId()); - dbPrivacy.setEmail(privacy.isEmail()); - dbPrivacy.setSex(privacy.isSex()); - dbPrivacy.setBirthdate(privacy.isBirthdate()); - dbPrivacy.setQq(privacy.isQq()); - dbPrivacy.setLastLoginAt(privacy.isLastLoginAt()); - dbPrivacy.setCreatedAt(privacy.isCreatedAt()); - dbPrivacy.setUserId(user.getId()); - dbPrivacy.setUpdatedAt(Time.now()); - super.update(dbPrivacy); - } -} diff --git a/src/main/java/com/imyeyu/api/modules/common/service/implement/UserProfileServiceImplement.java b/src/main/java/com/imyeyu/api/modules/common/service/implement/UserProfileServiceImplement.java deleted file mode 100644 index 0601ba9..0000000 --- a/src/main/java/com/imyeyu/api/modules/common/service/implement/UserProfileServiceImplement.java +++ /dev/null @@ -1,104 +0,0 @@ -package com.imyeyu.api.modules.common.service.implement; - -import com.imyeyu.api.config.dbsource.TimiServerDBConfig; -import com.imyeyu.api.modules.common.entity.Attachment; -import com.imyeyu.api.modules.common.entity.User; -import com.imyeyu.api.modules.common.entity.UserProfile; -import com.imyeyu.api.modules.common.mapper.UserProfileMapper; -import com.imyeyu.api.modules.common.service.AttachmentService; -import com.imyeyu.api.modules.common.service.UserProfileService; -import com.imyeyu.api.modules.common.service.UserService; -import com.imyeyu.api.modules.common.vo.user.UserRequest; -import com.imyeyu.io.IOSize; -import com.imyeyu.java.TimiJava; -import com.imyeyu.java.bean.timi.TimiCode; -import com.imyeyu.java.bean.timi.TimiException; -import com.imyeyu.spring.mapper.BaseMapper; -import com.imyeyu.spring.service.AbstractEntityService; -import lombok.RequiredArgsConstructor; -import org.springframework.context.annotation.Lazy; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import org.springframework.web.multipart.MultipartFile; - -import java.io.IOException; - -/** - * 用户数据服务实现 - * - * @author 夜雨 - * @since 2021-07-27 17:08 - */ -@Service -@RequiredArgsConstructor(onConstructor_ = {@Lazy}) -public class UserProfileServiceImplement extends AbstractEntityService implements UserProfileService { - - private final UserService userService; - private final AttachmentService attachmentService; - - private final UserProfileMapper mapper; - - @Override - protected BaseMapper mapper() { - return mapper; - } - - @Override - public void update(UserProfile profile) { - UserProfile dbProfile = get(profile.getUserId()); - dbProfile.setWrapperType(profile.getWrapperType()); - dbProfile.setAvatarType(profile.getAvatarType()); - dbProfile.setSex(profile.getSex()); - dbProfile.setBirthdate(profile.getBirthdate()); - dbProfile.setQq(profile.getQq()); - dbProfile.setDescription(profile.getDescription()); - super.update(dbProfile); - } - - @Transactional(TimiServerDBConfig.ROLLBACKER) - @Override - public void update(UserRequest request) { - try { - User user = userService.getLoginUser(); - if (TimiJava.isNotEmpty(request.getProfile().getWrapper())) { - Attachment dbWrapper = attachmentService.getByAttachType(Attachment.BizType.USER, request.getId(), User.AttachType.WRAPPER); - // TODO 限制 PNG - if (dbWrapper != null) { - attachmentService.delete(dbWrapper.getId()); - } - MultipartFile wrapper = request.getProfile().getWrapper(); - // 字节数据 - byte[] bytes = wrapper.getInputStream().readAllBytes(); - if (IOSize.MB < bytes.length) { - throw new TimiException(TimiCode.ARG_BAD).msgKey("限制上传文件大小 1 MB"); - } - Attachment wrapperAttach = new Attachment(); - wrapperAttach.setName(request.getId() + ".png"); - wrapperAttach.setBizType(Attachment.BizType.USER); - wrapperAttach.setBizId(request.getId()); - wrapperAttach.setAttachTypeValue(User.AttachType.WRAPPER); - wrapperAttach.setSize(wrapper.getSize()); - wrapperAttach.setInputStream(wrapper.getInputStream()); - attachmentService.create(wrapperAttach); - } - if (TimiJava.isNotEmpty(request.getProfile().getAvatar())) { - Attachment dbAvatar = attachmentService.getByAttachType(Attachment.BizType.USER, request.getId(), User.AttachType.AVATAR); - if (dbAvatar != null) { - attachmentService.delete(dbAvatar.getId()); - } - MultipartFile avatar = request.getProfile().getAvatar(); - Attachment avatarAttach = new Attachment(); - avatarAttach.setName(request.getId() + ".png"); - avatarAttach.setBizType(Attachment.BizType.USER); - avatarAttach.setBizId(request.getId()); - avatarAttach.setAttachTypeValue(User.AttachType.AVATAR); - avatarAttach.setSize(avatar.getSize()); - avatarAttach.setInputStream(avatar.getInputStream()); - attachmentService.create(avatarAttach); - } - update(request.getProfile()); - } catch (IOException e) { - throw new RuntimeException(e); - } - } -} diff --git a/src/main/java/com/imyeyu/api/modules/common/service/implement/UserServiceImplement.java b/src/main/java/com/imyeyu/api/modules/common/service/implement/UserServiceImplement.java deleted file mode 100644 index c7d70fd..0000000 --- a/src/main/java/com/imyeyu/api/modules/common/service/implement/UserServiceImplement.java +++ /dev/null @@ -1,403 +0,0 @@ -package com.imyeyu.api.modules.common.service.implement; - -import com.imyeyu.api.config.dbsource.TimiServerDBConfig; -import com.imyeyu.api.modules.blog.util.UserToken; -import com.imyeyu.api.modules.common.entity.Attachment; -import com.imyeyu.api.modules.common.entity.CommentReply; -import com.imyeyu.api.modules.common.entity.EmailQueue; -import com.imyeyu.api.modules.common.entity.User; -import com.imyeyu.api.modules.common.entity.UserConfig; -import com.imyeyu.api.modules.common.entity.UserPrivacy; -import com.imyeyu.api.modules.common.entity.UserProfile; -import com.imyeyu.api.modules.common.mapper.CommentMapper; -import com.imyeyu.api.modules.common.mapper.CommentReplyMapper; -import com.imyeyu.api.modules.common.mapper.UserMapper; -import com.imyeyu.api.modules.common.mapper.UserProfileMapper; -import com.imyeyu.api.modules.common.service.AttachmentService; -import com.imyeyu.api.modules.common.service.EmailQueueService; -import com.imyeyu.api.modules.common.service.UserConfigService; -import com.imyeyu.api.modules.common.service.UserPrivacyService; -import com.imyeyu.api.modules.common.service.UserProfileService; -import com.imyeyu.api.modules.common.service.UserService; -import com.imyeyu.api.modules.common.vo.user.LoginRequest; -import com.imyeyu.api.modules.common.vo.user.LoginResponse; -import com.imyeyu.api.modules.common.vo.user.RegisterRequest; -import com.imyeyu.api.modules.common.vo.user.UserProfileView; -import com.imyeyu.api.modules.common.vo.user.UserView; -import com.imyeyu.api.modules.git.entity.Developer; -import com.imyeyu.api.modules.git.service.DeveloperService; -import com.imyeyu.api.modules.minecraft.service.PlayerService; -import com.imyeyu.java.TimiJava; -import com.imyeyu.java.bean.timi.TimiCode; -import com.imyeyu.java.bean.timi.TimiException; -import com.imyeyu.spring.TimiSpring; -import com.imyeyu.spring.bean.Logic; -import com.imyeyu.spring.mapper.BaseMapper; -import com.imyeyu.spring.service.AbstractEntityService; -import com.imyeyu.spring.util.Redis; -import com.imyeyu.utils.Calc; -import com.imyeyu.utils.Digest; -import com.imyeyu.utils.Text; -import com.imyeyu.utils.Time; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.BeanUtils; -import org.springframework.context.annotation.Lazy; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import java.util.List; -import java.util.UUID; - -/** - * 用户管理服务实现 - * - * @author 夜雨 - * @since 2021-02-23 21:43 - */ -@Slf4j -@Service -@RequiredArgsConstructor(onConstructor_ = {@Lazy}) -public class UserServiceImplement extends AbstractEntityService implements UserService, TimiJava { - - private final PlayerService mcPlayerService; - private final DeveloperService gitDeveloperService; - private final EmailQueueService emailQueueService; - private final UserConfigService configService; - private final AttachmentService attachmentService; - private final UserProfileService profileService; - private final UserPrivacyService privacyService; - - private final UserMapper mapper; - private final CommentMapper commentMapper; - private final UserProfileMapper userProfileMapper; - private final CommentReplyMapper commentReplyMapper; - - private final Redis redisUserExpFlag; - private final Redis redisUserEmailVerify; - private final Redis redisUserResetPWVerify; - - private final UserToken userToken; - - @Override - protected BaseMapper mapper() { - return mapper; - } - - @Transactional(TimiServerDBConfig.ROLLBACKER) - @Override - public void update(User user) { - User userByName = getByName(user.getName()); - if (userByName != null && !userByName.getId().equals(user.getId())) { - throw new TimiException(TimiCode.DATA_EXIST).msgKey("user.name.exist"); - } - User userByEmail = getByEmail(user.getEmail()); - if (userByEmail != null && !userByEmail.getId().equals(user.getId())) { - throw new TimiException(TimiCode.DATA_EXIST).msgKey("user.email.exist_and_verified"); - } - User dbUser = get(user.getId()); - if (TimiJava.isNotEmpty(user.getEmail())) { - if (!user.getEmail().equals(dbUser.getEmail())) { - // 重新验证 - dbUser.setEmailVerifyAt(null); - } - } - dbUser.setEmail(user.getEmail()); - dbUser.setName(user.getName()); - super.update(dbUser); - } - - @Transactional(TimiServerDBConfig.ROLLBACKER) - @Override - public LoginResponse register(RegisterRequest request) { - if (getByName(request.getName()) != null) { - throw new TimiException(TimiCode.DATA_EXIST).msgKey("user.register.exist_name"); - } - if (getByEmail(request.getEmail()) != null) { - throw new TimiException(TimiCode.DATA_EXIST).msgKey("user.register.exist_email"); - } - User user = new User(); - user.setEmail(request.getEmail()); - user.setName(request.getName()); - user.setPassword(digestPassword(request.getPassword(), Text.randomString(16))); - user.setCreatedAt(Time.now()); - // 注册账号 - create(user); - // 初始化资料 - profileService.create(new UserProfile(user.getId())); - // 初始化隐私控制 - privacyService.create(new UserPrivacy(user.getId())); - // 初始化设置 - configService.create(new UserConfig(user.getId())); - // 初始化开发者 - gitDeveloperService.create(new Developer(user.getId())); - // 自动登录 - return login(new LoginRequest(String.valueOf(user.getId()), request.getPassword())); - } - - @Transactional(TimiServerDBConfig.ROLLBACKER) - @Override - public LoginResponse login(LoginRequest request) { - if (TimiJava.isEmpty(request)) { - throw new TimiException(TimiCode.ARG_MISS); - } - // 用户 - if (TimiJava.isEmpty(request.getUser())) { - throw new TimiException(TimiCode.ARG_MISS).msgKey("user.login.user.empty"); - } - // 密码 - if (TimiJava.isEmpty(request.getPassword())) { - throw new TimiException(TimiCode.ARG_MISS).msgKey("user.login.password.empty"); - } - User result = get(request.getUser()); - if (request.getUser().contains("@")) { - if (!result.emailVerified()) { - throw new TimiException(TimiCode.RESULT_BAD).msgKey("user.login.email.not_verify"); - } - } - if (TimiJava.isEmpty(result)) { - throw new TimiException(TimiCode.RESULT_NULL).msgKey("user.login.not_found"); - } - if (result.isBanning()) { - throw new TimiException(TimiCode.RESULT_BAN).msgKey("user.login.baned"); - } - if (isInvalidPassword(result.getPassword(), request.getPassword())) { - throw new TimiException(TimiCode.RESULT_BAD).msgKey("user.login.password.mismatch"); - } - // 用户数据 - UserProfile profile = profileService.get(result.getId()); - profile.setLastLoginIP(TimiSpring.getRequestIP()); - profile.setLastLoginAt(Time.now()); - updateExp(profile.getUserId()); - userProfileMapper.update(profile); - // 生成并缓存 Token - String token = UUID.randomUUID().toString(); - Long expireAt = userToken.set(token, result.getId()); - return new LoginResponse(result.getId(), token, expireAt); - } - - @Override - public UserView view(Long userId) { - UserProfileView profile = new UserProfileView(); - { - // 附件 - List attachmentList = attachmentService.listByBizId(Attachment.BizType.USER, userId); - boolean hasAvatar = false, hasWrapper = false; - for (int i = 0; i < attachmentList.size(); i++) { - if (!hasAvatar && User.AttachType.AVATAR.toString().equals(attachmentList.get(i).getAttachType())) { - hasAvatar = true; - } - if (!hasWrapper && User.AttachType.WRAPPER.toString().equals(attachmentList.get(i).getAttachType())) { - hasWrapper = true; - } - } - if (!hasAvatar) { - attachmentList.add(attachmentService.getByAttachType(Attachment.BizType.USER, 0, User.AttachType.DEFAULT_AVATAR)); - } - if (!hasWrapper) { - attachmentList.add(attachmentService.getByAttachType(Attachment.BizType.USER, 0, User.AttachType.DEFAULT_WRAPPER)); - } - profile.setAttachmentList(attachmentList); - } - BeanUtils.copyProperties(profileService.get(userId), profile); - - UserView view = new UserView(); - view.setProfile(profile); - BeanUtils.copyProperties(get(userId), view); - return view; - } - - @Override - public boolean isInvalidPassword(String digestPassword, String password) { - // "$SHA$盐$摘要(摘要(明文) + 盐) - String[] arr = digestPassword.split("\\$"); - return digestPassword(digestPassword, arr[2]).equals(password); - } - - @Transactional(TimiServerDBConfig.ROLLBACKER) - @Override - public LoginResponse login4Token() { - String token = TimiSpring.getToken(); - try { - User user = getLoginUser(); - updateExp(user.getId()); - return new LoginResponse(user.getId(), token, userToken.getExpireAt(token)); - } catch (Exception e) { - throw new TimiException(TimiCode.IGNORE); - } - } - - @Override - public void logout() { - userToken.clear(TimiSpring.getToken()); - } - - @Override - public User get(String user) { - User result; - if (Calc.isNumber(user)) { - result = get(Long.parseLong(user)); - } else if (user.contains("@")) { - result = getByEmail(user); - } else { - result = getByName(user); - } - return result; - } - - @Override - public boolean isLogged() { - return userToken.isValid(TimiSpring.getToken()); - } - - @Override - public User getLoginUser() { - String token = TimiSpring.getToken(); - if (TimiJava.isEmpty(token)) { - return null; - } - return userToken.getUser(token); - } - - @Override - public User getByName(String name) { - return mapper.selectByName(name); - } - - @Override - public User getByEmail(String email) { - return mapper.selectByEmail(email); - } - - @Transactional(TimiServerDBConfig.ROLLBACKER) - @Override - public void sendEmailVerify() { - User user = getLoginUser(); - if (TimiJava.isEmpty(user.getEmail())) { - throw new TimiException(TimiCode.RESULT_BAD).msgKey("user.email.empty"); - } - if (user.emailVerified()) { - throw new TimiException(TimiCode.RESULT_BAD).msgKey("user.email.verified"); - } - EmailQueue emailQueue = new EmailQueue(); - emailQueue.setBizType(EmailQueue.BizType.EMAIL_VERIFY); - emailQueue.setBizId(user.getId()); - emailQueue.setSendAt(Time.now()); - emailQueueService.create(emailQueue); - } - - @Transactional(TimiServerDBConfig.ROLLBACKER) - @Override - public void emailVerifyCallback(String key) { - User user = getLoginUser(); - if (user.emailVerified()) { - throw new TimiException(TimiCode.RESULT_BAD).msgKey("user.email.verified"); - } - Long userId = redisUserEmailVerify.get(key); - if (userId == null) { - throw new TimiException(TimiCode.ARG_BAD).msgKey("user.email.illegal_key"); - } - if (!userId.equals(user.getId())) { - throw new TimiException(TimiCode.ARG_BAD).msgKey("user.email.illegal_key"); - } - redisUserEmailVerify.destroy(key); - user.setEmailVerifyAt(null); - update(user); - } - - @Transactional(TimiServerDBConfig.ROLLBACKER) - @Override - public void updatePassword(String oldPassword, String newPassword) { - String token = TimiSpring.getToken(); - User user = getLoginUser(); - if (isInvalidPassword(user.getPassword(), oldPassword)) { - throw new TimiException(TimiCode.RESULT_BAD).msgKey("user.password.mismatch"); - } - // 更新密码 - user.setPassword(digestPassword(newPassword, Text.randomString(16))); - user.setUpdatedAt(Time.now()); - mapper.update(user); - // 清除登录会话 - userToken.clear(token); - } - - @Transactional(TimiServerDBConfig.ROLLBACKER) - @Override - public void sendPasswordForgetVerify(String user) { - User result = get(user); - if (result == null) { - throw new TimiException(TimiCode.RESULT_NULL).msgKey("user.password.forget.not_found"); - } - if (TimiJava.isEmpty(result.getEmail()) || !result.emailVerified()) { - throw new TimiException(TimiCode.RESULT_NULL).msgKey("user.password.forget.not_valid_email"); - } - EmailQueue emailQueue = new EmailQueue(); - emailQueue.setBizType(EmailQueue.BizType.RESET_PASSWORD); - emailQueue.setBizId(result.getId()); - emailQueue.setSendAt(Time.now()); - emailQueueService.create(emailQueue); - } - - @Transactional(TimiServerDBConfig.ROLLBACKER) - @Override - public void resetPasswordByKey(String key, String password) { - Long userId = redisUserResetPWVerify.get(key); - if (userId == null) { - throw new TimiException(TimiCode.ARG_BAD).msgKey("user.password.forget.illegal_key"); - } - User user = get(userId); - redisUserResetPWVerify.destroy(key); - user.setPassword(digestPassword(password, Text.randomString(16))); - user.setUpdatedAt(Time.now()); - mapper.update(user); - } - - @Transactional(TimiServerDBConfig.ROLLBACKER) - @Override - public void cancel(String password) { - String token = TimiSpring.getToken(); - User user = getLoginUser(); - if (isInvalidPassword(user.getPassword(), password)) { - throw new TimiException(TimiCode.ARG_BAD).msgKey("user.cancel.password_error"); - } - // 删除评论 - commentMapper.deleteByUserId(user.getId()); - { - // 删除回复 - CommentReply example = new CommentReply(); - example.setSenderId(user.getId()); - example.setReceiverId(user.getId()); - commentReplyMapper.deleteAllByExample(example, Logic.OR); - } - // 删除账号 - delete(user.getId()); - // 清除登录会话 - userToken.clear(token); - } - - private void updateExp(Long userId) { - if (!redisUserExpFlag.has(userId)) { - // 当天无登录标记,加经验 - UserProfile profile = profileService.get(userId); - profile.setExp(profile.getExp() + 2); - redisUserExpFlag.set(profile.getUserId(), "", Time.tomorrow() - Time.now()); - profileService.update(profile); - } - } - - /** - * 生成密码摘要 - * - * @param password 原始密码 - * @param salt 盐值 - * @return 密码摘要 - */ - private String digestPassword(String password, String salt) { - try { - return "$SHA$%s$%s".formatted(salt, Digest.sha256(Digest.sha256(password) + salt)); - } catch (Exception e) { - log.error("digest password error", e); - throw new TimiException(TimiCode.ERROR).msgKey("TODO digest password error"); - } - } -} diff --git a/src/main/java/com/imyeyu/api/modules/common/service/implement/VersionServiceImplement.java b/src/main/java/com/imyeyu/api/modules/common/service/implement/VersionServiceImplement.java deleted file mode 100644 index d0288c4..0000000 --- a/src/main/java/com/imyeyu/api/modules/common/service/implement/VersionServiceImplement.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.imyeyu.api.modules.common.service.implement; - -import lombok.RequiredArgsConstructor; -import com.imyeyu.api.modules.common.entity.Version; -import com.imyeyu.api.modules.common.mapper.VersionMapper; -import com.imyeyu.api.modules.common.service.VersionService; -import org.springframework.stereotype.Service; - -/** - * 版本服务实现 - * - * @author 夜雨 - * @since 2021-06-10 16:07 - */ -@Service -@RequiredArgsConstructor -public class VersionServiceImplement implements VersionService { - - private final VersionMapper mapper; - - @Override - public Version getByName(String name) { - return mapper.queryByName(name); - } -} diff --git a/src/main/java/com/imyeyu/api/modules/common/task/EmailTask.java b/src/main/java/com/imyeyu/api/modules/common/task/EmailTask.java deleted file mode 100644 index c06f672..0000000 --- a/src/main/java/com/imyeyu/api/modules/common/task/EmailTask.java +++ /dev/null @@ -1,219 +0,0 @@ -package com.imyeyu.api.modules.common.task; - -import freemarker.template.Template; -import jakarta.mail.internet.MimeMessage; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import com.imyeyu.java.TimiJava; -import com.imyeyu.java.bean.timi.TimiCode; -import com.imyeyu.api.config.dbsource.TimiServerDBConfig; -import com.imyeyu.api.modules.blog.entity.CommentRemindQueue; -import com.imyeyu.api.modules.blog.service.CommentRemindQueueService; -import com.imyeyu.api.modules.common.bean.EmailException; -import com.imyeyu.api.modules.common.entity.CommentReply; -import com.imyeyu.api.modules.common.entity.EmailQueue; -import com.imyeyu.api.modules.common.entity.EmailQueueLog; -import com.imyeyu.api.modules.common.entity.User; -import com.imyeyu.api.modules.common.service.CommentReplyService; -import com.imyeyu.api.modules.common.service.EmailQueueService; -import com.imyeyu.api.modules.common.service.UserService; -import com.imyeyu.spring.util.Redis; -import com.imyeyu.utils.Text; -import com.imyeyu.utils.Time; -import org.springframework.beans.BeanUtils; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.annotation.Configuration; -import org.springframework.mail.javamail.JavaMailSender; -import org.springframework.mail.javamail.MimeMessageHelper; -import org.springframework.scheduling.annotation.EnableScheduling; -import org.springframework.scheduling.annotation.Scheduled; -import org.springframework.transaction.annotation.Transactional; -import org.springframework.ui.freemarker.FreeMarkerTemplateUtils; -import org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -/** - * 邮件推送任务 - * - * @author 夜雨 - * @since 2021-08-24 14:10 - */ -@Slf4j -@Configuration -@EnableScheduling -@RequiredArgsConstructor -public class EmailTask implements TimiJava { - - @Value("${spring.profiles.active}") - private String env; - - @Value("${spring.mail.username}") - private String sendUser; - - private final UserService userService; - private final JavaMailSender mailSender; - private final EmailQueueService service; - private final CommentReplyService commentReplyService; - private final CommentRemindQueueService commentRemindQueueService; - - private final Redis redisUserEmailVerify; - private final Redis redisUserResetPWVerify; - - private final FreeMarkerConfigurer freeMarkerConfigurer; - - @Scheduled(fixedRate = Time.S * 8) - @Transactional(TimiServerDBConfig.ROLLBACKER) - public void traverseQueue() { - List emailQueueList = service.listAll(); - if (TimiJava.isNotEmpty(emailQueueList)) { - for (EmailQueue emailQueue : emailQueueList) { - long now = System.currentTimeMillis(); - if (emailQueue.getSendAt() < now) { - log.info(emailQueue.getUUID() + " 邮件推送:" + emailQueue.getBizType() + "." +emailQueue.getBizId()); - - EmailQueueLog emailQueueLog = new EmailQueueLog(); - emailQueueLog.setUUID(emailQueue.getUUID()); - emailQueueLog.setBizType(emailQueue.getBizType()); - emailQueueLog.setBizId(emailQueue.getBizId()); - emailQueueLog.setSendAt(emailQueue.getSendAt()); - try { - String sendTo = switch (emailQueue.getBizType()) { - case REPLY_REMINAD -> sendEmail4ReplyRemind(emailQueue); - case EMAIL_VERIFY -> sendEmail4EmailVerify(emailQueue); - case RESET_PASSWORD -> sendEmail4ResetPassword(emailQueue); - }; - emailQueueLog.setSendTo(sendTo); - emailQueueLog.setIsSent(true); - emailQueueLog.setCreatedAt(System.currentTimeMillis()); - log.info(emailQueue.getUUID() + " 邮件 " + emailQueueLog.getSendTo() + " 推送成功:" + (emailQueueLog.getCreatedAt() - now) + " ms"); - } catch (EmailException e) { - emailQueueLog.setIsSent(false); - log.error(emailQueue.getUUID() + " 邮件 " + e.getEmail() + " 推送中止:", e.getMessage()); - emailQueueLog.setExceptionMsg(e.getMessage()); - } catch (Exception e) { - emailQueueLog.setIsSent(false); - log.error(emailQueue.getUUID() + " 邮件 " + emailQueueLog.getSendTo() + " 推送异常", e); - emailQueueLog.setExceptionMsg(e.getMessage().substring(0,Math.min(200, e.getMessage().length())) + "..."); - } finally { - emailQueueLog.setCreatedAt(now); - service.addLog(emailQueueLog); - service.destroy(emailQueue.getUUID()); - } - } - } - } - } - - /** - * 发送邮件 - * - * @param to 目标邮箱 - * @param subject 标题 - * @param html HTML 字符串 - * @throws Exception 发送异常 - */ - private void sendEmail(String to, String subject, String html) throws Exception { - if (env.contains("dev")) { - log.info("skip send email in debug environment"); - log.info("send title: {}", subject); - log.info("send to: {}", to); - log.info("send detail: \n{}", html); - return; - } - MimeMessage message = mailSender.createMimeMessage(); - MimeMessageHelper helper = new MimeMessageHelper(message, true); - helper.setFrom(sendUser); - helper.setTo(to); - helper.setSubject(subject); - helper.setText(html, true); - mailSender.send(message); - } - - /** - * 邮箱验证 - * - * @param emailQueue 邮件队列 - * @return 发送目标 - * @throws Exception 服务异常 - */ - private String sendEmail4EmailVerify(EmailQueue emailQueue) throws Exception { - User user = userService.get(emailQueue.getBizId()); //.withData(); - - String key = Text.randomString(64); - redisUserEmailVerify.set(key, user.getId(), 600L); - - Map model = new HashMap<>(); - - model.put("user", user); - model.put("url", "https://www.imyeyu.net/user/space/%s?action=EMAIL_VERIFY&key=%s".formatted(user.getId(), key)); - - Template template = freeMarkerConfigurer.getConfiguration().getTemplate("EmailVerify.ftl"); - String html = FreeMarkerTemplateUtils.processTemplateIntoString(template, model); - - sendEmail(user.getEmail(), "Hey! 请继续完成 夜雨博客 的邮箱验证!", html); - return user.getEmail(); - } - - /** - * 回复提醒邮件 - * - * @param emailQueue 邮件队列 - * @return 发送目标 - * @throws Exception 服务异常 - */ - private String sendEmail4ReplyRemind(EmailQueue emailQueue) throws Exception { - User user = userService.get(emailQueue.getBizId()); // TODO .withData(); - List reminds = commentRemindQueueService.listByUserId(emailQueue.getBizId()); - if (reminds.isEmpty()) { - throw new EmailException(TimiCode.RESULT_NULL, "没有需要提醒的回复", user.getEmail()); - } - // 回查数据 - for (CommentRemindQueue remind : reminds) { - // 回复 - CommentReply reply = commentReplyService.get(remind.getReplyId()); - remind.setReply(reply); - if (TimiJava.isNotEmpty(remind.getReply().getSenderId())) { - // 发送者 - remind.getReply().setSender(userService.view(remind.getReply().getSenderId())); - } - } - - Map model = new HashMap<>(); - model.put("user", user); - model.put("reminds", reminds); - - Template template = freeMarkerConfigurer.getConfiguration().getTemplate("ReplyRemind.ftl"); - String html = FreeMarkerTemplateUtils.processTemplateIntoString(template, model); - - sendEmail(user.getEmail(), "Hey! 你在 夜雨博客 的评论收到新回复", html); - // 移除回复提醒队列 - commentRemindQueueService.destroyByUserId(emailQueue.getBizId()); - return user.getEmail(); - } - - /** - * 重置密码验证 - * - * @param emailQueue 邮件队列 - * @return 发送目标 - * @throws Exception 服务异常 - */ - private String sendEmail4ResetPassword(EmailQueue emailQueue) throws Exception { - User user = userService.get(emailQueue.getBizId()); // TODO .withData(); - - String key = Text.randomString(64); - redisUserResetPWVerify.set(key, user.getId(), 600L); - - Map model = new HashMap<>(); - model.put("user", user); - model.put("url", "https://www.imyeyu.net/user/pw-reset?key=" + key); - - Template template = freeMarkerConfigurer.getConfiguration().getTemplate("ResetPassword.ftl"); - String html = FreeMarkerTemplateUtils.processTemplateIntoString(template, model); - sendEmail(user.getEmail(), "Hey! 请继续完成 夜雨博客 的重置密码!", html); - return user.getEmail(); - } -} diff --git a/src/main/java/com/imyeyu/api/modules/common/task/MultilingualTranslateTask.java b/src/main/java/com/imyeyu/api/modules/common/task/MultilingualTranslateTask.java deleted file mode 100644 index 8c49fd0..0000000 --- a/src/main/java/com/imyeyu/api/modules/common/task/MultilingualTranslateTask.java +++ /dev/null @@ -1,165 +0,0 @@ -package com.imyeyu.api.modules.common.task; - -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.imyeyu.api.config.dbsource.TimiServerDBConfig; -import com.imyeyu.api.modules.common.bean.SettingKey; -import com.imyeyu.api.modules.common.entity.Multilingual; -import com.imyeyu.api.modules.common.service.MultilingualService; -import com.imyeyu.api.modules.common.service.SettingService; -import com.imyeyu.java.TimiJava; -import com.imyeyu.java.bean.Language; -import com.imyeyu.java.bean.timi.TimiCode; -import com.imyeyu.java.bean.timi.TimiException; -import com.imyeyu.java.ref.Ref; -import com.imyeyu.network.ArgMap; -import com.imyeyu.utils.Digest; -import com.imyeyu.utils.Time; -import lombok.AllArgsConstructor; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.apache.hc.client5.http.fluent.Request; -import org.springframework.context.annotation.Configuration; -import org.springframework.scheduling.annotation.EnableScheduling; -import org.springframework.scheduling.annotation.Scheduled; -import org.springframework.transaction.annotation.Transactional; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; - -/** - * @author 夜雨 - * @since 2025-05-31 11:06 - */ -@Slf4j -@Configuration -@EnableScheduling -@RequiredArgsConstructor -public class MultilingualTranslateTask { - - /** - * - * - * @author 夜雨 - * @since 2025-05-31 11:08 - */ - @AllArgsConstructor - public enum BaiduLanguage { - - ZH(Language.Enum.zh_CN), - EN(Language.Enum.en_US), - JP(Language.Enum.ja_JP), - KOR(Language.Enum.ko_KR), - RU(Language.Enum.ru_RU), - DE(Language.Enum.de_DE), - CHT(Language.Enum.zh_TW); - - /** 标准映射 */ - final Language.Enum language; - - /** - * 获取排除语言列表 - * - * @param baiduLanguage 排除语言 - * @return 语言列表 - */ - static List valuesWithout(BaiduLanguage... baiduLanguage) { - Set outList = Set.of(baiduLanguage); - - List result = new ArrayList<>(); - BaiduLanguage[] values = values(); - for (int i = 0; i < values.length; i++) { - if (!outList.contains(values[i])) { - result.add(values[i]); - } - } - return result; - } - } - - private final ObjectMapper jackson; - private final SettingService settingService; - private final MultilingualService service; - - @Scheduled(fixedRate = Time.M * 10) - @Transactional(TimiServerDBConfig.ROLLBACKER) - public void handle() { - try { - List list = service.listByNotTranslate(); - - Map cnMap = new HashMap<>(); - for (int i = 0; i < list.size(); i++) { - StringBuilder sb = new StringBuilder(); - for (int j = 0; j < Math.min(list.size() - i, 20); j++, i++) { - Multilingual multilingual = list.get(i); - sb.append(multilingual.getZhCN()).append("\r\n"); - cnMap.put(multilingual.getZhCN(), multilingual); - } - i--; - List languageList = BaiduLanguage.valuesWithout(BaiduLanguage.ZH); - for (int j = 0; j < languageList.size(); j++) { - Map result = doTranslate(sb.toString(), languageList.get(j)); - for (Map.Entry item : result.entrySet()) { - Multilingual multilingual = cnMap.get(item.getKey()); - Language.Enum lang = languageList.get(j).language; - String value = multilingual.getValue(lang); - if (TimiJava.isEmpty(value)) { - Ref.setFieldValue(multilingual, lang.toString().replace("_", ""), item.getValue()); - } - service.update(multilingual); - } - } - wait(1000); - } - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - - /** - * 文本翻译 - * - * @param text 原文本 - * @param to 目标语言 - * @return Map<原数据,翻译结果> - * @throws Exception 翻译异常 - */ - private synchronized Map doTranslate(String text, BaiduLanguage to) throws Exception { - String random = String.valueOf(Time.now()); - - String appId = settingService.getAsString(SettingKey.MULTILINGUAL_TRANSLATE_APP_ID); - String key = settingService.getAsString(SettingKey.MULTILINGUAL_TRANSLATE_KEY); - - ArgMap args = new ArgMap<>(); - args.put("q", text); - args.put("from", BaiduLanguage.ZH.toString().toLowerCase()); - args.put("to", to.toString().toLowerCase()); - args.put("appid", appId); - args.put("salt", random); - args.put("sign", Digest.md5(appId + text + random + key)); - - String response = Request.post(settingService.getAsString(SettingKey.MULTILINGUAL_TRANSLATE_API)) - .bodyForm(args.toNameValuePair()) - .execute() - .returnContent() - .asString(); - JsonNode jo = jackson.readTree(response); - if (jo.has("error_code")) { - System.err.println(jo); - throw new TimiException(TimiCode.ERROR, jo.get("error_msg").asText()); - } - JsonNode ja = jo.get("trans_result"); - - Map result = new HashMap<>(); - for (int i = 0; i < ja.size(); i++) { - JsonNode resultJO = ja.get(i); - result.put(resultJO.get("src").asText(), resultJO.get("dst").asText()); - } - wait(200); - return result; - } -} diff --git a/src/main/java/com/imyeyu/api/modules/common/vo/CaptchaRequest.java b/src/main/java/com/imyeyu/api/modules/common/vo/CaptchaRequest.java deleted file mode 100644 index e2280ea..0000000 --- a/src/main/java/com/imyeyu/api/modules/common/vo/CaptchaRequest.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.imyeyu.api.modules.common.vo; - -import lombok.Data; -import com.imyeyu.api.bean.CaptchaFrom; - -/** - * 验证码请求,不要执行参数校验,否则会被全局拦截器统一返回 JSON 错误,但这是验证码,需要返回错误图片 - * - * @author 夜雨 - * @since 2023-07-15 18:14 - */ -@Data -public class CaptchaRequest { - - private int width; - - private int height; - - private CaptchaFrom from; -} diff --git a/src/main/java/com/imyeyu/api/modules/common/vo/FeedbackRequest.java b/src/main/java/com/imyeyu/api/modules/common/vo/FeedbackRequest.java deleted file mode 100644 index 4e59192..0000000 --- a/src/main/java/com/imyeyu/api/modules/common/vo/FeedbackRequest.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.imyeyu.api.modules.common.vo; - -import jakarta.validation.constraints.Email; -import jakarta.validation.constraints.NotBlank; -import lombok.Data; - -/** - * @author 夜雨 - * @since 2023-05-26 13:21 - */ -@Data -public class FeedbackRequest { - - @NotBlank - private String from; - - @Email - private String email; - - @NotBlank - private String data; -} diff --git a/src/main/java/com/imyeyu/api/modules/common/vo/attach/BizDeleteReq.java b/src/main/java/com/imyeyu/api/modules/common/vo/attach/BizDeleteReq.java new file mode 100644 index 0000000..7ebb47a --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/common/vo/attach/BizDeleteReq.java @@ -0,0 +1,20 @@ +package com.imyeyu.api.modules.common.vo.attach; + +import com.imyeyu.api.modules.common.entity.Attachment; +import lombok.Data; + +/** + * 业务附件差分更新请求 + * + * @author 夜雨 + * @since 2026-03-11 00:00 + */ +@Data +public class BizDeleteReq { + + /** 业务类型 */ + private Attachment.BizType bizType; + + /** 业务 ID */ + private String bizId; +} diff --git a/src/main/java/com/imyeyu/api/modules/common/vo/attach/BizUpdateReq.java b/src/main/java/com/imyeyu/api/modules/common/vo/attach/BizUpdateReq.java new file mode 100644 index 0000000..6b90f1f --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/common/vo/attach/BizUpdateReq.java @@ -0,0 +1,26 @@ +package com.imyeyu.api.modules.common.vo.attach; + +import com.imyeyu.api.modules.common.entity.Attachment; +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; + +/** + * 业务附件差分更新请求 + * + * @author 夜雨 + * @since 2026-03-11 00:00 + */ +@Data +public class BizUpdateReq { + + /** 业务类型 */ + private Attachment.BizType bizType; + + /** 业务 ID */ + private String bizId; + + /** 附件项列表 */ + private List items = new ArrayList<>(); +} diff --git a/src/main/java/com/imyeyu/api/modules/common/vo/attach/TempFileResp.java b/src/main/java/com/imyeyu/api/modules/common/vo/attach/TempFileResp.java new file mode 100644 index 0000000..5516883 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/common/vo/attach/TempFileResp.java @@ -0,0 +1,15 @@ +package com.imyeyu.api.modules.common.vo.attach; + +import lombok.Data; + +/** + * @author 夜雨 + * @since 2025-09-27 01:37 + */ +@Data +public class TempFileResp { + + private String id; + + private Long expireAt; +} diff --git a/src/main/java/com/imyeyu/api/modules/common/vo/attach/TempFileSaveReq.java b/src/main/java/com/imyeyu/api/modules/common/vo/attach/TempFileSaveReq.java new file mode 100644 index 0000000..eedf3a9 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/common/vo/attach/TempFileSaveReq.java @@ -0,0 +1,18 @@ +package com.imyeyu.api.modules.common.vo.attach; + +import com.imyeyu.api.modules.common.entity.Attachment; +import lombok.Data; + +/** + * @author 夜雨 + * @since 2026-01-13 14:55 + */ +@Data +public class TempFileSaveReq { + + private Attachment.BizType bizType; + + private String bizId; + + private String[] tempFileIds; +} diff --git a/src/main/java/com/imyeyu/api/modules/common/vo/comment/CommentCreateReq.java b/src/main/java/com/imyeyu/api/modules/common/vo/comment/CommentCreateReq.java new file mode 100644 index 0000000..1c37455 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/common/vo/comment/CommentCreateReq.java @@ -0,0 +1,28 @@ +package com.imyeyu.api.modules.common.vo.comment; + +import lombok.Data; + +/** + * 创建主评论请求 + * + * @author 夜雨 + * @since 2026-05-27 18:40 + */ +@Data +public class CommentCreateReq { + + /** 业务类型 */ + protected String bizType; + + /** 业务 ID */ + protected String bizId; + + /** 评论用户 ID,登录用户评论时使用 */ + protected String userId; + + /** 评论用户昵称,游客评论时使用 */ + protected String userNick; + + /** 评论内容 */ + protected String content; +} diff --git a/src/main/java/com/imyeyu/api/modules/common/vo/comment/CommentLayerQueryReq.java b/src/main/java/com/imyeyu/api/modules/common/vo/comment/CommentLayerQueryReq.java new file mode 100644 index 0000000..9de3980 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/common/vo/comment/CommentLayerQueryReq.java @@ -0,0 +1,28 @@ +package com.imyeyu.api.modules.common.vo.comment; + +import lombok.Data; + +/** + * 评论层查询请求 + * + * @author 夜雨 + * @since 2026-05-27 17:20 + */ +@Data +public class CommentLayerQueryReq { + + /** 业务类型 */ + protected String bizType; + + /** 业务 ID */ + protected String bizId; + + /** 父评论 ID,查询主评论时为 null */ + protected String parentId; + + /** 起始偏移量 */ + protected Integer offset; + + /** 查询条数 */ + protected Integer limit; +} diff --git a/src/main/java/com/imyeyu/api/modules/common/vo/comment/CommentLayerResp.java b/src/main/java/com/imyeyu/api/modules/common/vo/comment/CommentLayerResp.java new file mode 100644 index 0000000..4540a8d --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/common/vo/comment/CommentLayerResp.java @@ -0,0 +1,26 @@ +package com.imyeyu.api.modules.common.vo.comment; + +import com.fasterxml.jackson.annotation.JsonView; +import com.imyeyu.api.modules.common.entity.Comment; +import com.imyeyu.spring.util.ResponseView; +import lombok.Data; + +import java.util.List; + +/** + * 评论层响应 + * + * @author 夜雨 + * @since 2026-05-27 17:20 + */ +@Data +public class CommentLayerResp { + + /** 当前层总数 */ + @JsonView(ResponseView.Public.class) + private Long total; + + /** 当前层评论列表 */ + @JsonView(ResponseView.Public.class) + private List list; +} diff --git a/src/main/java/com/imyeyu/api/modules/common/vo/comment/CommentNodeResp.java b/src/main/java/com/imyeyu/api/modules/common/vo/comment/CommentNodeResp.java new file mode 100644 index 0000000..56302f6 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/common/vo/comment/CommentNodeResp.java @@ -0,0 +1,30 @@ +package com.imyeyu.api.modules.common.vo.comment; + +import com.fasterxml.jackson.annotation.JsonView; +import com.imyeyu.api.modules.common.entity.Comment; +import com.imyeyu.spring.util.ResponseView; +import lombok.Data; + +import java.util.List; + +/** + * 评论节点响应 + * + * @author 夜雨 + * @since 2026-05-27 17:20 + */ +@Data +public class CommentNodeResp { + + /** 当前评论 */ + @JsonView(ResponseView.Public.class) + private Comment comment; + + /** 直属回复总数 */ + @JsonView(ResponseView.Public.class) + private Long replyTotal; + + /** 直属回复列表 */ + @JsonView(ResponseView.Public.class) + private List replyList; +} diff --git a/src/main/java/com/imyeyu/api/modules/common/vo/comment/CommentReplyReq.java b/src/main/java/com/imyeyu/api/modules/common/vo/comment/CommentReplyReq.java new file mode 100644 index 0000000..2d73c4d --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/common/vo/comment/CommentReplyReq.java @@ -0,0 +1,25 @@ +package com.imyeyu.api.modules.common.vo.comment; + +import lombok.Data; + +/** + * 回复评论请求 + * + * @author 夜雨 + * @since 2026-05-27 18:40 + */ +@Data +public class CommentReplyReq { + + /** 父评论 ID */ + protected String parentId; + + /** 评论用户 ID,登录用户评论时使用 */ + protected String userId; + + /** 评论用户昵称,游客评论时使用 */ + protected String userNick; + + /** 回复内容 */ + protected String content; +} diff --git a/src/main/java/com/imyeyu/api/modules/common/vo/comment/CommentThreadResp.java b/src/main/java/com/imyeyu/api/modules/common/vo/comment/CommentThreadResp.java new file mode 100644 index 0000000..4a82a7d --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/common/vo/comment/CommentThreadResp.java @@ -0,0 +1,30 @@ +package com.imyeyu.api.modules.common.vo.comment; + +import com.fasterxml.jackson.annotation.JsonView; +import com.imyeyu.api.modules.common.entity.Comment; +import com.imyeyu.spring.util.ResponseView; +import lombok.Data; + +import java.util.List; + +/** + * 评论线程响应 + * + * @author 夜雨 + * @since 2026-05-27 17:20 + */ +@Data +public class CommentThreadResp { + + /** 主评论 */ + @JsonView(ResponseView.Public.class) + private Comment root; + + /** 子评论总数 */ + @JsonView(ResponseView.Public.class) + private Long replyTotal; + + /** 子评论列表 */ + @JsonView(ResponseView.Public.class) + private List replyList; +} diff --git a/src/main/java/com/imyeyu/api/modules/common/vo/comment/FlatCommentListItemResp.java b/src/main/java/com/imyeyu/api/modules/common/vo/comment/FlatCommentListItemResp.java new file mode 100644 index 0000000..566bf50 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/common/vo/comment/FlatCommentListItemResp.java @@ -0,0 +1,30 @@ +package com.imyeyu.api.modules.common.vo.comment; + +import com.fasterxml.jackson.annotation.JsonView; +import com.imyeyu.api.modules.common.entity.Comment; +import com.imyeyu.spring.util.ResponseView; +import lombok.Data; + +import java.util.List; + +/** + * 扁平评论列表项 + * + * @author 夜雨 + * @since 2026-05-27 17:20 + */ +@Data +public class FlatCommentListItemResp { + + /** 主评论 */ + @JsonView(ResponseView.Public.class) + private Comment root; + + /** 回复预览总数 */ + @JsonView(ResponseView.Public.class) + private Long replyPreviewTotal; + + /** 回复预览列表 */ + @JsonView(ResponseView.Public.class) + private List replyPreviewList; +} diff --git a/src/main/java/com/imyeyu/api/modules/common/vo/comment/FlatCommentListResp.java b/src/main/java/com/imyeyu/api/modules/common/vo/comment/FlatCommentListResp.java new file mode 100644 index 0000000..c4109a5 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/common/vo/comment/FlatCommentListResp.java @@ -0,0 +1,25 @@ +package com.imyeyu.api.modules.common.vo.comment; + +import com.fasterxml.jackson.annotation.JsonView; +import com.imyeyu.spring.util.ResponseView; +import lombok.Data; + +import java.util.List; + +/** + * 扁平评论列表响应 + * + * @author 夜雨 + * @since 2026-05-27 17:20 + */ +@Data +public class FlatCommentListResp { + + /** 主评论总数 */ + @JsonView(ResponseView.Public.class) + private Long total; + + /** 主评论列表 */ + @JsonView(ResponseView.Public.class) + private List list; +} diff --git a/src/main/java/com/imyeyu/api/modules/common/vo/comment/NestedCommentListResp.java b/src/main/java/com/imyeyu/api/modules/common/vo/comment/NestedCommentListResp.java new file mode 100644 index 0000000..302f60a --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/common/vo/comment/NestedCommentListResp.java @@ -0,0 +1,25 @@ +package com.imyeyu.api.modules.common.vo.comment; + +import com.fasterxml.jackson.annotation.JsonView; +import com.imyeyu.spring.util.ResponseView; +import lombok.Data; + +import java.util.List; + +/** + * 层级评论列表响应 + * + * @author 夜雨 + * @since 2026-05-27 17:20 + */ +@Data +public class NestedCommentListResp { + + /** 主评论总数 */ + @JsonView(ResponseView.Public.class) + private Long total; + + /** 主评论列表 */ + @JsonView(ResponseView.Public.class) + private List list; +} diff --git a/src/main/java/com/imyeyu/api/modules/common/vo/user/LoginResponse.java b/src/main/java/com/imyeyu/api/modules/common/vo/user/LoginResponse.java deleted file mode 100644 index a0b6135..0000000 --- a/src/main/java/com/imyeyu/api/modules/common/vo/user/LoginResponse.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.imyeyu.api.modules.common.vo.user; - -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; - -/** - * 登录返回对象 - * - * @author 夜雨 - * @since 2023-05-05 17:57 - */ -@Data -@NoArgsConstructor -@AllArgsConstructor -public class LoginResponse { - - /** 登录用户 ID */ - private Long id; - - /** 令牌 */ - private String token; - - /** 过期时间 */ - private Long expireAt; -} diff --git a/src/main/java/com/imyeyu/api/modules/common/vo/user/RegisterRequest.java b/src/main/java/com/imyeyu/api/modules/common/vo/user/RegisterRequest.java deleted file mode 100644 index 6fc1cad..0000000 --- a/src/main/java/com/imyeyu/api/modules/common/vo/user/RegisterRequest.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.imyeyu.api.modules.common.vo.user; - -import jakarta.validation.constraints.Pattern; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; -import com.imyeyu.api.modules.common.validation.UserName; -import com.imyeyu.api.modules.common.validation.UserPassword; - -/** - * 注册请求对象 - * - * @author 夜雨 - * @since 2023-05-05 18:08 - */ -@Data -@NoArgsConstructor -@AllArgsConstructor -public class RegisterRequest { - - /** 用户名 */ - @UserName - private String name; - - /** 明文密码 */ - @UserPassword - private String password; - - /** 邮箱 */ - @Pattern(regexp = "^\\w+([-+.]\\w+)*@\\w+([-.]\\w+)*\\.\\w+([-.]\\w+)*$", message = "user.email.not_match_regex") - private String email; -} diff --git a/src/main/java/com/imyeyu/api/modules/common/vo/user/UserProfileView.java b/src/main/java/com/imyeyu/api/modules/common/vo/user/UserProfileView.java deleted file mode 100644 index 9c236ae..0000000 --- a/src/main/java/com/imyeyu/api/modules/common/vo/user/UserProfileView.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.imyeyu.api.modules.common.vo.user; - -import com.imyeyu.api.modules.common.entity.Attachment; -import com.imyeyu.api.modules.common.entity.UserProfile; -import com.imyeyu.spring.annotation.table.Transient; -import lombok.Data; -import lombok.EqualsAndHashCode; - -import java.util.List; - -/** - * @author 夜雨 - * @since 2024-02-21 13:00 - */ -@Data -@EqualsAndHashCode(callSuper = true) -public class UserProfileView extends UserProfile { - - @Transient - private List attachmentList; -} diff --git a/src/main/java/com/imyeyu/api/modules/common/vo/user/UserRequest.java b/src/main/java/com/imyeyu/api/modules/common/vo/user/UserRequest.java deleted file mode 100644 index f766570..0000000 --- a/src/main/java/com/imyeyu/api/modules/common/vo/user/UserRequest.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.imyeyu.api.modules.common.vo.user; - -import lombok.Data; -import lombok.EqualsAndHashCode; -import com.imyeyu.api.modules.common.entity.User; -import com.imyeyu.api.modules.common.entity.UserProfile; -import org.springframework.web.multipart.MultipartFile; - -/** - * @author 夜雨 - * @since 2024-02-21 11:55 - */ -@Data -@EqualsAndHashCode(callSuper = true) -public class UserRequest extends User { - - private UserProfileRequest profile; - - /** - * - * - * @author 夜雨 - * @since 2024-05-17 00:06 - */ - @Data - @EqualsAndHashCode(callSuper = true) - public static final class UserProfileRequest extends UserProfile { - - private MultipartFile wrapper; - - private MultipartFile avatar; - } -} diff --git a/src/main/java/com/imyeyu/api/modules/common/vo/user/UserView.java b/src/main/java/com/imyeyu/api/modules/common/vo/user/UserView.java deleted file mode 100644 index 8862316..0000000 --- a/src/main/java/com/imyeyu/api/modules/common/vo/user/UserView.java +++ /dev/null @@ -1,56 +0,0 @@ -package com.imyeyu.api.modules.common.vo.user; - -import com.imyeyu.java.bean.timi.TimiCode; -import com.imyeyu.java.bean.timi.TimiException; -import com.imyeyu.java.ref.Ref; -import com.imyeyu.api.TimiServerAPI; -import com.imyeyu.api.modules.common.entity.User; -import com.imyeyu.api.modules.common.entity.UserPrivacy; -import com.imyeyu.api.modules.common.service.UserPrivacyService; -import com.imyeyu.api.modules.common.service.UserService; -import com.imyeyu.spring.annotation.table.Transient; -import lombok.Data; -import lombok.EqualsAndHashCode; - -import java.util.List; - -/** - * 用户视图 - * - * @author 夜雨 - * @since 2024-03-05 20:00 - */ -@Data -@EqualsAndHashCode(callSuper = true) -public class UserView extends User { - - /** 用户信息视图 */ - @Transient - private UserProfileView profile; - - public UserView doFilter() { - try { - UserService userService = TimiServerAPI.applicationContext.getBean(UserService.class); - UserPrivacyService privacyService = TimiServerAPI.applicationContext.getBean(UserPrivacyService.class); - - password = null; - updatedAt = null; - emailVerifyAt = null; - - profile.setUpdatedAt(null); - profile.setLastLoginIP(null); - User loginUser = userService.getLoginUser(); - if (loginUser == null || !id.equals(loginUser.getId())) { - // 未登录,并且获取的用户资料不是自己的,执行隐私控制过滤 - UserPrivacy privacy = privacyService.get(id); - List filters = privacy.listFilterFields(); - for (int i = 0; i < filters.size(); i++) { - Ref.setFieldValue(profile, filters.get(i), null); - } - } - return this; - } catch (Exception e) { - throw new TimiException(TimiCode.ERROR, "TODO filter user fail", e); - } - } -} diff --git a/src/main/java/com/imyeyu/api/modules/forevermc/bean/ServerStatus.java b/src/main/java/com/imyeyu/api/modules/forevermc/bean/ServerStatus.java deleted file mode 100644 index f5107ed..0000000 --- a/src/main/java/com/imyeyu/api/modules/forevermc/bean/ServerStatus.java +++ /dev/null @@ -1,154 +0,0 @@ -package com.imyeyu.api.modules.forevermc.bean; - -import com.imyeyu.api.modules.forevermc.entity.Server; -import jakarta.validation.Valid; -import jakarta.validation.constraints.Min; -import jakarta.validation.constraints.NotBlank; -import lombok.Data; -import lombok.EqualsAndHashCode; - -import java.util.ArrayList; -import java.util.List; - -/** - * 服务器状态报告请求 - * - * @author 夜雨 - * @since 2021-12-02 19:56 - */ -@Data -public class ServerStatus implements Cloneable { - - @Valid - @NotBlank - private String id; - - /** true 为维护中 */ - private boolean isDebugging; - - /** TPS */ - @Min(0) - private double tps; - - /** 报告时间 */ - private long reportAt; - - /** 静态基本信息 */ - private BaseInfo baseInfo; - - /** 主世界状态 */ - private List worldStatusList = new ArrayList<>(); - - /** JVM 状态 */ - private JVM jvm = new JVM(); - - /** 在线列表 */ - private List onlineList = new ArrayList<>(); - - @Override - protected ServerStatus clone() throws CloneNotSupportedException { - ServerStatus clone = (ServerStatus) super.clone(); - clone.id = id; - clone.isDebugging = isDebugging; - clone.tps = tps; - clone.reportAt = reportAt; - clone.baseInfo = baseInfo; - return clone; - } - - /** - * 静态基本信息 - * - * @author 夜雨 - * @since 2025-01-19 10:25 - */ - @Data - @EqualsAndHashCode(callSuper = true) - public static class BaseInfo extends Server { - - /** 核心 */ - private String core; - - /** 启动时间 */ - private Long bootAt; - - /** 最大在线数量 */ - private Integer maxOnline; - - /** 图标 Base64 */ - private String icon; - - /** 游戏版本 */ - private String version; - } - - /** - * JVM 状态 - * - * @author 夜雨 - * @since 2022-11-11 14:52 - */ - @Data - public static class JVM { - - /** JVM 名称 */ - private String name; - - /** CPU 已使用 */ - private double cpuUsed; - - /** 内存状态 */ - private Memory memory; - - /** - * 内存状态 - * - * @author 夜雨 - * @since 2022-11-15 09:54 - */ - @Data - public static class Memory { - - /** 已使用 */ - private long used; - - /** 已申请 */ - private long committed; - - /** 最大内存 */ - private long max; - } - } - - - /** - * 世界时间 - * - * @author 夜雨 - * @since 2024-10-29 23:59 - */ - @Data - public static class WorldStatus { - - /** 世界名称 */ - private String name; - - /** 总时刻 */ - private long ticks; - - /** 总天数 */ - private int day; - - /** 时 */ - private int hour; - - /** 分 */ - private int minute; - - /** true 为正在下雨 */ - private boolean isRaining; - - /** true 为正在雷雨 */ - private boolean isThundering; - } -} diff --git a/src/main/java/com/imyeyu/api/modules/forevermc/controller/ClientPackController.java b/src/main/java/com/imyeyu/api/modules/forevermc/controller/ClientPackController.java deleted file mode 100644 index ef0b7b7..0000000 --- a/src/main/java/com/imyeyu/api/modules/forevermc/controller/ClientPackController.java +++ /dev/null @@ -1,42 +0,0 @@ -package com.imyeyu.api.modules.forevermc.controller; - -import com.imyeyu.api.modules.forevermc.entity.ClientPack; -import com.imyeyu.api.modules.forevermc.entity.ClientPackSrc; -import com.imyeyu.api.modules.forevermc.service.ClientPackService; -import com.imyeyu.spring.annotation.AOPLog; -import com.imyeyu.spring.annotation.RequestRateLimit; -import com.imyeyu.spring.bean.Page; -import com.imyeyu.spring.bean.PageResult; -import lombok.RequiredArgsConstructor; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -import java.util.List; - -/** - * @author 夜雨 - * @since 2025-07-24 21:43 - */ -@RestController -@RequiredArgsConstructor -@RequestMapping("/fmc/client") -public class ClientPackController { - - private final ClientPackService service; - - @AOPLog - @RequestRateLimit - @RequestMapping("/list") - public PageResult listClientPack(@RequestBody Page page) { - return service.page(page); - } - - @AOPLog - @RequestRateLimit - @RequestMapping("/src/{clientId}") - public List listClientPackSrc(@PathVariable("clientId") long clientId) { - return service.listClientPackSrc(clientId); - } -} diff --git a/src/main/java/com/imyeyu/api/modules/forevermc/controller/ServerController.java b/src/main/java/com/imyeyu/api/modules/forevermc/controller/ServerController.java deleted file mode 100644 index 1783be5..0000000 --- a/src/main/java/com/imyeyu/api/modules/forevermc/controller/ServerController.java +++ /dev/null @@ -1,133 +0,0 @@ -package com.imyeyu.api.modules.forevermc.controller; - -import com.imyeyu.api.modules.forevermc.bean.ServerStatus; -import com.imyeyu.api.modules.forevermc.entity.ClientPack; -import com.imyeyu.api.modules.forevermc.service.ClientPackService; -import com.imyeyu.api.modules.forevermc.service.ServerService; -import com.imyeyu.api.modules.minecraft.annotation.RequiredFMCServerToken; -import com.imyeyu.api.modules.minecraft.vo.server.ReportRequest; -import com.imyeyu.java.TimiJava; -import com.imyeyu.java.bean.timi.TimiCode; -import com.imyeyu.java.bean.timi.TimiException; -import com.imyeyu.spring.annotation.AOPLog; -import com.imyeyu.spring.annotation.IgnoreGlobalReturn; -import com.imyeyu.spring.annotation.RequestRateLimit; -import com.imyeyu.spring.bean.Page; -import com.imyeyu.spring.bean.PageResult; -import com.imyeyu.utils.Decoder; -import jakarta.servlet.http.HttpServletResponse; -import jakarta.validation.constraints.NotNull; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.apache.tika.Tika; -import org.springframework.beans.BeanUtils; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.Comparator; -import java.util.List; - -/** - * 服务器接口 - * - * @author 夜雨 - * @since 2024-08-06 17:20 - */ -@Slf4j -@Validated -@RestController -@RequiredArgsConstructor -@RequestMapping("/fmc/server") -public class ServerController { - - private final ServerService service; - private final ClientPackService clientService; - - /** - * 服务器状态报告,由 FMCServer 服务端插件发送 - * - * @param request 服务器状态 - */ - @RequiredFMCServerToken - @PostMapping("/report") - public void report(@NotNull @RequestBody ReportRequest request) { - service.report(request); - } - @AOPLog - @RequestRateLimit - @IgnoreGlobalReturn - @GetMapping("/icon/{serverId}") - public void icon(@PathVariable String serverId, HttpServletResponse resp) { - try { - resp.setHeader("Pragma", "no-cache"); - resp.setHeader("Cache-Control", "no-cache"); - resp.setDateHeader("Expires", -1); - - byte[] bytes = Decoder.base64(service.getIcon(serverId)); - String mimeType = new Tika().detect(bytes); - if (TimiJava.isNotEmpty(mimeType)) { - resp.setContentType(mimeType); - } - resp.getOutputStream().write(bytes); - resp.getOutputStream().flush(); - resp.getOutputStream().close(); - } catch (TimiException e) { - if (e.getCode() == TimiCode.RESULT_NULL) { - resp.setStatus(HttpServletResponse.SC_NOT_FOUND); - resp.setCharacterEncoding(StandardCharsets.UTF_8.toString()); - } else { - resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); - resp.setCharacterEncoding(StandardCharsets.UTF_8.toString()); - } - } catch (Exception e) { - log.error("read attachment error", e); - resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); - resp.setCharacterEncoding(StandardCharsets.UTF_8.toString()); - } - } - - @AOPLog - @RequestRateLimit - @GetMapping("/client/{serverId}") - public List listClientPack(@PathVariable("serverId") String serverId) { - return service.listClientPack(serverId); - } - - /** - * 获取服务器状态列表 - * - * @return 服务器状态列表 - */ - @RequestRateLimit - @GetMapping("/list") - public List listServer() { - List result = new ArrayList<>(); - List sourceList = service.listAll(); - for (int i = 0; i < sourceList.size(); i++) { - ServerStatus targetStatus = new ServerStatus(); - BeanUtils.copyProperties(sourceList.get(i), targetStatus); - if (targetStatus.getBaseInfo() != null) { - ServerStatus.BaseInfo targetBaseInfo = new ServerStatus.BaseInfo(); - BeanUtils.copyProperties(targetStatus.getBaseInfo(), targetBaseInfo); - targetBaseInfo.setIcon(null); - targetStatus.setBaseInfo(targetBaseInfo); - } - result.add(targetStatus); - } - result.sort(Comparator.comparingInt(o -> o.getBaseInfo().getOrder())); - return result; - } - - @RequestRateLimit - @PostMapping("/client/list") - public PageResult listClient(@RequestBody Page page) { - return clientService.page(page); - } -} diff --git a/src/main/java/com/imyeyu/api/modules/forevermc/entity/ClientPack.java b/src/main/java/com/imyeyu/api/modules/forevermc/entity/ClientPack.java deleted file mode 100644 index 755cc8e..0000000 --- a/src/main/java/com/imyeyu/api/modules/forevermc/entity/ClientPack.java +++ /dev/null @@ -1,45 +0,0 @@ -package com.imyeyu.api.modules.forevermc.entity; - -import com.imyeyu.spring.annotation.table.Column; -import com.imyeyu.spring.entity.Entity; -import lombok.Data; -import lombok.EqualsAndHashCode; - -/** - * 服务器客户端 - * - * @author 夜雨 - * @since 2025-01-27 20:40 - */ -@Data -@EqualsAndHashCode(callSuper = true) -public class ClientPack extends Entity { - - /** 名称 */ - private String name; - - /** 说明 */ - private String description; - - /** 游戏 ID */ - private String gameId; - - /** 游戏版本 */ - private String gameVersion; - - /** 客户端版本 */ - private String version; - - /** 需要运行时版本 */ - private String runtimeVersion; - - /** 文件大小 */ - private Long size; - - /** true 为支持 FMC 授权验证 */ - @Column("can_fmc_auth") - private Boolean canFMCAuth; - - /** true 为已过时 */ - private boolean isDeprecated; -} diff --git a/src/main/java/com/imyeyu/api/modules/forevermc/entity/ClientPackSrc.java b/src/main/java/com/imyeyu/api/modules/forevermc/entity/ClientPackSrc.java deleted file mode 100644 index 92193ab..0000000 --- a/src/main/java/com/imyeyu/api/modules/forevermc/entity/ClientPackSrc.java +++ /dev/null @@ -1,49 +0,0 @@ -package com.imyeyu.api.modules.forevermc.entity; - -import com.imyeyu.spring.entity.Entity; -import lombok.Data; -import lombok.EqualsAndHashCode; - -/** - * 服务器客户端下载源 - * - * @author 夜雨 - * @since 2025-01-27 20:41 - */ -@Data -@EqualsAndHashCode(callSuper = true) -public class ClientPackSrc extends Entity { - - /** - * 下载源类型 - * - * @author 夜雨 - * @since 2025-01-27 20:43 - */ - public enum BizType { - - /** Timi MongoDB */ - TIMI_MONGO, - - /** Timi 对象储存(未实现) */ - TIMI_COS, - - /** 第三方 URL */ - URL - } - - /** 客户端 ID,关联 {@link ClientPack#getId()} */ - private Long clientId; - - /** 下载源类型 */ - private BizType bizType; - - /** 下载源类型值 */ - private String bizValue; - - /** 排序 */ - private int order; - - /** true 为默认 */ - private boolean isDefault; -} diff --git a/src/main/java/com/imyeyu/api/modules/forevermc/entity/Server.java b/src/main/java/com/imyeyu/api/modules/forevermc/entity/Server.java deleted file mode 100644 index 47e6542..0000000 --- a/src/main/java/com/imyeyu/api/modules/forevermc/entity/Server.java +++ /dev/null @@ -1,50 +0,0 @@ -package com.imyeyu.api.modules.forevermc.entity; - -import com.imyeyu.spring.entity.Destroyable; -import com.imyeyu.spring.entity.UUIDEntity; -import lombok.Data; -import lombok.EqualsAndHashCode; - -/** - * 服务器 - * - * @author 夜雨 - * @since 2025-01-27 20:36 - */ -@Data -@EqualsAndHashCode(callSuper = true) -public class Server extends UUIDEntity implements Destroyable { - - /** - * 类型 - * - * @author 夜雨 - * @since 2025-03-03 10:06 - */ - public enum Type { - - /** 官方原版 */ - ORIGINAL, - - /** 模组 */ - MOD - } - - /** 上级服务器 ID */ - protected String pid; - - /** 类型 */ - protected Type type; - - /** 标题 */ - protected String title; - - /** 说明 */ - protected String description; - - /** 地址 */ - protected String host; - - /** 排序 */ - protected int order; -} diff --git a/src/main/java/com/imyeyu/api/modules/forevermc/entity/ServerClient.java b/src/main/java/com/imyeyu/api/modules/forevermc/entity/ServerClient.java deleted file mode 100644 index 9be05d3..0000000 --- a/src/main/java/com/imyeyu/api/modules/forevermc/entity/ServerClient.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.imyeyu.api.modules.forevermc.entity; - -import lombok.Data; - -/** - * @author 夜雨 - * @since 2025-07-23 21:54 - */ -@Data -public class ServerClient { - - private String serverId; - - private Long clientId; -} diff --git a/src/main/java/com/imyeyu/api/modules/forevermc/mapper/ClientPackMapper.java b/src/main/java/com/imyeyu/api/modules/forevermc/mapper/ClientPackMapper.java deleted file mode 100644 index 7504769..0000000 --- a/src/main/java/com/imyeyu/api/modules/forevermc/mapper/ClientPackMapper.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.imyeyu.api.modules.forevermc.mapper; - -import com.imyeyu.api.modules.forevermc.entity.ClientPack; -import com.imyeyu.spring.mapper.BaseMapper; - -/** - * @author 夜雨 - * @since 2025-02-07 17:03 - */ -public interface ClientPackMapper extends BaseMapper { - -} diff --git a/src/main/java/com/imyeyu/api/modules/forevermc/mapper/ClientPackSrcMapper.java b/src/main/java/com/imyeyu/api/modules/forevermc/mapper/ClientPackSrcMapper.java deleted file mode 100644 index d3a0f0c..0000000 --- a/src/main/java/com/imyeyu/api/modules/forevermc/mapper/ClientPackSrcMapper.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.imyeyu.api.modules.forevermc.mapper; - -import com.imyeyu.api.modules.forevermc.entity.ClientPackSrc; -import com.imyeyu.spring.mapper.BaseMapper; -import org.apache.ibatis.annotations.Select; - -import java.util.List; - -/** - * @author 夜雨 - * @since 2025-02-07 17:20 - */ -public interface ClientPackSrcMapper extends BaseMapper { - - @Select("SELECT * FROM client_pack_src WHERE client_id = #{clientId}" + NOT_DELETE + " ORDER BY `order` ASC") - List selectByClientId(long clientId); -} diff --git a/src/main/java/com/imyeyu/api/modules/forevermc/mapper/ServerClientMapper.java b/src/main/java/com/imyeyu/api/modules/forevermc/mapper/ServerClientMapper.java deleted file mode 100644 index 2613d8d..0000000 --- a/src/main/java/com/imyeyu/api/modules/forevermc/mapper/ServerClientMapper.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.imyeyu.api.modules.forevermc.mapper; - -import com.imyeyu.api.modules.forevermc.entity.ClientPack; - -import java.util.List; - -/** - * @author 夜雨 - * @since 2025-07-23 22:03 - */ -public interface ServerClientMapper { - - List selectByServerId(String serverId); -} diff --git a/src/main/java/com/imyeyu/api/modules/forevermc/mapper/ServerMapper.java b/src/main/java/com/imyeyu/api/modules/forevermc/mapper/ServerMapper.java deleted file mode 100644 index 706c134..0000000 --- a/src/main/java/com/imyeyu/api/modules/forevermc/mapper/ServerMapper.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.imyeyu.api.modules.forevermc.mapper; - -import com.imyeyu.api.modules.forevermc.entity.Server; -import com.imyeyu.spring.mapper.BaseMapper; - -/** - * @author 夜雨 - * @since 2025-01-27 20:44 - */ -public interface ServerMapper extends BaseMapper { -} diff --git a/src/main/java/com/imyeyu/api/modules/forevermc/service/ClientPackService.java b/src/main/java/com/imyeyu/api/modules/forevermc/service/ClientPackService.java deleted file mode 100644 index 8eb4bb6..0000000 --- a/src/main/java/com/imyeyu/api/modules/forevermc/service/ClientPackService.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.imyeyu.api.modules.forevermc.service; - -import com.imyeyu.api.modules.forevermc.entity.ClientPack; -import com.imyeyu.api.modules.forevermc.entity.ClientPackSrc; -import com.imyeyu.spring.service.DeletableService; -import com.imyeyu.spring.service.GettableService; -import com.imyeyu.spring.service.PageableService; -import com.imyeyu.spring.service.UpdatableService; - -import java.util.List; - -/** - * @author 夜雨 - * @since 2025-07-23 16:59 - */ -public interface ClientPackService extends PageableService, GettableService, UpdatableService, DeletableService { - - List listClientPackSrc(long clientId); -} diff --git a/src/main/java/com/imyeyu/api/modules/forevermc/service/ServerService.java b/src/main/java/com/imyeyu/api/modules/forevermc/service/ServerService.java deleted file mode 100644 index 805ccca..0000000 --- a/src/main/java/com/imyeyu/api/modules/forevermc/service/ServerService.java +++ /dev/null @@ -1,38 +0,0 @@ -package com.imyeyu.api.modules.forevermc.service; - -import com.imyeyu.api.modules.forevermc.bean.ServerStatus; -import com.imyeyu.api.modules.forevermc.entity.ClientPack; -import com.imyeyu.api.modules.minecraft.vo.server.ReportRequest; -import com.imyeyu.java.TimiJava; -import com.imyeyu.java.bean.timi.TimiException; - -import java.util.List; - -/** - * Minecraft 服务 - * - * @author 夜雨 - * @since 2021-12-02 09:53 - */ -public interface ServerService extends TimiJava { - - /** - * 缓存服务器状态 - * - * @param request 服务器状态 - * @throws TimiException 服务异常 - */ - void report(ReportRequest request); - - List listClientPack(String serverId); - - String getIcon(String serverId); - - /** - * 获取所有服务器状态列表(此处移除与服务端的通信令牌) - * - * @return 所有服务器状态列表 - * @throws TimiException 服务异常 - */ - List listAll(); -} diff --git a/src/main/java/com/imyeyu/api/modules/forevermc/service/implement/ClientPackServiceImplement.java b/src/main/java/com/imyeyu/api/modules/forevermc/service/implement/ClientPackServiceImplement.java deleted file mode 100644 index 76b8d85..0000000 --- a/src/main/java/com/imyeyu/api/modules/forevermc/service/implement/ClientPackServiceImplement.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.imyeyu.api.modules.forevermc.service.implement; - -import com.imyeyu.api.modules.forevermc.entity.ClientPack; -import com.imyeyu.api.modules.forevermc.entity.ClientPackSrc; -import com.imyeyu.api.modules.forevermc.mapper.ClientPackMapper; -import com.imyeyu.api.modules.forevermc.mapper.ClientPackSrcMapper; -import com.imyeyu.api.modules.forevermc.service.ClientPackService; -import com.imyeyu.spring.mapper.BaseMapper; -import com.imyeyu.spring.service.AbstractEntityService; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; - -import java.util.List; - -/** - * @author 夜雨 - * @since 2025-07-23 16:59 - */ -@Service -@RequiredArgsConstructor -public class ClientPackServiceImplement extends AbstractEntityService implements ClientPackService { - - private final ClientPackMapper mapper; - private final ClientPackSrcMapper srcMapper; - - @Override - protected BaseMapper mapper() { - return mapper; - } - - @Override - public List listClientPackSrc(long clientId) { - return srcMapper.selectByClientId(clientId); - } -} diff --git a/src/main/java/com/imyeyu/api/modules/forevermc/service/implement/ServerServiceImplement.java b/src/main/java/com/imyeyu/api/modules/forevermc/service/implement/ServerServiceImplement.java deleted file mode 100644 index 4527649..0000000 --- a/src/main/java/com/imyeyu/api/modules/forevermc/service/implement/ServerServiceImplement.java +++ /dev/null @@ -1,86 +0,0 @@ -package com.imyeyu.api.modules.forevermc.service.implement; - -import com.imyeyu.api.modules.forevermc.bean.ServerStatus; -import com.imyeyu.api.modules.forevermc.entity.ClientPack; -import com.imyeyu.api.modules.forevermc.entity.Server; -import com.imyeyu.api.modules.forevermc.mapper.ServerClientMapper; -import com.imyeyu.api.modules.forevermc.mapper.ServerMapper; -import com.imyeyu.api.modules.forevermc.service.ServerService; -import com.imyeyu.api.modules.minecraft.vo.server.ReportRequest; -import com.imyeyu.java.TimiJava; -import com.imyeyu.java.bean.timi.TimiCode; -import com.imyeyu.java.bean.timi.TimiException; -import com.imyeyu.utils.Time; -import lombok.RequiredArgsConstructor; -import org.springframework.beans.BeanUtils; -import org.springframework.scheduling.annotation.EnableScheduling; -import org.springframework.scheduling.annotation.Scheduled; -import org.springframework.stereotype.Service; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -/** - * Minecraft 服务实现 - * - * @author 夜雨 - * @version 2021-12-02 09:56 - */ -@Service -@EnableScheduling -@RequiredArgsConstructor -public class ServerServiceImplement implements ServerService { - - private final ServerMapper serverMapper; - private final ServerClientMapper serverClientMapper; - - private final Map serverMap = new HashMap<>(); - - /** 周期性填充数据库的基本信息 */ - @Scheduled(fixedRate = Time.S * 30) - private void fillBaseInfo() { - serverMap.entrySet().removeIf(entry -> entry.getValue() == null); - for (Map.Entry item : serverMap.entrySet()) { - if (item.getValue().getBaseInfo() == null) { - continue; - } - Server server = serverMapper.select(item.getKey()); - TimiException.required(server, "not found server"); - BeanUtils.copyProperties(server, item.getValue().getBaseInfo()); - } - } - - @Override - public void report(ReportRequest request) { - ServerStatus status = serverMap.get(request.getId()); - if (status != null) { - request.setBaseInfo(TimiJava.defaultIfNull(request.getBaseInfo(), status.getBaseInfo())); - if (request.getBaseInfo() == null || TimiJava.isEmpty(request.getBaseInfo().getCore())) { - // 需要报告基本信息,与调用方约定返回代码为 IGNORE - throw new TimiException(TimiCode.IGNORE); - } - } - serverMap.put(request.getId(), request); - } - - @Override - public List listClientPack(String serverId) { - return serverClientMapper.selectByServerId(serverId); - } - - @Override - public String getIcon(String serverId) { - ServerStatus status = serverMap.get(serverId); - if (status == null || status.getBaseInfo() == null || TimiJava.isEmpty(status.getBaseInfo().getIcon())) { - throw new TimiException(TimiCode.RESULT_NULL); - } - return status.getBaseInfo().getIcon(); - } - - @Override - public List listAll() { - return new ArrayList<>(serverMap.values()); - } -} diff --git a/src/main/java/com/imyeyu/api/modules/gao/annotation/RequireGaoPermission.java b/src/main/java/com/imyeyu/api/modules/gao/annotation/RequireGaoPermission.java new file mode 100644 index 0000000..e380d36 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/annotation/RequireGaoPermission.java @@ -0,0 +1,29 @@ +package com.imyeyu.api.modules.gao.annotation; + +import com.imyeyu.api.bean.PermissionMatchMode; +import com.imyeyu.api.modules.gao.bean.GaoPermissionCode; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/// +/// 要求 GAO 模块权限 +/// +/// @author Codex +/// @since 2026-07-26 +@Target({ElementType.METHOD, ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +public @interface RequireGaoPermission { + + /// 所需权限 + /// + /// @return 权限列表 + GaoPermissionCode[] value(); + + /// 匹配模式 + /// + /// @return 匹配模式 + PermissionMatchMode mode() default PermissionMatchMode.ALL; +} diff --git a/src/main/java/com/imyeyu/api/modules/gao/annotation/RequireGaoPermissionInterceptor.java b/src/main/java/com/imyeyu/api/modules/gao/annotation/RequireGaoPermissionInterceptor.java new file mode 100644 index 0000000..2c16f5c --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/annotation/RequireGaoPermissionInterceptor.java @@ -0,0 +1,46 @@ +package com.imyeyu.api.modules.gao.annotation; + +import com.imyeyu.api.bean.ModuleCode; +import com.imyeyu.api.bean.PermissionMatchMode; +import com.imyeyu.api.modules.gao.bean.GaoPermissionCode; +import com.imyeyu.api.modules.user.service.PermissionChecker; +import com.imyeyu.java.TimiJava; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.lang.NonNull; +import org.springframework.stereotype.Component; +import org.springframework.web.method.HandlerMethod; +import org.springframework.web.servlet.HandlerInterceptor; + +/// +/// GAO 权限注解处理器 +/// +/// @author Codex +/// @since 2026-07-27 +@Component +public class RequireGaoPermissionInterceptor implements HandlerInterceptor { + + private final PermissionChecker permissionChecker; + + public RequireGaoPermissionInterceptor(PermissionChecker permissionChecker) { + this.permissionChecker = permissionChecker; + } + + @Override + public boolean preHandle(@NonNull HttpServletRequest req, @NonNull HttpServletResponse resp, @NonNull Object handler) { + if (!(handler instanceof HandlerMethod handlerMethod)) { + return true; + } + RequireGaoPermission annotation = handlerMethod.getMethodAnnotation(RequireGaoPermission.class); + if (annotation == null || TimiJava.isEmpty(annotation.value())) { + return true; + } + String[] permissionCodeList = java.util.Arrays.stream(annotation.value()).map(GaoPermissionCode::getValue).toArray(String[]::new); + if (annotation.mode() == PermissionMatchMode.ANY) { + permissionChecker.checkAny(ModuleCode.GAO, permissionCodeList); + } else { + permissionChecker.checkAll(ModuleCode.GAO, permissionCodeList); + } + return true; + } +} diff --git a/src/main/java/com/imyeyu/api/modules/gao/annotation/RequireGaoRole.java b/src/main/java/com/imyeyu/api/modules/gao/annotation/RequireGaoRole.java new file mode 100644 index 0000000..2f12945 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/annotation/RequireGaoRole.java @@ -0,0 +1,29 @@ +package com.imyeyu.api.modules.gao.annotation; + +import com.imyeyu.api.bean.RoleMatchMode; +import com.imyeyu.api.modules.gao.bean.GaoRoleCode; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/// +/// 要求 GAO 模块角色 +/// +/// @author Codex +/// @since 2026-07-26 +@Target({ElementType.METHOD, ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +public @interface RequireGaoRole { + + /// 所需角色 + /// + /// @return 角色列表 + GaoRoleCode[] value(); + + /// 匹配模式 + /// + /// @return 匹配模式 + RoleMatchMode mode() default RoleMatchMode.ALL; +} diff --git a/src/main/java/com/imyeyu/api/modules/gao/annotation/RequireGaoRoleInterceptor.java b/src/main/java/com/imyeyu/api/modules/gao/annotation/RequireGaoRoleInterceptor.java new file mode 100644 index 0000000..600db09 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/annotation/RequireGaoRoleInterceptor.java @@ -0,0 +1,46 @@ +package com.imyeyu.api.modules.gao.annotation; + +import com.imyeyu.api.bean.ModuleCode; +import com.imyeyu.api.bean.RoleMatchMode; +import com.imyeyu.api.modules.gao.bean.GaoRoleCode; +import com.imyeyu.api.modules.user.service.RoleChecker; +import com.imyeyu.java.TimiJava; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.lang.NonNull; +import org.springframework.stereotype.Component; +import org.springframework.web.method.HandlerMethod; +import org.springframework.web.servlet.HandlerInterceptor; + +/// +/// GAO 角色注解处理器 +/// +/// @author Codex +/// @since 2026-07-27 +@Component +public class RequireGaoRoleInterceptor implements HandlerInterceptor { + + private final RoleChecker roleChecker; + + public RequireGaoRoleInterceptor(RoleChecker roleChecker) { + this.roleChecker = roleChecker; + } + + @Override + public boolean preHandle(@NonNull HttpServletRequest req, @NonNull HttpServletResponse resp, @NonNull Object handler) { + if (!(handler instanceof HandlerMethod handlerMethod)) { + return true; + } + RequireGaoRole annotation = handlerMethod.getMethodAnnotation(RequireGaoRole.class); + if (annotation == null || TimiJava.isEmpty(annotation.value())) { + return true; + } + String[] roleCodeList = java.util.Arrays.stream(annotation.value()).map(GaoRoleCode::name).toArray(String[]::new); + if (annotation.mode() == RoleMatchMode.ANY) { + roleChecker.checkAny(ModuleCode.GAO, roleCodeList); + } else { + roleChecker.checkAll(ModuleCode.GAO, roleCodeList); + } + return true; + } +} diff --git a/src/main/java/com/imyeyu/api/modules/gao/bean/GaoPermissionCode.java b/src/main/java/com/imyeyu/api/modules/gao/bean/GaoPermissionCode.java new file mode 100644 index 0000000..5f150b8 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/bean/GaoPermissionCode.java @@ -0,0 +1,94 @@ +package com.imyeyu.api.modules.gao.bean; + +import com.imyeyu.api.bean.ModuleCode; +import com.imyeyu.api.modules.user.bean.BuiltinPermissionCode; + +/// +/// GAO 模块权限枚举 +/// +/// @author Codex +/// @since 2026-07-26 +public enum GaoPermissionCode implements BuiltinPermissionCode { + + CUSTOMER_CREATE("CUSTOMER:CREATE", "客户创建"), + + CUSTOMER_READ("CUSTOMER:READ", "客户读取"), + + CUSTOMER_UPDATE("CUSTOMER:UPDATE", "客户修改"), + + CUSTOMER_DELETE("CUSTOMER:DELETE", "客户删除"), + + REGISTRATION_EVENT_CREATE("REGISTRATION_EVENT:CREATE", "登记事件创建"), + + REGISTRATION_EVENT_READ("REGISTRATION_EVENT:READ", "登记事件读取"), + + REGISTRATION_EVENT_UPDATE("REGISTRATION_EVENT:UPDATE", "登记事件修改"), + + REGISTRATION_EVENT_DELETE("REGISTRATION_EVENT:DELETE", "登记事件删除"), + + REGISTRATION_EVENT_RECORD_CREATE("REGISTRATION_EVENT_RECORD:CREATE", "登记事件记录创建"), + + REGISTRATION_EVENT_RECORD_READ("REGISTRATION_EVENT_RECORD:READ", "登记事件记录读取"), + + REGISTRATION_EVENT_RECORD_UPDATE("REGISTRATION_EVENT_RECORD:UPDATE", "登记事件记录修改"), + + REGISTRATION_EVENT_RECORD_DELETE("REGISTRATION_EVENT_RECORD:DELETE", "登记事件记录删除"), + + STAT_READ("STAT:READ", "统计读取"), + + QR_CODE_CREATE("QR_CODE:CREATE", "二维码创建"), + + USER_CREATE("USER:CREATE", "用户创建", true), + + USER_READ("USER:READ", "用户读取", true), + + USER_UPDATE("USER:UPDATE", "用户修改", true), + + USER_DELETE("USER:DELETE", "用户删除", true); + + final String value; + final String defaultName; + final boolean grantToStoreManager; + + GaoPermissionCode(String value, String defaultName) { + this(value, defaultName, false); + } + + GaoPermissionCode(String value, String defaultName, boolean grantToStoreManager) { + this.value = value; + this.defaultName = defaultName; + this.grantToStoreManager = grantToStoreManager; + } + + @Override + public ModuleCode getModuleCode() { + return ModuleCode.GAO; + } + + @Override + public String getValue() { + return value; + } + + @Override + public String getDefaultName() { + return defaultName; + } + + @Override + public boolean isGrantToSystem() { + return false; + } + + @Override + public boolean isGrantToAdmin() { + return false; + } + + /// true 为初始化时授权给店长角色 + /// + /// @return 是否授权给店长角色 + public boolean isGrantToStoreManager() { + return grantToStoreManager; + } +} diff --git a/src/main/java/com/imyeyu/api/modules/gao/bean/GaoRoleCode.java b/src/main/java/com/imyeyu/api/modules/gao/bean/GaoRoleCode.java new file mode 100644 index 0000000..b76855e --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/bean/GaoRoleCode.java @@ -0,0 +1,41 @@ +package com.imyeyu.api.modules.gao.bean; + +import com.imyeyu.api.bean.ModuleCode; +import com.imyeyu.api.modules.user.bean.BuiltinRoleCode; +import lombok.AllArgsConstructor; + +/// +/// GAO 模块角色枚举 +/// +/// @author Codex +/// @since 2026-07-26 +@AllArgsConstructor +public enum GaoRoleCode implements BuiltinRoleCode { + + /// 店长 + STORE_MANAGER("店长"), + + EMPLOYEE("员工"); + + final String defaultName; + + @Override + public ModuleCode getModuleCode() { + return ModuleCode.GAO; + } + + @Override + public String getDefaultName() { + return defaultName; + } + + @Override + public boolean isProtected() { + return false; + } + + @Override + public String getParentCode() { + return null; + } +} diff --git a/src/main/java/com/imyeyu/api/modules/gao/controller/GaoCustomerController.java b/src/main/java/com/imyeyu/api/modules/gao/controller/GaoCustomerController.java new file mode 100644 index 0000000..8d30908 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/controller/GaoCustomerController.java @@ -0,0 +1,190 @@ +package com.imyeyu.api.modules.gao.controller; + +import com.imyeyu.api.modules.gao.bean.GaoPermissionCode; +import com.imyeyu.api.modules.gao.bean.GaoRoleCode; +import com.imyeyu.api.bean.RoleMatchMode; +import com.imyeyu.api.modules.gao.annotation.RequireGaoPermission; +import com.imyeyu.api.modules.gao.annotation.RequireGaoRole; +import com.imyeyu.api.modules.gao.entity.GaoCustomer; +import com.imyeyu.api.modules.gao.entity.GaoRegistrationEvent; +import com.imyeyu.api.modules.gao.entity.GaoRegistrationEventRecord; +import com.imyeyu.api.modules.gao.service.GaoRegistrationEventService; +import com.imyeyu.api.modules.gao.service.GaoCustomerService; +import com.imyeyu.api.modules.gao.service.GaoRegistrationEventRecordService; +import com.imyeyu.api.modules.gao.vo.GaoCustomerDailyStatView; +import com.imyeyu.api.modules.gao.vo.GaoCustomerInviteUpdateReq; +import com.imyeyu.api.modules.gao.vo.GaoCustomerSearchReq; +import com.imyeyu.api.modules.gao.vo.GaoCustomerTrendStatReq; +import com.imyeyu.api.modules.gao.vo.GaoQuickRegistrationEventRecordReq; +import com.imyeyu.spring.annotation.AOPLog; +import com.imyeyu.spring.annotation.RequestRateLimit; +import com.imyeyu.spring.bean.Page; +import com.imyeyu.spring.bean.PageResult; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; + +/// +/// GAO 客户接口 +/// +/// @author Codex +/// @since 2026-07-27 +@RestController +@RequiredArgsConstructor +@RequestMapping("/gao/customer") +@RequireGaoRole(value = {GaoRoleCode.STORE_MANAGER, GaoRoleCode.EMPLOYEE}, mode = RoleMatchMode.ANY) +public class GaoCustomerController { + + private static final String SIGN_IN_REGISTRATION_EVENT_NAME = "签到"; + + private final GaoCustomerService service; + private final GaoRegistrationEventRecordService registrationEventRecordService; + private final GaoRegistrationEventService registrationEventService; + + @AOPLog + @RequestRateLimit + @RequireGaoPermission(GaoPermissionCode.CUSTOMER_READ) + @PostMapping("/list") + public PageResult list(@RequestBody Page page) { + PageResult result = service.page(page); + fillLastSignInAt(result.getList()); + return result; + } + + @AOPLog + @RequestRateLimit + @RequireGaoPermission(GaoPermissionCode.CUSTOMER_READ) + @PostMapping("/search") + public List search(@RequestBody GaoCustomerSearchReq req) { + return service.search(req); + } + + @AOPLog + @RequestRateLimit + @RequireGaoPermission(GaoPermissionCode.CUSTOMER_READ) + @PostMapping("/detail") + public GaoCustomer detail(@RequestParam String id) { + return service.get(id); + } + + @AOPLog + @RequestRateLimit + @RequireGaoPermission(GaoPermissionCode.CUSTOMER_READ) + @PostMapping("/detail/code") + public GaoCustomer detailByCode(@RequestParam String code) { + return service.getByCode(code); + } + + @AOPLog + @RequestRateLimit + @RequireGaoPermission(GaoPermissionCode.CUSTOMER_READ) + @PostMapping("/invited/list") + public List invitedList(@RequestParam String introducerCustomerId) { + return service.listInvited(introducerCustomerId); + } + + @AOPLog + @RequestRateLimit + @RequireGaoPermission(GaoPermissionCode.CUSTOMER_CREATE) + @PostMapping("/create") + public void create(@RequestBody GaoCustomer customer) { + service.create(customer); + } + + @AOPLog + @RequestRateLimit + @PostMapping("/quick/register") + public GaoRegistrationEventRecord quickRegister(@RequestBody GaoQuickRegistrationEventRecordReq req) { + return registrationEventRecordService.quickRegister(req); + } + + @AOPLog + @RequestRateLimit + @RequireGaoPermission(GaoPermissionCode.CUSTOMER_UPDATE) + @PostMapping("/update") + public void update(@RequestBody GaoCustomer customer) { + service.update(customer); + } + + @AOPLog + @RequestRateLimit + @RequireGaoPermission(GaoPermissionCode.CUSTOMER_DELETE) + @PostMapping("/delete") + public void delete(@RequestParam String id) { + service.delete(id); + } + + @AOPLog + @RequestRateLimit + @RequireGaoPermission(GaoPermissionCode.CUSTOMER_UPDATE) + @PostMapping("/invite/create") + public void createInvite(@RequestBody GaoCustomerInviteUpdateReq req) { + service.saveInviteRelation(req); + } + + @AOPLog + @RequestRateLimit + @RequireGaoPermission(GaoPermissionCode.CUSTOMER_READ) + @PostMapping("/invite/detail") + public GaoCustomer inviteDetail(@RequestParam String customerId) { + return service.get(customerId); + } + + @AOPLog + @RequestRateLimit + @RequireGaoPermission(GaoPermissionCode.CUSTOMER_UPDATE) + @PostMapping("/invite/update") + public void updateInvite(@RequestBody GaoCustomerInviteUpdateReq req) { + service.saveInviteRelation(req); + } + + @AOPLog + @RequestRateLimit + @RequireGaoPermission(GaoPermissionCode.CUSTOMER_UPDATE) + @PostMapping("/invite/delete") + public void deleteInvite(@RequestParam String customerId) { + service.deleteInviteRelation(customerId); + } + + @AOPLog + @RequestRateLimit + @RequireGaoPermission(GaoPermissionCode.CUSTOMER_READ) + @PostMapping("/stat/daily") + /// 按天统计客户新增数和累计总数 + public List dailyStat(@RequestBody GaoCustomerTrendStatReq req) { + return service.statDailyTrend(req); + } + + private void fillLastSignInAt(List customerList) { + if (customerList == null || customerList.isEmpty()) { + return; + } + String signInRegistrationEventId = null; + for (GaoRegistrationEvent registrationEvent : registrationEventService.listEnabled()) { + if (SIGN_IN_REGISTRATION_EVENT_NAME.equals(registrationEvent.getName())) { + signInRegistrationEventId = registrationEvent.getId(); + break; + } + } + if (signInRegistrationEventId == null) { + return; + } + List customerIdList = customerList.stream().map(GaoCustomer::getId).toList(); + Map recordMap = registrationEventRecordService.listLatestByCustomerIdListAndRegistrationEventId(customerIdList, signInRegistrationEventId).stream() + .collect(Collectors.toMap(GaoRegistrationEventRecord::getCustomerId, Function.identity(), (left, right) -> left)); + for (GaoCustomer customer : customerList) { + GaoRegistrationEventRecord record = recordMap.get(customer.getId()); + if (record != null) { + customer.setLastSignInAt(record.getRegisteredAt()); + } + } + } +} diff --git a/src/main/java/com/imyeyu/api/modules/gao/controller/GaoRegistrationEventController.java b/src/main/java/com/imyeyu/api/modules/gao/controller/GaoRegistrationEventController.java new file mode 100644 index 0000000..e9e7319 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/controller/GaoRegistrationEventController.java @@ -0,0 +1,82 @@ +package com.imyeyu.api.modules.gao.controller; + +import com.imyeyu.api.modules.gao.annotation.RequireGaoPermission; +import com.imyeyu.api.modules.gao.annotation.RequireGaoRole; +import com.imyeyu.api.bean.RoleMatchMode; +import com.imyeyu.api.modules.gao.bean.GaoPermissionCode; +import com.imyeyu.api.modules.gao.bean.GaoRoleCode; +import com.imyeyu.api.modules.gao.entity.GaoRegistrationEvent; +import com.imyeyu.api.modules.gao.service.GaoRegistrationEventService; +import com.imyeyu.spring.annotation.AOPLog; +import com.imyeyu.spring.annotation.RequestRateLimit; +import com.imyeyu.spring.bean.Page; +import com.imyeyu.spring.bean.PageResult; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +/// +/// GAO 登记事件接口 +/// +/// @author Codex +/// @since 2026-07-27 +@RestController +@RequiredArgsConstructor +@RequestMapping("/gao/registration-event") +@RequireGaoRole(value = {GaoRoleCode.STORE_MANAGER, GaoRoleCode.EMPLOYEE}, mode = RoleMatchMode.ANY) +public class GaoRegistrationEventController { + + private final GaoRegistrationEventService service; + + @AOPLog + @RequestRateLimit + @RequireGaoPermission(GaoPermissionCode.REGISTRATION_EVENT_READ) + @PostMapping("/list") + public PageResult list(@RequestBody Page page) { + return service.pageWithRegistrationEventLimit(page); + } + + @AOPLog + @RequestRateLimit + @PostMapping("/enabled/list") + public List enabledList() { + return service.listEnabled(); + } + + @AOPLog + @RequestRateLimit + @RequireGaoPermission(GaoPermissionCode.REGISTRATION_EVENT_READ) + @PostMapping("/detail") + public GaoRegistrationEvent detail(@RequestParam String id) { + return service.get(id); + } + + @AOPLog + @RequestRateLimit + @RequireGaoPermission(GaoPermissionCode.REGISTRATION_EVENT_CREATE) + @PostMapping("/create") + public void create(@RequestBody GaoRegistrationEvent registrationEvent) { + service.create(registrationEvent); + } + + @AOPLog + @RequestRateLimit + @RequireGaoPermission(GaoPermissionCode.REGISTRATION_EVENT_UPDATE) + @PostMapping("/update") + public void update(@RequestBody GaoRegistrationEvent registrationEvent) { + service.update(registrationEvent); + } + + @AOPLog + @RequestRateLimit + @RequireGaoPermission(GaoPermissionCode.REGISTRATION_EVENT_DELETE) + @PostMapping("/delete") + public void delete(@RequestParam String id) { + service.delete(id); + } +} diff --git a/src/main/java/com/imyeyu/api/modules/gao/controller/GaoRegistrationEventRecordController.java b/src/main/java/com/imyeyu/api/modules/gao/controller/GaoRegistrationEventRecordController.java new file mode 100644 index 0000000..cf2ea74 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/controller/GaoRegistrationEventRecordController.java @@ -0,0 +1,192 @@ +package com.imyeyu.api.modules.gao.controller; + +import com.imyeyu.api.modules.gao.bean.GaoPermissionCode; +import com.imyeyu.api.modules.gao.bean.GaoRoleCode; +import com.imyeyu.api.bean.RoleMatchMode; +import com.imyeyu.api.modules.gao.annotation.RequireGaoPermission; +import com.imyeyu.api.modules.gao.annotation.RequireGaoRole; +import com.imyeyu.api.modules.gao.entity.GaoRegistrationEventRecord; +import com.imyeyu.api.modules.gao.service.GaoRegistrationEventRecordService; +import com.imyeyu.api.modules.gao.vo.GaoRegistrationEventPeriodStatView; +import com.imyeyu.api.modules.gao.vo.GaoCustomerRegisterStatView; +import com.imyeyu.api.modules.gao.vo.GaoRegistrationEventRecordCreateReq; +import com.imyeyu.api.modules.gao.vo.GaoRegistrationEventRecordDailyStatView; +import com.imyeyu.api.modules.gao.vo.GaoRegistrationEventRecordPeriodReq; +import com.imyeyu.api.modules.gao.vo.GaoRegistrationEventRecordRangeReq; +import com.imyeyu.api.modules.gao.vo.GaoRegistrationEventRecordStatReq; +import com.imyeyu.api.modules.gao.vo.GaoRegistrationEventRecordTrendStatReq; +import com.imyeyu.spring.annotation.AOPLog; +import com.imyeyu.spring.annotation.IgnoreGlobalReturn; +import com.imyeyu.spring.annotation.RequestRateLimit; +import com.imyeyu.spring.bean.Page; +import com.imyeyu.spring.bean.PageResult; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.List; + +/// +/// GAO 登记事件记录接口 +/// +/// @author Codex +/// @since 2026-07-27 +@RestController +@RequiredArgsConstructor +@RequestMapping("/gao/registration-event-record") +@RequireGaoRole(value = {GaoRoleCode.STORE_MANAGER, GaoRoleCode.EMPLOYEE}, mode = RoleMatchMode.ANY) +public class GaoRegistrationEventRecordController { + + private final GaoRegistrationEventRecordService service; + + @AOPLog + @RequestRateLimit + @RequireGaoPermission(GaoPermissionCode.REGISTRATION_EVENT_RECORD_READ) + @PostMapping("/list") + public PageResult list(@RequestBody Page page) { + return service.page(page); + } + + @AOPLog + @RequestRateLimit + @RequireGaoPermission(GaoPermissionCode.REGISTRATION_EVENT_RECORD_READ) + @PostMapping("/detail") + public GaoRegistrationEventRecord detail(@RequestParam String id) { + return service.get(id); + } + + @AOPLog + @RequestRateLimit + @PostMapping("/create") + public GaoRegistrationEventRecord create(@RequestBody GaoRegistrationEventRecordCreateReq req) { + return service.createRecord(req); + } + + @AOPLog + @RequestRateLimit + @RequireGaoPermission(GaoPermissionCode.REGISTRATION_EVENT_RECORD_UPDATE) + @PostMapping("/update") + public void update(@RequestBody GaoRegistrationEventRecord record) { + service.update(record); + } + + @AOPLog + @RequestRateLimit + @RequireGaoPermission(GaoPermissionCode.REGISTRATION_EVENT_RECORD_DELETE) + @PostMapping("/delete") + public void delete(@RequestParam String id) { + service.delete(id); + } + + @AOPLog + @RequestRateLimit + @RequireGaoPermission(GaoPermissionCode.REGISTRATION_EVENT_RECORD_READ) + @PostMapping("/period/list") + public List periodList(@RequestBody GaoRegistrationEventRecordPeriodReq req) { + return service.listByRegistrationEventAndPeriod(req); + } + + @AOPLog + @RequestRateLimit + @RequireGaoPermission(GaoPermissionCode.REGISTRATION_EVENT_RECORD_READ) + @PostMapping("/range/list") + public List rangeList(@RequestBody GaoRegistrationEventRecordRangeReq req) { + return service.listByRange(req); + } + + @AOPLog + @RequestRateLimit + @IgnoreGlobalReturn + @RequireGaoPermission(GaoPermissionCode.REGISTRATION_EVENT_RECORD_READ) + @PostMapping("/range/export") + public void rangeExport(@RequestBody GaoRegistrationEventRecordRangeReq req, HttpServletResponse resp) throws IOException { + List list = service.listByRange(req); + resp.setContentType("application/vnd.ms-excel;charset=UTF-8"); + resp.setHeader("Content-Disposition", "attachment; filename=\"gao-registration-event-record.csv\""); + resp.getOutputStream().write(buildCsv(list).getBytes(StandardCharsets.UTF_8)); + } + + @AOPLog + @RequestRateLimit + @RequireGaoPermission(GaoPermissionCode.STAT_READ) + @PostMapping("/period/stat") + public List periodStat(@RequestBody GaoRegistrationEventRecordPeriodReq req) { + return service.statByRegistrationEventAndPeriodType(req.getRegistrationEventId(), req.getPeriodType()); + } + + @AOPLog + @RequestRateLimit + @RequireGaoPermission(GaoPermissionCode.STAT_READ) + @PostMapping("/customer/stat") + public List customerStat(@RequestParam String customerId) { + return service.statByCustomerId(customerId); + } + + @AOPLog + @RequestRateLimit + @RequireGaoPermission(GaoPermissionCode.STAT_READ) + @PostMapping("/customer/range/stat") + /// 按时间段统计单个客户各登记事件记录数 + public List customerRangeStat(@RequestBody GaoRegistrationEventRecordStatReq req) { + return service.statByCustomerAndRange(req); + } + + @AOPLog + @RequestRateLimit + @RequireGaoPermission(GaoPermissionCode.STAT_READ) + @PostMapping("/all-customer/range/stat") + /// 按时间段统计全部客户各登记事件记录数 + public List allCustomerRangeStat(@RequestBody GaoRegistrationEventRecordStatReq req) { + return service.statAllCustomerByRange(req); + } + + @AOPLog + @RequestRateLimit + @RequireGaoPermission(GaoPermissionCode.STAT_READ) + @PostMapping("/customer/registration-event/daily/stat") + /// 按天统计单个客户单个登记事件记录趋势 + public List customerRegistrationEventDailyStat(@RequestBody GaoRegistrationEventRecordTrendStatReq req) { + return service.statDailyByCustomerAndRegistrationEvent(req); + } + + @AOPLog + @RequestRateLimit + @RequireGaoPermission(GaoPermissionCode.STAT_READ) + @PostMapping("/all-customer/registration-event/daily/stat") + /// 按天统计全部客户单个登记事件记录趋势 + public List allCustomerRegistrationEventDailyStat(@RequestBody GaoRegistrationEventRecordTrendStatReq req) { + return service.statDailyByRegistrationEvent(req); + } + + private String buildCsv(List list) { + StringBuilder builder = new StringBuilder(); + builder.append((char) 65279); + builder.append("客户编码,客户姓名,登记事件,登记时间,登记人用户 ID,备注\n"); + for (GaoRegistrationEventRecord record : list) { + builder.append(csv(record.getCustomer() == null ? null : record.getCustomer().getCode())).append(','); + builder.append(csv(record.getCustomer() == null ? null : record.getCustomer().getName())).append(','); + builder.append(csv(record.getRegistrationEvent() == null ? null : record.getRegistrationEvent().getName())).append(','); + builder.append(csv(record.getRegisteredAt())).append(','); + builder.append(csv(record.getOperatorUserId())).append(','); + builder.append(csv(record.getRemark())).append('\n'); + } + return builder.toString(); + } + + private String csv(Object value) { + if (value == null) { + return ""; + } + String text = value.toString(); + if (!text.contains(",") && !text.contains("\"") && !text.contains("\n") && !text.contains("\r")) { + return text; + } + return "\"%s\"".formatted(text.replace("\"", "\"\"")); + } +} diff --git a/src/main/java/com/imyeyu/api/modules/gao/controller/GaoRoleController.java b/src/main/java/com/imyeyu/api/modules/gao/controller/GaoRoleController.java new file mode 100644 index 0000000..cff4cd3 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/controller/GaoRoleController.java @@ -0,0 +1,36 @@ +package com.imyeyu.api.modules.gao.controller; + +import com.imyeyu.api.bean.ModuleCode; +import com.imyeyu.api.modules.gao.annotation.RequireGaoRole; +import com.imyeyu.api.modules.gao.bean.GaoRoleCode; +import com.imyeyu.api.modules.user.entity.Role; +import com.imyeyu.api.modules.user.service.RoleService; +import com.imyeyu.spring.annotation.AOPLog; +import com.imyeyu.spring.annotation.RequestRateLimit; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +/// +/// GAO 角色接口 +/// +/// @author Codex +/// @since 2026-07-28 +@RestController +@RequiredArgsConstructor +@RequestMapping("/gao/role") +@RequireGaoRole(GaoRoleCode.STORE_MANAGER) +public class GaoRoleController { + + private final RoleService roleService; + + @AOPLog + @RequestRateLimit + @PostMapping("/available/list") + public List availableList() { + return roleService.listByModuleCode(ModuleCode.GAO); + } +} diff --git a/src/main/java/com/imyeyu/api/modules/gao/controller/GaoUserController.java b/src/main/java/com/imyeyu/api/modules/gao/controller/GaoUserController.java new file mode 100644 index 0000000..3f09d56 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/controller/GaoUserController.java @@ -0,0 +1,96 @@ +package com.imyeyu.api.modules.gao.controller; + +import com.imyeyu.api.bean.ModuleCode; +import com.imyeyu.api.modules.gao.annotation.RequireGaoPermission; +import com.imyeyu.api.modules.gao.annotation.RequireGaoRole; +import com.imyeyu.api.modules.gao.bean.GaoPermissionCode; +import com.imyeyu.api.modules.gao.bean.GaoRoleCode; +import com.imyeyu.api.modules.gao.entity.GaoUser; +import com.imyeyu.api.modules.gao.service.GaoUserService; +import com.imyeyu.api.modules.user.entity.Role; +import com.imyeyu.api.modules.user.service.RoleService; +import com.imyeyu.spring.annotation.AOPLog; +import com.imyeyu.spring.annotation.RequestRateLimit; +import com.imyeyu.spring.bean.Page; +import com.imyeyu.spring.bean.PageResult; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +/// +/// GAO 用户接口 +/// +/// @author Codex +/// @since 2026-07-30 +@RestController +@RequiredArgsConstructor +@RequestMapping("/gao/user") +@RequireGaoRole(GaoRoleCode.STORE_MANAGER) +public class GaoUserController { + + private final GaoUserService service; + private final RoleService roleService; + + @AOPLog + @RequestRateLimit + @RequireGaoPermission(GaoPermissionCode.USER_READ) + @PostMapping("/list") + public PageResult list(@RequestBody Page page) { + return service.pageWithUser(page); + } + + @AOPLog + @RequestRateLimit + @RequireGaoPermission(GaoPermissionCode.USER_READ) + @PostMapping("/detail") + public GaoUser detail(@RequestParam String id) { + return service.get(id); + } + + @AOPLog + @RequestRateLimit + @RequireGaoPermission(GaoPermissionCode.USER_READ) + @PostMapping("/detail/user") + public GaoUser detailByUserId(@RequestParam String userId) { + return service.getByUserId(userId); + } + + @AOPLog + @RequestRateLimit + @RequireGaoPermission(GaoPermissionCode.USER_CREATE) + @PostMapping("/create") + public GaoUser create(@RequestBody GaoUser gaoUser) { + service.create(gaoUser); + return service.get(gaoUser.getId()); + } + + @AOPLog + @RequestRateLimit + @RequireGaoPermission(GaoPermissionCode.USER_UPDATE) + @PostMapping("/update") + public GaoUser update(@RequestBody GaoUser gaoUser) { + service.update(gaoUser); + return service.get(gaoUser.getId()); + } + + @AOPLog + @RequestRateLimit + @RequireGaoPermission(GaoPermissionCode.USER_DELETE) + @PostMapping("/delete") + public void delete(@RequestParam String id) { + service.delete(id); + } + + @AOPLog + @RequestRateLimit + @RequireGaoPermission(GaoPermissionCode.USER_READ) + @PostMapping("/role/list") + public List roleList() { + return roleService.listByModuleCode(ModuleCode.GAO); + } +} diff --git a/src/main/java/com/imyeyu/api/modules/gao/entity/GaoCustomer.java b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoCustomer.java new file mode 100644 index 0000000..9ba3232 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoCustomer.java @@ -0,0 +1,77 @@ +package com.imyeyu.api.modules.gao.entity; + +import com.fasterxml.jackson.databind.JsonNode; +import com.imyeyu.api.modules.common.entity.Attachment; +import com.imyeyu.api.modules.user.bean.Gender; +import com.imyeyu.spring.annotation.table.Transient; +import com.imyeyu.spring.entity.UUIDEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.List; + +/// +/// 医疗护理客户 +/// +/// @author Codex +/// @since 2026-07-27 +@Data +@EqualsAndHashCode(callSuper = true) +public class GaoCustomer extends UUIDEntity { + + /// + /// 客户附件类型 + /// + /// @author Codex + /// @since 2026-07-27 + public enum AttachType { + + PHOTO + } + + /// 客户编码 + private String code; + + /// 姓名 + private String name; + + /// 性别 + private Gender gender; + + /// 年龄 + private Integer age; + + /// 电话 + private String telephone; + + /// 住址 + private String address; + + /// 备注 + private String remark; + + /// 病症 + private String disease; + + /// 调理部位,结构由前端决定 + private JsonNode conditioningPart; + + /// 介绍人客户 ID + private String introducerCustomerId; + + @Transient + private Long lastSignInAt; + + @Transient + private GaoCustomer introducerCustomer; + + @Transient + private Long invitedCount; + + @Transient + private List attachmentList; + + /// 临时照片附件 ID 列表 + @Transient + private List attachmentIdList; +} diff --git a/src/main/java/com/imyeyu/api/modules/gao/entity/GaoRegistrationEvent.java b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoRegistrationEvent.java new file mode 100644 index 0000000..83ca9ae --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoRegistrationEvent.java @@ -0,0 +1,37 @@ +package com.imyeyu.api.modules.gao.entity; + +import com.imyeyu.spring.annotation.table.Transient; +import com.imyeyu.spring.entity.UUIDEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.List; + +/// +/// 登记事件 +/// +/// @author Codex +/// @since 2026-07-27 +@Data +@EqualsAndHashCode(callSuper = true) +public class GaoRegistrationEvent extends UUIDEntity { + + /// 登记事件名称 + private String name; + + /// 说明 + private String description; + + /// true 为启用 + private Boolean enabled; + + /// true 为登记时要求拍照 + private Boolean requirePhoto; + + /// 排序值 + private Integer sort; + + /// 可登记角色代码列表 + @Transient + private List roleCodeList; +} diff --git a/src/main/java/com/imyeyu/api/modules/gao/entity/GaoRegistrationEventRecord.java b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoRegistrationEventRecord.java new file mode 100644 index 0000000..ec92999 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoRegistrationEventRecord.java @@ -0,0 +1,61 @@ +package com.imyeyu.api.modules.gao.entity; + +import com.imyeyu.api.modules.common.entity.Attachment; +import com.imyeyu.api.modules.user.entity.User; +import com.imyeyu.spring.annotation.table.Transient; +import com.imyeyu.spring.entity.UUIDEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.List; + +/// +/// 登记事件记录 +/// +/// @author Codex +/// @since 2026-07-27 +@Data +@EqualsAndHashCode(callSuper = true) +public class GaoRegistrationEventRecord extends UUIDEntity { + + /// + /// 登记事件记录附件类型 + /// + /// @author Codex + /// @since 2026-07-27 + public enum AttachType { + + PHOTO + } + + /// 客户 ID + private String customerId; + + /// 登记登记事件 ID + private String registrationEventId; + + /// 操作管理员用户 ID + private String operatorUserId; + + /// 备注 + private String remark; + + /// 登记时间 + private Long registeredAt; + + @Transient + private GaoCustomer customer; + + @Transient + private GaoRegistrationEvent registrationEvent; + + @Transient + private User operator; + + @Transient + private List attachmentList; + + /// 临时照片附件 ID 列表 + @Transient + private List attachmentIdList; +} diff --git a/src/main/java/com/imyeyu/api/modules/gao/entity/GaoRegistrationEventRoleRelation.java b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoRegistrationEventRoleRelation.java new file mode 100644 index 0000000..0922068 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoRegistrationEventRoleRelation.java @@ -0,0 +1,21 @@ +package com.imyeyu.api.modules.gao.entity; + +import com.imyeyu.spring.entity.UUIDEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/// +/// 登记事件角色关联 +/// +/// @author Codex +/// @since 2026-07-28 +@Data +@EqualsAndHashCode(callSuper = true) +public class GaoRegistrationEventRoleRelation extends UUIDEntity { + + /// 登记事件 ID + private String registrationEventId; + + /// 角色代码 + private String roleCode; +} diff --git a/src/main/java/com/imyeyu/api/modules/gao/entity/GaoUser.java b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoUser.java new file mode 100644 index 0000000..fd012ce --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoUser.java @@ -0,0 +1,38 @@ +package com.imyeyu.api.modules.gao.entity; + +import com.imyeyu.api.modules.user.entity.User; +import com.imyeyu.api.modules.user.entity.Role; +import com.imyeyu.spring.annotation.table.Transient; +import com.imyeyu.spring.entity.UUIDEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.List; + +/// +/// GAO 用户 +/// +/// @author Codex +/// @since 2026-07-30 +@Data +@EqualsAndHashCode(callSuper = true) +public class GaoUser extends UUIDEntity { + + /// 用户 ID + private String userId; + + /// true 为启用 + private Boolean enabled; + + /// 备注 + private String remark; + + @Transient + private User user; + + @Transient + private List roleList; + + @Transient + private List roleIdList; +} diff --git a/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoCustomerMapper.java b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoCustomerMapper.java new file mode 100644 index 0000000..d0e8399 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoCustomerMapper.java @@ -0,0 +1,39 @@ +package com.imyeyu.api.modules.gao.mapper; + +import com.imyeyu.api.modules.gao.entity.GaoCustomer; +import com.imyeyu.api.modules.gao.vo.GaoCustomerDailyStatView; +import com.imyeyu.spring.mapper.BaseMapper; +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Select; + +import java.util.List; + +/// +/// 客户 Mapper +/// +/// @author Codex +/// @since 2026-07-27 +public interface GaoCustomerMapper extends BaseMapper { + + @Select("SELECT * FROM `gao_customer` WHERE `code` = #{code} AND `deleted_at` IS NULL LIMIT 1") + GaoCustomer selectByCode(@Param("code") String code); + + List search(@Param("keyword") String keyword, @Param("introducerCustomerId") String introducerCustomerId); + + List selectInvitedList(@Param("introducerCustomerId") String introducerCustomerId); + + Long countInvited(@Param("introducerCustomerId") String introducerCustomerId); + + /// 统计每日新增客户数 + /// + /// @param beginCreatedAt 开始创建时间 + /// @param endCreatedAt 结束创建时间 + /// @return 每日新增客户数 + List statDailyNewCustomer(@Param("beginCreatedAt") Long beginCreatedAt, @Param("endCreatedAt") Long endCreatedAt); + + /// 统计开始时间前客户总数 + /// + /// @param beginCreatedAt 开始创建时间 + /// @return 客户总数 + Long countCreatedBefore(@Param("beginCreatedAt") Long beginCreatedAt); +} diff --git a/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoRegistrationEventMapper.java b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoRegistrationEventMapper.java new file mode 100644 index 0000000..01d8a56 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoRegistrationEventMapper.java @@ -0,0 +1,18 @@ +package com.imyeyu.api.modules.gao.mapper; + +import com.imyeyu.api.modules.gao.entity.GaoRegistrationEvent; +import com.imyeyu.spring.mapper.BaseMapper; +import org.apache.ibatis.annotations.Select; + +import java.util.List; + +/// +/// 登记事件 Mapper +/// +/// @author Codex +/// @since 2026-07-27 +public interface GaoRegistrationEventMapper extends BaseMapper { + + @Select("SELECT * FROM `gao_registration_event` WHERE `enabled` = TRUE AND `deleted_at` IS NULL ORDER BY `sort` ASC, `created_at` ASC") + List selectEnabledList(); +} diff --git a/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoRegistrationEventRecordMapper.java b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoRegistrationEventRecordMapper.java new file mode 100644 index 0000000..ad96232 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoRegistrationEventRecordMapper.java @@ -0,0 +1,106 @@ +package com.imyeyu.api.modules.gao.mapper; + +import com.imyeyu.api.modules.gao.entity.GaoRegistrationEventRecord; +import com.imyeyu.api.modules.gao.vo.GaoRegistrationEventPeriodStatView; +import com.imyeyu.api.modules.gao.vo.GaoCustomerRegisterStatView; +import com.imyeyu.api.modules.gao.vo.GaoRegistrationEventRecordDailyStatView; +import com.imyeyu.api.modules.gao.vo.GaoRegistrationEventRecordPeriodReq; +import com.imyeyu.api.modules.gao.vo.GaoRegistrationEventRecordRangeReq; +import com.imyeyu.spring.mapper.BaseMapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/// +/// 登记事件记录 Mapper +/// +/// @author Codex +/// @since 2026-07-27 +public interface GaoRegistrationEventRecordMapper extends BaseMapper { + + /// 按登记事件查询客户最后登记事件记录 + /// + /// @param customerIdList 客户 ID 列表 + /// @param registrationEventId 登记事件 ID + /// @return 最后登记事件记录列表 + List selectByRegistrationEventAndPeriod(@Param("registrationEventId") String registrationEventId, @Param("periodType") GaoRegistrationEventRecordPeriodReq.PeriodType periodType, @Param("periodValue") Integer periodValue); + + /// 按周期统计登记事件记录 + /// + /// @param registrationEventId 登记事件 ID + /// @param periodType 周期类型 + /// @return 统计结果 + List statByRegistrationEventAndPeriodType(@Param("registrationEventId") String registrationEventId, @Param("periodType") GaoRegistrationEventRecordPeriodReq.PeriodType periodType); + + /// 统计客户所有登记 + /// + /// @param customerId 客户 ID + /// @return 统计结果 + List statByCustomerId(@Param("customerId") String customerId); + + /// 按时间段统计单个客户登记 + /// + /// @param customerId 客户 ID + /// @param beginRegisteredAt 开始登记时间 + /// @param endRegisteredAt 结束登记时间 + /// @return 统计结果 + List statByCustomerIdAndRange(@Param("customerId") String customerId, @Param("beginRegisteredAt") Long beginRegisteredAt, @Param("endRegisteredAt") Long endRegisteredAt); + + /// 按时间段统计全部客户登记 + /// + /// @param beginRegisteredAt 开始登记时间 + /// @param endRegisteredAt 结束登记时间 + /// @return 统计结果 + List statAllCustomerByRange(@Param("beginRegisteredAt") Long beginRegisteredAt, @Param("endRegisteredAt") Long endRegisteredAt); + + /// 统计单个客户单个登记事件的每日登记事件记录数 + /// + /// @param customerId 客户 ID + /// @param registrationEventId 登记事件 ID + /// @param beginRegisteredAt 开始登记时间 + /// @param endRegisteredAt 结束登记时间 + /// @return 每日统计 + List statDailyCountByCustomerIdAndRegistrationEventIdRange(@Param("customerId") String customerId, @Param("registrationEventId") String registrationEventId, @Param("beginRegisteredAt") Long beginRegisteredAt, @Param("endRegisteredAt") Long endRegisteredAt); + + /// 统计全部客户单个登记事件的每日登记事件记录数 + /// + /// @param registrationEventId 登记事件 ID + /// @param beginRegisteredAt 开始登记时间 + /// @param endRegisteredAt 结束登记时间 + /// @return 每日统计 + List statDailyCountByRegistrationEventIdRange(@Param("registrationEventId") String registrationEventId, @Param("beginRegisteredAt") Long beginRegisteredAt, @Param("endRegisteredAt") Long endRegisteredAt); + + /// 统计时间段前单个客户单个登记事件累计登记事件记录数 + /// + /// @param customerId 客户 ID + /// @param registrationEventId 登记事件 ID + /// @param beginRegisteredAt 开始登记时间 + /// @return 累计登记数 + Long countBeforeByCustomerIdAndRegistrationEventId(@Param("customerId") String customerId, @Param("registrationEventId") String registrationEventId, @Param("beginRegisteredAt") Long beginRegisteredAt); + + /// 统计时间段前全部客户单个登记事件累计登记事件记录数 + /// + /// @param registrationEventId 登记事件 ID + /// @param beginRegisteredAt 开始登记时间 + /// @return 累计登记数 + Long countBeforeByRegistrationEventId(@Param("registrationEventId") String registrationEventId, @Param("beginRegisteredAt") Long beginRegisteredAt); + + /// 查询客户指定登记事件的最后登记事件记录 + /// + /// @param customerIdList 客户 ID 列表 + /// @param registrationEventId 登记事件 ID + /// @return 最后登记事件记录列表 + List selectLatestByCustomerIdListAndRegistrationEventId(@Param("customerIdList") List customerIdList, @Param("registrationEventId") String registrationEventId); + + /// 查询客户全部登记事件的最后登记事件记录 + /// + /// @param customerId 客户 ID + /// @return 最后登记事件记录列表 + List selectLatestByCustomerId(@Param("customerId") String customerId); + + /// 按任意时间范围查询登记事件记录 + /// + /// @param req 范围查询请求 + /// @return 登记事件记录列表 + List selectByRange(@Param("req") GaoRegistrationEventRecordRangeReq req); +} diff --git a/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoRegistrationEventRoleRelationMapper.java b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoRegistrationEventRoleRelationMapper.java new file mode 100644 index 0000000..7d10ae1 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoRegistrationEventRoleRelationMapper.java @@ -0,0 +1,23 @@ +package com.imyeyu.api.modules.gao.mapper; + +import com.imyeyu.api.modules.gao.entity.GaoRegistrationEventRoleRelation; +import com.imyeyu.spring.mapper.BaseMapper; +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Select; +import org.apache.ibatis.annotations.Update; + +import java.util.List; + +/// +/// 登记事件角色关联 Mapper +/// +/// @author Codex +/// @since 2026-07-28 +public interface GaoRegistrationEventRoleRelationMapper extends BaseMapper { + + @Select("SELECT `role_code` FROM `gao_registration_event_role_relation` WHERE `registration_event_id` = #{registrationEventId} AND `deleted_at` IS NULL ORDER BY `created_at` ASC") + List selectRoleCodeListByRegistrationEventId(@Param("registrationEventId") String registrationEventId); + + @Update("UPDATE `gao_registration_event_role_relation` SET `deleted_at` = UNIX_TIMESTAMP() WHERE `registration_event_id` = #{registrationEventId} AND `deleted_at` IS NULL") + void deleteByRegistrationEventId(@Param("registrationEventId") String registrationEventId); +} diff --git a/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoUserMapper.java b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoUserMapper.java new file mode 100644 index 0000000..88d550c --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoUserMapper.java @@ -0,0 +1,17 @@ +package com.imyeyu.api.modules.gao.mapper; + +import com.imyeyu.api.modules.gao.entity.GaoUser; +import com.imyeyu.spring.mapper.BaseMapper; +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Select; + +/// +/// GAO 用户 Mapper +/// +/// @author Codex +/// @since 2026-07-30 +public interface GaoUserMapper extends BaseMapper { + + @Select("SELECT * FROM `gao_user` WHERE `user_id` = #{userId} AND `deleted_at` IS NULL LIMIT 1") + GaoUser selectByUserId(@Param("userId") String userId); +} diff --git a/src/main/java/com/imyeyu/api/modules/gao/service/GaoCustomerService.java b/src/main/java/com/imyeyu/api/modules/gao/service/GaoCustomerService.java new file mode 100644 index 0000000..a6e92bf --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/service/GaoCustomerService.java @@ -0,0 +1,64 @@ +package com.imyeyu.api.modules.gao.service; + +import com.imyeyu.api.modules.gao.entity.GaoCustomer; +import com.imyeyu.api.modules.gao.vo.GaoCustomerDailyStatView; +import com.imyeyu.api.modules.gao.vo.GaoCustomerInviteUpdateReq; +import com.imyeyu.api.modules.gao.vo.GaoCustomerSearchReq; +import com.imyeyu.api.modules.gao.vo.GaoCustomerTrendStatReq; +import com.imyeyu.spring.service.BaseService; + +import java.util.List; + +/// +/// 客户服务 +/// +/// @author Codex +/// @since 2026-07-27 +public interface GaoCustomerService extends BaseService { + + /// 按编码查询客户 + /// + /// @param code 客户编码 + /// @return 客户 + GaoCustomer getByCode(String code); + + /// 按编码查询客户,不存在时返回 null + /// + /// @param code 客户编码 + /// @return 客户 + GaoCustomer getNullableByCode(String code); + + /// 按编码获取或快速创建客户 + /// + /// @param code 客户编码 + /// @return 客户 + GaoCustomer getOrQuickCreateByCode(String code); + + /// 搜索客户 + /// + /// @param req 搜索请求 + /// @return 客户列表 + List search(GaoCustomerSearchReq req); + + /// 查询邀请的客户 + /// + /// @param introducerCustomerId 介绍人客户 ID + /// @return 被邀请客户列表 + List listInvited(String introducerCustomerId); + + /// 保存邀请关系 + /// + /// @param req 邀请关系请求 + void saveInviteRelation(GaoCustomerInviteUpdateReq req); + + /// 删除邀请关系 + /// + /// @param customerId 客户 ID + void deleteInviteRelation(String customerId); + + /// 统计客户每日趋势 + /// + /// @param req 趋势统计请求 + /// @return 每日趋势列表 + List statDailyTrend(GaoCustomerTrendStatReq req); +} diff --git a/src/main/java/com/imyeyu/api/modules/gao/service/GaoRegistrationEventRecordService.java b/src/main/java/com/imyeyu/api/modules/gao/service/GaoRegistrationEventRecordService.java new file mode 100644 index 0000000..29f0fd5 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/service/GaoRegistrationEventRecordService.java @@ -0,0 +1,97 @@ +package com.imyeyu.api.modules.gao.service; + +import com.imyeyu.api.modules.gao.entity.GaoRegistrationEventRecord; +import com.imyeyu.api.modules.gao.vo.GaoRegistrationEventPeriodStatView; +import com.imyeyu.api.modules.gao.vo.GaoCustomerRegisterStatView; +import com.imyeyu.api.modules.gao.vo.GaoQuickRegistrationEventRecordReq; +import com.imyeyu.api.modules.gao.vo.GaoRegistrationEventRecordCreateReq; +import com.imyeyu.api.modules.gao.vo.GaoRegistrationEventRecordDailyStatView; +import com.imyeyu.api.modules.gao.vo.GaoRegistrationEventRecordPeriodReq; +import com.imyeyu.api.modules.gao.vo.GaoRegistrationEventRecordRangeReq; +import com.imyeyu.api.modules.gao.vo.GaoRegistrationEventRecordStatReq; +import com.imyeyu.api.modules.gao.vo.GaoRegistrationEventRecordTrendStatReq; +import com.imyeyu.spring.service.BaseService; + +import java.util.List; + +/// +/// 登记事件记录服务 +/// +/// @author Codex +/// @since 2026-07-27 +public interface GaoRegistrationEventRecordService extends BaseService { + + /// 创建登记事件记录 + /// + /// @param req 创建请求 + /// @return 创建后的登记事件记录 + GaoRegistrationEventRecord createRecord(GaoRegistrationEventRecordCreateReq req); + + /// 快速录入客户并创建登记事件记录 + /// + /// @param req 快速录入请求 + /// @return 创建后的登记事件记录 + GaoRegistrationEventRecord quickRegister(GaoQuickRegistrationEventRecordReq req); + + /// 按任意时间范围查询登记事件记录 + /// + /// @param req 范围查询请求 + /// @return 登记事件记录列表 + List listByRange(GaoRegistrationEventRecordRangeReq req); + + /// 按周期查询登记事件记录 + /// + /// @param req 周期请求 + /// @return 登记事件记录列表 + List listByRegistrationEventAndPeriod(GaoRegistrationEventRecordPeriodReq req); + + /// 按周期统计登记事件记录 + /// + /// @param registrationEventId 登记事件 ID + /// @param periodType 周期类型 + /// @return 统计列表 + List statByRegistrationEventAndPeriodType(String registrationEventId, GaoRegistrationEventRecordPeriodReq.PeriodType periodType); + + /// 查询客户指定登记事件的最后登记事件记录 + /// + /// @param customerIdList 客户 ID 列表 + /// @param registrationEventId 登记事件 ID + /// @return 每个客户的最后登记事件记录 + List listLatestByCustomerIdListAndRegistrationEventId(List customerIdList, String registrationEventId); + + /// 统计客户所有登记 + /// + /// @param customerId 客户 ID + /// @return 统计结果 + List statByCustomerId(String customerId); + + /// 按时间段统计单个客户登记 + /// + /// @param req 统计请求 + /// @return 统计结果 + List statByCustomerAndRange(GaoRegistrationEventRecordStatReq req); + + /// 按时间段统计全部客户登记 + /// + /// @param req 统计请求 + /// @return 统计结果 + List statAllCustomerByRange(GaoRegistrationEventRecordStatReq req); + + /// 按时间段统计单个客户单个登记事件每日趋势 + /// + /// @param req 趋势统计请求 + /// @return 每日趋势列表 + List statDailyByCustomerAndRegistrationEvent(GaoRegistrationEventRecordTrendStatReq req); + + /// 按时间段统计全部客户单个登记事件每日趋势 + /// + /// @param req 趋势统计请求 + /// @return 每日趋势列表 + List statDailyByRegistrationEvent(GaoRegistrationEventRecordTrendStatReq req); + + /// 查询客户全部登记事件的最后登记事件记录 + /// + /// @param customerId 客户 ID + /// @return 每个登记事件的最后登记事件记录 + List listLatestByCustomerId(String customerId); +} diff --git a/src/main/java/com/imyeyu/api/modules/gao/service/GaoRegistrationEventService.java b/src/main/java/com/imyeyu/api/modules/gao/service/GaoRegistrationEventService.java new file mode 100644 index 0000000..893a77a --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/service/GaoRegistrationEventService.java @@ -0,0 +1,33 @@ +package com.imyeyu.api.modules.gao.service; + +import com.imyeyu.api.modules.gao.entity.GaoRegistrationEvent; +import com.imyeyu.spring.bean.Page; +import com.imyeyu.spring.bean.PageResult; +import com.imyeyu.spring.service.BaseService; + +import java.util.List; + +/// +/// 登记事件服务 +/// +/// @author Codex +/// @since 2026-07-27 +public interface GaoRegistrationEventService extends BaseService { + + /// 分页查询登记事件并填充可登记角色 + /// + /// @param page 分页参数 + /// @return 登记事件分页 + PageResult pageWithRegistrationEventLimit(Page page); + + /// 查询启用的登记事件 + /// + /// @return 登记事件列表 + List listEnabled(); + + /// 查询登记事件要求的角色 + /// + /// @param registrationEventId 登记事件 ID + /// @return 角色代码列表 + List listRoleCode(String registrationEventId); +} diff --git a/src/main/java/com/imyeyu/api/modules/gao/service/GaoUserService.java b/src/main/java/com/imyeyu/api/modules/gao/service/GaoUserService.java new file mode 100644 index 0000000..a7d3efd --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/service/GaoUserService.java @@ -0,0 +1,26 @@ +package com.imyeyu.api.modules.gao.service; + +import com.imyeyu.api.modules.gao.entity.GaoUser; +import com.imyeyu.spring.bean.Page; +import com.imyeyu.spring.bean.PageResult; +import com.imyeyu.spring.service.BaseService; + +/// +/// GAO 用户服务 +/// +/// @author Codex +/// @since 2026-07-30 +public interface GaoUserService extends BaseService { + + /// 按用户 ID 查询 GAO 用户 + /// + /// @param userId 用户 ID + /// @return GAO 用户 + GaoUser getByUserId(String userId); + + /// 分页查询并填充用户详情 + /// + /// @param page 分页请求 + /// @return GAO 用户分页 + PageResult pageWithUser(Page page); +} diff --git a/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoCustomerServiceImplement.java b/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoCustomerServiceImplement.java new file mode 100644 index 0000000..2ec356a --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoCustomerServiceImplement.java @@ -0,0 +1,243 @@ +package com.imyeyu.api.modules.gao.service.implement; + +import com.imyeyu.api.config.dbsource.TimiServerDBConfig; +import com.imyeyu.api.modules.common.entity.Attachment; +import com.imyeyu.api.modules.common.service.AttachmentService; +import com.imyeyu.api.modules.common.vo.attach.BizUpdateReq; +import com.imyeyu.api.modules.gao.entity.GaoCustomer; +import com.imyeyu.api.modules.gao.mapper.GaoCustomerMapper; +import com.imyeyu.api.modules.gao.service.GaoCustomerService; +import com.imyeyu.api.modules.gao.vo.GaoCustomerDailyStatView; +import com.imyeyu.api.modules.gao.vo.GaoCustomerInviteUpdateReq; +import com.imyeyu.api.modules.gao.vo.GaoCustomerSearchReq; +import com.imyeyu.api.modules.gao.vo.GaoCustomerTrendStatReq; +import com.imyeyu.java.TimiJava; +import com.imyeyu.java.bean.timi.TimiCode; +import com.imyeyu.java.bean.timi.TimiException; +import com.imyeyu.spring.mapper.BaseMapper; +import com.imyeyu.spring.service.AbstractEntityService; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.Instant; +import java.time.LocalDate; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import java.time.temporal.ChronoUnit; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/// +/// 客户服务实现 +/// +/// @author Codex +/// @since 2026-07-27 +@Service +@RequiredArgsConstructor +public class GaoCustomerServiceImplement extends AbstractEntityService implements GaoCustomerService { + + private static final DateTimeFormatter DAY_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd"); + + private final GaoCustomerMapper mapper; + private final AttachmentService attachmentService; + + @Override + protected BaseMapper mapper() { + return mapper; + } + + @Override + public void create(GaoCustomer entity) { + checkEntity(entity, false); + super.create(entity); + saveAttachment(entity.getId(), resolveAttachmentList(entity)); + loadTransient(entity); + } + + @Override + public void update(GaoCustomer entity) { + checkEntity(entity, true); + super.update(entity); + saveAttachment(entity.getId(), resolveAttachmentList(entity)); + } + + @Override + public GaoCustomer get(String id) { + GaoCustomer customer = super.get(id); + loadTransient(customer); + return customer; + } + + @Override + public GaoCustomer getByCode(String code) { + TimiException.required(code, "not found customer.code"); + GaoCustomer customer = mapper.selectByCode(code); + TimiException.required(customer, "not found customer"); + loadTransient(customer); + return customer; + } + + @Override + public GaoCustomer getNullableByCode(String code) { + TimiException.required(code, "not found customer.code"); + GaoCustomer customer = mapper.selectByCode(code); + loadTransient(customer); + return customer; + } + + @Transactional(TimiServerDBConfig.ROLLBACKER) + @Override + public GaoCustomer getOrQuickCreateByCode(String code) { + GaoCustomer customer = getNullableByCode(code); + if (customer != null) { + return customer; + } + customer = new GaoCustomer(); + customer.setCode(code); + customer.setName(code); + create(customer); + return customer; + } + + @Override + public List search(GaoCustomerSearchReq req) { + if (req == null) { + req = new GaoCustomerSearchReq(); + } + List list = mapper.search(req.getKeyword(), req.getIntroducerCustomerId()); + list.forEach(this::loadTransient); + return list; + } + + @Override + public List listInvited(String introducerCustomerId) { + TimiException.required(introducerCustomerId, "not found introducerCustomerId"); + List list = mapper.selectInvitedList(introducerCustomerId); + list.forEach(this::loadTransient); + return list; + } + + @Transactional(TimiServerDBConfig.ROLLBACKER) + @Override + public void saveInviteRelation(GaoCustomerInviteUpdateReq req) { + TimiException.required(req, "not found invite req"); + TimiException.required(req.getCustomerId(), "not found invite req.customerId"); + GaoCustomer customer = super.get(req.getCustomerId()); + customer.setIntroducerCustomerId(req.getIntroducerCustomerId()); + checkEntity(customer, true); + super.update(customer); + } + + @Transactional(TimiServerDBConfig.ROLLBACKER) + @Override + public void deleteInviteRelation(String customerId) { + TimiException.required(customerId, "not found customerId"); + GaoCustomer customer = super.get(customerId); + customer.setIntroducerCustomerId(null); + super.update(customer); + } + + @Override + public List statDailyTrend(GaoCustomerTrendStatReq req) { + TimiException.required(req, "not found req"); + TimiException.required(req.getBeginCreatedAt(), "not found req.beginCreatedAt"); + TimiException.required(req.getEndCreatedAt(), "not found req.endCreatedAt"); + TimiException.requiredTrue(req.getEndCreatedAt() >= req.getBeginCreatedAt(), "req.endCreatedAt lt req.beginCreatedAt"); + ZoneId zoneId = ZoneId.systemDefault(); + LocalDate beginDate = Instant.ofEpochMilli(req.getBeginCreatedAt()).atZone(zoneId).toLocalDate(); + LocalDate endDate = Instant.ofEpochMilli(req.getEndCreatedAt()).atZone(zoneId).toLocalDate(); + checkDailyStatRange(beginDate, endDate); + long beginCreatedAt = beginDate.atStartOfDay(zoneId).toInstant().toEpochMilli(); + long endCreatedAt = endDate.plusDays(1).atStartOfDay(zoneId).toInstant().toEpochMilli() - 1; + Map newCustomerCountMap = new HashMap<>(); + for (GaoCustomerDailyStatView view : mapper.statDailyNewCustomer(beginCreatedAt, endCreatedAt)) { + newCustomerCountMap.put(view.getDateValue(), TimiJava.defaultIfNull(view.getNewCustomerCount(), 0L)); + } + long totalCustomerCount = TimiJava.defaultIfNull(mapper.countCreatedBefore(beginCreatedAt), 0L); + List list = new ArrayList<>(); + for (LocalDate date = beginDate; !date.isAfter(endDate); date = date.plusDays(1)) { + int dateValue = Integer.parseInt(DAY_FORMATTER.format(date)); + long newCustomerCount = TimiJava.defaultIfNull(newCustomerCountMap.get(dateValue), 0L); + totalCustomerCount += newCustomerCount; + GaoCustomerDailyStatView view = new GaoCustomerDailyStatView(); + view.setDateValue(dateValue); + view.setNewCustomerCount(newCustomerCount); + view.setTotalCustomerCount(totalCustomerCount); + list.add(view); + } + return list; + } + + private void checkDailyStatRange(LocalDate beginDate, LocalDate endDate) { + long days = ChronoUnit.DAYS.between(beginDate, endDate) + 1; + TimiException.requiredTrue(days <= 366, "daily stat range too large"); + } + + private void checkEntity(GaoCustomer entity, boolean requireId) { + TimiException.required(entity, "not found customer"); + if (requireId) { + TimiException.required(entity.getId(), "not found customer.id"); + } + TimiException.required(entity.getCode(), "not found customer.code"); + TimiException.required(entity.getName(), "not found customer.name"); + GaoCustomer dbCustomer = mapper.selectByCode(entity.getCode()); + if (dbCustomer != null && !dbCustomer.getId().equals(entity.getId())) { + throw new TimiException(TimiCode.ARG_BAD).msgKey("客户编码已存在"); + } + if (TimiJava.isNotEmpty(entity.getIntroducerCustomerId())) { + TimiException.requiredTrue(!entity.getIntroducerCustomerId().equals(entity.getId()), "customer.introducerCustomerId invalid"); + TimiException.required(super.get(entity.getIntroducerCustomerId()), "not found introducer customer"); + } + } + + private void loadTransient(GaoCustomer customer) { + if (customer == null) { + return; + } + if (TimiJava.isNotEmpty(customer.getIntroducerCustomerId())) { + customer.setIntroducerCustomer(super.get(customer.getIntroducerCustomerId())); + } + customer.setInvitedCount(mapper.countInvited(customer.getId())); + customer.setAttachmentList(attachmentService.listByBizId(Attachment.BizType.GAO_CUSTOMER, customer.getId(), GaoCustomer.AttachType.PHOTO.name())); + } + + private void saveAttachment(String customerId, List attachmentList) { + if (attachmentList == null) { + return; + } + for (Attachment attachment : attachmentList) { + attachment.setBizType(Attachment.BizType.GAO_CUSTOMER); + attachment.setBizId(customerId); + attachment.setAttachType(GaoCustomer.AttachType.PHOTO.name()); + } + BizUpdateReq req = new BizUpdateReq(); + req.setBizType(Attachment.BizType.GAO_CUSTOMER); + req.setBizId(customerId); + req.setItems(attachmentList); + attachmentService.updateByBiz(req); + } + + private List resolveAttachmentList(GaoCustomer customer) { + if (customer.getAttachmentIdList() != null) { + return buildPhotoAttachmentList(customer.getAttachmentIdList()); + } + return customer.getAttachmentList(); + } + + private List buildPhotoAttachmentList(List attachmentIdList) { + List list = new ArrayList<>(); + for (String attachmentId : attachmentIdList) { + if (attachmentId == null || attachmentId.isBlank()) { + continue; + } + Attachment attachment = new Attachment(); + attachment.setTempFileId(attachmentId.trim()); + attachment.setAttachType(GaoCustomer.AttachType.PHOTO.name()); + list.add(attachment); + } + return list; + } +} diff --git a/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoRegistrationEventRecordServiceImplement.java b/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoRegistrationEventRecordServiceImplement.java new file mode 100644 index 0000000..1958453 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoRegistrationEventRecordServiceImplement.java @@ -0,0 +1,325 @@ +package com.imyeyu.api.modules.gao.service.implement; + +import com.imyeyu.api.config.dbsource.TimiServerDBConfig; +import com.imyeyu.api.bean.ModuleCode; +import com.imyeyu.api.modules.common.entity.Attachment; +import com.imyeyu.api.modules.common.service.AttachmentService; +import com.imyeyu.api.modules.common.vo.attach.BizUpdateReq; +import com.imyeyu.api.modules.gao.entity.GaoCustomer; +import com.imyeyu.api.modules.gao.entity.GaoRegistrationEvent; +import com.imyeyu.api.modules.gao.entity.GaoRegistrationEventRecord; +import com.imyeyu.api.modules.gao.mapper.GaoRegistrationEventRecordMapper; +import com.imyeyu.api.modules.gao.service.GaoCustomerService; +import com.imyeyu.api.modules.gao.service.GaoRegistrationEventService; +import com.imyeyu.api.modules.gao.service.GaoRegistrationEventRecordService; +import com.imyeyu.api.modules.gao.vo.GaoRegistrationEventPeriodStatView; +import com.imyeyu.api.modules.gao.vo.GaoCustomerRegisterStatView; +import com.imyeyu.api.modules.gao.vo.GaoQuickRegistrationEventRecordReq; +import com.imyeyu.api.modules.gao.vo.GaoRegistrationEventRecordCreateReq; +import com.imyeyu.api.modules.gao.vo.GaoRegistrationEventRecordDailyStatView; +import com.imyeyu.api.modules.gao.vo.GaoRegistrationEventRecordPeriodReq; +import com.imyeyu.api.modules.gao.vo.GaoRegistrationEventRecordRangeReq; +import com.imyeyu.api.modules.gao.vo.GaoRegistrationEventRecordStatReq; +import com.imyeyu.api.modules.gao.vo.GaoRegistrationEventRecordTrendStatReq; +import com.imyeyu.api.modules.user.service.RoleChecker; +import com.imyeyu.api.modules.user.service.UserLoginService; +import com.imyeyu.api.modules.user.service.UserService; +import com.imyeyu.java.TimiJava; +import com.imyeyu.java.bean.timi.TimiException; +import com.imyeyu.spring.mapper.BaseMapper; +import com.imyeyu.spring.service.AbstractEntityService; +import com.imyeyu.utils.Time; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.Instant; +import java.time.LocalDate; +import java.time.ZoneId; +import java.time.temporal.ChronoUnit; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/// +/// 登记事件记录服务实现 +/// +/// @author Codex +/// @since 2026-07-27 +@Service +@RequiredArgsConstructor +public class GaoRegistrationEventRecordServiceImplement extends AbstractEntityService implements GaoRegistrationEventRecordService { + + private static final DateTimeFormatter DAY_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd"); + + private final GaoRegistrationEventRecordMapper mapper; + private final AttachmentService attachmentService; + private final RoleChecker roleChecker; + private final UserLoginService userLoginService; + private final UserService userService; + private final GaoCustomerService customerService; + private final GaoRegistrationEventService registrationEventService; + + @Override + protected BaseMapper mapper() { + return mapper; + } + + @Transactional(TimiServerDBConfig.ROLLBACKER) + @Override + public GaoRegistrationEventRecord createRecord(GaoRegistrationEventRecordCreateReq req) { + TimiException.required(req, "not found req"); + TimiException.required(req.getRegistrationEventId(), "not found req.registrationEventId"); + String customerId = resolveCustomerId(req); + GaoRegistrationEvent registrationEvent = registrationEventService.get(req.getRegistrationEventId()); + checkRegistrationEventPermission(registrationEvent); + if (Boolean.TRUE.equals(registrationEvent.getRequirePhoto())) { + TimiException.requiredTrue(req.getAttachmentIdList() != null && !req.getAttachmentIdList().isEmpty(), "required record photo"); + } + + GaoRegistrationEventRecord record = new GaoRegistrationEventRecord(); + record.setCustomerId(customerId); + record.setRegistrationEventId(req.getRegistrationEventId()); + record.setOperatorUserId(userLoginService.getRequireLoginUserId()); + record.setRemark(req.getRemark()); + record.setRegisteredAt(TimiJava.defaultIfNull(req.getRegisteredAt(), Time.now())); + super.create(record); + saveAttachment(record.getId(), buildPhotoAttachmentList(req.getAttachmentIdList())); + return get(record.getId()); + } + + @Transactional(TimiServerDBConfig.ROLLBACKER) + @Override + public GaoRegistrationEventRecord quickRegister(GaoQuickRegistrationEventRecordReq req) { + TimiException.required(req, "not found req"); + TimiException.required(req.getCustomerCode(), "not found req.customerCode"); + TimiException.required(req.getRegistrationEventId(), "not found req.registrationEventId"); + GaoCustomer customer = customerService.getOrQuickCreateByCode(req.getCustomerCode()); + GaoRegistrationEventRecordCreateReq recordReq = new GaoRegistrationEventRecordCreateReq(); + recordReq.setCustomerId(customer.getId()); + recordReq.setRegistrationEventId(req.getRegistrationEventId()); + recordReq.setRemark(req.getRemark()); + recordReq.setAttachmentIdList(req.getAttachmentIdList()); + return createRecord(recordReq); + } + + @Transactional(TimiServerDBConfig.ROLLBACKER) + @Override + public void update(GaoRegistrationEventRecord entity) { + TimiException.required(entity, "not found record"); + TimiException.required(entity.getId(), "not found record.id"); + TimiException.required(entity.getCustomerId(), "not found record.customerId"); + TimiException.required(entity.getRegistrationEventId(), "not found record.registrationEventId"); + customerService.get(entity.getCustomerId()); + GaoRegistrationEvent registrationEvent = registrationEventService.get(entity.getRegistrationEventId()); + checkRegistrationEventPermission(registrationEvent); + entity.setRegisteredAt(TimiJava.defaultIfNull(entity.getRegisteredAt(), Time.now())); + super.update(entity); + saveAttachment(entity.getId(), resolveAttachmentList(entity)); + } + + @Override + public GaoRegistrationEventRecord get(String id) { + GaoRegistrationEventRecord record = super.get(id); + loadTransient(record); + return record; + } + + @Override + public List listByRegistrationEventAndPeriod(GaoRegistrationEventRecordPeriodReq req) { + TimiException.required(req, "not found req"); + TimiException.required(req.getRegistrationEventId(), "not found req.registrationEventId"); + TimiException.required(req.getPeriodType(), "not found req.periodType"); + TimiException.required(req.getPeriodValue(), "not found req.periodValue"); + List list = mapper.selectByRegistrationEventAndPeriod(req.getRegistrationEventId(), req.getPeriodType(), req.getPeriodValue()); + list.forEach(this::loadTransient); + return list; + } + + @Override + public List listByRange(GaoRegistrationEventRecordRangeReq req) { + TimiException.required(req, "not found req"); + checkStatRange(req.getBeginRegisteredAt(), req.getEndRegisteredAt()); + List list = mapper.selectByRange(req); + list.forEach(this::loadTransient); + return list; + } + + @Override + public List statByRegistrationEventAndPeriodType(String registrationEventId, GaoRegistrationEventRecordPeriodReq.PeriodType periodType) { + TimiException.required(registrationEventId, "not found registrationEventId"); + TimiException.required(periodType, "not found periodType"); + return mapper.statByRegistrationEventAndPeriodType(registrationEventId, periodType); + } + + @Override + public List listLatestByCustomerIdListAndRegistrationEventId(List customerIdList, String registrationEventId) { + TimiException.required(registrationEventId, "not found registrationEventId"); + if (TimiJava.isEmpty(customerIdList)) { + return Collections.emptyList(); + } + List list = mapper.selectLatestByCustomerIdListAndRegistrationEventId(customerIdList, registrationEventId); + list.forEach(this::loadTransient); + return list; + } + + @Override + public List statByCustomerId(String customerId) { + TimiException.required(customerId, "not found customerId"); + return mapper.statByCustomerId(customerId); + } + + @Override + public List statByCustomerAndRange(GaoRegistrationEventRecordStatReq req) { + TimiException.required(req, "not found req"); + TimiException.required(req.getCustomerId(), "not found req.customerId"); + checkStatRange(req.getBeginRegisteredAt(), req.getEndRegisteredAt()); + return mapper.statByCustomerIdAndRange(req.getCustomerId(), req.getBeginRegisteredAt(), req.getEndRegisteredAt()); + } + + @Override + public List statAllCustomerByRange(GaoRegistrationEventRecordStatReq req) { + TimiException.required(req, "not found req"); + checkStatRange(req.getBeginRegisteredAt(), req.getEndRegisteredAt()); + return mapper.statAllCustomerByRange(req.getBeginRegisteredAt(), req.getEndRegisteredAt()); + } + + @Override + public List statDailyByCustomerAndRegistrationEvent(GaoRegistrationEventRecordTrendStatReq req) { + TimiException.required(req, "not found req"); + TimiException.required(req.getCustomerId(), "not found req.customerId"); + customerService.get(req.getCustomerId()); + return buildDailyTrend(req, true); + } + + @Override + public List statDailyByRegistrationEvent(GaoRegistrationEventRecordTrendStatReq req) { + TimiException.required(req, "not found req"); + return buildDailyTrend(req, false); + } + + @Override + public List listLatestByCustomerId(String customerId) { + TimiException.required(customerId, "not found customerId"); + List list = mapper.selectLatestByCustomerId(customerId); + list.forEach(this::loadTransient); + return list; + } + + private String resolveCustomerId(GaoRegistrationEventRecordCreateReq req) { + if (TimiJava.isNotEmpty(req.getCustomerId())) { + customerService.get(req.getCustomerId()); + return req.getCustomerId(); + } + TimiException.required(req.getCustomerCode(), "not found req.customerCode"); + GaoCustomer customer = customerService.getByCode(req.getCustomerCode()); + return customer.getId(); + } + + private void checkRegistrationEventPermission(GaoRegistrationEvent registrationEvent) { + List roleCodeList = registrationEventService.listRoleCode(registrationEvent.getId()); + if (roleCodeList != null && !roleCodeList.isEmpty()) { + roleChecker.checkAny(ModuleCode.GAO, roleCodeList.toArray(String[]::new)); + } + } + + private void checkStatRange(Long beginRegisteredAt, Long endRegisteredAt) { + TimiException.required(beginRegisteredAt, "not found beginRegisteredAt"); + TimiException.required(endRegisteredAt, "not found endRegisteredAt"); + TimiException.requiredTrue(endRegisteredAt >= beginRegisteredAt, "endRegisteredAt lt beginRegisteredAt"); + } + + private List buildDailyTrend(GaoRegistrationEventRecordTrendStatReq req, boolean byCustomer) { + TimiException.required(req.getRegistrationEventId(), "not found req.registrationEventId"); + registrationEventService.get(req.getRegistrationEventId()); + checkStatRange(req.getBeginRegisteredAt(), req.getEndRegisteredAt()); + ZoneId zoneId = ZoneId.systemDefault(); + LocalDate beginDate = Instant.ofEpochMilli(req.getBeginRegisteredAt()).atZone(zoneId).toLocalDate(); + LocalDate endDate = Instant.ofEpochMilli(req.getEndRegisteredAt()).atZone(zoneId).toLocalDate(); + checkDailyStatRange(beginDate, endDate); + long beginRegisteredAt = beginDate.atStartOfDay(zoneId).toInstant().toEpochMilli(); + long endRegisteredAt = endDate.plusDays(1).atStartOfDay(zoneId).toInstant().toEpochMilli() - 1; + Map dailyCountMap = new HashMap<>(); + List dbList = byCustomer + ? mapper.statDailyCountByCustomerIdAndRegistrationEventIdRange(req.getCustomerId(), req.getRegistrationEventId(), beginRegisteredAt, endRegisteredAt) + : mapper.statDailyCountByRegistrationEventIdRange(req.getRegistrationEventId(), beginRegisteredAt, endRegisteredAt); + for (GaoRegistrationEventRecordDailyStatView view : dbList) { + dailyCountMap.put(view.getDateValue(), TimiJava.defaultIfNull(view.getDailyCount(), 0L)); + } + long totalCount = byCustomer + ? TimiJava.defaultIfNull(mapper.countBeforeByCustomerIdAndRegistrationEventId(req.getCustomerId(), req.getRegistrationEventId(), beginRegisteredAt), 0L) + : TimiJava.defaultIfNull(mapper.countBeforeByRegistrationEventId(req.getRegistrationEventId(), beginRegisteredAt), 0L); + List list = new ArrayList<>(); + for (LocalDate date = beginDate; !date.isAfter(endDate); date = date.plusDays(1)) { + int dateValue = Integer.parseInt(DAY_FORMATTER.format(date)); + long dailyCount = TimiJava.defaultIfNull(dailyCountMap.get(dateValue), 0L); + totalCount += dailyCount; + GaoRegistrationEventRecordDailyStatView view = new GaoRegistrationEventRecordDailyStatView(); + view.setDateValue(dateValue); + view.setDailyCount(dailyCount); + view.setTotalCount(totalCount); + list.add(view); + } + return list; + } + + private void loadTransient(GaoRegistrationEventRecord record) { + if (record == null) { + return; + } + record.setCustomer(customerService.get(record.getCustomerId())); + record.setRegistrationEvent(registrationEventService.get(record.getRegistrationEventId())); + if (TimiJava.isNotEmpty(record.getOperatorUserId())) { + record.setOperator(userService.get(record.getOperatorUserId())); + } + record.setAttachmentList(attachmentService.listByBizId(Attachment.BizType.GAO_REGISTRATION_EVENT_RECORD, record.getId(), GaoRegistrationEventRecord.AttachType.PHOTO.name())); + } + + private void checkDailyStatRange(LocalDate beginDate, LocalDate endDate) { + long days = ChronoUnit.DAYS.between(beginDate, endDate) + 1; + TimiException.requiredTrue(days <= 366, "daily stat range too large"); + } + + private void saveAttachment(String recordId, List attachmentList) { + if (attachmentList == null) { + return; + } + for (Attachment attachment : attachmentList) { + attachment.setBizType(Attachment.BizType.GAO_REGISTRATION_EVENT_RECORD); + attachment.setBizId(recordId); + attachment.setAttachType(GaoRegistrationEventRecord.AttachType.PHOTO.name()); + } + BizUpdateReq req = new BizUpdateReq(); + req.setBizType(Attachment.BizType.GAO_REGISTRATION_EVENT_RECORD); + req.setBizId(recordId); + req.setItems(attachmentList); + attachmentService.updateByBiz(req); + } + + private List resolveAttachmentList(GaoRegistrationEventRecord record) { + if (record.getAttachmentIdList() != null) { + return buildPhotoAttachmentList(record.getAttachmentIdList()); + } + return record.getAttachmentList(); + } + + private List buildPhotoAttachmentList(List attachmentIdList) { + if (attachmentIdList == null) { + return null; + } + List list = new ArrayList<>(); + for (String attachmentId : attachmentIdList) { + if (attachmentId == null || attachmentId.isBlank()) { + continue; + } + Attachment attachment = new Attachment(); + attachment.setTempFileId(attachmentId.trim()); + attachment.setAttachType(GaoRegistrationEventRecord.AttachType.PHOTO.name()); + list.add(attachment); + } + return list; + } +} diff --git a/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoRegistrationEventServiceImplement.java b/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoRegistrationEventServiceImplement.java new file mode 100644 index 0000000..1f819ee --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoRegistrationEventServiceImplement.java @@ -0,0 +1,127 @@ +package com.imyeyu.api.modules.gao.service.implement; + +import com.imyeyu.api.config.dbsource.TimiServerDBConfig; +import com.imyeyu.api.modules.gao.entity.GaoRegistrationEventRoleRelation; +import com.imyeyu.api.modules.gao.entity.GaoRegistrationEvent; +import com.imyeyu.api.modules.gao.mapper.GaoRegistrationEventRoleRelationMapper; +import com.imyeyu.api.modules.gao.mapper.GaoRegistrationEventMapper; +import com.imyeyu.api.modules.gao.service.GaoRegistrationEventService; +import com.imyeyu.java.bean.timi.TimiException; +import com.imyeyu.spring.bean.Page; +import com.imyeyu.spring.bean.PageResult; +import com.imyeyu.spring.mapper.BaseMapper; +import com.imyeyu.spring.service.AbstractEntityService; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +/// +/// 登记事件服务实现 +/// +/// @author Codex +/// @since 2026-07-27 +@Service +@RequiredArgsConstructor +public class GaoRegistrationEventServiceImplement extends AbstractEntityService implements GaoRegistrationEventService { + + private final GaoRegistrationEventMapper mapper; + private final GaoRegistrationEventRoleRelationMapper roleRelationMapper; + + @Override + protected BaseMapper mapper() { + return mapper; + } + + @Override + public PageResult pageWithRegistrationEventLimit(Page page) { + PageResult result = page(page); + if (result.getList() != null) { + result.getList().forEach(this::loadRegistrationEventLimit); + } + return result; + } + + @Transactional(TimiServerDBConfig.ROLLBACKER) + @Override + public void create(GaoRegistrationEvent entity) { + checkEntity(entity, false); + super.create(entity); + saveRoleRelation(entity.getId(), entity.getRoleCodeList()); + } + + @Transactional(TimiServerDBConfig.ROLLBACKER) + @Override + public void update(GaoRegistrationEvent entity) { + checkEntity(entity, true); + super.update(entity); + saveRoleRelation(entity.getId(), entity.getRoleCodeList()); + } + + @Transactional(TimiServerDBConfig.ROLLBACKER) + @Override + public void delete(String id) { + roleRelationMapper.deleteByRegistrationEventId(id); + super.delete(id); + } + + @Override + public GaoRegistrationEvent get(String id) { + GaoRegistrationEvent registrationEvent = super.get(id); + registrationEvent.setRoleCodeList(roleRelationMapper.selectRoleCodeListByRegistrationEventId(id)); + return registrationEvent; + } + + @Override + public List listEnabled() { + List list = mapper.selectEnabledList(); + list.forEach(this::loadRegistrationEventLimit); + return list; + } + + @Override + public List listRoleCode(String registrationEventId) { + return roleRelationMapper.selectRoleCodeListByRegistrationEventId(registrationEventId); + } + + private void checkEntity(GaoRegistrationEvent entity, boolean requireId) { + TimiException.required(entity, "not found registrationEvent"); + if (requireId) { + TimiException.required(entity.getId(), "not found registrationEvent.id"); + } + TimiException.required(entity.getName(), "not found registrationEvent.name"); + if (entity.getEnabled() == null) { + entity.setEnabled(true); + } + if (entity.getRequirePhoto() == null) { + entity.setRequirePhoto(false); + } + if (entity.getSort() == null) { + entity.setSort(0); + } + } + + private void saveRoleRelation(String registrationEventId, List roleCodeList) { + roleRelationMapper.deleteByRegistrationEventId(registrationEventId); + if (roleCodeList == null || roleCodeList.isEmpty()) { + return; + } + Set distinctCodeSet = new LinkedHashSet<>(roleCodeList); + for (String roleCode : distinctCodeSet) { + if (roleCode == null || roleCode.isBlank()) { + continue; + } + GaoRegistrationEventRoleRelation relation = new GaoRegistrationEventRoleRelation(); + relation.setRegistrationEventId(registrationEventId); + relation.setRoleCode(roleCode.trim()); + roleRelationMapper.insert(relation); + } + } + + private void loadRegistrationEventLimit(GaoRegistrationEvent registrationEvent) { + registrationEvent.setRoleCodeList(roleRelationMapper.selectRoleCodeListByRegistrationEventId(registrationEvent.getId())); + } +} diff --git a/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoUserServiceImplement.java b/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoUserServiceImplement.java new file mode 100644 index 0000000..4733061 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoUserServiceImplement.java @@ -0,0 +1,172 @@ +package com.imyeyu.api.modules.gao.service.implement; + +import com.imyeyu.api.bean.ModuleCode; +import com.imyeyu.api.config.dbsource.TimiServerDBConfig; +import com.imyeyu.api.modules.gao.entity.GaoUser; +import com.imyeyu.api.modules.gao.mapper.GaoUserMapper; +import com.imyeyu.api.modules.gao.service.GaoUserService; +import com.imyeyu.api.modules.user.entity.Role; +import com.imyeyu.api.modules.user.entity.User; +import com.imyeyu.api.modules.user.mapper.UserMapper; +import com.imyeyu.api.modules.user.service.RoleService; +import com.imyeyu.api.modules.user.service.UserService; +import com.imyeyu.api.modules.user.vo.role.UserRoleAuthorizeReq; +import com.imyeyu.java.TimiJava; +import com.imyeyu.java.bean.timi.TimiCode; +import com.imyeyu.java.bean.timi.TimiException; +import com.imyeyu.spring.bean.Page; +import com.imyeyu.spring.bean.PageResult; +import com.imyeyu.spring.mapper.BaseMapper; +import com.imyeyu.spring.service.AbstractEntityService; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; + +/// +/// GAO 用户服务实现 +/// +/// @author Codex +/// @since 2026-07-30 +@Service +@RequiredArgsConstructor +public class GaoUserServiceImplement extends AbstractEntityService implements GaoUserService { + + private final GaoUserMapper mapper; + private final UserMapper userMapper; + private final UserService userService; + private final RoleService roleService; + + @Override + protected BaseMapper mapper() { + return mapper; + } + + @Transactional(TimiServerDBConfig.ROLLBACKER) + @Override + public void create(GaoUser entity) { + prepareUser(entity); + checkEntity(entity, false); + super.create(entity); + saveUserRole(entity); + loadUser(entity); + } + + @Transactional(TimiServerDBConfig.ROLLBACKER) + @Override + public void update(GaoUser entity) { + checkEntity(entity, true); + updateUser(entity); + super.update(entity); + saveUserRole(entity); + } + + @Override + public GaoUser get(String id) { + GaoUser gaoUser = super.get(id); + loadUser(gaoUser); + return gaoUser; + } + + @Override + public GaoUser getByUserId(String userId) { + TimiException.required(userId, "not found userId"); + GaoUser gaoUser = mapper.selectByUserId(userId); + TimiException.required(gaoUser, "not found gao user"); + loadUser(gaoUser); + return gaoUser; + } + + @Override + public PageResult pageWithUser(Page page) { + PageResult result = page(page); + loadUser(result.getList()); + return result; + } + + private void checkEntity(GaoUser entity, boolean requireId) { + TimiException.required(entity, "not found gao user"); + if (requireId) { + TimiException.required(entity.getId(), "not found gao user.id"); + } + TimiException.required(entity.getUserId(), "not found gao user.userId"); + TimiException.required(userMapper.select(entity.getUserId()), "not found user"); + GaoUser dbGaoUser = mapper.selectByUserId(entity.getUserId()); + if (dbGaoUser != null && !dbGaoUser.getId().equals(entity.getId())) { + throw new TimiException(TimiCode.ARG_BAD).msgKey("GAO 用户已存在"); + } + if (entity.getEnabled() == null) { + entity.setEnabled(true); + } + } + + private void prepareUser(GaoUser entity) { + TimiException.required(entity, "not found gao user"); + if (TimiJava.isNotEmpty(entity.getUserId())) { + return; + } + User user = entity.getUser(); + TimiException.required(user, "not found gao user.user"); + userService.create(user); + entity.setUserId(user.getId()); + } + + private void updateUser(GaoUser entity) { + User reqUser = entity.getUser(); + if (reqUser == null) { + return; + } + User user = userService.get(entity.getUserId()); + if (reqUser.getName() != null) { + user.setName(reqUser.getName()); + } + if (reqUser.getNick() != null) { + user.setNick(reqUser.getNick()); + } + if (reqUser.getPhoneNo() != null) { + user.setPhoneNo(reqUser.getPhoneNo()); + } + if (reqUser.getEmail() != null) { + user.setEmail(reqUser.getEmail()); + } + userService.update(user); + } + + private void saveUserRole(GaoUser entity) { + if (entity.getRoleIdList() == null) { + return; + } + UserRoleAuthorizeReq req = new UserRoleAuthorizeReq(); + req.setUserId(entity.getUserId()); + req.setModuleCode(ModuleCode.GAO); + req.setRoleIdList(entity.getRoleIdList()); + roleService.authorizeUserRole(req); + } + + private void loadUser(GaoUser gaoUser) { + if (gaoUser == null) { + return; + } + gaoUser.setUser(userMapper.select(gaoUser.getUserId())); + gaoUser.setRoleList(roleService.listByUserId(gaoUser.getUserId(), ModuleCode.GAO)); + } + + private void loadUser(List gaoUserList) { + if (gaoUserList == null || gaoUserList.isEmpty()) { + return; + } + List userIdList = gaoUserList.stream().map(GaoUser::getUserId).distinct().toList(); + Map userMap = userMapper.listByIdList(new ArrayList<>(userIdList)).stream() + .collect(Collectors.toMap(User::getId, Function.identity(), (left, right) -> left)); + for (GaoUser gaoUser : gaoUserList) { + gaoUser.setUser(userMap.get(gaoUser.getUserId())); + List roleList = roleService.listByUserId(gaoUser.getUserId(), ModuleCode.GAO); + gaoUser.setRoleList(roleList); + } + } +} diff --git a/src/main/java/com/imyeyu/api/modules/gao/vo/GaoCustomerDailyStatView.java b/src/main/java/com/imyeyu/api/modules/gao/vo/GaoCustomerDailyStatView.java new file mode 100644 index 0000000..3a75c4c --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/vo/GaoCustomerDailyStatView.java @@ -0,0 +1,21 @@ +package com.imyeyu.api.modules.gao.vo; + +import lombok.Data; + +/// +/// 客户每日统计视图 +/// +/// @author Codex +/// @since 2026-07-28 +@Data +public class GaoCustomerDailyStatView { + + /// 日期值,格式为 yyyyMMdd + private Integer dateValue; + + /// 当日新增客户数 + private Long newCustomerCount; + + /// 截止当日客户总数 + private Long totalCustomerCount; +} diff --git a/src/main/java/com/imyeyu/api/modules/gao/vo/GaoCustomerInviteUpdateReq.java b/src/main/java/com/imyeyu/api/modules/gao/vo/GaoCustomerInviteUpdateReq.java new file mode 100644 index 0000000..fe21f48 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/vo/GaoCustomerInviteUpdateReq.java @@ -0,0 +1,18 @@ +package com.imyeyu.api.modules.gao.vo; + +import lombok.Data; + +/// +/// 客户邀请关系更新请求 +/// +/// @author Codex +/// @since 2026-07-27 +@Data +public class GaoCustomerInviteUpdateReq { + + /// 客户 ID + private String customerId; + + /// 介绍人客户 ID + private String introducerCustomerId; +} diff --git a/src/main/java/com/imyeyu/api/modules/gao/vo/GaoCustomerRegisterStatView.java b/src/main/java/com/imyeyu/api/modules/gao/vo/GaoCustomerRegisterStatView.java new file mode 100644 index 0000000..741b448 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/vo/GaoCustomerRegisterStatView.java @@ -0,0 +1,30 @@ +package com.imyeyu.api.modules.gao.vo; + +import lombok.Data; + +/// +/// 客户登记统计视图 +/// +/// @author Codex +/// @since 2026-07-27 +@Data +public class GaoCustomerRegisterStatView { + + /// 客户 ID + private String customerId; + + /// 客户姓名 + private String customerName; + + /// 登记事件 ID + private String registrationEventId; + + /// 登记事件名称 + private String registrationEventName; + + /// 数量 + private Long count; + + /// 最后登记时间 + private Long latestRegisteredAt; +} diff --git a/src/main/java/com/imyeyu/api/modules/gao/vo/GaoCustomerSearchReq.java b/src/main/java/com/imyeyu/api/modules/gao/vo/GaoCustomerSearchReq.java new file mode 100644 index 0000000..244c1f9 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/vo/GaoCustomerSearchReq.java @@ -0,0 +1,18 @@ +package com.imyeyu.api.modules.gao.vo; + +import lombok.Data; + +/// +/// 客户搜索请求 +/// +/// @author Codex +/// @since 2026-07-27 +@Data +public class GaoCustomerSearchReq { + + /// 关键字,匹配编码、姓名、手机号、电话、住址、病症、条例部位 + private String keyword; + + /// 介绍人客户 ID + private String introducerCustomerId; +} diff --git a/src/main/java/com/imyeyu/api/modules/gao/vo/GaoCustomerTrendStatReq.java b/src/main/java/com/imyeyu/api/modules/gao/vo/GaoCustomerTrendStatReq.java new file mode 100644 index 0000000..b0ca66a --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/vo/GaoCustomerTrendStatReq.java @@ -0,0 +1,18 @@ +package com.imyeyu.api.modules.gao.vo; + +import lombok.Data; + +/// +/// 客户趋势统计请求 +/// +/// @author Codex +/// @since 2026-07-28 +@Data +public class GaoCustomerTrendStatReq { + + /// 开始创建时间 + private Long beginCreatedAt; + + /// 结束创建时间 + private Long endCreatedAt; +} diff --git a/src/main/java/com/imyeyu/api/modules/gao/vo/GaoQuickRegistrationEventRecordReq.java b/src/main/java/com/imyeyu/api/modules/gao/vo/GaoQuickRegistrationEventRecordReq.java new file mode 100644 index 0000000..96d7dc6 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/vo/GaoQuickRegistrationEventRecordReq.java @@ -0,0 +1,27 @@ +package com.imyeyu.api.modules.gao.vo; + +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; + +/// +/// 快速录入并登记请求 +/// +/// @author Codex +/// @since 2026-07-28 +@Data +public class GaoQuickRegistrationEventRecordReq { + + /// 客户编码 + private String customerCode; + + /// 登记登记事件 ID + private String registrationEventId; + + /// 备注 + private String remark; + + /// 临时照片附件 ID 列表 + private List attachmentIdList = new ArrayList<>(); +} diff --git a/src/main/java/com/imyeyu/api/modules/gao/vo/GaoRegistrationEventPeriodStatView.java b/src/main/java/com/imyeyu/api/modules/gao/vo/GaoRegistrationEventPeriodStatView.java new file mode 100644 index 0000000..c461e9f --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/vo/GaoRegistrationEventPeriodStatView.java @@ -0,0 +1,21 @@ +package com.imyeyu.api.modules.gao.vo; + +import lombok.Data; + +/// +/// 登记事件周期统计视图 +/// +/// @author Codex +/// @since 2026-07-27 +@Data +public class GaoRegistrationEventPeriodStatView { + + /// 登记事件 ID + private String registrationEventId; + + /// 周期值 + private Integer periodValue; + + /// 数量 + private Long count; +} diff --git a/src/main/java/com/imyeyu/api/modules/gao/vo/GaoRegistrationEventRecordCreateReq.java b/src/main/java/com/imyeyu/api/modules/gao/vo/GaoRegistrationEventRecordCreateReq.java new file mode 100644 index 0000000..2e23ebe --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/vo/GaoRegistrationEventRecordCreateReq.java @@ -0,0 +1,33 @@ +package com.imyeyu.api.modules.gao.vo; + +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; + +/// +/// 登记登记事件记录创建请求 +/// +/// @author Codex +/// @since 2026-07-27 +@Data +public class GaoRegistrationEventRecordCreateReq { + + /// 客户 ID + private String customerId; + + /// 客户编码,扫码登记可直接传这个 + private String customerCode; + + /// 登记事件 ID + private String registrationEventId; + + /// 备注 + private String remark; + + /// 登记时间 + private Long registeredAt; + + /// 临时照片附件 ID 列表 + private List attachmentIdList = new ArrayList<>(); +} diff --git a/src/main/java/com/imyeyu/api/modules/gao/vo/GaoRegistrationEventRecordDailyStatView.java b/src/main/java/com/imyeyu/api/modules/gao/vo/GaoRegistrationEventRecordDailyStatView.java new file mode 100644 index 0000000..477b902 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/vo/GaoRegistrationEventRecordDailyStatView.java @@ -0,0 +1,21 @@ +package com.imyeyu.api.modules.gao.vo; + +import lombok.Data; + +/// +/// 登记事件记录每日统计视图 +/// +/// @author Codex +/// @since 2026-07-28 +@Data +public class GaoRegistrationEventRecordDailyStatView { + + /// 日期值,格式为 yyyyMMdd + private Integer dateValue; + + /// 当日登记数 + private Long dailyCount; + + /// 截止当日累计登记数 + private Long totalCount; +} diff --git a/src/main/java/com/imyeyu/api/modules/gao/vo/GaoRegistrationEventRecordPeriodReq.java b/src/main/java/com/imyeyu/api/modules/gao/vo/GaoRegistrationEventRecordPeriodReq.java new file mode 100644 index 0000000..5fe8e2a --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/vo/GaoRegistrationEventRecordPeriodReq.java @@ -0,0 +1,35 @@ +package com.imyeyu.api.modules.gao.vo; + +import lombok.Data; + +/// +/// 登记事件记录按周期查询请求 +/// +/// @author Codex +/// @since 2026-07-27 +@Data +public class GaoRegistrationEventRecordPeriodReq { + + /// + /// 周期类型 + /// + /// @author Codex + /// @since 2026-07-27 + public enum PeriodType { + + DAY, + + MONTH, + + YEAR + } + + /// 登记事件 ID + private String registrationEventId; + + /// 周期类型 + private PeriodType periodType; + + /// 周期值,日为 yyyyMMdd,月为 yyyyMM,年为 yyyy + private Integer periodValue; +} diff --git a/src/main/java/com/imyeyu/api/modules/gao/vo/GaoRegistrationEventRecordRangeReq.java b/src/main/java/com/imyeyu/api/modules/gao/vo/GaoRegistrationEventRecordRangeReq.java new file mode 100644 index 0000000..6e7f1d8 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/vo/GaoRegistrationEventRecordRangeReq.java @@ -0,0 +1,24 @@ +package com.imyeyu.api.modules.gao.vo; + +import lombok.Data; + +/// +/// 登记事件记录范围查询请求 +/// +/// @author Codex +/// @since 2026-07-28 +@Data +public class GaoRegistrationEventRecordRangeReq { + + /// 客户 ID,为空查询全部客户 + private String customerId; + + /// 登记事件 ID,为空查询全部登记事件 + private String registrationEventId; + + /// 开始登记时间 + private Long beginRegisteredAt; + + /// 结束登记时间 + private Long endRegisteredAt; +} diff --git a/src/main/java/com/imyeyu/api/modules/gao/vo/GaoRegistrationEventRecordStatReq.java b/src/main/java/com/imyeyu/api/modules/gao/vo/GaoRegistrationEventRecordStatReq.java new file mode 100644 index 0000000..51214bf --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/vo/GaoRegistrationEventRecordStatReq.java @@ -0,0 +1,21 @@ +package com.imyeyu.api.modules.gao.vo; + +import lombok.Data; + +/// +/// 登记事件记录统计请求 +/// +/// @author Codex +/// @since 2026-07-28 +@Data +public class GaoRegistrationEventRecordStatReq { + + /// 客户 ID,全部客户统计时不传 + private String customerId; + + /// 开始登记时间 + private Long beginRegisteredAt; + + /// 结束登记时间 + private Long endRegisteredAt; +} diff --git a/src/main/java/com/imyeyu/api/modules/gao/vo/GaoRegistrationEventRecordTrendStatReq.java b/src/main/java/com/imyeyu/api/modules/gao/vo/GaoRegistrationEventRecordTrendStatReq.java new file mode 100644 index 0000000..b83be89 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/vo/GaoRegistrationEventRecordTrendStatReq.java @@ -0,0 +1,24 @@ +package com.imyeyu.api.modules.gao.vo; + +import lombok.Data; + +/// +/// 登记事件记录趋势统计请求 +/// +/// @author Codex +/// @since 2026-07-28 +@Data +public class GaoRegistrationEventRecordTrendStatReq { + + /// 客户 ID,全部客户趋势时不传 + private String customerId; + + /// 登记事件 ID + private String registrationEventId; + + /// 开始登记时间 + private Long beginRegisteredAt; + + /// 结束登记时间 + private Long endRegisteredAt; +} diff --git a/src/main/java/com/imyeyu/api/modules/gao/vo/GaoRegistrationEventSaveReq.java b/src/main/java/com/imyeyu/api/modules/gao/vo/GaoRegistrationEventSaveReq.java new file mode 100644 index 0000000..dfe8bd1 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/vo/GaoRegistrationEventSaveReq.java @@ -0,0 +1,22 @@ +package com.imyeyu.api.modules.gao.vo; + +import com.imyeyu.api.modules.gao.entity.GaoRegistrationEvent; +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; + +/// +/// 登记事件保存请求 +/// +/// @author Codex +/// @since 2026-07-27 +@Data +public class GaoRegistrationEventSaveReq { + + /// 登记事件 + private GaoRegistrationEvent registrationEvent; + + /// 可登记角色代码列表 + private List roleCodeList = new ArrayList<>(); +} diff --git a/src/main/java/com/imyeyu/api/modules/git/bean/AttachType.java b/src/main/java/com/imyeyu/api/modules/git/bean/AttachType.java deleted file mode 100644 index cbf3948..0000000 --- a/src/main/java/com/imyeyu/api/modules/git/bean/AttachType.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.imyeyu.api.modules.git.bean; - -/** - * @author 夜雨 - * @since 2024-02-21 17:10 - */ -public enum AttachType { - - ISSUE, - - MERGE, - - RELEASE -} diff --git a/src/main/java/com/imyeyu/api/modules/git/bean/GitCommit.java b/src/main/java/com/imyeyu/api/modules/git/bean/GitCommit.java deleted file mode 100644 index cd6d866..0000000 --- a/src/main/java/com/imyeyu/api/modules/git/bean/GitCommit.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.imyeyu.api.modules.git.bean; - -import lombok.Data; -import com.imyeyu.api.modules.common.vo.user.UserView; - -/** - * Git 提交信息 - * - * @author 夜雨 - * @since 2023-08-14 17:13 - */ -@Data -public class GitCommit { - - /** ID */ - private String id; - - /** 说明 */ - private String msg; - - /** 时间 */ - private long time; - - private UserView committer; -} diff --git a/src/main/java/com/imyeyu/api/modules/git/bean/gitea/API.java b/src/main/java/com/imyeyu/api/modules/git/bean/gitea/API.java deleted file mode 100644 index c3e1872..0000000 --- a/src/main/java/com/imyeyu/api/modules/git/bean/gitea/API.java +++ /dev/null @@ -1,59 +0,0 @@ -package com.imyeyu.api.modules.git.bean.gitea; - -import com.imyeyu.java.TimiJava; -import com.imyeyu.api.TimiServerAPI; -import com.imyeyu.api.modules.common.bean.SettingKey; -import com.imyeyu.api.modules.common.service.SettingService; -import com.imyeyu.utils.Encoder; -import com.imyeyu.utils.StringInterpolator; - -import java.util.Map; - -/** - * @author 夜雨 - * @since 2025-06-26 16:04 - */ -public enum API { - - REPO_LIST("/api/v1/repos/search"), - - REPO_GET("/api/v1/repos/{owner}/{repoName}"), - - REPO_BRANCHES_LIST("/api/v1/repos/{owner}/{repoName}/branches"), - - REPO_FILE_LIST("/api/v1/repos/{owner}/{repoName}/contents/{path}"), - - REPO_FILE_RAW("/api/v1/repos/{owner}/{repoName}/raw/{path}"), - - REPO_ARCHIVE("/api/v1/repos/{owner}/{repoName}/archive/{archiveFormat}"), - - REPO_COMMIT_LIST("/api/v1/repos/{owner}/{repoName}/commits"), - - REPO_LANGUAGES("/api/v1/repos/{owner}/{repoName}/languages") - ; - - static final StringInterpolator INTERPOLATOR = new StringInterpolator(StringInterpolator.SIMPLE_OBJ); - - final String uri; - - API(String uri) { - this.uri = uri; - } - - public String buildURL(Map argsURI) { - return buildURL(argsURI, null); - - } - - public String buildURL(Map argsURI, Map argsURL) { - SettingService settingService = TimiServerAPI.applicationContext.getBean(SettingService.class); - StringBuilder url = new StringBuilder(); - url.append(settingService.getAsString(SettingKey.GIT_API)); - url.append(INTERPOLATOR.inject(uri, argsURI)); - if (TimiJava.isNotEmpty(argsURL)) { - url.append("?"); - url.append(Encoder.urlArgs(argsURL)); - } - return url.toString(); - } -} diff --git a/src/main/java/com/imyeyu/api/modules/git/bean/gitea/Branch.java b/src/main/java/com/imyeyu/api/modules/git/bean/gitea/Branch.java deleted file mode 100644 index 04cd67a..0000000 --- a/src/main/java/com/imyeyu/api/modules/git/bean/gitea/Branch.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.imyeyu.api.modules.git.bean.gitea; - -import com.fasterxml.jackson.annotation.JsonProperty; -import lombok.Data; - -/** - * @author 夜雨 - * @since 2025-06-28 00:31 - */ -@Data -public class Branch { - - private String name; - - @JsonProperty("protected") - private boolean isProtected; -} diff --git a/src/main/java/com/imyeyu/api/modules/git/bean/gitea/File.java b/src/main/java/com/imyeyu/api/modules/git/bean/gitea/File.java deleted file mode 100644 index dedc107..0000000 --- a/src/main/java/com/imyeyu/api/modules/git/bean/gitea/File.java +++ /dev/null @@ -1,43 +0,0 @@ -package com.imyeyu.api.modules.git.bean.gitea; - -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import com.imyeyu.api.modules.git.util.GiteaTimestampAdapter; -import lombok.Data; - -/** - * @author 夜雨 - * @since 2025-06-29 18:43 - */ -@Data -public class File { - - /** - * - * - * @author 夜雨 - * @since 2025-06-29 19:00 - */ - public enum Type { - - file, - - dir - } - - private String name; - - private String path; - - private String sha; - - private Long size; - - private Type type; - - private String lastCommitSha; - - @JsonSerialize(using = GiteaTimestampAdapter.Serializer.class) - @JsonDeserialize(using = GiteaTimestampAdapter.Deserializer.class) - private Long lastCommitterDate; -} diff --git a/src/main/java/com/imyeyu/api/modules/git/bean/gitea/GiteaResponse.java b/src/main/java/com/imyeyu/api/modules/git/bean/gitea/GiteaResponse.java deleted file mode 100644 index bb1a984..0000000 --- a/src/main/java/com/imyeyu/api/modules/git/bean/gitea/GiteaResponse.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.imyeyu.api.modules.git.bean.gitea; - -import lombok.Data; - -/** - * @author 夜雨 - * @since 2025-07-08 15:05 - */ -@Data -public class GiteaResponse { - - private boolean ok; - private T data; -} diff --git a/src/main/java/com/imyeyu/api/modules/git/bean/gitea/Repository.java b/src/main/java/com/imyeyu/api/modules/git/bean/gitea/Repository.java deleted file mode 100644 index 051a523..0000000 --- a/src/main/java/com/imyeyu/api/modules/git/bean/gitea/Repository.java +++ /dev/null @@ -1,52 +0,0 @@ -package com.imyeyu.api.modules.git.bean.gitea; - -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import com.imyeyu.api.modules.git.util.GiteaTimestampAdapter; -import lombok.Data; - -import java.util.List; - -/** - * @author 夜雨 - * @since 2025-06-26 11:45 - */ -@Data -public class Repository { - - private Integer id; - - private String name; - - private String fullName; - - private String description; - - private Integer size; - - private String language; - - private String sshUrl; - - private String cloneUrl; - - private String avatarUrl; - - private String defaultBranch; - - private Boolean archived; - - @JsonSerialize(using = GiteaTimestampAdapter.Serializer.class) - @JsonDeserialize(using = GiteaTimestampAdapter.Deserializer.class) - private Long createdAt; - - @JsonSerialize(using = GiteaTimestampAdapter.Serializer.class) - @JsonDeserialize(using = GiteaTimestampAdapter.Deserializer.class) - private Long updatedAt; - - @JsonSerialize(using = GiteaTimestampAdapter.Serializer.class) - @JsonDeserialize(using = GiteaTimestampAdapter.Deserializer.class) - private Long archivedAt; - - private List licenses; -} diff --git a/src/main/java/com/imyeyu/api/modules/git/bean/hook/PostReceive.java b/src/main/java/com/imyeyu/api/modules/git/bean/hook/PostReceive.java deleted file mode 100644 index d837dbf..0000000 --- a/src/main/java/com/imyeyu/api/modules/git/bean/hook/PostReceive.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.imyeyu.api.modules.git.bean.hook; - -import lombok.Data; - -/** - * @author 夜雨 - * @since 2023-09-18 10:13 - */ -@Data -public class PostReceive { - - private String pusherName; - - private String repositoryName; - - private String branch; - - private String fromSHA1; - - private String toSHA1; -} diff --git a/src/main/java/com/imyeyu/api/modules/git/controller/DeveloperController.java b/src/main/java/com/imyeyu/api/modules/git/controller/DeveloperController.java deleted file mode 100644 index 6d88083..0000000 --- a/src/main/java/com/imyeyu/api/modules/git/controller/DeveloperController.java +++ /dev/null @@ -1,43 +0,0 @@ -package com.imyeyu.api.modules.git.controller; - -import lombok.RequiredArgsConstructor; -import com.imyeyu.api.annotation.EnableSetting; -import com.imyeyu.api.modules.common.bean.SettingKey; -import com.imyeyu.api.modules.git.entity.Developer; -import com.imyeyu.api.modules.git.service.DeveloperService; -import com.imyeyu.spring.annotation.AOPLog; -import com.imyeyu.spring.annotation.RequestRateLimit; -import com.imyeyu.spring.annotation.RequiredToken; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -/** - * @author 夜雨 - * @since 2023-09-19 13:48 - */ -@RestController -@RequiredArgsConstructor -@RequestMapping("/git/developer") -public class DeveloperController { - - private final DeveloperService service; - - @AOPLog - @RequiredToken - @RequestRateLimit - @PostMapping("") - public Developer view() { - return service.view(); - } - - @AOPLog - @RequiredToken - @EnableSetting(value = SettingKey.ENABLE_USER_UPDATE, message = "user.data.off_service") - @RequestRateLimit - @PostMapping("/update") - public void update(@RequestBody Developer developer) { - service.update(developer); - } -} diff --git a/src/main/java/com/imyeyu/api/modules/git/controller/IssueController.java b/src/main/java/com/imyeyu/api/modules/git/controller/IssueController.java deleted file mode 100644 index 2c721bf..0000000 --- a/src/main/java/com/imyeyu/api/modules/git/controller/IssueController.java +++ /dev/null @@ -1,108 +0,0 @@ -package com.imyeyu.api.modules.git.controller; - -import jakarta.validation.Valid; -import jakarta.validation.constraints.Min; -import jakarta.validation.constraints.NotNull; -import lombok.RequiredArgsConstructor; -import com.imyeyu.java.TimiJava; -import com.imyeyu.api.annotation.CaptchaValid; -import com.imyeyu.api.bean.CaptchaFrom; -import com.imyeyu.api.modules.common.entity.Attachment; -import com.imyeyu.api.modules.common.service.AttachmentService; -import com.imyeyu.api.modules.common.service.CommentService; -import com.imyeyu.api.modules.common.service.UserService; -import com.imyeyu.api.modules.git.entity.Issue; -import com.imyeyu.api.modules.git.service.IssueService; -import com.imyeyu.api.modules.git.vo.issue.IssuePage; -import com.imyeyu.api.modules.git.vo.issue.IssueRequest; -import com.imyeyu.api.modules.git.vo.issue.IssueView; -import com.imyeyu.spring.annotation.AOPLog; -import com.imyeyu.spring.annotation.RequestRateLimit; -import com.imyeyu.spring.annotation.RequiredToken; -import com.imyeyu.spring.bean.CaptchaData; -import com.imyeyu.spring.bean.PageResult; -import org.springframework.beans.BeanUtils; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -/** - * Git 反馈接口 - * - * @author 夜雨 - * @since 2023-08-15 15:02 - */ -@Validated -@RestController -@RequiredArgsConstructor -@RequestMapping("/git/issue") -public class IssueController { - - private final UserService userService; - private final IssueService service; - private final CommentService commentService; - private final AttachmentService attachmentService; - - /** - * 查看 - * - * @param id 反馈 ID - * @return 反馈数据 - */ - @AOPLog - @RequestRateLimit - @GetMapping("/{id}") - public IssueView view(@Min(1) @NotNull @PathVariable Long id) { - Issue issue = service.get(id); - IssueView view = new IssueView(); - BeanUtils.copyProperties(issue, view); - if (TimiJava.isNotEmpty(issue.getPublisherId())) { - view.setPublisher(userService.view(issue.getPublisherId())); - } - view.setAttachmentList(attachmentService.listByBizId(Attachment.BizType.GIT, id)); - return view; - } - - /** - * 获取反馈列表 - * - * @param issuePage 反馈页面参数 - * @return 反馈列表 - */ - @AOPLog - @RequestRateLimit - @PostMapping("/list") - public PageResult page(@Valid @RequestBody IssuePage issuePage) { - return service.page(issuePage); - } - - /** - * 创建反馈,不需要登录 - * - * @param captchaData 反馈请求 - */ - @AOPLog - @CaptchaValid(CaptchaFrom.GIT_ISSUE) - @RequestRateLimit - @PostMapping("/create") - public void create(@Valid @RequestBody CaptchaData captchaData) { - service.create(captchaData.getData()); - } - - /** - * 修改反馈,非讨论类型和未完成的反馈可以修改 - * - * @param issueRequest 修改反馈请求 - */ - @AOPLog - @RequiredToken - @RequestRateLimit - @PostMapping("/update") - public void update(@Valid @RequestBody IssueRequest issueRequest) { - service.update(issueRequest); - } -} diff --git a/src/main/java/com/imyeyu/api/modules/git/controller/MergeController.java b/src/main/java/com/imyeyu/api/modules/git/controller/MergeController.java deleted file mode 100644 index 5e01edb..0000000 --- a/src/main/java/com/imyeyu/api/modules/git/controller/MergeController.java +++ /dev/null @@ -1,110 +0,0 @@ -package com.imyeyu.api.modules.git.controller; - -import jakarta.validation.Valid; -import jakarta.validation.constraints.Min; -import jakarta.validation.constraints.NotNull; -import lombok.RequiredArgsConstructor; -import com.imyeyu.java.TimiJava; -import com.imyeyu.api.annotation.CaptchaValid; -import com.imyeyu.api.bean.CaptchaFrom; -import com.imyeyu.api.modules.common.entity.Attachment; -import com.imyeyu.api.modules.common.service.AttachmentService; -import com.imyeyu.api.modules.common.service.UserService; -import com.imyeyu.api.modules.git.entity.Merge; -import com.imyeyu.api.modules.git.service.IssueService; -import com.imyeyu.api.modules.git.service.MergeService; -import com.imyeyu.api.modules.git.vo.merge.MergePage; -import com.imyeyu.api.modules.git.vo.merge.MergeRequest; -import com.imyeyu.api.modules.git.vo.merge.MergeView; -import com.imyeyu.spring.annotation.AOPLog; -import com.imyeyu.spring.annotation.RequestRateLimit; -import com.imyeyu.spring.annotation.RequiredToken; -import com.imyeyu.spring.bean.CaptchaData; -import com.imyeyu.spring.bean.PageResult; -import org.springframework.beans.BeanUtils; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -/** - * 和并请求接口 - * - * @author 夜雨 - * @since 2023-08-15 15:02 - */ -@Validated -@RestController -@RequiredArgsConstructor -@RequestMapping("/git/merge") -public class MergeController { - - private final UserService userService; - private final MergeService service; - private final IssueService issueService; - private final AttachmentService attachmentService; - - /** - * 合并请求详情 - * - * @param id 和并请求 ID - * @return 详情数据 - */ - @AOPLog - @RequestRateLimit - @GetMapping("/{id}") - public MergeView view(@Min(1) @NotNull @PathVariable Long id) { - Merge merge = service.get(id); - MergeView view = new MergeView(); - BeanUtils.copyProperties(merge, view); - if (TimiJava.isNotEmpty(view.getIssueId())) { - view.setIssue(issueService.get(view.getIssueId())); - } - view.setRequester(userService.view(view.getRequesterId())); - view.setAttachmentList(attachmentService.listByBizId(Attachment.BizType.GIT, id)); - return view; - } - - /** - * 获取合并请求列表 - * - * @param mergePage 页面参数 - * @return 列表 - */ - @AOPLog - @RequestRateLimit - @PostMapping("/list") - public PageResult page(@Valid @RequestBody MergePage mergePage) { - return service.page(mergePage); - } - - /** - * 申请合并请求 - * - * @param captchaData 申请数据 - */ - @AOPLog - @CaptchaValid(CaptchaFrom.GIT_MERGE) - @RequiredToken - @RequestRateLimit - @PostMapping("/create") - public void create(@Valid @RequestBody CaptchaData captchaData) { - service.create(captchaData.getData()); - } - - /** - * 修改合并请求 - * - * @param mergeRequest 修改数据 - */ - @AOPLog - @RequestRateLimit - @RequiredToken - @PostMapping("/update") - public void update(@Valid @RequestBody MergeRequest mergeRequest) { - service.update(mergeRequest); - } -} diff --git a/src/main/java/com/imyeyu/api/modules/git/controller/ReleaseController.java b/src/main/java/com/imyeyu/api/modules/git/controller/ReleaseController.java deleted file mode 100644 index f58a36e..0000000 --- a/src/main/java/com/imyeyu/api/modules/git/controller/ReleaseController.java +++ /dev/null @@ -1,41 +0,0 @@ -package com.imyeyu.api.modules.git.controller; - -import jakarta.validation.Valid; -import lombok.RequiredArgsConstructor; -import com.imyeyu.api.modules.git.service.ReleaseService; -import com.imyeyu.api.modules.git.vo.release.ReleasePage; -import com.imyeyu.api.modules.git.vo.release.ReleaseView; -import com.imyeyu.spring.annotation.AOPLog; -import com.imyeyu.spring.annotation.RequestRateLimit; -import com.imyeyu.spring.bean.PageResult; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -/** - * Git 项目版本发布接口 - * - * @author 夜雨 - * @since 2023-08-16 11:25 - */ -@RestController -@RequiredArgsConstructor -@RequestMapping("/git/release") -public class ReleaseController { - - private final ReleaseService service; - - /** - * 获取版本发布列表 - * - * @param releasePage 版本发布页面参数 - * @return 版本发布列表 - */ - @AOPLog - @RequestRateLimit - @PostMapping("/list") - public PageResult page(@Valid @RequestBody ReleasePage releasePage) { - return service.pageByRepositoryId(releasePage); - } -} diff --git a/src/main/java/com/imyeyu/api/modules/git/controller/RepositoryController.java b/src/main/java/com/imyeyu/api/modules/git/controller/RepositoryController.java deleted file mode 100644 index 50da818..0000000 --- a/src/main/java/com/imyeyu/api/modules/git/controller/RepositoryController.java +++ /dev/null @@ -1,174 +0,0 @@ -package com.imyeyu.api.modules.git.controller; - -import com.imyeyu.io.IO; -import com.imyeyu.java.TimiJava; -import com.imyeyu.network.Network; -import com.imyeyu.api.modules.git.bean.gitea.File; -import com.imyeyu.api.modules.git.bean.gitea.Repository; -import com.imyeyu.api.modules.git.service.RepositoryService; -import com.imyeyu.api.modules.git.vo.repository.RepositoryView; -import com.imyeyu.api.modules.gitea.service.GiteaService; -import com.imyeyu.api.modules.gitea.vo.ActionLogView; -import com.imyeyu.spring.TimiSpring; -import com.imyeyu.spring.annotation.AOPLog; -import com.imyeyu.spring.annotation.IgnoreGlobalReturn; -import com.imyeyu.spring.annotation.RequestRateLimit; -import com.imyeyu.spring.bean.Page; -import com.imyeyu.spring.bean.PageResult; -import jakarta.servlet.http.HttpServletResponse; -import jakarta.validation.Valid; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.BeanUtils; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -import java.nio.charset.StandardCharsets; -import java.util.List; - -/** - * Git 仓库接口 - * - * @author 夜雨 - * @since 2023-08-06 01:19 - */ -@Slf4j -@RestController -@RequiredArgsConstructor -@RequestMapping("/git/repository") -public class RepositoryController { - - private final GiteaService giteaService; - private final RepositoryService service; - - - @RequestRateLimit - @PostMapping("/log") - public PageResult pagePush(@Valid @RequestBody Page page) { - return giteaService.pagePush(page); - } - - /** - * 查看仓库 - * - * @param name 仓库 ID - * @return 仓库数据 - */ - @AOPLog - @RequestRateLimit - @GetMapping("/{name}") - public RepositoryView view(@PathVariable String name) { - Repository source = service.get(name); - RepositoryView view = new RepositoryView(); - BeanUtils.copyProperties(source, view); - view.setBranchList(service.listBranches(name)); - return view; - } - - /** - * 获取仓库列表 - * - * @param page 页面参数 - * @return 仓库列表 - */ - @RequestRateLimit - @PostMapping("/list") - public PageResult page(@Valid @RequestBody Page page) { - return service.page(page); - } - - /** - * 获取仓库文件结构树 - * - * @param repoName 仓库名称 - * @param branch 分支名称 - * @return 文件结构树 - */ - @AOPLog - @RequestRateLimit(20) - @RequestMapping("/{repoName}:{branch}/file/list/**") - public List listFile(@PathVariable String repoName, @PathVariable String branch) { - return service.listFile(repoName, branch, TimiSpring.cutURIStartAt("/file/list/")); - } - - /** - * 加载仓库文件,/request/raw/ 后的路径为加载目标文件的绝对仓库路径 - * - * @param repositoryName 仓库名称 - * @param branch 分支名称 - * @param resp 返回体 - */ - @RequestRateLimit - @IgnoreGlobalReturn - @GetMapping("/{repositoryName}:{branch}/file/raw/**") - public void raw(@PathVariable String repositoryName, @PathVariable String branch, HttpServletResponse resp) { - try { - String path = TimiSpring.cutURIStartAt("/file/raw"); - if (TimiJava.isEmpty(path)) { - resp.setStatus(HttpServletResponse.SC_NOT_FOUND); - resp.setCharacterEncoding(StandardCharsets.UTF_8.toString()); - } else { - resp.setContentType(service.getFileMimeType(repositoryName, branch, path)); - IO.toOutputStream(service.getFileRaw(repositoryName, branch, path), resp.getOutputStream()); - } - } catch (Exception e) { - resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); - resp.setCharacterEncoding(StandardCharsets.UTF_8.toString()); - } - } - - /** - * 获取文件 MimeType - * - * @param repositoryName 仓库名称 - * @param branch 分支名称 - */ - @RequestRateLimit - @GetMapping("/{repositoryName}:{branch}/file/mime/**") - public String mimeType(@PathVariable String repositoryName, @PathVariable String branch) { - return service.getFileMimeType(repositoryName, branch, TimiSpring.cutURIStartAt("/file/mime")); - } - - /** - * 获取仓库提交消息列表 - * - * @param page 提交消息页面参数 - * @return 提交消息列表 - */ - @AOPLog - @RequestRateLimit - @PostMapping("/{repositoryName}:{branch}/log/push") - public PageResult pagePush(@PathVariable String repositoryName, @PathVariable String branch, @Valid @RequestBody Page page) { - if (branch.equals("all")) { - branch = null; - } - return giteaService.pagePush(repositoryName, branch, page); - } - - /** - * 打包下载仓库源码文件 - * - * @param repositoryName 仓库名称 - * @param branch 分支名称 - * @param resp 返回体 - */ - @AOPLog - @RequestRateLimit - @RequestMapping("/{repositoryName}:{branch}/archive") - public void downloadArchive(@PathVariable String repositoryName, @PathVariable String branch, HttpServletResponse resp) { - try { - resp.setHeader("Content-Disposition", Network.getFileDownloadHeader("%s-%s.tar.gz".formatted(repositoryName.toLowerCase(), branch))); - resp.setHeader("Accept-Ranges", "bytes"); - IO.toOutputStream(service.getArchive(repositoryName, branch), resp.getOutputStream()); - resp.flushBuffer(); - } catch (Exception e) { - log.error("downloadArchive error", e); - resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); - resp.setCharacterEncoding(StandardCharsets.UTF_8.toString()); - } - } -} diff --git a/src/main/java/com/imyeyu/api/modules/git/entity/CommitLog.java b/src/main/java/com/imyeyu/api/modules/git/entity/CommitLog.java deleted file mode 100644 index 65716df..0000000 --- a/src/main/java/com/imyeyu/api/modules/git/entity/CommitLog.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.imyeyu.api.modules.git.entity; - -import com.imyeyu.spring.annotation.table.Id; -import com.imyeyu.spring.annotation.table.Table; -import com.imyeyu.spring.entity.Creatable; -import com.imyeyu.spring.entity.IDEntity; -import lombok.Data; - -/** - * 提交日志 - * - * @author 夜雨 - * @since 2023-09-19 11:34 - */ -@Data -@Table("git_commit_log") -public class CommitLog implements IDEntity, Creatable { - - @Id - protected Long id; - - /** 所属推送 ID */ - protected Long pushId; - - /** SHA1 */ - protected String sha1; - - /** 说明 */ - protected String message; - - /** 提交时间 */ - protected Long createdAt; -} diff --git a/src/main/java/com/imyeyu/api/modules/git/entity/Developer.java b/src/main/java/com/imyeyu/api/modules/git/entity/Developer.java deleted file mode 100644 index eec640d..0000000 --- a/src/main/java/com/imyeyu/api/modules/git/entity/Developer.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.imyeyu.api.modules.git.entity; - -import com.imyeyu.api.modules.common.entity.User; -import com.imyeyu.spring.annotation.table.Id; -import com.imyeyu.spring.annotation.table.Table; -import com.imyeyu.spring.entity.Updatable; -import lombok.Data; -import lombok.NoArgsConstructor; - -/** - * 开发者 - * - * @author 夜雨 - * @since 2023-09-19 11:37 - */ -@Data -@NoArgsConstructor -@Table("git_developer") -public class Developer implements Updatable { - - /** 开发者 ID({@link User#getId()}) */ - @Id - protected Long developerId; - - /** 昵称 */ - protected String name; - - /** RSA 密钥 */ - protected String rsa; - - /** 更新时间 */ - protected Long updatedAt; - - public Developer(Long developerId) { - this.developerId = developerId; - } -} diff --git a/src/main/java/com/imyeyu/api/modules/git/entity/Issue.java b/src/main/java/com/imyeyu/api/modules/git/entity/Issue.java deleted file mode 100644 index e993e1d..0000000 --- a/src/main/java/com/imyeyu/api/modules/git/entity/Issue.java +++ /dev/null @@ -1,120 +0,0 @@ -package com.imyeyu.api.modules.git.entity; - -import com.imyeyu.spring.annotation.table.Table; -import lombok.Data; -import lombok.EqualsAndHashCode; -import com.imyeyu.api.modules.common.bean.CommentSupport; -import com.imyeyu.spring.entity.Entity; - -/** - * Git 问题反馈 - * - * @author 夜雨 - * @since 2023-08-16 15:18 - */ -@Data -@EqualsAndHashCode(callSuper = true) -@Table("git_issue") -public class Issue extends Entity implements CommentSupport { - - /** - * 类型 - * - * @author 夜雨 - * @since 2023-08-16 17:18 - */ - public enum Type { - - /** 异常 */ - BUG, - - /** 功能 */ - FEATURE, - - /** 安全 */ - SECURITY, - - /** 提问 */ - QUESTION, - - /** 讨论 */ - DISCUSS - } - - /** - * 状态 - * - * @author 夜雨 - * @since 2023-08-16 15:26 - */ - public enum Status { - - /** 待确认 */ - BEFORE_CONFIRM, - - /** 已确认,待开发 */ - CONFIRMED, - - /** 开发中 */ - DEVELOPING, - - /** 已完成 */ - FINISHED, - - /** 已关闭 */ - CLOSED - } - - /** 所属仓库 ID */ - protected long repositoryId; - - /** 发布者 ID */ - protected Long publisherId; - - /** 发布者昵称 */ - protected String publisherNick; - - /** 类型 */ - protected Type type; - - /** 版本 */ - protected String version; - - /** 标题 */ - protected String title; - - /** 描述 */ - protected String description; - - /** 状态 */ - protected Status status; - - /** 确认时间 */ - protected Long confirmedAt; - - /** 开发时间 */ - protected Long developAt; - - /** 关闭时间 */ - protected Long closedAt; - - @Override - public boolean canComment() { - return status != Status.CLOSED; - } - - @Override - public boolean canNotComment() { - return !canComment(); - } - - /** @return true 为可更新 */ - public boolean canUpdate() { - return type != Type.DISCUSS && !isClosed() && status != Status.FINISHED; - } - - /** @return true 为已关闭 */ - public boolean isClosed() { - return closedAt != null; - } -} diff --git a/src/main/java/com/imyeyu/api/modules/git/entity/Merge.java b/src/main/java/com/imyeyu/api/modules/git/entity/Merge.java deleted file mode 100644 index 8c6daa0..0000000 --- a/src/main/java/com/imyeyu/api/modules/git/entity/Merge.java +++ /dev/null @@ -1,133 +0,0 @@ -package com.imyeyu.api.modules.git.entity; - -import com.imyeyu.spring.annotation.table.Table; -import lombok.Data; -import lombok.EqualsAndHashCode; -import com.imyeyu.api.modules.common.bean.CommentSupport; -import com.imyeyu.spring.entity.Entity; - -/** - * 合并请求 - * - * @author 夜雨 - * @since 2023-08-16 14:52 - */ -@Data -@EqualsAndHashCode(callSuper = true) -@Table("git_merge") -public class Merge extends Entity implements CommentSupport { - - /** - * 类型 - * - * @author 夜雨 - * @since 2023-08-17 11:32 - */ - public enum Type { - - /** 异常 */ - BUG, - - /** 功能 */ - FEATURE, - - /** 安全 */ - SECURITY, - - /** 文档改进 */ - DOCUMENT, - - /** 重构 */ - REFACTOR - } - - /** - * 状态 - * - * @author 夜雨 - * @since 2023-08-17 11:33 - */ - public enum Status { - - /** 等待代码审查 */ - BEFORE_CHECK, - - /** 等待合并 */ - WAITING, - - /** 已合并 */ - MERGED, - - /** 已拒绝 */ - REJECTED, - - CLOSED - } - - /** 所属仓库 ID */ - protected long repositoryId; - - /** 申请合并用户 ID */ - protected long requesterId; - - /** 相关反馈 ID */ - protected Long issueId; - - /** 类型 */ - protected Type type; - - /** 来自分支 */ - protected String fromBranch; - - /** 去向分支(基准分支) */ - protected String toBranch; - - /** 说明标题 */ - protected String title; - - /** 合并说明 */ - protected String description; - - /** 通过审查时间 */ - protected Long checkedAt; - - /** 合并时间 */ - protected Long mergedAt; - - /** 拒绝时间 */ - protected Long rejectedAt; - - /** 拒绝原因 */ - protected String rejectReason; - - /** 关闭时间 */ - protected Long closedAt; - - /** 请求状态 */ - protected Status status; - - @Override - public boolean canComment() { - return status != Status.CLOSED; - } - - @Override - public boolean canNotComment() { - return !canComment(); - } - - /** @return true 为可更新 */ - public boolean canUpdate() { - return status == Status.BEFORE_CHECK && !isClosed(); - } - - /** @return true 为可更新 */ - public boolean canNotUpdate() { - return !canUpdate(); - } - - /** @return true 为已关闭 */ - public boolean isClosed() { - return closedAt != null; - } -} diff --git a/src/main/java/com/imyeyu/api/modules/git/entity/PushLog.java b/src/main/java/com/imyeyu/api/modules/git/entity/PushLog.java deleted file mode 100644 index 344f7f1..0000000 --- a/src/main/java/com/imyeyu/api/modules/git/entity/PushLog.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.imyeyu.api.modules.git.entity; - -import com.imyeyu.spring.annotation.table.Id; -import com.imyeyu.spring.annotation.table.Table; -import com.imyeyu.spring.entity.Creatable; -import com.imyeyu.spring.entity.IDEntity; -import lombok.Data; - -/** - * Git 推送日志 - * - * @author 夜雨 - * @since 2023-09-19 11:35 - */ -@Data -@Table("git_push_log") -public class PushLog implements IDEntity, Creatable { - - @Id - protected Long id; - - /** 所属仓库 ID */ - protected Long repositoryId; - - /** 推送 ID */ - protected Long pusherId; - - /** 分支 */ - protected String branch; - - /** 来源提交 */ - protected String from; - - /** 去向提交 */ - protected String to; - - /** 推送时间 */ - protected Long createdAt; -} diff --git a/src/main/java/com/imyeyu/api/modules/git/entity/Release.java b/src/main/java/com/imyeyu/api/modules/git/entity/Release.java deleted file mode 100644 index 7753cbb..0000000 --- a/src/main/java/com/imyeyu/api/modules/git/entity/Release.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.imyeyu.api.modules.git.entity; - -import com.imyeyu.spring.annotation.table.Table; -import lombok.Data; -import lombok.EqualsAndHashCode; -import com.imyeyu.spring.entity.Entity; - -/** - * Git 版本发布 - * - * @author 夜雨 - * @since 2023-08-16 11:25 - */ -@Data -@EqualsAndHashCode(callSuper = true) -@Table("git_release") -public class Release extends Entity { - - /** 所属仓库 ID */ - protected long repositoryId; - - /** 版本 */ - protected String version; - - /** 描述 */ - protected String description; - - /** SHA1 */ - protected String sha1; - - /** 发布时提交数量 */ - protected int commits; -} diff --git a/src/main/java/com/imyeyu/api/modules/git/mapper/DeveloperMapper.java b/src/main/java/com/imyeyu/api/modules/git/mapper/DeveloperMapper.java deleted file mode 100644 index 023c0fb..0000000 --- a/src/main/java/com/imyeyu/api/modules/git/mapper/DeveloperMapper.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.imyeyu.api.modules.git.mapper; - -import com.imyeyu.api.modules.git.entity.Developer; -import com.imyeyu.spring.mapper.BaseMapper; -import org.apache.ibatis.annotations.Select; - -/** - * @author 夜雨 - * @since 2023-09-19 11:42 - */ -public interface DeveloperMapper extends BaseMapper { - - @Select("SELECT * FROM git_developer WHERE BINARY name = #{name} LIMIT 1") - Developer queryByName(String name); -} diff --git a/src/main/java/com/imyeyu/api/modules/git/mapper/IssueMapper.java b/src/main/java/com/imyeyu/api/modules/git/mapper/IssueMapper.java deleted file mode 100644 index fb7867d..0000000 --- a/src/main/java/com/imyeyu/api/modules/git/mapper/IssueMapper.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.imyeyu.api.modules.git.mapper; - -import com.imyeyu.api.modules.git.entity.Issue; -import com.imyeyu.api.modules.git.vo.issue.IssuePage; -import com.imyeyu.spring.mapper.BaseMapper; -import org.apache.ibatis.annotations.Param; - -import java.util.List; - -/** - * Git 反馈表 - * - * @author 夜雨 - * @since 2023-08-16 15:19 - */ -public interface IssueMapper extends BaseMapper { - - /** - * 根据仓库 ID 统计总反馈数量 - * - * @param issuePage 仓库 ID - * @return 仓库反馈总数量 - */ - long countByIssuePage(@Param("issuePage") IssuePage issuePage); - - /** - * 根据仓库 ID 查询部分反馈 - * - * @param issuePage 仓库 ID - * @param offset 偏移 - * @param limit 数据量 - * @return 反馈列表 - */ - List listByIssuePage(@Param("issuePage") IssuePage issuePage, long offset, long limit); -} diff --git a/src/main/java/com/imyeyu/api/modules/git/mapper/MergeMapper.java b/src/main/java/com/imyeyu/api/modules/git/mapper/MergeMapper.java deleted file mode 100644 index 98dbb81..0000000 --- a/src/main/java/com/imyeyu/api/modules/git/mapper/MergeMapper.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.imyeyu.api.modules.git.mapper; - -import com.imyeyu.api.modules.git.entity.Merge; -import com.imyeyu.api.modules.git.vo.merge.MergePage; -import com.imyeyu.spring.mapper.BaseMapper; -import org.apache.ibatis.annotations.Param; -import org.apache.ibatis.annotations.Select; - -import java.util.List; - -/** - * @author 夜雨 - * @since 2023-08-17 11:42 - */ -public interface MergeMapper extends BaseMapper { - - @Select("SELECT * FROM git_merge WHERE id = #{id}" + NOT_DELETE + LIMIT_1) - Merge select(Long id); - - long countByMergePage(@Param("mergePage") MergePage mergePage); - - List listByMergePage(@Param("mergePage") MergePage mergePage, long offset, long limit); -} diff --git a/src/main/java/com/imyeyu/api/modules/git/mapper/ReleaseMapper.java b/src/main/java/com/imyeyu/api/modules/git/mapper/ReleaseMapper.java deleted file mode 100644 index eafd664..0000000 --- a/src/main/java/com/imyeyu/api/modules/git/mapper/ReleaseMapper.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.imyeyu.api.modules.git.mapper; - -import com.imyeyu.api.modules.git.entity.Release; -import com.imyeyu.api.modules.git.vo.release.ReleaseView; -import com.imyeyu.spring.mapper.BaseMapper; -import org.apache.ibatis.annotations.Select; - -import java.util.List; - -/** - * @author 夜雨 - * @since 2023-08-16 11:30 - */ -public interface ReleaseMapper extends BaseMapper { - - @Select("SELECT COUNT(1) FROM git_release WHERE repository_id = #{repositoryId}" + NOT_DELETE) - long countByRepositoryId(long repositoryId); - - List listByRepositoryId(long repositoryId, long offset, long limit); - - @Select("SELECT * FROM git_release WHERE repository_id = #{repositoryId}" + NOT_DELETE + "ORDER BY created_at DESC" + LIMIT_1) - Release queryLatestByRepositoryId(long repositoryId); -} diff --git a/src/main/java/com/imyeyu/api/modules/git/service/DeveloperService.java b/src/main/java/com/imyeyu/api/modules/git/service/DeveloperService.java deleted file mode 100644 index f893cfb..0000000 --- a/src/main/java/com/imyeyu/api/modules/git/service/DeveloperService.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.imyeyu.api.modules.git.service; - -import com.imyeyu.api.modules.git.entity.Developer; -import com.imyeyu.spring.service.CreatableService; -import com.imyeyu.spring.service.GettableService; -import com.imyeyu.spring.service.UpdatableService; - -/** - * @author 夜雨 - * @since 2023-09-19 11:40 - */ -public interface DeveloperService extends GettableService, CreatableService, UpdatableService { - - Developer view(); - - Developer getByName(String name); -} diff --git a/src/main/java/com/imyeyu/api/modules/git/service/IssueService.java b/src/main/java/com/imyeyu/api/modules/git/service/IssueService.java deleted file mode 100644 index caeccc3..0000000 --- a/src/main/java/com/imyeyu/api/modules/git/service/IssueService.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.imyeyu.api.modules.git.service; - -import com.imyeyu.api.modules.git.entity.Issue; -import com.imyeyu.api.modules.git.vo.issue.IssuePage; -import com.imyeyu.api.modules.git.vo.issue.IssueRequest; -import com.imyeyu.spring.bean.PageResult; -import com.imyeyu.spring.service.GettableService; - -/** - * @author 夜雨 - * @since 2023-08-16 15:18 - */ -public interface IssueService extends GettableService { - - void create(IssueRequest issueRequest); - - void update(IssueRequest issueRequest); - - PageResult page(IssuePage issuePage); -} diff --git a/src/main/java/com/imyeyu/api/modules/git/service/MergeService.java b/src/main/java/com/imyeyu/api/modules/git/service/MergeService.java deleted file mode 100644 index 31558d9..0000000 --- a/src/main/java/com/imyeyu/api/modules/git/service/MergeService.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.imyeyu.api.modules.git.service; - -import com.imyeyu.api.modules.git.entity.Merge; -import com.imyeyu.api.modules.git.vo.merge.MergePage; -import com.imyeyu.api.modules.git.vo.merge.MergeRequest; -import com.imyeyu.spring.bean.PageResult; -import com.imyeyu.spring.service.GettableService; - -/** - * @author 夜雨 - * @since 2023-08-16 14:46 - */ -public interface MergeService extends GettableService { - - void create(MergeRequest mergeRequest); - - void update(MergeRequest mergeRequest); - - PageResult page(MergePage mergePage); -} diff --git a/src/main/java/com/imyeyu/api/modules/git/service/ReleaseService.java b/src/main/java/com/imyeyu/api/modules/git/service/ReleaseService.java deleted file mode 100644 index f5035c3..0000000 --- a/src/main/java/com/imyeyu/api/modules/git/service/ReleaseService.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.imyeyu.api.modules.git.service; - -import com.imyeyu.api.modules.git.vo.release.ReleasePage; -import com.imyeyu.api.modules.git.vo.release.ReleaseView; -import com.imyeyu.spring.bean.PageResult; - -/** - * @author 夜雨 - * @since 2023-08-16 11:27 - */ -public interface ReleaseService { - - PageResult pageByRepositoryId(ReleasePage releasePage); -} diff --git a/src/main/java/com/imyeyu/api/modules/git/service/RepositoryService.java b/src/main/java/com/imyeyu/api/modules/git/service/RepositoryService.java deleted file mode 100644 index 42eda32..0000000 --- a/src/main/java/com/imyeyu/api/modules/git/service/RepositoryService.java +++ /dev/null @@ -1,44 +0,0 @@ -package com.imyeyu.api.modules.git.service; - -import com.imyeyu.java.bean.timi.TimiException; -import com.imyeyu.api.modules.git.bean.gitea.Branch; -import com.imyeyu.api.modules.git.bean.gitea.File; -import com.imyeyu.api.modules.git.bean.gitea.Repository; -import com.imyeyu.spring.service.PageableService; - -import java.io.InputStream; -import java.util.List; - -/** - * Git 仓库服务 - * - * @author 夜雨 - * @since 2023-08-06 01:19 - */ -public interface RepositoryService extends PageableService { - - /** - * 根据名称获取数据库仓库 - * - * @param repoName 仓库名 - * @return 数据库仓库 - * @throws TimiException 服务异常 - */ - Repository get(String repoName); - /** - * 获取仓库所有分支列表 - * - * @param repoName 仓库名 - * @return 分支列表 - * @throws TimiException 服务异常 - */ - List listBranches(String repoName); - - List listFile(String repoName, String branch, String path); - - InputStream getFileRaw(String repoName, String branch, String path); - - String getFileMimeType(String repoName, String branch, String path); - - InputStream getArchive(String repoName, String branch); -} diff --git a/src/main/java/com/imyeyu/api/modules/git/service/implement/DeveloperServiceImplement.java b/src/main/java/com/imyeyu/api/modules/git/service/implement/DeveloperServiceImplement.java deleted file mode 100644 index 4380fc4..0000000 --- a/src/main/java/com/imyeyu/api/modules/git/service/implement/DeveloperServiceImplement.java +++ /dev/null @@ -1,66 +0,0 @@ -package com.imyeyu.api.modules.git.service.implement; - -import com.imyeyu.java.TimiJava; -import com.imyeyu.java.bean.timi.TimiCode; -import com.imyeyu.java.bean.timi.TimiException; -import com.imyeyu.api.config.dbsource.TimiServerDBConfig; -import com.imyeyu.api.modules.common.service.UserService; -import com.imyeyu.api.modules.git.entity.Developer; -import com.imyeyu.api.modules.git.mapper.DeveloperMapper; -import com.imyeyu.api.modules.git.service.DeveloperService; -import com.imyeyu.spring.mapper.BaseMapper; -import com.imyeyu.spring.service.AbstractEntityService; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.context.annotation.Lazy; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -/** - * @author 夜雨 - * @since 2023-09-19 11:41 - */ -@Slf4j -@Service -@RequiredArgsConstructor(onConstructor_ = { @Lazy}) -public class DeveloperServiceImplement extends AbstractEntityService implements DeveloperService { - - private final UserService userService; - - private final DeveloperMapper mapper; - - @Override - protected BaseMapper mapper() { - return mapper; - } - - @Transactional(TimiServerDBConfig.ROLLBACKER) - @Override - public void update(Developer developer) { - Developer dbDeveloper = get(userService.getLoginUser().getId()); - dbDeveloper.setName(developer.getName()); - dbDeveloper.setRsa(developer.getRsa()); - - if (TimiJava.isNotEmpty(dbDeveloper.getName())) { - Developer developerByName = getByName(dbDeveloper.getName()); - if (developerByName != null && !developerByName.getDeveloperId().equals(dbDeveloper.getDeveloperId())) { - throw new TimiException(TimiCode.ARG_BAD).msgKey("TODO 开发者冲突,此名称已被使用"); - } - } - if (TimiJava.isNotEmpty(dbDeveloper.getRsa())) { - if (!dbDeveloper.getRsa().startsWith("ssh-rsa")) { - throw new TimiException(TimiCode.ARG_BAD).msgKey("TODO 无效的 RSA 公钥"); - } - } - super.update(dbDeveloper); - } - - @Override - public Developer view() { - return get(userService.getLoginUser().getId()); - } - - public Developer getByName(String name) { - return mapper.queryByName(name); - } -} diff --git a/src/main/java/com/imyeyu/api/modules/git/service/implement/IssueServiceImplement.java b/src/main/java/com/imyeyu/api/modules/git/service/implement/IssueServiceImplement.java deleted file mode 100644 index f918f98..0000000 --- a/src/main/java/com/imyeyu/api/modules/git/service/implement/IssueServiceImplement.java +++ /dev/null @@ -1,96 +0,0 @@ -package com.imyeyu.api.modules.git.service.implement; - -import com.imyeyu.java.TimiJava; -import com.imyeyu.java.bean.timi.TimiException; -import com.imyeyu.api.config.dbsource.TimiServerDBConfig; -import com.imyeyu.api.modules.common.entity.User; -import com.imyeyu.api.modules.common.service.UserService; -import com.imyeyu.api.modules.git.entity.Issue; -import com.imyeyu.api.modules.git.mapper.IssueMapper; -import com.imyeyu.api.modules.git.service.IssueService; -import com.imyeyu.api.modules.git.vo.issue.IssuePage; -import com.imyeyu.api.modules.git.vo.issue.IssueRequest; -import com.imyeyu.api.modules.gitea.entity.Repository; -import com.imyeyu.api.modules.gitea.service.GiteaService; -import com.imyeyu.spring.TimiSpring; -import com.imyeyu.spring.bean.PageResult; -import com.imyeyu.spring.mapper.BaseMapper; -import com.imyeyu.spring.service.AbstractEntityService; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -/** - * Git 反馈服务 - * - * @author 夜雨 - * @since 2023-08-16 15:18 - */ -@Service -@RequiredArgsConstructor -public class IssueServiceImplement extends AbstractEntityService implements IssueService { - - private final UserService userService; - private final GiteaService giteaService; - - private final IssueMapper mapper; - - @Override - protected BaseMapper mapper() { - return mapper; - } - - @Transactional(TimiServerDBConfig.ROLLBACKER) - @Override - public void create(IssueRequest issueRequest) { - Repository repository = giteaService.getRepository(issueRequest.getRepositoryId()); - Issue issue = new Issue(); - // 令牌和账号验证 - if (TimiJava.isNotEmpty(TimiSpring.getToken())) { - User publisher = userService.getLoginUser(); - TimiException.requiredTrue(!publisher.isBanning() && !publisher.isMuting(), "TODO publisher.banned"); - issue.setPublisherId(publisher.getId()); - issue.setPublisherNick(null); - } else { - // 昵称 - TimiException.required(issueRequest.getPublisherNick(), "TODO comment.nick.empty"); - issue.setPublisherNick(issueRequest.getPublisherNick()); - } - TimiException.required(issueRequest.getTitle(), "TODO comment.title.empty"); - TimiException.required(issueRequest.getDescription(), "TODO comment.data.empty"); - issue.setRepositoryId(repository.getId()); - issue.setType(issueRequest.getType()); - issue.setVersion(issueRequest.getVersion()); - issue.setTitle(issueRequest.getTitle()); - issue.setDescription(issueRequest.getDescription()); - issue.setStatus(Issue.Status.BEFORE_CONFIRM); - super.create(issue); - } - - @Transactional(TimiServerDBConfig.ROLLBACKER) - @Override - public void update(IssueRequest issueRequest) { - Issue issue = get(issueRequest.getId()); - TimiException.requiredTrue(issue.canUpdate(), "TODO can not update"); - - User publisher = userService.getLoginUser(); - TimiException.required(publisher.getId().equals(issue.getPublisherId()), "TODO not permission edit issue"); - TimiException.requiredTrue(!publisher.isMuting(), "TODO publisher.banned"); - - TimiException.required(issueRequest.getTitle(), "TODO comment.title.empty"); - TimiException.required(issueRequest.getDescription(), "TODO comment.data.empty"); - issue.setType(issueRequest.getType()); - issue.setVersion(issueRequest.getVersion()); - issue.setTitle(issueRequest.getTitle()); - issue.setDescription(issueRequest.getDescription()); - mapper.update(issue); - } - - @Override - public PageResult page(IssuePage issuePage) { - PageResult result = new PageResult<>(); - result.setList(mapper.listByIssuePage(issuePage, issuePage.getOffset(), issuePage.getLimit())); - result.setTotal(mapper.countByIssuePage(issuePage)); - return result; - } -} diff --git a/src/main/java/com/imyeyu/api/modules/git/service/implement/MergeServiceImplement.java b/src/main/java/com/imyeyu/api/modules/git/service/implement/MergeServiceImplement.java deleted file mode 100644 index 4ba2bf1..0000000 --- a/src/main/java/com/imyeyu/api/modules/git/service/implement/MergeServiceImplement.java +++ /dev/null @@ -1,112 +0,0 @@ -package com.imyeyu.api.modules.git.service.implement; - -import com.imyeyu.java.TimiJava; -import com.imyeyu.java.bean.timi.TimiException; -import com.imyeyu.api.config.dbsource.TimiServerDBConfig; -import com.imyeyu.api.modules.common.entity.User; -import com.imyeyu.api.modules.common.service.UserService; -import com.imyeyu.api.modules.git.entity.Issue; -import com.imyeyu.api.modules.git.entity.Merge; -import com.imyeyu.api.modules.git.mapper.MergeMapper; -import com.imyeyu.api.modules.git.service.IssueService; -import com.imyeyu.api.modules.git.service.MergeService; -import com.imyeyu.api.modules.git.service.RepositoryService; -import com.imyeyu.api.modules.git.vo.merge.MergePage; -import com.imyeyu.api.modules.git.vo.merge.MergeRequest; -import com.imyeyu.spring.bean.PageResult; -import com.imyeyu.spring.mapper.BaseMapper; -import com.imyeyu.spring.service.AbstractEntityService; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -/** - * @author 夜雨 - * @since 2023-08-17 11:38 - */ -@Service -@RequiredArgsConstructor -public class MergeServiceImplement extends AbstractEntityService implements MergeService { - - private final UserService userService; - private final IssueService issueService; - private final RepositoryService repositoryService; - - private final MergeMapper mapper; - - @Override - protected BaseMapper mapper() { - return mapper; - } - - @Transactional(TimiServerDBConfig.ROLLBACKER) - @Override - public void create(MergeRequest mergeRequest) { -// Repository repository = repositoryService.get(mergeRequest.getRepositoryId()); - User requester = userService.getLoginUser(); - TimiException.required(mergeRequest.getTitle(), "TODO mergeRequest.title.empty"); - TimiException.required(mergeRequest.getDescription(), "TODO mergeRequest.description.empty"); - TimiException.required(mergeRequest.getFromBranch(), "TODO mergeRequest.fromBranch.empty"); -// List branches = repositoryService.listAllBranches(repository.getId()); -// if (branches.stream().filter(i -> i.getName().equals(mergeRequest.getFromBranch())).toList().isEmpty()) { -// throw new TimiException(TimiCode.ARG_MISS).msgKey("TODO 来源分支不存在"); -// } -// if (branches.stream().filter(i -> i.getName().equals(mergeRequest.getToBranch())).toList().isEmpty()) { -// throw new TimiException(TimiCode.ARG_MISS).msgKey("TODO 去向分支不存在"); -// } - Merge merge = new Merge(); - if (TimiJava.isNotEmpty(mergeRequest.getIssueId())) { - Issue issue = issueService.get(mergeRequest.getIssueId()); - merge.setIssueId(issue.getId()); - } -// merge.setRepositoryId(repository.getId()); - merge.setRequesterId(requester.getId()); - merge.setType(mergeRequest.getType()); - merge.setFromBranch(mergeRequest.getFromBranch()); - merge.setToBranch(mergeRequest.getToBranch()); - merge.setTitle(mergeRequest.getTitle()); - merge.setDescription(mergeRequest.getDescription()); - merge.setStatus(Merge.Status.BEFORE_CHECK); - mapper.insert(merge); - } - - @Transactional(TimiServerDBConfig.ROLLBACKER) - @Override - public void update(MergeRequest mergeRequest) { - Merge merge = get(mergeRequest.getId()); - - TimiException.requiredTrue(merge.canNotUpdate(), "TODO can not update"); - TimiException.required(mergeRequest.getTitle(), "TODO mergeRequest.title.empty"); - TimiException.required(mergeRequest.getDescription(), "TODO mergeRequest.data.empty"); - TimiException.requiredTrue(mergeRequest.getFromBranch().equals(merge.getToBranch()), "TODO 来源分支和去向分支不能相等"); - - User requester = userService.getLoginUser(); - TimiException.requiredTrue(!requester.getId().equals(merge.getRequesterId()), "TODO not permission edit merge"); - -// List branches = repositoryService.listAllBranches(merge.getRepositoryId()); -// if (branches.stream().filter(i -> i.getName().equals(mergeRequest.getFromBranch())).toList().isEmpty()) { -// throw new TimiException(TimiCode.ARG_MISS).msgKey("TODO 来源分支不存在"); -// } -// if (branches.stream().filter(i -> i.getName().equals(mergeRequest.getToBranch())).toList().isEmpty()) { -// throw new TimiException(TimiCode.ARG_MISS).msgKey("TODO 去向分支不存在"); -// } -// if (TimiJava.isNotEmpty(mergeRequest.getIssueId())) { -// Issue issue = issueService.get(mergeRequest.getIssueId()); -// merge.setIssueId(issue.getId()); -// } - merge.setType(mergeRequest.getType()); - merge.setFromBranch(mergeRequest.getFromBranch()); - merge.setToBranch(mergeRequest.getToBranch()); - merge.setTitle(mergeRequest.getTitle()); - merge.setDescription(mergeRequest.getDescription()); - mapper.update(merge); - } - - @Override - public PageResult page(MergePage mergePage) { - PageResult result = new PageResult<>(); - result.setList(mapper.listByMergePage(mergePage, mergePage.getOffset(), mergePage.getLimit())); - result.setTotal(mapper.countByMergePage(mergePage)); - return result; - } -} diff --git a/src/main/java/com/imyeyu/api/modules/git/service/implement/ReleaseServiceImplement.java b/src/main/java/com/imyeyu/api/modules/git/service/implement/ReleaseServiceImplement.java deleted file mode 100644 index 46df108..0000000 --- a/src/main/java/com/imyeyu/api/modules/git/service/implement/ReleaseServiceImplement.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.imyeyu.api.modules.git.service.implement; - -import lombok.RequiredArgsConstructor; -import com.imyeyu.api.modules.git.mapper.ReleaseMapper; -import com.imyeyu.api.modules.git.service.ReleaseService; -import com.imyeyu.api.modules.git.service.RepositoryService; -import com.imyeyu.api.modules.git.vo.release.ReleasePage; -import com.imyeyu.api.modules.git.vo.release.ReleaseView; -import com.imyeyu.spring.bean.PageResult; -import org.springframework.stereotype.Service; - -/** - * @author 夜雨 - * @since 2023-08-16 11:27 - */ -@Service -@RequiredArgsConstructor -public class ReleaseServiceImplement implements ReleaseService { - - private final RepositoryService repositoryService; - - private final ReleaseMapper mapper; - - @Override - public PageResult pageByRepositoryId(ReleasePage releasePage) { -// Repository repository = repositoryService.get(releasePage.getRepositoryId()); - PageResult result = new PageResult<>(); -// result.setList(mapper.listByRepositoryId(repository.getId(), releasePage.getOffset(), releasePage.getLimit())); -// result.setTotal(mapper.countByRepositoryId(repository.getId())); - return result; - } -} diff --git a/src/main/java/com/imyeyu/api/modules/git/service/implement/RepositoryServiceImplement.java b/src/main/java/com/imyeyu/api/modules/git/service/implement/RepositoryServiceImplement.java deleted file mode 100644 index 8d2740e..0000000 --- a/src/main/java/com/imyeyu/api/modules/git/service/implement/RepositoryServiceImplement.java +++ /dev/null @@ -1,224 +0,0 @@ -package com.imyeyu.api.modules.git.service.implement; - -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.PropertyNamingStrategies; -import com.imyeyu.java.TimiJava; -import com.imyeyu.java.bean.timi.TimiCode; -import com.imyeyu.java.bean.timi.TimiException; -import com.imyeyu.network.Network; -import com.imyeyu.api.modules.common.bean.SettingKey; -import com.imyeyu.api.modules.common.service.SettingService; -import com.imyeyu.api.modules.common.service.UserService; -import com.imyeyu.api.modules.git.bean.gitea.API; -import com.imyeyu.api.modules.git.bean.gitea.Branch; -import com.imyeyu.api.modules.git.bean.gitea.File; -import com.imyeyu.api.modules.git.bean.gitea.GiteaResponse; -import com.imyeyu.api.modules.git.bean.gitea.Repository; -import com.imyeyu.api.modules.git.service.RepositoryService; -import com.imyeyu.api.modules.gitea.entity.User; -import com.imyeyu.api.modules.gitea.service.GiteaService; -import com.imyeyu.spring.bean.Page; -import com.imyeyu.spring.bean.PageResult; -import jakarta.activation.MimetypesFileTypeMap; -import jakarta.annotation.PostConstruct; -import lombok.Cleanup; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.apache.hc.client5.http.fluent.Request; -import org.apache.hc.core5.http.ClassicHttpResponse; -import org.apache.hc.core5.http.io.entity.EntityUtils; -import org.apache.tika.Tika; -import org.eclipse.jgit.lib.ObjectId; -import org.eclipse.jgit.lib.ObjectLoader; -import org.eclipse.jgit.lib.ObjectReader; -import org.eclipse.jgit.revwalk.RevCommit; -import org.eclipse.jgit.revwalk.RevTree; -import org.eclipse.jgit.revwalk.RevWalk; -import org.eclipse.jgit.storage.file.FileRepositoryBuilder; -import org.eclipse.jgit.treewalk.TreeWalk; -import org.springframework.stereotype.Service; - -import java.io.InputStream; -import java.nio.file.Path; -import java.util.HashMap; -import java.util.List; - -/** - * Git 仓库服务 - * - * @author 夜雨 - * @since 2023-08-06 01:19 - */ -@Slf4j -@Service -@RequiredArgsConstructor -public class RepositoryServiceImplement implements RepositoryService { - - private final UserService userService; - private final GiteaService giteaService; - private final SettingService settingService; - private final ObjectMapper jackson; - - private User owner; - private ObjectMapper giteaJackson; - - @PostConstruct - private void postConstruct() { - owner = giteaService.getOwner(); - giteaJackson = jackson.copy().setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE); - } - - @Override - public PageResult page(Page page) { - try { - ClassicHttpResponse resp = (ClassicHttpResponse) Request.get(API.REPO_LIST.buildURL(null, new HashMap<>() {{ - put("uid", owner.getId()); - put("sort", "updated"); - put("order", "desc"); - put("page", page.getIndex() + 1); - put("limit", page.getLimit()); - }})).execute().returnResponse(); - String respText = EntityUtils.toString(resp.getEntity()); - GiteaResponse> respObj = giteaJackson.readValue(respText, new TypeReference<>() {}); - - PageResult result = new PageResult<>(); - result.setTotal(Long.parseLong(resp.getHeader("X-Total-Count").getValue())); - result.setList(respObj.getData()); - return result; - } catch (Exception e) { - log.error("list repository error", e); - throw new TimiException(TimiCode.ERROR, "list repository error", e); - } - } - - @Override - public Repository get(String repoName) { - try { - String respText = Request.get(API.REPO_GET.buildURL(new HashMap<>() {{ - put("owner", owner.getName()); - put("repoName", repoName); - }})).execute().returnContent().asString(); - return giteaJackson.readValue(respText, Repository.class); - } catch (Exception e) { - log.error("get repository error", e); - throw new TimiException(TimiCode.ERROR, "get repository error", e); - } - } - - @Override - public List listBranches(String repoName) { - try { - String respText = Request.get(API.REPO_BRANCHES_LIST.buildURL(new HashMap<>() {{ - put("owner", owner.getName()); - put("repoName", repoName); - }})).execute().returnContent().asString(); - return giteaJackson.readValue(respText, new TypeReference<>() {}); - } catch (Exception e) { - log.error("list repository branches error", e); - throw new TimiException(TimiCode.ERROR, "list repository branches error", e); - } - } - - @Override - public List listFile(String repoName, String branch, String path) { - try { - String respText = Request.get(API.REPO_FILE_LIST.buildURL(new HashMap<>() {{ - put("owner", owner.getName()); - put("repoName", repoName); - put("path", path); - }}, new HashMap<>() {{ - put("ref", branch); - }})).execute().returnContent().asString(); - List list = giteaJackson.readValue(respText, new TypeReference<>() {}); - // 排序 - list.sort((f1, f2) -> { - if (f1.getType() == File.Type.dir && f2.getType() == File.Type.file) { - return -1; - } else { - if (f1.getType() == File.Type.file && f2.getType() == File.Type.dir) { - return 1; - } - return f1.getName().compareToIgnoreCase(f2.getName()); - } - }); - return list; - } catch (Exception e) { - log.error("list repository file error", e); - throw new TimiException(TimiCode.ERROR, "list repository file error", e); - } - } - - @Override - public InputStream getFileRaw(String repoName, String branch, String path) { - try { - ClassicHttpResponse resp = (ClassicHttpResponse) Request.get(API.REPO_FILE_RAW.buildURL(new HashMap<>() {{ - put("owner", owner.getName()); - put("repoName", repoName); - put("path", path); - }}, new HashMap<>() {{ - put("ref", branch); - }})).execute().returnResponse(); - return resp.getEntity().getContent(); - } catch (Exception e) { - log.error("get repository file raw error", e); - throw new TimiException(TimiCode.ERROR, "get repository file raw error", e); - } - } - - @Override - public String getFileMimeType(String repoName, String branch, String path) { - try { - String repoPath = settingService.getAsString(SettingKey.GIT_REPO_PATH); - if (TimiJava.isEmpty(repoPath)) { - // 根据文件名猜测 - return new MimetypesFileTypeMap().getContentType(Network.uriFileName(path)); - } else { - repoPath = "%s/%s.git".formatted(repoPath, repoName.toLowerCase()); - // 仓库 - @Cleanup - org.eclipse.jgit.lib.Repository repo = new FileRepositoryBuilder().setGitDir(Path.of(repoPath).toFile()).build(); - TimiException.required(repo, "not found repository: %s".formatted(repoPath)); - - // 分支 - ObjectId commitId = repo.resolve("refs/heads/%s".formatted(branch)); - TimiException.required(commitId, "not found branch: %s".formatted(branch)); - - // 提交 - RevWalk revWalk = new RevWalk(repo); - RevCommit commit = revWalk.parseCommit(commitId); - RevTree tree = commit.getTree(); - - // 文件 - path = path.substring(1); - TreeWalk treeWalk = TreeWalk.forPath(repo, path, tree); - TimiException.required(treeWalk, "not found file: %s".formatted(path)); - ObjectId fileObjectId = treeWalk.getObjectId(0); - - // 读取文件 - ObjectReader reader = repo.newObjectReader(); - ObjectLoader loader = reader.open(fileObjectId); - InputStream stream = loader.openStream(); - return new Tika().detect(stream); - } - } catch (Exception e) { - log.error("get repository file mime error", e); - throw new TimiException(TimiCode.ERROR, "get repository file mime error", e); - } - } - - @Override - public InputStream getArchive(String repoName, String branch) { - try { - ClassicHttpResponse resp = (ClassicHttpResponse) Request.get(API.REPO_ARCHIVE.buildURL(new HashMap<>() {{ - put("owner", owner.getName()); - put("repoName", repoName); - put("archiveFormat", "%s.tar.gz".formatted(branch)); - }})).execute().returnResponse(); - return resp.getEntity().getContent(); - } catch (Exception e) { - log.error("get repository archive error", e); - throw new TimiException(TimiCode.ERROR, "get repository archive error", e); - } - } -} diff --git a/src/main/java/com/imyeyu/api/modules/git/util/GiteaTimestampAdapter.java b/src/main/java/com/imyeyu/api/modules/git/util/GiteaTimestampAdapter.java deleted file mode 100644 index 2fd1827..0000000 --- a/src/main/java/com/imyeyu/api/modules/git/util/GiteaTimestampAdapter.java +++ /dev/null @@ -1,54 +0,0 @@ -package com.imyeyu.api.modules.git.util; - -import com.fasterxml.jackson.core.JacksonException; -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonDeserializer; -import com.fasterxml.jackson.databind.JsonSerializer; -import com.fasterxml.jackson.databind.SerializerProvider; -import lombok.AccessLevel; -import lombok.NoArgsConstructor; - -import java.io.IOException; -import java.time.Instant; - -/** - * @author 夜雨 - * @since 2025-06-26 11:40 - */ -@NoArgsConstructor(access = AccessLevel.PRIVATE) -public final class GiteaTimestampAdapter { - - /** - * - * - * @author 夜雨 - * @since 2026-04-07 22:16 - */ - public static class Serializer extends JsonSerializer { - - @Override - public void serialize(Long value, JsonGenerator gen, SerializerProvider serializers) throws IOException { - if (value == null) { - gen.writeNull(); - } else { - gen.writeNumber(value); - } - } - } - - /** - * - * - * @author 夜雨 - * @since 2026-04-07 22:19 - */ - public static class Deserializer extends JsonDeserializer { - - @Override - public Long deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JacksonException { - return Instant.parse(p.getText()).toEpochMilli(); - } - } -} diff --git a/src/main/java/com/imyeyu/api/modules/git/vo/developer/DeveloperRequest.java b/src/main/java/com/imyeyu/api/modules/git/vo/developer/DeveloperRequest.java deleted file mode 100644 index 30532f8..0000000 --- a/src/main/java/com/imyeyu/api/modules/git/vo/developer/DeveloperRequest.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.imyeyu.api.modules.git.vo.developer; - -import lombok.Data; - -/** - * @author 夜雨 - * @since 2023-09-19 11:57 - */ -@Data -public class DeveloperRequest { - - private String email; - - private String rsa; -} diff --git a/src/main/java/com/imyeyu/api/modules/git/vo/issue/IssuePage.java b/src/main/java/com/imyeyu/api/modules/git/vo/issue/IssuePage.java deleted file mode 100644 index 512c594..0000000 --- a/src/main/java/com/imyeyu/api/modules/git/vo/issue/IssuePage.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.imyeyu.api.modules.git.vo.issue; - -import lombok.Data; -import lombok.EqualsAndHashCode; -import com.imyeyu.api.modules.git.entity.Issue; -import com.imyeyu.spring.bean.Page; - -/** - * @author 夜雨 - * @since 2023-08-15 15:04 - */ -@Data -@EqualsAndHashCode(callSuper = true) -public class IssuePage extends Page { - - private long repositoryId; - - private Issue.Type type; - - private Issue.Status status; -} diff --git a/src/main/java/com/imyeyu/api/modules/git/vo/issue/IssueRequest.java b/src/main/java/com/imyeyu/api/modules/git/vo/issue/IssueRequest.java deleted file mode 100644 index 56f8394..0000000 --- a/src/main/java/com/imyeyu/api/modules/git/vo/issue/IssueRequest.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.imyeyu.api.modules.git.vo.issue; - -import jakarta.validation.constraints.NotNull; -import lombok.Data; -import com.imyeyu.api.modules.git.entity.Issue; - -/** - * 工单反馈 - * - * @author 夜雨 - * @since 2023-08-15 15:51 - */ -@Data -public class IssueRequest { - - /** 工单 ID,在编辑时携带 */ - private Long id; - - /** 工单类型 */ - @NotNull - private Issue.Type type; - - /** 所属仓库 ID,创建工单时携带,编辑时不需要 */ - private Long repositoryId; - - /** 反馈用户昵称 */ - private String publisherNick; - - /** 版本 */ - private String version; - - /** 标题 */ - private String title; - - /** 反馈数据 */ - private String description; -} diff --git a/src/main/java/com/imyeyu/api/modules/git/vo/issue/IssueView.java b/src/main/java/com/imyeyu/api/modules/git/vo/issue/IssueView.java deleted file mode 100644 index 5be5b2c..0000000 --- a/src/main/java/com/imyeyu/api/modules/git/vo/issue/IssueView.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.imyeyu.api.modules.git.vo.issue; - -import lombok.Data; -import lombok.EqualsAndHashCode; -import com.imyeyu.api.modules.common.entity.Attachment; -import com.imyeyu.api.modules.common.vo.user.UserView; -import com.imyeyu.api.modules.git.entity.Issue; - -import java.util.List; - -/** - * @author 夜雨 - * @since 2023-08-17 15:40 - */ -@Data -@EqualsAndHashCode(callSuper = true) -public class IssueView extends Issue { - - private UserView publisher; - - private List attachmentList; -} diff --git a/src/main/java/com/imyeyu/api/modules/git/vo/merge/MergePage.java b/src/main/java/com/imyeyu/api/modules/git/vo/merge/MergePage.java deleted file mode 100644 index 0583748..0000000 --- a/src/main/java/com/imyeyu/api/modules/git/vo/merge/MergePage.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.imyeyu.api.modules.git.vo.merge; - -import lombok.Data; -import lombok.EqualsAndHashCode; -import com.imyeyu.api.modules.git.entity.Merge; -import com.imyeyu.spring.bean.Page; - -/** - * @author 夜雨 - * @since 2023-08-17 11:52 - */ -@Data -@EqualsAndHashCode(callSuper = true) -public class MergePage extends Page { - - private long repositoryId; - - private Merge.Type type; - - private Merge.Status status; -} diff --git a/src/main/java/com/imyeyu/api/modules/git/vo/merge/MergeRequest.java b/src/main/java/com/imyeyu/api/modules/git/vo/merge/MergeRequest.java deleted file mode 100644 index 591ff55..0000000 --- a/src/main/java/com/imyeyu/api/modules/git/vo/merge/MergeRequest.java +++ /dev/null @@ -1,44 +0,0 @@ -package com.imyeyu.api.modules.git.vo.merge; - -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; -import lombok.Data; -import com.imyeyu.api.modules.git.entity.Merge; - -/** - * 申请合并请求 - * - * @author 夜雨 - * @since 2023-08-17 11:43 - */ -@Data -public class MergeRequest { - - /** 和并请求 ID,编辑时携带 */ - private Long id; - - /** 所属仓库 ID */ - private long repositoryId; - - private Long issueId; - - /** 类型 */ - @NotNull(message = "请选择类型") - private Merge.Type type; - - /** 来源分支 */ - @NotBlank(message = "请选择来源分支") - private String fromBranch; - - /** 去向分支 */ - @NotBlank(message = "请选择去向分支") - private String toBranch; - - /** 说明标题 */ - @NotBlank(message = "请填写标题") - private String title; - - /** 合并说明 */ - @NotBlank(message = "请填写合并相关说明") - private String description; -} diff --git a/src/main/java/com/imyeyu/api/modules/git/vo/merge/MergeView.java b/src/main/java/com/imyeyu/api/modules/git/vo/merge/MergeView.java deleted file mode 100644 index bea34b7..0000000 --- a/src/main/java/com/imyeyu/api/modules/git/vo/merge/MergeView.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.imyeyu.api.modules.git.vo.merge; - -import lombok.Data; -import lombok.EqualsAndHashCode; -import com.imyeyu.api.modules.common.entity.Attachment; -import com.imyeyu.api.modules.common.vo.user.UserView; -import com.imyeyu.api.modules.git.entity.Issue; -import com.imyeyu.api.modules.git.entity.Merge; - -import java.util.List; - -/** - * @author 夜雨 - * @since 2023-08-17 15:40 - */ -@Data -@EqualsAndHashCode(callSuper = true) -public class MergeView extends Merge { - - private Issue issue; - - private UserView requester; - - private List attachmentList; -} diff --git a/src/main/java/com/imyeyu/api/modules/git/vo/release/ReleasePage.java b/src/main/java/com/imyeyu/api/modules/git/vo/release/ReleasePage.java deleted file mode 100644 index 5d744fe..0000000 --- a/src/main/java/com/imyeyu/api/modules/git/vo/release/ReleasePage.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.imyeyu.api.modules.git.vo.release; - -import lombok.Data; -import lombok.EqualsAndHashCode; -import com.imyeyu.spring.bean.Page; - -/** - * @author 夜雨 - * @since 2023-08-16 11:46 - */ -@Data -@EqualsAndHashCode(callSuper = true) -public class ReleasePage extends Page { - - private long repositoryId; -} diff --git a/src/main/java/com/imyeyu/api/modules/git/vo/release/ReleaseView.java b/src/main/java/com/imyeyu/api/modules/git/vo/release/ReleaseView.java deleted file mode 100644 index df5b23b..0000000 --- a/src/main/java/com/imyeyu/api/modules/git/vo/release/ReleaseView.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.imyeyu.api.modules.git.vo.release; - -import lombok.Data; -import lombok.EqualsAndHashCode; -import com.imyeyu.api.modules.common.entity.Attachment; -import com.imyeyu.api.modules.git.entity.Release; - -import java.util.List; - -/** - * @author 夜雨 - * @since 2024-03-11 11:29 - */ -@Data -@EqualsAndHashCode(callSuper = true) -public class ReleaseView extends Release { - - private List attachmentList; -} diff --git a/src/main/java/com/imyeyu/api/modules/git/vo/repository/RepositoryView.java b/src/main/java/com/imyeyu/api/modules/git/vo/repository/RepositoryView.java deleted file mode 100644 index adb5053..0000000 --- a/src/main/java/com/imyeyu/api/modules/git/vo/repository/RepositoryView.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.imyeyu.api.modules.git.vo.repository; - -import com.imyeyu.api.modules.git.bean.gitea.Branch; -import com.imyeyu.api.modules.git.bean.gitea.Repository; -import lombok.Data; -import lombok.EqualsAndHashCode; - -import java.util.List; - -/** - * @author 夜雨 - * @since 2024-01-09 15:21 - */ -@Data -@EqualsAndHashCode(callSuper = true) -public class RepositoryView extends Repository { - - private List branchList; -} diff --git a/src/main/java/com/imyeyu/api/modules/gitea/bean/ActionLogDTO.java b/src/main/java/com/imyeyu/api/modules/gitea/bean/ActionLogDTO.java deleted file mode 100644 index a6dfe8e..0000000 --- a/src/main/java/com/imyeyu/api/modules/gitea/bean/ActionLogDTO.java +++ /dev/null @@ -1,65 +0,0 @@ -package com.imyeyu.api.modules.gitea.bean; - -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import com.imyeyu.api.modules.gitea.util.GiteaUTCTimestampAdapter; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.Getter; - -/** - * @author 夜雨 - * @since 2025-06-27 15:37 - */ -@Data -public class ActionLogDTO { - - /** - * - * - * @author 夜雨 - * @since 2025-06-29 21:01 - */ - @Getter - @AllArgsConstructor - public enum Operation { - - PUSH_BRANCH(5); - - final int value; - } - - private int repoId; - - private String repoName; - - private int operatorId; - - private String refName; - - private String content; - - private Long operatedAt; - - /** - * - * - * @author 夜雨 - * @since 2025-06-27 16:20 - */ - @Data - public static class Commit { - - @JsonProperty("Sha1") - private String sha; - - @JsonProperty("Message") - private String message; - - @JsonProperty("Timestamp") - @JsonSerialize(using = GiteaUTCTimestampAdapter.Serializer.class) - @JsonDeserialize(using = GiteaUTCTimestampAdapter.Deserializer.class) - private Long timestamp; - } -} diff --git a/src/main/java/com/imyeyu/api/modules/gitea/entity/Action.java b/src/main/java/com/imyeyu/api/modules/gitea/entity/Action.java deleted file mode 100644 index 3749011..0000000 --- a/src/main/java/com/imyeyu/api/modules/gitea/entity/Action.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.imyeyu.api.modules.gitea.entity; - -import lombok.Data; - -/** - * @author 夜雨 - * @since 2025-06-27 15:24 - */ -@Data -public class Action { - - private Long id; - private Long userId; - private Integer opType; - private Long actUserId; - private Long repoId; - private Long commentId; - private Boolean isDeleted; - private String refName; - private Boolean isPrivate; - private String content; - private Long createdUnix; -} diff --git a/src/main/java/com/imyeyu/api/modules/gitea/entity/Repository.java b/src/main/java/com/imyeyu/api/modules/gitea/entity/Repository.java deleted file mode 100644 index 06d510c..0000000 --- a/src/main/java/com/imyeyu/api/modules/gitea/entity/Repository.java +++ /dev/null @@ -1,59 +0,0 @@ -package com.imyeyu.api.modules.gitea.entity; - -import com.imyeyu.spring.annotation.table.Id; -import lombok.Data; - -/** - * @author 夜雨 - * @since 2025-06-29 21:19 - */ -@Data -public class Repository { - - @Id - private Long id; - private Long ownerId; - private String ownerName; - private String lowerName; - private String name; - private String description; - private String website; - private Integer originalServiceType; - private String originalUrl; - private String defaultBranch; - private String defaultWikiBranch; - private Integer numWatches; - private Integer numStars; - private Integer numForks; - private Integer numIssues; - private Integer numClosedIssues; - private Integer numPulls; - private Integer numClosedPulls; - private Integer numMilestones; - private Integer numClosedMilestones; - private Integer numProjects; - private Integer numClosedProjects; - private Integer numActionRuns; - private Integer numClosedActionRuns; - private Boolean isPrivate; - private Boolean isEmpty; - private Boolean isArchived; - private Boolean isMirror; - private Integer status; - private Boolean isFork; - private Long forkId; - private Boolean isTemplate; - private Long templateId; - private Long size; - private Long gitSize; - private Long lfsSize; - private Boolean isFsckEnabled; - private Boolean closeIssuesViaCommitInAnyBranch; - private String topics; - private String objectFormatName; - private Integer trustModel; - private String avatar; - private Long createdUnix; - private Long updatedUnix; - private Long archivedUnix; -} diff --git a/src/main/java/com/imyeyu/api/modules/gitea/entity/User.java b/src/main/java/com/imyeyu/api/modules/gitea/entity/User.java deleted file mode 100644 index d9c7d6a..0000000 --- a/src/main/java/com/imyeyu/api/modules/gitea/entity/User.java +++ /dev/null @@ -1,152 +0,0 @@ -package com.imyeyu.api.modules.gitea.entity; - -import com.imyeyu.spring.annotation.table.Id; -import lombok.Data; - -/** - * Gitea用户实体 - * @author 夜雨 - * @since 2025-07-03 14:49 - */ -@Data -public class User { - - /** ID */ - @Id - private Long id; - - /** 小写用户名 */ - private String lowerName; - - /** 用户名 */ - private String name; - - /** 全名 */ - private String fullName; - - /** 邮箱 */ - private String email; - - /** 是否保持邮箱私密 */ - private Boolean keepEmailPrivate; - - /** 邮件通知偏好设置 */ - private String emailNotificationsPreference; - - /** 密码 */ - private String passwd; - - /** 密码哈希算法 */ - private String passwdHashAlgo; - - /** 是否必须修改密码 */ - private Boolean mustChangePassword; - - /** 登录类型 */ - private Integer loginType; - - /** 登录源 */ - private Long loginSource; - - /** 登录名 */ - private String loginName; - - /** 类型 */ - private Integer type; - - /** 位置 */ - private String location; - - /** 网站 */ - private String website; - - /** 随机字符串 */ - private String rands; - - /** 盐值 */ - private String salt; - - /** 语言 */ - private String language; - - /** 描述 */ - private String description; - - /** 创建时间(Unix时间戳) */ - private Long createdUnix; - - /** 更新时间(Unix时间戳) */ - private Long updatedUnix; - - /** 最后登录时间(Unix时间戳) */ - private Long lastLoginUnix; - - /** 最后仓库可见性 */ - private Boolean lastRepoVisibility; - - /** 最大仓库创建数 */ - private Integer maxRepoCreation; - - /** 是否激活 */ - private Boolean isActive; - - /** 是否管理员 */ - private Boolean isAdmin; - - /** 是否受限 */ - private Boolean isRestricted; - - /** 是否允许Git钩子 */ - private Boolean allowGitHook; - - /** 是否允许导入本地仓库 */ - private Boolean allowImportLocal; - - /** 是否允许创建组织 */ - private Boolean allowCreateOrganization; - - /** 是否禁止登录 */ - private Boolean prohibitLogin; - - /** 头像URL */ - private String avatar; - - /** 头像邮箱 */ - private String avatarEmail; - - /** 是否使用自定义头像 */ - private Boolean useCustomAvatar; - - /** 关注者数量 */ - private Integer numFollowers; - - /** 正在关注数量 */ - private Integer numFollowing; - - /** 星标数量 */ - private Integer numStars; - - /** 仓库数量 */ - private Integer numRepos; - - /** 团队数量 */ - private Integer numTeams; - - /** 成员数量 */ - private Integer numMembers; - - /** 可见性 */ - private Integer visibility; - - /** 仓库管理员是否可以更改团队访问权限 */ - private Boolean repoAdminChangeTeamAccess; - - /** 差异视图样式 */ - private String diffViewStyle; - - /** 主题 */ - private String theme; - - /** 是否保持活动私密 */ - private Boolean keepActivityPrivate; -} diff --git a/src/main/java/com/imyeyu/api/modules/gitea/mapper/ActionMapper.java b/src/main/java/com/imyeyu/api/modules/gitea/mapper/ActionMapper.java deleted file mode 100644 index bf192d6..0000000 --- a/src/main/java/com/imyeyu/api/modules/gitea/mapper/ActionMapper.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.imyeyu.api.modules.gitea.mapper; - -import com.imyeyu.api.modules.gitea.bean.ActionLogDTO; - -import java.util.List; - -/** - * @author 夜雨 - * @since 2025-06-27 15:26 - */ -public interface ActionMapper { - - long count(Long repoId, String branch, ActionLogDTO.Operation operation); - - List list(Long repoId, String branch, ActionLogDTO.Operation operation, long offset, long limit); -} diff --git a/src/main/java/com/imyeyu/api/modules/gitea/mapper/GiteaUserMapper.java b/src/main/java/com/imyeyu/api/modules/gitea/mapper/GiteaUserMapper.java deleted file mode 100644 index e45e21e..0000000 --- a/src/main/java/com/imyeyu/api/modules/gitea/mapper/GiteaUserMapper.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.imyeyu.api.modules.gitea.mapper; - -import com.imyeyu.api.modules.gitea.entity.User; -import com.imyeyu.spring.mapper.BaseMapper; - -/** - * @author 夜雨 - * @since 2025-07-03 14:56 - */ -public interface GiteaUserMapper extends BaseMapper { -} diff --git a/src/main/java/com/imyeyu/api/modules/gitea/mapper/RepositoryMapper.java b/src/main/java/com/imyeyu/api/modules/gitea/mapper/RepositoryMapper.java deleted file mode 100644 index 82e0235..0000000 --- a/src/main/java/com/imyeyu/api/modules/gitea/mapper/RepositoryMapper.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.imyeyu.api.modules.gitea.mapper; - -import com.imyeyu.api.modules.gitea.entity.Repository; -import com.imyeyu.spring.mapper.BaseMapper; - -/** - * @author 夜雨 - * @since 2025-06-30 00:17 - */ -public interface RepositoryMapper extends BaseMapper { - -} diff --git a/src/main/java/com/imyeyu/api/modules/gitea/service/GiteaService.java b/src/main/java/com/imyeyu/api/modules/gitea/service/GiteaService.java deleted file mode 100644 index 8657f65..0000000 --- a/src/main/java/com/imyeyu/api/modules/gitea/service/GiteaService.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.imyeyu.api.modules.gitea.service; - -import com.imyeyu.java.bean.timi.TimiException; -import com.imyeyu.api.modules.gitea.entity.Repository; -import com.imyeyu.api.modules.gitea.entity.User; -import com.imyeyu.api.modules.gitea.vo.ActionLogView; -import com.imyeyu.spring.bean.Page; -import com.imyeyu.spring.bean.PageResult; - -/** - * @author 夜雨 - * @since 2025-06-27 15:22 - */ -public interface GiteaService { - - User getOwner() throws TimiException; - - Repository getRepository(Long id) throws TimiException; - - Repository getRepositoryByName(String repoName) throws TimiException; - - PageResult pagePush(Page page) throws TimiException; - - PageResult pagePush(String repoName, String branch, Page page) throws TimiException; -} diff --git a/src/main/java/com/imyeyu/api/modules/gitea/service/implement/GiteaServiceImplement.java b/src/main/java/com/imyeyu/api/modules/gitea/service/implement/GiteaServiceImplement.java deleted file mode 100644 index 8d9d9ba..0000000 --- a/src/main/java/com/imyeyu/api/modules/gitea/service/implement/GiteaServiceImplement.java +++ /dev/null @@ -1,65 +0,0 @@ -package com.imyeyu.api.modules.gitea.service.implement; - -import com.imyeyu.java.bean.timi.TimiException; -import com.imyeyu.api.modules.gitea.bean.ActionLogDTO; -import com.imyeyu.api.modules.gitea.entity.Repository; -import com.imyeyu.api.modules.gitea.entity.User; -import com.imyeyu.api.modules.gitea.mapper.ActionMapper; -import com.imyeyu.api.modules.gitea.mapper.RepositoryMapper; -import com.imyeyu.api.modules.gitea.mapper.GiteaUserMapper; -import com.imyeyu.api.modules.gitea.service.GiteaService; -import com.imyeyu.api.modules.gitea.vo.ActionLogView; -import com.imyeyu.spring.bean.Page; -import com.imyeyu.spring.bean.PageResult; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; - -/** - * @author 夜雨 - * @since 2025-06-27 15:26 - */ -@Service -@RequiredArgsConstructor -public class GiteaServiceImplement implements GiteaService { - - private final ActionMapper actionMapper; - private final GiteaUserMapper giteaUserMapper; - private final RepositoryMapper repositoryMapper; - - @Override - public User getOwner() throws TimiException { - return giteaUserMapper.select(1L); - } - - @Override - public Repository getRepository(Long id) throws TimiException { - return repositoryMapper.select(id); - } - - @Override - public Repository getRepositoryByName(String repoName) throws TimiException { - Repository example = new Repository(); - example.setName(repoName); - return repositoryMapper.selectByExample(example); - } - - @Override - public PageResult pagePush(Page page) throws TimiException { - PageResult result = new PageResult<>(); - result.setTotal(actionMapper.count(null, null, ActionLogDTO.Operation.PUSH_BRANCH)); - result.setList(actionMapper.list(null, null, ActionLogDTO.Operation.PUSH_BRANCH, page.getOffset(), page.getLimit()).stream().map(ActionLogView::fromDTO).toList()); - return result; - } - - @Override - public PageResult pagePush(String repoName, String branch, Page page) throws TimiException { - Repository example = new Repository(); - example.setName(repoName); - Repository repo = repositoryMapper.selectByExample(example); - - PageResult result = new PageResult<>(); - result.setTotal(actionMapper.count(repo.getId(), branch, ActionLogDTO.Operation.PUSH_BRANCH)); - result.setList(actionMapper.list(repo.getId(), branch, ActionLogDTO.Operation.PUSH_BRANCH, page.getOffset(), page.getLimit()).stream().map(ActionLogView::fromDTO).toList()); - return result; - } -} diff --git a/src/main/java/com/imyeyu/api/modules/gitea/util/GiteaUTCTimestampAdapter.java b/src/main/java/com/imyeyu/api/modules/gitea/util/GiteaUTCTimestampAdapter.java deleted file mode 100644 index 3605ba6..0000000 --- a/src/main/java/com/imyeyu/api/modules/gitea/util/GiteaUTCTimestampAdapter.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.imyeyu.api.modules.gitea.util; - -import com.fasterxml.jackson.core.JacksonException; -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonDeserializer; -import com.fasterxml.jackson.databind.JsonSerializer; -import com.fasterxml.jackson.databind.SerializerProvider; - -import java.io.IOException; -import java.time.Instant; -import java.time.OffsetDateTime; -import java.time.ZoneOffset; - -/** - * - * - * @author 夜雨 - * @since 2025-06-27 16:18 - */ -public class GiteaUTCTimestampAdapter { - - public static class Deserializer extends JsonDeserializer { - - @Override - public Long deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JacksonException { - return OffsetDateTime.parse(p.getText()).toInstant().toEpochMilli(); - } - } - - public static class Serializer extends JsonSerializer { - - @Override - public void serialize(Long timestamp, JsonGenerator gen, SerializerProvider serializers) throws IOException { - gen.writeString(OffsetDateTime.ofInstant(Instant.ofEpochMilli(timestamp), ZoneOffset.UTC).toString()); - } - } -} diff --git a/src/main/java/com/imyeyu/api/modules/gitea/vo/ActionLogView.java b/src/main/java/com/imyeyu/api/modules/gitea/vo/ActionLogView.java deleted file mode 100644 index 3ef1155..0000000 --- a/src/main/java/com/imyeyu/api/modules/gitea/vo/ActionLogView.java +++ /dev/null @@ -1,81 +0,0 @@ -package com.imyeyu.api.modules.gitea.vo; - -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.imyeyu.java.TimiJava; -import com.imyeyu.api.TimiServerAPI; -import com.imyeyu.api.modules.common.service.UserService; -import com.imyeyu.api.modules.common.vo.user.UserView; -import com.imyeyu.api.modules.gitea.bean.ActionLogDTO; -import lombok.Data; - -import java.util.ArrayList; -import java.util.List; - -/** - * @author 夜雨 - * @since 2025-06-27 15:31 - */ -@Data -public class ActionLogView { - - private int repoId; - - private String repoName; - - private String refName; - - private Long operatedAt; - - private List commitList; - - private UserView operator; - - public static ActionLogView fromDTO(ActionLogDTO dto) { - ObjectMapper jackson = TimiServerAPI.applicationContext.getBean(ObjectMapper.class); - UserService userService = TimiServerAPI.applicationContext.getBean(UserService.class); - - ActionLogView view = new ActionLogView(); - view.setRepoId(dto.getRepoId()); - view.setRepoName(dto.getRepoName()); - view.setRefName(dto.getRefName()); - view.setOperatedAt(dto.getOperatedAt()); - view.setOperator(userService.view((long) dto.getOperatorId()).doFilter()); - { - if (TimiJava.isNotEmpty(dto.getContent())) { - try { - JsonNode content = jackson.readTree(dto.getContent()); - List commitList = jackson.convertValue(content.get("Commits"), new TypeReference<>() {}); - view.setCommitList(new ArrayList<>()); - for (ActionLogDTO.Commit dtoCommit : commitList) { - Commit commit = new Commit(); - commit.setSha(dtoCommit.getSha()); - commit.setMessage(dtoCommit.getMessage().trim()); - commit.setCommittedAt(dtoCommit.getTimestamp()); - view.getCommitList().add(commit); - } - } catch (Exception e) { - throw new IllegalStateException("read gitea action log content error", e); - } - } - } - return view; - } - - /** - * - * - * @author 夜雨 - * @since 2025-06-27 15:37 - */ - @Data - public static class Commit { - - private String sha; - - private String message; - - private Long committedAt; - } -} diff --git a/src/main/java/com/imyeyu/api/modules/journal/controller/JournalController.java b/src/main/java/com/imyeyu/api/modules/journal/controller/JournalController.java index f8746a5..6805162 100644 --- a/src/main/java/com/imyeyu/api/modules/journal/controller/JournalController.java +++ b/src/main/java/com/imyeyu/api/modules/journal/controller/JournalController.java @@ -3,9 +3,9 @@ package com.imyeyu.api.modules.journal.controller; import com.fasterxml.jackson.databind.ObjectMapper; import com.imyeyu.api.bean.PreviewPage; import com.imyeyu.api.bean.wechat.InitCodeResponse; -import com.imyeyu.api.modules.common.bean.MediaAttach; -import com.imyeyu.api.modules.common.bean.SettingKey; +import com.imyeyu.api.modules.common.bean.attachment.MediaAttach; import com.imyeyu.api.modules.common.entity.Attachment; +import com.imyeyu.api.modules.common.entity.Setting; import com.imyeyu.api.modules.common.service.AttachmentService; import com.imyeyu.api.modules.common.service.SettingService; import com.imyeyu.api.modules.journal.bean.RequiredUploadPermission; @@ -48,251 +48,251 @@ import java.util.List; @RequiredArgsConstructor @RequestMapping("/journal") public class JournalController { - - private final JournalService service; - private final SettingService settingService; - private final AttachmentService attachmentService; - private final ObjectMapper jackson; - - private final JournalAPIInterceptor apiInterceptor; - - /** - * 初始化用户 OpenId - * - * @param code 微信授权码 - * @return OpenId - */ - @AOPLog - @RequestRateLimit - @PostMapping("/openid") - public String initOpenId(@RequestBody String code) { - try { - ArgMap args = new ArgMap<>(); - args.put("appid", settingService.getAsString(SettingKey.JOURNAL_APP_ID)); - args.put("secret", settingService.getAsString(SettingKey.JOURNAL_APP_SECRET)); - args.put("js_code", code); - args.put("grant_type", "authorization_code"); - String response = org.apache.hc.client5.http.fluent.Request.get(args.toURL("https://api.weixin.qq.com/sns/jscode2session")) - .execute() - .returnContent() - .asString(); - InitCodeResponse resp = jackson.readValue(response, InitCodeResponse.class); - return resp.getOpenid(); - } catch (Exception e) { - log.error("init WeChat openId error", e); - throw new TimiException(TimiCode.ERROR, "init openId error", e); - } - } - - @PostMapping("/can-upload") - public boolean canUpload() { - return apiInterceptor.canUploadKey(); - } - - @AOPLog - @RequestRateLimit - @RequestMapping("/{id}") - public JournalResponse detail(@PathVariable Long id) { - Journal journal = service.get(id); - JournalResponse resp = new JournalResponse(); - Page attachPage = new Page<>(); - { - Attachment example = new Attachment(); - example.setBizType(Attachment.BizType.JOURNAL); - example.setBizId(journal.getId()); - attachPage.setEqualsExample(example); - } - attachPage.setIndex(0); - attachPage.setSize(Long.MAX_VALUE); - resp.setItems(attachmentService.page(attachPage).getList()); - BeanUtils.copyProperties(journal, resp); - return resp; - } - - /** - * 创建记录 - * - * @param request - */ - @AOPLog - @RequestRateLimit - @RequiredUploadPermission - @PostMapping("/create") - public void create(@RequestBody JournalRequest request) { - service.create(request); - } - - /** - * 更新记录(支持附件差分保存) - * - * @param request 更新请求,包含 id、基本信息、保留的附件 ID 列表和新上传的临时文件 ID 列表 - */ - @AOPLog - @RequestRateLimit - @RequiredUploadPermission - @PostMapping("/update") - public void update(@RequestBody @Valid UpdateRequest request) { - service.update(request); - } - - /** - * 删除记录 - * - * @param id 记录 ID - */ - @AOPLog - @RequiredUploadPermission - @PostMapping("/delete") - public void delete(@RequestBody Long id) { - service.delete(id); - } - - /** - * 记录列表 - * - * @param page 查询页面 - * @return - */ - @AOPLog - @RequestRateLimit - @RequestMapping("/list") - public PageResult list(@RequestBody PreviewPage page) { - PageResult pageResult = service.page(page); - - PageResult result = new PageResult<>(); - result.setTotal(pageResult.getTotal()); - result.setList(pageResult.getList().stream().map(item -> { - JournalResponse resp = new JournalResponse(); - Page attachPage = new Page<>(); - { - Attachment example = new Attachment(); - example.setBizType(Attachment.BizType.JOURNAL); - example.setBizId(item.getId()); - attachPage.setEqualsExample(example); - } - attachPage.setIndex(0); - attachPage.setSize(switch (page.getType()) { - case NORMAL -> Long.MAX_VALUE; - case PREVIEW -> 2; // 原图缩略图并存 - }); - resp.setItems(attachmentService.page(attachPage).getList()); - BeanUtils.copyProperties(item, resp); - return resp; - }).toList()); - return result; - } - - @AOPLog - @RequestRateLimit - @PostMapping("/list/ids") - public List listByIds(@RequestBody Long[] ids) { - List result = new ArrayList<>(); - - List journals = service.listByIds(ids); - for (int i = 0; i < journals.size(); i++) { - Journal journal = journals.get(i); - JournalResponse resp = new JournalResponse(); - Page attachPage = new Page<>(); - { - Attachment example = new Attachment(); - example.setBizType(Attachment.BizType.JOURNAL); - example.setBizId(journal.getId()); - attachPage.setEqualsExample(example); - } - attachPage.setIndex(0); - attachPage.setSize(Long.MAX_VALUE); - resp.setItems(attachmentService.page(attachPage).getList()); - BeanUtils.copyProperties(journal, resp); - result.add(resp); - } - return result; - } - - /** - * 已记录照片、视频数量 - * - * @return - */ - @RequestRateLimit - @GetMapping("/total") - public long total() { - long journal = attachmentService.countByBizId(Attachment.BizType.JOURNAL, null, MediaAttach.Type.SOURCE); - long journalTravel = attachmentService.countByBizId(Attachment.BizType.JOURNAL_TRAVEL, null, MediaAttach.Type.SOURCE); - return journal + journalTravel; - } - - /** - * 创建瞬间(上传的临时文件持久化储存,微信限制单次上传数量) - * - * @param tempFileIds - * @return - */ - @AOPLog - @RequestRateLimit - @RequiredUploadPermission - @PostMapping("/moment/create") - public List createMoment(@RequestBody String[] tempFileIds) { - return service.createMoment(tempFileIds); - } - - /** - * 瞬间列表 - * - * @return - */ - @AOPLog - @RequestRateLimit - @PostMapping("/moment/list") - public List listMoment() { - Page page = new Page<>(); - { - Attachment example = new Attachment(); - example.setBizType(Attachment.BizType.JOURNAL_MOMENT); - example.setBizId(0L); - example.setAttachTypeValue(MediaAttach.Type.THUMB); - page.setEqualsExample(example); - } - page.setIndex(0); - page.setSize(Long.MAX_VALUE); - return attachmentService.page(page).getList(); - } - - /** - * 检查待上传文件是否已存在 - * - * @param md5s - * @return 不存在的文件 md5 列表 - */ - @AOPLog - @RequestRateLimit - @PostMapping("/moment/filter") - public String[] filterExistMoment(@RequestBody String[] md5s) { - return service.filterExistMoment(md5s); - } - - /** - * 删除瞬间 - * - * @param thumbIds 缩略图附件 ID 列表 - */ - @AOPLog - @RequestRateLimit - @RequiredUploadPermission - @PostMapping("/moment/delete") - public void deleteMoment(@RequestBody Long[] thumbIds) { - service.deleteMoment(thumbIds); - } - - /** - * 归档瞬间,归档记录 ID 不为空时表示归档到指定记录 - * - * @param request - */ - @AOPLog - @RequestRateLimit - @RequiredUploadPermission - @PostMapping("/moment/archive") - public void archiveMoment(@RequestBody ArchiveRequest request) { - service.archiveMoment(request); - } +// +// private final JournalService service; +// private final SettingService settingService; +// private final AttachmentService attachmentService; +// private final ObjectMapper jackson; +// +// private final JournalAPIInterceptor apiInterceptor; +// +// /** +// * 初始化用户 OpenId +// * +// * @param code 微信授权码 +// * @return OpenId +// */ +// @AOPLog +// @RequestRateLimit +// @PostMapping("/openid") +// public String initOpenId(@RequestBody String code) { +// try { +// ArgMap args = new ArgMap<>(); +// args.put("appid", settingService.getSystem(Setting.Module.Journal.APP_ID).asString()); +// args.put("secret", settingService.getSystem(Setting.Module.Journal.APP_SECRET).asString()); +// args.put("js_code", code); +// args.put("grant_type", "authorization_code"); +// String response = org.apache.hc.client5.http.fluent.Request.get(args.toURL("https://api.weixin.qq.com/sns/jscode2session")) +// .execute() +// .returnContent() +// .asString(); +// InitCodeResponse resp = jackson.readValue(response, InitCodeResponse.class); +// return resp.getOpenid(); +// } catch (Exception e) { +// log.error("init WeChat openId error", e); +// throw new TimiException(TimiCode.ERROR, "init openId error", e); +// } +// } +// +// @PostMapping("/can-upload") +// public boolean canUpload() { +// return apiInterceptor.canUploadKey(); +// } +// +// @AOPLog +// @RequestRateLimit +// @RequestMapping("/{id}") +// public JournalResponse detail(@PathVariable Long id) { +// Journal journal = service.get(id); +// JournalResponse resp = new JournalResponse(); +// Page attachPage = new Page<>(); +// { +// Attachment example = new Attachment(); +// example.setBizType(Attachment.BizType.JOURNAL); +// example.setBizId(journal.getId()); +// attachPage.setEqualsExample(example); +// } +// attachPage.setIndex(0); +// attachPage.setSize(Long.MAX_VALUE); +// resp.setItems(attachmentService.page(attachPage).getList()); +// BeanUtils.copyProperties(journal, resp); +// return resp; +// } +// +// /** +// * 创建记录 +// * +// * @param request +// */ +// @AOPLog +// @RequestRateLimit +// @RequiredUploadPermission +// @PostMapping("/create") +// public void create(@RequestBody JournalRequest request) { +// service.create(request); +// } +// +// /** +// * 更新记录(支持附件差分保存) +// * +// * @param request 更新请求,包含 id、基本信息、保留的附件 ID 列表和新上传的临时文件 ID 列表 +// */ +// @AOPLog +// @RequestRateLimit +// @RequiredUploadPermission +// @PostMapping("/update") +// public void update(@RequestBody @Valid UpdateRequest request) { +// service.update(request); +// } +// +// /** +// * 删除记录 +// * +// * @param id 记录 ID +// */ +// @AOPLog +// @RequiredUploadPermission +// @PostMapping("/delete") +// public void delete(@RequestBody Long id) { +// service.delete(id); +// } +// +// /** +// * 记录列表 +// * +// * @param page 查询页面 +// * @return +// */ +// @AOPLog +// @RequestRateLimit +// @RequestMapping("/list") +// public PageResult list(@RequestBody PreviewPage page) { +// PageResult pageResult = service.page(page); +// +// PageResult result = new PageResult<>(); +// result.setTotal(pageResult.getTotal()); +// result.setList(pageResult.getList().stream().map(item -> { +// JournalResponse resp = new JournalResponse(); +// Page attachPage = new Page<>(); +// { +// Attachment example = new Attachment(); +// example.setBizType(Attachment.BizType.JOURNAL); +// example.setBizId(item.getId()); +// attachPage.setEqualsExample(example); +// } +// attachPage.setIndex(0); +// attachPage.setSize(switch (page.getType()) { +// case NORMAL -> Long.MAX_VALUE; +// case PREVIEW -> 2; // 原图缩略图并存 +// }); +// resp.setItems(attachmentService.page(attachPage).getList()); +// BeanUtils.copyProperties(item, resp); +// return resp; +// }).toList()); +// return result; +// } +// +// @AOPLog +// @RequestRateLimit +// @PostMapping("/list/ids") +// public List listByIds(@RequestBody Long[] ids) { +// List result = new ArrayList<>(); +// +// List journals = service.listByIds(ids); +// for (int i = 0; i < journals.size(); i++) { +// Journal journal = journals.get(i); +// JournalResponse resp = new JournalResponse(); +// Page attachPage = new Page<>(); +// { +// Attachment example = new Attachment(); +// example.setBizType(Attachment.BizType.JOURNAL); +// example.setBizId(journal.getId()); +// attachPage.setEqualsExample(example); +// } +// attachPage.setIndex(0); +// attachPage.setSize(Long.MAX_VALUE); +// resp.setItems(attachmentService.page(attachPage).getList()); +// BeanUtils.copyProperties(journal, resp); +// result.add(resp); +// } +// return result; +// } +// +// /** +// * 已记录照片、视频数量 +// * +// * @return +// */ +// @RequestRateLimit +// @GetMapping("/total") +// public long total() { +// long journal = attachmentService.countByBizId(Attachment.BizType.JOURNAL, null, MediaAttach.Type.SOURCE); +// long journalTravel = attachmentService.countByBizId(Attachment.BizType.JOURNAL_TRAVEL, null, MediaAttach.Type.SOURCE); +// return journal + journalTravel; +// } +// +// /** +// * 创建瞬间(上传的临时文件持久化储存,微信限制单次上传数量) +// * +// * @param tempFileIds +// * @return +// */ +// @AOPLog +// @RequestRateLimit +// @RequiredUploadPermission +// @PostMapping("/moment/create") +// public List createMoment(@RequestBody String[] tempFileIds) { +// return service.createMoment(tempFileIds); +// } +// +// /** +// * 瞬间列表 +// * +// * @return +// */ +// @AOPLog +// @RequestRateLimit +// @PostMapping("/moment/list") +// public List listMoment() { +// Page page = new Page<>(); +// { +// Attachment example = new Attachment(); +// example.setBizType(Attachment.BizType.JOURNAL_MOMENT); +// example.setBizId(0L); +// example.setAttachTypeValue(MediaAttach.Type.THUMB); +// page.setEqualsExample(example); +// } +// page.setIndex(0); +// page.setSize(Long.MAX_VALUE); +// return attachmentService.page(page).getList(); +// } +// +// /** +// * 检查待上传文件是否已存在 +// * +// * @param md5s +// * @return 不存在的文件 md5 列表 +// */ +// @AOPLog +// @RequestRateLimit +// @PostMapping("/moment/filter") +// public String[] filterExistMoment(@RequestBody String[] md5s) { +// return service.filterExistMoment(md5s); +// } +// +// /** +// * 删除瞬间 +// * +// * @param thumbIds 缩略图附件 ID 列表 +// */ +// @AOPLog +// @RequestRateLimit +// @RequiredUploadPermission +// @PostMapping("/moment/delete") +// public void deleteMoment(@RequestBody Long[] thumbIds) { +// service.deleteMoment(thumbIds); +// } +// +// /** +// * 归档瞬间,归档记录 ID 不为空时表示归档到指定记录 +// * +// * @param request +// */ +// @AOPLog +// @RequestRateLimit +// @RequiredUploadPermission +// @PostMapping("/moment/archive") +// public void archiveMoment(@RequestBody ArchiveRequest request) { +// service.archiveMoment(request); +// } } diff --git a/src/main/java/com/imyeyu/api/modules/journal/controller/ToolController.java b/src/main/java/com/imyeyu/api/modules/journal/controller/ToolController.java index 32d8f5e..e5ef910 100644 --- a/src/main/java/com/imyeyu/api/modules/journal/controller/ToolController.java +++ b/src/main/java/com/imyeyu/api/modules/journal/controller/ToolController.java @@ -1,6 +1,5 @@ package com.imyeyu.api.modules.journal.controller; -import com.imyeyu.api.modules.common.bean.SettingKey; import com.imyeyu.api.modules.common.entity.Setting; import com.imyeyu.api.modules.common.service.SettingService; import com.imyeyu.api.modules.journal.bean.RequiredUploadPermission; @@ -26,13 +25,13 @@ public class ToolController { @GetMapping("/memo") public String getMemo() { - return settingService.getAsString(SettingKey.JOURNAL_MEMO); + return settingService.getSystem(Setting.Module.Journal.MEMO).asString(); } @RequiredUploadPermission @PostMapping("/memo/update") public void updateMemo(@RequestBodyValue String data) { - Setting setting = settingService.getByKey(SettingKey.JOURNAL_MEMO); + Setting setting = settingService.getSystem(Setting.Module.Journal.MEMO); setting.setValue(data); settingService.update(setting); } diff --git a/src/main/java/com/imyeyu/api/modules/journal/controller/TravelController.java b/src/main/java/com/imyeyu/api/modules/journal/controller/TravelController.java index 9acac57..d7a49fb 100644 --- a/src/main/java/com/imyeyu/api/modules/journal/controller/TravelController.java +++ b/src/main/java/com/imyeyu/api/modules/journal/controller/TravelController.java @@ -66,7 +66,7 @@ public class TravelController { @RequestRateLimit @RequiredUploadPermission @PostMapping("/delete") - public void delete(@RequestBody Long id) { + public void delete(@RequestBody String id) { service.delete(id); } @@ -79,7 +79,7 @@ public class TravelController { @AOPLog @RequestRateLimit @GetMapping("/{id}") - public Travel detail(@PathVariable Long id) { + public Travel detail(@PathVariable String id) { return service.get(id); } diff --git a/src/main/java/com/imyeyu/api/modules/journal/controller/TravelLocationController.java b/src/main/java/com/imyeyu/api/modules/journal/controller/TravelLocationController.java index 87a43cb..becb804 100644 --- a/src/main/java/com/imyeyu/api/modules/journal/controller/TravelLocationController.java +++ b/src/main/java/com/imyeyu/api/modules/journal/controller/TravelLocationController.java @@ -1,28 +1,10 @@ package com.imyeyu.api.modules.journal.controller; -import com.imyeyu.api.bean.PreviewPage; -import com.imyeyu.api.modules.common.bean.MediaAttach; -import com.imyeyu.api.modules.common.entity.Attachment; -import com.imyeyu.api.modules.common.service.AttachmentService; -import com.imyeyu.api.modules.journal.bean.RequiredUploadPermission; -import com.imyeyu.api.modules.journal.entity.TravelLocation; -import com.imyeyu.api.modules.journal.service.TravelLocationService; -import com.imyeyu.spring.annotation.AOPLog; -import com.imyeyu.spring.annotation.RequestRateLimit; -import com.imyeyu.spring.bean.Page; -import com.imyeyu.spring.bean.PageResult; -import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -import java.util.List; - /** * 旅行地点接口 * @@ -34,108 +16,4 @@ import java.util.List; @RequiredArgsConstructor @RequestMapping("/journal/travel/location") public class TravelLocationController { - - private final AttachmentService attachmentService; - private final TravelLocationService service; - - /** - * 创建旅行地点 - * - * @param location 旅行地点 - */ - @AOPLog - @RequestRateLimit - @RequiredUploadPermission - @PostMapping("/create") - public void create(@RequestBody @Valid TravelLocation location) { - service.create(location); - } - - /** - * 更新旅行地点 - * - * @param location 旅行地点 - */ - @AOPLog - @RequestRateLimit - @RequiredUploadPermission - @PostMapping("/update") - public void update(@RequestBody @Valid TravelLocation location) { - service.update(location); - } - - /** - * 删除旅行地点(级联删除附件) - * - * @param id 地点 ID - */ - @AOPLog - @RequestRateLimit - @RequiredUploadPermission - @PostMapping("/delete") - public void delete(@RequestBody Long id) { - service.delete(id); - } - - /** - * 查询旅行地点详情 - * - * @param id 地点 ID - * @return 旅行地点 - */ - @AOPLog - @RequestRateLimit - @GetMapping("/{id}") - public TravelLocation detail(@PathVariable Long id) { - TravelLocation location = service.get(id); - location.setItems(attachmentService.listByBizId(Attachment.BizType.JOURNAL_TRAVEL, location.getId())); - return location; - } - - /** - * 旅行地点列表 - * - * @param page 分页参数 - * @return 地点列表 - */ - @AOPLog - @RequestRateLimit - @PostMapping("/list") - public PageResult list(@RequestBody PreviewPage page) { - PageResult result = service.page(page); - for (TravelLocation location : result.getList()) { - Page attachPage = new Page<>(); - { - Attachment example = new Attachment(); - example.setBizType(Attachment.BizType.JOURNAL_TRAVEL); - example.setBizId(location.getId()); - example.setAttachTypeValue(MediaAttach.Type.THUMB); - attachPage.setEqualsExample(example); - } - attachPage.setIndex(0); - attachPage.setSize(1); // 列表接口只允许预览 - location.setItems(attachmentService.page(attachPage).getList()); - } - return result; - } - - @AOPLog - @RequestRateLimit - @PostMapping("/list/ids") - public List listByIds(@RequestBody Long[] ids) { - List locationList = service.listByIds(ids); - for (TravelLocation location : locationList) { - Page attachPage = new Page<>(); - { - Attachment example = new Attachment(); - example.setBizType(Attachment.BizType.JOURNAL_TRAVEL); - example.setBizId(location.getId()); - attachPage.setEqualsExample(example); - } - attachPage.setIndex(0); - attachPage.setSize(Long.MAX_VALUE); - location.setItems(attachmentService.page(attachPage).getList()); - } - return locationList; - } } diff --git a/src/main/java/com/imyeyu/api/modules/journal/entity/Journal.java b/src/main/java/com/imyeyu/api/modules/journal/entity/Journal.java index 9f2eba4..6593c74 100644 --- a/src/main/java/com/imyeyu/api/modules/journal/entity/Journal.java +++ b/src/main/java/com/imyeyu/api/modules/journal/entity/Journal.java @@ -1,6 +1,6 @@ package com.imyeyu.api.modules.journal.entity; -import com.imyeyu.spring.entity.Entity; +import com.imyeyu.spring.entity.UUIDEntity; import lombok.Data; import lombok.EqualsAndHashCode; @@ -10,7 +10,7 @@ import lombok.EqualsAndHashCode; */ @Data @EqualsAndHashCode(callSuper = true) -public class Journal extends Entity { +public class Journal extends UUIDEntity { /** * diff --git a/src/main/java/com/imyeyu/api/modules/journal/entity/Travel.java b/src/main/java/com/imyeyu/api/modules/journal/entity/Travel.java index ebb2161..ec20d80 100644 --- a/src/main/java/com/imyeyu/api/modules/journal/entity/Travel.java +++ b/src/main/java/com/imyeyu/api/modules/journal/entity/Travel.java @@ -1,7 +1,7 @@ package com.imyeyu.api.modules.journal.entity; import com.imyeyu.spring.annotation.table.PageIgnore; -import com.imyeyu.spring.entity.Entity; +import com.imyeyu.spring.entity.UUIDEntity; import lombok.Data; import lombok.EqualsAndHashCode; @@ -13,7 +13,7 @@ import lombok.EqualsAndHashCode; */ @Data @EqualsAndHashCode(callSuper = true) -public class Travel extends Entity { +public class Travel extends UUIDEntity { /** * 交通类型 diff --git a/src/main/java/com/imyeyu/api/modules/journal/entity/TravelLocation.java b/src/main/java/com/imyeyu/api/modules/journal/entity/TravelLocation.java index 28c56c9..e0e4535 100644 --- a/src/main/java/com/imyeyu/api/modules/journal/entity/TravelLocation.java +++ b/src/main/java/com/imyeyu/api/modules/journal/entity/TravelLocation.java @@ -1,13 +1,10 @@ package com.imyeyu.api.modules.journal.entity; -import com.imyeyu.api.modules.common.entity.Attachment; -import com.imyeyu.spring.annotation.table.Transient; -import com.imyeyu.spring.entity.Entity; +import com.imyeyu.spring.entity.UUIDEntity; import lombok.Data; import lombok.EqualsAndHashCode; import java.math.BigDecimal; -import java.util.List; /** * 旅行地点 @@ -17,7 +14,7 @@ import java.util.List; */ @Data @EqualsAndHashCode(callSuper = true) -public class TravelLocation extends Entity { +public class TravelLocation extends UUIDEntity { /** * 地点类型 @@ -53,7 +50,7 @@ public class TravelLocation extends Entity { } /** 旅行计划 ID */ - private Long travelId; + private String travelId; /** 类型 */ private Type type; @@ -88,12 +85,4 @@ public class TravelLocation extends Entity { /** 重要程度 */ private Integer importance; - @Transient - private Long[] attachmentIds; - - @Transient - private String[] tempFileIds; - - @Transient - private List items; } diff --git a/src/main/java/com/imyeyu/api/modules/journal/mapper/JournalMapper.java b/src/main/java/com/imyeyu/api/modules/journal/mapper/JournalMapper.java index d37f1f0..117a252 100644 --- a/src/main/java/com/imyeyu/api/modules/journal/mapper/JournalMapper.java +++ b/src/main/java/com/imyeyu/api/modules/journal/mapper/JournalMapper.java @@ -9,7 +9,7 @@ import java.util.List; * @author 夜雨 * @since 2025-09-26 15:54 */ -public interface JournalMapper extends BaseMapper { +public interface JournalMapper extends BaseMapper { List listByIds(Long[] ids); } diff --git a/src/main/java/com/imyeyu/api/modules/journal/mapper/TravelLocationMapper.java b/src/main/java/com/imyeyu/api/modules/journal/mapper/TravelLocationMapper.java index 5a3e022..c9a5cc5 100644 --- a/src/main/java/com/imyeyu/api/modules/journal/mapper/TravelLocationMapper.java +++ b/src/main/java/com/imyeyu/api/modules/journal/mapper/TravelLocationMapper.java @@ -11,7 +11,7 @@ import java.util.List; * @author 夜雨 * @since 2025-12-12 14:35 */ -public interface TravelLocationMapper extends BaseMapper { +public interface TravelLocationMapper extends BaseMapper { List listByIds(Long[] ids); } diff --git a/src/main/java/com/imyeyu/api/modules/journal/mapper/TravelMapper.java b/src/main/java/com/imyeyu/api/modules/journal/mapper/TravelMapper.java index 0f221b5..3e9bac4 100644 --- a/src/main/java/com/imyeyu/api/modules/journal/mapper/TravelMapper.java +++ b/src/main/java/com/imyeyu/api/modules/journal/mapper/TravelMapper.java @@ -12,7 +12,7 @@ import java.util.List; * @author 夜雨 * @since 2025-12-12 14:35 */ -public interface TravelMapper extends BaseMapper { +public interface TravelMapper extends BaseMapper { /** * 根据 ID 列表查询 diff --git a/src/main/java/com/imyeyu/api/modules/journal/service/JournalService.java b/src/main/java/com/imyeyu/api/modules/journal/service/JournalService.java index 7924947..f676805 100644 --- a/src/main/java/com/imyeyu/api/modules/journal/service/JournalService.java +++ b/src/main/java/com/imyeyu/api/modules/journal/service/JournalService.java @@ -1,41 +1,11 @@ package com.imyeyu.api.modules.journal.service; -import com.imyeyu.api.modules.common.entity.Attachment; import com.imyeyu.api.modules.journal.entity.Journal; -import com.imyeyu.api.modules.journal.vo.journal.ArchiveRequest; -import com.imyeyu.api.modules.journal.vo.journal.JournalRequest; -import com.imyeyu.api.modules.journal.vo.journal.UpdateRequest; -import com.imyeyu.java.bean.timi.TimiException; -import com.imyeyu.spring.service.DeletableService; -import com.imyeyu.spring.service.GettableService; -import com.imyeyu.spring.service.PageableService; - -import java.util.List; +import com.imyeyu.spring.service.BaseService; /** * @author 夜雨 * @since 2025-09-26 15:53 */ -public interface JournalService extends PageableService, GettableService, DeletableService { - - void create(JournalRequest request) throws TimiException; - - void update(UpdateRequest request) throws TimiException; - - List listByIds(Long... ids); - - /** - * 过滤已存在瞬间 - * - * @param md5s 原图文件 MD5 列表 - * @return 未持久化储存的 MD5 列表 - * @throws TimiException - */ - String[] filterExistMoment(String[] md5s) throws TimiException; - - List createMoment(String[] tempFileIds) throws TimiException; - - void deleteMoment(Long[] thumbIds) throws TimiException; - - void archiveMoment(ArchiveRequest request) throws TimiException; +public interface JournalService extends BaseService { } diff --git a/src/main/java/com/imyeyu/api/modules/journal/service/TravelLocationService.java b/src/main/java/com/imyeyu/api/modules/journal/service/TravelLocationService.java index 261f19a..37dd344 100644 --- a/src/main/java/com/imyeyu/api/modules/journal/service/TravelLocationService.java +++ b/src/main/java/com/imyeyu/api/modules/journal/service/TravelLocationService.java @@ -1,26 +1,13 @@ package com.imyeyu.api.modules.journal.service; import com.imyeyu.api.modules.journal.entity.TravelLocation; -import com.imyeyu.java.bean.timi.TimiException; import com.imyeyu.spring.service.BaseService; -import java.util.List; - /** * 旅行地点服务 * * @author 夜雨 * @since 2025-12-12 14:40 */ -public interface TravelLocationService extends BaseService { - - List listByIds(Long... ids); - - /** - * 根据旅行 ID 删除所有地点 - * - * @param travelId 旅行 ID - * @throws TimiException 服务异常 - */ - void deleteByTravelId(Long travelId) throws TimiException; +public interface TravelLocationService extends BaseService { } diff --git a/src/main/java/com/imyeyu/api/modules/journal/service/TravelService.java b/src/main/java/com/imyeyu/api/modules/journal/service/TravelService.java index 4aef024..54d32d2 100644 --- a/src/main/java/com/imyeyu/api/modules/journal/service/TravelService.java +++ b/src/main/java/com/imyeyu/api/modules/journal/service/TravelService.java @@ -9,5 +9,5 @@ import com.imyeyu.spring.service.BaseService; * @author 夜雨 * @since 2025-12-12 14:40 */ -public interface TravelService extends BaseService { +public interface TravelService extends BaseService { } diff --git a/src/main/java/com/imyeyu/api/modules/journal/service/implement/JournalServiceImplement.java b/src/main/java/com/imyeyu/api/modules/journal/service/implement/JournalServiceImplement.java index 055b315..76b3637 100644 --- a/src/main/java/com/imyeyu/api/modules/journal/service/implement/JournalServiceImplement.java +++ b/src/main/java/com/imyeyu/api/modules/journal/service/implement/JournalServiceImplement.java @@ -1,35 +1,13 @@ package com.imyeyu.api.modules.journal.service.implement; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.imyeyu.api.config.dbsource.TimiServerDBConfig; -import com.imyeyu.api.modules.common.bean.MediaAttach; -import com.imyeyu.api.modules.common.bean.Metadata; -import com.imyeyu.api.modules.common.entity.Attachment; -import com.imyeyu.api.modules.common.service.AttachmentService; -import com.imyeyu.api.modules.common.service.SettingService; -import com.imyeyu.api.modules.common.service.TempFileService; import com.imyeyu.api.modules.journal.entity.Journal; import com.imyeyu.api.modules.journal.mapper.JournalMapper; import com.imyeyu.api.modules.journal.service.JournalService; -import com.imyeyu.api.modules.journal.vo.journal.ArchiveRequest; -import com.imyeyu.api.modules.journal.vo.journal.JournalRequest; -import com.imyeyu.api.modules.journal.vo.journal.UpdateRequest; -import com.imyeyu.java.TimiJava; -import com.imyeyu.java.bean.timi.TimiCode; -import com.imyeyu.java.bean.timi.TimiException; import com.imyeyu.spring.mapper.BaseMapper; import com.imyeyu.spring.service.AbstractEntityService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.BeanUtils; import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Set; -import java.util.stream.Collectors; /** * @author 夜雨 @@ -38,169 +16,12 @@ import java.util.stream.Collectors; @Slf4j @Service @RequiredArgsConstructor -public class JournalServiceImplement extends AbstractEntityService implements JournalService { - - private final ObjectMapper jackson; - - private final SettingService settingService; - private final TempFileService tempFileService; - private final AttachmentService attachmentService; +public class JournalServiceImplement extends AbstractEntityService implements JournalService { private final JournalMapper mapper; @Override - protected BaseMapper mapper() { + protected BaseMapper mapper() { return mapper; } - - @Transactional(TimiServerDBConfig.ROLLBACKER) - @Override - public void create(JournalRequest request) throws TimiException { - try { - Journal journal = new Journal(); - BeanUtils.copyProperties(request, journal); - super.create(journal); - - String[] tempFileIds = request.getTempFileIds(); - if (TimiJava.isEmpty(tempFileIds)) { - return; - } - for (int i = 0; i < tempFileIds.length; i++) { - Attachment tempFileAttach = attachmentService.getByMongoId(tempFileIds[i]); - - Attachment sourceAttach = new Attachment(); - sourceAttach.setName(tempFileAttach.getName()); - sourceAttach.setBizType(Attachment.BizType.JOURNAL); - sourceAttach.setBizId(journal.getId()); - sourceAttach.setInputStream(tempFileAttach.openInputStream()); - attachmentService.createMedia(sourceAttach); - } - } catch (Exception e) { - log.error("create journal error", e); - throw new TimiException(TimiCode.ERROR).msgKey("TODO create journal error"); - } - } - - @Transactional(TimiServerDBConfig.ROLLBACKER) - @Override - public void update(UpdateRequest request) throws TimiException { - TimiException.required(request.getId(), "not found request.id"); - Journal journal = get(request.getId()); - BeanUtils.copyProperties(request, journal); - super.update(journal); - - // 删除 - Set dbAttachSet = attachmentService.listByBizId(Attachment.BizType.JOURNAL, journal.getId(), MediaAttach.Type.THUMB) - .stream() - .map(Attachment::getId) - .collect(Collectors.toSet()); - Set retainIds = Set.of(TimiJava.firstNotEmpty(request.getAttachmentIds(), new Long[0])); - dbAttachSet.removeAll(retainIds); - for (Long removeId : dbAttachSet) { - attachmentService.deleteMedia(removeId); - } - // 新增 - for (String tempFileId : TimiJava.defaultIfNull(request.getTempFileIds(), new String[0])) { - Attachment tempFileAttach = attachmentService.getByMongoId(tempFileId); - - Attachment sourceAttach = new Attachment(); - sourceAttach.setName(tempFileAttach.getName()); - sourceAttach.setBizType(Attachment.BizType.JOURNAL); - sourceAttach.setBizId(journal.getId()); - sourceAttach.setInputStream(tempFileAttach.openInputStream()); - attachmentService.createMedia(sourceAttach); - } - } - - @Transactional(TimiServerDBConfig.ROLLBACKER) - @Override - public void delete(Long id) { - super.delete(id); - attachmentService.deleteByBizId(Attachment.BizType.JOURNAL, id); - } - - @Override - public List listByIds(Long... ids) { - return mapper.listByIds(ids); - } - - @Override - public String[] filterExistMoment(String[] md5s) throws TimiException { - if (TimiJava.isEmpty(md5s)) { - return null; - } - Set dbMD5 = attachmentService.listByMd5s(Attachment.BizType.JOURNAL_MOMENT, null, List.of(MediaAttach.Type.SOURCE), Arrays.asList(md5s)) - .stream() - .map(Attachment::getMd5) - .collect(Collectors.toSet()); - return Arrays.stream(md5s).filter(md5 -> !dbMD5.contains(md5)).toArray(String[]::new); - } - - @Override - public List createMoment(String[] tempFileIds) throws TimiException { - if (TimiJava.isEmpty(tempFileIds)) { - return null; - } - try { - List thumbResult = new ArrayList<>(); - for (int i = 0; i < tempFileIds.length; i++) { - Attachment tempFileAttach = attachmentService.getByMongoId(tempFileIds[i]); - - Attachment sourceAttach = new Attachment(); - sourceAttach.setName(tempFileAttach.getName()); - sourceAttach.setBizType(Attachment.BizType.JOURNAL_MOMENT); - sourceAttach.setBizId(0L); - sourceAttach.setAttachTypeValue(MediaAttach.Type.SOURCE); - sourceAttach.setSize(tempFileAttach.getSize()); - sourceAttach.setInputStream(tempFileAttach.openInputStream()); - thumbResult.add(attachmentService.createMedia(sourceAttach)); - } - return thumbResult; - } catch (Exception e) { - log.error("create moment error", e); - throw new TimiException(TimiCode.ERROR).msgKey("TODO create moment error"); - } - } - - @Override - public void deleteMoment(Long[] thumbIds) throws TimiException { - for (int i = 0; i < thumbIds.length; i++) { - attachmentService.deleteMedia(thumbIds[i]); - } - } - - @Override - public void archiveMoment(ArchiveRequest request) throws TimiException { - try { - Journal journal; - if (request.getId() == null) { - // 归档为新纪录 - journal = new Journal(); - BeanUtils.copyProperties(request, journal); - super.create(journal); - } else { - // 归档到指定记录 - journal = get(request.getId()); - } - Long[] thumbIds = request.getThumbIds(); - if (TimiJava.isEmpty(thumbIds)) { - return; - } - for (int i = 0; i < thumbIds.length; i++) { - Attachment thumbAttach = attachmentService.get(thumbIds[i]); - thumbAttach.setBizType(Attachment.BizType.JOURNAL); - thumbAttach.setBizId(journal.getId()); - attachmentService.update(thumbAttach); - - Metadata.ThumbImage thumbMetadata = jackson.convertValue(thumbAttach.getMetadata(), Metadata.ThumbImage.class); - Attachment sourceAttach = attachmentService.get(thumbMetadata.getSourceId()); - sourceAttach.setBizType(Attachment.BizType.JOURNAL); - sourceAttach.setBizId(journal.getId()); - attachmentService.update(sourceAttach); - } - } catch (Exception e) { - log.error("archive journal error", e); - throw new TimiException(TimiCode.ERROR).msgKey("TODO archive journal error"); - } - } } diff --git a/src/main/java/com/imyeyu/api/modules/journal/service/implement/TravelLocationServiceImplement.java b/src/main/java/com/imyeyu/api/modules/journal/service/implement/TravelLocationServiceImplement.java index 5c79d09..85e4626 100644 --- a/src/main/java/com/imyeyu/api/modules/journal/service/implement/TravelLocationServiceImplement.java +++ b/src/main/java/com/imyeyu/api/modules/journal/service/implement/TravelLocationServiceImplement.java @@ -1,26 +1,14 @@ package com.imyeyu.api.modules.journal.service.implement; -import com.imyeyu.api.config.dbsource.TimiServerDBConfig; -import com.imyeyu.api.modules.common.bean.MediaAttach; -import com.imyeyu.api.modules.common.entity.Attachment; -import com.imyeyu.api.modules.common.service.AttachmentService; import com.imyeyu.api.modules.journal.entity.TravelLocation; import com.imyeyu.api.modules.journal.mapper.TravelLocationMapper; import com.imyeyu.api.modules.journal.service.TravelLocationService; -import com.imyeyu.api.modules.journal.service.TravelService; -import com.imyeyu.java.TimiJava; -import com.imyeyu.java.bean.timi.TimiException; import com.imyeyu.spring.mapper.BaseMapper; import com.imyeyu.spring.service.AbstractEntityService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import java.util.List; -import java.util.Set; -import java.util.stream.Collectors; /** * 旅行地点服务实现 @@ -31,88 +19,12 @@ import java.util.stream.Collectors; @Slf4j @Service @RequiredArgsConstructor(onConstructor_ = {@Lazy}) -public class TravelLocationServiceImplement extends AbstractEntityService implements TravelLocationService { - - private final TravelService travelService; - private final AttachmentService attachmentService; +public class TravelLocationServiceImplement extends AbstractEntityService implements TravelLocationService { private final TravelLocationMapper mapper; @Override - protected BaseMapper mapper() { + protected BaseMapper mapper() { return mapper; } - - @Transactional(TimiServerDBConfig.ROLLBACKER) - @Override - public void create(TravelLocation travelLocation) { - super.create(travelLocation); - for (String tempFileId : travelLocation.getTempFileIds()) { - Attachment tempFileAttach = attachmentService.getByMongoId(tempFileId); - - Attachment attach = new Attachment(); - attach.setName(tempFileAttach.getName()); - attach.setBizType(Attachment.BizType.JOURNAL_TRAVEL); - attach.setBizId(travelLocation.getId()); - attach.setInputStream(tempFileAttach.openInputStream()); - attachmentService.createMedia(attach); - } - // 更新操作时间以保证排序 - travelService.update(travelService.get(travelLocation.getTravelId())); - } - - @Transactional(TimiServerDBConfig.ROLLBACKER) - @Override - public void update(TravelLocation travelLocation) { - // 使用 update 以允许设置 score 和 importance 为 null - mapper.update(travelLocation); - // 删除 - if (TimiJava.isNotEmpty(travelLocation.getAttachmentIds())) { - Set dbAttachSet = attachmentService.listByBizId(Attachment.BizType.JOURNAL_TRAVEL, travelLocation.getId(), MediaAttach.Type.THUMB) - .stream() - .map(Attachment::getId) - .collect(Collectors.toSet()); - Set retainIds = Set.of(travelLocation.getAttachmentIds()); - dbAttachSet.removeAll(retainIds); - for (Long removeId : dbAttachSet) { - attachmentService.deleteMedia(removeId); - } - } - // 新增 - for (String tempFileId : TimiJava.defaultIfNull(travelLocation.getTempFileIds(), new String[0])) { - Attachment tempFileAttach = attachmentService.getByMongoId(tempFileId); - - Attachment attach = new Attachment(); - attach.setName(tempFileAttach.getName()); - attach.setBizType(Attachment.BizType.JOURNAL_TRAVEL); - attach.setBizId(travelLocation.getId()); - attach.setInputStream(tempFileAttach.openInputStream()); - attachmentService.createMedia(attach); - } - // 更新操作时间以保证排序 - travelService.update(travelService.get(travelLocation.getTravelId())); - } - - @Transactional(TimiServerDBConfig.ROLLBACKER) - @Override - public void delete(Long id) { - super.delete(id); - attachmentService.deleteByBizId(Attachment.BizType.JOURNAL_TRAVEL, id); - } - - @Override - public List listByIds(Long... ids) { - return mapper.listByIds(ids); - } - - @Transactional(TimiServerDBConfig.ROLLBACKER) - @Override - public void deleteByTravelId(Long travelId) throws TimiException { - TravelLocation example = new TravelLocation(); - example.setTravelId(travelId); - List locationList = mapper.selectAllByExample(example); - for (TravelLocation location : locationList) { - delete(location.getId()); - } - } } diff --git a/src/main/java/com/imyeyu/api/modules/journal/service/implement/TravelServiceImplement.java b/src/main/java/com/imyeyu/api/modules/journal/service/implement/TravelServiceImplement.java index 96b9105..074d7af 100644 --- a/src/main/java/com/imyeyu/api/modules/journal/service/implement/TravelServiceImplement.java +++ b/src/main/java/com/imyeyu/api/modules/journal/service/implement/TravelServiceImplement.java @@ -1,9 +1,7 @@ package com.imyeyu.api.modules.journal.service.implement; -import com.imyeyu.api.config.dbsource.TimiServerDBConfig; import com.imyeyu.api.modules.journal.entity.Travel; import com.imyeyu.api.modules.journal.mapper.TravelMapper; -import com.imyeyu.api.modules.journal.service.TravelLocationService; import com.imyeyu.api.modules.journal.service.TravelService; import com.imyeyu.spring.mapper.BaseMapper; import com.imyeyu.spring.service.AbstractEntityService; @@ -11,7 +9,6 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; /** * 旅行计划服务实现 @@ -22,27 +19,12 @@ import org.springframework.transaction.annotation.Transactional; @Slf4j @Service @RequiredArgsConstructor(onConstructor_ = {@Lazy}) -public class TravelServiceImplement extends AbstractEntityService implements TravelService { +public class TravelServiceImplement extends AbstractEntityService implements TravelService { private final TravelMapper mapper; - private final TravelLocationService travelLocationService; @Override - protected BaseMapper mapper() { + protected BaseMapper mapper() { return mapper; } - - @Transactional(TimiServerDBConfig.ROLLBACKER) - @Override - public void update(Travel travel) { - // 使用 update 以允许设置 travelAt 和 days 为 null - mapper.update(travel); - } - - @Transactional(TimiServerDBConfig.ROLLBACKER) - @Override - public void delete(Long id) { - super.delete(id); - travelLocationService.deleteByTravelId(id); - } } diff --git a/src/main/java/com/imyeyu/api/modules/journal/util/JournalAPIInterceptor.java b/src/main/java/com/imyeyu/api/modules/journal/util/JournalAPIInterceptor.java index 7ec5883..291b33a 100644 --- a/src/main/java/com/imyeyu/api/modules/journal/util/JournalAPIInterceptor.java +++ b/src/main/java/com/imyeyu/api/modules/journal/util/JournalAPIInterceptor.java @@ -1,6 +1,6 @@ package com.imyeyu.api.modules.journal.util; -import com.imyeyu.api.modules.common.bean.SettingKey; +import com.imyeyu.api.modules.common.entity.Setting; import com.imyeyu.api.modules.common.service.SettingService; import com.imyeyu.api.modules.journal.bean.RequiredUploadPermission; import com.imyeyu.java.TimiJava; @@ -32,12 +32,11 @@ public class JournalAPIInterceptor implements HandlerInterceptor { private final SettingService settingService; - private Set keys, openIds; + private Set keys; @PostConstruct private void postConstruct() { - keys = Set.of(settingService.getAsString(SettingKey.JOURNAL_KEY).split(",")); - openIds = Set.of(settingService.getAsString(SettingKey.JOURNAL_OPEN_ID_WHITE_LIST).split(",")); +// keys = settingService.getSystem(Setting.Module.Journal.KEY).asSet(); } public boolean preHandle(@NonNull HttpServletRequest req, @NonNull HttpServletResponse resp, @NonNull Object handler) { diff --git a/src/main/java/com/imyeyu/api/modules/lyric/controller/LyricController.java b/src/main/java/com/imyeyu/api/modules/lyric/controller/LyricController.java index 277a9a2..ab0699b 100644 --- a/src/main/java/com/imyeyu/api/modules/lyric/controller/LyricController.java +++ b/src/main/java/com/imyeyu/api/modules/lyric/controller/LyricController.java @@ -1,32 +1,11 @@ package com.imyeyu.api.modules.lyric.controller; -import jakarta.validation.Valid; -import jakarta.validation.constraints.Min; -import jakarta.validation.constraints.NotNull; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import com.imyeyu.io.IOSize; -import com.imyeyu.java.bean.timi.TimiCode; -import com.imyeyu.java.bean.timi.TimiException; -import com.imyeyu.api.annotation.CaptchaValid; -import com.imyeyu.api.bean.CaptchaFrom; -import com.imyeyu.api.modules.common.service.UserService; -import com.imyeyu.api.modules.lyric.entity.Lyric; -import com.imyeyu.api.modules.lyric.entity.LyricCorrect; +import com.imyeyu.api.modules.user.service.UserService; import com.imyeyu.api.modules.lyric.service.LyricCorrectService; import com.imyeyu.api.modules.lyric.service.LyricService; -import com.imyeyu.api.modules.lyric.vo.LyricCorrectRequest; -import com.imyeyu.api.modules.lyric.vo.LyricRequest; -import com.imyeyu.spring.annotation.AOPLog; -import com.imyeyu.spring.annotation.RequestRateLimit; -import com.imyeyu.spring.annotation.RequiredToken; -import com.imyeyu.spring.bean.CaptchaData; -import com.imyeyu.spring.bean.Page; -import com.imyeyu.spring.bean.PageResult; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; /** @@ -44,38 +23,36 @@ public class LyricController { private final UserService userService; private final LyricService service; private final LyricCorrectService correctService; - - @AOPLog - @RequestRateLimit - @PostMapping("") - public Lyric get(@Valid @RequestBody LyricRequest request) { - return service.get(request); - } - - @AOPLog - @CaptchaValid(CaptchaFrom.LYRIC_CORRECT) - @RequestRateLimit - @PostMapping("/correct") - public long correctRequest(CaptchaData request) { - if (IOSize.MB * 120 < request.getData().getFile().getSize()) { - throw new TimiException(TimiCode.ARG_BAD).msgKey("lyric.correct_request.too_big"); - } - return correctService.correctRequest(request.getData()); - } - - @AOPLog - @RequiredToken - @RequestRateLimit - @PostMapping("/correct/list") - public PageResult listCorrect(@Valid @RequestBody Page page) { - return correctService.pageByToken(page); - } - - @AOPLog - @RequiredToken - @RequestRateLimit - @PostMapping("/correct/cancel") - public void cancelCorrectRequest(@Min(1) @NotNull @RequestParam Long id) { - correctService.cancelCorrectRequest(id); - } +// +// @AOPLog +// @RequestRateLimit +// @PostMapping("") +// public Lyric get(@Valid @RequestBody LyricRequest request) { +// return service.get(request); +// } +// +// @AOPLog +// @CaptchaValid(CaptchaFrom.LYRIC_CORRECT) +// @RequestRateLimit +// @PostMapping("/correct") +// public long correctRequest(CaptchaData request) { +// if (IOSize.MB * 120 < request.getData().getFile().getSize()) { +// throw new TimiException(TimiCode.ARG_BAD).msgKey("lyric.correct_request.too_big"); +// } +// return correctService.correctRequest(request.getData()); +// } +// +// @AOPLog +// @RequestRateLimit +// @PostMapping("/correct/list") +// public PageResult listCorrect(@Valid @RequestBody Page page) { +// return correctService.pageByToken(page); +// } +// +// @AOPLog +// @RequestRateLimit +// @PostMapping("/correct/cancel") +// public void cancelCorrectRequest(@Min(1) @NotNull @RequestParam Long id) { +// correctService.cancelCorrectRequest(id); +// } } diff --git a/src/main/java/com/imyeyu/api/modules/lyric/entity/Lyric.java b/src/main/java/com/imyeyu/api/modules/lyric/entity/Lyric.java index 16cd4c6..cea608c 100644 --- a/src/main/java/com/imyeyu/api/modules/lyric/entity/Lyric.java +++ b/src/main/java/com/imyeyu/api/modules/lyric/entity/Lyric.java @@ -1,8 +1,9 @@ package com.imyeyu.api.modules.lyric.entity; +import com.imyeyu.spring.annotation.table.PageIgnore; +import com.imyeyu.spring.entity.UUIDEntity; import lombok.Data; import lombok.EqualsAndHashCode; -import com.imyeyu.spring.entity.Entity; /** * 歌词 @@ -12,7 +13,7 @@ import com.imyeyu.spring.entity.Entity; */ @Data @EqualsAndHashCode(callSuper = true) -public class Lyric extends Entity { +public class Lyric extends UUIDEntity { /** 歌曲名称 */ private String song; @@ -21,7 +22,11 @@ public class Lyric extends Entity { private String singer; /** 歌词 */ - private String data; + @PageIgnore + private String content; + + /** 被引用次数 */ + private Long citations; /** 更新者 ID */ private Long updatedBy; diff --git a/src/main/java/com/imyeyu/api/modules/lyric/entity/LyricCorrect.java b/src/main/java/com/imyeyu/api/modules/lyric/entity/LyricCorrect.java index 34b8568..ac4d28e 100644 --- a/src/main/java/com/imyeyu/api/modules/lyric/entity/LyricCorrect.java +++ b/src/main/java/com/imyeyu/api/modules/lyric/entity/LyricCorrect.java @@ -1,8 +1,9 @@ package com.imyeyu.api.modules.lyric.entity; +import com.imyeyu.spring.annotation.table.PageIgnore; +import com.imyeyu.spring.entity.UUIDEntity; import lombok.Data; import lombok.EqualsAndHashCode; -import com.imyeyu.spring.entity.Entity; /** * 歌词更新请求 @@ -12,10 +13,10 @@ import com.imyeyu.spring.entity.Entity; */ @Data @EqualsAndHashCode(callSuper = true) -public class LyricCorrect extends Entity { +public class LyricCorrect extends UUIDEntity { /** 歌词 ID */ - private Long lyricId; + private String lyricId; /** 歌曲名 */ private String song; @@ -24,10 +25,11 @@ public class LyricCorrect extends Entity { private String singer; /** 歌词 */ - private String data; + @PageIgnore + private String content; /** 申请人 */ - private Long requestBy; + private String requestBy; /** 申请 IP */ private String requestIP; @@ -42,7 +44,7 @@ public class LyricCorrect extends Entity { private Long rejectAt; /** 拒绝原因 */ - private Long rejectReason; + private String rejectReason; /** * 是否可取消 @@ -50,7 +52,7 @@ public class LyricCorrect extends Entity { * @param requestBy 操作人 * @return true 为可取消 */ - public boolean canCancel(Long requestBy) { + public boolean canCancel(String requestBy) { return id != null && this.requestBy.equals(requestBy) && approvalAt == null && rejectAt == null; } } diff --git a/src/main/java/com/imyeyu/api/modules/lyric/mapper/LyricCorrectMapper.java b/src/main/java/com/imyeyu/api/modules/lyric/mapper/LyricCorrectMapper.java index 1775374..8e43a57 100644 --- a/src/main/java/com/imyeyu/api/modules/lyric/mapper/LyricCorrectMapper.java +++ b/src/main/java/com/imyeyu/api/modules/lyric/mapper/LyricCorrectMapper.java @@ -10,7 +10,7 @@ import org.apache.ibatis.annotations.Select; * @author 夜雨 * @since 2023-04-24 17:06 */ -public interface LyricCorrectMapper extends BaseMapper { +public interface LyricCorrectMapper extends BaseMapper { @Select("UPDATE lyric_corrects SET cancel_at = FLOOR(UNIX_TIMESTAMP(NOW(3)) * 1000) WHERE id = #{id}") void cancelCorrectRequest(long id); diff --git a/src/main/java/com/imyeyu/api/modules/lyric/mapper/LyricMapper.java b/src/main/java/com/imyeyu/api/modules/lyric/mapper/LyricMapper.java index 55326e2..9517b72 100644 --- a/src/main/java/com/imyeyu/api/modules/lyric/mapper/LyricMapper.java +++ b/src/main/java/com/imyeyu/api/modules/lyric/mapper/LyricMapper.java @@ -12,7 +12,7 @@ import java.util.List; * @author 夜雨 * @since 2023-02-02 11:54 */ -public interface LyricMapper extends BaseMapper { +public interface LyricMapper extends BaseMapper { @Select("SELECT * FROM lyric WHERE song LIKE CONCAT('%', #{song}, '%') OR singer LIKE CONCAT('%', #{singer}, '%') LIMIT 1") Lyric selectBySong(String song, String singer); diff --git a/src/main/java/com/imyeyu/api/modules/lyric/service/LyricCorrectService.java b/src/main/java/com/imyeyu/api/modules/lyric/service/LyricCorrectService.java index ca89335..777ac13 100644 --- a/src/main/java/com/imyeyu/api/modules/lyric/service/LyricCorrectService.java +++ b/src/main/java/com/imyeyu/api/modules/lyric/service/LyricCorrectService.java @@ -1,9 +1,6 @@ package com.imyeyu.api.modules.lyric.service; import com.imyeyu.api.modules.lyric.entity.LyricCorrect; -import com.imyeyu.api.modules.lyric.vo.LyricCorrectRequest; -import com.imyeyu.spring.bean.Page; -import com.imyeyu.spring.bean.PageResult; import com.imyeyu.spring.service.CreatableService; /** @@ -13,10 +10,4 @@ import com.imyeyu.spring.service.CreatableService; * @since 2023-04-24 17:04 */ public interface LyricCorrectService extends CreatableService { - - PageResult pageByToken(Page page); - - long correctRequest(LyricCorrectRequest request); - - void cancelCorrectRequest(long id); } diff --git a/src/main/java/com/imyeyu/api/modules/lyric/service/LyricService.java b/src/main/java/com/imyeyu/api/modules/lyric/service/LyricService.java index 49e911e..ffbaf67 100644 --- a/src/main/java/com/imyeyu/api/modules/lyric/service/LyricService.java +++ b/src/main/java/com/imyeyu/api/modules/lyric/service/LyricService.java @@ -1,11 +1,7 @@ package com.imyeyu.api.modules.lyric.service; -import com.imyeyu.java.bean.timi.TimiException; import com.imyeyu.api.modules.lyric.entity.Lyric; -import com.imyeyu.api.modules.lyric.vo.LyricRequest; -import com.imyeyu.spring.service.GettableService; - -import java.util.List; +import com.imyeyu.spring.service.BaseService; /** * 歌词服务 @@ -13,15 +9,5 @@ import java.util.List; * @author 夜雨 * @since 2023-02-02 11:55 */ -public interface LyricService extends GettableService { - - /** - * 获取歌词 - * - * @return 歌词 - * @throws TimiException 服务异常 - */ - Lyric get(LyricRequest request); - - List listAll4Simple(); +public interface LyricService extends BaseService { } diff --git a/src/main/java/com/imyeyu/api/modules/lyric/service/implement/LyricCorrectServiceImplement.java b/src/main/java/com/imyeyu/api/modules/lyric/service/implement/LyricCorrectServiceImplement.java index 2ac14d1..5f2f1f4 100644 --- a/src/main/java/com/imyeyu/api/modules/lyric/service/implement/LyricCorrectServiceImplement.java +++ b/src/main/java/com/imyeyu/api/modules/lyric/service/implement/LyricCorrectServiceImplement.java @@ -1,27 +1,15 @@ package com.imyeyu.api.modules.lyric.service.implement; -import com.imyeyu.api.config.dbsource.TimiServerDBConfig; -import com.imyeyu.api.modules.common.entity.Attachment; -import com.imyeyu.api.modules.common.entity.User; import com.imyeyu.api.modules.common.service.AttachmentService; -import com.imyeyu.api.modules.common.service.UserService; +import com.imyeyu.api.modules.user.service.UserService; import com.imyeyu.api.modules.lyric.entity.LyricCorrect; import com.imyeyu.api.modules.lyric.mapper.LyricCorrectMapper; import com.imyeyu.api.modules.lyric.service.LyricCorrectService; -import com.imyeyu.api.modules.lyric.vo.LyricCorrectRequest; -import com.imyeyu.java.TimiJava; -import com.imyeyu.java.bean.timi.TimiCode; -import com.imyeyu.java.bean.timi.TimiException; -import com.imyeyu.spring.TimiSpring; -import com.imyeyu.spring.bean.Page; -import com.imyeyu.spring.bean.PageResult; import com.imyeyu.spring.mapper.BaseMapper; import com.imyeyu.spring.service.AbstractEntityService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import org.springframework.web.multipart.MultipartFile; /** * @author 夜雨 @@ -30,78 +18,14 @@ import org.springframework.web.multipart.MultipartFile; @Slf4j @Service @RequiredArgsConstructor -public class LyricCorrectServiceImplement extends AbstractEntityService implements LyricCorrectService { +public class LyricCorrectServiceImplement extends AbstractEntityService implements LyricCorrectService { private final UserService userService; private final AttachmentService attachmentService; private final LyricCorrectMapper mapper; @Override - protected BaseMapper mapper() { + protected BaseMapper mapper() { return mapper; } - - @Override - public void create(LyricCorrect lyricCorrect) { - if (TimiJava.isNotEmpty(lyricCorrect.getRequestBy())) { - User user = userService.get(lyricCorrect.getRequestBy()); - if (user.isBanning()) { - throw new TimiException(TimiCode.RESULT_BAN).msgKey("user.banded"); - } - } - super.create(lyricCorrect); - } - - @Override - public PageResult pageByToken(Page page) { - throw new TimiException(TimiCode.ERROR_NOT_SUPPORT).msgKey("TODO"); - } - - @Transactional(TimiServerDBConfig.ROLLBACKER) - @Override - public long correctRequest(LyricCorrectRequest request) { - LyricCorrect correct = new LyricCorrect(); - correct.setLyricId(request.getLyricId()); - correct.setSong(request.getSong()); - correct.setSinger(request.getSinger()); - correct.setData(request.getData()); - correct.setRequestIP(TimiSpring.getRequestIP()); - - String token = TimiSpring.getToken(); - if (TimiJava.isNotEmpty(token)) { - User user = userService.getLoginUser(); - correct.setRequestBy(user.getId()); - } - create(correct); - try { - MultipartFile file = request.getFile(); - Attachment attachment = new Attachment(); - attachment.setBizType(Attachment.BizType.LYRIC); - attachment.setBizId(correct.getId()); - attachment.setName(correct.getId() + ".lrc"); - attachment.setAttachTypeValue(User.AttachType.WRAPPER); - attachment.setSize(file.getSize()); - attachment.setInputStream(file.getInputStream()); - attachmentService.create(attachment); - } catch (Exception e) { - log.error("save lyric error", e); - throw new TimiException(TimiCode.ERROR).msgKey("TODO save lyric error"); - } - return correct.getId(); - } - - @Override - public void cancelCorrectRequest(long id) { - User requestBy = userService.getLoginUser(); - LyricCorrect correct = mapper.select(id); - if (correct == null) { - throw new TimiException(TimiCode.RESULT_NULL).msgKey("lyric.correct_request.not_found"); - } - if (!correct.canCancel(requestBy.getId())) { - throw new TimiException(TimiCode.RESULT_BAD).msgKey("lyric.correct_request.unsupported_cancel"); - } - Attachment attachment = attachmentService.getByBizId(Attachment.BizType.LYRIC, correct.getId()); - attachmentService.delete(attachment.getId()); - mapper.cancelCorrectRequest(correct.getId()); - } } diff --git a/src/main/java/com/imyeyu/api/modules/lyric/service/implement/LyricServiceImplement.java b/src/main/java/com/imyeyu/api/modules/lyric/service/implement/LyricServiceImplement.java index 55ef399..df73a5f 100644 --- a/src/main/java/com/imyeyu/api/modules/lyric/service/implement/LyricServiceImplement.java +++ b/src/main/java/com/imyeyu/api/modules/lyric/service/implement/LyricServiceImplement.java @@ -2,24 +2,20 @@ package com.imyeyu.api.modules.lyric.service.implement; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -import com.imyeyu.java.bean.timi.TimiCode; -import com.imyeyu.java.bean.timi.TimiException; import com.imyeyu.api.modules.lyric.entity.Lyric; import com.imyeyu.api.modules.lyric.mapper.LyricMapper; import com.imyeyu.api.modules.lyric.service.LyricService; -import com.imyeyu.api.modules.lyric.vo.LyricRequest; -import com.imyeyu.utils.Collect; +import com.imyeyu.java.bean.timi.TimiCode; +import com.imyeyu.java.bean.timi.TimiException; +import com.imyeyu.spring.mapper.BaseMapper; +import com.imyeyu.spring.service.AbstractEntityService; import com.imyeyu.utils.Encoder; -import com.imyeyu.utils.Text; -import com.imyeyu.utils.Time; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.hc.client5.http.fluent.Request; import org.springframework.stereotype.Service; import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.List; import java.util.Map; /** @@ -31,7 +27,7 @@ import java.util.Map; @Slf4j @Service @RequiredArgsConstructor -public class LyricServiceImplement implements LyricService { +public class LyricServiceImplement extends AbstractEntityService implements LyricService { /** 酷狗歌曲查询接口 */ private static final String API_GET_SONG = "http://mobilecdn.kugou.com/api/v3/search/song?"; @@ -53,44 +49,49 @@ public class LyricServiceImplement implements LyricService { private final ObjectMapper jackson; @Override - public Lyric get(Long id) { - return mapper.select(id); + protected BaseMapper mapper() { + return null; } - @Override - public Lyric get(LyricRequest request) { - // 从数据库模糊搜索获取 - Lyric lyric = mapper.selectBySong(request.getSong().trim(), request.getSinger().trim()); - if (lyric != null) { - return lyric; - } - { - // 从缓存模糊字符串搜索 - String name = request.getSong().trim() + "_" + request.getSinger().trim(); - Map result = new HashMap<>(); - for (Map.Entry item : allLyric.entrySet()) { - result.put(item.getKey(), Text.similarityRatio(name, item.getValue())); - } - LinkedHashMap sortedMap = Collect.sortMapByNumberValueDESC(result); - if (sortedMap.keySet().iterator().hasNext()) { - Long guessID = sortedMap.keySet().iterator().next(); - Number ratio = result.get(guessID); - if (.7 < ratio.doubleValue()) { - return this.get(guessID); - } - } - } - // 从 API 创建 - lyric = fetchLyric(request.getSong(), request.getSinger(), request.getTl()); - lyric.setCreatedAt(Time.now()); - mapper.insert(lyric); - return lyric; - } - - @Override - public List listAll4Simple() { - return mapper.listAll4Simple(); - } + // @Override +// public Lyric get(Long id) { +// return mapper.select(id); +// } +// +// @Override +// public Lyric get(LyricRequest request) { +// // 从数据库模糊搜索获取 +// Lyric lyric = mapper.selectBySong(request.getSong().trim(), request.getSinger().trim()); +// if (lyric != null) { +// return lyric; +// } +// { +// // 从缓存模糊字符串搜索 +// String name = request.getSong().trim() + "_" + request.getSinger().trim(); +// Map result = new HashMap<>(); +// for (Map.Entry item : allLyric.entrySet()) { +// result.put(item.getKey(), Text.similarityRatio(name, item.getValue())); +// } +// LinkedHashMap sortedMap = Collect.sortMapByNumberValueDESC(result); +// if (sortedMap.keySet().iterator().hasNext()) { +// Long guessID = sortedMap.keySet().iterator().next(); +// Number ratio = result.get(guessID); +// if (.7 < ratio.doubleValue()) { +// return this.get(guessID); +// } +// } +// } +// // 从 API 创建 +// lyric = fetchLyric(request.getSong(), request.getSinger(), request.getTl()); +// lyric.setCreatedAt(Time.now()); +// mapper.insert(lyric); +// return lyric; +// } +// +// @Override +// public List listAll4Simple() { +// return mapper.listAll4Simple(); +// } /** * 从 API 获取 @@ -131,7 +132,6 @@ public class LyricServiceImplement implements LyricService { Lyric lyric = new Lyric(); lyric.setSong(data.path("songname").asText()); lyric.setSinger(data.path("singername").asText()); - lyric.setData(response); return lyric; } catch (Exception e) { log.error("fetch lyric fail", e); diff --git a/src/main/java/com/imyeyu/api/modules/lyric/util/InitLyricSearch.java b/src/main/java/com/imyeyu/api/modules/lyric/util/InitLyricSearch.java index dbdb40b..d66476d 100644 --- a/src/main/java/com/imyeyu/api/modules/lyric/util/InitLyricSearch.java +++ b/src/main/java/com/imyeyu/api/modules/lyric/util/InitLyricSearch.java @@ -1,6 +1,5 @@ package com.imyeyu.api.modules.lyric.util; -import com.imyeyu.api.modules.lyric.entity.Lyric; import com.imyeyu.api.modules.lyric.service.LyricService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.CommandLineRunner; @@ -9,7 +8,6 @@ import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Component; import java.util.HashMap; -import java.util.List; import java.util.Map; /** @@ -29,10 +27,10 @@ public class InitLyricSearch implements CommandLineRunner { @Override public void run(String... args) throws Exception { - List list = service.listAll4Simple(); - for (int i = 0; i < list.size(); i++) { - allLyric.put(list.get(i).getId(), list.get(i).getSong() + "_" + list.get(i).getSinger()); - } +// List list = service.listAll4Simple(); +// for (int i = 0; i < list.size(); i++) { +// allLyric.put(list.get(i).getId(), list.get(i).getSong() + "_" + list.get(i).getSinger()); +// } } /** @return 所有歌词映射,Map<ID, Song_Singer> */ diff --git a/src/main/java/com/imyeyu/api/modules/minecraft/annotation/RequiredFMCServerToken.java b/src/main/java/com/imyeyu/api/modules/minecraft/annotation/RequiredFMCServerToken.java deleted file mode 100644 index bcef703..0000000 --- a/src/main/java/com/imyeyu/api/modules/minecraft/annotation/RequiredFMCServerToken.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.imyeyu.api.modules.minecraft.annotation; - -import org.springframework.stereotype.Component; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * 需要令牌 - * - * @author 夜雨 - * @since 2024-08-06 16:38 - */ -@Target(ElementType.METHOD) -@Retention(RetentionPolicy.RUNTIME) -@Component -public @interface RequiredFMCServerToken { -} diff --git a/src/main/java/com/imyeyu/api/modules/minecraft/annotation/RequiredFMCServerTokenInterceptor.java b/src/main/java/com/imyeyu/api/modules/minecraft/annotation/RequiredFMCServerTokenInterceptor.java deleted file mode 100644 index d6f0f9a..0000000 --- a/src/main/java/com/imyeyu/api/modules/minecraft/annotation/RequiredFMCServerTokenInterceptor.java +++ /dev/null @@ -1,51 +0,0 @@ -package com.imyeyu.api.modules.minecraft.annotation; - -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import lombok.RequiredArgsConstructor; -import com.imyeyu.java.bean.timi.TimiCode; -import com.imyeyu.java.bean.timi.TimiException; -import com.imyeyu.api.modules.common.bean.SettingKey; -import com.imyeyu.api.modules.common.service.SettingService; -import com.imyeyu.spring.TimiSpring; -import org.springframework.lang.NonNull; -import org.springframework.stereotype.Component; -import org.springframework.web.method.HandlerMethod; -import org.springframework.web.servlet.HandlerInterceptor; - -/** - * Minecraft 服务器接口验证令牌 - * - * @author 夜雨 - * @since 2024-08-06 16:40 - */ -@Component -@RequiredArgsConstructor -public class RequiredFMCServerTokenInterceptor implements HandlerInterceptor { - - private final SettingService settingService; - - /** - * 处理请求 - * - * @param req 请求 - * @param resp 返回 - * @param handler 处理方法 - * @return true 为通过 - */ - @Override - public boolean preHandle(@NonNull HttpServletRequest req, @NonNull HttpServletResponse resp, @NonNull Object handler) { - // 方法注解 - if (handler instanceof HandlerMethod handlerMethod) { - RequiredFMCServerToken requiredFMCServerToken = handlerMethod.getMethodAnnotation(RequiredFMCServerToken.class); - if (requiredFMCServerToken == null) { - return true; - } - if (!settingService.getAsString(SettingKey.FMC_SERVER_TOKEN).equals(TimiSpring.getToken())) { - throw new TimiException(TimiCode.ARG_MISS).msgKey("token.illegal"); - } - return true; - } - return true; - } -} diff --git a/src/main/java/com/imyeyu/api/modules/minecraft/bean/AttachType.java b/src/main/java/com/imyeyu/api/modules/minecraft/bean/AttachType.java deleted file mode 100644 index af79e0c..0000000 --- a/src/main/java/com/imyeyu/api/modules/minecraft/bean/AttachType.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.imyeyu.api.modules.minecraft.bean; - -/** - * @author 夜雨 - * @since 2024-04-28 22:06 - */ -public enum AttachType { - - LAUNCHER_BG, - - LAUNCHER_BGM -} diff --git a/src/main/java/com/imyeyu/api/modules/minecraft/controller/MinecraftController.java b/src/main/java/com/imyeyu/api/modules/minecraft/controller/MinecraftController.java deleted file mode 100644 index 780f8d0..0000000 --- a/src/main/java/com/imyeyu/api/modules/minecraft/controller/MinecraftController.java +++ /dev/null @@ -1,78 +0,0 @@ -package com.imyeyu.api.modules.minecraft.controller; - -import jakarta.validation.Valid; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; -import lombok.extern.slf4j.Slf4j; -import com.imyeyu.java.bean.timi.TimiCode; -import com.imyeyu.java.bean.timi.TimiException; -import com.imyeyu.api.annotation.EnableSetting; -import com.imyeyu.api.modules.blog.util.UserToken; -import com.imyeyu.api.modules.common.bean.SettingKey; -import com.imyeyu.api.modules.minecraft.service.FMCImageMapService; -import com.imyeyu.spring.annotation.AOPLog; -import com.imyeyu.spring.annotation.RequestRateLimit; -import com.imyeyu.spring.annotation.RequiredToken; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.multipart.MultipartFile; - -import java.util.List; - -/** - * Minecraft 相关接口 - * - * @author 夜雨 - * @since 2021-05-20 00:24 - */ -@Slf4j -@Validated -@RestController -@RequestMapping("/fmc") -public class MinecraftController { - - @Autowired - private FMCImageMapService fmcImageMapService; - - @Autowired - private UserToken userToken; - - /** - * 上传 FMCImageMap 插件图床图片 - * - * @param file 文件 - * @return 头像资源路径 - */ - @AOPLog - @RequiredToken - @EnableSetting(value = SettingKey.FMC_ENABLE_IMAGE_MAP_UPLOAD, message = "minecraft.fmc_image_map.off_service") - @RequestRateLimit - @PostMapping("/imagemap/upload") - public String uploadFMCImageMap(@NotNull @RequestParam("request") MultipartFile file) { - if (file.getOriginalFilename() != null && !file.getOriginalFilename().endsWith(".png")) { - throw new TimiException(TimiCode.ARG_BAD).msgKey("minecraft.fmc_image_map.png_only"); - } - return fmcImageMapService.upload(file); - } - - @AOPLog - @RequiredToken - @RequestRateLimit - @PostMapping("/imagemap/list") - public List listAllFMCImageMap() { - return fmcImageMapService.findAll(); - } - - @AOPLog - @RequiredToken - @RequestRateLimit - @PostMapping("/imagemap/delete") - public void deleteFMCImageMap(@NotBlank @Valid @RequestBody String fileName) { - fmcImageMapService.delete(fileName); - } -} diff --git a/src/main/java/com/imyeyu/api/modules/minecraft/controller/PackController.java b/src/main/java/com/imyeyu/api/modules/minecraft/controller/PackController.java deleted file mode 100644 index 6872ae2..0000000 --- a/src/main/java/com/imyeyu/api/modules/minecraft/controller/PackController.java +++ /dev/null @@ -1,47 +0,0 @@ -package com.imyeyu.api.modules.minecraft.controller; - -import com.imyeyu.java.bean.timi.TimiCode; -import com.imyeyu.java.bean.timi.TimiResponse; -import com.imyeyu.api.modules.minecraft.entity.MinecraftPack; -import com.imyeyu.api.modules.minecraft.entity.MinecraftPackSource; -import com.imyeyu.api.modules.minecraft.service.PackService; -import com.imyeyu.spring.annotation.RequestRateLimit; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -import java.util.List; - -/** - * 客户端接口,通常是 ForeverMC 启动器请求 - * - * @author 夜雨 - * @since 2023-06-13 17:15 - */ -@Slf4j -@RestController -@RequestMapping("/fmc/pack") -public class PackController { - - @Autowired - private PackService service; - - /** - * 可用客户端列表 - * - * @return 客户端列表 - */ - @RequestRateLimit - @RequestMapping("/list") - public TimiResponse listPacks() { - List list = service.listPacks(); - for (int i = 0; i < list.size(); i++) { - List sourceList = list.get(i).getSourceList(); - for (int j = 0; j < sourceList.size(); j++) { - sourceList.get(j).setOrder(null); - } - } - return new TimiResponse<>(TimiCode.SUCCESS, list); - } -} diff --git a/src/main/java/com/imyeyu/api/modules/minecraft/controller/PlayerController.java b/src/main/java/com/imyeyu/api/modules/minecraft/controller/PlayerController.java deleted file mode 100644 index 21b8387..0000000 --- a/src/main/java/com/imyeyu/api/modules/minecraft/controller/PlayerController.java +++ /dev/null @@ -1,126 +0,0 @@ -package com.imyeyu.api.modules.minecraft.controller; - -import jakarta.annotation.PostConstruct; -import jakarta.validation.Valid; -import lombok.RequiredArgsConstructor; -import com.imyeyu.java.TimiJava; -import com.imyeyu.java.bean.timi.TimiCode; -import com.imyeyu.java.bean.timi.TimiException; -import com.imyeyu.api.annotation.EnableSetting; -import com.imyeyu.api.config.RedisConfig; -import com.imyeyu.api.modules.blog.util.UserToken; -import com.imyeyu.api.modules.common.bean.SettingKey; -import com.imyeyu.api.modules.common.service.UserService; -import com.imyeyu.api.modules.minecraft.entity.MinecraftPlayer; -import com.imyeyu.api.modules.minecraft.service.PlayerService; -import com.imyeyu.api.modules.minecraft.vo.TokenRequest; -import com.imyeyu.api.modules.minecraft.vo.TokenResponse; -import com.imyeyu.spring.annotation.AOPLog; -import com.imyeyu.spring.annotation.RequestRateLimit; -import com.imyeyu.spring.annotation.RequiredToken; -import com.imyeyu.spring.util.Redis; -import com.imyeyu.spring.util.RedisSerializers; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestHeader; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -import java.util.List; - -/** - * 玩家控制(登录或账号管理) - * - * @author 夜雨 - * @since 2024-03-29 12:55 - */ -@RestController -@RequestMapping("/fmc/player") -@RequiredArgsConstructor -public class PlayerController { - - private final RedisConfig redisConfig; - - private final UserService userService; - private final PlayerService service; - - private final UserToken userToken; - - private Redis redis; - - @PostConstruct - private void postConstruct() { - redis = redisConfig.getRedis(redisConfig.getDatabase().getFmcPlayerToken(), RedisSerializers.STRING, RedisSerializers.LONG); - } - - /** - * 该玩家名是否已绑定 - * - * @param name 玩家名 - * @return true 为已绑定 - */ - @RequestRateLimit - @EnableSetting(value = SettingKey.FMC_PLAYER_LOGIN_ENABLE, message = "登录服务未启用") - @GetMapping("/bound/{name}") - public boolean isBound(@PathVariable("name") String name) { - return service.getByName(name) != null; - } - - @AOPLog - @RequiredToken - @RequestRateLimit - @PostMapping("/bind") - public void bind(@RequestBody String name) { - MinecraftPlayer player = new MinecraftPlayer(); - player.setName(name); - player.setUserId(userService.getLoginUser().getId()); - service.create(player); - } - - @AOPLog - @RequiredToken - @RequestRateLimit - @PostMapping("/unbind") - public void unbind(@RequestBody Long id) { - service.listByUserId(userService.getLoginUser().getId()) - .stream() - .filter(player -> player.getId().equals(id)) - .findFirst() - .ifPresent(player -> service.delete(player.getId())); - } - - @RequestRateLimit - @PostMapping("/list") - public List listPlayer(@RequestHeader("Token") String token) { - Long userId = TimiJava.defaultIfNull(redis.get(token), userToken.getUserId(token)); - if (userId == null) { - throw new TimiException(TimiCode.RESULT_BAD).msgKey("token.illegal"); - } - List result = service.listByUserId(userId); - for (int i = 0; i < result.size(); i++) { - MinecraftPlayer player = result.get(i); - player.setLastLoginIP(null); - player.setCreatedAt(null); - player.setUpdatedAt(null); - } - return result; - } - - @AOPLog - @RequestRateLimit - @EnableSetting(value = SettingKey.FMC_PLAYER_LOGIN_ENABLE, message = "登录服务未启用") - @PostMapping("/login") - public TokenResponse login(@RequestBody Long playerId, @RequestHeader("Token") String token) { - return service.login(playerId, token); - } - - @AOPLog - @RequestRateLimit - @EnableSetting(value = SettingKey.FMC_PLAYER_LOGIN_ENABLE, message = "登录服务未启用") - @PostMapping("/login/token") - public TokenResponse genLoginToken(@Valid @RequestBody TokenRequest request) { - return service.genLoginToken(request); - } -} diff --git a/src/main/java/com/imyeyu/api/modules/minecraft/entity/MinecraftPack.java b/src/main/java/com/imyeyu/api/modules/minecraft/entity/MinecraftPack.java deleted file mode 100644 index 5969e1c..0000000 --- a/src/main/java/com/imyeyu/api/modules/minecraft/entity/MinecraftPack.java +++ /dev/null @@ -1,45 +0,0 @@ -package com.imyeyu.api.modules.minecraft.entity; - -import com.imyeyu.spring.entity.Entity; -import lombok.Data; -import lombok.EqualsAndHashCode; - -import java.util.List; - -/** - * 整合包 - * - * @author 夜雨 - * @since 2023-06-13 17:17 - */ -@Data -@EqualsAndHashCode(callSuper = true) -public class MinecraftPack extends Entity { - - /** 名称 */ - private String name; - - /** 整合包版本 */ - private String ver; - - /** 标题 */ - private String title; - - /** 说明 */ - private String description; - - /** 游戏版本 */ - private String gameVer; - - /** 默认配置 */ - private String defOption; - - /** 文件大小 */ - private long size; - - /** true 为已过时 */ - private boolean isDeprecated; - - /** 下载源列表 */ - private List sourceList; -} diff --git a/src/main/java/com/imyeyu/api/modules/minecraft/entity/MinecraftPackSource.java b/src/main/java/com/imyeyu/api/modules/minecraft/entity/MinecraftPackSource.java deleted file mode 100644 index 0a8a962..0000000 --- a/src/main/java/com/imyeyu/api/modules/minecraft/entity/MinecraftPackSource.java +++ /dev/null @@ -1,47 +0,0 @@ -package com.imyeyu.api.modules.minecraft.entity; - -import lombok.Data; -import lombok.EqualsAndHashCode; -import com.imyeyu.spring.entity.Entity; - -/** - * 整合包下载源 - * - * @author 夜雨 - * @since 2023-06-13 17:32 - */ -@Data -@EqualsAndHashCode(callSuper = true) -public class MinecraftPackSource extends Entity { - - /** - * - * - * @author 夜雨 - * @since 2024-06-18 12:53 - */ - public enum Type { - - ATTACH, - - URL - } - - /** 整合包 ID */ - private Long packId; - - /** 名称 */ - private String name; - - /** 类型 */ - private Type type; - - /** 地址 */ - private String data; - - /** 排序 */ - private Integer order; - - /** true 为默认 */ - private boolean isDefault; -} diff --git a/src/main/java/com/imyeyu/api/modules/minecraft/entity/MinecraftPlayer.java b/src/main/java/com/imyeyu/api/modules/minecraft/entity/MinecraftPlayer.java deleted file mode 100644 index 3b57019..0000000 --- a/src/main/java/com/imyeyu/api/modules/minecraft/entity/MinecraftPlayer.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.imyeyu.api.modules.minecraft.entity; - -import com.imyeyu.spring.entity.Entity; -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.NoArgsConstructor; - -/** - * @author 夜雨 - * @since 2024-03-29 10:08 - */ -@Data -@NoArgsConstructor -@EqualsAndHashCode(callSuper = true) -public class MinecraftPlayer extends Entity { - - private Long userId; - - private String name; - - private String lastLoginIP; - - private Long lastLoginAt; - - public MinecraftPlayer(Long userId) { - this.userId = userId; - } -} diff --git a/src/main/java/com/imyeyu/api/modules/minecraft/mapper/PackMapper.java b/src/main/java/com/imyeyu/api/modules/minecraft/mapper/PackMapper.java deleted file mode 100644 index 9eb6d17..0000000 --- a/src/main/java/com/imyeyu/api/modules/minecraft/mapper/PackMapper.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.imyeyu.api.modules.minecraft.mapper; - -import com.imyeyu.api.modules.minecraft.entity.MinecraftPack; - -import java.util.List; - -/** - * @author 夜雨 - * @since 2023-06-13 17:34 - */ -public interface PackMapper { - - List listPacks(); -} diff --git a/src/main/java/com/imyeyu/api/modules/minecraft/mapper/PlayerMapper.java b/src/main/java/com/imyeyu/api/modules/minecraft/mapper/PlayerMapper.java deleted file mode 100644 index cafc031..0000000 --- a/src/main/java/com/imyeyu/api/modules/minecraft/mapper/PlayerMapper.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.imyeyu.api.modules.minecraft.mapper; - -import com.imyeyu.api.modules.minecraft.entity.MinecraftPlayer; -import com.imyeyu.spring.mapper.BaseMapper; -import org.apache.ibatis.annotations.Select; - -import java.util.List; - -/** - * @author 夜雨 - * @since 2024-03-29 10:08 - */ -public interface PlayerMapper extends BaseMapper { - - @Select("SELECT * FROM minecraft_player WHERE user_id = #{userId}" + NOT_DELETE) - List listByUserId(Long userId); - - @Select("SELECT * FROM minecraft_player WHERE name = #{name}" + NOT_DELETE + LIMIT_1) - MinecraftPlayer selectByName(String name); -} diff --git a/src/main/java/com/imyeyu/api/modules/minecraft/service/FMCImageMapService.java b/src/main/java/com/imyeyu/api/modules/minecraft/service/FMCImageMapService.java deleted file mode 100644 index 340b0b5..0000000 --- a/src/main/java/com/imyeyu/api/modules/minecraft/service/FMCImageMapService.java +++ /dev/null @@ -1,38 +0,0 @@ -package com.imyeyu.api.modules.minecraft.service; - -import com.imyeyu.java.bean.timi.TimiException; -import org.springframework.web.multipart.MultipartFile; - -import java.util.List; - -/** - * @author 夜雨 - * @since 2023-07-15 18:11 - */ -public interface FMCImageMapService { - - /** - * 上传 FMCImageMap 插件图床图片 - * - * @param file 上传文件对象 - * @return 回调该资源路径 - * @throws TimiException 服务异常 - */ - String upload(MultipartFile file); - - /** - * 获取该用户上传 FMCImageMap 插件图床的图片 - * - * @return 文件名 - * @throws TimiException 服务异常 - */ - List findAll(); - - /** - * 删除上传 IMCImgMap 插件图床的图片 - * - * @param fileName 图片文件名 - * @throws TimiException 服务异常 - */ - void delete(String fileName); -} diff --git a/src/main/java/com/imyeyu/api/modules/minecraft/service/PackService.java b/src/main/java/com/imyeyu/api/modules/minecraft/service/PackService.java deleted file mode 100644 index 049d7a7..0000000 --- a/src/main/java/com/imyeyu/api/modules/minecraft/service/PackService.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.imyeyu.api.modules.minecraft.service; - -import com.imyeyu.api.modules.minecraft.entity.MinecraftPack; - -import java.util.List; - -/** - * @author 夜雨 - * @since 2023-06-13 17:33 - */ -public interface PackService { - - List listPacks(); -} diff --git a/src/main/java/com/imyeyu/api/modules/minecraft/service/PlayerService.java b/src/main/java/com/imyeyu/api/modules/minecraft/service/PlayerService.java deleted file mode 100644 index 1c5e62c..0000000 --- a/src/main/java/com/imyeyu/api/modules/minecraft/service/PlayerService.java +++ /dev/null @@ -1,38 +0,0 @@ -package com.imyeyu.api.modules.minecraft.service; - -import com.imyeyu.java.bean.timi.TimiException; -import com.imyeyu.api.modules.minecraft.entity.MinecraftPlayer; -import com.imyeyu.api.modules.minecraft.vo.TokenRequest; -import com.imyeyu.api.modules.minecraft.vo.TokenResponse; -import com.imyeyu.spring.service.CreatableService; -import com.imyeyu.spring.service.DeletableService; - -import java.util.List; - -/** - * @author 夜雨 - * @version 2024-03-29 10:08 - */ -public interface PlayerService extends CreatableService, DeletableService { - - /** - * 根据绑定玩家名获取玩家 - * - * @param name 绑定玩家名 - * @return 玩家 - * @throws TimiException 服务异常 - */ - MinecraftPlayer getByName(String name) throws TimiException; - - /** - * 获取玩家 - * - * @return 玩家 - * @throws TimiException 服务异常 - */ - List listByUserId(Long userId) throws TimiException; - - TokenResponse login(Long playerId, String loginToken) throws TimiException; - - TokenResponse genLoginToken(TokenRequest request) throws TimiException; -} diff --git a/src/main/java/com/imyeyu/api/modules/minecraft/service/implement/FMCImageMapServiceImplement.java b/src/main/java/com/imyeyu/api/modules/minecraft/service/implement/FMCImageMapServiceImplement.java deleted file mode 100644 index 0045c9f..0000000 --- a/src/main/java/com/imyeyu/api/modules/minecraft/service/implement/FMCImageMapServiceImplement.java +++ /dev/null @@ -1,62 +0,0 @@ -package com.imyeyu.api.modules.minecraft.service.implement; - -import lombok.RequiredArgsConstructor; -import com.imyeyu.api.modules.common.service.UserService; -import com.imyeyu.api.modules.minecraft.service.FMCImageMapService; -import com.imyeyu.api.modules.system.service.FileService; -import com.imyeyu.utils.OS; -import org.jcodec.api.NotSupportedException; -import org.springframework.stereotype.Service; -import org.springframework.web.multipart.MultipartFile; - -import java.util.List; - -/** - * @author 夜雨 - * @version 2023-07-15 18:11 - */ -@Service -@RequiredArgsConstructor -public class FMCImageMapServiceImplement implements FMCImageMapService, OS.FileSystem { - - private final UserService userService; - private final FileService fileService; - - @Override - public String upload(MultipartFile file) { - throw new NotSupportedException("TODO update"); -// try { -// User user = userService.getLoginUser(); -// BufferedImage img = ImageIO.read(file.getInputStream()); -// // 限制尺寸为 128 -// if (img.getWidth() != 128 || img.getHeight() != 128) { -// throw new TimiException(TimiCode.ARG_BAD).msgKey("图片尺寸需为 128 × 128 像素"); -// } -// // 文件夹 -// fileService.mkdirs(config.getResources().getMcMap() + SEP + user.getId() + SEP); -// // 处理文件 -// ResourceFile res = new ResourceFile(); -// res.setName(System.currentTimeMillis() + "_" + Digest.md5(String.valueOf(Math.random())).substring(0, 8) + ".png"); -// res.setPath(config.getResources().getMcMap() + SEP + user.getId() + SEP); -// res.setInputStream(file.getInputStream()); -// fileService.upload(res); -// return res.getFullPath(); -// } catch (Exception e) { -// throw new TimiException(TimiCode.ERROR).msgKey("上传文件异常:" + e.getMessage()); -// } - } - - @Override - public List findAll() { - throw new NotSupportedException("TODO update"); -// User user = userService.getLoginUser(); -// return fileService.list4TimiServer(config.getResources().getMcMap() + SEP + user.getId() + SEP).stream().map(File::getName).collect(Collectors.toList()); - } - - @Override - public void delete(String fileName) { - throw new NotSupportedException("TODO update"); -// User user = userService.getLoginUser(); -// fileService.destroy(config.getResources().getMcMap() + SEP + user.getId() + SEP + fileName); - } -} diff --git a/src/main/java/com/imyeyu/api/modules/minecraft/service/implement/PackServiceImplement.java b/src/main/java/com/imyeyu/api/modules/minecraft/service/implement/PackServiceImplement.java deleted file mode 100644 index d36ebb5..0000000 --- a/src/main/java/com/imyeyu/api/modules/minecraft/service/implement/PackServiceImplement.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.imyeyu.api.modules.minecraft.service.implement; - -import com.imyeyu.api.modules.minecraft.entity.MinecraftPack; -import com.imyeyu.api.modules.minecraft.mapper.PackMapper; -import com.imyeyu.api.modules.minecraft.service.PackService; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; - -import java.util.List; - -/** - * @author 夜雨 - * @version 2023-06-13 17:34 - */ -@Service -public class PackServiceImplement implements PackService { - - @Autowired - private PackMapper mapper; - - @Override - public List listPacks() { - return mapper.listPacks(); - } -} diff --git a/src/main/java/com/imyeyu/api/modules/minecraft/service/implement/PlayerServiceImplement.java b/src/main/java/com/imyeyu/api/modules/minecraft/service/implement/PlayerServiceImplement.java deleted file mode 100644 index 8382e4c..0000000 --- a/src/main/java/com/imyeyu/api/modules/minecraft/service/implement/PlayerServiceImplement.java +++ /dev/null @@ -1,151 +0,0 @@ -package com.imyeyu.api.modules.minecraft.service.implement; - -import com.imyeyu.java.bean.timi.TimiCode; -import com.imyeyu.java.bean.timi.TimiException; -import com.imyeyu.api.config.RedisConfig; -import com.imyeyu.api.modules.common.bean.SettingKey; -import com.imyeyu.api.modules.common.entity.User; -import com.imyeyu.api.modules.common.service.SettingService; -import com.imyeyu.api.modules.common.service.UserService; -import com.imyeyu.api.modules.minecraft.entity.MinecraftPlayer; -import com.imyeyu.api.modules.minecraft.mapper.PlayerMapper; -import com.imyeyu.api.modules.minecraft.service.PlayerService; -import com.imyeyu.api.modules.minecraft.vo.TokenRequest; -import com.imyeyu.api.modules.minecraft.vo.TokenResponse; -import com.imyeyu.spring.TimiSpring; -import com.imyeyu.spring.mapper.BaseMapper; -import com.imyeyu.spring.service.AbstractEntityService; -import com.imyeyu.spring.util.Redis; -import com.imyeyu.spring.util.RedisSerializers; -import com.imyeyu.utils.Calc; -import com.imyeyu.utils.Time; -import jakarta.annotation.PostConstruct; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Service; - -import java.util.List; -import java.util.UUID; - -/** - * @author 夜雨 - * @version 2024-03-29 10:08 - */ -@Slf4j -@Service -@RequiredArgsConstructor -public class PlayerServiceImplement extends AbstractEntityService implements PlayerService { - - private final RedisConfig redisConfig; - - private final UserService userService; - private final SettingService settingService; - - private final PlayerMapper mapper; - - private Redis redis; - - @Override - protected BaseMapper mapper() { - return mapper; - } - - @PostConstruct - private void postConstruct() { - redis = redisConfig.getRedis(redisConfig.getDatabase().getFmcPlayerToken(), RedisSerializers.STRING, RedisSerializers.LONG); - } - - @Override - public void create(MinecraftPlayer player) { - if (player.getName().contains(" ")) { - throw new TimiException(TimiCode.ARG_BAD).msgKey("绑定玩家昵称不允许有空格"); - } - // 自查重 - List playerList = listByUserId(player.getUserId()); - if (settingService.getAsInt(SettingKey.FMC_MAX_BIND) <= playerList.size()) { - throw new TimiException(TimiCode.RESULT_BAD).msgKey("已达到最大绑定数量:%s 个".formatted(settingService.getAsInt(SettingKey.FMC_MAX_BIND))); - } - for (int i = 0; i < playerList.size(); i++) { - if (playerList.get(i).getName().equals(player.getName())) { - throw new TimiException(TimiCode.RESULT_BAD).msgKey("已绑定此玩家名称"); - } - } - // 全局查重 - MinecraftPlayer playerByName = getByName(player.getName()); - if (playerByName != null && !playerByName.getUserId().equals(player.getUserId())) { - throw new TimiException(TimiCode.DATA_EXIST).msgKey("此玩家昵称已被绑定,请使用其他昵称"); - } - MinecraftPlayer dbPlayer = new MinecraftPlayer(); - dbPlayer.setUserId(player.getUserId()); - dbPlayer.setName(player.getName()); - super.create(dbPlayer); - } - - @Override - public MinecraftPlayer getByName(String name) throws TimiException { - return mapper.selectByName(name); - } - - @Override - public List listByUserId(Long userId) throws TimiException { - return mapper.listByUserId(userId); - } - - @Override - public TokenResponse login(Long playerId, String loginToken) throws TimiException { - Long userId = redis.get(loginToken); - if (userId == null) { - throw new TimiException(TimiCode.RESULT_BAD).msgKey("token.illegal"); - } - List playerList = listByUserId(userId); - MinecraftPlayer player = null; - for (int i = 0; i < playerList.size(); i++) { - if (playerList.get(i).getId().equals(playerId)) { - player = playerList.get(i); - break; - } - } - if (player == null) { - throw new TimiException(TimiCode.RESULT_BAD).msgKey("token.illegal"); - } - player.setLastLoginIP(TimiSpring.getRequestIP()); - player.setLastLoginAt(Time.now()); - mapper.update(player); - - String playerToken = UUID.randomUUID().toString(); - long ttl = settingService.getAsInt(SettingKey.FMC_PLAYER_LOGIN_TOKEN_TTL) * Time.D; - redis.set(playerToken, playerId, ttl); - return new TokenResponse(userId, playerToken, Time.now() + ttl); - } - - @Override - public TokenResponse genLoginToken(TokenRequest request) throws TimiException { - String reqUser = request.getUser(); - - User user; - if (Calc.isNumber(reqUser)) { - user = userService.get(Long.valueOf(reqUser)); - } else if (reqUser.contains("@")) { - user = userService.getByEmail(reqUser); - } else { - MinecraftPlayer playerByName = getByName(reqUser); - if (playerByName == null) { - throw new TimiException(TimiCode.RESULT_NULL).msgKey("not found player"); - } - user = userService.get(playerByName.getUserId()); - } - if (user == null) { - throw new TimiException(TimiCode.RESULT_NULL).msgKey("未注册或绑定此玩家昵称,请到 https://%s 进行注册或绑定".formatted(settingService.getAsString(SettingKey.DOMAIN_SPACE))); - } - if (user.isBanning()) { - throw new TimiException(TimiCode.RESULT_BAN).msgKey("账号封禁中"); - } - if (userService.isInvalidPassword(user.getPassword(), request.getPassword())) { - throw new TimiException(TimiCode.ARG_BAD).msgKey("密码错误"); - } - String token = UUID.randomUUID().toString(); - long ttl = settingService.getAsInt(SettingKey.FMC_PLAYER_LOGIN_TOKEN_TTL) * Time.D; - redis.set(token, user.getId(), ttl); - return new TokenResponse(user.getId(), token, Time.now() + ttl); - } -} diff --git a/src/main/java/com/imyeyu/api/modules/minecraft/vo/LoginRequest.java b/src/main/java/com/imyeyu/api/modules/minecraft/vo/LoginRequest.java deleted file mode 100644 index 3d663b5..0000000 --- a/src/main/java/com/imyeyu/api/modules/minecraft/vo/LoginRequest.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.imyeyu.api.modules.minecraft.vo; - -import lombok.Data; - -/** - * @author 夜雨 - * @version 2024-03-29 14:14 - */ -@Data -public class LoginRequest { - - private Long playerId; - - private String token; -} diff --git a/src/main/java/com/imyeyu/api/modules/minecraft/vo/TokenRequest.java b/src/main/java/com/imyeyu/api/modules/minecraft/vo/TokenRequest.java deleted file mode 100644 index 4b0bd86..0000000 --- a/src/main/java/com/imyeyu/api/modules/minecraft/vo/TokenRequest.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.imyeyu.api.modules.minecraft.vo; - -import jakarta.validation.constraints.NotBlank; -import lombok.Data; - -/** - * @author 夜雨 - * @version 2024-03-29 14:14 - */ -@Data -public class TokenRequest { - - @NotBlank(message = "todo.msg") - private String user; - - @NotBlank(message = "todo.msg") - private String password; -} diff --git a/src/main/java/com/imyeyu/api/modules/minecraft/vo/TokenResponse.java b/src/main/java/com/imyeyu/api/modules/minecraft/vo/TokenResponse.java deleted file mode 100644 index cecb059..0000000 --- a/src/main/java/com/imyeyu/api/modules/minecraft/vo/TokenResponse.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.imyeyu.api.modules.minecraft.vo; - -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; - -/** - * 令牌返回 - * - * @author 夜雨 - * @since 2024-03-29 10:13 - */ -@Data -@NoArgsConstructor -@AllArgsConstructor -public class TokenResponse { - - /** 用户 ID */ - private Long userId; - - /** 令牌 */ - private String token; - - /** 过期于 */ - private long expiredAt; -} diff --git a/src/main/java/com/imyeyu/api/modules/minecraft/vo/server/ReportRequest.java b/src/main/java/com/imyeyu/api/modules/minecraft/vo/server/ReportRequest.java deleted file mode 100644 index 6b7bc27..0000000 --- a/src/main/java/com/imyeyu/api/modules/minecraft/vo/server/ReportRequest.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.imyeyu.api.modules.minecraft.vo.server; - -import lombok.Data; -import lombok.EqualsAndHashCode; -import com.imyeyu.api.modules.forevermc.bean.ServerStatus; - -/** - * @author 夜雨 - * @since 2024-08-06 16:36 - */ -@Data -@EqualsAndHashCode(callSuper = true) -public class ReportRequest extends ServerStatus { -} diff --git a/src/main/java/com/imyeyu/api/modules/mirror/AttachmentMirror.java b/src/main/java/com/imyeyu/api/modules/mirror/AttachmentMirror.java index f39e8d9..677bc13 100644 --- a/src/main/java/com/imyeyu/api/modules/mirror/AttachmentMirror.java +++ b/src/main/java/com/imyeyu/api/modules/mirror/AttachmentMirror.java @@ -65,14 +65,13 @@ public abstract class AttachmentMirror extends AbstractMirror { List diffRemoveList = diffRemove(dbFiles); syncRemoved = diffRemoveList.size(); - for (int i = 0; i < diffRemoveList.size(); i++) { - attachmentService.destroy(diffRemoveList.get(i).getId()); + for (Attachment attachment : diffRemoveList) { + attachmentService.destroy(attachment.getId()); } List diffAddList = diffAdd(dbFiles); syncAdded = diffAddList.size(); - for (int i = 0; i < diffAddList.size(); i++) { - Attachment request = diffAddList.get(i); + for (Attachment request : diffAddList) { request.setBizType(Attachment.BizType.MIRROR); request.setBizId(mirror.getId()); attachmentService.create(request); diff --git a/src/main/java/com/imyeyu/api/modules/mirror/FabricAPIMirror.java b/src/main/java/com/imyeyu/api/modules/mirror/FabricAPIMirror.java index 0267525..f560651 100644 --- a/src/main/java/com/imyeyu/api/modules/mirror/FabricAPIMirror.java +++ b/src/main/java/com/imyeyu/api/modules/mirror/FabricAPIMirror.java @@ -85,9 +85,8 @@ public class FabricAPIMirror extends AttachmentMirror { } List result = new ArrayList<>(); { - List attachmentList = attachmentService.listByBizId(Attachment.BizType.MIRROR, mirror.getId(), AttachType.FABRIC_API); - for (int i = 0; i < attachmentList.size(); i++) { - Attachment attachment = attachmentList.get(i); + List attachmentList = attachmentService.listByBizId(Attachment.BizType.MIRROR, mirror.getId(), AttachType.FABRIC_API.toString()); + for (Attachment attachment : attachmentList) { // 附件名:fabric-api-(fabricVer)+(minecraftVer).jar String[] args = attachment.getName().split("-")[2].replaceAll("\\.jar", "").split("\\+"); @@ -137,8 +136,7 @@ public class FabricAPIMirror extends AttachmentMirror { versionList.add("fabric-api-%s.jar".formatted(version)); } List result = new ArrayList<>(); - for (int i = 0; i < dbFiles.size(); i++) { - Attachment attachment = dbFiles.get(i); + for (Attachment attachment : dbFiles) { if (!versionList.contains(attachment.getName())) { log.info("Syncing a miss fabric-api for {}", attachment.getName()); result.add(attachment); diff --git a/src/main/java/com/imyeyu/api/modules/mirror/MirrorSyncTask.java b/src/main/java/com/imyeyu/api/modules/mirror/MirrorSyncTask.java index 120fad4..310c7f3 100644 --- a/src/main/java/com/imyeyu/api/modules/mirror/MirrorSyncTask.java +++ b/src/main/java/com/imyeyu/api/modules/mirror/MirrorSyncTask.java @@ -33,38 +33,38 @@ public class MirrorSyncTask implements SchedulingConfigurer, TimiJava { @Override public void configureTasks(ScheduledTaskRegistrar taskRegistrar) { taskRegistrar.addTriggerTask(() -> { - List mirrors = service.listAll(); - long now = Time.now(); - for (int i = 0; i < mirrors.size(); i++) { - Mirror dbMirror = mirrors.get(i); - if (!dbMirror.isEnable()) { - continue; - } - try { - Object bean = TimiServerAPI.applicationContext.getBean(Class.forName(dbMirror.getBean())); - if (bean instanceof AbstractMirror mirror) { - if (!mirror.isInitialized()) { - mirror.initialize(dbMirror); - } - if (now - mirror.lastSyncAt < dbMirror.getPeriod() * Time.M) { - continue; - } - switch (mirror.status) { - case IDLE -> { - log.info("[{}] Starting..", dbMirror.getBean()); - threadPoolTaskExecutor.execute(() -> mirror.sync0(dbMirror)); - } - case SYNCING -> log.warn("[%s] is syncing, skipped in this period. This task maybe need more time to finish work"); - case FAIL -> { - log.info("[{}] Retrying..", dbMirror.getBean()); - threadPoolTaskExecutor.execute(() -> mirror.sync0(dbMirror)); - } - } - } - } catch (ClassNotFoundException e) { - log.error("[%s] Initialization fail".formatted(dbMirror.getBean()), e); - } - } +// List mirrors = service.listAll(); +// long now = Time.now(); +// for (int i = 0; i < mirrors.size(); i++) { +// Mirror dbMirror = mirrors.get(i); +// if (!dbMirror.isEnable()) { +// continue; +// } +// try { +// Object bean = TimiServerAPI.applicationContext.getBean(Class.forName(dbMirror.getBean())); +// if (bean instanceof AbstractMirror mirror) { +// if (!mirror.isInitialized()) { +// mirror.initialize(dbMirror); +// } +// if (now - mirror.lastSyncAt < dbMirror.getPeriod() * Time.M) { +// continue; +// } +// switch (mirror.status) { +// case IDLE -> { +// log.info("[{}] Starting..", dbMirror.getBean()); +// threadPoolTaskExecutor.execute(() -> mirror.sync0(dbMirror)); +// } +// case SYNCING -> log.warn("[%s] is syncing, skipped in this period. This task maybe need more time to finish work"); +// case FAIL -> { +// log.info("[{}] Retrying..", dbMirror.getBean()); +// threadPoolTaskExecutor.execute(() -> mirror.sync0(dbMirror)); +// } +// } +// } +// } catch (ClassNotFoundException e) { +// log.error("[%s] Initialization fail".formatted(dbMirror.getBean()), e); +// } +// } }, tc -> new CronTrigger("0 * * * * ?").nextExecution(tc)); } } diff --git a/src/main/java/com/imyeyu/api/modules/mirror/OpenJDKMirror.java b/src/main/java/com/imyeyu/api/modules/mirror/OpenJDKMirror.java index 28d04ed..5f97da9 100644 --- a/src/main/java/com/imyeyu/api/modules/mirror/OpenJDKMirror.java +++ b/src/main/java/com/imyeyu/api/modules/mirror/OpenJDKMirror.java @@ -56,9 +56,8 @@ public class OpenJDKMirror extends AttachmentMirror { Map githubNameMap = githubMirrorResult.stream().collect(Collectors.toMap(OpenJDK::getName, item -> item)); List result = new ArrayList<>(); { - List attachmentList = attachmentService.listByBizId(Attachment.BizType.MIRROR, mirror.getId(), AttachType.OPEN_JDK); - for (int i = 0; i < attachmentList.size(); i++) { - Attachment attachment = attachmentList.get(i); + List attachmentList = attachmentService.listByBizId(Attachment.BizType.MIRROR, mirror.getId(), AttachType.OPEN_JDK.toString()); + for (Attachment attachment : attachmentList) { OpenJDK jdk = githubNameMap.get(attachment.getName()); jdk.setData(attachment.getMongoId()); result.add(jdk); @@ -97,8 +96,7 @@ public class OpenJDKMirror extends AttachmentMirror { Set githubNameSet = githubMirrorResult.stream().map(OpenJDK::getName).collect(Collectors.toSet()); List result = new ArrayList<>(); - for (int i = 0; i < dbFiles.size(); i++) { - Attachment attachment = dbFiles.get(i); + for (Attachment attachment : dbFiles) { if (!githubNameSet.contains(attachment.getName())) { log.info("Syncing a miss open-jdk for {}", attachment.getName()); result.add(attachment); diff --git a/src/main/java/com/imyeyu/api/modules/mirror/controller/MirrorController.java b/src/main/java/com/imyeyu/api/modules/mirror/controller/MirrorController.java index cf9f098..5876b9f 100644 --- a/src/main/java/com/imyeyu/api/modules/mirror/controller/MirrorController.java +++ b/src/main/java/com/imyeyu/api/modules/mirror/controller/MirrorController.java @@ -39,7 +39,7 @@ public class MirrorController { @RequestRateLimit @GetMapping("/{mirrorName}") public JsonNode get(@PathVariable String mirrorName) { - return service.getByName(mirrorName).getData(); + return null; } /** @@ -51,14 +51,14 @@ public class MirrorController { @GetMapping("/list") public List list() { List result = new ArrayList<>(); - List mirrors = service.listAll(); - for (int i = 0; i < mirrors.size(); i++) { - MirrorView target = new MirrorView(); - BeanUtils.copyProperties(mirrors.get(i), target); - target.setBean(null); - target.setData(null); - result.add(target); - } +// List mirrors = service.listAll(); +// for (int i = 0; i < mirrors.size(); i++) { +// MirrorView target = new MirrorView(); +// BeanUtils.copyProperties(mirrors.get(i), target); +// target.setBean(null); +// target.setData(null); +// result.add(target); +// } return result; } } diff --git a/src/main/java/com/imyeyu/api/modules/mirror/entity/Mirror.java b/src/main/java/com/imyeyu/api/modules/mirror/entity/Mirror.java index 7409e27..d9910b8 100644 --- a/src/main/java/com/imyeyu/api/modules/mirror/entity/Mirror.java +++ b/src/main/java/com/imyeyu/api/modules/mirror/entity/Mirror.java @@ -1,7 +1,7 @@ package com.imyeyu.api.modules.mirror.entity; import com.fasterxml.jackson.databind.JsonNode; -import com.imyeyu.spring.entity.Entity; +import com.imyeyu.spring.entity.UUIDEntity; import lombok.Data; import lombok.EqualsAndHashCode; @@ -13,7 +13,7 @@ import lombok.EqualsAndHashCode; */ @Data @EqualsAndHashCode(callSuper = true) -public class Mirror extends Entity { +public class Mirror extends UUIDEntity { /** 执行 JavaBean */ protected String bean; diff --git a/src/main/java/com/imyeyu/api/modules/mirror/mapper/MirrorMapper.java b/src/main/java/com/imyeyu/api/modules/mirror/mapper/MirrorMapper.java index fd83fd1..b004a3e 100644 --- a/src/main/java/com/imyeyu/api/modules/mirror/mapper/MirrorMapper.java +++ b/src/main/java/com/imyeyu/api/modules/mirror/mapper/MirrorMapper.java @@ -13,17 +13,5 @@ import java.util.List; * @author 夜雨 * @version 2024-05-23 15:21 */ -public interface MirrorMapper extends BaseMapper { - - @Select("SELECT * FROM mirror WHERE id = #{id} LIMIT 1") - Mirror select(Long id); - - @Update("UPDATE mirror SET data = #{data}, last_sync_at = #{lastSyncAt} WHERE id = #{id} LIMIT 1") - void update(Mirror mirror); - - @Select("SELECT * FROM mirror WHERE name = #{name} LIMIT 1") - Mirror queryByName(String name); - - @Select("SELECT * FROM mirror WHERE deleted_at IS NULL") - List listAll(); +public interface MirrorMapper extends BaseMapper { } diff --git a/src/main/java/com/imyeyu/api/modules/mirror/service/MirrorService.java b/src/main/java/com/imyeyu/api/modules/mirror/service/MirrorService.java index 9e9cc53..93d7752 100644 --- a/src/main/java/com/imyeyu/api/modules/mirror/service/MirrorService.java +++ b/src/main/java/com/imyeyu/api/modules/mirror/service/MirrorService.java @@ -1,34 +1,14 @@ package com.imyeyu.api.modules.mirror.service; -import com.imyeyu.java.bean.timi.TimiException; import com.imyeyu.api.modules.mirror.entity.Mirror; import com.imyeyu.spring.service.GettableService; import com.imyeyu.spring.service.UpdatableService; -import java.util.List; - /** * 镜像服务 * * @author 夜雨 * @version 2024-05-23 15:21 */ -public interface MirrorService extends GettableService, UpdatableService { - - /** - * 根据名称获取 - * - * @param name 名称 - * @return 镜像 - * @throws TimiException 服务异常 - */ - Mirror getByName(String name) throws TimiException; - - /** - * 获取所有镜像 - * - * @return 镜像列表 - * @throws TimiException 服务异常 - */ - List listAll() throws TimiException; +public interface MirrorService extends GettableService, UpdatableService { } diff --git a/src/main/java/com/imyeyu/api/modules/mirror/service/implement/MirrorServiceImplement.java b/src/main/java/com/imyeyu/api/modules/mirror/service/implement/MirrorServiceImplement.java index 5edcd1f..e345dc4 100644 --- a/src/main/java/com/imyeyu/api/modules/mirror/service/implement/MirrorServiceImplement.java +++ b/src/main/java/com/imyeyu/api/modules/mirror/service/implement/MirrorServiceImplement.java @@ -1,9 +1,9 @@ package com.imyeyu.api.modules.mirror.service.implement; -import com.imyeyu.java.bean.timi.TimiException; import com.imyeyu.api.modules.mirror.entity.Mirror; import com.imyeyu.api.modules.mirror.mapper.MirrorMapper; import com.imyeyu.api.modules.mirror.service.MirrorService; +import com.imyeyu.java.bean.timi.TimiException; import com.imyeyu.spring.mapper.BaseMapper; import com.imyeyu.spring.service.AbstractEntityService; import lombok.RequiredArgsConstructor; @@ -19,22 +19,12 @@ import java.util.List; */ @Service @RequiredArgsConstructor -public class MirrorServiceImplement extends AbstractEntityService implements MirrorService { +public class MirrorServiceImplement extends AbstractEntityService implements MirrorService { private final MirrorMapper mapper; @Override - protected BaseMapper mapper() { + protected BaseMapper mapper() { return mapper; } - - @Override - public Mirror getByName(String name) throws TimiException { - return mapper.queryByName(name); - } - - @Override - public List listAll() throws TimiException { - return mapper.listAll(); - } } diff --git a/src/main/java/com/imyeyu/api/modules/music/runner/ControllerBootstrapRunner.java b/src/main/java/com/imyeyu/api/modules/music/runner/ControllerBootstrapRunner.java index 70568a0..9534539 100644 --- a/src/main/java/com/imyeyu/api/modules/music/runner/ControllerBootstrapRunner.java +++ b/src/main/java/com/imyeyu/api/modules/music/runner/ControllerBootstrapRunner.java @@ -1,5 +1,9 @@ package com.imyeyu.api.modules.music.runner; +import com.imyeyu.api.config.CORSConfig; +import com.imyeyu.api.modules.common.entity.Setting; +import com.imyeyu.api.modules.common.service.SettingService; +import com.imyeyu.api.modules.music.handler.ControllerMessageHandler; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.Channel; import io.netty.channel.ChannelFutureListener; @@ -24,10 +28,6 @@ import io.netty.handler.stream.ChunkedWriteHandler; import lombok.NonNull; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import com.imyeyu.api.config.CORSConfig; -import com.imyeyu.api.modules.common.bean.SettingKey; -import com.imyeyu.api.modules.common.service.SettingService; -import com.imyeyu.api.modules.music.handler.ControllerMessageHandler; import org.springframework.beans.BeansException; import org.springframework.boot.ApplicationArguments; import org.springframework.boot.ApplicationRunner; @@ -35,7 +35,6 @@ import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.context.ApplicationListener; import org.springframework.context.event.ContextClosedEvent; -import org.springframework.stereotype.Component; import java.net.InetSocketAddress; @@ -46,7 +45,7 @@ import java.net.InetSocketAddress; * @version 2021-11-03 15:29 */ @Slf4j -@Component +//@Component @RequiredArgsConstructor public class ControllerBootstrapRunner implements ApplicationRunner, ApplicationListener, ApplicationContextAware { @@ -63,6 +62,10 @@ public class ControllerBootstrapRunner implements ApplicationRunner, Application } public void run(ApplicationArguments args) { + if (settingService.getSystem(Setting.Module.Music.ENABLE).isTrue()) { + return; + } + final DefaultFullHttpResponse NFP = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.NOT_FOUND); final DefaultFullHttpResponse BRP = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.BAD_REQUEST); @@ -72,7 +75,7 @@ public class ControllerBootstrapRunner implements ApplicationRunner, Application ServerBootstrap bootstrap = new ServerBootstrap(); bootstrap.group(boss, worker); bootstrap.channel(NioServerSocketChannel.class); - bootstrap.localAddress(new InetSocketAddress(settingService.getAsInt(SettingKey.MUSIC_CONTROLLER_PORT))); + bootstrap.localAddress(new InetSocketAddress(settingService.getSystem(Setting.Module.Music.CONTROLLER_PORT).asInteger())); bootstrap.childHandler(new ChannelInitializer() { @Override @@ -86,7 +89,7 @@ public class ControllerBootstrapRunner implements ApplicationRunner, Application @Override public void channelRead(ChannelHandlerContext ctx, Object obj) throws Exception { if (obj instanceof FullHttpRequest req) { - if (!req.uri().equals(settingService.getAsString(SettingKey.MUSIC_CONTROLLER_URI))) { + if (!req.uri().equals(settingService.getSystem(Setting.Module.Music.CONTROLLER_URI).asString())) { // 访问的路径不是 Web Socket 的端点地址,响应 404 ctx.channel().writeAndFlush(NFP).addListener(ChannelFutureListener.CLOSE); return; @@ -94,8 +97,8 @@ public class ControllerBootstrapRunner implements ApplicationRunner, Application // 域名限制 String reqOrigin = req.headers().get(HttpHeaderNames.ORIGIN); String[] allowOrigin = corsConfig.getAllowOrigin(); - for (int i = 0; i < allowOrigin.length; i++) { - if (allowOrigin[i].equals(reqOrigin)) { + for (String s : allowOrigin) { + if (s.equals(reqOrigin)) { super.channelRead(ctx, obj); return; } @@ -108,10 +111,10 @@ public class ControllerBootstrapRunner implements ApplicationRunner, Application }); pipeline.addLast(new WebSocketServerCompressionHandler()); pipeline.addLast(new WebSocketServerProtocolHandler( - settingService.getAsString(SettingKey.MUSIC_CONTROLLER_URI), + settingService.getSystem(Setting.Module.Music.CONTROLLER_URI).asString(), null, true, - settingService.getAsInt(SettingKey.MUSIC_MAX_FRAME_LENGTH) + settingService.getSystem(Setting.Module.Music.MAX_FRAME_LENGTH).asLong() ) ); @@ -120,7 +123,7 @@ public class ControllerBootstrapRunner implements ApplicationRunner, Application } }); channel = bootstrap.bind().sync().channel(); - log.info("TimiMusicRC controller service startup with " + settingService.getAsString(SettingKey.MUSIC_CONTROLLER_IP)); + log.info("TimiMusicRC controller service startup with {}", settingService.getSystem(Setting.Module.Music.CONTROLLER_IP).asString()); } catch (Exception e) { log.error("TimiMusicRC controller service startup error", e); } diff --git a/src/main/java/com/imyeyu/api/modules/music/runner/PlayerBootstrapRunner.java b/src/main/java/com/imyeyu/api/modules/music/runner/PlayerBootstrapRunner.java index 050b3c4..2f356ff 100644 --- a/src/main/java/com/imyeyu/api/modules/music/runner/PlayerBootstrapRunner.java +++ b/src/main/java/com/imyeyu/api/modules/music/runner/PlayerBootstrapRunner.java @@ -1,5 +1,8 @@ package com.imyeyu.api.modules.music.runner; +import com.imyeyu.api.modules.common.entity.Setting; +import com.imyeyu.api.modules.common.service.SettingService; +import com.imyeyu.api.modules.music.handler.PlayerMessageHandler; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.Channel; import io.netty.channel.ChannelInitializer; @@ -12,9 +15,6 @@ import io.netty.handler.codec.string.StringDecoder; import lombok.NonNull; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import com.imyeyu.api.modules.common.bean.SettingKey; -import com.imyeyu.api.modules.common.service.SettingService; -import com.imyeyu.api.modules.music.handler.PlayerMessageHandler; import org.springframework.beans.BeansException; import org.springframework.boot.ApplicationArguments; import org.springframework.boot.ApplicationRunner; @@ -22,7 +22,6 @@ import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.context.ApplicationListener; import org.springframework.context.event.ContextClosedEvent; -import org.springframework.stereotype.Component; import java.net.InetSocketAddress; import java.nio.charset.StandardCharsets; @@ -34,7 +33,7 @@ import java.nio.charset.StandardCharsets; * @version 2021-11-03 19:18 */ @Slf4j -@Component +//@Component @RequiredArgsConstructor public class PlayerBootstrapRunner implements ApplicationRunner, ApplicationListener, ApplicationContextAware { @@ -50,19 +49,22 @@ public class PlayerBootstrapRunner implements ApplicationRunner, ApplicationList } public void run(ApplicationArguments args) { + if (settingService.getSystem(Setting.Module.Music.ENABLE).isTrue()) { + return; + } ServerBootstrap bootstrap = new ServerBootstrap(); boss = new NioEventLoopGroup(); worker = new NioEventLoopGroup(); try { bootstrap.group(boss, worker); bootstrap.channel(NioServerSocketChannel.class); - bootstrap.localAddress(new InetSocketAddress(settingService.getAsInt(SettingKey.MUSIC_PLAYER_PORT))); + bootstrap.localAddress(new InetSocketAddress(settingService.getSystem(Setting.Module.Music.PLAYER_PORT).asInteger())); bootstrap.childHandler(new ChannelInitializer<>() { @Override protected void initChannel(Channel ch) { ch.pipeline().addLast(new LengthFieldBasedFrameDecoder( - settingService.getAsInt(SettingKey.MUSIC_MAX_FRAME_LENGTH), + settingService.getSystem(Setting.Module.Music.MAX_FRAME_LENGTH).asInteger(), 0, 4, 0, @@ -77,7 +79,7 @@ public class PlayerBootstrapRunner implements ApplicationRunner, ApplicationList channel = bootstrap.bind().sync().channel(); - log.info("TimiMusicRC player service startup with " + settingService.getAsString(SettingKey.MUSIC_PLAYER_IP)); + log.info("TimiMusicRC player service startup with {}", settingService.getSystem(Setting.Module.Music.PLAYER_IP).asString()); } catch (Exception e) { log.error("TimiMusicRC player service startup error", e); } diff --git a/src/main/java/com/imyeyu/api/modules/system/bean/AccessKey.java b/src/main/java/com/imyeyu/api/modules/system/bean/AccessKey.java new file mode 100644 index 0000000..4123bd7 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/system/bean/AccessKey.java @@ -0,0 +1,19 @@ +package com.imyeyu.api.modules.system.bean; + +import lombok.Data; + +/** + * 访问令牌 + * + * @author 夜雨 + * @since 2026-04-28 14:35 + */ +@Data +public class AccessKey { + + /** 值 */ + private String value; + + /** true 为高权限密钥 */ + private Boolean highAuthority; +} diff --git a/src/main/java/com/imyeyu/api/modules/system/bean/ServerFile.java b/src/main/java/com/imyeyu/api/modules/system/bean/ServerFile.java index 0c32222..baf4295 100644 --- a/src/main/java/com/imyeyu/api/modules/system/bean/ServerFile.java +++ b/src/main/java/com/imyeyu/api/modules/system/bean/ServerFile.java @@ -1,24 +1,14 @@ package com.imyeyu.api.modules.system.bean; -import com.imyeyu.java.TimiJava; -import com.imyeyu.java.bean.timi.TimiCode; -import com.imyeyu.java.bean.timi.TimiException; import com.imyeyu.api.TimiServerAPI; -import com.imyeyu.api.modules.common.bean.SettingKey; import com.imyeyu.api.modules.common.service.SettingService; -import lombok.AllArgsConstructor; import lombok.Data; -import lombok.Getter; import lombok.extern.slf4j.Slf4j; +import org.apache.tika.Tika; -import javax.imageio.ImageIO; -import javax.imageio.ImageReader; -import javax.imageio.stream.FileImageInputStream; -import javax.imageio.stream.ImageInputStream; import java.io.File; import java.io.IOException; import java.io.Serializable; -import java.util.Iterator; /** * 文件属性 @@ -30,120 +20,28 @@ import java.util.Iterator; @Slf4j public class ServerFile implements Serializable { - /** - * 文件类型 - * - * @author 夜雨 - * @version 2022-07-05 14:45 - */ - @Getter - public enum FileType { - - DIRECTORY, - FILE, - TXT, - CODE, - SCRIPT, - ZIP, - MICROSOFT, - JAVA, - VIDEO, - FONT, - AUDIO, - IMAGE, - SYSTEM; - - String[] extensions; - - /** - * 字符串格式查找文件类型 - * - * @param extension 字符串格式 - * @return 文件类型 - */ - public static FileType fromExtension(String extension) { - FileType[] types = values(); - for (int i = 0; i < types.length; i++) { - final String[] extensions = types[i].getExtensions(); - for (int j = 0; j < extensions.length; j++) { - if (extensions[j].equalsIgnoreCase(extension)) { - return types[i]; - } - } - } - return FILE; - } - - public void setExtensions(String[] extensions) { - if (TimiJava.isNotEmpty(this.extensions)) { - throw new TimiException(TimiCode.ERROR, "readonly"); - } - this.extensions = extensions; - } - } - - /** - * 支持预览的扩展名 - * - * @author 夜雨 - * @version 2022-07-05 16:42 - */ - @Getter - @AllArgsConstructor - public enum SupportPreviewExtension { - - PNG("png"), - BMP("bmp"), - JPG("jpg"), - JPEG("jpeg"), - MP4("mp4"), - MOV("mov"); - - /** 名称 */ - final String name; - - /** - * 字符串格式查找文件类型 - * - * @param extension 字符串格式 - * @return 文件类型 - */ - public static SupportPreviewExtension fromExtension(String extension) { - SupportPreviewExtension[] extensions = values(); - for (int i = 0; i < extensions.length; i++) { - if (extensions[i].name.equalsIgnoreCase(extension)) { - return extensions[i]; - } - } - return null; - } - } - // ---------- 基本属性 ---------- /** 文件名 */ private String name; - /** 扩展名 */ - private String extension; - /** 绝对路径(非文件系统绝对路径,仅基于配置路径) */ private String absolutePath; + /** MimeType */ + private String mimeType; + /** 大小 */ private Long size; /** 修改时间 */ private Long modifiedAt; - /** 类型 */ - private FileType type; - /** true 为文件 */ - private boolean isFile; + private Boolean file; /** true 为文件夹 */ - private boolean isDirectory; + private Boolean directory; // ---------- 权限 ---------- @@ -154,81 +52,37 @@ public class ServerFile implements Serializable { private boolean canWrite; /** true 为隐藏 */ - private boolean isHidden; - - // ---------- 预览 ---------- + private Boolean isHidden; /** true 为可预览 */ private boolean canPreview; - /** 最大预览宽度 */ - private int previewWidth; - - /** 最大预览高度 */ - private int previewHeight; - - /** 预览地址 */ - private String previewURI; - public ServerFile(File file) { name = file.getName(); absolutePath = file.getAbsolutePath().replaceAll("\\\\", "/"); SettingService settingService = TimiServerAPI.applicationContext.getBean(SettingService.class); - absolutePath = absolutePath.substring(settingService.getAsString(SettingKey.SYSTEM_FILE_BASE).length()); + absolutePath = absolutePath.substring("E:\\IDEAProject\\TimiServerAPI\\data\\file".length()); modifiedAt = file.lastModified(); - isFile = file.isFile(); - isDirectory = file.isDirectory(); + this.file = file.isFile(); + directory = file.isDirectory(); canRead = file.canRead(); canWrite = file.canWrite(); isHidden = file.isHidden(); - previewWidth = previewHeight = -1; // 大小 - if (isFile) { + if (this.file) { size = file.length(); - } - - // 类型 - type = isFile ? FileType.FILE : FileType.DIRECTORY; - if (isFile) { - if (name.lastIndexOf('.') != -1) { - extension = name.substring(name.lastIndexOf('.') + 1); - type = FileType.fromExtension(extension); - } - } - - // 预览 - SupportPreviewExtension previewExtension = SupportPreviewExtension.fromExtension(extension); - if (previewExtension != null) { try { - // 解析预览最大尺寸 - switch (previewExtension) { - case PNG, BMP, JPG, JPEG -> { - // 图片 - Iterator readerIterator = ImageIO.getImageReadersBySuffix(extension); - if (readerIterator.hasNext()) { - ImageReader reader = readerIterator.next(); - try { - ImageInputStream stream = new FileImageInputStream(file); - reader.setInput(stream); - previewWidth = reader.getWidth(reader.getMinIndex()); - previewHeight = reader.getHeight(reader.getMinIndex()); - } catch (IOException e) { - log.info(e.getMessage(), e); - } finally { - reader.dispose(); - } - } else { - log.error("No reader found for given format: {}", extension); - } - } - } - canPreview = true; - previewURI = "/system/file/preview" + absolutePath; - } catch (Exception e) { - log.error("parse file preview image fail", e); + mimeType = new Tika().detect(file); + + canPreview = switch (mimeType = new Tika().detect(file)) { + case "image/png", "image/jpeg", "video/mp4", "video/quicktime" -> true; + default -> false; + }; + } catch (IOException e) { + throw new RuntimeException(e); } } } diff --git a/src/main/java/com/imyeyu/api/modules/system/controller/AsyncTaskController.java b/src/main/java/com/imyeyu/api/modules/system/controller/AsyncTaskController.java index e6ad584..d3c18e8 100644 --- a/src/main/java/com/imyeyu/api/modules/system/controller/AsyncTaskController.java +++ b/src/main/java/com/imyeyu/api/modules/system/controller/AsyncTaskController.java @@ -1,12 +1,12 @@ package com.imyeyu.api.modules.system.controller; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; import com.imyeyu.api.modules.system.service.AsyncTaskService; import com.imyeyu.api.modules.system.service.implement.AbstractAsyncTask; import com.imyeyu.api.modules.system.task.async.DebugAsyncTask; import com.imyeyu.api.modules.system.vo.AsyncTaskView; import com.imyeyu.spring.annotation.AOPLog; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; @@ -37,7 +37,7 @@ public class AsyncTaskController { } DebugAsyncTask task = new DebugAsyncTask(second); service.addAsyncTask(task); - return task.getUuid(); + return task.getId(); } @GetMapping("/detail") diff --git a/src/main/java/com/imyeyu/api/modules/system/controller/FileController.java b/src/main/java/com/imyeyu/api/modules/system/controller/FileController.java index 055870b..ba9cf58 100644 --- a/src/main/java/com/imyeyu/api/modules/system/controller/FileController.java +++ b/src/main/java/com/imyeyu/api/modules/system/controller/FileController.java @@ -1,20 +1,12 @@ package com.imyeyu.api.modules.system.controller; -import com.imyeyu.io.IO; -import com.imyeyu.java.TimiJava; -import com.imyeyu.java.bean.timi.TimiCode; -import com.imyeyu.java.bean.timi.TimiException; -import com.imyeyu.network.Network; -import com.imyeyu.api.modules.common.bean.SettingKey; -import com.imyeyu.api.modules.common.entity.Tag; +import com.imyeyu.api.modules.common.entity.Setting; import com.imyeyu.api.modules.common.service.SettingService; import com.imyeyu.api.modules.common.service.TagService; -import com.imyeyu.api.modules.common.vo.tag.TagRequest; import com.imyeyu.api.modules.system.bean.ServerFile; import com.imyeyu.api.modules.system.bean.TransferFile; import com.imyeyu.api.modules.system.service.AsyncTaskService; import com.imyeyu.api.modules.system.service.FileService; -import com.imyeyu.api.modules.system.task.async.FileCalcSizeAsyncTask; import com.imyeyu.api.modules.system.task.async.FileCopyAsyncTask; import com.imyeyu.api.modules.system.task.async.FileMoveAsyncTask; import com.imyeyu.api.modules.system.task.async.FileTarAsyncTask; @@ -23,6 +15,11 @@ import com.imyeyu.api.modules.system.task.async.FileUnZipAsyncTask; import com.imyeyu.api.modules.system.task.async.FileZipAsyncTask; import com.imyeyu.api.modules.system.util.ResourceHandler; import com.imyeyu.api.modules.system.vo.ListFileToRequest; +import com.imyeyu.io.IO; +import com.imyeyu.java.TimiJava; +import com.imyeyu.java.bean.timi.TimiCode; +import com.imyeyu.java.bean.timi.TimiException; +import com.imyeyu.network.Network; import com.imyeyu.spring.TimiSpring; import com.imyeyu.spring.annotation.AOPLog; import com.imyeyu.spring.annotation.IgnoreGlobalReturn; @@ -43,7 +40,6 @@ import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import javax.imageio.ImageIO; @@ -51,6 +47,7 @@ import java.awt.image.BufferedImage; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.RandomAccessFile; +import java.net.URI; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; @@ -97,24 +94,6 @@ public class FileController implements TimiJava, OS.FileSystem { return file; } - @AOPLog - @RequestRateLimit - @PostMapping("/tag") - public void tag(@RequestBody TagRequest request) { - Tag tag = new Tag(); - tag.setBizType(Tag.BizType.SERVER_FILE); - tag.setBizID(request.getBizId()); - tag.setValue(request.getZhCN()); - tagService.create(tag); - } - - @AOPLog - @RequestRateLimit - @GetMapping("/tag/list") - public List listTag(@RequestParam String bizId) { - return tagService.listByBizID(Tag.BizType.SERVER_FILE, bizId); - } - /** * 深度搜索 * @@ -152,29 +131,6 @@ public class FileController implements TimiJava, OS.FileSystem { return service.canAccess(path) && service.isExist(path); } - /** - * 文件夹大小 - * - * @param req 请求 - * @param resp 返回 - * @return 异步任务 UUID - */ - @AOPLog - @RequestRateLimit - @GetMapping("/size/**") - public String size(HttpServletRequest req, HttpServletResponse resp) { - String path = req.getServletPath().substring("/system/file/size".length()); - if (TimiJava.isEmpty(path)) { - throw new TimiException(TimiCode.ARG_MISS).msgKey("缺少参数:path"); - } - File file = new File(path); - service.checkAccessPermission(file.getAbsolutePath()); - // 异步任务 - FileCalcSizeAsyncTask task = new FileCalcSizeAsyncTask(file); - asyncTaskService.addAsyncTask(task); - return task.getUuid(); - } - /** * 通用资源加载接口 * @@ -187,7 +143,7 @@ public class FileController implements TimiJava, OS.FileSystem { public void read(HttpServletRequest req, HttpServletResponse resp) { try { String path = req.getServletPath().substring("/system/file/read".length()); - path = settingService.getAsString(SettingKey.SYSTEM_FILE_BASE) + path; + path = "E:\\IDEAProject\\TimiServerAPI\\data\\file" + path; if (TimiJava.isEmpty(path)) { resp.setStatus(HttpServletResponse.SC_NOT_FOUND); return; @@ -239,7 +195,7 @@ public class FileController implements TimiJava, OS.FileSystem { resp.setStatus(HttpServletResponse.SC_NOT_FOUND); return; } - path = settingService.getAsString(SettingKey.SYSTEM_FILE_BASE) + path; +// path = settingService.getAsString(Setting.Module.System.FILE_BASE_PATH) + path; File file = new File(path); if (!file.exists()) { log.warn("preview a not exist file: {}", path); @@ -247,29 +203,15 @@ public class FileController implements TimiJava, OS.FileSystem { return; } service.checkAccessPermission(file.getAbsolutePath()); - String fileName = file.getName(); - if (fileName.lastIndexOf('.') == -1) { - log.warn("preview a not support file: {}", path); - resp.setStatus(HttpServletResponse.SC_BAD_REQUEST); - return; - } - String extension = fileName.substring(fileName.lastIndexOf('.') + 1); - ServerFile.SupportPreviewExtension ext = ServerFile.SupportPreviewExtension.fromExtension(extension); - if (ext == null) { - log.warn("preview a not support file: {}", path); - resp.setStatus(HttpServletResponse.SC_BAD_REQUEST); - return; - } - switch (ext) { - case PNG, BMP, JPG, JPEG -> { - // 图片 - String mimeType = new Tika().detect(file); + String mimeType = new Tika().detect(file); + switch (mimeType) { + case "image/png", "image/bmp", "image/jpeg" -> { if (TimiJava.isNotEmpty(mimeType)) { resp.setContentType(mimeType); } Thumbnails.of(file).size(256, 256).toOutputStream(resp.getOutputStream()); } - case MP4, MOV -> { + case "video/mp4", "video/quicktime" -> { // 视频 ByteArrayOutputStream baos = new ByteArrayOutputStream(); Picture picture = FrameGrab.getFrameAtSec(file, 2); @@ -277,6 +219,7 @@ public class FileController implements TimiJava, OS.FileSystem { ImageIO.write(bi, "png", baos); Thumbnails.of(IO.toInputStream(baos)).size(256, 256).toOutputStream(resp.getOutputStream()); } + default -> resp.setStatus(HttpServletResponse.SC_BAD_REQUEST); } } catch (Exception e) { resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); @@ -340,7 +283,7 @@ public class FileController implements TimiJava, OS.FileSystem { // 异步任务 FileZipAsyncTask task = new FileZipAsyncTask(listFileToRequest.getList(), listFileToRequest.getTo()); asyncTaskService.addAsyncTask(task); - return task.getUuid(); + return task.getId(); } /** @@ -367,7 +310,7 @@ public class FileController implements TimiJava, OS.FileSystem { // 异步任务 FileUnZipAsyncTask task = new FileUnZipAsyncTask(zipFile, params.get("to")); asyncTaskService.addAsyncTask(task); - return task.getUuid(); + return task.getId(); } /** @@ -382,7 +325,7 @@ public class FileController implements TimiJava, OS.FileSystem { public String tar(@RequestBody ListFileToRequest listFileToRequest) { FileTarAsyncTask task = new FileTarAsyncTask(listFileToRequest.getList(), listFileToRequest.getTo()); asyncTaskService.addAsyncTask(task); - return task.getUuid(); + return task.getId(); } /** @@ -409,7 +352,7 @@ public class FileController implements TimiJava, OS.FileSystem { } FileUnTarAsyncTask task = new FileUnTarAsyncTask(tarFile, params.get("to")); asyncTaskService.addAsyncTask(task); - return task.getUuid(); + return task.getId(); } /** @@ -424,7 +367,7 @@ public class FileController implements TimiJava, OS.FileSystem { public String cut(@RequestBody ListFileToRequest listFileToRequest) { FileMoveAsyncTask task = new FileMoveAsyncTask(listFileToRequest.getList(), listFileToRequest.getTo()); asyncTaskService.addAsyncTask(task); - return task.getUuid(); + return task.getId(); } /** @@ -439,7 +382,7 @@ public class FileController implements TimiJava, OS.FileSystem { public String copy(@RequestBody ListFileToRequest listFileToRequest) { FileCopyAsyncTask task = new FileCopyAsyncTask(listFileToRequest.getList(), listFileToRequest.getTo()); asyncTaskService.addAsyncTask(task); - return task.getUuid(); + return task.getId(); } /** @@ -466,7 +409,7 @@ public class FileController implements TimiJava, OS.FileSystem { public void download(HttpServletResponse resp) { try { String path = TimiSpring.cutURIStartAt("/system/file/download"); - path = settingService.getAsString(SettingKey.SYSTEM_FILE_BASE) + path; + path = "E:/IDEAProject/TimiServerAPI/data/file" + path; File file = new File(path); service.checkAccessPermission(file.getAbsolutePath()); @@ -476,6 +419,9 @@ public class FileController implements TimiJava, OS.FileSystem { resp.setHeader("Accept-Ranges", "bytes"); RequestRange range = TimiSpring.getRequestRange(file.length()); + if (range == null && resp.isCommitted()) { + return; + } if (range == null) { // 完整文件 resp.setContentLengthLong(file.length()); diff --git a/src/main/java/com/imyeyu/api/modules/system/controller/SystemController.java b/src/main/java/com/imyeyu/api/modules/system/controller/SystemController.java index c82b25b..c2de2de 100644 --- a/src/main/java/com/imyeyu/api/modules/system/controller/SystemController.java +++ b/src/main/java/com/imyeyu/api/modules/system/controller/SystemController.java @@ -1,28 +1,16 @@ package com.imyeyu.api.modules.system.controller; -import com.imyeyu.api.modules.common.entity.Attachment; import com.imyeyu.api.modules.common.service.AttachmentService; import com.imyeyu.api.modules.system.service.StatusService; -import com.imyeyu.api.modules.system.service.SystemService; import com.imyeyu.api.modules.system.vo.SystemStatusHistoryView; import com.imyeyu.api.modules.system.vo.SystemStatusSnapshotView; -import com.imyeyu.api.modules.system.vo.TempAttachRequest; -import com.imyeyu.io.IO; -import com.imyeyu.java.bean.timi.TimiCode; -import com.imyeyu.java.bean.timi.TimiException; -import com.imyeyu.spring.annotation.AOPLog; -import jakarta.validation.constraints.NotNull; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.multipart.MultipartFile; -import java.io.ByteArrayInputStream; -import java.io.IOException; import java.util.concurrent.Semaphore; /** @@ -38,7 +26,6 @@ import java.util.concurrent.Semaphore; public class SystemController { private final StatusService statusService; - private final SystemService service; private final AttachmentService attachmentService; private final Semaphore updateSemaphore = new Semaphore(1); @@ -64,98 +51,7 @@ public class SystemController { * @return 状态历史 */ @GetMapping("/status/history") - public SystemStatusHistoryView getStatusHistory( - @RequestParam(required = false) String window, - @RequestParam(required = false) String metrics - ) { + public SystemStatusHistoryView getStatusHistory(@RequestParam(required = false) String window, @RequestParam(required = false) String metrics) { return statusService.getStatusHistory(window, metrics); } - - /** - * 更新系统 - * - * @param file 更新文件 - */ - @AOPLog - @PostMapping("/update") - public void update(@NotNull @RequestParam("file") MultipartFile file) { - if (updateSemaphore.tryAcquire()) { - try { - service.update(file); - } finally { - updateSemaphore.release(); - } - } else { - throw new TimiException(TimiCode.ERROR_SERVICE_OFF).msgKey("TODO updating"); - } - } - - /** - * 恢复系统 - */ - @AOPLog - @RequestMapping("/restore") - public void restore() { - if (restoreSemaphore.tryAcquire()) { - try { - service.restore(); - } finally { - restoreSemaphore.release(); - } - } else { - throw new TimiException(TimiCode.ERROR_SERVICE_OFF).msgKey("TODO updating"); - } - } - - /** - * 关闭系统 - */ - @AOPLog - @RequestMapping("/shutdown") - public void shutdown() { - service.shutdown(); - } - - /** - * 重启系统 - */ - @AOPLog - @RequestMapping("/reboot") - public void reboot() { - if (rebootSemaphore.tryAcquire()) { - try { - service.reboot(); - } finally { - rebootSemaphore.release(); - } - } else { - throw new TimiException(TimiCode.ERROR_SERVICE_OFF).msgKey("TODO rebooting"); - } - } - - /** - * 上传临时附件 - * - * @param request 上传请求 - */ - @AOPLog - @PostMapping("/attach") - public void uploadAttachment(TempAttachRequest request) { - try { - for (MultipartFile file : request.getFile()) { - byte[] bytes = IO.toBytes(file.getInputStream()); - - Attachment attach = new Attachment(); - attach.setName(file.getOriginalFilename()); - attach.setBizType(request.getBizType()); - attach.setBizId(request.getBizId()); - attach.setAttachType(request.getAttachType()); - attach.setSize((long) bytes.length); - attach.setInputStream(new ByteArrayInputStream(bytes)); - attachmentService.create(attach); - } - } catch (IOException e) { - throw new RuntimeException(e); - } - } } diff --git a/src/main/java/com/imyeyu/api/modules/system/controller/TerminalController.java b/src/main/java/com/imyeyu/api/modules/system/controller/TerminalController.java deleted file mode 100644 index d906378..0000000 --- a/src/main/java/com/imyeyu/api/modules/system/controller/TerminalController.java +++ /dev/null @@ -1,99 +0,0 @@ -package com.imyeyu.api.modules.system.controller; - -import jakarta.servlet.http.HttpServletResponse; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import com.imyeyu.api.modules.common.service.SettingService; -import com.imyeyu.api.modules.system.service.TerminalService; -import com.imyeyu.api.modules.system.vo.terminal.ExecCommand; -import com.imyeyu.spring.annotation.AOPLog; -import com.imyeyu.spring.annotation.RequiredToken; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -import java.io.IOException; -import java.nio.charset.StandardCharsets; - -/** - * 控制台系统 - * - * @author 夜雨 - * @version 2022-07-22 10:04 - */ -@Slf4j -@RestController -@RequiredArgsConstructor -@RequestMapping("/system/terminal") -public class TerminalController { - - private final SettingService settingService; - private final TerminalService service; - - /** - * 创建会话 - * - * @return 会话 ID - */ - @AOPLog - @PostMapping("/create") - public String create() { - return service.create(); - } - - /** - * 会话是否存活 - * - * @param sessionId 会话 ID - * @return true 为存活状态 - */ - @RequiredToken - @PostMapping("/alive") - public boolean isAlive(@RequestBody String sessionId) { - return service.isAlive(sessionId); - } - - /** - * 指令路径补充 - * - * @param path 路径 - * @return 补充的字符 - */ - @AOPLog - @RequiredToken - @PostMapping("/fill") - public synchronized String pathFill(@RequestBody String path) { - return service.pathFill(path); - } - - /** - * 执行指令 - * - * @param resp 服务回调 - */ - @RequiredToken - @PostMapping("/exec") - public synchronized void exec(@RequestBody ExecCommand execCommand, HttpServletResponse resp) { - service.exec(execCommand, s -> { - try { - resp.getOutputStream().write(s.getBytes(StandardCharsets.UTF_8)); - resp.getOutputStream().flush(); - } catch (IOException e) { - log.error("command callback response error", e); - } - }); - } - - /** - * 关闭会话 - * - * @param sessionId 会话 ID - */ - @AOPLog - @RequiredToken - @PostMapping("/close") - public void close(@RequestBody String sessionId) { - service.close(sessionId); - } -} diff --git a/src/main/java/com/imyeyu/api/modules/system/entity/AsyncTask.java b/src/main/java/com/imyeyu/api/modules/system/entity/AsyncTask.java index a65357f..c4d8ac6 100644 --- a/src/main/java/com/imyeyu/api/modules/system/entity/AsyncTask.java +++ b/src/main/java/com/imyeyu/api/modules/system/entity/AsyncTask.java @@ -1,6 +1,8 @@ package com.imyeyu.api.modules.system.entity; import com.imyeyu.spring.annotation.Entity; +import com.imyeyu.spring.annotation.table.AutoUUID; +import com.imyeyu.spring.annotation.table.Id; import lombok.Data; /** @@ -80,7 +82,9 @@ public class AsyncTask { } /** 任务 ID */ - protected String uuid; + @Id + @AutoUUID + protected String id; /** 名称 */ protected String name; diff --git a/src/main/java/com/imyeyu/api/modules/system/entity/Logger.java b/src/main/java/com/imyeyu/api/modules/system/entity/Logger.java new file mode 100644 index 0000000..3efbb96 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/system/entity/Logger.java @@ -0,0 +1,75 @@ +package com.imyeyu.api.modules.system.entity; + +import com.imyeyu.spring.annotation.table.AutoUUID; +import com.imyeyu.spring.annotation.table.Id; +import com.imyeyu.spring.entity.Creatable; +import com.imyeyu.spring.entity.IDEntity; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 日志 + * + * @author 夜雨 + * @since 2026-05-13 14:40 + */ +@Data +@NoArgsConstructor +public class Logger implements IDEntity, Creatable { + + /** + * 日志级别 + * + * @author 夜雨 + * @since 2025-12-25 19:08 + */ + public enum Level { + + INFO, + + WARN, + + ERROR, + + DEBUG + } + + /** 主键 */ + @Id + @AutoUUID + private String id; + + /** 日志级别 */ + private Level level; + + /** 模块名称 */ + private String module; + + /** 操作描述 */ + private String operation; + + /** 用户 ID */ + private String userId; + + /** 内容 */ + private String content; + + /** 结果 */ + private String result; + + /** 异常 */ + private String exception; + + /** 执行时长 */ + private Integer duration; + + /** IP */ + private String ip; + + /** 创建时间 */ + private Long createdAt; + + public Logger(Level level) { + this.level = level; + } +} diff --git a/src/main/java/com/imyeyu/api/modules/system/mapper/LoggerMapper.java b/src/main/java/com/imyeyu/api/modules/system/mapper/LoggerMapper.java new file mode 100644 index 0000000..194efe5 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/system/mapper/LoggerMapper.java @@ -0,0 +1,13 @@ +package com.imyeyu.api.modules.system.mapper; + +import com.imyeyu.api.modules.system.entity.Logger; +import com.imyeyu.spring.mapper.BaseMapper; + +/** + * 日志 Mapper + * + * @author 夜雨 + * @since 2026-05-13 14:46 + */ +public interface LoggerMapper extends BaseMapper { +} diff --git a/src/main/java/com/imyeyu/api/modules/system/service/LoggerService.java b/src/main/java/com/imyeyu/api/modules/system/service/LoggerService.java new file mode 100644 index 0000000..7d4b2fe --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/system/service/LoggerService.java @@ -0,0 +1,56 @@ +package com.imyeyu.api.modules.system.service; + +import com.imyeyu.api.modules.system.entity.Logger; +import com.imyeyu.spring.service.CreatableService; +import com.imyeyu.spring.service.DestroyableService; +import com.imyeyu.spring.service.GettableService; +import com.imyeyu.spring.service.PageableService; + +/** + * 日志服务 + * + * @author 夜雨 + * @since 2026-05-13 14:46 + */ +public interface LoggerService extends GettableService, PageableService, CreatableService, DestroyableService { + + void createSync(Logger logger); + + void create(Logger.Level level, String module, String operation, String userId, String content, String result); + + default void info(String module, String operation, String userId) { + create(Logger.Level.INFO, module, operation, userId, null, null); + } + + default void info(String module, String operation, String content, String result) { + create(Logger.Level.INFO, module, operation, null, content, result); + } + + default void info(String module, String operation, String userId, String content, String result) { + create(Logger.Level.INFO, module, operation, userId, content, result); + } + + default void warning(String module, String operation, String userId) { + create(Logger.Level.WARN, module, operation, userId, null, null); + } + + default void warning(String module, String operation, String content, String result) { + create(Logger.Level.WARN, module, operation, null, content, result); + } + + default void warning(String module, String operation, String userId, String content, String result) { + create(Logger.Level.WARN, module, operation, userId, content, result); + } + + default void error(String module, String operation, String userId) { + create(Logger.Level.ERROR, module, operation, userId, null, null); + } + + default void error(String module, String operation, String content, String result) { + create(Logger.Level.ERROR, module, operation, null, content, result); + } + + default void error(String module, String operation, String userId, String content, String result) { + create(Logger.Level.ERROR, module, operation, userId, content, result); + } +} diff --git a/src/main/java/com/imyeyu/api/modules/system/service/SystemService.java b/src/main/java/com/imyeyu/api/modules/system/service/SystemService.java deleted file mode 100644 index b872b29..0000000 --- a/src/main/java/com/imyeyu/api/modules/system/service/SystemService.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.imyeyu.api.modules.system.service; - -import org.springframework.web.multipart.MultipartFile; - -/** - * @author 夜雨 - * @version 2024-03-13 00:44 - */ -public interface SystemService { - - void update(MultipartFile file); - - void restore(); - - void shutdown(); - - void reboot(); -} diff --git a/src/main/java/com/imyeyu/api/modules/system/service/implement/AbstractAsyncTask.java b/src/main/java/com/imyeyu/api/modules/system/service/implement/AbstractAsyncTask.java index f2a0ec2..d7b02db 100644 --- a/src/main/java/com/imyeyu/api/modules/system/service/implement/AbstractAsyncTask.java +++ b/src/main/java/com/imyeyu/api/modules/system/service/implement/AbstractAsyncTask.java @@ -1,12 +1,12 @@ package com.imyeyu.api.modules.system.service.implement; import ch.qos.logback.classic.Level; -import lombok.extern.slf4j.Slf4j; -import com.imyeyu.java.bean.timi.TimiCode; -import com.imyeyu.java.bean.timi.TimiException; import com.imyeyu.api.TimiServerAPI; import com.imyeyu.api.modules.system.entity.AsyncTask; +import com.imyeyu.java.bean.timi.TimiCode; +import com.imyeyu.java.bean.timi.TimiException; import com.imyeyu.utils.Time; +import lombok.extern.slf4j.Slf4j; import org.springframework.scheduling.config.CronTask; import org.springframework.scheduling.config.ScheduledTask; import org.springframework.scheduling.config.ScheduledTaskRegistrar; @@ -39,7 +39,7 @@ public abstract class AbstractAsyncTask extends AsyncTask { final StringBuilder logBuffer = new StringBuilder(); public AbstractAsyncTask() { - uuid = UUID.randomUUID().toString(); + id = UUID.randomUUID().toString(); status = AsyncTask.Status.IDLE; progress = -1; canPause = true; diff --git a/src/main/java/com/imyeyu/api/modules/system/service/implement/AsyncTaskServiceImplement.java b/src/main/java/com/imyeyu/api/modules/system/service/implement/AsyncTaskServiceImplement.java index 827cd82..01d6c84 100644 --- a/src/main/java/com/imyeyu/api/modules/system/service/implement/AsyncTaskServiceImplement.java +++ b/src/main/java/com/imyeyu/api/modules/system/service/implement/AsyncTaskServiceImplement.java @@ -71,7 +71,7 @@ public class AsyncTaskServiceImplement implements AsyncTaskService { if (Time.M * 3 < now - task.getDiedAt()) { // 死亡 3 分钟移除 iterator.remove(); - log.info("remove died async task uuid = " + task.getUuid()); + log.info("remove died async task uuid = " + task.getId()); } } } @@ -94,7 +94,7 @@ public class AsyncTaskServiceImplement implements AsyncTaskService { task.setCreatedAt(Time.now()); mapper.insert(task); - map.put(task.getUuid(), task); + map.put(task.getId(), task); } /** @@ -130,7 +130,7 @@ public class AsyncTaskServiceImplement implements AsyncTaskService { /** @param task 移除异步任务 */ public void removeAsyncTask(AbstractAsyncTask task) { task.interrupt(); - map.remove(task.getUuid()); + map.remove(task.getId()); } /** @return 异步任务列表 */ diff --git a/src/main/java/com/imyeyu/api/modules/system/service/implement/FileServiceImplement.java b/src/main/java/com/imyeyu/api/modules/system/service/implement/FileServiceImplement.java index 652e5ad..c969a3d 100644 --- a/src/main/java/com/imyeyu/api/modules/system/service/implement/FileServiceImplement.java +++ b/src/main/java/com/imyeyu/api/modules/system/service/implement/FileServiceImplement.java @@ -1,26 +1,22 @@ package com.imyeyu.api.modules.system.service.implement; +import com.imyeyu.api.modules.common.entity.Setting; +import com.imyeyu.api.modules.common.service.SettingService; +import com.imyeyu.api.modules.system.bean.ServerFile; +import com.imyeyu.api.modules.system.bean.TransferFile; +import com.imyeyu.api.modules.system.service.AsyncTaskService; +import com.imyeyu.api.modules.system.service.FileService; +import com.imyeyu.api.modules.system.util.SystemAPIInterceptor; import com.imyeyu.io.IO; import com.imyeyu.java.TimiJava; import com.imyeyu.java.bean.timi.TimiCode; import com.imyeyu.java.bean.timi.TimiException; -import com.imyeyu.api.modules.common.bean.SettingKey; -import com.imyeyu.api.modules.common.service.SettingService; -import com.imyeyu.api.modules.system.bean.FileSyncConfig; -import com.imyeyu.api.modules.system.bean.ServerFile; -import com.imyeyu.api.modules.system.bean.TransferFile; -import com.imyeyu.api.modules.system.entity.AsyncTask; -import com.imyeyu.api.modules.system.service.AsyncTaskService; -import com.imyeyu.api.modules.system.service.FileService; -import com.imyeyu.api.modules.system.task.async.FileSyncTask; -import com.imyeyu.api.modules.system.util.SystemAPIInterceptor; import com.imyeyu.utils.Decoder; import com.imyeyu.utils.OS; import jakarta.annotation.PostConstruct; import lombok.Data; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.scheduling.config.CronTask; import org.springframework.scheduling.config.ScheduledTaskRegistrar; import org.springframework.stereotype.Service; @@ -33,7 +29,6 @@ import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Deque; import java.util.List; -import java.util.Map; /** * 文件服务,不可直接使用入参路径 @@ -70,44 +65,44 @@ public class FileServiceImplement implements TimiJava, FileService { @PostConstruct private void fileSyncPostConstruct() { - FileSyncConfig config = settingService.fromYaml(SettingKey.SYSTEM_FILE_SYNC, FileSyncConfig.class); - if (config.isEnable()) { - scheduledTaskRegistrar.scheduleCronTask(new CronTask(() -> { - Map tasks = config.getTasks(); - configTask: for (Map.Entry item : tasks.entrySet()) { - if (item.getValue().isEnable()) { - List runningTaskList = asyncTaskService.listAll(); - for (int i = 0; i < runningTaskList.size(); i++) { - AbstractAsyncTask runningTask = runningTaskList.get(i); - if (runningTask.getType() == AsyncTask.Type.FILE_SYNC && runningTask.getName().equals(item.getKey())) { - continue configTask; - } - } - FileSyncTask task = new FileSyncTask(item.getKey(), item.getValue()); - asyncTaskService.addAsyncTask(task); - } - } - }, config.getCron())); - } +// FileSyncConfig config = settingService.fromYaml(SettingKey.SYSTEM_FILE_SYNC, FileSyncConfig.class); +// if (config.isEnable()) { +// scheduledTaskRegistrar.scheduleCronTask(new CronTask(() -> { +// Map tasks = config.getTasks(); +// configTask: for (Map.Entry item : tasks.entrySet()) { +// if (item.getValue().isEnable()) { +// List runningTaskList = asyncTaskService.listAll(); +// for (int i = 0; i < runningTaskList.size(); i++) { +// AbstractAsyncTask runningTask = runningTaskList.get(i); +// if (runningTask.getType() == AsyncTask.Type.FILE_SYNC && runningTask.getName().equals(item.getKey())) { +// continue configTask; +// } +// } +// FileSyncTask task = new FileSyncTask(item.getKey(), item.getValue()); +// asyncTaskService.addAsyncTask(task); +// } +// } +// }, config.getCron())); +// } } @PostConstruct private void fileFilterPostConstruct() { - option = settingService.fromYaml(SettingKey.SYSTEM_FILE_FILTER, FilterOption.class); +// option = settingService.fromYaml(SettingKey.SYSTEM_FILE_FILTER, FilterOption.class); } @Override public boolean canAccess(String path) { - if (interceptor.isSuperKey()) { + if (interceptor.isHighAuthorityKey()) { return true; } - for (int i = 0; i < option.allow.size(); i++) { - if (!path.startsWith(option.allow.get(i))) { + for (String allow : option.allow) { + if (!path.startsWith(allow)) { return false; } } - for (int i = 0; i < option.exclude.size(); i++) { - if (path.startsWith(option.exclude.get(i))) { + for (String exclude : option.exclude) { + if (path.startsWith(exclude)) { return false; } } @@ -262,7 +257,8 @@ public class FileServiceImplement implements TimiJava, FileService { } private String getPath(String path) { - String basePath = settingService.getAsString(SettingKey.SYSTEM_FILE_BASE); +// String basePath = settingService.getSystem(Setting.Module.System.FILE_BASE_PATH).asString(); + String basePath = "E:\\IDEAProject\\TimiServerAPI\\data\\file"; if (TimiJava.isEmpty(path)) { return basePath; } diff --git a/src/main/java/com/imyeyu/api/modules/system/service/implement/LoggerServiceImplement.java b/src/main/java/com/imyeyu/api/modules/system/service/implement/LoggerServiceImplement.java new file mode 100644 index 0000000..4a7ac4e --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/system/service/implement/LoggerServiceImplement.java @@ -0,0 +1,48 @@ +package com.imyeyu.api.modules.system.service.implement; + +import com.imyeyu.api.modules.system.entity.Logger; +import com.imyeyu.api.modules.system.mapper.LoggerMapper; +import com.imyeyu.api.modules.system.service.LoggerService; +import com.imyeyu.spring.mapper.BaseMapper; +import com.imyeyu.spring.service.AbstractEntityService; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +/** + * 日志服务实现 + * + * @author 夜雨 + * @since 2026-05-13 14:46 + */ +@Service +@RequiredArgsConstructor +public class LoggerServiceImplement extends AbstractEntityService implements LoggerService { + + private final LoggerMapper mapper; + + @Override + protected BaseMapper mapper() { + return mapper; + } + + @Override + public void create(Logger logger) { + // 默认异步 + Thread.startVirtualThread(() -> super.create(logger)); + } + + @Override + public void createSync(Logger logger) { + super.create(logger); + } + + public void create(Logger.Level level, String module, String operation, String userId, String content, String result) { + Logger log = new Logger(level); + log.setModule(module); + log.setOperation(operation); + log.setUserId(userId); + log.setContent(content); + log.setResult(result); + create(log); + } +} diff --git a/src/main/java/com/imyeyu/api/modules/system/service/implement/StatusServiceImplement.java b/src/main/java/com/imyeyu/api/modules/system/service/implement/StatusServiceImplement.java index 105122d..fb5c123 100644 --- a/src/main/java/com/imyeyu/api/modules/system/service/implement/StatusServiceImplement.java +++ b/src/main/java/com/imyeyu/api/modules/system/service/implement/StatusServiceImplement.java @@ -1,6 +1,6 @@ package com.imyeyu.api.modules.system.service.implement; -import com.imyeyu.api.modules.common.bean.SettingKey; +import com.imyeyu.api.modules.common.entity.Setting; import com.imyeyu.api.modules.common.service.SettingService; import com.imyeyu.api.modules.system.bean.ServerStatus; import com.imyeyu.api.modules.system.service.StatusService; @@ -33,7 +33,7 @@ public class StatusServiceImplement implements StatusService { @Override public SystemStatusSnapshotView getStatus(String metrics) { long serverTime = Time.now(); - int sampleRateMs = settingService.getAsInt(SettingKey.SYSTEM_STATUS_RATE); + int sampleRateMs = settingService.getSystem(Setting.Module.SystemStatus.RATE).asInteger(); EnumSet selectedMetrics = parseMetrics(metrics); synchronized (serverStatus) { @@ -48,7 +48,7 @@ public class StatusServiceImplement implements StatusService { @Override public SystemStatusHistoryView getStatusHistory(String window, String metrics) { long serverTime = Time.now(); - int sampleRateMs = settingService.getAsInt(SettingKey.SYSTEM_STATUS_RATE); + int sampleRateMs = settingService.getSystem(Setting.Module.SystemStatus.RATE).asInteger(); EnumSet selectedMetrics = parseMetrics(metrics); synchronized (serverStatus) { @@ -261,7 +261,7 @@ public class StatusServiceImplement implements StatusService { */ private long parseWindowMs(String window, int sampleRateMs) { if (window == null || window.isBlank()) { - return (long) settingService.getAsInt(SettingKey.SYSTEM_STATUS_LIMIT) * sampleRateMs; + return (long) settingService.getSystem(Setting.Module.SystemStatus.RATE).asInteger() * sampleRateMs; } String normalized = window.trim().toLowerCase(Locale.ROOT); @@ -277,7 +277,7 @@ public class StatusServiceImplement implements StatusService { try { return Math.max(sampleRateMs, Long.parseLong(valueText) * unit); } catch (NumberFormatException e) { - return (long) settingService.getAsInt(SettingKey.SYSTEM_STATUS_LIMIT) * sampleRateMs; + return (long) settingService.getSystem(Setting.Module.SystemStatus.RATE).asInteger() * sampleRateMs; } } diff --git a/src/main/java/com/imyeyu/api/modules/system/service/implement/SystemServiceImplement.java b/src/main/java/com/imyeyu/api/modules/system/service/implement/SystemServiceImplement.java deleted file mode 100644 index 57a641c..0000000 --- a/src/main/java/com/imyeyu/api/modules/system/service/implement/SystemServiceImplement.java +++ /dev/null @@ -1,73 +0,0 @@ -package com.imyeyu.api.modules.system.service.implement; - -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import com.imyeyu.io.IO; -import com.imyeyu.java.TimiJava; -import com.imyeyu.java.bean.timi.TimiCode; -import com.imyeyu.java.bean.timi.TimiException; -import com.imyeyu.api.TimiServerAPI; -import com.imyeyu.api.modules.common.bean.SettingKey; -import com.imyeyu.api.modules.common.service.SettingService; -import com.imyeyu.api.modules.system.service.SystemService; -import com.imyeyu.utils.OS; -import org.springframework.boot.SpringApplication; -import org.springframework.context.support.AbstractApplicationContext; -import org.springframework.scheduling.annotation.Async; -import org.springframework.stereotype.Service; -import org.springframework.web.multipart.MultipartFile; - -import javax.naming.NoPermissionException; -import java.io.IOException; - -/** - * @author 夜雨 - * @version 2024-03-13 00:45 - */ -@Slf4j -@Service -@RequiredArgsConstructor -public class SystemServiceImplement implements SystemService { - - private final SettingService settingService; - - @Override - public void update(MultipartFile file) { - try { - IO.toFile(IO.file("timi-server-api.jar"), file.getInputStream()); - } catch (NoPermissionException | IOException e) { - log.error("update core error", e); - throw new TimiException(TimiCode.ERROR).msgKey("TODO update core error"); - } - } - - @Override - public void restore() { - try { - IO.copy(IO.file("default.jar"), IO.file("timi-server-api.jar").getAbsolutePath()); - } catch (NoPermissionException | IOException e) { - log.error("restore core error", e); - throw new TimiException(TimiCode.ERROR).msgKey("TODO restore core error"); - } - } - - @Override - public void shutdown() { - if (TimiServerAPI.applicationContext instanceof AbstractApplicationContext ctx) { - ctx.close(); - } - } - - @Async - @Override - public void reboot() { - if (TimiServerAPI.applicationContext instanceof AbstractApplicationContext ctx) { - String command = settingService.getAsString(SettingKey.SYSTEM_REBOOT_COMMAND); - if (TimiJava.isEmpty(command)) { - throw new TimiException(TimiCode.ERROR_NPE_VARIABLE).msgKey("TODO not support reboot"); - } - OS.runAfterShutdown(command); - SpringApplication.exit(ctx); - } - } -} diff --git a/src/main/java/com/imyeyu/api/modules/system/service/implement/TerminalServiceImplement.java b/src/main/java/com/imyeyu/api/modules/system/service/implement/TerminalServiceImplement.java deleted file mode 100644 index dd072a1..0000000 --- a/src/main/java/com/imyeyu/api/modules/system/service/implement/TerminalServiceImplement.java +++ /dev/null @@ -1,132 +0,0 @@ -package com.imyeyu.api.modules.system.service.implement; - -import jakarta.annotation.PostConstruct; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import com.imyeyu.java.TimiJava; -import com.imyeyu.java.bean.CallbackArg; -import com.imyeyu.java.bean.timi.TimiCode; -import com.imyeyu.java.bean.timi.TimiException; -import com.imyeyu.api.modules.common.bean.SettingKey; -import com.imyeyu.api.modules.common.service.SettingService; -import com.imyeyu.api.modules.system.bean.TerminalPipe; -import com.imyeyu.api.modules.system.service.TerminalService; -import com.imyeyu.api.modules.system.vo.terminal.ExecCommand; -import com.imyeyu.spring.TimiSpring; -import com.imyeyu.utils.OS; -import org.springframework.stereotype.Service; - -import java.io.File; -import java.io.IOException; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.UUID; - -/** - * 指令服务 - * - * @author 夜雨 - * @version 2022-07-22 10:30 - */ -@Slf4j -@Service -@RequiredArgsConstructor -public class TerminalServiceImplement implements TerminalService, TimiJava, OS.FileSystem { - - /** Linux 控制台 */ - private static final String STARTUP_LINUX = "/bin/bash"; - - /** Windows 控制台 */ - private static final String STARTUP_WINDOWS = "cmd"; - - private final SettingService settingService; - - /** 会话管道映射 Map<SessionID, 管道>,此 SessionID 由 {@link #create()} 创建,非 HTTP SessionID */ - private final Map pipes = new HashMap<>(); - private final Set execLogFilters = new HashSet<>(); - - @PostConstruct - private void postConstruct() { - execLogFilters.addAll(List.of(settingService.getAsString(SettingKey.SYSTEM_TERMINAL_FILTERS).split(","))); - } - - @Override - public Map listTerminalPipe() { - return pipes; - } - - @Override - public String create() { - try { - String id = UUID.randomUUID().toString(); - pipes.put(id, new TerminalPipe(Runtime.getRuntime().exec(OS.IS_WINDOWS ? STARTUP_WINDOWS : STARTUP_LINUX))); - return id; - } catch (IOException e) { - throw new TimiException(TimiCode.ERROR).msgKey("创建控制台会话失败:" + e.getLocalizedMessage()); - } - } - - @Override - public boolean isAlive(String sessionID) { - return pipes.get(sessionID) != null && pipes.get(sessionID).getStatus() != TerminalPipe.Status.DIED; - } - - @Override - public String pathFill(String path) { - int splitAt = path.lastIndexOf(SEP); - if (splitAt == -1) { - return ""; - } - String parent = path.substring(0, splitAt + 1); - File file = new File(parent); - if (!file.exists()) { - return ""; - } - File[] files = file.listFiles(); - if (files == null) { - return ""; - } - String startWith = path.substring(splitAt + 1); - for (int i = 0; i < files.length; i++) { - if (files[i].getName().startsWith(startWith)) { - if (files[i].isDirectory()) { - return files[i].getName().substring(startWith.length()) + SEP; - } else { - return files[i].getName().substring(startWith.length()); - } - } - } - return ""; - } - - @Override - public void exec(ExecCommand execCommand, CallbackArg callback) { - try { - if (!execLogFilters.contains(execCommand.getCommand())) { - log.info("IP: {} -> [{}]# {}", TimiSpring.getRequestIP(), execCommand.getSessionId(), execCommand.getCommand()); - } - TerminalPipe pipe = pipes.get(execCommand.getSessionId()); - if (pipe == null) { - throw new TimiException(TimiCode.RESULT_NULL).msgKey("控制台会话不存在"); - } - synchronized (pipe.getLock()) { - pipe.exec(callback, execCommand.getCommand()); - pipe.getLock().wait(); - } - } catch (InterruptedException e) { - throw new TimiException(TimiCode.ERROR).msgKey("指令执行失败:" + e.getMessage()); - } - } - - @Override - public void close(String sessionID) { - TerminalPipe pipe = pipes.get(sessionID); - if (pipe == null) { - throw new TimiException(TimiCode.RESULT_NULL).msgKey("控制台会话不存在"); - } - pipe.destroy(); - } -} diff --git a/src/main/java/com/imyeyu/api/modules/system/task/DockerStatusTask.java b/src/main/java/com/imyeyu/api/modules/system/task/DockerStatusTask.java index 71d8550..f47db50 100644 --- a/src/main/java/com/imyeyu/api/modules/system/task/DockerStatusTask.java +++ b/src/main/java/com/imyeyu/api/modules/system/task/DockerStatusTask.java @@ -28,7 +28,7 @@ import java.util.concurrent.TimeUnit; * @since 2026-04-06 */ @Slf4j -@Service +//@Service @RequiredArgsConstructor public class DockerStatusTask implements SchedulingConfigurer { diff --git a/src/main/java/com/imyeyu/api/modules/system/task/ServerStatusTask.java b/src/main/java/com/imyeyu/api/modules/system/task/ServerStatusTask.java index 940ef61..0873ea8 100644 --- a/src/main/java/com/imyeyu/api/modules/system/task/ServerStatusTask.java +++ b/src/main/java/com/imyeyu/api/modules/system/task/ServerStatusTask.java @@ -1,6 +1,6 @@ package com.imyeyu.api.modules.system.task; -import com.imyeyu.api.modules.common.bean.SettingKey; +import com.imyeyu.api.modules.common.entity.Setting; import com.imyeyu.api.modules.common.service.SettingService; import com.imyeyu.api.modules.system.bean.ServerStatus; import com.imyeyu.api.modules.system.task.status.StatusCollectContext; @@ -26,7 +26,7 @@ import java.util.List; * @since 2022-01-31 15:18 */ @Slf4j -@Service +//@Service @RequiredArgsConstructor public class ServerStatusTask implements SchedulingConfigurer { @@ -50,23 +50,23 @@ public class ServerStatusTask implements SchedulingConfigurer { hardware, Time.now() ); - synchronized (status) { - for (StatusCollector collector : statusCollectors) { - collector.initialize(context); - } - } +// synchronized (status) { +// for (StatusCollector collector : statusCollectors) { +// collector.initialize(context); +// } +// } - taskRegistrar.addTriggerTask(() -> { - synchronized (status) { - context.setCollectAt(Time.now()); - for (StatusCollector collector : statusCollectors) { - collector.collect(context); - } - status.getUpdateAxis().addLast(context.getCollectAt()); - if (settingService.getAsInt(SettingKey.SYSTEM_STATUS_LIMIT) < status.getUpdateAxis().size()) { - status.getUpdateAxis().pollFirst(); - } - } - }, triggerContext -> new CronTrigger("0/%s * * * * ?".formatted(settingService.getAsInt(SettingKey.SYSTEM_STATUS_RATE) / 1000)).nextExecution(triggerContext)); +// taskRegistrar.addTriggerTask(() -> { +// synchronized (status) { +// context.setCollectAt(Time.now()); +// for (StatusCollector collector : statusCollectors) { +// collector.collect(context); +// } +// status.getUpdateAxis().addLast(context.getCollectAt()); +// if (settingService.getSystem(Setting.Module.SystemStatus.LIMIT).asInteger() < status.getUpdateAxis().size()) { +// status.getUpdateAxis().pollFirst(); +// } +// } +// }, triggerContext -> new CronTrigger("0/%s * * * * ?".formatted(settingService.getSystem(Setting.Module.SystemStatus.RATE).asInteger() / 1000)).nextExecution(triggerContext)); } } diff --git a/src/main/java/com/imyeyu/api/modules/system/task/TerminalTask.java b/src/main/java/com/imyeyu/api/modules/system/task/TerminalTask.java deleted file mode 100644 index ad56891..0000000 --- a/src/main/java/com/imyeyu/api/modules/system/task/TerminalTask.java +++ /dev/null @@ -1,45 +0,0 @@ -package com.imyeyu.api.modules.system.task; - -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import com.imyeyu.api.modules.common.bean.SettingKey; -import com.imyeyu.api.modules.common.service.SettingService; -import com.imyeyu.api.modules.system.bean.TerminalPipe; -import com.imyeyu.api.modules.system.service.TerminalService; -import com.imyeyu.utils.Time; -import org.springframework.context.annotation.Configuration; -import org.springframework.scheduling.annotation.EnableScheduling; -import org.springframework.scheduling.annotation.Scheduled; - -/** - * 控制台会话存活轮询 - * - * @author 夜雨 - * @version 2022-07-25 10:27 - */ -@Slf4j -@Configuration -@EnableScheduling -@RequiredArgsConstructor -public class TerminalTask { - - private final SettingService settingService; - private final TerminalService service; - - @Scheduled(fixedRate = Time.M) - private void run() { - service.listTerminalPipe().entrySet().removeIf(entry -> { - if (entry.getValue().getStatus() == TerminalPipe.Status.DIED) { - log.info("kill the died terminal session: " + entry.getKey()); - return true; - } - long diedAt = entry.getValue().getLastExecAt() + Time.M * settingService.getAsInt(SettingKey.SYSTEM_TERMINAL_TTL); - if (diedAt < Time.now()) { - entry.getValue().destroy(); - log.info("kill the timeout terminal session [DIED at: {}]: {}", Time.toDateTime(diedAt), entry.getKey()); - return true; - } - return false; - }); - } -} diff --git a/src/main/java/com/imyeyu/api/modules/system/task/UpsStatusTask.java b/src/main/java/com/imyeyu/api/modules/system/task/UpsStatusTask.java index f84eca1..e5a8b35 100644 --- a/src/main/java/com/imyeyu/api/modules/system/task/UpsStatusTask.java +++ b/src/main/java/com/imyeyu/api/modules/system/task/UpsStatusTask.java @@ -39,9 +39,9 @@ public class UpsStatusTask implements SchedulingConfigurer { if (!collectEnabled) { return; } - PeriodicTrigger trigger = new PeriodicTrigger(collectRateMs, TimeUnit.MILLISECONDS); - trigger.setInitialDelay(0); - taskRegistrar.addTriggerTask(this::collect, trigger); +// PeriodicTrigger trigger = new PeriodicTrigger(collectRateMs, TimeUnit.MILLISECONDS); +// trigger.setInitialDelay(0); +// taskRegistrar.addTriggerTask(this::collect, trigger); } /** diff --git a/src/main/java/com/imyeyu/api/modules/system/task/status/AbstractDequeStatusCollector.java b/src/main/java/com/imyeyu/api/modules/system/task/status/AbstractDequeStatusCollector.java index 3aa2791..b77286e 100644 --- a/src/main/java/com/imyeyu/api/modules/system/task/status/AbstractDequeStatusCollector.java +++ b/src/main/java/com/imyeyu/api/modules/system/task/status/AbstractDequeStatusCollector.java @@ -1,6 +1,6 @@ package com.imyeyu.api.modules.system.task.status; -import com.imyeyu.api.modules.common.bean.SettingKey; +import com.imyeyu.api.modules.common.entity.Setting; import java.util.Deque; @@ -22,7 +22,7 @@ public abstract class AbstractDequeStatusCollector implements StatusCollector { */ protected void putDeque(StatusCollectContext context, Deque deque, T value) { deque.addLast(value); - if (context.getSettingService().getAsInt(SettingKey.SYSTEM_STATUS_LIMIT) < deque.size()) { + if (context.getSettingService().getSystem(Setting.Module.SystemStatus.LIMIT).asInteger() < deque.size()) { deque.pollFirst(); } } diff --git a/src/main/java/com/imyeyu/api/modules/system/task/status/collector/NetworkStatusCollector.java b/src/main/java/com/imyeyu/api/modules/system/task/status/collector/NetworkStatusCollector.java index 5c3d390..24be298 100644 --- a/src/main/java/com/imyeyu/api/modules/system/task/status/collector/NetworkStatusCollector.java +++ b/src/main/java/com/imyeyu/api/modules/system/task/status/collector/NetworkStatusCollector.java @@ -1,10 +1,9 @@ package com.imyeyu.api.modules.system.task.status.collector; -import com.imyeyu.api.modules.common.bean.SettingKey; +import com.imyeyu.api.modules.common.entity.Setting; import com.imyeyu.api.modules.system.task.status.AbstractDequeStatusCollector; import com.imyeyu.api.modules.system.task.status.StatusCollectContext; import lombok.extern.slf4j.Slf4j; -import org.bytedeco.librealsense.context; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; import oshi.hardware.NetworkIF; @@ -25,7 +24,7 @@ public class NetworkStatusCollector extends AbstractDequeStatusCollector { @Override public void initialize(StatusCollectContext context) { List networkIFs = context.getHardware().getNetworkIFs(); - String targetMac = context.getSettingService().getAsString(SettingKey.SYSTEM_STATUS_NETWORK_MAC); + String targetMac = context.getSettingService().getSystem(Setting.Module.SystemStatus.NETWORK_MAC).asString(); for (NetworkIF networkIF : networkIFs) { if (networkIF.getMacaddr().equals(targetMac)) { networkIF.updateAttributes(); @@ -42,8 +41,8 @@ public class NetworkStatusCollector extends AbstractDequeStatusCollector { @Override public void collect(StatusCollectContext context) { - String targetMac = context.getSettingService().getAsString(SettingKey.SYSTEM_STATUS_NETWORK_MAC); - int sampleRateMs = context.getSettingService().getAsInt(SettingKey.SYSTEM_STATUS_RATE); + String targetMac = context.getSettingService().getSystem(Setting.Module.SystemStatus.NETWORK_MAC).asString(); + int sampleRateMs = context.getSettingService().getSystem(Setting.Module.SystemStatus.RATE).asInteger(); for (NetworkIF networkIF : context.getHardware().getNetworkIFs()) { if (networkIF.getMacaddr().equals(targetMac)) { networkIF.updateAttributes(); diff --git a/src/main/java/com/imyeyu/api/modules/system/util/LoggerFilter.java b/src/main/java/com/imyeyu/api/modules/system/util/LoggerFilter.java index d70210d..efa669e 100644 --- a/src/main/java/com/imyeyu/api/modules/system/util/LoggerFilter.java +++ b/src/main/java/com/imyeyu/api/modules/system/util/LoggerFilter.java @@ -19,9 +19,11 @@ public class LoggerFilter extends Filter { if (event.getLevel() == Level.INFO && event.getLoggerName().startsWith("org.jaudiotagger")) { return FilterReply.DENY; } - // oshi COM 异常警告 - if (event.getMessage().contains("COM exception") || event.getMessage().contains("Reflect exception")) { - return FilterReply.DENY; + if (event.getMessage() != null) { + // oshi COM 异常警告 + if (event.getMessage().contains("COM exception") || event.getMessage().contains("Reflect exception")) { + return FilterReply.DENY; + } } return FilterReply.ACCEPT; } diff --git a/src/main/java/com/imyeyu/api/modules/system/util/SystemAPIInterceptor.java b/src/main/java/com/imyeyu/api/modules/system/util/SystemAPIInterceptor.java index 8e52fa6..b1cadf0 100644 --- a/src/main/java/com/imyeyu/api/modules/system/util/SystemAPIInterceptor.java +++ b/src/main/java/com/imyeyu/api/modules/system/util/SystemAPIInterceptor.java @@ -1,11 +1,15 @@ package com.imyeyu.api.modules.system.util; -import com.imyeyu.java.TimiJava; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.imyeyu.api.modules.common.entity.Setting; +import com.imyeyu.api.modules.common.service.SettingService; +import com.imyeyu.api.modules.system.bean.AccessKey; import com.imyeyu.java.bean.timi.TimiCode; import com.imyeyu.java.bean.timi.TimiException; -import com.imyeyu.api.modules.common.bean.SettingKey; -import com.imyeyu.api.modules.common.service.SettingService; import com.imyeyu.spring.TimiSpring; +import jakarta.annotation.PostConstruct; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; @@ -14,6 +18,10 @@ import org.springframework.lang.NonNull; import org.springframework.stereotype.Component; import org.springframework.web.servlet.HandlerInterceptor; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + /** * @author 夜雨 * @version 2023-11-23 17:09 @@ -24,28 +32,33 @@ import org.springframework.web.servlet.HandlerInterceptor; public class SystemAPIInterceptor implements HandlerInterceptor { public static final String PATH = "/system/**"; - private static final String IS_SUPER_KEY = "SYSTEM_API_SUPER_KEY"; + private static final String HIGH_AUTHORITY = "SYSTEM_API_HIGH_AUTHORITY_KEY"; + private final ObjectMapper jackson; private final SettingService settingService; - public boolean preHandle(@NonNull HttpServletRequest req, @NonNull HttpServletResponse resp, @NonNull Object handler) { - String key = TimiSpring.getHeader("Token"); - if (TimiJava.isEmpty(key)) { - key = req.getParameter("token"); - } - String dbKey = settingService.getAsString(SettingKey.SYSTEM_API_KEY); - String dbSuperKey = settingService.getAsString(SettingKey.SYSTEM_API_SUPER_KEY); - if (dbSuperKey.equals(key)) { - TimiSpring.setRequestAttr(IS_SUPER_KEY, true); - return true; - } - if (dbKey.equals(key)) { - return true; - } - throw new TimiException(TimiCode.ARG_MISS).msgKey("TODO key invalid"); + private final Map keyMap = new HashMap<>(); + + @PostConstruct + private void postConstruct() throws JsonProcessingException { +// List list = jackson.readValue(settingService.getSystem(Setting.Module.System.API_KEY).asString(), new TypeReference<>() {}); +// for (AccessKey key : list) { +// keyMap.put(key.getValue(), key.getHighAuthority()); +// } } - public boolean isSuperKey() { - return TimiSpring.hasRequestAttr(IS_SUPER_KEY); + public boolean preHandle(@NonNull HttpServletRequest req, @NonNull HttpServletResponse resp, @NonNull Object handler) { +// String key = TimiSpring.getKey(); +// if (keyMap.containsKey(key)) { +// TimiSpring.setRequestAttr(HIGH_AUTHORITY, keyMap.get(key)); +// return true; +// } +// throw new TimiException(TimiCode.PERMISSION_ERROR).msgKey("TODO invalid key"); + return true; + } + + public boolean isHighAuthorityKey() { +// return TimiSpring.getRequestAttr(HIGH_AUTHORITY, Boolean.class); + return true; } } diff --git a/src/main/java/com/imyeyu/api/modules/system/vo/TempAttachRequest.java b/src/main/java/com/imyeyu/api/modules/system/vo/TempAttachRequest.java deleted file mode 100644 index cc26465..0000000 --- a/src/main/java/com/imyeyu/api/modules/system/vo/TempAttachRequest.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.imyeyu.api.modules.system.vo; - -import com.imyeyu.api.modules.common.entity.Attachment; -import lombok.Data; -import lombok.EqualsAndHashCode; -import org.springframework.web.multipart.MultipartFile; - -/** - * TODO 这是个临时对象 - * - * @author 夜雨 - * @version 2024-04-29 00:29 - */ -@Data -@EqualsAndHashCode(callSuper = true) -public class TempAttachRequest extends Attachment { - - private MultipartFile[] file; -} diff --git a/src/main/java/com/imyeyu/api/modules/user/bean/BootstrapData.java b/src/main/java/com/imyeyu/api/modules/user/bean/BootstrapData.java new file mode 100644 index 0000000..91ff2a9 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/user/bean/BootstrapData.java @@ -0,0 +1,108 @@ +package com.imyeyu.api.modules.user.bean; + +import com.imyeyu.api.TimiServerAPI; +import com.imyeyu.api.bean.CoreRoleCode; +import com.imyeyu.api.bean.ModuleCode; +import com.imyeyu.api.modules.user.entity.Role; +import com.imyeyu.api.modules.user.entity.User; +import com.imyeyu.api.modules.user.entity.UserRole; +import com.imyeyu.api.modules.user.mapper.RoleMapper; +import com.imyeyu.api.modules.user.mapper.UserMapper; +import com.imyeyu.java.bean.timi.TimiCode; +import com.imyeyu.java.bean.timi.TimiException; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; + +import java.util.Set; + +/** + * 自举数据常量 + * + * @author 夜雨 + * @since 2026-06-07 10:42 + */ +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public final class BootstrapData { + + /** System 用户名 */ + public static final String SYS_USER_NAME = "System"; + + /** System 用户昵称 */ + public static final String SYS_USER_NICK = "System"; + + /** System 角色代码 */ + public static final String SYS_ROLE_CODE = CoreRoleCode.SYSTEM.name(); + + /** 管理角色代码 */ + public static final String ADMIN_ROLE_CODE = CoreRoleCode.ADMIN.name(); + + /** System 角色模块 */ + public static final ModuleCode SYS_ROLE_MODULE_CODE = ModuleCode.CORE; + + /** 管理角色模块 */ + public static final ModuleCode ADMIN_ROLE_MODULE_CODE = ModuleCode.CORE; + + /** System 角色名称 */ + public static final String SYS_ROLE_NAME = CoreRoleCode.SYSTEM.getDefaultName(); + + /** 管理角色名称 */ + public static final String ADMIN_ROLE_NAME = CoreRoleCode.ADMIN.getDefaultName(); + + /** 自举权限代码集合 */ + public static final Set PROTECTED_PERMISSION_CODE_SET = BuiltinAuthRegistry.SYSTEM_PERMISSION_CODE_SET; + + public static boolean isProtectedRoleCode(ModuleCode moduleCode, String code) { + return BuiltinAuthRegistry.isProtectedRole(moduleCode, code); + } + + public static void checkProtectedRoleCode(ModuleCode moduleCode, String code) { + if (BootstrapData.isProtectedRoleCode(moduleCode, code)) { + throw new TimiException(TimiCode.PERMISSION_ERROR); + } + } + + /** + * 判断是否为自举权限 + * + * @param code 权限码 + * @return 是否为自举权限 + */ + public static boolean isProtectedPermissionCode(BuiltinPermissionCode code) { + return code != null && code.isGrantToSystem(); + } + + public static boolean isProtectedPermissionCode(String code) { + return PROTECTED_PERMISSION_CODE_SET.contains(code); + } + + public static void checkProtectedPermissionCode(BuiltinPermissionCode code) { + if (BootstrapData.isProtectedPermissionCode(code)) { + throw new TimiException(TimiCode.PERMISSION_ERROR); + } + } + + public static boolean isProtectedUserRole(UserRole userRole) { + if (userRole == null) { + return false; + } + UserMapper userMapper = TimiServerAPI.applicationContext.getBean(UserMapper.class); + RoleMapper roleMapper = TimiServerAPI.applicationContext.getBean(RoleMapper.class); + + User user = userMapper.select(userRole.getUserId()); + if (user == null || !BootstrapData.SYS_USER_NAME.equals(user.getName())) { + return false; + } + Role role = roleMapper.select(userRole.getRoleId()); + return isProtectedRole(role); + } + + public static void checkProtectedUserRole(UserRole userRole) { + if (BootstrapData.isProtectedUserRole(userRole)) { + throw new TimiException(TimiCode.PERMISSION_ERROR); + } + } + + public static boolean isProtectedRole(Role role) { + return role != null && isProtectedRoleCode(role.getModuleCode(), role.getCode()); + } +} diff --git a/src/main/java/com/imyeyu/api/modules/user/bean/BuiltinAuthRegistry.java b/src/main/java/com/imyeyu/api/modules/user/bean/BuiltinAuthRegistry.java new file mode 100644 index 0000000..2c27f25 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/user/bean/BuiltinAuthRegistry.java @@ -0,0 +1,61 @@ +package com.imyeyu.api.modules.user.bean; + +import com.imyeyu.api.bean.CorePermissionCode; +import com.imyeyu.api.bean.CoreRoleCode; +import com.imyeyu.api.bean.ModuleCode; +import com.imyeyu.api.modules.gao.bean.GaoPermissionCode; +import com.imyeyu.api.modules.gao.bean.GaoRoleCode; + +import java.util.Arrays; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/// +/// 内建权限与角色注册中心 +/// +/// @author Codex +/// @since 2026-07-28 +public final class BuiltinAuthRegistry { + + private BuiltinAuthRegistry() { + } + + /// 内建权限列表 + public static final List PERMISSION_CODE_LIST = Stream.concat( + Arrays.stream(CorePermissionCode.values()), + Arrays.stream(GaoPermissionCode.values()) + ).map(BuiltinPermissionCode.class::cast).toList(); + + /// 内建角色列表 + public static final List ROLE_CODE_LIST = Stream.concat( + Arrays.stream(CoreRoleCode.values()), + Arrays.stream(GaoRoleCode.values()) + ).map(BuiltinRoleCode.class::cast).toList(); + + /// SYSTEM 角色必有权限集合 + public static final Set SYSTEM_PERMISSION_CODE_SET = PERMISSION_CODE_LIST.stream() + .filter(BuiltinPermissionCode::isGrantToSystem) + .map(BuiltinPermissionCode::getValue) + .collect(Collectors.toCollection(LinkedHashSet::new)); + + /// 受保护角色集合 + public static final Set PROTECTED_ROLE_KEY_SET = ROLE_CODE_LIST.stream() + .filter(BuiltinRoleCode::isProtected) + .map(item -> toRoleKey(item.getModuleCode(), ((Enum) item).name())) + .collect(Collectors.toCollection(LinkedHashSet::new)); + + public static String toRoleKey(ModuleCode moduleCode, String code) { + return "%s:%s".formatted(moduleCode.name(), code); + } + + public static String toPermissionKey(ModuleCode moduleCode, String code) { + return "%s:%s".formatted(moduleCode.name(), code); + } + + public static boolean isProtectedRole(ModuleCode moduleCode, String code) { + return PROTECTED_ROLE_KEY_SET.contains(toRoleKey(moduleCode, code)); + } +} diff --git a/src/main/java/com/imyeyu/api/modules/user/bean/BuiltinPermissionCode.java b/src/main/java/com/imyeyu/api/modules/user/bean/BuiltinPermissionCode.java new file mode 100644 index 0000000..9527fda --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/user/bean/BuiltinPermissionCode.java @@ -0,0 +1,36 @@ +package com.imyeyu.api.modules.user.bean; + +import com.imyeyu.api.bean.ModuleCode; + +/// +/// 内建权限定义 +/// +/// @author Codex +/// @since 2026-07-28 +public interface BuiltinPermissionCode { + + /// 权限模块 + /// + /// @return 模块代码 + ModuleCode getModuleCode(); + + /// 权限值 + /// + /// @return 权限值 + String getValue(); + + /// 默认名称 + /// + /// @return 默认名称 + String getDefaultName(); + + /// true 为初始化时授权给 SYSTEM 角色 + /// + /// @return 是否授权给 SYSTEM 角色 + boolean isGrantToSystem(); + + /// true 为初始化时授权给 ADMIN 角色 + /// + /// @return 是否授权给 ADMIN 角色 + boolean isGrantToAdmin(); +} diff --git a/src/main/java/com/imyeyu/api/modules/user/bean/BuiltinRoleCode.java b/src/main/java/com/imyeyu/api/modules/user/bean/BuiltinRoleCode.java new file mode 100644 index 0000000..d12fae2 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/user/bean/BuiltinRoleCode.java @@ -0,0 +1,31 @@ +package com.imyeyu.api.modules.user.bean; + +import com.imyeyu.api.bean.ModuleCode; + +/// +/// 内建角色定义 +/// +/// @author Codex +/// @since 2026-07-28 +public interface BuiltinRoleCode { + + /// 角色模块 + /// + /// @return 模块代码 + ModuleCode getModuleCode(); + + /// 默认名称 + /// + /// @return 默认名称 + String getDefaultName(); + + /// true 为受保护角色 + /// + /// @return 是否受保护 + boolean isProtected(); + + /// 父角色代码 + /// + /// @return 父角色代码 + String getParentCode(); +} diff --git a/src/main/java/com/imyeyu/api/modules/user/bean/Gender.java b/src/main/java/com/imyeyu/api/modules/user/bean/Gender.java new file mode 100644 index 0000000..5cd4c31 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/user/bean/Gender.java @@ -0,0 +1,12 @@ +package com.imyeyu.api.modules.user.bean; + +/// +/// +/// @author 夜雨 +/// @since 2026-07-30 14:35 +public enum Gender { + + MALE, + + FEMALE, +} diff --git a/src/main/java/com/imyeyu/api/modules/user/controller/PermissionController.java b/src/main/java/com/imyeyu/api/modules/user/controller/PermissionController.java new file mode 100644 index 0000000..773fbf6 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/user/controller/PermissionController.java @@ -0,0 +1,73 @@ +package com.imyeyu.api.modules.user.controller; + +import com.imyeyu.api.annotation.RequireCorePermission; +import com.imyeyu.api.annotation.RequireCoreRole; +import com.imyeyu.api.bean.CorePermissionCode; +import com.imyeyu.api.bean.CoreRoleCode; +import com.imyeyu.api.modules.user.entity.Permission; +import com.imyeyu.api.modules.user.service.PermissionService; +import com.imyeyu.api.modules.user.vo.permission.BatchCreateReq; +import com.imyeyu.spring.annotation.AOPLog; +import com.imyeyu.spring.bean.Page; +import com.imyeyu.spring.bean.PageResult; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +/** + * 权限 SDK 控制器 + * + * @author 夜雨 + * @since 2025-11-02 09:54 + */ +@Slf4j +@RestController +@RequiredArgsConstructor +@RequestMapping("/user/permission") +public class PermissionController { + + private final PermissionService service; + + @RequireCoreRole(CoreRoleCode.ADMIN) + @RequireCorePermission(CorePermissionCode.PERMISSION_READ) + @PostMapping("/list") + public PageResult list(@RequestBody Page page) { + return service.page(page); + } + + @AOPLog + @RequireCoreRole(CoreRoleCode.ADMIN) + @RequireCorePermission(CorePermissionCode.PERMISSION_READ) + @PostMapping("/create") + public void create(@RequestBody Permission permission) { + service.create(permission); + } + + @AOPLog + @RequireCoreRole(CoreRoleCode.ADMIN) + @RequireCorePermission(CorePermissionCode.PERMISSION_CREATE) + @PostMapping("/create/batch") + public void createBatch(@RequestBody BatchCreateReq req) { + service.create(req); + } + + @AOPLog + @RequireCoreRole(CoreRoleCode.ADMIN) + @RequireCorePermission(CorePermissionCode.PERMISSION_UPDATE) + @PostMapping("/update") + public void update(@RequestBody Permission permission) { + service.update(permission); + } + + @AOPLog + @RequireCoreRole(CoreRoleCode.ADMIN) + @RequireCorePermission(CorePermissionCode.PERMISSION_DELETE) + @PostMapping("/delete") + public void delete(@RequestParam("id") String id) { + service.delete(id); + } +} diff --git a/src/main/java/com/imyeyu/api/modules/user/controller/RoleController.java b/src/main/java/com/imyeyu/api/modules/user/controller/RoleController.java new file mode 100644 index 0000000..b4c203f --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/user/controller/RoleController.java @@ -0,0 +1,115 @@ +package com.imyeyu.api.modules.user.controller; + +import com.imyeyu.api.annotation.RequireCorePermission; +import com.imyeyu.api.annotation.RequireCoreRole; +import com.imyeyu.api.bean.CorePermissionCode; +import com.imyeyu.api.bean.CoreRoleCode; +import com.imyeyu.api.bean.ModuleCode; +import com.imyeyu.api.modules.user.entity.Permission; +import com.imyeyu.api.modules.user.entity.Role; +import com.imyeyu.api.modules.user.service.PermissionService; +import com.imyeyu.api.modules.user.service.RoleService; +import com.imyeyu.api.modules.user.vo.role.UserRoleAuthorizeReq; +import com.imyeyu.spring.annotation.AOPLog; +import com.imyeyu.spring.bean.Page; +import com.imyeyu.spring.bean.PageResult; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +/** + * 角色 SDK 控制器 + * + * @author 夜雨 + * @since 2025-11-02 10:00 + */ +@Slf4j +@RestController +@RequiredArgsConstructor +@RequestMapping("/user/role") +public class RoleController { + + private final RoleService service; + private final PermissionService permissionService; + + @RequireCoreRole(CoreRoleCode.ADMIN) + @RequireCorePermission(CorePermissionCode.ROLE_READ) + @PostMapping("/list") + public PageResult list(@RequestBody Page page) { + PageResult result = service.page(page); + if (result.getList() != null) { + result.getList().forEach(this::fillRoleDetail); + } + return result; + } + + @RequireCoreRole(CoreRoleCode.ADMIN) + @RequireCorePermission(CorePermissionCode.ROLE_READ) + @PostMapping("/detail") + public Role detail(@RequestParam String id) { + Role role = service.get(id); + fillRoleDetail(role); + return role; + } + + @AOPLog + @RequireCoreRole(CoreRoleCode.ADMIN) + @RequireCorePermission(CorePermissionCode.ROLE_CREATE) + @PostMapping("/create") + public void create(@RequestBody Role role) { + service.create(role); + } + + @AOPLog + @RequireCoreRole(CoreRoleCode.ADMIN) + @RequireCorePermission(CorePermissionCode.ROLE_UPDATE) + @PostMapping("/update") + public void update(@RequestBody Role role) { + service.update(role); + } + + @AOPLog + @RequireCoreRole(CoreRoleCode.ADMIN) + @RequireCorePermission(CorePermissionCode.ROLE_DELETE) + @PostMapping("/delete") + public void delete(@RequestParam String id) { + service.delete(id); + } + + @RequireCoreRole(CoreRoleCode.ADMIN) + @RequireCorePermission(CorePermissionCode.USER_ROLE_READ) + @PostMapping("/authorized/list") + public List listUserRole(@RequestParam String userId, @RequestParam(required = false) ModuleCode moduleCode) { + if (moduleCode != null) { + return service.listByUserId(userId, moduleCode); + } + return service.listByUserId(userId); + } + + @AOPLog + @RequireCoreRole(CoreRoleCode.ADMIN) + @RequireCorePermission(value = {CorePermissionCode.USER_ROLE_CREATE, CorePermissionCode.USER_ROLE_DELETE}) + @PostMapping("/authorized/create") + public void authorizeUserRole(@RequestBody UserRoleAuthorizeReq req) { + service.authorizeUserRole(req); + } + + @RequireCoreRole(CoreRoleCode.ADMIN) + @RequireCorePermission(CorePermissionCode.USER_ROLE_READ) + @PostMapping("/authorized/permission") + public List listAuthorizedPermission(@RequestParam String userId) { + return service.listAllPermissionByUserId(userId); + } + + private void fillRoleDetail(Role role) { + role.setPermissionList(permissionService.listByRoleId(role.getId())); + role.setAllPermissionList(service.listPermissionByRoleId(role.getId())); + role.setChildRoleList(service.listChildByParentRoleId(role.getId())); + } +} diff --git a/src/main/java/com/imyeyu/api/modules/user/controller/UserController.java b/src/main/java/com/imyeyu/api/modules/user/controller/UserController.java new file mode 100644 index 0000000..5ff2101 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/user/controller/UserController.java @@ -0,0 +1,270 @@ +package com.imyeyu.api.modules.user.controller; + +import com.fasterxml.jackson.annotation.JsonView; +import com.imyeyu.api.annotation.RequireCorePermission; +import com.imyeyu.api.annotation.RequireCoreRole; +import com.imyeyu.api.bean.CorePermissionCode; +import com.imyeyu.api.bean.CoreRoleCode; +import com.imyeyu.api.modules.common.entity.Attachment; +import com.imyeyu.api.modules.common.service.AttachmentService; +import com.imyeyu.api.modules.common.vo.attach.BizUpdateReq; +import com.imyeyu.api.modules.user.entity.User; +import com.imyeyu.api.modules.user.service.UserLoginService; +import com.imyeyu.api.modules.user.service.UserService; +import com.imyeyu.api.modules.user.vo.CancelRequest; +import com.imyeyu.api.modules.user.vo.LoginRequest; +import com.imyeyu.api.modules.user.vo.LoginResponse; +import com.imyeyu.api.modules.user.vo.PhoneVerifyCodeSendRequest; +import com.imyeyu.api.modules.user.vo.PhoneVerifyRequest; +import com.imyeyu.api.modules.user.vo.UpdatePasswordRequest; +import com.imyeyu.spring.TimiSpring; +import com.imyeyu.spring.annotation.AOPLog; +import com.imyeyu.spring.annotation.CaptchaValid; +import com.imyeyu.spring.annotation.RequestRateLimit; +import com.imyeyu.spring.annotation.RequiredToken; +import com.imyeyu.spring.bean.CaptchaData; +import com.imyeyu.spring.bean.Page; +import com.imyeyu.spring.bean.PageResult; +import com.imyeyu.spring.util.ResponseView; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.util.ArrayList; +import java.util.List; + +/** + * 用户接口 + * + * @author 夜雨 + * @since 2021-02-23 21:38 + */ +@Slf4j +@Validated +@RestController +@RequiredArgsConstructor +@RequestMapping("/user") +public class UserController { + + private final UserService userService; + private final UserLoginService userLoginService; + private final AttachmentService attachmentService; + + @RequireCoreRole(CoreRoleCode.ADMIN) + @RequestRateLimit + @RequireCorePermission(CorePermissionCode.USER_READ) + @PostMapping("/list") + public PageResult list(@RequestBody Page page) { + return userService.page(page); + } + + @AOPLog + @RequireCoreRole(CoreRoleCode.ADMIN) + @RequestRateLimit + @RequireCorePermission(CorePermissionCode.USER_READ) + @RequestMapping("/detail") + public User detail(@RequestParam String id) { + return userService.get(id); + } + + @AOPLog + @RequireCoreRole(CoreRoleCode.ADMIN) + @RequestRateLimit + @RequireCorePermission(CorePermissionCode.USER_CREATE) + @PostMapping("/create") + public void create(@RequestBody User user) { + userService.create(user); + } + + @AOPLog + @RequireCoreRole(CoreRoleCode.ADMIN) + @RequestRateLimit + @RequireCorePermission(CorePermissionCode.USER_UPDATE) + @PostMapping("/update") + public void update(@RequestBody User user) { + userService.update(user); + } + + @AOPLog + @RequireCoreRole(CoreRoleCode.ADMIN) + @RequestRateLimit + @RequestMapping("/delete") + public void delete(@RequestParam String id) { + userService.delete(id); + } + + /** + * 用户注册 + */ + @AOPLog + @JsonView(ResponseView.Public.class) + @RequestRateLimit + @CaptchaValid + @PostMapping("/register") + public LoginResponse register(@RequestBody @Valid CaptchaData user) { + userService.create(user.getData()); + // 登录 + LoginRequest loginRequest = new LoginRequest(); + loginRequest.setUser(user.getData().getName()); + loginRequest.setPassword(user.getData().getPassword()); + return userLoginService.login(loginRequest); + } + + /** + * 用户登录 + * + * @param req 登录请求 + * @return 登录结果 + */ + @AOPLog + @JsonView(ResponseView.Public.class) + @RequestRateLimit + @PostMapping("/login") + public LoginResponse login(@RequestBody @Valid CaptchaData req) { + return userLoginService.login(req.getData()); + } + + @AOPLog + @RequiredToken + @RequestRateLimit + @RequestMapping("/login/token") + public LoginResponse token() { + return userLoginService.login(TimiSpring.getToken(), userLoginService.getRequireLoginUserId()); + } + + /** + * 更新当前用户资料 + * + * @param req 用户资料 + * @return 最新登录用户 + */ + @AOPLog + @RequiredToken + @RequestRateLimit + @RequireCorePermission(CorePermissionCode.USER_UPDATE) + @PostMapping("/current/update") + public LoginResponse updateCurrent(@RequestBody User req) { + User loginUser = userLoginService.getRequireLoginUser(); + User user = userService.get(loginUser.getId()); + if (req.getName() != null) { + user.setName(req.getName()); + } + if (req.getNick() != null) { + user.setNick(req.getNick()); + } + if (req.getGender() != null) { + user.setGender(req.getGender()); + } + if (req.getDescription() != null) { + user.setDescription(req.getDescription()); + } + if (req.getAvatarType() != null) { + user.setAvatarType(req.getAvatarType()); + } + if (req.getWrapperType() != null) { + user.setWrapperType(req.getWrapperType()); + } + userService.update(user); + updateCurrentAvatar(loginUser.getId(), req.getTempFileIdList()); + return userLoginService.login(TimiSpring.getToken(), loginUser.getId()); + } + + /** + * 用户登出 + */ + @AOPLog + @PostMapping("/logout") + public void logout() { + userLoginService.logout(); + } + + /** + * 修改当前用户密码 + * + * @param req 修改密码请求 + */ + @AOPLog + @RequireCorePermission(CorePermissionCode.USER_UPDATE) + @PostMapping("/update/password") + public void updatePassword(@RequestBody @Valid UpdatePasswordRequest req) { + userLoginService.updatePassword(req.getOldValue(), req.getNewValue()); + } + + @AOPLog + @RequireCorePermission(CorePermissionCode.USER_UPDATE) + @PostMapping("/update/email") + public void updateEmail() { + + } + + /** + * 发送手机号验证码 + * + * @param req 手机号验证码请求 + */ + @AOPLog + @RequiredToken + @RequireCorePermission(CorePermissionCode.USER_UPDATE) + @PostMapping("/phone/verify/send") + public void sendPhoneCode(@RequestBody @Valid PhoneVerifyCodeSendRequest req) { + userLoginService.sendPhoneVerifyCode(req.getPhoneNo()); + } + + /** + * 验证手机号 + * + * @param req 手机号验证请求 + */ + @AOPLog + @RequiredToken + @RequireCorePermission(CorePermissionCode.USER_UPDATE) + @PostMapping("/phone/verify") + public void verifyPhone(@RequestBody @Valid PhoneVerifyRequest req) { + userLoginService.verifyPhoneNo(req.getPhoneNo(), req.getVerifyCode()); + } + + /** + * 注销当前用户 + * + * @param req 注销请求 + */ + @AOPLog + @RequireCorePermission(CorePermissionCode.USER_DELETE) + @PostMapping("/cancel") + public void cancel(@RequestBody @Valid CancelRequest req) { + userLoginService.deactivate(req.getPassword()); + } + + private void updateCurrentAvatar(String userId, List tempFileIdList) { + if (tempFileIdList == null || tempFileIdList.isEmpty()) { + return; + } + BizUpdateReq req = new BizUpdateReq(); + req.setBizType(Attachment.BizType.USER); + req.setBizId(userId); + List items = attachmentService.listByBizId(Attachment.BizType.USER, userId).stream() + .filter(item -> !User.AttachType.AVATAR.name().equals(item.getAttachType())) + .map(item -> { + Attachment attachment = new Attachment(); + attachment.setId(item.getId()); + return attachment; + }) + .toList(); + List avatarItems = tempFileIdList.stream().map(tempFileId -> { + Attachment attachment = new Attachment(); + attachment.setTempFileId(tempFileId); + attachment.setAttachTypeValue(User.AttachType.AVATAR); + return attachment; + }).toList(); + items = new ArrayList<>(items); + items.addAll(avatarItems); + req.setItems(items); + attachmentService.updateByBiz(req); + } +} diff --git a/src/main/java/com/imyeyu/api/modules/user/entity/Permission.java b/src/main/java/com/imyeyu/api/modules/user/entity/Permission.java new file mode 100644 index 0000000..f7935f0 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/user/entity/Permission.java @@ -0,0 +1,35 @@ +package com.imyeyu.api.modules.user.entity; + +import com.imyeyu.api.annotation.MultilingualField; +import com.imyeyu.api.bean.ModuleCode; +import com.imyeyu.spring.annotation.table.Transient; +import com.imyeyu.spring.entity.UUIDEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 权限 + * + * @author 夜雨 + * @since 2026-05-13 14:40 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class Permission extends UUIDEntity { + + /** 模块代码 */ + private ModuleCode moduleCode; + + /** 代码 */ + private String code; + + /** 名称多语言映射 ID */ + private String nameLangId; + + /** 说明 */ + private String description; + + @Transient + @MultilingualField("nameLangId") + private String name; +} diff --git a/src/main/java/com/imyeyu/api/modules/user/entity/Role.java b/src/main/java/com/imyeyu/api/modules/user/entity/Role.java new file mode 100644 index 0000000..abc40a1 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/user/entity/Role.java @@ -0,0 +1,58 @@ +package com.imyeyu.api.modules.user.entity; + +import com.imyeyu.api.annotation.MultilingualField; +import com.imyeyu.api.bean.ModuleCode; +import com.imyeyu.spring.annotation.table.Transient; +import com.imyeyu.spring.entity.UUIDEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.List; +import java.util.Set; + +/** + * 角色 + * + * @author 夜雨 + * @since 2026-05-13 14:40 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class Role extends UUIDEntity { + + /** 模块代码 */ + private ModuleCode moduleCode; + + /** 代码 */ + private String code; + + /** 名称 */ + private String nameLangId; + + /** 说明 */ + private String description; + + @Transient + @MultilingualField("nameLangId") + private String name; + + /** 角色自身的权限列表,用于编辑 */ + @Transient + protected List permissionList; + + /** 所有权限列表,包含从子角色继承的权限 */ + @Transient + protected List allPermissionList; + + /** 子角色列表 */ + @Transient + protected List childRoleList; + + /** 权限 ID 列表 */ + @Transient + protected Set permissionIdList; + + /** 子角色 ID 列表 */ + @Transient + protected Set childRoleIdList; +} diff --git a/src/main/java/com/imyeyu/api/modules/user/entity/RolePermission.java b/src/main/java/com/imyeyu/api/modules/user/entity/RolePermission.java new file mode 100644 index 0000000..241b877 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/user/entity/RolePermission.java @@ -0,0 +1,49 @@ +package com.imyeyu.api.modules.user.entity; + +import com.imyeyu.api.modules.user.bean.BootstrapData; +import com.imyeyu.spring.annotation.table.AutoUUID; +import com.imyeyu.spring.annotation.table.DeleteColumn; +import com.imyeyu.spring.annotation.table.Id; +import com.imyeyu.spring.annotation.table.Transient; +import com.imyeyu.spring.entity.Creatable; +import com.imyeyu.spring.entity.Deletable; +import com.imyeyu.spring.entity.IDEntity; +import lombok.Data; + +/** + * 角色权限 + * + * @author 夜雨 + * @since 2026-05-13 14:40 + */ +@Data +public class RolePermission implements IDEntity, Creatable, Deletable { + + /** 主键 */ + @Id + @AutoUUID + protected String id; + + /** 角色 ID */ + protected String roleId; + + /** 权限 ID */ + protected String permissionId; + + /** 创建时间 */ + protected Long createdAt; + + /** 删除时间 */ + @DeleteColumn + protected Long deletedAt; + + @Transient + protected Role role; + + @Transient + protected Permission permission; + + public boolean isProtected() { + return BootstrapData.isProtectedRole(role) && BootstrapData.isProtectedPermissionCode(permission.getCode()); + } +} diff --git a/src/main/java/com/imyeyu/api/modules/user/entity/RoleRelation.java b/src/main/java/com/imyeyu/api/modules/user/entity/RoleRelation.java new file mode 100644 index 0000000..2073fa9 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/user/entity/RoleRelation.java @@ -0,0 +1,37 @@ +package com.imyeyu.api.modules.user.entity; + +import com.imyeyu.spring.annotation.table.AutoUUID; +import com.imyeyu.spring.annotation.table.DeleteColumn; +import com.imyeyu.spring.annotation.table.Id; +import com.imyeyu.spring.entity.Creatable; +import com.imyeyu.spring.entity.Deletable; +import com.imyeyu.spring.entity.IDEntity; +import lombok.Data; + +/** + * 角色关联 + * + * @author 夜雨 + * @since 2026-05-13 14:40 + */ +@Data +public class RoleRelation implements IDEntity, Creatable, Deletable { + + /** 主键 */ + @Id + @AutoUUID + private String id; + + /** 上级角色 ID */ + private String parentRoleId; + + /** 子级角色 ID */ + private String childRoleId; + + /** 创建时间 */ + private Long createdAt; + + /** 删除时间 */ + @DeleteColumn + private Long deletedAt; +} diff --git a/src/main/java/com/imyeyu/api/modules/user/entity/User.java b/src/main/java/com/imyeyu/api/modules/user/entity/User.java new file mode 100644 index 0000000..2b7fe33 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/user/entity/User.java @@ -0,0 +1,156 @@ +package com.imyeyu.api.modules.user.entity; + +import com.fasterxml.jackson.annotation.JsonView; +import com.imyeyu.api.modules.common.bean.ImageType; +import com.imyeyu.api.modules.common.entity.Attachment; +import com.imyeyu.api.modules.common.entity.Setting; +import com.imyeyu.api.modules.user.bean.Gender; +import com.imyeyu.spring.annotation.table.Transient; +import com.imyeyu.spring.entity.UUIDEntity; +import com.imyeyu.spring.util.ResponseView; +import com.imyeyu.utils.Time; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.List; + +/** + * 用户 + * + * @author 夜雨 + * @since 2021-03-01 17:11 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class User extends UUIDEntity { + + /** + * + * + * @author 夜雨 + * @since 2024-02-21 14:48 + */ + public enum AttachType { + + AVATAR, + + WRAPPER, + + LICENSE, + + DEFAULT_AVATAR, + + DEFAULT_WRAPPER + } + + /** 用户名 */ + @JsonView(ResponseView.Public.class) + protected String name; + + /** 昵称 */ + @JsonView(ResponseView.Public.class) + protected String nick; + + /** 密码 */ + @JsonView(ResponseView.Admin.class) + protected String password; + + /** 邮箱 */ + @JsonView(ResponseView.Public.class) + protected String email; + + /** 邮箱验证时间 */ + @JsonView(ResponseView.Admin.class) + protected Long emailVerifyAt; + + /** 手机号 */ + @JsonView(ResponseView.Public.class) + protected String phoneNo; + + /** 手机号验证时间 */ + @JsonView(ResponseView.Admin.class) + protected Long phoneNoVerifyAt; + + /** 背景图渲染算法 */ + @JsonView(ResponseView.Public.class) + protected ImageType wrapperType; + + /** 头像渲染算法 */ + @JsonView(ResponseView.Public.class) + protected ImageType avatarType; + + /** 经验值 */ + @JsonView(ResponseView.Public.class) + protected Integer exp; + + /** 性别 */ + @JsonView(ResponseView.Public.class) + protected Gender gender; + + /** 出生日期 */ + @JsonView(ResponseView.Public.class) + protected Long birthdate; + + /** QQ */ + @JsonView(ResponseView.Public.class) + protected String qq; + + /** 说明 */ + @JsonView(ResponseView.Public.class) + protected String description; + + /** 上次登录 IP */ + @JsonView(ResponseView.Admin.class) + protected String lastLoginIP; + + /** 上次登录时间 */ + @JsonView(ResponseView.Admin.class) + protected Long lastLoginAt; + + /** 解除禁言时间 */ + @JsonView(ResponseView.Admin.class) + protected Long unmuteAt; + + /** 解除封禁时间 */ + @JsonView(ResponseView.Admin.class) + protected Long unbanAt; + + @Transient + @JsonView(ResponseView.Public.class) + protected List roleList; + + @Transient + @JsonView(ResponseView.Public.class) + protected List permissionList; + + @Transient + @JsonView(ResponseView.Public.class) + protected List attachmentList; + + @Transient + @JsonView(ResponseView.Public.class) + protected List settingList; + + @Transient + @JsonView(ResponseView.Public.class) + protected List tempFileIdList; + + /** @return true 为禁言中 */ + public boolean isMuting() { + return unmuteAt != null && Time.now() < unmuteAt; + } + + /** @return true 为封禁中 */ + public boolean isBanning() { + return unbanAt != null && Time.now() < unbanAt; + } + + public boolean isEmailVerified() { + return emailVerifyAt != null; + } + + /** @return true 为手机号已验证 */ + public boolean isPhoneNoVerified() { + return phoneNoVerifyAt != null; + } +} diff --git a/src/main/java/com/imyeyu/api/modules/user/entity/UserBehaviorRecord.java b/src/main/java/com/imyeyu/api/modules/user/entity/UserBehaviorRecord.java new file mode 100644 index 0000000..a26328d --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/user/entity/UserBehaviorRecord.java @@ -0,0 +1,56 @@ +package com.imyeyu.api.modules.user.entity; + +import com.imyeyu.api.modules.common.entity.Comment; +import com.imyeyu.spring.annotation.table.AutoUUID; +import com.imyeyu.spring.annotation.table.DeleteColumn; +import com.imyeyu.spring.annotation.table.Id; +import com.imyeyu.spring.annotation.table.Transient; +import com.imyeyu.spring.entity.Creatable; +import com.imyeyu.spring.entity.Deletable; +import com.imyeyu.spring.entity.IDEntity; +import lombok.Data; + +/** + * 用户行为记录 + * + * @author 夜雨 + * @since 2026-05-27 17:10 + */ +@Data +public class UserBehaviorRecord implements IDEntity, Creatable, Deletable { + + /** 主键 */ + @Id + @AutoUUID + private String id; + + /** 业务类型 */ + private String bizType; + + /** 用户 ID */ + private String userId; + + /** 目标类型 */ + private String targetType; + + /** 目标 ID */ + private String targetId; + + /** 行为类型 */ + private String actionType; + + /** 创建时间 */ + private Long createdAt; + + /** 删除时间 */ + @DeleteColumn + private Long deletedAt; + + /** 用户信息 */ + @Transient + private User user; + + /** 评论信息 */ + @Transient + private Comment comment; +} diff --git a/src/main/java/com/imyeyu/api/modules/user/entity/UserRole.java b/src/main/java/com/imyeyu/api/modules/user/entity/UserRole.java new file mode 100644 index 0000000..01a4e94 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/user/entity/UserRole.java @@ -0,0 +1,37 @@ +package com.imyeyu.api.modules.user.entity; + +import com.imyeyu.spring.annotation.table.AutoUUID; +import com.imyeyu.spring.annotation.table.DeleteColumn; +import com.imyeyu.spring.annotation.table.Id; +import com.imyeyu.spring.entity.Creatable; +import com.imyeyu.spring.entity.Deletable; +import com.imyeyu.spring.entity.IDEntity; +import lombok.Data; + +/** + * 用户角色 + * + * @author 夜雨 + * @since 2026-05-13 14:40 + */ +@Data +public class UserRole implements IDEntity, Creatable, Deletable { + + /** 主键 */ + @Id + @AutoUUID + private String id; + + /** 用户 ID */ + private String userId; + + /** 角色 ID */ + private String roleId; + + /** 创建时间 */ + private Long createdAt; + + /** 删除时间 */ + @DeleteColumn + private Long deletedAt; +} diff --git a/src/main/java/com/imyeyu/api/modules/user/mapper/PermissionMapper.java b/src/main/java/com/imyeyu/api/modules/user/mapper/PermissionMapper.java new file mode 100644 index 0000000..769f38f --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/user/mapper/PermissionMapper.java @@ -0,0 +1,17 @@ +package com.imyeyu.api.modules.user.mapper; + +import com.imyeyu.api.modules.user.entity.Permission; +import com.imyeyu.spring.mapper.BaseMapper; + +import java.util.List; + +/** + * 权限 Mapper + * + * @author 夜雨 + * @since 2026-05-13 14:46 + */ +public interface PermissionMapper extends BaseMapper { + + List selectByIdList(List list); +} diff --git a/src/main/java/com/imyeyu/api/modules/user/mapper/RoleMapper.java b/src/main/java/com/imyeyu/api/modules/user/mapper/RoleMapper.java new file mode 100644 index 0000000..459e5f4 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/user/mapper/RoleMapper.java @@ -0,0 +1,23 @@ +package com.imyeyu.api.modules.user.mapper; + +import com.imyeyu.api.bean.ModuleCode; +import com.imyeyu.api.modules.user.entity.Role; +import com.imyeyu.spring.mapper.BaseMapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 角色 Mapper + * + * @author 夜雨 + * @since 2026-05-13 14:46 + */ +public interface RoleMapper extends BaseMapper { + + List selectByIdList(List idList); + + List selectByModuleCodeAndCodeList(@Param("moduleCode") ModuleCode moduleCode, @Param("codeList") List codeList); + + List selectByModuleCode(@Param("moduleCode") ModuleCode moduleCode); +} diff --git a/src/main/java/com/imyeyu/api/modules/user/mapper/RolePermissionMapper.java b/src/main/java/com/imyeyu/api/modules/user/mapper/RolePermissionMapper.java new file mode 100644 index 0000000..f9e28d0 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/user/mapper/RolePermissionMapper.java @@ -0,0 +1,28 @@ +package com.imyeyu.api.modules.user.mapper; + +import com.imyeyu.api.modules.user.entity.Permission; +import com.imyeyu.api.modules.user.entity.RolePermission; +import com.imyeyu.spring.mapper.BaseMapper; + +import java.util.List; + +/** + * 角色权限 Mapper + * + * @author 夜雨 + * @since 2026-05-13 14:46 + */ +public interface RolePermissionMapper extends BaseMapper { + + default List selectByRoleId(String roleId) { + return selectByRoleIdList(List.of(roleId)); + } + + List selectByRoleIdList(List roleIdList); + + List selectPermissionByRoleIdList(List roleIdList); + + void createBatch(List rolePermissionList); + + void deleteByPermissionIdList(String roleId, List permissionIdList); +} diff --git a/src/main/java/com/imyeyu/api/modules/user/mapper/RoleRelationMapper.java b/src/main/java/com/imyeyu/api/modules/user/mapper/RoleRelationMapper.java new file mode 100644 index 0000000..546f55b --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/user/mapper/RoleRelationMapper.java @@ -0,0 +1,25 @@ +package com.imyeyu.api.modules.user.mapper; + +import com.imyeyu.api.modules.user.entity.RoleRelation; +import com.imyeyu.spring.mapper.BaseMapper; + +import java.util.List; + +/** + * 角色关联 Mapper + * + * @author 夜雨 + * @since 2026-05-13 14:46 + */ +public interface RoleRelationMapper extends BaseMapper { + + List selectByParentRoleId(String parentRoleId); + + List selectChildRoleIdListByParentRoleId(String parentRoleId); + + List selectChildRoleIdListByParentRoleIdList(List parentRoleIdList); + + void createBatch(List roleRelationList); + + void deleteByChildRoleIdList(String parentRoleId, List childRoleIdList); +} diff --git a/src/main/java/com/imyeyu/api/modules/user/mapper/UserBehaviorRecordMapper.java b/src/main/java/com/imyeyu/api/modules/user/mapper/UserBehaviorRecordMapper.java new file mode 100644 index 0000000..ffe9af7 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/user/mapper/UserBehaviorRecordMapper.java @@ -0,0 +1,39 @@ +package com.imyeyu.api.modules.user.mapper; + +import com.imyeyu.api.modules.user.entity.UserBehaviorRecord; +import com.imyeyu.spring.mapper.BaseMapper; + +import java.util.List; + +/** + * 用户行为记录 Mapper + * + * @author 夜雨 + * @since 2026-05-27 17:10 + */ +public interface UserBehaviorRecordMapper extends BaseMapper { + + /** + * 查询有效行为记录 + * + * @param bizType 业务类型 + * @param userId 用户 ID + * @param targetType 目标类型 + * @param targetId 目标 ID + * @param actionType 行为类型 + * @return 行为记录,不存在时返回 {@code null} + */ + UserBehaviorRecord selectActive(String bizType, String userId, String targetType, String targetId, String actionType); + + /** + * 查询用户有效行为目标 ID 列表 + * + * @param bizType 业务类型 + * @param userId 用户 ID + * @param targetType 目标类型 + * @param actionType 行为类型 + * @param targetIdList 目标 ID 列表 + * @return 目标 ID 列表 + */ + List selectActiveTargetIdList(String bizType, String userId, String targetType, String actionType, List targetIdList); +} diff --git a/src/main/java/com/imyeyu/api/modules/user/mapper/UserMapper.java b/src/main/java/com/imyeyu/api/modules/user/mapper/UserMapper.java new file mode 100644 index 0000000..feefe9f --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/user/mapper/UserMapper.java @@ -0,0 +1,36 @@ +package com.imyeyu.api.modules.user.mapper; + +import com.imyeyu.api.modules.user.entity.User; +import com.imyeyu.spring.mapper.BaseMapper; +import org.apache.ibatis.annotations.Select; + +import java.util.List; + +/** + * 用户 + * + * @author 夜雨 + * @since 2021-02-23 21:33 + */ +public interface UserMapper extends BaseMapper { + + @Select("SELECT * FROM `user` WHERE `email` = #{email} AND `email_verify_at` IS NULL" + NOT_DELETE + LIMIT_1) + User selectByEmail(String email); + + @Select("SELECT * FROM `user` WHERE `email` = #{email} AND `email_verify_at` < " + UNIX_TIME + NOT_DELETE + LIMIT_1) + User selectByVerifiedEmail(String email); + + @Select("SELECT * FROM `user` WHERE `phone_no` = #{phoneNo}" + NOT_DELETE + LIMIT_1) + User selectByPhoneNo(String phoneNo); + + @Select("SELECT * FROM `user` WHERE `phone_no` = #{phoneNo} AND `phone_no_verify_at` < " + UNIX_TIME + NOT_DELETE + LIMIT_1) + User selectByVerifiedPhoneNo(String phoneNo); + + /** + * 根据 ID 列表查询用户 + * + * @param idList 用户 ID 列表 + * @return 用户列表 + */ + List listByIdList(List idList); +} diff --git a/src/main/java/com/imyeyu/api/modules/user/mapper/UserRoleMapper.java b/src/main/java/com/imyeyu/api/modules/user/mapper/UserRoleMapper.java new file mode 100644 index 0000000..729e0fc --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/user/mapper/UserRoleMapper.java @@ -0,0 +1,19 @@ +package com.imyeyu.api.modules.user.mapper; + +import com.imyeyu.api.modules.user.entity.UserRole; +import com.imyeyu.spring.mapper.BaseMapper; + +import java.util.List; + +/** + * 用户角色 Mapper + * + * @author 夜雨 + * @since 2026-05-13 14:46 + */ +public interface UserRoleMapper extends BaseMapper { + + void createBatch(List userRoleList); + + void deleteByRoleIdList(String userId, List roleIdList); +} diff --git a/src/main/java/com/imyeyu/api/modules/user/service/PermissionChecker.java b/src/main/java/com/imyeyu/api/modules/user/service/PermissionChecker.java new file mode 100644 index 0000000..3a00e04 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/user/service/PermissionChecker.java @@ -0,0 +1,56 @@ +package com.imyeyu.api.modules.user.service; + +import com.imyeyu.api.bean.ModuleCode; + +import java.util.Set; + +/** + * 权限检查器 + * + * @author 夜雨 + * @since 2026-06-05 17:45 + */ +public interface PermissionChecker { + + /** + * 查询当前用户权限码集合 + * + * @return 权限码集合 + */ + Set codeSet(); + + /** + * 查询指定用户权限码集合 + * + * @param userId 用户 ID + * @return 权限码集合 + */ + Set codeSet(String userId); + + /// 判断当前用户是否拥有全部权限 + /// + /// @param moduleCode 模块代码 + /// @param codeList 权限代码列表 + /// @return 是否拥有 + boolean hasAll(ModuleCode moduleCode, String... codeList); + + /// 判断当前用户是否拥有任一权限 + /// + /// @param moduleCode 模块代码 + /// @param codeList 权限代码列表 + /// @return 是否拥有 + boolean hasAny(ModuleCode moduleCode, String... codeList); + + /// 校验当前用户拥有全部权限 + /// + /// @param moduleCode 模块代码 + /// @param codeList 权限代码列表 + void checkAll(ModuleCode moduleCode, String... codeList); + + /// 校验当前用户拥有任一权限 + /// + /// @param moduleCode 模块代码 + /// @param codeList 权限代码列表 + void checkAny(ModuleCode moduleCode, String... codeList); + +} diff --git a/src/main/java/com/imyeyu/api/modules/user/service/PermissionService.java b/src/main/java/com/imyeyu/api/modules/user/service/PermissionService.java new file mode 100644 index 0000000..3dac03e --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/user/service/PermissionService.java @@ -0,0 +1,33 @@ +package com.imyeyu.api.modules.user.service; + +import com.imyeyu.api.modules.user.entity.Permission; +import com.imyeyu.api.modules.user.vo.permission.BatchCreateReq; +import com.imyeyu.spring.service.BaseService; + +import java.util.List; + +/// 权限服务 +/// +/// @author 夜雨 +/// @since 2026-05-13 14:46 +public interface PermissionService extends BaseService { + + /// 批量创建权限 + /// + /// @param req 批量创建请求 + void create(BatchCreateReq req); + + /// 查询角色权限 + /// + /// @param roleId 角色 ID + /// @return 权限列表 + default List listByRoleId(String roleId) { + return listByRoleId(List.of(roleId)); + } + + /// 查询多个角色的权限并集 + /// + /// @param roleIdList 角色 ID 列表 + /// @return 权限列表 + List listByRoleId(List roleIdList); +} diff --git a/src/main/java/com/imyeyu/api/modules/user/service/RoleChecker.java b/src/main/java/com/imyeyu/api/modules/user/service/RoleChecker.java new file mode 100644 index 0000000..57b790b --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/user/service/RoleChecker.java @@ -0,0 +1,44 @@ +package com.imyeyu.api.modules.user.service; + +import com.imyeyu.api.bean.ModuleCode; + +import java.util.Set; + +/// +/// 角色检查器 +/// +/// @author 夜雨 +/// @since 2026-06-05 17:45 +public interface RoleChecker { + + /// 查询当前用户角色代码集合 + /// + /// @return 角色代码集合,格式为 MODULE:ROLE + Set codeSet(); + + /// 判断当前用户是否拥有全部角色 + /// + /// @param moduleCode 模块代码 + /// @param roleCodeList 角色代码列表 + /// @return 是否拥有 + boolean hasAll(ModuleCode moduleCode, String... roleCodeList); + + /// 判断当前用户是否拥有任一角色 + /// + /// @param moduleCode 模块代码 + /// @param roleCodeList 角色代码列表 + /// @return 是否拥有 + boolean hasAny(ModuleCode moduleCode, String... roleCodeList); + + /// 校验当前用户拥有全部角色 + /// + /// @param moduleCode 模块代码 + /// @param roleCodeList 角色代码列表 + void checkAll(ModuleCode moduleCode, String... roleCodeList); + + /// 校验当前用户拥有任一角色 + /// + /// @param moduleCode 模块代码 + /// @param roleCodeList 角色代码列表 + void checkAny(ModuleCode moduleCode, String... roleCodeList); +} diff --git a/src/main/java/com/imyeyu/api/modules/user/service/RoleRelationService.java b/src/main/java/com/imyeyu/api/modules/user/service/RoleRelationService.java new file mode 100644 index 0000000..199adba --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/user/service/RoleRelationService.java @@ -0,0 +1,31 @@ +package com.imyeyu.api.modules.user.service; + +import com.imyeyu.api.modules.user.entity.RoleRelation; +import com.imyeyu.spring.service.BaseService; + +import java.util.List; + +/// 角色继承关系服务 +/// +/// @author 夜雨 +/// @since 2026-05-13 14:46 +public interface RoleRelationService extends BaseService { + + /// 查询直属子角色关联 + /// + /// @param parentRoleId 父角色 ID + /// @return 角色关联列表 + List listByParentRoleId(String parentRoleId); + + /// 查询直属子角色 ID 列表 + /// + /// @param parentRoleId 父角色 ID + /// @return 子角色 ID 列表 + List listChildRoleIdByParentRoleId(String parentRoleId); + + /// 递归展开全部子角色 ID + /// + /// @param parentRoleIdList 父角色 ID 列表 + /// @return 全部子角色 ID 列表 + List listAllChildRoleIdByParentRoleIdList(List parentRoleIdList); +} diff --git a/src/main/java/com/imyeyu/api/modules/user/service/RoleService.java b/src/main/java/com/imyeyu/api/modules/user/service/RoleService.java new file mode 100644 index 0000000..eda63dd --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/user/service/RoleService.java @@ -0,0 +1,83 @@ +package com.imyeyu.api.modules.user.service; + +import com.imyeyu.api.bean.ModuleCode; +import com.imyeyu.api.modules.user.entity.Permission; +import com.imyeyu.api.modules.user.entity.Role; +import com.imyeyu.api.modules.user.vo.role.UserRoleAuthorizeReq; +import com.imyeyu.spring.service.BaseService; + +import java.util.List; + +/// 角色服务 +/// +/// @author 夜雨 +/// @since 2026-05-13 14:46 +public interface RoleService extends BaseService { + + /// 给用户授权角色 + /// + /// @param req 用户角色授权请求 + void authorizeUserRole(UserRoleAuthorizeReq req); + + /// 查询用户直属角色 + /// + /// @param userId 用户 ID + /// @return 角色列表 + List listByUserId(String userId); + + /// 查询用户指定模块直属角色 + /// + /// @param userId 用户 ID + /// @param moduleCode 模块代码 + /// @return 角色列表 + List listByUserId(String userId, ModuleCode moduleCode); + + /// 查询模块角色 + /// + /// @param moduleCode 模块代码 + /// @return 角色列表 + List listByModuleCode(ModuleCode moduleCode); + + /// 查询用户直属角色编码 + /// + /// @param userId 用户 ID + /// @return 角色编码列表,格式为 MODULE:ROLE + default List listCodeByUserId(String userId) { + return listByUserId(userId).stream().map(role -> "%s:%s".formatted(role.getModuleCode().name(), role.getCode())).toList(); + } + + /// 查询用户全部角色编码 + /// + /// @param userId 用户 ID + /// @return 包含子角色的角色编码列表,格式为 MODULE:ROLE + List listAllCodeByUserId(String userId); + + /// 查询用户全部权限 + /// + /// @param userId 用户 ID + /// @return 权限列表 + List listAllPermissionByUserId(String userId); + + /// 查询用户全部权限编码 + /// + /// @param userId 用户 ID + /// @return 权限编码列表,格式为 MODULE:PERMISSION + default List listAllPermissionCodeByUserId(String userId) { + return listAllPermissionByUserId(userId).stream() + .map(permission -> "%s:%s".formatted(permission.getModuleCode().name(), permission.getCode())) + .distinct() + .toList(); + } + + /// 查询直属子角色 + /// + /// @param parentRoleId 父角色 ID + /// @return 子角色列表 + List listChildByParentRoleId(String parentRoleId); + + /// 查询角色全部权限 + /// + /// @param roleId 角色 ID + /// @return 权限列表 + List listPermissionByRoleId(String roleId); +} diff --git a/src/main/java/com/imyeyu/api/modules/user/service/UserBehaviorRecordService.java b/src/main/java/com/imyeyu/api/modules/user/service/UserBehaviorRecordService.java new file mode 100644 index 0000000..bf423f2 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/user/service/UserBehaviorRecordService.java @@ -0,0 +1,48 @@ +package com.imyeyu.api.modules.user.service; + +import com.imyeyu.api.modules.user.entity.UserBehaviorRecord; +import com.imyeyu.spring.service.BaseService; + +import java.util.List; +import java.util.Set; + +/** + * 用户行为记录服务 + * + * @author 夜雨 + * @since 2026-05-27 17:10 + */ +public interface UserBehaviorRecordService extends BaseService { + + /** + * 记录用户行为 + * + * @param entity 行为记录 + * @return true 为新增记录 + */ + boolean record(UserBehaviorRecord entity); + + /** + * 取消用户行为 + * + * @param bizType 业务类型 + * @param userId 用户 ID + * @param targetType 目标类型 + * @param targetId 目标 ID + * @param actionType 行为类型 + * @return true 为已取消记录 + */ + boolean cancel(String bizType, String userId, String targetType, String targetId, String actionType); + + /** + * 查询用户有效行为目标 ID 集合 + * + * @param bizType 业务类型 + * @param userId 用户 ID + * @param targetType 目标类型 + * @param actionType 行为类型 + * @param targetIdList 目标 ID 列表 + * @return 目标 ID 集合 + */ + Set getActiveTargetIdSet(String bizType, String userId, String targetType, String actionType, List targetIdList); +} diff --git a/src/main/java/com/imyeyu/api/modules/user/service/UserLoginService.java b/src/main/java/com/imyeyu/api/modules/user/service/UserLoginService.java new file mode 100644 index 0000000..cd79a0f --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/user/service/UserLoginService.java @@ -0,0 +1,76 @@ +package com.imyeyu.api.modules.user.service; + +import com.imyeyu.api.modules.user.entity.User; +import com.imyeyu.api.modules.user.vo.LoginRequest; +import com.imyeyu.api.modules.user.vo.LoginResponse; + +/** + * 用户登录服务 + * + * @author 夜雨 + * @since 2026-06-09 + */ +public interface UserLoginService { + + /** + * 用户登录 + * + * @param req 登录请求 + * @return 登录响应 + */ + LoginResponse login(LoginRequest req); + + LoginResponse login(String token, String userId); + + /** + * 根据令牌获取用户 + * + * @return 用户 + */ + User getLoginUser(); + + /** + * 根据令牌获取必须有效登录的用户,否则抛出错误 + * + * @return 用户 + */ + User getRequireLoginUser(); + + default String getRequireLoginUserId() { + return getRequireLoginUser().getId(); + } + + /** + * 用户登出 + * + */ + void logout(); + + /** + * 更新当前用户密码 + * + */ + void updatePassword(String oldPassword, String newPassword); + + /** + * 发送手机号验证码 + * + * @param phoneNo 手机号 + */ + void sendPhoneVerifyCode(String phoneNo); + + /** + * 验证当前用户手机号 + * + * @param phoneNo 手机号 + * @param verifyCode 验证码 + */ + void verifyPhoneNo(String phoneNo, String verifyCode); + + /** + * 注销当前用户 + * + * @param password 当前密码 + */ + void deactivate(String password); +} diff --git a/src/main/java/com/imyeyu/api/modules/user/service/UserRoleService.java b/src/main/java/com/imyeyu/api/modules/user/service/UserRoleService.java new file mode 100644 index 0000000..e2e3ee3 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/user/service/UserRoleService.java @@ -0,0 +1,39 @@ +package com.imyeyu.api.modules.user.service; + +import com.imyeyu.api.modules.user.entity.UserRole; +import com.imyeyu.spring.service.BaseService; + +import java.util.List; + +/// 用户角色绑定服务 +/// +/// @author 夜雨 +/// @since 2026-05-13 14:46 +public interface UserRoleService extends BaseService { + + /// 批量创建用户角色绑定 + /// + /// @param list 用户角色绑定列表 + void createBatch(List list); + + /// 查询用户角色绑定 + /// + /// @param userId 用户 ID + /// @return 用户角色绑定列表 + List listByUserId(String userId); + + /// 查询用户直属角色 ID + /// + /// @param userId 用户 ID + /// @return 角色 ID 列表 + default List listRoleIdByUserId(String userId) { + List bindingList = listByUserId(userId); + return bindingList.stream().map(UserRole::getRoleId).toList(); + } + + /// 删除用户指定角色绑定 + /// + /// @param userId 用户 ID + /// @param roleIdList 角色 ID 列表 + void deleteByRoleIdList(String userId, List roleIdList); +} diff --git a/src/main/java/com/imyeyu/api/modules/user/service/UserService.java b/src/main/java/com/imyeyu/api/modules/user/service/UserService.java new file mode 100644 index 0000000..465ab0e --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/user/service/UserService.java @@ -0,0 +1,23 @@ +package com.imyeyu.api.modules.user.service; + +import com.imyeyu.api.modules.user.entity.User; +import com.imyeyu.spring.service.BaseService; + +/** + * 用户管理服务 + * + * @author 夜雨 + * @since 2021-02-23 21:32 + */ +public interface UserService extends BaseService { + + User getByName(String name); + + User getByEmail(String email); + + User getByVerifiedEmail(String email); + + User getByPhoneNo(String phoneNo); + + User getByVerifiedPhoneNo(String phoneNo); +} diff --git a/src/main/java/com/imyeyu/api/modules/user/service/implement/PermissionCheckerImplement.java b/src/main/java/com/imyeyu/api/modules/user/service/implement/PermissionCheckerImplement.java new file mode 100644 index 0000000..a975026 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/user/service/implement/PermissionCheckerImplement.java @@ -0,0 +1,75 @@ +package com.imyeyu.api.modules.user.service.implement; + +import com.imyeyu.api.bean.ModuleCode; +import com.imyeyu.api.modules.user.service.PermissionChecker; +import com.imyeyu.api.modules.user.service.RoleService; +import com.imyeyu.api.modules.user.service.UserLoginService; +import com.imyeyu.java.bean.timi.TimiCode; +import com.imyeyu.java.bean.timi.TimiException; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +/** + * 权限检查器实现 + * + * @author 夜雨 + * @since 2026-06-05 17:45 + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class PermissionCheckerImplement implements PermissionChecker { + + private final RoleService roleService; + private final UserLoginService userLoginService; + + public Set codeSet() { + return codeSet(userLoginService.getRequireLoginUserId()); + } + + @Override + public Set codeSet(String userId) { + return new HashSet<>(roleService.listAllPermissionCodeByUserId(userId)); + } + + @Override + public boolean hasAll(ModuleCode moduleCode, String... permissionCodeList) { + Set codeSet = codeSet(); + return Arrays.stream(permissionCodeList).allMatch(permissionCode -> codeSet.contains(buildPermissionKey(moduleCode, permissionCode))); + } + + @Override + public boolean hasAny(ModuleCode moduleCode, String... permissionCodeList) { + Set codeSet = codeSet(); + return Arrays.stream(permissionCodeList).anyMatch(permissionCode -> codeSet.contains(buildPermissionKey(moduleCode, permissionCode))); + } + + @Override + public void checkAll(ModuleCode moduleCode, String... permissionCodeList) { + if (!hasAll(moduleCode, permissionCodeList)) { + String codeList = buildPermissionKeys(moduleCode, permissionCodeList).toString(); + throw new TimiException(TimiCode.PERMISSION_ERROR).msgKey("权限不足:%s".formatted(codeList)); + } + } + + @Override + public void checkAny(ModuleCode moduleCode, String... permissionCodeList) { + if (!hasAny(moduleCode, permissionCodeList)) { + String codeList = buildPermissionKeys(moduleCode, permissionCodeList).toString(); + throw new TimiException(TimiCode.PERMISSION_ERROR).msgKey("权限不足:%s".formatted(codeList)); + } + } + + private String buildPermissionKey(ModuleCode moduleCode, String permissionCode) { + return "%s:%s".formatted(moduleCode.name(), permissionCode); + } + + private Set buildPermissionKeys(ModuleCode moduleCode, String... permissionCodeList) { + return Arrays.stream(permissionCodeList).map(permissionCode -> buildPermissionKey(moduleCode, permissionCode)).collect(java.util.stream.Collectors.toSet()); + } +} diff --git a/src/main/java/com/imyeyu/api/modules/user/service/implement/PermissionServiceImplement.java b/src/main/java/com/imyeyu/api/modules/user/service/implement/PermissionServiceImplement.java new file mode 100644 index 0000000..cfc1922 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/user/service/implement/PermissionServiceImplement.java @@ -0,0 +1,87 @@ +package com.imyeyu.api.modules.user.service.implement; + +import com.imyeyu.api.config.dbsource.TimiServerDBConfig; +import com.imyeyu.api.modules.common.mapper.MultilingualMapper; +import com.imyeyu.api.modules.common.service.MultilingualService; +import com.imyeyu.api.modules.user.entity.Permission; +import com.imyeyu.api.modules.user.mapper.PermissionMapper; +import com.imyeyu.api.modules.user.mapper.RolePermissionMapper; +import com.imyeyu.api.modules.user.service.PermissionService; +import com.imyeyu.api.modules.user.vo.permission.BatchCreateReq; +import com.imyeyu.api.util.RedisMultilingual; +import com.imyeyu.java.TimiJava; +import com.imyeyu.spring.TimiSpring; +import com.imyeyu.spring.mapper.BaseMapper; +import com.imyeyu.spring.service.AbstractEntityService; +import com.imyeyu.utils.Time; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +/// 权限服务实现 +/// +/// @author 夜雨 +/// @since 2026-05-13 14:46 +@Service +@RequiredArgsConstructor +public class PermissionServiceImplement extends AbstractEntityService implements PermissionService { + + private final PermissionMapper mapper; + private final RolePermissionMapper rolePermissionMapper; + + private final MultilingualMapper multilingualMapper; + private final MultilingualService multilingualService; + private final RedisMultilingual multilingual; + + @Override + protected BaseMapper mapper() { + return mapper; + } + + @Transactional(TimiServerDBConfig.ROLLBACKER) + @Override + public void create(Permission permission) { + permission.setNameLangId(multilingualService.create(permission.getName())); + super.create(permission); + } + + @Transactional(TimiServerDBConfig.ROLLBACKER) + @Override + public void create(BatchCreateReq req) { + if (TimiJava.isEmpty(req.getTypes())) { + return; + } + List permissionList = new ArrayList<>(); + for (BatchCreateReq.Type type : req.getTypes()) { + Permission permission = new Permission(); + permission.setId(UUID.randomUUID().toString()); + permission.setModuleCode(req.getModuleCode()); + permission.setCode(req.getPrefixCode() + "_" + type.name()); + permission.setName(req.getPrefixName() + multilingual.map(TimiSpring.getLanguage()).text(type.name().toLowerCase())); + permission.setCreatedAt(Time.now()); + permissionList.add(permission); + } + for (Permission permission : permissionList) { + super.create(permission); + } + } + + @Transactional(TimiServerDBConfig.ROLLBACKER) + @Override + public void update(Permission permission) { + permission.setNameLangId(multilingualService.createIfDifferent(permission.getNameLangId(), permission.getName())); + mapper.updateSelective(permission); + } + + @Override + public List listByRoleId(List roleIdList) { + if (TimiJava.isEmpty(roleIdList)) { + return List.of(); + } + return rolePermissionMapper.selectPermissionByRoleIdList(roleIdList); + } +} diff --git a/src/main/java/com/imyeyu/api/modules/user/service/implement/RoleCheckerImplement.java b/src/main/java/com/imyeyu/api/modules/user/service/implement/RoleCheckerImplement.java new file mode 100644 index 0000000..243e2e1 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/user/service/implement/RoleCheckerImplement.java @@ -0,0 +1,68 @@ +package com.imyeyu.api.modules.user.service.implement; + +import com.imyeyu.api.bean.ModuleCode; +import com.imyeyu.api.modules.user.service.RoleChecker; +import com.imyeyu.api.modules.user.service.RoleService; +import com.imyeyu.api.modules.user.service.UserLoginService; +import com.imyeyu.java.bean.timi.TimiCode; +import com.imyeyu.java.bean.timi.TimiException; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +/// +/// 角色检查器实现 +/// +/// @author 夜雨 +/// @since 2026-06-05 17:45 +@Slf4j +@Service +@RequiredArgsConstructor +public class RoleCheckerImplement implements RoleChecker { + + private final RoleService roleService; + private final UserLoginService userLoginService; + + @Override + public Set codeSet() { + return new HashSet<>(roleService.listAllCodeByUserId(userLoginService.getRequireLoginUserId())); + } + + @Override + public boolean hasAll(ModuleCode moduleCode, String... roleCodeList) { + Set codeSet = codeSet(); + return Arrays.stream(roleCodeList).allMatch(roleCode -> codeSet.contains(buildRoleKey(moduleCode, roleCode))); + } + + @Override + public boolean hasAny(ModuleCode moduleCode, String... roleCodeList) { + Set codeSet = codeSet(); + return Arrays.stream(roleCodeList).anyMatch(roleCode -> codeSet.contains(buildRoleKey(moduleCode, roleCode))); + } + + @Override + public void checkAll(ModuleCode moduleCode, String... roleCodeList) { + if (!hasAll(moduleCode, roleCodeList)) { + throw new TimiException(TimiCode.PERMISSION_ERROR).msgKey("require role: %s".formatted(buildRoleKeys(moduleCode, roleCodeList))); + } + } + + @Override + public void checkAny(ModuleCode moduleCode, String... roleCodeList) { + if (!hasAny(moduleCode, roleCodeList)) { + throw new TimiException(TimiCode.PERMISSION_ERROR).msgKey("require role: %s".formatted(buildRoleKeys(moduleCode, roleCodeList))); + } + } + + private String buildRoleKey(ModuleCode moduleCode, String roleCode) { + return "%s:%s".formatted(moduleCode.name(), roleCode); + } + + private Set buildRoleKeys(ModuleCode moduleCode, String... roleCodeList) { + return Arrays.stream(roleCodeList).map(roleCode -> buildRoleKey(moduleCode, roleCode)).collect(java.util.stream.Collectors.toSet()); + } +} diff --git a/src/main/java/com/imyeyu/api/modules/user/service/implement/RoleRelationServiceImplement.java b/src/main/java/com/imyeyu/api/modules/user/service/implement/RoleRelationServiceImplement.java new file mode 100644 index 0000000..a12f818 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/user/service/implement/RoleRelationServiceImplement.java @@ -0,0 +1,68 @@ +package com.imyeyu.api.modules.user.service.implement; + +import com.imyeyu.api.modules.user.entity.RoleRelation; +import com.imyeyu.api.modules.user.mapper.RoleRelationMapper; +import com.imyeyu.api.modules.user.service.RoleRelationService; +import com.imyeyu.java.TimiJava; +import com.imyeyu.spring.mapper.BaseMapper; +import com.imyeyu.spring.service.AbstractEntityService; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/// 角色关联服务实现 +/// +/// @author 夜雨 +/// @since 2026-05-13 14:46 +@Service +@RequiredArgsConstructor +public class RoleRelationServiceImplement extends AbstractEntityService implements RoleRelationService { + + private final RoleRelationMapper mapper; + + @Override + protected BaseMapper mapper() { + return mapper; + } + + @Override + public List listByParentRoleId(String parentRoleId) { + if (TimiJava.isEmpty(parentRoleId)) { + return List.of(); + } + RoleRelation example = new RoleRelation(); + example.setParentRoleId(parentRoleId); + return mapper.selectAllByExample(example); + } + + @Override + public List listChildRoleIdByParentRoleId(String parentRoleId) { + return listByParentRoleId(parentRoleId).stream().map(RoleRelation::getChildRoleId).distinct().toList(); + } + + @Override + public List listAllChildRoleIdByParentRoleIdList(List parentRoleIdList) { + if (TimiJava.isEmpty(parentRoleIdList)) { + return List.of(); + } + Set result = new HashSet<>(); + List currentRoleIdList = new ArrayList<>(parentRoleIdList); + while (TimiJava.isNotEmpty(currentRoleIdList)) { + List childRoleIdList = currentRoleIdList.stream() + .flatMap(roleId -> listChildRoleIdByParentRoleId(roleId).stream()) + .filter(childRoleId -> !result.contains(childRoleId)) + .distinct() + .toList(); + if (TimiJava.isEmpty(childRoleIdList)) { + break; + } + result.addAll(childRoleIdList); + currentRoleIdList = childRoleIdList; + } + return new ArrayList<>(result); + } +} diff --git a/src/main/java/com/imyeyu/api/modules/user/service/implement/RoleServiceImplement.java b/src/main/java/com/imyeyu/api/modules/user/service/implement/RoleServiceImplement.java new file mode 100644 index 0000000..09688f1 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/user/service/implement/RoleServiceImplement.java @@ -0,0 +1,294 @@ +package com.imyeyu.api.modules.user.service.implement; + +import com.imyeyu.api.bean.ModuleCode; +import com.imyeyu.api.config.dbsource.TimiServerDBConfig; +import com.imyeyu.api.modules.common.entity.Multilingual; +import com.imyeyu.api.modules.common.mapper.MultilingualMapper; +import com.imyeyu.api.modules.common.service.MultilingualService; +import com.imyeyu.api.modules.user.entity.Permission; +import com.imyeyu.api.modules.user.entity.Role; +import com.imyeyu.api.modules.user.entity.RolePermission; +import com.imyeyu.api.modules.user.entity.RoleRelation; +import com.imyeyu.api.modules.user.entity.UserRole; +import com.imyeyu.api.modules.user.mapper.RoleMapper; +import com.imyeyu.api.modules.user.mapper.RolePermissionMapper; +import com.imyeyu.api.modules.user.mapper.RoleRelationMapper; +import com.imyeyu.api.modules.user.service.PermissionService; +import com.imyeyu.api.modules.user.service.RoleService; +import com.imyeyu.api.modules.user.service.UserRoleService; +import com.imyeyu.api.modules.user.vo.role.UserRoleAuthorizeReq; +import com.imyeyu.java.TimiJava; +import com.imyeyu.java.bean.timi.TimiCode; +import com.imyeyu.java.bean.timi.TimiException; +import com.imyeyu.spring.mapper.BaseMapper; +import com.imyeyu.spring.service.AbstractEntityService; +import com.imyeyu.utils.Time; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.UUID; +import java.util.stream.Collectors; + +/// 角色服务实现 +/// +/// @author 夜雨 +/// @since 2026-05-13 14:46 +@Service +@RequiredArgsConstructor +public class RoleServiceImplement extends AbstractEntityService implements RoleService { + + private final UserRoleService userRoleService; + private final PermissionService permissionService; + private final MultilingualMapper multilingualMapper; + private final MultilingualService multilingualService; + + private final RoleMapper mapper; + private final RoleRelationMapper roleRelationMapper; + private final RolePermissionMapper rolePermissionMapper; + + @Override + protected BaseMapper mapper() { + return mapper; + } + + @Transactional(TimiServerDBConfig.ROLLBACKER) + @Override + public void create(Role role) { + role.setNameLangId(multilingualService.create(role.getName())); + super.create(role); + + if (TimiJava.isNotEmpty(role.getPermissionIdList())) { + List rolePermissionList = new ArrayList<>(); + long now = Time.now(); + for (String permissionId : role.getPermissionIdList()) { + RolePermission rolePermission = new RolePermission(); + rolePermission.setId(UUID.randomUUID().toString()); + rolePermission.setRoleId(role.getId()); + rolePermission.setPermissionId(permissionId); + rolePermission.setCreatedAt(now); + rolePermissionList.add(rolePermission); + } + rolePermissionMapper.createBatch(rolePermissionList); + } + + if (TimiJava.isNotEmpty(role.getChildRoleIdList())) { + List roleRelationList = new ArrayList<>(); + long now = Time.now(); + for (String childRoleId : role.getChildRoleIdList()) { + RoleRelation roleRelation = new RoleRelation(); + roleRelation.setId(UUID.randomUUID().toString()); + roleRelation.setParentRoleId(role.getId()); + roleRelation.setChildRoleId(childRoleId); + roleRelation.setCreatedAt(now); + roleRelationList.add(roleRelation); + } + roleRelationMapper.createBatch(roleRelationList); + } + } + + @Transactional(TimiServerDBConfig.ROLLBACKER) + @Override + public void update(Role role) { + role.setNameLangId(multilingualService.createIfDifferent(role.getNameLangId(), role.getName())); + mapper.updateSelective(role); + { + Set dbIdSet = rolePermissionMapper.selectByRoleId(role.getId()).stream().map(RolePermission::getPermissionId).collect(Collectors.toSet()); + Set newIdSet = TimiJava.defaultIfEmpty(new HashSet<>(role.getPermissionIdList()), new HashSet<>()); + + Set deleteIdList = new HashSet<>(dbIdSet); + deleteIdList.removeAll(newIdSet); + + Set addIdList = new HashSet<>(newIdSet); + addIdList.removeAll(dbIdSet); + + if (TimiJava.isNotEmpty(deleteIdList)) { + rolePermissionMapper.deleteByPermissionIdList(role.getId(), new ArrayList<>(deleteIdList)); + } + if (TimiJava.isNotEmpty(addIdList)) { + List rolePermissionList = new ArrayList<>(); + long now = Time.now(); + for (String permissionId : addIdList) { + RolePermission rolePermission = new RolePermission(); + rolePermission.setId(UUID.randomUUID().toString()); + rolePermission.setRoleId(role.getId()); + rolePermission.setPermissionId(permissionId); + rolePermission.setCreatedAt(now); + rolePermissionList.add(rolePermission); + } + rolePermissionMapper.createBatch(rolePermissionList); + } + } + + { + Set dbIdSet = roleRelationMapper.selectByParentRoleId(role.getId()).stream().map(RoleRelation::getChildRoleId).collect(Collectors.toSet()); + Set newIdSet = TimiJava.defaultIfEmpty(new HashSet<>(role.getChildRoleIdList()), new HashSet<>()); + + Set deleteIdList = new HashSet<>(dbIdSet); + deleteIdList.removeAll(newIdSet); + + Set addIdList = new HashSet<>(newIdSet); + addIdList.removeAll(dbIdSet); + + if (TimiJava.isNotEmpty(deleteIdList)) { + roleRelationMapper.deleteByChildRoleIdList(role.getId(), new ArrayList<>(deleteIdList)); + } + if (TimiJava.isNotEmpty(addIdList)) { + List roleRelationList = new ArrayList<>(); + long now = Time.now(); + for (String childRoleId : addIdList) { + RoleRelation roleRelation = new RoleRelation(); + roleRelation.setId(UUID.randomUUID().toString()); + roleRelation.setParentRoleId(role.getId()); + roleRelation.setChildRoleId(childRoleId); + roleRelation.setCreatedAt(now); + roleRelationList.add(roleRelation); + } + roleRelationMapper.createBatch(roleRelationList); + } + } + } + + @Transactional(TimiServerDBConfig.ROLLBACKER) + @Override + public void authorizeUserRole(UserRoleAuthorizeReq req) { + TimiException.required(req, "not found req"); + TimiException.required(req.getUserId(), "not found req.userId"); + if (req.getModuleCode() == null) { + throw new TimiException(TimiCode.ARG_MISS).msgKey("moduleCode"); + } + List bindingList = userRoleService.listByUserId(req.getUserId()); + List bindingRoleIdList = bindingList.stream().map(UserRole::getRoleId).toList(); + Set dbIdSet = mapper.selectByIdList(bindingRoleIdList).stream() + .filter(role -> req.getModuleCode() == role.getModuleCode()) + .map(Role::getId) + .collect(Collectors.toSet()); + Set newIdSet = new HashSet<>(); + if (TimiJava.isNotEmpty(req.getRoleIdList())) { + newIdSet.addAll(mapper.selectByIdList(req.getRoleIdList()).stream() + .filter(role -> req.getModuleCode() == role.getModuleCode()) + .map(Role::getId) + .toList()); + } + if (TimiJava.isNotEmpty(req.getRoleCodeList())) { + newIdSet.addAll(mapper.selectByModuleCodeAndCodeList(req.getModuleCode(), req.getRoleCodeList()).stream().map(Role::getId).toList()); + } + Set deleteIdList = new HashSet<>(dbIdSet); + deleteIdList.removeAll(newIdSet); + + Set addIdList = new HashSet<>(newIdSet); + addIdList.removeAll(dbIdSet); + + if (TimiJava.isNotEmpty(deleteIdList)) { + userRoleService.deleteByRoleIdList(req.getUserId(), new ArrayList<>(deleteIdList)); + } + if (TimiJava.isNotEmpty(addIdList)) { + List roleBindingList = new ArrayList<>(); + long now = Time.now(); + for (String roleId : addIdList) { + UserRole roleBinding = new UserRole(); + roleBinding.setId(UUID.randomUUID().toString()); + roleBinding.setUserId(req.getUserId()); + roleBinding.setRoleId(roleId); + roleBinding.setCreatedAt(now); + roleBindingList.add(roleBinding); + } + userRoleService.createBatch(roleBindingList); + } + } + + @Override + public List listByUserId(String userId) { + return mapper.selectByIdList(userRoleService.listRoleIdByUserId(userId)); + } + + @Override + public List listByUserId(String userId, ModuleCode moduleCode) { + TimiException.required(moduleCode, "not found moduleCode"); + return listByUserId(userId).stream() + .filter(role -> moduleCode == role.getModuleCode()) + .toList(); + } + + @Override + public List listByModuleCode(ModuleCode moduleCode) { + TimiException.required(moduleCode, "not found moduleCode"); + return mapper.selectByModuleCode(moduleCode); + } + + @Override + public List listAllCodeByUserId(String userId) { + List roleIdList = listByUserId(userId).stream().map(Role::getId).toList(); + List allRoleIdList = getAllRoleIdsIncludingChildren(roleIdList); + if (TimiJava.isEmpty(allRoleIdList)) { + return List.of(); + } + return mapper.selectByIdList(allRoleIdList).stream().map(role -> "%s:%s".formatted(role.getModuleCode().name(), role.getCode())).distinct().toList(); + } + + @Override + public List listAllPermissionByUserId(String userId) { + List roleIdList = listByUserId(userId).stream().map(Role::getId).toList(); + List allRoleIdList = getAllRoleIdsIncludingChildren(roleIdList); + return permissionService.listByRoleId(allRoleIdList); + } + + @Override + public List listChildByParentRoleId(String parentRoleId) { + List childRoleIdList = roleRelationMapper.selectChildRoleIdListByParentRoleId(parentRoleId); + if (TimiJava.isEmpty(childRoleIdList)) { + return new ArrayList<>(); + } + return mapper.selectByIdList(childRoleIdList); + } + + @Override + public List listPermissionByRoleId(String roleId) { + List allRoleIdList = getAllRoleIdsIncludingChildren(List.of(roleId)); + return permissionService.listByRoleId(allRoleIdList); + } + + /// 递归获取角色及其所有子角色的 ID 列表 + /// + /// @param roleIdList 角色 ID 列表 + /// @return 包含所有子角色的 ID 列表 + private List getAllRoleIdsIncludingChildren(List roleIdList) { + if (TimiJava.isEmpty(roleIdList)) { + return new ArrayList<>(); + } + Set allRoleIdSet = new HashSet<>(roleIdList); + List currentLevelRoleIdList = new ArrayList<>(roleIdList); + + while (TimiJava.isNotEmpty(currentLevelRoleIdList)) { + List childRoleIdList = roleRelationMapper.selectChildRoleIdListByParentRoleIdList(currentLevelRoleIdList); + if (TimiJava.isEmpty(childRoleIdList)) { + break; + } + List newChildRoleIdList = childRoleIdList.stream().filter(id -> !allRoleIdSet.contains(id)).toList(); + if (TimiJava.isEmpty(newChildRoleIdList)) { + break; + } + allRoleIdSet.addAll(newChildRoleIdList); + currentLevelRoleIdList = newChildRoleIdList; + } + return new ArrayList<>(allRoleIdSet); + } + + private String saveNameMultilingual(String langId, String name) { + if (TimiJava.isEmpty(name)) { + return langId; + } + if (TimiJava.isEmpty(langId)) { + return multilingualService.create(name); + } + Multilingual multilingual = new Multilingual(); + multilingual.setId(langId); + multilingual.setZhCN(name); + multilingualMapper.updateSelective(multilingual); + return langId; + } +} diff --git a/src/main/java/com/imyeyu/api/modules/user/service/implement/UserBehaviorRecordServiceImplement.java b/src/main/java/com/imyeyu/api/modules/user/service/implement/UserBehaviorRecordServiceImplement.java new file mode 100644 index 0000000..5c70362 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/user/service/implement/UserBehaviorRecordServiceImplement.java @@ -0,0 +1,81 @@ +package com.imyeyu.api.modules.user.service.implement; + +import com.imyeyu.api.config.dbsource.TimiServerDBConfig; +import com.imyeyu.api.modules.user.entity.UserBehaviorRecord; +import com.imyeyu.api.modules.user.mapper.UserBehaviorRecordMapper; +import com.imyeyu.api.modules.user.service.UserBehaviorRecordService; +import com.imyeyu.java.TimiJava; +import com.imyeyu.java.bean.timi.TimiException; +import com.imyeyu.spring.mapper.BaseMapper; +import com.imyeyu.spring.service.AbstractEntityService; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * 用户行为记录服务实现 + * + * @author 夜雨 + * @since 2026-05-27 17:10 + */ +@Service +@RequiredArgsConstructor +public class UserBehaviorRecordServiceImplement extends AbstractEntityService implements UserBehaviorRecordService { + + private final UserBehaviorRecordMapper mapper; + + @Override + protected BaseMapper mapper() { + return mapper; + } + + @Transactional(TimiServerDBConfig.ROLLBACKER) + @Override + public void create(UserBehaviorRecord entity) { + record(entity); + } + + @Transactional(TimiServerDBConfig.ROLLBACKER) + @Override + public boolean record(UserBehaviorRecord entity) { + TimiException.required(entity, "not found entity"); + TimiException.required(entity.getBizType(), "not found entity.bizType"); + TimiException.required(entity.getUserId(), "not found entity.userId"); + TimiException.required(entity.getTargetType(), "not found entity.targetType"); + TimiException.required(entity.getTargetId(), "not found entity.targetId"); + TimiException.required(entity.getActionType(), "not found entity.actionType"); + + UserBehaviorRecord dbRecord = mapper.selectActive(entity.getBizType(), entity.getUserId(), entity.getTargetType(), entity.getTargetId(), entity.getActionType()); + if (dbRecord != null) { + return false; + } + super.create(entity); + return true; + } + + @Transactional(TimiServerDBConfig.ROLLBACKER) + @Override + public boolean cancel(String bizType, String userId, String targetType, String targetId, String actionType) { + if (TimiJava.isEmpty(bizType) || TimiJava.isEmpty(userId) || TimiJava.isEmpty(targetType) || TimiJava.isEmpty(targetId) || TimiJava.isEmpty(actionType)) { + return false; + } + UserBehaviorRecord dbRecord = mapper.selectActive(bizType, userId, targetType, targetId, actionType); + if (dbRecord == null) { + return false; + } + super.delete(dbRecord.getId()); + return true; + } + + @Override + public Set getActiveTargetIdSet(String bizType, String userId, String targetType, String actionType, List targetIdList) { + if (TimiJava.isEmpty(bizType) || TimiJava.isEmpty(userId) || TimiJava.isEmpty(targetType) || TimiJava.isEmpty(actionType) || TimiJava.isEmpty(targetIdList)) { + return Set.of(); + } + return new HashSet<>(mapper.selectActiveTargetIdList(bizType, userId, targetType, actionType, targetIdList)); + } +} diff --git a/src/main/java/com/imyeyu/api/modules/user/service/implement/UserLoginServiceImplement.java b/src/main/java/com/imyeyu/api/modules/user/service/implement/UserLoginServiceImplement.java new file mode 100644 index 0000000..ca9c817 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/user/service/implement/UserLoginServiceImplement.java @@ -0,0 +1,195 @@ +package com.imyeyu.api.modules.user.service.implement; + +import com.imyeyu.api.config.dbsource.TimiServerDBConfig; +import com.imyeyu.api.modules.common.entity.Attachment; +import com.imyeyu.api.modules.common.entity.Setting; +import com.imyeyu.api.modules.common.service.AttachmentService; +import com.imyeyu.api.modules.common.service.SettingService; +import com.imyeyu.api.modules.user.bean.BootstrapData; +import com.imyeyu.api.modules.user.entity.User; +import com.imyeyu.api.modules.user.service.RoleService; +import com.imyeyu.api.modules.user.service.UserLoginService; +import com.imyeyu.api.modules.user.service.UserService; +import com.imyeyu.api.modules.user.vo.LoginRequest; +import com.imyeyu.api.modules.user.vo.LoginResponse; +import com.imyeyu.java.TimiJava; +import com.imyeyu.java.bean.timi.TimiException; +import com.imyeyu.spring.TimiSpring; +import com.imyeyu.spring.util.Redis; +import com.imyeyu.utils.Digest; +import com.imyeyu.utils.Time; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.concurrent.ThreadLocalRandom; +import java.util.UUID; + +/// 用户登录服务实现 +/// +/// @author 夜雨 +/// @since 2026-06-09 +@Slf4j +@Service +@RequiredArgsConstructor(onConstructor_ = {@Lazy}) +public class UserLoginServiceImplement implements UserLoginService, TimiJava { + + private static final String REGEX_PHONE_NO = "^1\\d{10}$"; + private static final String USER_TOKEN_KEY_PREFIX = "USER:TOKEN:"; + private static final String PHONE_VERIFY_KEY_PREFIX = "USER:PHONE:VERIFY:"; + private static final long PHONE_VERIFY_CODE_TTL = Time.M * 5; + + private final UserService userService; + private final RoleService roleService; + private final SettingService settingService; + private final AttachmentService attachmentService; + + private final Redis redisUserToken; + + @Qualifier("redisUserPhoneVerify") + private final Redis redisUserPhoneVerify; + + @Transactional(TimiServerDBConfig.ROLLBACKER) + @Override + public LoginResponse login(LoginRequest req) { + // 获取 + User dbUser = userService.get(req.getUser()); + if (dbUser == null && req.getUser().contains("@")) { + dbUser = userService.getByVerifiedEmail(req.getUser()); + } else if (dbUser == null && isPhoneNo(req.getUser())) { + dbUser = userService.getByVerifiedPhoneNo(req.getUser()); + } else if (dbUser == null) { + dbUser = userService.getByName(req.getUser()); + } + TimiException.required(dbUser, "not found user"); + // 校验 + boolean isValid = Digest.argon2idVerify(dbUser.getPassword(), req.getPassword()); + TimiException.requiredTrue(isValid, "invalid password"); + TimiException.requiredTrue(!dbUser.isBanning(), "banned user"); + // 登录 + return login(UUID.randomUUID().toString(), dbUser.getId()); + } + + @Override + public LoginResponse login(String token, String userId) { + Long time = settingService.getSystem(Setting.Module.User.TOKEN_TTL).asTime(); + long now = Time.now(); + long expireAt = now + time; + redisUserToken.set(getUserTokenKey(token), userId, expireAt - now); + + LoginResponse result = new LoginResponse(); + result.setToken(token); + result.setExpireAt(expireAt); + result.setUser(userService.get(userId)); + + User user = result.getUser(); + user.setPermissionList(roleService.listAllPermissionCodeByUserId(user.getId())); + user.setRoleList(roleService.listAllCodeByUserId(user.getId())); + user.setAttachmentList(attachmentService.listByBizId(Attachment.BizType.USER, user.getId())); + return result; + } + + @Override + public User getLoginUser() { + String token = TimiSpring.getToken(); + TimiException.required(token, "not found token"); + String userId = redisUserToken.get(getUserTokenKey(token)); + if (TimiJava.isEmpty(userId)) { + return null; + } + return userService.get(userId); + } + + @Override + public User getRequireLoginUser() { + String token = TimiSpring.getToken(); + TimiException.required(token, "not found token"); + String userId = redisUserToken.get(getUserTokenKey(token)); + TimiException.required(userId, "invalid token"); + return userService.get(userId); + } + + @Override + public void logout() { + redisUserToken.destroy(getUserTokenKey(TimiSpring.getToken())); + } + + @Transactional(TimiServerDBConfig.ROLLBACKER) + @Override + public void updatePassword(String oldPassword, String newPassword) { + TimiException.requiredTrue(!oldPassword.equals(newPassword), "new password cannot be same as old password"); + User user = getRequireLoginUser(); + // 校验 + boolean isValid = Digest.argon2idVerify(user.getPassword(), oldPassword); + TimiException.requiredTrue(isValid, "invalid old password"); + // 更新 + User update = new User(); + update.setId(user.getId()); + update.setPassword(Digest.argon2id(newPassword)); + userService.update(update); + logout(); + } + + @Transactional(TimiServerDBConfig.ROLLBACKER) + @Override + public void sendPhoneVerifyCode(String phoneNo) { + TimiException.requiredTrue(isPhoneNo(phoneNo), "invalid phone number"); + User verifiedUser = userService.getByVerifiedPhoneNo(phoneNo); + User loginUser = getRequireLoginUser(); + TimiException.requiredTrue(verifiedUser == null || verifiedUser.getId().equals(loginUser.getId()), "existed phone number"); + + String verifyCode = "%06d".formatted(ThreadLocalRandom.current().nextInt(1000000)); + redisUserPhoneVerify.set(getPhoneVerifyKey(phoneNo), verifyCode, PHONE_VERIFY_CODE_TTL); + log.info("手机号验证码 phoneNo={}, code={}", phoneNo, verifyCode); + } + + @Transactional(TimiServerDBConfig.ROLLBACKER) + @Override + public void verifyPhoneNo(String phoneNo, String verifyCode) { + TimiException.requiredTrue(isPhoneNo(phoneNo), "invalid phone number"); + TimiException.required(verifyCode, "not found verify code"); + + User user = getRequireLoginUser(); + User verifiedUser = userService.getByVerifiedPhoneNo(phoneNo); + TimiException.requiredTrue(verifiedUser == null || verifiedUser.getId().equals(user.getId()), "existed phone number"); + + String cacheCode = redisUserPhoneVerify.get(getPhoneVerifyKey(phoneNo)); + TimiException.required(cacheCode, "invalid verify code"); + TimiException.requiredTrue(cacheCode.equals(verifyCode), "invalid verify code"); + + User update = new User(); + update.setId(user.getId()); + update.setPhoneNo(phoneNo); + update.setPhoneNoVerifyAt(Time.now()); + userService.update(update); + redisUserPhoneVerify.destroy(getPhoneVerifyKey(phoneNo)); + } + + @Transactional(TimiServerDBConfig.ROLLBACKER) + @Override + public void deactivate(String password) { + User user = getRequireLoginUser(); + TimiException.requiredTrue(!BootstrapData.SYS_USER_NAME.equals(user.getName()), "can not deactivate System user"); + + boolean isValid = Digest.argon2idVerify(user.getPassword(), password); + TimiException.requiredTrue(isValid, "invalid password"); + + logout(); + userService.delete(user.getId()); + } + + private boolean isPhoneNo(String value) { + return TimiJava.isNotEmpty(value) && value.matches(REGEX_PHONE_NO); + } + + private String getPhoneVerifyKey(String phoneNo) { + return "%s%s".formatted(PHONE_VERIFY_KEY_PREFIX, phoneNo); + } + + private String getUserTokenKey(String token) { + return "%s%s".formatted(USER_TOKEN_KEY_PREFIX, token); + } +} diff --git a/src/main/java/com/imyeyu/api/modules/user/service/implement/UserRoleServiceImplement.java b/src/main/java/com/imyeyu/api/modules/user/service/implement/UserRoleServiceImplement.java new file mode 100644 index 0000000..1ed6dc2 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/user/service/implement/UserRoleServiceImplement.java @@ -0,0 +1,44 @@ +package com.imyeyu.api.modules.user.service.implement; + +import com.imyeyu.api.modules.user.entity.UserRole; +import com.imyeyu.api.modules.user.mapper.UserRoleMapper; +import com.imyeyu.api.modules.user.service.UserRoleService; +import com.imyeyu.spring.mapper.BaseMapper; +import com.imyeyu.spring.service.AbstractEntityService; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.List; + +/// 用户角色服务实现 +/// +/// @author 夜雨 +/// @since 2026-05-13 14:46 +@Service +@RequiredArgsConstructor +public class UserRoleServiceImplement extends AbstractEntityService implements UserRoleService { + + private final UserRoleMapper mapper; + + @Override + protected BaseMapper mapper() { + return mapper; + } + + @Override + public void createBatch(List list) { + mapper.createBatch(list); + } + + @Override + public List listByUserId(String userId) { + UserRole example = new UserRole(); + example.setUserId(userId); + return mapper.selectAllByExample(example); + } + + @Override + public void deleteByRoleIdList(String userId, List roleIdList) { + mapper.deleteByRoleIdList(userId, roleIdList); + } +} diff --git a/src/main/java/com/imyeyu/api/modules/user/service/implement/UserServiceImplement.java b/src/main/java/com/imyeyu/api/modules/user/service/implement/UserServiceImplement.java new file mode 100644 index 0000000..2a336ef --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/user/service/implement/UserServiceImplement.java @@ -0,0 +1,143 @@ +package com.imyeyu.api.modules.user.service.implement; + +import com.imyeyu.api.modules.common.bean.ImageType; +import com.imyeyu.api.modules.user.bean.BootstrapData; +import com.imyeyu.api.modules.user.entity.User; +import com.imyeyu.api.modules.user.mapper.UserMapper; +import com.imyeyu.api.modules.user.service.UserService; +import com.imyeyu.java.TimiJava; +import com.imyeyu.java.bean.timi.TimiException; +import com.imyeyu.spring.mapper.BaseMapper; +import com.imyeyu.spring.service.AbstractEntityService; +import com.imyeyu.utils.Digest; +import com.imyeyu.utils.Regex; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Service; + +/** + * 用户管理服务实现 + * + * @author 夜雨 + * @since 2021-02-23 21:43 + */ +@Slf4j +@Service +@RequiredArgsConstructor(onConstructor_ = {@Lazy}) +public class UserServiceImplement extends AbstractEntityService implements UserService, TimiJava { + + private static final String REGEX_PHONE_NO = "^1\\d{10}$"; + + private final UserMapper mapper; + + @Override + protected BaseMapper mapper() { + return mapper; + } + + @Override + public void create(User user) { + TimiException.requiredTrue(Regex.isMatch(Regex.PASSWORD, user.getPassword()), "invalid password"); + { + User byName = getByName(user.getName()); + TimiException.requiredNull(byName, "existed user name"); + } + { + if (TimiJava.isNotEmpty(user.getEmail())) { + User byEmail = getByVerifiedEmail(user.getEmail()); + TimiException.requiredNull(byEmail, "existed email"); + } + } + { + if (TimiJava.isNotEmpty(user.getPhoneNo())) { + validatePhoneNo(user.getPhoneNo()); + User byPhoneNo = getByVerifiedPhoneNo(user.getPhoneNo()); + TimiException.requiredNull(byPhoneNo, "existed phone number"); + } + } + + User dbUser = new User(); + dbUser.setName(user.getName()); + dbUser.setNick(TimiJava.defaultIfEmpty(user.getNick(), user.getName())); + dbUser.setWrapperType(TimiJava.defaultIfNull(user.getWrapperType(), ImageType.PIXELATED)); + dbUser.setAvatarType(TimiJava.defaultIfNull(user.getAvatarType(), ImageType.PIXELATED)); + dbUser.setPassword(Digest.argon2id(user.getPassword())); + dbUser.setEmail(user.getEmail()); + dbUser.setPhoneNo(user.getPhoneNo()); + dbUser.setExp(TimiJava.defaultIfNull(user.getExp(), 0)); + super.create(dbUser); + user.setId(dbUser.getId()); + } + + @Override + public void update(User user) { + User dbUser = get(user.getId()); + String name = TimiJava.defaultIfEmpty(user.getName(), dbUser.getName()); + String email = TimiJava.defaultIfEmpty(user.getEmail(), dbUser.getEmail()); + String phoneNo = TimiJava.defaultIfEmpty(user.getPhoneNo(), dbUser.getPhoneNo()); + if (!dbUser.getName().equals(name)) { + // 修改名称 + User byName = getByName(name); + TimiException.requiredTrue(byName == null || byName.getId().equals(dbUser.getId()), "existed user name"); + TimiException.requiredTrue(!dbUser.getName().equals(BootstrapData.SYS_USER_NAME), "invalid action"); + } + if (TimiJava.isNotEmpty(email) && !email.equals(dbUser.getEmail())) { + User byEmail = getByVerifiedEmail(email); + TimiException.requiredTrue(byEmail == null || byEmail.getId().equals(dbUser.getId()), "existed email"); + } + if (TimiJava.isNotEmpty(phoneNo)) { + validatePhoneNo(phoneNo); + if (!phoneNo.equals(dbUser.getPhoneNo())) { + User byPhoneNo = getByVerifiedPhoneNo(phoneNo); + TimiException.requiredTrue(byPhoneNo == null || byPhoneNo.getId().equals(dbUser.getId()), "existed phone number"); + } + } + super.update(user); + } + + @Override + public void delete(String id) { + User user = get(id); + TimiException.requiredTrue(!user.getName().equals(BootstrapData.SYS_USER_NAME), "invalid action"); + super.delete(id); + } + + @Override + public void destroy(String id) { + User user = get(id); + TimiException.requiredTrue(!user.getName().equals(BootstrapData.SYS_USER_NAME), "invalid action"); + super.destroy(id); + } + + @Override + public User getByName(String name) { + User example = new User(); + example.setName(name); + return mapper.selectByExample(example); + } + + @Override + public User getByEmail(String email) { + return mapper.selectByEmail(email); + } + + @Override + public User getByVerifiedEmail(String email) { + return mapper.selectByVerifiedEmail(email); + } + + @Override + public User getByPhoneNo(String phoneNo) { + return mapper.selectByPhoneNo(phoneNo); + } + + @Override + public User getByVerifiedPhoneNo(String phoneNo) { + return mapper.selectByVerifiedPhoneNo(phoneNo); + } + + private void validatePhoneNo(String phoneNo) { + TimiException.requiredTrue(Regex.isMatch(REGEX_PHONE_NO, phoneNo), "invalid phone number"); + } +} diff --git a/src/main/java/com/imyeyu/api/modules/common/validation/UserName.java b/src/main/java/com/imyeyu/api/modules/user/validation/UserName.java similarity index 88% rename from src/main/java/com/imyeyu/api/modules/common/validation/UserName.java rename to src/main/java/com/imyeyu/api/modules/user/validation/UserName.java index 2fb97c7..4e9b53d 100644 --- a/src/main/java/com/imyeyu/api/modules/common/validation/UserName.java +++ b/src/main/java/com/imyeyu/api/modules/user/validation/UserName.java @@ -1,8 +1,8 @@ -package com.imyeyu.api.modules.common.validation; +package com.imyeyu.api.modules.user.validation; import jakarta.validation.Constraint; import jakarta.validation.Payload; -import com.imyeyu.api.modules.common.validation.validtor.UserNameValidator; +import com.imyeyu.api.modules.user.validation.validtor.UserNameValidator; import java.lang.annotation.Documented; import java.lang.annotation.Retention; diff --git a/src/main/java/com/imyeyu/api/modules/common/validation/UserPassword.java b/src/main/java/com/imyeyu/api/modules/user/validation/UserPassword.java similarity index 88% rename from src/main/java/com/imyeyu/api/modules/common/validation/UserPassword.java rename to src/main/java/com/imyeyu/api/modules/user/validation/UserPassword.java index 140828b..3cd6080 100644 --- a/src/main/java/com/imyeyu/api/modules/common/validation/UserPassword.java +++ b/src/main/java/com/imyeyu/api/modules/user/validation/UserPassword.java @@ -1,8 +1,8 @@ -package com.imyeyu.api.modules.common.validation; +package com.imyeyu.api.modules.user.validation; import jakarta.validation.Constraint; import jakarta.validation.Payload; -import com.imyeyu.api.modules.common.validation.validtor.UserPasswordValidator; +import com.imyeyu.api.modules.user.validation.validtor.UserPasswordValidator; import java.lang.annotation.Documented; import java.lang.annotation.Retention; diff --git a/src/main/java/com/imyeyu/api/modules/common/validation/validtor/UserNameValidator.java b/src/main/java/com/imyeyu/api/modules/user/validation/validtor/UserNameValidator.java similarity index 86% rename from src/main/java/com/imyeyu/api/modules/common/validation/validtor/UserNameValidator.java rename to src/main/java/com/imyeyu/api/modules/user/validation/validtor/UserNameValidator.java index 3cc8bc7..6b6ca23 100644 --- a/src/main/java/com/imyeyu/api/modules/common/validation/validtor/UserNameValidator.java +++ b/src/main/java/com/imyeyu/api/modules/user/validation/validtor/UserNameValidator.java @@ -1,6 +1,6 @@ -package com.imyeyu.api.modules.common.validation.validtor; +package com.imyeyu.api.modules.user.validation.validtor; -import com.imyeyu.api.modules.common.validation.UserName; +import com.imyeyu.api.modules.user.validation.UserName; import com.imyeyu.java.TimiJava; import com.imyeyu.spring.util.AbstractValidator; import com.imyeyu.utils.Regex; diff --git a/src/main/java/com/imyeyu/api/modules/common/validation/validtor/UserPasswordValidator.java b/src/main/java/com/imyeyu/api/modules/user/validation/validtor/UserPasswordValidator.java similarity index 69% rename from src/main/java/com/imyeyu/api/modules/common/validation/validtor/UserPasswordValidator.java rename to src/main/java/com/imyeyu/api/modules/user/validation/validtor/UserPasswordValidator.java index cd4e8f2..9200435 100644 --- a/src/main/java/com/imyeyu/api/modules/common/validation/validtor/UserPasswordValidator.java +++ b/src/main/java/com/imyeyu/api/modules/user/validation/validtor/UserPasswordValidator.java @@ -1,6 +1,6 @@ -package com.imyeyu.api.modules.common.validation.validtor; +package com.imyeyu.api.modules.user.validation.validtor; -import com.imyeyu.api.modules.common.validation.UserPassword; +import com.imyeyu.api.modules.user.validation.UserPassword; import com.imyeyu.java.TimiJava; import com.imyeyu.spring.util.AbstractValidator; import com.imyeyu.utils.Regex; @@ -21,10 +21,10 @@ public class UserPasswordValidator extends AbstractValidator roleIdList; + + private List roleCodeList; +} diff --git a/src/main/java/com/imyeyu/api/util/AES.java b/src/main/java/com/imyeyu/api/util/AES.java deleted file mode 100644 index b1d108f..0000000 --- a/src/main/java/com/imyeyu/api/util/AES.java +++ /dev/null @@ -1,84 +0,0 @@ -package com.imyeyu.api.util; - -import org.springframework.stereotype.Component; - -import javax.crypto.Cipher; -import javax.crypto.KeyGenerator; -import javax.crypto.SecretKey; -import javax.crypto.spec.SecretKeySpec; - -/** - * AES 加解密 - * - * @author 夜雨 - * @version 2021-08-11 00:52 - */ -@Component -public class AES { - - /** - * 生产密钥 - * - * @return 密钥 - * @throws Exception 生产异常 - */ - public byte[] initKey() throws Exception { - KeyGenerator keyGen = KeyGenerator.getInstance("AES"); - keyGen.init(256); - return keyGen.generateKey().getEncoded(); - } - - /** - * 加密字符串 - * - * @param data 待加密字符串 - * @param key 密钥 - * @return 加密结果 - * @throws Exception 加密异常 - */ - public byte[] encrypt(String data, byte[] key) throws Exception { - return encrypt(data.getBytes(), key); - } - - /** - * 加密 - * - * @param data 待加密字节数据 - * @param key 密钥 - * @return 加密结果 - * @throws Exception 加密异常 - */ - public byte[] encrypt(byte[] data, byte[] key) throws Exception { - SecretKey secretKey = new SecretKeySpec(key, "AES"); - Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding"); - cipher.init(Cipher.ENCRYPT_MODE, secretKey); // 密钥 - return cipher.doFinal(data); // 加密返回 - } - - /** - * 解密字符串 - * - * @param data 待解密字节数据 - * @param key 密钥 - * @return 解密结果 - * @throws Exception 解密异常 - */ - public byte[] decrypt(String data, byte[] key) throws Exception { - return decrypt(data.getBytes(), key); - } - - /** - * 解密 - * - * @param data 待解密字节数据 - * @param key 密钥 - * @return 解密结果 - * @throws Exception 解密异常 - */ - public byte[] decrypt(byte[] data, byte[] key) throws Exception { - SecretKey secretKey = new SecretKeySpec(key, "AES"); - Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding"); - cipher.init(Cipher.DECRYPT_MODE, secretKey); - return cipher.doFinal(data); - } -} diff --git a/src/main/java/com/imyeyu/api/util/CaptchaManager.java b/src/main/java/com/imyeyu/api/util/CaptchaManager.java index 9223a3c..152b27a 100644 --- a/src/main/java/com/imyeyu/api/util/CaptchaManager.java +++ b/src/main/java/com/imyeyu/api/util/CaptchaManager.java @@ -3,14 +3,17 @@ package com.imyeyu.api.util; import com.imyeyu.java.TimiJava; import com.imyeyu.java.bean.timi.TimiCode; import com.imyeyu.java.bean.timi.TimiException; -import com.imyeyu.api.bean.CaptchaFrom; import com.imyeyu.spring.TimiSpring; +import com.imyeyu.spring.util.Redis; import com.imyeyu.utils.Calc; +import com.imyeyu.utils.Encoder; import com.imyeyu.utils.Time; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; +import javax.imageio.ImageIO; import java.awt.AlphaComposite; import java.awt.Color; import java.awt.Font; @@ -19,6 +22,9 @@ import java.awt.Graphics2D; import java.awt.geom.AffineTransform; import java.awt.geom.QuadCurve2D; import java.awt.image.BufferedImage; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.UUID; /** * 验证码绘制 @@ -28,15 +34,13 @@ import java.awt.image.BufferedImage; */ @Slf4j @Component +@RequiredArgsConstructor public class CaptchaManager { - /** 会话频率限制时间,毫秒 */ - private static final int LOCK_TIME = 1000; - /** 会话频率限制键,插值会话 ID */ private static final String LOCK_KEY = "CAPTCHA:LOCK:%s"; - /** 会话值缓存键,插值验证码来源 */ + /** 会话值缓存键,插值唯一 ID */ private static final String CACHE_KEY = "CAPTCHA:%s"; /** 绘制字符,移除 O,o,I,l */ @@ -49,20 +53,22 @@ public class CaptchaManager { @Value("${spring.profiles.active}") private String env; + private final Redis redisCaptcha; - public BufferedImage generate(CaptchaFrom from, int width, int height) throws InterruptedException { - return generate(from, 4, width, height); + public Result generate(int width, int height) { + return generate(width, height, 4); } /** * 生成并缓存验证码 * - * @param from 来自模块 * @param width 宽度 * @param height 高度 + * @param length 长度 * @return 图片流 */ - public BufferedImage generate(CaptchaFrom from, int length, int width, int height) throws InterruptedException { + public Result generate(int width, int height, int length) { + long lockTime = 1000; // 加锁 String lockKey = LOCK_KEY.formatted(TimiSpring.getSession().getId()); Long lockAt = TimiSpring.getSessionAttr(lockKey, Long.class); @@ -70,17 +76,22 @@ public class CaptchaManager { TimiSpring.setSessionAttr(lockKey, Time.now()); if (lockAt != null) { long diff = Time.now() - lockAt; - if (diff < LOCK_TIME) { + if (diff < lockTime) { // 限制频率 synchronized (this) { - wait(LOCK_TIME - diff); + try { + wait(lockTime - diff); + } catch (InterruptedException e) { + log.error("lock error", e); + throw new RuntimeException(e); + } } } } - int fontWidth = width / 4; - int fontSize = (int) (height * .8); - int yOffset = (int) (height * .2); + int fontWidth = (int) (width * .9 / 4); + int fontSize = (int) (height * .7); + int yOffset = (int) (height * .4); StringBuilder value = new StringBuilder(); // 图片流 @@ -139,9 +150,19 @@ public class CaptchaManager { g.draw(curve); } } - // 写入缓存 - TimiSpring.setSessionAttr(CACHE_KEY.formatted(from), value.toString()); - return image; + + try { + String uniqueId = UUID.randomUUID().toString(); + // 写入缓存 + redisCaptcha.set(CACHE_KEY.formatted(uniqueId), value.toString(), Time.parseToMS("3m")); + + // 返回 + ByteArrayOutputStream os = new ByteArrayOutputStream(); + ImageIO.write(image, "png", os); + return new Result(uniqueId, "data:image/png;base64," + Encoder.base64(os.toByteArray())); + } catch (IOException e) { + throw new TimiException(TimiCode.ERROR); + } } /** @return 错误回调图像 */ @@ -158,30 +179,40 @@ public class CaptchaManager { } /** - *

验证。通过验证不抛出任何异常也不返回任何内容,否则抛出相应异常 - *

会验证非空和期限 - *

测试环境总是通过验证 + * 验证 * - * @param captcha 提交的验证码 - * @param from 来自模块 + *

    + *
  • 通过验证不抛出任何异常也不返回任何内容,否则抛出相应异常
  • + *
  • 会验证非空和期限
  • + *
+ * + * @param uniqueId 唯一识别 ID + * @param value 提交的验证码 * @throws TimiException 验证异常 */ - public void test(String captcha, String from) { - if (TimiJava.isEmpty(captcha)) { - throw new TimiException(TimiCode.ARG_MISS).msgKey("captcha.miss"); + public void verify(String uniqueId, String value) { + if (TimiJava.isEmpty(value)) { + throw new TimiException(TimiCode.ARG_MISS, "not found value"); } if (env.startsWith("dev")) { return; } - // Session 验证 - String sessionCaptcha = TimiSpring.getSessionAttrAsString(CACHE_KEY.formatted(from)); - if (TimiJava.isEmpty(sessionCaptcha)) { + String cacheKey = CACHE_KEY.formatted(uniqueId); + String cacheValue = redisCaptcha.get(cacheKey); + if (TimiJava.isEmpty(cacheValue)) { throw new TimiException(TimiCode.ARG_EXPIRED).msgKey("captcha.expire"); } - if (!captcha.trim().equalsIgnoreCase(sessionCaptcha)) { + if (!value.trim().equalsIgnoreCase(cacheValue)) { throw new TimiException(TimiCode.RESULT_BAD).msgKey("captcha.error"); } // 清除缓存 - TimiSpring.removeSessionAttr(CACHE_KEY.formatted(from)); + redisCaptcha.destroy(cacheKey); + } + + /** + * @author 夜雨 + * @since 2025-10-30 14:48 + */ + public record Result(String id, String data) { } } diff --git a/src/main/java/com/imyeyu/api/util/InitApplication.java b/src/main/java/com/imyeyu/api/util/InitApplication.java index 9362f7a..ce08ece 100644 --- a/src/main/java/com/imyeyu/api/util/InitApplication.java +++ b/src/main/java/com/imyeyu/api/util/InitApplication.java @@ -1,18 +1,9 @@ package com.imyeyu.api.util; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.node.ArrayNode; -import com.fasterxml.jackson.databind.node.ObjectNode; -import com.imyeyu.api.modules.common.bean.SettingKey; -import com.imyeyu.api.modules.common.entity.Multilingual; -import com.imyeyu.api.modules.common.entity.Setting; import com.imyeyu.api.modules.common.service.SettingService; -import com.imyeyu.api.modules.system.bean.ServerFile; -import com.imyeyu.java.ref.Ref; import com.imyeyu.lang.mapper.AbstractLanguageMapper; import com.imyeyu.spring.TimiSpring; import com.imyeyu.spring.util.GlobalReturnHandler; -import com.imyeyu.spring.util.Redis; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.eclipse.jgit.api.ArchiveCommand; @@ -23,10 +14,6 @@ import org.springframework.boot.ApplicationRunner; import org.springframework.stereotype.Component; import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Objects; /** * SpringBoot 启动事件,主要输出基本参数,避免混淆运行环境 @@ -57,23 +44,6 @@ public class InitApplication implements ApplicationRunner { private final SettingService settingService; private final RedisMultilingual redisMultilingual; private final GlobalReturnHandler globalReturnHandler; - private final Redis redisLanguage; - - private void logBaseInfo() { - log.info("JDBC URL: {}", jdbcURL); - log.info("Redis URL: {}:{}", redisURL, redisPort); - log.info("System Setting:"); - List settings = settingService.listAll(); - for (Setting setting : settings) { - String value = Objects.requireNonNullElse(setting.getValue(), ""); - if (64 < value.length()) { - value = value.substring(0, 64) + ".."; - } - value = value.replaceAll("[\\r\\n]+", ""); - log.info("\t{}: {}", setting.getKey(), value); - } - log.info("Init Application Finished."); - } private void initGitCommand() { ArchiveCommand.registerFormat("tar.gz", new TarFormat()); @@ -86,38 +56,13 @@ public class InitApplication implements ApplicationRunner { }); } - private void initFileType() { - ObjectNode items = settingService.getAsJsonObject(SettingKey.SYSTEM_FILE_TYPE); - - String[] extensions; - ArrayNode extensionsArray; - JsonNode itemObject; - List extensionsList; - for (Map.Entry item : (Iterable>) items::fields) { - ServerFile.FileType fileType = Ref.toType(ServerFile.FileType.class, item.getKey()); - itemObject = item.getValue(); - extensionsList = new ArrayList<>(); - extensionsArray = (ArrayNode) itemObject.get("extensions"); - for (JsonNode extensionNode : extensionsArray) { - if (extensionNode.isObject()) { - extensionsList.add(extensionNode.path("value").asText()); - } else { - extensionsList.add(extensionNode.asText()); - } - } - extensions = new String[extensionsList.size()]; - // 设置扩展名所属文件类型 - fileType.setExtensions(extensionsList.toArray(extensions)); - } - } - @Override public void run(ApplicationArguments args) throws Exception { Method[] methods = getClass().getDeclaredMethods(); - for (int i = 0; i < methods.length; i++) { - if (!methods[i].getName().equals("run") && !methods[i].getName().contains("$")) { - methods[i].setAccessible(true); - methods[i].invoke(this); + for (Method method : methods) { + if (!method.getName().equals("run") && !method.getName().contains("$")) { + method.setAccessible(true); + method.invoke(this); } } } diff --git a/src/main/java/com/imyeyu/api/util/InitBuiltinUser.java b/src/main/java/com/imyeyu/api/util/InitBuiltinUser.java new file mode 100644 index 0000000..7ca70d9 --- /dev/null +++ b/src/main/java/com/imyeyu/api/util/InitBuiltinUser.java @@ -0,0 +1,222 @@ +package com.imyeyu.api.util; + +import com.imyeyu.api.config.dbsource.TimiServerDBConfig; +import com.imyeyu.api.bean.ModuleCode; +import com.imyeyu.api.modules.common.bean.ImageType; +import com.imyeyu.api.modules.gao.bean.GaoPermissionCode; +import com.imyeyu.api.modules.gao.bean.GaoRoleCode; +import com.imyeyu.api.modules.system.service.LoggerService; +import com.imyeyu.api.modules.user.bean.BootstrapData; +import com.imyeyu.api.modules.user.bean.BuiltinAuthRegistry; +import com.imyeyu.api.modules.user.bean.BuiltinPermissionCode; +import com.imyeyu.api.modules.user.bean.BuiltinRoleCode; +import com.imyeyu.api.modules.user.entity.Permission; +import com.imyeyu.api.modules.user.entity.Role; +import com.imyeyu.api.modules.user.entity.RolePermission; +import com.imyeyu.api.modules.user.entity.RoleRelation; +import com.imyeyu.api.modules.user.entity.User; +import com.imyeyu.api.modules.user.entity.UserRole; +import com.imyeyu.api.modules.user.mapper.PermissionMapper; +import com.imyeyu.api.modules.user.mapper.RoleMapper; +import com.imyeyu.api.modules.user.mapper.RolePermissionMapper; +import com.imyeyu.api.modules.user.mapper.RoleRelationMapper; +import com.imyeyu.api.modules.user.mapper.UserRoleMapper; +import com.imyeyu.api.modules.user.service.PermissionService; +import com.imyeyu.api.modules.user.service.RoleRelationService; +import com.imyeyu.api.modules.user.service.RoleService; +import com.imyeyu.api.modules.user.service.UserRoleService; +import com.imyeyu.api.modules.user.service.UserService; +import com.imyeyu.java.TimiJava; +import com.imyeyu.utils.Text; +import lombok.RequiredArgsConstructor; +import org.springframework.boot.ApplicationArguments; +import org.springframework.boot.ApplicationRunner; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * 内建数据初始化器 + * + * @author 夜雨 + * @since 2026-06-05 15:45 + */ +@Component +@RequiredArgsConstructor +public class InitBuiltinUser implements ApplicationRunner { + + private final RoleService roleService; + private final UserService userService; + private final LoggerService loggerService; + private final UserRoleService userRoleService; + private final PermissionService permissionService; + private final RoleRelationService roleRelationService; + + private final RoleMapper roleMapper; + private final UserRoleMapper userRoleMapper; + private final PermissionMapper permissionMapper; + private final RolePermissionMapper rolePermissionMapper; + private final RoleRelationMapper roleRelationMapper; + + /** + * 初始化内建数据 + * + * @param args 启动参数 + * @throws Exception 初始化异常 + */ + @Override + @Transactional(TimiServerDBConfig.ROLLBACKER) + public void run(ApplicationArguments args) throws Exception { + // 先同步所有内建权限和角色,再补齐继承与默认授权关系 + Map permissionMap = initBuiltinPermissionMap(); + Map roleMap = initBuiltinRoleMap(); + bindBuiltinRoleRelation(roleMap); + bindBuiltinPermission(roleMap, permissionMap); + + Role systemRole = roleMap.get(BuiltinAuthRegistry.toRoleKey(BootstrapData.SYS_ROLE_MODULE_CODE, BootstrapData.SYS_ROLE_CODE)); + // 初始化系统用户 + User user = userService.getByName(BootstrapData.SYS_USER_NAME); + if (user == null) { + // 创建 + String password = Text.randomPassword(32); + + user = new User(); + user.setName(BootstrapData.SYS_USER_NAME); + user.setNick(BootstrapData.SYS_USER_NICK); + user.setPassword(password); + user.setWrapperType(ImageType.PIXELATED); + user.setAvatarType(ImageType.PIXELATED); + user.setExp(0); + userService.create(user); + + String logContent = "初始化 %s 自举用户成功,请立即修改密码:%s".formatted(BootstrapData.SYS_USER_NAME, password); + loggerService.info("SYSTEM", "INITIALIZE_SYSTEM_USER", user.getId(), logContent, null); + } + // 授权系统用户角色 + { + UserRole example = new UserRole(); + example.setUserId(user.getId()); + example.setRoleId(systemRole.getId()); + if (userRoleMapper.selectByExample(example) != null) { + return; + } + UserRole userRole = new UserRole(); + userRole.setUserId(user.getId()); + userRole.setRoleId(systemRole.getId()); + userRoleService.create(userRole); + } + } + + /// 按内建定义补齐权限表,不存在才创建 + /// + /// @return 权限代码到权限实体的映射 + private Map initBuiltinPermissionMap() { + Map permissionMap = new LinkedHashMap<>(); + for (BuiltinPermissionCode item : BuiltinAuthRegistry.PERMISSION_CODE_LIST) { + Permission example = new Permission(); + example.setModuleCode(item.getModuleCode()); + example.setCode(item.getValue()); + Permission permission = permissionMapper.selectByExample(example); + if (permission == null) { + permission = new Permission(); + permission.setModuleCode(item.getModuleCode()); + permission.setCode(item.getValue()); + permission.setName(item.getDefaultName()); + permissionService.create(permission); + permission = permissionMapper.selectByExample(example); + } + permissionMap.put(BuiltinAuthRegistry.toPermissionKey(item.getModuleCode(), item.getValue()), permission); + } + return permissionMap; + } + + /// 按内建定义补齐角色表,不存在才创建 + /// + /// @return 模块加角色代码到角色实体的映射 + private Map initBuiltinRoleMap() { + Map roleMap = new LinkedHashMap<>(); + for (BuiltinRoleCode item : BuiltinAuthRegistry.ROLE_CODE_LIST) { + String code = ((Enum) item).name(); + Role example = new Role(); + example.setModuleCode(item.getModuleCode()); + example.setCode(code); + Role role = roleMapper.selectByExample(example); + if (role == null) { + role = new Role(); + role.setModuleCode(item.getModuleCode()); + role.setCode(code); + role.setName(item.getDefaultName()); + roleService.create(role); + role = roleMapper.selectByExample(example); + } + roleMap.put(BuiltinAuthRegistry.toRoleKey(item.getModuleCode(), code), role); + } + return roleMap; + } + + /// 按角色定义补齐父子角色关系 + /// + /// @param roleMap 已同步的角色映射 + private void bindBuiltinRoleRelation(Map roleMap) { + for (BuiltinRoleCode item : BuiltinAuthRegistry.ROLE_CODE_LIST) { + if (TimiJava.isEmpty(item.getParentCode())) { + continue; + } + Role parentRole = roleMap.get(BuiltinAuthRegistry.toRoleKey(item.getModuleCode(), item.getParentCode())); + Role childRole = roleMap.get(BuiltinAuthRegistry.toRoleKey(item.getModuleCode(), ((Enum) item).name())); + if (parentRole == null || childRole == null) { + continue; + } + RoleRelation relation = new RoleRelation(); + relation.setParentRoleId(parentRole.getId()); + relation.setChildRoleId(childRole.getId()); + if (roleRelationMapper.selectByExample(relation) == null) { + roleRelationService.create(relation); + } + } + } + + /// 按权限定义补齐 SYSTEM 和 ADMIN 的默认权限 + /// + /// @param roleMap 已同步的角色映射 + /// @param permissionMap 已同步的权限映射 + private void bindBuiltinPermission(Map roleMap, Map permissionMap) { + Role systemRole = roleMap.get(BuiltinAuthRegistry.toRoleKey(BootstrapData.SYS_ROLE_MODULE_CODE, BootstrapData.SYS_ROLE_CODE)); + Role adminRole = roleMap.get(BuiltinAuthRegistry.toRoleKey(BootstrapData.ADMIN_ROLE_MODULE_CODE, BootstrapData.ADMIN_ROLE_CODE)); + Role gaoStoreManagerRole = roleMap.get(BuiltinAuthRegistry.toRoleKey(ModuleCode.GAO, GaoRoleCode.STORE_MANAGER.name())); + for (BuiltinPermissionCode item : BuiltinAuthRegistry.PERMISSION_CODE_LIST) { + Permission permission = permissionMap.get(BuiltinAuthRegistry.toPermissionKey(item.getModuleCode(), item.getValue())); + if (permission == null) { + continue; + } + if (item.isGrantToSystem() && systemRole != null) { + createRolePermissionIfAbsent(systemRole.getId(), permission.getId()); + } + if (item.isGrantToAdmin() && adminRole != null) { + createRolePermissionIfAbsent(adminRole.getId(), permission.getId()); + } + if (item instanceof GaoPermissionCode gaoPermission && gaoPermission.isGrantToStoreManager() && gaoStoreManagerRole != null) { + createRolePermissionIfAbsent(gaoStoreManagerRole.getId(), permission.getId()); + } + } + } + + /// 角色权限关系不存在时才创建 + /// + /// @param roleId 角色 ID + /// @param permissionId 权限 ID + private void createRolePermissionIfAbsent(String roleId, String permissionId) { + RolePermission example = new RolePermission(); + example.setRoleId(roleId); + example.setPermissionId(permissionId); + if (rolePermissionMapper.selectByExample(example) != null) { + return; + } + RolePermission rolePermission = new RolePermission(); + rolePermission.setRoleId(roleId); + rolePermission.setPermissionId(permissionId); + rolePermissionMapper.insert(rolePermission); + } +} diff --git a/src/main/java/com/imyeyu/api/util/MultilingualFieldBinder.java b/src/main/java/com/imyeyu/api/util/MultilingualFieldBinder.java new file mode 100644 index 0000000..cf660bc --- /dev/null +++ b/src/main/java/com/imyeyu/api/util/MultilingualFieldBinder.java @@ -0,0 +1,377 @@ +package com.imyeyu.api.util; + +import com.fasterxml.jackson.databind.JsonNode; +import com.imyeyu.api.annotation.MultilingualField; +import com.imyeyu.api.modules.common.entity.Multilingual; +import com.imyeyu.api.modules.common.service.MultilingualService; +import com.imyeyu.java.TimiJava; +import com.imyeyu.java.bean.Language; +import com.imyeyu.spring.TimiSpring; +import com.imyeyu.utils.StringInterpolator; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.io.File; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.Reader; +import java.io.Writer; +import java.lang.reflect.Array; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.nio.file.Path; +import java.time.temporal.TemporalAccessor; +import java.util.ArrayList; +import java.util.Collection; +import java.util.IdentityHashMap; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +/** + * 统一回填响应对象中的多语言字段 + * + * @author 夜雨 + * @since 2026-07-17 00:00 + */ +@Slf4j +@Component +@RequiredArgsConstructor +public class MultilingualFieldBinder { + + private static final StringInterpolator INTERPOLATOR = new StringInterpolator(StringInterpolator.SIMPLE_OBJ); + private static final Map, List> TRAVERSE_CACHE = new ConcurrentHashMap<>(); + private static final Map, List> BINDING_CACHE = new ConcurrentHashMap<>(); + + private final MultilingualService multilingualService; + + /** + * 根据当前请求语言回填对象中的多语言字段 + * + * @param body 响应对象 + */ + public void bind(Object body) { + if (body == null) { + return; + } + LinkedHashSet langIdSet = new LinkedHashSet<>(); + // 第一遍只收集多语言 ID,避免递归回填时重复查询 + collectLangId(body, new IdentityHashMap<>(), langIdSet); + if (langIdSet.isEmpty()) { + return; + } + Map multilingualMap = new LinkedHashMap<>(); + for (Multilingual item : multilingualService.listByIdList(new ArrayList<>(langIdSet))) { + multilingualMap.put(item.getId(), item); + } + // 第二遍执行字段回填,模板替换也在这一遍统一完成 + fillValue(body, new IdentityHashMap<>(), multilingualMap, TimiSpring.getLanguage()); + } + + /** + * 递归扫描响应对象,提取所有待翻译字段依赖的多语言 ID + * + * @param current 当前对象 + * @param visited 已访问对象,避免循环引用死递归 + * @param langIdSet 收集到的多语言 ID + */ + private void collectLangId(Object current, IdentityHashMap visited, LinkedHashSet langIdSet) { + switch (current) { + case null -> { + return; + } + case Collection collection -> { + if (visited.put(current, Boolean.TRUE) != null) { + return; + } + for (Object item : collection) { + collectLangId(item, visited, langIdSet); + } + return; + } + case Map map -> { + if (visited.put(current, Boolean.TRUE) != null) { + return; + } + for (Object item : map.values()) { + collectLangId(item, visited, langIdSet); + } + return; + } + default -> { + } + } + if (current.getClass().isArray()) { + if (visited.put(current, Boolean.TRUE) != null) { + return; + } + int length = Array.getLength(current); + for (int i = 0; i < length; i++) { + collectLangId(Array.get(current, i), visited, langIdSet); + } + return; + } + if (isSimpleValueType(current.getClass())) { + return; + } + if (visited.put(current, Boolean.TRUE) != null) { + return; + } + for (BindingField field : getBindingFields(current.getClass())) { + String langId = getStringValue(field.langIdField(), current); + if (TimiJava.isNotEmpty(langId)) { + langIdSet.add(langId); + } + } + for (Field field : getTraverseFields(current.getClass())) { + collectLangId(getFieldValue(field, current), visited, langIdSet); + } + } + + /** + * 递归回填响应对象中的多语言展示字段 + * + * @param current 当前对象 + * @param visited 已访问对象,避免循环引用死递归 + * @param multilingualMap 本次响应涉及的多语言缓存 + * @param language 当前请求语言 + */ + private void fillValue(Object current, IdentityHashMap visited, Map multilingualMap, Language.Enum language) { + if (current == null) { + return; + } + if (current instanceof Collection collection) { + if (visited.put(current, Boolean.TRUE) != null) { + return; + } + for (Object item : collection) { + fillValue(item, visited, multilingualMap, language); + } + return; + } + if (current instanceof Map map) { + if (visited.put(current, Boolean.TRUE) != null) { + return; + } + for (Object item : map.values()) { + fillValue(item, visited, multilingualMap, language); + } + return; + } + if (current.getClass().isArray()) { + if (visited.put(current, Boolean.TRUE) != null) { + return; + } + int length = Array.getLength(current); + for (int i = 0; i < length; i++) { + fillValue(Array.get(current, i), visited, multilingualMap, language); + } + return; + } + if (isSimpleValueType(current.getClass())) { + return; + } + if (visited.put(current, Boolean.TRUE) != null) { + return; + } + for (BindingField field : getBindingFields(current.getClass())) { + String langId = getStringValue(field.langIdField(), current); + if (TimiJava.isEmpty(langId)) { + continue; + } + Multilingual multilingual = multilingualMap.get(langId); + if (multilingual == null) { + continue; + } + String value = multilingual.getValue(language); + if (TimiJava.isEmpty(value)) { + value = multilingual.getZhCN(); + } + if (TimiJava.isEmpty(value)) { + continue; + } + Map argMap = getArgMapValue(field.argMapField(), current); + if (!argMap.isEmpty()) { + value = INTERPOLATOR.inject(value, argMap); + } + if (TimiJava.isEmpty(value)) { + continue; + } + setFieldValue(field.targetField(), current, value); + } + for (Field field : getTraverseFields(current.getClass())) { + fillValue(getFieldValue(field, current), visited, multilingualMap, language); + } + } + + private List getBindingFields(Class clazz) { + return BINDING_CACHE.computeIfAbsent(clazz, key -> { + Map fieldMap = new LinkedHashMap<>(); + List declaredFieldList = getAllFields(key); + for (Field field : declaredFieldList) { + fieldMap.put(field.getName(), field); + } + List result = new ArrayList<>(); + for (Field field : declaredFieldList) { + MultilingualField annotation = field.getAnnotation(MultilingualField.class); + if (annotation == null) { + continue; + } + Field langIdField = fieldMap.get(annotation.value()); + if (langIdField == null) { + log.warn("MultilingualField 配置错误,类 {} 不存在字段 {}", key.getName(), annotation.value()); + continue; + } + Field argMapField = null; + if (TimiJava.isNotEmpty(annotation.argMap())) { + argMapField = fieldMap.get(annotation.argMap()); + if (argMapField == null) { + log.warn("MultilingualField 配置错误,类 {} 不存在字段 {}", key.getName(), annotation.argMap()); + continue; + } + if (!Map.class.isAssignableFrom(argMapField.getType())) { + log.warn("MultilingualField 配置错误,类 {} 字段 {} 不是 Map", key.getName(), annotation.argMap()); + continue; + } + } + result.add(new BindingField(field, langIdField, argMapField)); + } + return result; + }); + } + + /** + * 生成需要继续向下遍历的字段列表 + * + * @param clazz 目标类型 + * @return 可递归字段列表 + */ + private List getTraverseFields(Class clazz) { + return TRAVERSE_CACHE.computeIfAbsent(clazz, key -> { + List result = new ArrayList<>(); + for (Field field : getAllFields(key)) { + if (Modifier.isStatic(field.getModifiers()) || field.isSynthetic()) { + continue; + } + if (field.getType().isArray() + || Collection.class.isAssignableFrom(field.getType()) + || Map.class.isAssignableFrom(field.getType())) { + field.setAccessible(true); + result.add(field); + continue; + } + if (isSimpleValueType(field.getType())) { + continue; + } + field.setAccessible(true); + result.add(field); + } + return result; + }); + } + + private List getAllFields(Class clazz) { + List result = new ArrayList<>(); + Class current = clazz; + while (current != null && current != Object.class) { + for (Field field : current.getDeclaredFields()) { + if (Modifier.isStatic(field.getModifiers()) || field.isSynthetic()) { + continue; + } + field.setAccessible(true); + result.add(field); + } + current = current.getSuperclass(); + } + return result; + } + + /** + * 仅接受字符串类型的 langId 字段 + * + * @param field 字段 + * @param target 对象 + * @return 多语言 ID + */ + private String getStringValue(Field field, Object target) { + Object value = getFieldValue(field, target); + if (value instanceof String string) { + return string; + } + return null; + } + + /** + * 仅读取 Map 类型模板参数字段,未配置时返回空 Map + * + * @param field 字段 + * @param target 对象 + * @return 模板参数 + */ + @SuppressWarnings("unchecked") + private Map getArgMapValue(Field field, Object target) { + if (field == null) { + return Map.of(); + } + Object value = getFieldValue(field, target); + if (value instanceof Map map) { + return (Map) map; + } + return Map.of(); + } + + private Object getFieldValue(Field field, Object target) { + try { + return field.get(target); + } catch (IllegalAccessException e) { + throw new IllegalStateException("读取字段失败: %s#%s".formatted(target.getClass().getName(), field.getName()), e); + } + } + + private void setFieldValue(Field field, Object target, Object value) { + try { + field.set(target, value); + } catch (IllegalAccessException e) { + throw new IllegalStateException("写入字段失败: %s#%s".formatted(target.getClass().getName(), field.getName()), e); + } + } + + private boolean isSimpleValueType(Class clazz) { + if (clazz.isPrimitive() || clazz.isEnum() || clazz.isAnnotation()) { + return true; + } + if (CharSequence.class.isAssignableFrom(clazz) + || Number.class.isAssignableFrom(clazz) + || Boolean.class == clazz + || Character.class == clazz + || UUID.class == clazz + || TemporalAccessor.class.isAssignableFrom(clazz) + || Class.class == clazz + || JsonNode.class.isAssignableFrom(clazz) + || File.class.isAssignableFrom(clazz) + || Path.class.isAssignableFrom(clazz) + || InputStream.class.isAssignableFrom(clazz) + || OutputStream.class.isAssignableFrom(clazz) + || Reader.class.isAssignableFrom(clazz) + || Writer.class.isAssignableFrom(clazz)) { + return true; + } + Package currentPackage = clazz.getPackage(); + if (currentPackage == null) { + return false; + } + String packageName = currentPackage.getName(); + return packageName.startsWith("java.") + || packageName.startsWith("javax.") + || packageName.startsWith("jakarta.") + || packageName.startsWith("sun.") + || packageName.startsWith("com.fasterxml.jackson."); + } + + private record BindingField(Field targetField, Field langIdField, Field argMapField) { + } +} diff --git a/src/main/java/com/imyeyu/api/util/MultilingualResponseBodyAdvice.java b/src/main/java/com/imyeyu/api/util/MultilingualResponseBodyAdvice.java new file mode 100644 index 0000000..cf1f873 --- /dev/null +++ b/src/main/java/com/imyeyu/api/util/MultilingualResponseBodyAdvice.java @@ -0,0 +1,42 @@ +package com.imyeyu.api.util; + +import jakarta.validation.constraints.NotNull; +import lombok.RequiredArgsConstructor; +import org.springframework.core.MethodParameter; +import org.springframework.http.MediaType; +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.http.server.ServerHttpRequest; +import org.springframework.http.server.ServerHttpResponse; +import org.springframework.stereotype.Component; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice; + +/** + * 统一处理响应中的多语言字段回填 + * + * @author 夜雨 + * @since 2026-07-17 00:00 + */ +@Component +@RestControllerAdvice +@RequiredArgsConstructor +public class MultilingualResponseBodyAdvice implements ResponseBodyAdvice { + + private final MultilingualFieldBinder multilingualFieldBinder; + + @Override + public boolean supports(@NotNull MethodParameter returnType, @NotNull Class> converterType) { + return true; + } + + @Override + public Object beforeBodyWrite(Object body, + @NotNull MethodParameter returnType, + @NotNull MediaType selectedContentType, + @NotNull Class> selectedConverterType, + @NotNull ServerHttpRequest request, + @NotNull ServerHttpResponse response) { + multilingualFieldBinder.bind(body); + return body; + } +} diff --git a/src/main/java/com/imyeyu/api/util/RedisLanguage.java b/src/main/java/com/imyeyu/api/util/RedisLanguage.java index 0a07385..51aadd9 100644 --- a/src/main/java/com/imyeyu/api/util/RedisLanguage.java +++ b/src/main/java/com/imyeyu/api/util/RedisLanguage.java @@ -4,44 +4,40 @@ import com.imyeyu.api.TimiServerAPI; import com.imyeyu.api.modules.common.service.MultilingualService; import com.imyeyu.java.bean.Language; import com.imyeyu.lang.mapper.AbstractLanguageMapper; +import com.imyeyu.utils.StringInterpolator; import org.jcodec.api.NotSupportedException; +import java.util.Map; + /** * @author 夜雨 * @version 2024-04-03 11:01 */ public class RedisLanguage extends AbstractLanguageMapper { + private static final StringInterpolator INTERPOLATOR = new StringInterpolator(StringInterpolator.SIMPLE_OBJ); + public RedisLanguage(Language.Enum language) { super(language); } @Override - public void add(String key, String value) { + public void add(String id, String value) { throw new NotSupportedException("not supported to add value, MultilingualService will auto cache when not found value"); } @Override - public boolean has(String key) { - return text(key).equals(key); + public boolean has(String id) { + return text(id).equals(id); } @Override - public String text(String key) { - String result = TimiServerAPI.applicationContext.getBean(MultilingualService.class).getByKey(language, key); - if (result.startsWith("@")) { - // 递归映射 - return text(result.substring(1)); - } else { - if (result.startsWith("\\@")) { - return result.substring(1); - } else { - return result; - } - } + public String text(String id) { + return TimiServerAPI.applicationContext.getBean(MultilingualService.class).get(id).getValue(language); } - public String text(Long id) { - return TimiServerAPI.applicationContext.getBean(MultilingualService.class).get(language, id); + @Override + public String textArgs(String id, Map args) { + return INTERPOLATOR.inject(text(id), args); } } diff --git a/src/main/java/com/imyeyu/api/util/RedisMultilingual.java b/src/main/java/com/imyeyu/api/util/RedisMultilingual.java index a692455..731f905 100644 --- a/src/main/java/com/imyeyu/api/util/RedisMultilingual.java +++ b/src/main/java/com/imyeyu/api/util/RedisMultilingual.java @@ -1,6 +1,5 @@ package com.imyeyu.api.util; -import com.imyeyu.api.modules.common.service.MultilingualService; import com.imyeyu.java.bean.Language; import com.imyeyu.lang.multi.Multilingual; import jakarta.annotation.PostConstruct; @@ -15,13 +14,11 @@ import org.springframework.stereotype.Component; @RequiredArgsConstructor public class RedisMultilingual extends Multilingual { - private final MultilingualService service; - @PostConstruct private void postConstruct() { Language.Enum[] languages = Language.Enum.values(); - for (int i = 0; i < languages.length; i++) { - add(languages[i], new RedisLanguage(languages[i])); + for (Language.Enum language : languages) { + add(language, new RedisLanguage(language)); } } } diff --git a/src/main/resources/application_export.yml b/src/main/resources/application_export.yml index 1ae5249..9d50a76 100644 --- a/src/main/resources/application_export.yml +++ b/src/main/resources/application_export.yml @@ -13,20 +13,7 @@ spring: host: dev.vm.imyeyu.test port: 6379 password: - database: - locker: 0 # ID: Integer 全局锁,ID 规范:应用:模块:业务:方法 - multilingual: 1 # ID: Multilingual 多语言缓存 - multilingual-map: 2 # Key: ID 多语言键缓存 - article-ranking: 3 # AID: ArticleRanking(JSON) 热门文章排位 - article-read: 4 # IP: [AID..] IP 阅读文章记录 - user-token: 5 # TOKEN: LONG 用户登录令牌 - user-exp-flag: 6 # UID: NULL 用户登录经验标记,暂时没有值,数据死亡时间为次日零时 - user-email-verify: 7 # AES_KEY: UID 邮箱验证密钥缓存 - user-reset-pw-verify: 8 # AES_KEY: UID 重置密码密钥缓存 - qps-limit: 9 # APP.IP.method: COUNT_IN_LIFE_CYCLE 接口访问频率控制(多个系统公用,需要 App 标记) - setting: 10 # Setting: SettingValue 系统配置 - fmc-player-token: 11 # TOKEN: USER_ID|PLAYER_ID MC 登录缓存 - clipboard: 12 # ID: CONTENT 共享剪切板 + database: 0 datasource: # 数据库配置 timiserver: jdbc-url: jdbc:mysql://dev.vm.imyeyu.test:3306/timi_server?serverTimezone=UTC&characterEncoding=UTF-8 @@ -50,6 +37,14 @@ spring: database: db username: root password: qweqwe123 + flyway: + enabled: true + locations: classpath:db/migration/timiserver + url: ${spring.datasource.timiserver.jdbc-url} + user: ${spring.datasource.timiserver.username} + password: ${spring.datasource.timiserver.password} + baseline-on-migrate: false + validate-on-migrate: true # CORS 跨域 cors: diff --git a/src/main/resources/db.sql b/src/main/resources/db.sql deleted file mode 100644 index ef7d531..0000000 --- a/src/main/resources/db.sql +++ /dev/null @@ -1,677 +0,0 @@ -/* - Navicat Premium Dump SQL - - Source Server : dev_mariadb - Source Server Type : MariaDB - Source Server Version : 110802 (11.8.2-MariaDB-ubu2404) - Source Host : vm.imyeyu.dev:3307 - Source Schema : timi_server - - Target Server Type : MariaDB - Target Server Version : 110802 (11.8.2-MariaDB-ubu2404) - File Encoding : 65001 - - Date: 12/12/2025 14:24:29 -*/ - -SET NAMES utf8mb4; -SET FOREIGN_KEY_CHECKS = 0; - --- ---------------------------- --- Table structure for article --- ---------------------------- -DROP TABLE IF EXISTS `article`; -CREATE TABLE `article` ( - `id` bigint(20) NOT NULL AUTO_INCREMENT, - `title` varchar(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '标题', - `type` varchar(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '类型', - `digest` varchar(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '摘要', - `data` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '数据', - `extend_data` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT '扩展数据 JSON' CHECK (json_valid(`extend_data`)), - `reads` int(11) NULL DEFAULT 0 COMMENT '阅读', - `likes` int(11) NULL DEFAULT 0 COMMENT '喜欢', - `show_comment` tinyint(1) NULL DEFAULT 1 COMMENT '1 为显示历史评论', - `can_comment` tinyint(1) NULL DEFAULT 1 COMMENT '1 为允许评论', - `can_ranking` tinyint(1) NULL DEFAULT 1 COMMENT '1 为允许进入每周排行列表', - `can_list` tinyint(1) NULL DEFAULT NULL COMMENT '1 为允许通过列表查询', - `created_at` bigint(20) NULL DEFAULT NULL, - `updated_at` bigint(20) NULL DEFAULT NULL, - `deleted_at` bigint(20) NULL DEFAULT NULL, - PRIMARY KEY (`id`) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 128 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '文章' ROW_FORMAT = Dynamic; - --- ---------------------------- --- Table structure for async_task --- ---------------------------- -DROP TABLE IF EXISTS `async_task`; -CREATE TABLE `async_task` ( - `uuid` varchar(38) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, - `name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL, - `type` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL, - `message` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL, - `status` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL, - `progress` double NULL DEFAULT NULL, - `start_at` bigint(20) NULL DEFAULT NULL, - `interrupt_at` bigint(20) NULL DEFAULT NULL, - `error_at` bigint(20) NULL DEFAULT NULL, - `died_at` bigint(20) NULL DEFAULT NULL, - `can_pause` tinyint(1) NULL DEFAULT NULL, - `can_interrupt` tinyint(1) NULL DEFAULT NULL, - `is_periodical` tinyint(1) NULL DEFAULT NULL, - `cron` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL, - `created_at` bigint(20) NULL DEFAULT NULL, - PRIMARY KEY (`uuid`) USING BTREE -) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci ROW_FORMAT = Dynamic; - --- ---------------------------- --- Table structure for attachment --- ---------------------------- -DROP TABLE IF EXISTS `attachment`; -CREATE TABLE `attachment` ( - `id` bigint(20) NOT NULL AUTO_INCREMENT, - `biz_type` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, - `biz_id` bigint(20) NOT NULL, - `attach_type` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL, - `mongo_id` varchar(24) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, - `title` varchar(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL, - `name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL, - `mime_type` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL, - `metadata` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL, - `size` bigint(20) NULL DEFAULT NULL, - `md5` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL, - `ext` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL, - `created_at` bigint(20) NOT NULL, - `updated_at` bigint(20) NULL DEFAULT NULL, - `deleted_at` bigint(20) NULL DEFAULT NULL, - `destroy_at` bigint(20) NULL DEFAULT NULL, - PRIMARY KEY (`id`) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 530 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '附件' ROW_FORMAT = Dynamic; - --- ---------------------------- --- Table structure for bill --- ---------------------------- -DROP TABLE IF EXISTS `bill`; -CREATE TABLE `bill` ( - `id` bigint(20) NOT NULL AUTO_INCREMENT, - `revenue_type` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '收入类型', - `expenditure_type` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '支出类型', - `description` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '说明', - `remarks` varchar(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '备注', - `decimal` bigint(20) NOT NULL DEFAULT 0 COMMENT '金额(放大了 100 倍)', - `created_at` bigint(20) NULL DEFAULT NULL, - `updated_at` bigint(20) NULL DEFAULT NULL, - `deleted_at` bigint(20) NULL DEFAULT NULL, - PRIMARY KEY (`id`) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 3687 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '消费账单' ROW_FORMAT = Dynamic; - --- ---------------------------- --- Table structure for comment --- ---------------------------- -DROP TABLE IF EXISTS `comment`; -CREATE TABLE `comment` ( - `id` bigint(20) NOT NULL AUTO_INCREMENT, - `biz_type` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '关联业务', - `biz_id` bigint(20) NOT NULL COMMENT '关联业务 ID', - `user_id` bigint(20) NULL DEFAULT NULL COMMENT '评论者', - `nick` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '评论者昵称', - `content` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '内容', - `ip` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL, - `created_at` bigint(20) NULL DEFAULT NULL, - `updated_at` bigint(20) NULL DEFAULT NULL, - `deleted_at` bigint(20) NULL DEFAULT NULL, - PRIMARY KEY (`id`) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 69 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '评论' ROW_FORMAT = Dynamic; - --- ---------------------------- --- Table structure for comment_remind_queue --- ---------------------------- -DROP TABLE IF EXISTS `comment_remind_queue`; -CREATE TABLE `comment_remind_queue` ( - `uuid` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT 'UUID', - `user_id` bigint(20) NOT NULL COMMENT '被回复用户 ID', - `reply_id` bigint(20) NOT NULL COMMENT '回复 ID', - PRIMARY KEY (`uuid`) USING BTREE -) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '评论回复提醒队列,被回复用户是注册用户并且开启邮件推送时,缓存在本表,由邮件推送服务操作' ROW_FORMAT = Dynamic; - --- ---------------------------- --- Table structure for comment_reply --- ---------------------------- -DROP TABLE IF EXISTS `comment_reply`; -CREATE TABLE `comment_reply` ( - `id` bigint(20) NOT NULL AUTO_INCREMENT, - `comment_id` bigint(20) NOT NULL, - `reply_id` bigint(20) NULL DEFAULT NULL COMMENT '被回复的回复,回复主评论时为 NULL', - `sender_id` bigint(20) NULL DEFAULT NULL COMMENT '回复者', - `receiver_id` bigint(20) NULL DEFAULT NULL COMMENT '被回复者', - `sender_nick` varchar(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '回复者昵称', - `receiver_nick` varchar(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '被回复者昵称', - `content` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '内容', - `ip` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '回复时 IP', - `ignored_at` bigint(20) NULL DEFAULT NULL COMMENT '被回复者忽略该回复的时间', - `created_at` bigint(20) NULL DEFAULT NULL, - `updated_at` bigint(20) NULL DEFAULT NULL, - `deleted_at` bigint(20) NULL DEFAULT NULL, - PRIMARY KEY (`id`) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 38 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '评论回复' ROW_FORMAT = Dynamic; - --- ---------------------------- --- Table structure for comment_reply_record --- ---------------------------- -DROP TABLE IF EXISTS `comment_reply_record`; -CREATE TABLE `comment_reply_record` ( - `id` bigint(20) NOT NULL AUTO_INCREMENT, - `user_id` bigint(20) NOT NULL COMMENT '用户 ID', - `reply_id` bigint(20) NOT NULL COMMENT '回复 ID', - `created_at` bigint(20) NOT NULL, - `deleted_at` bigint(20) NULL DEFAULT NULL, - PRIMARY KEY (`id`) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '评论回复记录(显示在个人空间\"回复我的\"),因“仅删除回复记录不删除实体回复”功能而作此表' ROW_FORMAT = Dynamic; - --- ---------------------------- --- Table structure for email_filter --- ---------------------------- -DROP TABLE IF EXISTS `email_filter`; -CREATE TABLE `email_filter` ( - `id` bigint(20) NOT NULL AUTO_INCREMENT, - `email` varchar(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, - `from` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, - `created_at` bigint(20) NULL DEFAULT NULL, - `deleted_at` bigint(20) NULL DEFAULT NULL, - PRIMARY KEY (`id`) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '通过邮件直接拒绝系统发送的列表' ROW_FORMAT = Dynamic; - --- ---------------------------- --- Table structure for email_queue --- ---------------------------- -DROP TABLE IF EXISTS `email_queue`; -CREATE TABLE `email_queue` ( - `uuid` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT 'UUID', - `biz_type` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT 'EmailType 推送类型', - `biz_id` bigint(20) NOT NULL DEFAULT 0 COMMENT '推送数据 ID', - `send_at` bigint(20) NOT NULL DEFAULT 0 COMMENT '推送时间', - PRIMARY KEY (`uuid`) USING BTREE -) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '邮件推送队列' ROW_FORMAT = Dynamic; - --- ---------------------------- --- Table structure for email_queue_log --- ---------------------------- -DROP TABLE IF EXISTS `email_queue_log`; -CREATE TABLE `email_queue_log` ( - `id` bigint(20) NOT NULL AUTO_INCREMENT, - `uuid` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '队列 UUID', - `biz_type` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT 'EmailType 类型', - `biz_id` bigint(20) NULL DEFAULT NULL COMMENT '数据 ID', - `send_to` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '目标', - `send_at` bigint(20) NULL DEFAULT NULL COMMENT '时间', - `is_sent` tinyint(4) NULL DEFAULT NULL COMMENT '1 为成功', - `exception_msg` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '异常信息', - `created_at` bigint(20) NULL DEFAULT NULL, - `updated_at` bigint(20) NULL DEFAULT NULL, - `deleted_at` bigint(20) NULL DEFAULT NULL, - PRIMARY KEY (`id`) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '邮件推送队列日志' ROW_FORMAT = Dynamic; - --- ---------------------------- --- Table structure for feedback --- ---------------------------- -DROP TABLE IF EXISTS `feedback`; -CREATE TABLE `feedback` ( - `id` bigint(20) NOT NULL AUTO_INCREMENT, - `from` varchar(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '来自', - `email` varchar(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '联系邮箱', - `data` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '内容', - `ip` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT 'IP', - `created_at` bigint(20) NULL DEFAULT NULL, - `updated_at` bigint(20) NULL DEFAULT NULL, - `deleted_at` bigint(20) NULL DEFAULT NULL, - PRIMARY KEY (`id`) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '反馈' ROW_FORMAT = Dynamic; - --- ---------------------------- --- Table structure for friend --- ---------------------------- -DROP TABLE IF EXISTS `friend`; -CREATE TABLE `friend` ( - `id` bigint(20) NOT NULL AUTO_INCREMENT, - `icon` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '图标 Base64', - `name` varchar(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '标题', - `link` varchar(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '链接', - `created_at` bigint(20) NULL DEFAULT NULL, - `updated_at` bigint(20) NULL DEFAULT NULL, - `deleted_at` bigint(20) NULL DEFAULT NULL, - PRIMARY KEY (`id`) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 8 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '友链' ROW_FORMAT = Dynamic; - --- ---------------------------- --- Table structure for git_issue --- ---------------------------- -DROP TABLE IF EXISTS `git_issue`; -CREATE TABLE `git_issue` ( - `id` bigint(20) NOT NULL AUTO_INCREMENT, - `repository_id` bigint(20) NOT NULL COMMENT '所属仓库 ID', - `publisher_id` bigint(20) NULL DEFAULT NULL COMMENT '所属用户 ID', - `publisher_nick` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '游客昵称', - `type` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '类型', - `version` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '版本', - `title` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '标题', - `description` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '说明', - `status` varbinary(50) NULL DEFAULT NULL COMMENT '状态', - `confirmed_at` bigint(20) NULL DEFAULT NULL COMMENT '确认时间', - `develop_at` bigint(20) NULL DEFAULT NULL COMMENT '开发时间', - `closed_at` bigint(20) NULL DEFAULT NULL COMMENT '关闭时间', - `created_at` bigint(20) NULL DEFAULT NULL, - `updated_at` bigint(20) NULL DEFAULT NULL, - `deleted_at` bigint(20) NULL DEFAULT NULL, - PRIMARY KEY (`id`) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'Git 问题报告' ROW_FORMAT = Dynamic; - --- ---------------------------- --- Table structure for git_merge --- ---------------------------- -DROP TABLE IF EXISTS `git_merge`; -CREATE TABLE `git_merge` ( - `id` bigint(20) NOT NULL AUTO_INCREMENT, - `repository_id` bigint(20) NOT NULL COMMENT '所属仓库 ID', - `requester_id` bigint(20) NOT NULL COMMENT '请求用户 ID', - `issue_id` bigint(20) NULL DEFAULT NULL COMMENT '相关反馈 ID', - `type` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '类型', - `from_branch` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '来源分支', - `to_branch` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '去向分支', - `title` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '标题', - `description` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '说明', - `checked_at` bigint(20) NULL DEFAULT NULL COMMENT '通过审查时间', - `merged_at` bigint(20) NULL DEFAULT NULL COMMENT '合并时间', - `rejected_at` bigint(20) NULL DEFAULT NULL COMMENT '拒绝时间', - `reject_reason` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '拒绝原因', - `closed_at` bigint(20) NULL DEFAULT NULL COMMENT '关闭时间', - `status` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '状态', - `created_at` bigint(20) NOT NULL, - `updated_at` bigint(20) NULL DEFAULT NULL, - `deleted_at` bigint(20) NULL DEFAULT NULL, - PRIMARY KEY (`id`) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'Git 合并申请' ROW_FORMAT = Dynamic; - --- ---------------------------- --- Table structure for git_release --- ---------------------------- -DROP TABLE IF EXISTS `git_release`; -CREATE TABLE `git_release` ( - `id` bigint(20) NOT NULL AUTO_INCREMENT, - `repository_id` bigint(20) NOT NULL COMMENT '所属仓库 ID', - `version` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '版本', - `description` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '说明', - `sha1` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '版本 SHA1', - `commits` int(11) NOT NULL DEFAULT 0 COMMENT '此版本后提交次数', - `created_at` bigint(20) NULL DEFAULT NULL, - `updated_at` bigint(20) NULL DEFAULT NULL, - `deleted_at` bigint(20) NULL DEFAULT NULL, - PRIMARY KEY (`id`) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'Git 版本发布' ROW_FORMAT = Dynamic; - --- ---------------------------- --- Table structure for icon --- ---------------------------- -DROP TABLE IF EXISTS `icon`; -CREATE TABLE `icon` ( - `id` bigint(20) NOT NULL AUTO_INCREMENT, - `name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, - `unicode` varchar(8) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL, - `svg` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, - `created_at` bigint(20) NULL DEFAULT NULL, - `updated_at` bigint(20) NULL DEFAULT NULL, - `deleted_at` bigint(20) NULL DEFAULT NULL, - PRIMARY KEY (`id`) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 150 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '字体图标' ROW_FORMAT = Dynamic; - --- ---------------------------- --- Table structure for journal --- ---------------------------- -DROP TABLE IF EXISTS `journal`; -CREATE TABLE `journal` ( - `id` bigint(20) NOT NULL AUTO_INCREMENT, - `type` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL, - `idea` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL, - `lat` double NULL DEFAULT NULL, - `lng` double NULL DEFAULT NULL, - `location` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL, - `weather` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL, - `pusher` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL, - `created_at` bigint(20) NULL DEFAULT NULL, - `updated_at` bigint(20) NULL DEFAULT NULL, - `deleted_at` bigint(20) NULL DEFAULT NULL, - PRIMARY KEY (`id`) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 48 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci ROW_FORMAT = Dynamic; - --- ---------------------------- --- Table structure for lyric --- ---------------------------- -DROP TABLE IF EXISTS `lyric`; -CREATE TABLE `lyric` ( - `id` bigint(20) NOT NULL AUTO_INCREMENT, - `song` varchar(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '歌曲名', - `singer` varchar(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '演唱', - `data` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '歌词', - `citations` bigint(20) NOT NULL DEFAULT 0 COMMENT '被引用次数,更新者不为空时开始计数', - `updated_by` bigint(20) NULL DEFAULT NULL COMMENT '更新者 ID', - `created_at` bigint(20) NULL DEFAULT NULL, - `updated_at` bigint(20) NULL DEFAULT NULL, - `deleted_at` bigint(20) NULL DEFAULT NULL, - PRIMARY KEY (`id`) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '歌词' ROW_FORMAT = Dynamic; - --- ---------------------------- --- Table structure for lyric_corrects --- ---------------------------- -DROP TABLE IF EXISTS `lyric_corrects`; -CREATE TABLE `lyric_corrects` ( - `id` bigint(20) NOT NULL AUTO_INCREMENT, - `lyric_id` bigint(20) NULL DEFAULT NULL COMMENT '申请更新歌词 ID', - `song` varchar(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '歌曲名', - `singer` varchar(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '演唱', - `data` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '歌词', - `request_by` bigint(20) NULL DEFAULT NULL COMMENT '申请人', - `request_ip` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '申请人 IP', - `cancel_at` bigint(20) NULL DEFAULT NULL COMMENT '取消时间', - `approval_at` bigint(20) NULL DEFAULT NULL COMMENT '通过时间', - `reject_at` bigint(20) NULL DEFAULT NULL COMMENT '拒绝时间', - `reject_reason` varchar(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '拒绝原因', - `created_at` bigint(20) NULL DEFAULT NULL, - `updated_at` bigint(20) NULL DEFAULT NULL, - `deleted_at` bigint(20) NULL DEFAULT NULL, - PRIMARY KEY (`id`) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '歌词更新申请' ROW_FORMAT = Dynamic; - --- ---------------------------- --- Table structure for minecraft_pack --- ---------------------------- -DROP TABLE IF EXISTS `minecraft_pack`; -CREATE TABLE `minecraft_pack` ( - `id` bigint(20) NOT NULL AUTO_INCREMENT, - `name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '游戏 ID', - `ver` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '整合包版本', - `title` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '标题', - `description` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '简介', - `game_ver` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '游戏版本', - `def_option` varchar(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '默认配置', - `size` bigint(20) NOT NULL DEFAULT 0 COMMENT '文件大小', - `is_deprecated` tinyint(4) NOT NULL DEFAULT 0 COMMENT 'true 为已过时', - `created_at` bigint(20) NULL DEFAULT NULL, - `updated_at` bigint(20) NULL DEFAULT NULL, - `deleted_at` bigint(20) NULL DEFAULT NULL, - PRIMARY KEY (`id`) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'Minecraft 客户端列表' ROW_FORMAT = Dynamic; - --- ---------------------------- --- Table structure for minecraft_pack_source --- ---------------------------- -DROP TABLE IF EXISTS `minecraft_pack_source`; -CREATE TABLE `minecraft_pack_source` ( - `id` bigint(20) NOT NULL AUTO_INCREMENT, - `pack_id` bigint(20) NOT NULL COMMENT '整合版 ID', - `name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '数据源名称', - `type` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '数据类型', - `data` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '数据', - `order` int(11) NOT NULL DEFAULT 0 COMMENT '排序', - `is_default` tinyint(4) NOT NULL DEFAULT 0 COMMENT 'true 为默认', - `created_at` bigint(20) NULL DEFAULT NULL, - `updated_at` bigint(20) NULL DEFAULT NULL, - `deleted_at` bigint(20) NULL DEFAULT NULL, - PRIMARY KEY (`id`) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'Minecraft 客户端下载源' ROW_FORMAT = Dynamic; - --- ---------------------------- --- Table structure for minecraft_player --- ---------------------------- -DROP TABLE IF EXISTS `minecraft_player`; -CREATE TABLE `minecraft_player` ( - `id` bigint(20) NOT NULL AUTO_INCREMENT, - `user_id` bigint(20) NOT NULL COMMENT '关联 User.id', - `name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '游戏名', - `last_login_ip` varchar(15) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '最近登录 IP', - `last_login_at` bigint(20) NULL DEFAULT NULL COMMENT '最近登录', - `created_at` bigint(20) NULL DEFAULT NULL, - `updated_at` bigint(20) NULL DEFAULT NULL, - `deleted_at` bigint(20) NULL DEFAULT NULL, - PRIMARY KEY (`id`, `user_id`) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'Minecraft 玩家' ROW_FORMAT = Dynamic; - --- ---------------------------- --- Table structure for mirror --- ---------------------------- -DROP TABLE IF EXISTS `mirror`; -CREATE TABLE `mirror` ( - `id` bigint(20) NOT NULL AUTO_INCREMENT, - `bean` varchar(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '执行类', - `name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '名称', - `data` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '镜像数据', - `period` int(11) NOT NULL COMMENT '周期(分钟)', - `last_sync_at` bigint(20) NULL DEFAULT NULL COMMENT '最近同步时间', - `is_enable` tinyint(4) NOT NULL DEFAULT 0 COMMENT 'true 为启用同步', - `created_at` bigint(20) NULL DEFAULT NULL, - `updated_at` bigint(20) NULL DEFAULT NULL, - `deleted_at` bigint(20) NULL DEFAULT NULL, - PRIMARY KEY (`id`) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '镜像' ROW_FORMAT = Dynamic; - --- ---------------------------- --- Table structure for multilingual --- ---------------------------- -DROP TABLE IF EXISTS `multilingual`; -CREATE TABLE `multilingual` ( - `id` bigint(20) NOT NULL AUTO_INCREMENT, - `key` varchar(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, - `zh_cn` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL, - `zh_tw` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL, - `en_us` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL, - `ru_ru` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL, - `ja_jp` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL, - `de_de` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL, - `ko_kr` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL, - `created_at` bigint(20) NULL DEFAULT NULL, - `updated_at` bigint(20) NULL DEFAULT NULL, - `deleted_at` bigint(20) NULL DEFAULT NULL, - PRIMARY KEY (`id`) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 795 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '多语言映射' ROW_FORMAT = Dynamic; - --- ---------------------------- --- Table structure for setting --- ---------------------------- -DROP TABLE IF EXISTS `setting`; -CREATE TABLE `setting` ( - `key` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, - `value` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL, - `type` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, - `description` varchar(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL, - `is_private` tinyint(1) NOT NULL DEFAULT 1, - `created_at` bigint(20) NULL DEFAULT NULL, - `updated_at` bigint(20) NULL DEFAULT NULL, - PRIMARY KEY (`key`) USING BTREE -) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '系统配置' ROW_FORMAT = Dynamic; - --- ---------------------------- --- Table structure for tag --- ---------------------------- -DROP TABLE IF EXISTS `tag`; -CREATE TABLE `tag` ( - `id` bigint(20) NOT NULL, - `biz_type` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, - `biz_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, - `value` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL, - `created_at` bigint(20) NULL DEFAULT NULL, - `updated_at` bigint(20) NULL DEFAULT NULL, - `deleted_at` bigint(20) NULL DEFAULT NULL, - PRIMARY KEY (`id`) USING BTREE -) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci ROW_FORMAT = Dynamic; - --- ---------------------------- --- Table structure for task --- ---------------------------- -DROP TABLE IF EXISTS `task`; -CREATE TABLE `task` ( - `id` bigint(20) NOT NULL AUTO_INCREMENT, - `name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '名称', - `digest` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '摘要', - `status` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '状态', - `is_public` tinyint(1) NOT NULL DEFAULT 1 COMMENT 'true 为公开', - `created_at` bigint(20) NULL DEFAULT NULL, - `updated_at` bigint(20) NULL DEFAULT NULL, - `deleted_at` bigint(20) NULL DEFAULT NULL, - PRIMARY KEY (`id`) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '任务列表' ROW_FORMAT = Dynamic; - --- ---------------------------- --- Table structure for task_detail --- ---------------------------- -DROP TABLE IF EXISTS `task_detail`; -CREATE TABLE `task_detail` ( - `id` bigint(20) NOT NULL AUTO_INCREMENT, - `task_id` bigint(20) NOT NULL, - `type` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '类型', - `status` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '任务状态', - `digest` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '摘要', - `description` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '附加说明', - `complete_at` bigint(20) NULL DEFAULT NULL COMMENT '完成时间', - `close_at` bigint(20) NULL DEFAULT NULL COMMENT '关闭时间', - `close_reason` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '关闭原因', - `created_at` bigint(20) NULL DEFAULT NULL, - `updated_at` bigint(20) NULL DEFAULT NULL, - `deleted_at` bigint(20) NULL DEFAULT NULL, - PRIMARY KEY (`id`) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '任务详细列表' ROW_FORMAT = Dynamic; - --- ---------------------------- --- Table structure for template --- ---------------------------- -DROP TABLE IF EXISTS `template`; -CREATE TABLE `template` ( - `id` bigint(20) NOT NULL AUTO_INCREMENT, - `biz_type` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, - `biz_code` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, - `data` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL, - `created_at` bigint(20) NULL DEFAULT NULL, - `updated_at` bigint(20) NULL DEFAULT NULL, - `deleted_at` bigint(20) NULL DEFAULT NULL, - PRIMARY KEY (`id`) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 6 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '模板' ROW_FORMAT = Dynamic; - --- ---------------------------- --- Table structure for travel --- ---------------------------- -DROP TABLE IF EXISTS `travel`; -CREATE TABLE `travel` ( - `id` bigint(20) NOT NULL, - `transportation_type` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '交通类型', - `title` varchar(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '标题', - `content` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '内容', - `travel_at` bigint(20) NULL DEFAULT NULL COMMENT '出行时间', - `days` int(11) NULL DEFAULT NULL COMMENT '天数', - `status` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '状态', - `created_at` bigint(20) NULL DEFAULT NULL, - `updated_at` bigint(20) NULL DEFAULT NULL, - `deleted_at` bigint(20) NULL DEFAULT NULL, - PRIMARY KEY (`id`) USING BTREE -) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '旅行计划' ROW_FORMAT = Dynamic; - --- ---------------------------- --- Table structure for travel_location --- ---------------------------- -DROP TABLE IF EXISTS `travel_location`; -CREATE TABLE `travel_location` ( - `id` bigint(20) NOT NULL, - `travel_id` bigint(20) NOT NULL COMMENT '旅行计划 ID', - `type` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '类型', - `title` varchar(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '标题', - `description` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '说明', - `lat` double NULL DEFAULT NULL COMMENT '经度', - `lng` double NULL DEFAULT NULL COMMENT '维度', - `location` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '位置', - `amount` decimal(10, 2) NULL DEFAULT NULL COMMENT '费用', - `require_id_card` tinyint(4) NULL DEFAULT NULL COMMENT '1 为需要身份证', - `score` int(11) NULL DEFAULT NULL COMMENT '必要评分', - `created_at` bigint(20) NULL DEFAULT NULL, - `updated_at` bigint(20) NULL DEFAULT NULL, - `deleted_at` bigint(20) NULL DEFAULT NULL, - PRIMARY KEY (`id`) USING BTREE -) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '旅行地点' ROW_FORMAT = Dynamic; - --- ---------------------------- --- Table structure for user --- ---------------------------- -DROP TABLE IF EXISTS `user`; -CREATE TABLE `user` ( - `id` bigint(20) NOT NULL AUTO_INCREMENT, - `name` varchar(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '用户名', - `password` varchar(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '密码摘要', - `email` varchar(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '邮箱', - `email_verify_at` bigint(20) NULL DEFAULT NULL COMMENT '邮箱验证时间', - `unmute_at` bigint(20) NULL DEFAULT NULL COMMENT '解除禁言时间', - `unban_at` bigint(20) NULL DEFAULT NULL COMMENT '解除封禁时间', - `created_at` bigint(20) NULL DEFAULT NULL, - `updated_at` bigint(20) NULL DEFAULT NULL, - `deleted_at` bigint(20) NULL DEFAULT NULL, - PRIMARY KEY (`id`) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '用户' ROW_FORMAT = Dynamic; - --- ---------------------------- --- Table structure for user_config --- ---------------------------- -DROP TABLE IF EXISTS `user_config`; -CREATE TABLE `user_config` ( - `user_id` bigint(20) NOT NULL, - `email_reply_remind` tinyint(1) NOT NULL DEFAULT 1 COMMENT '1 为邮箱接收回复提醒', - `updated_at` bigint(20) NULL DEFAULT NULL COMMENT '配置更新时间', - PRIMARY KEY (`user_id`) USING BTREE -) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '用户配置' ROW_FORMAT = Dynamic; - --- ---------------------------- --- Table structure for user_privacy --- ---------------------------- -DROP TABLE IF EXISTS `user_privacy`; -CREATE TABLE `user_privacy` ( - `user_id` bigint(20) NOT NULL, - `email` tinyint(1) NOT NULL DEFAULT 1, - `sex` tinyint(1) NOT NULL DEFAULT 1, - `birthdate` tinyint(1) NOT NULL DEFAULT 1, - `qq` tinyint(1) NOT NULL DEFAULT 1, - `last_login_at` tinyint(1) NOT NULL DEFAULT 1, - `created_at` tinyint(1) NOT NULL DEFAULT 1, - `updated_at` bigint(20) NULL DEFAULT NULL, - PRIMARY KEY (`user_id`) USING BTREE -) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '用户隐私控制,user_id 关联用户,updated_at 为本表更新时间,其他字段 1 为公开,0 为不公开' ROW_FORMAT = Dynamic; - --- ---------------------------- --- Table structure for user_profile --- ---------------------------- -DROP TABLE IF EXISTS `user_profile`; -CREATE TABLE `user_profile` ( - `user_id` bigint(20) NOT NULL COMMENT '用户 ID', - `wrapper_type` varchar(15) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'PIXELATED' COMMENT '背景图渲染算法', - `avatar_type` varchar(15) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'PIXELATED' COMMENT '头像渲染算法', - `exp` int(11) NOT NULL DEFAULT 0 COMMENT '经验值', - `sex` int(11) NULL DEFAULT NULL COMMENT '性别:0 女,1 男', - `birthdate` bigint(20) NULL DEFAULT NULL COMMENT '出生日期', - `qq` varchar(15) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT 'QQ', - `description` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '说明', - `last_login_ip` varchar(15) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '上次登录 IP', - `last_login_at` bigint(20) NULL DEFAULT NULL COMMENT '上次登录时间', - `updated_at` bigint(20) NULL DEFAULT NULL, - PRIMARY KEY (`user_id`) USING BTREE -) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '用户资料' ROW_FORMAT = Dynamic; - --- ---------------------------- --- Table structure for version --- ---------------------------- -DROP TABLE IF EXISTS `version`; -CREATE TABLE `version` ( - `id` bigint(20) NOT NULL AUTO_INCREMENT, - `name` varchar(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '名称', - `version` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '版本', - `content` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '更新摘要', - `url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '更新地址', - `created_at` bigint(20) NULL DEFAULT NULL, - `updated_at` bigint(20) NULL DEFAULT NULL, - `deleted_at` bigint(20) NULL DEFAULT NULL, - PRIMARY KEY (`id`) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 15 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '软件版本管理' ROW_FORMAT = Dynamic; - -SET FOREIGN_KEY_CHECKS = 1; diff --git a/src/main/resources/db/migration/timiserver/V10__create_tag_tables.sql b/src/main/resources/db/migration/timiserver/V10__create_tag_tables.sql new file mode 100644 index 0000000..4b8ce1f --- /dev/null +++ b/src/main/resources/db/migration/timiserver/V10__create_tag_tables.sql @@ -0,0 +1,23 @@ +DROP TABLE IF EXISTS `tag`; +CREATE TABLE `tag` ( + `id` VARCHAR(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `name_lang_id` VARCHAR(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '名称语言映射 ID', + `created_at` BIGINT(20) COMMENT '创建时间', + `updated_at` BIGINT(20) COMMENT '更新时间', + `deleted_at` BIGINT(20) COMMENT '删除时间', + PRIMARY KEY (`id`) USING BTREE, + INDEX `idx_name_lang_id`(`name_lang_id` ASC) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '标签' ROW_FORMAT = Dynamic; + +DROP TABLE IF EXISTS `tag_apply`; +CREATE TABLE `tag_apply` ( + `id` VARCHAR(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `biz_type` VARCHAR(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '业务类型', + `biz_id` VARCHAR(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '业务 ID', + `tag_id` VARCHAR(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '标签 ID', + `created_at` BIGINT(20) COMMENT '创建时间', + `updated_at` BIGINT(20) COMMENT '更新时间', + `deleted_at` BIGINT(20) COMMENT '删除时间', + PRIMARY KEY (`id`) USING BTREE, + INDEX `idx_tag_apply`(`biz_type` ASC, `biz_id` ASC, `tag_id` ASC) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '标签应用' ROW_FORMAT = Dynamic; diff --git a/src/main/resources/db/migration/timiserver/V11__create_icon_table.sql b/src/main/resources/db/migration/timiserver/V11__create_icon_table.sql new file mode 100644 index 0000000..9dad13a --- /dev/null +++ b/src/main/resources/db/migration/timiserver/V11__create_icon_table.sql @@ -0,0 +1,12 @@ +DROP TABLE IF EXISTS `icon`; +CREATE TABLE `icon` ( + `id` VARCHAR(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `name` VARCHAR(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `unicode` VARCHAR(8) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, + `svg` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `created_at` BIGINT(20), + `updated_at` BIGINT(20), + `deleted_at` BIGINT(20), + PRIMARY KEY (`id`) USING BTREE, + INDEX `idx_name`(`name` ASC) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '图标' ROW_FORMAT = Dynamic; diff --git a/src/main/resources/db/migration/timiserver/V12__create_lyric_tables.sql b/src/main/resources/db/migration/timiserver/V12__create_lyric_tables.sql new file mode 100644 index 0000000..b0392f1 --- /dev/null +++ b/src/main/resources/db/migration/timiserver/V12__create_lyric_tables.sql @@ -0,0 +1,32 @@ +DROP TABLE IF EXISTS `lyric`; +CREATE TABLE `lyric` ( + `id` VARCHAR(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '主键', + `song` VARCHAR(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '歌曲名', + `singer` VARCHAR(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '演唱', + `content` LONGTEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '歌词', + `citations` BIGINT(20) NOT NULL DEFAULT 0 COMMENT '被引用次数,更新者不为空时开始计数', + `updated_by` BIGINT(20) COMMENT '更新者 ID', + `created_at` BIGINT(20), + `updated_at` BIGINT(20), + `deleted_at` BIGINT(20), + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '歌词' ROW_FORMAT = Dynamic; + +DROP TABLE IF EXISTS `lyric_corrects`; +CREATE TABLE `lyric_corrects` ( + `id` VARCHAR(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '主键', + `lyric_id` VARCHAR(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT '申请更新歌词 ID', + `song` VARCHAR(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT '歌曲名', + `singer` VARCHAR(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT '演唱', + `content` LONGTEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT '歌词', + `request_by` VARCHAR(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT '申请人', + `request_ip` VARCHAR(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT '申请人 IP', + `cancel_at` BIGINT(20) COMMENT '取消时间', + `approval_at` BIGINT(20) COMMENT '通过时间', + `reject_at` BIGINT(20) COMMENT '拒绝时间', + `reject_reason` VARCHAR(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT '拒绝原因', + `created_at` BIGINT(20), + `updated_at` BIGINT(20), + `deleted_at` BIGINT(20), + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '歌词修正申请' ROW_FORMAT = Dynamic; diff --git a/src/main/resources/db/migration/timiserver/V13__create_mirror_table.sql b/src/main/resources/db/migration/timiserver/V13__create_mirror_table.sql new file mode 100644 index 0000000..5384442 --- /dev/null +++ b/src/main/resources/db/migration/timiserver/V13__create_mirror_table.sql @@ -0,0 +1,15 @@ +DROP TABLE IF EXISTS `mirror`; +CREATE TABLE `mirror` ( + `id` VARCHAR(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '主键', + `bean` VARCHAR(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '执行类', + `name` VARCHAR(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '名称', + `data` LONGTEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT '镜像数据', + `period` INT(11) NOT NULL COMMENT '周期(分钟)', + `last_sync_at` BIGINT(20) COMMENT '最近同步时间', + `is_enable` TINYINT(1) NOT NULL DEFAULT 0 COMMENT 'true 为启用同步', + `created_at` BIGINT(20), + `updated_at` BIGINT(20), + `deleted_at` BIGINT(20), + PRIMARY KEY (`id`) USING BTREE, + INDEX `idx_bean`(`bean` ASC) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '镜像' ROW_FORMAT = Dynamic; diff --git a/src/main/resources/db/migration/timiserver/V14__create_journal_travel_tables.sql b/src/main/resources/db/migration/timiserver/V14__create_journal_travel_tables.sql new file mode 100644 index 0000000..53d6169 --- /dev/null +++ b/src/main/resources/db/migration/timiserver/V14__create_journal_travel_tables.sql @@ -0,0 +1,54 @@ +DROP TABLE IF EXISTS `journal`; +CREATE TABLE `journal` ( + `id` VARCHAR(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `type` VARCHAR(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, + `idea` LONGTEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, + `lat` decimal(10, 7) COMMENT '纬度', + `lng` decimal(10, 7) COMMENT '经度', + `location` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, + `weather` VARCHAR(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, + `pusher` VARCHAR(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, + `created_at` BIGINT(20), + `updated_at` BIGINT(20), + `deleted_at` BIGINT(20), + PRIMARY KEY (`id`) USING BTREE, + INDEX `idx_deleted_created`(`deleted_at` ASC, `created_at` ASC) USING BTREE, + INDEX `idx_type_deleted_created`(`type` ASC, `deleted_at` ASC, `created_at` ASC) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '旅行日记' ROW_FORMAT = Dynamic; + +DROP TABLE IF EXISTS `travel`; +CREATE TABLE `travel` ( + `id` VARCHAR(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '主键', + `transportation_type` VARCHAR(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT '交通类型', + `title` VARCHAR(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT '标题', + `content` LONGTEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT '内容', + `travel_at` BIGINT(20) COMMENT '出行时间', + `days` INT(11) COMMENT '天数', + `status` VARCHAR(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT '状态', + `created_at` BIGINT(20), + `updated_at` BIGINT(20), + `deleted_at` BIGINT(20), + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '旅行计划' ROW_FORMAT = Dynamic; + +DROP TABLE IF EXISTS `travel_location`; +CREATE TABLE `travel_location` ( + `id` VARCHAR(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '主键', + `travel_id` VARCHAR(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '旅行计划 ID', + `type` VARCHAR(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT '类型', + `title` VARCHAR(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT '标题', + `description` LONGTEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT '说明', + `lat` decimal(10, 7) COMMENT '纬度', + `lng` decimal(10, 7) COMMENT '经度', + `location` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT '位置', + `amount` decimal(10, 2) COMMENT '费用', + `require_id_card` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '1 为需要身份证', + `require_appoINTment` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '1 为需要预约', + `score` INT(11) COMMENT '评分', + `importance` INT(11) COMMENT '重要程度', + `created_at` BIGINT(20), + `updated_at` BIGINT(20), + `deleted_at` BIGINT(20), + PRIMARY KEY (`id`) USING BTREE, + INDEX `idx_travel_deleted`(`travel_id` ASC, `deleted_at` ASC) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '旅行地点' ROW_FORMAT = Dynamic; diff --git a/src/main/resources/db/migration/timiserver/V15__create_gao_registration_event_tables.sql b/src/main/resources/db/migration/timiserver/V15__create_gao_registration_event_tables.sql new file mode 100644 index 0000000..3906755 --- /dev/null +++ b/src/main/resources/db/migration/timiserver/V15__create_gao_registration_event_tables.sql @@ -0,0 +1,72 @@ +CREATE TABLE IF NOT EXISTS `gao_customer` ( + `id` VARCHAR(36) NOT NULL, + `code` VARCHAR(64) NOT NULL COMMENT '客户编码', + `name` VARCHAR(64) NOT NULL COMMENT '姓名', + `gender` VARCHAR(16) NULL COMMENT '性别代码,MALE 男,FEMALE 女', + `age` INT NULL COMMENT '年龄', + `telephone` VARCHAR(32) NULL COMMENT '电话', + `address` VARCHAR(255) NULL COMMENT '住址', + `remark` TEXT NULL COMMENT '备注', + `disease` VARCHAR(255) NULL COMMENT '病症', + `conditioning_part` JSON NULL COMMENT '调理部位', + `introducer_customer_id` VARCHAR(36) NULL COMMENT '介绍人客户 ID', + `created_at` BIGINT NOT NULL, + `updated_at` BIGINT NULL, + `deleted_at` BIGINT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `uk_gao_customer_code` (`code`), + KEY `idx_gao_customer_introducer_customer_id` (`introducer_customer_id`) +) COMMENT='客户'; + +CREATE TABLE IF NOT EXISTS `gao_registration_event` ( + `id` VARCHAR(36) NOT NULL, + `name` VARCHAR(64) NOT NULL COMMENT '登记事件名称', + `description` VARCHAR(255) NULL COMMENT '说明', + `enabled` BIT(1) NOT NULL COMMENT 'true 为启用', + `require_photo` BIT(1) NOT NULL COMMENT 'true 为登记时要求拍照', + `sort` INT NOT NULL DEFAULT 0 COMMENT '排序值', + `created_at` BIGINT NOT NULL, + `updated_at` BIGINT NULL, + `deleted_at` BIGINT NULL, + PRIMARY KEY (`id`) +) COMMENT='登记事件'; + +CREATE TABLE IF NOT EXISTS `gao_registration_event_role_relation` ( + `id` VARCHAR(36) NOT NULL, + `registration_event_id` VARCHAR(36) NOT NULL COMMENT '登记事件 ID', + `role_code` VARCHAR(128) NOT NULL COMMENT '角色代码', + `created_at` BIGINT NOT NULL, + `updated_at` BIGINT NULL, + `deleted_at` BIGINT NULL, + PRIMARY KEY (`id`), + KEY `idx_gao_registration_event_role_relation_registration_event_id` (`registration_event_id`) +) COMMENT='登记事件角色关联'; + +CREATE TABLE IF NOT EXISTS `gao_registration_event_record` ( + `id` VARCHAR(36) NOT NULL, + `customer_id` VARCHAR(36) NOT NULL COMMENT '客户 ID', + `registration_event_id` VARCHAR(36) NOT NULL COMMENT '登记事件 ID', + `operator_user_id` VARCHAR(36) NOT NULL COMMENT '登记管理员用户 ID', + `remark` TEXT NULL COMMENT '备注', + `registered_at` BIGINT NOT NULL COMMENT '登记时间', + `created_at` BIGINT NOT NULL, + `updated_at` BIGINT NULL, + `deleted_at` BIGINT NULL, + PRIMARY KEY (`id`), + KEY `idx_gao_registration_event_record_customer_id` (`customer_id`), + KEY `idx_gao_registration_event_record_registration_event_id` (`registration_event_id`), + KEY `idx_gao_registration_event_record_registered_at` (`registered_at`) +) COMMENT='登记事件记录'; + +CREATE TABLE IF NOT EXISTS `gao_user` ( + `id` VARCHAR(36) NOT NULL, + `user_id` VARCHAR(36) NOT NULL COMMENT '用户 ID', + `enabled` BIT(1) NOT NULL COMMENT 'true 为启用', + `remark` VARCHAR(255) NULL COMMENT '备注', + `created_at` BIGINT NOT NULL, + `updated_at` BIGINT NULL, + `deleted_at` BIGINT NULL, + PRIMARY KEY (`id`), + KEY `idx_gao_user_user_id` (`user_id`), + KEY `idx_gao_user_enabled` (`enabled`, `deleted_at`, `created_at`) +) COMMENT='GAO 用户'; diff --git a/src/main/resources/db/migration/timiserver/V1__init_schema.sql b/src/main/resources/db/migration/timiserver/V1__init_schema.sql new file mode 100644 index 0000000..1117b7d --- /dev/null +++ b/src/main/resources/db/migration/timiserver/V1__init_schema.sql @@ -0,0 +1,219 @@ +/* + Navicat Premium Dump SQL + + Source Server : dev_mariadb + Source Server Type : MariaDB + Source Server Version : 110802 (11.8.2-MariaDB-ubu2404) + Source Host : vm.imyeyu.develop:3306 + Source Schema : timi_server + + Target Server Type : MariaDB + Target Server Version : 110802 (11.8.2-MariaDB-ubu2404) + File Encoding : 65001 + + Date: 12/05/2026 23:34:03 +*/ + +SET NAMES utf8mb4; +SET FOREIGN_KEY_CHECKS = 0; + +-- ---------------------------- +-- Table structure for logger +-- ---------------------------- +DROP TABLE IF EXISTS `logger`; +CREATE TABLE `logger` ( + `id` VARCHAR(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '主键', + `level` VARCHAR(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '日志级别', + `module` VARCHAR(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT '模块名称', + `operation` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '操作描述', + `user_id` VARCHAR(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT '用户 ID', + `content` LONGTEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT '内容', + `result` LONGTEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT '结果', + `exception` LONGTEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT '异常', + `duration` INT(11) COMMENT '执行时长(ms)', + `ip` VARCHAR(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT 'IP', + `created_at` BIGINT(20) NOT NULL COMMENT '创建时间', + PRIMARY KEY (`id`) USING BTREE, + INDEX `idx_level`(`level` ASC) USING BTREE, + INDEX `idx_module`(`module` ASC) USING BTREE, + INDEX `idx_user_id`(`user_id` ASC) USING BTREE, + INDEX `idx_created_at`(`created_at` ASC) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '日志' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for multilingual +-- ---------------------------- +DROP TABLE IF EXISTS `multilingual`; +CREATE TABLE `multilingual` ( + `id` VARCHAR(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `zh_cn` LONGTEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT '简体中文', + `zh_tw` LONGTEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT '繁体中文', + `en_us` LONGTEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT '英语', + `ru_ru` LONGTEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT '俄语', + `ja_jp` LONGTEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT '日语', + `de_de` LONGTEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT '德语', + `ko_kr` LONGTEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT '韩语', + `last_access_at` BIGINT(20) COMMENT '最后访问时间', + `created_at` BIGINT(20) COMMENT '创建时间', + `updated_at` BIGINT(20) COMMENT '更新时间', + `deleted_at` BIGINT(20) COMMENT '删除时间', + PRIMARY KEY (`id`) USING BTREE, + INDEX `idx_last_access_at`(`last_access_at` ASC) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '多语言映射' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for permission +-- ---------------------------- +DROP TABLE IF EXISTS `permission`; +CREATE TABLE `permission` ( + `id` VARCHAR(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `module_code` VARCHAR(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '模块代码', + `code` VARCHAR(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '代码', + `name_lang_id` VARCHAR(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT '名称语言映射 ID', + `description` LONGTEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT '说明', + `created_at` BIGINT(20) COMMENT '创建时间', + `updated_at` BIGINT(20) COMMENT '更新时间', + `deleted_at` BIGINT(20) COMMENT '删除时间', + PRIMARY KEY (`id`) USING BTREE, + UNIQUE INDEX `uk_module_code_code`(`module_code` ASC, `code` ASC) USING BTREE, + INDEX `idx_module_code`(`module_code` ASC) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '权限' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for role +-- ---------------------------- +DROP TABLE IF EXISTS `role`; +CREATE TABLE `role` ( + `id` VARCHAR(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `module_code` VARCHAR(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '模块代码', + `code` VARCHAR(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '代码', + `name_lang_id` VARCHAR(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT '名称语言映射 ID', + `description` LONGTEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT '说明', + `created_at` BIGINT(20) COMMENT '创建时间', + `updated_at` BIGINT(20) COMMENT '更新时间', + `deleted_at` BIGINT(20) COMMENT '删除时间', + PRIMARY KEY (`id`) USING BTREE, + UNIQUE INDEX `uk_module_code_code`(`module_code` ASC, `code` ASC) USING BTREE, + INDEX `idx_module_code`(`module_code` ASC) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '角色' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for role_permission +-- ---------------------------- +DROP TABLE IF EXISTS `role_permission`; +CREATE TABLE `role_permission` ( + `id` VARCHAR(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `role_id` VARCHAR(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '角色 ID', + `permission_id` VARCHAR(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '权限 ID', + `created_at` BIGINT(20) COMMENT '创建时间', + `deleted_at` BIGINT(20) COMMENT '删除时间', + PRIMARY KEY (`id`) USING BTREE, + INDEX `idx_role_permission`(`role_id` ASC, `permission_id` ASC) USING BTREE, + INDEX `idx_permission_id`(`permission_id` ASC) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '角色权限' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for role_relation +-- ---------------------------- +DROP TABLE IF EXISTS `role_relation`; +CREATE TABLE `role_relation` ( + `id` VARCHAR(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `parent_role_id` VARCHAR(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '上级角色', + `child_role_id` VARCHAR(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '子级角色', + `created_at` BIGINT(20) COMMENT '创建时间', + `deleted_at` BIGINT(20) COMMENT '删除时间', + PRIMARY KEY (`id`) USING BTREE, + INDEX `idx_role_relation`(`parent_role_id` ASC, `child_role_id` ASC) USING BTREE, + INDEX `idx_child_role_id`(`child_role_id` ASC) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '角色关联' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for setting +-- ---------------------------- +DROP TABLE IF EXISTS `setting`; +CREATE TABLE `setting` ( + `id` VARCHAR(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `biz_type` VARCHAR(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '', + `biz_id` VARCHAR(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, + `code` VARCHAR(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `value` LONGTEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, + `value_type` VARCHAR(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, + `value_args` LONGTEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT '组件配置', + `description` LONGTEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT '说明', + `is_private` TINYINT(1) NOT NULL DEFAULT 1 COMMENT '1 为私有', + `created_at` BIGINT(20), + `updated_at` BIGINT(20), + `deleted_at` BIGINT(20), + PRIMARY KEY (`id`) USING BTREE, + INDEX `idx_setting_key`(`biz_type` ASC, `biz_id` ASC, `code` ASC) USING BTREE, + INDEX `idx_private_deleted`(`is_private` ASC, `deleted_at` ASC) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '配置' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for user +-- ---------------------------- +DROP TABLE IF EXISTS `user`; +CREATE TABLE `user` ( + `id` VARCHAR(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '主键', + `name` VARCHAR(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '用户名', + `nick` VARCHAR(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '昵称', + `password` VARCHAR(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '密码摘要', + `email` VARCHAR(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT '邮箱', + `email_verify_at` BIGINT(20) COMMENT '邮箱验证时间', + `phone_no` VARCHAR(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT '手机号', + `phone_no_verify_at` BIGINT(20) COMMENT '手机号验证时间', + `wrapper_type` VARCHAR(15) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'PIXELATED' COMMENT '背景图渲染算法', + `avatar_type` VARCHAR(15) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'PIXELATED' COMMENT '头像渲染算法', + `exp` INT(11) NOT NULL DEFAULT 0 COMMENT '经验值', + `gender` varchar(16) COMMENT '性别', + `birthdate` BIGINT(20) COMMENT '出生日期', + `qq` VARCHAR(15) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT 'QQ', + `description` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT '说明', + `last_login_ip` VARCHAR(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT '上次登录 IP', + `last_login_at` BIGINT(20) COMMENT '上次登录时间', + `unmute_at` BIGINT(20) COMMENT '解除禁言时间', + `unban_at` BIGINT(20) COMMENT '解除封禁时间', + `created_at` BIGINT(20), + `updated_at` BIGINT(20), + `deleted_at` BIGINT(20), + PRIMARY KEY (`id`) USING BTREE, + UNIQUE INDEX `uk_name`(`name` ASC) USING BTREE, + INDEX `idx_email`(`email` ASC) USING BTREE, + INDEX `idx_phone_no`(`phone_no` ASC) USING BTREE, + INDEX `idx_deleted_created`(`deleted_at` ASC, `created_at` ASC) USING BTREE, + INDEX `idx_last_login_at`(`last_login_at` ASC) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '用户' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for user_behavior_record +-- ---------------------------- +DROP TABLE IF EXISTS `user_behavior_record`; +CREATE TABLE `user_behavior_record` ( + `id` VARCHAR(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '主键', + `biz_type` VARCHAR(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '业务类型', + `user_id` VARCHAR(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '用户 ID', + `target_type` VARCHAR(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '目标类型', + `target_id` VARCHAR(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '目标 ID', + `action_type` VARCHAR(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '行为类型', + `created_at` BIGINT(20) COMMENT '创建时间', + `deleted_at` BIGINT(20) COMMENT '删除时间', + PRIMARY KEY (`id`) USING BTREE, + INDEX `idx_active`(`biz_type` ASC, `user_id` ASC, `target_type` ASC, `target_id` ASC, `action_type` ASC, `deleted_at` ASC) USING BTREE, + INDEX `idx_user_action_created`(`user_id` ASC, `biz_type` ASC, `target_type` ASC, `action_type` ASC, `deleted_at` ASC, `created_at` ASC) USING BTREE, + INDEX `idx_target`(`target_type` ASC, `target_id` ASC, `action_type` ASC, `deleted_at` ASC) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '用户行为' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for user_role +-- ---------------------------- +DROP TABLE IF EXISTS `user_role`; +CREATE TABLE `user_role` ( + `id` VARCHAR(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `user_id` VARCHAR(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '用户 ID', + `role_id` VARCHAR(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '角色 ID', + `created_at` BIGINT(20) COMMENT '创建时间', + `deleted_at` BIGINT(20) COMMENT '删除时间', + PRIMARY KEY (`id`) USING BTREE, + INDEX `idx_user_role`(`user_id` ASC, `role_id` ASC) USING BTREE, + INDEX `idx_role_id`(`role_id` ASC) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '用户角色' ROW_FORMAT = Dynamic; diff --git a/src/main/resources/db/migration/timiserver/V2__init_system_data.sql b/src/main/resources/db/migration/timiserver/V2__init_system_data.sql new file mode 100644 index 0000000..f1434a0 --- /dev/null +++ b/src/main/resources/db/migration/timiserver/V2__init_system_data.sql @@ -0,0 +1,19 @@ +SET @created_at = FLOOR(UNIX_TIMESTAMP(NOW(3)) * 1000); +SET @zero_uuid = '00000000-0000-0000-0000-000000000000'; + +INSERT INTO `setting` (`id`, + `biz_type`, + `biz_id`, + `code`, + `value`, + `value_type`, + `value_args`, + `description`, + `is_private`, + `created_at`, + `updated_at`, + `deleted_at` +) VALUES +(UUID(), 'USER', @zero_uuid, 'ENABLE_LOGIN', 'TRUE', 'BOOLEAN', NULL, NULL, 1, @created_at, NULL, NULL), +(UUID(), 'USER', @zero_uuid, 'TOKEN_TTL', '7d', 'TIME', NULL, NULL, 1, @created_at, NULL, NULL) +; diff --git a/src/main/resources/db/migration/timiserver/V3__create_article_table.sql b/src/main/resources/db/migration/timiserver/V3__create_article_table.sql new file mode 100644 index 0000000..257495e --- /dev/null +++ b/src/main/resources/db/migration/timiserver/V3__create_article_table.sql @@ -0,0 +1,22 @@ +DROP TABLE IF EXISTS `article`; +CREATE TABLE `article` ( + `id` VARCHAR(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `biz_type` VARCHAR(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '业务类型', + `biz_id` VARCHAR(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '业务 ID', + `title` VARCHAR(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT '标题', + `digest` LONGTEXT COLLATE utf8mb4_unicode_ci COMMENT '摘要', + `content_type` VARCHAR(32) COLLATE utf8mb4_unicode_ci COMMENT '内容类型', + `content` LONGTEXT COLLATE utf8mb4_unicode_ci COMMENT '内容', + `reads` INT(11) DEFAULT 0 COMMENT '阅读', + `likes` INT(11) DEFAULT 0 COMMENT '喜欢', + `published_at` BIGINT(20), + `created_at` BIGINT(20), + `updated_at` BIGINT(20), + `deleted_at` BIGINT(20), + PRIMARY KEY (`id`) USING BTREE, + INDEX `idx_biz_type_biz_id` (`biz_type`, `biz_id`) USING BTREE, + INDEX `idx_published_at` (`published_at`) USING BTREE, + INDEX `idx_created_at` (`created_at`) USING BTREE, + INDEX `idx_deleted_at` (`deleted_at`) USING BTREE, + INDEX `idx_reads` (`reads`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '文章' ROW_FORMAT = Dynamic; diff --git a/src/main/resources/db/migration/timiserver/V4__create_async_task_table.sql b/src/main/resources/db/migration/timiserver/V4__create_async_task_table.sql new file mode 100644 index 0000000..e57c09a --- /dev/null +++ b/src/main/resources/db/migration/timiserver/V4__create_async_task_table.sql @@ -0,0 +1,19 @@ +DROP TABLE IF EXISTS `async_task`; +CREATE TABLE `async_task` ( + `id` VARCHAR(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `name` VARCHAR(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, + `type` VARCHAR(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, + `message` LONGTEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, + `status` VARCHAR(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, + `progress` double DEFAULT -1, + `start_at` BIGINT(20), + `interrupt_at` BIGINT(20), + `error_at` BIGINT(20), + `died_at` BIGINT(20), + `can_pause` TINYINT(1), + `can_interrupt` TINYINT(1), + `is_periodical` TINYINT(1), + `cron` VARCHAR(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, + `created_at` BIGINT(20), + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '异步任务' ROW_FORMAT = Dynamic; diff --git a/src/main/resources/db/migration/timiserver/V5__create_attachment_table.sql b/src/main/resources/db/migration/timiserver/V5__create_attachment_table.sql new file mode 100644 index 0000000..5e8d498 --- /dev/null +++ b/src/main/resources/db/migration/timiserver/V5__create_attachment_table.sql @@ -0,0 +1,24 @@ +DROP TABLE IF EXISTS `attachment`; +CREATE TABLE `attachment` ( + `id` VARCHAR(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `biz_type` VARCHAR(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `biz_id` VARCHAR(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, + `attach_type` VARCHAR(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, + `mongo_id` VARCHAR(24) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `title` VARCHAR(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, + `name` VARCHAR(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, + `mime_type` VARCHAR(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, + `metadata` LONGTEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, + `size` BIGINT(20), + `md5` VARCHAR(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, + `uploader_ip` VARCHAR(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, + `is_destroyed` TINYINT(1) NOT NULL DEFAULT 0, + `created_at` BIGINT(20) NOT NULL, + `updated_at` BIGINT(20), + `deleted_at` BIGINT(20), + `destroy_at` BIGINT(20), + PRIMARY KEY (`id`) USING BTREE, + INDEX `idx_biz_deleted_destroy`(`biz_type` ASC, `biz_id` ASC, `deleted_at` ASC, `destroy_at` ASC) USING BTREE, + UNIQUE INDEX `uk_mongo_id`(`mongo_id` ASC) USING BTREE, + INDEX `idx_destroyed_destroy_at`(`is_destroyed` ASC, `destroy_at` ASC) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '附件' ROW_FORMAT = Dynamic; diff --git a/src/main/resources/db/migration/timiserver/V6__create_comment_table.sql b/src/main/resources/db/migration/timiserver/V6__create_comment_table.sql new file mode 100644 index 0000000..20868b2 --- /dev/null +++ b/src/main/resources/db/migration/timiserver/V6__create_comment_table.sql @@ -0,0 +1,28 @@ +DROP TABLE IF EXISTS `comment`; +CREATE TABLE `comment` ( + `id` VARCHAR(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `biz_type` VARCHAR(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '业务类型', + `biz_id` VARCHAR(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '业务 ID', + `parent_id` VARCHAR(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT '父评论 ID,主评论为 NULL', + `root_id` VARCHAR(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT '根评论 ID,主评论为自身 ID', + `user_id` VARCHAR(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT '评论用户 ID', + `user_nick` VARCHAR(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT '评论用户昵称,游客评论时使用', + `reply_user_id` VARCHAR(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT '被回复用户 ID', + `reply_user_nick` VARCHAR(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT '被回复用户昵称,被回复人为游客时使用', + `level` INT(11) NOT NULL DEFAULT 1 COMMENT '评论层级,1 为主评论', + `content` LONGTEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '内容', + `ip` VARCHAR(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT '评论 IP', + `reply_count` INT(11) NOT NULL DEFAULT 0 COMMENT '回复数', + `like_count` INT(11) NOT NULL DEFAULT 0 COMMENT '点赞数', + `status` VARCHAR(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'NORMAL' COMMENT '评论状态', + `is_top` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否置顶', + `last_reply_at` BIGINT(20) COMMENT '最后回复时间', + `created_at` BIGINT(20), + `updated_at` BIGINT(20), + `deleted_at` BIGINT(20), + PRIMARY KEY (`id`) USING BTREE, + INDEX `idx_biz_parent_status_deleted`(`biz_type` ASC, `biz_id` ASC, `parent_id` ASC, `status` ASC, `deleted_at` ASC) USING BTREE, + INDEX `idx_root_deleted_created`(`root_id` ASC, `deleted_at` ASC, `created_at` ASC) USING BTREE, + INDEX `idx_parent_deleted_created`(`parent_id` ASC, `deleted_at` ASC, `created_at` ASC) USING BTREE, + INDEX `idx_user_id`(`user_id` ASC) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '评论' ROW_FORMAT = Dynamic; diff --git a/src/main/resources/db/migration/timiserver/V7__create_data_dict_tables.sql b/src/main/resources/db/migration/timiserver/V7__create_data_dict_tables.sql new file mode 100644 index 0000000..890a061 --- /dev/null +++ b/src/main/resources/db/migration/timiserver/V7__create_data_dict_tables.sql @@ -0,0 +1,29 @@ +DROP TABLE IF EXISTS `data_dict`; +CREATE TABLE `data_dict` ( + `id` VARCHAR(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `biz_type` VARCHAR(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '业务类型', + `code` VARCHAR(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '代码', + `parent_code` VARCHAR(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT '父级代码', + `name_lang_id` VARCHAR(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT '名称语言映射 ID', + `created_at` BIGINT(20), + `updated_at` BIGINT(20), + `deleted_at` BIGINT(20), + PRIMARY KEY (`id`) USING BTREE, + INDEX `idx_biz_code`(`biz_type` ASC, `code` ASC) USING BTREE, + INDEX `idx_parent_code`(`biz_type` ASC, `parent_code` ASC) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '字典' ROW_FORMAT = Dynamic; + +DROP TABLE IF EXISTS `data_dict_option`; +CREATE TABLE `data_dict_option` ( + `id` VARCHAR(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `dict_id` VARCHAR(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '所属字典 ID', + `value_code` VARCHAR(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '值代码', + `value_parent_code` VARCHAR(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT '父级值代码', + `value_lang_id` VARCHAR(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT '值语言映射 ID', + `created_at` BIGINT(20), + `updated_at` BIGINT(20), + `deleted_at` BIGINT(20), + PRIMARY KEY (`id`) USING BTREE, + INDEX `idx_dict_value_code`(`dict_id` ASC, `value_code` ASC) USING BTREE, + INDEX `idx_dict_parent_code`(`dict_id` ASC, `value_parent_code` ASC) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '字典选项' ROW_FORMAT = Dynamic; diff --git a/src/main/resources/db/migration/timiserver/V8__create_email_filter_table.sql b/src/main/resources/db/migration/timiserver/V8__create_email_filter_table.sql new file mode 100644 index 0000000..994b4c3 --- /dev/null +++ b/src/main/resources/db/migration/timiserver/V8__create_email_filter_table.sql @@ -0,0 +1,9 @@ +DROP TABLE IF EXISTS `email_filter`; +CREATE TABLE `email_filter` ( + `id` VARCHAR(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `email` VARCHAR(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `created_at` BIGINT(20), + `deleted_at` BIGINT(20), + PRIMARY KEY (`id`) USING BTREE, + INDEX `idx_email`(`email` ASC) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '拒收邮件邮箱' ROW_FORMAT = Dynamic; diff --git a/src/main/resources/db/migration/timiserver/V9__create_notify_tables.sql b/src/main/resources/db/migration/timiserver/V9__create_notify_tables.sql new file mode 100644 index 0000000..21eeca3 --- /dev/null +++ b/src/main/resources/db/migration/timiserver/V9__create_notify_tables.sql @@ -0,0 +1,39 @@ +DROP TABLE IF EXISTS `notify`; +CREATE TABLE `notify` ( + `id` VARCHAR(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `biz_type` VARCHAR(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT '业务类型', + `biz_reminded_type` VARCHAR(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT '被提醒者类型', + `biz_reminded_id` VARCHAR(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT '被提醒者 ID', + `args` LONGTEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT '参数', + `created_by` VARCHAR(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT '创建者', + `created_at` BIGINT(20) COMMENT '创建时间', + `updated_at` BIGINT(20) COMMENT '更新时间', + `deleted_at` BIGINT(20) COMMENT '删除时间', + PRIMARY KEY (`id`) USING BTREE, + INDEX `idx_biz_reminded_created`(`biz_type` ASC, `biz_reminded_type` ASC, `biz_reminded_id` ASC, `created_at` ASC) USING BTREE, + INDEX `idx_deleted_created`(`deleted_at` ASC, `created_at` ASC) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '通知' ROW_FORMAT = Dynamic; + +DROP TABLE IF EXISTS `notify_detail`; +CREATE TABLE `notify_detail` ( + `id` VARCHAR(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `notify_id` VARCHAR(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '所属通知 ID', + `msg_type` VARCHAR(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT '消息类型', + `template_id` VARCHAR(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT '模板 ID', + `subject` LONGTEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT '标题', + `data` LONGTEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT '内容', + `args` LONGTEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT '参数', + `send_from` VARCHAR(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT '发送来源', + `send_to` VARCHAR(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT '发送去向', + `send_at` BIGINT(20) COMMENT '发送时间', + `retry` INT(11) COMMENT '重试计数', + `timeout_at` BIGINT(20) COMMENT '超时时间', + `result_desc` LONGTEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT '结果描述', + `status` VARCHAR(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT '状态', + `created_at` BIGINT(20) COMMENT '创建时间', + `updated_at` BIGINT(20) COMMENT '更新时间', + `deleted_at` BIGINT(20) COMMENT '删除时间', + PRIMARY KEY (`id`) USING BTREE, + INDEX `idx_notify_id`(`notify_id` ASC) USING BTREE, + INDEX `idx_status_created`(`status` ASC, `created_at` ASC) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '通知详情' ROW_FORMAT = Dynamic; diff --git a/src/main/resources/mapper/blog/ArticleMapper.xml b/src/main/resources/mapper/blog/ArticleMapper.xml deleted file mode 100644 index 50dc53b..0000000 --- a/src/main/resources/mapper/blog/ArticleMapper.xml +++ /dev/null @@ -1,56 +0,0 @@ - - - - article - - - `id`, - `type`, - `title`, - `digest`, - `likes`, - `reads`, - `created_at`, - `updated_at` - - - - FROM - article - WHERE - can_list IS TRUE - AND deleted_at IS NULL - - - - - - - AND ( - `title` LIKE CONCAT('%', #{keyword}, '%') - OR `digest` LIKE CONCAT('%', #{keyword}, '%') - ) - - - - diff --git a/src/main/resources/mapper/common/AttachmentMapper.xml b/src/main/resources/mapper/common/AttachmentMapper.xml deleted file mode 100644 index bc4b472..0000000 --- a/src/main/resources/mapper/common/AttachmentMapper.xml +++ /dev/null @@ -1,79 +0,0 @@ - - - - attachment - - - - diff --git a/src/main/resources/mapper/common/CommentMapper.xml b/src/main/resources/mapper/common/CommentMapper.xml deleted file mode 100644 index 5267cf2..0000000 --- a/src/main/resources/mapper/common/CommentMapper.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - comment - - \ No newline at end of file diff --git a/src/main/resources/mapper/common/IconMapper.xml b/src/main/resources/mapper/common/IconMapper.xml deleted file mode 100644 index 2af2307..0000000 --- a/src/main/resources/mapper/common/IconMapper.xml +++ /dev/null @@ -1,35 +0,0 @@ - - - - icon - - - FROM ( - SELECT - id - FROM - icon_labels - WHERE - ${lang} LIKE CONCAT('%', #{label}, '%') - AND icon_labels.deleted_at IS NULL - ) AS labels - LEFT JOIN - icon_label - ON - icon_label.icon_label_id = labels.id - AND labels.id IS NOT NULL - LEFT JOIN - - ON - icon.id = icon_label.icon_id - WHERE - icon.id IS NOT NULL - AND icon.deleted_at IS NULL - - - - \ No newline at end of file diff --git a/src/main/resources/mapper/common/MultilingualMapper.xml b/src/main/resources/mapper/common/MultilingualMapper.xml deleted file mode 100644 index bb410ba..0000000 --- a/src/main/resources/mapper/common/MultilingualMapper.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - \ No newline at end of file diff --git a/src/main/resources/mapper/common/SettingMapper.xml b/src/main/resources/mapper/common/SettingMapper.xml deleted file mode 100644 index 205b98e..0000000 --- a/src/main/resources/mapper/common/SettingMapper.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - setting - - \ No newline at end of file diff --git a/src/main/resources/mapper/common/TaskMapper.xml b/src/main/resources/mapper/common/TaskMapper.xml deleted file mode 100644 index fa3f8f8..0000000 --- a/src/main/resources/mapper/common/TaskMapper.xml +++ /dev/null @@ -1,55 +0,0 @@ - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/main/resources/mapper/common/UserConfigMapper.xml b/src/main/resources/mapper/common/UserConfigMapper.xml deleted file mode 100644 index 19f7d9e..0000000 --- a/src/main/resources/mapper/common/UserConfigMapper.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - user_config - \ No newline at end of file diff --git a/src/main/resources/mapper/common/UserPrivacyMapper.xml b/src/main/resources/mapper/common/UserPrivacyMapper.xml deleted file mode 100644 index 05497fb..0000000 --- a/src/main/resources/mapper/common/UserPrivacyMapper.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/src/main/resources/mapper/common/UserProfileMapper.xml b/src/main/resources/mapper/common/UserProfileMapper.xml deleted file mode 100644 index 2e9d523..0000000 --- a/src/main/resources/mapper/common/UserProfileMapper.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - user_profile - \ No newline at end of file diff --git a/src/main/resources/mapper/git/IssueMapper.xml b/src/main/resources/mapper/git/IssueMapper.xml deleted file mode 100644 index fbbc6b3..0000000 --- a/src/main/resources/mapper/git/IssueMapper.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - git_issue - - repository_id = #{issuePage.repositoryId} - - AND type = #{issuePage.type} - - - AND status = #{issuePage.status} - - - AND ( - title LIKE CONCAT('%', #{issuePage.keyword}, '%') - OR description LIKE CONCAT('%', #{issuePage.keyword}, '%') - ) - - AND deleted_at IS NULL - - - - \ No newline at end of file diff --git a/src/main/resources/mapper/git/MergeMapper.xml b/src/main/resources/mapper/git/MergeMapper.xml deleted file mode 100644 index e2af187..0000000 --- a/src/main/resources/mapper/git/MergeMapper.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - git_merge - - repository_id = #{mergePage.repositoryId} - - AND type = #{mergePage.type} - - - AND status = #{mergePage.status} - - - AND ( - title LIKE CONCAT('%', #{mergePage.keyword}, '%') - OR description LIKE CONCAT('%', #{mergePage.keyword}, '%') - ) - - AND deleted_at IS NULL - - - - \ No newline at end of file diff --git a/src/main/resources/mapper/git/ReleaseMapper.xml b/src/main/resources/mapper/git/ReleaseMapper.xml deleted file mode 100644 index 7311067..0000000 --- a/src/main/resources/mapper/git/ReleaseMapper.xml +++ /dev/null @@ -1,43 +0,0 @@ - - - - git_release - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/main/resources/mapper/gitea/ActionMapper.xml b/src/main/resources/mapper/gitea/ActionMapper.xml deleted file mode 100644 index 4e418de..0000000 --- a/src/main/resources/mapper/gitea/ActionMapper.xml +++ /dev/null @@ -1,52 +0,0 @@ - - - - - - diff --git a/src/main/resources/mapper/journal/JournalMapper.xml b/src/main/resources/mapper/journal/JournalMapper.xml deleted file mode 100644 index a93c279..0000000 --- a/src/main/resources/mapper/journal/JournalMapper.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - journal - - diff --git a/src/main/resources/mapper/journal/TravelLocationMapper.xml b/src/main/resources/mapper/journal/TravelLocationMapper.xml deleted file mode 100644 index d7c83d6..0000000 --- a/src/main/resources/mapper/journal/TravelLocationMapper.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - travel_location - - diff --git a/src/main/resources/mapper/minecraft/PackMapper.xml b/src/main/resources/mapper/minecraft/PackMapper.xml deleted file mode 100644 index 98ca058..0000000 --- a/src/main/resources/mapper/minecraft/PackMapper.xml +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/main/resources/mapper/minecraft/PlayerMapper.xml b/src/main/resources/mapper/minecraft/PlayerMapper.xml deleted file mode 100644 index 1b09c71..0000000 --- a/src/main/resources/mapper/minecraft/PlayerMapper.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/main/resources/mapper/system/AsyncTaskMapper.xml b/src/main/resources/mapper/system/AsyncTaskMapper.xml deleted file mode 100644 index cfdccf6..0000000 --- a/src/main/resources/mapper/system/AsyncTaskMapper.xml +++ /dev/null @@ -1,56 +0,0 @@ - - - - async_task - - INSERT INTO ( - uuid, - name, - type, - message, - status, - progress, - can_pause, - can_interrupt, - is_periodical, - cron, - start_at, - interrupt_at, - error_at, - died_at, - created_at - ) VALUES ( - #{uuid}, - #{name}, - #{type}, - #{message}, - #{status}, - #{progress}, - #{canPause}, - #{canInterrupt}, - #{isPeriodical}, - #{cron}, - #{startAt}, - #{interruptAt}, - #{errorAt}, - #{diedAt}, - #{createdAt} - ) - - - UPDATE - - SET - name = #{name}, - type = #{type}, - message = #{message}, - status = #{status}, - progress = #{progress}, - start_at = #{startAt}, - interrupt_at = #{interruptAt}, - error_at = #{errorAt}, - died_at = #{diedAt} - WHERE - uuid = #{uuid} - - diff --git a/src/main/resources/mapper/timi-server/GaoCustomerMapper.xml b/src/main/resources/mapper/timi-server/GaoCustomerMapper.xml new file mode 100644 index 0000000..f302e6f --- /dev/null +++ b/src/main/resources/mapper/timi-server/GaoCustomerMapper.xml @@ -0,0 +1,67 @@ + + + + + + + + + + + + + diff --git a/src/main/resources/mapper/timi-server/GaoRegistrationEventRecordMapper.xml b/src/main/resources/mapper/timi-server/GaoRegistrationEventRecordMapper.xml new file mode 100644 index 0000000..01ed5a2 --- /dev/null +++ b/src/main/resources/mapper/timi-server/GaoRegistrationEventRecordMapper.xml @@ -0,0 +1,233 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/mapper/timi-server/MultilingualMapper.xml b/src/main/resources/mapper/timi-server/MultilingualMapper.xml new file mode 100644 index 0000000..2ab67e1 --- /dev/null +++ b/src/main/resources/mapper/timi-server/MultilingualMapper.xml @@ -0,0 +1,21 @@ + + + + + diff --git a/src/main/resources/mapper/timi-server/RoleMapper.xml b/src/main/resources/mapper/timi-server/RoleMapper.xml new file mode 100644 index 0000000..ddda0ca --- /dev/null +++ b/src/main/resources/mapper/timi-server/RoleMapper.xml @@ -0,0 +1,49 @@ + + + + + + + + diff --git a/src/main/resources/mapper/timi-server/RolePermissionMapper.xml b/src/main/resources/mapper/timi-server/RolePermissionMapper.xml new file mode 100644 index 0000000..8efab7c --- /dev/null +++ b/src/main/resources/mapper/timi-server/RolePermissionMapper.xml @@ -0,0 +1,51 @@ + + + + + + + INSERT INTO `role_permission` ( + id, + role_id, + permission_id, + created_at + ) VALUES + + (#{item.id}, #{item.roleId}, #{item.permissionId}, #{item.createdAt}) + + + + UPDATE `role_permission` + SET `deleted_at` = UNIX_TIMESTAMP() + WHERE + `role_id` = #{roleId} + AND `permission_id` IN + + #{item} + + + diff --git a/src/main/resources/mapper/timi-server/RoleRelationMapper.xml b/src/main/resources/mapper/timi-server/RoleRelationMapper.xml new file mode 100644 index 0000000..dea0b6e --- /dev/null +++ b/src/main/resources/mapper/timi-server/RoleRelationMapper.xml @@ -0,0 +1,52 @@ + + + + + + + + INSERT INTO `role_relation` ( + id, + parent_role_id, + child_role_id, + created_at + ) VALUES + + (#{item.id}, #{item.parentRoleId}, #{item.childRoleId}, #{item.createdAt}) + + + + UPDATE `role_relation` + SET `deleted_at` = UNIX_TIMESTAMP() + WHERE + `parent_role_id` = #{parentRoleId} + AND `child_role_id` IN + + #{item} + + + diff --git a/src/main/resources/mapper/timi-server/UserMapper.xml b/src/main/resources/mapper/timi-server/UserMapper.xml new file mode 100644 index 0000000..fd49dd3 --- /dev/null +++ b/src/main/resources/mapper/timi-server/UserMapper.xml @@ -0,0 +1,21 @@ + + + + + diff --git a/src/main/resources/mapper/timi-server/UserRoleMapper.xml b/src/main/resources/mapper/timi-server/UserRoleMapper.xml new file mode 100644 index 0000000..55ce81d --- /dev/null +++ b/src/main/resources/mapper/timi-server/UserRoleMapper.xml @@ -0,0 +1,25 @@ + + + + + INSERT INTO `user_role` ( + id, + user_id, + role_id, + created_at + ) VALUES + + (#{item.id}, #{item.userId}, #{item.roleId}, #{item.createdAt}) + + + + UPDATE `user_role` + SET `deleted_at` = UNIX_TIMESTAMP() + WHERE + `user_id` = #{userId} + AND `role_id` IN + + #{item} + + + diff --git a/src/test/java/test/SpringTest.java b/src/test/java/test/SpringTest.java index 0fca9c3..94da887 100644 --- a/src/test/java/test/SpringTest.java +++ b/src/test/java/test/SpringTest.java @@ -1,121 +1,11 @@ package test; import com.imyeyu.api.TimiServerAPI; -import com.imyeyu.api.modules.blog.entity.Article; -import com.imyeyu.api.modules.blog.mapper.ArticleMapper; -import com.imyeyu.api.modules.common.entity.Icon; -import com.imyeyu.api.modules.common.mapper.AttachmentMapper; -import com.imyeyu.api.modules.common.mapper.IconMapper; -import com.imyeyu.api.modules.common.mapper.MultilingualMapper; -import com.imyeyu.api.modules.common.service.SettingService; -import com.imyeyu.api.modules.forevermc.entity.Server; -import com.imyeyu.api.modules.forevermc.mapper.ServerMapper; -import org.junit.Test; import org.junit.runner.RunWith; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.data.mongodb.core.query.Query; -import org.springframework.data.mongodb.gridfs.GridFsTemplate; import org.springframework.test.context.junit4.SpringRunner; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - @SpringBootTest(classes = TimiServerAPI.class) @RunWith(SpringRunner.class) public class SpringTest { - - @Autowired - private IconMapper iconMapper; - - @Autowired - private MultilingualMapper multilingualMapper; - - @Autowired - private SettingService settingService; - - @Autowired - private ServerMapper serverMapper; - - @Autowired - private ArticleMapper articleMapper; - - @Autowired - private AttachmentMapper attachmentMapper; - - @Autowired - private GridFsTemplate gridFsTemplate; - - @Test - public void testMongoDB() { - gridFsTemplate.find(new Query()); - } - - @Test - public void testBaseMapper() { - String id; - Server select; - { - // 插入 - Server server = new Server(); - server.setTitle("test insert title"); - server.setDescription("test insert desc"); - server.setHost("test insert host"); - serverMapper.insert(server); - id = server.getId(); - - // 查询 - select = serverMapper.select(id); - assert select.getId().equals(id); - assert select.getTitle().equals(server.getTitle()); - assert select.getCreatedAt() != null; - } - { - // 更新 - select = serverMapper.select(id); - select.setTitle("test update title"); - serverMapper.update(select); - - select = serverMapper.select(id); - assert select.getId().equals(id); - assert select.getTitle().equals("test update title"); - assert select.getUpdatedAt() != null; - } - { - // 软删 - serverMapper.delete(id); - - select = serverMapper.select(id); - assert select == null; - } - { - // 硬删 - System.out.println("destroy id = " + id); - serverMapper.destroy(id); - } - } - - - @Test - public void testBaseMapperGenId() { - Article last = articleMapper.select(128L); - - Article test = new Article(); - test.setTitle("title"); - test.setData("data"); - articleMapper.insert(test); - assert test.getId() != null && test.getId() - 1 == last.getId(); - - articleMapper.destroy(test.getId()); - } - - @Test - public void iconJson() throws Exception { - List icon = iconMapper.listAll(); - Map map = new HashMap<>(); - for (int i = 0; i < icon.size(); i++) { - map.put(icon.get(i).getName(), icon.get(i).getSvg()); - } - } } diff --git a/src/test/java/test/Test.java b/src/test/java/test/Test.java index c15e0d0..5872508 100644 --- a/src/test/java/test/Test.java +++ b/src/test/java/test/Test.java @@ -1,26 +1,19 @@ -package test; - +import com.imyeyu.utils.Decoder; +import com.imyeyu.utils.Encoder; import com.imyeyu.utils.Text; import com.imyeyu.utils.Time; -import java.util.UUID; - -/** - * @author 夜雨 - * @version 2023-11-10 16:34 - */ -public class Test { - - public static void main(String[] args) throws Exception { - - UUID uuid = UUID.randomUUID(); - System.out.println(Time.now()); - System.out.println(uuid); - System.out.println(uuid.toString().toUpperCase()); - System.out.println(Text.randomString(6)); - System.out.println(Text.randomString(8)); - System.out.println(Text.randomString(16)); - System.out.println(Text.randomString(32)); - System.out.println(Text.randomString(64)); - } +void main() { + UUID uuid = UUID.randomUUID(); + System.out.println(Time.toDateTime(1776602500136L)); + System.out.println(Time.now()); + System.out.println(uuid); + System.out.println(uuid.toString().toUpperCase()); + System.out.println(Text.randomString(6)); + System.out.println(Text.randomString(8)); + System.out.println(Text.randomString(16)); + System.out.println(Text.randomString(32)); + System.out.println(Text.randomString(64)); + System.out.println(Encoder.base64("admin:D8gycpMPEwIhebgT6rIaqcheDT3MpfTD")); + System.out.println(Decoder.base64String("YWRtaW46RDhneWNwTVBFd0loZWJnVDZySWFxY2hlRFQzTXBmVEQ=")); }